Skip to main content

bevy_picking/
cursor.rs

1//! Provides a way to automatically set the mouse cursor based on hovered entity.
2use 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/// A resource that specifies the cursor icon to be used when the mouse is not hovering over
22/// any other entity. This is used to set the default cursor icon for the window.
23#[derive(Deref, Resource, Debug, Clone, Default, Reflect)]
24#[reflect(Resource, Debug, Default)]
25pub struct DefaultCursor(pub EntityCursor);
26
27/// A component that specifies the cursor shape to be used when the pointer hovers over an entity.
28/// This is copied to the windows's [`CursorIcon`] component.
29///
30/// This is effectively the same type as [`CustomCursor`] but with different methods, and used
31/// in different places.
32#[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 cursor image.
37    Custom(CustomCursor),
38    #[default]
39    /// System provided cursor icon.
40    System(SystemCursorIcon),
41}
42
43/// A resource used to override any [`EntityCursor`] cursor changes.
44///
45/// This is meant for cases like loading where you don't want the cursor to imply you
46/// can interact with something.
47#[derive(Deref, Resource, Debug, Clone, Default, Reflect)]
48#[reflect(Resource, Default, Clone, Debug)]
49pub struct OverrideCursor(pub Option<EntityCursor>);
50
51impl EntityCursor {
52    /// Convert the [`EntityCursor`] to a [`CursorIcon`] so that it can be inserted into a
53    /// window.
54    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    /// Compare the [`EntityCursor`] to a [`CursorIcon`] so that we can see whether or not
63    /// the window cursor needs to be changed.
64    pub fn eq_cursor_icon(&self, cursor_icon: &CursorIcon) -> bool {
65        // If feature custom_cursor is not enabled in bevy_feathers, we can't know if it is or not
66        // in bevy_window. So we use the wrapper function `as_system` to let bevy_window check its own feature.
67        // Otherwise it is not possible to have a match that both covers all cases and doesn't have unreachable
68        // branches under all feature combinations.
69        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
84/// System which updates the window cursor icon whenever the mouse hovers over an entity with
85/// a [`CursorIcon`] component. If no entity is hovered, the cursor icon is set to
86/// the cursor in the [`DefaultCursor`] resource.
87pub(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
121/// Plugin that supports automatically changing the cursor based on the hovered entity.
122pub 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}