From 29f02d54581af5657c4e841381c20885eb1e4020 Mon Sep 17 00:00:00 2001 From: moppeeer Date: Thu, 15 Oct 2026 17:49:01 +0200 Subject: [PATCH] Canvas: pages show their paper while their render is on its way Co-Authored-By: Claude Opus 5.5 --- ROADMAP.md | 1 + crates/ui-egui/src/canvas.rs | 42 ++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/ROADMAP.md b/ROADMAP.md index d60a6bf49..671a7cffa 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -347,6 +347,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m Newest first. +- 2026-10-15: A page scrolled or jumped to shows its paper while its render is on its way, as in InDesign, instead of the black of its shadow. - **2026-10-14** · The Type tool draws a new text frame over a locked empty frame, as InDesign does, instead of turning the locked frame into a text frame. No readiness percentage change. - **2026-10-14** · 100% black shows as pure black on screen by default, as InDesign's Appearance of Black does out of the box (Display All Blacks as Rich Black); Preferences › Appearance of Black › Display All Blacks Accurately still shows it as the dark grey it prints, and a saved choice is kept. No readiness percentage change. - **2026-10-14** · Pathfinder keeps the frontmost object, as InDesign does: a photo placed over shapes and intersected with them shows through the shapes (it was dropped, and the shapes kept their black fill); Add, Exclude Overlap and Minus Back take the front object's fill too, Subtract still keeps the back one. No readiness percentage change. diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 40b146f56..cb630d6b1 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -344,6 +344,15 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { let r = xf.rect(slot.bounds); painter.rect_filled(r.translate(vec2(1.5, 1.5)), 0.0, Color32::BLACK); } + // Paper under the rendered content: a page scrolled or jumped to shows its paper colour, + // not the shadow's black, until its render arrives. + let [r, g, b, _] = doc.resolve_color(designcraft_color::swatch::PAPER, 1.0).unwrap_or(designcraft_color::Color::WHITE).to_rgba8(1.0); + let paper = Color32::from_rgb(r, g, b); + for slot in &layout.slots { + for page in doc.spread(slot.spread).map(|sp| sp.pages.as_slice()).unwrap_or_default() { + painter.rect_filled(xf.rect(slot.xf.transform_rect_bbox(page.bounds())), 0.0, paper); + } + } // Rendered content. render_texture(app, ui.ctx(), rect, &xf, &layout, preview); if let (Some(tex), Some(sh)) = (&app.canvas.texture, app.canvas.shown) { @@ -2233,6 +2242,39 @@ mod tests { assert_eq!(press(Some(up), 0.0, 1.0), 1); } + /// While a page's render is on its way the page shows paper, not the black of its shadow. + #[test] + fn pages_show_paper_under_their_render() { + let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default()); + app.run("file.new", serde_json::json!({"pages": 3, "facingPages": true})).unwrap(); + let ctx = egui::Context::default(); + let mut shapes = Vec::new(); + for _ in 0..2 { + let raw = egui::RawInput { screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(1440.0, 900.0))), ..Default::default() }; + let mut out = ctx.run_ui(raw, |ui| show(&mut app, ui)); + out.textures_delta.clear(); + shapes = out.shapes; + } + let st = app.session.active().unwrap(); + let layout = CanvasLayout::new(&st.doc, false); + let xf = Xf::new(app.canvas_rect.unwrap(), app.view().unwrap()); + // The paper swatch on screen (white, as the colour settings show it). + let [r, g, b, _] = st.doc.resolve_color(designcraft_color::swatch::PAPER, 1.0).unwrap().to_rgba8(1.0); + let paper: Vec = shapes + .iter() + .filter_map(|c| match &c.shape { + egui::Shape::Rect(s) if s.fill == Color32::from_rgb(r, g, b) => Some(s.rect), + _ => None, + }) + .collect(); + // Each page on its own (a spread's pages may differ in size). + assert_eq!(paper.len(), st.doc.page_count()); + for abs in 0..st.doc.page_count() { + let want = xf.rect(layout.page_rect(&st.doc, abs).unwrap()); + assert!(paper.iter().any(|r| (r.min - want.min).length() < 0.01 && (r.max - want.max).length() < 0.01), "page {abs}"); + } + } + fn harness(app: DesignApp) -> Harness<'static, DesignApp> { let mut h = Harness::builder().with_size(vec2(1440.0, 900.0)).build_ui_state( |ui, app: &mut DesignApp| {