Compare commits

...
Author SHA1 Message Date
Brandon Thomas 0e9c3a5912 Merge remote-tracking branch 'origin/main' into finish-844 2026-10-07 21:49:09 -04:00
bflatasticandClaude Opus 5.5 ffb2b5f188 Menu rows touch, as in native menus: long menus are a fifth shorter (#402)
Menu rows inherited the dialogs' 6 pt vertical item spacing, so each row
took 30 pt and long menus such as Layer ran far past the window on smaller
displays. Rows now touch (24 pt apart) and separators are 9 pt.

The denser layout exposed a latent gap from #315. In a window that runs
under the taskbar, egui opens a submenu downward whenever it fits the
window, so a submenu row near the bottom of a top-level menu could open
under the taskbar. A top-level menu now ends a scrolling submenu's height
above the taskbar in that case.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 05:15:13 +07:00
3 changed files with 24 additions and 1 deletions

No files matched your search

+4
View File
@@ -292,6 +292,10 @@ pub fn level_room_in(screen: Rect, visible: Rect, bar_bottom: Option<f32>, depth
let want = rows.unwrap_or(f32::INFINITY);
if want > down && up > down && opens_up(want.min(up)) { up } else { down }.min(below_bar)
}
// A window running under the taskbar: egui opens a submenu downward whenever it fits the
// window, so a top-level menu ends a scrolling submenu's height above the taskbar, leaving
// room for the submenu of its last row.
None if visible.bottom() < screen.bottom() - 0.5 => below_bar - (4.0 * ARROW + frame),
None => below_bar,
} - frame;
if room.is_finite() { room.max(4.0 * ARROW) } else { 4.0 * ARROW }
+9
View File
@@ -409,6 +409,15 @@ fn level_room_keeps_every_level_below_the_bar() {
assert_eq!(level_room(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(100.0, 40.0)), bar, 1, None, frame), 4.0 * ARROW);
assert_eq!(level_room(screen, Some(f32::NAN), 1, None, frame), level_room(screen, None, 1, None, frame));
assert_eq!(level_room(egui::Rect::NOTHING, bar, 2, Some(row), frame), 4.0 * ARROW);
// A window running under the taskbar: a top-level menu leaves a scrolling submenu's room above
// it, so the submenu of its last row (which egui opens downward) stays above the taskbar too.
let visible = egui::Rect::from_min_max(screen.min, egui::pos2(screen.right(), 600.0));
let top = level_room_in(screen, visible, bar, 1, None, None, frame);
assert_eq!(top, 600.0 - EDGE - (30.0 + EDGE) - (4.0 * ARROW + frame) - frame);
let last_row = egui::Rect::from_min_size(egui::pos2(100.0, 30.0 + EDGE + top - 24.0), egui::vec2(200.0, 24.0));
// A 200 pt submenu fits the window downward, so egui opens it that way: it scrolls in what is left.
let room = level_room_in(screen, visible, bar, 2, Some(last_row), Some(200.0), frame);
assert!(room >= 4.0 * ARROW && last_row.top() - (frame - 2.0) / 2.0 + room + frame <= 600.0 - EDGE + 0.5, "room {room}");
}
/// #315: a 1366 × 768 pt display with a Windows taskbar. `window` is the window's content size and
+11 -1
View File
@@ -983,12 +983,18 @@ fn render_level(ui: &mut egui::Ui, items: &[&MenuItem], depth: usize, clicked: &
crate::menu_nav::level(ui, depth, nav, |ui, nav| render_level_rows(ui, items, depth, clicked, nav));
}
/// Height of a menu separator: a line with room above and below, as in native menus.
const MENU_SEPARATOR: f32 = 9.0;
fn render_level_rows(ui: &mut egui::Ui, items: &[&MenuItem], depth: usize, clicked: &mut Option<String>, nav: &mut crate::menu_nav::Nav) {
let t = crate::theme::Tokens::get(ui.ctx());
let lang = crate::i18n::current();
// Items never wrap: the menu widens to its longest label plus shortcut (translations can be
// longer than the English).
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
// Rows touch, as in native menus: the dialog spacing between them made long menus a fifth
// taller than they need to be (#402).
ui.spacing_mut().item_spacing.y = 0.0;
if t.pro {
// Spectrum/macOS menus: blue highlight row with white text.
let v = &mut ui.style_mut().visuals;
@@ -1006,7 +1012,7 @@ fn render_level_rows(ui: &mut egui::Ui, items: &[&MenuItem], depth: usize, click
if it.path.len() == depth {
if it.label == "---" {
if !last_was_sep && i + 1 < items.len() {
ui.separator();
ui.add(egui::Separator::default().spacing(MENU_SEPARATOR));
last_was_sep = true;
}
continue;
@@ -1258,6 +1264,10 @@ mod tests {
let font = egui::TextStyle::Button.resolve(&harness.ctx.global_style()).size;
assert!(a.height() >= font + 8.0, "{theme:?}: item height {} for a {font} pt font", a.height());
assert!(b.top() - a.top() >= font + 10.0, "{theme:?}: rows {} apart", b.top() - a.top());
// #402: rows touch (no dialog spacing between them), so long menus fit the window.
let next = harness.get_by_label_contains("New from Clipboard").rect();
assert!((next.top() - a.bottom()).abs() < 0.5, "{theme:?}: {} pt between rows", next.top() - a.bottom());
assert!(next.top() - a.top() <= 24.5, "{theme:?}: rows {} apart", next.top() - a.top());
}
}