Skip to content

Commit a750cfe

Browse files
authored
Split CursorOptions off of Window (#19668)
# Objective - Fixes #19627 - Tackles part of #19644 - Supersedes #19629 - `Window` has become a very very very big component - As such, our change detection does not *really* work on it, as e.g. moving the mouse will cause a change for the entire window - We circumvented this with a cache - But, some things *shouldn't* be cached as they can be changed from outside the user's control, notably the cursor grab mode on web - So, we need to disable the cache for that - But because change detection is broken, that would result in the cursor grab mode being set every frame the mouse is moved - That is usually *not* what a dev wants, as it forces the cursor to be locked even when the end-user is trying to free the cursor on the browser - the cache in this situation is invalid due to #8949 ## Solution - Split `Window` into multiple components, each with working change detection - Disable caching of the cursor grab mode - This will only attempt to force the grab mode when the `CursorOptions` were touched by the user, which is *much* rarer than simply moving the mouse. - If this PR is merged, I'll do the exact same for the other constituents of `Window` as a follow-up ## Testing - Ran all the changed examples
1 parent d1c6fbe commit a750cfe

File tree

13 files changed

+212
-112
lines changed

13 files changed

+212
-112
lines changed

crates/bevy_window/src/lib.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ impl Default for WindowPlugin {
5757
fn default() -> Self {
5858
WindowPlugin {
5959
primary_window: Some(Window::default()),
60+
primary_cursor_options: Some(CursorOptions::default()),
6061
exit_condition: ExitCondition::OnAllClosed,
6162
close_when_requested: true,
6263
}
@@ -76,6 +77,13 @@ pub struct WindowPlugin {
7677
/// [`exit_on_all_closed`].
7778
pub primary_window: Option<Window>,
7879

80+
/// Settings for the cursor on the primary window.
81+
///
82+
/// Defaults to `Some(CursorOptions::default())`.
83+
///
84+
/// Has no effect if [`WindowPlugin::primary_window`] is `None`.
85+
pub primary_cursor_options: Option<CursorOptions>,
86+
7987
/// Whether to exit the app when there are no open windows.
8088
///
8189
/// If disabling this, ensure that you send the [`bevy_app::AppExit`]
@@ -122,10 +130,14 @@ impl Plugin for WindowPlugin {
122130
.add_event::<AppLifecycle>();
123131

124132
if let Some(primary_window) = &self.primary_window {
125-
app.world_mut().spawn(primary_window.clone()).insert((
133+
let mut entity_commands = app.world_mut().spawn(primary_window.clone());
134+
entity_commands.insert((
126135
PrimaryWindow,
127136
RawHandleWrapperHolder(Arc::new(Mutex::new(None))),
128137
));
138+
if let Some(primary_cursor_options) = &self.primary_cursor_options {
139+
entity_commands.insert(primary_cursor_options.clone());
140+
}
129141
}
130142

131143
match self.exit_condition {
@@ -168,7 +180,8 @@ impl Plugin for WindowPlugin {
168180
// Register window descriptor and related types
169181
#[cfg(feature = "bevy_reflect")]
170182
app.register_type::<Window>()
171-
.register_type::<PrimaryWindow>();
183+
.register_type::<PrimaryWindow>()
184+
.register_type::<CursorOptions>();
172185
}
173186
}
174187

crates/bevy_window/src/window.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,8 @@ impl ContainsEntity for NormalizedWindowRef {
158158
all(feature = "serialize", feature = "bevy_reflect"),
159159
reflect(Serialize, Deserialize)
160160
)]
161+
#[require(CursorOptions)]
161162
pub struct Window {
162-
/// The cursor options of this window. Cursor icons are set with the `Cursor` component on the
163-
/// window entity.
164-
pub cursor_options: CursorOptions,
165163
/// What presentation mode to give the window.
166164
pub present_mode: PresentMode,
167165
/// Which fullscreen or windowing mode should be used.
@@ -470,7 +468,6 @@ impl Default for Window {
470468
Self {
471469
title: DEFAULT_WINDOW_TITLE.to_owned(),
472470
name: None,
473-
cursor_options: Default::default(),
474471
present_mode: Default::default(),
475472
mode: Default::default(),
476473
position: Default::default(),
@@ -728,11 +725,11 @@ impl WindowResizeConstraints {
728725
}
729726

730727
/// Cursor data for a [`Window`].
731-
#[derive(Debug, Clone)]
728+
#[derive(Component, Debug, Clone)]
732729
#[cfg_attr(
733730
feature = "bevy_reflect",
734731
derive(Reflect),
735-
reflect(Debug, Default, Clone)
732+
reflect(Component, Debug, Default, Clone)
736733
)]
737734
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
738735
#[cfg_attr(

crates/bevy_winit/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ use winit::{event_loop::EventLoop, window::WindowId};
2525
use bevy_a11y::AccessibilityRequested;
2626
use bevy_app::{App, Last, Plugin};
2727
use bevy_ecs::prelude::*;
28-
use bevy_window::{exit_on_all_closed, Window, WindowCreated};
29-
use system::{changed_windows, check_keyboard_focus_lost, despawn_windows};
28+
use bevy_window::{exit_on_all_closed, CursorOptions, Window, WindowCreated};
29+
use system::{changed_cursor_options, changed_windows, check_keyboard_focus_lost, despawn_windows};
3030
pub use system::{create_monitors, create_windows};
3131
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
3232
pub use winit::platform::web::CustomCursorExtWebSys;
@@ -142,6 +142,7 @@ impl<T: BufferedEvent> Plugin for WinitPlugin<T> {
142142
// `exit_on_all_closed` only checks if windows exist but doesn't access data,
143143
// so we don't need to care about its ordering relative to `changed_windows`
144144
changed_windows.ambiguous_with(exit_on_all_closed),
145+
changed_cursor_options,
145146
despawn_windows,
146147
check_keyboard_focus_lost,
147148
)
@@ -211,6 +212,7 @@ pub type CreateWindowParams<'w, 's, F = ()> = (
211212
(
212213
Entity,
213214
&'static mut Window,
215+
&'static CursorOptions,
214216
Option<&'static RawHandleWrapperHolder>,
215217
),
216218
F,

crates/bevy_winit/src/state.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ use bevy_window::{
4646
WindowScaleFactorChanged, WindowThemeChanged,
4747
};
4848
#[cfg(target_os = "android")]
49-
use bevy_window::{PrimaryWindow, RawHandleWrapper};
49+
use bevy_window::{CursorOptions, PrimaryWindow, RawHandleWrapper};
5050

5151
use crate::{
5252
accessibility::ACCESS_KIT_ADAPTERS,
@@ -474,7 +474,7 @@ impl<T: BufferedEvent> ApplicationHandler<T> for WinitAppRunnerState<T> {
474474
if let Ok((window_component, mut cache)) = windows.get_mut(self.world_mut(), window)
475475
{
476476
if window_component.is_changed() {
477-
cache.window = window_component.clone();
477+
**cache = window_component.clone();
478478
}
479479
}
480480
});
@@ -605,10 +605,12 @@ impl<T: BufferedEvent> WinitAppRunnerState<T> {
605605
{
606606
// Get windows that are cached but without raw handles. Those window were already created, but got their
607607
// handle wrapper removed when the app was suspended.
608+
608609
let mut query = self.world_mut()
609-
.query_filtered::<(Entity, &Window), (With<CachedWindow>, Without<RawHandleWrapper>)>();
610-
if let Ok((entity, window)) = query.single(&self.world()) {
610+
.query_filtered::<(Entity, &Window, &CursorOptions), (With<CachedWindow>, Without<RawHandleWrapper>)>();
611+
if let Ok((entity, window, cursor_options)) = query.single(&self.world()) {
611612
let window = window.clone();
613+
let cursor_options = cursor_options.clone();
612614

613615
WINIT_WINDOWS.with_borrow_mut(|winit_windows| {
614616
ACCESS_KIT_ADAPTERS.with_borrow_mut(|adapters| {
@@ -622,6 +624,7 @@ impl<T: BufferedEvent> WinitAppRunnerState<T> {
622624
event_loop,
623625
entity,
624626
&window,
627+
&cursor_options,
625628
adapters,
626629
&mut handlers,
627630
&accessibility_requested,

0 commit comments

Comments
 (0)