From 23793e49db67794a1ffd626528908a0035f027f4 Mon Sep 17 00:00:00 2001 From: Pietro Date: Tue, 14 May 2024 15:57:31 +0200 Subject: [PATCH 01/40] fix: migrating to winit 0.30 --- crates/bevy_a11y/Cargo.toml | 2 +- crates/bevy_winit/Cargo.toml | 6 +-- crates/bevy_winit/src/accessibility.rs | 41 ++++++++---------- crates/bevy_winit/src/lib.rs | 33 ++++++++------ crates/bevy_winit/src/system.rs | 6 +-- crates/bevy_winit/src/winit_windows.rs | 60 +++++++++++++------------- examples/window/low_power.rs | 3 +- 7 files changed, 76 insertions(+), 75 deletions(-) diff --git a/crates/bevy_a11y/Cargo.toml b/crates/bevy_a11y/Cargo.toml index 4ee262fa22974..e45b112bdc960 100644 --- a/crates/bevy_a11y/Cargo.toml +++ b/crates/bevy_a11y/Cargo.toml @@ -14,7 +14,7 @@ bevy_app = { path = "../bevy_app", version = "0.14.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.14.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" } -accesskit = "0.12" +accesskit = "0.14" [lints] workspace = true diff --git a/crates/bevy_winit/Cargo.toml b/crates/bevy_winit/Cargo.toml index 064a3afcdc124..8a3db084d9ab1 100644 --- a/crates/bevy_winit/Cargo.toml +++ b/crates/bevy_winit/Cargo.toml @@ -32,8 +32,8 @@ bevy_tasks = { path = "../bevy_tasks", version = "0.14.0-dev" } # other # feature rwh_06 refers to window_raw_handle@v0.6 -winit = { version = "0.29", default-features = false, features = ["rwh_06"] } -accesskit_winit = { version = "0.17", default-features = false, features = [ +winit = { version = "0.30", default-features = false, features = ["rwh_06"] } +accesskit_winit = { version = "0.20", default-features = false, features = [ "rwh_06", ] } approx = { version = "0.5", default-features = false } @@ -42,7 +42,7 @@ raw-window-handle = "0.6" serde = { version = "1.0", features = ["derive"], optional = true } [target.'cfg(target_os = "android")'.dependencies] -winit = { version = "0.29", default-features = false, features = [ +winit = { version = "0.30", default-features = false, features = [ "android-native-activity", "rwh_06", ] } diff --git a/crates/bevy_winit/src/accessibility.rs b/crates/bevy_winit/src/accessibility.rs index 1764f9cbaa828..663158bfa170c 100644 --- a/crates/bevy_winit/src/accessibility.rs +++ b/crates/bevy_winit/src/accessibility.rs @@ -8,7 +8,7 @@ use std::{ use accesskit_winit::Adapter; use bevy_a11y::{ accesskit::{ - ActionHandler, ActionRequest, NodeBuilder, NodeClassSet, NodeId, Role, Tree, TreeUpdate, + ActionHandler, ActionRequest, NodeBuilder, NodeId, Role, Tree, TreeUpdate, }, AccessibilityNode, AccessibilityRequested, AccessibilitySystem, Focus, }; @@ -24,6 +24,7 @@ use bevy_ecs::{ }; use bevy_hierarchy::{Children, Parent}; use bevy_window::{PrimaryWindow, Window, WindowClosed}; +use crate::EventLoopProxy; /// Maps window entities to their `AccessKit` [`Adapter`]s. #[derive(Default, Deref, DerefMut)] @@ -55,24 +56,18 @@ pub(crate) fn prepare_accessibility_for_window( ) { let mut root_builder = NodeBuilder::new(Role::Window); root_builder.set_name(name.into_boxed_str()); - let root = root_builder.build(&mut NodeClassSet::lock_global()); - - let accesskit_window_id = NodeId(entity.to_bits()); - let handler = WinitActionHandler::default(); - let adapter = Adapter::with_action_handler( - winit_window, - move || { - accessibility_requested.set(true); - TreeUpdate { - nodes: vec![(accesskit_window_id, root)], - tree: Some(Tree::new(accesskit_window_id)), - focus: accesskit_window_id, - } - }, - Box::new(handler.clone()), - ); - adapters.insert(entity, adapter); - handlers.insert(entity, handler); + let root = root_builder.build(); + // TODO: restore this + // + // let accesskit_window_id = NodeId(entity.to_bits()); + // let handler = WinitActionHandler::default(); + // let adapter = Adapter::with_mixed_handlers( + // winit_window, + // &handler, + // event_loop_proxy.clone(), + // ); + // adapters.insert(entity, adapter); + // handlers.insert(entity, handler); } fn window_closed( @@ -106,7 +101,7 @@ fn should_update_accessibility_nodes( } fn update_accessibility_nodes( - adapters: NonSend, + mut adapters: NonSendMut, focus: Res, primary_window: Query<(Entity, &Window), With>, nodes: Query<( @@ -120,7 +115,7 @@ fn update_accessibility_nodes( let Ok((primary_window_id, primary_window)) = primary_window.get_single() else { return; }; - let Some(adapter) = adapters.get(&primary_window_id) else { + let Some(adapter) = adapters.get_mut(&primary_window_id) else { return; }; if focus.is_changed() || !nodes.is_empty() { @@ -155,7 +150,7 @@ fn update_adapter( queue_node_for_update(entity, parent, &node_entities, &mut window_children); add_children_nodes(children, &node_entities, &mut node); let node_id = NodeId(entity.to_bits()); - let node = node.build(&mut NodeClassSet::lock_global()); + let node = node.build(); to_update.push((node_id, node)); } let mut window_node = NodeBuilder::new(Role::Window); @@ -164,7 +159,7 @@ fn update_adapter( window_node.set_name(title.into_boxed_str()); } window_node.set_children(window_children); - let window_node = window_node.build(&mut NodeClassSet::lock_global()); + let window_node = window_node.build(); let node_id = NodeId(primary_window_id.to_bits()); let window_update = (node_id, window_node); to_update.insert(0, window_update); diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 60794135e39aa..70f06ec3a1863 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -60,8 +60,9 @@ pub use winit::platform::android::activity as android_activity; use winit::event::StartCause; use winit::{ event::{self, DeviceEvent, Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop, EventLoopBuilder, EventLoopWindowTarget}, + event_loop::{ControlFlow, EventLoop, EventLoopBuilder}, }; +use winit::event_loop::ActiveEventLoop; use crate::accessibility::{AccessKitAdapters, AccessKitPlugin, WinitActionHandlers}; @@ -95,14 +96,14 @@ pub struct WinitPlugin { impl Plugin for WinitPlugin { fn build(&self, app: &mut App) { - let mut event_loop_builder = EventLoopBuilder::::with_user_event(); + let mut event_loop_builder = EventLoop::::with_user_event(); // linux check is needed because x11 might be enabled on other platforms. #[cfg(all(target_os = "linux", feature = "x11"))] { use winit::platform::x11::EventLoopBuilderExtX11; - // This allows a Bevy app to be started and ran outside of the main thread. + // This allows a Bevy app to be started and ran outside the main thread. // A use case for this is to allow external applications to spawn a thread // which runs a Bevy app without requiring the Bevy app to need to reside on // the main thread, which can be problematic. @@ -222,6 +223,8 @@ impl Default for WinitAppRunnerState { } } + + #[derive(PartialEq, Eq, Debug)] enum UpdateState { NotYetStarted, @@ -259,7 +262,10 @@ pub type CreateWindowParams<'w, 's, F = ()> = ( /// Use `NonSend` to receive this resource. pub type EventLoopProxy = winit::event_loop::EventLoopProxy; -type UserEvent = RequestRedraw; +#[derive(Debug, Clone, Copy)] +pub enum UserEvent { + WakeUp, +} /// The default [`App::runner`] for the [`WinitPlugin`] plugin. /// @@ -294,14 +300,15 @@ pub fn winit_runner(mut app: App) -> AppExit { EventWriter, NonSend, Query<(&mut Window, &mut CachedWindow)>, - NonSend, + NonSendMut, )> = SystemState::new(app.world_mut()); let mut create_window = SystemState::>>::from_world(app.world_mut()); let mut winit_events = Vec::default(); + // set up the event loop - let event_handler = move |event, event_loop: &EventLoopWindowTarget| { + let event_handler = move |event, event_loop: &ActiveEventLoop| { // The event loop is in the process of exiting, so don't deliver any new events if event_loop.exiting() { return; @@ -343,14 +350,14 @@ fn handle_winit_event( EventWriter, NonSend, Query<(&mut Window, &mut CachedWindow)>, - NonSend, + NonSendMut, )>, focused_windows_state: &mut SystemState<(Res, Query<(Entity, &Window)>)>, redraw_event_reader: &mut ManualEventReader, winit_events: &mut Vec, exit_notify: &SyncSender, event: Event, - event_loop: &EventLoopWindowTarget, + event_loop: &ActiveEventLoop, ) { #[cfg(feature = "trace")] let _span = bevy_utils::tracing::info_span!("winit event_handler").entered(); @@ -542,7 +549,7 @@ fn handle_winit_event( Event::WindowEvent { event, window_id, .. } => { - let (mut window_resized, winit_windows, mut windows, access_kit_adapters) = + let (mut window_resized, winit_windows, mut windows, mut access_kit_adapters) = event_writer_system_state.get_mut(app.world_mut()); let Some(window) = winit_windows.get_window_entity(window_id) else { @@ -557,7 +564,7 @@ fn handle_winit_event( // Allow AccessKit to respond to `WindowEvent`s before they reach // the engine. - if let Some(adapter) = access_kit_adapters.get(&window) { + 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); } @@ -611,10 +618,10 @@ fn handle_winit_event( window, }); } - WindowEvent::TouchpadMagnify { delta, .. } => { + WindowEvent::PinchGesture { delta, .. } => { winit_events.send(TouchpadMagnify(delta as f32)); } - WindowEvent::TouchpadRotate { delta, .. } => { + WindowEvent::RotationGesture { delta, .. } => { winit_events.send(TouchpadRotate(delta)); } WindowEvent::MouseWheel { delta, .. } => match delta { @@ -774,7 +781,7 @@ fn handle_winit_event( } runner_state.activity_state = UpdateState::WillResume; } - Event::UserEvent(RequestRedraw) => { + Event::UserEvent(UserEvent::WakeUp) => { runner_state.redraw_requested = true; } _ => (), diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index d871c0fb6e207..ba976f8540f51 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -14,8 +14,8 @@ use bevy_window::{ use winit::{ dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}, - event_loop::EventLoopWindowTarget, }; +use winit::event_loop::{ActiveEventLoop, EventLoop}; use bevy_ecs::query::With; #[cfg(target_arch = "wasm32")] @@ -36,7 +36,7 @@ use crate::{ /// default values. #[allow(clippy::too_many_arguments)] pub fn create_windows( - event_loop: &EventLoopWindowTarget, + event_loop: &ActiveEventLoop, ( mut commands, mut created_windows, @@ -202,7 +202,7 @@ pub(crate) fn changed_windows( } if window.cursor.icon != cache.window.cursor.icon { - winit_window.set_cursor_icon(converters::convert_cursor_icon(window.cursor.icon)); + winit_window.set_cursor(converters::convert_cursor_icon(window.cursor.icon)); } if window.cursor.grab_mode != cache.window.cursor.grab_mode { diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index ecea140f73fea..dcb85701591b0 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -7,10 +7,7 @@ use bevy_window::{ CursorGrabMode, Window, WindowMode, WindowPosition, WindowResolution, WindowWrapper, }; -use winit::{ - dpi::{LogicalSize, PhysicalPosition}, - monitor::MonitorHandle, -}; +use winit::{dpi::{LogicalSize, PhysicalPosition}, event_loop, monitor::MonitorHandle}; use crate::{ accessibility::{prepare_accessibility_for_window, AccessKitAdapters, WinitActionHandlers}, @@ -37,21 +34,22 @@ impl WinitWindows { /// Creates a `winit` window and associates it with our entity. pub fn create_window( &mut self, - event_loop: &winit::event_loop::EventLoopWindowTarget, + event_loop: &winit::event_loop::ActiveEventLoop, entity: Entity, window: &Window, adapters: &mut AccessKitAdapters, handlers: &mut WinitActionHandlers, accessibility_requested: &AccessibilityRequested, ) -> &WindowWrapper { - let mut winit_window_builder = winit::window::WindowBuilder::new(); + let mut winit_window_attributes = winit::window::Window::default_attributes(); + let mut winit_window_builder = event_loop.create_window(winit::window::Window::default_attributes()); // Due to a UIA limitation, winit windows need to be invisible for the // AccessKit adapter is initialized. - winit_window_builder = winit_window_builder.with_visible(false); + winit_window_attributes = winit_window_attributes.with_visible(false); - winit_window_builder = match window.mode { - WindowMode::BorderlessFullscreen => winit_window_builder.with_fullscreen(Some( + winit_window_attributes = match window.mode { + WindowMode::BorderlessFullscreen => winit_window_attributes.with_fullscreen(Some( winit::window::Fullscreen::Borderless(event_loop.primary_monitor()), )), mode @ (WindowMode::Fullscreen | WindowMode::SizedFullscreen) => { @@ -66,11 +64,11 @@ impl WinitWindows { _ => unreachable!(), }; - winit_window_builder + winit_window_attributes .with_fullscreen(Some(winit::window::Fullscreen::Exclusive(videomode))) } else { warn!("Could not determine primary monitor, ignoring exclusive fullscreen request for window {:?}", window.title); - winit_window_builder + winit_window_attributes } } WindowMode::Windowed => { @@ -81,19 +79,19 @@ impl WinitWindows { event_loop.primary_monitor(), None, ) { - winit_window_builder = winit_window_builder.with_position(position); + winit_window_attributes = winit_window_attributes.with_position(position); } let logical_size = LogicalSize::new(window.width(), window.height()); if let Some(sf) = window.resolution.scale_factor_override() { - winit_window_builder.with_inner_size(logical_size.to_physical::(sf.into())) + winit_window_attributes.with_inner_size(logical_size.to_physical::(sf.into())) } else { - winit_window_builder.with_inner_size(logical_size) + winit_window_attributes.with_inner_size(logical_size) } } }; - winit_window_builder = winit_window_builder + winit_window_attributes = winit_window_attributes .with_window_level(convert_window_level(window.window_level)) .with_theme(window.window_theme.map(convert_window_theme)) .with_resizable(window.resizable) @@ -105,7 +103,7 @@ impl WinitWindows { #[cfg(target_os = "windows")] { use winit::platform::windows::WindowBuilderExtWindows; - winit_window_builder = winit_window_builder.with_skip_taskbar(window.skip_taskbar); + winit_window_attributes = winit_window_attributes.with_skip_taskbar(window.skip_taskbar); } #[cfg(any( @@ -128,8 +126,8 @@ impl WinitWindows { ) ))] { - winit_window_builder = winit::platform::wayland::WindowBuilderExtWayland::with_name( - winit_window_builder, + winit_window_attributes = winit::platform::wayland::WindowBuilderExtWayland::with_name( + winit_window_attributes, name.clone(), "", ); @@ -146,17 +144,17 @@ impl WinitWindows { ) ))] { - winit_window_builder = winit::platform::x11::WindowBuilderExtX11::with_name( - winit_window_builder, + winit_window_attributes = winit::platform::x11::WindowBuilderExtX11::with_name( + winit_window_attributes, name.clone(), "", ); } #[cfg(target_os = "windows")] { - winit_window_builder = + winit_window_attributes = winit::platform::windows::WindowBuilderExtWindows::with_class_name( - winit_window_builder, + winit_window_attributes, name.clone(), ); } @@ -172,17 +170,17 @@ impl WinitWindows { height: constraints.max_height, }; - let winit_window_builder = + let winit_window_attributes = if constraints.max_width.is_finite() && constraints.max_height.is_finite() { - winit_window_builder + winit_window_attributes .with_min_inner_size(min_inner_size) .with_max_inner_size(max_inner_size) } else { - winit_window_builder.with_min_inner_size(min_inner_size) + winit_window_attributes.with_min_inner_size(min_inner_size) }; #[allow(unused_mut)] - let mut winit_window_builder = winit_window_builder.with_title(window.title.as_str()); + let mut winit_window_attributes = winit_window_attributes.with_title(window.title.as_str()); #[cfg(target_arch = "wasm32")] { @@ -197,18 +195,18 @@ impl WinitWindows { .expect("Cannot query for canvas element."); if let Some(canvas) = canvas { let canvas = canvas.dyn_into::().ok(); - winit_window_builder = winit_window_builder.with_canvas(canvas); + winit_window_attributes = winit_window_attributes.with_canvas(canvas); } else { panic!("Cannot find element: {}.", selector); } } - winit_window_builder = - winit_window_builder.with_prevent_default(window.prevent_default_event_handling); - winit_window_builder = winit_window_builder.with_append(true); + winit_window_attributes = + winit_window_attributes.with_prevent_default(window.prevent_default_event_handling); + winit_window_attributes = winit_window_attributes.with_append(true); } - let winit_window = winit_window_builder.build(event_loop).unwrap(); + let winit_window = event_loop.create_window(winit_window_attributes).unwrap(); let name = window.title.clone(); prepare_accessibility_for_window( &winit_window, diff --git a/examples/window/low_power.rs b/examples/window/low_power.rs index 9f00de2f840c0..0689b77ca71ec 100644 --- a/examples/window/low_power.rs +++ b/examples/window/low_power.rs @@ -9,6 +9,7 @@ use bevy::{ window::{PresentMode, RequestRedraw, WindowPlugin}, winit::{EventLoopProxy, WinitSettings}, }; +use bevy::winit::UserEvent; fn main() { App::new() @@ -87,7 +88,7 @@ fn update_winit( // when there are no inputs, so you send redraw requests while the animation is playing. // Note that in this example the RequestRedraw winit event will make the app run in the same // way as continuous - let _ = event_loop_proxy.send_event(RequestRedraw); + let _ = event_loop_proxy.send_event(UserEvent::WakeUp); WinitSettings::desktop_app() } }; From fac832b155ace386537d2aeaf0bf26c83188e52b Mon Sep 17 00:00:00 2001 From: Pietro Date: Tue, 14 May 2024 16:02:49 +0200 Subject: [PATCH 02/40] chore: removing deprecations --- crates/bevy_winit/src/lib.rs | 5 ++--- crates/bevy_winit/src/system.rs | 2 +- crates/bevy_winit/src/winit_windows.rs | 9 ++++----- examples/window/low_power.rs | 2 +- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 70f06ec3a1863..60b3cb9e0bc68 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -60,7 +60,7 @@ pub use winit::platform::android::activity as android_activity; use winit::event::StartCause; use winit::{ event::{self, DeviceEvent, Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop, EventLoopBuilder}, + event_loop::{ControlFlow, EventLoop}, }; use winit::event_loop::ActiveEventLoop; @@ -223,8 +223,6 @@ impl Default for WinitAppRunnerState { } } - - #[derive(PartialEq, Eq, Debug)] enum UpdateState { NotYetStarted, @@ -262,6 +260,7 @@ pub type CreateWindowParams<'w, 's, F = ()> = ( /// Use `NonSend` to receive this resource. pub type EventLoopProxy = winit::event_loop::EventLoopProxy; +/// The default event that can be used to wake the window loop #[derive(Debug, Clone, Copy)] pub enum UserEvent { WakeUp, diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index ba976f8540f51..58692ca78bd30 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -15,7 +15,7 @@ use bevy_window::{ use winit::{ dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}, }; -use winit::event_loop::{ActiveEventLoop, EventLoop}; +use winit::event_loop::{ActiveEventLoop}; use bevy_ecs::query::With; #[cfg(target_arch = "wasm32")] diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index dcb85701591b0..3271cc26336f6 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -7,7 +7,7 @@ use bevy_window::{ CursorGrabMode, Window, WindowMode, WindowPosition, WindowResolution, WindowWrapper, }; -use winit::{dpi::{LogicalSize, PhysicalPosition}, event_loop, monitor::MonitorHandle}; +use winit::{dpi::{LogicalSize, PhysicalPosition}, monitor::MonitorHandle}; use crate::{ accessibility::{prepare_accessibility_for_window, AccessKitAdapters, WinitActionHandlers}, @@ -42,7 +42,6 @@ impl WinitWindows { accessibility_requested: &AccessibilityRequested, ) -> &WindowWrapper { let mut winit_window_attributes = winit::window::Window::default_attributes(); - let mut winit_window_builder = event_loop.create_window(winit::window::Window::default_attributes()); // Due to a UIA limitation, winit windows need to be invisible for the // AccessKit adapter is initialized. @@ -278,7 +277,7 @@ pub fn get_fitting_videomode( monitor: &MonitorHandle, width: u32, height: u32, -) -> winit::monitor::VideoMode { +) -> winit::monitor::VideoModeHandle { let mut modes = monitor.video_modes().collect::>(); fn abs_diff(a: u32, b: u32) -> u32 { @@ -306,10 +305,10 @@ pub fn get_fitting_videomode( modes.first().unwrap().clone() } -/// Gets the "best" videomode from a monitor. +/// Gets the "best" video-mode handle from a monitor. /// /// The heuristic for "best" prioritizes width, height, and refresh rate in that order. -pub fn get_best_videomode(monitor: &MonitorHandle) -> winit::monitor::VideoMode { +pub fn get_best_videomode(monitor: &MonitorHandle) -> winit::monitor::VideoModeHandle { let mut modes = monitor.video_modes().collect::>(); modes.sort_by(|a, b| { use std::cmp::Ordering::*; diff --git a/examples/window/low_power.rs b/examples/window/low_power.rs index 0689b77ca71ec..98f096723a17e 100644 --- a/examples/window/low_power.rs +++ b/examples/window/low_power.rs @@ -6,7 +6,7 @@ use bevy::{ prelude::*, utils::Duration, - window::{PresentMode, RequestRedraw, WindowPlugin}, + window::{PresentMode, WindowPlugin}, winit::{EventLoopProxy, WinitSettings}, }; use bevy::winit::UserEvent; From 146a11736e6e1bfa6a5ee5719955a8d1be900733 Mon Sep 17 00:00:00 2001 From: Pietro Date: Tue, 14 May 2024 16:59:27 +0200 Subject: [PATCH 03/40] chore: split content in files --- crates/bevy_winit/src/accessibility.rs | 55 +- crates/bevy_winit/src/lib.rs | 727 ++----------------------- crates/bevy_winit/src/runner.rs | 621 +++++++++++++++++++++ crates/bevy_winit/src/state.rs | 73 +++ 4 files changed, 790 insertions(+), 686 deletions(-) create mode 100644 crates/bevy_winit/src/runner.rs create mode 100644 crates/bevy_winit/src/state.rs diff --git a/crates/bevy_winit/src/accessibility.rs b/crates/bevy_winit/src/accessibility.rs index 663158bfa170c..927825ceb4688 100644 --- a/crates/bevy_winit/src/accessibility.rs +++ b/crates/bevy_winit/src/accessibility.rs @@ -13,6 +13,7 @@ use bevy_a11y::{ AccessibilityNode, AccessibilityRequested, AccessibilitySystem, Focus, }; use bevy_a11y::{ActionRequest as ActionRequestWrapper, ManageAccessibilityUpdates}; +use bevy_a11y::accesskit::ActivationHandler; use bevy_app::{App, Plugin, PostUpdate}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::entity::EntityHashMap; @@ -44,7 +45,37 @@ impl ActionHandler for WinitActionHandler { requests.push_back(request); } } - +// +// struct UiState; +// impl UiState { +// fn new() -> Arc> { +// Arc::new(Mutex::new(Self)) +// } +// // +// // fn build_root(&mut self) -> Node { +// // let mut root_builder = NodeBuilder::new(Role::Window); +// // root_builder.set_name(name.into_boxed_str()); +// // let root = root_builder.build(); +// // +// // let mut builder = NodeBuilder::new(Role::Window); +// // builder.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); +// // if self.announcement.is_some() { +// // builder.push_child(ANNOUNCEMENT_ID); +// // } +// // builder.set_name(WINDOW_TITLE); +// // builder.build() +// // } +// } +// +// struct TearoffActivationHandler { +// state: Arc>, +// } +// +// impl ActivationHandler for TearoffActivationHandler { +// fn request_initial_tree(&mut self) -> Option { +// Some(self.state.lock().unwrap().build_initial_tree()) +// } +// } /// Prepares accessibility for a winit window. pub(crate) fn prepare_accessibility_for_window( winit_window: &winit::window::Window, @@ -54,18 +85,30 @@ pub(crate) fn prepare_accessibility_for_window( adapters: &mut AccessKitAdapters, handlers: &mut WinitActionHandlers, ) { - let mut root_builder = NodeBuilder::new(Role::Window); - root_builder.set_name(name.into_boxed_str()); - let root = root_builder.build(); - // TODO: restore this // + // let ui = Ui::new // let accesskit_window_id = NodeId(entity.to_bits()); - // let handler = WinitActionHandler::default(); + // let handler = TearoffActivationHandler { + // state: Arc::clone(Ui::new + // }; // let adapter = Adapter::with_mixed_handlers( // winit_window, // &handler, // event_loop_proxy.clone(), // ); + // TODO: restore this + // let adapter = Adapter::with_action_handler( + // winit_window, + // move || { + // accessibility_requested.set(true); + // TreeUpdate { + // nodes: vec![(accesskit_window_id, root)], + // tree: Some(Tree::new(accesskit_window_id)), + // focus: accesskit_window_id, + // } + // }, + // Box::new(handler.clone()), + // ); // adapters.insert(entity, adapter); // handlers.insert(entity, handler); } diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 60b3cb9e0bc68..0be40487daf3d 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -1,8 +1,8 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![forbid(unsafe_code)] #![doc( - html_logo_url = "https://bevyengine.org/assets/icon.png", - html_favicon_url = "https://bevyengine.org/assets/icon.png" +html_logo_url = "https://bevyengine.org/assets/icon.png", +html_favicon_url = "https://bevyengine.org/assets/icon.png" )] //! `bevy_winit` provides utilities to handle window creation and the eventloop through [`winit`] @@ -12,61 +12,41 @@ //! The app's [runner](bevy_app::App::runner) is set by `WinitPlugin` and handles the `winit` [`EventLoop`]. //! See `winit_runner` for details. -pub mod accessibility; -mod converters; -mod system; -mod winit_config; -pub mod winit_event; -mod winit_windows; - -use std::sync::mpsc::{sync_channel, SyncSender}; +use accesskit_winit::Event as AccessKitEvent; +use winit::event_loop::EventLoop; +#[cfg(target_os = "android")] +pub use winit::platform::android::activity as android_activity; -use approx::relative_eq; use bevy_a11y::AccessibilityRequested; -use bevy_utils::Instant; -pub use system::create_windows; -use system::{changed_windows, despawn_windows, CachedWindow}; -use winit::dpi::{LogicalSize, PhysicalSize}; -pub use winit_config::*; -pub use winit_event::*; -pub use winit_windows::*; - -use bevy_app::{App, AppExit, Last, Plugin, PluginsState}; -use bevy_ecs::event::ManualEventReader; +use bevy_app::{App, Last, Plugin}; use bevy_ecs::prelude::*; -use bevy_ecs::system::SystemState; -use bevy_input::{ - mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel}, - touchpad::{TouchpadMagnify, TouchpadRotate}, -}; -use bevy_math::{ivec2, DVec2, Vec2}; -#[cfg(not(target_arch = "wasm32"))] -use bevy_tasks::tick_global_task_pools_on_main_thread; -use bevy_utils::tracing::{error, trace, warn}; #[allow(deprecated)] use bevy_window::{ - exit_on_all_closed, ApplicationLifetime, CursorEntered, CursorLeft, CursorMoved, - FileDragAndDrop, Ime, ReceivedCharacter, RequestRedraw, Window, - WindowBackendScaleFactorChanged, WindowCloseRequested, WindowCreated, WindowDestroyed, - WindowFocused, WindowMoved, WindowOccluded, WindowResized, WindowScaleFactorChanged, - WindowThemeChanged, + exit_on_all_closed + , Window + , WindowCreated + , WindowResized + , }; #[cfg(target_os = "android")] use bevy_window::{PrimaryWindow, RawHandleWrapper}; - -#[cfg(target_os = "android")] -pub use winit::platform::android::activity as android_activity; - -use winit::event::StartCause; -use winit::{ - event::{self, DeviceEvent, Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, -}; -use winit::event_loop::ActiveEventLoop; +use system::{changed_windows, despawn_windows}; +pub use system::create_windows; +pub use winit_config::*; +pub use winit_event::*; +pub use winit_windows::*; use crate::accessibility::{AccessKitAdapters, AccessKitPlugin, WinitActionHandlers}; +use crate::runner::winit_runner; -use crate::converters::convert_winit_theme; +pub mod accessibility; +mod converters; +mod runner; +mod state; +mod system; +mod winit_config; +pub mod winit_event; +mod winit_windows; /// [`AndroidApp`] provides an interface to query the application state as well as monitor events /// (for example lifecycle and input events). @@ -173,72 +153,35 @@ impl Plugin for WinitPlugin { } } -trait AppSendEvent { - fn send(&mut self, event: impl Into); -} -impl AppSendEvent for Vec { - fn send(&mut self, event: impl Into) { - self.push(Into::::into(event)); - } -} - -/// Persistent state that is used to run the [`App`] according to the current -/// [`UpdateMode`]. -struct WinitAppRunnerState { - /// Current activity state of the app. - activity_state: UpdateState, - /// Current update mode of the app. - update_mode: UpdateMode, - /// Is `true` if a new [`WindowEvent`] has been received since the last update. - window_event_received: bool, - /// Is `true` if a new [`DeviceEvent`] has been received since the last update. - device_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, +/// The default event that can be used to wake the window loop +#[derive(Debug)] +pub enum UserEvent { + /// Wraps `accesskit` events + AccessKit(AccessKitEvent), + /// Wakes up the loop if in wait state + WakeUp, } -impl WinitAppRunnerState { - fn reset_on_update(&mut self) { - self.window_event_received = false; - self.device_event_received = false; +impl From for UserEvent { + fn from(evt: AccessKitEvent) -> Self { + UserEvent::AccessKit(evt) } } -impl Default for WinitAppRunnerState { - fn default() -> Self { - Self { - activity_state: UpdateState::NotYetStarted, - update_mode: UpdateMode::Continuous, - window_event_received: false, - device_event_received: false, - redraw_requested: false, - wait_elapsed: false, - // 3 seems to be enough, 5 is a safe margin - startup_forced_updates: 5, - } - } -} +/// The [`winit::event_loop::EventLoopProxy`] with the specific [`winit::event::Event::UserEvent`] used in the [`winit_runner`]. +/// +/// The `EventLoopProxy` can be used to request a redraw from outside bevy. +/// +/// Use `NonSend` to receive this resource. +pub type EventLoopProxy = winit::event_loop::EventLoopProxy; -#[derive(PartialEq, Eq, Debug)] -enum UpdateState { - NotYetStarted, - Active, - Suspended, - WillSuspend, - WillResume, +trait AppSendEvent { + fn send(&mut self, event: impl Into); } -impl UpdateState { - #[inline] - fn is_active(&self) -> bool { - match self { - Self::NotYetStarted | Self::Suspended => false, - Self::Active | Self::WillSuspend | Self::WillResume => true, - } +impl AppSendEvent for Vec { + fn send(&mut self, event: impl Into) { + self.push(Into::::into(event)); } } @@ -253,582 +196,6 @@ pub type CreateWindowParams<'w, 's, F = ()> = ( Res<'w, AccessibilityRequested>, ); -/// The [`winit::event_loop::EventLoopProxy`] with the specific [`winit::event::Event::UserEvent`] used in the [`winit_runner`]. -/// -/// The `EventLoopProxy` can be used to request a redraw from outside bevy. -/// -/// Use `NonSend` to receive this resource. -pub type EventLoopProxy = winit::event_loop::EventLoopProxy; - -/// The default event that can be used to wake the window loop -#[derive(Debug, Clone, Copy)] -pub enum UserEvent { - WakeUp, -} - -/// 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(mut app: App) -> AppExit { - if app.plugins_state() == PluginsState::Ready { - app.finish(); - app.cleanup(); - } - - let event_loop = app - .world_mut() - .remove_non_send_resource::>() - .unwrap(); - - app.world_mut() - .insert_non_send_resource(event_loop.create_proxy()); - - let mut runner_state = WinitAppRunnerState::default(); - - // Create a channel with a size of 1, since ideally only one exit code will be sent before exiting the app. - let (exit_sender, exit_receiver) = sync_channel(1); - - // prepare structures to access data in the world - let mut redraw_event_reader = ManualEventReader::::default(); - - let mut focused_windows_state: SystemState<(Res, Query<(Entity, &Window)>)> = - SystemState::new(app.world_mut()); - - let mut event_writer_system_state: SystemState<( - EventWriter, - NonSend, - Query<(&mut Window, &mut CachedWindow)>, - NonSendMut, - )> = SystemState::new(app.world_mut()); - - let mut create_window = - SystemState::>>::from_world(app.world_mut()); - let mut winit_events = Vec::default(); - - // set up the event loop - let event_handler = move |event, event_loop: &ActiveEventLoop| { - // The event loop is in the process of exiting, so don't deliver any new events - if event_loop.exiting() { - return; - } - - handle_winit_event( - &mut app, - &mut runner_state, - &mut create_window, - &mut event_writer_system_state, - &mut focused_windows_state, - &mut redraw_event_reader, - &mut winit_events, - &exit_sender, - event, - event_loop, - ); - }; - - 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(event_handler) { - error!("winit event loop returned an error: {err}"); - } - - // If everything is working correctly then the event loop only exits after it's sent a exit code. - exit_receiver - .try_recv() - .map_err(|err| error!("Failed to receive a app exit code! This is a bug. Reason: {err}")) - .unwrap_or(AppExit::error()) -} - -#[allow(clippy::too_many_arguments /* TODO: probs can reduce # of args */)] -fn handle_winit_event( - app: &mut App, - runner_state: &mut WinitAppRunnerState, - create_window: &mut SystemState>>, - event_writer_system_state: &mut SystemState<( - EventWriter, - NonSend, - Query<(&mut Window, &mut CachedWindow)>, - NonSendMut, - )>, - focused_windows_state: &mut SystemState<(Res, Query<(Entity, &Window)>)>, - redraw_event_reader: &mut ManualEventReader, - winit_events: &mut Vec, - exit_notify: &SyncSender, - event: Event, - event_loop: &ActiveEventLoop, -) { - #[cfg(feature = "trace")] - let _span = bevy_utils::tracing::info_span!("winit event_handler").entered(); - - if app.plugins_state() != PluginsState::Cleaned { - if app.plugins_state() != PluginsState::Ready { - #[cfg(not(target_arch = "wasm32"))] - tick_global_task_pools_on_main_thread(); - } else { - app.finish(); - app.cleanup(); - } - runner_state.redraw_requested = true; - } - - // create any new windows - // (even if app did not update, some may have been created by plugin setup) - create_windows(event_loop, create_window.get_mut(app.world_mut())); - create_window.apply(app.world_mut()); - - match event { - Event::AboutToWait => { - if let Some(app_redraw_events) = app.world().get_resource::>() { - if redraw_event_reader.read(app_redraw_events).last().is_some() { - runner_state.redraw_requested = true; - } - } - - let (config, windows) = focused_windows_state.get(app.world()); - let focused = windows.iter().any(|(_, window)| window.focused); - - let mut update_mode = config.update_mode(focused); - let mut should_update = should_update(runner_state, update_mode); - - if runner_state.startup_forced_updates > 0 { - runner_state.startup_forced_updates -= 1; - // Ensure that an update is triggered on the first iterations for app initialization - should_update = true; - } - - if runner_state.activity_state == UpdateState::WillSuspend { - runner_state.activity_state = UpdateState::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 = app - .world_mut() - .query_filtered::>(); - let entity = query.single(&app.world()); - app.world_mut() - .entity_mut(entity) - .remove::(); - } - } - - if runner_state.activity_state == UpdateState::WillResume { - runner_state.activity_state = UpdateState::Active; - // Trigger the update to enter the active state - should_update = true; - // Trigger the next redraw ro refresh the screen immediately - runner_state.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 = app - .world_mut() - .query_filtered::<(Entity, &Window), (With, Without)>(); - if let Ok((entity, window)) = query.get_single(&app.world()) { - let window = window.clone(); - - let ( - .., - mut winit_windows, - mut adapters, - mut handlers, - accessibility_requested, - ) = create_window.get_mut(app.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(); - - app.world_mut().entity_mut(entity).insert(wrapper); - } - } - } - - // 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. - run_app_update(runner_state, app, winit_events); - - // Running the app may have changed the WinitSettings resource, so we have to re-extract it. - let (config, windows) = focused_windows_state.get(app.world()); - let focused = windows.iter().any(|(_, window)| window.focused); - - update_mode = config.update_mode(focused); - } - - 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 = app.world().non_send_resource::(); - 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() { - runner_state.redraw_requested = true; - } - } - UpdateMode::Reactive { wait } | UpdateMode::ReactiveLowPower { 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 runner_state.wait_elapsed { - event_loop.set_control_flow(ControlFlow::WaitUntil(next)); - } - } - } - } - - if update_mode != runner_state.update_mode { - // Trigger the next redraw since we're changing the update mode - runner_state.redraw_requested = true; - runner_state.update_mode = update_mode; - } - - if runner_state.redraw_requested - && runner_state.activity_state != UpdateState::Suspended - { - let winit_windows = app.world().non_send_resource::(); - for window in winit_windows.windows.values() { - window.request_redraw(); - } - runner_state.redraw_requested = false; - } - } - Event::NewEvents(cause) => { - runner_state.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, - }; - } - Event::WindowEvent { - event, window_id, .. - } => { - let (mut window_resized, winit_windows, mut windows, mut access_kit_adapters) = - event_writer_system_state.get_mut(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); - } - } - - runner_state.window_event_received = true; - - match event { - WindowEvent::Resized(size) => { - react_to_resize(&mut win, size, &mut window_resized, window); - } - WindowEvent::CloseRequested => winit_events.send(WindowCloseRequested { window }), - WindowEvent::KeyboardInput { ref event, .. } => { - if event.state.is_pressed() { - if let Some(char) = &event.text { - let char = char.clone(); - #[allow(deprecated)] - winit_events.send(ReceivedCharacter { window, char }); - } - } - winit_events.send(converters::convert_keyboard_input(event, window)); - } - 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(); - winit_events.send(CursorMoved { - window, - position, - delta, - }); - } - WindowEvent::CursorEntered { .. } => { - winit_events.send(CursorEntered { window }); - } - WindowEvent::CursorLeft { .. } => { - win.set_physical_cursor_position(None); - winit_events.send(CursorLeft { window }); - } - WindowEvent::MouseInput { state, button, .. } => { - winit_events.send(MouseButtonInput { - button: converters::convert_mouse_button(button), - state: converters::convert_element_state(state), - window, - }); - } - WindowEvent::PinchGesture { delta, .. } => { - winit_events.send(TouchpadMagnify(delta as f32)); - } - WindowEvent::RotationGesture { delta, .. } => { - winit_events.send(TouchpadRotate(delta)); - } - WindowEvent::MouseWheel { delta, .. } => match delta { - event::MouseScrollDelta::LineDelta(x, y) => { - winit_events.send(MouseWheel { - unit: MouseScrollUnit::Line, - x, - y, - window, - }); - } - event::MouseScrollDelta::PixelDelta(p) => { - 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); - winit_events.send(converters::convert_touch_input(touch, location, window)); - } - WindowEvent::ScaleFactorChanged { - scale_factor, - mut inner_size_writer, - } => { - let prior_factor = win.resolution.scale_factor(); - win.resolution.set_scale_factor(scale_factor as f32); - // Note: this may be different from new_scale_factor if - // `scale_factor_override` is set to Some(thing) - let new_factor = win.resolution.scale_factor(); - - let mut new_inner_size = - PhysicalSize::new(win.physical_width(), win.physical_height()); - let scale_factor_override = win.resolution.scale_factor_override(); - if let Some(forced_factor) = scale_factor_override { - // This window is overriding the OS-suggested DPI, so its physical size - // should be set based on the overriding value. Its logical size already - // incorporates any resize constraints. - let maybe_new_inner_size = LogicalSize::new(win.width(), win.height()) - .to_physical::(forced_factor as f64); - if let Err(err) = inner_size_writer.request_inner_size(new_inner_size) { - warn!("Winit Failed to resize the window: {err}"); - } else { - new_inner_size = maybe_new_inner_size; - } - } - let new_logical_width = new_inner_size.width as f32 / new_factor; - let new_logical_height = new_inner_size.height as f32 / new_factor; - - let width_equal = relative_eq!(win.width(), new_logical_width); - let height_equal = relative_eq!(win.height(), new_logical_height); - win.resolution - .set_physical_resolution(new_inner_size.width, new_inner_size.height); - - winit_events.send(WindowBackendScaleFactorChanged { - window, - scale_factor, - }); - if scale_factor_override.is_none() && !relative_eq!(new_factor, prior_factor) { - winit_events.send(WindowScaleFactorChanged { - window, - scale_factor, - }); - } - - if !width_equal || !height_equal { - winit_events.send(WindowResized { - window, - width: new_logical_width, - height: new_logical_height, - }); - } - } - WindowEvent::Focused(focused) => { - win.focused = focused; - winit_events.send(WindowFocused { window, focused }); - } - WindowEvent::Occluded(occluded) => { - winit_events.send(WindowOccluded { window, occluded }); - } - WindowEvent::DroppedFile(path_buf) => { - winit_events.send(FileDragAndDrop::DroppedFile { window, path_buf }); - } - WindowEvent::HoveredFile(path_buf) => { - winit_events.send(FileDragAndDrop::HoveredFile { window, path_buf }); - } - WindowEvent::HoveredFileCancelled => { - winit_events.send(FileDragAndDrop::HoveredFileCanceled { window }); - } - WindowEvent::Moved(position) => { - let position = ivec2(position.x, position.y); - win.position.set(position); - winit_events.send(WindowMoved { window, position }); - } - WindowEvent::Ime(event) => match event { - event::Ime::Preedit(value, cursor) => { - winit_events.send(Ime::Preedit { - window, - value, - cursor, - }); - } - event::Ime::Commit(value) => { - winit_events.send(Ime::Commit { window, value }); - } - event::Ime::Enabled => { - winit_events.send(Ime::Enabled { window }); - } - event::Ime::Disabled => { - winit_events.send(Ime::Disabled { window }); - } - }, - WindowEvent::ThemeChanged(theme) => { - winit_events.send(WindowThemeChanged { - window, - theme: convert_winit_theme(theme), - }); - } - WindowEvent::Destroyed => { - winit_events.send(WindowDestroyed { window }); - } - WindowEvent::RedrawRequested => { - run_app_update(runner_state, app, winit_events); - } - _ => {} - } - - let mut windows = app.world_mut().query::<(&mut Window, &mut CachedWindow)>(); - if let Ok((window_component, mut cache)) = windows.get_mut(app.world_mut(), window) { - if window_component.is_changed() { - cache.window = window_component.clone(); - } - } - } - Event::DeviceEvent { event, .. } => { - runner_state.device_event_received = true; - if let DeviceEvent::MouseMotion { delta: (x, y) } = event { - let delta = Vec2::new(x as f32, y as f32); - winit_events.send(MouseMotion { delta }); - } - } - Event::Suspended => { - winit_events.send(ApplicationLifetime::Suspended); - // Mark the state as `WillSuspend`. This will let the schedule run one last time - // before actually suspending to let the application react - runner_state.activity_state = UpdateState::WillSuspend; - } - Event::Resumed => { - match runner_state.activity_state { - UpdateState::NotYetStarted => winit_events.send(ApplicationLifetime::Started), - _ => winit_events.send(ApplicationLifetime::Resumed), - } - runner_state.activity_state = UpdateState::WillResume; - } - Event::UserEvent(UserEvent::WakeUp) => { - runner_state.redraw_requested = true; - } - _ => (), - } - - if let Some(app_exit) = app.should_exit() { - if let Err(err) = exit_notify.try_send(app_exit) { - error!("Failed to send a app exit notification! This is a bug. Reason: {err}"); - }; - event_loop.exit(); - return; - } - - // We drain events after every received winit event in addition to on app update to ensure - // the work of pushing events into event queues is spread out over time in case the app becomes - // dormant for a long stretch. - forward_winit_events(winit_events, app); -} - -fn should_update(runner_state: &WinitAppRunnerState, update_mode: UpdateMode) -> bool { - let handle_event = match update_mode { - UpdateMode::Continuous | UpdateMode::Reactive { .. } => { - runner_state.wait_elapsed - || runner_state.window_event_received - || runner_state.device_event_received - } - UpdateMode::ReactiveLowPower { .. } => { - runner_state.wait_elapsed || runner_state.window_event_received - } - }; - - handle_event && runner_state.activity_state.is_active() -} - -fn run_app_update( - runner_state: &mut WinitAppRunnerState, - app: &mut App, - winit_events: &mut Vec, -) { - runner_state.reset_on_update(); - - forward_winit_events(winit_events, app); - - if app.plugins_state() == PluginsState::Cleaned { - app.update(); - } -} - fn react_to_resize( win: &mut Mut<'_, Window>, size: winit::dpi::PhysicalSize, diff --git a/crates/bevy_winit/src/runner.rs b/crates/bevy_winit/src/runner.rs new file mode 100644 index 0000000000000..0ef6bbf819c8b --- /dev/null +++ b/crates/bevy_winit/src/runner.rs @@ -0,0 +1,621 @@ +use std::sync::mpsc::{sync_channel, SyncSender}; + +use approx::relative_eq; +use winit::{ + event::{self, DeviceEvent, Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, +}; +use winit::dpi::{LogicalSize, PhysicalSize}; +use winit::event::StartCause; +use winit::event_loop::ActiveEventLoop; +#[cfg(target_os = "android")] +pub use winit::platform::android::activity as android_activity; + +use bevy_a11y::AccessibilityRequested; +use bevy_app::{App, AppExit, PluginsState}; +use bevy_ecs::event::ManualEventReader; +use bevy_ecs::prelude::*; +use bevy_ecs::system::SystemState; +use bevy_input::{ + mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel}, + touchpad::{TouchpadMagnify, TouchpadRotate}, +}; +use bevy_math::{DVec2, ivec2, Vec2}; +#[cfg(not(target_arch = "wasm32"))] +use bevy_tasks::tick_global_task_pools_on_main_thread; +use bevy_utils::Instant; +use bevy_utils::tracing::{error, trace, warn}; +#[allow(deprecated)] +use bevy_window::{ + ApplicationLifetime, CursorEntered, CursorLeft, CursorMoved, + FileDragAndDrop, Ime, ReceivedCharacter, RequestRedraw, Window, + WindowBackendScaleFactorChanged, WindowCloseRequested, WindowCreated, WindowDestroyed, + WindowFocused, WindowMoved, WindowOccluded, WindowResized, WindowScaleFactorChanged, + WindowThemeChanged, +}; +#[cfg(target_os = "android")] +use bevy_window::{PrimaryWindow, RawHandleWrapper}; + +use crate::{AppSendEvent, converters, react_to_resize}; +use crate::accessibility::{AccessKitAdapters, WinitActionHandlers}; +use crate::state::{UpdateState, WinitAppRunnerState}; +use crate::system::{CachedWindow}; +pub use crate::system::create_windows; +use crate::UserEvent; +pub use crate::winit_config::*; +pub use crate::winit_event::*; +pub use crate::winit_windows::*; + +/// The parameters of the [`create_windows`] system. +pub type CreateWindowParams<'w, 's, F = ()> = ( + Commands<'w, 's>, + Query<'w, 's, (Entity, &'static mut Window), F>, + EventWriter<'w, WindowCreated>, + NonSendMut<'w, WinitWindows>, + NonSendMut<'w, AccessKitAdapters>, + ResMut<'w, WinitActionHandlers>, + Res<'w, AccessibilityRequested>, +); + +/// 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(mut app: App) -> AppExit { + if app.plugins_state() == PluginsState::Ready { + app.finish(); + app.cleanup(); + } + + let event_loop = app + .world_mut() + .remove_non_send_resource::>() + .unwrap(); + + app.world_mut() + .insert_non_send_resource(event_loop.create_proxy()); + + let mut runner_state = WinitAppRunnerState::default(); + + // Create a channel with a size of 1, since ideally only one exit code will be sent before exiting the app. + let (exit_sender, exit_receiver) = sync_channel(1); + + // prepare structures to access data in the world + let mut redraw_event_reader = ManualEventReader::::default(); + + let mut focused_windows_state: SystemState<(Res, Query<(Entity, &Window)>)> = + SystemState::new(app.world_mut()); + + let mut event_writer_system_state: SystemState<( + EventWriter, + NonSend, + Query<(&mut Window, &mut CachedWindow)>, + NonSendMut, + )> = SystemState::new(app.world_mut()); + + let mut create_window = + SystemState::>>::from_world(app.world_mut()); + let mut winit_events = Vec::default(); + + // set up the event loop + let event_handler = move |event, event_loop: &ActiveEventLoop| { + // The event loop is in the process of exiting, so don't deliver any new events + if event_loop.exiting() { + return; + } + + handle_winit_event( + &mut app, + &mut runner_state, + &mut create_window, + &mut event_writer_system_state, + &mut focused_windows_state, + &mut redraw_event_reader, + &mut winit_events, + &exit_sender, + event, + event_loop, + ); + }; + + 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(event_handler) { + error!("winit event loop returned an error: {err}"); + } + + // If everything is working correctly then the event loop only exits after it's sent a exit code. + exit_receiver + .try_recv() + .map_err(|err| error!("Failed to receive a app exit code! This is a bug. Reason: {err}")) + .unwrap_or(AppExit::error()) +} + +#[allow(clippy::too_many_arguments /* TODO: probs can reduce # of args */)] +fn handle_winit_event( + app: &mut App, + runner_state: &mut WinitAppRunnerState, + create_window: &mut SystemState>>, + event_writer_system_state: &mut SystemState<( + EventWriter, + NonSend, + Query<(&mut Window, &mut CachedWindow)>, + NonSendMut, + )>, + focused_windows_state: &mut SystemState<(Res, Query<(Entity, &Window)>)>, + redraw_event_reader: &mut ManualEventReader, + winit_events: &mut Vec, + exit_notify: &SyncSender, + event: Event, + event_loop: &ActiveEventLoop, +) { + #[cfg(feature = "trace")] + let _span = bevy_utils::tracing::info_span!("winit event_handler").entered(); + + if app.plugins_state() != PluginsState::Cleaned { + if app.plugins_state() != PluginsState::Ready { + #[cfg(not(target_arch = "wasm32"))] + tick_global_task_pools_on_main_thread(); + } else { + app.finish(); + app.cleanup(); + } + runner_state.redraw_requested = true; + } + + // create any new windows + // (even if app did not update, some may have been created by plugin setup) + create_windows(event_loop, create_window.get_mut(app.world_mut())); + create_window.apply(app.world_mut()); + + match event { + Event::AboutToWait => { + if let Some(app_redraw_events) = app.world().get_resource::>() { + if redraw_event_reader.read(app_redraw_events).last().is_some() { + runner_state.redraw_requested = true; + } + } + + let (config, windows) = focused_windows_state.get(app.world()); + let focused = windows.iter().any(|(_, window)| window.focused); + + let mut update_mode = config.update_mode(focused); + let mut should_update = should_update(runner_state, update_mode); + + if runner_state.startup_forced_updates > 0 { + runner_state.startup_forced_updates -= 1; + // Ensure that an update is triggered on the first iterations for app initialization + should_update = true; + } + + if runner_state.activity_state == UpdateState::WillSuspend { + runner_state.activity_state = UpdateState::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 = app + .world_mut() + .query_filtered::>(); + let entity = query.single(&app.world()); + app.world_mut() + .entity_mut(entity) + .remove::(); + } + } + + if runner_state.activity_state == UpdateState::WillResume { + runner_state.activity_state = UpdateState::Active; + // Trigger the update to enter the active state + should_update = true; + // Trigger the next redraw ro refresh the screen immediately + runner_state.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 = app + .world_mut() + .query_filtered::<(Entity, &Window), (With, Without)>(); + if let Ok((entity, window)) = query.get_single(&app.world()) { + let window = window.clone(); + + let ( + .., + mut winit_windows, + mut adapters, + mut handlers, + accessibility_requested, + ) = create_window.get_mut(app.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(); + + app.world_mut().entity_mut(entity).insert(wrapper); + } + } + } + + // 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. + run_app_update(runner_state, app, winit_events); + + // Running the app may have changed the WinitSettings resource, so we have to re-extract it. + let (config, windows) = focused_windows_state.get(app.world()); + let focused = windows.iter().any(|(_, window)| window.focused); + + update_mode = config.update_mode(focused); + } + + 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 = app.world().non_send_resource::(); + 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() { + runner_state.redraw_requested = true; + } + } + UpdateMode::Reactive { wait } | UpdateMode::ReactiveLowPower { 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 runner_state.wait_elapsed { + event_loop.set_control_flow(ControlFlow::WaitUntil(next)); + } + } + } + } + + if update_mode != runner_state.update_mode { + // Trigger the next redraw since we're changing the update mode + runner_state.redraw_requested = true; + runner_state.update_mode = update_mode; + } + + if runner_state.redraw_requested + && runner_state.activity_state != UpdateState::Suspended + { + let winit_windows = app.world().non_send_resource::(); + for window in winit_windows.windows.values() { + window.request_redraw(); + } + runner_state.redraw_requested = false; + } + } + Event::NewEvents(cause) => { + runner_state.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, + }; + } + Event::WindowEvent { + event, window_id, .. + } => { + let (mut window_resized, winit_windows, mut windows, mut access_kit_adapters) = + event_writer_system_state.get_mut(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); + } + } + + runner_state.window_event_received = true; + + match event { + WindowEvent::Resized(size) => { + react_to_resize(&mut win, size, &mut window_resized, window); + } + WindowEvent::CloseRequested => winit_events.send(WindowCloseRequested { window }), + WindowEvent::KeyboardInput { ref event, .. } => { + if event.state.is_pressed() { + if let Some(char) = &event.text { + let char = char.clone(); + #[allow(deprecated)] + winit_events.send(ReceivedCharacter { window, char }); + } + } + winit_events.send(converters::convert_keyboard_input(event, window)); + } + 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(); + winit_events.send(CursorMoved { + window, + position, + delta, + }); + } + WindowEvent::CursorEntered { .. } => { + winit_events.send(CursorEntered { window }); + } + WindowEvent::CursorLeft { .. } => { + win.set_physical_cursor_position(None); + winit_events.send(CursorLeft { window }); + } + WindowEvent::MouseInput { state, button, .. } => { + winit_events.send(MouseButtonInput { + button: converters::convert_mouse_button(button), + state: converters::convert_element_state(state), + window, + }); + } + WindowEvent::PinchGesture { delta, .. } => { + winit_events.send(TouchpadMagnify(delta as f32)); + } + WindowEvent::RotationGesture { delta, .. } => { + winit_events.send(TouchpadRotate(delta)); + } + WindowEvent::MouseWheel { delta, .. } => match delta { + event::MouseScrollDelta::LineDelta(x, y) => { + winit_events.send(MouseWheel { + unit: MouseScrollUnit::Line, + x, + y, + window, + }); + } + event::MouseScrollDelta::PixelDelta(p) => { + 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); + winit_events.send(converters::convert_touch_input(touch, location, window)); + } + WindowEvent::ScaleFactorChanged { + scale_factor, + mut inner_size_writer, + } => { + let prior_factor = win.resolution.scale_factor(); + win.resolution.set_scale_factor(scale_factor as f32); + // Note: this may be different from new_scale_factor if + // `scale_factor_override` is set to Some(thing) + let new_factor = win.resolution.scale_factor(); + + let mut new_inner_size = + PhysicalSize::new(win.physical_width(), win.physical_height()); + let scale_factor_override = win.resolution.scale_factor_override(); + if let Some(forced_factor) = scale_factor_override { + // This window is overriding the OS-suggested DPI, so its physical size + // should be set based on the overriding value. Its logical size already + // incorporates any resize constraints. + let maybe_new_inner_size = LogicalSize::new(win.width(), win.height()) + .to_physical::(forced_factor as f64); + if let Err(err) = inner_size_writer.request_inner_size(new_inner_size) { + warn!("Winit Failed to resize the window: {err}"); + } else { + new_inner_size = maybe_new_inner_size; + } + } + let new_logical_width = new_inner_size.width as f32 / new_factor; + let new_logical_height = new_inner_size.height as f32 / new_factor; + + let width_equal = relative_eq!(win.width(), new_logical_width); + let height_equal = relative_eq!(win.height(), new_logical_height); + win.resolution + .set_physical_resolution(new_inner_size.width, new_inner_size.height); + + winit_events.send(WindowBackendScaleFactorChanged { + window, + scale_factor, + }); + if scale_factor_override.is_none() && !relative_eq!(new_factor, prior_factor) { + winit_events.send(WindowScaleFactorChanged { + window, + scale_factor, + }); + } + + if !width_equal || !height_equal { + winit_events.send(WindowResized { + window, + width: new_logical_width, + height: new_logical_height, + }); + } + } + WindowEvent::Focused(focused) => { + win.focused = focused; + winit_events.send(WindowFocused { window, focused }); + } + WindowEvent::Occluded(occluded) => { + winit_events.send(WindowOccluded { window, occluded }); + } + WindowEvent::DroppedFile(path_buf) => { + winit_events.send(FileDragAndDrop::DroppedFile { window, path_buf }); + } + WindowEvent::HoveredFile(path_buf) => { + winit_events.send(FileDragAndDrop::HoveredFile { window, path_buf }); + } + WindowEvent::HoveredFileCancelled => { + winit_events.send(FileDragAndDrop::HoveredFileCanceled { window }); + } + WindowEvent::Moved(position) => { + let position = ivec2(position.x, position.y); + win.position.set(position); + winit_events.send(WindowMoved { window, position }); + } + WindowEvent::Ime(event) => match event { + event::Ime::Preedit(value, cursor) => { + winit_events.send(Ime::Preedit { + window, + value, + cursor, + }); + } + event::Ime::Commit(value) => { + winit_events.send(Ime::Commit { window, value }); + } + event::Ime::Enabled => { + winit_events.send(Ime::Enabled { window }); + } + event::Ime::Disabled => { + winit_events.send(Ime::Disabled { window }); + } + }, + WindowEvent::ThemeChanged(theme) => { + winit_events.send(WindowThemeChanged { + window, + theme: converters::convert_winit_theme(theme), + }); + } + WindowEvent::Destroyed => { + winit_events.send(WindowDestroyed { window }); + } + WindowEvent::RedrawRequested => { + run_app_update(runner_state, app, winit_events); + } + _ => {} + } + + let mut windows = app.world_mut().query::<(&mut Window, &mut CachedWindow)>(); + if let Ok((window_component, mut cache)) = windows.get_mut(app.world_mut(), window) { + if window_component.is_changed() { + cache.window = window_component.clone(); + } + } + } + Event::DeviceEvent { event, .. } => { + runner_state.device_event_received = true; + if let DeviceEvent::MouseMotion { delta: (x, y) } = event { + let delta = Vec2::new(x as f32, y as f32); + winit_events.send(MouseMotion { delta }); + } + } + Event::Suspended => { + winit_events.send(ApplicationLifetime::Suspended); + // Mark the state as `WillSuspend`. This will let the schedule run one last time + // before actually suspending to let the application react + runner_state.activity_state = UpdateState::WillSuspend; + } + Event::Resumed => { + match runner_state.activity_state { + UpdateState::NotYetStarted => winit_events.send(ApplicationLifetime::Started), + _ => winit_events.send(ApplicationLifetime::Resumed), + } + runner_state.activity_state = UpdateState::WillResume; + } + Event::UserEvent(UserEvent::WakeUp) => { + runner_state.redraw_requested = true; + } + _ => (), + } + + if let Some(app_exit) = app.should_exit() { + if let Err(err) = exit_notify.try_send(app_exit) { + error!("Failed to send a app exit notification! This is a bug. Reason: {err}"); + }; + event_loop.exit(); + return; + } + + // We drain events after every received winit event in addition to on app update to ensure + // the work of pushing events into event queues is spread out over time in case the app becomes + // dormant for a long stretch. + forward_winit_events(winit_events, app); +} + +fn should_update(runner_state: &WinitAppRunnerState, update_mode: UpdateMode) -> bool { + let handle_event = match update_mode { + UpdateMode::Continuous | UpdateMode::Reactive { .. } => { + runner_state.wait_elapsed + || runner_state.window_event_received + || runner_state.device_event_received + } + UpdateMode::ReactiveLowPower { .. } => { + runner_state.wait_elapsed || runner_state.window_event_received + } + }; + + handle_event && runner_state.activity_state.is_active() +} + +fn run_app_update( + runner_state: &mut WinitAppRunnerState, + app: &mut App, + winit_events: &mut Vec, +) { + runner_state.reset_on_update(); + + forward_winit_events(winit_events, app); + + if app.plugins_state() == PluginsState::Cleaned { + app.update(); + } +} diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs new file mode 100644 index 0000000000000..c70debbc11f19 --- /dev/null +++ b/crates/bevy_winit/src/state.rs @@ -0,0 +1,73 @@ +#[cfg(target_os = "android")] +pub use winit::platform::android::activity as android_activity; + +#[cfg(target_os = "android")] +use bevy_window::{PrimaryWindow, RawHandleWrapper}; + +use crate::UpdateMode; + +/// [`AndroidApp`] provides an interface to query the application state as well as monitor events +/// (for example lifecycle and input events). +#[cfg(target_os = "android")] +pub static ANDROID_APP: std::sync::OnceLock = + std::sync::OnceLock::new(); + +#[derive(PartialEq, Eq, Debug)] +pub(crate) enum UpdateState { + NotYetStarted, + Active, + Suspended, + WillSuspend, + WillResume, +} + +impl UpdateState { + #[inline] + pub(crate) fn is_active(&self) -> bool { + match self { + Self::NotYetStarted | Self::Suspended => false, + Self::Active | Self::WillSuspend | Self::WillResume => true, + } + } +} + +/// Persistent state that is used to run the [`App`] according to the current +/// [`UpdateMode`]. +pub(crate) struct WinitAppRunnerState { + /// Current activity state of the app. + pub(crate) activity_state: UpdateState, + /// Current update mode of the app. + pub(crate) update_mode: UpdateMode, + /// Is `true` if a new [`WindowEvent`] has been received since the last update. + pub(crate) window_event_received: bool, + /// Is `true` if a new [`DeviceEvent`] has been received since the last update. + pub(crate) device_event_received: bool, + /// Is `true` if the app has requested a redraw since the last update. + pub(crate) redraw_requested: bool, + /// Is `true` if enough time has elapsed since `last_update` to run another update. + pub(crate) wait_elapsed: bool, + /// Number of "forced" updates to trigger on application start + pub(crate) startup_forced_updates: u32, +} + +impl WinitAppRunnerState { + pub(crate) fn reset_on_update(&mut self) { + self.window_event_received = false; + self.device_event_received = false; + } +} + +impl Default for WinitAppRunnerState { + fn default() -> Self { + Self { + activity_state: UpdateState::NotYetStarted, + update_mode: UpdateMode::Continuous, + window_event_received: false, + device_event_received: false, + redraw_requested: false, + wait_elapsed: false, + // 3 seems to be enough, 5 is a safe margin + startup_forced_updates: 5, + } + } +} From 1dcf70177964cf4bfed4786ab703ccae00147c63 Mon Sep 17 00:00:00 2001 From: Pietro Date: Tue, 14 May 2024 17:40:39 +0200 Subject: [PATCH 04/40] fix: app is running again --- crates/bevy_winit/src/lib.rs | 5 +- crates/bevy_winit/src/state.rs | 692 ++++++++++++++++++++++++++++++++- 2 files changed, 686 insertions(+), 11 deletions(-) diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 0be40487daf3d..15b36136b1a60 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -37,11 +37,12 @@ pub use winit_event::*; pub use winit_windows::*; use crate::accessibility::{AccessKitAdapters, AccessKitPlugin, WinitActionHandlers}; -use crate::runner::winit_runner; +use crate::state::winit_runner; +// use crate::runner::winit_runner; pub mod accessibility; mod converters; -mod runner; +// mod runner; mod state; mod system; mod winit_config; diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index c70debbc11f19..f7b2b15a7bba7 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -1,10 +1,44 @@ +use std::sync::mpsc::sync_channel; +use std::time::Instant; +use approx::relative_eq; +use winit::application::ApplicationHandler; +use winit::dpi::{LogicalSize, PhysicalSize}; +use winit::event; +use winit::event::{DeviceEvent, DeviceId, StartCause, WindowEvent}; +use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; #[cfg(target_os = "android")] pub use winit::platform::android::activity as android_activity; +use winit::window::WindowId; +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::{Added, Events, NonSend, Query}; +use bevy_ecs::system::SystemState; +use bevy_ecs::world::FromWorld; +use bevy_log::{error, trace, warn}; +use bevy_math::{DVec2, ivec2, Vec2}; +use bevy_input::{ + mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel}, + touchpad::{TouchpadMagnify, TouchpadRotate}, +}; +use bevy_tasks::tick_global_task_pools_on_main_thread; #[cfg(target_os = "android")] use bevy_window::{PrimaryWindow, RawHandleWrapper}; +#[allow(deprecated)] +use bevy_window::{ + ApplicationLifetime, CursorEntered, CursorLeft, CursorMoved, + FileDragAndDrop, Ime, ReceivedCharacter, RequestRedraw, Window, + WindowBackendScaleFactorChanged, WindowCloseRequested, WindowCreated, WindowDestroyed, + WindowFocused, WindowMoved, WindowOccluded, WindowResized, WindowScaleFactorChanged, + WindowThemeChanged, +}; -use crate::UpdateMode; +use crate::{AppSendEvent, converters, create_windows, CreateWindowParams, react_to_resize, UpdateMode, UserEvent, WinitEvent, WinitSettings, WinitWindows}; +use crate::accessibility::AccessKitAdapters; +use crate::system::CachedWindow; +use crate::winit_event::forward_winit_events; /// [`AndroidApp`] provides an interface to query the application state as well as monitor events /// (for example lifecycle and input events). @@ -34,6 +68,9 @@ impl UpdateState { /// Persistent state that is used to run the [`App`] according to the current /// [`UpdateMode`]. pub(crate) struct WinitAppRunnerState { + /// Current activity state of the app. + pub(crate) app: App, + /// Current activity state of the app. pub(crate) activity_state: UpdateState, /// Current update mode of the app. @@ -48,18 +85,15 @@ pub(crate) struct WinitAppRunnerState { pub(crate) wait_elapsed: bool, /// Number of "forced" updates to trigger on application start pub(crate) startup_forced_updates: u32, -} -impl WinitAppRunnerState { - pub(crate) fn reset_on_update(&mut self) { - self.window_event_received = false; - self.device_event_received = false; - } + /// Winit events to send + winit_events: Vec, } -impl Default for WinitAppRunnerState { - fn default() -> Self { +impl WinitAppRunnerState { + fn new(app: App) -> Self { Self { + app, activity_state: UpdateState::NotYetStarted, update_mode: UpdateMode::Continuous, window_event_received: false, @@ -68,6 +102,646 @@ impl Default for WinitAppRunnerState { wait_elapsed: false, // 3 seems to be enough, 5 is a safe margin startup_forced_updates: 5, + winit_events: Vec::new(), + } + } + pub(crate) fn reset_on_update(&mut self) { + self.window_event_received = false; + self.device_event_received = false; + } +} + +impl ApplicationHandler for WinitAppRunnerState { + fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) { + #[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; + } + + // create any new windows + // (even if app did not update, some may have been created by plugin setup) + let mut create_window = + SystemState::>>::from_world(self.app.world_mut()); + create_windows(event_loop, create_window.get_mut(self.app.world_mut())); + create_window.apply(self.app.world_mut()); + + + + 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) { + match self.activity_state { + UpdateState::NotYetStarted => self.winit_events.send(ApplicationLifetime::Started), + _ => self.winit_events.send(ApplicationLifetime::Resumed), } + self.activity_state = UpdateState::WillResume; } + + fn user_event(&mut self, _event_loop: &ActiveEventLoop, _event: UserEvent) { + self.redraw_requested = true; + } + + fn window_event(&mut self, _event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) { + let mut event_writer_system_state: SystemState<( + EventWriter, + NonSend, + Query<(&mut Window, &mut CachedWindow)>, + NonSendMut, + )> = SystemState::new(self.app.world_mut()); + + let (mut window_resized, winit_windows, mut windows, mut access_kit_adapters) = + 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); + } + } + + self.window_event_received = true; + + match event { + WindowEvent::Resized(size) => { + react_to_resize(&mut win, size, &mut window_resized, window); + } + WindowEvent::CloseRequested => self.winit_events.send(WindowCloseRequested { window }), + WindowEvent::KeyboardInput { ref event, .. } => { + if event.state.is_pressed() { + if let Some(char) = &event.text { + let char = char.clone(); + #[allow(deprecated)] + self.winit_events.send(ReceivedCharacter { window, char }); + } + } + self.winit_events.send(converters::convert_keyboard_input(event, window)); + } + 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(TouchpadMagnify(delta as f32)); + } + WindowEvent::RotationGesture { delta, .. } => { + self.winit_events.send(TouchpadRotate(delta)); + } + 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::ScaleFactorChanged { + scale_factor, + mut inner_size_writer, + } => { + let prior_factor = win.resolution.scale_factor(); + win.resolution.set_scale_factor(scale_factor as f32); + // Note: this may be different from new_scale_factor if + // `scale_factor_override` is set to Some(thing) + let new_factor = win.resolution.scale_factor(); + + let mut new_inner_size = + PhysicalSize::new(win.physical_width(), win.physical_height()); + let scale_factor_override = win.resolution.scale_factor_override(); + if let Some(forced_factor) = scale_factor_override { + // This window is overriding the OS-suggested DPI, so its physical size + // should be set based on the overriding value. Its logical size already + // incorporates any resize constraints. + let maybe_new_inner_size = LogicalSize::new(win.width(), win.height()) + .to_physical::(forced_factor as f64); + if let Err(err) = inner_size_writer.request_inner_size(new_inner_size) { + warn!("Winit Failed to resize the window: {err}"); + } else { + new_inner_size = maybe_new_inner_size; + } + } + let new_logical_width = new_inner_size.width as f32 / new_factor; + let new_logical_height = new_inner_size.height as f32 / new_factor; + + let width_equal = relative_eq!(win.width(), new_logical_width); + let height_equal = relative_eq!(win.height(), new_logical_height); + win.resolution + .set_physical_resolution(new_inner_size.width, new_inner_size.height); + + self.winit_events.send(WindowBackendScaleFactorChanged { + window, + scale_factor, + }); + if scale_factor_override.is_none() && !relative_eq!(new_factor, prior_factor) { + self.winit_events.send(WindowScaleFactorChanged { + window, + scale_factor, + }); + } + + if !width_equal || !height_equal { + self.winit_events.send(WindowResized { + window, + width: new_logical_width, + height: new_logical_height, + }); + } + } + WindowEvent::Focused(focused) => { + win.focused = focused; + self.winit_events.send(WindowFocused { window, focused }); + } + 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 }); + } + WindowEvent::RedrawRequested => { + self.run_app_update(); + } + _ => {} + } + + let mut windows = self.app.world_mut().query::<(&mut Window, &mut CachedWindow)>(); + if let Ok((window_component, mut cache)) = windows.get_mut(self.app.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) { + let mut redraw_event_reader = ManualEventReader::::default(); + + let mut focused_windows_state: SystemState<(Res, Query<(Entity, &Window)>)> = + SystemState::new(self.app.world_mut()); + + if let Some(app_redraw_events) = self.app.world().get_resource::>() { + if redraw_event_reader.read(app_redraw_events).last().is_some() { + self.redraw_requested = true; + } + } + + let (config, windows) = focused_windows_state.get(self.app.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.activity_state == UpdateState::WillSuspend { + self.activity_state = UpdateState::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 = app + .world_mut() + .query_filtered::>(); + let entity = query.single(&app.world()); + app.world_mut() + .entity_mut(entity) + .remove::(); + } + } + + if self.activity_state == UpdateState::WillResume { + self.activity_state = UpdateState::Active; + // Trigger the update to enter the active state + should_update = true; + // Trigger the next redraw ro 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 = app + .world_mut() + .query_filtered::<(Entity, &Window), (With, Without)>(); + if let Ok((entity, window)) = query.get_single(&app.world()) { + let window = window.clone(); + + let ( + .., + mut winit_windows, + mut adapters, + mut handlers, + accessibility_requested, + ) = create_window.get_mut(app.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(); + + app.world_mut().entity_mut(entity).insert(wrapper); + } + } + } + + // 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.app.world()); + let focused = windows.iter().any(|(_, window)| window.focused); + + update_mode = config.update_mode(focused); + } + + 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.app.world().non_send_resource::(); + 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 } | UpdateMode::ReactiveLowPower { 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 update_mode != self.update_mode { + // Trigger the next redraw since we're changing the update mode + self.redraw_requested = true; + self.update_mode = update_mode; + } + + if self.redraw_requested + && self.activity_state != UpdateState::Suspended + { + let winit_windows = self.app.world().non_send_resource::(); + for window in winit_windows.windows.values() { + window.request_redraw(); + } + self.redraw_requested = false; + } + } + + fn suspended(&mut self, _event_loop: &ActiveEventLoop) { + self.winit_events.send(ApplicationLifetime::Suspended); + // Mark the state as `WillSuspend`. This will let the schedule run one last time + // before actually suspending to let the application react + self.activity_state = UpdateState::WillSuspend; + } +} + +impl WinitAppRunnerState { + fn should_update(&self, update_mode: UpdateMode) -> bool { + let handle_event = match update_mode { + UpdateMode::Continuous | UpdateMode::Reactive { .. } => { + self.wait_elapsed + || self.window_event_received + || self.device_event_received + } + UpdateMode::ReactiveLowPower { .. } => { + self.wait_elapsed || self.window_event_received + } + }; + + handle_event && self.activity_state.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 = &mut self.winit_events; + let app = &mut self.app; + + if buffered_events.is_empty() { + return; + } + + for winit_event in buffered_events.iter() { + match winit_event.clone() { + WinitEvent::ApplicationLifetime(e) => { + app.world_mut().send_event(e); + } + WinitEvent::CursorEntered(e) => { + app.world_mut().send_event(e); + } + WinitEvent::CursorLeft(e) => { + app.world_mut().send_event(e); + } + WinitEvent::CursorMoved(e) => { + app.world_mut().send_event(e); + } + WinitEvent::FileDragAndDrop(e) => { + app.world_mut().send_event(e); + } + WinitEvent::Ime(e) => { + app.world_mut().send_event(e); + } + WinitEvent::ReceivedCharacter(e) => { + app.world_mut().send_event(e); + } + WinitEvent::RequestRedraw(e) => { + app.world_mut().send_event(e); + } + WinitEvent::WindowBackendScaleFactorChanged(e) => { + app.world_mut().send_event(e); + } + WinitEvent::WindowCloseRequested(e) => { + app.world_mut().send_event(e); + } + WinitEvent::WindowCreated(e) => { + app.world_mut().send_event(e); + } + WinitEvent::WindowDestroyed(e) => { + app.world_mut().send_event(e); + } + WinitEvent::WindowFocused(e) => { + app.world_mut().send_event(e); + } + WinitEvent::WindowMoved(e) => { + app.world_mut().send_event(e); + } + WinitEvent::WindowOccluded(e) => { + app.world_mut().send_event(e); + } + WinitEvent::WindowResized(e) => { + app.world_mut().send_event(e); + } + WinitEvent::WindowScaleFactorChanged(e) => { + app.world_mut().send_event(e); + } + WinitEvent::WindowThemeChanged(e) => { + app.world_mut().send_event(e); + } + WinitEvent::MouseButtonInput(e) => { + app.world_mut().send_event(e); + } + WinitEvent::MouseMotion(e) => { + app.world_mut().send_event(e); + } + WinitEvent::MouseWheel(e) => { + app.world_mut().send_event(e); + } + WinitEvent::TouchpadMagnify(e) => { + app.world_mut().send_event(e); + } + WinitEvent::TouchpadRotate(e) => { + app.world_mut().send_event(e); + } + WinitEvent::TouchInput(e) => { + app.world_mut().send_event(e); + } + WinitEvent::KeyboardInput(e) => { + app.world_mut().send_event(e); + } + } + } + app.world_mut() + .resource_mut::>() + .send_batch(buffered_events.drain(..)); + } +} + +/// 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(mut app: App) -> AppExit { + if app.plugins_state() == PluginsState::Ready { + app.finish(); + app.cleanup(); + } + + let event_loop = app + .world_mut() + .remove_non_send_resource::>() + .unwrap(); + + app.world_mut() + .insert_non_send_resource(event_loop.create_proxy()); + + let mut runner_state = WinitAppRunnerState::new(app); + + // TODO: restore this + // // Create a channel with a size of 1, since ideally only one exit code will be sent before exiting the app. + // let (exit_sender, exit_receiver) = sync_channel(1); + // + // + // let mut create_window = + // SystemState::>>::from_world(app.world_mut()); + // let mut winit_events = Vec::default(); + // + // // set up the event loop + // let event_handler = move |event, event_loop: &ActiveEventLoop| { + // // The event loop is in the process of exiting, so don't deliver any new events + // if event_loop.exiting() { + // return; + // } + // + // crate::runner::handle_winit_event( + // &mut app, + // &mut runner_state, + // &mut create_window, + // &mut event_writer_system_state, + // &mut focused_windows_state, + // &mut redraw_event_reader, + // &mut winit_events, + // &exit_sender, + // event, + // event_loop, + // ); + // }; + + 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}"); + } + + // TODO: restore this + // // If everything is working correctly then the event loop only exits after it's sent a exit code. + // exit_receiver + // .try_recv() + // .map_err(|err| error!("Failed to receive a app exit code! This is a bug. Reason: {err}")) + // .unwrap_or(AppExit::error()) + + // TODO: remove this + AppExit::error() } From c2b1f6879977a33a48962ad837e492c6f65636de Mon Sep 17 00:00:00 2001 From: Pietro Date: Tue, 14 May 2024 17:41:23 +0200 Subject: [PATCH 05/40] chore: renamed file --- crates/bevy_winit/src/{runner.rs => TOBEDELETED_runner.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename crates/bevy_winit/src/{runner.rs => TOBEDELETED_runner.rs} (100%) diff --git a/crates/bevy_winit/src/runner.rs b/crates/bevy_winit/src/TOBEDELETED_runner.rs similarity index 100% rename from crates/bevy_winit/src/runner.rs rename to crates/bevy_winit/src/TOBEDELETED_runner.rs From bdbf9f8153f268ff06f2aa85d56d7ea399978226 Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 15 May 2024 10:28:41 +0200 Subject: [PATCH 06/40] fix: restored exit events --- crates/bevy_winit/src/state.rs | 64 ++++++++++------------------------ 1 file changed, 19 insertions(+), 45 deletions(-) diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index f7b2b15a7bba7..687c7c1f37b9a 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -1,4 +1,4 @@ -use std::sync::mpsc::sync_channel; +use std::sync::mpsc::{sync_channel, SyncSender}; use std::time::Instant; use approx::relative_eq; use winit::application::ApplicationHandler; @@ -68,11 +68,12 @@ impl UpdateState { /// Persistent state that is used to run the [`App`] according to the current /// [`UpdateMode`]. pub(crate) struct WinitAppRunnerState { - /// Current activity state of the app. + /// The running app. pub(crate) app: App, - /// Current activity state of the app. pub(crate) activity_state: UpdateState, + /// Exit value once the loop is finished. + pub(crate) app_exit: Option, /// Current update mode of the app. pub(crate) update_mode: UpdateMode, /// Is `true` if a new [`WindowEvent`] has been received since the last update. @@ -95,6 +96,7 @@ impl WinitAppRunnerState { Self { app, activity_state: UpdateState::NotYetStarted, + app_exit: None, update_mode: UpdateMode::Continuous, window_event_received: false, device_event_received: false, @@ -134,8 +136,6 @@ impl ApplicationHandler for WinitAppRunnerState { create_windows(event_loop, create_window.get_mut(self.app.world_mut())); create_window.apply(self.app.world_mut()); - - self.wait_elapsed = match cause { StartCause::WaitCancelled { requested_resume: Some(resume), @@ -382,7 +382,7 @@ impl ApplicationHandler for WinitAppRunnerState { } } - fn device_event(&mut self, event_loop: &ActiveEventLoop, device_id: DeviceId, event: DeviceEvent) { + 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); @@ -548,6 +548,13 @@ impl ApplicationHandler for WinitAppRunnerState { } self.redraw_requested = false; } + + if let Some(app_exit) = self.app.should_exit() { + self.app_exit = Some(app_exit); + event_loop.exit(); + return; + } + } fn suspended(&mut self, _event_loop: &ActiveEventLoop) { @@ -699,49 +706,16 @@ pub fn winit_runner(mut app: App) -> AppExit { let mut runner_state = WinitAppRunnerState::new(app); - // TODO: restore this - // // Create a channel with a size of 1, since ideally only one exit code will be sent before exiting the app. - // let (exit_sender, exit_receiver) = sync_channel(1); - // - // - // let mut create_window = - // SystemState::>>::from_world(app.world_mut()); - // let mut winit_events = Vec::default(); - // - // // set up the event loop - // let event_handler = move |event, event_loop: &ActiveEventLoop| { - // // The event loop is in the process of exiting, so don't deliver any new events - // if event_loop.exiting() { - // return; - // } - // - // crate::runner::handle_winit_event( - // &mut app, - // &mut runner_state, - // &mut create_window, - // &mut event_writer_system_state, - // &mut focused_windows_state, - // &mut redraw_event_reader, - // &mut winit_events, - // &exit_sender, - // event, - // event_loop, - // ); - // }; - 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}"); } - // TODO: restore this - // // If everything is working correctly then the event loop only exits after it's sent a exit code. - // exit_receiver - // .try_recv() - // .map_err(|err| error!("Failed to receive a app exit code! This is a bug. Reason: {err}")) - // .unwrap_or(AppExit::error()) - - // TODO: remove this - AppExit::error() + // 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() + }) } From 5249c3066dffd607c5f4609f8c395f0dbe87c83e Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 15 May 2024 11:38:59 +0200 Subject: [PATCH 07/40] fix: restored accessibility (to be tested) --- crates/bevy_winit/src/accessibility.rs | 178 +++++++++++++++---------- crates/bevy_winit/src/lib.rs | 18 +-- crates/bevy_winit/src/state.rs | 149 +++++++++++---------- crates/bevy_winit/src/system.rs | 6 +- crates/bevy_winit/src/winit_event.rs | 89 ------------- crates/bevy_winit/src/winit_windows.rs | 28 ++-- examples/window/low_power.rs | 2 +- 7 files changed, 211 insertions(+), 259 deletions(-) diff --git a/crates/bevy_winit/src/accessibility.rs b/crates/bevy_winit/src/accessibility.rs index 927825ceb4688..40c1edb14229a 100644 --- a/crates/bevy_winit/src/accessibility.rs +++ b/crates/bevy_winit/src/accessibility.rs @@ -6,14 +6,12 @@ use std::{ }; use accesskit_winit::Adapter; +use bevy_a11y::accesskit::{ActivationHandler, DeactivationHandler, Node}; use bevy_a11y::{ - accesskit::{ - ActionHandler, ActionRequest, NodeBuilder, NodeId, Role, Tree, TreeUpdate, - }, + accesskit::{ActionHandler, ActionRequest, NodeBuilder, NodeId, Role, Tree, TreeUpdate}, AccessibilityNode, AccessibilityRequested, AccessibilitySystem, Focus, }; use bevy_a11y::{ActionRequest as ActionRequestWrapper, ManageAccessibilityUpdates}; -use bevy_a11y::accesskit::ActivationHandler; use bevy_app::{App, Plugin, PostUpdate}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::entity::EntityHashMap; @@ -21,23 +19,87 @@ use bevy_ecs::{ prelude::{DetectChanges, Entity, EventReader, EventWriter}, query::With, schedule::IntoSystemConfigs, - system::{NonSend, NonSendMut, Query, Res, ResMut, Resource}, + system::{NonSendMut, Query, Res, ResMut, Resource}, }; use bevy_hierarchy::{Children, Parent}; use bevy_window::{PrimaryWindow, Window, WindowClosed}; -use crate::EventLoopProxy; /// Maps window entities to their `AccessKit` [`Adapter`]s. #[derive(Default, Deref, DerefMut)] pub struct AccessKitAdapters(pub EntityHashMap); -/// Maps window entities to their respective [`WinitActionHandler`]s. +/// Maps window entities to their respective [`WinitActionRequests`]s. #[derive(Resource, Default, Deref, DerefMut)] -pub struct WinitActionHandlers(pub EntityHashMap); +pub struct WinitActionRequestHandlers(pub EntityHashMap>>); + +struct AccessKitState { + name: String, + entity: Entity, + requested: AccessibilityRequested, +} + +impl AccessKitState { + fn new( + name: impl Into, + entity: Entity, + requested: AccessibilityRequested, + ) -> Arc> { + let name = name.into(); + + Arc::new(Mutex::new(Self { + name, + entity, + requested, + })) + } + + fn build_root(&mut self) -> Node { + let mut builder = NodeBuilder::new(Role::Window); + builder.set_name(self.name.clone()); + builder.build() + } + + fn build_initial_tree(&mut self) -> TreeUpdate { + let root = self.build_root(); + let accesskit_window_id = NodeId(self.entity.to_bits()); + let mut tree = Tree::new(accesskit_window_id); + tree.app_name = Some(self.name.clone()); + self.requested.set(true); + + TreeUpdate { + nodes: vec![(accesskit_window_id, root)], + tree: Some(tree), + focus: accesskit_window_id, + } + } +} + +struct WinitActivationHandler(Arc>); + +impl ActivationHandler for WinitActivationHandler { + fn request_initial_tree(&mut self) -> Option { + Some(self.0.lock().unwrap().build_initial_tree()) + } +} + +impl WinitActivationHandler { + pub fn new(state: Arc>) -> Self { + Self(state) + } +} -/// Forwards `AccessKit` [`ActionRequest`]s from winit to an event channel. #[derive(Clone, Default, Deref, DerefMut)] -pub struct WinitActionHandler(pub Arc>>); +pub struct WinitActionRequestHandler(pub VecDeque); + +impl WinitActionRequestHandler { + fn new() -> Arc> { + Arc::new(Mutex::new(Self(VecDeque::new()))) + } +} + +/// Forwards `AccessKit` [`ActionRequest`]s from winit to an event channel. +#[derive(Clone, Default)] +struct WinitActionHandler(Arc>); impl ActionHandler for WinitActionHandler { fn do_action(&mut self, request: ActionRequest) { @@ -45,37 +107,19 @@ impl ActionHandler for WinitActionHandler { requests.push_back(request); } } -// -// struct UiState; -// impl UiState { -// fn new() -> Arc> { -// Arc::new(Mutex::new(Self)) -// } -// // -// // fn build_root(&mut self) -> Node { -// // let mut root_builder = NodeBuilder::new(Role::Window); -// // root_builder.set_name(name.into_boxed_str()); -// // let root = root_builder.build(); -// // -// // let mut builder = NodeBuilder::new(Role::Window); -// // builder.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); -// // if self.announcement.is_some() { -// // builder.push_child(ANNOUNCEMENT_ID); -// // } -// // builder.set_name(WINDOW_TITLE); -// // builder.build() -// // } -// } -// -// struct TearoffActivationHandler { -// state: Arc>, -// } -// -// impl ActivationHandler for TearoffActivationHandler { -// fn request_initial_tree(&mut self) -> Option { -// Some(self.state.lock().unwrap().build_initial_tree()) -// } -// } + +impl WinitActionHandler { + pub fn new(handler: Arc>) -> Self { + Self(handler) + } +} + +struct WinitDeactivationHandler; + +impl DeactivationHandler for WinitDeactivationHandler { + fn deactivate_accessibility(&mut self) {} +} + /// Prepares accessibility for a winit window. pub(crate) fn prepare_accessibility_for_window( winit_window: &winit::window::Window, @@ -83,49 +127,39 @@ pub(crate) fn prepare_accessibility_for_window( name: String, accessibility_requested: AccessibilityRequested, adapters: &mut AccessKitAdapters, - handlers: &mut WinitActionHandlers, + handlers: &mut WinitActionRequestHandlers, ) { - // - // let ui = Ui::new - // let accesskit_window_id = NodeId(entity.to_bits()); - // let handler = TearoffActivationHandler { - // state: Arc::clone(Ui::new - // }; - // let adapter = Adapter::with_mixed_handlers( - // winit_window, - // &handler, - // event_loop_proxy.clone(), - // ); - // TODO: restore this - // let adapter = Adapter::with_action_handler( - // winit_window, - // move || { - // accessibility_requested.set(true); - // TreeUpdate { - // nodes: vec![(accesskit_window_id, root)], - // tree: Some(Tree::new(accesskit_window_id)), - // focus: accesskit_window_id, - // } - // }, - // Box::new(handler.clone()), - // ); - // adapters.insert(entity, adapter); - // handlers.insert(entity, handler); + let state = AccessKitState::new(name, entity, accessibility_requested); + let activation_handler = WinitActivationHandler::new(Arc::clone(&state)); + + let action_request_handler = WinitActionRequestHandler::new(); + let action_handler = WinitActionHandler::new(Arc::clone(&action_request_handler)); + let deactivation_handler = WinitDeactivationHandler; + + let adapter = Adapter::with_direct_handlers( + &winit_window, + activation_handler, + action_handler, + deactivation_handler, + ); + + adapters.insert(entity, adapter); + handlers.insert(entity, action_request_handler); } fn window_closed( mut adapters: NonSendMut, - mut receivers: ResMut, + mut handlers: ResMut, mut events: EventReader, ) { for WindowClosed { window, .. } in events.read() { adapters.remove(window); - receivers.remove(window); + handlers.remove(window); } } fn poll_receivers( - handlers: Res, + handlers: Res, mut actions: EventWriter, ) { for (_id, handler) in handlers.iter() { @@ -252,7 +286,7 @@ pub struct AccessKitPlugin; impl Plugin for AccessKitPlugin { fn build(&self, app: &mut App) { app.init_non_send_resource::() - .init_resource::() + .init_resource::() .add_event::() .add_systems( PostUpdate, diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 15b36136b1a60..a576c5c44d689 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -1,8 +1,8 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![forbid(unsafe_code)] #![doc( -html_logo_url = "https://bevyengine.org/assets/icon.png", -html_favicon_url = "https://bevyengine.org/assets/icon.png" + html_logo_url = "https://bevyengine.org/assets/icon.png", + html_favicon_url = "https://bevyengine.org/assets/icon.png" )] //! `bevy_winit` provides utilities to handle window creation and the eventloop through [`winit`] @@ -21,22 +21,16 @@ use bevy_a11y::AccessibilityRequested; use bevy_app::{App, Last, Plugin}; use bevy_ecs::prelude::*; #[allow(deprecated)] -use bevy_window::{ - exit_on_all_closed - , Window - , WindowCreated - , WindowResized - , -}; +use bevy_window::{exit_on_all_closed, Window, WindowCreated, WindowResized}; #[cfg(target_os = "android")] use bevy_window::{PrimaryWindow, RawHandleWrapper}; -use system::{changed_windows, despawn_windows}; pub use system::create_windows; +use system::{changed_windows, despawn_windows}; pub use winit_config::*; pub use winit_event::*; pub use winit_windows::*; -use crate::accessibility::{AccessKitAdapters, AccessKitPlugin, WinitActionHandlers}; +use crate::accessibility::{AccessKitAdapters, AccessKitPlugin, WinitActionRequestHandlers}; use crate::state::winit_runner; // use crate::runner::winit_runner; @@ -193,7 +187,7 @@ pub type CreateWindowParams<'w, 's, F = ()> = ( EventWriter<'w, WindowCreated>, NonSendMut<'w, WinitWindows>, NonSendMut<'w, AccessKitAdapters>, - ResMut<'w, WinitActionHandlers>, + ResMut<'w, WinitActionRequestHandlers>, Res<'w, AccessibilityRequested>, ); diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index 687c7c1f37b9a..6744408d0014c 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -1,14 +1,4 @@ -use std::sync::mpsc::{sync_channel, SyncSender}; -use std::time::Instant; use approx::relative_eq; -use winit::application::ApplicationHandler; -use winit::dpi::{LogicalSize, PhysicalSize}; -use winit::event; -use winit::event::{DeviceEvent, DeviceId, StartCause, WindowEvent}; -use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; -#[cfg(target_os = "android")] -pub use winit::platform::android::activity as android_activity; -use winit::window::WindowId; use bevy_app::{App, AppExit, PluginsState}; use bevy_ecs::change_detection::{DetectChanges, NonSendMut, Res}; use bevy_ecs::entity::Entity; @@ -16,29 +6,39 @@ use bevy_ecs::event::{EventWriter, ManualEventReader}; use bevy_ecs::prelude::{Added, Events, NonSend, Query}; use bevy_ecs::system::SystemState; use bevy_ecs::world::FromWorld; -use bevy_log::{error, trace, warn}; -use bevy_math::{DVec2, ivec2, Vec2}; use bevy_input::{ mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel}, touchpad::{TouchpadMagnify, TouchpadRotate}, }; +use bevy_log::{error, trace, warn}; +use bevy_math::{ivec2, DVec2, Vec2}; use bevy_tasks::tick_global_task_pools_on_main_thread; - +use std::time::Instant; +use winit::application::ApplicationHandler; +use winit::dpi::{LogicalSize, PhysicalSize}; +use winit::event; +use winit::event::{DeviceEvent, DeviceId, StartCause, WindowEvent}; +use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; #[cfg(target_os = "android")] -use bevy_window::{PrimaryWindow, RawHandleWrapper}; +pub use winit::platform::android::activity as android_activity; +use winit::window::WindowId; + #[allow(deprecated)] use bevy_window::{ - ApplicationLifetime, CursorEntered, CursorLeft, CursorMoved, - FileDragAndDrop, Ime, ReceivedCharacter, RequestRedraw, Window, - WindowBackendScaleFactorChanged, WindowCloseRequested, WindowCreated, WindowDestroyed, - WindowFocused, WindowMoved, WindowOccluded, WindowResized, WindowScaleFactorChanged, - WindowThemeChanged, + ApplicationLifetime, 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::{AppSendEvent, converters, create_windows, CreateWindowParams, react_to_resize, UpdateMode, UserEvent, WinitEvent, WinitSettings, WinitWindows}; use crate::accessibility::AccessKitAdapters; use crate::system::CachedWindow; -use crate::winit_event::forward_winit_events; +use crate::{ + converters, create_windows, react_to_resize, AppSendEvent, CreateWindowParams, UpdateMode, + UserEvent, WinitEvent, WinitSettings, WinitWindows, +}; /// [`AndroidApp`] provides an interface to query the application state as well as monitor events /// (for example lifecycle and input events). @@ -116,7 +116,7 @@ impl WinitAppRunnerState { impl ApplicationHandler for WinitAppRunnerState { fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) { #[cfg(feature = "trace")] - let _span = bevy_utils::tracing::info_span!("winit event_handler").entered(); + 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 { @@ -161,7 +161,12 @@ impl ApplicationHandler for WinitAppRunnerState { self.redraw_requested = true; } - fn window_event(&mut self, _event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) { + fn window_event( + &mut self, + _event_loop: &ActiveEventLoop, + window_id: WindowId, + event: WindowEvent, + ) { let mut event_writer_system_state: SystemState<( EventWriter, NonSend, @@ -205,7 +210,8 @@ impl ApplicationHandler for WinitAppRunnerState { self.winit_events.send(ReceivedCharacter { window, char }); } } - self.winit_events.send(converters::convert_keyboard_input(event, window)); + self.winit_events + .send(converters::convert_keyboard_input(event, window)); } WindowEvent::CursorMoved { position, .. } => { let physical_position = DVec2::new(position.x, position.y); @@ -216,8 +222,7 @@ impl ApplicationHandler for WinitAppRunnerState { }); win.set_physical_cursor_position(Some(physical_position)); - let position = - (physical_position / win.resolution.scale_factor() as f64).as_vec2(); + let position = (physical_position / win.resolution.scale_factor() as f64).as_vec2(); self.winit_events.send(CursorMoved { window, position, @@ -266,7 +271,8 @@ impl ApplicationHandler for WinitAppRunnerState { let location = touch .location .to_logical(win.resolution.scale_factor() as f64); - self.winit_events.send(converters::convert_touch_input(touch, location, window)); + self.winit_events + .send(converters::convert_touch_input(touch, location, window)); } WindowEvent::ScaleFactorChanged { scale_factor, @@ -328,13 +334,16 @@ impl ApplicationHandler for WinitAppRunnerState { self.winit_events.send(WindowOccluded { window, occluded }); } WindowEvent::DroppedFile(path_buf) => { - self.winit_events.send(FileDragAndDrop::DroppedFile { window, path_buf }); + self.winit_events + .send(FileDragAndDrop::DroppedFile { window, path_buf }); } WindowEvent::HoveredFile(path_buf) => { - self.winit_events.send(FileDragAndDrop::HoveredFile { window, path_buf }); + self.winit_events + .send(FileDragAndDrop::HoveredFile { window, path_buf }); } WindowEvent::HoveredFileCancelled => { - self.winit_events.send(FileDragAndDrop::HoveredFileCanceled { window }); + self.winit_events + .send(FileDragAndDrop::HoveredFileCanceled { window }); } WindowEvent::Moved(position) => { let position = ivec2(position.x, position.y); @@ -374,7 +383,10 @@ impl ApplicationHandler for WinitAppRunnerState { _ => {} } - let mut windows = self.app.world_mut().query::<(&mut Window, &mut CachedWindow)>(); + let mut windows = self + .app + .world_mut() + .query::<(&mut Window, &mut CachedWindow)>(); if let Ok((window_component, mut cache)) = windows.get_mut(self.app.world_mut(), window) { if window_component.is_changed() { cache.window = window_component.clone(); @@ -382,7 +394,12 @@ impl ApplicationHandler for WinitAppRunnerState { } } - fn device_event(&mut self, _event_loop: &ActiveEventLoop, _device_id: DeviceId, event: DeviceEvent) { + 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); @@ -495,28 +512,28 @@ impl ApplicationHandler for WinitAppRunnerState { // 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.app.world().non_send_resource::(); - 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); - } + 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.app.world().non_send_resource::(); + 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() { @@ -539,9 +556,7 @@ impl ApplicationHandler for WinitAppRunnerState { self.update_mode = update_mode; } - if self.redraw_requested - && self.activity_state != UpdateState::Suspended - { + if self.redraw_requested && self.activity_state != UpdateState::Suspended { let winit_windows = self.app.world().non_send_resource::(); for window in winit_windows.windows.values() { window.request_redraw(); @@ -554,7 +569,6 @@ impl ApplicationHandler for WinitAppRunnerState { event_loop.exit(); return; } - } fn suspended(&mut self, _event_loop: &ActiveEventLoop) { @@ -569,21 +583,15 @@ impl WinitAppRunnerState { fn should_update(&self, update_mode: UpdateMode) -> bool { let handle_event = match update_mode { UpdateMode::Continuous | UpdateMode::Reactive { .. } => { - self.wait_elapsed - || self.window_event_received - || self.device_event_received - } - UpdateMode::ReactiveLowPower { .. } => { - self.wait_elapsed || self.window_event_received + self.wait_elapsed || self.window_event_received || self.device_event_received } + UpdateMode::ReactiveLowPower { .. } => self.wait_elapsed || self.window_event_received, }; handle_event && self.activity_state.is_active() } - fn run_app_update( - &mut self, - ) { + fn run_app_update(&mut self) { self.reset_on_update(); self.forward_winit_events(); @@ -713,9 +721,8 @@ pub fn winit_runner(mut app: App) -> AppExit { } // 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() - }) + runner_state.app_exit.unwrap_or_else(|| { + error!("Failed to receive a app exit code! This is a bug"); + AppExit::error() + }) } diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index 58692ca78bd30..ece5a3dbbfea5 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -12,10 +12,8 @@ use bevy_window::{ WindowMode, WindowResized, }; -use winit::{ - dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}, -}; -use winit::event_loop::{ActiveEventLoop}; +use winit::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; +use winit::event_loop::ActiveEventLoop; use bevy_ecs::query::With; #[cfg(target_arch = "wasm32")] diff --git a/crates/bevy_winit/src/winit_event.rs b/crates/bevy_winit/src/winit_event.rs index a314ea289621e..84a638996b49d 100644 --- a/crates/bevy_winit/src/winit_event.rs +++ b/crates/bevy_winit/src/winit_event.rs @@ -189,92 +189,3 @@ impl From for WinitEvent { Self::KeyboardInput(e) } } - -/// Forwards buffered [`WinitEvent`] events to the app. -pub(crate) fn forward_winit_events(buffered_events: &mut Vec, app: &mut App) { - if buffered_events.is_empty() { - return; - } - for winit_event in buffered_events.iter() { - match winit_event.clone() { - WinitEvent::ApplicationLifetime(e) => { - app.world_mut().send_event(e); - } - WinitEvent::CursorEntered(e) => { - app.world_mut().send_event(e); - } - WinitEvent::CursorLeft(e) => { - app.world_mut().send_event(e); - } - WinitEvent::CursorMoved(e) => { - app.world_mut().send_event(e); - } - WinitEvent::FileDragAndDrop(e) => { - app.world_mut().send_event(e); - } - WinitEvent::Ime(e) => { - app.world_mut().send_event(e); - } - WinitEvent::ReceivedCharacter(e) => { - app.world_mut().send_event(e); - } - WinitEvent::RequestRedraw(e) => { - app.world_mut().send_event(e); - } - WinitEvent::WindowBackendScaleFactorChanged(e) => { - app.world_mut().send_event(e); - } - WinitEvent::WindowCloseRequested(e) => { - app.world_mut().send_event(e); - } - WinitEvent::WindowCreated(e) => { - app.world_mut().send_event(e); - } - WinitEvent::WindowDestroyed(e) => { - app.world_mut().send_event(e); - } - WinitEvent::WindowFocused(e) => { - app.world_mut().send_event(e); - } - WinitEvent::WindowMoved(e) => { - app.world_mut().send_event(e); - } - WinitEvent::WindowOccluded(e) => { - app.world_mut().send_event(e); - } - WinitEvent::WindowResized(e) => { - app.world_mut().send_event(e); - } - WinitEvent::WindowScaleFactorChanged(e) => { - app.world_mut().send_event(e); - } - WinitEvent::WindowThemeChanged(e) => { - app.world_mut().send_event(e); - } - WinitEvent::MouseButtonInput(e) => { - app.world_mut().send_event(e); - } - WinitEvent::MouseMotion(e) => { - app.world_mut().send_event(e); - } - WinitEvent::MouseWheel(e) => { - app.world_mut().send_event(e); - } - WinitEvent::TouchpadMagnify(e) => { - app.world_mut().send_event(e); - } - WinitEvent::TouchpadRotate(e) => { - app.world_mut().send_event(e); - } - WinitEvent::TouchInput(e) => { - app.world_mut().send_event(e); - } - WinitEvent::KeyboardInput(e) => { - app.world_mut().send_event(e); - } - } - } - app.world_mut() - .resource_mut::>() - .send_batch(buffered_events.drain(..)); -} diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 3271cc26336f6..9806e97f8c6a3 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -7,10 +7,15 @@ use bevy_window::{ CursorGrabMode, Window, WindowMode, WindowPosition, WindowResolution, WindowWrapper, }; -use winit::{dpi::{LogicalSize, PhysicalPosition}, monitor::MonitorHandle}; +use winit::{ + dpi::{LogicalSize, PhysicalPosition}, + monitor::MonitorHandle, +}; use crate::{ - accessibility::{prepare_accessibility_for_window, AccessKitAdapters, WinitActionHandlers}, + accessibility::{ + prepare_accessibility_for_window, AccessKitAdapters, WinitActionRequestHandlers, + }, converters::{convert_enabled_buttons, convert_window_level, convert_window_theme}, }; @@ -38,7 +43,7 @@ impl WinitWindows { entity: Entity, window: &Window, adapters: &mut AccessKitAdapters, - handlers: &mut WinitActionHandlers, + handlers: &mut WinitActionRequestHandlers, accessibility_requested: &AccessibilityRequested, ) -> &WindowWrapper { let mut winit_window_attributes = winit::window::Window::default_attributes(); @@ -83,7 +88,8 @@ impl WinitWindows { let logical_size = LogicalSize::new(window.width(), window.height()); if let Some(sf) = window.resolution.scale_factor_override() { - winit_window_attributes.with_inner_size(logical_size.to_physical::(sf.into())) + winit_window_attributes + .with_inner_size(logical_size.to_physical::(sf.into())) } else { winit_window_attributes.with_inner_size(logical_size) } @@ -102,7 +108,8 @@ impl WinitWindows { #[cfg(target_os = "windows")] { use winit::platform::windows::WindowBuilderExtWindows; - winit_window_attributes = winit_window_attributes.with_skip_taskbar(window.skip_taskbar); + winit_window_attributes = + winit_window_attributes.with_skip_taskbar(window.skip_taskbar); } #[cfg(any( @@ -125,11 +132,12 @@ impl WinitWindows { ) ))] { - winit_window_attributes = winit::platform::wayland::WindowBuilderExtWayland::with_name( - winit_window_attributes, - name.clone(), - "", - ); + winit_window_attributes = + winit::platform::wayland::WindowBuilderExtWayland::with_name( + winit_window_attributes, + name.clone(), + "", + ); } #[cfg(all( diff --git a/examples/window/low_power.rs b/examples/window/low_power.rs index 98f096723a17e..9762eadbfa57f 100644 --- a/examples/window/low_power.rs +++ b/examples/window/low_power.rs @@ -3,13 +3,13 @@ //! This is useful for making desktop applications, or any other program that doesn't need to be //! running the event loop non-stop. +use bevy::winit::UserEvent; use bevy::{ prelude::*, utils::Duration, window::{PresentMode, WindowPlugin}, winit::{EventLoopProxy, WinitSettings}, }; -use bevy::winit::UserEvent; fn main() { App::new() From 1533a7a52e0855795f88ed6311702f07f7e54952 Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 15 May 2024 11:45:41 +0200 Subject: [PATCH 08/40] chore: fixed fmt --- crates/bevy_winit/src/TOBEDELETED_runner.rs | 621 -------------------- crates/bevy_winit/src/accessibility.rs | 20 +- crates/bevy_winit/src/winit_event.rs | 1 - 3 files changed, 10 insertions(+), 632 deletions(-) delete mode 100644 crates/bevy_winit/src/TOBEDELETED_runner.rs diff --git a/crates/bevy_winit/src/TOBEDELETED_runner.rs b/crates/bevy_winit/src/TOBEDELETED_runner.rs deleted file mode 100644 index 0ef6bbf819c8b..0000000000000 --- a/crates/bevy_winit/src/TOBEDELETED_runner.rs +++ /dev/null @@ -1,621 +0,0 @@ -use std::sync::mpsc::{sync_channel, SyncSender}; - -use approx::relative_eq; -use winit::{ - event::{self, DeviceEvent, Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, -}; -use winit::dpi::{LogicalSize, PhysicalSize}; -use winit::event::StartCause; -use winit::event_loop::ActiveEventLoop; -#[cfg(target_os = "android")] -pub use winit::platform::android::activity as android_activity; - -use bevy_a11y::AccessibilityRequested; -use bevy_app::{App, AppExit, PluginsState}; -use bevy_ecs::event::ManualEventReader; -use bevy_ecs::prelude::*; -use bevy_ecs::system::SystemState; -use bevy_input::{ - mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel}, - touchpad::{TouchpadMagnify, TouchpadRotate}, -}; -use bevy_math::{DVec2, ivec2, Vec2}; -#[cfg(not(target_arch = "wasm32"))] -use bevy_tasks::tick_global_task_pools_on_main_thread; -use bevy_utils::Instant; -use bevy_utils::tracing::{error, trace, warn}; -#[allow(deprecated)] -use bevy_window::{ - ApplicationLifetime, CursorEntered, CursorLeft, CursorMoved, - FileDragAndDrop, Ime, ReceivedCharacter, RequestRedraw, Window, - WindowBackendScaleFactorChanged, WindowCloseRequested, WindowCreated, WindowDestroyed, - WindowFocused, WindowMoved, WindowOccluded, WindowResized, WindowScaleFactorChanged, - WindowThemeChanged, -}; -#[cfg(target_os = "android")] -use bevy_window::{PrimaryWindow, RawHandleWrapper}; - -use crate::{AppSendEvent, converters, react_to_resize}; -use crate::accessibility::{AccessKitAdapters, WinitActionHandlers}; -use crate::state::{UpdateState, WinitAppRunnerState}; -use crate::system::{CachedWindow}; -pub use crate::system::create_windows; -use crate::UserEvent; -pub use crate::winit_config::*; -pub use crate::winit_event::*; -pub use crate::winit_windows::*; - -/// The parameters of the [`create_windows`] system. -pub type CreateWindowParams<'w, 's, F = ()> = ( - Commands<'w, 's>, - Query<'w, 's, (Entity, &'static mut Window), F>, - EventWriter<'w, WindowCreated>, - NonSendMut<'w, WinitWindows>, - NonSendMut<'w, AccessKitAdapters>, - ResMut<'w, WinitActionHandlers>, - Res<'w, AccessibilityRequested>, -); - -/// 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(mut app: App) -> AppExit { - if app.plugins_state() == PluginsState::Ready { - app.finish(); - app.cleanup(); - } - - let event_loop = app - .world_mut() - .remove_non_send_resource::>() - .unwrap(); - - app.world_mut() - .insert_non_send_resource(event_loop.create_proxy()); - - let mut runner_state = WinitAppRunnerState::default(); - - // Create a channel with a size of 1, since ideally only one exit code will be sent before exiting the app. - let (exit_sender, exit_receiver) = sync_channel(1); - - // prepare structures to access data in the world - let mut redraw_event_reader = ManualEventReader::::default(); - - let mut focused_windows_state: SystemState<(Res, Query<(Entity, &Window)>)> = - SystemState::new(app.world_mut()); - - let mut event_writer_system_state: SystemState<( - EventWriter, - NonSend, - Query<(&mut Window, &mut CachedWindow)>, - NonSendMut, - )> = SystemState::new(app.world_mut()); - - let mut create_window = - SystemState::>>::from_world(app.world_mut()); - let mut winit_events = Vec::default(); - - // set up the event loop - let event_handler = move |event, event_loop: &ActiveEventLoop| { - // The event loop is in the process of exiting, so don't deliver any new events - if event_loop.exiting() { - return; - } - - handle_winit_event( - &mut app, - &mut runner_state, - &mut create_window, - &mut event_writer_system_state, - &mut focused_windows_state, - &mut redraw_event_reader, - &mut winit_events, - &exit_sender, - event, - event_loop, - ); - }; - - 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(event_handler) { - error!("winit event loop returned an error: {err}"); - } - - // If everything is working correctly then the event loop only exits after it's sent a exit code. - exit_receiver - .try_recv() - .map_err(|err| error!("Failed to receive a app exit code! This is a bug. Reason: {err}")) - .unwrap_or(AppExit::error()) -} - -#[allow(clippy::too_many_arguments /* TODO: probs can reduce # of args */)] -fn handle_winit_event( - app: &mut App, - runner_state: &mut WinitAppRunnerState, - create_window: &mut SystemState>>, - event_writer_system_state: &mut SystemState<( - EventWriter, - NonSend, - Query<(&mut Window, &mut CachedWindow)>, - NonSendMut, - )>, - focused_windows_state: &mut SystemState<(Res, Query<(Entity, &Window)>)>, - redraw_event_reader: &mut ManualEventReader, - winit_events: &mut Vec, - exit_notify: &SyncSender, - event: Event, - event_loop: &ActiveEventLoop, -) { - #[cfg(feature = "trace")] - let _span = bevy_utils::tracing::info_span!("winit event_handler").entered(); - - if app.plugins_state() != PluginsState::Cleaned { - if app.plugins_state() != PluginsState::Ready { - #[cfg(not(target_arch = "wasm32"))] - tick_global_task_pools_on_main_thread(); - } else { - app.finish(); - app.cleanup(); - } - runner_state.redraw_requested = true; - } - - // create any new windows - // (even if app did not update, some may have been created by plugin setup) - create_windows(event_loop, create_window.get_mut(app.world_mut())); - create_window.apply(app.world_mut()); - - match event { - Event::AboutToWait => { - if let Some(app_redraw_events) = app.world().get_resource::>() { - if redraw_event_reader.read(app_redraw_events).last().is_some() { - runner_state.redraw_requested = true; - } - } - - let (config, windows) = focused_windows_state.get(app.world()); - let focused = windows.iter().any(|(_, window)| window.focused); - - let mut update_mode = config.update_mode(focused); - let mut should_update = should_update(runner_state, update_mode); - - if runner_state.startup_forced_updates > 0 { - runner_state.startup_forced_updates -= 1; - // Ensure that an update is triggered on the first iterations for app initialization - should_update = true; - } - - if runner_state.activity_state == UpdateState::WillSuspend { - runner_state.activity_state = UpdateState::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 = app - .world_mut() - .query_filtered::>(); - let entity = query.single(&app.world()); - app.world_mut() - .entity_mut(entity) - .remove::(); - } - } - - if runner_state.activity_state == UpdateState::WillResume { - runner_state.activity_state = UpdateState::Active; - // Trigger the update to enter the active state - should_update = true; - // Trigger the next redraw ro refresh the screen immediately - runner_state.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 = app - .world_mut() - .query_filtered::<(Entity, &Window), (With, Without)>(); - if let Ok((entity, window)) = query.get_single(&app.world()) { - let window = window.clone(); - - let ( - .., - mut winit_windows, - mut adapters, - mut handlers, - accessibility_requested, - ) = create_window.get_mut(app.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(); - - app.world_mut().entity_mut(entity).insert(wrapper); - } - } - } - - // 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. - run_app_update(runner_state, app, winit_events); - - // Running the app may have changed the WinitSettings resource, so we have to re-extract it. - let (config, windows) = focused_windows_state.get(app.world()); - let focused = windows.iter().any(|(_, window)| window.focused); - - update_mode = config.update_mode(focused); - } - - 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 = app.world().non_send_resource::(); - 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() { - runner_state.redraw_requested = true; - } - } - UpdateMode::Reactive { wait } | UpdateMode::ReactiveLowPower { 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 runner_state.wait_elapsed { - event_loop.set_control_flow(ControlFlow::WaitUntil(next)); - } - } - } - } - - if update_mode != runner_state.update_mode { - // Trigger the next redraw since we're changing the update mode - runner_state.redraw_requested = true; - runner_state.update_mode = update_mode; - } - - if runner_state.redraw_requested - && runner_state.activity_state != UpdateState::Suspended - { - let winit_windows = app.world().non_send_resource::(); - for window in winit_windows.windows.values() { - window.request_redraw(); - } - runner_state.redraw_requested = false; - } - } - Event::NewEvents(cause) => { - runner_state.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, - }; - } - Event::WindowEvent { - event, window_id, .. - } => { - let (mut window_resized, winit_windows, mut windows, mut access_kit_adapters) = - event_writer_system_state.get_mut(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); - } - } - - runner_state.window_event_received = true; - - match event { - WindowEvent::Resized(size) => { - react_to_resize(&mut win, size, &mut window_resized, window); - } - WindowEvent::CloseRequested => winit_events.send(WindowCloseRequested { window }), - WindowEvent::KeyboardInput { ref event, .. } => { - if event.state.is_pressed() { - if let Some(char) = &event.text { - let char = char.clone(); - #[allow(deprecated)] - winit_events.send(ReceivedCharacter { window, char }); - } - } - winit_events.send(converters::convert_keyboard_input(event, window)); - } - 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(); - winit_events.send(CursorMoved { - window, - position, - delta, - }); - } - WindowEvent::CursorEntered { .. } => { - winit_events.send(CursorEntered { window }); - } - WindowEvent::CursorLeft { .. } => { - win.set_physical_cursor_position(None); - winit_events.send(CursorLeft { window }); - } - WindowEvent::MouseInput { state, button, .. } => { - winit_events.send(MouseButtonInput { - button: converters::convert_mouse_button(button), - state: converters::convert_element_state(state), - window, - }); - } - WindowEvent::PinchGesture { delta, .. } => { - winit_events.send(TouchpadMagnify(delta as f32)); - } - WindowEvent::RotationGesture { delta, .. } => { - winit_events.send(TouchpadRotate(delta)); - } - WindowEvent::MouseWheel { delta, .. } => match delta { - event::MouseScrollDelta::LineDelta(x, y) => { - winit_events.send(MouseWheel { - unit: MouseScrollUnit::Line, - x, - y, - window, - }); - } - event::MouseScrollDelta::PixelDelta(p) => { - 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); - winit_events.send(converters::convert_touch_input(touch, location, window)); - } - WindowEvent::ScaleFactorChanged { - scale_factor, - mut inner_size_writer, - } => { - let prior_factor = win.resolution.scale_factor(); - win.resolution.set_scale_factor(scale_factor as f32); - // Note: this may be different from new_scale_factor if - // `scale_factor_override` is set to Some(thing) - let new_factor = win.resolution.scale_factor(); - - let mut new_inner_size = - PhysicalSize::new(win.physical_width(), win.physical_height()); - let scale_factor_override = win.resolution.scale_factor_override(); - if let Some(forced_factor) = scale_factor_override { - // This window is overriding the OS-suggested DPI, so its physical size - // should be set based on the overriding value. Its logical size already - // incorporates any resize constraints. - let maybe_new_inner_size = LogicalSize::new(win.width(), win.height()) - .to_physical::(forced_factor as f64); - if let Err(err) = inner_size_writer.request_inner_size(new_inner_size) { - warn!("Winit Failed to resize the window: {err}"); - } else { - new_inner_size = maybe_new_inner_size; - } - } - let new_logical_width = new_inner_size.width as f32 / new_factor; - let new_logical_height = new_inner_size.height as f32 / new_factor; - - let width_equal = relative_eq!(win.width(), new_logical_width); - let height_equal = relative_eq!(win.height(), new_logical_height); - win.resolution - .set_physical_resolution(new_inner_size.width, new_inner_size.height); - - winit_events.send(WindowBackendScaleFactorChanged { - window, - scale_factor, - }); - if scale_factor_override.is_none() && !relative_eq!(new_factor, prior_factor) { - winit_events.send(WindowScaleFactorChanged { - window, - scale_factor, - }); - } - - if !width_equal || !height_equal { - winit_events.send(WindowResized { - window, - width: new_logical_width, - height: new_logical_height, - }); - } - } - WindowEvent::Focused(focused) => { - win.focused = focused; - winit_events.send(WindowFocused { window, focused }); - } - WindowEvent::Occluded(occluded) => { - winit_events.send(WindowOccluded { window, occluded }); - } - WindowEvent::DroppedFile(path_buf) => { - winit_events.send(FileDragAndDrop::DroppedFile { window, path_buf }); - } - WindowEvent::HoveredFile(path_buf) => { - winit_events.send(FileDragAndDrop::HoveredFile { window, path_buf }); - } - WindowEvent::HoveredFileCancelled => { - winit_events.send(FileDragAndDrop::HoveredFileCanceled { window }); - } - WindowEvent::Moved(position) => { - let position = ivec2(position.x, position.y); - win.position.set(position); - winit_events.send(WindowMoved { window, position }); - } - WindowEvent::Ime(event) => match event { - event::Ime::Preedit(value, cursor) => { - winit_events.send(Ime::Preedit { - window, - value, - cursor, - }); - } - event::Ime::Commit(value) => { - winit_events.send(Ime::Commit { window, value }); - } - event::Ime::Enabled => { - winit_events.send(Ime::Enabled { window }); - } - event::Ime::Disabled => { - winit_events.send(Ime::Disabled { window }); - } - }, - WindowEvent::ThemeChanged(theme) => { - winit_events.send(WindowThemeChanged { - window, - theme: converters::convert_winit_theme(theme), - }); - } - WindowEvent::Destroyed => { - winit_events.send(WindowDestroyed { window }); - } - WindowEvent::RedrawRequested => { - run_app_update(runner_state, app, winit_events); - } - _ => {} - } - - let mut windows = app.world_mut().query::<(&mut Window, &mut CachedWindow)>(); - if let Ok((window_component, mut cache)) = windows.get_mut(app.world_mut(), window) { - if window_component.is_changed() { - cache.window = window_component.clone(); - } - } - } - Event::DeviceEvent { event, .. } => { - runner_state.device_event_received = true; - if let DeviceEvent::MouseMotion { delta: (x, y) } = event { - let delta = Vec2::new(x as f32, y as f32); - winit_events.send(MouseMotion { delta }); - } - } - Event::Suspended => { - winit_events.send(ApplicationLifetime::Suspended); - // Mark the state as `WillSuspend`. This will let the schedule run one last time - // before actually suspending to let the application react - runner_state.activity_state = UpdateState::WillSuspend; - } - Event::Resumed => { - match runner_state.activity_state { - UpdateState::NotYetStarted => winit_events.send(ApplicationLifetime::Started), - _ => winit_events.send(ApplicationLifetime::Resumed), - } - runner_state.activity_state = UpdateState::WillResume; - } - Event::UserEvent(UserEvent::WakeUp) => { - runner_state.redraw_requested = true; - } - _ => (), - } - - if let Some(app_exit) = app.should_exit() { - if let Err(err) = exit_notify.try_send(app_exit) { - error!("Failed to send a app exit notification! This is a bug. Reason: {err}"); - }; - event_loop.exit(); - return; - } - - // We drain events after every received winit event in addition to on app update to ensure - // the work of pushing events into event queues is spread out over time in case the app becomes - // dormant for a long stretch. - forward_winit_events(winit_events, app); -} - -fn should_update(runner_state: &WinitAppRunnerState, update_mode: UpdateMode) -> bool { - let handle_event = match update_mode { - UpdateMode::Continuous | UpdateMode::Reactive { .. } => { - runner_state.wait_elapsed - || runner_state.window_event_received - || runner_state.device_event_received - } - UpdateMode::ReactiveLowPower { .. } => { - runner_state.wait_elapsed || runner_state.window_event_received - } - }; - - handle_event && runner_state.activity_state.is_active() -} - -fn run_app_update( - runner_state: &mut WinitAppRunnerState, - app: &mut App, - winit_events: &mut Vec, -) { - runner_state.reset_on_update(); - - forward_winit_events(winit_events, app); - - if app.plugins_state() == PluginsState::Cleaned { - app.update(); - } -} diff --git a/crates/bevy_winit/src/accessibility.rs b/crates/bevy_winit/src/accessibility.rs index 40c1edb14229a..e2d471851b891 100644 --- a/crates/bevy_winit/src/accessibility.rs +++ b/crates/bevy_winit/src/accessibility.rs @@ -32,6 +32,16 @@ pub struct AccessKitAdapters(pub EntityHashMap); #[derive(Resource, Default, Deref, DerefMut)] pub struct WinitActionRequestHandlers(pub EntityHashMap>>); +/// Forwards `AccessKit` [`ActionRequest`]s from winit to an event channel. +#[derive(Clone, Default, Deref, DerefMut)] +pub struct WinitActionRequestHandler(pub VecDeque); + +impl WinitActionRequestHandler { + fn new() -> Arc> { + Arc::new(Mutex::new(Self(VecDeque::new()))) + } +} + struct AccessKitState { name: String, entity: Entity, @@ -88,16 +98,6 @@ impl WinitActivationHandler { } } -#[derive(Clone, Default, Deref, DerefMut)] -pub struct WinitActionRequestHandler(pub VecDeque); - -impl WinitActionRequestHandler { - fn new() -> Arc> { - Arc::new(Mutex::new(Self(VecDeque::new()))) - } -} - -/// Forwards `AccessKit` [`ActionRequest`]s from winit to an event channel. #[derive(Clone, Default)] struct WinitActionHandler(Arc>); diff --git a/crates/bevy_winit/src/winit_event.rs b/crates/bevy_winit/src/winit_event.rs index 84a638996b49d..568ea01d5c3b9 100644 --- a/crates/bevy_winit/src/winit_event.rs +++ b/crates/bevy_winit/src/winit_event.rs @@ -1,7 +1,6 @@ #![allow(deprecated)] #![allow(missing_docs)] -use bevy_app::App; use bevy_ecs::prelude::*; use bevy_input::keyboard::KeyboardInput; use bevy_input::touch::TouchInput; From ce6e392b6744f5a06c15cb55e069ba4ab99e07f7 Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 15 May 2024 12:59:14 +0200 Subject: [PATCH 09/40] fix: revisited AppLifecycle events --- crates/bevy_window/src/event.rs | 10 +- crates/bevy_window/src/lib.rs | 4 +- crates/bevy_winit/src/state.rs | 406 ++++++++++++++------------- crates/bevy_winit/src/winit_event.rs | 10 +- examples/mobile/src/lib.rs | 10 +- 5 files changed, 229 insertions(+), 211 deletions(-) diff --git a/crates/bevy_window/src/event.rs b/crates/bevy_window/src/event.rs index 9bc698acaed72..114ac44ac4dca 100644 --- a/crates/bevy_window/src/event.rs +++ b/crates/bevy_window/src/event.rs @@ -388,13 +388,17 @@ pub struct WindowThemeChanged { derive(serde::Serialize, serde::Deserialize), reflect(Serialize, Deserialize) )] -pub enum ApplicationLifetime { +pub enum AppLifecycle { /// The application just started. Started, + /// The application is going to be suspended. + /// Applications have one frame to react to this event before being paused in the background. + WillSuspend, /// The application was suspended. - /// - /// On Android, applications have one frame to react to this event before being paused in the background. Suspended, + /// The application is going to be resumed. + /// Applications have one extra frame to react to this event before being fully resumed. + WillResume, /// The application was resumed. Resumed, } diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index 7ecf8d21c0e02..fcb895a619e73 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -106,7 +106,7 @@ impl Plugin for WindowPlugin { .add_event::() .add_event::() .add_event::() - .add_event::(); + .add_event::(); if let Some(primary_window) = &self.primary_window { let initial_focus = app @@ -153,7 +153,7 @@ impl Plugin for WindowPlugin { .register_type::() .register_type::() .register_type::() - .register_type::(); + .register_type::(); // Register window descriptor and related types app.register_type::() diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index 6744408d0014c..0c555f6a4734e 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -25,7 +25,7 @@ use winit::window::WindowId; #[allow(deprecated)] use bevy_window::{ - ApplicationLifetime, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, + AppLifecycle, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, ReceivedCharacter, RequestRedraw, Window, WindowBackendScaleFactorChanged, WindowCloseRequested, WindowDestroyed, WindowFocused, WindowMoved, WindowOccluded, WindowResized, WindowScaleFactorChanged, WindowThemeChanged, @@ -80,6 +80,8 @@ pub(crate) struct WinitAppRunnerState { pub(crate) window_event_received: bool, /// Is `true` if a new [`DeviceEvent`] has been received since the last update. pub(crate) device_event_received: bool, + /// Is `true` if a new [`UserEvent`] has been received since the last update. + pub(crate) user_event_received: bool, /// Is `true` if the app has requested a redraw since the last update. pub(crate) redraw_requested: bool, /// Is `true` if enough time has elapsed since `last_update` to run another update. @@ -100,6 +102,7 @@ impl WinitAppRunnerState { 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 @@ -110,10 +113,26 @@ impl WinitAppRunnerState { pub(crate) fn reset_on_update(&mut self) { self.window_event_received = false; self.device_event_received = false; + self.user_event_received = false; } } impl ApplicationHandler for WinitAppRunnerState { + fn resumed(&mut self, _event_loop: &ActiveEventLoop) { + match self.activity_state { + UpdateState::NotYetStarted => self.winit_events.send(AppLifecycle::Started), + _ => self.winit_events.send(AppLifecycle::WillResume), + } + self.activity_state = UpdateState::WillResume; + } + + fn suspended(&mut self, _event_loop: &ActiveEventLoop) { + self.winit_events.send(AppLifecycle::WillSuspend); + // Mark the state as `WillSuspend`. This will let the schedule run one last time + // before actually suspending to let the application react + self.activity_state = UpdateState::WillSuspend; + } + fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) { #[cfg(feature = "trace")] let _span = bevy_utils::tracing::info_span!("winit event_handler").entered(); @@ -149,16 +168,187 @@ impl ApplicationHandler for WinitAppRunnerState { }; } - fn resumed(&mut self, _event_loop: &ActiveEventLoop) { - match self.activity_state { - UpdateState::NotYetStarted => self.winit_events.send(ApplicationLifetime::Started), - _ => self.winit_events.send(ApplicationLifetime::Resumed), + fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { + let mut redraw_event_reader = ManualEventReader::::default(); + + let mut focused_windows_state: SystemState<(Res, Query<(Entity, &Window)>)> = + SystemState::new(self.app.world_mut()); + + if let Some(app_redraw_events) = self.app.world().get_resource::>() { + if redraw_event_reader.read(app_redraw_events).last().is_some() { + self.redraw_requested = true; + } + } + + let (config, windows) = focused_windows_state.get(self.app.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.activity_state == UpdateState::WillSuspend { + self.winit_events.send(AppLifecycle::Suspended); + self.activity_state = UpdateState::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 = app + .world_mut() + .query_filtered::>(); + let entity = query.single(&app.world()); + app.world_mut() + .entity_mut(entity) + .remove::(); + } + } + + if self.activity_state == UpdateState::WillResume { + self.winit_events.send(AppLifecycle::Resumed); + self.activity_state = UpdateState::Active; + // Trigger the update to enter the active state + should_update = true; + // Trigger the next redraw ro 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 = app + .world_mut() + .query_filtered::<(Entity, &Window), (With, Without)>(); + if let Ok((entity, window)) = query.get_single(&app.world()) { + let window = window.clone(); + + let ( + .., + mut winit_windows, + mut adapters, + mut handlers, + accessibility_requested, + ) = create_window.get_mut(app.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(); + + app.world_mut().entity_mut(entity).insert(wrapper); + } + } + } + + // 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.app.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.app.world().non_send_resource::(); + 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 } | UpdateMode::ReactiveLowPower { 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.activity_state != UpdateState::Suspended { + let winit_windows = self.app.world().non_send_resource::(); + 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(); + return; } - self.activity_state = UpdateState::WillResume; } - fn user_event(&mut self, _event_loop: &ActiveEventLoop, _event: UserEvent) { - self.redraw_requested = true; + 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 window_event( @@ -377,9 +567,6 @@ impl ApplicationHandler for WinitAppRunnerState { WindowEvent::Destroyed => { self.winit_events.send(WindowDestroyed { window }); } - WindowEvent::RedrawRequested => { - self.run_app_update(); - } _ => {} } @@ -394,188 +581,9 @@ impl ApplicationHandler for WinitAppRunnerState { } } - 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) { - let mut redraw_event_reader = ManualEventReader::::default(); - - let mut focused_windows_state: SystemState<(Res, Query<(Entity, &Window)>)> = - SystemState::new(self.app.world_mut()); - - if let Some(app_redraw_events) = self.app.world().get_resource::>() { - if redraw_event_reader.read(app_redraw_events).last().is_some() { - self.redraw_requested = true; - } - } - - let (config, windows) = focused_windows_state.get(self.app.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.activity_state == UpdateState::WillSuspend { - self.activity_state = UpdateState::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 = app - .world_mut() - .query_filtered::>(); - let entity = query.single(&app.world()); - app.world_mut() - .entity_mut(entity) - .remove::(); - } - } - - if self.activity_state == UpdateState::WillResume { - self.activity_state = UpdateState::Active; - // Trigger the update to enter the active state - should_update = true; - // Trigger the next redraw ro 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 = app - .world_mut() - .query_filtered::<(Entity, &Window), (With, Without)>(); - if let Ok((entity, window)) = query.get_single(&app.world()) { - let window = window.clone(); - - let ( - .., - mut winit_windows, - mut adapters, - mut handlers, - accessibility_requested, - ) = create_window.get_mut(app.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(); - - app.world_mut().entity_mut(entity).insert(wrapper); - } - } - } - - // 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.app.world()); - let focused = windows.iter().any(|(_, window)| window.focused); - - update_mode = config.update_mode(focused); - } - - 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.app.world().non_send_resource::(); - 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 } | UpdateMode::ReactiveLowPower { 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 update_mode != self.update_mode { - // Trigger the next redraw since we're changing the update mode - self.redraw_requested = true; - self.update_mode = update_mode; - } - - if self.redraw_requested && self.activity_state != UpdateState::Suspended { - let winit_windows = self.app.world().non_send_resource::(); - 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(); - return; - } - } - - fn suspended(&mut self, _event_loop: &ActiveEventLoop) { - self.winit_events.send(ApplicationLifetime::Suspended); - // Mark the state as `WillSuspend`. This will let the schedule run one last time - // before actually suspending to let the application react - self.activity_state = UpdateState::WillSuspend; + fn user_event(&mut self, _event_loop: &ActiveEventLoop, _event: UserEvent) { + self.redraw_requested = true; + self.user_event_received = true; } } @@ -583,9 +591,15 @@ impl WinitAppRunnerState { fn should_update(&self, update_mode: UpdateMode) -> bool { let handle_event = match update_mode { UpdateMode::Continuous | UpdateMode::Reactive { .. } => { - self.wait_elapsed || self.window_event_received || self.device_event_received + self.wait_elapsed + || self.user_event_received + || self.window_event_received + || self.device_event_received } - UpdateMode::ReactiveLowPower { .. } => self.wait_elapsed || self.window_event_received, + UpdateMode::ReactiveLowPower { .. } => + self.wait_elapsed + || self.user_event_received + || self.window_event_received, }; handle_event && self.activity_state.is_active() @@ -611,7 +625,7 @@ impl WinitAppRunnerState { for winit_event in buffered_events.iter() { match winit_event.clone() { - WinitEvent::ApplicationLifetime(e) => { + WinitEvent::AppLifecycle(e) => { app.world_mut().send_event(e); } WinitEvent::CursorEntered(e) => { diff --git a/crates/bevy_winit/src/winit_event.rs b/crates/bevy_winit/src/winit_event.rs index 568ea01d5c3b9..a07c2cd610430 100644 --- a/crates/bevy_winit/src/winit_event.rs +++ b/crates/bevy_winit/src/winit_event.rs @@ -12,7 +12,7 @@ use bevy_reflect::Reflect; #[cfg(feature = "serialize")] use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; use bevy_window::{ - ApplicationLifetime, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, + AppLifecycle, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, ReceivedCharacter, RequestRedraw, WindowBackendScaleFactorChanged, WindowCloseRequested, WindowCreated, WindowDestroyed, WindowFocused, WindowMoved, WindowOccluded, WindowResized, WindowScaleFactorChanged, WindowThemeChanged, @@ -32,7 +32,7 @@ use bevy_window::{ reflect(Serialize, Deserialize) )] pub enum WinitEvent { - ApplicationLifetime(ApplicationLifetime), + AppLifecycle(AppLifecycle), CursorEntered(CursorEntered), CursorLeft(CursorLeft), CursorMoved(CursorMoved), @@ -63,9 +63,9 @@ pub enum WinitEvent { KeyboardInput(KeyboardInput), } -impl From for WinitEvent { - fn from(e: ApplicationLifetime) -> Self { - Self::ApplicationLifetime(e) +impl From for WinitEvent { + fn from(e: AppLifecycle) -> Self { + Self::AppLifecycle(e) } } impl From for WinitEvent { diff --git a/examples/mobile/src/lib.rs b/examples/mobile/src/lib.rs index 60af2455d52f4..4b5ca76f36992 100644 --- a/examples/mobile/src/lib.rs +++ b/examples/mobile/src/lib.rs @@ -4,7 +4,7 @@ use bevy::{ color::palettes::basic::*, input::touch::TouchPhase, prelude::*, - window::{ApplicationLifetime, WindowMode}, + window::{AppLifecycle, WindowMode}, }; // the `bevy_main` proc_macro generates the required boilerplate for iOS and Android @@ -166,14 +166,14 @@ fn setup_music(asset_server: Res, mut commands: Commands) { // Pause audio when app goes into background and resume when it returns. // This is handled by the OS on iOS, but not on Android. fn handle_lifetime( - mut lifetime_events: EventReader, + mut lifetime_events: EventReader, music_controller: Query<&AudioSink>, ) { for event in lifetime_events.read() { match event { - ApplicationLifetime::Suspended => music_controller.single().pause(), - ApplicationLifetime::Resumed => music_controller.single().play(), - ApplicationLifetime::Started => (), + AppLifecycle::Suspended => music_controller.single().pause(), + AppLifecycle::Resumed => music_controller.single().play(), + AppLifecycle::Started => (), } } } From 7e7ac675e87d93d17c0eb67ec42c01f9e1eef455 Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 15 May 2024 13:15:58 +0200 Subject: [PATCH 10/40] chore: refactored AppLifecycle --- crates/bevy_window/src/event.rs | 15 ++++++- crates/bevy_winit/src/state.rs | 80 ++++++++++++++------------------- 2 files changed, 47 insertions(+), 48 deletions(-) diff --git a/crates/bevy_window/src/event.rs b/crates/bevy_window/src/event.rs index 114ac44ac4dca..a6e73a8bcf12d 100644 --- a/crates/bevy_window/src/event.rs +++ b/crates/bevy_window/src/event.rs @@ -389,8 +389,10 @@ pub struct WindowThemeChanged { reflect(Serialize, Deserialize) )] pub enum AppLifecycle { + /// The application is not started yet. + NotYetStarted, /// The application just started. - Started, + Running, /// The application is going to be suspended. /// Applications have one frame to react to this event before being paused in the background. WillSuspend, @@ -402,3 +404,14 @@ pub enum AppLifecycle { /// The application was resumed. Resumed, } + +impl AppLifecycle { + /// Return `true` if the app can be updated. + #[inline] + pub fn is_active(&self) -> bool { + match self { + Self::NotYetStarted | Self::Suspended => false, + Self::Running | Self::WillSuspend | Self::WillResume | Self::Resumed => true, + } + } +} diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index 0c555f6a4734e..c7cb0a719c7ec 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -46,49 +46,32 @@ use crate::{ pub static ANDROID_APP: std::sync::OnceLock = std::sync::OnceLock::new(); -#[derive(PartialEq, Eq, Debug)] -pub(crate) enum UpdateState { - NotYetStarted, - Active, - Suspended, - WillSuspend, - WillResume, -} - -impl UpdateState { - #[inline] - pub(crate) fn is_active(&self) -> bool { - match self { - Self::NotYetStarted | Self::Suspended => false, - Self::Active | Self::WillSuspend | Self::WillResume => true, - } - } -} - /// Persistent state that is used to run the [`App`] according to the current /// [`UpdateMode`]. pub(crate) struct WinitAppRunnerState { /// The running app. - pub(crate) app: App, - /// Current activity state of the app. - pub(crate) activity_state: UpdateState, + app: App, /// Exit value once the loop is finished. - pub(crate) app_exit: Option, + app_exit: Option, /// Current update mode of the app. - pub(crate) update_mode: UpdateMode, + update_mode: UpdateMode, /// Is `true` if a new [`WindowEvent`] has been received since the last update. - pub(crate) window_event_received: bool, + window_event_received: bool, /// Is `true` if a new [`DeviceEvent`] has been received since the last update. - pub(crate) device_event_received: bool, + device_event_received: bool, /// Is `true` if a new [`UserEvent`] has been received since the last update. - pub(crate) user_event_received: bool, + user_event_received: bool, /// Is `true` if the app has requested a redraw since the last update. - pub(crate) redraw_requested: bool, + redraw_requested: bool, /// Is `true` if enough time has elapsed since `last_update` to run another update. - pub(crate) wait_elapsed: bool, + wait_elapsed: bool, /// Number of "forced" updates to trigger on application start - pub(crate) startup_forced_updates: u32, + 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, } @@ -97,7 +80,8 @@ impl WinitAppRunnerState { fn new(app: App) -> Self { Self { app, - activity_state: UpdateState::NotYetStarted, + lifecycle: AppLifecycle::NotYetStarted, + previous_lifecycle: AppLifecycle::NotYetStarted, app_exit: None, update_mode: UpdateMode::Continuous, window_event_received: false, @@ -110,7 +94,8 @@ impl WinitAppRunnerState { winit_events: Vec::new(), } } - pub(crate) fn reset_on_update(&mut self) { + + fn reset_on_update(&mut self) { self.window_event_received = false; self.device_event_received = false; self.user_event_received = false; @@ -119,18 +104,15 @@ impl WinitAppRunnerState { impl ApplicationHandler for WinitAppRunnerState { fn resumed(&mut self, _event_loop: &ActiveEventLoop) { - match self.activity_state { - UpdateState::NotYetStarted => self.winit_events.send(AppLifecycle::Started), - _ => self.winit_events.send(AppLifecycle::WillResume), - } - self.activity_state = UpdateState::WillResume; + // Mark the state as `WillResume`. This will let the schedule run one extra time + // when actually resuming the app + self.lifecycle = AppLifecycle::WillResume; } fn suspended(&mut self, _event_loop: &ActiveEventLoop) { - self.winit_events.send(AppLifecycle::WillSuspend); // Mark the state as `WillSuspend`. This will let the schedule run one last time // before actually suspending to let the application react - self.activity_state = UpdateState::WillSuspend; + self.lifecycle = AppLifecycle::WillSuspend; } fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) { @@ -192,9 +174,8 @@ impl ApplicationHandler for WinitAppRunnerState { should_update = true; } - if self.activity_state == UpdateState::WillSuspend { - self.winit_events.send(AppLifecycle::Suspended); - self.activity_state = UpdateState::Suspended; + if self.lifecycle == AppLifecycle::WillSuspend { + self.lifecycle = AppLifecycle::Suspended; // Trigger one last update to enter the suspended state should_update = true; @@ -212,9 +193,8 @@ impl ApplicationHandler for WinitAppRunnerState { } } - if self.activity_state == UpdateState::WillResume { - self.winit_events.send(AppLifecycle::Resumed); - self.activity_state = UpdateState::Active; + if self.lifecycle == AppLifecycle::WillResume { + self.lifecycle = AppLifecycle::Resumed; // Trigger the update to enter the active state should_update = true; // Trigger the next redraw ro refresh the screen immediately @@ -254,6 +234,12 @@ impl ApplicationHandler for WinitAppRunnerState { } } + // 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(); @@ -323,7 +309,7 @@ impl ApplicationHandler for WinitAppRunnerState { } } - if self.redraw_requested && self.activity_state != UpdateState::Suspended { + if self.redraw_requested && self.lifecycle != AppLifecycle::Suspended { let winit_windows = self.app.world().non_send_resource::(); for window in winit_windows.windows.values() { window.request_redraw(); @@ -602,7 +588,7 @@ impl WinitAppRunnerState { || self.window_event_received, }; - handle_event && self.activity_state.is_active() + handle_event && self.lifecycle.is_active() } fn run_app_update(&mut self) { From 7303fde1b397837207e675f075bc7b51706094e8 Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 15 May 2024 13:19:53 +0200 Subject: [PATCH 11/40] fix: fixing android compilation --- crates/bevy_winit/src/state.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index c7cb0a719c7ec..c629541b273ef 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -3,7 +3,7 @@ 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::{Added, Events, NonSend, Query}; +use bevy_ecs::prelude::*; use bevy_ecs::system::SystemState; use bevy_ecs::world::FromWorld; use bevy_input::{ @@ -183,11 +183,11 @@ impl ApplicationHandler for WinitAppRunnerState { { // Remove the `RawHandleWrapper` from the primary window. // This will trigger the surface destruction. - let mut query = app + let mut query = self.app .world_mut() .query_filtered::>(); - let entity = query.single(&app.world()); - app.world_mut() + let entity = query.single(&self.app.world()); + self.app.world_mut() .entity_mut(entity) .remove::(); } @@ -204,10 +204,10 @@ impl ApplicationHandler for WinitAppRunnerState { { // 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 = app + let mut query = self.app .world_mut() .query_filtered::<(Entity, &Window), (With, Without)>(); - if let Ok((entity, window)) = query.get_single(&app.world()) { + if let Ok((entity, window)) = query.get_single(&self.app.world()) { let window = window.clone(); let ( @@ -216,7 +216,7 @@ impl ApplicationHandler for WinitAppRunnerState { mut adapters, mut handlers, accessibility_requested, - ) = create_window.get_mut(app.world_mut()); + ) = create_window.get_mut(self.app.world_mut()); let winit_window = winit_windows.create_window( event_loop, @@ -229,7 +229,7 @@ impl ApplicationHandler for WinitAppRunnerState { let wrapper = RawHandleWrapper::new(winit_window).unwrap(); - app.world_mut().entity_mut(entity).insert(wrapper); + self.app.world_mut().entity_mut(entity).insert(wrapper); } } } From 577bce643e3bc4ce49c6bbb0ca38988bcdc0505c Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 15 May 2024 14:24:36 +0200 Subject: [PATCH 12/40] fix: fixing android compilation --- crates/bevy_winit/src/lib.rs | 12 ------------ crates/bevy_winit/src/state.rs | 10 ++-------- examples/mobile/src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index a576c5c44d689..7f672f516a7d0 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -12,7 +12,6 @@ //! The app's [runner](bevy_app::App::runner) is set by `WinitPlugin` and handles the `winit` [`EventLoop`]. //! See `winit_runner` for details. -use accesskit_winit::Event as AccessKitEvent; use winit::event_loop::EventLoop; #[cfg(target_os = "android")] pub use winit::platform::android::activity as android_activity; @@ -22,8 +21,6 @@ use bevy_app::{App, Last, Plugin}; use bevy_ecs::prelude::*; #[allow(deprecated)] use bevy_window::{exit_on_all_closed, Window, WindowCreated, WindowResized}; -#[cfg(target_os = "android")] -use bevy_window::{PrimaryWindow, RawHandleWrapper}; pub use system::create_windows; use system::{changed_windows, despawn_windows}; pub use winit_config::*; @@ -32,7 +29,6 @@ pub use winit_windows::*; use crate::accessibility::{AccessKitAdapters, AccessKitPlugin, WinitActionRequestHandlers}; use crate::state::winit_runner; -// use crate::runner::winit_runner; pub mod accessibility; mod converters; @@ -151,18 +147,10 @@ impl Plugin for WinitPlugin { /// The default event that can be used to wake the window loop #[derive(Debug)] pub enum UserEvent { - /// Wraps `accesskit` events - AccessKit(AccessKitEvent), /// Wakes up the loop if in wait state WakeUp, } -impl From for UserEvent { - fn from(evt: AccessKitEvent) -> Self { - UserEvent::AccessKit(evt) - } -} - /// The [`winit::event_loop::EventLoopProxy`] with the specific [`winit::event::Event::UserEvent`] used in the [`winit_runner`]. /// /// The `EventLoopProxy` can be used to request a redraw from outside bevy. diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index c629541b273ef..0b4f6c1dd3525 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -19,8 +19,6 @@ use winit::dpi::{LogicalSize, PhysicalSize}; use winit::event; use winit::event::{DeviceEvent, DeviceId, StartCause, WindowEvent}; use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; -#[cfg(target_os = "android")] -pub use winit::platform::android::activity as android_activity; use winit::window::WindowId; #[allow(deprecated)] @@ -40,12 +38,6 @@ use crate::{ UserEvent, WinitEvent, WinitSettings, WinitWindows, }; -/// [`AndroidApp`] provides an interface to query the application state as well as monitor events -/// (for example lifecycle and input events). -#[cfg(target_os = "android")] -pub static ANDROID_APP: std::sync::OnceLock = - std::sync::OnceLock::new(); - /// Persistent state that is used to run the [`App`] according to the current /// [`UpdateMode`]. pub(crate) struct WinitAppRunnerState { @@ -210,6 +202,8 @@ impl ApplicationHandler for WinitAppRunnerState { if let Ok((entity, window)) = query.get_single(&self.app.world()) { let window = window.clone(); + let mut create_window = SystemState::::from_world(self.app.world_mut()); + let ( .., mut winit_windows, diff --git a/examples/mobile/src/lib.rs b/examples/mobile/src/lib.rs index 4b5ca76f36992..ce639d25f647a 100644 --- a/examples/mobile/src/lib.rs +++ b/examples/mobile/src/lib.rs @@ -173,7 +173,7 @@ fn handle_lifetime( match event { AppLifecycle::Suspended => music_controller.single().pause(), AppLifecycle::Resumed => music_controller.single().play(), - AppLifecycle::Started => (), + _ => (), } } } From 6575d0f3cdcf652fc43fab1ac992e794f14ae85a Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 15 May 2024 14:44:09 +0200 Subject: [PATCH 13/40] fix: cleaning up code --- crates/bevy_winit/src/state.rs | 140 ++++++++++++++------------- crates/bevy_winit/src/winit_event.rs | 6 +- 2 files changed, 78 insertions(+), 68 deletions(-) diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index 0b4f6c1dd3525..492b44c4aae90 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -23,10 +23,10 @@ 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, + 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}; @@ -92,6 +92,14 @@ impl WinitAppRunnerState { 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 ApplicationHandler for WinitAppRunnerState { @@ -125,9 +133,9 @@ impl ApplicationHandler for WinitAppRunnerState { // create any new windows // (even if app did not update, some may have been created by plugin setup) let mut create_window = - SystemState::>>::from_world(self.app.world_mut()); - create_windows(event_loop, create_window.get_mut(self.app.world_mut())); - create_window.apply(self.app.world_mut()); + SystemState::>>::from_world(self.world_mut()); + create_windows(event_loop, create_window.get_mut(self.world_mut())); + create_window.apply(self.world_mut()); self.wait_elapsed = match cause { StartCause::WaitCancelled { @@ -146,15 +154,15 @@ impl ApplicationHandler for WinitAppRunnerState { let mut redraw_event_reader = ManualEventReader::::default(); let mut focused_windows_state: SystemState<(Res, Query<(Entity, &Window)>)> = - SystemState::new(self.app.world_mut()); + SystemState::new(self.world_mut()); - if let Some(app_redraw_events) = self.app.world().get_resource::>() { + if let Some(app_redraw_events) = self.world().get_resource::>() { if redraw_event_reader.read(app_redraw_events).last().is_some() { self.redraw_requested = true; } } - let (config, windows) = focused_windows_state.get(self.app.world()); + 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); @@ -175,11 +183,11 @@ impl ApplicationHandler for WinitAppRunnerState { { // Remove the `RawHandleWrapper` from the primary window. // This will trigger the surface destruction. - let mut query = self.app + let mut query = self .world_mut() .query_filtered::>(); - let entity = query.single(&self.app.world()); - self.app.world_mut() + let entity = query.single(&self.world()); + self.world_mut() .entity_mut(entity) .remove::(); } @@ -196,13 +204,13 @@ impl ApplicationHandler for WinitAppRunnerState { { // 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.app - .world_mut() + let mut query = self.world_mut() .query_filtered::<(Entity, &Window), (With, Without)>(); - if let Ok((entity, window)) = query.get_single(&self.app.world()) { + if let Ok((entity, window)) = query.get_single(&self.world()) { let window = window.clone(); - let mut create_window = SystemState::::from_world(self.app.world_mut()); + let mut create_window = + SystemState::::from_world(self.world_mut()); let ( .., @@ -210,7 +218,7 @@ impl ApplicationHandler for WinitAppRunnerState { mut adapters, mut handlers, accessibility_requested, - ) = create_window.get_mut(self.app.world_mut()); + ) = create_window.get_mut(self.world_mut()); let winit_window = winit_windows.create_window( event_loop, @@ -223,7 +231,7 @@ impl ApplicationHandler for WinitAppRunnerState { let wrapper = RawHandleWrapper::new(winit_window).unwrap(); - self.app.world_mut().entity_mut(entity).insert(wrapper); + self.world_mut().entity_mut(entity).insert(wrapper); } } } @@ -243,7 +251,7 @@ impl ApplicationHandler for WinitAppRunnerState { 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.app.world()); + let (config, windows) = focused_windows_state.get(self.world()); let focused = windows.iter().any(|(_, window)| window.focused); update_mode = config.update_mode(focused); @@ -272,7 +280,7 @@ impl ApplicationHandler for WinitAppRunnerState { all(target_os = "linux", any(feature = "x11", feature = "wayland")) )))] { - let winit_windows = self.app.world().non_send_resource::(); + let winit_windows = self.world().non_send_resource::(); let visible = winit_windows.windows.iter().any(|(_, w)| { w.is_visible().unwrap_or(false) }); @@ -304,7 +312,7 @@ impl ApplicationHandler for WinitAppRunnerState { } if self.redraw_requested && self.lifecycle != AppLifecycle::Suspended { - let winit_windows = self.app.world().non_send_resource::(); + let winit_windows = self.world().non_send_resource::(); for window in winit_windows.windows.values() { window.request_redraw(); } @@ -325,6 +333,7 @@ impl ApplicationHandler for WinitAppRunnerState { 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 }); @@ -337,15 +346,17 @@ impl ApplicationHandler for WinitAppRunnerState { window_id: WindowId, event: WindowEvent, ) { + self.window_event_received = true; + let mut event_writer_system_state: SystemState<( EventWriter, NonSend, Query<(&mut Window, &mut CachedWindow)>, NonSendMut, - )> = SystemState::new(self.app.world_mut()); + )> = SystemState::new(self.world_mut()); let (mut window_resized, winit_windows, mut windows, mut access_kit_adapters) = - event_writer_system_state.get_mut(self.app.world_mut()); + event_writer_system_state.get_mut(self.world_mut()); let Some(window) = winit_windows.get_window_entity(window_id) else { warn!("Skipped event {event:?} for unknown winit Window Id {window_id:?}"); @@ -365,8 +376,6 @@ impl ApplicationHandler for WinitAppRunnerState { } } - self.window_event_received = true; - match event { WindowEvent::Resized(size) => { react_to_resize(&mut win, size, &mut window_resized, window); @@ -550,11 +559,8 @@ impl ApplicationHandler for WinitAppRunnerState { _ => {} } - let mut windows = self - .app - .world_mut() - .query::<(&mut Window, &mut CachedWindow)>(); - if let Ok((window_component, mut cache)) = windows.get_mut(self.app.world_mut(), 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(); } @@ -562,8 +568,8 @@ impl ApplicationHandler for WinitAppRunnerState { } fn user_event(&mut self, _event_loop: &ActiveEventLoop, _event: UserEvent) { - self.redraw_requested = true; self.user_event_received = true; + self.redraw_requested = true; } } @@ -576,10 +582,9 @@ impl WinitAppRunnerState { || self.window_event_received || self.device_event_received } - UpdateMode::ReactiveLowPower { .. } => - self.wait_elapsed - || self.user_event_received - || self.window_event_received, + UpdateMode::ReactiveLowPower { .. } => { + self.wait_elapsed || self.user_event_received || self.window_event_received + } }; handle_event && self.lifecycle.is_active() @@ -596,95 +601,100 @@ impl WinitAppRunnerState { } fn forward_winit_events(&mut self) { - let buffered_events = &mut self.winit_events; - let app = &mut self.app; + let buffered_events = self + .winit_events + .drain(..) + .collect::>(); 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) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::CursorEntered(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::CursorLeft(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::CursorMoved(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::FileDragAndDrop(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::Ime(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::ReceivedCharacter(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::RequestRedraw(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::WindowBackendScaleFactorChanged(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::WindowCloseRequested(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::WindowCreated(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::WindowDestroyed(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::WindowFocused(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::WindowMoved(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::WindowOccluded(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::WindowResized(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::WindowScaleFactorChanged(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::WindowThemeChanged(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::MouseButtonInput(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::MouseMotion(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::MouseWheel(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::TouchpadMagnify(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::TouchpadRotate(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::TouchInput(e) => { - app.world_mut().send_event(e); + world.send_event(e); } WinitEvent::KeyboardInput(e) => { - app.world_mut().send_event(e); + world.send_event(e); } } } - app.world_mut() + + world .resource_mut::>() - .send_batch(buffered_events.drain(..)); + .send_batch(buffered_events); } } diff --git a/crates/bevy_winit/src/winit_event.rs b/crates/bevy_winit/src/winit_event.rs index a07c2cd610430..92d3c775a858b 100644 --- a/crates/bevy_winit/src/winit_event.rs +++ b/crates/bevy_winit/src/winit_event.rs @@ -12,9 +12,9 @@ use bevy_reflect::Reflect; #[cfg(feature = "serialize")] use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; use bevy_window::{ - AppLifecycle, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, - ReceivedCharacter, RequestRedraw, WindowBackendScaleFactorChanged, WindowCloseRequested, - WindowCreated, WindowDestroyed, WindowFocused, WindowMoved, WindowOccluded, WindowResized, + AppLifecycle, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, ReceivedCharacter, + RequestRedraw, WindowBackendScaleFactorChanged, WindowCloseRequested, WindowCreated, + WindowDestroyed, WindowFocused, WindowMoved, WindowOccluded, WindowResized, WindowScaleFactorChanged, WindowThemeChanged, }; From cd040ce8e0bed1bfadcd1ad52f6334b969de71a7 Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 15 May 2024 14:45:55 +0200 Subject: [PATCH 14/40] chore: fixed fmt --- crates/bevy_winit/src/state.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index 492b44c4aae90..ec19b8f811519 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -601,10 +601,7 @@ impl WinitAppRunnerState { } fn forward_winit_events(&mut self) { - let buffered_events = self - .winit_events - .drain(..) - .collect::>(); + let buffered_events = self.winit_events.drain(..).collect::>(); if buffered_events.is_empty() { return; From 5e5f2188c39825d7eeefffab14e6ef4ff1301ec2 Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 15 May 2024 15:17:33 +0200 Subject: [PATCH 15/40] fix: fixed wasm compilation --- crates/bevy_tasks/Cargo.toml | 2 +- crates/bevy_utils/Cargo.toml | 2 +- crates/bevy_winit/src/lib.rs | 17 ----------------- crates/bevy_winit/src/state.rs | 3 ++- crates/bevy_winit/src/winit_windows.rs | 2 +- 5 files changed, 5 insertions(+), 21 deletions(-) diff --git a/crates/bevy_tasks/Cargo.toml b/crates/bevy_tasks/Cargo.toml index c7db7eeae7aec..f2c59e073bde8 100644 --- a/crates/bevy_tasks/Cargo.toml +++ b/crates/bevy_tasks/Cargo.toml @@ -22,7 +22,7 @@ concurrent-queue = { version = "2.0.0", optional = true } wasm-bindgen-futures = "0.4" [dev-dependencies] -web-time = { version = "0.2" } +web-time = { version = "1.1" } [lints] workspace = true diff --git a/crates/bevy_utils/Cargo.toml b/crates/bevy_utils/Cargo.toml index 8b6ac50a76031..54e7bf938dfef 100644 --- a/crates/bevy_utils/Cargo.toml +++ b/crates/bevy_utils/Cargo.toml @@ -14,7 +14,7 @@ detailed_trace = [] [dependencies] ahash = "0.8.7" tracing = { version = "0.1", default-features = false, features = ["std"] } -web-time = { version = "0.2" } +web-time = { version = "1.1" } hashbrown = { version = "0.14", features = ["serde"] } bevy_utils_proc_macros = { version = "0.14.0-dev", path = "macros" } thread_local = "1.0" diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 7f672f516a7d0..54def0cf09a3b 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -32,7 +32,6 @@ use crate::state::winit_runner; pub mod accessibility; mod converters; -// mod runner; mod state; mod system; mod winit_config; @@ -122,22 +121,6 @@ impl Plugin for WinitPlugin { .build() .expect("Failed to build event loop"); - // iOS, macOS, and Android don't like it if you create windows before the event loop is - // initialized. - // - // See: - // - https://github.com/rust-windowing/winit/blob/master/README.md#macos - // - https://github.com/rust-windowing/winit/blob/master/README.md#ios - #[cfg(not(any(target_os = "android", target_os = "ios", target_os = "macos")))] - { - // Otherwise, we want to create a window before `bevy_render` initializes the renderer - // so that we have a surface to use as a hint. This improves compatibility with `wgpu` - // backends, especially WASM/WebGL2. - let mut create_window = SystemState::::from_world(app.world_mut()); - create_windows(&event_loop, create_window.get_mut(app.world_mut())); - create_window.apply(app.world_mut()); - } - // `winit`'s windows are bound to the event loop that created them, so the event loop must // be inserted as a resource here to pass it onto the runner. app.insert_non_send_resource(event_loop); diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index ec19b8f811519..3478f33eab274 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -12,8 +12,9 @@ use bevy_input::{ }; 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 std::time::Instant; +use bevy_utils::Instant; use winit::application::ApplicationHandler; use winit::dpi::{LogicalSize, PhysicalSize}; use winit::event; diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 9806e97f8c6a3..c76b3686a7e95 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -192,7 +192,7 @@ impl WinitWindows { #[cfg(target_arch = "wasm32")] { use wasm_bindgen::JsCast; - use winit::platform::web::WindowBuilderExtWebSys; + use winit::platform::web::WindowAttributesExtWebSys; if let Some(selector) = &window.canvas { let window = web_sys::window().unwrap(); From ffd88ccd76158980e3f16936523a4ebd54332baf Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 15 May 2024 15:27:01 +0200 Subject: [PATCH 16/40] fix: fixed AppLifecycle states --- crates/bevy_window/src/event.rs | 10 ++++------ crates/bevy_winit/src/state.rs | 10 +++++----- examples/mobile/src/lib.rs | 2 +- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/crates/bevy_window/src/event.rs b/crates/bevy_window/src/event.rs index a6e73a8bcf12d..682ccde3bd80f 100644 --- a/crates/bevy_window/src/event.rs +++ b/crates/bevy_window/src/event.rs @@ -390,8 +390,8 @@ pub struct WindowThemeChanged { )] pub enum AppLifecycle { /// The application is not started yet. - NotYetStarted, - /// The application just started. + Idle, + /// The application is running. Running, /// The application is going to be suspended. /// Applications have one frame to react to this event before being paused in the background. @@ -401,8 +401,6 @@ pub enum AppLifecycle { /// The application is going to be resumed. /// Applications have one extra frame to react to this event before being fully resumed. WillResume, - /// The application was resumed. - Resumed, } impl AppLifecycle { @@ -410,8 +408,8 @@ impl AppLifecycle { #[inline] pub fn is_active(&self) -> bool { match self { - Self::NotYetStarted | Self::Suspended => false, - Self::Running | Self::WillSuspend | Self::WillResume | Self::Resumed => true, + Self::Idle | Self::Suspended => false, + Self::Running | Self::WillSuspend | Self::WillResume => true, } } } diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index 3478f33eab274..5144ca707541a 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -73,8 +73,8 @@ impl WinitAppRunnerState { fn new(app: App) -> Self { Self { app, - lifecycle: AppLifecycle::NotYetStarted, - previous_lifecycle: AppLifecycle::NotYetStarted, + lifecycle: AppLifecycle::Idle, + previous_lifecycle: AppLifecycle::Idle, app_exit: None, update_mode: UpdateMode::Continuous, window_event_received: false, @@ -195,10 +195,10 @@ impl ApplicationHandler for WinitAppRunnerState { } if self.lifecycle == AppLifecycle::WillResume { - self.lifecycle = AppLifecycle::Resumed; - // Trigger the update to enter the active state + self.lifecycle = AppLifecycle::Running; + // Trigger the update to enter the running state should_update = true; - // Trigger the next redraw ro refresh the screen immediately + // Trigger the next redraw to refresh the screen immediately self.redraw_requested = true; #[cfg(target_os = "android")] diff --git a/examples/mobile/src/lib.rs b/examples/mobile/src/lib.rs index ce639d25f647a..22591a0f7cc8c 100644 --- a/examples/mobile/src/lib.rs +++ b/examples/mobile/src/lib.rs @@ -172,7 +172,7 @@ fn handle_lifetime( for event in lifetime_events.read() { match event { AppLifecycle::Suspended => music_controller.single().pause(), - AppLifecycle::Resumed => music_controller.single().play(), + AppLifecycle::Running => music_controller.single().play(), _ => (), } } From 299cad7d29480cfbc337a1f267817ed8e00d494a Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 15 May 2024 15:48:07 +0200 Subject: [PATCH 17/40] fix: refactored UpdateMode --- crates/bevy_winit/src/state.rs | 16 ++++++-- crates/bevy_winit/src/winit_config.rs | 59 +++++++++++++++------------ examples/window/low_power.rs | 9 ++-- 3 files changed, 51 insertions(+), 33 deletions(-) diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index 5144ca707541a..4f44055610d3d 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -302,7 +302,7 @@ impl ApplicationHandler for WinitAppRunnerState { self.redraw_requested = true; } } - UpdateMode::Reactive { wait } | UpdateMode::ReactiveLowPower { wait } => { + 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 { @@ -577,14 +577,22 @@ impl ApplicationHandler for WinitAppRunnerState { impl WinitAppRunnerState { fn should_update(&self, update_mode: UpdateMode) -> bool { let handle_event = match update_mode { - UpdateMode::Continuous | UpdateMode::Reactive { .. } => { + UpdateMode::Continuous => { self.wait_elapsed || self.user_event_received || self.window_event_received || self.device_event_received } - UpdateMode::ReactiveLowPower { .. } => { - self.wait_elapsed || self.user_event_received || self.window_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) } }; diff --git a/crates/bevy_winit/src/winit_config.rs b/crates/bevy_winit/src/winit_config.rs index 2e77de82aa2c7..be38736155303 100644 --- a/crates/bevy_winit/src/winit_config.rs +++ b/crates/bevy_winit/src/winit_config.rs @@ -18,9 +18,7 @@ impl WinitSettings { pub fn game() -> Self { WinitSettings { focused_mode: UpdateMode::Continuous, - unfocused_mode: UpdateMode::ReactiveLowPower { - wait: Duration::from_secs_f64(1.0 / 60.0), // 60Hz - }, + unfocused_mode: UpdateMode::reactive(Duration::from_secs_f64(1.0 / 60.0)), // 60Hz, } } @@ -32,12 +30,8 @@ impl WinitSettings { /// Use the [`EventLoopProxy`](crate::EventLoopProxy) to request a redraw from outside bevy. pub fn desktop_app() -> Self { WinitSettings { - focused_mode: UpdateMode::Reactive { - wait: Duration::from_secs(5), - }, - unfocused_mode: UpdateMode::ReactiveLowPower { - wait: Duration::from_secs(60), - }, + focused_mode: UpdateMode::reactive(Duration::from_secs(5)), + unfocused_mode: UpdateMode::reactive_low_power(Duration::from_secs(60)), } } @@ -72,7 +66,7 @@ pub enum UpdateMode { /// [`AppExit`](bevy_app::AppExit) event appears: /// - `wait` time has elapsed since the previous update /// - a redraw has been requested by [`RequestRedraw`](bevy_window::RequestRedraw) - /// - new [window](`winit::event::WindowEvent`) or [raw input](`winit::event::DeviceEvent`) + /// - new [window](`winit::event::WindowEvent`), [raw input](`winit::event::DeviceEvent`), or custom /// events have appeared /// - a redraw has been requested with the [`EventLoopProxy`](crate::EventLoopProxy) Reactive { @@ -81,23 +75,38 @@ pub enum UpdateMode { /// **Note:** This has no upper limit. /// The [`App`](bevy_app::App) will wait indefinitely if you set this to [`Duration::MAX`]. wait: Duration, + /// Reacts to device events, that will wake up the loop if it's in a wait wtate + react_to_device_events: bool, + /// Reacts to user events, that will wake up the loop if it's in a wait wtate + react_to_user_events: bool, + /// Reacts to window events, that will wake up the loop if it's in a wait wtate + react_to_window_events: bool, }, - /// The [`App`](bevy_app::App) will update in response to the following, until an - /// [`AppExit`](bevy_app::AppExit) event appears: - /// - `wait` time has elapsed since the previous update - /// - a redraw has been requested by [`RequestRedraw`](bevy_window::RequestRedraw) - /// - new [window events](`winit::event::WindowEvent`) have appeared - /// - a redraw has been requested with the [`EventLoopProxy`](crate::EventLoopProxy) +} + +impl UpdateMode { + /// Reactive mode, will update the app for any kind of event + pub fn reactive(wait: Duration) -> Self { + Self::Reactive { + wait, + react_to_device_events: true, + react_to_user_events: true, + react_to_window_events: true, + } + } + + /// Low power mode /// - /// **Note:** Unlike [`Reactive`](`UpdateMode::Reactive`), this mode will ignore events that + /// Unlike [`Reactive`](`UpdateMode::reactive()`), this will ignore events that /// don't come from interacting with a window, like [`MouseMotion`](winit::event::DeviceEvent::MouseMotion). - /// Use this mode if, for example, you only want your app to update when the mouse cursor is + /// Use this if, for example, you only want your app to update when the mouse cursor is /// moving over a window, not just moving in general. This can greatly reduce power consumption. - ReactiveLowPower { - /// The approximate time from the start of one update to the next. - /// - /// **Note:** This has no upper limit. - /// The [`App`](bevy_app::App) will wait indefinitely if you set this to [`Duration::MAX`]. - wait: Duration, - }, + pub fn reactive_low_power(wait: Duration) -> Self { + Self::Reactive { + wait, + react_to_device_events: true, + react_to_user_events: true, + react_to_window_events: false, + } + } } diff --git a/examples/window/low_power.rs b/examples/window/low_power.rs index 9762eadbfa57f..7c6af860b6504 100644 --- a/examples/window/low_power.rs +++ b/examples/window/low_power.rs @@ -20,9 +20,7 @@ fn main() { // You can also customize update behavior with the fields of [`WinitSettings`] .insert_resource(WinitSettings { focused_mode: bevy::winit::UpdateMode::Continuous, - unfocused_mode: bevy::winit::UpdateMode::ReactiveLowPower { - wait: Duration::from_millis(10), - }, + unfocused_mode: bevy::winit::UpdateMode::reactive_low_power(Duration::from_millis(10)), }) .insert_resource(ExampleMode::Game) .add_plugins(DefaultPlugins.set(WindowPlugin { @@ -79,7 +77,10 @@ fn update_winit( // (e.g. the mouse hovers over a visible part of the out of focus window), a // [`RequestRedraw`] event is received, or one minute has passed without the app // updating. - WinitSettings::desktop_app() + WinitSettings { + focused_mode: bevy::winit::UpdateMode::reactive(Duration::from_secs(1)), + unfocused_mode: bevy::winit::UpdateMode::reactive_low_power(Duration::from_secs(5)), + } } ApplicationWithRedraw => { // Sending a `RequestRedraw` event is useful when you want the app to update the next From c871130a699c81325c1094bf22f520fd23c25836 Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 15 May 2024 16:49:19 +0200 Subject: [PATCH 18/40] fix: extended WinitPlugin and loop to handle custom events --- Cargo.toml | 11 + crates/bevy_internal/src/default_plugins.rs | 2 +- crates/bevy_winit/src/lib.rs | 20 +- crates/bevy_winit/src/state.rs | 429 ++++++++++---------- examples/README.md | 1 + examples/window/custom_user_event.rs | 42 ++ 6 files changed, 283 insertions(+), 222 deletions(-) create mode 100644 examples/window/custom_user_event.rs diff --git a/Cargo.toml b/Cargo.toml index b8ebfd23269d4..31ed34765563d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2801,6 +2801,17 @@ description = "Creates a solid color window" category = "Window" wasm = true +[[example]] +name = "custom_user_event" +path = "examples/window/custom_user_event.rs" +doc-scrape-examples = true + +[package.metadata.example.custom_user_event] +name = "Custom User Event" +description = "Handles custom user events within the event loop" +category = "Window" +wasm = true + [[example]] name = "low_power" path = "examples/window/low_power.rs" diff --git a/crates/bevy_internal/src/default_plugins.rs b/crates/bevy_internal/src/default_plugins.rs index 10d595df6ea91..be9806553ec95 100644 --- a/crates/bevy_internal/src/default_plugins.rs +++ b/crates/bevy_internal/src/default_plugins.rs @@ -68,7 +68,7 @@ impl PluginGroup for DefaultPlugins { #[cfg(feature = "bevy_winit")] { - group = group.add(bevy_winit::WinitPlugin::default()); + group = group.add(bevy_winit::WinitPlugin::::default()); } #[cfg(feature = "bevy_render")] diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 54def0cf09a3b..751afbd08d455 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -12,6 +12,7 @@ //! The app's [runner](bevy_app::App::runner) is set by `WinitPlugin` and handles the `winit` [`EventLoop`]. //! See `winit_runner` for details. +use std::marker::PhantomData; use winit::event_loop::EventLoop; #[cfg(target_os = "android")] pub use winit::platform::android::activity as android_activity; @@ -51,7 +52,7 @@ pub static ANDROID_APP: std::sync::OnceLock = /// replace the existing [`App`] runner with one that constructs an [event loop](EventLoop) to /// receive window and input events from the OS. #[derive(Default)] -pub struct WinitPlugin { +pub struct WinitPlugin { /// Allows the window (and the event loop) to be created on any thread /// instead of only the main thread. /// @@ -62,11 +63,12 @@ pub struct WinitPlugin { /// Only works on Linux (X11/Wayland) and Windows. /// This field is ignored on other platforms. pub run_on_any_thread: bool, + marker: PhantomData, } -impl Plugin for WinitPlugin { +impl Plugin for WinitPlugin { fn build(&self, app: &mut App) { - let mut event_loop_builder = EventLoop::::with_user_event(); + let mut event_loop_builder = EventLoop::::with_user_event(); // linux check is needed because x11 might be enabled on other platforms. #[cfg(all(target_os = "linux", feature = "x11"))] @@ -103,7 +105,7 @@ impl Plugin for WinitPlugin { app.init_non_send_resource::() .init_resource::() .add_event::() - .set_runner(winit_runner) + .set_runner(winit_runner::) .add_systems( Last, ( @@ -128,18 +130,16 @@ impl Plugin for WinitPlugin { } /// The default event that can be used to wake the window loop -#[derive(Debug)] -pub enum UserEvent { - /// Wakes up the loop if in wait state - WakeUp, -} +/// Wakes up the loop if in wait state +#[derive(Debug, Default, Clone, Copy, Event)] +pub struct WakeUp; /// The [`winit::event_loop::EventLoopProxy`] with the specific [`winit::event::Event::UserEvent`] used in the [`winit_runner`]. /// /// The `EventLoopProxy` can be used to request a redraw from outside bevy. /// /// Use `NonSend` to receive this resource. -pub type EventLoopProxy = winit::event_loop::EventLoopProxy; +pub type EventLoopProxy = winit::event_loop::EventLoopProxy; trait AppSendEvent { fn send(&mut self, event: impl Into); diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index 4f44055610d3d..d9a8dc4809372 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -15,6 +15,7 @@ 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::{LogicalSize, PhysicalSize}; use winit::event; @@ -36,23 +37,23 @@ use crate::accessibility::AccessKitAdapters; use crate::system::CachedWindow; use crate::{ converters, create_windows, react_to_resize, AppSendEvent, CreateWindowParams, UpdateMode, - UserEvent, WinitEvent, WinitSettings, WinitWindows, + WinitEvent, WinitSettings, WinitWindows, }; /// Persistent state that is used to run the [`App`] according to the current /// [`UpdateMode`]. -pub(crate) struct WinitAppRunnerState { +pub struct WinitAppRunnerState { /// The running app. app: App, /// Exit value once the loop is finished. app_exit: Option, /// Current update mode of the app. update_mode: UpdateMode, - /// Is `true` if a new [`WindowEvent`] has been received since the last update. + /// Is `true` if a new [`WindowEvent`] event has been received since the last update. window_event_received: bool, - /// Is `true` if a new [`DeviceEvent`] has been received since the last update. + /// Is `true` if a new [`DeviceEvent`] event has been received since the last update. device_event_received: bool, - /// Is `true` if a new [`UserEvent`] has been received since the last update. + /// 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, @@ -67,10 +68,13 @@ pub(crate) struct WinitAppRunnerState { previous_lifecycle: AppLifecycle, /// Winit events to send winit_events: Vec, + _marker: PhantomData, } -impl WinitAppRunnerState { - fn new(app: App) -> Self { +impl WinitAppRunnerState { + fn new(mut app: App) -> Self { + app.add_event::(); + Self { app, lifecycle: AppLifecycle::Idle, @@ -85,6 +89,7 @@ impl WinitAppRunnerState { // 3 seems to be enough, 5 is a safe margin startup_forced_updates: 5, winit_events: Vec::new(), + _marker: PhantomData::default(), } } @@ -103,19 +108,7 @@ impl WinitAppRunnerState { } } -impl ApplicationHandler for WinitAppRunnerState { - 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 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; - } - +impl ApplicationHandler for WinitAppRunnerState { fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) { #[cfg(feature = "trace")] let _span = bevy_utils::tracing::info_span!("winit event_handler").entered(); @@ -151,194 +144,17 @@ impl ApplicationHandler for WinitAppRunnerState { }; } - fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { - let mut redraw_event_reader = ManualEventReader::::default(); - - let mut focused_windows_state: SystemState<(Res, Query<(Entity, &Window)>)> = - SystemState::new(self.world_mut()); - - if let Some(app_redraw_events) = self.world().get_resource::>() { - 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::>(); - let entity = query.single(&self.world()); - self.world_mut() - .entity_mut(entity) - .remove::(); - } - } - - 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, Without)>(); - if let Ok((entity, window)) = query.get_single(&self.world()) { - let window = window.clone(); - - let mut create_window = - SystemState::::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::(); - 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::(); - 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(); - return; - } + 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 device_event( - &mut self, - _event_loop: &ActiveEventLoop, - _device_id: DeviceId, - event: DeviceEvent, - ) { - self.device_event_received = true; + fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: T) { + self.user_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 }); - } + self.world_mut().send_event(event); + self.redraw_requested = true; } fn window_event( @@ -568,13 +384,204 @@ impl ApplicationHandler for WinitAppRunnerState { } } - fn user_event(&mut self, _event_loop: &ActiveEventLoop, _event: UserEvent) { - self.user_event_received = true; - self.redraw_requested = true; + 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) { + let mut redraw_event_reader = ManualEventReader::::default(); + + let mut focused_windows_state: SystemState<(Res, Query<(Entity, &Window)>)> = + SystemState::new(self.world_mut()); + + if let Some(app_redraw_events) = self.world().get_resource::>() { + 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::>(); + let entity = query.single(&self.world()); + self.world_mut() + .entity_mut(entity) + .remove::(); + } + } + + 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, Without)>(); + if let Ok((entity, window)) = query.get_single(&self.world()) { + let window = window.clone(); + + let mut create_window = + SystemState::::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::(); + 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::(); + 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(); + return; + } + } + + 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; } } -impl WinitAppRunnerState { +impl WinitAppRunnerState { fn should_update(&self, update_mode: UpdateMode) -> bool { let handle_event = match update_mode { UpdateMode::Continuous => { @@ -708,7 +715,7 @@ impl WinitAppRunnerState { /// /// Overriding the app's [runner](bevy_app::App::runner) while using `WinitPlugin` will bypass the /// `EventLoop`. -pub fn winit_runner(mut app: App) -> AppExit { +pub fn winit_runner(mut app: App) -> AppExit { if app.plugins_state() == PluginsState::Ready { app.finish(); app.cleanup(); @@ -716,7 +723,7 @@ pub fn winit_runner(mut app: App) -> AppExit { let event_loop = app .world_mut() - .remove_non_send_resource::>() + .remove_non_send_resource::>() .unwrap(); app.world_mut() diff --git a/examples/README.md b/examples/README.md index 0125037e08055..d3ed93b6b2206 100644 --- a/examples/README.md +++ b/examples/README.md @@ -452,6 +452,7 @@ Example | Description Example | Description --- | --- [Clear Color](../examples/window/clear_color.rs) | Creates a solid color window +[Custom User Event](../examples/window/custom_user_event.rs) | Handles custom user events within the event loop [Low Power](../examples/window/low_power.rs) | Demonstrates settings to reduce power use for bevy applications [Multiple Windows](../examples/window/multiple_windows.rs) | Demonstrates creating multiple windows, and rendering to them [Scale Factor Override](../examples/window/scale_factor_override.rs) | Illustrates how to customize the default window settings diff --git a/examples/window/custom_user_event.rs b/examples/window/custom_user_event.rs new file mode 100644 index 0000000000000..a369cf6271a8f --- /dev/null +++ b/examples/window/custom_user_event.rs @@ -0,0 +1,42 @@ +//! Shows how to create a custom event that can be handled by the event loop. + +use bevy::prelude::*; +use bevy::winit::{EventLoopProxy, WakeUp, WinitPlugin}; + +#[derive(Default, Event)] +struct CustomEvent {} + +fn main() { + let winit_plugin = WinitPlugin::::default(); + + App::new() + .add_plugins( + DefaultPlugins + .build() + .disable::>() + .add(winit_plugin), + ) + .add_systems(Startup, setup) + .add_systems(Update, (send_event, handle_event)) + .run(); +} + +fn setup(mut commands: Commands) { + commands.spawn(Camera2dBundle::default()); +} + +fn send_event( + input: Res>, + event_loop_proxy: NonSend>, +) { + if input.just_pressed(KeyCode::Space) { + let _ = event_loop_proxy.send_event(CustomEvent {}); + info!("Sending custom event through the proxy"); + } +} + +fn handle_event(mut events: EventReader) { + for _ in events.read() { + info!("Received event"); + } +} From ffe6f030cfefe1549a0e5452c1955236f22bd37b Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 15 May 2024 17:36:42 +0200 Subject: [PATCH 19/40] fix: fixing Windows and Linux compilation --- Cargo.toml | 5 ++ crates/bevy_winit/src/winit_windows.rs | 8 +-- examples/window/custom_user_event.rs | 77 ++++++++++++++++++++++++-- 3 files changed, 80 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 31ed34765563d..32e3a26138b35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -358,6 +358,11 @@ crossbeam-channel = "0.5.0" argh = "0.1.12" thiserror = "1.0" +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +once_cell = "1.16" +wasm-bindgen = { version = "0.2" } +web-sys = { version = "0.3", features = ["Window"] } + [[example]] name = "hello_world" path = "examples/hello_world.rs" diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index c76b3686a7e95..a57e3d2333614 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -107,7 +107,7 @@ impl WinitWindows { #[cfg(target_os = "windows")] { - use winit::platform::windows::WindowBuilderExtWindows; + use winit::platform::windows::WindowAttributesExtWindows; winit_window_attributes = winit_window_attributes.with_skip_taskbar(window.skip_taskbar); } @@ -133,7 +133,7 @@ impl WinitWindows { ))] { winit_window_attributes = - winit::platform::wayland::WindowBuilderExtWayland::with_name( + winit::platform::wayland::WindowAttributesExtWayland::with_name( winit_window_attributes, name.clone(), "", @@ -151,7 +151,7 @@ impl WinitWindows { ) ))] { - winit_window_attributes = winit::platform::x11::WindowBuilderExtX11::with_name( + winit_window_attributes = winit::platform::x11::WindowAttributesExtX11::with_name( winit_window_attributes, name.clone(), "", @@ -160,7 +160,7 @@ impl WinitWindows { #[cfg(target_os = "windows")] { winit_window_attributes = - winit::platform::windows::WindowBuilderExtWindows::with_class_name( + winit::platform::windows::WindowAttributesExtWindows::with_class_name( winit_window_attributes, name.clone(), ); diff --git a/examples/window/custom_user_event.rs b/examples/window/custom_user_event.rs index a369cf6271a8f..e54f47d67dc9b 100644 --- a/examples/window/custom_user_event.rs +++ b/examples/window/custom_user_event.rs @@ -1,10 +1,24 @@ //! Shows how to create a custom event that can be handled by the event loop. +use std::fmt::Formatter; use bevy::prelude::*; use bevy::winit::{EventLoopProxy, WakeUp, WinitPlugin}; -#[derive(Default, Event)] -struct CustomEvent {} +#[derive(Default, Debug, Event)] +enum CustomEvent { + #[default] + WakeUp, + Key(char), +} + +impl std::fmt::Display for CustomEvent { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::WakeUp => write!(f, "Wake up"), + Self::Key(ch) => write!(f, "Key: {ch}"), + } + } +} fn main() { let winit_plugin = WinitPlugin::::default(); @@ -16,7 +30,11 @@ fn main() { .disable::>() .add(winit_plugin), ) - .add_systems(Startup, setup) + .add_systems(Startup, ( + setup, + #[cfg(target_arch = "wasm32")] + wasm::expose_event_loop_proxy, + )) .add_systems(Update, (send_event, handle_event)) .run(); } @@ -30,13 +48,60 @@ fn send_event( event_loop_proxy: NonSend>, ) { if input.just_pressed(KeyCode::Space) { - let _ = event_loop_proxy.send_event(CustomEvent {}); + let _ = event_loop_proxy.send_event(CustomEvent::WakeUp); info!("Sending custom event through the proxy"); } } fn handle_event(mut events: EventReader) { - for _ in events.read() { - info!("Received event"); + for evt in events.read() { + info!("Received event: {evt:?}"); + } +} + +#[cfg(target_arch = "wasm32")] +pub(crate) mod wasm { + use std::sync::{Arc, Mutex}; + + use bevy::{ecs::system::NonSend, winit::EventLoopProxy}; + use once_cell::sync::Lazy; + + use wasm_bindgen::prelude::*; + use wasm_bindgen::JsCast; + use web_sys::KeyboardEvent; + use crate::CustomEvent; + + pub static EVENT_LOOP_PROXY: Lazy>>>> = + Lazy::new(|| Arc::new(Mutex::new(None))); + + pub(crate) fn expose_event_loop_proxy(event_loop_proxy: NonSend>) { + *EVENT_LOOP_PROXY.lock().unwrap() = Some((*event_loop_proxy).clone()); + + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + let closure = Closure::wrap(Box::new(move |event: KeyboardEvent| { + let key = event.key(); + if key == "e" { + send_custom_event('e').unwrap(); + } + }) as Box); + + document + .add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref()) + .unwrap(); + + closure.forget(); + } + + fn send_custom_event(ch: char) -> Result<(), String> { + let proxy = EVENT_LOOP_PROXY.lock().unwrap(); + if let Some(proxy) = &*proxy { + proxy + .send_event(CustomEvent::Key(ch)) + .map_err(|_| "Failed to send event".to_string()) + } else { + Err("Event loop proxy not found".to_string()) + } } } From f7b285af6fa68ce2c2799a84fa52092b2e51f454 Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 15 May 2024 17:38:42 +0200 Subject: [PATCH 20/40] chore: fixed fmt --- examples/window/custom_user_event.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/examples/window/custom_user_event.rs b/examples/window/custom_user_event.rs index e54f47d67dc9b..e2c4970135358 100644 --- a/examples/window/custom_user_event.rs +++ b/examples/window/custom_user_event.rs @@ -1,8 +1,8 @@ //! Shows how to create a custom event that can be handled by the event loop. -use std::fmt::Formatter; use bevy::prelude::*; use bevy::winit::{EventLoopProxy, WakeUp, WinitPlugin}; +use std::fmt::Formatter; #[derive(Default, Debug, Event)] enum CustomEvent { @@ -30,11 +30,14 @@ fn main() { .disable::>() .add(winit_plugin), ) - .add_systems(Startup, ( - setup, - #[cfg(target_arch = "wasm32")] - wasm::expose_event_loop_proxy, - )) + .add_systems( + Startup, + ( + setup, + #[cfg(target_arch = "wasm32")] + wasm::expose_event_loop_proxy, + ), + ) .add_systems(Update, (send_event, handle_event)) .run(); } @@ -66,10 +69,10 @@ pub(crate) mod wasm { use bevy::{ecs::system::NonSend, winit::EventLoopProxy}; use once_cell::sync::Lazy; + use crate::CustomEvent; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use web_sys::KeyboardEvent; - use crate::CustomEvent; pub static EVENT_LOOP_PROXY: Lazy>>>> = Lazy::new(|| Arc::new(Mutex::new(None))); From 5011160ef309da7981e9cbdd6f0435b690df2645 Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 15 May 2024 18:51:59 +0200 Subject: [PATCH 21/40] chore: fixed clippy --- crates/bevy_winit/src/accessibility.rs | 2 +- crates/bevy_winit/src/state.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/bevy_winit/src/accessibility.rs b/crates/bevy_winit/src/accessibility.rs index e2d471851b891..e72c6c8cfe79c 100644 --- a/crates/bevy_winit/src/accessibility.rs +++ b/crates/bevy_winit/src/accessibility.rs @@ -137,7 +137,7 @@ pub(crate) fn prepare_accessibility_for_window( let deactivation_handler = WinitDeactivationHandler; let adapter = Adapter::with_direct_handlers( - &winit_window, + winit_window, activation_handler, action_handler, deactivation_handler, diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index d9a8dc4809372..34193edb6a611 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -89,7 +89,7 @@ impl WinitAppRunnerState { // 3 seems to be enough, 5 is a safe margin startup_forced_updates: 5, winit_events: Vec::new(), - _marker: PhantomData::default(), + _marker: PhantomData, } } @@ -570,7 +570,6 @@ impl ApplicationHandler for WinitAppRunnerState { if let Some(app_exit) = self.app.should_exit() { self.app_exit = Some(app_exit); event_loop.exit(); - return; } } From cba01a2e07d6b8d2c9d444e165ff18232069e943 Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 15 May 2024 19:20:10 +0200 Subject: [PATCH 22/40] fix: added WinitPlugin name and fixed UpdateMode::reactive_low_power --- crates/bevy_winit/src/lib.rs | 4 ++++ crates/bevy_winit/src/winit_config.rs | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 751afbd08d455..d4e53eda117ba 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -67,6 +67,10 @@ pub struct WinitPlugin { } impl Plugin for WinitPlugin { + fn name(&self) -> &str { + "bevy_winit::WinitPlugin" + } + fn build(&self, app: &mut App) { let mut event_loop_builder = EventLoop::::with_user_event(); diff --git a/crates/bevy_winit/src/winit_config.rs b/crates/bevy_winit/src/winit_config.rs index be38736155303..727cf6a967bdd 100644 --- a/crates/bevy_winit/src/winit_config.rs +++ b/crates/bevy_winit/src/winit_config.rs @@ -104,9 +104,9 @@ impl UpdateMode { pub fn reactive_low_power(wait: Duration) -> Self { Self::Reactive { wait, - react_to_device_events: true, + react_to_device_events: false, react_to_user_events: true, - react_to_window_events: false, + react_to_window_events: true, } } } From 80900cc0a28bf04060ee6d1e3d3148fa2e0dd88a Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 15 May 2024 19:22:02 +0200 Subject: [PATCH 23/40] fix: restored reactive_low_power in WinitSettings::game --- crates/bevy_winit/src/winit_config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_winit/src/winit_config.rs b/crates/bevy_winit/src/winit_config.rs index 727cf6a967bdd..104384086693c 100644 --- a/crates/bevy_winit/src/winit_config.rs +++ b/crates/bevy_winit/src/winit_config.rs @@ -18,7 +18,7 @@ impl WinitSettings { pub fn game() -> Self { WinitSettings { focused_mode: UpdateMode::Continuous, - unfocused_mode: UpdateMode::reactive(Duration::from_secs_f64(1.0 / 60.0)), // 60Hz, + unfocused_mode: UpdateMode::reactive_low_power(Duration::from_secs_f64(1.0 / 60.0)), // 60Hz, } } From 8bddb18c12bba54d818a31226f63a956601e69af Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 15 May 2024 19:49:42 +0200 Subject: [PATCH 24/40] fix: cached event_writer_system_state --- crates/bevy_winit/src/state.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index 34193edb6a611..461320f696bfc 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -69,12 +69,26 @@ pub struct WinitAppRunnerState { /// Winit events to send winit_events: Vec, _marker: PhantomData, + + event_writer_system_state: SystemState<( + EventWriter<'static, WindowResized>, + NonSend<'static, WinitWindows>, + Query<'static, 'static, (&'static mut Window, &'static mut CachedWindow)>, + NonSendMut<'static, AccessKitAdapters>, + )> } impl WinitAppRunnerState { fn new(mut app: App) -> Self { app.add_event::(); + let event_writer_system_state: SystemState<( + EventWriter, + NonSend, + Query<(&mut Window, &mut CachedWindow)>, + NonSendMut, + )> = SystemState::new(app.world_mut()); + Self { app, lifecycle: AppLifecycle::Idle, @@ -90,6 +104,7 @@ impl WinitAppRunnerState { startup_forced_updates: 5, winit_events: Vec::new(), _marker: PhantomData, + event_writer_system_state, } } @@ -165,15 +180,8 @@ impl ApplicationHandler for WinitAppRunnerState { ) { self.window_event_received = true; - let mut event_writer_system_state: SystemState<( - EventWriter, - NonSend, - Query<(&mut Window, &mut CachedWindow)>, - NonSendMut, - )> = SystemState::new(self.world_mut()); - let (mut window_resized, winit_windows, mut windows, mut access_kit_adapters) = - event_writer_system_state.get_mut(self.world_mut()); + 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:?}"); From b63a7ad09581d46511c2cd4c00167141c9d78c8f Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 15 May 2024 19:55:09 +0200 Subject: [PATCH 25/40] fix: fixed low_power example --- crates/bevy_winit/src/state.rs | 2 +- examples/window/custom_user_event.rs | 4 +++- examples/window/low_power.rs | 6 +++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index 461320f696bfc..339c98d18b770 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -75,7 +75,7 @@ pub struct WinitAppRunnerState { NonSend<'static, WinitWindows>, Query<'static, 'static, (&'static mut Window, &'static mut CachedWindow)>, NonSendMut<'static, AccessKitAdapters>, - )> + )>, } impl WinitAppRunnerState { diff --git a/examples/window/custom_user_event.rs b/examples/window/custom_user_event.rs index e2c4970135358..e4e0249d6cf43 100644 --- a/examples/window/custom_user_event.rs +++ b/examples/window/custom_user_event.rs @@ -52,7 +52,9 @@ fn send_event( ) { if input.just_pressed(KeyCode::Space) { let _ = event_loop_proxy.send_event(CustomEvent::WakeUp); - info!("Sending custom event through the proxy"); + } + if input.just_pressed(KeyCode::KeyE) { + let _ = event_loop_proxy.send_event(CustomEvent::Key('e')); } } diff --git a/examples/window/low_power.rs b/examples/window/low_power.rs index 7c6af860b6504..70f0846dbd875 100644 --- a/examples/window/low_power.rs +++ b/examples/window/low_power.rs @@ -3,7 +3,7 @@ //! This is useful for making desktop applications, or any other program that doesn't need to be //! running the event loop non-stop. -use bevy::winit::UserEvent; +use bevy::winit::WakeUp; use bevy::{ prelude::*, utils::Duration, @@ -55,7 +55,7 @@ enum ExampleMode { fn update_winit( mode: Res, mut winit_config: ResMut, - event_loop_proxy: NonSend, + event_loop_proxy: NonSend>, ) { use ExampleMode::*; *winit_config = match *mode { @@ -89,7 +89,7 @@ fn update_winit( // when there are no inputs, so you send redraw requests while the animation is playing. // Note that in this example the RequestRedraw winit event will make the app run in the same // way as continuous - let _ = event_loop_proxy.send_event(UserEvent::WakeUp); + let _ = event_loop_proxy.send_event(WakeUp); WinitSettings::desktop_app() } }; From 6f09a1378cb833b5c726177a13c30f982622c721 Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 15 May 2024 22:19:50 +0200 Subject: [PATCH 26/40] fix: fixed mobile example on iOS --- examples/mobile/Makefile | 2 +- examples/mobile/src/lib.rs | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/examples/mobile/Makefile b/examples/mobile/Makefile index be01ae2e0ea80..c83ed40bb63a6 100644 --- a/examples/mobile/Makefile +++ b/examples/mobile/Makefile @@ -2,7 +2,7 @@ DEVICE = ${DEVICE_ID} ifndef DEVICE_ID - DEVICE=$(shell xcrun simctl list devices 'iOS' | grep -v 'unavailable' | grep -v '^--' | grep -v '==' | head -n 1 | grep -E -o -i "([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})") + DEVICE=$(shell xcrun simctl list devices 'iOS' | grep -v 'Shutdown' | grep -v '^--' | grep -v '==' | head -n 1 | grep -E -o -i "([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})") endif run: install diff --git a/examples/mobile/src/lib.rs b/examples/mobile/src/lib.rs index 22591a0f7cc8c..c7db3cabd46d1 100644 --- a/examples/mobile/src/lib.rs +++ b/examples/mobile/src/lib.rs @@ -167,12 +167,16 @@ fn setup_music(asset_server: Res, mut commands: Commands) { // This is handled by the OS on iOS, but not on Android. fn handle_lifetime( mut lifetime_events: EventReader, - music_controller: Query<&AudioSink>, + music_controller_q: Query<&AudioSink>, ) { + let Ok(music_controller) = music_controller_q.get_single() else { + return; + }; + for event in lifetime_events.read() { match event { - AppLifecycle::Suspended => music_controller.single().pause(), - AppLifecycle::Running => music_controller.single().play(), + AppLifecycle::Suspended => music_controller.pause(), + AppLifecycle::Running => music_controller.play(), _ => (), } } From e10e69c46586e131aafa404f1881f80856cc284c Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 15 May 2024 22:33:37 +0200 Subject: [PATCH 27/40] fix: fixed without winit example --- examples/app/without_winit.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/app/without_winit.rs b/examples/app/without_winit.rs index 6a1b3782a3350..afd28072b5dc3 100644 --- a/examples/app/without_winit.rs +++ b/examples/app/without_winit.rs @@ -1,11 +1,11 @@ //! Create an application without winit (runs single time, no event loop). use bevy::prelude::*; -use bevy::winit::WinitPlugin; +use bevy::winit::{WakeUp, WinitPlugin}; fn main() { App::new() - .add_plugins(DefaultPlugins.build().disable::()) + .add_plugins(DefaultPlugins.build().disable::>()) .add_systems(Update, setup_system) .run(); } From a9a46ed427c5e7bf664431b53b6510d9dc8f3305 Mon Sep 17 00:00:00 2001 From: Pietro Date: Thu, 16 May 2024 00:25:10 +0200 Subject: [PATCH 28/40] fix: fixed cleaning up windows when exiting --- crates/bevy_winit/src/state.rs | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index 339c98d18b770..c256f54922494 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -125,6 +125,10 @@ impl WinitAppRunnerState { impl ApplicationHandler for WinitAppRunnerState { 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(); @@ -139,13 +143,6 @@ impl ApplicationHandler for WinitAppRunnerState { self.redraw_requested = true; } - // create any new windows - // (even if app did not update, some may have been created by plugin setup) - let mut create_window = - SystemState::>>::from_world(self.world_mut()); - create_windows(event_loop, create_window.get_mut(self.world_mut())); - create_window.apply(self.world_mut()); - self.wait_elapsed = match cause { StartCause::WaitCancelled { requested_resume: Some(resume), @@ -407,6 +404,13 @@ impl ApplicationHandler for WinitAppRunnerState { } 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::>>::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::::default(); let mut focused_windows_state: SystemState<(Res, Query<(Entity, &Window)>)> = @@ -577,6 +581,7 @@ impl ApplicationHandler for WinitAppRunnerState { if let Some(app_exit) = self.app.should_exit() { self.app_exit = Some(app_exit); + event_loop.exit(); } } @@ -586,6 +591,11 @@ impl ApplicationHandler for WinitAppRunnerState { // 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 WinitAppRunnerState { From 9051b314266e5067fd7d855c9c58b3317a522686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Sun, 19 May 2024 01:11:22 +0200 Subject: [PATCH 29/40] support webgl2 --- crates/bevy_render/src/lib.rs | 24 ++++++++++++++++-------- crates/bevy_window/src/lib.rs | 7 ++++++- crates/bevy_window/src/raw_handle.rs | 11 ++++++++++- crates/bevy_winit/src/lib.rs | 12 +++++++++++- crates/bevy_winit/src/system.rs | 8 ++++++-- 5 files changed, 49 insertions(+), 13 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index dea5f7bb7d38f..120425fc948d3 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -58,7 +58,7 @@ use bevy_utils::prelude::default; pub use extract_param::Extract; use bevy_hierarchy::ValidParentCheckPlugin; -use bevy_window::{PrimaryWindow, RawHandleWrapper}; +use bevy_window::{PrimaryWindow, RawHandleWrapperHolder}; use extract_resource::ExtractResourcePlugin; use globals::GlobalsPlugin; use render_asset::RenderAssetBytesPerFrame; @@ -268,10 +268,9 @@ impl Plugin for RenderPlugin { )); let mut system_state: SystemState< - Query<&RawHandleWrapper, With>, + Query<&RawHandleWrapperHolder, With>, > = SystemState::new(app.world_mut()); let primary_window = system_state.get(app.world()).get_single().ok().cloned(); - let settings = render_creation.clone(); let async_renderer = async move { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { @@ -282,11 +281,20 @@ impl Plugin for RenderPlugin { }); // SAFETY: Plugins should be set up on the main thread. - let surface = primary_window.map(|wrapper| unsafe { - let handle = wrapper.get_handle(); - instance - .create_surface(handle) - .expect("Failed to create wgpu surface") + let surface = primary_window.and_then(|wrapper| unsafe { + let maybe_handle = wrapper.0.lock().expect( + "Couldn't get the window handle in time for renderer initialization", + ); + if let Some(wrapper) = maybe_handle.as_ref() { + let handle = wrapper.get_handle(); + Some( + instance + .create_surface(handle) + .expect("Failed to create wgpu surface"), + ) + } else { + None + } }); let request_adapter_options = wgpu::RequestAdapterOptions { diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index fcb895a619e73..9dd91bd86219d 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -11,6 +11,8 @@ //! The [`WindowPlugin`] sets up some global window-related parameters and //! is part of the [`DefaultPlugins`](https://docs.rs/bevy/latest/bevy/struct.DefaultPlugins.html). +use std::sync::{Arc, Mutex}; + use bevy_a11y::Focus; mod cursor; @@ -112,7 +114,10 @@ impl Plugin for WindowPlugin { let initial_focus = app .world_mut() .spawn(primary_window.clone()) - .insert(PrimaryWindow) + .insert(( + PrimaryWindow, + RawHandleWrapperHolder(Arc::new(Mutex::new(None))), + )) .id(); if let Some(mut focus) = app.world_mut().get_resource_mut::() { **focus = Some(initial_focus); diff --git a/crates/bevy_window/src/raw_handle.rs b/crates/bevy_window/src/raw_handle.rs index 0b799a1142936..81b9a37096a83 100644 --- a/crates/bevy_window/src/raw_handle.rs +++ b/crates/bevy_window/src/raw_handle.rs @@ -5,7 +5,12 @@ use raw_window_handle::{ DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle, WindowHandle, }; -use std::{any::Any, marker::PhantomData, ops::Deref, sync::Arc}; +use std::{ + any::Any, + marker::PhantomData, + ops::Deref, + sync::{Arc, Mutex}, +}; /// A wrapper over a window. /// @@ -116,3 +121,7 @@ impl HasDisplayHandle for ThreadLockedRawWindowHandleWrapper { Ok(unsafe { DisplayHandle::borrow_raw(self.0.display_handle) }) } } + +/// Holder of the [`RawHandleWrapper`] with wrappers, to allow use in asynchronous context +#[derive(Debug, Clone, Component)] +pub struct RawHandleWrapperHolder(pub Arc>>); diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index d4e53eda117ba..6d54214b02ab2 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -12,6 +12,7 @@ //! The app's [runner](bevy_app::App::runner) is set by `WinitPlugin` and handles the `winit` [`EventLoop`]. //! See `winit_runner` for details. +use bevy_window::RawHandleWrapperHolder; use std::marker::PhantomData; use winit::event_loop::EventLoop; #[cfg(target_os = "android")] @@ -158,7 +159,16 @@ impl AppSendEvent for Vec { /// The parameters of the [`create_windows`] system. pub type CreateWindowParams<'w, 's, F = ()> = ( Commands<'w, 's>, - Query<'w, 's, (Entity, &'static mut Window), F>, + Query< + 'w, + 's, + ( + Entity, + &'static mut Window, + Option<&'static RawHandleWrapperHolder>, + ), + F, + >, EventWriter<'w, WindowCreated>, NonSendMut<'w, WinitWindows>, NonSendMut<'w, AccessKitAdapters>, diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index ece5a3dbbfea5..40861a2d28996 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -45,7 +45,7 @@ pub fn create_windows( accessibility_requested, ): SystemParamItem>, ) { - for (entity, mut window) in &mut created_windows { + for (entity, mut window, handle_holder) in &mut created_windows { if winit_windows.get_window(entity).is_some() { continue; } @@ -78,7 +78,11 @@ pub fn create_windows( }); if let Ok(handle_wrapper) = RawHandleWrapper::new(winit_window) { - commands.entity(entity).insert(handle_wrapper); + let mut entity = commands.entity(entity); + entity.insert(handle_wrapper.clone()); + if let Some(handle_holder) = handle_holder { + *handle_holder.0.lock().unwrap() = Some(handle_wrapper); + } } #[cfg(target_arch = "wasm32")] From 00fa59b9abd61626ae7f61d77b3b6980a7fe12be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Sun, 19 May 2024 11:44:48 +0200 Subject: [PATCH 30/40] fix patches --- .../extra-window-resized-events.patch | 30 +++++++++---------- .../remove-desktop-app-mode.patch | 12 +++----- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/tools/example-showcase/extra-window-resized-events.patch b/tools/example-showcase/extra-window-resized-events.patch index 345e2f63822a3..c73d686aa6506 100644 --- a/tools/example-showcase/extra-window-resized-events.patch +++ b/tools/example-showcase/extra-window-resized-events.patch @@ -1,17 +1,17 @@ -diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs -index 63d79b1d2..83ed56293 100644 ---- a/crates/bevy_winit/src/lib.rs -+++ b/crates/bevy_winit/src/lib.rs -@@ -429,6 +429,12 @@ fn handle_winit_event( +diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs +index c256f5492..ae065111b 100644 +--- a/crates/bevy_winit/src/state.rs ++++ b/crates/bevy_winit/src/state.rs +@@ -198,6 +198,12 @@ impl ApplicationHandler for WinitAppRunnerState { + } + } - runner_state.window_event_received = true; - -+ window_resized.send(WindowResized { -+ window, -+ width: win.width(), -+ height: win.height(), -+ }); ++ window_resized.send(WindowResized { ++ window, ++ width: win.width(), ++ height: win.height(), ++ }); + - match event { - WindowEvent::Resized(size) => { - react_to_resize(&mut win, size, &mut window_resized, window); + match event { + WindowEvent::Resized(size) => { + react_to_resize(&mut win, size, &mut window_resized, window); diff --git a/tools/example-showcase/remove-desktop-app-mode.patch b/tools/example-showcase/remove-desktop-app-mode.patch index 7551ce3dc56e1..2d21e07902b89 100644 --- a/tools/example-showcase/remove-desktop-app-mode.patch +++ b/tools/example-showcase/remove-desktop-app-mode.patch @@ -1,18 +1,14 @@ diff --git a/crates/bevy_winit/src/winit_config.rs b/crates/bevy_winit/src/winit_config.rs -index f2cb424ec..e68e01de0 100644 +index 104384086..6e3c8dd83 100644 --- a/crates/bevy_winit/src/winit_config.rs +++ b/crates/bevy_winit/src/winit_config.rs -@@ -31,14 +31,7 @@ impl WinitSettings { +@@ -29,10 +29,7 @@ impl WinitSettings { /// /// Use the [`EventLoopProxy`](crate::EventLoopProxy) to request a redraw from outside bevy. pub fn desktop_app() -> Self { - WinitSettings { -- focused_mode: UpdateMode::Reactive { -- wait: Duration::from_secs(5), -- }, -- unfocused_mode: UpdateMode::ReactiveLowPower { -- wait: Duration::from_secs(60), -- }, +- focused_mode: UpdateMode::reactive(Duration::from_secs(5)), +- unfocused_mode: UpdateMode::reactive_low_power(Duration::from_secs(60)), - } + Self::default() } From e5eff40d16730be84efce1d72a4090d1375307ce Mon Sep 17 00:00:00 2001 From: Pietro Date: Thu, 16 May 2024 12:36:00 +0200 Subject: [PATCH 31/40] chore: simplified import --- crates/bevy_winit/src/winit_windows.rs | 42 ++++++++++++++------------ 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index a57e3d2333614..13b6ecb9e4d5e 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -9,7 +9,9 @@ use bevy_window::{ use winit::{ dpi::{LogicalSize, PhysicalPosition}, - monitor::MonitorHandle, + event_loop::ActiveEventLoop, + monitor::{MonitorHandle, VideoModeHandle}, + window::{CursorGrabMode as WinitCursorGrabMode, Fullscreen, WindowId, Window as WinitWindow} }; use crate::{ @@ -24,11 +26,11 @@ use crate::{ #[derive(Debug, Default)] pub struct WinitWindows { /// Stores [`winit`] windows by window identifier. - pub windows: HashMap>, + pub windows: HashMap>, /// Maps entities to `winit` window identifiers. - pub entity_to_winit: EntityHashMap, + pub entity_to_winit: EntityHashMap, /// Maps `winit` window identifiers to entities. - pub winit_to_entity: HashMap, + pub winit_to_entity: HashMap, // Many `winit` window functions (e.g. `set_window_icon`) can only be called on the main thread. // If they're called on other threads, the program might hang. This marker indicates that this // type is not thread-safe and will be `!Send` and `!Sync`. @@ -39,14 +41,14 @@ impl WinitWindows { /// Creates a `winit` window and associates it with our entity. pub fn create_window( &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, + event_loop: &ActiveEventLoop, entity: Entity, window: &Window, adapters: &mut AccessKitAdapters, handlers: &mut WinitActionRequestHandlers, accessibility_requested: &AccessibilityRequested, - ) -> &WindowWrapper { - let mut winit_window_attributes = winit::window::Window::default_attributes(); + ) -> &WindowWrapper { + let mut winit_window_attributes = WinitWindow::default_attributes(); // Due to a UIA limitation, winit windows need to be invisible for the // AccessKit adapter is initialized. @@ -54,7 +56,7 @@ impl WinitWindows { winit_window_attributes = match window.mode { WindowMode::BorderlessFullscreen => winit_window_attributes.with_fullscreen(Some( - winit::window::Fullscreen::Borderless(event_loop.primary_monitor()), + Fullscreen::Borderless(event_loop.primary_monitor()), )), mode @ (WindowMode::Fullscreen | WindowMode::SizedFullscreen) => { if let Some(primary_monitor) = event_loop.primary_monitor() { @@ -69,7 +71,7 @@ impl WinitWindows { }; winit_window_attributes - .with_fullscreen(Some(winit::window::Fullscreen::Exclusive(videomode))) + .with_fullscreen(Some(Fullscreen::Exclusive(videomode))) } else { warn!("Could not determine primary monitor, ignoring exclusive fullscreen request for window {:?}", window.title); winit_window_attributes @@ -252,7 +254,7 @@ impl WinitWindows { } /// Get the winit window that is associated with our entity. - pub fn get_window(&self, entity: Entity) -> Option<&WindowWrapper> { + pub fn get_window(&self, entity: Entity) -> Option<&WindowWrapper> { self.entity_to_winit .get(&entity) .and_then(|winit_id| self.windows.get(winit_id)) @@ -261,7 +263,7 @@ impl WinitWindows { /// Get the entity associated with the winit window id. /// /// This is mostly just an intermediary step between us and winit. - pub fn get_window_entity(&self, winit_id: winit::window::WindowId) -> Option { + pub fn get_window_entity(&self, winit_id: WindowId) -> Option { self.winit_to_entity.get(&winit_id).cloned() } @@ -271,7 +273,7 @@ impl WinitWindows { pub fn remove_window( &mut self, entity: Entity, - ) -> Option> { + ) -> Option> { let winit_id = self.entity_to_winit.remove(&entity)?; self.winit_to_entity.remove(&winit_id); self.windows.remove(&winit_id) @@ -285,7 +287,7 @@ pub fn get_fitting_videomode( monitor: &MonitorHandle, width: u32, height: u32, -) -> winit::monitor::VideoModeHandle { +) -> VideoModeHandle { let mut modes = monitor.video_modes().collect::>(); fn abs_diff(a: u32, b: u32) -> u32 { @@ -316,7 +318,7 @@ pub fn get_fitting_videomode( /// Gets the "best" video-mode handle from a monitor. /// /// The heuristic for "best" prioritizes width, height, and refresh rate in that order. -pub fn get_best_videomode(monitor: &MonitorHandle) -> winit::monitor::VideoModeHandle { +pub fn get_best_videomode(monitor: &MonitorHandle) -> VideoModeHandle { let mut modes = monitor.video_modes().collect::>(); modes.sort_by(|a, b| { use std::cmp::Ordering::*; @@ -334,15 +336,15 @@ pub fn get_best_videomode(monitor: &MonitorHandle) -> winit::monitor::VideoModeH modes.first().unwrap().clone() } -pub(crate) fn attempt_grab(winit_window: &winit::window::Window, grab_mode: CursorGrabMode) { +pub(crate) fn attempt_grab(winit_window: &WinitWindow, grab_mode: CursorGrabMode) { let grab_result = match grab_mode { - CursorGrabMode::None => winit_window.set_cursor_grab(winit::window::CursorGrabMode::None), + CursorGrabMode::None => winit_window.set_cursor_grab(WinitCursorGrabMode::None), CursorGrabMode::Confined => winit_window - .set_cursor_grab(winit::window::CursorGrabMode::Confined) - .or_else(|_e| winit_window.set_cursor_grab(winit::window::CursorGrabMode::Locked)), + .set_cursor_grab(WinitCursorGrabMode::Confined) + .or_else(|_e| winit_window.set_cursor_grab(WinitCursorGrabMode::Locked)), CursorGrabMode::Locked => winit_window - .set_cursor_grab(winit::window::CursorGrabMode::Locked) - .or_else(|_e| winit_window.set_cursor_grab(winit::window::CursorGrabMode::Confined)), + .set_cursor_grab(WinitCursorGrabMode::Locked) + .or_else(|_e| winit_window.set_cursor_grab(WinitCursorGrabMode::Confined)), }; if let Err(err) = grab_result { From 2016d839b5b27049161970a928215b3ab1257ec6 Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 22 May 2024 19:32:40 +0200 Subject: [PATCH 32/40] fix: fixed comments --- crates/bevy_internal/src/default_plugins.rs | 2 +- crates/bevy_winit/src/lib.rs | 5 ++++- crates/bevy_winit/src/state.rs | 2 +- examples/window/custom_user_event.rs | 4 +++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/crates/bevy_internal/src/default_plugins.rs b/crates/bevy_internal/src/default_plugins.rs index be9806553ec95..4af28a58b99ae 100644 --- a/crates/bevy_internal/src/default_plugins.rs +++ b/crates/bevy_internal/src/default_plugins.rs @@ -68,7 +68,7 @@ impl PluginGroup for DefaultPlugins { #[cfg(feature = "bevy_winit")] { - group = group.add(bevy_winit::WinitPlugin::::default()); + group = group.add::(bevy_winit::WinitPlugin::default()); } #[cfg(feature = "bevy_render")] diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 6d54214b02ab2..0c4d424928b5b 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -52,8 +52,11 @@ pub static ANDROID_APP: std::sync::OnceLock = /// This plugin will add systems and resources that sync with the `winit` backend and also /// replace the existing [`App`] runner with one that constructs an [event loop](EventLoop) to /// receive window and input events from the OS. +/// +/// The `T` event type can be used to pass custom events to the `winit`'s loop, and handled as events +/// in systems. #[derive(Default)] -pub struct WinitPlugin { +pub struct WinitPlugin { /// Allows the window (and the event loop) to be created on any thread /// instead of only the main thread. /// diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index c256f54922494..233957ad82b6d 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -42,7 +42,7 @@ use crate::{ /// Persistent state that is used to run the [`App`] according to the current /// [`UpdateMode`]. -pub struct WinitAppRunnerState { +struct WinitAppRunnerState { /// The running app. app: App, /// Exit value once the loop is finished. diff --git a/examples/window/custom_user_event.rs b/examples/window/custom_user_event.rs index e4e0249d6cf43..973d7547a73ca 100644 --- a/examples/window/custom_user_event.rs +++ b/examples/window/custom_user_event.rs @@ -1,4 +1,4 @@ -//! Shows how to create a custom event that can be handled by the event loop. +//! Shows how to create a custom event that can be handled by `winit`'s event loop. use bevy::prelude::*; use bevy::winit::{EventLoopProxy, WakeUp, WinitPlugin}; @@ -27,6 +27,8 @@ fn main() { .add_plugins( DefaultPlugins .build() + // Only one event type can be handled at once + // so we must disable the default event type .disable::>() .add(winit_plugin), ) From 67b9b4ea4c8e888f650f48550364f57b612a17df Mon Sep 17 00:00:00 2001 From: Pietro Date: Wed, 22 May 2024 19:40:06 +0200 Subject: [PATCH 33/40] =?UTF-8?q?chore:=20fmt=20=E2=9D=A4=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/bevy_winit/src/winit_windows.rs | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 13b6ecb9e4d5e..582c2a1efb0d7 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -11,7 +11,7 @@ use winit::{ dpi::{LogicalSize, PhysicalPosition}, event_loop::ActiveEventLoop, monitor::{MonitorHandle, VideoModeHandle}, - window::{CursorGrabMode as WinitCursorGrabMode, Fullscreen, WindowId, Window as WinitWindow} + window::{CursorGrabMode as WinitCursorGrabMode, Fullscreen, Window as WinitWindow, WindowId}, }; use crate::{ @@ -55,9 +55,8 @@ impl WinitWindows { winit_window_attributes = winit_window_attributes.with_visible(false); winit_window_attributes = match window.mode { - WindowMode::BorderlessFullscreen => winit_window_attributes.with_fullscreen(Some( - Fullscreen::Borderless(event_loop.primary_monitor()), - )), + WindowMode::BorderlessFullscreen => winit_window_attributes + .with_fullscreen(Some(Fullscreen::Borderless(event_loop.primary_monitor()))), mode @ (WindowMode::Fullscreen | WindowMode::SizedFullscreen) => { if let Some(primary_monitor) = event_loop.primary_monitor() { let videomode = match mode { @@ -70,8 +69,7 @@ impl WinitWindows { _ => unreachable!(), }; - winit_window_attributes - .with_fullscreen(Some(Fullscreen::Exclusive(videomode))) + winit_window_attributes.with_fullscreen(Some(Fullscreen::Exclusive(videomode))) } else { warn!("Could not determine primary monitor, ignoring exclusive fullscreen request for window {:?}", window.title); winit_window_attributes @@ -270,10 +268,7 @@ impl WinitWindows { /// Remove a window from winit. /// /// This should mostly just be called when the window is closing. - pub fn remove_window( - &mut self, - entity: Entity, - ) -> Option> { + pub fn remove_window(&mut self, entity: Entity) -> Option> { let winit_id = self.entity_to_winit.remove(&entity)?; self.winit_to_entity.remove(&winit_id); self.windows.remove(&winit_id) @@ -283,11 +278,7 @@ impl WinitWindows { /// Gets the "best" video mode which fits the given dimensions. /// /// The heuristic for "best" prioritizes width, height, and refresh rate in that order. -pub fn get_fitting_videomode( - monitor: &MonitorHandle, - width: u32, - height: u32, -) -> VideoModeHandle { +pub fn get_fitting_videomode(monitor: &MonitorHandle, width: u32, height: u32) -> VideoModeHandle { let mut modes = monitor.video_modes().collect::>(); fn abs_diff(a: u32, b: u32) -> u32 { From 032d45929c9d9bba6d346ed60f41ee0c01ca6483 Mon Sep 17 00:00:00 2001 From: Pietro Date: Thu, 23 May 2024 09:53:35 +0200 Subject: [PATCH 34/40] fix: fixed comments --- Cargo.toml | 1 - examples/app/without_winit.rs | 4 +-- examples/mobile/Makefile | 2 +- examples/mobile/src/lib.rs | 12 ++++--- examples/window/custom_user_event.rs | 48 ++++++++++++++++------------ 5 files changed, 38 insertions(+), 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 991969d844320..b8894d2d34135 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -359,7 +359,6 @@ argh = "0.1.12" thiserror = "1.0" [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -once_cell = "1.16" wasm-bindgen = { version = "0.2" } web-sys = { version = "0.3", features = ["Window"] } diff --git a/examples/app/without_winit.rs b/examples/app/without_winit.rs index afd28072b5dc3..6a1b3782a3350 100644 --- a/examples/app/without_winit.rs +++ b/examples/app/without_winit.rs @@ -1,11 +1,11 @@ //! Create an application without winit (runs single time, no event loop). use bevy::prelude::*; -use bevy::winit::{WakeUp, WinitPlugin}; +use bevy::winit::WinitPlugin; fn main() { App::new() - .add_plugins(DefaultPlugins.build().disable::>()) + .add_plugins(DefaultPlugins.build().disable::()) .add_systems(Update, setup_system) .run(); } diff --git a/examples/mobile/Makefile b/examples/mobile/Makefile index c83ed40bb63a6..0e02db34889c1 100644 --- a/examples/mobile/Makefile +++ b/examples/mobile/Makefile @@ -2,7 +2,7 @@ DEVICE = ${DEVICE_ID} ifndef DEVICE_ID - DEVICE=$(shell xcrun simctl list devices 'iOS' | grep -v 'Shutdown' | grep -v '^--' | grep -v '==' | head -n 1 | grep -E -o -i "([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})") + DEVICE=$(shell xcrun simctl list devices 'iOS' | grep -v 'unavailable' | grep -v 'Shutdown' | grep -v '^--' | grep -v '==' | head -n 1 | grep -E -o -i "([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})") endif run: install diff --git a/examples/mobile/src/lib.rs b/examples/mobile/src/lib.rs index c7db3cabd46d1..aa3f27c104883 100644 --- a/examples/mobile/src/lib.rs +++ b/examples/mobile/src/lib.rs @@ -166,18 +166,20 @@ fn setup_music(asset_server: Res, mut commands: Commands) { // Pause audio when app goes into background and resume when it returns. // This is handled by the OS on iOS, but not on Android. fn handle_lifetime( - mut lifetime_events: EventReader, - music_controller_q: Query<&AudioSink>, + mut lifecycle_events: EventReader, + music_controller: Query<&AudioSink>, ) { - let Ok(music_controller) = music_controller_q.get_single() else { + let Ok(music_controller) = music_controller.get_single() else { return; }; - for event in lifetime_events.read() { + for event in lifecycle_events.read() { match event { + AppLifecycle::Idle => {} + AppLifecycle::WillSuspend => {} AppLifecycle::Suspended => music_controller.pause(), + AppLifecycle::WillResume => {} AppLifecycle::Running => music_controller.play(), - _ => (), } } } diff --git a/examples/window/custom_user_event.rs b/examples/window/custom_user_event.rs index 973d7547a73ca..6fe3c57d7a034 100644 --- a/examples/window/custom_user_event.rs +++ b/examples/window/custom_user_event.rs @@ -3,6 +3,7 @@ use bevy::prelude::*; use bevy::winit::{EventLoopProxy, WakeUp, WinitPlugin}; use std::fmt::Formatter; +use std::sync::OnceLock; #[derive(Default, Debug, Event)] enum CustomEvent { @@ -20,6 +21,8 @@ impl std::fmt::Display for CustomEvent { } } +static EVENT_LOOP_PROXY: OnceLock> = OnceLock::new(); + fn main() { let winit_plugin = WinitPlugin::::default(); @@ -36,8 +39,9 @@ fn main() { Startup, ( setup, + expose_event_loop_proxy, #[cfg(target_arch = "wasm32")] - wasm::expose_event_loop_proxy, + wasm::setup_js_closure, ), ) .add_systems(Update, (send_event, handle_event)) @@ -48,42 +52,47 @@ fn setup(mut commands: Commands) { commands.spawn(Camera2dBundle::default()); } -fn send_event( - input: Res>, - event_loop_proxy: NonSend>, -) { +fn send_event(input: Res>) { + let Some(event_loop_proxy) = EVENT_LOOP_PROXY.get() else { + return; + }; + if input.just_pressed(KeyCode::Space) { let _ = event_loop_proxy.send_event(CustomEvent::WakeUp); } + + // This simulates sending a custom event through an external thread. + #[cfg(not(target_arch = "wasm32"))] if input.just_pressed(KeyCode::KeyE) { - let _ = event_loop_proxy.send_event(CustomEvent::Key('e')); + let handler = std::thread::spawn(|| { + let _ = event_loop_proxy.send_event(CustomEvent::Key('e')); + }); + + handler.join().unwrap(); } } +fn expose_event_loop_proxy(event_loop_proxy: NonSend>) { + EVENT_LOOP_PROXY.set((*event_loop_proxy).clone()).unwrap(); +} + fn handle_event(mut events: EventReader) { for evt in events.read() { info!("Received event: {evt:?}"); } } +/// Since the [`EventLoopProxy`] can be exposed to the javascript environment, it can +/// be used to send events inside the loop, to be handled by a system or simply to wake up +/// the loop if that's currently waiting for a timeout or a user event. #[cfg(target_arch = "wasm32")] pub(crate) mod wasm { - use std::sync::{Arc, Mutex}; - - use bevy::{ecs::system::NonSend, winit::EventLoopProxy}; - use once_cell::sync::Lazy; - - use crate::CustomEvent; + use crate::{CustomEvent, EVENT_LOOP_PROXY}; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use web_sys::KeyboardEvent; - pub static EVENT_LOOP_PROXY: Lazy>>>> = - Lazy::new(|| Arc::new(Mutex::new(None))); - - pub(crate) fn expose_event_loop_proxy(event_loop_proxy: NonSend>) { - *EVENT_LOOP_PROXY.lock().unwrap() = Some((*event_loop_proxy).clone()); - + pub(crate) fn setup_js_closure() { let window = web_sys::window().unwrap(); let document = window.document().unwrap(); @@ -102,8 +111,7 @@ pub(crate) mod wasm { } fn send_custom_event(ch: char) -> Result<(), String> { - let proxy = EVENT_LOOP_PROXY.lock().unwrap(); - if let Some(proxy) = &*proxy { + if let Some(proxy) = EVENT_LOOP_PROXY.get() { proxy .send_event(CustomEvent::Key(ch)) .map_err(|_| "Failed to send event".to_string()) From 4a2729c9117b6787414c60a54deb9397961de1f1 Mon Sep 17 00:00:00 2001 From: Pietro Date: Thu, 23 May 2024 16:01:45 +0200 Subject: [PATCH 35/40] fix: fix CI --- examples/mobile/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/mobile/src/lib.rs b/examples/mobile/src/lib.rs index aa3f27c104883..45a89a076c8ac 100644 --- a/examples/mobile/src/lib.rs +++ b/examples/mobile/src/lib.rs @@ -175,10 +175,8 @@ fn handle_lifetime( for event in lifecycle_events.read() { match event { - AppLifecycle::Idle => {} - AppLifecycle::WillSuspend => {} + AppLifecycle::Idle | AppLifecycle::WillSuspend | AppLifecycle::WillResume => {} AppLifecycle::Suspended => music_controller.pause(), - AppLifecycle::WillResume => {} AppLifecycle::Running => music_controller.play(), } } From 0dc017e607c33e8eee1f3e6ccf0b354cafcc9101 Mon Sep 17 00:00:00 2001 From: Pietro Date: Tue, 28 May 2024 12:52:37 +0200 Subject: [PATCH 36/40] fix: fixed scale factor override --- crates/bevy_window/src/window.rs | 10 +- crates/bevy_winit/src/lib.rs | 18 +--- crates/bevy_winit/src/state.rs | 126 ++++++++++++----------- crates/bevy_winit/src/system.rs | 46 ++++++++- examples/window/low_power.rs | 2 + examples/window/scale_factor_override.rs | 31 ++++-- 6 files changed, 139 insertions(+), 94 deletions(-) diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 453c9245b58d8..fdb8ae5c3d90f 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -687,10 +687,10 @@ impl Default for WindowResolution { impl WindowResolution { /// Creates a new [`WindowResolution`]. - pub fn new(logical_width: f32, logical_height: f32) -> Self { + pub fn new(physical_width: f32, physical_height: f32) -> Self { Self { - physical_width: logical_width as u32, - physical_height: logical_height as u32, + physical_width: physical_width as u32, + physical_height: physical_height as u32, ..Default::default() } } @@ -783,9 +783,7 @@ impl WindowResolution { /// Set the window's scale factor, this may get overridden by the backend. #[inline] pub fn set_scale_factor(&mut self, scale_factor: f32) { - let (width, height) = (self.width(), self.height()); self.scale_factor = scale_factor; - self.set(width, height); } /// Set the window's scale factor, this will be used over what the backend decides. @@ -794,9 +792,7 @@ impl WindowResolution { /// size is not within the limits. #[inline] pub fn set_scale_factor_override(&mut self, scale_factor_override: Option) { - let (width, height) = (self.width(), self.height()); self.scale_factor_override = scale_factor_override; - self.set(width, height); } } diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 0c4d424928b5b..8be97fc8082cc 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -22,7 +22,7 @@ use bevy_a11y::AccessibilityRequested; use bevy_app::{App, Last, Plugin}; use bevy_ecs::prelude::*; #[allow(deprecated)] -use bevy_window::{exit_on_all_closed, Window, WindowCreated, WindowResized}; +use bevy_window::{exit_on_all_closed, Window, WindowCreated}; pub use system::create_windows; use system::{changed_windows, despawn_windows}; pub use winit_config::*; @@ -178,19 +178,3 @@ pub type CreateWindowParams<'w, 's, F = ()> = ( ResMut<'w, WinitActionRequestHandlers>, Res<'w, AccessibilityRequested>, ); - -fn react_to_resize( - win: &mut Mut<'_, Window>, - size: winit::dpi::PhysicalSize, - window_resized: &mut EventWriter, - window: Entity, -) { - win.resolution - .set_physical_resolution(size.width, size.height); - - window_resized.send(WindowResized { - window, - width: win.width(), - height: win.height(), - }); -} diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index 233957ad82b6d..a79e6a34d6538 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -17,7 +17,7 @@ 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::{LogicalSize, PhysicalSize}; +use winit::dpi::PhysicalSize; use winit::event; use winit::event::{DeviceEvent, DeviceId, StartCause, WindowEvent}; use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; @@ -36,8 +36,8 @@ use bevy_window::{PrimaryWindow, RawHandleWrapper}; use crate::accessibility::AccessKitAdapters; use crate::system::CachedWindow; use crate::{ - converters, create_windows, react_to_resize, AppSendEvent, CreateWindowParams, UpdateMode, - WinitEvent, WinitSettings, WinitWindows, + converters, create_windows, AppSendEvent, CreateWindowParams, UpdateMode, WinitEvent, + WinitSettings, WinitWindows, }; /// Persistent state that is used to run the [`App`] according to the current @@ -72,6 +72,8 @@ struct WinitAppRunnerState { 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>, @@ -84,6 +86,8 @@ impl WinitAppRunnerState { let event_writer_system_state: SystemState<( EventWriter, + EventWriter, + EventWriter, NonSend, Query<(&mut Window, &mut CachedWindow)>, NonSendMut, @@ -177,8 +181,14 @@ impl ApplicationHandler for WinitAppRunnerState { ) { self.window_event_received = true; - let (mut window_resized, winit_windows, mut windows, mut access_kit_adapters) = - self.event_writer_system_state.get_mut(self.app.world_mut()); + 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:?}"); @@ -200,7 +210,16 @@ impl ApplicationHandler for WinitAppRunnerState { match event { WindowEvent::Resized(size) => { - react_to_resize(&mut win, size, &mut window_resized, window); + 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 }), WindowEvent::KeyboardInput { ref event, .. } => { @@ -275,58 +294,6 @@ impl ApplicationHandler for WinitAppRunnerState { self.winit_events .send(converters::convert_touch_input(touch, location, window)); } - WindowEvent::ScaleFactorChanged { - scale_factor, - mut inner_size_writer, - } => { - let prior_factor = win.resolution.scale_factor(); - win.resolution.set_scale_factor(scale_factor as f32); - // Note: this may be different from new_scale_factor if - // `scale_factor_override` is set to Some(thing) - let new_factor = win.resolution.scale_factor(); - - let mut new_inner_size = - PhysicalSize::new(win.physical_width(), win.physical_height()); - let scale_factor_override = win.resolution.scale_factor_override(); - if let Some(forced_factor) = scale_factor_override { - // This window is overriding the OS-suggested DPI, so its physical size - // should be set based on the overriding value. Its logical size already - // incorporates any resize constraints. - let maybe_new_inner_size = LogicalSize::new(win.width(), win.height()) - .to_physical::(forced_factor as f64); - if let Err(err) = inner_size_writer.request_inner_size(new_inner_size) { - warn!("Winit Failed to resize the window: {err}"); - } else { - new_inner_size = maybe_new_inner_size; - } - } - let new_logical_width = new_inner_size.width as f32 / new_factor; - let new_logical_height = new_inner_size.height as f32 / new_factor; - - let width_equal = relative_eq!(win.width(), new_logical_width); - let height_equal = relative_eq!(win.height(), new_logical_height); - win.resolution - .set_physical_resolution(new_inner_size.width, new_inner_size.height); - - self.winit_events.send(WindowBackendScaleFactorChanged { - window, - scale_factor, - }); - if scale_factor_override.is_none() && !relative_eq!(new_factor, prior_factor) { - self.winit_events.send(WindowScaleFactorChanged { - window, - scale_factor, - }); - } - - if !width_equal || !height_equal { - self.winit_events.send(WindowResized { - window, - width: new_logical_width, - height: new_logical_height, - }); - } - } WindowEvent::Focused(focused) => { win.focused = focused; self.winit_events.send(WindowFocused { window, focused }); @@ -760,3 +727,46 @@ pub fn winit_runner(mut app: App) -> AppExit { AppExit::error() }) } + +pub(crate) fn react_to_resize( + window_entity: Entity, + window: &mut Mut<'_, Window>, + size: PhysicalSize, + window_resized: &mut EventWriter, +) { + window + .resolution + .set_physical_resolution(size.width, size.height); + + bevy_log::info!("Resizing: {size:?}"); + 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, + window_scale_factor_changed: &mut EventWriter, +) { + 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, + }); + } +} diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index 40861a2d28996..d5d1176e48dc5 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -19,6 +19,7 @@ use bevy_ecs::query::With; #[cfg(target_arch = "wasm32")] use winit::platform::web::WindowExtWebSys; +use crate::state::react_to_resize; use crate::{ converters::{ self, convert_enabled_buttons, convert_window_level, convert_window_theme, @@ -177,19 +178,54 @@ pub(crate) fn changed_windows( WindowMode::Windowed => Some(None), }; + info!("WindowMode: {new_mode:?}"); + if let Some(new_mode) = new_mode { if winit_window.fullscreen() != new_mode { winit_window.set_fullscreen(new_mode); } } } + if window.resolution != cache.window.resolution { - let physical_size = PhysicalSize::new( - window.resolution.physical_width(), - window.resolution.physical_height(), + let mut physical_size = winit_window.inner_size(); + + let cached_physical_size = PhysicalSize::new( + cache.window.physical_width(), + cache.window.physical_height(), ); - if let Some(size_now) = winit_window.request_inner_size(physical_size) { - crate::react_to_resize(&mut window, size_now, &mut window_resized, entity); + + let base_scale_factor = window.resolution.base_scale_factor(); + + // Note: this may be different from `winit`'s base scale factor if + // `scale_factor_override` is set to Some(f32) + let scale_factor = window.scale_factor(); + let cached_scale_factor = cache.window.scale_factor(); + + // Check and update `winit`'s physical size only if the window is not maximized + if scale_factor != cached_scale_factor && !winit_window.is_maximized() { + let logical_size = + if let Some(cached_factor) = cache.window.resolution.scale_factor_override() { + physical_size.to_logical::(cached_factor as f64) + } else { + physical_size.to_logical::(base_scale_factor as f64) + }; + + // Scale factor changed, updating physical and logical size + if let Some(forced_factor) = window.resolution.scale_factor_override() { + // This window is overriding the OS-suggested DPI, so its physical size + // should be set based on the overriding value. Its logical size already + // incorporates any resize constraints. + physical_size = logical_size.to_physical::(forced_factor as f64); + } else { + physical_size = logical_size.to_physical::(base_scale_factor as f64); + } + } + + if physical_size != cached_physical_size { + if let Some(new_physical_size) = winit_window.request_inner_size(physical_size) { + react_to_resize(entity, &mut window, new_physical_size, &mut window_resized); + } } } diff --git a/examples/window/low_power.rs b/examples/window/low_power.rs index 70f0846dbd875..795d064b352b6 100644 --- a/examples/window/low_power.rs +++ b/examples/window/low_power.rs @@ -3,6 +3,7 @@ //! This is useful for making desktop applications, or any other program that doesn't need to be //! running the event loop non-stop. +use bevy::window::WindowResolution; use bevy::winit::WakeUp; use bevy::{ prelude::*, @@ -27,6 +28,7 @@ fn main() { primary_window: Some(Window { // Turn off vsync to maximize CPU/GPU usage present_mode: PresentMode::AutoNoVsync, + resolution: WindowResolution::new(800., 640.).with_scale_factor_override(1.), ..default() }), ..default() diff --git a/examples/window/scale_factor_override.rs b/examples/window/scale_factor_override.rs index 8ca92469c108c..8139f982aa3e0 100644 --- a/examples/window/scale_factor_override.rs +++ b/examples/window/scale_factor_override.rs @@ -3,6 +3,9 @@ use bevy::{prelude::*, window::WindowResolution}; +#[derive(Component)] +struct CustomText; + fn main() { App::new() .add_plugins(DefaultPlugins.set(WindowPlugin { @@ -39,7 +42,7 @@ fn setup(mut commands: Commands) { parent .spawn(NodeBundle { style: Style { - width: Val::Px(200.0), + width: Val::Px(300.0), height: Val::Percent(100.0), border: UiRect::all(Val::Px(2.0)), ..default() @@ -48,7 +51,8 @@ fn setup(mut commands: Commands) { ..default() }) .with_children(|parent| { - parent.spawn( + parent.spawn(( + CustomText, TextBundle::from_section( "Example text", TextStyle { @@ -60,19 +64,32 @@ fn setup(mut commands: Commands) { align_self: AlignSelf::FlexEnd, ..default() }), - ); + )); }); }); } /// Set the title of the window to the current override -fn display_override(mut windows: Query<&mut Window>) { +fn display_override( + mut windows: Query<&mut Window>, + mut custom_text: Query<&mut Text, With>, +) { let mut window = windows.single_mut(); - window.title = format!( - "Scale override: {:?}", - window.resolution.scale_factor_override() + let text = format!( + "Scale factor: {:.1} {}", + window.scale_factor(), + if window.resolution.scale_factor_override().is_some() { + "(overridden)" + } else { + "(default)" + } ); + + window.title = text.clone(); + + let mut custom_text = custom_text.single_mut(); + custom_text.sections[0].value = text; } /// This system toggles scale factor overrides when enter is pressed From d85ed0537542b67980f7c90c7fc308d3e864364f Mon Sep 17 00:00:00 2001 From: Pietro Date: Tue, 28 May 2024 13:16:14 +0200 Subject: [PATCH 37/40] fix: fixed CI --- examples/window/scale_factor_override.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/window/scale_factor_override.rs b/examples/window/scale_factor_override.rs index 8139f982aa3e0..b814dac6f41f6 100644 --- a/examples/window/scale_factor_override.rs +++ b/examples/window/scale_factor_override.rs @@ -86,7 +86,7 @@ fn display_override( } ); - window.title = text.clone(); + window.title.clone_from(&text); let mut custom_text = custom_text.single_mut(); custom_text.sections[0].value = text; From 1307fca6ab6e49b812ab35445598cea1b611fd16 Mon Sep 17 00:00:00 2001 From: Pietro Date: Tue, 28 May 2024 13:22:52 +0200 Subject: [PATCH 38/40] chore: removed logs --- crates/bevy_winit/src/state.rs | 1 - crates/bevy_winit/src/system.rs | 2 -- 2 files changed, 3 deletions(-) diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index a79e6a34d6538..df0aab42dec31 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -738,7 +738,6 @@ pub(crate) fn react_to_resize( .resolution .set_physical_resolution(size.width, size.height); - bevy_log::info!("Resizing: {size:?}"); window_resized.send(WindowResized { window: window_entity, width: window.width(), diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index d5d1176e48dc5..57a9de8978adb 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -178,8 +178,6 @@ pub(crate) fn changed_windows( WindowMode::Windowed => Some(None), }; - info!("WindowMode: {new_mode:?}"); - if let Some(new_mode) = new_mode { if winit_window.fullscreen() != new_mode { winit_window.set_fullscreen(new_mode); From 08b68f768a504cedb59ded8b00b9f953416ee81b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Sat, 1 Jun 2024 21:46:54 +0200 Subject: [PATCH 39/40] update patch --- tools/example-showcase/extra-window-resized-events.patch | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/example-showcase/extra-window-resized-events.patch b/tools/example-showcase/extra-window-resized-events.patch index c73d686aa6506..71fe860356cad 100644 --- a/tools/example-showcase/extra-window-resized-events.patch +++ b/tools/example-showcase/extra-window-resized-events.patch @@ -1,8 +1,8 @@ diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs -index c256f5492..ae065111b 100644 +index df0aab42d..6e28a6e9c 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs -@@ -198,6 +198,12 @@ impl ApplicationHandler for WinitAppRunnerState { +@@ -208,6 +208,12 @@ impl ApplicationHandler for WinitAppRunnerState { } } @@ -14,4 +14,4 @@ index c256f5492..ae065111b 100644 + match event { WindowEvent::Resized(size) => { - react_to_resize(&mut win, size, &mut window_resized, window); + react_to_resize(window, &mut win, size, &mut window_resized); From 0a8e99ffbe6f21e092b79ce97441fd4911889596 Mon Sep 17 00:00:00 2001 From: Pietro <106191044+pietrosophya@users.noreply.github.com> Date: Mon, 3 Jun 2024 09:51:25 +0200 Subject: [PATCH 40/40] Update examples/mobile/Makefile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: François Mockers --- examples/mobile/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/mobile/Makefile b/examples/mobile/Makefile index 0e02db34889c1..be01ae2e0ea80 100644 --- a/examples/mobile/Makefile +++ b/examples/mobile/Makefile @@ -2,7 +2,7 @@ DEVICE = ${DEVICE_ID} ifndef DEVICE_ID - DEVICE=$(shell xcrun simctl list devices 'iOS' | grep -v 'unavailable' | grep -v 'Shutdown' | grep -v '^--' | grep -v '==' | head -n 1 | grep -E -o -i "([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})") + DEVICE=$(shell xcrun simctl list devices 'iOS' | grep -v 'unavailable' | grep -v '^--' | grep -v '==' | head -n 1 | grep -E -o -i "([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})") endif run: install