bevy/crates/bevy_winit/src/state.rs

805 lines
30 KiB
Rust
Raw Normal View History

fix: upgrade to winit v0.30 (#13366) # Objective - Upgrade winit to v0.30 - Fixes https://github.com/bevyengine/bevy/issues/13331 ## Solution This is a rewrite/adaptation of the new trait system described and implemented in `winit` v0.30. ## Migration Guide The custom UserEvent is now renamed as WakeUp, used to wake up the loop if anything happens outside the app (a new [custom_user_event](https://github.com/bevyengine/bevy/pull/13366/files#diff-2de8c0a8d3028d0059a3d80ae31b2bbc1cde2595ce2d317ea378fe3e0cf6ef2d) shows this behavior. The internal `UpdateState` has been removed and replaced internally by the AppLifecycle. When changed, the AppLifecycle is sent as an event. The `UpdateMode` now accepts only two values: `Continuous` and `Reactive`, but the latter exposes 3 new properties to enable reactive to device, user or window events. The previous `UpdateMode::Reactive` is now equivalent to `UpdateMode::reactive()`, while `UpdateMode::ReactiveLowPower` to `UpdateMode::reactive_low_power()`. The `ApplicationLifecycle` has been renamed as `AppLifecycle`, and now contains the possible values of the application state inside the event loop: * `Idle`: the loop has not started yet * `Running` (previously called `Started`): the loop is running * `WillSuspend`: the loop is going to be suspended * `Suspended`: the loop is suspended * `WillResume`: the loop is going to be resumed Note: the `Resumed` state has been removed since the resumed app is just running. Finally, now that `winit` enables this, it extends the `WinitPlugin` to support custom events. ## Test platforms - [x] Windows - [x] MacOs - [x] Linux (x11) - [x] Linux (Wayland) - [x] Android - [x] iOS - [x] WASM/WebGPU - [x] WASM/WebGL2 ## Outstanding issues / regressions - [ ] iOS: build failed in CI - blocking, but may just be flakiness - [x] Cross-platform: when the window is maximised, changes in the scale factor don't apply, to make them apply one has to make the window smaller again. (Re-maximising keeps the updated scale factor) - non-blocking, but good to fix - [ ] Android: it's pretty easy to quickly open and close the app and then the music keeps playing when suspended. - non-blocking but worrying - [ ] Web: the application will hang when switching tabs - Not new, duplicate of https://github.com/bevyengine/bevy/issues/13486 - [ ] Cross-platform?: Screenshot failure, `ERROR present_frames: wgpu_core::present: No work has been submitted for this frame before` taking the first screenshot, but after pressing space - non-blocking, but good to fix --------- Co-authored-by: François <francois.mockers@vleue.com>
2024-06-03 13:06:48 +00:00
use approx::relative_eq;
use bevy_app::{App, AppExit, PluginsState};
use bevy_ecs::change_detection::{DetectChanges, NonSendMut, Res};
use bevy_ecs::entity::Entity;
use bevy_ecs::event::{EventWriter, ManualEventReader};
use bevy_ecs::prelude::*;
use bevy_ecs::system::SystemState;
use bevy_ecs::world::FromWorld;
use bevy_input::{
gestures::*,
flush key_input cache when Bevy loses focus (Adopted) (#13678) This was adopted from #12878. I rebased the changes resolved the following merge conflicts: - moved over the changes originally done in bevy_winit/src/lib.rs's `handle_winit_event` into bevy_winit/src/state.rs's `window_event` function - moved WinitEvent::KeyboardFocusLost event forwarding originally done in bevy_winit/src/winit_event.rs to the equivalent in bevy_winit/src/state.rs Tested this by following the modified keyboard_input example from the original PR. First, I verified I could reproduce the issue without the changes. Then, after applying the changes, I verified that when I Alt+Tabbed away from the running example that the log showed I released Alt and when I tabbed back it didn't behave like Alt was stuck. The following is from the original pull request by @gavlig # Objective This helps avoiding stuck key presses after switching from and back to Bevy window. Key press event gets stuck because window loses focus before receiving a key release event thus we end up with false positive in ButtonInput. ## Solution I saw two ways to fix this: 1. add bevy_window as dependency and read WindowFocus events 2. add a KeyboardFocusLost event specifically for this. I chose the latter because adding another dependency felt wrong, but if that is more preferable changing this pr won't be a problem. Also if someone sees another way please let me know. To test the bug use this small modification over examples/keyboard_input.rs: (it will work only if you have Alt-Tab combination for switching between windows in your OS, otherwise change AltLeft accordingly) ``` //! Demonstrates handling a key press/release. use bevy::{prelude::*, input::keyboard::KeyboardInput}; fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Update, keyboard_input_system) .run(); } /// This system prints 'Alt' key state fn keyboard_input_system(keyboard_input: Res<ButtonInput<KeyCode>>, mut keyboard_input_events: EventReader<KeyboardInput>) { for event in keyboard_input_events.read() { info!("{:?}", event); } if keyboard_input.pressed(KeyCode::AltLeft) { info!("'Alt' currently pressed"); } if keyboard_input.just_pressed(KeyCode::AltLeft) { info!("'Alt' just pressed"); } if keyboard_input.just_released(KeyCode::AltLeft) { info!("'Alt' just released"); } } ``` Here i made a quick video with demo of the fix: https://youtu.be/qTvUCk4IHvo In first part i press Alt and Alt+Tab to switch back and forth from example app, logs will indicate that too. In second part I applied fix and you'll see that Alt will no longer be pressed when window gets unfocused ## Migration Guide `WinitEvent` has a new enum variant: `WinitEvent::KeyboardFocusLost`. Co-authored-by: gavlig <gavlig@gmail.com>
2024-06-05 02:06:47 +00:00
keyboard::KeyboardFocusLost,
fix: upgrade to winit v0.30 (#13366) # Objective - Upgrade winit to v0.30 - Fixes https://github.com/bevyengine/bevy/issues/13331 ## Solution This is a rewrite/adaptation of the new trait system described and implemented in `winit` v0.30. ## Migration Guide The custom UserEvent is now renamed as WakeUp, used to wake up the loop if anything happens outside the app (a new [custom_user_event](https://github.com/bevyengine/bevy/pull/13366/files#diff-2de8c0a8d3028d0059a3d80ae31b2bbc1cde2595ce2d317ea378fe3e0cf6ef2d) shows this behavior. The internal `UpdateState` has been removed and replaced internally by the AppLifecycle. When changed, the AppLifecycle is sent as an event. The `UpdateMode` now accepts only two values: `Continuous` and `Reactive`, but the latter exposes 3 new properties to enable reactive to device, user or window events. The previous `UpdateMode::Reactive` is now equivalent to `UpdateMode::reactive()`, while `UpdateMode::ReactiveLowPower` to `UpdateMode::reactive_low_power()`. The `ApplicationLifecycle` has been renamed as `AppLifecycle`, and now contains the possible values of the application state inside the event loop: * `Idle`: the loop has not started yet * `Running` (previously called `Started`): the loop is running * `WillSuspend`: the loop is going to be suspended * `Suspended`: the loop is suspended * `WillResume`: the loop is going to be resumed Note: the `Resumed` state has been removed since the resumed app is just running. Finally, now that `winit` enables this, it extends the `WinitPlugin` to support custom events. ## Test platforms - [x] Windows - [x] MacOs - [x] Linux (x11) - [x] Linux (Wayland) - [x] Android - [x] iOS - [x] WASM/WebGPU - [x] WASM/WebGL2 ## Outstanding issues / regressions - [ ] iOS: build failed in CI - blocking, but may just be flakiness - [x] Cross-platform: when the window is maximised, changes in the scale factor don't apply, to make them apply one has to make the window smaller again. (Re-maximising keeps the updated scale factor) - non-blocking, but good to fix - [ ] Android: it's pretty easy to quickly open and close the app and then the music keeps playing when suspended. - non-blocking but worrying - [ ] Web: the application will hang when switching tabs - Not new, duplicate of https://github.com/bevyengine/bevy/issues/13486 - [ ] Cross-platform?: Screenshot failure, `ERROR present_frames: wgpu_core::present: No work has been submitted for this frame before` taking the first screenshot, but after pressing space - non-blocking, but good to fix --------- Co-authored-by: François <francois.mockers@vleue.com>
2024-06-03 13:06:48 +00:00
mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel},
};
use bevy_log::{error, trace, warn};
use bevy_math::{ivec2, DVec2, Vec2};
#[cfg(not(target_arch = "wasm32"))]
use bevy_tasks::tick_global_task_pools_on_main_thread;
use bevy_utils::Instant;
use std::marker::PhantomData;
use winit::application::ApplicationHandler;
use winit::dpi::PhysicalSize;
use winit::event;
use winit::event::{DeviceEvent, DeviceId, StartCause, WindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::window::WindowId;
#[allow(deprecated)]
use bevy_window::{
AppLifecycle, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, ReceivedCharacter,
RequestRedraw, Window, WindowBackendScaleFactorChanged, WindowCloseRequested, WindowDestroyed,
WindowFocused, WindowMoved, WindowOccluded, WindowResized, WindowScaleFactorChanged,
WindowThemeChanged,
};
#[cfg(target_os = "android")]
use bevy_window::{PrimaryWindow, RawHandleWrapper};
use crate::accessibility::AccessKitAdapters;
use crate::system::CachedWindow;
use crate::{
converters, create_windows, AppSendEvent, CreateWindowParams, UpdateMode, WinitEvent,
WinitSettings, WinitWindows,
};
/// Persistent state that is used to run the [`App`] according to the current
/// [`UpdateMode`].
struct WinitAppRunnerState<T: Event> {
/// The running app.
app: App,
/// Exit value once the loop is finished.
app_exit: Option<AppExit>,
/// Current update mode of the app.
update_mode: UpdateMode,
/// Is `true` if a new [`WindowEvent`] event has been received since the last update.
window_event_received: bool,
/// Is `true` if a new [`DeviceEvent`] event has been received since the last update.
device_event_received: bool,
/// Is `true` if a new [`T`] event has been received since the last update.
user_event_received: bool,
/// Is `true` if the app has requested a redraw since the last update.
redraw_requested: bool,
/// Is `true` if enough time has elapsed since `last_update` to run another update.
wait_elapsed: bool,
/// Number of "forced" updates to trigger on application start
startup_forced_updates: u32,
/// Current app lifecycle state.
lifecycle: AppLifecycle,
/// The previous app lifecycle state.
previous_lifecycle: AppLifecycle,
/// Winit events to send
winit_events: Vec<WinitEvent>,
_marker: PhantomData<T>,
event_writer_system_state: SystemState<(
EventWriter<'static, WindowResized>,
EventWriter<'static, WindowBackendScaleFactorChanged>,
EventWriter<'static, WindowScaleFactorChanged>,
NonSend<'static, WinitWindows>,
Query<'static, 'static, (&'static mut Window, &'static mut CachedWindow)>,
NonSendMut<'static, AccessKitAdapters>,
)>,
}
impl<T: Event> WinitAppRunnerState<T> {
fn new(mut app: App) -> Self {
app.add_event::<T>();
let event_writer_system_state: SystemState<(
EventWriter<WindowResized>,
EventWriter<WindowBackendScaleFactorChanged>,
EventWriter<WindowScaleFactorChanged>,
NonSend<WinitWindows>,
Query<(&mut Window, &mut CachedWindow)>,
NonSendMut<AccessKitAdapters>,
)> = SystemState::new(app.world_mut());
Self {
app,
lifecycle: AppLifecycle::Idle,
previous_lifecycle: AppLifecycle::Idle,
app_exit: None,
update_mode: UpdateMode::Continuous,
window_event_received: false,
device_event_received: false,
user_event_received: false,
redraw_requested: false,
wait_elapsed: false,
// 3 seems to be enough, 5 is a safe margin
startup_forced_updates: 5,
winit_events: Vec::new(),
_marker: PhantomData,
event_writer_system_state,
}
}
fn reset_on_update(&mut self) {
self.window_event_received = false;
self.device_event_received = false;
self.user_event_received = false;
}
fn world(&self) -> &World {
self.app.world()
}
fn world_mut(&mut self) -> &mut World {
self.app.world_mut()
}
}
impl<T: Event> ApplicationHandler<T> for WinitAppRunnerState<T> {
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
if event_loop.exiting() {
return;
}
#[cfg(feature = "trace")]
let _span = bevy_utils::tracing::info_span!("winit event_handler").entered();
if self.app.plugins_state() != PluginsState::Cleaned {
if self.app.plugins_state() != PluginsState::Ready {
#[cfg(not(target_arch = "wasm32"))]
tick_global_task_pools_on_main_thread();
} else {
self.app.finish();
self.app.cleanup();
}
self.redraw_requested = true;
}
self.wait_elapsed = match cause {
StartCause::WaitCancelled {
requested_resume: Some(resume),
..
} => {
// If the resume time is not after now, it means that at least the wait timeout
// has elapsed.
resume <= Instant::now()
}
_ => true,
};
}
fn resumed(&mut self, _event_loop: &ActiveEventLoop) {
// Mark the state as `WillResume`. This will let the schedule run one extra time
// when actually resuming the app
self.lifecycle = AppLifecycle::WillResume;
}
fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: T) {
self.user_event_received = true;
self.world_mut().send_event(event);
self.redraw_requested = true;
}
fn window_event(
&mut self,
_event_loop: &ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
) {
self.window_event_received = true;
let (
mut window_resized,
mut window_backend_scale_factor_changed,
mut window_scale_factor_changed,
winit_windows,
mut windows,
mut access_kit_adapters,
) = self.event_writer_system_state.get_mut(self.app.world_mut());
let Some(window) = winit_windows.get_window_entity(window_id) else {
warn!("Skipped event {event:?} for unknown winit Window Id {window_id:?}");
return;
};
let Ok((mut win, _)) = windows.get_mut(window) else {
warn!("Window {window:?} is missing `Window` component, skipping event {event:?}");
return;
};
// Allow AccessKit to respond to `WindowEvent`s before they reach
// the engine.
if let Some(adapter) = access_kit_adapters.get_mut(&window) {
if let Some(winit_window) = winit_windows.get_window(window) {
adapter.process_event(winit_window, &event);
}
}
match event {
WindowEvent::Resized(size) => {
react_to_resize(window, &mut win, size, &mut window_resized);
}
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
react_to_scale_factor_change(
window,
&mut win,
scale_factor,
&mut window_backend_scale_factor_changed,
&mut window_scale_factor_changed,
);
}
WindowEvent::CloseRequested => self.winit_events.send(WindowCloseRequested { window }),
Fix phantom key presses in winit on focus change (#13299) (#13696) # Objective Fixes #13299 On Linux/X11, changing focus into a winit window will produce winit KeyboardInput events with a "is_synthetic=true" flag that are not intended to be used. Bevy erroneously passes them on to the user, resulting in phantom key presses. ## Solution This patch properly filters out winit KeyboardInput events with "is_synthetic=true". For example, pressing Alt+Tab to focus a bevy winit window results in a permanently stuck Tab key until the user presses Tab once again to produce a winit KeyboardInput release event. The Tab key press event that causes this problem is "synthetic", should not be used according to the winit devs, and simply ignoring it fixes this problem. Synthetic key **releases** are still evaluated though, as they are essential for correct release key handling. For example, if the user binds the key combination Alt+1 to the action "move the window to workspace 1", places the bevy game in workspace 2, focuses the game and presses Alt+1, then the key release event for the "1" key will be synthetic. If we would filter out all synthetic keys, the bevy game would think that the 1 key remains pressed forever, until the user manually presses+releases the key again inside bevy. Reference: https://docs.rs/winit/0.30.0/winit/event/enum.WindowEvent.html#variant.KeyboardInput.field.is_synthetic Relevant discussion: https://github.com/rust-windowing/winit/issues/3543 ## Testing Tested with the "keyboard_input_events" example. Entering/exiting the window with various keys, as well as changing its workspace, produces the correct press/release events.
2024-06-17 14:49:16 +00:00
WindowEvent::KeyboardInput {
ref event,
is_synthetic,
..
} => {
// 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.
if event.state.is_pressed() {
if let Some(char) = &event.text {
let char = char.clone();
#[allow(deprecated)]
self.winit_events.send(ReceivedCharacter { window, char });
}
fix: upgrade to winit v0.30 (#13366) # Objective - Upgrade winit to v0.30 - Fixes https://github.com/bevyengine/bevy/issues/13331 ## Solution This is a rewrite/adaptation of the new trait system described and implemented in `winit` v0.30. ## Migration Guide The custom UserEvent is now renamed as WakeUp, used to wake up the loop if anything happens outside the app (a new [custom_user_event](https://github.com/bevyengine/bevy/pull/13366/files#diff-2de8c0a8d3028d0059a3d80ae31b2bbc1cde2595ce2d317ea378fe3e0cf6ef2d) shows this behavior. The internal `UpdateState` has been removed and replaced internally by the AppLifecycle. When changed, the AppLifecycle is sent as an event. The `UpdateMode` now accepts only two values: `Continuous` and `Reactive`, but the latter exposes 3 new properties to enable reactive to device, user or window events. The previous `UpdateMode::Reactive` is now equivalent to `UpdateMode::reactive()`, while `UpdateMode::ReactiveLowPower` to `UpdateMode::reactive_low_power()`. The `ApplicationLifecycle` has been renamed as `AppLifecycle`, and now contains the possible values of the application state inside the event loop: * `Idle`: the loop has not started yet * `Running` (previously called `Started`): the loop is running * `WillSuspend`: the loop is going to be suspended * `Suspended`: the loop is suspended * `WillResume`: the loop is going to be resumed Note: the `Resumed` state has been removed since the resumed app is just running. Finally, now that `winit` enables this, it extends the `WinitPlugin` to support custom events. ## Test platforms - [x] Windows - [x] MacOs - [x] Linux (x11) - [x] Linux (Wayland) - [x] Android - [x] iOS - [x] WASM/WebGPU - [x] WASM/WebGL2 ## Outstanding issues / regressions - [ ] iOS: build failed in CI - blocking, but may just be flakiness - [x] Cross-platform: when the window is maximised, changes in the scale factor don't apply, to make them apply one has to make the window smaller again. (Re-maximising keeps the updated scale factor) - non-blocking, but good to fix - [ ] Android: it's pretty easy to quickly open and close the app and then the music keeps playing when suspended. - non-blocking but worrying - [ ] Web: the application will hang when switching tabs - Not new, duplicate of https://github.com/bevyengine/bevy/issues/13486 - [ ] Cross-platform?: Screenshot failure, `ERROR present_frames: wgpu_core::present: No work has been submitted for this frame before` taking the first screenshot, but after pressing space - non-blocking, but good to fix --------- Co-authored-by: François <francois.mockers@vleue.com>
2024-06-03 13:06:48 +00:00
}
Fix phantom key presses in winit on focus change (#13299) (#13696) # Objective Fixes #13299 On Linux/X11, changing focus into a winit window will produce winit KeyboardInput events with a "is_synthetic=true" flag that are not intended to be used. Bevy erroneously passes them on to the user, resulting in phantom key presses. ## Solution This patch properly filters out winit KeyboardInput events with "is_synthetic=true". For example, pressing Alt+Tab to focus a bevy winit window results in a permanently stuck Tab key until the user presses Tab once again to produce a winit KeyboardInput release event. The Tab key press event that causes this problem is "synthetic", should not be used according to the winit devs, and simply ignoring it fixes this problem. Synthetic key **releases** are still evaluated though, as they are essential for correct release key handling. For example, if the user binds the key combination Alt+1 to the action "move the window to workspace 1", places the bevy game in workspace 2, focuses the game and presses Alt+1, then the key release event for the "1" key will be synthetic. If we would filter out all synthetic keys, the bevy game would think that the 1 key remains pressed forever, until the user manually presses+releases the key again inside bevy. Reference: https://docs.rs/winit/0.30.0/winit/event/enum.WindowEvent.html#variant.KeyboardInput.field.is_synthetic Relevant discussion: https://github.com/rust-windowing/winit/issues/3543 ## Testing Tested with the "keyboard_input_events" example. Entering/exiting the window with various keys, as well as changing its workspace, produces the correct press/release events.
2024-06-17 14:49:16 +00:00
self.winit_events
.send(converters::convert_keyboard_input(event, window));
fix: upgrade to winit v0.30 (#13366) # Objective - Upgrade winit to v0.30 - Fixes https://github.com/bevyengine/bevy/issues/13331 ## Solution This is a rewrite/adaptation of the new trait system described and implemented in `winit` v0.30. ## Migration Guide The custom UserEvent is now renamed as WakeUp, used to wake up the loop if anything happens outside the app (a new [custom_user_event](https://github.com/bevyengine/bevy/pull/13366/files#diff-2de8c0a8d3028d0059a3d80ae31b2bbc1cde2595ce2d317ea378fe3e0cf6ef2d) shows this behavior. The internal `UpdateState` has been removed and replaced internally by the AppLifecycle. When changed, the AppLifecycle is sent as an event. The `UpdateMode` now accepts only two values: `Continuous` and `Reactive`, but the latter exposes 3 new properties to enable reactive to device, user or window events. The previous `UpdateMode::Reactive` is now equivalent to `UpdateMode::reactive()`, while `UpdateMode::ReactiveLowPower` to `UpdateMode::reactive_low_power()`. The `ApplicationLifecycle` has been renamed as `AppLifecycle`, and now contains the possible values of the application state inside the event loop: * `Idle`: the loop has not started yet * `Running` (previously called `Started`): the loop is running * `WillSuspend`: the loop is going to be suspended * `Suspended`: the loop is suspended * `WillResume`: the loop is going to be resumed Note: the `Resumed` state has been removed since the resumed app is just running. Finally, now that `winit` enables this, it extends the `WinitPlugin` to support custom events. ## Test platforms - [x] Windows - [x] MacOs - [x] Linux (x11) - [x] Linux (Wayland) - [x] Android - [x] iOS - [x] WASM/WebGPU - [x] WASM/WebGL2 ## Outstanding issues / regressions - [ ] iOS: build failed in CI - blocking, but may just be flakiness - [x] Cross-platform: when the window is maximised, changes in the scale factor don't apply, to make them apply one has to make the window smaller again. (Re-maximising keeps the updated scale factor) - non-blocking, but good to fix - [ ] Android: it's pretty easy to quickly open and close the app and then the music keeps playing when suspended. - non-blocking but worrying - [ ] Web: the application will hang when switching tabs - Not new, duplicate of https://github.com/bevyengine/bevy/issues/13486 - [ ] Cross-platform?: Screenshot failure, `ERROR present_frames: wgpu_core::present: No work has been submitted for this frame before` taking the first screenshot, but after pressing space - non-blocking, but good to fix --------- Co-authored-by: François <francois.mockers@vleue.com>
2024-06-03 13:06:48 +00:00
}
}
WindowEvent::CursorMoved { position, .. } => {
let physical_position = DVec2::new(position.x, position.y);
let last_position = win.physical_cursor_position();
let delta = last_position.map(|last_pos| {
(physical_position.as_vec2() - last_pos) / win.resolution.scale_factor()
});
win.set_physical_cursor_position(Some(physical_position));
let position = (physical_position / win.resolution.scale_factor() as f64).as_vec2();
self.winit_events.send(CursorMoved {
window,
position,
delta,
});
}
WindowEvent::CursorEntered { .. } => {
self.winit_events.send(CursorEntered { window });
}
WindowEvent::CursorLeft { .. } => {
win.set_physical_cursor_position(None);
self.winit_events.send(CursorLeft { window });
}
WindowEvent::MouseInput { state, button, .. } => {
self.winit_events.send(MouseButtonInput {
button: converters::convert_mouse_button(button),
state: converters::convert_element_state(state),
window,
});
}
WindowEvent::PinchGesture { delta, .. } => {
self.winit_events.send(PinchGesture(delta as f32));
fix: upgrade to winit v0.30 (#13366) # Objective - Upgrade winit to v0.30 - Fixes https://github.com/bevyengine/bevy/issues/13331 ## Solution This is a rewrite/adaptation of the new trait system described and implemented in `winit` v0.30. ## Migration Guide The custom UserEvent is now renamed as WakeUp, used to wake up the loop if anything happens outside the app (a new [custom_user_event](https://github.com/bevyengine/bevy/pull/13366/files#diff-2de8c0a8d3028d0059a3d80ae31b2bbc1cde2595ce2d317ea378fe3e0cf6ef2d) shows this behavior. The internal `UpdateState` has been removed and replaced internally by the AppLifecycle. When changed, the AppLifecycle is sent as an event. The `UpdateMode` now accepts only two values: `Continuous` and `Reactive`, but the latter exposes 3 new properties to enable reactive to device, user or window events. The previous `UpdateMode::Reactive` is now equivalent to `UpdateMode::reactive()`, while `UpdateMode::ReactiveLowPower` to `UpdateMode::reactive_low_power()`. The `ApplicationLifecycle` has been renamed as `AppLifecycle`, and now contains the possible values of the application state inside the event loop: * `Idle`: the loop has not started yet * `Running` (previously called `Started`): the loop is running * `WillSuspend`: the loop is going to be suspended * `Suspended`: the loop is suspended * `WillResume`: the loop is going to be resumed Note: the `Resumed` state has been removed since the resumed app is just running. Finally, now that `winit` enables this, it extends the `WinitPlugin` to support custom events. ## Test platforms - [x] Windows - [x] MacOs - [x] Linux (x11) - [x] Linux (Wayland) - [x] Android - [x] iOS - [x] WASM/WebGPU - [x] WASM/WebGL2 ## Outstanding issues / regressions - [ ] iOS: build failed in CI - blocking, but may just be flakiness - [x] Cross-platform: when the window is maximised, changes in the scale factor don't apply, to make them apply one has to make the window smaller again. (Re-maximising keeps the updated scale factor) - non-blocking, but good to fix - [ ] Android: it's pretty easy to quickly open and close the app and then the music keeps playing when suspended. - non-blocking but worrying - [ ] Web: the application will hang when switching tabs - Not new, duplicate of https://github.com/bevyengine/bevy/issues/13486 - [ ] Cross-platform?: Screenshot failure, `ERROR present_frames: wgpu_core::present: No work has been submitted for this frame before` taking the first screenshot, but after pressing space - non-blocking, but good to fix --------- Co-authored-by: François <francois.mockers@vleue.com>
2024-06-03 13:06:48 +00:00
}
WindowEvent::RotationGesture { delta, .. } => {
self.winit_events.send(RotationGesture(delta));
}
WindowEvent::DoubleTapGesture { .. } => {
self.winit_events.send(DoubleTapGesture);
}
WindowEvent::PanGesture { delta, .. } => {
self.winit_events.send(PanGesture(Vec2 {
x: delta.x,
y: delta.y,
}));
fix: upgrade to winit v0.30 (#13366) # Objective - Upgrade winit to v0.30 - Fixes https://github.com/bevyengine/bevy/issues/13331 ## Solution This is a rewrite/adaptation of the new trait system described and implemented in `winit` v0.30. ## Migration Guide The custom UserEvent is now renamed as WakeUp, used to wake up the loop if anything happens outside the app (a new [custom_user_event](https://github.com/bevyengine/bevy/pull/13366/files#diff-2de8c0a8d3028d0059a3d80ae31b2bbc1cde2595ce2d317ea378fe3e0cf6ef2d) shows this behavior. The internal `UpdateState` has been removed and replaced internally by the AppLifecycle. When changed, the AppLifecycle is sent as an event. The `UpdateMode` now accepts only two values: `Continuous` and `Reactive`, but the latter exposes 3 new properties to enable reactive to device, user or window events. The previous `UpdateMode::Reactive` is now equivalent to `UpdateMode::reactive()`, while `UpdateMode::ReactiveLowPower` to `UpdateMode::reactive_low_power()`. The `ApplicationLifecycle` has been renamed as `AppLifecycle`, and now contains the possible values of the application state inside the event loop: * `Idle`: the loop has not started yet * `Running` (previously called `Started`): the loop is running * `WillSuspend`: the loop is going to be suspended * `Suspended`: the loop is suspended * `WillResume`: the loop is going to be resumed Note: the `Resumed` state has been removed since the resumed app is just running. Finally, now that `winit` enables this, it extends the `WinitPlugin` to support custom events. ## Test platforms - [x] Windows - [x] MacOs - [x] Linux (x11) - [x] Linux (Wayland) - [x] Android - [x] iOS - [x] WASM/WebGPU - [x] WASM/WebGL2 ## Outstanding issues / regressions - [ ] iOS: build failed in CI - blocking, but may just be flakiness - [x] Cross-platform: when the window is maximised, changes in the scale factor don't apply, to make them apply one has to make the window smaller again. (Re-maximising keeps the updated scale factor) - non-blocking, but good to fix - [ ] Android: it's pretty easy to quickly open and close the app and then the music keeps playing when suspended. - non-blocking but worrying - [ ] Web: the application will hang when switching tabs - Not new, duplicate of https://github.com/bevyengine/bevy/issues/13486 - [ ] Cross-platform?: Screenshot failure, `ERROR present_frames: wgpu_core::present: No work has been submitted for this frame before` taking the first screenshot, but after pressing space - non-blocking, but good to fix --------- Co-authored-by: François <francois.mockers@vleue.com>
2024-06-03 13:06:48 +00:00
}
WindowEvent::MouseWheel { delta, .. } => match delta {
event::MouseScrollDelta::LineDelta(x, y) => {
self.winit_events.send(MouseWheel {
unit: MouseScrollUnit::Line,
x,
y,
window,
});
}
event::MouseScrollDelta::PixelDelta(p) => {
self.winit_events.send(MouseWheel {
unit: MouseScrollUnit::Pixel,
x: p.x as f32,
y: p.y as f32,
window,
});
}
},
WindowEvent::Touch(touch) => {
let location = touch
.location
.to_logical(win.resolution.scale_factor() as f64);
self.winit_events
.send(converters::convert_touch_input(touch, location, window));
}
WindowEvent::Focused(focused) => {
win.focused = focused;
self.winit_events.send(WindowFocused { window, focused });
flush key_input cache when Bevy loses focus (Adopted) (#13678) This was adopted from #12878. I rebased the changes resolved the following merge conflicts: - moved over the changes originally done in bevy_winit/src/lib.rs's `handle_winit_event` into bevy_winit/src/state.rs's `window_event` function - moved WinitEvent::KeyboardFocusLost event forwarding originally done in bevy_winit/src/winit_event.rs to the equivalent in bevy_winit/src/state.rs Tested this by following the modified keyboard_input example from the original PR. First, I verified I could reproduce the issue without the changes. Then, after applying the changes, I verified that when I Alt+Tabbed away from the running example that the log showed I released Alt and when I tabbed back it didn't behave like Alt was stuck. The following is from the original pull request by @gavlig # Objective This helps avoiding stuck key presses after switching from and back to Bevy window. Key press event gets stuck because window loses focus before receiving a key release event thus we end up with false positive in ButtonInput. ## Solution I saw two ways to fix this: 1. add bevy_window as dependency and read WindowFocus events 2. add a KeyboardFocusLost event specifically for this. I chose the latter because adding another dependency felt wrong, but if that is more preferable changing this pr won't be a problem. Also if someone sees another way please let me know. To test the bug use this small modification over examples/keyboard_input.rs: (it will work only if you have Alt-Tab combination for switching between windows in your OS, otherwise change AltLeft accordingly) ``` //! Demonstrates handling a key press/release. use bevy::{prelude::*, input::keyboard::KeyboardInput}; fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Update, keyboard_input_system) .run(); } /// This system prints 'Alt' key state fn keyboard_input_system(keyboard_input: Res<ButtonInput<KeyCode>>, mut keyboard_input_events: EventReader<KeyboardInput>) { for event in keyboard_input_events.read() { info!("{:?}", event); } if keyboard_input.pressed(KeyCode::AltLeft) { info!("'Alt' currently pressed"); } if keyboard_input.just_pressed(KeyCode::AltLeft) { info!("'Alt' just pressed"); } if keyboard_input.just_released(KeyCode::AltLeft) { info!("'Alt' just released"); } } ``` Here i made a quick video with demo of the fix: https://youtu.be/qTvUCk4IHvo In first part i press Alt and Alt+Tab to switch back and forth from example app, logs will indicate that too. In second part I applied fix and you'll see that Alt will no longer be pressed when window gets unfocused ## Migration Guide `WinitEvent` has a new enum variant: `WinitEvent::KeyboardFocusLost`. Co-authored-by: gavlig <gavlig@gmail.com>
2024-06-05 02:06:47 +00:00
if !focused {
self.winit_events.send(KeyboardFocusLost);
}
fix: upgrade to winit v0.30 (#13366) # Objective - Upgrade winit to v0.30 - Fixes https://github.com/bevyengine/bevy/issues/13331 ## Solution This is a rewrite/adaptation of the new trait system described and implemented in `winit` v0.30. ## Migration Guide The custom UserEvent is now renamed as WakeUp, used to wake up the loop if anything happens outside the app (a new [custom_user_event](https://github.com/bevyengine/bevy/pull/13366/files#diff-2de8c0a8d3028d0059a3d80ae31b2bbc1cde2595ce2d317ea378fe3e0cf6ef2d) shows this behavior. The internal `UpdateState` has been removed and replaced internally by the AppLifecycle. When changed, the AppLifecycle is sent as an event. The `UpdateMode` now accepts only two values: `Continuous` and `Reactive`, but the latter exposes 3 new properties to enable reactive to device, user or window events. The previous `UpdateMode::Reactive` is now equivalent to `UpdateMode::reactive()`, while `UpdateMode::ReactiveLowPower` to `UpdateMode::reactive_low_power()`. The `ApplicationLifecycle` has been renamed as `AppLifecycle`, and now contains the possible values of the application state inside the event loop: * `Idle`: the loop has not started yet * `Running` (previously called `Started`): the loop is running * `WillSuspend`: the loop is going to be suspended * `Suspended`: the loop is suspended * `WillResume`: the loop is going to be resumed Note: the `Resumed` state has been removed since the resumed app is just running. Finally, now that `winit` enables this, it extends the `WinitPlugin` to support custom events. ## Test platforms - [x] Windows - [x] MacOs - [x] Linux (x11) - [x] Linux (Wayland) - [x] Android - [x] iOS - [x] WASM/WebGPU - [x] WASM/WebGL2 ## Outstanding issues / regressions - [ ] iOS: build failed in CI - blocking, but may just be flakiness - [x] Cross-platform: when the window is maximised, changes in the scale factor don't apply, to make them apply one has to make the window smaller again. (Re-maximising keeps the updated scale factor) - non-blocking, but good to fix - [ ] Android: it's pretty easy to quickly open and close the app and then the music keeps playing when suspended. - non-blocking but worrying - [ ] Web: the application will hang when switching tabs - Not new, duplicate of https://github.com/bevyengine/bevy/issues/13486 - [ ] Cross-platform?: Screenshot failure, `ERROR present_frames: wgpu_core::present: No work has been submitted for this frame before` taking the first screenshot, but after pressing space - non-blocking, but good to fix --------- Co-authored-by: François <francois.mockers@vleue.com>
2024-06-03 13:06:48 +00:00
}
WindowEvent::Occluded(occluded) => {
self.winit_events.send(WindowOccluded { window, occluded });
}
WindowEvent::DroppedFile(path_buf) => {
self.winit_events
.send(FileDragAndDrop::DroppedFile { window, path_buf });
}
WindowEvent::HoveredFile(path_buf) => {
self.winit_events
.send(FileDragAndDrop::HoveredFile { window, path_buf });
}
WindowEvent::HoveredFileCancelled => {
self.winit_events
.send(FileDragAndDrop::HoveredFileCanceled { window });
}
WindowEvent::Moved(position) => {
let position = ivec2(position.x, position.y);
win.position.set(position);
self.winit_events.send(WindowMoved { window, position });
}
WindowEvent::Ime(event) => match event {
event::Ime::Preedit(value, cursor) => {
self.winit_events.send(Ime::Preedit {
window,
value,
cursor,
});
}
event::Ime::Commit(value) => {
self.winit_events.send(Ime::Commit { window, value });
}
event::Ime::Enabled => {
self.winit_events.send(Ime::Enabled { window });
}
event::Ime::Disabled => {
self.winit_events.send(Ime::Disabled { window });
}
},
WindowEvent::ThemeChanged(theme) => {
self.winit_events.send(WindowThemeChanged {
window,
theme: converters::convert_winit_theme(theme),
});
}
WindowEvent::Destroyed => {
self.winit_events.send(WindowDestroyed { window });
}
_ => {}
}
let mut windows = self.world_mut().query::<(&mut Window, &mut CachedWindow)>();
if let Ok((window_component, mut cache)) = windows.get_mut(self.world_mut(), window) {
if window_component.is_changed() {
cache.window = window_component.clone();
}
}
}
fn device_event(
&mut self,
_event_loop: &ActiveEventLoop,
_device_id: DeviceId,
event: DeviceEvent,
) {
self.device_event_received = true;
if let DeviceEvent::MouseMotion { delta: (x, y) } = event {
let delta = Vec2::new(x as f32, y as f32);
self.winit_events.send(MouseMotion { delta });
}
}
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
// create any new windows
// (even if app did not update, some may have been created by plugin setup)
let mut create_window =
SystemState::<CreateWindowParams<Added<Window>>>::from_world(self.world_mut());
create_windows(event_loop, create_window.get_mut(self.world_mut()));
create_window.apply(self.world_mut());
let mut redraw_event_reader = ManualEventReader::<RequestRedraw>::default();
let mut focused_windows_state: SystemState<(Res<WinitSettings>, Query<(Entity, &Window)>)> =
SystemState::new(self.world_mut());
if let Some(app_redraw_events) = self.world().get_resource::<Events<RequestRedraw>>() {
if redraw_event_reader.read(app_redraw_events).last().is_some() {
self.redraw_requested = true;
}
}
let (config, windows) = focused_windows_state.get(self.world());
let focused = windows.iter().any(|(_, window)| window.focused);
let mut update_mode = config.update_mode(focused);
let mut should_update = self.should_update(update_mode);
if self.startup_forced_updates > 0 {
self.startup_forced_updates -= 1;
// Ensure that an update is triggered on the first iterations for app initialization
should_update = true;
}
if self.lifecycle == AppLifecycle::WillSuspend {
self.lifecycle = AppLifecycle::Suspended;
// Trigger one last update to enter the suspended state
should_update = true;
#[cfg(target_os = "android")]
{
// Remove the `RawHandleWrapper` from the primary window.
// This will trigger the surface destruction.
let mut query = self
.world_mut()
.query_filtered::<Entity, With<PrimaryWindow>>();
let entity = query.single(&self.world());
self.world_mut()
.entity_mut(entity)
.remove::<RawHandleWrapper>();
}
}
if self.lifecycle == AppLifecycle::WillResume {
self.lifecycle = AppLifecycle::Running;
// Trigger the update to enter the running state
should_update = true;
// Trigger the next redraw to refresh the screen immediately
self.redraw_requested = true;
#[cfg(target_os = "android")]
{
// Get windows that are cached but without raw handles. Those window were already created, but got their
// handle wrapper removed when the app was suspended.
let mut query = self.world_mut()
.query_filtered::<(Entity, &Window), (With<CachedWindow>, Without<bevy_window::RawHandleWrapper>)>();
if let Ok((entity, window)) = query.get_single(&self.world()) {
let window = window.clone();
let mut create_window =
SystemState::<CreateWindowParams>::from_world(self.world_mut());
let (
..,
mut winit_windows,
mut adapters,
mut handlers,
accessibility_requested,
) = create_window.get_mut(self.world_mut());
let winit_window = winit_windows.create_window(
event_loop,
entity,
&window,
&mut adapters,
&mut handlers,
&accessibility_requested,
);
let wrapper = RawHandleWrapper::new(winit_window).unwrap();
self.world_mut().entity_mut(entity).insert(wrapper);
}
}
}
// Notifies a lifecycle change
if self.lifecycle != self.previous_lifecycle {
self.previous_lifecycle = self.lifecycle;
self.winit_events.send(self.lifecycle);
}
// This is recorded before running app.update(), to run the next cycle after a correct timeout.
// If the cycle takes more than the wait timeout, it will be re-executed immediately.
let begin_frame_time = Instant::now();
if should_update {
// Not redrawing, but the timeout elapsed.
self.run_app_update();
// Running the app may have changed the WinitSettings resource, so we have to re-extract it.
let (config, windows) = focused_windows_state.get(self.world());
let focused = windows.iter().any(|(_, window)| window.focused);
update_mode = config.update_mode(focused);
}
// The update mode could have been changed, so we need to redraw and force an update
if update_mode != self.update_mode {
// Trigger the next redraw since we're changing the update mode
self.redraw_requested = true;
// Consider the wait as elapsed since it could have been cancelled by a user event
self.wait_elapsed = true;
self.update_mode = update_mode;
}
match update_mode {
UpdateMode::Continuous => {
// per winit's docs on [Window::is_visible](https://docs.rs/winit/latest/winit/window/struct.Window.html#method.is_visible),
// we cannot use the visibility to drive rendering on these platforms
// so we cannot discern whether to beneficially use `Poll` or not?
cfg_if::cfg_if! {
if #[cfg(not(any(
target_arch = "wasm32",
target_os = "android",
target_os = "ios",
all(target_os = "linux", any(feature = "x11", feature = "wayland"))
)))]
{
let winit_windows = self.world().non_send_resource::<WinitWindows>();
let visible = winit_windows.windows.iter().any(|(_, w)| {
w.is_visible().unwrap_or(false)
});
event_loop.set_control_flow(if visible {
ControlFlow::Wait
} else {
ControlFlow::Poll
});
}
else {
event_loop.set_control_flow(ControlFlow::Wait);
}
}
// Trigger the next redraw to refresh the screen immediately if waiting
if let ControlFlow::Wait = event_loop.control_flow() {
self.redraw_requested = true;
}
}
UpdateMode::Reactive { wait, .. } => {
// Set the next timeout, starting from the instant before running app.update() to avoid frame delays
if let Some(next) = begin_frame_time.checked_add(wait) {
if self.wait_elapsed {
event_loop.set_control_flow(ControlFlow::WaitUntil(next));
}
}
}
}
if self.redraw_requested && self.lifecycle != AppLifecycle::Suspended {
let winit_windows = self.world().non_send_resource::<WinitWindows>();
for window in winit_windows.windows.values() {
window.request_redraw();
}
self.redraw_requested = false;
}
if let Some(app_exit) = self.app.should_exit() {
self.app_exit = Some(app_exit);
event_loop.exit();
}
}
fn suspended(&mut self, _event_loop: &ActiveEventLoop) {
// Mark the state as `WillSuspend`. This will let the schedule run one last time
// before actually suspending to let the application react
self.lifecycle = AppLifecycle::WillSuspend;
}
fn exiting(&mut self, _event_loop: &ActiveEventLoop) {
let world = self.world_mut();
world.clear_all();
}
}
impl<T: Event> WinitAppRunnerState<T> {
fn should_update(&self, update_mode: UpdateMode) -> bool {
let handle_event = match update_mode {
UpdateMode::Continuous => {
self.wait_elapsed
|| self.user_event_received
|| self.window_event_received
|| self.device_event_received
}
UpdateMode::Reactive {
react_to_device_events,
react_to_user_events,
react_to_window_events,
..
} => {
self.wait_elapsed
|| (react_to_device_events && self.device_event_received)
|| (react_to_user_events && self.user_event_received)
|| (react_to_window_events && self.window_event_received)
}
};
handle_event && self.lifecycle.is_active()
}
fn run_app_update(&mut self) {
self.reset_on_update();
self.forward_winit_events();
if self.app.plugins_state() == PluginsState::Cleaned {
self.app.update();
}
}
fn forward_winit_events(&mut self) {
let buffered_events = self.winit_events.drain(..).collect::<Vec<_>>();
if buffered_events.is_empty() {
return;
}
let world = self.world_mut();
for winit_event in buffered_events.iter() {
match winit_event.clone() {
WinitEvent::AppLifecycle(e) => {
world.send_event(e);
}
WinitEvent::CursorEntered(e) => {
world.send_event(e);
}
WinitEvent::CursorLeft(e) => {
world.send_event(e);
}
WinitEvent::CursorMoved(e) => {
world.send_event(e);
}
WinitEvent::FileDragAndDrop(e) => {
world.send_event(e);
}
WinitEvent::Ime(e) => {
world.send_event(e);
}
WinitEvent::ReceivedCharacter(e) => {
world.send_event(e);
}
WinitEvent::RequestRedraw(e) => {
world.send_event(e);
}
WinitEvent::WindowBackendScaleFactorChanged(e) => {
world.send_event(e);
}
WinitEvent::WindowCloseRequested(e) => {
world.send_event(e);
}
WinitEvent::WindowCreated(e) => {
world.send_event(e);
}
WinitEvent::WindowDestroyed(e) => {
world.send_event(e);
}
WinitEvent::WindowFocused(e) => {
world.send_event(e);
}
WinitEvent::WindowMoved(e) => {
world.send_event(e);
}
WinitEvent::WindowOccluded(e) => {
world.send_event(e);
}
WinitEvent::WindowResized(e) => {
world.send_event(e);
}
WinitEvent::WindowScaleFactorChanged(e) => {
world.send_event(e);
}
WinitEvent::WindowThemeChanged(e) => {
world.send_event(e);
}
WinitEvent::MouseButtonInput(e) => {
world.send_event(e);
}
WinitEvent::MouseMotion(e) => {
world.send_event(e);
}
WinitEvent::MouseWheel(e) => {
world.send_event(e);
}
WinitEvent::PinchGesture(e) => {
world.send_event(e);
}
WinitEvent::RotationGesture(e) => {
world.send_event(e);
}
WinitEvent::DoubleTapGesture(e) => {
fix: upgrade to winit v0.30 (#13366) # Objective - Upgrade winit to v0.30 - Fixes https://github.com/bevyengine/bevy/issues/13331 ## Solution This is a rewrite/adaptation of the new trait system described and implemented in `winit` v0.30. ## Migration Guide The custom UserEvent is now renamed as WakeUp, used to wake up the loop if anything happens outside the app (a new [custom_user_event](https://github.com/bevyengine/bevy/pull/13366/files#diff-2de8c0a8d3028d0059a3d80ae31b2bbc1cde2595ce2d317ea378fe3e0cf6ef2d) shows this behavior. The internal `UpdateState` has been removed and replaced internally by the AppLifecycle. When changed, the AppLifecycle is sent as an event. The `UpdateMode` now accepts only two values: `Continuous` and `Reactive`, but the latter exposes 3 new properties to enable reactive to device, user or window events. The previous `UpdateMode::Reactive` is now equivalent to `UpdateMode::reactive()`, while `UpdateMode::ReactiveLowPower` to `UpdateMode::reactive_low_power()`. The `ApplicationLifecycle` has been renamed as `AppLifecycle`, and now contains the possible values of the application state inside the event loop: * `Idle`: the loop has not started yet * `Running` (previously called `Started`): the loop is running * `WillSuspend`: the loop is going to be suspended * `Suspended`: the loop is suspended * `WillResume`: the loop is going to be resumed Note: the `Resumed` state has been removed since the resumed app is just running. Finally, now that `winit` enables this, it extends the `WinitPlugin` to support custom events. ## Test platforms - [x] Windows - [x] MacOs - [x] Linux (x11) - [x] Linux (Wayland) - [x] Android - [x] iOS - [x] WASM/WebGPU - [x] WASM/WebGL2 ## Outstanding issues / regressions - [ ] iOS: build failed in CI - blocking, but may just be flakiness - [x] Cross-platform: when the window is maximised, changes in the scale factor don't apply, to make them apply one has to make the window smaller again. (Re-maximising keeps the updated scale factor) - non-blocking, but good to fix - [ ] Android: it's pretty easy to quickly open and close the app and then the music keeps playing when suspended. - non-blocking but worrying - [ ] Web: the application will hang when switching tabs - Not new, duplicate of https://github.com/bevyengine/bevy/issues/13486 - [ ] Cross-platform?: Screenshot failure, `ERROR present_frames: wgpu_core::present: No work has been submitted for this frame before` taking the first screenshot, but after pressing space - non-blocking, but good to fix --------- Co-authored-by: François <francois.mockers@vleue.com>
2024-06-03 13:06:48 +00:00
world.send_event(e);
}
WinitEvent::PanGesture(e) => {
fix: upgrade to winit v0.30 (#13366) # Objective - Upgrade winit to v0.30 - Fixes https://github.com/bevyengine/bevy/issues/13331 ## Solution This is a rewrite/adaptation of the new trait system described and implemented in `winit` v0.30. ## Migration Guide The custom UserEvent is now renamed as WakeUp, used to wake up the loop if anything happens outside the app (a new [custom_user_event](https://github.com/bevyengine/bevy/pull/13366/files#diff-2de8c0a8d3028d0059a3d80ae31b2bbc1cde2595ce2d317ea378fe3e0cf6ef2d) shows this behavior. The internal `UpdateState` has been removed and replaced internally by the AppLifecycle. When changed, the AppLifecycle is sent as an event. The `UpdateMode` now accepts only two values: `Continuous` and `Reactive`, but the latter exposes 3 new properties to enable reactive to device, user or window events. The previous `UpdateMode::Reactive` is now equivalent to `UpdateMode::reactive()`, while `UpdateMode::ReactiveLowPower` to `UpdateMode::reactive_low_power()`. The `ApplicationLifecycle` has been renamed as `AppLifecycle`, and now contains the possible values of the application state inside the event loop: * `Idle`: the loop has not started yet * `Running` (previously called `Started`): the loop is running * `WillSuspend`: the loop is going to be suspended * `Suspended`: the loop is suspended * `WillResume`: the loop is going to be resumed Note: the `Resumed` state has been removed since the resumed app is just running. Finally, now that `winit` enables this, it extends the `WinitPlugin` to support custom events. ## Test platforms - [x] Windows - [x] MacOs - [x] Linux (x11) - [x] Linux (Wayland) - [x] Android - [x] iOS - [x] WASM/WebGPU - [x] WASM/WebGL2 ## Outstanding issues / regressions - [ ] iOS: build failed in CI - blocking, but may just be flakiness - [x] Cross-platform: when the window is maximised, changes in the scale factor don't apply, to make them apply one has to make the window smaller again. (Re-maximising keeps the updated scale factor) - non-blocking, but good to fix - [ ] Android: it's pretty easy to quickly open and close the app and then the music keeps playing when suspended. - non-blocking but worrying - [ ] Web: the application will hang when switching tabs - Not new, duplicate of https://github.com/bevyengine/bevy/issues/13486 - [ ] Cross-platform?: Screenshot failure, `ERROR present_frames: wgpu_core::present: No work has been submitted for this frame before` taking the first screenshot, but after pressing space - non-blocking, but good to fix --------- Co-authored-by: François <francois.mockers@vleue.com>
2024-06-03 13:06:48 +00:00
world.send_event(e);
}
WinitEvent::TouchInput(e) => {
world.send_event(e);
}
WinitEvent::KeyboardInput(e) => {
world.send_event(e);
}
flush key_input cache when Bevy loses focus (Adopted) (#13678) This was adopted from #12878. I rebased the changes resolved the following merge conflicts: - moved over the changes originally done in bevy_winit/src/lib.rs's `handle_winit_event` into bevy_winit/src/state.rs's `window_event` function - moved WinitEvent::KeyboardFocusLost event forwarding originally done in bevy_winit/src/winit_event.rs to the equivalent in bevy_winit/src/state.rs Tested this by following the modified keyboard_input example from the original PR. First, I verified I could reproduce the issue without the changes. Then, after applying the changes, I verified that when I Alt+Tabbed away from the running example that the log showed I released Alt and when I tabbed back it didn't behave like Alt was stuck. The following is from the original pull request by @gavlig # Objective This helps avoiding stuck key presses after switching from and back to Bevy window. Key press event gets stuck because window loses focus before receiving a key release event thus we end up with false positive in ButtonInput. ## Solution I saw two ways to fix this: 1. add bevy_window as dependency and read WindowFocus events 2. add a KeyboardFocusLost event specifically for this. I chose the latter because adding another dependency felt wrong, but if that is more preferable changing this pr won't be a problem. Also if someone sees another way please let me know. To test the bug use this small modification over examples/keyboard_input.rs: (it will work only if you have Alt-Tab combination for switching between windows in your OS, otherwise change AltLeft accordingly) ``` //! Demonstrates handling a key press/release. use bevy::{prelude::*, input::keyboard::KeyboardInput}; fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Update, keyboard_input_system) .run(); } /// This system prints 'Alt' key state fn keyboard_input_system(keyboard_input: Res<ButtonInput<KeyCode>>, mut keyboard_input_events: EventReader<KeyboardInput>) { for event in keyboard_input_events.read() { info!("{:?}", event); } if keyboard_input.pressed(KeyCode::AltLeft) { info!("'Alt' currently pressed"); } if keyboard_input.just_pressed(KeyCode::AltLeft) { info!("'Alt' just pressed"); } if keyboard_input.just_released(KeyCode::AltLeft) { info!("'Alt' just released"); } } ``` Here i made a quick video with demo of the fix: https://youtu.be/qTvUCk4IHvo In first part i press Alt and Alt+Tab to switch back and forth from example app, logs will indicate that too. In second part I applied fix and you'll see that Alt will no longer be pressed when window gets unfocused ## Migration Guide `WinitEvent` has a new enum variant: `WinitEvent::KeyboardFocusLost`. Co-authored-by: gavlig <gavlig@gmail.com>
2024-06-05 02:06:47 +00:00
WinitEvent::KeyboardFocusLost(e) => {
world.send_event(e);
}
fix: upgrade to winit v0.30 (#13366) # Objective - Upgrade winit to v0.30 - Fixes https://github.com/bevyengine/bevy/issues/13331 ## Solution This is a rewrite/adaptation of the new trait system described and implemented in `winit` v0.30. ## Migration Guide The custom UserEvent is now renamed as WakeUp, used to wake up the loop if anything happens outside the app (a new [custom_user_event](https://github.com/bevyengine/bevy/pull/13366/files#diff-2de8c0a8d3028d0059a3d80ae31b2bbc1cde2595ce2d317ea378fe3e0cf6ef2d) shows this behavior. The internal `UpdateState` has been removed and replaced internally by the AppLifecycle. When changed, the AppLifecycle is sent as an event. The `UpdateMode` now accepts only two values: `Continuous` and `Reactive`, but the latter exposes 3 new properties to enable reactive to device, user or window events. The previous `UpdateMode::Reactive` is now equivalent to `UpdateMode::reactive()`, while `UpdateMode::ReactiveLowPower` to `UpdateMode::reactive_low_power()`. The `ApplicationLifecycle` has been renamed as `AppLifecycle`, and now contains the possible values of the application state inside the event loop: * `Idle`: the loop has not started yet * `Running` (previously called `Started`): the loop is running * `WillSuspend`: the loop is going to be suspended * `Suspended`: the loop is suspended * `WillResume`: the loop is going to be resumed Note: the `Resumed` state has been removed since the resumed app is just running. Finally, now that `winit` enables this, it extends the `WinitPlugin` to support custom events. ## Test platforms - [x] Windows - [x] MacOs - [x] Linux (x11) - [x] Linux (Wayland) - [x] Android - [x] iOS - [x] WASM/WebGPU - [x] WASM/WebGL2 ## Outstanding issues / regressions - [ ] iOS: build failed in CI - blocking, but may just be flakiness - [x] Cross-platform: when the window is maximised, changes in the scale factor don't apply, to make them apply one has to make the window smaller again. (Re-maximising keeps the updated scale factor) - non-blocking, but good to fix - [ ] Android: it's pretty easy to quickly open and close the app and then the music keeps playing when suspended. - non-blocking but worrying - [ ] Web: the application will hang when switching tabs - Not new, duplicate of https://github.com/bevyengine/bevy/issues/13486 - [ ] Cross-platform?: Screenshot failure, `ERROR present_frames: wgpu_core::present: No work has been submitted for this frame before` taking the first screenshot, but after pressing space - non-blocking, but good to fix --------- Co-authored-by: François <francois.mockers@vleue.com>
2024-06-03 13:06:48 +00:00
}
}
world
.resource_mut::<Events<WinitEvent>>()
.send_batch(buffered_events);
}
}
/// The default [`App::runner`] for the [`WinitPlugin`] plugin.
///
/// Overriding the app's [runner](bevy_app::App::runner) while using `WinitPlugin` will bypass the
/// `EventLoop`.
pub fn winit_runner<T: Event>(mut app: App) -> AppExit {
if app.plugins_state() == PluginsState::Ready {
app.finish();
app.cleanup();
}
let event_loop = app
.world_mut()
.remove_non_send_resource::<EventLoop<T>>()
.unwrap();
app.world_mut()
.insert_non_send_resource(event_loop.create_proxy());
let mut runner_state = WinitAppRunnerState::new(app);
trace!("starting winit event loop");
// TODO(clean): the winit docs mention using `spawn` instead of `run` on WASM.
if let Err(err) = event_loop.run_app(&mut runner_state) {
error!("winit event loop returned an error: {err}");
}
// If everything is working correctly then the event loop only exits after it's sent an exit code.
runner_state.app_exit.unwrap_or_else(|| {
error!("Failed to receive a app exit code! This is a bug");
AppExit::error()
})
}
pub(crate) fn react_to_resize(
window_entity: Entity,
window: &mut Mut<'_, Window>,
size: PhysicalSize<u32>,
window_resized: &mut EventWriter<WindowResized>,
) {
window
.resolution
.set_physical_resolution(size.width, size.height);
window_resized.send(WindowResized {
window: window_entity,
width: window.width(),
height: window.height(),
});
}
pub(crate) fn react_to_scale_factor_change(
window_entity: Entity,
window: &mut Mut<'_, Window>,
scale_factor: f64,
window_backend_scale_factor_changed: &mut EventWriter<WindowBackendScaleFactorChanged>,
window_scale_factor_changed: &mut EventWriter<WindowScaleFactorChanged>,
) {
window.resolution.set_scale_factor(scale_factor as f32);
window_backend_scale_factor_changed.send(WindowBackendScaleFactorChanged {
window: window_entity,
scale_factor,
});
let prior_factor = window.resolution.scale_factor();
let scale_factor_override = window.resolution.scale_factor_override();
if scale_factor_override.is_none() && !relative_eq!(scale_factor as f32, prior_factor) {
window_scale_factor_changed.send(WindowScaleFactorChanged {
window: window_entity,
scale_factor,
});
}
}