Skip to content

Commit 1d73022

Browse files
author
Fernan Lukban
committed
Test commit
1 parent 7cd2a57 commit 1d73022

File tree

4 files changed

+203
-102
lines changed

4 files changed

+203
-102
lines changed

crates/bevy_render/src/view/window/cursor.rs

+95-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use bevy_app::{App, Last, Plugin};
22
use bevy_asset::{AssetId, Assets, Handle};
33
use bevy_ecs::{
4+
bundle::Bundle,
45
change_detection::DetectChanges,
56
component::Component,
67
entity::Entity,
@@ -12,7 +13,7 @@ use bevy_ecs::{
1213
};
1314
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
1415
use bevy_utils::{tracing::warn, HashSet};
15-
use bevy_window::{SystemCursorIcon, Window};
16+
use bevy_window::{CursorGrabMode, SystemCursorIcon, Window};
1617
use bevy_winit::{
1718
convert_system_cursor_icon, CursorSource, CustomCursorCache, CustomCursorCacheKey,
1819
PendingCursor,
@@ -30,18 +31,89 @@ impl Plugin for CursorPlugin {
3031
.add_systems(Last, update_cursors);
3132

3233
app.observe(on_remove_cursor_icon);
34+
app.observe(on_remove_cursor_hit_test);
35+
app.observe(on_remove_cursor_grab_mode);
3336
}
3437
}
3538

39+
/// Cursor data for a [`Window`].
40+
#[derive(Debug, Clone, Reflect, Bundle, Default)]
41+
#[reflect(Debug, Default)]
42+
pub struct Cursor {
43+
icon: CursorIcon,
44+
grab_mode: CursorGrabMode,
45+
hit_test: CursorHitTest,
46+
}
47+
48+
impl Default for Cursor {
49+
fn default() -> Self {
50+
Cursor {
51+
hit_test: true,
52+
..default()
53+
}
54+
}
55+
}
56+
57+
/// Defines if and how the cursor is grabbed by a [`Window`].
58+
///
59+
/// ## Platform-specific
60+
///
61+
/// - **`Windows`** doesn't support [`CursorGrabMode::Locked`]
62+
/// - **`macOS`** doesn't support [`CursorGrabMode::Confined`]
63+
/// - **`iOS/Android`** don't have cursors.
64+
///
65+
/// Since `Windows` and `macOS` have different [`CursorGrabMode`] support, we first try to set the grab mode that was asked for. If it doesn't work then use the alternate grab mode.
66+
#[derive(Component, Default, Debug, Clone, Copy, PartialEq, Eq, Reflect)]
67+
#[cfg_attr(
68+
feature = "serialize",
69+
derive(serde::Serialize, serde::Deserialize),
70+
reflect(Serialize, Deserialize)
71+
)]
72+
#[reflect(Debug, PartialEq, Default)]
73+
pub enum CursorGrabMode {
74+
/// The cursor can freely leave the window.
75+
#[default]
76+
None,
77+
/// The cursor is confined to the window area.
78+
Confined,
79+
/// The cursor is locked inside the window area to a certain position.
80+
Locked,
81+
}
82+
83+
/// Set whether or not mouse events within *this* window are captured or fall through to the Window below.
84+
///
85+
/// ## Platform-specific
86+
///
87+
/// - iOS / Android / Web / X11: Unsupported.
88+
#[derive(Component, Default, Debug, Clone, Copy, PartialEq, Eq, Reflect)]
89+
#[cfg_attr(
90+
feature = "serialize",
91+
derive(serde::Serialize, serde::Deserialize),
92+
reflect(Serialize, Deserialize)
93+
)]
94+
#[reflect(Debug, PartialEq, Default)]
95+
pub struct CursorHitTest(bool);
96+
3697
/// Insert into a window entity to set the cursor for that window.
3798
#[derive(Component, Debug, Clone, Reflect, PartialEq, Eq)]
3899
#[reflect(Component, Debug, Default, PartialEq)]
39100
pub enum CursorIcon {
40-
/// Makes the cursor hidden
101+
///
102+
/// A cursor that is empty
103+
///
104+
/// ## Platform-specific
105+
///
106+
/// - **`Windows`**, **`X11`**, and **`Wayland`**: The cursor is hidden only when inside the window.
107+
/// To stop the cursor from leaving the window, change [`CursorGrabMode`] component of a Window to
108+
/// [`CursorGrabMode::Locked`] or [`CursorGrabMode::Confined`]
109+
/// - **`macOS`**: The cursor is hidden only when the window is focused.
110+
/// - **`iOS`** and **`Android`** do not have cursors
41111
Hidden,
112+
42113
/// Custom cursor image.
43114
Custom(CustomCursor),
44115
/// System provided cursor icon.
116+
#[default]
45117
System(SystemCursorIcon),
46118
}
47119

