1use crate::{hover::HoverMap, pointer::PointerId, PickingSystems};
3use bevy_app::{App, Plugin, PreUpdate};
4use bevy_derive::Deref;
5use bevy_ecs::{
6 component::Component,
7 entity::Entity,
8 hierarchy::ChildOf,
9 query::{With, Without},
10 reflect::{ReflectComponent, ReflectResource},
11 resource::Resource,
12 schedule::IntoScheduleConfigs,
13 system::{Commands, Query, Res},
14 template::FromTemplate,
15};
16use bevy_reflect::{std_traits::ReflectDefault, Reflect};
17#[cfg(feature = "custom_cursor")]
18use bevy_window::CustomCursor;
19use bevy_window::{CursorIcon, SystemCursorIcon, Window};
20
21#[derive(Deref, Resource, Debug, Clone, Default, Reflect)]
24#[reflect(Resource, Debug, Default)]
25pub struct DefaultCursor(pub EntityCursor);
26
27#[derive(Component, Debug, Clone, Reflect, PartialEq, Eq, FromTemplate)]
33#[reflect(Component, Debug, Default, PartialEq, Clone)]
34pub enum EntityCursor {
35 #[cfg(feature = "custom_cursor")]
36 Custom(CustomCursor),
38 #[default]
39 System(SystemCursorIcon),
41}
42
43#[derive(Deref, Resource, Debug, Clone, Default, Reflect)]
48#[reflect(Resource, Default, Clone, Debug)]
49pub struct OverrideCursor(pub Option<EntityCursor>);
50
51impl EntityCursor {
52 pub fn to_cursor_icon(&self) -> CursorIcon {
55 match self {
56 #[cfg(feature = "custom_cursor")]
57 EntityCursor::Custom(custom_cursor) => CursorIcon::Custom(custom_cursor.clone()),
58 EntityCursor::System(icon) => CursorIcon::from(*icon),
59 }
60 }
61
62 pub fn eq_cursor_icon(&self, cursor_icon: &CursorIcon) -> bool {
65 match (self, cursor_icon, cursor_icon.as_system()) {
70 #[cfg(feature = "custom_cursor")]
71 (EntityCursor::Custom(custom), CursorIcon::Custom(other), _) => custom == other,
72 (EntityCursor::System(system), _, Some(cursor_icon)) => *system == *cursor_icon,
73 _ => false,
74 }
75 }
76}
77
78impl Default for EntityCursor {
79 fn default() -> Self {
80 EntityCursor::System(Default::default())
81 }
82}
83
84pub(crate) fn update_cursor(
88 mut commands: Commands,
89 hover_map: Option<Res<HoverMap>>,
90 parent_query: Query<&ChildOf>,
91 cursor_query: Query<&EntityCursor, Without<Window>>,
92 q_windows: Query<(Entity, Option<&CursorIcon>), With<Window>>,
93 r_default_cursor: Res<DefaultCursor>,
94 r_override_cursor: Res<OverrideCursor>,
95) {
96 let cursor = r_override_cursor.0.as_ref().unwrap_or_else(|| {
97 hover_map
98 .and_then(|hover_map| match hover_map.get(&PointerId::Mouse) {
99 Some(hover_set) => hover_set.keys().find_map(|entity| {
100 cursor_query.get(*entity).ok().or_else(|| {
101 parent_query
102 .iter_ancestors(*entity)
103 .find_map(|e| cursor_query.get(e).ok())
104 })
105 }),
106 None => None,
107 })
108 .unwrap_or(&r_default_cursor)
109 });
110
111 for (entity, prev_cursor) in q_windows.iter() {
112 if let Some(prev_cursor) = prev_cursor
113 && cursor.eq_cursor_icon(prev_cursor)
114 {
115 continue;
116 }
117 commands.entity(entity).insert(cursor.to_cursor_icon());
118 }
119}
120
121pub struct CursorIconPlugin;
123
124impl Plugin for CursorIconPlugin {
125 fn build(&self, app: &mut App) {
126 if app.world().get_resource::<DefaultCursor>().is_none() {
127 app.init_resource::<DefaultCursor>();
128 app.init_resource::<OverrideCursor>();
129 }
130 app.add_systems(PreUpdate, update_cursor.in_set(PickingSystems::Last));
131 }
132}