diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 3402c935..db7dac16 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -1,7 +1,7 @@ use serde_json::json; use super::*; -use vectorcraft_tools::{PointerEvent, PointerKind}; +use vectorcraft_tools::{PointerEvent, PointerKind, ToolKey}; fn session() -> Session { let mut s = Session::new(); @@ -319,6 +319,9 @@ fn flare_tool_two_step_gesture_is_one_undo() { s.select_tool("flare", v).unwrap(); s.pointer(&PointerEvent::new(PointerKind::Down, 200.0, 200.0), v).unwrap(); s.pointer(&PointerEvent::new(PointerKind::Drag, 240.0, 200.0), v).unwrap(); + // ↓ twice: 13 rays (one compound path with a subpath per ray). + s.tool_key(ToolKey::Down, Default::default(), v).unwrap(); + s.tool_key(ToolKey::Down, Default::default(), v).unwrap(); s.pointer(&PointerEvent::new(PointerKind::Up, 240.0, 200.0), v).unwrap(); s.pointer(&PointerEvent::new(PointerKind::Move, 500.0, 400.0), v).unwrap(); s.pointer(&PointerEvent::new(PointerKind::Down, 500.0, 400.0), v).unwrap(); @@ -334,6 +337,8 @@ fn flare_tool_two_step_gesture_is_one_undo() { assert!(b.x1 > 400.0 && b.y1 > 300.0, "{b:?}"); let centre = children.iter().find(|c| c.name.as_deref() == Some("Center")).unwrap().geometric_bounds().unwrap(); assert!((centre.width() - 80.0).abs() < 0.5); + let rays = children.iter().find(|c| c.name.as_deref() == Some("Rays")).unwrap(); + assert_eq!(rays.path_data().unwrap().subpaths.len(), 13); } #[test] diff --git a/crates/tools/src/extra.rs b/crates/tools/src/extra.rs index 3cee14ce..b3d51ba5 100644 --- a/crates/tools/src/extra.rs +++ b/crates/tools/src/extra.rs @@ -27,11 +27,16 @@ pub struct FlareTool { placed: Option<(Point, f64)>, hover: Point, began: bool, + /// Ray count changed with ↑/↓ during the gesture (None = the command's default, 15). + rays: Option, } impl FlareTool { - fn params(c: Point, diameter: f64, end: Option) -> Value { + fn params(&self, c: Point, diameter: f64, end: Option) -> Value { let mut v = json!({ "cx": c.x, "cy": c.y, "diameter": diameter.max(1.0) }); + if let Some(r) = self.rays { + v["rays"] = json!(r); + } if let Some(e) = end { v["x2"] = json!(e.x); v["y2"] = json!(e.y); @@ -63,21 +68,21 @@ impl Tool for FlareTool { self.began = true; return vec![ Action::Begin("Flare".into()), - Action::Preview("shape.flare".into(), Self::params(c, 2.0 * ev.pos.distance(c), None)), + Action::Preview("shape.flare".into(), self.params(c, 2.0 * ev.pos.distance(c), None)), ]; } if self.began { - return vec![Action::Preview("shape.flare".into(), Self::params(c, 2.0 * ev.pos.distance(c), None))]; + return vec![Action::Preview("shape.flare".into(), self.params(c, 2.0 * ev.pos.distance(c), None))]; } } else if let Some((c, d)) = self.placed { - return vec![Action::Preview("shape.flare".into(), Self::params(c, d, Some(ev.pos)))]; + return vec![Action::Preview("shape.flare".into(), self.params(c, d, Some(ev.pos)))]; } vec![] } PointerKind::Up => { if let Some((c, d)) = self.placed.take() { self.began = false; - return vec![Action::Preview("shape.flare".into(), Self::params(c, d, Some(ev.pos))), Action::Commit]; + return vec![Action::Preview("shape.flare".into(), self.params(c, d, Some(ev.pos))), Action::Commit]; } let Some((c, p)) = self.drag.take() else { return vec![] }; if self.began { @@ -90,7 +95,7 @@ impl Tool for FlareTool { } PointerKind::Move => { if let Some((c, d)) = self.placed { - return vec![Action::Preview("shape.flare".into(), Self::params(c, d, Some(ev.pos)))]; + return vec![Action::Preview("shape.flare".into(), self.params(c, d, Some(ev.pos)))]; } vec![] } @@ -98,7 +103,19 @@ impl Tool for FlareTool { } } fn key(&mut self, _cx: &ToolContext, key: ToolKey, _mods: Mods) -> Vec { + // ↑/↓ while drawing add/remove rays. + if matches!(key, ToolKey::Up | ToolKey::Down) && self.began { + let r = self.rays.unwrap_or(15); + self.rays = Some(if key == ToolKey::Up { (r + 1).min(50) } else { r.saturating_sub(1) }); + let preview = match (self.drag, self.placed) { + (Some((c, p)), _) => self.params(c, 2.0 * p.distance(c), None), + (_, Some((c, d))) => self.params(c, d, Some(self.hover)), + _ => return vec![], + }; + return vec![Action::Preview("shape.flare".into(), preview)]; + } if key == ToolKey::Escape && self.busy() { + self.rays = None; self.drag = None; self.placed = None; let began = std::mem::take(&mut self.began);