Measure tool: Shift keeps the end on the pointer's step after the grid rounds it

With a Constrain Angle other than 0, the grid point a Shift-drag's end
rounds to can lie nearer another 45-degree step, and the measurement then
reported that step. A Shift-drag from (0, 0) to (5, -3) with a 30-degree
Constrain Angle and a 9 pt grid measured -15 degrees instead of 30. The
end is now turned back onto the step nearest the pointer's direction,
keeping its length.
This commit is contained in:
dotCooCoo
2026-11-04 08:12:37 +07:00
committed by bflatastic
parent 4c0e0b959a
commit aeafc359ea
+26 -2
View File
@@ -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() {