diff --git a/crates/tools/src/xform/measure.rs b/crates/tools/src/xform/measure.rs index 140697dd6..cf04c85c5 100644 --- a/crates/tools/src/xform/measure.rs +++ b/crates/tools/src/xform/measure.rs @@ -27,7 +27,8 @@ pub fn measure(a: Point, b: Point) -> (f64, f64, f64, f64) { /// it. With Shift held, the end stays on the 45° step from the Constrain Angle nearest the /// pointer's direction: `p` is projected onto the step, then snapped along it as the Line Segment /// tool snaps a Shift-drawn end. With Snap to Point alone, the point `p` snaps to (an anchor, or -/// in line with a ruler guide) is projected instead. +/// in line with a ruler guide) is projected instead. When the snapped point lies nearer another +/// step (the grid rounds it past the halfway angle), it is turned back onto the pointer's step. pub(crate) fn end_point(cx: &ToolContext, a: Point, p: Point, shift: bool) -> Point { if !shift { return crate::guides::snap_draw(cx, p, &[]).0; @@ -42,7 +43,9 @@ pub(crate) fn end_point(cx: &ToolContext, a: Point, p: Point, shift: bool) -> Po let s = if to_point { crate::guides::snap_draw(cx, p, &[]).0 } else { p }; let q = a + u * (s - a).dot(u); let leave = crate::guides::Leave::segment(cx, a, true); - crate::guides::PointSnap::new(cx, || crate::guides::Targets::collect(cx.doc, &[], None)).snap_from(cx, q, Some(&leave)).0 + let r = crate::guides::PointSnap::new(cx, || crate::guides::Targets::collect(cx.doc, &[], None)).snap_from(cx, q, Some(&leave)).0; + let v = r - a; + if v.cross(u).abs() <= 1e-9 * v.hypot().max(1.0) { r } else { a + u * v.hypot() } } impl Tool for MeasureTool { @@ -222,6 +225,27 @@ mod tests { assert!((o["dy"].as_f64().unwrap() + 100.0).abs() < 1e-9, "{o}"); } + /// With a Constrain Angle of 30°, the grid point a Shift-drag's end rounds to can lie nearer + /// another step; the end stays on the step nearest the pointer's direction. + #[test] + fn shift_drag_keeps_its_step_after_the_grid_rounds_it() { + let (d, _) = doc_with_rect(); + let s = Selection::default(); + let p = paint(); + let mut cx = cx(&d, &s, &p); + cx.smart_guides = false; + cx.snap_to_grid = true; + cx.constrain_angle = 30.0; + let shift = Mods { shift: true, ..Mods::default() }; + let mut t = MeasureTool::default(); + // (5, -3) points 31° up, so the step is 30°; the end lands at about (5.05, -2.92), which + // the 9 pt grid rounds to (9, 0), nearer the -15° step. + t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 0.0, 0.0)); + t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 5.0, -3.0).with_mods(shift)); + let o = t.options(); + assert!((o["angle"].as_f64().unwrap() - 30.0).abs() < 1e-9, "{o}"); + } + /// With Snap to Point alone, a Shift-drag's end slides along its 45° step to a ruler guide. #[test] fn shift_drag_meets_a_ruler_guide() {