Selection: a Shift-constrained move that snaps stays on its line

A move that Shift kept level, upright or on a diagonal left that line
when it snapped. With View › Snap to Grid on, a level Shift-drag also
moved the selection up or down onto a gridline, and Snap to Pixel,
Smart Guides and Snap to Point moved it off the line in the same way.
Each axis snapped on its own after Shift had constrained the move.

With the Selection tool, and with Group Selection and Direct Selection
moving whole objects, a Shift-constrained move now slides along its
line until it snaps. With Snap to Grid or Snap to Pixel, the move
slides along the line by the shorter of the two distances that put it
on a gridline or a whole pixel on either axis. The bounds line up with
the other objects, or space evenly with them, only on the axes the line
runs along, and the grabbed point stops where the line passes an anchor
or crosses a ruler guide. A level or upright move snaps along its own
axis only. Moves without Shift snap as before.

Docs: docs/target-app-parity.md.
This commit is contained in:
dotCooCoo
2026-10-18 12:40:10 +07:00
committed by bflatastic
parent 138e9ca466
commit 034ef916ad
5 changed files with 333 additions and 2 deletions
+18
View File
@@ -1784,6 +1784,24 @@ fn bounding_box_handles_snap_to_the_grid() {
}
}
/// A Shift-constrained move with Snap to Grid on stays level: the square's center lands on a
/// gridline across, and its top stays where it was, off the grid.
#[test]
fn a_shift_move_snapped_to_the_grid_stays_on_its_line() {
let mut s = session();
let a = rect(&mut s, 101.0, 101.0, 100.0, 100.0);
let step = s.doc().unwrap().doc.grid.spacing / f64::from(s.doc().unwrap().doc.grid.subdivisions.max(1));
let on_grid = |v: f64| (v / step - (v / step).round()).abs() < 1e-9;
let v = ViewInfo { snap_to_grid: true, ..ViewInfo::default() };
let shift = vectorcraft_tools::Mods { shift: true, ..Default::default() };
s.select_tool("selection", v).unwrap();
for (kind, x, y) in [(PointerKind::Down, 151.0, 151.0), (PointerKind::Drag, 180.0, 153.0), (PointerKind::Up, 180.0, 153.0)] {
s.pointer(&PointerEvent::new(kind, x, y).with_mods(shift), v).unwrap();
}
let b = s.doc().unwrap().doc.node(a).unwrap().geometric_bounds().unwrap();
assert!(on_grid(b.center().x) && (b.y0 - 101.0).abs() < 1e-9, "{b:?}, grid step {step}");
}
/// Follow-up to #1254: toggling or adding anchors of a path selected as a whole starts from all
/// its anchors, so toggling one deselects it alone and adding one keeps them all.
#[test]