@@ -171,9 +243,27 @@ pub fn update_cursors(
171243
pub fn on_remove_cursor_icon(trigger: Trigger<OnRemove, CursorIcon>, mut commands: Commands) {
172244
commands
173245
.entity(trigger.entity())
174-
.insert(PendingCursor(Some(CursorSource::System(
175-
convert_system_cursor_icon(SystemCursorIcon::Default),
176-
))));
246+
.insert(PendingCursor(Some(CursorSource::Default())));
247+
}
248+
249+
/// Resets the cursor hit test to the default when `CursorHitTest` is removed.
250+
pub fn on_remove_cursor_hit_test(
251+
trigger: Trigger<OnRemove, CursorHitTest>,
252+
mut commands: Commands,
253+
) {
254+
commands
255+
.entity(trigger.entity())
256+
.insert(CursorHitTest::default());
257+
}
258+
259+
/// Resets the cursor grab mode when `CursorGrabMode` is removed.
260+
pub fn on_remove_cursor_grab_mode(
261+
trigger: Trigger<OnRemove, CursorGrabMode>,
262+
mut commands: Commands,
263+
) {
264+
commands
265+
.entity(trigger.entity())
266+
.insert(CursorGrabMode::default());
177267
}
178268

179269
/// Returns the image data as a `Vec<u8>`.

crates/bevy_window/src/window.rs

-78
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,6 @@ impl NormalizedWindowRef {
126126
)]
127127
#[reflect(Component, Default, Debug)]
128128
pub struct Window {
129-
/// The cursor options of this window. Cursor icons are set with the `Cursor` component on the
130-
/// window entity.
131-
pub cursor_options: CursorOptions,
132129
/// What presentation mode to give the window.
133130
pub present_mode: PresentMode,
134131
/// Which fullscreen or windowing mode should be used.
@@ -314,7 +311,6 @@ impl Default for Window {
314311
Self {
315312
title: "App".to_owned(),
316313
name: None,
317-
cursor_options: Default::default(),
318314
present_mode: Default::default(),
319315
mode: Default::default(),
320316
position: Default::default(),
@@ -540,54 +536,6 @@ impl WindowResizeConstraints {
540536
}
541537
}
542538

543-
/// Cursor data for a [`Window`].
544-
#[derive(Debug, Clone, Reflect)]
545-
#[cfg_attr(
546-
feature = "serialize",
547-
derive(serde::Serialize, serde::Deserialize),
548-
reflect(Serialize, Deserialize)
549-
)]
550-
#[reflect(Debug, Default)]
551-
pub struct CursorOptions {
552-
/// Whether the cursor is visible or not.
553-
///
554-
/// ## Platform-specific
555-
///
556-
/// - **`Windows`**, **`X11`**, and **`Wayland`**: The cursor is hidden only when inside the window.
557-
/// To stop the cursor from leaving the window, change [`CursorOptions::grab_mode`] to [`CursorGrabMode::Locked`] or [`CursorGrabMode::Confined`]
558-
/// - **`macOS`**: The cursor is hidden only when the window is focused.
559-
/// - **`iOS`** and **`Android`** do not have cursors
560-
pub visible: bool,
561-
562-
/// Whether or not the cursor is locked by or confined within the window.
563-
///
564-
/// ## Platform-specific
565-
///
566-
/// - **`Windows`** doesn't support [`CursorGrabMode::Locked`]
567-
/// - **`macOS`** doesn't support [`CursorGrabMode::Confined`]
568-
/// - **`iOS/Android`** don't have cursors.
569-
///
570-
/// Since `Windows` and `macOS` have different [`CursorGrabMode`] support, we first try to set the grab mode that was asked for. If it doesn't work then use the alternate grab mode.
571-
pub grab_mode: CursorGrabMode,
572-
573-
/// Set whether or not mouse events within *this* window are captured or fall through to the Window below.
574-
///
575-
/// ## Platform-specific
576-
///
577-
/// - iOS / Android / Web / X11: Unsupported.
578-
pub hit_test: bool,
579-
}
580-
581-
impl Default for CursorOptions {
582-
fn default() -> Self {
583-
CursorOptions {
584-
visible: true,
585-
grab_mode: CursorGrabMode::None,
586-
hit_test: true,
587-
}
588-
}
589-
}
590-
591539
/// Defines where a [`Window`] should be placed on the screen.
592540
#[derive(Default, Debug, Clone, Copy, PartialEq, Reflect)]
593541
#[cfg_attr(
@@ -864,32 +812,6 @@ impl From<DVec2> for WindowResolution {
864812
}
865813
}
866814

