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::{
|
2024-06-04 12:44:25 +00:00
|
|
|
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, .. } => {
|
2024-06-04 12:44:25 +00:00
|
|
|
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, .. } => {
|
2024-06-04 12:44:25 +00:00
|
|
|
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);
|
|
|
|
}
|
2024-06-04 12:44:25 +00:00
|
|
|
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);
|
|
|
|
}
|
2024-06-04 12:44:25 +00:00
|
|
|
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,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|