mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-26 11:27:10 -06:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57957ef917 | ||
|
|
277f352d06 | ||
|
|
fbea1b2f8f |
+38
-37
@@ -362,6 +362,43 @@ mod tests {
|
||||
assert!(near(corner_radius(Some(10_000), -5.0, 100.0), 0.0));
|
||||
}
|
||||
|
||||
/// One rerouting case: the free end settles and the attached end keeps its site.
|
||||
fn assert_transformed_reroute(kind: ShapeKind, rot: f32, flip_h: bool, flip_v: bool, free: Option<ConnEnd>, free_start: bool) {
|
||||
let attached = Some(ConnEnd { id: 7, site: 3 });
|
||||
let (start, end) = if free_start { (free, attached) } else { (attached, free) };
|
||||
let mut obj = shape(kind, 40.0, 20.0, ShapeExtra { start, end, ..Default::default() });
|
||||
let InlineObject::Shape { float, .. } = &mut obj else { panic!() };
|
||||
float.rot = rot;
|
||||
float.flip_h = flip_h;
|
||||
float.flip_v = flip_v;
|
||||
// Frame (10, 30, 40, 20), centre (30, 40): mirror, then rotate
|
||||
// clockwise in y-down group space. Do not use the routing transform
|
||||
// to compute the expectation.
|
||||
let sign = if free_start { -1.0 } else { 1.0 };
|
||||
let dx = sign * if flip_h { -20.0 } else { 20.0 };
|
||||
let dy = sign * if flip_v { -10.0 } else { 10.0 };
|
||||
let (sin, cos) = rot.to_radians().sin_cos();
|
||||
let expected = (30.0 + dx * cos - dy * sin, 40.0 + dx * sin + dy * cos);
|
||||
let kids = vec![
|
||||
GroupChild { x: 100.0, y: 200.0, obj: shape(ShapeKind::Rectangle, 40.0, 20.0, ShapeExtra { id: 7, ..Default::default() }) },
|
||||
GroupChild { x: 10.0, y: 30.0, obj },
|
||||
];
|
||||
let mut group = group(kids);
|
||||
assert!(reroute_group(&mut group));
|
||||
let InlineObject::Group { children, .. } = &group else { panic!() };
|
||||
let c = &children[1];
|
||||
let InlineObject::Shape { w, h, float, .. } = &c.obj else { panic!() };
|
||||
let (start, end) = ends(c.x, c.y, *w, *h, float.flip_h, float.flip_v);
|
||||
let (free, attached) = if free_start { (start, end) } else { (end, start) };
|
||||
assert!(
|
||||
(free.0 - expected.0).abs() < 0.001 && (free.1 - expected.1).abs() < 0.001,
|
||||
"{kind:?}, {rot}, {flip_h}, {flip_v}, {free_start}: {free:?} != {expected:?}"
|
||||
);
|
||||
assert_eq!(attached, (140.0, 210.0));
|
||||
assert_eq!(float.rot, 0.0);
|
||||
assert!(!reroute_group(&mut group), "rerouting must settle");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rerouting_preserves_transformed_free_and_unresolved_ends() {
|
||||
for kind in [ShapeKind::StraightConnector, ShapeKind::ElbowConnector, ShapeKind::CurvedConnector] {
|
||||
@@ -369,43 +406,7 @@ mod tests {
|
||||
for (flip_h, flip_v) in [(false, false), (true, false), (false, true), (true, true)] {
|
||||
for free in [None, Some(ConnEnd { id: 404, site: 0 }), Some(ConnEnd { id: 7, site: 99 })] {
|
||||
for free_start in [false, true] {
|
||||
let attached = Some(ConnEnd { id: 7, site: 3 });
|
||||
let (start, end) = if free_start { (free, attached) } else { (attached, free) };
|
||||
let mut obj = shape(kind, 40.0, 20.0, ShapeExtra { start, end, ..Default::default() });
|
||||
let InlineObject::Shape { float, .. } = &mut obj else { panic!() };
|
||||
float.rot = rot;
|
||||
float.flip_h = flip_h;
|
||||
float.flip_v = flip_v;
|
||||
// Frame (10, 30, 40, 20), centre (30, 40): mirror, then rotate
|
||||
// clockwise in y-down group space. Do not use the routing transform
|
||||
// to compute the expectation.
|
||||
let sign = if free_start { -1.0 } else { 1.0 };
|
||||
let dx = sign * if flip_h { -20.0 } else { 20.0 };
|
||||
let dy = sign * if flip_v { -10.0 } else { 10.0 };
|
||||
let (sin, cos) = rot.to_radians().sin_cos();
|
||||
let expected = (30.0 + dx * cos - dy * sin, 40.0 + dx * sin + dy * cos);
|
||||
let kids = vec![
|
||||
GroupChild {
|
||||
x: 100.0,
|
||||
y: 200.0,
|
||||
obj: shape(ShapeKind::Rectangle, 40.0, 20.0, ShapeExtra { id: 7, ..Default::default() }),
|
||||
},
|
||||
GroupChild { x: 10.0, y: 30.0, obj },
|
||||
];
|
||||
let mut group = group(kids);
|
||||
assert!(reroute_group(&mut group));
|
||||
let InlineObject::Group { children, .. } = &group else { panic!() };
|
||||
let c = &children[1];
|
||||
let InlineObject::Shape { w, h, float, .. } = &c.obj else { panic!() };
|
||||
let (start, end) = ends(c.x, c.y, *w, *h, float.flip_h, float.flip_v);
|
||||
let (free, attached) = if free_start { (start, end) } else { (end, start) };
|
||||
assert!(
|
||||
(free.0 - expected.0).abs() < 0.001 && (free.1 - expected.1).abs() < 0.001,
|
||||
"{kind:?}, {rot}, {flip_h}, {flip_v}, {free_start}: {free:?} != {expected:?}"
|
||||
);
|
||||
assert_eq!(attached, (140.0, 210.0));
|
||||
assert_eq!(float.rot, 0.0);
|
||||
assert!(!reroute_group(&mut group), "rerouting must settle");
|
||||
assert_transformed_reroute(kind, rot, flip_h, flip_v, free, free_start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,7 +570,7 @@ impl P {
|
||||
}
|
||||
out.pop()
|
||||
};
|
||||
if !matches!(c, '^' | '_') && !(matches!(c, '┬' | '┴') && self.no_limits == 0) && !is_combining(c) {
|
||||
if !(matches!(c, '^' | '_') || matches!(c, '┬' | '┴') && self.no_limits == 0 || is_combining(c)) {
|
||||
break;
|
||||
}
|
||||
// The base is already deep: the operator is text (see `too_deep`).
|
||||
|
||||
@@ -377,21 +377,15 @@ impl Props {
|
||||
}
|
||||
0x0104 => self.pib = Some(op),
|
||||
0x0181 => self.fill_color = color(op),
|
||||
0x01BF => {
|
||||
// fFilled (bit 4) is valid when fUsefFilled (bit 20) is set.
|
||||
if op & (1 << 20) != 0 {
|
||||
self.filled = Some(op & (1 << 4) != 0);
|
||||
}
|
||||
}
|
||||
// fFilled (bit 4) is valid when fUsefFilled (bit 20) is set.
|
||||
0x01BF if op & (1 << 20) != 0 => self.filled = Some(op & (1 << 4) != 0),
|
||||
0x01BF => {}
|
||||
0x01C0 => self.line_color = color(op),
|
||||
0x01CB => self.line_width = Some(op),
|
||||
0x01D0 | 0x01D1 => self.arrowhead |= op != 0,
|
||||
0x01FF => {
|
||||
// fLine (bit 3) is valid when fUsefLine (bit 19) is set.
|
||||
if op & (1 << 19) != 0 {
|
||||
self.line = Some(op & (1 << 3) != 0);
|
||||
}
|
||||
}
|
||||
// fLine (bit 3) is valid when fUsefLine (bit 19) is set.
|
||||
0x01FF if op & (1 << 19) != 0 => self.line = Some(op & (1 << 3) != 0),
|
||||
0x01FF => {}
|
||||
0x0384..=0x0387 => self.wrap_dist[(pid - 0x0384) as usize] = Some(op),
|
||||
0x038F => self.posh = Some(op),
|
||||
0x0390 => self.posrelh = Some(op),
|
||||
|
||||
@@ -732,11 +732,10 @@ impl Reader<'_> {
|
||||
};
|
||||
self.emit_text(sc, pb, c, props);
|
||||
}
|
||||
"w:cr" => {
|
||||
if !sc.fields.iter().any(|f| f.phase == Phase::Instr) {
|
||||
self.emit_text(sc, pb, "\n", props);
|
||||
}
|
||||
"w:cr" if !sc.fields.iter().any(|f| f.phase == Phase::Instr) => {
|
||||
self.emit_text(sc, pb, "\n", props);
|
||||
}
|
||||
"w:cr" => {}
|
||||
"w:noBreakHyphen" => self.emit_text(sc, pb, "\u{2011}", props),
|
||||
"w:softHyphen" => self.emit_text(sc, pb, "\u{00AD}", props),
|
||||
"w:sym" => {
|
||||
|
||||
@@ -327,6 +327,51 @@ mod tests {
|
||||
(turn((x, y)), turn((x + w, y + h)))
|
||||
}
|
||||
|
||||
/// One scaled-group case: rerouting keeps the visual free end and the resolved sites.
|
||||
fn assert_scaled_group_reroute(size: (f32, f32), kind: ShapeKind, spin: (f32, bool, bool), free: Option<ConnEnd>, free_start: bool) {
|
||||
let (w, h) = size;
|
||||
let (rot, flip_h, flip_v) = spin;
|
||||
let attached = Some(ConnEnd { id: 7, site: 3 });
|
||||
let (start, end) = if free_start { (free, attached) } else { (attached, free) };
|
||||
let group = InlineObject::Group {
|
||||
w,
|
||||
h,
|
||||
ch_w: 200.0,
|
||||
ch_h: 300.0,
|
||||
float: Float { rot: 25.0, flip_h: true, ..Default::default() },
|
||||
canvas: None,
|
||||
children: vec![
|
||||
GroupChild { x: 100.0, y: 200.0, obj: shape(ShapeKind::Rectangle, ShapeExtra { id: 7, ..Default::default() }, Float::default()) },
|
||||
GroupChild {
|
||||
x: 10.0,
|
||||
y: 30.0,
|
||||
obj: shape(kind, ShapeExtra { start, end, ..Default::default() }, Float { rot, flip_h, flip_v, ..Default::default() }),
|
||||
},
|
||||
],
|
||||
};
|
||||
// Rendering scales each child rectangle before applying its own spin.
|
||||
let before = visual_ends(&group);
|
||||
let expected = if free_start { before.0 } else { before.1 };
|
||||
let mut s = Session::new(wordcraft_doc::Document::new());
|
||||
let pos = s.doc.start_of(StoryRef::Body);
|
||||
s.doc.para_mut(pos.story, &pos.path).unwrap().insert_object(pos.off, group, &Default::default()).unwrap();
|
||||
assert!(reroute(&mut s));
|
||||
let group = s.doc.para_at(&pos).unwrap().object_at(pos.off).unwrap();
|
||||
let after = visual_ends(group);
|
||||
let actual = if free_start { after.0 } else { after.1 };
|
||||
assert!(
|
||||
(actual.0 - expected.0).abs() < 0.001 && (actual.1 - expected.1).abs() < 0.001,
|
||||
"{w}x{h}, {kind:?}, {rot}, {flip_h}, {flip_v}, {free_start}: {actual:?} != {expected:?}"
|
||||
);
|
||||
let InlineObject::Group { children, .. } = group else { panic!("group") };
|
||||
let c = &children[1];
|
||||
let InlineObject::Shape { w, h, float, .. } = &c.obj else { panic!("connector") };
|
||||
let routed = ends(c.x, c.y, *w, *h, float.flip_h, float.flip_v);
|
||||
assert_eq!(if free_start { routed.1 } else { routed.0 }, (140.0, 210.0), "resolved sites stay in child coordinates");
|
||||
assert_eq!(float.rot, 0.0);
|
||||
assert!(!reroute(&mut s), "rerouting must settle");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rerouting_scaled_group_preserves_visual_free_and_unresolved_ends() {
|
||||
for (w, h) in [(200.0, 300.0), (400.0, 600.0), (400.0, 150.0)] {
|
||||
@@ -335,53 +380,7 @@ mod tests {
|
||||
for (flip_h, flip_v) in [(false, false), (true, false), (false, true), (true, true)] {
|
||||
for free in [None, Some(ConnEnd { id: 404, site: 0 }), Some(ConnEnd { id: 7, site: 99 })] {
|
||||
for free_start in [false, true] {
|
||||
let attached = Some(ConnEnd { id: 7, site: 3 });
|
||||
let (start, end) = if free_start { (free, attached) } else { (attached, free) };
|
||||
let group = InlineObject::Group {
|
||||
w,
|
||||
h,
|
||||
ch_w: 200.0,
|
||||
ch_h: 300.0,
|
||||
float: Float { rot: 25.0, flip_h: true, ..Default::default() },
|
||||
canvas: None,
|
||||
children: vec![
|
||||
GroupChild {
|
||||
x: 100.0,
|
||||
y: 200.0,
|
||||
obj: shape(ShapeKind::Rectangle, ShapeExtra { id: 7, ..Default::default() }, Float::default()),
|
||||
},
|
||||
GroupChild {
|
||||
x: 10.0,
|
||||
y: 30.0,
|
||||
obj: shape(
|
||||
kind,
|
||||
ShapeExtra { start, end, ..Default::default() },
|
||||
Float { rot, flip_h, flip_v, ..Default::default() },
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
// Rendering scales each child rectangle before applying its own spin.
|
||||
let before = visual_ends(&group);
|
||||
let expected = if free_start { before.0 } else { before.1 };
|
||||
let mut s = Session::new(wordcraft_doc::Document::new());
|
||||
let pos = s.doc.start_of(StoryRef::Body);
|
||||
s.doc.para_mut(pos.story, &pos.path).unwrap().insert_object(pos.off, group, &Default::default()).unwrap();
|
||||
assert!(reroute(&mut s));
|
||||
let group = s.doc.para_at(&pos).unwrap().object_at(pos.off).unwrap();
|
||||
let after = visual_ends(group);
|
||||
let actual = if free_start { after.0 } else { after.1 };
|
||||
assert!(
|
||||
(actual.0 - expected.0).abs() < 0.001 && (actual.1 - expected.1).abs() < 0.001,
|
||||
"{w}x{h}, {kind:?}, {rot}, {flip_h}, {flip_v}, {free_start}: {actual:?} != {expected:?}"
|
||||
);
|
||||
let InlineObject::Group { children, .. } = group else { panic!("group") };
|
||||
let c = &children[1];
|
||||
let InlineObject::Shape { w, h, float, .. } = &c.obj else { panic!("connector") };
|
||||
let routed = ends(c.x, c.y, *w, *h, float.flip_h, float.flip_v);
|
||||
assert_eq!(if free_start { routed.1 } else { routed.0 }, (140.0, 210.0), "resolved sites stay in child coordinates");
|
||||
assert_eq!(float.rot, 0.0);
|
||||
assert!(!reroute(&mut s), "rerouting must settle");
|
||||
assert_scaled_group_reroute((w, h), kind, (rot, flip_h, flip_v), free, free_start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,6 +356,31 @@ pub enum Piece {
|
||||
|
||||
/// Read Replace with text: `^` codes (unless `literal`), `\1`–`\9` with wildcards, and with a
|
||||
/// regex also `$n`, `${n}`, `${name}`, `$name`, `$$`, `\n` (paragraph mark), `\t` and `\\`.
|
||||
/// Pushes the pending literal text, if any.
|
||||
fn flush_literal(cur: &mut String, out: &mut Vec<Piece>) {
|
||||
if !cur.is_empty() {
|
||||
out.push(Piece::Lit(std::mem::take(cur)));
|
||||
}
|
||||
}
|
||||
|
||||
/// The `^` code whose letter is `chars[i]` (`^&`, `^c`, a replace code or a numeric one): pushes
|
||||
/// its piece and returns the characters it used, or `None` when it isn't a code.
|
||||
fn caret_code(chars: &[char], i: usize, cur: &mut String, out: &mut Vec<Piece>) -> Option<usize> {
|
||||
let &n = chars.get(i)?;
|
||||
if n == '&' || n == 'c' {
|
||||
flush_literal(cur, out);
|
||||
out.push(if n == '&' { Piece::Found } else { Piece::Clip });
|
||||
return Some(1);
|
||||
}
|
||||
if let Some(ch) = replace_code(n) {
|
||||
cur.push(ch);
|
||||
return Some(1);
|
||||
}
|
||||
let (ch, used) = numeric_code(chars, i)?;
|
||||
cur.push(ch);
|
||||
Some(used)
|
||||
}
|
||||
|
||||
pub fn parse_replace(with: &str, o: &Opts) -> Vec<Piece> {
|
||||
// With a regex, `special: false` turns off only the `^` codes; group references still expand.
|
||||
if o.literal && !o.regex {
|
||||
@@ -364,11 +389,6 @@ pub fn parse_replace(with: &str, o: &Opts) -> Vec<Piece> {
|
||||
let chars: Vec<char> = with.chars().collect();
|
||||
let mut out = Vec::new();
|
||||
let mut cur = String::new();
|
||||
let flush = |cur: &mut String, out: &mut Vec<Piece>| {
|
||||
if !cur.is_empty() {
|
||||
out.push(Piece::Lit(std::mem::take(cur)));
|
||||
}
|
||||
};
|
||||
let mut i = 0;
|
||||
while let Some(&c) = chars.get(i) {
|
||||
i += 1;
|
||||
@@ -379,7 +399,7 @@ pub fn parse_replace(with: &str, o: &Opts) -> Vec<Piece> {
|
||||
i += used;
|
||||
}
|
||||
Some((Ref::Group(pc), used)) => {
|
||||
flush(&mut cur, &mut out);
|
||||
flush_literal(&mut cur, &mut out);
|
||||
out.extend(pc);
|
||||
i += used;
|
||||
}
|
||||
@@ -391,7 +411,7 @@ pub fn parse_replace(with: &str, o: &Opts) -> Vec<Piece> {
|
||||
if c == '\\' && o.wildcards {
|
||||
match chars.get(i) {
|
||||
Some(d) if d.is_ascii_digit() && *d != '0' => {
|
||||
flush(&mut cur, &mut out);
|
||||
flush_literal(&mut cur, &mut out);
|
||||
out.push(Piece::Group(d.to_digit(10).unwrap_or(0) as usize));
|
||||
i += 1;
|
||||
continue;
|
||||
@@ -406,28 +426,14 @@ pub fn parse_replace(with: &str, o: &Opts) -> Vec<Piece> {
|
||||
}
|
||||
if c == '^'
|
||||
&& !o.literal
|
||||
&& let Some(&n) = chars.get(i)
|
||||
&& let Some(used) = caret_code(&chars, i, &mut cur, &mut out)
|
||||
{
|
||||
if n == '&' || n == 'c' {
|
||||
flush(&mut cur, &mut out);
|
||||
out.push(if n == '&' { Piece::Found } else { Piece::Clip });
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if let Some(ch) = replace_code(n) {
|
||||
cur.push(ch);
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if let Some((ch, used)) = numeric_code(&chars, i) {
|
||||
cur.push(ch);
|
||||
i += used;
|
||||
continue;
|
||||
}
|
||||
i += used;
|
||||
continue;
|
||||
}
|
||||
cur.push(c);
|
||||
}
|
||||
flush(&mut cur, &mut out);
|
||||
flush_literal(&mut cur, &mut out);
|
||||
out
|
||||
}
|
||||
|
||||
|
||||
@@ -899,7 +899,7 @@ fn tab_indent_on(s: &Session) -> bool {
|
||||
fn tab_indents(s: &mut Session, f: &Pos) -> bool {
|
||||
let Some(para) = s.doc.para_at(f).filter(|p| !p.is_empty()) else { return false };
|
||||
if f.off == 0 {
|
||||
return !s.doc.styles.resolve_para(¶.props).numbering.is_some_and(|n| n.num != 0);
|
||||
return s.doc.styles.resolve_para(¶.props).numbering.is_none_or(|n| n.num == 0);
|
||||
}
|
||||
let l = s.layout();
|
||||
l.line_bounds(f, s.page_hint).is_some_and(|(a, _)| a == *f)
|
||||
|
||||
@@ -4528,7 +4528,7 @@ fn place_table(ctx: &mut Ctx, pb: &mut PageBuilder, t: &wordcraft_doc::Table, bl
|
||||
&& !pb.at_top()
|
||||
&& !fresh
|
||||
&& rest.is_none()
|
||||
&& !ri.checked_sub(1).and_then(|prev| kept.get(prev)).is_some_and(|&(end, _)| end >= ri)
|
||||
&& ri.checked_sub(1).and_then(|prev| kept.get(prev)).is_none_or(|&(end, _)| end < ri)
|
||||
&& kept.get(ri).is_some_and(|&(end, height)| {
|
||||
if end == ri {
|
||||
return false;
|
||||
|
||||
@@ -314,13 +314,12 @@ fn parse_aff(aff: &str) -> Result<Rules, String> {
|
||||
match dir {
|
||||
"SET" | "LANG" | "NAME" | "VERSION" | "HOME" | "WORDCHARS" | "MAXNGRAMSUGS" | "MAXDIFF" | "ONLYMAXDIFF" | "NOSPLITSUGS"
|
||||
| "SUGSWITHDOTS" | "WARN" | "FORBIDWARN" | "MAXCPDSUGS" => {}
|
||||
"FLAG" => {
|
||||
// Per-character flag modes (`FLAG UTF-8`, as VERO's pt_BR dictionary uses) work
|
||||
// with the one-character parsing here; only the multi-character encodings differ.
|
||||
if !matches!(at(1), "UTF-8" | "DEFAULT" | "utf-8" | "default" | "UTF8" | "") {
|
||||
return Err(format!("line {}: FLAG {} is not supported (one-character flags only)", n + 1, at(1)));
|
||||
}
|
||||
// Per-character flag modes (`FLAG UTF-8`, as VERO's pt_BR dictionary uses) work
|
||||
// with the one-character parsing here; only the multi-character encodings differ.
|
||||
"FLAG" if !matches!(at(1), "UTF-8" | "DEFAULT" | "utf-8" | "default" | "UTF8" | "") => {
|
||||
return Err(format!("line {}: FLAG {} is not supported (one-character flags only)", n + 1, at(1)));
|
||||
}
|
||||
"FLAG" => {}
|
||||
directive @ ("FORBIDDENWORD" | "NOSUGGEST") => {
|
||||
let mut it = at(1).chars().filter(|&c| c != EMOJI_SELECTOR);
|
||||
match (it.next(), it.next()) {
|
||||
|
||||
@@ -952,7 +952,7 @@ fn alert(ui: &mut Ui, text: &Option<String>) {
|
||||
|
||||
fn split_cells_ui(app: &mut WordApp, ui: &mut Ui, f: &mut SplitCellsForm) -> bool {
|
||||
// Several cells split one by one keep their rows.
|
||||
let rows_on = !(f.cells > 1 && !f.merge_first);
|
||||
let rows_on = f.cells <= 1 || f.merge_first;
|
||||
egui::Grid::new("split_cells").num_columns(2).show(ui, |ui| {
|
||||
ui.label(tl!("Number of columns:"));
|
||||
ui.add(egui::DragValue::new(&mut f.columns).range(1..=63));
|
||||
|
||||
@@ -773,7 +773,7 @@ pub fn logic(app: &mut WordApp, ctx: &egui::Context) {
|
||||
}
|
||||
// Alt off while an Alt key is still down (AltGr on Linux turns Level 3 Shift on, not
|
||||
// Alt): the key's release ends the press, not this.
|
||||
egui::Event::ModifiersChanged(modifiers) if !(app.ui.alt_key_down && !modifiers.alt) => {
|
||||
egui::Event::ModifiersChanged(modifiers) if !app.ui.alt_key_down || modifiers.alt => {
|
||||
alt_released |= alt_transition(app, modifiers.alt, *modifiers);
|
||||
}
|
||||
egui::Event::PointerButton { pressed: true, .. } => {
|
||||
|
||||
@@ -272,7 +272,7 @@ pub fn pointer(app: &mut WordApp, ui: &Ui, resp: &egui::Response, pages: &[Rect]
|
||||
let adding = ui.input(|i| i.modifiers.shift || i.modifiers.command);
|
||||
// Ctrl on the object that is already selected starts a copy-drag instead.
|
||||
let again = selected(app).is_some_and(|(p, _)| p == object.pos());
|
||||
if pressed && adding && grab == Grab::Move && selected(app).is_some() && !(again && !ui.input(|i| i.modifiers.shift)) {
|
||||
if pressed && adding && grab == Grab::Move && selected(app).is_some() && (ui.input(|i| i.modifiers.shift) || !again) {
|
||||
let _ = app.run("select.addObject", json!({"pos": object.pos()}));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ pub fn run(args: &[&str]) -> Result<(), String> {
|
||||
None => 100,
|
||||
};
|
||||
let pos: Vec<&str> =
|
||||
args.iter().enumerate().filter(|(i, a)| !a.starts_with("--") && !(*i > 0 && args[i - 1] == "--dpi")).map(|(_, a)| *a).collect();
|
||||
args.iter().enumerate().filter(|(i, a)| !(a.starts_with("--") || *i > 0 && args[i - 1] == "--dpi")).map(|(_, a)| *a).collect();
|
||||
let [reference, test, out] = pos.as_slice() else {
|
||||
return Err("usage: cargo xtask visual-diff REF.pdf TEST.pdf OUTDIR [--private] [--dpi N]".into());
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user