diff --git a/crates/engine/tests/dimensions.rs b/crates/engine/tests/dimensions.rs index 83e7be165..05915505c 100644 --- a/crates/engine/tests/dimensions.rs +++ b/crates/engine/tests/dimensions.rs @@ -407,3 +407,17 @@ fn shift_shows_the_constrained_line_while_picking_the_second_point() { assert_eq!(end(&mut s, false).y, 310.0); assert!((end(&mut s, true).y - 300.0).abs() < 1e-9); } + +/// A click near a line, away from its anchors, snaps to the closest point of the line (#2432). +#[test] +fn clicking_near_a_line_snaps_the_measured_point_onto_it() { + let mut s = session(); + edge(&mut s); + s.select_tool("dimension", ViewInfo::default()).unwrap(); + for (x, y) in [(130.0, 103.0), (150.0, 97.0), (140.0, 130.0)] { + ev(&mut s, PointerKind::Down, x, y); + ev(&mut s, PointerKind::Up, x, y); + } + let id = s.doc().unwrap().selection.objects[0].0; + assert_eq!(text(&s, id), "20.00 pt"); +} diff --git a/crates/tools/src/dimension.rs b/crates/tools/src/dimension.rs index 9fd871c70..2cd39dfc6 100644 --- a/crates/tools/src/dimension.rs +++ b/crates/tools/src/dimension.rs @@ -119,6 +119,20 @@ fn anchor_near(cx: &ToolContext, edge: &SegmentRef, p: Point) -> Option<(usize, .min_by(|(_, a), (_, b)| a.distance(p).total_cmp(&b.distance(p))) } +/// The point of `edge` closest to `p`. +fn nearest_on(cx: &ToolContext, edge: &SegmentRef, p: Point) -> Option { + use vectorcraft_geom::kurbo::ParamCurveNearest; + let sp = cx.doc.node(edge.node)?.path_data()?.subpaths.get(edge.subpath)?; + if edge.segment >= sp.segment_count() { + return None; + } + let c = sp.segment(edge.segment); + let t = c.nearest(p, 1e-4).t; + use vectorcraft_geom::kurbo::ParamCurve; + let q = c.eval(t); + (q.x.is_finite() && q.y.is_finite()).then_some(q) +} + impl DimensionTool { fn target(&self, cx: &ToolContext, p: Point) -> Option { let edge = pick(cx, p)?; @@ -216,11 +230,14 @@ impl DimensionTool { Kind::Linear => { let (mut at, _) = crate::guides::snap_pick(cx, p); let mut reference = None; - if let Some(edge) = pick(cx, p) - && let Some((anchor, point)) = anchor_near(cx, &edge, p) - { - at = point; - reference = Some(PointRef { node: edge.node, subpath: edge.subpath, anchor }); + if let Some(edge) = pick(cx, p) { + if let Some((anchor, point)) = anchor_near(cx, &edge, p) { + at = point; + reference = Some(PointRef { node: edge.node, subpath: edge.subpath, anchor }); + } else if let Some(point) = nearest_on(cx, &edge, p) { + // Not on an anchor: snap to the closest point of the line (#2432). + at = point; + } } if mods.shift && let Some((last, _)) = self.points.last()