diff --git a/crates/tools/src/shape.rs b/crates/tools/src/shape.rs index 30e5b4c1..e5e3d51a 100644 --- a/crates/tools/src/shape.rs +++ b/crates/tools/src/shape.rs @@ -1,8 +1,8 @@ //! Shape tools: Rectangle, Rounded Rectangle, Ellipse, Polygon, Star, Line Segment. //! -//! Drag draws (Shift constrains to square/circle/45°, Alt draws from the centre); a click without a -//! drag asks the UI for the size dialog (like Illustrator). ↑/↓ during a polygon/star drag change the -//! side/point count. +//! Drag draws (Shift constrains to square/circle/45°, Alt draws from the centre, Space held moves +//! the shape being drawn); a click without a drag asks the UI for the size dialog (like +//! Illustrator). ↑/↓ during a polygon/star drag change the side/point count. use serde_json::{Value, json}; use vectorcraft_geom::{Point, Rect}; @@ -120,10 +120,15 @@ impl Tool for ShapeTool { vec![] } PointerKind::Drag => { - let Some(s) = self.start else { return vec![] }; + let Some(mut s) = self.start else { return vec![] }; let (pos, g) = crate::guides::snap_draw(cx, ev.pos, &[]); self.guides = g; let ev = &PointerEvent { pos, ..*ev }; + // Space held while drawing: the shape moves with the pointer instead of growing. + if ev.mods.space && self.began { + s += ev.pos - self.last; + self.start = Some(s); + } self.last = ev.pos; self.mods = ev.mods; let mut out = vec![]; @@ -239,6 +244,31 @@ mod tests { assert_eq!(t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 50.0, 30.0)), vec![Action::Commit]); } + #[test] + fn space_moves_the_shape_being_drawn() { + let (d, _) = doc_with_rect(); + let s = Selection::default(); + let p = paint(); + let cx = cx(&d, &s, &p); + let space = Mods { space: true, ..Default::default() }; + let mut t = ShapeTool::new("rectangle"); + t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 50.0, 50.0)); + t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 150.0, 120.0)); + // Space held: the 100 × 70 rectangle follows the pointer. + let a = t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 250.0, 170.0).with_mods(space)); + assert_eq!(a.last(), Some(&Action::Preview("shape.rectangle".into(), json!({"x": 150.0, "y": 100.0, "width": 100.0, "height": 70.0})))); + // Let go of Space: it grows again, from where it was moved to. + let a = t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 270.0, 190.0)); + assert_eq!(a.last(), Some(&Action::Preview("shape.rectangle".into(), json!({"x": 150.0, "y": 100.0, "width": 120.0, "height": 90.0})))); + assert_eq!(t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 270.0, 190.0)), vec![Action::Commit]); + // A star (drawn from its centre) moves its centre. + let mut t = ShapeTool::new("star"); + t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 50.0, 50.0)); + t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 80.0, 50.0)); + let a = t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 90.0, 60.0).with_mods(space)); + assert!(matches!(a.last(), Some(Action::Preview(_, v)) if v["cx"] == 60.0 && v["cy"] == 60.0), "{a:?}"); + } + #[test] fn shift_square_alt_center() { let t = ShapeTool::new("ellipse");