From ebc171be57b2aceba34f590960739891d8416fad Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Tue, 3 Nov 2026 11:26:59 +0000 Subject: [PATCH] engine: extend an ink stroke in place and cap a gesture's samples --- crates/engine/src/tests.rs | 16 ++++++++++++++++ crates/engine/src/tools.rs | 1 + crates/engine/src/tools/drag.rs | 24 ++++++++++++++++++------ crates/engine/src/tools/pointer.rs | 4 +++- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 68de13418..b7d029f30 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -5023,6 +5023,22 @@ fn pen_pressure_is_kept_and_a_mouse_draws_at_full_width() { assert_eq!(got, vec![vec![0.2, 1.0, 0.4], vec![1.0, 1.0]]); } +#[test] +fn a_long_ink_stroke_is_extended_in_place_and_capped() { + let mut s = session(); + s.set_tool(tools::ToolKind::Ink { mode: "pen".into(), color: deckcraft_model::Rgba::rgb(20, 20, 20), width: 4.0 }); + let ev = |kind, x: f64| tools::PointerEvent { kind, x, y: 80.0, mods: Mods::default(), tol: 3.0, pressure: Some(0.5) }; + s.pointer(ev(PointerKind::Down, 0.0)).unwrap(); + let n = tools::MAX_GESTURE_POINTS + 5_000; + for i in 1..n { + s.pointer(ev(PointerKind::Drag, (i % 500) as f64)).unwrap(); + } + assert_eq!(s.tool.ink_preview.len(), tools::MAX_GESTURE_POINTS); + s.pointer(ev(PointerKind::Up, 0.0)).unwrap(); + let kept: usize = ink_pressures(&s).iter().map(Vec::len).sum(); + assert!((2..=tools::MAX_GESTURE_POINTS).contains(&kept), "{kept}"); +} + fn ink_pressures(s: &Session) -> Vec> { let st = s.doc().unwrap(); let Some(sh) = st.shapes().iter().rev().find(|sh| matches!(sh.kind, deckcraft_model::ShapeKind::Ink { .. })) else { diff --git a/crates/engine/src/tools.rs b/crates/engine/src/tools.rs index 82f53dfc3..50a0fbb02 100644 --- a/crates/engine/src/tools.rs +++ b/crates/engine/src/tools.rs @@ -17,6 +17,7 @@ mod geometry; mod hit; mod pointer; +pub(crate) use drag::MAX_GESTURE_POINTS; pub use hit::{line_xfrm, resize_box, table_rows, text_pos}; #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/crates/engine/src/tools/drag.rs b/crates/engine/src/tools/drag.rs index dcee9cf7d..0c80df052 100644 --- a/crates/engine/src/tools/drag.rs +++ b/crates/engine/src/tools/drag.rs @@ -12,9 +12,24 @@ struct MoveDrag { enter_text: Option<(ShapeId, Point)>, } +/// Samples one ink or freeform gesture keeps; later pointer moves extend it no further. +pub(crate) const MAX_GESTURE_POINTS: usize = 20_000; + impl Session { /// Update the active drag interaction using the pointer position and modifiers. pub(super) fn drag(&mut self, p: Point, mods: Mods, tol: f64) -> Result { + // Ink grows by one sample per move: extend it (and its preview) in place rather than + // copying the whole stroke twice per event. + if matches!(self.tool.drag, Some(Drag::Ink { .. })) { + let sample = self.ink_sample(p); + if let Some(Drag::Ink { points }) = self.tool.drag.as_mut() + && points.len() < MAX_GESTURE_POINTS + { + points.push(sample); + self.tool.ink_preview.push(sample); + } + return Ok(Value::Null); + } let Some(drag) = self.tool.drag.clone() else { return Ok(Value::Null) }; match drag { Drag::Guide { index } => { @@ -181,7 +196,7 @@ impl Session { } Drag::Freeform { mode, mut points, pressed } => { // Dragging scribbles (except for curves, which only take clicked points). - if pressed && mode != "curve" && points.last().is_none_or(|l| (*l - p).hypot() >= 1.5) { + if pressed && mode != "curve" && points.len() < MAX_GESTURE_POINTS && points.last().is_none_or(|l| (*l - p).hypot() >= 1.5) { points.push(p); } let mut v = points.clone(); @@ -191,11 +206,8 @@ impl Session { self.tool.path_preview = v; self.tool.drag = Some(Drag::Freeform { mode, points, pressed }); } - Drag::Ink { mut points } => { - points.push(self.ink_sample(p)); - self.tool.ink_preview = points.clone(); - self.tool.drag = Some(Drag::Ink { points }); - } + // Handled above. + Drag::Ink { .. } => {} } Ok(Value::Null) } diff --git a/crates/engine/src/tools/pointer.rs b/crates/engine/src/tools/pointer.rs index d20ae9315..8433a998e 100644 --- a/crates/engine/src/tools/pointer.rs +++ b/crates/engine/src/tools/pointer.rs @@ -228,7 +228,9 @@ impl Session { self.tool.drag = Some(Drag::Freeform { mode, points, pressed: false }); return self.finish_freeform(true); } - points.push(p); + if points.len() < super::MAX_GESTURE_POINTS { + points.push(p); + } self.tool.path_preview = points.clone(); self.tool.drag = Some(Drag::Freeform { mode, points, pressed: true }); return Ok(Value::Null);