bevy_picking/
pointer.rs

1//! Types and systems for pointer inputs, such as position and buttons.
2//!
3//! The picking system is built around the concept of a 'Pointer', which is an
4//! abstract representation of a user input with a specific screen location. The cursor
5//! and touch input is provided under [`crate::input`], but you can also implement
6//! your own custom pointers by supplying a unique ID.
7//!
8//! The purpose of this module is primarily to provide a common interface that can be
9//! driven by lower-level input devices and consumed by higher-level interaction systems.
10
11use bevy_ecs::prelude::*;
12use bevy_input::mouse::MouseScrollUnit;
13use bevy_math::Vec2;
14use bevy_platform::collections::HashMap;
15use bevy_reflect::prelude::*;
16use bevy_render::camera::{Camera, NormalizedRenderTarget};
17use bevy_window::PrimaryWindow;
18
19use uuid::Uuid;
20
21use core::{fmt::Debug, ops::Deref};
22
23use crate::backend::HitData;
24
25/// Identifies a unique pointer entity. `Mouse` and `Touch` pointers are automatically spawned.
26///
27/// This component is needed because pointers can be spawned and despawned, but they need to have a
28/// stable ID that persists regardless of the Entity they are associated with.
29#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, Component, Reflect)]
30#[require(PointerLocation, PointerPress, PointerInteraction)]
31#[reflect(Component, Default, Debug, Hash, PartialEq, Clone)]
32pub enum PointerId {
33    /// The mouse pointer.
34    #[default]
35    Mouse,
36    /// A touch input, usually numbered by window touch events from `winit`.
37    Touch(u64),
38    /// A custom, uniquely identified pointer. Useful for mocking inputs or implementing a software
39    /// controlled cursor.
40    #[reflect(ignore, clone)]
41    Custom(Uuid),
42}
43
44impl PointerId {
45    /// Returns true if the pointer is a touch input.
46    pub fn is_touch(&self) -> bool {
47        matches!(self, PointerId::Touch(_))
48    }
49    /// Returns true if the pointer is the mouse.
50    pub fn is_mouse(&self) -> bool {
51        matches!(self, PointerId::Mouse)
52    }
53    /// Returns true if the pointer is a custom input.
54    pub fn is_custom(&self) -> bool {
55        matches!(self, PointerId::Custom(_))
56    }
57    /// Returns the touch id if the pointer is a touch input.
58    pub fn get_touch_id(&self) -> Option<u64> {
59        if let PointerId::Touch(id) = self {
60            Some(*id)
61        } else {
62            None
63        }
64    }
65}
66
67/// Holds a list of entities this pointer is currently interacting with, sorted from nearest to
68/// farthest.
69#[derive(Debug, Default, Clone, Component, Reflect)]
70#[reflect(Component, Default, Debug, Clone)]
71pub struct PointerInteraction {
72    pub(crate) sorted_entities: Vec<(Entity, HitData)>,
73}
74
75impl PointerInteraction {
76    /// Returns the nearest hit entity and data about that intersection.
77    pub fn get_nearest_hit(&self) -> Option<&(Entity, HitData)> {
78        self.sorted_entities.first()
79    }
80}
81
82impl Deref for PointerInteraction {
83    type Target = Vec<(Entity, HitData)>;
84
85    fn deref(&self) -> &Self::Target {
86        &self.sorted_entities
87    }
88}
89
90/// A resource that maps each [`PointerId`] to their [`Entity`] for easy lookups.
91#[derive(Debug, Clone, Default, Resource)]
92pub struct PointerMap {
93    inner: HashMap<PointerId, Entity>,
94}
95
96impl PointerMap {
97    /// Get the [`Entity`] of the supplied [`PointerId`].
98    pub fn get_entity(&self, pointer_id: PointerId) -> Option<Entity> {
99        self.inner.get(&pointer_id).copied()
100    }
101}
102
103/// Update the [`PointerMap`] resource with the current frame's data.
104pub fn update_pointer_map(pointers: Query<(Entity, &PointerId)>, mut map: ResMut<PointerMap>) {
105    map.inner.clear();
106    for (entity, id) in &pointers {
107        map.inner.insert(*id, entity);
108    }
109}
110
111/// Tracks the state of the pointer's buttons in response to [`PointerInput`] events.
112#[derive(Debug, Default, Clone, Component, Reflect, PartialEq, Eq)]
113#[reflect(Component, Default, Debug, PartialEq, Clone)]
114pub struct PointerPress {
115    primary: bool,
116    secondary: bool,
117    middle: bool,
118}
119
120impl PointerPress {
121    /// Returns true if the primary pointer button is pressed.
122    #[inline]
123    pub fn is_primary_pressed(&self) -> bool {
124        self.primary
125    }
126
127    /// Returns true if the secondary pointer button is pressed.
128    #[inline]
129    pub fn is_secondary_pressed(&self) -> bool {
130        self.secondary
131    }
132
133    /// Returns true if the middle (tertiary) pointer button is pressed.
134    #[inline]
135    pub fn is_middle_pressed(&self) -> bool {
136        self.middle
137    }
138
139    /// Returns true if any pointer button is pressed.
140    #[inline]
141    pub fn is_any_pressed(&self) -> bool {
142        self.primary || self.middle || self.secondary
143    }
144}
145
146/// The stage of the pointer button press event
147#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect)]
148#[reflect(Clone, PartialEq)]
149pub enum PressDirection {
150    /// The pointer button was just pressed
151    Pressed,
152    /// The pointer button was just released
153    Released,
154}
155
156/// The button that was just pressed or released
157#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect)]
158#[reflect(Clone, PartialEq)]
159pub enum PointerButton {
160    /// The primary pointer button
161    Primary,
162    /// The secondary pointer button
163    Secondary,
164    /// The tertiary pointer button
165    Middle,
166}
167
168impl PointerButton {
169    /// Iterator over all buttons that a pointer can have.
170    pub fn iter() -> impl Iterator<Item = PointerButton> {
171        [Self::Primary, Self::Secondary, Self::Middle].into_iter()
172    }
173}
174
175/// Component that tracks a pointer's current [`Location`].
176#[derive(Debug, Default, Clone, Component, Reflect, PartialEq)]
177#[reflect(Component, Default, Debug, PartialEq, Clone)]
178pub struct PointerLocation {
179    /// The [`Location`] of the pointer. Note that a location is both the target, and the position
180    /// on the target.
181    #[reflect(ignore, clone)]
182    pub location: Option<Location>,
183}
184
185impl PointerLocation {
186    ///Returns a [`PointerLocation`] associated with the given location
187    pub fn new(location: Location) -> Self {
188        Self {
189            location: Some(location),
190        }
191    }
192
193    /// Returns `Some(&`[`Location`]`)` if the pointer is active, or `None` if the pointer is
194    /// inactive.
195    pub fn location(&self) -> Option<&Location> {
196        self.location.as_ref()
197    }
198}
199
200/// The location of a pointer, including the current [`NormalizedRenderTarget`], and the x/y
201/// position of the pointer on this render target.
202///
203/// Note that:
204/// - a pointer can move freely between render targets
205/// - a pointer is not associated with a [`Camera`] because multiple cameras can target the same
206///   render target. It is up to picking backends to associate a Pointer's `Location` with a
207///   specific `Camera`, if any.
208#[derive(Debug, Clone, Component, Reflect, PartialEq)]
209#[reflect(Component, Debug, PartialEq, Clone)]
210pub struct Location {
211    /// The [`NormalizedRenderTarget`] associated with the pointer, usually a window.
212    pub target: NormalizedRenderTarget,
213    /// The position of the pointer in the `target`.
214    pub position: Vec2,
215}
216
217impl Location {
218    /// Returns `true` if this pointer's [`Location`] is within the [`Camera`]'s viewport.
219    ///
220    /// Note this returns `false` if the location and camera have different render targets.
221    #[inline]
222    pub fn is_in_viewport(
223        &self,
224        camera: &Camera,
225        primary_window: &Query<Entity, With<PrimaryWindow>>,
226    ) -> bool {
227        if camera
228            .target
229            .normalize(Some(match primary_window.single() {
230                Ok(w) => w,
231                Err(_) => return false,
232            }))
233            .as_ref()
234            != Some(&self.target)
235        {
236            return false;
237        }
238
239        camera
240            .logical_viewport_rect()
241            .is_some_and(|rect| rect.contains(self.position))
242    }
243}
244
245/// Event sent to drive a pointer.
246#[derive(Debug, Clone, Copy, Reflect)]
247#[reflect(Clone)]
248pub enum PointerAction {
249    /// Causes the pointer to press a button.
250    Press(PointerButton),
251    /// Causes the pointer to release a button.
252    Release(PointerButton),
253    /// Move the pointer.
254    Move {
255        /// How much the pointer moved from the previous position.
256        delta: Vec2,
257    },
258    /// Scroll the pointer
259    Scroll {
260        /// The mouse scroll unit.
261        unit: MouseScrollUnit,
262        /// The horizontal scroll value.
263        x: f32,
264        /// The vertical scroll value.
265        y: f32,
266    },
267    /// Cancel the pointer. Often used for touch events.
268    Cancel,
269}
270
271/// An input event effecting a pointer.
272#[derive(Event, Debug, Clone, Reflect)]
273#[reflect(Clone)]
274pub struct PointerInput {
275    /// The id of the pointer.
276    pub pointer_id: PointerId,
277    /// The location of the pointer. For [`PointerAction::Move`], this is the location after the movement.
278    pub location: Location,
279    /// The action that the event describes.
280    pub action: PointerAction,
281}
282
283impl PointerInput {
284    /// Creates a new pointer input event.
285    ///
286    /// Note that `location` refers to the position of the pointer *after* the event occurred.
287    pub fn new(pointer_id: PointerId, location: Location, action: PointerAction) -> PointerInput {
288        PointerInput {
289            pointer_id,
290            location,
291            action,
292        }
293    }
294
295    /// Returns true if the `target_button` of this pointer was just pressed.
296    #[inline]
297    pub fn button_just_pressed(&self, target_button: PointerButton) -> bool {
298        if let PointerAction::Press(button) = self.action {
299            button == target_button
300        } else {
301            false
302        }
303    }
304
305    /// Returns true if the `target_button` of this pointer was just released.
306    #[inline]
307    pub fn button_just_released(&self, target_button: PointerButton) -> bool {
308        if let PointerAction::Release(button) = self.action {
309            button == target_button
310        } else {
311            false
312        }
313    }
314
315    /// Updates pointer entities according to the input events.
316    pub fn receive(
317        mut events: EventReader<PointerInput>,
318        mut pointers: Query<(&PointerId, &mut PointerLocation, &mut PointerPress)>,
319    ) {
320        for event in events.read() {
321            match event.action {
322                PointerAction::Press(button) => {
323                    pointers
324                        .iter_mut()
325                        .for_each(|(pointer_id, _, mut pointer)| {
326                            if *pointer_id == event.pointer_id {
327                                match button {
328                                    PointerButton::Primary => pointer.primary = true,
329                                    PointerButton::Secondary => pointer.secondary = true,
330                                    PointerButton::Middle => pointer.middle = true,
331                                }
332                            }
333                        });
334                }
335                PointerAction::Release(button) => {
336                    pointers
337                        .iter_mut()
338                        .for_each(|(pointer_id, _, mut pointer)| {
339                            if *pointer_id == event.pointer_id {
340                                match button {
341                                    PointerButton::Primary => pointer.primary = false,
342                                    PointerButton::Secondary => pointer.secondary = false,
343                                    PointerButton::Middle => pointer.middle = false,
344                                }
345                            }
346                        });
347                }
348                PointerAction::Move { .. } => {
349                    pointers.iter_mut().for_each(|(id, mut pointer, _)| {
350                        if *id == event.pointer_id {
351                            pointer.location = Some(event.location.to_owned());
352                        }
353                    });
354                }
355                _ => {}
356            }
357        }
358    }
359}