Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_feathers/src/cursor.rs
9358 views
1
//! Provides a way to automatically set the mouse cursor based on hovered entity.
2
use bevy_app::{App, Plugin, PreUpdate};
3
use bevy_derive::Deref;
4
use bevy_ecs::{
5
component::Component,
6
entity::Entity,
7
hierarchy::ChildOf,
8
query::{With, Without},
9
reflect::{ReflectComponent, ReflectResource},
10
resource::Resource,
11
schedule::IntoScheduleConfigs,
12
system::{Commands, Query, Res},
13
};
14
use bevy_picking::{hover::HoverMap, pointer::PointerId, PickingSystems};
15
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
16
#[cfg(feature = "custom_cursor")]
17
use bevy_window::CustomCursor;
18
use bevy_window::{CursorIcon, SystemCursorIcon, Window};
19
20
/// A resource that specifies the cursor icon to be used when the mouse is not hovering over
21
/// any other entity. This is used to set the default cursor icon for the window.
22
#[derive(Deref, Resource, Debug, Clone, Default, Reflect)]
23
#[reflect(Resource, Debug, Default)]
24
pub struct DefaultCursor(pub EntityCursor);
25
26
/// A component that specifies the cursor shape to be used when the pointer hovers over an entity.
27
/// This is copied to the windows's [`CursorIcon`] component.
28
///
29
/// This is effectively the same type as [`CustomCursor`] but with different methods, and used
30
/// in different places.
31
#[derive(Component, Debug, Clone, Reflect, PartialEq, Eq)]
32
#[reflect(Component, Debug, Default, PartialEq, Clone)]
33
pub enum EntityCursor {
34
#[cfg(feature = "custom_cursor")]
35
/// Custom cursor image.
36
Custom(CustomCursor),
37
/// System provided cursor icon.
38
System(SystemCursorIcon),
39
}
40
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
48
impl EntityCursor {
49
/// Convert the [`EntityCursor`] to a [`CursorIcon`] so that it can be inserted into a
50
/// window.
51
pub fn to_cursor_icon(&self) -> CursorIcon {
52
match self {
53
#[cfg(feature = "custom_cursor")]
54
EntityCursor::Custom(custom_cursor) => CursorIcon::Custom(custom_cursor.clone()),
55
EntityCursor::System(icon) => CursorIcon::from(*icon),
56
}
57
}
58
59
/// Compare the [`EntityCursor`] to a [`CursorIcon`] so that we can see whether or not
60
/// the window cursor needs to be changed.
61
pub fn eq_cursor_icon(&self, cursor_icon: &CursorIcon) -> bool {
62
// If feature custom_cursor is not enabled in bevy_feathers, we can't know if it is or not
63
// in bevy_window. So we use the wrapper function `as_system` to let bevy_window check its own feature.
64
// Otherwise it is not possible to have a match that both covers all cases and doesn't have unreachable
65
// branches under all feature combinations.
66
match (self, cursor_icon, cursor_icon.as_system()) {
67
#[cfg(feature = "custom_cursor")]
68
(EntityCursor::Custom(custom), CursorIcon::Custom(other), _) => custom == other,
69
(EntityCursor::System(system), _, Some(cursor_icon)) => *system == *cursor_icon,
70
_ => false,
71
}
72
}
73
}
74
75
impl Default for EntityCursor {
76
fn default() -> Self {
77
EntityCursor::System(Default::default())
78
}
79
}
80
81
/// System which updates the window cursor icon whenever the mouse hovers over an entity with
82
/// a [`CursorIcon`] component. If no entity is hovered, the cursor icon is set to
83
/// the cursor in the [`DefaultCursor`] resource.
84
pub(crate) fn update_cursor(
85
mut commands: Commands,
86
hover_map: Option<Res<HoverMap>>,
87
parent_query: Query<&ChildOf>,
88
cursor_query: Query<&EntityCursor, Without<Window>>,
89
q_windows: Query<(Entity, Option<&CursorIcon>), With<Window>>,
90
r_default_cursor: Res<DefaultCursor>,
91
r_override_cursor: Res<OverrideCursor>,
92
) {
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
});
107
108
for (entity, prev_cursor) in q_windows.iter() {
109
if let Some(prev_cursor) = prev_cursor
110
&& cursor.eq_cursor_icon(prev_cursor)
111
{
112
continue;
113
}
114
commands.entity(entity).insert(cursor.to_cursor_icon());
115
}
116
}
117
118
/// Plugin that supports automatically changing the cursor based on the hovered entity.
119
pub struct CursorIconPlugin;
120
121
impl Plugin for CursorIconPlugin {
122
fn build(&self, app: &mut App) {
123
if app.world().get_resource::<DefaultCursor>().is_none() {
124
app.init_resource::<DefaultCursor>();
125
app.init_resource::<OverrideCursor>();
126
}
127
app.add_systems(PreUpdate, update_cursor.in_set(PickingSystems::Last));
128
}
129
}
130
131