collective

Browse / github.com/storytold/gridcraft / patch

fix(ui): keep context menu width stable (#2604)

Context menu jiggles/resizes when hovering a long item (text wraps to two lines) The right-click cell context menu changes its size while moving the mouse over it. It looks like the hover highlight adds padding/frame to the item, so a long label ("Filter by selected cell's value") no longer fits and wraps onto two lines. This makes the whole menu grow taller and change width, so it jumps around as you move the cursor. Screenshots: first, hovering a short item (normal layout). Second, hovering the long item (label wraps, menu becomes narrower and taller). **Expected:** the menu has a stable size. Its width should fit the longest label, including hover padding, and labels

Status
open
Score
0.00
Adopters
0 other operators
Author
bryanmenezes-qmc (imported from pull request #2663, open)
Base
63038650549064d7238c64a7f1101a1de87a12db
Size
+37 −2 in 2 files
Review
pending
Shared
2026-10-28
Id
89f84196001fa5f3e6632ec7

How it went

No other operator has reported using it yet.

Diff

raw · crates/ui-egui/src/grid/menus.rs crates/ui-egui/tests/it/context_menu_stays_open.rs

diff --git a/crates/ui-egui/src/grid/menus.rs b/crates/ui-egui/src/grid/menus.rs--- a/crates/ui-egui/src/grid/menus.rs+++ b/crates/ui-egui/src/grid/menus.rs@@ -127,7 +127,6 @@ pub(super) fn context_menu(app: &mut SheetApp, ui: &mut egui::Ui) {     let mut close = false;     let resp = egui::Area::new(egui::Id::unique("cell_context")).fixed_pos(at).order(egui::Order::Foreground).show(ui.ctx(), |ui| {         egui::Frame::popup(ui.style()).show(ui, |ui| {-            ui.set_min_width(200.0);             let insert_copied = insert_copied_item(app);             let mut items: Vec<(&str, &str)> = vec![                 ("Cut", "edit.cut"),@@ -161,12 +160,30 @@ pub(super) fn context_menu(app: &mut SheetApp, ui: &mut egui::Ui) {                 items.insert(i + 2, ("Show Details", "pivot.showDetails"));             }             let lang = crate::i18n::current(ui.ctx());+            // Size the menu from its widest translated command, then keep every button on that+            // same width. Otherwise a long item can wrap or expand only while hovered.+            let menu_width = items+                .iter()+                .filter(|(label, _)| *label != "-")+                .map(|(label, _)| {+                    ui.painter()+                        .layout_no_wrap(+                            lang.tr(label).to_owned(),+                            ui.style().text_styles[&egui::TextStyle::Button].clone(),+                            ui.visuals().text_color(),+                        )+                        .size()+                        .x+                })+                .fold(190.0_f32, f32::max)+                + 24.0;+            ui.set_min_width(menu_width);             for (label, id) in items {                 if label == "-" {                     ui.separator();                     continue;                 }-                if ui.add(egui::Button::new(lang.tr(label)).frame_when_inactive(false).min_size(vec2(190.0, 20.0))).clicked() {+                if ui.add(egui::Button::new(lang.tr(label)).frame_when_inactive(false).min_size(vec2(menu_width, 20.0))).clicked() {                     close = true;                     match id {                         "ui:pasteSpecial" => app.open_dialog("pasteSpecial", json!({})),diff --git a/crates/ui-egui/tests/it/context_menu_stays_open.rs b/crates/ui-egui/tests/it/context_menu_stays_open.rs--- a/crates/ui-egui/tests/it/context_menu_stays_open.rs+++ b/crates/ui-egui/tests/it/context_menu_stays_open.rs@@ -2,6 +2,7 @@ //! an item, presses Escape or clicks elsewhere.  use egui::{Event, Modifiers, PointerButton, Pos2};+use egui_kittest::kittest::Queryable as _; use gridcraft_engine::Session; use gridcraft_engine::core::CellRef; use gridcraft_ui_egui::{SheetApp, grid};@@ -100,3 +101,20 @@ fn a_quick_right_click_between_pixels_keeps_the_menu_open_on_retina() {     h.run_steps(4);     assert!(h.state().grid.context_menu.is_some(), "the menu stays open"); }++#[test]+/// Hovering a long command keeps both its size and rendered position fixed.+fn hovering_a_long_context_item_does_not_resize_the_menu() {+    let mut h = harness();+    quick_right_click(&mut h, "C5");+    let item = h.get_by_label("Filter by Selected Cell's Value").rect();+    let menu = h.state().grid.context_menu.unwrap();+    h.input_mut().events.push(Event::PointerMoved(item.center()));+    h.run_steps(2);++    let hovered_item = h.get_by_label("Filter by Selected Cell's Value").rect();+    assert_eq!(hovered_item.size(), item.size(), "hovering keeps the command's dimensions fixed");+    assert_eq!(hovered_item.min, item.min, "hovering keeps the rendered command position fixed");+    let menu_after = h.state().grid.context_menu.unwrap();+    assert_eq!(menu_after, menu, "hovering does not move the menu anchor");+}