mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-13 13:52:31 +00:00
Snapping: a drawn or resized edge snaps to the page's centre line, as InDesign's smart guides do
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8dff7ecdb0
commit
4cde0668be
2 files changed
+56
No files matched your search
@@ -348,6 +348,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m
|
||||
|
||||
Newest first.
|
||||
|
||||
- **2026-10-20** · Smart guides: an edge being drawn or resized snaps to the page's centre line, as in InDesign, so a frame drawn from the top margin to the middle of the page stops there instead of at the nearest baseline grid line. No readiness percentage change.
|
||||
- **2026-10-16** · Type › Reload Fonts (`font.reload`): fonts installed, edited or removed while DesignCraft runs are read again without a restart, and the open documents recompose with them (#328, #547). An edited font file is noticed by its length and modification time; bundled, added and document fonts stay as they are. InDesign does this by itself, so noticing changes automatically is still open (gap 6). No readiness percentage change.
|
||||
- **2026-10-16** · Parents can be deleted from their Pages-panel context menu or `layout.parents.delete {id}`. Applied-page and based-on references are cleared, local override copies survive, and Undo restores the parent. Addresses the deletion portion of #956; no readiness percentage change.
|
||||
- **2026-10-17** · File › Package writes the document's and the IDML's links relative to the package (`Links/logo.png`, `file:Links/logo.png`), so a package holds no path from the machine that made it; opening a document resolves them to the files beside it. Links › Copy Link(s) To still links by full path. (#340). No readiness percentage change.
|
||||
|
||||
@@ -1074,9 +1074,15 @@ fn pass_align(cx: &ToolContext, req: &SnapRequest<'_>, axis: Axis, tol: f64) ->
|
||||
}
|
||||
let flags = axis_flags(req, axis);
|
||||
let mut best: Option<Best> = None;
|
||||
// An edge being drawn or resized also meets the page's centre line, as InDesign's smart
|
||||
// guides do: a frame drawn from the top margin down to the middle of the page stops there.
|
||||
let edge_to_centre = cx.snap.align_centers && matches!(req.gesture, Gesture::Create | Gesture::Resize);
|
||||
if let Some(sp) = cx.doc.spread(req.spread) {
|
||||
for page in &sp.pages {
|
||||
offer_box(&mut best, req.rect, flags, axis, page.bounds(), tol, cx.snap.align_edges, cx.snap.align_centers);
|
||||
if edge_to_centre {
|
||||
offer_edges_to_centre(&mut best, req.rect, flags, axis, page.bounds(), tol);
|
||||
}
|
||||
}
|
||||
for item in &sp.items {
|
||||
let Some(rect) = item_target_rect(cx.selection, req, item) else { continue };
|
||||
@@ -1291,6 +1297,18 @@ fn offer_box(best: &mut Option<Best>, moving: Rect, flags: [bool; 3], axis: Axis
|
||||
}
|
||||
}
|
||||
|
||||
/// The moving rect's enabled edges against the target's centre line.
|
||||
fn offer_edges_to_centre(best: &mut Option<Best>, moving: Rect, flags: [bool; 3], axis: Axis, target: Rect, tol: f64) {
|
||||
let (m0, _, m1) = axis_triple(moving, axis);
|
||||
let (_, t_mid, _) = axis_triple(target, axis);
|
||||
if flags[0] {
|
||||
offer(best, m0, t_mid, tol, Some(target));
|
||||
}
|
||||
if flags[2] {
|
||||
offer(best, m1, t_mid, tol, Some(target));
|
||||
}
|
||||
}
|
||||
|
||||
fn offer(best: &mut Option<Best>, moving: f64, target: f64, tol: f64, span: Option<Rect>) {
|
||||
if !moving.is_finite() || !target.is_finite() {
|
||||
return;
|
||||
@@ -1985,6 +2003,43 @@ mod tests {
|
||||
assert_eq!(hit.delta.x, 0.0);
|
||||
}
|
||||
|
||||
/// A frame drawn on an A4 landscape page from the top margin down to the middle of the page
|
||||
/// stops at the page's centre line (297.6 pt), not at the hidden baseline grid line 1.4 pt
|
||||
/// further down (300 pt), as InDesign's smart guides do.
|
||||
#[test]
|
||||
fn a_drawn_edge_snaps_to_the_page_centre() {
|
||||
let a4 = NewDocument { width: 841.89, height: 595.276, ..NewDocument::default() };
|
||||
let doc = with_baseline_grid(Document::new(&a4));
|
||||
let cache = Cache::new();
|
||||
let layout = CanvasLayout::new(&doc, false);
|
||||
let sel = Selection::default();
|
||||
let cx = ctx_on(&doc, &sel, &cache, &layout);
|
||||
let create = |gesture, y_edges, rect| SnapRequest {
|
||||
spread: SpreadRef::Doc(0),
|
||||
gesture,
|
||||
rect,
|
||||
x_edges: [false, false, false],
|
||||
y_edges,
|
||||
exclude: &[],
|
||||
copying: false,
|
||||
lengths: [None, None],
|
||||
angle: None,
|
||||
radius: 0.0,
|
||||
pointer: Point::new(0.0, 0.0),
|
||||
};
|
||||
let centre = 595.276 / 2.0;
|
||||
let hit = snap(&cx, create(Gesture::Create, [false, false, true], Rect::new(500.0, 42.52, 800.0, 298.6)));
|
||||
assert!((hit.delta.y - (centre - 298.6)).abs() < 1e-6, "{}", hit.delta.y);
|
||||
// Drawn upwards, the top edge meets it too; a resized edge does as well.
|
||||
let hit = snap(&cx, create(Gesture::Create, [true, false, false], Rect::new(500.0, 296.0, 800.0, 552.0)));
|
||||
assert!((hit.delta.y - (centre - 296.0)).abs() < 1e-6, "{}", hit.delta.y);
|
||||
let hit = snap(&cx, create(Gesture::Resize, [false, false, true], Rect::new(500.0, 42.52, 800.0, 298.6)));
|
||||
assert!((hit.delta.y - (centre - 298.6)).abs() < 1e-6, "{}", hit.delta.y);
|
||||
// A moved frame still lines up by its centre only: its bottom edge goes to the baseline.
|
||||
let hit = snap(&cx, create(Gesture::Move, [true, false, true], Rect::new(500.0, 42.52, 800.0, 298.6)));
|
||||
assert!((hit.delta.y - 1.4).abs() < 1e-6, "{}", hit.delta.y);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_does_not_match_spacing() {
|
||||
let mut doc = Document::new(&NewDocument::default());
|
||||
|
||||
Reference in new issue
Block a user