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.
This commit is contained in:
Emil Ernerfeldt 2023-04-19 15:27:51 +02:00 committed by GitHub
parent ede3ded977
commit d1af798a9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 50 additions and 16 deletions

View File

@ -927,6 +927,8 @@ pub struct WindowInfo {
pub maximized: bool, pub maximized: bool,
/// Is the window focused and able to receive input? /// Is the window focused and able to receive input?
///
/// This should be the same as [`egui::InputState::focused`].
pub focused: bool, pub focused: bool,
/// Window inner size in egui points (logical pixels). /// Window inner size in egui points (logical pixels).

View File

@ -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.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 = None;
self.latest_touch_pos_id = None; self.latest_touch_pos_id = None;
} }

View File

@ -90,7 +90,7 @@ impl State {
/// The returned `State` must not outlive the input `display_target`. /// The returned `State` must not outlive the input `display_target`.
pub fn new(display_target: &dyn HasRawDisplayHandle) -> Self { pub fn new(display_target: &dyn HasRawDisplayHandle) -> Self {
let egui_input = egui::RawInput { 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() ..Default::default()
}; };
@ -314,11 +314,14 @@ impl State {
consumed, consumed,
} }
} }
WindowEvent::Focused(has_focus) => { WindowEvent::Focused(focused) => {
self.egui_input.has_focus = *has_focus; self.egui_input.focused = *focused;
// We will not be given a KeyboardInput event when the modifiers are released while // 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. // the window does not have focus. Unset all modifier state to be safe.
self.egui_input.modifiers = egui::Modifiers::default(); self.egui_input.modifiers = egui::Modifiers::default();
self.egui_input
.events
.push(egui::Event::WindowFocused(*focused));
EventResponse { EventResponse {
repaint: true, repaint: true,
consumed: false, consumed: false,

View File

@ -1139,7 +1139,7 @@ impl Context {
{ {
let state = self.frame_state_mut(|fs| fs.accesskit_state.take()); let state = self.frame_state_mut(|fs| fs.accesskit_state.take());
if let Some(state) = state { 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 root_id = crate::accesskit_root_id().accesskit_id();
let nodes = self.write(|ctx| { let nodes = self.write(|ctx| {
state state

View File

@ -63,8 +63,10 @@ pub struct RawInput {
/// drag-and-drop support using `eframe::NativeOptions`. /// drag-and-drop support using `eframe::NativeOptions`.
pub dropped_files: Vec<DroppedFile>, pub dropped_files: Vec<DroppedFile>,
/// The window has the keyboard focus (i.e. is receiving key presses). /// The native window has the keyboard focus (i.e. is receiving key presses).
pub has_focus: bool, ///
/// False when the user alt-tab away from the application, for instance.
pub focused: bool,
} }
impl Default for RawInput { impl Default for RawInput {
@ -79,7 +81,7 @@ impl Default for RawInput {
events: vec![], events: vec![],
hovered_files: Default::default(), hovered_files: Default::default(),
dropped_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), events: std::mem::take(&mut self.events),
hovered_files: self.hovered_files.clone(), hovered_files: self.hovered_files.clone(),
dropped_files: std::mem::take(&mut self.dropped_files), 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 events,
mut hovered_files, mut hovered_files,
mut dropped_files, mut dropped_files,
has_focus, focused,
} = newer; } = newer;
self.screen_rect = screen_rect.or(self.screen_rect); self.screen_rect = screen_rect.or(self.screen_rect);
@ -128,7 +130,7 @@ impl RawInput {
self.events.append(&mut events); self.events.append(&mut events);
self.hovered_files.append(&mut hovered_files); self.hovered_files.append(&mut hovered_files);
self.dropped_files.append(&mut dropped_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, 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. /// An assistive technology (e.g. screen reader) requested an action.
#[cfg(feature = "accesskit")] #[cfg(feature = "accesskit")]
AccessKitActionRequest(accesskit::ActionRequest), AccessKitActionRequest(accesskit::ActionRequest),
@ -847,7 +852,7 @@ impl RawInput {
events, events,
hovered_files, hovered_files,
dropped_files, dropped_files,
has_focus, focused,
} = self; } = self;
ui.label(format!("screen_rect: {:?} points", screen_rect)); ui.label(format!("screen_rect: {:?} points", screen_rect));
@ -865,7 +870,7 @@ impl RawInput {
ui.label(format!("modifiers: {:#?}", modifiers)); ui.label(format!("modifiers: {:#?}", modifiers));
ui.label(format!("hovered_files: {}", hovered_files.len())); ui.label(format!("hovered_files: {}", hovered_files.len()));
ui.label(format!("dropped_files: {}", dropped_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.scope(|ui| {
ui.set_min_height(150.0); ui.set_min_height(150.0);
ui.label(format!("events: {:#?}", events)) ui.label(format!("events: {:#?}", events))

View File

@ -104,6 +104,11 @@ pub struct InputState {
/// and will effectively slow down the animation when FPS drops below 10. /// and will effectively slow down the animation when FPS drops below 10.
pub stable_dt: f32, 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? /// Which modifier keys are down at the start of the frame?
pub modifiers: Modifiers, pub modifiers: Modifiers,
@ -129,6 +134,7 @@ impl Default for InputState {
unstable_dt: 1.0 / 60.0, unstable_dt: 1.0 / 60.0,
predicted_dt: 1.0 / 60.0, predicted_dt: 1.0 / 60.0,
stable_dt: 1.0 / 60.0, stable_dt: 1.0 / 60.0,
focused: false,
modifiers: Default::default(), modifiers: Default::default(),
keys_down: Default::default(), keys_down: Default::default(),
events: 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 { InputState {
pointer, pointer,
touch_states: self.touch_states, touch_states: self.touch_states,
@ -201,7 +221,8 @@ impl InputState {
unstable_dt, unstable_dt,
predicted_dt: new.predicted_dt, predicted_dt: new.predicted_dt,
stable_dt, stable_dt,
modifiers: new.modifiers, focused: new.focused,
modifiers,
keys_down, keys_down,
events: new.events.clone(), // TODO(emilk): remove clone() and use raw.events events: new.events.clone(), // TODO(emilk): remove clone() and use raw.events
raw: new, raw: new,
@ -926,6 +947,7 @@ impl InputState {
unstable_dt, unstable_dt,
predicted_dt, predicted_dt,
stable_dt, stable_dt,
focused,
modifiers, modifiers,
keys_down, keys_down,
events, events,
@ -969,6 +991,7 @@ impl InputState {
)); ));
ui.label(format!("predicted_dt: {:.1} ms", 1e3 * predicted_dt)); ui.label(format!("predicted_dt: {:.1} ms", 1e3 * predicted_dt));
ui.label(format!("stable_dt: {:.1} ms", 1e3 * stable_dt)); ui.label(format!("stable_dt: {:.1} ms", 1e3 * stable_dt));
ui.label(format!("focused: {}", focused));
ui.label(format!("modifiers: {:#?}", modifiers)); ui.label(format!("modifiers: {:#?}", modifiers));
ui.label(format!("keys_down: {:?}", keys_down)); ui.label(format!("keys_down: {:?}", keys_down));
ui.scope(|ui| { ui.scope(|ui| {

View File

@ -233,7 +233,7 @@ impl Response {
/// also has the keyboard focus. That makes this function suitable /// also has the keyboard focus. That makes this function suitable
/// for style choices, e.g. a thicker border around focused widgets. /// for style choices, e.g. a thicker border around focused widgets.
pub fn has_focus(&self) -> bool { 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. /// True if this widget has keyboard focus this frame, but didn't last frame.