From 8e58b9a23f6cfaaf16ea2f0e245cc5ed02c6df4a Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Thu, 15 Oct 2026 17:17:29 +0200 Subject: [PATCH] Menus: keep scroll bar clear of shortcuts and stop above the status bar (#2086) --- crates/ui-egui/src/chrome.rs | 3 ++- crates/ui-egui/src/lib.rs | 3 +++ crates/ui-egui/src/widgets.rs | 50 ++++++++++++++++++++++++++++------- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/crates/ui-egui/src/chrome.rs b/crates/ui-egui/src/chrome.rs index ac6c0cfb7..dc04bf9bf 100644 --- a/crates/ui-egui/src/chrome.rs +++ b/crates/ui-egui/src/chrome.rs @@ -826,7 +826,7 @@ fn hint_segments(tool: &str) -> Vec<(Cow<'static, str>, bool)> { pub fn hint_bar(app: &mut VectorcraftApp, ui: &mut Ui) { let t = Tokens::get(ui.ctx()); - egui::Panel::bottom("hint_bar") + let panel = egui::Panel::bottom("hint_bar") .exact_size(28.0) .frame(egui::Frame::NONE.fill(t.panel).inner_margin(egui::Margin::symmetric(10, 0)).stroke(Stroke::new(1.0, t.border))) .show(ui, |ui| { @@ -850,6 +850,7 @@ pub fn hint_bar(app: &mut VectorcraftApp, ui: &mut Ui) { ui.label(job); }); }); + widgets::set_menu_floor(ui.ctx(), panel.response.rect.top()); } #[cfg(test)] diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 2ad6c9656..743f1d65f 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -1310,6 +1310,9 @@ impl VectorcraftApp { if self.ui.status_bar && self.ui.screen_mode < 3 { chrome::status_bar(self, ui); chrome::hint_bar(self, ui); + } else { + // Keeps the previous frame's value valid for menus drawn above, before the bars. + widgets::set_menu_floor(&ctx, ctx.content_rect().bottom()); } if self.ui.toolbar && self.ui.screen_mode < 3 { toolbar::show(self, ui); diff --git a/crates/ui-egui/src/widgets.rs b/crates/ui-egui/src/widgets.rs index 8fd3291e1..de2253709 100644 --- a/crates/ui-egui/src/widgets.rs +++ b/crates/ui-egui/src/widgets.rs @@ -840,17 +840,33 @@ pub fn popover(resp: &Response, toggle: bool, content: impl FnOnce(&mut Ui) - inner } -/// The body of a menu or popup list: as tall as its items up to the bottom of the window, and -/// scrolling only past that, so long menus (Window, Effect, a panel's menu) stay reachable on -/// small windows. -pub fn menu_scroll(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R { - // Room left below the popup's first item, less the popup frame and a small gap. +/// Records where the bottom bars (status and hint bars) start this frame, so menus can stop above +/// them (#2086). Without bars it is the bottom of the window. +pub fn set_menu_floor(ctx: &egui::Context, y: f32) { + ctx.data_mut(|d| d.insert_temp(egui::Id::new("menu_floor"), y)); +} + +/// The height a menu list may take: from `top` down to `floor` less a small gap, at least 120 pt. +pub fn menu_room(floor: f32, top: f32) -> f32 { const BOTTOM_GAP: f32 = 12.0; const MIN_HEIGHT: f32 = 120.0; - let room = (ui.ctx().content_rect().bottom() - ui.next_widget_position().y - BOTTOM_GAP).max(MIN_HEIGHT); - // The minimum lets the list grow past a popup's default 400 pt size; it still shrinks to - // its items when they need less. - egui::ScrollArea::vertical().max_height(room).min_scrolled_height(room).show(ui, add_contents).inner + (floor - top - BOTTOM_GAP).max(MIN_HEIGHT) +} + +/// The body of a menu or popup list: as tall as its items up to the bottom bars of the window, and +/// scrolling only past that, so long menus (Window, Effect, a panel's menu) stay reachable on +/// small windows. Its scroll bar has a column of its own, so it never covers the shortcuts or the +/// submenu arrows (#2086). +pub fn menu_scroll(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R { + let floor = ui.ctx().data(|d| d.get_temp::(egui::Id::new("menu_floor"))).unwrap_or_else(|| ui.ctx().content_rect().bottom()); + let room = menu_room(floor, ui.next_widget_position().y); + ui.scope(|ui| { + ui.spacing_mut().scroll = egui::style::ScrollStyle { bar_width: 6.0, bar_inner_margin: 2.0, ..egui::style::ScrollStyle::solid() }; + // The minimum lets the list grow past a popup's default 400 pt size; it still shrinks to + // its items when they need less. + egui::ScrollArea::vertical().max_height(room).min_scrolled_height(room).show(ui, add_contents).inner + }) + .inner } /// A column of tool or panel buttons (the toolbar, the dock's icon column) that scrolls when the @@ -1950,3 +1966,19 @@ mod spinner_modifier_tests { assert_eq!(spinner_step(0.0, 1.0, 0.0, false, egui::Modifiers::SHIFT), 0.0); } } + +#[cfg(test)] +mod menu_room_tests { + use super::*; + + #[test] + fn menu_stops_above_the_bottom_bars() { + // Window 1000 pt tall, bars start at 948: a menu opening at y = 100 gets 836 pt, not 888. + assert_eq!(menu_room(948.0, 100.0), 836.0); + } + + #[test] + fn menu_keeps_a_minimum_height() { + assert_eq!(menu_room(300.0, 290.0), 120.0); + } +}