From 770f10bc19b6df4f3b667fea3addea19ab4cc95c Mon Sep 17 00:00:00 2001 From: Joshua Holmes <91363480+joshua-holmes@users.noreply.github.com> Date: Tue, 6 May 2025 15:23:59 -0700 Subject: [PATCH] Remove remaining internal use of `!Send` resources (#18386) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective Remaining work for and closes #17682. First half of work for that issue was completed in [PR 17730](https://github.com/bevyengine/bevy/pull/17730). However, the rest of the work ended up getting blocked because we needed a way of forcing systems to run on the main thread without the use of `!Send` resources. That was unblocked by [PR 18301](https://github.com/bevyengine/bevy/pull/18301). This work should finish unblocking the resources-as-components effort. # Testing Ran several examples using my Linux machine, just to make sure things are working as expected and no surprises pop up. --------- Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com> Co-authored-by: François Mockers --- crates/bevy_gilrs/src/lib.rs | 19 + crates/bevy_winit/src/accessibility.rs | 87 ++-- crates/bevy_winit/src/lib.rs | 12 +- crates/bevy_winit/src/state.rs | 581 +++++++++++---------- crates/bevy_winit/src/system.rs | 691 +++++++++++++------------ crates/bevy_winit/src/winit_windows.rs | 10 + 6 files changed, 734 insertions(+), 666 deletions(-) diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index 5e5a2ba2bd..b9f1d9d286 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -117,3 +117,22 @@ impl Plugin for GilrsPlugin { } } } + +#[cfg(test)] +mod tests { + use super::*; + + // Regression test for https://github.com/bevyengine/bevy/issues/17697 + #[test] + fn world_is_truly_send() { + let mut app = App::new(); + app.add_plugins(GilrsPlugin); + let world = core::mem::take(app.world_mut()); + + let handler = std::thread::spawn(move || { + drop(world); + }); + + handler.join().unwrap(); + } +} diff --git a/crates/bevy_winit/src/accessibility.rs b/crates/bevy_winit/src/accessibility.rs index 907dfb4b77..b14fd4f57f 100644 --- a/crates/bevy_winit/src/accessibility.rs +++ b/crates/bevy_winit/src/accessibility.rs @@ -2,6 +2,7 @@ use alloc::{collections::VecDeque, sync::Arc}; use bevy_input_focus::InputFocus; +use core::cell::RefCell; use std::sync::Mutex; use winit::event_loop::ActiveEventLoop; @@ -16,13 +17,26 @@ use bevy_a11y::{ }; use bevy_app::{App, Plugin, PostUpdate}; use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::{entity::EntityHashMap, prelude::*}; +use bevy_ecs::{entity::EntityHashMap, prelude::*, system::NonSendMarker}; use bevy_window::{PrimaryWindow, Window, WindowClosed}; +thread_local! { + /// Temporary storage of access kit adapter data to replace usage of `!Send` resources. This will be replaced with proper + /// storage of `!Send` data after issue #17667 is complete. + pub static ACCESS_KIT_ADAPTERS: RefCell = const { RefCell::new(AccessKitAdapters::new()) }; +} + /// Maps window entities to their `AccessKit` [`Adapter`]s. #[derive(Default, Deref, DerefMut)] pub struct AccessKitAdapters(pub EntityHashMap); +impl AccessKitAdapters { + /// Creates a new empty `AccessKitAdapters`. + pub const fn new() -> Self { + Self(EntityHashMap::new()) + } +} + /// Maps window entities to their respective [`ActionRequest`]s. #[derive(Resource, Default, Deref, DerefMut)] pub struct WinitActionRequestHandlers(pub EntityHashMap>>); @@ -144,14 +158,16 @@ pub(crate) fn prepare_accessibility_for_window( } fn window_closed( - mut adapters: NonSendMut, mut handlers: ResMut, mut events: EventReader, + _non_send_marker: NonSendMarker, ) { - for WindowClosed { window, .. } in events.read() { - adapters.remove(window); - handlers.remove(window); - } + ACCESS_KIT_ADAPTERS.with_borrow_mut(|adapters| { + for WindowClosed { window, .. } in events.read() { + adapters.remove(window); + handlers.remove(window); + } + }); } fn poll_receivers( @@ -174,7 +190,6 @@ fn should_update_accessibility_nodes( } fn update_accessibility_nodes( - mut adapters: NonSendMut, focus: Option>, primary_window: Query<(Entity, &Window), With>, nodes: Query<( @@ -184,35 +199,38 @@ fn update_accessibility_nodes( Option<&ChildOf>, )>, node_entities: Query>, + _non_send_marker: NonSendMarker, ) { - let Ok((primary_window_id, primary_window)) = primary_window.single() else { - return; - }; - let Some(adapter) = adapters.get_mut(&primary_window_id) else { - return; - }; - let Some(focus) = focus else { - return; - }; - if focus.is_changed() || !nodes.is_empty() { - // Don't panic if the focused entity does not currently exist - // It's probably waiting to be spawned - if let Some(focused_entity) = focus.0 { - if !node_entities.contains(focused_entity) { - return; + ACCESS_KIT_ADAPTERS.with_borrow_mut(|adapters| { + let Ok((primary_window_id, primary_window)) = primary_window.single() else { + return; + }; + let Some(adapter) = adapters.get_mut(&primary_window_id) else { + return; + }; + let Some(focus) = focus else { + return; + }; + if focus.is_changed() || !nodes.is_empty() { + // Don't panic if the focused entity does not currently exist + // It's probably waiting to be spawned + if let Some(focused_entity) = focus.0 { + if !node_entities.contains(focused_entity) { + return; + } } - } - adapter.update_if_active(|| { - update_adapter( - nodes, - node_entities, - primary_window, - primary_window_id, - focus, - ) - }); - } + adapter.update_if_active(|| { + update_adapter( + nodes, + node_entities, + primary_window, + primary_window_id, + focus, + ) + }); + } + }); } fn update_adapter( @@ -290,8 +308,7 @@ pub struct AccessKitPlugin; impl Plugin for AccessKitPlugin { fn build(&self, app: &mut App) { - app.init_non_send_resource::() - .init_resource::() + app.init_resource::() .add_event::() .add_systems( PostUpdate, diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 97943cc14a..d7c880a9b9 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -18,6 +18,7 @@ use bevy_derive::Deref; use bevy_reflect::prelude::ReflectDefault; use bevy_reflect::Reflect; use bevy_window::{RawHandleWrapperHolder, WindowEvent}; +use core::cell::RefCell; use core::marker::PhantomData; use winit::{event_loop::EventLoop, window::WindowId}; @@ -37,7 +38,7 @@ pub use winit_config::*; pub use winit_windows::*; use crate::{ - accessibility::{AccessKitAdapters, AccessKitPlugin, WinitActionRequestHandlers}, + accessibility::{AccessKitPlugin, WinitActionRequestHandlers}, state::winit_runner, winit_monitors::WinitMonitors, }; @@ -53,6 +54,10 @@ mod winit_config; mod winit_monitors; mod winit_windows; +thread_local! { + static WINIT_WINDOWS: RefCell = const { RefCell::new(WinitWindows::new()) }; +} + /// A [`Plugin`] that uses `winit` to create and manage windows, and receive window and input /// events. /// @@ -124,8 +129,7 @@ impl Plugin for WinitPlugin { .build() .expect("Failed to build event loop"); - app.init_non_send_resource::() - .init_resource::() + app.init_resource::() .init_resource::() .insert_resource(DisplayHandleWrapper(event_loop.owned_display_handle())) .add_event::() @@ -210,8 +214,6 @@ pub type CreateWindowParams<'w, 's, F = ()> = ( F, >, EventWriter<'w, WindowCreated>, - NonSendMut<'w, WinitWindows>, - NonSendMut<'w, AccessKitAdapters>, ResMut<'w, WinitActionRequestHandlers>, Res<'w, AccessibilityRequested>, Res<'w, WinitMonitors>, diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index 33ad693c5a..1855539cc5 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}; #[cfg(feature = "custom_cursor")] use bevy_asset::AssetId; use bevy_ecs::{ - change_detection::{DetectChanges, NonSendMut, Res}, + change_detection::{DetectChanges, Res}, entity::Entity, event::{EventCursor, EventWriter}, prelude::*, @@ -49,11 +49,11 @@ use bevy_window::{ use bevy_window::{PrimaryWindow, RawHandleWrapper}; use crate::{ - accessibility::AccessKitAdapters, + accessibility::ACCESS_KIT_ADAPTERS, converters, create_windows, system::{create_monitors, CachedWindow, WinitWindowPressedKeys}, AppSendEvent, CreateMonitorParams, CreateWindowParams, EventLoopProxyWrapper, - RawWinitWindowEvent, UpdateMode, WinitSettings, WinitWindows, + RawWinitWindowEvent, UpdateMode, WinitSettings, WINIT_WINDOWS, }; /// Persistent state that is used to run the [`App`] according to the current @@ -94,7 +94,6 @@ struct WinitAppRunnerState { EventWriter<'static, WindowResized>, EventWriter<'static, WindowBackendScaleFactorChanged>, EventWriter<'static, WindowScaleFactorChanged>, - NonSend<'static, WinitWindows>, Query< 'static, 'static, @@ -104,7 +103,6 @@ struct WinitAppRunnerState { &'static mut WinitWindowPressedKeys, ), >, - NonSendMut<'static, AccessKitAdapters>, )>, } @@ -117,9 +115,7 @@ impl WinitAppRunnerState { EventWriter, EventWriter, EventWriter, - NonSend, Query<(&mut Window, &mut CachedWindow, &mut WinitWindowPressedKeys)>, - NonSendMut, )> = SystemState::new(app.world_mut()); Self { @@ -255,219 +251,236 @@ impl ApplicationHandler for WinitAppRunnerState { ) { self.window_event_received = true; - let ( - mut window_resized, - mut window_backend_scale_factor_changed, - mut window_scale_factor_changed, - winit_windows, - mut windows, - mut access_kit_adapters, - ) = self.event_writer_system_state.get_mut(self.app.world_mut()); + #[cfg_attr( + not(target_os = "windows"), + expect(unused_mut, reason = "only needs to be mut on windows for now") + )] + let mut manual_run_redraw_requested = false; - let Some(window) = winit_windows.get_window_entity(window_id) else { - warn!("Skipped event {event:?} for unknown winit Window Id {window_id:?}"); - return; - }; + WINIT_WINDOWS.with_borrow(|winit_windows| { + ACCESS_KIT_ADAPTERS.with_borrow_mut(|access_kit_adapters| { + let ( + mut window_resized, + mut window_backend_scale_factor_changed, + mut window_scale_factor_changed, + mut windows, + ) = self.event_writer_system_state.get_mut(self.app.world_mut()); - let Ok((mut win, _, mut pressed_keys)) = windows.get_mut(window) else { - warn!("Window {window:?} is missing `Window` component, skipping event {event:?}"); - return; - }; + let Some(window) = winit_windows.get_window_entity(window_id) else { + warn!("Skipped event {event:?} for unknown winit Window Id {window_id:?}"); + return; + }; - // Store a copy of the event to send to an EventWriter later. - self.raw_winit_events.push(RawWinitWindowEvent { - window_id, - event: event.clone(), - }); + let Ok((mut win, _, mut pressed_keys)) = windows.get_mut(window) else { + warn!( + "Window {window:?} is missing `Window` component, skipping event {event:?}" + ); + return; + }; - // Allow AccessKit to respond to `WindowEvent`s before they reach - // the engine. - if let Some(adapter) = access_kit_adapters.get_mut(&window) { - if let Some(winit_window) = winit_windows.get_window(window) { - adapter.process_event(winit_window, &event); - } - } - - match event { - WindowEvent::Resized(size) => { - react_to_resize(window, &mut win, size, &mut window_resized); - } - WindowEvent::ScaleFactorChanged { scale_factor, .. } => { - react_to_scale_factor_change( - window, - &mut win, - scale_factor, - &mut window_backend_scale_factor_changed, - &mut window_scale_factor_changed, - ); - } - WindowEvent::CloseRequested => self - .bevy_window_events - .send(WindowCloseRequested { window }), - WindowEvent::KeyboardInput { - ref event, - // On some platforms, winit sends "synthetic" key press events when the window - // gains or loses focus. These are not implemented on every platform, so we ignore - // winit's synthetic key pressed and implement the same mechanism ourselves. - // (See the `WinitWindowPressedKeys` component) - is_synthetic: false, - .. - } => { - let keyboard_input = converters::convert_keyboard_input(event, window); - if event.state.is_pressed() { - pressed_keys - .0 - .insert(keyboard_input.key_code, keyboard_input.logical_key.clone()); - } else { - pressed_keys.0.remove(&keyboard_input.key_code); - } - self.bevy_window_events.send(keyboard_input); - } - 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() + // Store a copy of the event to send to an EventWriter later. + self.raw_winit_events.push(RawWinitWindowEvent { + window_id, + event: event.clone(), }); - win.set_physical_cursor_position(Some(physical_position)); - let position = (physical_position / win.resolution.scale_factor() as f64).as_vec2(); - self.bevy_window_events.send(CursorMoved { - window, - position, - delta, - }); - } - WindowEvent::CursorEntered { .. } => { - self.bevy_window_events.send(CursorEntered { window }); - } - WindowEvent::CursorLeft { .. } => { - win.set_physical_cursor_position(None); - self.bevy_window_events.send(CursorLeft { window }); - } - WindowEvent::MouseInput { state, button, .. } => { - self.bevy_window_events.send(MouseButtonInput { - button: converters::convert_mouse_button(button), - state: converters::convert_element_state(state), - window, - }); - } - WindowEvent::PinchGesture { delta, .. } => { - self.bevy_window_events.send(PinchGesture(delta as f32)); - } - WindowEvent::RotationGesture { delta, .. } => { - self.bevy_window_events.send(RotationGesture(delta)); - } - WindowEvent::DoubleTapGesture { .. } => { - self.bevy_window_events.send(DoubleTapGesture); - } - WindowEvent::PanGesture { delta, .. } => { - self.bevy_window_events.send(PanGesture(Vec2 { - x: delta.x, - y: delta.y, - })); - } - WindowEvent::MouseWheel { delta, .. } => match delta { - event::MouseScrollDelta::LineDelta(x, y) => { - self.bevy_window_events.send(MouseWheel { - unit: MouseScrollUnit::Line, - x, - y, - window, - }); - } - event::MouseScrollDelta::PixelDelta(p) => { - self.bevy_window_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.bevy_window_events - .send(converters::convert_touch_input(touch, location, window)); - } - WindowEvent::Focused(focused) => { - win.focused = focused; - self.bevy_window_events - .send(WindowFocused { window, focused }); - } - WindowEvent::Occluded(occluded) => { - self.bevy_window_events - .send(WindowOccluded { window, occluded }); - } - WindowEvent::DroppedFile(path_buf) => { - self.bevy_window_events - .send(FileDragAndDrop::DroppedFile { window, path_buf }); - } - WindowEvent::HoveredFile(path_buf) => { - self.bevy_window_events - .send(FileDragAndDrop::HoveredFile { window, path_buf }); - } - WindowEvent::HoveredFileCancelled => { - self.bevy_window_events - .send(FileDragAndDrop::HoveredFileCanceled { window }); - } - WindowEvent::Moved(position) => { - let position = ivec2(position.x, position.y); - win.position.set(position); - self.bevy_window_events - .send(WindowMoved { window, position }); - } - WindowEvent::Ime(event) => match event { - event::Ime::Preedit(value, cursor) => { - self.bevy_window_events.send(Ime::Preedit { - window, - value, - cursor, - }); - } - event::Ime::Commit(value) => { - self.bevy_window_events.send(Ime::Commit { window, value }); - } - event::Ime::Enabled => { - self.bevy_window_events.send(Ime::Enabled { window }); - } - event::Ime::Disabled => { - self.bevy_window_events.send(Ime::Disabled { window }); - } - }, - WindowEvent::ThemeChanged(theme) => { - self.bevy_window_events.send(WindowThemeChanged { - window, - theme: converters::convert_winit_theme(theme), - }); - } - WindowEvent::Destroyed => { - self.bevy_window_events.send(WindowDestroyed { window }); - } - WindowEvent::RedrawRequested => { - self.ran_update_since_last_redraw = false; - - // https://github.com/bevyengine/bevy/issues/17488 - #[cfg(target_os = "windows")] - { - // Have the startup behavior run in about_to_wait, which prevents issues with - // invisible window creation. https://github.com/bevyengine/bevy/issues/18027 - if self.startup_forced_updates == 0 { - self.redraw_requested(_event_loop); + // 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); } } - } - _ => {} - } - 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(); - } + match event { + WindowEvent::Resized(size) => { + react_to_resize(window, &mut win, size, &mut window_resized); + } + WindowEvent::ScaleFactorChanged { scale_factor, .. } => { + react_to_scale_factor_change( + window, + &mut win, + scale_factor, + &mut window_backend_scale_factor_changed, + &mut window_scale_factor_changed, + ); + } + WindowEvent::CloseRequested => self + .bevy_window_events + .send(WindowCloseRequested { window }), + WindowEvent::KeyboardInput { + ref event, + // On some platforms, winit sends "synthetic" key press events when the window + // gains or loses focus. These are not implemented on every platform, so we ignore + // winit's synthetic key pressed and implement the same mechanism ourselves. + // (See the `WinitWindowPressedKeys` component) + is_synthetic: false, + .. + } => { + let keyboard_input = converters::convert_keyboard_input(event, window); + if event.state.is_pressed() { + pressed_keys.0.insert( + keyboard_input.key_code, + keyboard_input.logical_key.clone(), + ); + } else { + pressed_keys.0.remove(&keyboard_input.key_code); + } + self.bevy_window_events.send(keyboard_input); + } + 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.bevy_window_events.send(CursorMoved { + window, + position, + delta, + }); + } + WindowEvent::CursorEntered { .. } => { + self.bevy_window_events.send(CursorEntered { window }); + } + WindowEvent::CursorLeft { .. } => { + win.set_physical_cursor_position(None); + self.bevy_window_events.send(CursorLeft { window }); + } + WindowEvent::MouseInput { state, button, .. } => { + self.bevy_window_events.send(MouseButtonInput { + button: converters::convert_mouse_button(button), + state: converters::convert_element_state(state), + window, + }); + } + WindowEvent::PinchGesture { delta, .. } => { + self.bevy_window_events.send(PinchGesture(delta as f32)); + } + WindowEvent::RotationGesture { delta, .. } => { + self.bevy_window_events.send(RotationGesture(delta)); + } + WindowEvent::DoubleTapGesture { .. } => { + self.bevy_window_events.send(DoubleTapGesture); + } + WindowEvent::PanGesture { delta, .. } => { + self.bevy_window_events.send(PanGesture(Vec2 { + x: delta.x, + y: delta.y, + })); + } + WindowEvent::MouseWheel { delta, .. } => match delta { + event::MouseScrollDelta::LineDelta(x, y) => { + self.bevy_window_events.send(MouseWheel { + unit: MouseScrollUnit::Line, + x, + y, + window, + }); + } + event::MouseScrollDelta::PixelDelta(p) => { + self.bevy_window_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.bevy_window_events + .send(converters::convert_touch_input(touch, location, window)); + } + WindowEvent::Focused(focused) => { + win.focused = focused; + self.bevy_window_events + .send(WindowFocused { window, focused }); + } + WindowEvent::Occluded(occluded) => { + self.bevy_window_events + .send(WindowOccluded { window, occluded }); + } + WindowEvent::DroppedFile(path_buf) => { + self.bevy_window_events + .send(FileDragAndDrop::DroppedFile { window, path_buf }); + } + WindowEvent::HoveredFile(path_buf) => { + self.bevy_window_events + .send(FileDragAndDrop::HoveredFile { window, path_buf }); + } + WindowEvent::HoveredFileCancelled => { + self.bevy_window_events + .send(FileDragAndDrop::HoveredFileCanceled { window }); + } + WindowEvent::Moved(position) => { + let position = ivec2(position.x, position.y); + win.position.set(position); + self.bevy_window_events + .send(WindowMoved { window, position }); + } + WindowEvent::Ime(event) => match event { + event::Ime::Preedit(value, cursor) => { + self.bevy_window_events.send(Ime::Preedit { + window, + value, + cursor, + }); + } + event::Ime::Commit(value) => { + self.bevy_window_events.send(Ime::Commit { window, value }); + } + event::Ime::Enabled => { + self.bevy_window_events.send(Ime::Enabled { window }); + } + event::Ime::Disabled => { + self.bevy_window_events.send(Ime::Disabled { window }); + } + }, + WindowEvent::ThemeChanged(theme) => { + self.bevy_window_events.send(WindowThemeChanged { + window, + theme: converters::convert_winit_theme(theme), + }); + } + WindowEvent::Destroyed => { + self.bevy_window_events.send(WindowDestroyed { window }); + } + WindowEvent::RedrawRequested => { + self.ran_update_since_last_redraw = false; + + // https://github.com/bevyengine/bevy/issues/17488 + #[cfg(target_os = "windows")] + { + // Have the startup behavior run in about_to_wait, which prevents issues with + // invisible window creation. https://github.com/bevyengine/bevy/issues/18027 + if self.startup_forced_updates == 0 { + manual_run_redraw_requested = true; + } + } + } + _ => {} + } + + 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(); + } + } + }); + }); + + if manual_run_redraw_requested { + self.redraw_requested(_event_loop); } } @@ -506,19 +519,20 @@ impl ApplicationHandler for WinitAppRunnerState { // invisible window creation. https://github.com/bevyengine/bevy/issues/18027 #[cfg(target_os = "windows")] { - let winit_windows = self.world().non_send_resource::(); - let headless = winit_windows.windows.is_empty(); - let exiting = self.app_exit.is_some(); - let reactive = matches!(self.update_mode, UpdateMode::Reactive { .. }); - let all_invisible = winit_windows - .windows - .iter() - .all(|(_, w)| !w.is_visible().unwrap_or(false)); - if !exiting - && (self.startup_forced_updates > 0 || headless || all_invisible || reactive) - { - self.redraw_requested(event_loop); - } + WINIT_WINDOWS.with_borrow(|winit_windows| { + let headless = winit_windows.windows.is_empty(); + let exiting = self.app_exit.is_some(); + let reactive = matches!(self.update_mode, UpdateMode::Reactive { .. }); + let all_invisible = winit_windows + .windows + .iter() + .all(|(_, w)| !w.is_visible().unwrap_or(false)); + if !exiting + && (self.startup_forced_updates > 0 || headless || all_invisible || reactive) + { + self.redraw_requested(event_loop); + } + }); } } @@ -591,35 +605,33 @@ impl 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.world_mut() - .query_filtered::<(Entity, &Window), (With, Without)>(); + .query_filtered::<(Entity, &Window), (With, Without)>(); if let Ok((entity, window)) = query.single(&self.world()) { let window = window.clone(); - let mut create_window = - SystemState::::from_world(self.world_mut()); + WINIT_WINDOWS.with_borrow_mut(|winit_windows| { + ACCESS_KIT_ADAPTERS.with_borrow_mut(|adapters| { + let mut create_window = + SystemState::::from_world(self.world_mut()); - let ( - .., - mut winit_windows, - mut adapters, - mut handlers, - accessibility_requested, - monitors, - ) = create_window.get_mut(self.world_mut()); + let (.., mut handlers, accessibility_requested, monitors) = + create_window.get_mut(self.world_mut()); - let winit_window = winit_windows.create_window( - event_loop, - entity, - &window, - &mut adapters, - &mut handlers, - &accessibility_requested, - &monitors, - ); + let winit_window = winit_windows.create_window( + event_loop, + entity, + &window, + adapters, + &mut handlers, + &accessibility_requested, + &monitors, + ); - let wrapper = RawHandleWrapper::new(winit_window).unwrap(); + let wrapper = RawHandleWrapper::new(winit_window).unwrap(); - self.world_mut().entity_mut(entity).insert(wrapper); + self.world_mut().entity_mut(entity).insert(wrapper); + }); + }); } } } @@ -684,9 +696,10 @@ impl WinitAppRunnerState { 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) + let visible = WINIT_WINDOWS.with_borrow(|winit_windows| { + winit_windows.windows.iter().any(|(_, w)| { + w.is_visible().unwrap_or(false) + }) }); event_loop.set_control_flow(if visible { @@ -716,10 +729,11 @@ impl WinitAppRunnerState { } 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(); - } + WINIT_WINDOWS.with_borrow(|winit_windows| { + for window in winit_windows.windows.values() { + window.request_redraw(); + } + }); self.redraw_requested = false; } @@ -871,48 +885,47 @@ impl WinitAppRunnerState { fn update_cursors(&mut self, #[cfg(feature = "custom_cursor")] event_loop: &ActiveEventLoop) { #[cfg(feature = "custom_cursor")] let mut windows_state: SystemState<( - NonSendMut, ResMut, Query<(Entity, &mut PendingCursor), Changed>, )> = SystemState::new(self.world_mut()); #[cfg(feature = "custom_cursor")] - let (winit_windows, mut cursor_cache, mut windows) = - windows_state.get_mut(self.world_mut()); + let (mut cursor_cache, mut windows) = windows_state.get_mut(self.world_mut()); #[cfg(not(feature = "custom_cursor"))] let mut windows_state: SystemState<( - NonSendMut, Query<(Entity, &mut PendingCursor), Changed>, )> = SystemState::new(self.world_mut()); #[cfg(not(feature = "custom_cursor"))] - let (winit_windows, mut windows) = windows_state.get_mut(self.world_mut()); + let (mut windows,) = windows_state.get_mut(self.world_mut()); - for (entity, mut pending_cursor) in windows.iter_mut() { - let Some(winit_window) = winit_windows.get_window(entity) else { - continue; - }; - let Some(pending_cursor) = pending_cursor.0.take() else { - continue; - }; + WINIT_WINDOWS.with_borrow(|winit_windows| { + for (entity, mut pending_cursor) in windows.iter_mut() { + let Some(winit_window) = winit_windows.get_window(entity) else { + continue; + }; + let Some(pending_cursor) = pending_cursor.0.take() else { + continue; + }; - let final_cursor: winit::window::Cursor = match pending_cursor { - #[cfg(feature = "custom_cursor")] - CursorSource::CustomCached(cache_key) => { - let Some(cached_cursor) = cursor_cache.0.get(&cache_key) else { - error!("Cursor should have been cached, but was not found"); - continue; - }; - cached_cursor.clone().into() - } - #[cfg(feature = "custom_cursor")] - CursorSource::Custom((cache_key, cursor)) => { - let custom_cursor = event_loop.create_custom_cursor(cursor); - cursor_cache.0.insert(cache_key, custom_cursor.clone()); - custom_cursor.into() - } - CursorSource::System(system_cursor) => system_cursor.into(), - }; - winit_window.set_cursor(final_cursor); - } + let final_cursor: winit::window::Cursor = match pending_cursor { + #[cfg(feature = "custom_cursor")] + CursorSource::CustomCached(cache_key) => { + let Some(cached_cursor) = cursor_cache.0.get(&cache_key) else { + error!("Cursor should have been cached, but was not found"); + continue; + }; + cached_cursor.clone().into() + } + #[cfg(feature = "custom_cursor")] + CursorSource::Custom((cache_key, cursor)) => { + let custom_cursor = event_loop.create_custom_cursor(cursor); + cursor_cache.0.insert(cache_key, custom_cursor.clone()); + custom_cursor.into() + } + CursorSource::System(system_cursor) => system_cursor.into(), + }; + winit_window.set_cursor(final_cursor); + } + }); } } diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index 32925db26b..97483c7358 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -6,7 +6,7 @@ use bevy_ecs::{ prelude::{Changed, Component}, query::QueryFilter, removal_detection::RemovedComponents, - system::{Local, NonSendMut, Query, SystemParamItem}, + system::{Local, NonSendMarker, Query, SystemParamItem}, }; use bevy_input::keyboard::{Key, KeyCode, KeyboardFocusLost, KeyboardInput}; use bevy_window::{ @@ -30,6 +30,7 @@ use winit::platform::ios::WindowExtIOS; use winit::platform::web::WindowExtWebSys; use crate::{ + accessibility::ACCESS_KIT_ADAPTERS, converters::{ convert_enabled_buttons, convert_resize_direction, convert_window_level, convert_window_theme, convert_winit_theme, @@ -37,7 +38,7 @@ use crate::{ get_selected_videomode, select_monitor, state::react_to_resize, winit_monitors::WinitMonitors, - CreateMonitorParams, CreateWindowParams, WinitWindows, + CreateMonitorParams, CreateWindowParams, WINIT_WINDOWS, }; /// Creates new windows on the [`winit`] backend for each entity with a newly-added @@ -51,78 +52,80 @@ pub fn create_windows( mut commands, mut created_windows, mut window_created_events, - mut winit_windows, - mut adapters, mut handlers, accessibility_requested, monitors, ): SystemParamItem>, ) { - for (entity, mut window, handle_holder) in &mut created_windows { - if winit_windows.get_window(entity).is_some() { - continue; - } + WINIT_WINDOWS.with_borrow_mut(|winit_windows| { + ACCESS_KIT_ADAPTERS.with_borrow_mut(|adapters| { + for (entity, mut window, handle_holder) in &mut created_windows { + if winit_windows.get_window(entity).is_some() { + continue; + } - info!("Creating new window {} ({})", window.title.as_str(), entity); + info!("Creating new window {} ({})", window.title.as_str(), entity); - let winit_window = winit_windows.create_window( - event_loop, - entity, - &window, - &mut adapters, - &mut handlers, - &accessibility_requested, - &monitors, - ); + let winit_window = winit_windows.create_window( + event_loop, + entity, + &window, + adapters, + &mut handlers, + &accessibility_requested, + &monitors, + ); - if let Some(theme) = winit_window.theme() { - window.window_theme = Some(convert_winit_theme(theme)); - } + if let Some(theme) = winit_window.theme() { + window.window_theme = Some(convert_winit_theme(theme)); + } - window - .resolution - .set_scale_factor_and_apply_to_physical_size(winit_window.scale_factor() as f32); + window + .resolution + .set_scale_factor_and_apply_to_physical_size(winit_window.scale_factor() as f32); - commands.entity(entity).insert(( - CachedWindow { - window: window.clone(), - }, - WinitWindowPressedKeys::default(), - )); + commands.entity(entity).insert(( + CachedWindow { + window: window.clone(), + }, + WinitWindowPressedKeys::default(), + )); - if let Ok(handle_wrapper) = RawHandleWrapper::new(winit_window) { - commands.entity(entity).insert(handle_wrapper.clone()); - if let Some(handle_holder) = handle_holder { - *handle_holder.0.lock().unwrap() = Some(handle_wrapper); + if let Ok(handle_wrapper) = RawHandleWrapper::new(winit_window) { + commands.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")] + { + if window.fit_canvas_to_parent { + let canvas = winit_window + .canvas() + .expect("window.canvas() can only be called in main thread."); + let style = canvas.style(); + style.set_property("width", "100%").unwrap(); + style.set_property("height", "100%").unwrap(); + } + } + + #[cfg(target_os = "ios")] + { + winit_window.recognize_pinch_gesture(window.recognize_pinch_gesture); + winit_window.recognize_rotation_gesture(window.recognize_rotation_gesture); + winit_window.recognize_doubletap_gesture(window.recognize_doubletap_gesture); + if let Some((min, max)) = window.recognize_pan_gesture { + winit_window.recognize_pan_gesture(true, min, max); + } else { + winit_window.recognize_pan_gesture(false, 0, 0); + } + } + + window_created_events.write(WindowCreated { window: entity }); } - } - - #[cfg(target_arch = "wasm32")] - { - if window.fit_canvas_to_parent { - let canvas = winit_window - .canvas() - .expect("window.canvas() can only be called in main thread."); - let style = canvas.style(); - style.set_property("width", "100%").unwrap(); - style.set_property("height", "100%").unwrap(); - } - } - - #[cfg(target_os = "ios")] - { - winit_window.recognize_pinch_gesture(window.recognize_pinch_gesture); - winit_window.recognize_rotation_gesture(window.recognize_rotation_gesture); - winit_window.recognize_doubletap_gesture(window.recognize_doubletap_gesture); - if let Some((min, max)) = window.recognize_pan_gesture { - winit_window.recognize_pan_gesture(true, min, max); - } else { - winit_window.recognize_pan_gesture(false, 0, 0); - } - } - - window_created_events.write(WindowCreated { window: entity }); - } + }); + }); } /// Check whether keyboard focus was lost. This is different from window @@ -239,9 +242,9 @@ pub(crate) fn despawn_windows( window_entities: Query>, mut closing_events: EventWriter, mut closed_events: EventWriter, - mut winit_windows: NonSendMut, mut windows_to_drop: Local>>, mut exit_events: EventReader, + _non_send_marker: NonSendMarker, ) { // Drop all the windows that are waiting to be closed windows_to_drop.clear(); @@ -254,13 +257,15 @@ pub(crate) fn despawn_windows( // rather than having the component added // and removed in the same frame. if !window_entities.contains(window) { - if let Some(window) = winit_windows.remove_window(window) { - // Keeping WindowWrapper that are dropped for one frame - // Otherwise the last `Arc` of the window could be in the rendering thread, and dropped there - // This would hang on macOS - // Keeping the wrapper and dropping it next frame in this system ensure its dropped in the main thread - windows_to_drop.push(window); - } + WINIT_WINDOWS.with_borrow_mut(|winit_windows| { + if let Some(window) = winit_windows.remove_window(window) { + // Keeping WindowWrapper that are dropped for one frame + // Otherwise the last `Arc` of the window could be in the rendering thread, and dropped there + // This would hang on macOS + // Keeping the wrapper and dropping it next frame in this system ensure its dropped in the main thread + windows_to_drop.push(window); + } + }); closed_events.write(WindowClosed { window }); } } @@ -291,296 +296,298 @@ pub struct CachedWindow { /// - [`Window::focused`] cannot be manually changed to `false` after the window is created. pub(crate) fn changed_windows( mut changed_windows: Query<(Entity, &mut Window, &mut CachedWindow), Changed>, - winit_windows: NonSendMut, monitors: Res, mut window_resized: EventWriter, + _non_send_marker: NonSendMarker, ) { - for (entity, mut window, mut cache) in &mut changed_windows { - let Some(winit_window) = winit_windows.get_window(entity) else { - continue; - }; + WINIT_WINDOWS.with_borrow(|winit_windows| { + for (entity, mut window, mut cache) in &mut changed_windows { + let Some(winit_window) = winit_windows.get_window(entity) else { + continue; + }; - if window.title != cache.window.title { - winit_window.set_title(window.title.as_str()); - } + if window.title != cache.window.title { + winit_window.set_title(window.title.as_str()); + } - if window.mode != cache.window.mode { - let new_mode = match window.mode { - WindowMode::BorderlessFullscreen(monitor_selection) => { - Some(Some(winit::window::Fullscreen::Borderless(select_monitor( - &monitors, - winit_window.primary_monitor(), - winit_window.current_monitor(), - &monitor_selection, - )))) - } - WindowMode::Fullscreen(monitor_selection, video_mode_selection) => { - let monitor = &select_monitor( - &monitors, - winit_window.primary_monitor(), - winit_window.current_monitor(), - &monitor_selection, - ) - .unwrap_or_else(|| { - panic!("Could not find monitor for {:?}", monitor_selection) - }); - - if let Some(video_mode) = get_selected_videomode(monitor, &video_mode_selection) - { - Some(Some(winit::window::Fullscreen::Exclusive(video_mode))) - } else { - warn!( - "Could not find valid fullscreen video mode for {:?} {:?}", - monitor_selection, video_mode_selection - ); - None + if window.mode != cache.window.mode { + let new_mode = match window.mode { + WindowMode::BorderlessFullscreen(monitor_selection) => { + Some(Some(winit::window::Fullscreen::Borderless(select_monitor( + &monitors, + winit_window.primary_monitor(), + winit_window.current_monitor(), + &monitor_selection, + )))) } - } - WindowMode::Windowed => Some(None), - }; + WindowMode::Fullscreen(monitor_selection, video_mode_selection) => { + let monitor = &select_monitor( + &monitors, + winit_window.primary_monitor(), + winit_window.current_monitor(), + &monitor_selection, + ) + .unwrap_or_else(|| { + panic!("Could not find monitor for {:?}", monitor_selection) + }); - 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 mut physical_size = PhysicalSize::new( - window.resolution.physical_width(), - window.resolution.physical_height(), - ); - - let cached_physical_size = PhysicalSize::new( - cache.window.physical_width(), - cache.window.physical_height(), - ); - - 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); - } - } - } - - if window.physical_cursor_position() != cache.window.physical_cursor_position() { - if let Some(physical_position) = window.physical_cursor_position() { - let position = PhysicalPosition::new(physical_position.x, physical_position.y); - - if let Err(err) = winit_window.set_cursor_position(position) { - error!("could not set cursor position: {}", err); - } - } - } - - if window.cursor_options.grab_mode != cache.window.cursor_options.grab_mode - && crate::winit_windows::attempt_grab(winit_window, window.cursor_options.grab_mode) - .is_err() - { - window.cursor_options.grab_mode = cache.window.cursor_options.grab_mode; - } - - if window.cursor_options.visible != cache.window.cursor_options.visible { - winit_window.set_cursor_visible(window.cursor_options.visible); - } - - if window.cursor_options.hit_test != cache.window.cursor_options.hit_test { - if let Err(err) = winit_window.set_cursor_hittest(window.cursor_options.hit_test) { - window.cursor_options.hit_test = cache.window.cursor_options.hit_test; - warn!( - "Could not set cursor hit test for window {}: {}", - window.title, err - ); - } - } - - if window.decorations != cache.window.decorations - && window.decorations != winit_window.is_decorated() - { - winit_window.set_decorations(window.decorations); - } - - if window.resizable != cache.window.resizable - && window.resizable != winit_window.is_resizable() - { - winit_window.set_resizable(window.resizable); - } - - if window.enabled_buttons != cache.window.enabled_buttons { - winit_window.set_enabled_buttons(convert_enabled_buttons(window.enabled_buttons)); - } - - if window.resize_constraints != cache.window.resize_constraints { - let constraints = window.resize_constraints.check_constraints(); - let min_inner_size = LogicalSize { - width: constraints.min_width, - height: constraints.min_height, - }; - let max_inner_size = LogicalSize { - width: constraints.max_width, - height: constraints.max_height, - }; - - winit_window.set_min_inner_size(Some(min_inner_size)); - if constraints.max_width.is_finite() && constraints.max_height.is_finite() { - winit_window.set_max_inner_size(Some(max_inner_size)); - } - } - - if window.position != cache.window.position { - if let Some(position) = crate::winit_window_position( - &window.position, - &window.resolution, - &monitors, - winit_window.primary_monitor(), - winit_window.current_monitor(), - ) { - let should_set = match winit_window.outer_position() { - Ok(current_position) => current_position != position, - _ => true, + if let Some(video_mode) = get_selected_videomode(monitor, &video_mode_selection) + { + Some(Some(winit::window::Fullscreen::Exclusive(video_mode))) + } else { + warn!( + "Could not find valid fullscreen video mode for {:?} {:?}", + monitor_selection, video_mode_selection + ); + None + } + } + WindowMode::Windowed => Some(None), }; - if should_set { - winit_window.set_outer_position(position); - } - } - } - - if let Some(maximized) = window.internal.take_maximize_request() { - winit_window.set_maximized(maximized); - } - - if let Some(minimized) = window.internal.take_minimize_request() { - winit_window.set_minimized(minimized); - } - - if window.internal.take_move_request() { - if let Err(e) = winit_window.drag_window() { - warn!("Winit returned an error while attempting to drag the window: {e}"); - } - } - - if let Some(resize_direction) = window.internal.take_resize_request() { - if let Err(e) = - winit_window.drag_resize_window(convert_resize_direction(resize_direction)) - { - warn!("Winit returned an error while attempting to drag resize the window: {e}"); - } - } - - if window.focused != cache.window.focused && window.focused { - winit_window.focus_window(); - } - - if window.window_level != cache.window.window_level { - winit_window.set_window_level(convert_window_level(window.window_level)); - } - - // Currently unsupported changes - if window.transparent != cache.window.transparent { - window.transparent = cache.window.transparent; - warn!("Winit does not currently support updating transparency after window creation."); - } - - #[cfg(target_arch = "wasm32")] - if window.canvas != cache.window.canvas { - window.canvas.clone_from(&cache.window.canvas); - warn!( - "Bevy currently doesn't support modifying the window canvas after initialization." - ); - } - - if window.ime_enabled != cache.window.ime_enabled { - winit_window.set_ime_allowed(window.ime_enabled); - } - - if window.ime_position != cache.window.ime_position { - winit_window.set_ime_cursor_area( - LogicalPosition::new(window.ime_position.x, window.ime_position.y), - PhysicalSize::new(10, 10), - ); - } - - if window.window_theme != cache.window.window_theme { - winit_window.set_theme(window.window_theme.map(convert_window_theme)); - } - - if window.visible != cache.window.visible { - winit_window.set_visible(window.visible); - } - - #[cfg(target_os = "ios")] - { - if window.recognize_pinch_gesture != cache.window.recognize_pinch_gesture { - winit_window.recognize_pinch_gesture(window.recognize_pinch_gesture); - } - if window.recognize_rotation_gesture != cache.window.recognize_rotation_gesture { - winit_window.recognize_rotation_gesture(window.recognize_rotation_gesture); - } - if window.recognize_doubletap_gesture != cache.window.recognize_doubletap_gesture { - winit_window.recognize_doubletap_gesture(window.recognize_doubletap_gesture); - } - if window.recognize_pan_gesture != cache.window.recognize_pan_gesture { - match ( - window.recognize_pan_gesture, - cache.window.recognize_pan_gesture, - ) { - (Some(_), Some(_)) => { - warn!("Bevy currently doesn't support modifying PanGesture number of fingers recognition. Please disable it before re-enabling it with the new number of fingers"); + if let Some(new_mode) = new_mode { + if winit_window.fullscreen() != new_mode { + winit_window.set_fullscreen(new_mode); } - (Some((min, max)), _) => winit_window.recognize_pan_gesture(true, min, max), - _ => winit_window.recognize_pan_gesture(false, 0, 0), } } - if window.prefers_home_indicator_hidden != cache.window.prefers_home_indicator_hidden { - winit_window - .set_prefers_home_indicator_hidden(window.prefers_home_indicator_hidden); + if window.resolution != cache.window.resolution { + let mut physical_size = PhysicalSize::new( + window.resolution.physical_width(), + window.resolution.physical_height(), + ); + + let cached_physical_size = PhysicalSize::new( + cache.window.physical_width(), + cache.window.physical_height(), + ); + + 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); + } + } } - if window.prefers_status_bar_hidden != cache.window.prefers_status_bar_hidden { - winit_window.set_prefers_status_bar_hidden(window.prefers_status_bar_hidden); + + if window.physical_cursor_position() != cache.window.physical_cursor_position() { + if let Some(physical_position) = window.physical_cursor_position() { + let position = PhysicalPosition::new(physical_position.x, physical_position.y); + + if let Err(err) = winit_window.set_cursor_position(position) { + error!("could not set cursor position: {}", err); + } + } } - if window.preferred_screen_edges_deferring_system_gestures - != cache - .window - .preferred_screen_edges_deferring_system_gestures + + if window.cursor_options.grab_mode != cache.window.cursor_options.grab_mode + && crate::winit_windows::attempt_grab(winit_window, window.cursor_options.grab_mode) + .is_err() { - use crate::converters::convert_screen_edge; - let preferred_edge = - convert_screen_edge(window.preferred_screen_edges_deferring_system_gestures); - winit_window.set_preferred_screen_edges_deferring_system_gestures(preferred_edge); + window.cursor_options.grab_mode = cache.window.cursor_options.grab_mode; } + + if window.cursor_options.visible != cache.window.cursor_options.visible { + winit_window.set_cursor_visible(window.cursor_options.visible); + } + + if window.cursor_options.hit_test != cache.window.cursor_options.hit_test { + if let Err(err) = winit_window.set_cursor_hittest(window.cursor_options.hit_test) { + window.cursor_options.hit_test = cache.window.cursor_options.hit_test; + warn!( + "Could not set cursor hit test for window {}: {}", + window.title, err + ); + } + } + + if window.decorations != cache.window.decorations + && window.decorations != winit_window.is_decorated() + { + winit_window.set_decorations(window.decorations); + } + + if window.resizable != cache.window.resizable + && window.resizable != winit_window.is_resizable() + { + winit_window.set_resizable(window.resizable); + } + + if window.enabled_buttons != cache.window.enabled_buttons { + winit_window.set_enabled_buttons(convert_enabled_buttons(window.enabled_buttons)); + } + + if window.resize_constraints != cache.window.resize_constraints { + let constraints = window.resize_constraints.check_constraints(); + let min_inner_size = LogicalSize { + width: constraints.min_width, + height: constraints.min_height, + }; + let max_inner_size = LogicalSize { + width: constraints.max_width, + height: constraints.max_height, + }; + + winit_window.set_min_inner_size(Some(min_inner_size)); + if constraints.max_width.is_finite() && constraints.max_height.is_finite() { + winit_window.set_max_inner_size(Some(max_inner_size)); + } + } + + if window.position != cache.window.position { + if let Some(position) = crate::winit_window_position( + &window.position, + &window.resolution, + &monitors, + winit_window.primary_monitor(), + winit_window.current_monitor(), + ) { + let should_set = match winit_window.outer_position() { + Ok(current_position) => current_position != position, + _ => true, + }; + + if should_set { + winit_window.set_outer_position(position); + } + } + } + + if let Some(maximized) = window.internal.take_maximize_request() { + winit_window.set_maximized(maximized); + } + + if let Some(minimized) = window.internal.take_minimize_request() { + winit_window.set_minimized(minimized); + } + + if window.internal.take_move_request() { + if let Err(e) = winit_window.drag_window() { + warn!("Winit returned an error while attempting to drag the window: {e}"); + } + } + + if let Some(resize_direction) = window.internal.take_resize_request() { + if let Err(e) = + winit_window.drag_resize_window(convert_resize_direction(resize_direction)) + { + warn!("Winit returned an error while attempting to drag resize the window: {e}"); + } + } + + if window.focused != cache.window.focused && window.focused { + winit_window.focus_window(); + } + + if window.window_level != cache.window.window_level { + winit_window.set_window_level(convert_window_level(window.window_level)); + } + + // Currently unsupported changes + if window.transparent != cache.window.transparent { + window.transparent = cache.window.transparent; + warn!("Winit does not currently support updating transparency after window creation."); + } + + #[cfg(target_arch = "wasm32")] + if window.canvas != cache.window.canvas { + window.canvas.clone_from(&cache.window.canvas); + warn!( + "Bevy currently doesn't support modifying the window canvas after initialization." + ); + } + + if window.ime_enabled != cache.window.ime_enabled { + winit_window.set_ime_allowed(window.ime_enabled); + } + + if window.ime_position != cache.window.ime_position { + winit_window.set_ime_cursor_area( + LogicalPosition::new(window.ime_position.x, window.ime_position.y), + PhysicalSize::new(10, 10), + ); + } + + if window.window_theme != cache.window.window_theme { + winit_window.set_theme(window.window_theme.map(convert_window_theme)); + } + + if window.visible != cache.window.visible { + winit_window.set_visible(window.visible); + } + + #[cfg(target_os = "ios")] + { + if window.recognize_pinch_gesture != cache.window.recognize_pinch_gesture { + winit_window.recognize_pinch_gesture(window.recognize_pinch_gesture); + } + if window.recognize_rotation_gesture != cache.window.recognize_rotation_gesture { + winit_window.recognize_rotation_gesture(window.recognize_rotation_gesture); + } + if window.recognize_doubletap_gesture != cache.window.recognize_doubletap_gesture { + winit_window.recognize_doubletap_gesture(window.recognize_doubletap_gesture); + } + if window.recognize_pan_gesture != cache.window.recognize_pan_gesture { + match ( + window.recognize_pan_gesture, + cache.window.recognize_pan_gesture, + ) { + (Some(_), Some(_)) => { + warn!("Bevy currently doesn't support modifying PanGesture number of fingers recognition. Please disable it before re-enabling it with the new number of fingers"); + } + (Some((min, max)), _) => winit_window.recognize_pan_gesture(true, min, max), + _ => winit_window.recognize_pan_gesture(false, 0, 0), + } + } + + if window.prefers_home_indicator_hidden != cache.window.prefers_home_indicator_hidden { + winit_window + .set_prefers_home_indicator_hidden(window.prefers_home_indicator_hidden); + } + if window.prefers_status_bar_hidden != cache.window.prefers_status_bar_hidden { + winit_window.set_prefers_status_bar_hidden(window.prefers_status_bar_hidden); + } + if window.preferred_screen_edges_deferring_system_gestures + != cache + .window + .preferred_screen_edges_deferring_system_gestures + { + use crate::converters::convert_screen_edge; + let preferred_edge = + convert_screen_edge(window.preferred_screen_edges_deferring_system_gestures); + winit_window.set_preferred_screen_edges_deferring_system_gestures(preferred_edge); + } + } + cache.window = window.clone(); } - cache.window = window.clone(); - } + }); } /// This keeps track of which keys are pressed on each window. diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index b1d4b3d7b6..d666491311 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -42,6 +42,16 @@ pub struct WinitWindows { } impl WinitWindows { + /// Creates a new instance of `WinitWindows`. + pub const fn new() -> Self { + Self { + windows: HashMap::new(), + entity_to_winit: EntityHashMap::new(), + winit_to_entity: HashMap::new(), + _not_send_sync: core::marker::PhantomData, + } + } + /// Creates a `winit` window and associates it with our entity. pub fn create_window( &mut self,