From dea57b348b0901af6f99d7bb2d53e767a20efa5a Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Fri, 13 Nov 2026 19:00:22 -0800 Subject: [PATCH] Dimension tool: the first preview of a drag-out is drawn at the pointer Dragging out a dimension from a segment (Linear) or a corner (Angular) drew its first preview frame at the wrong distance. The preview placed the dimension at the pointer before reading the segment's end points or the corner from the art, and measured the offset from the placeholder points the dimension starts with. A drag from the top edge of a rectangle to 40 pt above it showed an offset of 60 instead of -40. The preview now clears the offset, reads the art's points, places the dimension at the pointer and checks the result. Clearing the offset keeps an out-of-range offset from the last pointer position from failing the read. Tests: the segment and corner drag-out tests fail without the change. placement_recovers_after_a_position_out_of_range also passes on main; it fails when only the offset clear is removed. --- crates/tools/src/dimension.rs | 58 ++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/crates/tools/src/dimension.rs b/crates/tools/src/dimension.rs index 14f7f85881..ff99091153 100644 --- a/crates/tools/src/dimension.rs +++ b/crates/tools/src/dimension.rs @@ -344,10 +344,19 @@ impl DimensionTool { _ => Orientation::Aligned, }; } - d.place(p); + // A dimension dragged out from art starts with placeholder points. Read the art's points + // before placing the dimension at the pointer. Clear the offset first: an out-of-range + // offset from the last pointer position fails the read. + if !matches!(d.mode, DimensionMode::Radius | DimensionMode::Diameter) { + d.offset = 0.0; + } if d.resolve(cx.doc).is_err() { return vec![]; } + d.place(p); + if d.validate().is_err() { + return vec![]; + } let mut actions = vec![]; if !self.began { actions.push(Action::Begin("Create Dimension".into())); @@ -675,6 +684,53 @@ mod tests { assert!(matches!(placed.last(), Some(Action::Commit)), "{placed:?}"); } + /// A pointer position too far away to place the dimension shows no preview, and the preview + /// comes back where the pointer returns. + #[test] + fn placement_recovers_after_a_position_out_of_range() { + let (d, _) = doc_with_rect(); + let (s, p) = (Selection::default(), paint()); + let c = cx(&d, &s, &p); + let mut t = DimensionTool::default(); + for (x, y) in [(400.0, 400.0), (450.0, 400.0)] { + t.pointer(&c, &PointerEvent::new(PointerKind::Down, x, y)); + t.pointer(&c, &PointerEvent::new(PointerKind::Up, x, y)); + } + assert!(t.pointer(&c, &PointerEvent::new(PointerKind::Move, 420.0, 3.0e6)).is_empty()); + let acts = t.pointer(&c, &PointerEvent::new(PointerKind::Move, 420.0, 450.0)); + let Some(Action::Preview(_, params)) = acts.last() else { panic!("{acts:?}") }; + assert_eq!(params["offset"], json!(50.0)); + } + + /// Dragging out from a segment previews the dimension at the pointer from the first drag on. + #[test] + fn the_first_preview_of_a_segment_drag_is_at_the_pointer() { + let (d, _) = doc_with_rect(); + let (s, p) = (Selection::default(), paint()); + let c = cx(&d, &s, &p); + let mut t = DimensionTool::default(); + t.pointer(&c, &PointerEvent::new(PointerKind::Down, 150.0, 100.0)); + let acts = t.pointer(&c, &PointerEvent::new(PointerKind::Drag, 150.0, 60.0)); + let Some(Action::Preview(_, params)) = acts.last() else { panic!("{acts:?}") }; + assert_eq!(params["offset"], json!(-40.0), "40 pt above the top edge"); + } + + /// Dragging out from a corner previews the angle at the pointer's distance from the corner + /// from the first drag on. + #[test] + fn the_first_preview_of_a_corner_drag_is_at_the_pointer() { + let (d, _) = doc_with_rect(); + let (s, p) = (Selection::default(), paint()); + let c = cx(&d, &s, &p); + let mut t = DimensionTool::default(); + t.set_option("mode", &json!("angular")); + t.pointer(&c, &PointerEvent::new(PointerKind::Down, 100.0, 100.0)); + let acts = t.pointer(&c, &PointerEvent::new(PointerKind::Drag, 130.0, 140.0)); + let Some(Action::Preview(_, params)) = acts.last() else { panic!("{acts:?}") }; + assert_eq!(params["vertex"], json!([100.0, 100.0])); + assert_eq!(params["offset"], json!(50.0), "50 pt from the corner"); + } + /// A Linear press on an anchor picks that anchor even when the pointer moves a few pixels /// before the release: it does not drag out the dimension of a segment that ends there. Both /// picks keep their links to the path, so the dimension measures between the two anchors.