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 <noreply@anthropic.com>

* contributors: add TaylorBurke

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
authored and GitHub committed 2026-10-19 14:18:33 -04:00
1 parent dfe94d6ab9
commit 209a384ef0
2 files changed
+65 -12

No files matched your search

+3
View File
@@ -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
+62 -12
View File
@@ -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<String> =
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() {