From d721ef9f62176f0317ca176b7ae1075e2f5c1c2a Mon Sep 17 00:00:00 2001 From: texjer Date: Wed, 4 Nov 2026 19:36:38 -0500 Subject: [PATCH] Place: a grid previews the cells it places on a rotated spread The preview divided the dragged rectangle on the canvas and the release divided it on the spread, so on a rotated spread two columns previewed across the canvas and landed down it. The preview now takes the release's cells and shows each where it lands on the canvas. --- crates/tools/src/nav.rs | 13 +++++++++---- crates/tools/src/tests.rs | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/crates/tools/src/nav.rs b/crates/tools/src/nav.rs index d4925009f..c86fc4fec 100644 --- a/crates/tools/src/nav.rs +++ b/crates/tools/src/nav.rs @@ -197,11 +197,16 @@ impl Tool for PlaceGun { // no frame is drawn yet, the loaded cursor shows what lands. let click = (self.cur - a).hypot() < cx.tol(3.0); // A grid draws its cells; one too small for them falls back to the single frame, as the - // release does. - if !click { - let cells = grid_cells(designcraft_geom::Rect::from_points(a, self.cur), self.columns, self.rows); + // release does. The cells are the release's, worked out on the spread: on a rotated spread + // its columns don't run across the canvas. + if !click && let Some((sr, sa)) = cx.layout.spread_at(a) { + let cells = grid_cells(designcraft_geom::Rect::from_points(sa, cx.layout.to_spread(sr, self.cur)), self.columns, self.rows); if !cells.is_empty() { - return cells.into_iter().map(crate::Overlay::Marquee).collect(); + let on_canvas = |x: f64, y: f64| cx.layout.to_canvas(sr, Point::new(x, y)); + return cells + .into_iter() + .map(|c| crate::Overlay::Marquee(designcraft_geom::Rect::from_points(on_canvas(c.x0, c.y0), on_canvas(c.x1, c.y1)))) + .collect(); } } let Some(size) = cx.loaded else { diff --git a/crates/tools/src/tests.rs b/crates/tools/src/tests.rs index 6fb3a6af7..9fd67fdd8 100644 --- a/crates/tools/src/tests.rs +++ b/crates/tools/src/tests.rs @@ -2441,3 +2441,40 @@ fn place_gun_grid_too_small_for_its_cells_previews_and_places_one_frame() { assert_eq!(out, single_out); assert!(matches!(out.as_slice(), [Action::Exec(cmd, _)] if cmd == "place.drop"), "{out:?}"); } + +/// On a rotated spread a place-cursor grid previews the cells it places: worked out on the spread, +/// then shown on the canvas, so two columns on the spread don't preview as two across the canvas. +#[test] +fn place_gun_grid_preview_matches_the_release_on_a_rotated_spread() { + let mut d = Document::new(&NewDocument::default()); + std::sync::Arc::make_mut(&mut d.spreads[0]).pages[0].view_rotation = 1; + let (s, c, l) = (Selection::default(), Cache::new(), CanvasLayout::new(&d, false)); + let cx = ctx(&d, &s, &c, &l); + let (start, end) = (Point::new(10.0, 10.0), Point::new(210.0, 110.0)); + let (spread, a) = l.spread_at(start).unwrap(); + let b = l.to_spread(spread, end); + let mut t = create("placeGun"); + let _ = t.pointer(&cx, &PointerEvent::new(PointerKind::Down, start.x, start.y)); + let _ = t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, end.x, end.y)); + assert_eq!(t.key(&cx, ToolKey::Right, Mods::default()), vec![Action::Handled]); + let shown = t.overlays(&cx); + let out = t.pointer(&cx, &PointerEvent::new(PointerKind::Up, end.x, end.y)); + assert!(matches!(out.as_slice(), [Action::Exec(cmd, p)] if cmd == "place.grid" && p["columns"] == 2), "{out:?}"); + // Two columns on the spread, 6 pt apart, each shown where its corners land on the canvas. + let r = Rect::from_points(a, b); + let w = (r.width() - 6.0) / 2.0; + let expected: Vec = [r.x0, r.x0 + w + 6.0] + .into_iter() + .map(|x| Overlay::Marquee(Rect::from_points(l.to_canvas(spread, Point::new(x, r.y0)), l.to_canvas(spread, Point::new(x + w, r.y1))))) + .collect(); + assert_eq!(shown.len(), 2, "{shown:?}"); + for (got, want) in shown.iter().zip(&expected) { + let (Overlay::Marquee(got), Overlay::Marquee(want)) = (got, want) else { panic!("{got:?}") }; + for (g, w) in [(got.x0, want.x0), (got.y0, want.y0), (got.x1, want.x1), (got.y1, want.y1)] { + assert!((g - w).abs() < 1e-6, "{shown:?} against {expected:?}"); + } + } + // A quarter turn: the columns stack down the canvas. + let (Overlay::Marquee(first), Overlay::Marquee(second)) = (&shown[0], &shown[1]) else { panic!("{shown:?}") }; + assert!((first.x0 - second.x0).abs() < 1e-6, "{shown:?}"); +}