mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-07 12:13:19 +00:00
Merge pull request #5947 from mockydevs/fix/grid-snap-anchors
Direct Selection: with Snap to Grid, a dragged straight segment lands its anchors on the grid
This commit is contained in:
@@ -67,12 +67,14 @@ use crate::{Action, Cursor, Mods, Overlay, PointerEvent, PointerKind, Tool, Tool
|
||||
enum State {
|
||||
Idle,
|
||||
/// Dragging the selected anchors, which move alike: `grab` is where the anchor pressed on (or
|
||||
/// the point of a straight segment nearest the pointer pressed on it) was, the point that
|
||||
/// snaps.
|
||||
/// the point of a straight segment nearest the pointer pressed on it, `segment`) was, the
|
||||
/// point that snaps. With Snap to Grid or Snap to Pixel, a segment's drag snaps its anchors
|
||||
/// instead (#3835).
|
||||
MoveAnchors {
|
||||
start: Point,
|
||||
grab: Point,
|
||||
began: bool,
|
||||
segment: bool,
|
||||
},
|
||||
/// Moving (Alt: copying) the selected objects; `grab`: the anchor an Alt-press picked the
|
||||
/// path by, which snaps as a dragged anchor does; else `hold`, where the press took hold of
|
||||
@@ -849,7 +851,9 @@ impl Tool for DirectSelectionTool {
|
||||
self.state = State::Marquee { start: p, cur: p, toggle: ev.mods.shift, enclosed: false, whole: ev.mods.alt };
|
||||
vec![]
|
||||
}
|
||||
(PointerKind::Drag, State::MoveAnchors { start, grab, began }) => self.drag_move_anchors(cx, ev, p, (start, grab, began)),
|
||||
(PointerKind::Drag, State::MoveAnchors { start, grab, began, segment }) => {
|
||||
self.drag_move_anchors(cx, ev, p, (start, grab, began, segment))
|
||||
}
|
||||
(PointerKind::Drag, State::Segment { id, si, seg, t, start, began }) => {
|
||||
let mut out = vec![];
|
||||
if !began {
|
||||
@@ -1288,7 +1292,7 @@ impl DirectSelectionTool {
|
||||
self.state = if already && cx.selection.anchors.is_empty() {
|
||||
State::MoveObject { start: p, grab: Some(grab), hold: grab, began: false, deselect: None }
|
||||
} else {
|
||||
State::MoveAnchors { start: p, grab, began: false }
|
||||
State::MoveAnchors { start: p, grab, began: false, segment: false }
|
||||
};
|
||||
if ev.mods.shift {
|
||||
return vec![Action::Exec("select.anchors".into(), json!({"id": id.0, "anchors": [[si, ai]], "mode": "toggle"}))];
|
||||
@@ -1354,7 +1358,7 @@ impl DirectSelectionTool {
|
||||
} else {
|
||||
// Held by the segment itself, so the segment is what lands on a
|
||||
// ruler guide or lines up, not the pressed spot beside it (#3701).
|
||||
State::MoveAnchors { start: p, grab: hold_point(cx, &h, p), began: false }
|
||||
State::MoveAnchors { start: p, grab: hold_point(cx, &h, p), began: false, segment: true }
|
||||
};
|
||||
if ev.mods.shift {
|
||||
return vec![Action::Exec("select.segments".into(), json!({"id": h.leaf.0, "segments": [[s.si, s.seg]], "mode": "toggle"}))];
|
||||
@@ -1381,23 +1385,36 @@ impl DirectSelectionTool {
|
||||
}
|
||||
vec![]
|
||||
}
|
||||
fn drag_move_anchors(&mut self, cx: &ToolContext, ev: &PointerEvent, p: Point, (start, grab, began): (Point, Point, bool)) -> Vec<Action> {
|
||||
fn drag_move_anchors(
|
||||
&mut self,
|
||||
cx: &ToolContext,
|
||||
ev: &PointerEvent,
|
||||
p: Point,
|
||||
(start, grab, began, segment): (Point, Point, bool, bool),
|
||||
) -> Vec<Action> {
|
||||
let mut out = vec![];
|
||||
if !began {
|
||||
if p.distance(start) < cx.tol(3.0) {
|
||||
return out;
|
||||
}
|
||||
out.push(Action::Begin("Move".into()));
|
||||
self.state = State::MoveAnchors { start, grab, began: true };
|
||||
self.state = State::MoveAnchors { start, grab, began: true, segment };
|
||||
self.anchor_snap = Some(PointSnap::new(cx, || Targets::for_anchor_drag(cx.doc, cx.selection)));
|
||||
self.line_end = line_end_leave(cx, grab);
|
||||
// A segment held by a point along it lands its anchors on the grid (the box round
|
||||
// them, an edge on a gridline on each axis), not that point (#3835).
|
||||
let grid = segment && (cx.snap_to_grid || cx.snap_to_pixel);
|
||||
self.partial_grid =
|
||||
grid.then(|| MoveSnap::grid_only(vectorcraft_effects::preview::anchor_bounds(cx.selection, cx.doc, cx.preview_bounds)));
|
||||
}
|
||||
let mut d = move_delta(start, p, ev.mods.shift, cx.constrain_angle);
|
||||
let unsnapped = d;
|
||||
// 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 let Some(snap) = &self.anchor_snap {
|
||||
if segment && let Some(grid) = &self.partial_grid {
|
||||
(d, self.guides) = if ev.mods.shift { grid.snap_along(cx, grab, d, false) } else { grid.snap(cx, grab, d, false) };
|
||||
} else 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().or(self.line_end.as_ref()));
|
||||
(d, self.guides) = (q - grab, guides);
|
||||
|
||||
@@ -1862,6 +1862,29 @@ fn a_move_of_objects_lands_an_edge_on_the_grid() {
|
||||
}
|
||||
}
|
||||
|
||||
/// #3835: with Snap to Grid on, a straight segment dragged by a point along it lands its anchors
|
||||
/// on the grid, not that point. B's top edge, (300, 300)–(400, 300), pressed at (350, 300) and
|
||||
/// dragged by (22, 23), moves its anchors to 322–422 across and 323 down: the right end is nearest
|
||||
/// the gridline 423 and the edge nearest 324, so the move is (23, 24). Snapping the pressed point
|
||||
/// instead would move it by (19, 24), onto the intersection (369, 324), leaving both anchors off
|
||||
/// the grid. An anchor pressed on still lands on an intersection itself.
|
||||
#[test]
|
||||
fn a_dragged_segment_lands_its_anchors_on_the_grid() {
|
||||
let (d, _, b) = two_squares();
|
||||
let p = paint();
|
||||
let mut s = anchors_of(b, &[(0, 0), (0, 1)]);
|
||||
s.segments.insert(b, [(0, 0)].into_iter().collect());
|
||||
let c = ToolContext { snap_to_grid: true, ..cx(&d, &s, &p) };
|
||||
let mut t = DirectSelectionTool::new(false);
|
||||
assert_eq!(drag(&mut t, &c, (350.0, 300.0), (372.0, 323.0), Mods::default()).0, json!({"dx": 23.0, "dy": 24.0}));
|
||||
let (v, _) = drag(&mut t, &c, (350.0, 300.0), (363.0, 301.0), Mods { shift: true, ..Mods::default() });
|
||||
let (x, y) = (v["dx"].as_f64().unwrap(), v["dy"].as_f64().unwrap());
|
||||
assert!((x - 14.0).abs() < 1e-9 && y.abs() < 1e-9, "{v}");
|
||||
let one = anchors_of(b, &[(0, 0)]);
|
||||
let c = ToolContext { snap_to_grid: true, ..cx(&d, &one, &p) };
|
||||
assert_eq!(drag(&mut t, &c, (300.0, 300.0), (322.0, 323.0), Mods::default()).0, json!({"dx": 24.0, "dy": 24.0}));
|
||||
}
|
||||
|
||||
/// Group Selection and Direct Selection on a whole path keep a Shift-constrained move on its
|
||||
/// line when it snaps, as the Selection tool does: level by 13.04 with Snap to Grid on, B's
|
||||
/// right side (413.04) lands on the gridline 414, and its top stays at 300, between 297 and
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Parity with Adobe Illustrator
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (Snap to Glyph's glyph bounds give corners and turn with rotated type, #5112; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Hindi catalog and contributor loanword preferences; full wording review in progress; Properties in Edit Artboards: a Transform section with the reference point above the Artboards section, #5219; the Control bar's Stroke: and Opacity: links scrub their fields, #4088; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window, #4155; Control bar Paragraph section with type selected, #5372; translating unchanged type reuses its glyph and character-stroke outlines; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (Direct Selection: with Snap to Grid, a dragged straight segment lands its anchors on the grid, #3835; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Hindi catalog and contributor loanword preferences; full wording review in progress; Properties in Edit Artboards: a Transform section with the reference point above the Artboards section, #5219; the Control bar's Stroke: and Opacity: links scrub their fields, #4088; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window, #4155; Control bar Paragraph section with type selected, #5372; translating unchanged type reuses its glyph and character-stroke outlines; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
|
||||
|
||||
## Feature submission evidence
|
||||
|
||||
@@ -1088,6 +1088,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
View › Proof Setup › Color blindness – Tritanopia-type turns Proof Colors on and proofs the canvas with the tritanopia simulation of Brettel, Viénot and Mollon (1997). The Separations Preview panel's Proof Setup list offers the same target (`view.proofSetup {target: "tritanopia"}`). Grays keep their value.
|
||||
The Separations Preview panel's Gamut Check button reports one color outside the CMYK gamut in the singular and more in the plural, in English and in each language's plural forms (`gamut_check_status_takes_the_plural_form` in `crates/ui-egui/src/i18n/tests.rs`).
|
||||
- **Document Color Mode outside the layers:** File › Document Color Mode (in one undo step) and `document.open {colorMode}` also convert the colors of graph designs, graphic styles, opacity-mask art, the tile that pattern editing's Cancel puts back and the art of brushes (with a Hue Shift brush's key color); a tint of a global swatch there becomes the converted swatch's color at its tint, as in the art.
|
||||
- **Segments dragged onto the grid (#3835):** with View › Snap to Grid (or Snap to Pixel) on, a straight segment dragged with Direct Selection or Group Selection lands its anchors on the grid, the box round them putting an edge on a gridline on each axis as a moved object's does (Shift keeps the move on its line), instead of the point it was pressed at landing on an intersection and leaving both anchors off the grid; a dragged anchor still lands on an intersection itself, and drawn points and resize handles already snapped (`direct::tests::a_dragged_segment_lands_its_anchors_on_the_grid`).
|
||||
- **Spinner arrows in the shadow and glow dialogs (#5367, #5368):** Drop Shadow's Opacity, X Offset, Y Offset, Blur and Darkness and the Outer and Inner Glow's Opacity and Blur have up and down arrows beside the field, as the other number fields with arrows do: a click steps by one (a unit for distances; Shift ×10, Ctrl/Cmd ÷10), holding repeats, Opacity and Darkness stop at 0 and 100 % and Blur at 0 (`dialogs::effect` test `drop_shadow_and_outer_glow_fields_have_spinner_arrows`).
|
||||
- **Colour swatches in effect dialogs (#5365):** a colour parameter of an effect dialog (Outer Glow's Color, Inner Glow's, Drop Shadow's) shows a swatch beside its hex field that follows the value typed in it; a double-click on the swatch opens the Color Picker over the dialog on that colour, whose OK fills the field and whose Cancel comes back unchanged, and the dialog previews the colour when it returns (`dialogs::effect` test `outer_glow_colour_swatch_opens_the_color_picker`). Drop Shadow draws Color beside its Darkness choice rather than in the shared grid, so its swatch came separately (#5364; `drop_shadow_colour_swatch_opens_the_color_picker`); with Darkness chosen it is dimmed.
|
||||
- **Document Color Mode for effects and graphs:** File › Document Color Mode and `document.open {colorMode}` convert the colors of effects (Drop Shadow, Inner Glow, Outer Glow and the other effects with a color, on objects, their fills and strokes and in graphic styles) and the series paints and label styles that a graph keeps, so a graph drawn again from new data keeps the converted colors; File › Document Color Mode also converts the fill and stroke that new art takes. Undo restores the original new-art fill and stroke and Redo applies the conversion again.
|
||||
@@ -1704,6 +1705,7 @@ Edit › Copy and Cut put the selected ruler guides on the clipboard, with the s
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-06 | minor | Direct Selection: with Snap to Grid, a dragged straight segment lands its anchors on the grid (#3835); evidence added; scores unchanged. |
|
||||
| 2026-11-06 | minor | Snap to Glyph › Glyph Bounds: the corners of each glyph's box are points a drawn point lands on, also on a curved side (`B`), and the box turns with rotated type, its edges then Angular Guides (#5112; [Snap to Glyph options](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/snap-to-glyph-options.html)); scores and estimates unchanged. |
|
||||
| 2026-11-06 | minor | Drop Shadow: a swatch beside Color opens the Color Picker over the dialog, as the other effect colour fields do (#5364); correction: the #5365 entry listed Drop Shadow, whose Color is drawn outside the shared grid and had no swatch; scores unchanged. |
|
||||
| 2026-11-06 | minor | Drop Shadow, Outer Glow and Inner Glow: their number fields have spinner arrows (#5367, #5368); evidence added; scores unchanged. |
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
# UI and interaction parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (Drop Shadow: Color's swatch opens the Color Picker, #5364; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Layers: Rename (`ui.rename`) opens the name field of the selected layer, group or object, #5324; Properties in Edit Artboards: Transform section above the Artboards section, #5219; scrubby labels: the Control bar's Stroke: and Opacity: links and the Properties panel's Stroke link scrub their fields, #4088; Properties: Quick Actions has Repeat Options for one selected repeat, #5181; Direct Selection: Shift-click picks several blend spine points, which drag together, #5285; Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box, #3901; Join tool trims two crossing open paths and joins them at the crossing, #5844; Control bar: Paragraph: link and Align left/center/right while type is selected, #5372; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (Direct Selection: with Snap to Grid, a dragged straight segment lands its anchors on the grid, #3835; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Layers: Rename (`ui.rename`) opens the name field of the selected layer, group or object, #5324; Properties in Edit Artboards: Transform section above the Artboards section, #5219; scrubby labels: the Control bar's Stroke: and Opacity: links and the Properties panel's Stroke link scrub their fields, #4088; Properties: Quick Actions has Repeat Options for one selected repeat, #5181; Direct Selection: Shift-click picks several blend spine points, which drag together, #5285; Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box, #3901; Join tool trims two crossing open paths and joins them at the crossing, #5844; Control bar: Paragraph: link and Align left/center/right while type is selected, #5372; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged)
|
||||
|
||||
## Feature submission evidence
|
||||
|
||||
@@ -38,6 +38,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-06 | minor | Snap to Grid: a straight segment dragged with Direct Selection lands its anchors on the grid, not the pressed point (#3835); scores and estimates unchanged |
|
||||
| 2026-11-06 | minor | Drop Shadow: a swatch beside Color follows the hex and a double-click opens the Color Picker over the dialog, whose OK fills Color (#5364); scores and estimates unchanged |
|
||||
| 2026-11-06 | minor | Drop Shadow, Outer Glow and Inner Glow: spinner arrows beside Opacity, the offsets, Blur and Darkness (#5367, #5368); scores and estimates unchanged |
|
||||
| 2026-11-06 | minor | Rename (`ui.rename`, no default key) opens the Layers panel's name field on the selected layer, group or object's row, as double-clicking its name does; a key can be assigned in Keyboard Shortcuts (#5324, a VectorCraft addition); scores and estimates unchanged |
|
||||
@@ -206,7 +207,7 @@ tools, never checked side by side) isn't captured row by row.
|
||||
| Spacing guides (equal distances) | partial | for moves and bounding-box resizes (#394); not while drawing |
|
||||
| Distance Guides: Alt/Option over another object, or an empty part of an artboard, shows the selection's distances to it (Smart Guides › Invoke Distance Guides) | done | Selection tool hover with Smart Guides on: the gap between facing edges, else the offsets of matching edges, to another object; the left, right, top and bottom distances to the artboard; labelled in the General unit. The selection is measured as a whole. Nothing while moving or nudging, which the public pages don't describe. [Use Distance Guides](https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/use-distance-guides-for-accurate-placement.html), [Smart Guides options](https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/smart-guides-options.html) |
|
||||
| Snapping tolerance preference | done | |
|
||||
| Snap to Point, Snap to Grid, Snap to Pixel; Pixel Preview | done | #1840: with Snap to Grid on, anchors and objects snap to the grid even when Snap to Pixel is also ticked (#1816 waits for the reporter) |
|
||||
| Snap to Point, Snap to Grid, Snap to Pixel; Pixel Preview | done | #1840: with Snap to Grid on, anchors and objects snap to the grid even when Snap to Pixel is also ticked (#1816 waits for the reporter); #3835: a straight segment dragged with Direct Selection lands its anchors on the grid, the box round them putting an edge on a gridline on each axis, as a moved object does |
|
||||
| Snap to Glyph | done | View › Snap to Glyph (`view.snapToGlyph`); new type and drawing snap to live-text baselines and glyph bounds when Smart Guides are on (#3124). Character panel menu › Show Snap to Glyph Options: a Snap to Glyph checkbox (View › Snap to Glyph), then a checkbox for each guide (Baseline, x-height, Glyph Bounds, Proximity Guides, Angular Guides, Anchor Point), dimmed while Snap to Glyph is off (#4232); showing them scrolls the panel to them (#5273) |
|
||||
| Tangent and perpendicular snapping for line endpoints (new in 30.0) | done | #2530: tangent snapping to curves and perpendicular snapping to straight segments for lines drawn with the Line Segment, Pen and Curvature tools, and for an existing line whose end anchor is dragged with Direct Selection (the only anchor selected, an open path's end on a straight segment; Shift keeps its angle step instead; Group Selection moves the path whole and does not snap so); mid-line anchors, curved segments' ends and curves as perpendicular targets are excluded, as the pages say. Saved Preferences › Smart Guides › Geometric Guides controls, on by default. [Perpendicular](https://helpx.adobe.com/illustrator/desktop/measure-and-align/plot-and-measure/snap-to-perpendicular.html) and [tangent](https://helpx.adobe.com/illustrator/desktop/measure-and-align/plot-and-measure/snap-to-tangent.html) pages; test `dragged_line_ends_snap_perpendicular_to_segments_and_tangent_to_curves` |
|
||||
| Snapping panel/popover consolidating the snap options (30.0) | partial | Control bar › Snapping provides quick access through existing view commands and prefs.set; no View › Snapping submenu. Floating-panel parity remains unverified. |
|
||||
|
||||
Reference in New Issue
Block a user