From d1af798a9b32a01dd7b9292ec87605d86de71196 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 19 Apr 2023 15:27:51 +0200 Subject: [PATCH] Clear all keys and modifies on focus change (#2933) It is very easy for keys to become stuck when we alt-tab, or a save-dialog opens by Ctrl+S, etc. Therefore we new clear all the modifiers and down keys to avoid that. --- crates/eframe/src/epi.rs | 2 ++ crates/eframe/src/web/backend.rs | 5 +++-- crates/egui-winit/src/lib.rs | 9 ++++++--- crates/egui/src/context.rs | 2 +- crates/egui/src/data/input.rs | 21 +++++++++++++-------- crates/egui/src/input_state.rs | 25 ++++++++++++++++++++++++- crates/egui/src/response.rs | 2 +- 7 files changed, 50 insertions(+), 16 deletions(-) diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index f3b895ce..ba06562d 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -927,6 +927,8 @@ pub struct WindowInfo { pub maximized: bool, /// Is the window focused and able to receive input? + /// + /// This should be the same as [`egui::InputState::focused`]. pub focused: bool, /// Window inner size in egui points (logical pixels). diff --git a/crates/eframe/src/web/backend.rs b/crates/eframe/src/web/backend.rs index a7950b34..2f1fc3f7 100644 --- a/crates/eframe/src/web/backend.rs +++ b/crates/eframe/src/web/backend.rs @@ -31,9 +31,10 @@ impl WebInput { } } - pub fn on_web_page_focus_change(&mut self, has_focus: bool) { + pub fn on_web_page_focus_change(&mut self, focused: bool) { self.raw.modifiers = egui::Modifiers::default(); - self.raw.has_focus = has_focus; + self.raw.focused = focused; + self.raw.events.push(egui::Event::WindowFocused(focused)); self.latest_touch_pos = None; self.latest_touch_pos_id = None; } diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index e4508051..75c9028c 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -90,7 +90,7 @@ impl State { /// The returned `State` must not outlive the input `display_target`. pub fn new(display_target: &dyn HasRawDisplayHandle) -> Self { let egui_input = egui::RawInput { - has_focus: false, // winit will tell us when we have focus + focused: false, // winit will tell us when we have focus ..Default::default() }; @@ -314,11 +314,14 @@ impl State { consumed, } } - WindowEvent::Focused(has_focus) => { - self.egui_input.has_focus = *has_focus; + WindowEvent::Focused(focused) => { + self.egui_input.focused = *focused; // We will not be given a KeyboardInput event when the modifiers are released while // the window does not have focus. Unset all modifier state to be safe. self.egui_input.modifiers = egui::Modifiers::default(); + self.egui_input + .events + .push(egui::Event::WindowFocused(*focused)); EventResponse { repaint: true, consumed: false, diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 7225fb33..c3aa9922 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1139,7 +1139,7 @@ impl Context { { let state = self.frame_state_mut(|fs| fs.accesskit_state.take()); if let Some(state) = state { - let has_focus = self.input(|i| i.raw.has_focus); + let has_focus = self.input(|i| i.raw.focused); let root_id = crate::accesskit_root_id().accesskit_id(); let nodes = self.write(|ctx| { state diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 67c1853a..78183901 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -63,8 +63,10 @@ pub struct RawInput { /// drag-and-drop support using `eframe::NativeOptions`. pub dropped_files: Vec, - /// The window has the keyboard focus (i.e. is receiving key presses). - pub has_focus: bool, + /// The native window has the keyboard focus (i.e. is receiving key presses). + /// + /// False when the user alt-tab away from the application, for instance. + pub focused: bool, } impl Default for RawInput { @@ -79,7 +81,7 @@ impl Default for RawInput { events: vec![], hovered_files: Default::default(), dropped_files: Default::default(), - has_focus: true, // integrations opt into global focus tracking + focused: true, // integrations opt into global focus tracking } } } @@ -100,7 +102,7 @@ impl RawInput { events: std::mem::take(&mut self.events), hovered_files: self.hovered_files.clone(), dropped_files: std::mem::take(&mut self.dropped_files), - has_focus: self.has_focus, + focused: self.focused, } } @@ -116,7 +118,7 @@ impl RawInput { mut events, mut hovered_files, mut dropped_files, - has_focus, + focused, } = newer; self.screen_rect = screen_rect.or(self.screen_rect); @@ -128,7 +130,7 @@ impl RawInput { self.events.append(&mut events); self.hovered_files.append(&mut hovered_files); self.dropped_files.append(&mut dropped_files); - self.has_focus = has_focus; + self.focused = focused; } } @@ -294,6 +296,9 @@ pub enum Event { modifiers: Modifiers, }, + /// The native window gained or lost focused (e.g. the user clicked alt-tab). + WindowFocused(bool), + /// An assistive technology (e.g. screen reader) requested an action. #[cfg(feature = "accesskit")] AccessKitActionRequest(accesskit::ActionRequest), @@ -847,7 +852,7 @@ impl RawInput { events, hovered_files, dropped_files, - has_focus, + focused, } = self; ui.label(format!("screen_rect: {:?} points", screen_rect)); @@ -865,7 +870,7 @@ impl RawInput { ui.label(format!("modifiers: {:#?}", modifiers)); ui.label(format!("hovered_files: {}", hovered_files.len())); ui.label(format!("dropped_files: {}", dropped_files.len())); - ui.label(format!("has_focus: {}", has_focus)); + ui.label(format!("focused: {}", focused)); ui.scope(|ui| { ui.set_min_height(150.0); ui.label(format!("events: {:#?}", events)) diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index 12b13253..67cc0631 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -104,6 +104,11 @@ pub struct InputState { /// and will effectively slow down the animation when FPS drops below 10. pub stable_dt: f32, + /// The native window has the keyboard focus (i.e. is receiving key presses). + /// + /// False when the user alt-tab away from the application, for instance. + pub focused: bool, + /// Which modifier keys are down at the start of the frame? pub modifiers: Modifiers, @@ -129,6 +134,7 @@ impl Default for InputState { unstable_dt: 1.0 / 60.0, predicted_dt: 1.0 / 60.0, stable_dt: 1.0 / 60.0, + focused: false, modifiers: Default::default(), keys_down: Default::default(), events: Default::default(), @@ -189,6 +195,20 @@ impl InputState { } } + let mut modifiers = new.modifiers; + + let focused_changed = self.focused != new.focused + || new + .events + .iter() + .any(|e| matches!(e, Event::WindowFocused(_))); + if focused_changed { + // It is very common for keys to become stuck when we alt-tab, or a save-dialog opens by Ctrl+S. + // Therefore we clear all the modifiers and down keys here to avoid that. + modifiers = Default::default(); + keys_down = Default::default(); + } + InputState { pointer, touch_states: self.touch_states, @@ -201,7 +221,8 @@ impl InputState { unstable_dt, predicted_dt: new.predicted_dt, stable_dt, - modifiers: new.modifiers, + focused: new.focused, + modifiers, keys_down, events: new.events.clone(), // TODO(emilk): remove clone() and use raw.events raw: new, @@ -926,6 +947,7 @@ impl InputState { unstable_dt, predicted_dt, stable_dt, + focused, modifiers, keys_down, events, @@ -969,6 +991,7 @@ impl InputState { )); ui.label(format!("predicted_dt: {:.1} ms", 1e3 * predicted_dt)); ui.label(format!("stable_dt: {:.1} ms", 1e3 * stable_dt)); + ui.label(format!("focused: {}", focused)); ui.label(format!("modifiers: {:#?}", modifiers)); ui.label(format!("keys_down: {:?}", keys_down)); ui.scope(|ui| { diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index ac408823..bca858ef 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -233,7 +233,7 @@ impl Response { /// also has the keyboard focus. That makes this function suitable /// for style choices, e.g. a thicker border around focused widgets. pub fn has_focus(&self) -> bool { - self.ctx.input(|i| i.raw.has_focus) && self.ctx.memory(|mem| mem.has_focus(self.id)) + self.ctx.input(|i| i.focused) && self.ctx.memory(|mem| mem.has_focus(self.id)) } /// True if this widget has keyboard focus this frame, but didn't last frame.