diff --git a/ROADMAP.md b/ROADMAP.md index a72d4b4..bf25860 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -132,6 +132,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | Date | What landed | | 2026-10-11 | Fonts: Palatino Linotype and Book Antiqua fall back to Palatino (macOS) or TeX Gyre Pagella / P052 / URW Palladio (Linux) before generic serif (#670) | |---|---| +| 2026-10-11 | Design tab: the style set gallery gets a box and gaps of its own, so it no longer runs into Colors and Fonts (#638) | | 2026-10-11 | File › Print: page preview, page range (all, current, selected, custom 1-3,5), odd/even, copies and collate go into the printed PDF (`file.printPdf`, `ui.print` params); printer, duplex and pages per sheet are still the system dialog's (#641, stage 1) | | 2026-10-11 | A vertically merged table cell (eraser on a horizontal border) lays out, shows and clicks as one cell spanning its rows (#640) | | 2026-10-11 | Word XML files (#548): Word XML Document (Flat OPC `.xml`) opens and saves through the DOCX reader and writer; Word 2003 XML opens (styles, lists, tables, sections, notes, comments, pictures, hyperlinks) | diff --git a/crates/ui-egui/src/previews.rs b/crates/ui-egui/src/previews.rs index e088741..5127d10 100644 --- a/crates/ui-egui/src/previews.rs +++ b/crates/ui-egui/src/previews.rs @@ -410,12 +410,19 @@ pub fn style_gallery(app: &mut WordApp, ui: &mut Ui, state: &Value) { } } +/// Style sets shown in the Design tab's gallery, in order. +const STYLE_SETS: [&str; 7] = ["default", "basic", "lines", "shaded", "casual", "centered", "minimalist"]; +/// Width of one gallery thumbnail, points. +const STYLE_SET_W: f32 = 56.0; +/// Width the whole gallery takes with the ribbon's 1 pt item spacing. +pub const STYLE_SET_GALLERY_W: f32 = STYLE_SETS.len() as f32 * (STYLE_SET_W + 1.0) - 1.0; + /// Design tab: style set tiles. pub fn style_set_gallery(app: &mut WordApp, ui: &mut Ui) { let t = Tokens::get(ui.ctx()); let ppp = ui.ctx().pixels_per_point(); - for name in ["default", "basic", "lines", "shaded", "casual", "centered", "minimalist"] { - let (r, resp) = ui.allocate_exact_size(vec2(56.0, crate::widgets::CONTENT_H), Sense::click()); + for name in STYLE_SETS { + let (r, resp) = ui.allocate_exact_size(vec2(STYLE_SET_W, crate::widgets::CONTENT_H), Sense::click()); if resp.hovered() { ui.painter().rect_filled(r, 3.0, t.hover); } diff --git a/crates/ui-egui/src/ribbon.rs b/crates/ui-egui/src/ribbon.rs index c7b88ba..78fac2d 100644 --- a/crates/ui-egui/src/ribbon.rs +++ b/crates/ui-egui/src/ribbon.rs @@ -921,6 +921,20 @@ fn gallery_pen(ui: &mut Ui, app: &mut WordApp, i: usize, pen: wordcraft_engine:: }); } +/// The Design tab's style set gallery in a box of its own, sized from its thumbnails and clipped +/// to it, with a gap on each side: the Colors and Fonts column can't be drawn over, or cut by, +/// the thumbnails (#638). Returns the box and the space the thumbnails used. +fn style_set_box(app: &mut WordApp, ui: &mut Ui) -> (Rect, Rect) { + ui.add_space(4.0); + let (gallery, _) = ui.allocate_exact_size(vec2(crate::previews::STYLE_SET_GALLERY_W, CONTENT_H), Sense::hover()); + let mut gui = ui.new_child(egui::UiBuilder::new().max_rect(gallery).layout(egui::Layout::left_to_right(egui::Align::Min))); + gui.set_clip_rect(gallery.intersect(ui.clip_rect())); + gui.spacing_mut().item_spacing = vec2(1.0, 1.0); + crate::previews::style_set_gallery(app, &mut gui); + ui.add_space(8.0); + (gallery, gui.min_rect()) +} + fn design(app: &mut WordApp, ui: &mut Ui) { group(ui, "Themes", None, app, |ui, app| { menu_button(ui, app, "themes", Some("Themes"), "Themes", true, |ui, app| { @@ -930,7 +944,7 @@ fn design(app: &mut WordApp, ui: &mut Ui) { }); }); group(ui, "Document Formatting", None, app, |ui, app| { - crate::previews::style_set_gallery(app, ui); + style_set_box(app, ui); stack(ui, |ui| { menu_button(ui, app, "colors", Some("Colors"), "Theme Colors", false, |ui, app| { for (name, ..) in wordcraft_engine::cmd::design::THEMES { @@ -2253,6 +2267,26 @@ mod tests { wordcraft_engine::cmd::insert::base64_encode(&b) } + /// The style set gallery fills exactly its own box and the next control starts after a gap, + /// so Colors and Fonts can't overlap the thumbnails (#638). + #[test] + fn design_style_set_gallery_stays_in_its_box() { + let mut app = WordApp::new(Session::new(wordcraft_doc::Document::new()), crate::Services::default()); + let mut seen = None; + let mut h = egui_kittest::Harness::builder().with_size(vec2(1440.0, 200.0)).build_ui(|ui| { + ui.horizontal_top(|ui| { + let (gallery, used) = style_set_box(&mut app, ui); + seen = Some((gallery, used, ui.cursor().min.x)); + }); + }); + h.run_steps(2); + drop(h); + let (gallery, used, next_x) = seen.unwrap(); + assert!((gallery.width() - crate::previews::STYLE_SET_GALLERY_W).abs() < 0.01); + assert!(used.right() <= gallery.right() + 0.01, "{used:?} {gallery:?}"); + assert!(next_x >= gallery.right() + 8.0 - 0.01, "{next_x} {gallery:?}"); + } + #[test] fn picture_format_tab_appears_when_picture_selected() { let mut s = Session::new(wordcraft_doc::Document::new());