867-
/// Defines if and how the cursor is grabbed by a [`Window`].
868-
///
869-
/// ## Platform-specific
870-
///
871-
/// - **`Windows`** doesn't support [`CursorGrabMode::Locked`]
872-
/// - **`macOS`** doesn't support [`CursorGrabMode::Confined`]
873-
/// - **`iOS/Android`** don't have cursors.
874-
///
875-
/// Since `Windows` and `macOS` have different [`CursorGrabMode`] support, we first try to set the grab mode that was asked for. If it doesn't work then use the alternate grab mode.
876-
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Reflect)]
877-
#[cfg_attr(
878-
feature = "serialize",
879-
derive(serde::Serialize, serde::Deserialize),
880-
reflect(Serialize, Deserialize)
881-
)]
882-
#[reflect(Debug, PartialEq, Default)]
883-
pub enum CursorGrabMode {
884-
/// The cursor can freely leave the window.
885-
#[default]
886-
None,
887-
/// The cursor is confined to the window area.
888-
Confined,
889-
/// The cursor is locked inside the window area to a certain position.
890-
Locked,
891-
}
892-
893815
/// Stores internal [`Window`] state that isn't directly accessible.
894816
#[derive(Default, Debug, Copy, Clone, PartialEq, Reflect)]
895817
#[cfg_attr(

crates/bevy_winit/src/system.rs

