From 0bbba8cafa03e984f8c0b3b37ad3c2f813ef2bb3 Mon Sep 17 00:00:00 2001 From: Mawfyy Date: Wed, 7 Oct 2026 22:14:45 -0500 Subject: [PATCH] #161: keep the Save-changes prompt inside the screen (#207) An unwrapped title carried a long file name past the viewport: egui's Modal sizes to its content, so the centered panel grew to the name's full width, clipped its own message and pushed Save/Cancel off-screen (reported on Windows, reproduced everywhere). Wrap the title to the dialog width in the save prompt and the password prompt, and pin the title and all three buttons inside a 1365x719 screen in a regression test. Co-authored-by: Mawfyy Co-authored-by: Brandon Thomas --- ROADMAP.md | 1 + crates/ui-egui/src/dialogs.rs | 11 +++++++---- crates/ui-egui/tests/editing.rs | 24 ++++++++++++++++++++++++ parity/acrobat-features.toml | 2 +- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index c84ec79d..52ed232a 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -130,6 +130,7 @@ Newest first. One line per session: the date, what moved, and the new overall pe - **2026-10-07 (community, #190):** Create PDF from Images offers embedded resolution, 72 DPI, or custom DPI (1–1200) without resampling. The engine and doc_create share the choice; Windows image context menus open the chooser through --create-images. Overall estimate unchanged (about 30–35%). - **2026-10-08 (community):** Cut and stack in Print > Multiple: single-sided n-up sheets can be cut into cell piles and restacked in selected page order, with aligned gutter marks and blanks kept in their cells. Available through doc_print; duplex is refused. Grid-size overflow returns an error. No change to the parity count or the overall estimate (about 30–35%). +- **2026-10-07 (community, #161):** The "Save changes?" prompt keeps a long file name inside the modal: the title wraps to the dialog width, so the centered panel no longer stretches past the viewport, clips its message and pushes Save/Cancel off-screen (Windows report). The password prompt's file-name line wraps the same way, and a regression test pins the title and all three buttons inside a 1365×719 screen. Parity counts unchanged; ≈ 30–35%. - **2026-10-07 (community, #143):** Windows MSI gets a full-UI welcome dialog with a "Create a desktop shortcut" checkbox (on by default; `INSTALLDESKTOPSHORTCUT=0` for unattended installs), progress, files in use, repair/remove, and a "Setup completed successfully" screen with Finish (plus distinct failed/cancelled screens). Start Menu and desktop shortcuts are now plain links to `pdfcraft.exe` instead of advertised MSI shortcuts. Packaging checks the compiled MSI's shortcut, checkbox and dialog tables; the ARM64 smoke test checks both links' targets, their removal on uninstall, and the opt-out. Installer polish only; feature counts unchanged. ≈ 30–35%. - **2026-10-07 (localization):** The interface translates through the same system as PhotoCraft: one TSV catalog per language (`crates/ui-egui/src/i18n/ja.tsv`), the `tl!` macro, entries keyed by command id or with plural forms, and catalog tests (format, duplicates, placeholders, command ids). Catalogs are validated strictly (unknown escapes, reserved contexts, placeholders, ellipses, plural forms), a bad line is skipped and logged at run time, `fmt` substitutes in one pass, the drawing language is per thread, and each `.tsv` is an attributed asset (`kind = "translation"`, checked by `cargo xtask assets`); validation, attribution and the language-switch control test folded in from #175. The 49 existing Japanese strings moved over unchanged, plus `Auto`. The `language` preference now defaults to `auto` (follow the system language; English when it has no catalog); `en`/`ja` still persist, and unknown values are rejected. Not covered: dialogs and panels, Windows system-language detection. ≈ 30–35% (unchanged). - **2026-10-07 (session 16):** PrintCraft is renamed PdfCraft: repo `storytold/pdfcraft`, crates and binaries `pdfcraft*`, bundle id `ai.storyteller.pdfcraft`. On first launch the app moves the old PrintCraft settings and crash-recovery folders to the new name, so upgrades keep recent files, preferences and unsaved work. No feature change. ≈ 30–35%. diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index e4497623..ad9bbe62 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -1336,9 +1336,12 @@ fn save_prompt(app: &mut PdfCraftApp, ctx: &egui::Context) { ui.set_width(420.0); ui.horizontal(|ui| { ui.add(crate::icons::image("save", 22.0, t.accent)); - ui.label( - egui::RichText::new(crate::i18n::fmt(tl!("Save changes to “{name}” before closing?"), &[("name", &name)])) - .font(theme::semibold(16.0)), + ui.add( + egui::Label::new( + egui::RichText::new(crate::i18n::fmt(tl!("Save changes to “{name}” before closing?"), &[("name", &name)])) + .font(theme::semibold(16.0)), + ) + .wrap(), ); }); ui.add_space(6.0); @@ -1437,7 +1440,7 @@ fn password(app: &mut PdfCraftApp, ctx: &egui::Context) { ui.label(egui::RichText::new(tl!("Password required")).font(theme::semibold(17.0))); }); ui.add_space(6.0); - ui.label(crate::i18n::fmt(tl!("“{name}” is protected. Enter a password to open it."), &[("name", &prompt.name)])); + ui.add(egui::Label::new(crate::i18n::fmt(tl!("“{name}” is protected. Enter a password to open it."), &[("name", &prompt.name)])).wrap()); ui.add_space(8.0); let r = ui.add(egui::TextEdit::singleline(&mut prompt.input).password(true).hint_text(tl!("Password")).desired_width(f32::INFINITY)); // Enter submits. The field keeps focus (we request it every frame), so check the key diff --git a/crates/ui-egui/tests/editing.rs b/crates/ui-egui/tests/editing.rs index 6b7dfaed..01728407 100644 --- a/crates/ui-egui/tests/editing.rs +++ b/crates/ui-egui/tests/editing.rs @@ -387,6 +387,30 @@ fn quitting_with_unsaved_changes_asks_for_each_document() { assert!(h.state().close_request.is_none()); } +#[test] +fn save_prompt_stays_inside_the_screen_for_a_long_filename() { + // Issue #161: an unwrapped title carrying a long filename widened the centered modal past + // the viewport, clipping the message and pushing the Save/Cancel buttons off-screen. + let name = "Psychology_ The Science of Mind and Behaviour, -- Nigel Holt, Andy Bremner, Michael \ + Vliek, Ed Sutherland, -- 5, 2024 -- McGraw-Hill Education (UK) Ltd -- isbn13 97815268.pdf"; + let mut h = Harness::builder().with_size(egui::vec2(1365.0, 719.0)).build_eframe(move |_cc| { + let mut app = PdfCraftApp::new(); + app.open_bytes(name, None, fixture(1)).expect("fixture opens"); + app.close_request = Some(CloseRequest::Tab(0)); + app + }); + h.run_steps(4); + let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1365.0, 719.0)); + let inside = |r: egui::Rect| screen.contains(r.min) && screen.contains(r.max); + let title = h.get_by_label_contains("Save changes to"); + let title_rect = title.rect(); + assert!(inside(title_rect), "the title rect {title_rect:?} leaves the screen"); + for button in ["Save", "Cancel", "Don't save"] { + let rect = h.get_by_label(button).rect(); + assert!(inside(rect), "the {button} button rect {rect:?} leaves the screen"); + } +} + #[test] fn document_properties_edit_is_one_undoable_step() { let mut h = harness(1, |app| app.set_option("dialog", "properties").unwrap()); diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml index f6c39b82..4ccc8e59 100644 --- a/parity/acrobat-features.toml +++ b/parity/acrobat-features.toml @@ -574,7 +574,7 @@ milestone = "M4" status = "shipped" commands = ["file.close"] tools = ["doc_close"] -evidence = ["crates/ui-egui/tests/editing.rs::closing_a_dirty_tab_asks_and_cancel_keeps_it", "crates/ui-egui/tests/editing.rs::closing_a_dirty_tab_can_save_first", "crates/ui-egui/tests/editing.rs::quitting_with_unsaved_changes_asks_for_each_document"] +evidence = ["crates/ui-egui/tests/editing.rs::closing_a_dirty_tab_asks_and_cancel_keeps_it", "crates/ui-egui/tests/editing.rs::closing_a_dirty_tab_can_save_first", "crates/ui-egui/tests/editing.rs::quitting_with_unsaved_changes_asks_for_each_document", "crates/ui-egui/tests/editing.rs::save_prompt_stays_inside_the_screen_for_a_long_filename"] notes = "" [[feature]]