From d964c1cb661fb2a2ca38672d13d2259825c47cfa Mon Sep 17 00:00:00 2001 From: Hector Villarreal Date: Tue, 3 Nov 2026 03:39:43 -0600 Subject: [PATCH] #941: a waiting key tip request stays with the window that made it Addresses CodeRabbit review on #3250, "Keep deferred requests with their window": the request waiting for the next frame was shared across windows, so the root window, drawn first, could take a second window's Alt+I, S. It is now parked with each window's interface state and moved into the one-frame request when that window's ui begins. Test: legacy_keytips::a_waiting_key_tip_request_stays_with_its_window. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/app.rs | 8 ++++++-- crates/ui-egui/src/window_ui.rs | 4 ++++ crates/ui-egui/tests/it/legacy_keytips.rs | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/app.rs b/crates/ui-egui/src/app.rs index 762a8c704..94b6b01e7 100644 --- a/crates/ui-egui/src/app.rs +++ b/crates/ui-egui/src/app.rs @@ -108,8 +108,9 @@ impl SheetApp { self.ensure_text_width(ctx); // A keytip control request lives one frame: the ribbon consumes it during `ui`, and // anything that never reached a control (collapsed ribbon, tab switch) is dropped. One - // made with a switch to the control's tab waits for the frame that draws that tab. - self.ui.keytip_invoke = self.ui.keytip_invoke_next.take(); + // made with a switch to the control's tab waits, with its window, for the next `ui` of + // that window. + self.ui.keytip_invoke = None; // Floating pictures' textures go with their workbook, even when no sheet is painted. let open: Vec = self.session.documents().iter().map(|d| d.uid).collect(); floating_pictures::forget_closed(ctx, &open); @@ -195,6 +196,9 @@ impl SheetApp { let t0 = now_ms(); // A script may have switched windows since the last frame. self.follow_active_window(); + // This window's key tip request that waited for this frame (its tab or ribbon was just + // shown); any other window's request was dropped before it. + self.ui.keytip_invoke = self.ui.keytip_invoke_next.take(); text_box::before_ui(self, &ctx); // Another window came forward without going through a command (which settles drafts // first): the draft goes into its own window instead of floating over this one. If its diff --git a/crates/ui-egui/src/window_ui.rs b/crates/ui-egui/src/window_ui.rs index cef736ecc..93f2c699a 100644 --- a/crates/ui-egui/src/window_ui.rs +++ b/crates/ui-egui/src/window_ui.rs @@ -31,6 +31,8 @@ pub struct WindowUi { ribbon_tab: String, /// The status bar's Ready/Enter/Edit, which follows the window's cell draft. mode: gridcraft_engine::Mode, + /// A key tip's control request waiting for the frame that draws this window's ribbon. + keytip_invoke_next: Option<&'static str>, } impl WindowUi { @@ -70,6 +72,7 @@ impl SheetApp { open_switch_windows, ribbon_tab, mode, + keytip_invoke_next, } = w; std::mem::swap(&mut self.editor, editor); std::mem::swap(&mut self.text_box_editor, text_box_editor); @@ -86,6 +89,7 @@ impl SheetApp { std::mem::swap(&mut self.open_switch_windows, open_switch_windows); std::mem::swap(&mut self.ui.ribbon_tab, ribbon_tab); std::mem::swap(&mut self.session.mode, mode); + std::mem::swap(&mut self.ui.keytip_invoke_next, keytip_invoke_next); } /// The window whose interface state is in the per-window fields. diff --git a/crates/ui-egui/tests/it/legacy_keytips.rs b/crates/ui-egui/tests/it/legacy_keytips.rs index c2490f2bf..57deea0aa 100644 --- a/crates/ui-egui/tests/it/legacy_keytips.rs +++ b/crates/ui-egui/tests/it/legacy_keytips.rs @@ -50,3 +50,22 @@ fn alt_i_s_expands_a_collapsed_ribbon_on_the_insert_tab() { assert!(h.query_by_label("€").is_some(), "the Symbol gallery is open"); assert!(!h.state().session.journal.iter().any(|(id, _)| id == "insert.symbol")); } + +#[test] +fn a_waiting_key_tip_request_stays_with_its_window() { + // A request waiting for the frame that draws its control belongs to the window whose keys + // made it: another window drawn first must not take it. + let mut s = Session::new(); + s.new_workbook(); + let mut h = crate::common::os_harness(s, OperatingSystem::Nix); + h.run_steps(2); + let first = h.state().session.active_window().unwrap(); + h.state_mut().run("view.newWindow", json!({})).unwrap(); + let second = h.state().session.active_window().unwrap(); + h.state_mut().show_window(second); + h.state_mut().ui.keytip_invoke_next = Some("insert.symbol"); + h.state_mut().show_window(first); + assert_eq!(h.state().ui.keytip_invoke_next, None, "the first window doesn't see the second window's request"); + h.state_mut().show_window(second); + assert_eq!(h.state().ui.keytip_invoke_next, Some("insert.symbol")); +}