Dimension tool: show where a Linear click snaps before the click (#2516)

The snap ring on an anchor (or a dot on a line) and the rubber band now follow the snapped point, so both measured points visibly land on anchors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
vangel76
2026-10-17 13:20:26 +07:00
committed by bflatastic
co-authored by Claude Opus 5.5
parent 848e7a296a
commit 4dcd0b6caa
2 changed files with 61 additions and 12 deletions
+31
View File
@@ -421,3 +421,34 @@ fn clicking_near_a_line_snaps_the_measured_point_onto_it() {
let id = s.doc().unwrap().selection.objects[0].0;
assert_eq!(text(&s, id), "20.00 pt");
}
/// Both picked points snap onto anchors a few pixels away, the rubber band and a ring show the
/// snap before the click, and the dimension stays linked to the anchors (#2516).
#[test]
fn both_points_snap_onto_anchors_with_a_visible_indicator() {
use vectorcraft_engine::tools::Overlay;
let mut s = session();
let line = edge(&mut s);
s.select_tool("dimension", ViewInfo::default()).unwrap();
ev(&mut s, PointerKind::Move, 103.0, 102.0);
let ring = |s: &mut Session| {
s.overlays(ViewInfo::default())
.into_iter()
.any(|o| matches!(o, Overlay::Anchor { p, size, .. } if size > 8.0 && p.x == 100.0 && p.y == 100.0))
};
assert!(ring(&mut s), "ring on the anchor before the first click");
ev(&mut s, PointerKind::Down, 103.0, 102.0);
ev(&mut s, PointerKind::Up, 103.0, 102.0);
ev(&mut s, PointerKind::Move, 169.0, 98.0);
let end = s.overlays(ViewInfo::default()).into_iter().find_map(|o| if let Overlay::Line { b, .. } = o { Some(b) } else { None }).unwrap();
assert_eq!((end.x, end.y), (172.0, 100.0), "rubber band ends on the anchor");
ev(&mut s, PointerKind::Down, 169.0, 98.0);
ev(&mut s, PointerKind::Up, 169.0, 98.0);
ev(&mut s, PointerKind::Down, 136.0, 130.0);
ev(&mut s, PointerKind::Up, 136.0, 130.0);
let id = s.doc().unwrap().selection.objects[0].0;
assert_eq!(text(&s, id), "72.00 pt");
run(&mut s, "select.set", json!({"ids":[line]}));
run(&mut s, "object.scale", json!({"sx":200,"sy":100}));
assert_eq!(text(&s, id), "144.00 pt", "linked to both anchors");
}
+30 -12
View File
@@ -133,6 +133,23 @@ fn nearest_on(cx: &ToolContext, edge: &SegmentRef, p: Point) -> Option<Point> {
(q.x.is_finite() && q.y.is_finite()).then_some(q)
}
/// Where a Linear pick at `p` lands: on an anchor within reach (kept as a link to its path),
/// else on the closest point of the picked line, else on the usual snap targets or `p` itself.
fn snap_linear(cx: &ToolContext, p: Point) -> (Point, Option<PointRef>) {
let (mut at, _) = crate::guides::snap_pick(cx, p);
let mut reference = None;
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;
}
}
(at, reference)
}
impl DimensionTool {
fn target(&self, cx: &ToolContext, p: Point) -> Option<Target> {
let edge = pick(cx, p)?;
@@ -228,17 +245,7 @@ impl DimensionTool {
fn click(&mut self, cx: &ToolContext, p: Point, target: Option<Target>, mods: Mods) -> Vec<Action> {
match self.kind {
Kind::Linear => {
let (mut at, _) = crate::guides::snap_pick(cx, p);
let mut reference = None;
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;
}
}
let (mut at, mut reference) = snap_linear(cx, p);
if mods.shift
&& let Some((last, _)) = self.points.last()
{
@@ -418,8 +425,19 @@ impl Tool for DimensionTool {
for edge in &self.lines {
segment(edge, &mut out);
}
// Linear: show where the next click lands (anchor ring, or a dot on a line), and draw the
// rubber band to that point, so the user sees the snap before clicking (#2516).
let linear_pick = self.kind == Kind::Linear && self.pending.is_none() && self.lines.is_empty();
let snapped = linear_pick.then(|| snap_linear(cx, self.at));
if let Some((q, reference)) = &snapped
&& (reference.is_some() || q.distance(self.at) > 1e-9)
{
let on_anchor = reference.is_some();
out.push(Overlay::Anchor { p: *q, color, filled: !on_anchor, size: if on_anchor { 10.0 } else { 6.0 } });
}
if let Some((p, _)) = self.points.last() {
let b = if self.shift { constrained(cx, *p, self.at) } else { self.at };
let target = snapped.map_or(self.at, |(q, _)| q);
let b = if self.shift { constrained(cx, *p, target) } else { target };
out.push(Overlay::Line { a: *p, b, color, dashed: true });
}
out