From b093e53e774aff257187c3564a8679e4e8536bda Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 7 Nov 2026 01:40:28 -0800 Subject: [PATCH] Smart Guides: Snap to Last Location puts a moved selection back where it began Preferences > Smart Guides gets Snap to Last Location (`snapToLastLocation`, off by default). With it and Smart Guides on, a selection moved by dragging with the Selection, Group Selection or Direct Selection tool, Shift too, goes back to where the move began while the move is within Snapping Tolerance, labeled "last location". An Alt copy is not pulled back onto its original. Source: https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/smart-guides-options.html The distance, the label and the copy rule are VectorCraft's own. --- crates/engine/src/cmd/prefscmds.rs | 1 + crates/engine/src/prefs.rs | 2 ++ crates/engine/src/tests/prefs.rs | 31 ++++++++++++++++++++++ crates/engine/src/tooling.rs | 1 + crates/tools/src/context.rs | 3 +++ crates/tools/src/guides/targets.rs | 18 +++++++++++-- crates/tools/src/lib.rs | 1 + crates/tools/src/select.rs | 12 +++++++-- crates/tools/src/select/tests.rs | 41 +++++++++++++++++++++++++++++ crates/ui-egui/src/i18n/bg.tsv | 1 + crates/ui-egui/src/i18n/bs.tsv | 1 + crates/ui-egui/src/i18n/ca.tsv | 1 + crates/ui-egui/src/i18n/cs.tsv | 1 + crates/ui-egui/src/i18n/da.tsv | 1 + crates/ui-egui/src/i18n/de.tsv | 1 + crates/ui-egui/src/i18n/el.tsv | 1 + crates/ui-egui/src/i18n/es.tsv | 1 + crates/ui-egui/src/i18n/fi.tsv | 1 + crates/ui-egui/src/i18n/fr.tsv | 1 + crates/ui-egui/src/i18n/hi.tsv | 1 + crates/ui-egui/src/i18n/hu.tsv | 1 + crates/ui-egui/src/i18n/id.tsv | 1 + crates/ui-egui/src/i18n/it.tsv | 1 + crates/ui-egui/src/i18n/ja.tsv | 1 + crates/ui-egui/src/i18n/ka.tsv | 1 + crates/ui-egui/src/i18n/ko.tsv | 1 + crates/ui-egui/src/i18n/nl.tsv | 1 + crates/ui-egui/src/i18n/pl.tsv | 1 + crates/ui-egui/src/i18n/pt-br.tsv | 1 + crates/ui-egui/src/i18n/ru.tsv | 1 + crates/ui-egui/src/i18n/sv.tsv | 1 + crates/ui-egui/src/i18n/tr.tsv | 1 + crates/ui-egui/src/i18n/uk.tsv | 1 + crates/ui-egui/src/i18n/vi.tsv | 1 + crates/ui-egui/src/i18n/zh-hans.tsv | 1 + crates/ui-egui/src/i18n/zh-hant.tsv | 1 + docs/mcp.md | 4 +++ docs/target-app-parity.md | 1 + 38 files changed, 138 insertions(+), 4 deletions(-) diff --git a/crates/engine/src/cmd/prefscmds.rs b/crates/engine/src/cmd/prefscmds.rs index 94a922400..6bd9c8dc5 100644 --- a/crates/engine/src/cmd/prefscmds.rs +++ b/crates/engine/src/cmd/prefscmds.rs @@ -247,6 +247,7 @@ pub const PREF_SPECS: &[PrefSpec] = &[ p!("measurementLabels", "Smart Guides", "Display Options", "Measurement Labels", bool), p!("spacingGuides", "Smart Guides", "Display Options", "Spacing Guides", bool), p!("distanceGuides", "Smart Guides", "Display Options", "Invoke Distance Guides", bool), + p!("snapToLastLocation", "Smart Guides", "Display Options", "Snap to Last Location", bool), p!("snappingTolerance", "Smart Guides", "", "Snapping Tolerance", num(0.0, 40.0, "pt")), p!("snapTangent", "Smart Guides", "Geometric Guides", "Snap to Tangent", bool), p!("snapPerpendicular", "Smart Guides", "Geometric Guides", "Snap to Perpendicular", bool), diff --git a/crates/engine/src/prefs.rs b/crates/engine/src/prefs.rs index ce7c5e4a6..b49c40786 100644 --- a/crates/engine/src/prefs.rs +++ b/crates/engine/src/prefs.rs @@ -115,6 +115,7 @@ pub struct Prefs { pub measurement_labels: bool, pub spacing_guides: bool, pub distance_guides: bool, + pub snap_to_last_location: bool, pub snapping_tolerance: f64, pub snap_tangent: bool, pub snap_perpendicular: bool, @@ -410,6 +411,7 @@ impl Default for Prefs { measurement_labels: true, spacing_guides: true, distance_guides: true, + snap_to_last_location: false, snapping_tolerance: 4.0, snap_tangent: true, snap_perpendicular: true, diff --git a/crates/engine/src/tests/prefs.rs b/crates/engine/src/tests/prefs.rs index a8617bf82..437701136 100644 --- a/crates/engine/src/tests/prefs.rs +++ b/crates/engine/src/tests/prefs.rs @@ -814,6 +814,37 @@ fn invoke_distance_guides_measures_the_selection_to_the_object_under_alt() { assert!(shown(&mut s, v).is_empty(), "Invoke Distance Guides off"); } +/// Smart Guides › Snap to Last Location (off by default): on, a square dragged 6 pt right and 5 pt +/// down with a 10 px Snapping Tolerance goes back to where it was; dragged 12 pt away, or with the +/// option or View › Smart Guides off, it lands where it is dragged. A value that is not a boolean +/// is refused. +#[test] +fn snap_to_last_location_puts_a_dragged_square_back() { + use vectorcraft_tools::PointerKind::{Down, Drag, Up}; + let mut s = new_doc(); + // Its edges and center stay out of reach of the 400 pt artboard's. + let m = square(&mut s, 100.0, 250.0); + let v = ViewInfo::default(); + s.select_tool("selection", v).unwrap(); + let drag = |s: &mut Session, v: ViewInfo, to: (f64, f64)| { + gesture(s, v, &[(Down, 125.0, 275.0), (Drag, 140.0, 290.0), (Drag, to.0, to.1), (Up, to.0, to.1)]); + let at = top_left(s, m); + if at != (100.0, 250.0) { + s.execute("edit.undo", &json!({})).unwrap(); + } + at + }; + set_pref(&mut s, "snappingTolerance", json!(10)); + assert_eq!(drag(&mut s, v, (131.0, 280.0)), (106.0, 255.0), "off by default"); + set_pref(&mut s, "snapToLastLocation", json!(true)); + assert_eq!(drag(&mut s, v, (131.0, 280.0)), (100.0, 250.0), "7.8 pt away: back where it was"); + assert_eq!(drag(&mut s, v, (134.0, 283.0)), (109.0, 258.0), "12 pt away"); + assert_eq!(drag(&mut s, ViewInfo { smart_guides: false, ..v }, (131.0, 280.0)), (106.0, 255.0), "View › Smart Guides off"); + let refused = s.execute("prefs.set", &json!({"key": "snapToLastLocation", "value": "yes"})); + assert!(refused.is_err(), "{refused:?}"); + assert_eq!(s.execute("prefs.get", &json!({"key": "snapToLastLocation"})).unwrap(), json!(true), "unchanged"); +} + /// Preferences › Smart Guides (#394): Color, Alignment Guides, Anchor/Path Labels, Measurement /// Labels, Transform Tools and Snapping Tolerance change what the Selection tool shows while a /// square is dragged into line with another, and how far the pull reaches; a hidden guide still diff --git a/crates/engine/src/tooling.rs b/crates/engine/src/tooling.rs index 7db168832..1871722c3 100644 --- a/crates/engine/src/tooling.rs +++ b/crates/engine/src/tooling.rs @@ -339,6 +339,7 @@ impl Session { transform_tools_guides: self.prefs.transform_tools_guides, spacing_guides: self.prefs.spacing_guides, distance_guides: self.prefs.distance_guides, + snap_to_last_location: self.prefs.snap_to_last_location, snapping_tolerance: self.prefs.snapping_tolerance, construction_angles: if self.prefs.construction_guides { vectorcraft_tools::guides::construction_angles(&self.prefs.construction_angles) diff --git a/crates/tools/src/context.rs b/crates/tools/src/context.rs index 85b727222..835303b44 100644 --- a/crates/tools/src/context.rs +++ b/crates/tools/src/context.rs @@ -242,6 +242,9 @@ pub struct ToolContext<'a> { /// object shows how far the selection is from it, and over an empty part of an artboard how /// far it is from the artboard's edges ([`guides::distance_guides`]). pub distance_guides: bool, + /// Smart Guides → Snap to Last Location: a selection moved by dragging goes back to where the + /// move began while it is within Snapping Tolerance of it ([`guides::Targets::snap_back`]). + pub snap_to_last_location: bool, /// Smart Guides → Snapping Tolerance (screen pixels): how near a smart guide target pulls the /// pointer, a dragged edge or a drawn point. pub snapping_tolerance: f64, diff --git a/crates/tools/src/guides/targets.rs b/crates/tools/src/guides/targets.rs index 709e1f53d..d553cb0f8 100644 --- a/crates/tools/src/guides/targets.rs +++ b/crates/tools/src/guides/targets.rs @@ -57,6 +57,9 @@ pub struct Targets { pub(super) perpendicular: bool, /// How the guides these targets produce look. pub(super) style: GuideStyle, + /// Smart Guides › Snap to Last Location: a move goes back to where it began when it comes near + /// ([`Self::snap_back`]). Set only for moving the selection itself ([`Self::for_move`]). + pub(super) last_location: bool, } /// How many anchors, and how many segments, the targets gather at most. @@ -163,19 +166,30 @@ impl Targets { /// Targets for moving the selection of `cx`: [`Self::collect`]'s but `exclude` (the selection, /// or nothing for a copy, which leaves it behind), styled as `cx` asks, and with Smart Guides › - /// Spacing Guides the objects it spaces itself evenly with ([`Self::snap_rect`]). + /// Spacing Guides the objects it spaces itself evenly with ([`Self::snap_rect`]). With Smart + /// Guides › Snap to Last Location, a move (not a copy, which has nothing in `exclude`) goes back + /// to where it began when it comes near ([`Self::snap_back`]). pub fn for_move(cx: &ToolContext, exclude: &[NodeId]) -> Self { let mut t = Self::collect(cx.doc, exclude, None).styled(cx); if cx.spacing_guides { t.boxes = object_boxes(cx.doc, &cx.selection.objects); } + t.last_location = cx.snap_to_last_location && !exclude.is_empty(); t } + /// Smart Guides › Snap to Last Location: a move by `d` that is no farther than `tol` from + /// where it began goes back there. Returns the guides for it, a "last location" label at the + /// center of `from` (the moved bounds when the move began), or None when the move stays where + /// it is: the option is off, the targets are for a copy, or `d` is longer than `tol`. + pub fn snap_back(&self, from: Option, d: Vec2, tol: f64) -> Option> { + (self.last_location && d.hypot() <= tol).then(|| from.and_then(|r| self.label(r.center(), "last location")).into_iter().collect()) + } + /// Targets for scaling the selection: the other art, styled as `cx` asks, and with Smart Guides › /// Spacing Guides the neighbours whose gaps the moving edge should match ([`Self::snap_scale`]). pub fn for_scale(cx: &ToolContext) -> Self { - Self::for_move(cx, &cx.selection.objects) + Self { last_location: false, ..Self::for_move(cx, &cx.selection.objects) } } /// Targets for a drawn point: the art in `visible` (all of it for None) but `exclude`, its diff --git a/crates/tools/src/lib.rs b/crates/tools/src/lib.rs index c6883c3fd..f77ba9dbf 100644 --- a/crates/tools/src/lib.rs +++ b/crates/tools/src/lib.rs @@ -532,6 +532,7 @@ pub(crate) mod testutil { transform_tools_guides: true, spacing_guides: true, distance_guides: true, + snap_to_last_location: false, snapping_tolerance: 4.0, construction_angles: guides::construction_angles(guides::DEFAULT_CONSTRUCTION_ANGLES), screen: None, diff --git a/crates/tools/src/select.rs b/crates/tools/src/select.rs index 0f879cb55..6d90e79e3 100644 --- a/crates/tools/src/select.rs +++ b/crates/tools/src/select.rs @@ -231,9 +231,13 @@ impl MoveSnap { } /// The move (`copy`: the copy) by `d` of the selection grabbed at `start`, snapped, and its - /// guides. + /// guides. With Smart Guides › Snap to Last Location, a move within Snapping Tolerance of where + /// it began goes back there before anything else snaps ([`Targets::snap_back`]). pub(crate) fn snap(&self, cx: &ToolContext, start: Point, mut d: Vec2, copy: bool) -> (Vec2, Vec) { let (targets, points) = if copy { (&self.copy_targets, &self.copy_points) } else { (&self.targets, &self.points) }; + if let Some(ov) = targets.as_ref().and_then(|t| t.snap_back(self.bounds, d, cx.snap_tol())) { + return (Vec2::ZERO, ov); + } let mut guides = vec![]; if cx.snap_to_grid && let Some(b) = self.bounds @@ -291,7 +295,8 @@ impl MoveSnap { /// whole pixel on either axis (the one axis of a level or upright line). The bounds line up /// with the other art ([`Targets::snap_rects_along`]) and the grabbed point lands where the line /// passes an anchor or crosses a ruler guide ([`Targets::snap_point_along`]). A move of zero - /// length does not snap. + /// length does not snap, and Snap to Last Location goes back to where the move began first, as + /// in [`Self::snap`]. pub(crate) fn snap_along(&self, cx: &ToolContext, start: Point, mut d: Vec2, copy: bool) -> (Vec2, Vec) { let len = d.hypot(); if !len.is_finite() || len <= 1e-9 { @@ -309,6 +314,9 @@ impl MoveSnap { .unwrap_or(0.0) }; let (targets, points) = if copy { (&self.copy_targets, &self.copy_points) } else { (&self.targets, &self.points) }; + if let Some(ov) = targets.as_ref().and_then(|t| t.snap_back(self.bounds, d, cx.snap_tol())) { + return (Vec2::ZERO, ov); + } let mut guides = vec![]; if cx.snap_to_grid && let Some(b) = self.bounds diff --git a/crates/tools/src/select/tests.rs b/crates/tools/src/select/tests.rs index 94fb67786..efb96ac5d 100644 --- a/crates/tools/src/select/tests.rs +++ b/crates/tools/src/select/tests.rs @@ -782,6 +782,47 @@ fn an_alt_copy_snaps_to_the_original_left_behind() { assert_eq!(dx(&t.pointer(&c, &ev(PointerKind::Drag, 253.0, 150.0))), 103.0); } +/// Smart Guides › Snap to Last Location: a move that comes within Snapping Tolerance of where it +/// began goes back there, labeled "last location", a Shift move too; farther out, with the option +/// or Smart Guides off, it stays where it is dragged, and an Alt copy snaps as it does with the +/// option off. +#[test] +fn snap_to_last_location_puts_a_move_back_where_it_began() { + let (mut d, id) = doc_with_rect(); + // An artboard whose edges and center are out of reach. + d.artboards[0].rect = Rect::new(0.0, 0.0, 1000.0, 1000.0); + let mut s = Selection::default(); + s.add(id); + let p = paint(); + let on = ToolContext { snap_to_last_location: true, snapping_tolerance: 10.0, ..cx(&d, &s, &p) }; + let moved = |c: &ToolContext, to: (f64, f64), m: Mods| { + let mut t = SelectionTool::default(); + t.pointer(c, &ev(PointerKind::Down, 150.0, 150.0).with_mods(m)); + let a = t.pointer(c, &ev(PointerKind::Drag, to.0, to.1).with_mods(m)); + let Some(Action::Preview(_, v)) = a.last() else { panic!("{a:?}") }; + let labels: Vec = + t.overlays(c).into_iter().filter_map(|o| if let Overlay::Label { text, .. } = o { Some(text) } else { None }).collect(); + (Vec2::new(v["matrix"][4].as_f64().unwrap(), v["matrix"][5].as_f64().unwrap()), labels) + }; + let none = Mods::default(); + let (back, labels) = moved(&on, (156.0, 155.0), none); + assert!(moved_by(back, (0.0, 0.0)), "{back:?}: 7.8 pt away, within 10"); + assert_eq!(labels, ["last location"]); + let shift = Mods { shift: true, ..none }; + assert!(moved_by(moved(&on, (157.0, 150.0), shift).0, (0.0, 0.0)), "a Shift move goes back too"); + let (out, labels) = moved(&on, (159.0, 158.0), none); + assert!(moved_by(out, (9.0, 8.0)), "{out:?}: 12 pt away, out of reach"); + assert!(labels.is_empty(), "{labels:?}"); + let off = ToolContext { snap_to_last_location: false, ..on }; + assert!(moved_by(moved(&off, (156.0, 155.0), none).0, (6.0, 5.0)), "the option off"); + let no_guides = ToolContext { smart_guides: false, ..on }; + assert!(moved_by(moved(&no_guides, (156.0, 155.0), none).0, (6.0, 5.0)), "Smart Guides off"); + // An Alt copy lines up with the original it leaves behind, as it does with the option off. + let alt = Mods { alt: true, ..none }; + let (copy, labels) = moved(&on, (156.0, 155.0), alt); + assert_eq!((copy, labels.contains(&"last location".to_string())), (moved(&off, (156.0, 155.0), alt).0, false), "{labels:?}"); +} + /// A move shows the filled arrow, hollow while it snaps (here B's left side onto A's right /// one); Alt, which leaves a copy behind, the double arrowhead. #[test] diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 846ef715e..131c6f8b6 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -3166,6 +3166,7 @@ digits Malayalam Малаялам Spaces Интервали Spacing Guides Водачи за разстояние Invoke Distance Guides Извикване на водачи за отстояния + Snap to Last Location Прилепване към последното местоположение Spacing: Разстояние: Spanish Испански Specified Distance Упоменато разстояние diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index e8deb527d..f78642c10 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -3654,6 +3654,7 @@ theme Appearance Izgled Snapping Tolerance Tolerancija hvatanja Spacing Guides Vodilice razmaka Invoke Distance Guides Pozovi vodiče udaljenosti + Snap to Last Location Prilijepi uz posljednju lokaciju Spanish Španski Subdivisions Podjele Templates Folder Folder predložaka diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index c7b7d2336..b85cafc2c 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -2032,6 +2032,7 @@ digits Malayalam Malaiàlam Invert Pie Inverteix el diagrama circular Inverted Round Rodó invertit Invoke Distance Guides Invoca les guies de distància + Snap to Last Location Ajusta a l’última ubicació Isolate Aïlla Isolate Blending Aïlla la fusió Isolate Group Aïlla el grup diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 559e7c8be..55876eaa3 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -3695,6 +3695,7 @@ theme Appearance Motiv Snapping Tolerance Tolerance přichytávání Spacing Guides Vodítka rozestupů Invoke Distance Guides Vyvolat vodítka vzdálenosti + Snap to Last Location Přichytávat k poslednímu umístění Spanish Španělština Subdivisions Dělení Templates Folder Složka šablon diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index a279b4254..654354f44 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -3423,6 +3423,7 @@ theme Appearance Udseende Snapping Tolerance Snap-tolerance Spacing Guides Afstandshjælpelinjer Invoke Distance Guides Aktivér hjælpelinjer for afstandsmåling + Snap to Last Location Fastgør til sidste placering Spanish Spansk Subdivisions Underinddelinger Templates Folder Skabelonmappe diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 11897c2cc..931623a77 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -3211,6 +3211,7 @@ effect Points Punkte Spaces Leerzeichen Spacing Guides Abstandshilfslinien Invoke Distance Guides Distanzhilfslinien aufrufen + Snap to Last Location An letzter Position ausrichten Spacing: Abstand: Spanish Spanisch Specified Distance Festgelegter Abstand diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 91dcff85f..bfb378a32 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -3634,6 +3634,7 @@ digits Malayalam Μαλαγιάλαμ Spaces Διαστήματα Spacing Guides Οδηγοί αποστάσεων Invoke Distance Guides Εμφάνιση οδηγών μέτρησης απόστασης + Snap to Last Location Προσκόλληση στην τελευταία θέση Spanish Ισπανικά Specified Distance Καθορισμένη απόσταση Specified Steps Καθορισμένα βήματα diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 082826c39..daf3d6e84 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -3250,6 +3250,7 @@ digits Malayalam Malayálam Spaces Espacios Spacing Guides Guías de espaciado Invoke Distance Guides Invocar guías de distancia + Snap to Last Location Ajustar a última ubicación Spacing: Espaciado: Spanish Español Specified Distance Distancia especificada diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index b518868f6..e4b8ff4ba 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -3619,6 +3619,7 @@ theme Appearance Ulkoasu Snapping Tolerance Tartuntatoleranssi Spacing Guides Välistysapuviivat Invoke Distance Guides Käytä etäisyysapuviivoja + Snap to Last Location Kohdista viimeiseen sijaintiin Spanish Espanja Subdivisions Jaot Templates Folder Mallipohjakansio diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 5b983385c..9613bdea3 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -3242,6 +3242,7 @@ digits Malayalam Malayalam Spaces Espaces Spacing Guides Repères d’espacement Invoke Distance Guides Appeler les repères de distance + Snap to Last Location Magnétisme du dernier emplacement Spacing: Espacement : Spanish Espagnol Specified Distance Distance définie diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index f4041d65d..71f483518 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -2251,6 +2251,7 @@ Invert Pie पाई उलटें Inverted Round उल्टे दौर Invoke Distance Guides दूरी गाइड्स दिखाएँ + Snap to Last Location अंतिम स्थान पर स्नैप करें Isolate पृथक Isolate Blending ब्लेंडिंग अलग करें Isolate Group अलग समूह diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index a0c164c54..94ab1f8c1 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -3885,6 +3885,7 @@ mark-style Japanese Japán Spacing Térköz Spacing Guides Térköz-segédvonalak Invoke Distance Guides Távolság-segédvonalak előhívása + Snap to Last Location Illesztés az utolsó helyre Spacing: Térköz: Spanish Spanyol Spatter Fröcskölés diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index d08aef173..3ed6d7b14 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -3423,6 +3423,7 @@ theme Appearance Tampilan Snapping Tolerance Toleransi kancing Spacing Guides Panduan jarak Invoke Distance Guides Panggil panduan pengukuran jarak + Snap to Last Location Snap ke lokasi terakhir Spanish Spanyol Subdivisions Subdivisi Templates Folder Folder templat diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 6e26739b5..c0bb946a0 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -3286,6 +3286,7 @@ digits Malayalam Malayalam Spaces Spazi Spacing Guides Guide di spaziatura Invoke Distance Guides Richiama guide di distanza + Snap to Last Location Aggancia all'ultima posizione Spacing: Spaziatura: Spanish Spagnolo Specified Distance Distanza specificata diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 1304256f8..aedfbaa52 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -3068,6 +3068,7 @@ mark-style Japanese 日本式 Spaces スペース Spacing Guides 間隔ガイド Invoke Distance Guides 距離ガイドを呼び出す + Snap to Last Location 最後の位置にスナップ Spacing: 間隔: Spanish スペイン語 Specify: 指定: diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 8af691caa..38f897c2e 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -3240,6 +3240,7 @@ theme Appearance იერსახე Snapping Tolerance მიბმის დაშვება Spacing Guides შუალედის დამხმარე ხაზები Invoke Distance Guides მანძილის დამხმარე ხაზების გამოძახება + Snap to Last Location ბოლო მდებარეობაზე მიბმა Spanish ესპანური Subdivisions ქვედანაყოფები Templates Folder შაბლონების საქაღალდე diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index f1dbfaadc..c30d06632 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -3057,6 +3057,7 @@ mark-style Japanese 일본식 Spaces 공백 Spacing Guides 간격 안내선 Invoke Distance Guides 거리 안내선 호출 + Snap to Last Location 마지막 위치에 스냅 Spacing: 간격: Spanish Español Specify: 지정: diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 3c9f7b9cf..b82336ceb 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -3437,6 +3437,7 @@ theme Appearance Vormgeving Snapping Tolerance Snapping-tolerantie Spacing Guides Hulplijnen voor tussenruimte Invoke Distance Guides Afstandshulplijnen oproepen + Snap to Last Location Uitlijnen op laatste locatie Spanish Spaans Subdivisions Onderverdelingen Templates Folder Sjablonenmap diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 1bc9d7c51..66c0e8bd9 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -3558,6 +3558,7 @@ theme Appearance Wygląd Snapping Tolerance Tolerancja przyciągania Spacing Guides Linie odstępów Invoke Distance Guides Wywołuj linie odległości + Snap to Last Location Przyciągaj do ostatniego położenia Spanish Hiszpański Subdivisions Podpodziały Templates Folder Folder szablonów diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index db631ddfd..8d762e6a7 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -2713,6 +2713,7 @@ digits Malayalam Malaiala Spaces Espaços Spacing Guides Guias de Espaçamento Invoke Distance Guides Invocar Guias de Distância + Snap to Last Location Ajustar ao Último Local Spacing: Espaçamento: Spanish Espanhol Specify: Especificar: diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 1cbfb62a2..e92a1f735 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -3281,6 +3281,7 @@ digits Malayalam Малаялам Spaces Пробелы Spacing Guides Направляющие интервалов Invoke Distance Guides Вызывать направляющие расстояний + Snap to Last Location Привязать к последнему положению Spacing: Интервал: Spanish Испанский Specified Distance Заданное расстояние diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index fb7afaa9f..ec3c3212c 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -3172,6 +3172,7 @@ digits Malayalam Malayalam Spaces Blanksteg Spacing Guides Avståndsstödlinjer Invoke Distance Guides Anropa mätstödlinjer för avstånd + Snap to Last Location Fäst mot senaste plats Spacing: Avstånd: Spanish Spanska Specified Distance Angivet avstånd diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 84e5a886b..013843f20 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -3074,6 +3074,7 @@ digits Malayalam Malayalam Spaces Alanlar Spacing Guides Aralık Kılavuzları Invoke Distance Guides Mesafe Kılavuzlarını Çağır + Snap to Last Location Son Konuma Yasla Spacing: Aralık: Spanish İspanyolca Specified Distance Belirtilen Mesafe diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 58abaae18..1d94e3e72 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -3279,6 +3279,7 @@ effect Points Точки Spaces Пробіли Spacing Guides Напрямні інтервалів Invoke Distance Guides Викликати напрямні відстаней + Snap to Last Location Прив’язати до останнього розташування Spacing: Інтервал: Spanish Іспанська Specified Distance Задана відстань diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index bdee09c89..b5b998423 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -3613,6 +3613,7 @@ mark-style Japanese Kiểu Nhật Spacing Khoảng cách Spacing Guides Đường căn khoảng cách Invoke Distance Guides Gọi đường căn đo khoảng cách + Snap to Last Location Hút vào vị trí cuối cùng Spacing: Khoảng cách: Spanish Tiếng Tây Ban Nha Spatter Bắn tung tóe diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 3979c9e85..5f5ef8a0e 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -3119,6 +3119,7 @@ digits Malayalam 马拉雅拉姆数字 Spaces 空格 Spacing Guides 间距参考线 Invoke Distance Guides 调用距离参考线 + Snap to Last Location 对齐上一位置 Spacing: 间距: Spanish 西班牙语 Specified Distance 指定距离 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 259e0c0f7..1759c5b49 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -4148,6 +4148,7 @@ digits Malayalam 馬來亞拉姆數字 Spaces 空格 Spacing Guides 間距參考線 Invoke Distance Guides 叫用距離參考線 + Snap to Last Location 靠齊上一個位置 Spacing: 間距: Spanish 西班牙文 Specified Distance 指定距離 diff --git a/docs/mcp.md b/docs/mcp.md index abb1c5902..ae113e774 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -3657,6 +3657,10 @@ Guides change where a snapped point lands: object under the pointer (the gap between their facing edges on an axis on which they lie apart, else the offsets of their two edges on it) or, over no object, to the left, right, top and bottom edges of the artboard under the pointer: measured lines labelled in the General unit. Off, Alt-hovering shows none. +- `snapToLastLocation` (Snap to Last Location, off by default): with Smart Guides on, a selection moved by dragging + (Selection, Group Selection or Direct Selection tool / `pointer_gesture` with `selection`, Shift too) goes back to + where the move began while the move is no longer than `snappingTolerance`, labeled "last location"; farther, it + snaps as before. An Alt copy is not pulled back onto its original. A value that is not a boolean is refused. The Snap to Glyph options (the desktop app's Character panel menu › Show Snap to Glyph Options; View › Snap to Glyph is the desktop app's): `view.snapToGlyph.options {baseline?, xHeight?, bounds?, proximity?, angular?, anchors?}` (or diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index c80550264..816dac6bb 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -914,6 +914,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - **Drawing aids:** Smart Guides and snapping (moving, with the Selection, Group Selection or Direct Selection tool, and resizing by the bounding-box handles with the Selection or Free Transform tool, Shift and Alt too: the dragged corner or side lands on the other objects' anchors and centres ("anchor", "center") or lines up with their edges and centres (a line and "align"); with Smart Guides › Spacing Guides, a moved selection that lines up with nothing along an axis lands as far from its neighbour as two other objects of its row or column are apart, or halfway between its two neighbours, the equal gaps marked; with View › Snap to Grid on, a moved selection lands on the grid instead, by whichever of its edges is nearest a gridline on each axis (the center does not snap), #740, #1816; the Rotate, Scale, Shear and Reflect tools drag from the anchor they grab and snap the pointer the same way, so a corner dragged onto another object's anchor scales exactly onto it, #482; anchors dragged with Direct Selection: the one pressed on lands on other anchors, its own path's that stay put among them, on path segments or in line with them, the others following, Shift keeps 45° steps, and with Smart Guides off Snap to Point pulls it onto anchors; the Artboard tool snaps artboards, and their bleed, to other artboards, bleeds and art, the grid or whole pixels while moving, copying and resizing them, and the Transform panel shows and edits the active artboard's X/Y/W/H; drawn points, #506: hovering shows where the press goes, the point lands on the other objects' anchors, centres and paths ("anchor", "center", "path") or lines up with their anchors, edges and centres ("align"), the Pen's on the path being drawn too, near a construction guide through the anchor before it on that guide (Preferences › Smart Guides › Construction Guides and Angles), and with Shift kept to its 45° step or a square's diagonal it slides along that way into line; the targets are gathered once per document state, from the art in the window, and the anchors, centres and lines near the pointer are found by bisection, so hovering never rescans the document), guides dragged out of the rulers (#451: they snap as moved guides do, Shift to the ruler's ticks; with the Artboard tool they are artboard guides, which run across their artboard only and move, copy and delete with it, `guide.add {artboard}`) that the Selection, Direct Selection and Group Selection tools then pick and drag (#414: the selected guides highlight and move together, Alt copies them, Shift snaps the dragged one to the ticks, otherwise it snaps to pixels, the grid or the edges, centres (so the sides' midpoints) and anchors of the art and the artboards, or with Smart Guides off Snap to Point pulls it onto anchors; Shift-clicked they join the selected art, and Shift-added art keeps them, so guides and art are selected together in either order, Delete then deleting both in one step; the arrow keys nudge them, Delete deletes them, dragging one back onto its ruler deletes it, Lock Guides and Hide Guides leave them alone; `guide.list`, `guide.select`, `guide.move` and `guide.remove` reach agents), a right-click on either ruler or the origin box to pick the document's units (the current one checked; `document.setUnits`, one undo step, #528), and Draw Normal / Behind / Inside modes. Smart Guides: a point drawn with the Pen or a shape tool, and an anchor dragged with Direct Selection, snaps to where paths and ruler guides cross, labelled "intersect" (#1658, part of #1602; a moved selection's own anchors don't). A move that Shift keeps level, upright or diagonal stays on that line when it snaps, with the Selection, Group Selection or Direct Selection tool. It slides along the line until the selection lands on the grid or on whole pixels, lines up with the other objects or is spaced evenly with them, or until the line passes an anchor or crosses a ruler guide near the point the selection was grabbed by. A level or upright move snaps along its own axis only. Distance Guides: with Smart Guides on (View › Smart Guides and Preferences › Smart Guides › Invoke Distance Guides, on by default), something selected and the Selection tool, holding Alt/Option over another object shows the distances between the selection's bounds and that object's: on an axis on which they lie apart, one line across the gap between their facing edges; on one on which they overlap, the offsets of their left and right (top and bottom) edges, each line running across the middle of where they overlap, with a dashed line out to the other object's edge when it passes beside it. Over an empty part of an artboard it shows the selection's distances from the artboard's left, right, top and bottom edges. Each line has ticks at its ends and its length in a pill of the smart guide colour, in the General unit; letting Alt go, pressing (a pan or zoom press too), moving off them, leaving the canvas or a floating panel, menu or dialog over the pointer takes them away, and with the pointer still they follow what lies under it as the view scrolls, zooms or turns, as the document, the tab or the selection changes, and when the Selection tool is switched to. Behaviour from the public [Use Distance Guides for accurate placement](https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/use-distance-guides-for-accurate-placement.html) and [Smart Guides options](https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/smart-guides-options.html) pages, which describe hovering only: nothing shows while moving or nudging. Tests: `select::tests::alt_over_*`, `select::tests::distance_guides_need_alt_a_selection_and_smart_guides`, `tests_prefs::invoke_distance_guides_measures_the_selection_to_the_object_under_alt`, `canvas::tests::alt_over_another_object_draws_the_distance_guide_label`, `canvas::tests::distance_guides_go_when_the_hover_ends`, `canvas::tests::distance_guides_follow_what_lies_under_a_still_pointer`. + Snap to Last Location: with Smart Guides on and Preferences › Smart Guides › Snap to Last Location on (off by default, VectorCraft's own choice), a selection moved by dragging with the Selection, Group Selection or Direct Selection tool, Shift too, goes back to where the move began while it is within Snapping Tolerance of it, labeled "last location"; an Alt copy is not pulled back onto its original (`prefs.set` key `snapToLastLocation`). The public [Smart Guides options](https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/smart-guides-options.html) page lists the option as snapping objects back to their previous position; the distance, the label and the copy rule are VectorCraft's own. Tests: `select::tests::snap_to_last_location_puts_a_move_back_where_it_began`, `tests_prefs::snap_to_last_location_puts_a_dragged_square_back`. A move pressed on a path's stroke, or within 6 px of its outline away from its anchors and center, is held by the nearest point of that path, with the Selection, Group Selection or Direct Selection tool, and a straight segment Direct Selection drags is held by its own nearest point. That point is what snaps, so the path lands exactly on a ruler guide (with "align") instead of the pressed spot landing there and the path a few pixels beside it, an offset that showed only zoomed far in (#3701). A press in the middle of a fill still snaps the pressed spot: the public [Align objects with guides](https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/align-graphic-objects-with-guides.html) page has Snap to Point align the point under the pointer, which is placed exactly where the object should meet the guide. A bounding-box handle dragged with the Selection or Free Transform tool snaps what it moves as a dragged point snaps (#2054). With View › Snap to Grid on, it snaps to the nearest gridline, and with Snap to Pixel on, it snaps to whole pixels. With Smart Guides off and Snap to Point on, it snaps to the other objects' anchors and the ruler guides within the Snap to Point distance. With Smart Guides and Snap to Point both on, it lines up with the ruler guides as well as the other objects. Shift still keeps the proportions, and Alt still scales about the center. A user slice's edge or corner handle dragged with the Slice Selection tool snaps as the Slice tool's drag does. With View › Snap to Grid on, it snaps to the nearest gridline, and with Snap to Pixel on, it snaps to whole pixels. Otherwise, with Smart Guides on, it lands on or lines up with the anchors, centers and edges of the art and the artboards, and with Smart Guides off and Snap to Point on, it snaps to the art's anchors and the ruler guides within the Snap to Point distance. Then a ruler guide within 5 px, shown and unlocked, moves the side or corner onto it. Shift still keeps the proportions, and Alt still resizes about the center.