From 209a384ef0ebdc18fd45ae14989cc78afff949e2 Mon Sep 17 00:00:00 2001 From: Taylor Burke Date: Mon, 19 Oct 2026 13:18:33 -0500 Subject: [PATCH] Home screen: fit the card and stack the buttons in a narrow window (#5774) * Home screen: fit the card and stack the buttons in a narrow window The Home screen's card was a fixed 460 points wide, centred on the canvas, with two 190-point buttons side by side. A tiling window manager can give PhotoCraft a canvas narrower than that (Hyprland on Omarchy gave a 728-point window, below the 760-point minimum), so the New document and Open buttons were clipped at the edges. The card is now as wide as the canvas when the canvas is narrower than 460 points. The buttons are sized from their labels (so longer translations still fit) and are stacked when two of them don't fit side by side; the card grows by one row when they stack. Adds start_screen_buttons_stay_inside_a_narrow_canvas, which fails before this change (New document starts at x = -1 in a 390-point canvas). Co-Authored-By: Claude Sonnet 5.5 * contributors: add TaylorBurke Co-Authored-By: Claude Sonnet 5.5 --------- Co-authored-by: Claude Sonnet 5.5 Co-authored-by: Brandon Thomas --- contributors/people.toml | 3 ++ crates/ui-egui/src/canvas.rs | 74 ++++++++++++++++++++++++++++++------ 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/contributors/people.toml b/contributors/people.toml index 0860ea17db..34c50d4d9f 100644 --- a/contributors/people.toml +++ b/contributors/people.toml @@ -163,6 +163,9 @@ sync_github_name = true real_name = "Sidney Markowitz" sync_github_name = true +[people.TaylorBurke] +sync_github_name = true + [people.1Git2Clone] sync_github_name = true diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 053716a572..02fb1b33c4 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -2824,6 +2824,13 @@ fn open_on_double_click(app: &mut PhotocraftApp, ui: &egui::Ui, area: Rect) { } } +/// The Home screen's card is this wide when the canvas has room, and as wide as the canvas when it doesn't. +const HOME_CARD_WIDTH: f32 = 460.0; +/// Least width of each of the Home screen's New document and Open buttons. +const HOME_BUTTON_WIDTH: f32 = 190.0; +/// Space between the Home screen's two buttons. +const HOME_BUTTON_GAP: f32 = 12.0; + fn start_screen(app: &mut PhotocraftApp, ui: &mut egui::Ui) { let t = crate::theme::Tokens::get(ui.ctx()); let area = ui.available_rect_before_wrap(); @@ -2834,9 +2841,16 @@ fn start_screen(app: &mut PhotocraftApp, ui: &mut egui::Ui) { let recent: Vec = if cfg!(target_arch = "wasm32") || !show_recent { Vec::new() } else { app.ui.recent_files.iter().take(HOME_RECENT).cloned().collect() }; let recent_h = if recent.is_empty() { 0.0 } else { 34.0 + recent.len() as f32 * HOME_RECENT_ROW }; - let card = Rect::from_center_size(area.center(), egui::vec2(460.0, 330.0 + recent_h)); + // A tiling window manager can give the canvas less room than the card wants: shrink the card + // to fit, and stack the buttons when they don't fit side by side. let new_label = crate::shortcuts::command_label(app, "New document…", "file.new"); let open_label = crate::shortcuts::command_label(app, "Open…", "file.open"); + let card_width = HOME_CARD_WIDTH.min(area.width()); + let button_width = HOME_BUTTON_WIDTH.max(crate::widgets::button_width(ui, &[&new_label, &open_label])); + let side_by_side = 2.0 * button_width + HOME_BUTTON_GAP <= card_width; + // Stacked buttons take one more row. + let stacked_h = if side_by_side { 0.0 } else { 44.0 }; + let card = Rect::from_center_size(area.center(), egui::vec2(card_width, 330.0 + recent_h + stacked_h)); ui.scope_builder(egui::UiBuilder::new().max_rect(card), |ui| { ui.vertical_centered(|ui| { ui.horizontal(|ui| { @@ -2852,17 +2866,26 @@ fn start_screen(app: &mut PhotocraftApp, ui: &mut egui::Ui) { ui.add_space(6.0); ui.label(egui::RichText::new(tl!("Create a new document or open an existing file.")).color(t.text_dim).size(14.0)); ui.add_space(22.0); - ui.horizontal(|ui| { - ui.add_space(((card.width() - 2.0 * 190.0 - 12.0) / 2.0).max(0.0)); - ui.spacing_mut().item_spacing.x = 12.0; - if crate::widgets::primary_button(ui, &new_label, 190.0).clicked() { - let fields = app.new_document_fields(); - app.ui.open_dialog(crate::state::DialogKind::NewDocument, fields); - } - if crate::widgets::secondary_button(ui, &open_label, 190.0).clicked() { - let _ = app.open_dialog_file(); - } - }); + let (mut new_clicked, mut open_clicked) = (false, false); + if side_by_side { + ui.horizontal(|ui| { + ui.add_space(((card.width() - 2.0 * button_width - HOME_BUTTON_GAP) / 2.0).max(0.0)); + ui.spacing_mut().item_spacing.x = HOME_BUTTON_GAP; + new_clicked = crate::widgets::primary_button(ui, &new_label, button_width).clicked(); + open_clicked = crate::widgets::secondary_button(ui, &open_label, button_width).clicked(); + }); + } else { + new_clicked = crate::widgets::primary_button(ui, &new_label, button_width).clicked(); + ui.add_space(HOME_BUTTON_GAP); + open_clicked = crate::widgets::secondary_button(ui, &open_label, button_width).clicked(); + } + if new_clicked { + let fields = app.new_document_fields(); + app.ui.open_dialog(crate::state::DialogKind::NewDocument, fields); + } + if open_clicked { + let _ = app.open_dialog_file(); + } ui.add_space(22.0); ui.horizontal(|ui| { let msg = start_screen_drop_hint(app); @@ -7273,6 +7296,33 @@ mod tests { assert!(hint.ends_with(&format!("paste a copied image with {}.", crate::notices::paste_hint(&wayland))), "{hint}"); } + /// A tiling window manager can hand PhotoCraft a canvas narrower than the Home card (460 + /// points): the two buttons must stay inside the window instead of being clipped at its edges. + #[test] + fn start_screen_buttons_stay_inside_a_narrow_canvas() { + use egui_kittest::kittest::Queryable; + for width in [390.0, 420.0, 520.0] { + let app = PhotocraftApp::new(photocraft_engine::Session::new(), crate::Services::default()); + let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(width, 760.0)).build_ui_state( + |ui, (app, ready): &mut (PhotocraftApp, bool)| { + if *ready { + egui::CentralPanel::default().frame(egui::Frame::NONE).show(ui, |ui| document_area(app, ui)); + } + }, + (app, false), + ); + PhotocraftApp::setup_context(&h.ctx, crate::theme::ThemeKind::default()); + h.state_mut().1 = true; + h.run_steps(3); + for label in ["New document", "Join us on Discord"] { + for node in h.query_all_by_label_contains(label) { + let r = node.rect(); + assert!(r.left() >= 0.0 && r.right() <= width, "`{label}` is clipped in a {width}-point-wide canvas: {r:?}"); + } + } + } + } + /// #4382: Interface › Show home recent files off hides the Home screen's list, not the recents. #[test] fn home_recent_list_hides_when_the_preference_is_off() {