Measure tool: Shift does not turn the end to the opposite step

With Snap to Point alone, an anchor behind the start could pull the
projected end behind it, and the measurement read 180 degrees from the
pointer's step. Such an end is now turned back onto the pointer's step,
keeping its length, as an end rounded nearer another step is.
This commit is contained in:
dotCooCoo
2026-11-04 08:12:37 +07:00
committed by bflatastic
parent aeafc359ea
commit 9051b70028
+27 -2
View File
@@ -28,7 +28,8 @@ pub fn measure(a: Point, b: Point) -> (f64, f64, f64, f64) {
/// 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. 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.
/// step (the grid rounds it past the halfway angle) or behind the start, 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;
@@ -45,7 +46,7 @@ pub(crate) fn end_point(cx: &ToolContext, a: Point, p: Point, shift: bool) -> Po
let leave = crate::guides::Leave::segment(cx, a, true);
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() }
if v.cross(u).abs() <= 1e-9 * v.hypot().max(1.0) && v.dot(u) >= 0.0 { r } else { a + u * v.hypot() }
}
impl Tool for MeasureTool {
@@ -246,6 +247,30 @@ mod tests {
assert!((o["angle"].as_f64().unwrap() - 30.0).abs() < 1e-9, "{o}");
}
/// With Snap to Point alone, an anchor behind the start can pull the projected end behind it;
/// the end is turned back onto the pointer's step instead of reading 180°.
#[test]
fn shift_drag_keeps_its_step_when_an_anchor_lies_behind_the_start() {
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_tolerance = 4.0;
cx.snapping_tolerance = 4.0;
assert!(cx.snap_to_point);
let shift = Mods { shift: true, ..Mods::default() };
let mut t = MeasureTool::default();
// From (200.3, 196.005), the pointer at (202.3, 196.805) points 0° and snaps to the anchor
// at (200, 200), which projects onto the 0° line at (200, 196.005), behind the start.
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 200.3, 196.005));
t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 202.3, 196.805).with_mods(shift));
let o = t.options();
assert!(o["angle"].as_f64().unwrap().abs() < 1e-9, "{o}");
assert!((o["dx"].as_f64().unwrap() - 0.3).abs() < 1e-9, "{o}");
assert!(o["dy"].as_f64().unwrap().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() {