mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-14 19:27:07 -04:00
feat(ui): the Page tool draws liquid guides from the rulers
Chris Cicchino's "Adobe InDesign – Page Tool and Liquid Layout" (11:00– 13:30) drags guides out of the rulers with the Page tool on a guide-based page: "these guides are a little bit different... they are a dashed line, a cyan dashed line" - liquid guides, which let the objects they cross stretch with the page. DesignCraft drew liquid guides dashed and applied them, but a guide could only become liquid through the Liquid Layout panel's list: ruler drags always made ruler guides. - canvas: with the Page tool, a page guide dragged from a ruler is a liquid guide; its preview is dashed while it's dragged. - engine: guide.add takes `liquid`. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
e862e0a613
commit
db19963bad
3 files changed
+57
-4
No files matched your search
@@ -129,6 +129,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m
|
||||
|
||||
Newest first.
|
||||
|
||||
- **2026-10-14** · With the Page tool, a guide dragged from a ruler is a liquid guide (dashed), as in InDesign; guide-based pages stretch the objects it crosses. No readiness percentage change.
|
||||
- **2026-10-12** · macOS Dock Quit uses the same unsaved-document prompts as window and menu Quit. Repeated requests keep the current answers; Cancel retains every tab and unsaved recovery checkpoint, and completed Save/Don't Save answers exit through the ordinary event loop. No readiness percentage change.
|
||||
- **2026-10-12** · Orderly native exit writes the latest recovery state after finalizing current quit approvals; incomplete or stale plans keep their documents, and Undo to the saved state clears stale recovery. Unavailable or failed recovery is reported. Cancelable macOS Dock Quit remains open (#544). No readiness percentage change.
|
||||
- **2026-10-12** · Recovered documents keep a checkpoint until saved, closed or reverted, including a second crash before autosave. Restarted document IDs and explicit recovery folders preserve other snapshots; failed republishing retains the original. No readiness percentage change.
|
||||
|
||||
@@ -21,7 +21,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"New Guide",
|
||||
[],
|
||||
None,
|
||||
"{orientation: horizontal|vertical, position (spread coordinate), spread?, page? (index in the spread; default: the page under `at` or the position), at? (the other coordinate), spreadGuide?: bool} → {page, index}",
|
||||
"{orientation: horizontal|vertical, position (spread coordinate), spread?, page? (index in the spread; default: the page under `at` or the position), at? (the other coordinate), spreadGuide?: bool, liquid?: bool (a liquid guide, as the Page tool draws from the rulers)} → {page, index}",
|
||||
has_doc,
|
||||
add
|
||||
),
|
||||
@@ -98,6 +98,7 @@ fn add(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let pos = finite_number(p, "position", "guide.add")?.ok_or_else(|| bad("guide.add", "missing position"))?;
|
||||
let r = super::spread_param(p, "guide.add")?;
|
||||
let spread_guide = p.get("spreadGuide").and_then(Value::as_bool).unwrap_or(false);
|
||||
let liquid = p.get("liquid").and_then(Value::as_bool).unwrap_or(false);
|
||||
let d = &s.doc()?.doc;
|
||||
let pi = match optional_usize(p, "page", "guide.add")? {
|
||||
Some(pi) => pi,
|
||||
@@ -114,7 +115,14 @@ fn add(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
s.edit(|d, _| {
|
||||
let sp = d.spread_mut(r).ok_or_else(|| bad("guide.add", "no such spread"))?;
|
||||
let pg = sp.pages.get_mut(pi).ok_or_else(|| bad("guide.add", "no such page"))?;
|
||||
pg.guides.push(Guide { orientation: o, position: pos, spread: spread_guide, locked: false, layer: Some(layer), liquid: false });
|
||||
pg.guides.push(Guide {
|
||||
orientation: o,
|
||||
position: pos,
|
||||
spread: spread_guide,
|
||||
locked: false,
|
||||
layer: Some(layer),
|
||||
liquid: liquid && !spread_guide,
|
||||
});
|
||||
Ok(json!({"page": pi, "index": pg.guides.len() - 1}))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -301,7 +301,12 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) {
|
||||
designcraft_doc::Orientation::Horizontal => (pos2(rect.min.x, p.y), pos2(rect.max.x, p.y)),
|
||||
designcraft_doc::Orientation::Vertical => (pos2(p.x, rect.min.y), pos2(p.x, rect.max.y)),
|
||||
};
|
||||
painter.line_segment([a, b], Stroke::new(1.0, col));
|
||||
// A new guide from the rulers with the Page tool is a liquid guide: dashed.
|
||||
if g.from.is_none() && app.session.tool_id() == "page" {
|
||||
dashed(&painter, a, b, Stroke::new(1.0, col), 6.0, 3.0);
|
||||
} else {
|
||||
painter.line_segment([a, b], Stroke::new(1.0, col));
|
||||
}
|
||||
}
|
||||
draw_frames(app, &painter, &xf, &doc, &layout);
|
||||
if app.ui.hidden_characters {
|
||||
@@ -1760,7 +1765,10 @@ fn guide_drag(app: &mut DesignApp, ui: &mut egui::Ui, resp: &egui::Response, rec
|
||||
let _ = app.run("guide.move", json!({"spread": r, "page": pi, "index": gi, "position": pos_of(sp)}));
|
||||
}
|
||||
(None, true, Some((r, sp, on_page))) => {
|
||||
let mut params = json!({"spread": r, "orientation": orient, "position": pos_of(sp), "at": sp.x, "spreadGuide": on_page.is_none()});
|
||||
// The Page tool draws liquid guides from the rulers.
|
||||
let liquid = app.session.tool_id() == "page" && on_page.is_some();
|
||||
let mut params =
|
||||
json!({"spread": r, "orientation": orient, "position": pos_of(sp), "at": sp.x, "spreadGuide": on_page.is_none(), "liquid": liquid});
|
||||
if let Some(pi) = on_page {
|
||||
params["page"] = json!(pi);
|
||||
}
|
||||
@@ -1791,6 +1799,42 @@ mod tests {
|
||||
h
|
||||
}
|
||||
|
||||
/// A primary-button drag from `a` to `b` over a few frames.
|
||||
fn drag(h: &mut Harness<'_, DesignApp>, a: Pos2, b: Pos2) {
|
||||
h.hover_at(a);
|
||||
h.step();
|
||||
h.event(egui::Event::PointerButton { pos: a, button: egui::PointerButton::Primary, pressed: true, modifiers: Default::default() });
|
||||
h.step();
|
||||
for k in 1..=4 {
|
||||
h.hover_at(a + (b - a) * (k as f32 / 4.0));
|
||||
h.step();
|
||||
}
|
||||
h.event(egui::Event::PointerButton { pos: b, button: egui::PointerButton::Primary, pressed: false, modifiers: Default::default() });
|
||||
h.run_steps(3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_page_tool_draws_liquid_guides_from_the_rulers() {
|
||||
// Chris Cicchino's Page Tool and Liquid Layout video (11:00): with the Page tool, a guide
|
||||
// dragged from the ruler is a liquid guide (dashed).
|
||||
let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
|
||||
app.run("file.new", json!({})).unwrap();
|
||||
app.session.set_tool("page");
|
||||
let mut h = harness(app);
|
||||
let r = h.state().canvas_rect.unwrap();
|
||||
let guides = |h: &Harness<'_, DesignApp>| h.state().session.doc().unwrap().doc.spreads[0].pages[0].guides.clone();
|
||||
drag(&mut h, pos2(r.center().x, r.min.y - 8.0), r.center());
|
||||
let g = guides(&h);
|
||||
assert_eq!(g.len(), 1, "{g:?}");
|
||||
assert!(g[0].liquid && g[0].orientation == designcraft_doc::Orientation::Horizontal);
|
||||
// With the Selection tool it's a ruler guide.
|
||||
h.state_mut().session.set_tool("selection");
|
||||
drag(&mut h, pos2(r.center().x, r.min.y - 8.0), r.center() + vec2(0.0, 40.0));
|
||||
let g = guides(&h);
|
||||
assert_eq!(g.len(), 2);
|
||||
assert!(!g[1].liquid);
|
||||
}
|
||||
|
||||
fn right_click(h: &mut Harness<'_, DesignApp>, p: Pos2) {
|
||||
h.hover_at(p);
|
||||
h.step();
|
||||
|
||||
Reference in new issue
Block a user