Skip to content

Commit e610c53

Browse files
Feathers: add override cursor (#21824)
# Objective Fixes #21801 and tweaks the feathers example to exhibit the new behavior. ## Solution What the issue suggested. Added a new resource and adjusted the `EntityCursor` interaction logic in `cursor.rs`. ## Testing Tested it via the example tweak where the wide button now toggles the override logic. (Note: I wasn't able to get the load cursor to show up on macOS, but I think this is a `winit` issue: `cursor `busyButClickableCursor` appears to be invalid`) --------- Co-authored-by: Alice Cecile <[email protected]>
1 parent f664896 commit e610c53

File tree

3 files changed

+36
-16
lines changed

3 files changed

+36
-16
lines changed

crates/bevy_feathers/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ bevy_ui = { path = "../bevy_ui", version = "0.18.0-dev", features = [
3131
] }
3232
bevy_ui_render = { path = "../bevy_ui_render", version = "0.18.0-dev" }
3333
bevy_window = { path = "../bevy_window", version = "0.18.0-dev" }
34+
bevy_derive = { path = "../bevy_derive", version = "0.18.0-dev" }
3435
smol_str = { version = "0.2", default-features = false }
3536

3637
# other

crates/bevy_feathers/src/cursor.rs

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Provides a way to automatically set the mouse cursor based on hovered entity.
22
use bevy_app::{App, Plugin, PreUpdate};
3+
use bevy_derive::Deref;
34
use bevy_ecs::{
45
component::Component,
56
entity::Entity,
@@ -18,7 +19,7 @@ use bevy_window::{CursorIcon, SystemCursorIcon, Window};
1819

1920
/// A resource that specifies the cursor icon to be used when the mouse is not hovering over
2021
/// any other entity. This is used to set the default cursor icon for the window.
21-
#[derive(Resource, Debug, Clone, Default, Reflect)]
22+
#[derive(Deref, Resource, Debug, Clone, Default, Reflect)]
2223
#[reflect(Resource, Debug, Default)]
2324
pub struct DefaultCursor(pub EntityCursor);
2425

@@ -37,6 +38,13 @@ pub enum EntityCursor {
3738
System(SystemCursorIcon),
3839
}
3940

41+
/// A component used to override any [`EntityCursor`] cursor changes.
42+
///
43+
/// This is meant for cases like loading where you don't want the cursor to imply you
44+
/// can interact with something.
45+
#[derive(Deref, Resource, Debug, Clone, Default, Reflect)]
46+
pub struct OverrideCursor(pub Option<EntityCursor>);
47+
4048
impl EntityCursor {
4149
/// Convert the [`EntityCursor`] to a [`CursorIcon`] so that it can be inserted into a
4250
/// window.
@@ -80,19 +88,22 @@ pub(crate) fn update_cursor(
8088
cursor_query: Query<&EntityCursor, Without<Window>>,
8189
q_windows: Query<(Entity, Option<&CursorIcon>), With<Window>>,
8290
r_default_cursor: Res<DefaultCursor>,
91+
r_override_cursor: Res<OverrideCursor>,
8392
) {
84-
let cursor = hover_map
85-
.and_then(|hover_map| match hover_map.get(&PointerId::Mouse) {
86-
Some(hover_set) => hover_set.keys().find_map(|entity| {
87-
cursor_query.get(*entity).ok().or_else(|| {
88-
parent_query
89-
.iter_ancestors(*entity)
90-
.find_map(|e| cursor_query.get(e).ok())
91-
})
92-
}),
93-
None => None,
94-
})
95-
.unwrap_or(&r_default_cursor.0);
93+
let cursor = r_override_cursor.0.as_ref().unwrap_or_else(|| {
94+
hover_map
95+
.and_then(|hover_map| match hover_map.get(&PointerId::Mouse) {
96+
Some(hover_set) => hover_set.keys().find_map(|entity| {
97+
cursor_query.get(*entity).ok().or_else(|| {
98+
parent_query
99+
.iter_ancestors(*entity)
100+
.find_map(|e| cursor_query.get(e).ok())
101+
})
102+
}),
103+
None => None,
104+
})
105+
.unwrap_or(&r_default_cursor)
106+
});
96107

97108
for (entity, prev_cursor) in q_windows.iter() {
98109
if let Some(prev_cursor) = prev_cursor
@@ -111,6 +122,7 @@ impl Plugin for CursorIconPlugin {
111122
fn build(&self, app: &mut App) {
112123
if app.world().get_resource::<DefaultCursor>().is_none() {
113124
app.init_resource::<DefaultCursor>();
125+
app.init_resource::<OverrideCursor>();
114126
}
115127
app.add_systems(PreUpdate, update_cursor.in_set(PickingSystems::Last));
116128
}

examples/ui/feathers.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use bevy::{
99
ColorSlider, ColorSliderProps, ColorSwatch, ColorSwatchValue, SliderBaseColor,
1010
SliderProps,
1111
},
12+
cursor::{EntityCursor, OverrideCursor},
1213
dark_theme::create_dark_theme,
1314
rounded_corners::RoundedCorners,
1415
theme::{ThemeBackgroundColor, ThemedText, UiTheme},
@@ -21,6 +22,7 @@ use bevy::{
2122
checkbox_self_update, observe, slider_self_update, Activate, RadioButton, RadioGroup,
2223
SliderPrecision, SliderStep, SliderValue, ValueChange,
2324
},
25+
window::SystemCursorIcon,
2426
};
2527

2628
/// A struct to hold the state of various widgets shown in the demo.
@@ -185,10 +187,15 @@ fn demo_root() -> impl Bundle {
185187
button(
186188
ButtonProps::default(),
187189
(),
188-
Spawn((Text::new("Button"), ThemedText))
190+
Spawn((Text::new("Toggle override"), ThemedText))
189191
),
190-
observe(|_activate: On<Activate>| {
191-
info!("Wide button clicked!");
192+
observe(|_activate: On<Activate>, mut ovr: ResMut<OverrideCursor>| {
193+
ovr.0 = if ovr.0.is_some() {
194+
None
195+
} else {
196+
Some(EntityCursor::System(SystemCursorIcon::Wait))
197+
};
198+
info!("Override cursor button clicked!");
192199
})
193200
),
194201
(

0 commit comments

Comments
 (0)