+100-18
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use bevy_ecs::{
66
removal_detection::RemovedComponents,
77
system::{Local, NonSendMut, Query, SystemParamItem},
88
};
9+
use bevy_render::Cursor;
910
use bevy_utils::tracing::{error, info, warn};
1011
use bevy_window::{
1112
ClosingWindow, Monitor, PrimaryMonitor, RawHandleWrapper, VideoMode, Window, WindowClosed,
@@ -83,6 +84,12 @@ pub fn create_windows<F: QueryFilter + 'static>(
8384
.resolution
8485
.set_scale_factor_and_apply_to_physical_size(winit_window.scale_factor() as f32);
8586

87+
let cursor = Cursor::default();
88+
commands.entity(entity).insert(cursor);
89+
commands
90+
.entity(entity)
91+
.insert(CachedCursor::from(cursor.clone()));
92+
8693
commands.entity(entity).insert(CachedWindow {
8794
window: window.clone(),
8895
});
@@ -234,6 +241,99 @@ pub struct CachedWindow {
234241
pub window: Window,
235242
}
236243

244+
/// The cached state of the Cursor so we can check whether
245+
/// - `CursorGrabMode`
246+
/// - `CursorHitTest`
247+
/// - `CursorIcon`
248+
/// were changed from within the app
249+
#[derive(Debug, Clone, Component)]
250+
pub struct CachedCursor {
251+
pub cursor: Cursor,
252+
}
253+
254+
impl From<Cursor> for CachedCursor {
255+
fn from(cursor: Cursor) -> Self {
256+
CachedCursor { cursor }
257+
}
258+
}
259+
260+
/// Propagates changes from [`Window`] entities to the [`winit`] backend.
261+
///
262+
/// # Notes
263+
/// -
264+
pub(crate) fn changed_cursor_grab_mode(
265+
mut changed_cursors: Query<
266+
(
267+
Entity,
268+
&Window,
269+
&mut CursorGrabMode,
270+
&mut CachedCursorGrabMode,
271+
),
272+
(With<Window>, Changed<CursorGrabMode>),
273+
>,
274+
winit_windows: NonSendMut<WinitWindows>,
275+
) {
276+
for (entity, window, cursor_grab_mode, cache) in &mut changed_cursors {
277+
let Some(winit_window) = winit_windows.get_window(entity) else {
278+
continue;
279+
};
280+
281+
if cursor_grab_mode != cache.cursor.grab_mode {
282+
match crate::winit_windows::attempt_grab(winit_window, cursor_grab_mode) {
283+
Ok(_) => {
284+
cache.cursor.grab_mode = cursor_grab_mode;
285+
}
286+
Err(error) => {
287+
cursor_grab_mode = cache.cursor.grab_mode;
288+
warn!(
289+
"Could not set cursor grab mode for window {:?}: {:?}",
290+
window.title, error
291+
);
292+
}
293+
}
294+
}
295+
}
296+
}
297+
298+
/// Propagates changes from [`Window`] entities to the [`winit`] backend.
299+
///
300+
/// # Notes
301+
/// -
302+
pub(crate) fn changed_cursor_hit_test(
303+
mut changed_cursors: Query<
304+
(
305+
Entity,
306+
&Window,
307+
&mut CursorHitTest,
308+
&mut CachedCursorGrabMode,
309+
),
310+
(With<Window>, Changed<HitTest>),
311+
>,
312+
winit_windows: NonSendMut<WinitWindows>,
313+
) {
314+
for (entity, window, cursor_hit_test, cache) in &mut changed_cursors {
315+
let Some(winit_window) = winit_windows.get_window(entity) else {
316+
continue;
317+
};
318+
319+
if cursor_hit_test != cache.cursor.hit_test {
320+
match winit_window.set_cursor_hittest(cursor_hit_test) {
321+
Ok(_) => {
322+
// Update the cache
323+
cache.cursor.hit_test = cursor_hit_test;
324+
}
325+
Err(err) => {
326+
cursor_hit_test = cache.cursor.hit_test;
327+
warn!(
328+
"Could not set cursor hit test for window {:?}: {:?}",
329+
window.title, err
330+
);
331+
}
332+
}
333+
}
334+
}
335+
}
336+
237337
/// Propagates changes from [`Window`] entities to the [`winit`] backend.
238338
///
239339
/// # Notes
@@ -363,24 +463,6 @@ pub(crate) fn changed_windows(
363463
}
364464
}
365465

366-
if window.cursor_options.grab_mode != cache.window.cursor_options.grab_mode {
367-
crate::winit_windows::attempt_grab(winit_window, window.cursor_options.grab_mode);
368-
}
369-
370-
if window.cursor_options.visible != cache.window.cursor_options.visible {
371-
winit_window.set_cursor_visible(window.cursor_options.visible);
372-
}
373-
374-
if window.cursor_options.hit_test != cache.window.cursor_options.hit_test {
375-
if let Err(err) = winit_window.set_cursor_hittest(window.cursor_options.hit_test) {
376-
window.cursor_options.hit_test = cache.window.cursor_options.hit_test;
377-
warn!(
378-
"Could not set cursor hit test for window {:?}: {:?}",
379-
window.title, err
380-
);
381-
}
382-
}
383-
384466
if window.decorations != cache.window.decorations
385467
&& window.decorations != winit_window.is_decorated()
386468
{

0 commit comments

Comments
 (0)