mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-09 03:51:42 +00:00
Keep object snapping when Snap to Pixel Grid is on
Fixes #2029 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8eb0b87646
commit
39c5833818
3 files changed
+47
-15
No files matched your search
@@ -218,15 +218,16 @@ impl Session {
|
||||
// Cmd/Ctrl (and Control on macOS) held while dragging suspends snapping.
|
||||
let snap_on = self.ds().map(|ds| ds.view.snap_objects).unwrap_or(true) && !ev.cmd && !ev.ctrl;
|
||||
let mut guides = Vec::new();
|
||||
if self.ds().map(|ds| ds.view.snap_pixel).unwrap_or(true) {
|
||||
// Keep the box's origin on whole pixels (a rotated parent frame's, inside one);
|
||||
// snapping to layers comes after, so it wins on the axes it snaps.
|
||||
d = targets.round_move(d);
|
||||
}
|
||||
if snap_on {
|
||||
let (c, g2) = targets.snap_move(d, SNAP_PX / ev.zoom.max(1e-6));
|
||||
d += c;
|
||||
guides = g2;
|
||||
}
|
||||
if self.ds().map(|ds| ds.view.snap_pixel).unwrap_or(true) {
|
||||
// Keep the box's origin on whole pixels (a rotated parent frame's, inside one).
|
||||
d = targets.round_move(d);
|
||||
}
|
||||
let ids = ids.clone();
|
||||
let dup = *duplicate;
|
||||
let page = self.page()?;
|
||||
@@ -475,20 +476,21 @@ impl Session {
|
||||
let targets = if snap { Some(Targets::gather(self.doc()?, page, &ids)) } else { None };
|
||||
let mut p = ev.p();
|
||||
let mut guides = Vec::new();
|
||||
let _ = start;
|
||||
// In the handle box's own axes (one layer: its local space).
|
||||
let (frame, w0, h0) = r.frame;
|
||||
// The pixel grid: an upright box's dragged sides land on whole pixels (the Scale tool
|
||||
// scales by the factor as dragged). Snapping to layers comes after, so it wins on the axes
|
||||
// it snaps.
|
||||
if !scale_tool && self.ds().map(|d| d.view.snap_pixel).unwrap_or(true) && crate::guides::axis_aligned(frame) {
|
||||
p = Point::new(p.x.round(), p.y.round());
|
||||
}
|
||||
if let Some(t) = &targets {
|
||||
let (dx, dy, g) = t.snap_point(p.x, p.y, SNAP_PX / ev.zoom.max(1e-6));
|
||||
p.x += dx;
|
||||
p.y += dy;
|
||||
guides = g;
|
||||
}
|
||||
let _ = start;
|
||||
// In the handle box's own axes (one layer: its local space).
|
||||
let (frame, w0, h0) = r.frame;
|
||||
// The pixel grid: an upright box's dragged sides land on whole pixels (the Scale tool
|
||||
// scales by the factor as dragged).
|
||||
if !scale_tool && self.ds().map(|d| d.view.snap_pixel).unwrap_or(true) && crate::guides::axis_aligned(frame) {
|
||||
p = Point::new(p.x.round(), p.y.round());
|
||||
}
|
||||
let q = frame.inverse() * p;
|
||||
let is_line = r.single && ids.first().and_then(|i| self.doc().ok()?.node(*i).map(|n| matches!(n.kind, NodeKind::Line))).unwrap_or(false);
|
||||
if scale_tool {
|
||||
|
||||
@@ -29,20 +29,23 @@ impl Session {
|
||||
let tool = self.tool.tool;
|
||||
let mut p = ev.p();
|
||||
let mut guides = Vec::new();
|
||||
let pixel = self.ds().map(|d| d.view.snap_pixel).unwrap_or(true);
|
||||
// The pixel grid first: snapping to layers comes after, so it wins on the axes it snaps.
|
||||
if pixel {
|
||||
p = Point::new(p.x.round(), p.y.round());
|
||||
}
|
||||
if self.ds().map(|d| d.view.snap_objects).unwrap_or(true) && !ev.cmd && !ev.ctrl {
|
||||
let (dx, dy, g) = targets.snap_point(p.x, p.y, SNAP_PX / ev.zoom.max(1e-6));
|
||||
p.x += dx;
|
||||
p.y += dy;
|
||||
guides = g;
|
||||
}
|
||||
let pixel = self.ds().map(|d| d.view.snap_pixel).unwrap_or(true);
|
||||
let mut created = id;
|
||||
let page = self.page()?;
|
||||
self.preview(|doc, sel| {
|
||||
let (mut a, mut b) = (start, p);
|
||||
let (mut a, b) = (start, p);
|
||||
if pixel {
|
||||
a = Point::new(a.x.round(), a.y.round());
|
||||
b = Point::new(b.x.round(), b.y.round());
|
||||
}
|
||||
let pw = doc.world_transform(parent);
|
||||
let (kind, base) = match tool {
|
||||
|
||||
@@ -625,6 +625,33 @@ fn smart_guides_snap_to_edges_centres_and_equal_spacing() {
|
||||
assert_eq!(bounds(&s, b).origin(), Point::new(198.0, 2.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapping_to_layers_wins_over_the_pixel_grid() {
|
||||
// Both on (the defaults): a layer's centre at x = 5.5 is still a target.
|
||||
let mut s = Session::new();
|
||||
let a = rect(&mut s, 0.0, 0.0, 11.0, 11.0, None);
|
||||
let b = rect(&mut s, 200.0, 300.0, 100.0, 100.0, None);
|
||||
let _ = a;
|
||||
ex(&mut s, "select.set", json!({"ids": [b]}));
|
||||
// Moving: B's centre (grabbed) snaps to A's; the free axis stays on whole pixels.
|
||||
s.pointer(&ev(PointerKind::Down, Point::new(250.0, 350.0), NONE)).unwrap();
|
||||
s.pointer(&ev(PointerKind::Move, Point::new(6.0, 352.4), NONE)).unwrap();
|
||||
assert!(s.tool.overlay.guides.iter().any(|g| near(g.x0, 5.5) && near(g.x1, 5.5)), "{:?}", s.tool.overlay.guides);
|
||||
s.pointer(&ev(PointerKind::Up, Point::new(6.0, 352.4), NONE)).unwrap();
|
||||
assert_eq!(bounds(&s, b).origin(), Point::new(-44.5, 302.0), "moved onto A's centre");
|
||||
ex(&mut s, "edit.undo", json!({}));
|
||||
// Out of reach of any layer, a move lands on whole pixels.
|
||||
drag(&mut s, Point::new(250.0, 350.0), Point::new(260.3, 360.6), NONE);
|
||||
assert_eq!(bounds(&s, b).origin(), Point::new(210.0, 311.0));
|
||||
ex(&mut s, "edit.undo", json!({}));
|
||||
// Resizing: the left handle snaps to A's centre; the bottom one lands on whole pixels.
|
||||
drag(&mut s, Point::new(200.0, 350.0), Point::new(6.0, 350.0), NONE);
|
||||
assert!(near(bounds(&s, b).x0, 5.5), "{:?}", bounds(&s, b));
|
||||
ex(&mut s, "edit.undo", json!({}));
|
||||
drag(&mut s, Point::new(250.0, 400.0), Point::new(250.0, 450.6), NONE);
|
||||
assert_eq!(size(&s, b), (100.0, 151.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn corner_cursors_stay_diagonal_whatever_the_proportions() {
|
||||
let mut s = session();
|
||||
|
||||
Reference in new issue
Block a user