diff --git a/crates/engine/src/cmd/prefscmds.rs b/crates/engine/src/cmd/prefscmds.rs index af09b032da..c605cd39f3 100644 --- a/crates/engine/src/cmd/prefscmds.rs +++ b/crates/engine/src/cmd/prefscmds.rs @@ -156,6 +156,7 @@ pub const PREF_SPECS: &[PrefSpec] = &[ p!("scrubNumericFields", "General", "Options", "Scrub Numeric Fields by Dragging", bool), p!("trackpadRotateView", "General", "Options", "Trackpad Gesture to Rotate View", bool), p!("holdToolKeys", "General", "Options", "Hold a Tool Shortcut to Use the Tool Temporarily", bool), + p!("boundingBoxWhileMovingAndDrawing", "General", "Options", "Show Bounding Box While Moving and Drawing", bool), // Selection & Anchor Display p!("selectionTolerance", "Selection & Anchor Display", "Selection", "Tolerance", num(1.0, 8.0, "px")), p!( diff --git a/crates/engine/src/interaction.rs b/crates/engine/src/interaction.rs index 1de58228a2..e3cd563d55 100644 --- a/crates/engine/src/interaction.rs +++ b/crates/engine/src/interaction.rs @@ -27,6 +27,9 @@ pub struct Interaction { /// A transform or a perspective edit with `copy` keeps the copies it made of the snapshot here /// ([`PreviewCopies`]). pub(crate) copies: Option>, + /// The bounding box of `selection` in `doc`, measured once for a move drag + /// ([`crate::Session::move_drag_box`]): each step offsets it by the drag's move. + pub(crate) move_box: std::sync::OnceLock>, } /// What Object → Transform → Transform Again (⌘D) repeats ([`SessionState::last_transform`]). diff --git a/crates/engine/src/prefs.rs b/crates/engine/src/prefs.rs index aa8b5c7f42..52c4b7a4f2 100644 --- a/crates/engine/src/prefs.rs +++ b/crates/engine/src/prefs.rs @@ -71,6 +71,11 @@ pub struct Prefs { /// A single-key tool shortcut held down chooses its tool until the key is let go, and then the /// tool active before comes back; a quick press chooses the tool for good. pub hold_tool_keys: bool, + /// With View › Show Bounding Box on, the bounding box stays visible while a drag moves the + /// selection, and the shape tools that show it draw it around the shape being drawn. Off (the + /// default), it hides during a move and while a shape is drawn, except that a Selection tool + /// move with Real-time Drawing off still moves the box. + pub bounding_box_while_moving_and_drawing: bool, /// Offset for Paste / duplicate (Illustrator pastes to the view centre; we offset by this). pub paste_offset: f64, // Selection & Anchor Display @@ -397,6 +402,7 @@ impl Default for Prefs { scrub_numeric_fields: true, trackpad_rotate_view: true, hold_tool_keys: true, + bounding_box_while_moving_and_drawing: false, paste_offset: 10.0, selection_tolerance: 3.0, marquee_selection: "touching".into(), diff --git a/crates/engine/src/session/edit.rs b/crates/engine/src/session/edit.rs index c7d22ab6c7..61a4cc8a01 100644 --- a/crates/engine/src/session/edit.rs +++ b/crates/engine/src/session/edit.rs @@ -267,6 +267,7 @@ impl Session { isolation: st.isolation, perspective_again: None, copies: None, + move_box: std::sync::OnceLock::new(), }); Ok(()) } diff --git a/crates/engine/src/tests/bboxrotate.rs b/crates/engine/src/tests/bboxrotate.rs index 1893f8c186..0d9188243c 100644 --- a/crates/engine/src/tests/bboxrotate.rs +++ b/crates/engine/src/tests/bboxrotate.rs @@ -375,3 +375,72 @@ fn the_selection_tool_reports_a_transforming_drag() { assert!(s.tool_busy() && !s.tool_transforming()); s.pointer(&ev(PointerKind::Up, 550.0, 450.0), v).unwrap(); } + +/// With General › Show Bounding Box While Moving and Drawing on, a move drag's bounding box is the +/// box measured once, at the drag's start, offset by each step's move: with Real-time Drawing on +/// and off, the later steps do not measure the selection again. Off, only the Selection tool with +/// Real-time Drawing off gives a box (the art waits for the release). +#[test] +fn a_move_drag_measures_the_bounding_box_once() { + for (real_time, while_moving) in [(true, true), (false, true), (true, false), (false, false)] { + let what = format!("real time {real_time}, box while moving {while_moving}"); + let mut s = session(); + let id = rect(&mut s, 100.0, 100.0, 100.0, 50.0); + sel(&mut s, &[id]); + s.execute("prefs.set", &json!({"key": "realTimeDrawing", "value": real_time})).unwrap(); + s.execute("prefs.set", &json!({"key": "boundingBoxWhileMovingAndDrawing", "value": while_moving})).unwrap(); + let v = ViewInfo::default(); + s.select_tool("selection", v).unwrap(); + let ev = |kind, x: f64, y: f64| PointerEvent::new(kind, x, y).with_mods(Mods::default()); + s.pointer(&ev(PointerKind::Down, 150.0, 125.0), v).unwrap(); + assert_eq!(s.move_drag_box(), None, "{what}: not before the drag begins"); + s.pointer(&ev(PointerKind::Drag, 170.0, 135.0), v).unwrap(); + if real_time && !while_moving { + assert_eq!(s.move_drag_box(), None, "{what}: no box during the move"); + s.pointer(&ev(PointerKind::Up, 170.0, 135.0), v).unwrap(); + continue; + } + assert_eq!(s.move_drag_box(), Some(OrientedBox::aligned(Rect::new(120.0, 110.0, 220.0, 160.0))), "{what}"); + // The measurement made at the drag's start is the one each step offsets: replaced by a + // stand-in, the next step's box is the stand-in moved. + let it = s.doc_mut().unwrap().interaction.as_mut().unwrap(); + assert_eq!(it.move_box.get(), Some(&Some(OrientedBox::aligned(Rect::new(100.0, 100.0, 200.0, 150.0)))), "{what}: measured at the start"); + it.move_box = std::sync::OnceLock::from(Some(OrientedBox::aligned(Rect::new(0.0, 0.0, 10.0, 10.0)))); + s.pointer(&ev(PointerKind::Drag, 200.0, 165.0), v).unwrap(); + assert_eq!(s.move_drag_box(), Some(OrientedBox::aligned(Rect::new(50.0, 40.0, 60.0, 50.0))), "{what}: not measured again"); + s.pointer(&ev(PointerKind::Up, 200.0, 165.0), v).unwrap(); + assert_eq!(s.move_drag_box(), None, "{what}: none after the release"); + assert_eq!(selection_box(&s), OrientedBox::aligned(Rect::new(150.0, 140.0, 250.0, 190.0)), "{what}: moved"); + } +} + +/// A shape being drawn has no preview applied after a step that failed (with Real-time Drawing on, +/// also when the failed command left a document other than the interaction's snapshot), and with +/// Real-time Drawing off, where the document stays at the snapshot. A successful step with Real-time +/// Drawing on applies it. +#[test] +fn a_shape_being_drawn_reports_whether_a_preview_is_applied() { + for real_time in [true, false] { + let what = format!("real time {real_time}"); + let mut s = session(); + s.execute("prefs.set", &json!({"key": "realTimeDrawing", "value": real_time})).unwrap(); + let v = ViewInfo::default(); + s.select_tool("rectangle", v).unwrap(); + s.pointer(&PointerEvent::new(PointerKind::Down, 100.0, 100.0), v).unwrap(); + s.pointer(&PointerEvent::new(PointerKind::Drag, 160.0, 140.0), v).unwrap(); + assert_eq!(s.interaction_unapplied(), !real_time, "{what}: after a step that succeeded"); + assert!(s.preview("shape.rectangle", &json!({"x": "left"})).is_err(), "{what}: the preview fails"); + assert!(s.interaction_unapplied(), "{what}: after a step that failed"); + if real_time { + // A failed command that copied the document on write leaves another document than the + // snapshot; no preview is applied all the same. + let st = s.doc_mut().unwrap(); + st.doc = std::sync::Arc::new((*st.doc).clone()); + assert!(s.interaction_unapplied(), "{what}: a failed step with a copied document"); + } + s.pointer(&PointerEvent::new(PointerKind::Drag, 180.0, 150.0), v).unwrap(); + assert_eq!(s.interaction_unapplied(), !real_time, "{what}: the next step succeeded"); + s.pointer(&PointerEvent::new(PointerKind::Up, 180.0, 150.0), v).unwrap(); + assert!(!s.interaction_unapplied(), "{what}: no interaction after the release"); + } +} diff --git a/crates/engine/src/tooling.rs b/crates/engine/src/tooling.rs index 9bc450f937..017663975a 100644 --- a/crates/engine/src/tooling.rs +++ b/crates/engine/src/tooling.rs @@ -1090,22 +1090,51 @@ impl Session { self.tool.resizing() } - /// With Real-time Drawing off, how far a Selection-tool move drag has carried the selection - /// so far: the document moves on release, and the bounding box shows at this offset meanwhile. - pub fn deferred_move(&self) -> Option { - if self.tool.id() != "selection" || !self.tool.transforming() { + /// Does the canvas draw the bounding box around a shape being drawn: the active tool is drawing + /// a new shape whose box it shows once drawn (see `Tool::drawing_boxed`), and General › Show + /// Bounding Box While Moving and Drawing is on? + pub fn draws_new_shape_box(&self) -> bool { + self.prefs.bounding_box_while_moving_and_drawing && self.tool.drawing_boxed() + } + + /// Has the interaction no preview applied to the document: none has run yet, the last one failed, + /// or Real-time Drawing is off and the document is still the interaction's snapshot? + pub fn interaction_unapplied(&self) -> bool { + self.active().is_some_and(|d| d.interaction.as_ref().is_some_and(|it| it.preview.is_none() || std::sync::Arc::ptr_eq(&it.doc, &d.doc))) + } + + /// The bounding box of the selection a move drag is carrying (an Alt-drag copy included), where + /// the drag has carried it: the box of the selection at the drag's start, measured once per + /// drag, offset by the move of the latest preview. With General › Show Bounding Box While + /// Moving and Drawing on, for every tool that moves the selection; off, only for the Selection + /// tool with Real-time Drawing off, where the document moves on release. `None` for any other + /// drag, and before a preview has moved the selection. + pub fn move_drag_box(&self) -> Option { + if !self.tool.moving() { + return None; + } + let st = self.active()?; + let it = st.interaction.as_ref()?; + let deferred = std::sync::Arc::ptr_eq(&it.doc, &st.doc); + if !self.prefs.bounding_box_while_moving_and_drawing && !(self.tool.id() == "selection" && deferred) { return None; } - let it = self.active()?.interaction.as_ref()?; if !matches!(it.label.as_str(), "Move" | "Copy") { return None; } - let (cmd, p) = self.unapplied_interaction_preview()?; + let (cmd, p) = it.preview.as_ref()?; if cmd != "object.transform" || p.get("objects").and_then(Value::as_bool) == Some(false) { return None; } - let [a, b, c, d, e, f] = crate::cmd::matrix_param(&p, "matrix")?.as_coeffs(); - (a == 1.0 && b == 0.0 && c == 0.0 && d == 1.0).then(|| vectorcraft_geom::Vec2::new(e, f)) + let [a, b, c, d, e, f] = crate::cmd::matrix_param(p, "matrix")?.as_coeffs(); + if !(a == 1.0 && b == 0.0 && c == 0.0 && d == 1.0) { + return None; + } + let measure = self.bounds_measure(); + let start = + (*it.move_box.get_or_init(|| vectorcraft_render::effects::preview::oriented_bounds_with(&it.doc, &it.selection.objects, measure)))?; + let moved = start.to_local(Point::new(e, f)).to_vec2(); + Some(vectorcraft_doc::OrientedBox { rect: start.rect + moved, ..start }) } /// Does the active tool take `key` ahead of the shortcuts bound to it (see `Tool::claims_key`)? @@ -1435,23 +1464,30 @@ impl Session { } /// With Real-time Drawing off, the selection's transform box ([`Session::transform_box`]) as the - /// pending preview of a handle resize leaves it, measured once per preview step; `None` when no - /// such preview is pending, or when the preview fails. - pub fn deferred_transform_box(&mut self) -> Option { - if !self.tool.resizing() { + /// pending preview of a handle resize or of a shape being drawn leaves it, measured once per + /// preview step, with the layer color of the first object it measures; `None` when no such + /// preview is pending, or when the preview fails. + pub fn deferred_transform_box(&mut self) -> Option<(vectorcraft_doc::OrientedBox, [u8; 3])> { + if !self.measures_deferred_box() { return None; } let (cmd, p) = self.unapplied_interaction_preview()?; self.deferred_feedback(&cmd, &p)?.transform_box } + /// Does the canvas draw the bounding box of what the pending preview leaves: during a handle + /// resize, or while a shape is drawn by a tool that shows its box ([`Session::draws_new_shape_box`])? + fn measures_deferred_box(&self) -> bool { + self.tool.resizing() || self.draws_new_shape_box() + } + fn deferred_preview_overlays_from_command(&mut self, cmd: &str, p: &Value) -> Vec { self.deferred_feedback(cmd, p).map(|f| f.overlays.clone()).unwrap_or_default() } /// Canvas feedback while Real-time Drawing is off: run the pending preview command once, take - /// outlines from the objects it touched (and, during a handle resize, their transform box), then - /// put the document back at the interaction snapshot. + /// outlines from the objects it touched (and, during a handle resize or while a shape is drawn, + /// their transform box), then put the document back at the interaction snapshot. fn deferred_feedback(&mut self, cmd: &str, p: &Value) -> Option<&DeferredFeedback> { if !self.deferred_overlay_cache.as_ref().is_some_and(|f| f.cmd == cmd && f.params == *p) { let feedback = self.measure_deferred_preview(cmd, p)?; @@ -1472,8 +1508,12 @@ impl Session { }; let (overlays, transform_box) = if self.preview(cmd, p).is_ok() { let overlays = self.active().map(|st| outlines_for_selection(&st.doc, &st.selection)).unwrap_or_default(); - let objects = self.active().map(|st| st.selection.objects.clone()).unwrap_or_default(); - (overlays, if self.tool.resizing() { self.transform_box(&objects) } else { None }) + let (objects, color) = self + .active() + .map(|st| (st.selection.objects.clone(), st.selection.objects.first().map(|id| st.doc.layer_color(*id)))) + .unwrap_or_default(); + let transform_box = if self.measures_deferred_box() { self.transform_box(&objects).zip(color) } else { None }; + (overlays, transform_box) } else { (vec![], None) }; @@ -1497,7 +1537,7 @@ pub(crate) struct DeferredFeedback { cmd: String, params: Value, overlays: Vec, - transform_box: Option, + transform_box: Option<(vectorcraft_doc::OrientedBox, [u8; 3])>, } /// The most points the outlines of [`outlines_for_selection`] are drawn for, the same number as the diff --git a/crates/tools/src/lib.rs b/crates/tools/src/lib.rs index c397231495..54a4d131bd 100644 --- a/crates/tools/src/lib.rs +++ b/crates/tools/src/lib.rs @@ -148,7 +148,8 @@ pub trait Tool: Send { false } /// Is a drag moving, scaling or rotating the selection (the canvas then hides the bounding box, - /// so only the art is seen going with the pointer, except during a handle resize)? + /// so only the art is seen going with the pointer, except during a handle resize, and during a + /// move with General › Show Bounding Box While Moving and Drawing on)? fn transforming(&self) -> bool { false } @@ -157,6 +158,18 @@ pub trait Tool: Send { fn resizing(&self) -> bool { false } + /// Is the transforming drag a move of the selection (an Alt-drag copy included)? With General › + /// Show Bounding Box While Moving and Drawing on, the canvas then draws the bounding box where + /// the drag has moved the selection. + fn moving(&self) -> bool { + false + } + /// Is a drag drawing a new shape that the tool shows the bounding box of once drawn? With + /// General › Show Bounding Box While Moving and Drawing on, the canvas then draws the box around + /// the shape as it is drawn. + fn drawing_boxed(&self) -> bool { + false + } /// Does the tool take `key` now, ahead of the command shortcuts bound to it (the Gradient tool /// with a stop selected takes Delete and the arrows)? fn claims_key(&self, _cx: &ToolContext, _key: ToolKey) -> bool { diff --git a/crates/tools/src/select.rs b/crates/tools/src/select.rs index 1658346127..002d3161db 100644 --- a/crates/tools/src/select.rs +++ b/crates/tools/src/select.rs @@ -837,6 +837,10 @@ impl Tool for SelectionTool { matches!(self.state, State::Scaling { .. }) } + fn moving(&self) -> bool { + matches!(self.state, State::Moving { began: true, .. }) + } + fn transforming(&self) -> bool { match &self.state { State::Moving { began: true, .. } | State::Scaling { .. } | State::Rotating { .. } => true, diff --git a/crates/tools/src/select/tests.rs b/crates/tools/src/select/tests.rs index 9069cda939..659e50a6a5 100644 --- a/crates/tools/src/select/tests.rs +++ b/crates/tools/src/select/tests.rs @@ -716,8 +716,10 @@ fn drag_moves_with_single_undo() { let cx = cx(&d, &s, &p); let mut t = SelectionTool::default(); assert!(t.pointer(&cx, &ev(PointerKind::Down, 150.0, 150.0)).is_empty()); + assert!(!t.moving(), "a press is not a move yet"); let a = t.pointer(&cx, &ev(PointerKind::Drag, 160.0, 150.0)); assert_eq!(a[0], Action::Begin("Move".into())); + assert!(t.moving()); assert!(matches!(&a[1], Action::Preview(c, v) if c == "object.transform" && v["matrix"][4] == 10.0)); let a = t.pointer(&cx, &ev(PointerKind::Up, 160.0, 150.0)); assert_eq!(a, vec![Action::Commit]); @@ -737,9 +739,10 @@ fn escape_cancels_a_move_or_an_alt_drag_copy() { t.pointer(&cx, &ev(PointerKind::Down, 150.0, 150.0).with_mods(m)); let a = t.pointer(&cx, &ev(PointerKind::Drag, 160.0, 150.0).with_mods(m)); assert!(matches!(&a[1], Action::Preview(_, v) if v["copy"] == m.alt), "{what}: {a:?}"); - assert!(t.transforming(), "{what}"); + // A move or a copy, which the canvas can show the bounding box for. + assert!(t.transforming() && t.moving() && !t.resizing(), "{what}"); assert_eq!(t.key(&cx, ToolKey::Escape, Mods::default()), vec![Action::Cancel], "{what}: Escape cancels the drag"); - assert!(!t.busy() && !t.transforming(), "{what}"); + assert!(!t.busy() && !t.transforming() && !t.moving(), "{what}"); assert!(t.overlays(&cx).iter().all(|o| !matches!(o, Overlay::Measure { .. })), "{what}: no readout stays"); assert!(t.pointer(&cx, &ev(PointerKind::Drag, 170.0, 150.0).with_mods(m)).is_empty(), "{what}: the drag is over"); assert!(t.pointer(&cx, &ev(PointerKind::Up, 170.0, 150.0).with_mods(m)).is_empty(), "{what}: the release commits nothing"); diff --git a/crates/tools/src/shape.rs b/crates/tools/src/shape.rs index 6609ef62aa..fb291796f7 100644 --- a/crates/tools/src/shape.rs +++ b/crates/tools/src/shape.rs @@ -668,6 +668,15 @@ impl Tool for ShapeTool { fn resizing(&self) -> bool { matches!(self.selection, Some(SelectionDrag::Scale { .. })) } + /// A move of the selection by its center point, as the Selection tool's move. + fn moving(&self) -> bool { + matches!(self.selection, Some(SelectionDrag::Move { began: true, .. })) + } + /// Drawing a shape with a tool that shows the selection's bounding box. The Line Segment tool + /// shows no box, so it reports none. + fn drawing_boxed(&self) -> bool { + shows_shape_widgets(self.id) && self.began + } /// Adjusting a widget (the canvas hides the bounding box meanwhile, as for the Selection tool). fn transforming(&self) -> bool { match &self.widget { diff --git a/crates/tools/src/shape/tests.rs b/crates/tools/src/shape/tests.rs index 663cdab3fd..c76eef404e 100644 --- a/crates/tools/src/shape/tests.rs +++ b/crates/tools/src/shape/tests.rs @@ -468,7 +468,7 @@ fn a_shape_tool_drags_the_selected_shapes_widgets() { matches!(a.last(), Some(Action::Preview(cmd, v)) if cmd == "object.setLiveShape" && v["radius"].as_f64().is_some_and(|r| r > 0.0)), "{id}: {a:?}" ); - assert!(t.transforming(), "{id}"); + assert!(t.transforming() && !t.drawing_boxed() && !t.moving(), "{id}"); assert_eq!(t.pointer(&c, &PointerEvent::new(PointerKind::Up, 130.0, 130.0)), vec![Action::Commit], "{id}"); assert!(!t.busy() && !t.transforming(), "{id}"); // Escape cancels a widget drag. @@ -529,14 +529,15 @@ fn a_shape_tool_moves_the_selection_by_its_centre_point_and_turns_it_from_a_corn // A drag moves the selection by the pointer's offset. let a = t.pointer(&c, &PointerEvent::new(PointerKind::Drag, 170.0, 160.0)); assert_eq!(a.first(), Some(&Action::Begin("Move".into())), "{id}: {a:?}"); - assert!(t.transforming(), "{id}"); + // A move, which the canvas can show the bounding box for. + assert!(t.transforming() && t.moving() && !t.resizing() && !t.drawing_boxed(), "{id}"); assert_eq!( a.last(), Some(&Action::Preview("object.transform".into(), json!({"matrix": [1.0, 0.0, 0.0, 1.0, 20.0, 10.0], "copy": false}))), "{id}" ); assert_eq!(t.pointer(&c, &PointerEvent::new(PointerKind::Up, 170.0, 160.0)), vec![Action::Commit], "{id}"); - assert!(!t.busy() && !t.transforming(), "{id}"); + assert!(!t.busy() && !t.transforming() && !t.moving(), "{id}"); } // Alt pressed once the drag has begun copies, and the copy cursor shows, as the Selection // tool's do. @@ -581,6 +582,32 @@ fn a_shape_tool_moves_the_selection_by_its_centre_point_and_turns_it_from_a_corn t.pointer(&c, &PointerEvent::new(PointerKind::Down, away.x, away.y)); let a = t.pointer(&c, &PointerEvent::new(PointerKind::Drag, 350.0, 340.0)); assert_eq!(a.first(), Some(&Action::Begin("Rectangle".into())), "{a:?}"); + assert!(t.drawing_boxed() && !t.moving() && !t.transforming()); +} + +/// The shape tools that show the selection's bounding box report a shape being drawn once the drag +/// has begun, until the release or Escape. The Line Segment tool shows no box and reports none. +#[test] +fn a_shape_being_drawn_has_its_bounding_box_drawn_by_the_tools_that_show_one() { + let (d, _) = doc_with_rect(); + let s = Selection::default(); + let p = paint(); + let c = cx(&d, &s, &p); + for id in ["rectangle", "roundedRectangle", "ellipse", "polygon", "star", "lineSegment"] { + let mut t = ShapeTool::new(id); + t.pointer(&c, &PointerEvent::new(PointerKind::Down, 100.0, 100.0)); + assert!(!t.drawing_boxed(), "{id}: not before the drag begins"); + t.pointer(&c, &PointerEvent::new(PointerKind::Drag, 160.0, 140.0)); + assert_eq!(t.drawing_boxed(), id != "lineSegment", "{id}"); + assert!(!t.moving() && !t.resizing(), "{id}"); + t.pointer(&c, &PointerEvent::new(PointerKind::Up, 160.0, 140.0)); + assert!(!t.drawing_boxed(), "{id}: not after the release"); + // Escape ends the drag. + t.pointer(&c, &PointerEvent::new(PointerKind::Down, 100.0, 100.0)); + t.pointer(&c, &PointerEvent::new(PointerKind::Drag, 160.0, 140.0)); + t.key(&c, ToolKey::Escape, Mods::default()); + assert!(!t.drawing_boxed(), "{id}: not after Escape"); + } } /// With a shape tool, a drag on one of the eight handles of the selection's bounding box scales the @@ -599,7 +626,7 @@ fn a_shape_tool_scales_the_selection_by_its_bounding_box_handles() { assert!(t.overlays(&c).is_empty(), "{id}"); assert_eq!(t.pointer(&c, &PointerEvent::new(PointerKind::Down, corner.x, corner.y)), vec![Action::Begin("Scale".into())], "{id}"); // A resize: the canvas keeps the box on show at the changing bounds. - assert!(t.busy() && t.transforming() && t.resizing(), "{id}"); + assert!(t.busy() && t.transforming() && t.resizing() && !t.moving(), "{id}"); let a = t.pointer(&c, &PointerEvent::new(PointerKind::Drag, 220.0, 230.0)); let want = scale_for_drag(Rect::new(100.0, 100.0, 200.0, 200.0), Handle::BottomRight, Point::new(220.0, 230.0), false, false); assert_eq!(a, vec![Action::Preview("object.transform".into(), json!({"matrix": matrix_json(want), "copy": false}))], "{id}"); diff --git a/crates/ui-egui/src/canvas/overlays.rs b/crates/ui-egui/src/canvas/overlays.rs index 8ff6faaf79..fdf48b9409 100644 --- a/crates/ui-egui/src/canvas/overlays.rs +++ b/crates/ui-egui/src/canvas/overlays.rs @@ -110,36 +110,44 @@ pub(super) fn mark_options(app: &VectorcraftApp, curve_points_from_tool: bool) - pub(super) fn selection_overlay(app: &mut VectorcraftApp, p: &egui::Painter, xf: &Xf, curve_points_from_tool: bool, marks: &mut SelectionMarks) { // The Selection tool's bounding box (rotated with the objects after a rotation), also shown by // the shape tools that keep a shape just drawn ready to adjust while they draw none (#5477). It - // hides while a drag moves or resizes the selection, so only the art is seen going with the - // pointer (#712). A handle resize keeps it at the changing bounds (#3526). - // With Real-time Drawing off the art waits for the release of a move drag, and the box moves - // with the pointer instead. + // hides while a drag moves, rotates or resizes the selection, so only the art is seen going with + // the pointer (#712). A handle resize keeps it at the changing bounds (#3526). With General › + // Show Bounding Box While Moving and Drawing on, a move draws it where the drag has moved the + // selection, and a shape tool draws it around the shape being drawn. + // With Real-time Drawing off the art waits for the release of a move drag. With the preference + // off, the box of a Selection tool move then moves with the pointer instead. let resizing = app.session.tool_resizing(); - let moved = app.session.deferred_move(); + let move_box = app.session.move_drag_box(); + let drawing_boxed = app.session.draws_new_shape_box(); + let follows = resizing || move_box.is_some() || drawing_boxed; let shape_tool = vectorcraft_tools::shape::shows_shape_widgets(app.session.tool_id()); - // A shape tool drawing a shape shows none of this; adjusting a widget, it shows the widgets. + // A shape tool drawing a shape shows none of its widgets; adjusting a widget, it shows the widgets. let drawing = shape_tool && app.session.tool_busy() && !app.session.tool_transforming(); // Cmd held lends the Selection tool to a press (Direct Selection's included, #3901): its box // shows while the key is down, where its handles take that press. let held = super::drag::mods(p.ctx().input(|i| i.modifiers), false); let lends_selection = !app.session.tool_busy() && app.session.lent_tool_id(&held) == Some("selection"); - let show_box = (app.session.tool_id() == "selection" || lends_selection || (shape_tool && (!app.session.tool_busy() || resizing))) - && (resizing || !app.session.tool_transforming() || moved.is_some()) - && app.ui.view.bounding_box - && app.session.active().is_some_and(|st| !st.selection.is_empty() && st.selection.anchors.is_empty()); - // With Real-time Drawing off the document stays at the drag's snapshot, so a resize measures the - // box from the pending preview; otherwise each step's revision measures it once (`selection_box`). - let bbox = if !show_box { - None - } else if let Some(b) = resizing.then(|| app.session.deferred_transform_box()).flatten() { - Some(b) + let show_box = (app.session.tool_id() == "selection" || lends_selection || (shape_tool && (!app.session.tool_busy() || follows))) + && (follows || !app.session.tool_transforming()) + && app.ui.view.bounding_box; + let selected_whole = app.session.active().is_some_and(|st| !st.selection.is_empty() && st.selection.anchors.is_empty()); + // With Real-time Drawing off the document stays at the drag's snapshot, so a resize or a shape + // being drawn measures the box from the pending preview. A shape being drawn shows no box until + // a preview has made it (never the box of the selection from before the drag, which a failed + // preview also leaves). A move offsets the box measured at the drag's start. Otherwise each + // step's revision measures it once (`selection_box`). + let deferred = if show_box && (resizing || drawing_boxed) { app.session.deferred_transform_box() } else { None }; + let (bbox, deferred_color) = if !show_box { + (None, None) + } else if let Some((b, color)) = deferred { + (Some(b), Some(color)) + } else if !selected_whole || (drawing_boxed && app.session.interaction_unapplied()) { + (None, None) + } else if move_box.is_some() { + (move_box, None) } else { - app.selection_box() + (app.selection_box(), None) }; - let bbox = bbox.map(|b| match moved { - Some(d) => vectorcraft_doc::OrientedBox { rect: b.rect + b.to_local(d.to_point()).to_vec2(), ..b }, - None => b, - }); let st = app.session.active(); let show_marks = !crate::dialogs::halftone::previewing(app); let big = show_marks && st.is_some_and(|st| too_many_anchors(st.selection.objects.iter().filter_map(|id| st.doc.node(*id)))); @@ -209,8 +217,11 @@ pub(super) fn selection_overlay(app: &mut VectorcraftApp, p: &egui::Painter, xf: } } // Bounding box with handles (Selection tool). - if let Some(b) = bbox { - let color = c32(st.doc.layer_color(st.selection.objects[0])); + // The layer color of the box's first object: with Real-time Drawing off, the shape being drawn + // is only in the pending preview, which gives its color. + let box_color = deferred_color.or_else(|| st.selection.objects.first().map(|id| st.doc.layer_color(*id))); + if let Some((b, color)) = bbox.zip(box_color) { + let color = c32(color); p.add(Shape::closed_line(b.corners().iter().map(|q| xf.to_screen(*q)).collect(), Stroke::new(1.0, color))); for h in vectorcraft_tools::bbox::Handle::ALL { let c = xf.to_screen(b.to_doc() * h.pos(b.rect)); @@ -218,8 +229,9 @@ pub(super) fn selection_overlay(app: &mut VectorcraftApp, p: &egui::Painter, xf: p.rect_filled(hr, 0.0, Color32::WHITE); p.rect_stroke(hr, 0.0, Stroke::new(1.0, color), StrokeKind::Inside); } - // The type widget beside it: hollow on point type, filled on area type. - if let Some(w) = vectorcraft_tools::typewidget::TypeWidget::of(&st.doc, &st.selection, &b, xf.zoom) { + // The type widget beside it: hollow on point type, filled on area type. A shape being drawn + // has none (with Real-time Drawing off, the selection is still the one before the drag). + if !drawing_boxed && let Some(w) = vectorcraft_tools::typewidget::TypeWidget::of(&st.doc, &st.selection, &b, xf.zoom) { let (c, r) = (xf.to_screen(w.at), vectorcraft_tools::typewidget::RADIUS_PX); p.circle_filled(c, r, if w.area { color } else { Color32::WHITE }); p.circle_stroke(c, r, Stroke::new(1.0, color)); diff --git a/crates/ui-egui/src/canvas/tests.rs b/crates/ui-egui/src/canvas/tests.rs index 503178a22b..b05dfd41a8 100644 --- a/crates/ui-egui/src/canvas/tests.rs +++ b/crates/ui-egui/src/canvas/tests.rs @@ -1360,13 +1360,15 @@ fn a_guide_dragged_out_of_a_ruler_shows_while_dragged() { /// With Preferences › Performance › Real-time Drawing and Editing off, the bounding box moves /// with a Selection-tool move drag while the object stays put until the release. With it on, the -/// art moves and the box hides during the drag (#712). +/// art moves and the box hides during the drag (#712), unless General › Show Bounding Box While +/// Moving and Drawing is on, which keeps the box with the art. #[test] fn real_time_drawing_off_moves_the_bounding_box_with_a_move_drag() { - for real_time in [true, false] { + for (real_time, while_moving) in [(true, false), (false, false), (true, true), (false, true)] { let mut app = VectorcraftApp::new(Session::new(), Default::default()); app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap(); app.session.execute("prefs.set", &json!({"key": "realTimeDrawing", "value": real_time})).unwrap(); + app.session.execute("prefs.set", &json!({"key": "boundingBoxWhileMovingAndDrawing", "value": while_moving})).unwrap(); app.ui.task_bar = false; app.ui.view.rulers = false; app.ui.view.smart_guides = false; @@ -1389,15 +1391,19 @@ fn real_time_drawing_off_moves_the_bounding_box_with_a_move_drag() { app.session.pointer(&PointerEvent::new(PointerKind::Down, 150.0, 130.0), view).unwrap(); app.session.pointer(&PointerEvent::new(PointerKind::Drag, 250.0, 230.0), view).unwrap(); let during = handles(&mut app); - if real_time { - assert!(during.is_empty(), "the box hides while the art moves: {during:?}"); - assert_eq!(left(&app), start + 100.0, "the art moves with the pointer"); + let what = format!("real-time drawing {real_time}, box while moving {while_moving}"); + if real_time && !while_moving { + assert!(during.is_empty(), "{what}: the box hides while the art moves: {during:?}"); } else { - assert_eq!(during.len(), 8, "the box shows during the drag"); + assert_eq!(during.len(), 8, "{what}: the box shows during the drag"); let corner = xf.to_screen(Point::new(200.0, 200.0)); - assert!(during.iter().any(|c| c.distance(corner) < 0.5), "the box's corner is at the dragged position: {during:?}"); - assert!(!during.iter().any(|c| c.distance(xf.to_screen(Point::new(100.0, 100.0))) < 0.5), "not at the start"); - assert_eq!(left(&app), start, "the object waits for the release"); + assert!(during.iter().any(|c| c.distance(corner) < 0.5), "{what}: the box's corner is at the dragged position: {during:?}"); + assert!(!during.iter().any(|c| c.distance(xf.to_screen(Point::new(100.0, 100.0))) < 0.5), "{what}: not at the start"); + } + if real_time { + assert_eq!(left(&app), start + 100.0, "{what}: the art moves with the pointer"); + } else { + assert_eq!(left(&app), start, "{what}: the object waits for the release"); } app.session.pointer(&PointerEvent::new(PointerKind::Up, 250.0, 230.0), view).unwrap(); assert_eq!(left(&app), start + 100.0, "real-time drawing {real_time}: moved on release"); @@ -4261,16 +4267,23 @@ fn hidden_corner_widgets_do_not_answer_the_pointer() { /// One headless canvas frame → the bounding box drawn: its outline's corners on screen (the /// closed four-point line in the layer color; `None` when it is hidden) and the number of its -/// handles (white 6 px squares). +/// handles (white 6 px squares, to 0.01 px: at a fractional screen position a square's corners +/// round to a size a little off 6). fn bounding_box_drawn(app: &mut VectorcraftApp, ctx: &egui::Context) -> (Option>, usize) { let st = app.session.active().unwrap(); - let layer = egui::epaint::ColorMode::Solid(c32(st.doc.layer_color(st.selection.objects[0]))); + let layer = st.doc.layer_color(st.selection.objects[0]); + bounding_box_drawn_in(app, ctx, layer) +} + +/// [`bounding_box_drawn`] for a box in the layer color `layer`, whatever is selected. +fn bounding_box_drawn_in(app: &mut VectorcraftApp, ctx: &egui::Context, layer: [u8; 3]) -> (Option>, usize) { + let layer = egui::epaint::ColorMode::Solid(c32(layer)); let drawn = shapes(app, ctx); let outline = drawn.iter().find_map(|s| match s { Shape::Path(ps) if ps.closed && ps.points.len() == 4 && ps.stroke.color == layer => Some(ps.points.clone()), _ => None, }); - let handle = |s: &&Shape| matches!(s, Shape::Rect(r) if r.rect.size() == vec2(6.0, 6.0) && r.fill == Color32::WHITE); + let handle = |s: &&Shape| matches!(s, Shape::Rect(r) if (r.rect.size() - vec2(6.0, 6.0)).length() < 0.01 && r.fill == Color32::WHITE); (outline, drawn.iter().filter(handle).count()) } @@ -4316,8 +4329,242 @@ fn a_resize_drag_keeps_the_bounding_box_at_the_new_bounds() { } } +/// With General › Show Bounding Box While Moving and Drawing on, a move of the selection keeps the +/// bounding box and its handles drawn where the drag carries the selection, with Real-time Drawing +/// on and off: a Selection-tool move, an Alt-drag copy, and a shape tool's move by the center point. +/// The box follows each step of the drag and ends where the release leaves the selection. With View +/// › Hide Bounding Box, no box shows during the move. With the preference off, the box hides during +/// the move (#712), except for a Selection-tool move with Real-time Drawing off, where the art waits +/// for the release and the box moves instead. +#[test] +fn a_move_drag_keeps_the_bounding_box_where_the_drag_carries_the_selection() { + for (real_time, while_moving) in [(true, true), (false, true), (true, false), (false, false)] { + for (tool, alt) in [("selection", false), ("selection", true), ("rectangle", false)] { + let what = format!("{tool}, alt {alt}, real time {real_time}, box while moving {while_moving}"); + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap(); + app.session.execute("shape.rectangle", &json!({"x": 100, "y": 100, "width": 100, "height": 60})).unwrap(); + app.session.execute("attributes.set", &json!({"showCenter": true})).unwrap(); + app.session.execute("prefs.set", &json!({"key": "realTimeDrawing", "value": real_time})).unwrap(); + app.session.execute("prefs.set", &json!({"key": "boundingBoxWhileMovingAndDrawing", "value": while_moving})).unwrap(); + let ctx = egui::Context::default(); + frame(&mut app, &ctx, vec![]); + app.select_tool(tool); + let (outline, handles) = bounding_box_drawn(&mut app, &ctx); + let before = box_corners(&mut app); + assert!(outline.is_some_and(|o| same_corners(&o, &before)) && handles == 8, "{what}: the box at rest"); + let mods = Mods { alt, ..Mods::default() }; + let view = app.view_info(); + let at = |kind, x, y| PointerEvent::new(kind, x, y).with_mods(mods); + app.session.pointer(&at(PointerKind::Down, 150.0, 130.0), view).unwrap(); + app.session.pointer(&at(PointerKind::Drag, 170.0, 140.0), view).unwrap(); + assert!(app.session.tool_transforming() && !app.session.tool_resizing(), "{what}: the drag moves the selection"); + let (first, handles) = bounding_box_drawn(&mut app, &ctx); + if !while_moving && !(tool == "selection" && !real_time) { + assert_eq!((first, handles), (None, 0), "{what}: the box hides during the move"); + app.session.pointer(&at(PointerKind::Up, 190.0, 165.0), view).unwrap(); + assert_eq!(bounding_box_drawn(&mut app, &ctx).1, 8, "{what}: back after the move"); + continue; + } + assert_eq!(handles, 8, "{what}: the handles show during the move"); + app.session.pointer(&at(PointerKind::Drag, 190.0, 165.0), view).unwrap(); + let (during, handles) = bounding_box_drawn(&mut app, &ctx); + assert_eq!(handles, 8, "{what}"); + assert!( + first.as_ref().zip(during.as_ref()).is_some_and(|(a, b)| !same_corners(a, b)), + "{what}: the box follows the drag: {first:?}, {during:?}" + ); + app.ui.view.bounding_box = false; + assert_eq!(bounding_box_drawn(&mut app, &ctx), (None, 0), "{what}: View › Hide Bounding Box"); + app.ui.view.bounding_box = true; + app.session.pointer(&at(PointerKind::Up, 190.0, 165.0), view).unwrap(); + let after = box_corners(&mut app); + assert!(!same_corners(&after, &before), "{what}: the drag moved the rectangle"); + assert!(during.as_ref().is_some_and(|o| same_corners(o, &after)), "{what}: {during:?}, moved to {after:?}"); + } + } +} + +/// With General › Show Bounding Box While Moving and Drawing on, the Rectangle, Rounded Rectangle, +/// Ellipse, Polygon and Star tools draw the bounding box and its handles around the shape being +/// drawn, at each step of the drag, where the release leaves the new shape's box; with Real-time +/// Drawing on and off, and whether or not something was selected before (off, the document and the +/// selection stay as they were until the release). Line Segment, which shows no box, draws none, +/// and View › Hide Bounding Box hides it. With the preference off, no box shows while a shape is +/// drawn. +#[test] +fn a_shape_being_drawn_shows_its_bounding_box() { + for (real_time, while_drawing) in [(true, true), (false, true), (true, false), (false, false)] { + for selected in [false, true] { + for tool in ["rectangle", "roundedRectangle", "ellipse", "polygon", "star", "lineSegment"] { + let what = format!("{tool}, real time {real_time}, selected before {selected}, box while drawing {while_drawing}"); + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap(); + let old = + app.session.execute("shape.rectangle", &json!({"x": 20, "y": 20, "width": 30, "height": 30})).unwrap()["id"].as_u64().unwrap(); + let layer = app.session.active().unwrap().doc.layer_color(vectorcraft_doc::NodeId(old)); + if !selected { + app.session.execute("select.none", &json!({})).unwrap(); + } + app.session.execute("prefs.set", &json!({"key": "realTimeDrawing", "value": real_time})).unwrap(); + app.session.execute("prefs.set", &json!({"key": "boundingBoxWhileMovingAndDrawing", "value": while_drawing})).unwrap(); + let ctx = egui::Context::default(); + frame(&mut app, &ctx, vec![]); + app.select_tool(tool); + frame(&mut app, &ctx, vec![]); + let view = app.view_info(); + app.session.pointer(&PointerEvent::new(PointerKind::Down, 150.0, 120.0), view).unwrap(); + app.session.pointer(&PointerEvent::new(PointerKind::Drag, 180.0, 140.0), view).unwrap(); + assert_eq!(app.session.draws_new_shape_box(), while_drawing && tool != "lineSegment", "{what}"); + let (first, handles) = bounding_box_drawn_in(&mut app, &ctx, layer); + if tool == "lineSegment" || !while_drawing { + assert_eq!((first, handles), (None, 0), "{what}: no box"); + continue; + } + assert_eq!(handles, 8, "{what}: the handles show while the shape is drawn"); + app.session.pointer(&PointerEvent::new(PointerKind::Drag, 210.0, 170.0), view).unwrap(); + let (during, handles) = bounding_box_drawn_in(&mut app, &ctx, layer); + assert_eq!(handles, 8, "{what}"); + assert!( + first.as_ref().zip(during.as_ref()).is_some_and(|(a, b)| !same_corners(a, b)), + "{what}: the box follows the drag: {first:?}, {during:?}" + ); + app.ui.view.bounding_box = false; + assert_eq!(bounding_box_drawn_in(&mut app, &ctx, layer), (None, 0), "{what}: View › Hide Bounding Box"); + app.ui.view.bounding_box = true; + app.session.pointer(&PointerEvent::new(PointerKind::Up, 210.0, 170.0), view).unwrap(); + let selection = app.session.active().unwrap().selection.objects.clone(); + assert!(selection.len() == 1 && selection[0] != vectorcraft_doc::NodeId(old), "{what}: the new shape is selected"); + let after = box_corners(&mut app); + assert!(during.as_ref().is_some_and(|o| same_corners(o, &after)), "{what}: {during:?}, drawn to {after:?}"); + } + } + } +} + +/// With General › Show Bounding Box While Moving and Drawing on, a failed preview of a shape being +/// drawn (with Real-time Drawing on it leaves the document and the selection as they were before +/// the drag) shows no box rather than the box of the selection from before the drag. With +/// Real-time Drawing on and off. +#[test] +fn a_failed_preview_of_a_shape_being_drawn_shows_no_bounding_box() { + for real_time in [true, false] { + let what = format!("real time {real_time}"); + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap(); + let old = app.session.execute("shape.rectangle", &json!({"x": 20, "y": 20, "width": 30, "height": 30})).unwrap()["id"].as_u64().unwrap(); + let layer = app.session.active().unwrap().doc.layer_color(vectorcraft_doc::NodeId(old)); + app.session.execute("prefs.set", &json!({"key": "realTimeDrawing", "value": real_time})).unwrap(); + app.session.execute("prefs.set", &json!({"key": "boundingBoxWhileMovingAndDrawing", "value": true})).unwrap(); + let ctx = egui::Context::default(); + frame(&mut app, &ctx, vec![]); + app.select_tool("rectangle"); + frame(&mut app, &ctx, vec![]); + let view = app.view_info(); + app.session.pointer(&PointerEvent::new(PointerKind::Down, 150.0, 120.0), view).unwrap(); + app.session.pointer(&PointerEvent::new(PointerKind::Drag, 180.0, 140.0), view).unwrap(); + assert_eq!(bounding_box_drawn_in(&mut app, &ctx, layer).1, 8, "{what}: the box of the shape being drawn"); + assert!(app.session.preview("shape.rectangle", &json!({"x": "left"})).is_err(), "{what}: the preview fails"); + assert!(app.session.draws_new_shape_box(), "{what}: the shape is still being drawn"); + let selection = app.session.active().unwrap().selection.objects.clone(); + assert_eq!(selection, vec![vectorcraft_doc::NodeId(old)], "{what}: the selection from before the drag"); + assert_eq!(bounding_box_drawn_in(&mut app, &ctx, layer), (None, 0), "{what}: no box, and not the old selection's"); + app.session.pointer(&PointerEvent::new(PointerKind::Drag, 210.0, 170.0), view).unwrap(); + assert_eq!(bounding_box_drawn_in(&mut app, &ctx, layer).1, 8, "{what}: the next step's preview shows the box again"); + } +} + +/// With General › Show Bounding Box While Moving and Drawing on, the box of a shape being drawn is +/// in the color of the layer the shape goes on, also when the selection from before the drag is on +/// another layer: with Real-time Drawing off, the shape is only in the pending preview. +#[test] +fn a_shape_being_drawn_has_its_box_in_its_own_layers_color() { + for real_time in [true, false] { + let what = format!("real time {real_time}"); + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap(); + let old = app.session.execute("shape.rectangle", &json!({"x": 20, "y": 20, "width": 30, "height": 30})).unwrap()["id"].as_u64().unwrap(); + let old_layer = app.session.active().unwrap().doc.layer_color(vectorcraft_doc::NodeId(old)); + let new_layer = vectorcraft_doc::NodeId(app.session.execute("layer.new", &json!({})).unwrap()["id"].as_u64().unwrap()); + let new_color = app.session.active().unwrap().doc.layer_color(new_layer); + assert_ne!(old_layer, new_color, "{what}: the two layers have different colors"); + assert_eq!(app.session.active().unwrap().selection.objects, vec![vectorcraft_doc::NodeId(old)], "{what}: the old rectangle stays selected"); + app.session.execute("prefs.set", &json!({"key": "realTimeDrawing", "value": real_time})).unwrap(); + app.session.execute("prefs.set", &json!({"key": "boundingBoxWhileMovingAndDrawing", "value": true})).unwrap(); + let ctx = egui::Context::default(); + frame(&mut app, &ctx, vec![]); + app.select_tool("rectangle"); + frame(&mut app, &ctx, vec![]); + let view = app.view_info(); + app.session.pointer(&PointerEvent::new(PointerKind::Down, 150.0, 120.0), view).unwrap(); + app.session.pointer(&PointerEvent::new(PointerKind::Drag, 180.0, 140.0), view).unwrap(); + let (outline, handles) = bounding_box_drawn_in(&mut app, &ctx, new_color); + assert!(outline.is_some() && handles == 8, "{what}: the box in the new layer's color"); + assert_eq!(bounding_box_drawn_in(&mut app, &ctx, old_layer).0, None, "{what}: not in the old selection's layer color"); + app.session.pointer(&PointerEvent::new(PointerKind::Up, 180.0, 140.0), view).unwrap(); + let st = app.session.active().unwrap(); + assert_eq!(st.doc.layer_containing(st.selection.objects[0]), Some(new_layer), "{what}: the shape went on the new layer"); + } +} + +/// With General › Show Bounding Box While Moving and Drawing on and Real-time Drawing off, a shape +/// drawn while point type is selected shows its box but not the type widget, which belongs to the +/// type selected before the drag. +#[test] +fn a_shape_being_drawn_has_no_type_widget() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap(); + let id = app.session.execute("text.create", &json!({"x": 40, "y": 40, "text": "Type"})).unwrap()["id"].as_u64().unwrap(); + app.session.execute("select.set", &json!({"ids": [id]})).unwrap(); + let layer = app.session.active().unwrap().doc.layer_color(vectorcraft_doc::NodeId(id)); + app.session.execute("prefs.set", &json!({"key": "realTimeDrawing", "value": false})).unwrap(); + app.session.execute("prefs.set", &json!({"key": "boundingBoxWhileMovingAndDrawing", "value": true})).unwrap(); + let ctx = egui::Context::default(); + let widgets = |app: &mut VectorcraftApp| { + let r = vectorcraft_tools::typewidget::RADIUS_PX; + shapes(app, &ctx).iter().filter(|s| matches!(s, Shape::Circle(c) if c.radius == r && c.fill != Color32::TRANSPARENT)).count() + }; + frame(&mut app, &ctx, vec![]); + app.select_tool("selection"); + assert_eq!(widgets(&mut app), 1, "the type widget of the selected type"); + app.select_tool("rectangle"); + frame(&mut app, &ctx, vec![]); + let view = app.view_info(); + app.session.pointer(&PointerEvent::new(PointerKind::Down, 150.0, 120.0), view).unwrap(); + app.session.pointer(&PointerEvent::new(PointerKind::Drag, 210.0, 170.0), view).unwrap(); + assert_eq!(bounding_box_drawn_in(&mut app, &ctx, layer).1, 8, "the box of the shape being drawn"); + assert_eq!(widgets(&mut app), 0, "no type widget beside it"); +} + +/// With General › Show Bounding Box While Moving and Drawing on, the Pencil, the Pen and the +/// Paintbrush draw no bounding box while they draw, as they show none afterwards; the selection's +/// box does not show either. +#[test] +fn freehand_and_pen_drawing_show_no_bounding_box() { + for tool in ["pencil", "pen", "paintbrush"] { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap(); + app.session.execute("shape.rectangle", &json!({"x": 20, "y": 20, "width": 30, "height": 30})).unwrap(); + app.session.execute("prefs.set", &json!({"key": "boundingBoxWhileMovingAndDrawing", "value": true})).unwrap(); + let ctx = egui::Context::default(); + frame(&mut app, &ctx, vec![]); + app.select_tool(tool); + frame(&mut app, &ctx, vec![]); + let view = app.view_info(); + app.session.pointer(&PointerEvent::new(PointerKind::Down, 150.0, 120.0), view).unwrap(); + for (x, y) in [(170.0, 130.0), (190.0, 150.0), (210.0, 170.0)] { + app.session.pointer(&PointerEvent::new(PointerKind::Drag, x, y), view).unwrap(); + } + assert!(!app.session.draws_new_shape_box(), "{tool}"); + let layer = app.session.active().unwrap().doc.layer_color(app.session.active().unwrap().doc.default_layer().unwrap()); + assert_eq!(bounding_box_drawn_in(&mut app, &ctx, layer), (None, 0), "{tool}: no box while it draws"); + app.session.pointer(&PointerEvent::new(PointerKind::Up, 210.0, 170.0), view).unwrap(); + } +} + /// A move, a rotation and a corner-radius drag keep hiding the bounding box and its handles until -/// the release (#712, #1229). +/// the release (#712, #1229). With General › Show Bounding Box While Moving and Drawing on, a move +/// shows them, and a rotation and a corner-radius drag still hide them. #[test] fn a_move_rotate_or_corner_radius_drag_hides_the_bounding_box() { let mut app = VectorcraftApp::new(Session::new(), Default::default()); @@ -4347,6 +4594,18 @@ fn a_move_rotate_or_corner_radius_drag_hides_the_bounding_box() { }; assert_eq!(drag(&mut app, corner, corner + vectorcraft_geom::Vec2::new(10.0, 10.0)), (None, 0), "a corner-radius drag"); assert_eq!(bounding_box_drawn(&mut app, &ctx).1, 8, "back after the corner-radius drag"); + // General › Show Bounding Box While Moving and Drawing keeps the box during a move only. The + // corner-radius drag and the rotation are undone first: the rectangle is at 110, 110 to 210, 210. + app.session.execute("edit.undo", &json!({})).unwrap(); + app.session.execute("edit.undo", &json!({})).unwrap(); + app.session.execute("prefs.set", &json!({"key": "boundingBoxWhileMovingAndDrawing", "value": true})).unwrap(); + assert_eq!(drag(&mut app, Point::new(150.0, 150.0), Point::new(160.0, 160.0)).1, 8, "a move with the preference on"); + assert_eq!(drag(&mut app, Point::new(220.0 + offset, 220.0 + offset), Point::new(270.0, 170.0)), (None, 0), "a rotation with the preference on"); + let corner = { + let st = app.session.active().unwrap(); + vectorcraft_tools::corners::CornerWidgets::of(&st.doc, &st.selection, app.view_info().zoom, false).unwrap().visible().next().unwrap() + }; + assert_eq!(drag(&mut app, corner, corner + vectorcraft_geom::Vec2::new(10.0, 10.0)), (None, 0), "a corner-radius drag with the preference on"); } /// View › Hide Edges hides the bounding box and the type widget beside it, and the Selection diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index e666fcc8c6..26503b272a 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -2851,6 +2851,7 @@ digits Malayalam Малаялам Rotate View Tool Инструмент „Завъртане на изглед“ Trackpad Gesture to Rotate View Завъртане на изгледа с жест на тракпада Hold a Tool Shortcut to Use the Tool Temporarily Задържане на клавиша на инструмент за временното му използване + Show Bounding Box While Moving and Drawing Показване на ограничителна рамка при преместване и рисуване Rotate… Завъртане… Rotation Завъртане Roughen… Грапавост… diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index 2381deb135..618a7fdb28 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -1398,6 +1398,7 @@ lighting Light Svjetlo Rotate View Tool Alat za rotaciju prikaza Trackpad Gesture to Rotate View Pokret dodirne ploče za rotaciju prikaza Hold a Tool Shortcut to Use the Tool Temporarily Držite prečicu alata za privremenu upotrebu alata + Show Bounding Box While Moving and Drawing Prikaži granični okvir pri pomjeranju i crtanju Rotate View Rotiraj prikaz Rounded Rectangle Tool Alat zaobljeni pravougaonik Scale Tool Alat za skaliranje diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index cd6dfad587..12c346012d 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -4643,6 +4643,7 @@ digits Malayalam Malaiàlam Tracking Espaiat Trackpad Gesture to Rotate View Gesticulació del trackpad per girar la vista Hold a Tool Shortcut to Use the Tool Temporarily Mantén premuda la drecera d’una eina per usar-la temporalment + Show Bounding Box While Moving and Drawing Mostra el quadre delimitador en moure i dibuixar Trademark Symbol Símbol de marca comercial Transform Transforma Transform Again Transforma de nou diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 00de3e7057..7d5b47eec3 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -1420,6 +1420,7 @@ lighting Light Světlo Rotate View Tool Nástroj Otočení zobrazení Trackpad Gesture to Rotate View Otáčet zobrazení gestem na trackpadu Hold a Tool Shortcut to Use the Tool Temporarily Podržením klávesové zkratky nástroj dočasně použít + Show Bounding Box While Moving and Drawing Zobrazit ohraničovací rámeček při přesouvání a kreslení Rotate View Otočení zobrazení Rounded Rectangle Tool Nástroj Zaoblený obdélník Scale Tool Nástroj Změna velikosti diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index f48e9d6ea3..efd03e778f 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -1333,6 +1333,7 @@ lighting Light Lys Rotate View Tool Visningsroteringsværktøj Trackpad Gesture to Rotate View Rotér visning med bevægelse på pegefeltet Hold a Tool Shortcut to Use the Tool Temporarily Hold et værktøjs genvejstast nede for at bruge værktøjet midlertidigt + Show Bounding Box While Moving and Drawing Vis afgrænsningsramme under flytning og tegning Rotate View Rotér visning Rounded Rectangle Tool Afrundet rektangelværktøj Scale Tool Skaleringsværktøj diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 0de0dce0b9..a4dd68d8e4 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -2893,6 +2893,7 @@ digits Malayalam Malayalam Rotate View Tool Ansichtdrehung-Werkzeug Trackpad Gesture to Rotate View Ansicht mit Trackpad-Geste drehen Hold a Tool Shortcut to Use the Tool Temporarily Werkzeug-Tastaturbefehl gedrückt halten, um das Werkzeug vorübergehend zu verwenden + Show Bounding Box While Moving and Drawing Begrenzungsrahmen beim Verschieben und Zeichnen einblenden Rotate… Drehen… Rotation Drehung Roughen… Aufrauen… diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index a75edff7a8..2a85190289 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -1971,6 +1971,7 @@ font-technology Variable Μεταβλητές Rotate View Tool Εργαλείο περιστροφής προβολής (Rotate View Tool) Trackpad Gesture to Rotate View Περιστροφή προβολής με χειρονομία στην επιφάνεια αφής Hold a Tool Shortcut to Use the Tool Temporarily Προσωρινή χρήση εργαλείου κρατώντας πατημένη τη συντόμευσή του + Show Bounding Box While Moving and Drawing Εμφάνιση πλαισίου οριοθέτησης κατά τη μετακίνηση και τη σχεδίαση Rotate… Περιστροφή… Rotation Περιστροφή Rows: Γραμμές: diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 6d548f6743..5a19d07b3a 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -2926,6 +2926,7 @@ digits Malayalam Malayálam Rotate View Tool Herramienta Rotar vista Trackpad Gesture to Rotate View Rotar vista con un gesto del trackpad Hold a Tool Shortcut to Use the Tool Temporarily Mantener pulsado el atajo de una herramienta para usarla temporalmente + Show Bounding Box While Moving and Drawing Mostrar cuadro delimitador al mover y dibujar Rotate… Rotar… Rotation Rotación Roughen… Hacer áspero… diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 7ae54d2bef..a3e97a85f5 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -1396,6 +1396,7 @@ lighting Light Valo Rotate View Tool Näkymän kiertotyökalu Trackpad Gesture to Rotate View Kierrä näkymää ohjauslevyn eleellä Hold a Tool Shortcut to Use the Tool Temporarily Käytä työkalua tilapäisesti pitämällä sen pikanäppäintä painettuna + Show Bounding Box While Moving and Drawing Näytä rajausruutu siirrettäessä ja piirrettäessä Rotate View Kierrä näkymää Rounded Rectangle Tool Pyöristetyn suorakulmion työkalu Scale Tool Skaalaustyökalu diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 35c649f895..f221046119 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -2918,6 +2918,7 @@ digits Malayalam Malayalam Rotate View Tool Outil Faire pivoter l’affichage Trackpad Gesture to Rotate View Faire pivoter l’affichage avec un geste du trackpad Hold a Tool Shortcut to Use the Tool Temporarily Maintenir le raccourci d’un outil pour l’utiliser temporairement + Show Bounding Box While Moving and Drawing Afficher le cadre de sélection pendant le déplacement et le dessin Rotate… Rotation… Rotation Rotation Roughen… Rugosité… diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 335b0c99f0..ef5bb21fb4 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -5036,6 +5036,7 @@ antiAlias Sharp शार्प Tracking ट्रैकिंग Trackpad Gesture to Rotate View दृश्य घुमाने के लिए ट्रैकपैड जेस्चर Hold a Tool Shortcut to Use the Tool Temporarily किसी टूल का शॉर्टकट दबाए रखकर उसे अस्थायी रूप से उपयोग करें + Show Bounding Box While Moving and Drawing खिसकाते और बनाते समय बाउंडिंग बॉक्स दिखाएँ Trademark Symbol ट्रेडमार्क सिंबल Transform रूपांतरण करें Transform Again फिर से बदलना diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index c3ed22bce8..07bb83abf2 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -3502,6 +3502,7 @@ mark-style Japanese Japán Rotate View Tool Nézet elforgatása Trackpad Gesture to Rotate View Nézet elforgatása kézmozdulattal az érintőpadon Hold a Tool Shortcut to Use the Tool Temporarily Eszköz ideiglenes használata a billentyűparancsa nyomva tartásával + Show Bounding Box While Moving and Drawing Befoglaló keret megjelenítése mozgatás és rajzolás közben Rotate… Elforgatás… Rotation Elforgatás Rotation relative to: Elforgatás viszonyítási alapja: diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 87c3f55cd5..fc27a42daa 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -1333,6 +1333,7 @@ lighting Light Cahaya Rotate View Tool Alat putar tampilan Trackpad Gesture to Rotate View Putar tampilan dengan gestur trackpad Hold a Tool Shortcut to Use the Tool Temporarily Tahan pintasan alat untuk menggunakan alat itu sementara + Show Bounding Box While Moving and Drawing Tampilkan kotak pembatas saat memindahkan dan menggambar Rotate View Putar tampilan Rounded Rectangle Tool Alat persegi panjang bulat Scale Tool Alat skala diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index a6edc31792..f5fc3d4dae 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -2969,6 +2969,7 @@ digits Malayalam Malayalam Rotate View Tool Strumento Ruota vista Trackpad Gesture to Rotate View Ruota vista con un gesto sul trackpad Hold a Tool Shortcut to Use the Tool Temporarily Tieni premuta la scorciatoia di uno strumento per usarlo temporaneamente + Show Bounding Box While Moving and Drawing Mostra riquadro di delimitazione durante lo spostamento e il disegno Rotate… Ruota… Rotation Rotazione Roughen… Irruvidisci… diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 65d7721aa1..4a2bcf1928 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -2954,6 +2954,7 @@ mark-style Japanese 日本式 Rotate View Tool 回転ビューツール Trackpad Gesture to Rotate View トラックパッドのジェスチャーでビューを回転 Hold a Tool Shortcut to Use the Tool Temporarily ツールのショートカットを押している間だけツールを使用 + Show Bounding Box While Moving and Drawing 移動中と描画中にバウンディングボックスを表示 Rotation 回転 Round Cap 丸型線端 Round Join ラウンド結合 diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index eb686a14a0..6c74e2d99d 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -5821,6 +5821,7 @@ imageTrace Outline კონტური Thread Text ტექსტის დაკავშირება Trackpad Gesture to Rotate View ხედის შემობრუნება ტრეკპადის ჟესტით Hold a Tool Shortcut to Use the Tool Temporarily ხელსაწყოს დროებით გამოყენება მისი მალსახმობის დაჭერით + Show Bounding Box While Moving and Drawing შემომსაზღვრელი ჩარჩოს ჩვენება გადაადგილებისა და ხატვისას Typing აკრეფა Uniformly Scaled თანაბრად მასშტაბირებული Unlock Variables ცვლადების განბლოკვა diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 52a10ab8e8..48e95dcb67 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -2943,6 +2943,7 @@ mark-style Japanese 일본식 Rotate View Tool 보기 회전 도구 Trackpad Gesture to Rotate View 트랙패드 제스처로 보기 회전 Hold a Tool Shortcut to Use the Tool Temporarily 도구 단축키를 누르고 있는 동안 도구를 임시로 사용 + Show Bounding Box While Moving and Drawing 이동 및 그리기 중 경계 상자 표시 Rotation 회전 Round Cap 둥근 끝 Round Join 둥근 연결 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 4f9d484155..318de97201 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -1347,6 +1347,7 @@ lighting Light Licht Rotate View Tool Weergave draaien-gereedschap Trackpad Gesture to Rotate View Weergave draaien met trackpadgebaar Hold a Tool Shortcut to Use the Tool Temporarily Sneltoets van een gereedschap ingedrukt houden om het tijdelijk te gebruiken + Show Bounding Box While Moving and Drawing Begrenzingskader tonen tijdens verplaatsen en tekenen Rotate View Weergave draaien Rounded Rectangle Tool Afgeronde rechthoek-gereedschap Scale Tool Schalen-gereedschap diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index e44328480f..4c1c3222db 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -1341,6 +1341,7 @@ lighting Light Światło Rotate View Tool Narzędzie Obrót widoku Trackpad Gesture to Rotate View Obracaj widok gestem na gładziku Hold a Tool Shortcut to Use the Tool Temporarily Używaj narzędzia tymczasowo, przytrzymując jego skrót + Show Bounding Box While Moving and Drawing Pokaż obwiednię podczas przesuwania i rysowania Rounded Rectangle Tool Narzędzie Zaokrąglony prostokąt Scale Tool Narzędzie Skalowanie Scallop Tool Narzędzie Ząbkowanie diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 3ce111f7a4..46aee510e5 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -2455,6 +2455,7 @@ digits Malayalam Malaiala Rotate View Tool Ferramenta Girar Visualização Trackpad Gesture to Rotate View Girar Visualização com Gesto do Trackpad Hold a Tool Shortcut to Use the Tool Temporarily Manter o Atalho de uma Ferramenta Pressionado para Usá-la Temporariamente + Show Bounding Box While Moving and Drawing Mostrar Caixa Delimitadora ao Mover e Desenhar Rotate… Girar… Rotation Rotação Roughen… Enrugar… diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index c93807c08a..aab0cc932d 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -2956,6 +2956,7 @@ digits Malayalam Малаялам Rotate View Tool Инструмент «Поворот вида» Trackpad Gesture to Rotate View Поворачивать вид жестом трекпада Hold a Tool Shortcut to Use the Tool Temporarily Временно использовать инструмент, удерживая его клавишу + Show Bounding Box While Moving and Drawing Показывать ограничительную рамку при перемещении и рисовании Rotate… Повернуть… Rotation Поворот Roughen… Огрубление… diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index ce20b6c72d..7d62c434b9 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -2862,6 +2862,7 @@ digits Malayalam Malayalam Rotate View Tool Verktyget Rotera vy Trackpad Gesture to Rotate View Rotera vyn med en gest på styrplattan Hold a Tool Shortcut to Use the Tool Temporarily Håll ned ett verktygs kortkommando för att använda verktyget tillfälligt + Show Bounding Box While Moving and Drawing Visa begränsningsram vid flyttning och ritning Rotate… Rotera… Rotation Rotation Roughen… Ojämn… diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index db3c4501b7..125fd1f969 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -5708,6 +5708,7 @@ theme Appearance Görünüm Thread Text Metni Bağla Trackpad Gesture to Rotate View Görünümü Döndürmek İçin İzleme Dörtgeni Hareketi Hold a Tool Shortcut to Use the Tool Temporarily Aracı Geçici Olarak Kullanmak İçin Kısayolunu Basılı Tutma + Show Bounding Box While Moving and Drawing Taşırken ve Çizerken Sınırlayıcı Kutuyu Göster Typing Yazma Uniformly Scaled Tekdüze Ölçeklenmiş Unlock Variables Değişkenlerin Kilidini Aç diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 151a886cf9..1a95fbfb20 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -2961,6 +2961,7 @@ digits Malayalam Малаялам Rotate View Tool Інструмент «Поворот виду» Trackpad Gesture to Rotate View Повертати вид жестом трекпада Hold a Tool Shortcut to Use the Tool Temporarily Тимчасово використовувати інструмент, утримуючи його клавішу + Show Bounding Box While Moving and Drawing Показувати обмежувальну рамку під час переміщення та малювання Rotate… Повернути… Rotation Поворот Roughen… Надати нерівності… diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index f3b505c648..ace7c211b5 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -3265,6 +3265,7 @@ mark-style Japanese Kiểu Nhật Rotate View Tool Công cụ Xoay chế độ xem Trackpad Gesture to Rotate View Xoay chế độ xem bằng cử chỉ trên bàn di chuột Hold a Tool Shortcut to Use the Tool Temporarily Giữ phím tắt của công cụ để tạm thời dùng công cụ đó + Show Bounding Box While Moving and Drawing Hiện hộp bao khi di chuyển và vẽ Rotate… Xoay… Rotation Góc xoay Rotation relative to: Xoay tương đối với: diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index c7219b9448..0b4d5d9d02 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -2825,6 +2825,7 @@ digits Malayalam 马拉雅拉姆数字 Rotate View Tool 旋转视图工具 Trackpad Gesture to Rotate View 使用触控板手势旋转视图 Hold a Tool Shortcut to Use the Tool Temporarily 按住工具快捷键以临时使用该工具 + Show Bounding Box While Moving and Drawing 移动和绘制时显示定界框 Rotate… 旋转… Rotation 旋转 Roughen… 粗糙效果… diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 1c753576a5..4eb98c5a99 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -3934,6 +3934,7 @@ digits Malayalam 馬來亞拉姆數字 Rotate View Tool 旋轉檢視工具 Trackpad Gesture to Rotate View 使用觸控式軌跡板手勢旋轉檢視 Hold a Tool Shortcut to Use the Tool Temporarily 按住工具快速鍵以暫時使用該工具 + Show Bounding Box While Moving and Drawing 移動和繪製時顯示邊框 Rotate… 旋轉… Rotation 旋轉 Roughen… 粗糙效果… diff --git a/docs/mcp.md b/docs/mcp.md index baa2631f7b..8eea90be16 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -5019,7 +5019,11 @@ default: View › Actual Size / `view.actualSize` is one document point per scre preference `holdToolKeys` also needs the desktop app. It is on by default: a single-key tool shortcut held for 0.25 s or more, or held while its tool is used, chooses the tool until the key is let go and then returns to the tool active before, after any drag in progress; a quicker press chooses the tool for good. With it off, every press and every key -repeat chooses the tool. `highlightAlternateGlyphs` (off by default: on, the canvas paints a pink cell behind glyphs OpenType features +repeat chooses the tool. The canvas preference `boundingBoxWhileMovingAndDrawing` (off by default) also needs the +desktop app: on, with View › Show Bounding Box, the bounding box and its handles stay visible while a drag moves the +selection, and the Rectangle, Rounded Rectangle, Ellipse, Polygon and Star tools draw the box around the shape being +drawn; off, the box hides during both, except that a Selection tool move with `realTimeDrawing` off still moves the +box. `highlightAlternateGlyphs` (off by default: on, the canvas paints a pink cell behind glyphs OpenType features substituted — ligatures, contextual forms — on screen; Document Setup's Highlight Substituted Glyphs stays separate). `highlightSubstitutedFonts` (on by default: the canvas paints pink behind text drawn in a substitute for a missing font, in every document). `blackOnScreen` diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index f28078ec33..49b979bdcd 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -871,6 +871,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - Units › East Asian Type (#394, `unitsAsianType`): Points by default; it offers every unit and Q and Ha, a quarter of a millimetre each (0.709 pt: the [Illustrator Scripting Guide](https://ai-scripting.docsforadobe.dev/scripting/measurementUnits/); `q` and `h` are the field suffixes [InDesign's notation table](https://helpx.adobe.com/in/indesign/desktop/layout-and-grid-tools/rulers-and-measure-tools/change-ruler-measurement-units.html) lists), and only it lists them: rulers, Document Setup and the other Units preferences keep their ten. The public pages do not say which fields follow it, so no field uses it yet and that waits for a source. The model stays in points; any length field reads `13q`/`13h`. Evidence: `q_and_ha_are_a_quarter_millimetre`, `east_asian_type_unit_*` (`crates/engine/src/tests/units.rs`). - Preferences audit (2026-10-30, #394): every Preferences dialog option is read by shipped code, by its field or its key, except User Interface › Open Documents As Tabs, Units › East Asian Type, Hyphenation › Default Language, Plug-ins & Scratch Disks › Primary and Secondary, and Clipboard Handling › Legacy vector clipboard and its mode, which are stored only; [gaps.md G12](gaps.md#g12-preferences-and-panels-that-dont-act-yet) says what each waits on. - With Performance › Real-time Drawing and Editing off, the bounding box moves with a Selection-tool move drag and the object moves when the button is released ([help](https://web.archive.org/web/20220324053041id_/https://helpx.adobe.com/illustrator/using/real-time-editing.html); `real_time_drawing_off_moves_the_bounding_box_with_a_move_drag`). + - VectorCraft's own preference General › Show Bounding Box While Moving and Drawing (`boundingBoxWhileMovingAndDrawing`, off by default): on, with View › Show Bounding Box, the bounding box and its handles stay visible while a drag moves the selection (a Selection tool move, an Alt-drag copy, a shape tool's move by the center point), measured once when the drag starts and moved with it, and the Rectangle, Rounded Rectangle, Ellipse, Polygon and Star tools draw the box around the shape being drawn. A rotation and a widget drag still hide the box until the release, and the Line Segment tool, the Pen, the Pencil and the Paintbrush draw none (`a_move_drag_keeps_the_bounding_box_where_the_drag_carries_the_selection`, `a_shape_being_drawn_shows_its_bounding_box`, `a_move_drag_measures_the_bounding_box_once`). - Performance › Real-time Drawing and Editing off defers a drag's edits to its release, but a ruler guide dragged out of a ruler or moved still shows as it is dragged, where only its position label showed (#4646; `a_guide_dragged_out_of_a_ruler_shows_while_dragged`, `real_time_drawing_off_still_shows_a_dragged_guide`). - With Real-time Drawing and Editing off, a dragged selection whose outline points and evaluated objects come to more than 100,000 shows its moved bounds instead, as the selection at rest does (#525, #2460; `real_time_drawing_off_draws_the_bounds_of_a_selection_too_big_to_outline`, `real_time_drawing_off_draws_the_bounds_of_a_group_too_big_to_outline`, `real_time_drawing_off_draws_the_bounds_of_a_repeat_with_too_many_objects`). - With Real-time Drawing and Editing off, a move or handle drag of a group, blend, envelope, repeat or intertwine draws the outline of each of its objects instead of one union of them, as VectorCraft's own rule (`real_time_drawing_off_outlines_a_dragged_group_member_by_member`, `real_time_drawing_off_outlines_a_resized_group_member_by_member`, `real_time_drawing_off_outlines_a_dragged_blend_step_by_step`, `real_time_drawing_off_outlines_a_dragged_envelope_object_by_object`). A group with a Pathfinder effect draws the area the effect makes, a gradient mesh draws its mesh lines, and a symbol instance draws its bounds (`real_time_drawing_off_outlines_a_dragged_pathfinder_group_by_its_area`, `real_time_drawing_off_outlines_each_mesh_of_a_dragged_repeat_once`, `real_time_drawing_off_outlines_a_dragged_symbol_instance_by_its_bounds`).