mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-28 20:26:57 -06:00
Merge #868: Page tool draws liquid guides from the rulers (dashed)
# Conflicts: # ROADMAP.md # crates/engine/src/cmd/guides.rs # crates/ui-egui/src/canvas.rs
This commit is contained in:
@@ -128,6 +128,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-13** · Control-channel clicks and drags validate positive counts and the complete pending-event budget before queuing input (#732). Oversized requests leave pending gestures intact, preventing unbounded click-count allocation. No readiness percentage change.
|
||||
- **2026-10-13** · Switching between document and parent pages refreshes the canvas immediately, preventing black parent pages and stale document objects when the zoom stays the same. Ordinary edits retain partial repainting. No readiness percentage change.
|
||||
- **2026-10-13** · Placed PDFs are drawn through pdfcraft's patched hayro crates: calculator functions whose `idiv` divides by zero or overflows, chains of stitching functions a hundred thousand deep, and mesh shadings with points far off the page, ten thousand overlapping triangles or forty thousand patches no longer panic, overflow the stack, run for over a minute or use more than 2 GiB of memory. A page with a `/UserUnit`, and its trim, bleed and art boxes, are sized in points. 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} or {orientation: both, point: [x, y], spread?, page?, spreadGuide?: bool} → {page, index}; `both` adds a horizontal guide through y and a vertical guide through x as one undo step → {guides: [{page, index}, {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)} or {orientation: both, point: [x, y], spread?, page?, spreadGuide?: bool, liquid?: bool} → {page, index}; `both` adds a horizontal guide through y and a vertical guide through x as one undo step → {guides: [{page, index}, {page, index}]}",
|
||||
has_doc,
|
||||
add
|
||||
),
|
||||
@@ -96,6 +96,7 @@ fn page_at(d: &Document, r: SpreadRef, x: f64) -> usize {
|
||||
fn add(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
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 page = optional_usize(p, "page", "guide.add")?;
|
||||
// (orientation, position, x used to find the page)
|
||||
let guides: Vec<(Orientation, f64, f64)> = if str_param(p, "orientation") == Some("both") {
|
||||
@@ -132,7 +133,7 @@ fn add(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let mut out = Vec::new();
|
||||
for (o, pos, pi) in placed {
|
||||
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 });
|
||||
out.push(json!({"page": pi, "index": pg.guides.len() - 1}));
|
||||
}
|
||||
match <[Value; 1]>::try_from(out) {
|
||||
|
||||
@@ -454,7 +454,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 {
|
||||
@@ -2615,7 +2620,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);
|
||||
}
|
||||
@@ -2713,6 +2721,42 @@ mod tests {
|
||||
assert!(app.canvas.shown.is_none());
|
||||
}
|
||||
|
||||
/// 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