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
This commit is contained in:
parent
d1c6fbea57
commit
a750cfe4a1
@ -57,6 +57,7 @@ impl Default for WindowPlugin {
|
||||
fn default() -> Self {
|
||||
WindowPlugin {
|
||||
primary_window: Some(Window::default()),
|
||||
primary_cursor_options: Some(CursorOptions::default()),
|
||||
exit_condition: ExitCondition::OnAllClosed,
|
||||
close_when_requested: true,
|
||||
}
|
||||
@ -76,6 +77,13 @@ pub struct WindowPlugin {
|
||||
/// [`exit_on_all_closed`].
|
||||
pub primary_window: Option<Window>,
|
||||
|
||||
/// Settings for the cursor on the primary window.
|
||||
///
|
||||
/// Defaults to `Some(CursorOptions::default())`.
|
||||
///
|
||||
/// Has no effect if [`WindowPlugin::primary_window`] is `None`.
|
||||
pub primary_cursor_options: Option<CursorOptions>,
|
||||
|
||||
/// Whether to exit the app when there are no open windows.
|
||||
///
|
||||
/// If disabling this, ensure that you send the [`bevy_app::AppExit`]
|
||||
@ -122,10 +130,14 @@ impl Plugin for WindowPlugin {
|
||||
.add_event::<AppLifecycle>();
|
||||
|
||||
if let Some(primary_window) = &self.primary_window {
|
||||
app.world_mut().spawn(primary_window.clone()).insert((
|
||||
let mut entity_commands = app.world_mut().spawn(primary_window.clone());
|
||||
entity_commands.insert((
|
||||
PrimaryWindow,
|
||||
RawHandleWrapperHolder(Arc::new(Mutex::new(None))),
|
||||
));
|
||||
if let Some(primary_cursor_options) = &self.primary_cursor_options {
|
||||
entity_commands.insert(primary_cursor_options.clone());
|
||||
}
|
||||
}
|
||||
|
||||
match self.exit_condition {
|
||||
@ -168,7 +180,8 @@ impl Plugin for WindowPlugin {
|
||||
// Register window descriptor and related types
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
app.register_type::<Window>()
|
||||
.register_type::<PrimaryWindow>();
|
||||
.register_type::<PrimaryWindow>()
|
||||
.register_type::<CursorOptions>();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,10 +158,8 @@ impl ContainsEntity for NormalizedWindowRef {
|
||||
all(feature = "serialize", feature = "bevy_reflect"),
|
||||
reflect(Serialize, Deserialize)
|
||||
)]
|
||||
#[require(CursorOptions)]
|
||||
pub struct Window {
|
||||
/// The cursor options of this window. Cursor icons are set with the `Cursor` component on the
|
||||
/// window entity.
|
||||
pub cursor_options: CursorOptions,
|
||||
/// What presentation mode to give the window.
|
||||
pub present_mode: PresentMode,
|
||||
/// Which fullscreen or windowing mode should be used.
|
||||
@ -470,7 +468,6 @@ impl Default for Window {
|
||||
Self {
|
||||
title: DEFAULT_WINDOW_TITLE.to_owned(),
|
||||
name: None,
|
||||
cursor_options: Default::default(),
|
||||
present_mode: Default::default(),
|
||||
mode: Default::default(),
|
||||
position: Default::default(),
|
||||
@ -728,11 +725,11 @@ impl WindowResizeConstraints {
|
||||
}
|
||||
|
||||
/// Cursor data for a [`Window`].
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Component, Debug, Clone)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
reflect(Debug, Default, Clone)
|
||||
reflect(Component, Debug, Default, Clone)
|
||||
)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
|
@ -25,8 +25,8 @@ use winit::{event_loop::EventLoop, window::WindowId};
|
||||
use bevy_a11y::AccessibilityRequested;
|
||||
use bevy_app::{App, Last, Plugin};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_window::{exit_on_all_closed, Window, WindowCreated};
|
||||
use system::{changed_windows, check_keyboard_focus_lost, despawn_windows};
|
||||
use bevy_window::{exit_on_all_closed, CursorOptions, Window, WindowCreated};
|
||||
use system::{changed_cursor_options, changed_windows, check_keyboard_focus_lost, despawn_windows};
|
||||
pub use system::{create_monitors, create_windows};
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
pub use winit::platform::web::CustomCursorExtWebSys;
|
||||
@ -142,6 +142,7 @@ impl<T: BufferedEvent> Plugin for WinitPlugin<T> {
|
||||
// `exit_on_all_closed` only checks if windows exist but doesn't access data,
|
||||
// so we don't need to care about its ordering relative to `changed_windows`
|
||||
changed_windows.ambiguous_with(exit_on_all_closed),
|
||||
changed_cursor_options,
|
||||
despawn_windows,
|
||||
check_keyboard_focus_lost,
|
||||
)
|
||||
@ -211,6 +212,7 @@ pub type CreateWindowParams<'w, 's, F = ()> = (
|
||||
(
|
||||
Entity,
|
||||
&'static mut Window,
|
||||
&'static CursorOptions,
|
||||
Option<&'static RawHandleWrapperHolder>,
|
||||
),
|
||||
F,
|
||||
|
@ -46,7 +46,7 @@ use bevy_window::{
|
||||
WindowScaleFactorChanged, WindowThemeChanged,
|
||||
};
|
||||
#[cfg(target_os = "android")]
|
||||
use bevy_window::{PrimaryWindow, RawHandleWrapper};
|
||||
use bevy_window::{CursorOptions, PrimaryWindow, RawHandleWrapper};
|
||||
|
||||
use crate::{
|
||||
accessibility::ACCESS_KIT_ADAPTERS,
|
||||
@ -474,7 +474,7 @@ impl<T: BufferedEvent> ApplicationHandler<T> for WinitAppRunnerState<T> {
|
||||
if let Ok((window_component, mut cache)) = windows.get_mut(self.world_mut(), window)
|
||||
{
|
||||
if window_component.is_changed() {
|
||||
cache.window = window_component.clone();
|
||||
**cache = window_component.clone();
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -605,10 +605,12 @@ impl<T: BufferedEvent> WinitAppRunnerState<T> {
|
||||
{
|
||||
// Get windows that are cached but without raw handles. Those window were already created, but got their
|
||||
// handle wrapper removed when the app was suspended.
|
||||
|
||||
let mut query = self.world_mut()
|
||||
.query_filtered::<(Entity, &Window), (With<CachedWindow>, Without<RawHandleWrapper>)>();
|
||||
if let Ok((entity, window)) = query.single(&self.world()) {
|
||||
.query_filtered::<(Entity, &Window, &CursorOptions), (With<CachedWindow>, Without<RawHandleWrapper>)>();
|
||||
if let Ok((entity, window, cursor_options)) = query.single(&self.world()) {
|
||||
let window = window.clone();
|
||||
let cursor_options = cursor_options.clone();
|
||||
|
||||
WINIT_WINDOWS.with_borrow_mut(|winit_windows| {
|
||||
ACCESS_KIT_ADAPTERS.with_borrow_mut(|adapters| {
|
||||
@ -622,6 +624,7 @@ impl<T: BufferedEvent> WinitAppRunnerState<T> {
|
||||
event_loop,
|
||||
entity,
|
||||
&window,
|
||||
&cursor_options,
|
||||
adapters,
|
||||
&mut handlers,
|
||||
&accessibility_requested,
|
||||
|
@ -1,6 +1,8 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::{
|
||||
change_detection::DetectChangesMut,
|
||||
entity::Entity,
|
||||
event::EventWriter,
|
||||
lifecycle::RemovedComponents,
|
||||
@ -10,9 +12,9 @@ use bevy_ecs::{
|
||||
};
|
||||
use bevy_input::keyboard::{Key, KeyCode, KeyboardFocusLost, KeyboardInput};
|
||||
use bevy_window::{
|
||||
ClosingWindow, Monitor, PrimaryMonitor, RawHandleWrapper, VideoMode, Window, WindowClosed,
|
||||
WindowClosing, WindowCreated, WindowEvent, WindowFocused, WindowMode, WindowResized,
|
||||
WindowWrapper,
|
||||
ClosingWindow, CursorOptions, Monitor, PrimaryMonitor, RawHandleWrapper, VideoMode, Window,
|
||||
WindowClosed, WindowClosing, WindowCreated, WindowEvent, WindowFocused, WindowMode,
|
||||
WindowResized, WindowWrapper,
|
||||
};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
@ -59,7 +61,7 @@ pub fn create_windows<F: QueryFilter + 'static>(
|
||||
) {
|
||||
WINIT_WINDOWS.with_borrow_mut(|winit_windows| {
|
||||
ACCESS_KIT_ADAPTERS.with_borrow_mut(|adapters| {
|
||||
for (entity, mut window, handle_holder) in &mut created_windows {
|
||||
for (entity, mut window, cursor_options, handle_holder) in &mut created_windows {
|
||||
if winit_windows.get_window(entity).is_some() {
|
||||
continue;
|
||||
}
|
||||
@ -70,6 +72,7 @@ pub fn create_windows<F: QueryFilter + 'static>(
|
||||
event_loop,
|
||||
entity,
|
||||
&window,
|
||||
cursor_options,
|
||||
adapters,
|
||||
&mut handlers,
|
||||
&accessibility_requested,
|
||||
@ -85,9 +88,8 @@ pub fn create_windows<F: QueryFilter + 'static>(
|
||||
.set_scale_factor_and_apply_to_physical_size(winit_window.scale_factor() as f32);
|
||||
|
||||
commands.entity(entity).insert((
|
||||
CachedWindow {
|
||||
window: window.clone(),
|
||||
},
|
||||
CachedWindow(window.clone()),
|
||||
CachedCursorOptions(cursor_options.clone()),
|
||||
WinitWindowPressedKeys::default(),
|
||||
));
|
||||
|
||||
@ -281,10 +283,12 @@ pub(crate) fn despawn_windows(
|
||||
}
|
||||
|
||||
/// The cached state of the window so we can check which properties were changed from within the app.
|
||||
#[derive(Debug, Clone, Component)]
|
||||
pub struct CachedWindow {
|
||||
pub window: Window,
|
||||
}
|
||||
#[derive(Debug, Clone, Component, Deref, DerefMut)]
|
||||
pub(crate) struct CachedWindow(Window);
|
||||
|
||||
/// The cached state of the window so we can check which properties were changed from within the app.
|
||||
#[derive(Debug, Clone, Component, Deref, DerefMut)]
|
||||
pub(crate) struct CachedCursorOptions(CursorOptions);
|
||||
|
||||
/// Propagates changes from [`Window`] entities to the [`winit`] backend.
|
||||
///
|
||||
@ -306,11 +310,11 @@ pub(crate) fn changed_windows(
|
||||
continue;
|
||||
};
|
||||
|
||||
if window.title != cache.window.title {
|
||||
if window.title != cache.title {
|
||||
winit_window.set_title(window.title.as_str());
|
||||
}
|
||||
|
||||
if window.mode != cache.window.mode {
|
||||
if window.mode != cache.mode {
|
||||
let new_mode = match window.mode {
|
||||
WindowMode::BorderlessFullscreen(monitor_selection) => {
|
||||
Some(Some(winit::window::Fullscreen::Borderless(select_monitor(
|
||||
@ -352,15 +356,15 @@ pub(crate) fn changed_windows(
|
||||
}
|
||||
}
|
||||
|
||||
if window.resolution != cache.window.resolution {
|
||||
if window.resolution != cache.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(),
|
||||
cache.physical_width(),
|
||||
cache.physical_height(),
|
||||
);
|
||||
|
||||
let base_scale_factor = window.resolution.base_scale_factor();
|
||||
@ -368,12 +372,12 @@ pub(crate) fn changed_windows(
|
||||
// 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();
|
||||
let cached_scale_factor = cache.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() {
|
||||
if let Some(cached_factor) = cache.resolution.scale_factor_override() {
|
||||
physical_size.to_logical::<f32>(cached_factor as f64)
|
||||
} else {
|
||||
physical_size.to_logical::<f32>(base_scale_factor as f64)
|
||||
@ -397,7 +401,7 @@ pub(crate) fn changed_windows(
|
||||
}
|
||||
}
|
||||
|
||||
if window.physical_cursor_position() != cache.window.physical_cursor_position() {
|
||||
if window.physical_cursor_position() != cache.physical_cursor_position() {
|
||||
if let Some(physical_position) = window.physical_cursor_position() {
|
||||
let position = PhysicalPosition::new(physical_position.x, physical_position.y);
|
||||
|
||||
@ -407,44 +411,23 @@ pub(crate) fn changed_windows(
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
if window.decorations != cache.decorations
|
||||
&& window.decorations != winit_window.is_decorated()
|
||||
{
|
||||
winit_window.set_decorations(window.decorations);
|
||||
}
|
||||
|
||||
if window.resizable != cache.window.resizable
|
||||
if window.resizable != cache.resizable
|
||||
&& window.resizable != winit_window.is_resizable()
|
||||
{
|
||||
winit_window.set_resizable(window.resizable);
|
||||
}
|
||||
|
||||
if window.enabled_buttons != cache.window.enabled_buttons {
|
||||
if window.enabled_buttons != cache.enabled_buttons {
|
||||
winit_window.set_enabled_buttons(convert_enabled_buttons(window.enabled_buttons));
|
||||
}
|
||||
|
||||
if window.resize_constraints != cache.window.resize_constraints {
|
||||
if window.resize_constraints != cache.resize_constraints {
|
||||
let constraints = window.resize_constraints.check_constraints();
|
||||
let min_inner_size = LogicalSize {
|
||||
width: constraints.min_width,
|
||||
@ -461,7 +444,7 @@ pub(crate) fn changed_windows(
|
||||
}
|
||||
}
|
||||
|
||||
if window.position != cache.window.position {
|
||||
if window.position != cache.position {
|
||||
if let Some(position) = crate::winit_window_position(
|
||||
&window.position,
|
||||
&window.resolution,
|
||||
@ -502,62 +485,62 @@ pub(crate) fn changed_windows(
|
||||
}
|
||||
}
|
||||
|
||||
if window.focused != cache.window.focused && window.focused {
|
||||
if window.focused != cache.focused && window.focused {
|
||||
winit_window.focus_window();
|
||||
}
|
||||
|
||||
if window.window_level != cache.window.window_level {
|
||||
if window.window_level != cache.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;
|
||||
if window.transparent != cache.transparent {
|
||||
window.transparent = cache.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);
|
||||
if window.canvas != cache.canvas {
|
||||
window.canvas.clone_from(&cache.canvas);
|
||||
warn!(
|
||||
"Bevy currently doesn't support modifying the window canvas after initialization."
|
||||
);
|
||||
}
|
||||
|
||||
if window.ime_enabled != cache.window.ime_enabled {
|
||||
if window.ime_enabled != cache.ime_enabled {
|
||||
winit_window.set_ime_allowed(window.ime_enabled);
|
||||
}
|
||||
|
||||
if window.ime_position != cache.window.ime_position {
|
||||
if window.ime_position != cache.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 {
|
||||
if window.window_theme != cache.window_theme {
|
||||
winit_window.set_theme(window.window_theme.map(convert_window_theme));
|
||||
}
|
||||
|
||||
if window.visible != cache.window.visible {
|
||||
if window.visible != cache.visible {
|
||||
winit_window.set_visible(window.visible);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
{
|
||||
if window.recognize_pinch_gesture != cache.window.recognize_pinch_gesture {
|
||||
if window.recognize_pinch_gesture != cache.recognize_pinch_gesture {
|
||||
winit_window.recognize_pinch_gesture(window.recognize_pinch_gesture);
|
||||
}
|
||||
if window.recognize_rotation_gesture != cache.window.recognize_rotation_gesture {
|
||||
if window.recognize_rotation_gesture != cache.recognize_rotation_gesture {
|
||||
winit_window.recognize_rotation_gesture(window.recognize_rotation_gesture);
|
||||
}
|
||||
if window.recognize_doubletap_gesture != cache.window.recognize_doubletap_gesture {
|
||||
if window.recognize_doubletap_gesture != cache.recognize_doubletap_gesture {
|
||||
winit_window.recognize_doubletap_gesture(window.recognize_doubletap_gesture);
|
||||
}
|
||||
if window.recognize_pan_gesture != cache.window.recognize_pan_gesture {
|
||||
if window.recognize_pan_gesture != cache.recognize_pan_gesture {
|
||||
match (
|
||||
window.recognize_pan_gesture,
|
||||
cache.window.recognize_pan_gesture,
|
||||
cache.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");
|
||||
@ -567,16 +550,15 @@ pub(crate) fn changed_windows(
|
||||
}
|
||||
}
|
||||
|
||||
if window.prefers_home_indicator_hidden != cache.window.prefers_home_indicator_hidden {
|
||||
if window.prefers_home_indicator_hidden != cache.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 {
|
||||
if window.prefers_status_bar_hidden != cache.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;
|
||||
@ -585,7 +567,59 @@ pub(crate) fn changed_windows(
|
||||
winit_window.set_preferred_screen_edges_deferring_system_gestures(preferred_edge);
|
||||
}
|
||||
}
|
||||
cache.window = window.clone();
|
||||
**cache = window.clone();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn changed_cursor_options(
|
||||
mut changed_windows: Query<
|
||||
(
|
||||
Entity,
|
||||
&Window,
|
||||
&mut CursorOptions,
|
||||
&mut CachedCursorOptions,
|
||||
),
|
||||
Changed<CursorOptions>,
|
||||
>,
|
||||
_non_send_marker: NonSendMarker,
|
||||
) {
|
||||
WINIT_WINDOWS.with_borrow(|winit_windows| {
|
||||
for (entity, window, mut cursor_options, mut cache) in &mut changed_windows {
|
||||
// This system already only runs when the cursor options change, so we need to bypass change detection or the next frame will also run this system
|
||||
let cursor_options = cursor_options.bypass_change_detection();
|
||||
let Some(winit_window) = winit_windows.get_window(entity) else {
|
||||
continue;
|
||||
};
|
||||
// Don't check the cache for the grab mode. It can change through external means, leaving the cache outdated.
|
||||
if let Err(err) =
|
||||
crate::winit_windows::attempt_grab(winit_window, cursor_options.grab_mode)
|
||||
{
|
||||
warn!(
|
||||
"Could not set cursor grab mode for window {}: {}",
|
||||
window.title, err
|
||||
);
|
||||
cursor_options.grab_mode = cache.grab_mode;
|
||||
} else {
|
||||
cache.grab_mode = cursor_options.grab_mode;
|
||||
}
|
||||
|
||||
if cursor_options.visible != cache.visible {
|
||||
winit_window.set_cursor_visible(cursor_options.visible);
|
||||
cache.visible = cursor_options.visible;
|
||||
}
|
||||
|
||||
if cursor_options.hit_test != cache.hit_test {
|
||||
if let Err(err) = winit_window.set_cursor_hittest(cursor_options.hit_test) {
|
||||
warn!(
|
||||
"Could not set cursor hit test for window {}: {}",
|
||||
window.title, err
|
||||
);
|
||||
cursor_options.hit_test = cache.hit_test;
|
||||
} else {
|
||||
cache.hit_test = cursor_options.hit_test;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ use bevy_ecs::entity::Entity;
|
||||
use bevy_ecs::entity::EntityHashMap;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_window::{
|
||||
CursorGrabMode, MonitorSelection, VideoModeSelection, Window, WindowMode, WindowPosition,
|
||||
WindowResolution, WindowWrapper,
|
||||
CursorGrabMode, CursorOptions, MonitorSelection, VideoModeSelection, Window, WindowMode,
|
||||
WindowPosition, WindowResolution, WindowWrapper,
|
||||
};
|
||||
use tracing::warn;
|
||||
|
||||
@ -58,6 +58,7 @@ impl WinitWindows {
|
||||
event_loop: &ActiveEventLoop,
|
||||
entity: Entity,
|
||||
window: &Window,
|
||||
cursor_options: &CursorOptions,
|
||||
adapters: &mut AccessKitAdapters,
|
||||
handlers: &mut WinitActionRequestHandlers,
|
||||
accessibility_requested: &AccessibilityRequested,
|
||||
@ -310,16 +311,16 @@ impl WinitWindows {
|
||||
winit_window.set_visible(window.visible);
|
||||
|
||||
// Do not set the grab mode on window creation if it's none. It can fail on mobile.
|
||||
if window.cursor_options.grab_mode != CursorGrabMode::None {
|
||||
let _ = attempt_grab(&winit_window, window.cursor_options.grab_mode);
|
||||
if cursor_options.grab_mode != CursorGrabMode::None {
|
||||
let _ = attempt_grab(&winit_window, cursor_options.grab_mode);
|
||||
}
|
||||
|
||||
winit_window.set_cursor_visible(window.cursor_options.visible);
|
||||
winit_window.set_cursor_visible(cursor_options.visible);
|
||||
|
||||
// Do not set the cursor hittest on window creation if it's false, as it will always fail on
|
||||
// some platforms and log an unfixable warning.
|
||||
if !window.cursor_options.hit_test {
|
||||
if let Err(err) = winit_window.set_cursor_hittest(window.cursor_options.hit_test) {
|
||||
if !cursor_options.hit_test {
|
||||
if let Err(err) = winit_window.set_cursor_hittest(cursor_options.hit_test) {
|
||||
warn!(
|
||||
"Could not set cursor hit test for window {}: {}",
|
||||
window.title, err
|
||||
|
@ -10,7 +10,7 @@ use bevy::{
|
||||
app::AppExit,
|
||||
input::common_conditions::{input_just_pressed, input_just_released},
|
||||
prelude::*,
|
||||
window::{PrimaryWindow, WindowLevel},
|
||||
window::{CursorOptions, PrimaryWindow, WindowLevel},
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@ -219,12 +219,13 @@ fn get_cursor_world_pos(
|
||||
/// Update whether the window is clickable or not
|
||||
fn update_cursor_hit_test(
|
||||
cursor_world_pos: Res<CursorWorldPos>,
|
||||
mut primary_window: Single<&mut Window, With<PrimaryWindow>>,
|
||||
primary_window: Single<(&Window, &mut CursorOptions), With<PrimaryWindow>>,
|
||||
bevy_logo_transform: Single<&Transform, With<BevyLogo>>,
|
||||
) {
|
||||
let (window, mut cursor_options) = primary_window.into_inner();
|
||||
// If the window has decorations (e.g. a border) then it should be clickable
|
||||
if primary_window.decorations {
|
||||
primary_window.cursor_options.hit_test = true;
|
||||
if window.decorations {
|
||||
cursor_options.hit_test = true;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -234,7 +235,7 @@ fn update_cursor_hit_test(
|
||||
};
|
||||
|
||||
// If the cursor is within the radius of the Bevy logo make the window clickable otherwise the window is not clickable
|
||||
primary_window.cursor_options.hit_test = bevy_logo_transform
|
||||
cursor_options.hit_test = bevy_logo_transform
|
||||
.translation
|
||||
.truncate()
|
||||
.distance(cursor_world_pos)
|
||||
|
@ -8,7 +8,7 @@
|
||||
use bevy::{
|
||||
input::mouse::{AccumulatedMouseMotion, AccumulatedMouseScroll, MouseScrollUnit},
|
||||
prelude::*,
|
||||
window::CursorGrabMode,
|
||||
window::{CursorGrabMode, CursorOptions},
|
||||
};
|
||||
use std::{f32::consts::*, fmt};
|
||||
|
||||
@ -126,7 +126,7 @@ Freecam Controls:
|
||||
|
||||
fn run_camera_controller(
|
||||
time: Res<Time>,
|
||||
mut windows: Query<&mut Window>,
|
||||
mut windows: Query<(&Window, &mut CursorOptions)>,
|
||||
accumulated_mouse_motion: Res<AccumulatedMouseMotion>,
|
||||
accumulated_mouse_scroll: Res<AccumulatedMouseScroll>,
|
||||
mouse_button_input: Res<ButtonInput<MouseButton>>,
|
||||
@ -226,18 +226,18 @@ fn run_camera_controller(
|
||||
// Handle cursor grab
|
||||
if cursor_grab_change {
|
||||
if cursor_grab {
|
||||
for mut window in &mut windows {
|
||||
for (window, mut cursor_options) in &mut windows {
|
||||
if !window.focused {
|
||||
continue;
|
||||
}
|
||||
|
||||
window.cursor_options.grab_mode = CursorGrabMode::Locked;
|
||||
window.cursor_options.visible = false;
|
||||
cursor_options.grab_mode = CursorGrabMode::Locked;
|
||||
cursor_options.visible = false;
|
||||
}
|
||||
} else {
|
||||
for mut window in &mut windows {
|
||||
window.cursor_options.grab_mode = CursorGrabMode::None;
|
||||
window.cursor_options.visible = true;
|
||||
for (_, mut cursor_options) in &mut windows {
|
||||
cursor_options.grab_mode = CursorGrabMode::None;
|
||||
cursor_options.visible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
//! Demonstrates how to grab and hide the mouse cursor.
|
||||
|
||||
use bevy::{prelude::*, window::CursorGrabMode};
|
||||
use bevy::{
|
||||
prelude::*,
|
||||
window::{CursorGrabMode, CursorOptions},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
@ -12,17 +15,17 @@ fn main() {
|
||||
// This system grabs the mouse when the left mouse button is pressed
|
||||
// and releases it when the escape key is pressed
|
||||
fn grab_mouse(
|
||||
mut window: Single<&mut Window>,
|
||||
mut cursor_options: Single<&mut CursorOptions>,
|
||||
mouse: Res<ButtonInput<MouseButton>>,
|
||||
key: Res<ButtonInput<KeyCode>>,
|
||||
) {
|
||||
if mouse.just_pressed(MouseButton::Left) {
|
||||
window.cursor_options.visible = false;
|
||||
window.cursor_options.grab_mode = CursorGrabMode::Locked;
|
||||
cursor_options.visible = false;
|
||||
cursor_options.grab_mode = CursorGrabMode::Locked;
|
||||
}
|
||||
|
||||
if key.just_pressed(KeyCode::Escape) {
|
||||
window.cursor_options.visible = true;
|
||||
window.cursor_options.grab_mode = CursorGrabMode::None;
|
||||
cursor_options.visible = true;
|
||||
cursor_options.grab_mode = CursorGrabMode::None;
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
//! If you build this, and hit 'P' it should toggle on/off the mouse's passthrough.
|
||||
//! Note: this example will not work on following platforms: iOS / Android / Web / X11. Window fall through is not supported there.
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::{prelude::*, window::CursorOptions};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
@ -46,9 +46,9 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
// A simple system to handle some keyboard input and toggle on/off the hit test.
|
||||
fn toggle_mouse_passthrough(
|
||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||
mut window: Single<&mut Window>,
|
||||
mut cursor_options: Single<&mut CursorOptions>,
|
||||
) {
|
||||
if keyboard_input.just_pressed(KeyCode::KeyP) {
|
||||
window.cursor_options.hit_test = !window.cursor_options.hit_test;
|
||||
cursor_options.hit_test = !cursor_options.hit_test;
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,9 @@ use bevy::winit::cursor::{CustomCursor, CustomCursorImage};
|
||||
use bevy::{
|
||||
diagnostic::{FrameCount, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||
prelude::*,
|
||||
window::{CursorGrabMode, PresentMode, SystemCursorIcon, WindowLevel, WindowTheme},
|
||||
window::{
|
||||
CursorGrabMode, CursorOptions, PresentMode, SystemCursorIcon, WindowLevel, WindowTheme,
|
||||
},
|
||||
winit::cursor::CursorIcon,
|
||||
};
|
||||
|
||||
@ -128,10 +130,10 @@ fn change_title(mut window: Single<&mut Window>, time: Res<Time>) {
|
||||
);
|
||||
}
|
||||
|
||||
fn toggle_cursor(mut window: Single<&mut Window>, input: Res<ButtonInput<KeyCode>>) {
|
||||
fn toggle_cursor(mut cursor_options: Single<&mut CursorOptions>, input: Res<ButtonInput<KeyCode>>) {
|
||||
if input.just_pressed(KeyCode::Space) {
|
||||
window.cursor_options.visible = !window.cursor_options.visible;
|
||||
window.cursor_options.grab_mode = match window.cursor_options.grab_mode {
|
||||
cursor_options.visible = !cursor_options.visible;
|
||||
cursor_options.grab_mode = match cursor_options.grab_mode {
|
||||
CursorGrabMode::None => CursorGrabMode::Locked,
|
||||
CursorGrabMode::Locked | CursorGrabMode::Confined => CursorGrabMode::None,
|
||||
};
|
||||
|
44
release-content/migration-guides/split-window.md
Normal file
44
release-content/migration-guides/split-window.md
Normal file
@ -0,0 +1,44 @@
|
||||
---
|
||||
title: Window is now split into multiple components
|
||||
pull_requests: [19668]
|
||||
---
|
||||
|
||||
`Window` has become a very large component over the last few releases. To improve our internal handling of it and to make it more approachable, we
|
||||
have split it into multiple components, all on the same entity. So far, this affects `CursorOptions`:
|
||||
|
||||
```rust
|
||||
// old
|
||||
fn lock_cursor(primary_window: Single<&mut Window, With<PrimaryWindow>>) {
|
||||
primary_window.cursor_options.grab_mode = CursorGrabMode::Locked;
|
||||
}
|
||||
|
||||
// new
|
||||
fn lock_cursor(primary_cursor_options: Single<&mut CursorOptions, With<PrimaryWindow>>) {
|
||||
primary_cursor_options.grab_mode = CursorGrabMode::Locked;
|
||||
}
|
||||
```
|
||||
|
||||
This split also applies when specifying the initial settings for the primary window:
|
||||
|
||||
```rust
|
||||
// old
|
||||
app.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
cursor_options: CursorOptions {
|
||||
grab_mode: CursorGrabMode::Locked,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
}));
|
||||
|
||||
// new
|
||||
app.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||
primary_cursor_options: Some(CursorOptions {
|
||||
grab_mode: CursorGrabMode::Locked,
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
}));
|
||||
```
|
@ -3,7 +3,7 @@ index f578658cd..ffac22062 100644
|
||||
--- a/crates/bevy_window/src/window.rs
|
||||
+++ b/crates/bevy_window/src/window.rs
|
||||
@@ -318,7 +318,7 @@ impl Default for Window {
|
||||
cursor_options: Default::default(),
|
||||
name: None,
|
||||
present_mode: Default::default(),
|
||||
mode: Default::default(),
|
||||
- position: Default::default(),
|
||||
|
Loading…
Reference in New Issue
Block a user