diff --git a/crates/tools/src/direct.rs b/crates/tools/src/direct.rs index cf17647c..9020413f 100644 --- a/crates/tools/src/direct.rs +++ b/crates/tools/src/direct.rs @@ -32,7 +32,7 @@ use vectorcraft_geom::{Anchor, PathData, Point, Rect}; use crate::bbox::move_delta; use crate::corners::{self, CornerDrag, over_widget}; -use crate::guides::{HandleSnap, PointSnap, Targets}; +use crate::guides::{HandleSnap, Leave, PointSnap, Targets}; use crate::meshedit::MeshEdit; use crate::pathtype::{self, BracketDrag, over_bracket}; use crate::rulerguide::GuideEdit; @@ -428,12 +428,12 @@ impl Tool for DirectSelectionTool { self.anchor_snap = Some(PointSnap::new(cx, || Targets::for_anchor_drag(cx.doc, cx.selection))); } let mut d = move_delta(start, p, ev.mods.shift); - // The grabbed anchor snaps and the others follow it; Shift keeps the angle instead. + // The grabbed anchor snaps and the others follow it. Shift keeps the move at its + // angle, and the anchor slides along it onto what Smart Guides find there (#886). self.guides.clear(); - if !ev.mods.shift - && let Some(snap) = &self.anchor_snap - { - let (q, guides) = snap.snap(cx, grab + d); + if let Some(snap) = &self.anchor_snap { + let shift = ev.mods.shift.then(|| Leave::segment(cx, grab, true)); + let (q, guides) = snap.snap_from(cx, grab + (p - start), shift.as_ref()); (d, self.guides) = (q - grab, guides); } out.push(Action::Preview("path.moveAnchors".into(), json!({"dx": d.x, "dy": d.y}))); @@ -1022,10 +1022,16 @@ mod tests { // In line with B's own bottom-left corner, which stays put. let (v, labels) = drag(&mut t, &c, (300.0, 300.0), (303.0, 330.0), none); assert_eq!((v, labels), (json!({"dx": 0.0, "dy": 30.0}), vec!["align".to_string()])); - // Shift keeps the move at 45° steps, unsnapped. - let (v, labels) = drag(&mut t, &c, (300.0, 300.0), (203.0, 199.0), Mods { shift: true, ..none }); - let s = move_delta(Point::new(300.0, 300.0), Point::new(203.0, 199.0), true); - assert_eq!((v, labels), (json!({"dx": s.x, "dy": s.y}), vec![])); + // Shift keeps the move at 45° steps and still snaps along that line (#886): on the + // diagonal from B's corner, onto A's bottom-right corner… + let shift = Mods { shift: true, ..none }; + let (v, labels) = drag(&mut t, &c, (300.0, 300.0), (203.0, 199.0), shift); + let near = |v: &serde_json::Value, (x, y): (f64, f64)| (v["dx"].as_f64().unwrap() - x).hypot(v["dy"].as_f64().unwrap() - y) < 1e-9; + assert!(near(&v, (-100.0, -100.0)) && labels == ["anchor"], "{v} {labels:?}"); + // …and with nothing in reach, at the pointer's distance along its angle. + let (v, labels) = drag(&mut t, &c, (300.0, 300.0), (380.0, 302.0), shift); + let s = move_delta(Point::new(300.0, 300.0), Point::new(380.0, 302.0), true); + assert!(near(&v, (s.x, s.y)) && labels.is_empty(), "{v} {labels:?}"); // Two anchors: the one pressed on (B's top-right) lands on A's corner. let two = anchors_of(b, &[(0, 0), (0, 1)]); let c = cx(&d, &two, &p);