diff --git a/crates/doc/src/appearance/profile.rs b/crates/doc/src/appearance/profile.rs index fabe408e1..01b9c9fc5 100644 --- a/crates/doc/src/appearance/profile.rs +++ b/crates/doc/src/appearance/profile.rs @@ -90,7 +90,8 @@ impl WidthProfile { /// discontinuous point (two points at `t`, within `1e-7`). pub fn around(&self, t: f64) -> ((f64, f64), (f64, f64)) { let p = &self.points; - let i = p.partition_point(|q| q.0 < t - 1e-7); + // The same distance test below, so a point exactly 1e-7 before `t` is never skipped by rounding. + let i = p.partition_point(|q| q.0 < t && (q.0 - t).abs() > 1e-7); if let Some(first) = p.get(i).filter(|q| (q.0 - t).abs() <= 1e-7) { let j = i + p[i..].partition_point(|q| q.0 - first.0 <= 1e-9) - 1; if j > i { @@ -353,5 +354,8 @@ mod tests { // Many points at one position are one step, found without a pass over them all. let many = WidthProfile { points: (0..1000).map(|i| (0.5, i as f64, i as f64)).collect() }; assert_eq!(many.around(0.5), ((0.0, 0.0), (999.0, 999.0))); + // 0.4999999 == 0.5 - 1e-7, yet its distance from 0.5 rounds above 1e-7: it is not part of the step. + let near = WidthProfile { points: vec![(0.4999999, 2.0, 2.0), (0.5, 1.0, 1.0), (0.5, 0.5, 0.25)] }; + assert_eq!(near.around(0.5), ((1.0, 1.0), (0.5, 0.25))); } } diff --git a/crates/tools/src/symbolism.rs b/crates/tools/src/symbolism.rs index eed36da85..1618d6e7d 100644 --- a/crates/tools/src/symbolism.rs +++ b/crates/tools/src/symbolism.rs @@ -117,6 +117,19 @@ impl SymbolismTool { } } +impl SymbolismTool { + /// Ends the drag: what is committed is the last preview, so the whole drag is shown first. + fn finish(&mut self, cx: &ToolContext) -> Vec { + self.active = false; + let stale = (self.previewed != self.points.len()).then(|| { + let (c, v) = self.command(cx); + Action::Preview(c, v) + }); + self.points.clear(); + stale.into_iter().chain([Action::Commit]).collect() + } +} + impl Tool for SymbolismTool { fn id(&self) -> &'static str { self.id @@ -151,16 +164,7 @@ impl Tool for SymbolismTool { let (c, v) = self.command(cx); vec![Action::Preview(c, v)] } - PointerKind::Up if self.active => { - self.active = false; - // What is committed is the last preview: show the whole drag first. - let stale = (self.previewed != self.points.len()).then(|| { - let (c, v) = self.command(cx); - Action::Preview(c, v) - }); - self.points.clear(); - stale.into_iter().chain([Action::Commit]).collect() - } + PointerKind::Up if self.active => self.finish(cx), _ => vec![], } } @@ -182,11 +186,9 @@ impl Tool for SymbolismTool { } vec![] } - fn deactivate(&mut self, _cx: &ToolContext) -> Vec { + fn deactivate(&mut self, cx: &ToolContext) -> Vec { if self.active { - self.active = false; - self.points.clear(); - return vec![Action::Commit]; + return self.finish(cx); } vec![] } @@ -335,6 +337,23 @@ mod tests { assert_eq!(up.last(), Some(&Action::Commit)); } + #[test] + fn switching_tools_mid_drag_previews_the_whole_drag_before_committing() { + let (d, _) = doc_with_rect(); + let (s, p) = (Selection::default(), paint()); + let cx = cx(&d, &s, &p); + let mut t = SymbolismTool::new("symbolShifter"); + t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 0.0, 0.0)); + let n = 7 * crate::PREVIEW_ALL_UNTIL + 1; // not a point that previews + for i in 1..n { + t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 50.0 * i as f64, 0.0)); + } + let a = t.deactivate(&cx); + assert!(matches!(a.first(), Some(Action::Preview(_, v)) if v["points"].as_array().unwrap().len() == n), "{a:?}"); + assert_eq!(a.last(), Some(&Action::Commit)); + assert!(!t.busy()); + } + #[test] fn adjust_tools_map_to_symbol_adjust() { let (d, _) = doc_with_rect();