Fix window spawning triggering ButtonInput<KeyCode>::just_pressed/just_released (#12372)

# Objective

Fix #12273

## Solution

– Only emit `KeyboardFocusLost` when the keyboard focus is lost
– ignore synthetic key releases too, not just key presses (as they're
already covered by `KeyboardFocusLost`)

---

## Changelog

### Fixed

- Don't trigger `ButtonInput<KeyCode>::just_pressed`/`just_released`
when spawning a window/focus moving between Bevy windows
This commit is contained in:
SpecificProtagonist 2024-09-30 20:24:36 +02:00 committed by GitHub
parent c2d193abd5
commit e7c6228e8b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 32 additions and 19 deletions

View file

@ -62,8 +62,7 @@ use {
///
/// `ButtonInput<KeyCode>` is tied to window focus. For example, if the user holds a button
/// while the window loses focus, [`ButtonInput::just_released`] will be triggered. Similarly if the window
/// regains focus, [`ButtonInput::just_pressed`] will be triggered. Currently this happens even if the
/// focus switches from one Bevy window to another (for example because a new window was just spawned).
/// regains focus, [`ButtonInput::just_pressed`] will be triggered.
///
/// `ButtonInput<GamepadButton>` is independent of window focus.
///

View file

@ -134,7 +134,7 @@ pub struct KeyboardFocusLost;
/// ## Differences
///
/// The main difference between the [`KeyboardInput`] event and the [`ButtonInput<KeyCode>`] resources is that
/// the latter have convenient functions such as [`ButtonInput::pressed`], [`ButtonInput::just_pressed`] and [`ButtonInput::just_released`].
/// the latter has convenient functions such as [`ButtonInput::pressed`], [`ButtonInput::just_pressed`] and [`ButtonInput::just_released`] and is window id agnostic.
pub fn keyboard_input_system(
mut key_input: ResMut<ButtonInput<KeyCode>>,
mut keyboard_input_events: EventReader<KeyboardInput>,

View file

@ -27,7 +27,7 @@ use bevy_ecs::prelude::*;
use bevy_window::{exit_on_all_closed, Window, WindowCreated};
pub use converters::convert_system_cursor_icon;
pub use state::{CursorSource, CustomCursorCache, CustomCursorCacheKey, PendingCursor};
use system::{changed_windows, despawn_windows};
use system::{changed_windows, check_keyboard_focus_lost, despawn_windows};
pub use system::{create_monitors, create_windows};
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
pub use winit::platform::web::CustomCursorExtWebSys;
@ -133,6 +133,7 @@ impl<T: Event> Plugin for WinitPlugin<T> {
// so we don't need to care about its ordering relative to `changed_windows`
changed_windows.ambiguous_with(exit_on_all_closed),
despawn_windows,
check_keyboard_focus_lost,
)
.chain(),
);

View file

@ -10,7 +10,6 @@ use bevy_ecs::{
};
use bevy_input::{
gestures::*,
keyboard::KeyboardFocusLost,
mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel},
};
use bevy_log::{error, trace, warn};
@ -267,18 +266,14 @@ impl<T: Event> ApplicationHandler<T> for WinitAppRunnerState<T> {
.send(WindowCloseRequested { window }),
WindowEvent::KeyboardInput {
ref event,
is_synthetic,
// On some platforms, winit sends "synthetic" key press events when the window
// gains or loses focus. These should not be handled, so we only process key
// events if they are not synthetic key presses.
is_synthetic: false,
..
} => {
// Winit sends "synthetic" key press events when the window gains focus. These
// should not be handled, so we only process key events if they are not synthetic
// key presses. "synthetic" key release events should still be handled though, for
// properly releasing keys when the window loses focus.
if !(is_synthetic && event.state.is_pressed()) {
// Process the keyboard input event, as long as it's not a synthetic key press.
self.bevy_window_events
.send(converters::convert_keyboard_input(event, window));
}
self.bevy_window_events
.send(converters::convert_keyboard_input(event, window));
}
WindowEvent::CursorMoved { position, .. } => {
let physical_position = DVec2::new(position.x, position.y);
@ -354,9 +349,6 @@ impl<T: Event> ApplicationHandler<T> for WinitAppRunnerState<T> {
win.focused = focused;
self.bevy_window_events
.send(WindowFocused { window, focused });
if !focused {
self.bevy_window_events.send(KeyboardFocusLost);
}
}
WindowEvent::Occluded(occluded) => {
self.bevy_window_events

View file

@ -6,10 +6,11 @@ use bevy_ecs::{
removal_detection::RemovedComponents,
system::{Local, NonSendMut, Query, SystemParamItem},
};
use bevy_input::keyboard::KeyboardFocusLost;
use bevy_utils::tracing::{error, info, warn};
use bevy_window::{
ClosingWindow, Monitor, PrimaryMonitor, RawHandleWrapper, VideoMode, Window, WindowClosed,
WindowClosing, WindowCreated, WindowMode, WindowResized, WindowWrapper,
WindowClosing, WindowCreated, WindowFocused, WindowMode, WindowResized, WindowWrapper,
};
use winit::{
@ -122,6 +123,26 @@ pub fn create_windows<F: QueryFilter + 'static>(
}
}
/// Check whether keyboard focus was lost. This is different from window
/// focus in that swapping between Bevy windows keeps window focus.
pub(crate) fn check_keyboard_focus_lost(
mut focus_events: EventReader<WindowFocused>,
mut keyboard_focus: EventWriter<KeyboardFocusLost>,
) {
let mut focus_lost = false;
let mut focus_gained = false;
for e in focus_events.read() {
if e.focused {
focus_gained = true;
} else {
focus_lost = true;
}
}
if focus_lost & !focus_gained {
keyboard_focus.send(KeyboardFocusLost);
}
}
/// Synchronize available monitors as reported by [`winit`] with [`Monitor`] entities in the world.
pub fn create_monitors(
event_loop: &ActiveEventLoop,