bevy_picking/events.rs
1//! This module defines a stateful set of interaction events driven by the `PointerInput` stream
2//! and the hover state of each Pointer.
3//!
4//! # Usage
5//!
6//! To receive events from this module, you must use an [`Observer`] or [`MessageReader`] with [`Pointer<E>`] events.
7//! The simplest example, registering a callback when an entity is hovered over by a pointer, looks like this:
8//!
9//! ```rust
10//! # use bevy_ecs::prelude::*;
11//! # use bevy_picking::prelude::*;
12//! # let mut world = World::default();
13//! world.spawn_empty()
14//! .observe(|event: On<Pointer<Over>>| {
15//! println!("I am being hovered over");
16//! });
17//! ```
18//!
19//! Observers give us three important properties:
20//! 1. They allow for attaching event handlers to specific entities,
21//! 2. they allow events to bubble up the entity hierarchy,
22//! 3. and they allow events of different types to be called in a specific order.
23//!
24//! The order in which interaction events are received is extremely important, and you can read more
25//! about it on the docs for the dispatcher system: [`pointer_events`]. This system runs in
26//! [`PreUpdate`](bevy_app::PreUpdate) in [`PickingSystems::Hover`](crate::PickingSystems::Hover). All pointer-event
27//! observers resolve during the sync point between [`pointer_events`] and
28//! [`update_interactions`](crate::hover::update_interactions).
29//!
30//! # Events Types
31//!
32//! The events this module defines fall into a few broad categories:
33//! + Hovering and movement: [`Over`], [`Enter`], [`Move`], [`Leave`], and [`Out`].
34//! + Clicking and pressing: [`Press`], [`Release`], and [`Click`].
35//! + Dragging and dropping: [`DragStart`], [`Drag`], [`DragEnd`], [`DragEnter`], [`DragOver`], [`DragDrop`], [`DragLeave`].
36//!
37//! When received by an observer, these events will always be wrapped by the [`Pointer`] type, which contains
38//! general metadata about the pointer event.
39
40use core::{fmt::Debug, time::Duration};
41use std::collections::HashSet;
42
43use bevy_camera::NormalizedRenderTarget;
44use bevy_derive::{Deref, DerefMut};
45use bevy_ecs::{
46 entity::{EntityHashMap, EntityHashSet},
47 prelude::*,
48 query::QueryData,
49 system::SystemParam,
50 traversal::Traversal,
51};
52use bevy_input::{mouse::MouseScrollUnit, touch::TouchPhase};
53use bevy_math::Vec2;
54use bevy_platform::collections::HashMap;
55use bevy_platform::time::Instant;
56use bevy_reflect::prelude::*;
57use bevy_window::Window;
58use tracing::debug;
59
60use crate::{
61 backend::{prelude::PointerLocation, HitData},
62 hover::{get_hovered_entities, is_directly_hovered, HoverMap, PreviousHoverMap},
63 pointer::{Location, PointerAction, PointerButton, PointerId, PointerInput, PointerMap},
64 PickingSettings,
65};
66
67/// Stores the common data needed for all pointer events.
68///
69/// The documentation for the [`pointer_events`] explains the events this module exposes and
70/// the order in which they fire.
71#[derive(Message, EntityEvent, Clone, PartialEq, Debug, Reflect, Component)]
72#[entity_event(propagate = PointerTraversal, auto_propagate)]
73#[reflect(Component, Debug, Clone)]
74pub struct Pointer<E: Debug + Clone + Reflect> {
75 /// The entity this pointer event happened for.
76 pub entity: Entity,
77 /// The pointer that triggered this event
78 pub pointer_id: PointerId,
79 /// The location of the pointer during this event
80 pub pointer_location: Location,
81 /// Additional event-specific data. [`DragDrop`] for example, has an additional field to describe
82 /// the `Entity` that is being dropped on the target.
83 pub event: E,
84 /// Whether to propagate the event via `PointerTraversal`
85 /// For [`Enter`] and [`Leave`] events, this is set to false.
86 pub(crate) propagate: bool,
87}
88
89/// A traversal query (i.e. it implements [`Traversal`]) intended for use with [`Pointer`] events.
90///
91/// Unless shortcircuited out by the [`Pointer`] event itself, this will always traverse to the
92/// parent if the entity being visited has one. Otherwise, it propagates to the pointer's
93/// window and stops there.
94#[derive(QueryData)]
95pub struct PointerTraversal {
96 child_of: Option<&'static ChildOf>,
97 window: Option<&'static Window>,
98}
99
100impl<E> Traversal<Pointer<E>> for PointerTraversal
101where
102 E: Debug + Clone + Reflect,
103{
104 fn traverse(item: Self::Item<'_, '_>, pointer: &Pointer<E>) -> Option<Entity> {
105 if !pointer.propagate {
106 return None;
107 }
108
109 let PointerTraversalItem { child_of, window } = item;
110
111 // Send event to parent, if it has one.
112 if let Some(child_of) = child_of {
113 return Some(child_of.parent());
114 };
115
116 // Otherwise, send it to the window entity (unless this is a window entity).
117 if window.is_none()
118 && let NormalizedRenderTarget::Window(window_ref) = pointer.pointer_location.target
119 {
120 return Some(window_ref.entity());
121 }
122
123 None
124 }
125}
126
127impl<E: Debug + Clone + Reflect> core::fmt::Display for Pointer<E> {
128 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
129 f.write_fmt(format_args!(
130 "{:?}, {:.1?}, {:.1?}",
131 self.pointer_id, self.pointer_location.position, self.event
132 ))
133 }
134}
135
136impl<E: Debug + Clone + Reflect> core::ops::Deref for Pointer<E> {
137 type Target = E;
138
139 fn deref(&self) -> &Self::Target {
140 &self.event
141 }
142}
143
144impl<E: Debug + Clone + Reflect> Pointer<E> {
145 /// Construct a new `Pointer<E>` event that propagates
146 pub fn new(id: PointerId, location: Location, event: E, entity: Entity) -> Self {
147 Self::new_inner(id, location, event, entity, true)
148 }
149
150 /// Construct a new `Pointer<E>` event that does not propagate
151 pub fn new_without_propagate(
152 id: PointerId,
153 location: Location,
154 event: E,
155 entity: Entity,
156 ) -> Self {
157 Self::new_inner(id, location, event, entity, false)
158 }
159
160 fn new_inner(
161 id: PointerId,
162 location: Location,
163 event: E,
164 entity: Entity,
165 propagate: bool,
166 ) -> Self {
167 Self {
168 pointer_id: id,
169 pointer_location: location,
170 event,
171 entity,
172 propagate,
173 }
174 }
175}
176
177/// Fires when a pointer is canceled, and its current interaction state is dropped.
178#[derive(Clone, PartialEq, Debug, Reflect)]
179#[reflect(Clone, PartialEq)]
180pub struct Cancel {
181 /// Information about the picking intersection.
182 pub hit: HitData,
183}
184
185/// Fires when a pointer crosses into the bounds of a [target entity](EntityEvent::event_target).
186/// Unlike [`Enter`], this event bubbles up to all of the
187/// [target entity's](EntityEvent::event_target) ancestors (traversed via the [`ChildOf`] relationship)
188/// without restriction. Refer to [`pointer_events`] for more information on how these events are triggered.
189/// Refer to [`PointerTraversal`] for how [`Pointer`] events are propagated.
190#[derive(Clone, PartialEq, Debug, Reflect)]
191#[reflect(Clone, PartialEq)]
192pub struct Over {
193 /// Information about the picking intersection.
194 pub hit: HitData,
195}
196
197/// Fires when a pointer crosses into the bounds of a [target entity](EntityEvent::event_target).
198/// Unlike [`Over`], this event bubbles up through a subset of the
199/// [target entity's](EntityEvent::event_target) ancestors
200/// (traversed via the [`ChildOf`] relationship).
201///
202/// ### Event Propagation
203/// An ancestor of a [target entity](EntityEvent::event_target) will receive an [`Enter`] event
204/// when the ancestor does not have a direct relation to any entity hovered by the
205/// pointer in the previous frame. For example, for a given pointer:
206///
207/// If the previously hovered entity C has the following entity ancestry: A -> B -> C
208///
209/// And the currently hovered entity E has the following entity ancestry: A -> D -> E
210///
211/// [`Enter`] events would be sent for both E and its direct ancestor D.
212/// An [`Enter`] event would not be sent for A because it is a shared ancestor of both C and E.
213///
214/// Note: An [`Enter`] event may be fired for an ancestor even if the pointer does not enter
215/// within the ancestor's bounds. More concretely, if a child's bounds extend beyond the parent's
216/// and the pointer enters the child's bounds without crossing into the parent's,
217/// two [`Enter`] events are still emitted for both the child and the parent.
218/// This matches the triggering behavior of `mouseenter` events on the web.
219/// To find out whether a pointer is within the target entity's bounds
220/// immediately upon entering, check the value of [`is_in_bounds`](Enter::is_in_bounds).
221///
222/// Refer to [`pointer_events`] for more information on how these events are triggered.
223#[derive(Clone, PartialEq, Debug, Reflect)]
224#[reflect(Clone, PartialEq)]
225pub struct Enter {
226 /// Information about the picking intersection.
227 pub hit: HitData,
228 /// Whether this pointer directly entered into the target entity's bounds at the
229 /// time of the event.
230 /// This may be false if this entity's child's bounds extended beyond the entity and
231 /// the pointer entered within the child's bounds only.
232 pub is_in_bounds: bool,
233}
234
235/// Fires when a pointer crosses out of the bounds of a [target entity](EntityEvent::event_target).
236/// Unlike [`Leave`], this event bubbles up to all of the
237/// [target entity's](EntityEvent::event_target) ancestors (traversed via the [`ChildOf`] relationship)
238/// without restriction. Refer to [`pointer_events`] for more information on how these events are triggered.
239/// Refer to [`PointerTraversal`] for how [`Pointer`] events are propagated.
240#[derive(Clone, PartialEq, Debug, Reflect)]
241#[reflect(Clone, PartialEq)]
242pub struct Out {
243 /// Information about the latest prior picking intersection.
244 pub hit: HitData,
245}
246
247/// Fires when a pointer crosses out of the bounds of a [target entity](EntityEvent::event_target).
248/// Unlike [`Out`], this event bubbles up through a subset of the
249/// [target entity's](EntityEvent::event_target) ancestors
250/// (traversed via the [`ChildOf`] relationship).
251///
252/// ### Event Propagation
253/// An ancestor of a [target entity](EntityEvent::event_target) will receive a [`Leave`] event
254/// when the ancestor does not have a direct relation to any entity hovered by the
255/// pointer in the current frame. For example, for a given pointer:
256///
257/// If the previously hovered entity C has the following entity ancestry: A -> B -> C
258///
259/// And the currently hovered entity E has the following entity ancestry: A -> D -> E
260///
261/// [`Leave`] events would be sent for both C and its direct ancestor B.
262/// A [`Leave`] event would not be sent for A because it is a shared ancestor of both C and E.
263///
264/// Note: A [`Leave`] event may be fired for an ancestor even if the pointer does not leave
265/// the ancestor's bounds. More concretely, if a child's bounds extend beyond the parent's
266/// and the pointer leaves from within those extended bounds,
267/// two [`Leave`] events are still emitted for both the child and the parent.
268/// This matches the triggering behavior of `mouseleave` events on the web.
269/// To find out whether the pointer was within the target entity's bounds
270/// right before leaving, check the value of [`was_in_bounds`](Leave::was_in_bounds).
271///
272/// Refer to [`pointer_events`] for more information on how these events are triggered.
273#[derive(Clone, PartialEq, Debug, Reflect)]
274#[reflect(Clone, PartialEq)]
275pub struct Leave {
276 /// Information about the latest prior picking intersection.
277 pub hit: HitData,
278 /// Whether this pointer directly exited out of the target entity's bounds
279 /// at the time of the event.
280 /// This may be false if this entity's child's bounds extended beyond the entity and
281 /// the pointer exited out of the child's bounds only.
282 pub was_in_bounds: bool,
283}
284
285/// Fires when a pointer button is pressed over the [target entity](EntityEvent::event_target).
286#[derive(Clone, PartialEq, Debug, Reflect)]
287#[reflect(Clone, PartialEq)]
288pub struct Press {
289 /// Pointer button pressed to trigger this event.
290 pub button: PointerButton,
291 /// Information about the picking intersection.
292 pub hit: HitData,
293 /// Number of consecutive presses, starting at `1`.
294 pub count: u8,
295}
296
297/// Fires when a pointer button is released over the [target entity](EntityEvent::event_target).
298#[derive(Clone, PartialEq, Debug, Reflect)]
299#[reflect(Clone, PartialEq)]
300pub struct Release {
301 /// Pointer button lifted to trigger this event.
302 pub button: PointerButton,
303 /// Information about the picking intersection.
304 pub hit: HitData,
305}
306
307/// Fires when a pointer sends a pointer pressed event followed by a pointer released event, with the same
308/// [target entity](EntityEvent::event_target) for both events.
309#[derive(Clone, PartialEq, Debug, Reflect)]
310#[reflect(Clone, PartialEq)]
311pub struct Click {
312 /// Pointer button pressed and lifted to trigger this event.
313 pub button: PointerButton,
314 /// Information about the picking intersection.
315 pub hit: HitData,
316 /// Duration between the pointer pressed and lifted for this click
317 pub duration: Duration,
318 /// Number of consecutive clicks, starting at `1`.
319 pub count: u8,
320}
321
322/// Fires while a pointer is moving over the [target entity](EntityEvent::event_target).
323#[derive(Clone, PartialEq, Debug, Reflect)]
324#[reflect(Clone, PartialEq)]
325pub struct Move {
326 /// Information about the picking intersection.
327 pub hit: HitData,
328 /// The change in position since the last move event.
329 ///
330 /// This is stored in screen pixels, not world coordinates. Screen pixels go from top-left to
331 /// bottom-right, whereas (in 2D) world coordinates go from bottom-left to top-right. Consider
332 /// using methods on [`Camera`](bevy_camera::Camera) to convert from screen-space to
333 /// world-space.
334 pub delta: Vec2,
335}
336
337/// Fires when the [target entity](EntityEvent::event_target) receives a pointer pressed event followed by a pointer move event.
338#[derive(Clone, PartialEq, Debug, Reflect)]
339#[reflect(Clone, PartialEq)]
340pub struct DragStart {
341 /// Pointer button pressed and moved to trigger this event.
342 pub button: PointerButton,
343 /// Information about the picking intersection.
344 pub hit: HitData,
345}
346
347/// Fires while the [target entity](EntityEvent::event_target) is being dragged.
348#[derive(Clone, PartialEq, Debug, Reflect)]
349#[reflect(Clone, PartialEq)]
350pub struct Drag {
351 /// Pointer button pressed and moved to trigger this event.
352 pub button: PointerButton,
353 /// The total distance vector of a drag, measured from drag start to the current position.
354 ///
355 /// This is stored in screen pixels, not world coordinates. Screen pixels go from top-left to
356 /// bottom-right, whereas (in 2D) world coordinates go from bottom-left to top-right. Consider
357 /// using methods on [`Camera`](bevy_camera::Camera) to convert from screen-space to
358 /// world-space.
359 pub distance: Vec2,
360 /// The change in position since the last drag event.
361 ///
362 /// This is stored in screen pixels, not world coordinates. Screen pixels go from top-left to
363 /// bottom-right, whereas (in 2D) world coordinates go from bottom-left to top-right. Consider
364 /// using methods on [`Camera`](bevy_camera::Camera) to convert from screen-space to
365 /// world-space.
366 pub delta: Vec2,
367}
368
369/// Fires when a pointer is dragging the [target entity](EntityEvent::event_target) and a pointer released event is received.
370#[derive(Clone, PartialEq, Debug, Reflect)]
371#[reflect(Clone, PartialEq)]
372pub struct DragEnd {
373 /// Pointer button pressed, moved, and released to trigger this event.
374 pub button: PointerButton,
375 /// The vector of drag movement measured from start to final pointer position.
376 ///
377 /// This is stored in screen pixels, not world coordinates. Screen pixels go from top-left to
378 /// bottom-right, whereas (in 2D) world coordinates go from bottom-left to top-right. Consider
379 /// using methods on [`Camera`](bevy_camera::Camera) to convert from screen-space to
380 /// world-space.
381 pub distance: Vec2,
382}
383
384/// Fires when a pointer dragging the `dragged` entity enters the [target entity](EntityEvent::event_target)
385#[derive(Clone, PartialEq, Debug, Reflect)]
386#[reflect(Clone, PartialEq)]
387pub struct DragEnter {
388 /// Pointer button pressed to enter drag.
389 pub button: PointerButton,
390 /// The entity that was being dragged when the pointer entered the [target entity](EntityEvent::event_target).
391 pub dragged: Entity,
392 /// Information about the picking intersection.
393 pub hit: HitData,
394}
395
396/// Fires while the `dragged` entity is being dragged over the [target entity](EntityEvent::event_target).
397#[derive(Clone, PartialEq, Debug, Reflect)]
398#[reflect(Clone, PartialEq)]
399pub struct DragOver {
400 /// Pointer button pressed while dragging over.
401 pub button: PointerButton,
402 /// The entity that was being dragged when the pointer was over the [target entity](EntityEvent::event_target).
403 pub dragged: Entity,
404 /// Information about the picking intersection.
405 pub hit: HitData,
406}
407
408/// Fires when a pointer dragging the `dragged` entity leaves the [target entity](EntityEvent::event_target).
409#[derive(Clone, PartialEq, Debug, Reflect)]
410#[reflect(Clone, PartialEq)]
411pub struct DragLeave {
412 /// Pointer button pressed while leaving drag.
413 pub button: PointerButton,
414 /// The entity that was being dragged when the pointer left the [target entity](EntityEvent::event_target).
415 pub dragged: Entity,
416 /// Information about the latest prior picking intersection.
417 pub hit: HitData,
418}
419
420/// Fires when a pointer drops the `dropped` entity onto the [target entity](EntityEvent::event_target).
421#[derive(Clone, PartialEq, Debug, Reflect)]
422#[reflect(Clone, PartialEq)]
423pub struct DragDrop {
424 /// Pointer button released to drop.
425 pub button: PointerButton,
426 /// The entity that was dropped onto the [target entity](EntityEvent::event_target).
427 pub dropped: Entity,
428 /// Information about the picking intersection.
429 pub hit: HitData,
430}
431
432/// Dragging state.
433#[derive(Clone, PartialEq, Debug, Reflect)]
434#[reflect(Clone, PartialEq)]
435pub struct DragEntry {
436 /// The position of the pointer at drag start.
437 ///
438 /// This is stored in screen pixels, not world coordinates. Screen pixels go from top-left to
439 /// bottom-right, whereas (in 2D) world coordinates go from bottom-left to top-right. Consider
440 /// using [`Camera::viewport_to_world`](bevy_camera::Camera::viewport_to_world) or
441 /// [`Camera::viewport_to_world_2d`](bevy_camera::Camera::viewport_to_world_2d) to
442 /// convert from screen-space to world-space.
443 pub start_pos: Vec2,
444 /// The latest position of the pointer during this drag, used to compute deltas.
445 ///
446 /// This is stored in screen pixels, not world coordinates. Screen pixels go from top-left to
447 /// bottom-right, whereas (in 2D) world coordinates go from bottom-left to top-right. Consider
448 /// using [`Camera::viewport_to_world`](bevy_camera::Camera::viewport_to_world) or
449 /// [`Camera::viewport_to_world_2d`](bevy_camera::Camera::viewport_to_world_2d) to
450 /// convert from screen-space to world-space.
451 pub latest_pos: Vec2,
452}
453
454/// Fires while a pointer is scrolling over the [target entity](EntityEvent::event_target).
455#[derive(Clone, PartialEq, Debug, Reflect)]
456#[reflect(Clone, PartialEq)]
457pub struct Scroll {
458 /// The mouse scroll unit.
459 pub unit: MouseScrollUnit,
460 /// The horizontal scroll value.
461 pub x: f32,
462 /// The vertical scroll value.
463 pub y: f32,
464 /// Information about the picking intersection.
465 pub hit: HitData,
466 /// Touch phase of the input.
467 ///
468 /// When using a mouse, this will always be [`TouchPhase::Moved`].
469 pub phase: TouchPhase,
470}
471
472/// An entry in the cache that drives the `pointer_events` system, storing additional data
473/// about pointer button presses.
474#[derive(Debug, Clone, Default)]
475pub struct PointerButtonState {
476 /// Stores the press location and start time for each button currently being pressed by the pointer.
477 pub pressing: EntityHashMap<(Location, Instant, HitData)>,
478 /// Stores the latest click time and count for each clicked entity.
479 pub clicking: EntityHashMap<(Instant, u8)>,
480 /// Stores the starting and current locations for each entity currently being dragged by the pointer.
481 pub dragging: EntityHashMap<DragEntry>,
482 /// Stores the hit data for each entity currently being dragged over by the pointer.
483 pub dragging_over: EntityHashMap<HitData>,
484}
485
486impl PointerButtonState {
487 /// Clears all press and drag data tracked for this button on its pointer.
488 pub fn clear(&mut self) {
489 self.pressing.clear();
490 self.dragging.clear();
491 self.dragging_over.clear();
492 }
493}
494
495/// A cache map containing the ancestry of hovered entities
496#[derive(Debug, Clone, Default, Deref, DerefMut)]
497pub struct HoveredEntityAncestors(EntityHashMap<EntityHashSet>);
498
499impl HoveredEntityAncestors {
500 /// Clears self and rebuilds a map of every hovered entity to its ancestors.
501 ///
502 /// This map is used to calculate which entities should receive [`Enter`] or [`Leave`] events.
503 pub fn rebuild(
504 &mut self,
505 hover_map: &HoverMap,
506 pointer_state: &PointerState,
507 ancestors_query: &Query<&ChildOf>,
508 ) {
509 self.clear();
510 for hovered_entity in hover_map
511 .iter()
512 .flat_map(|(_, hashmap)| hashmap.iter().map(|data| *data.0))
513 {
514 // If the ancestors were already added into the map, do not re-fetch
515 if self.contains_key(&hovered_entity) {
516 continue;
517 }
518 // If the ancestors were previously fetched, just re-use the entry.
519 if let Some(previous_entry) =
520 pointer_state.hovered_entity_ancestors.get(&hovered_entity)
521 {
522 self.insert(hovered_entity, previous_entry.clone());
523 } else {
524 let mut ancestors = EntityHashSet::new();
525 for member in ancestors_query.iter_ancestors(hovered_entity) {
526 ancestors.insert(member);
527 }
528 self.insert(hovered_entity, ancestors);
529 }
530 }
531 }
532
533 /// Returns a new combined `HashSet` of ancestors for the provided `hover_entities`
534 pub fn get_ancestors_union(&self, hover_entities: &EntityHashSet) -> EntityHashSet {
535 hover_entities
536 .iter()
537 .flat_map(|entity| self.get(entity))
538 .flat_map(|set| set.iter().copied())
539 .collect::<EntityHashSet>()
540 }
541
542 /// Returns the ancestors for the provided `hover_entity`, if it has been created
543 pub fn get_ancestors(&self, hover_entity: &Entity) -> Option<&EntityHashSet> {
544 self.get(hover_entity)
545 }
546}
547
548/// State for all pointers.
549#[derive(Debug, Clone, Default, Resource)]
550pub struct PointerState {
551 /// Pressing and dragging state, organized by pointer and button.
552 pub pointer_buttons: HashMap<(PointerId, PointerButton), PointerButtonState>,
553 /// A cache map providing the set of an entity's ancestors for a given hovered entity.
554 pub hovered_entity_ancestors: HoveredEntityAncestors,
555}
556
557impl PointerState {
558 /// Retrieves the current state for a specific pointer and button, if it has been created.
559 pub fn get(&self, pointer_id: PointerId, button: PointerButton) -> Option<&PointerButtonState> {
560 self.pointer_buttons.get(&(pointer_id, button))
561 }
562
563 /// Provides write access to the state of a pointer and button, creating it if it does not yet exist.
564 pub fn get_mut(
565 &mut self,
566 pointer_id: PointerId,
567 button: PointerButton,
568 ) -> &mut PointerButtonState {
569 self.pointer_buttons
570 .entry((pointer_id, button))
571 .or_default()
572 }
573
574 /// Retrieves the ancestors for a given hovered entity
575 pub fn get_ancestors(&self, hovered_entity: &Entity) -> Option<&EntityHashSet> {
576 self.hovered_entity_ancestors.get_ancestors(hovered_entity)
577 }
578
579 /// Retrieves the union of ancestors for the given hovered entities
580 pub fn get_ancestors_union(&self, hovered_entities: &EntityHashSet) -> EntityHashSet {
581 self.hovered_entity_ancestors
582 .get_ancestors_union(hovered_entities)
583 }
584
585 /// Clears all the data associated with all of the buttons on a pointer. Does not free the underlying memory.
586 pub fn clear(&mut self, pointer_id: PointerId) {
587 for button in PointerButton::iter() {
588 if let Some(state) = self.pointer_buttons.get_mut(&(pointer_id, button)) {
589 state.clear();
590 }
591 }
592 }
593}
594
595/// A helper system param for accessing the picking event writers.
596#[derive(SystemParam)]
597pub struct PickingMessageWriters<'w> {
598 cancel_events: MessageWriter<'w, Pointer<Cancel>>,
599 click_events: MessageWriter<'w, Pointer<Click>>,
600 pressed_events: MessageWriter<'w, Pointer<Press>>,
601 drag_drop_events: MessageWriter<'w, Pointer<DragDrop>>,
602 drag_end_events: MessageWriter<'w, Pointer<DragEnd>>,
603 drag_enter_events: MessageWriter<'w, Pointer<DragEnter>>,
604 drag_events: MessageWriter<'w, Pointer<Drag>>,
605 drag_leave_events: MessageWriter<'w, Pointer<DragLeave>>,
606 drag_over_events: MessageWriter<'w, Pointer<DragOver>>,
607 drag_start_events: MessageWriter<'w, Pointer<DragStart>>,
608 scroll_events: MessageWriter<'w, Pointer<Scroll>>,
609 move_events: MessageWriter<'w, Pointer<Move>>,
610 out_events: MessageWriter<'w, Pointer<Out>>,
611 over_events: MessageWriter<'w, Pointer<Over>>,
612 leave_events: MessageWriter<'w, Pointer<Leave>>,
613 enter_events: MessageWriter<'w, Pointer<Enter>>,
614 released_events: MessageWriter<'w, Pointer<Release>>,
615}
616
617/// Dispatches interaction events to the target entities.
618///
619/// Within a single frame, events are dispatched in the following order:
620/// + [`Out`] → [`Leave`] → [`DragLeave`].
621/// + [`DragEnter`] → [`Enter`] → [`Over`].
622/// + Any number of any of the following:
623/// + For each movement: [`DragStart`] → [`Drag`] → [`DragOver`] → [`Move`].
624/// + For each button press: [`Press`] or [`Click`] → [`Release`] → [`DragDrop`] → [`DragEnd`] → [`DragLeave`].
625/// + For each pointer cancellation: [`Cancel`].
626///
627/// Additionally, across multiple frames, the following are also strictly
628/// ordered by the interaction state machine:
629/// + When a pointer moves over the target:
630/// [`Over`], [`Enter`], [`Move`], [`Leave`], [`Out`].
631/// + When a pointer presses buttons on the target:
632/// [`Press`], [`Click`], [`Release`].
633/// + When a pointer drags the target:
634/// [`DragStart`], [`Drag`], [`DragEnd`].
635/// + When a pointer drags something over the target:
636/// [`DragEnter`], [`DragOver`], [`DragDrop`], [`DragLeave`].
637/// + When a pointer is canceled:
638/// No other events will follow the [`Cancel`] event for that pointer.
639///
640/// Four events -- [`Over`], [`Enter`], [`Leave`] and [`Out`] -- are driven only by the [`HoverMap`].
641/// The rest rely on additional data from the [`PointerInput`] event stream. To
642/// receive these events for a custom pointer, you must add [`PointerInput`]
643/// events.
644///
645/// When the pointer goes from hovering entity A to entity B, entity A will
646/// receive [`Out`] and [`Enter`] and then entity B will receive [`Leave`] and [`Over`].
647/// No entity will ever receive both an [`Over`] and an [`Out`] or
648/// an [`Enter`] and a [`Leave`] event during the same frame.
649///
650/// When we account for event bubbling, the two pairs of events,
651/// [`Out`] [`Over`] and [`Enter`] [`Leave`], behave differently. When the hovering focus shifts
652/// between children, parent entities may receive redundant [`Out`] → [`Over`] pairs. In
653/// the case of [`Enter`] → [`Leave`], shared parent entities will not receive [`Enter`]
654/// or [`Leave`].
655///
656/// Both [`Click`] and [`Release`] target the entity hovered in the *previous frame*,
657/// rather than the current frame. This is because touch pointers hover nothing
658/// on the frame they are released. The end effect is that these two events can
659/// be received sequentially after an [`Out`] event (but always on the same frame
660/// as the [`Out`] event).
661///
662/// Note: Though it is common for the [`PointerInput`] stream may contain
663/// multiple pointer movements and presses each frame, the hover state is
664/// determined only by the pointer's *final position*. Since the hover state
665/// ultimately determines which entities receive events, this may mean that an
666/// entity can receive events from before or after it was actually hovered.
667pub fn pointer_events(
668 // Input
669 mut input_events: MessageReader<PointerInput>,
670 // ECS State
671 pointers: Query<&PointerLocation>,
672 ancestors_query: Query<&ChildOf>,
673 pointer_map: Res<PointerMap>,
674 hover_map: Res<HoverMap>,
675 previous_hover_map: Res<PreviousHoverMap>,
676 picking_settings: Res<PickingSettings>,
677 mut pointer_state: ResMut<PointerState>,
678 mut hovered_entity_ancestors: Local<HoveredEntityAncestors>,
679 mut sent_leave: Local<HashSet<(PointerId, Entity)>>,
680 mut sent_enter: Local<HashSet<(PointerId, Entity)>>,
681 // Output
682 mut commands: Commands,
683 mut message_writers: PickingMessageWriters,
684) {
685 // Setup utilities
686 let now = Instant::now();
687 let pointer_location = |pointer_id: PointerId| {
688 pointer_map
689 .get_entity(pointer_id)
690 .and_then(|entity| pointers.get(entity).ok())
691 .and_then(|pointer| pointer.location.clone())
692 };
693 hovered_entity_ancestors.rebuild(&hover_map, &pointer_state, &ancestors_query);
694 sent_leave.clear();
695 sent_enter.clear();
696
697 // If the entity was hovered by a specific pointer last frame...
698 for (pointer_id, hovered_entity, hit) in previous_hover_map
699 .iter()
700 .flat_map(|(id, hashmap)| hashmap.iter().map(|data| (*id, *data.0, data.1.clone())))
701 {
702 // ...but is now not being hovered by that same pointer...
703 if !hover_map
704 .get(&pointer_id)
705 .iter()
706 .any(|e| e.contains_key(&hovered_entity))
707 {
708 let Some(location) = pointer_location(pointer_id) else {
709 debug!(
710 "Unable to get location for pointer {:?} during pointer out",
711 pointer_id
712 );
713 continue;
714 };
715
716 // Always send Out events
717 let out_event = Pointer::new(
718 pointer_id,
719 location.clone(),
720 Out { hit: hit.clone() },
721 hovered_entity,
722 );
723 commands.trigger(out_event.clone());
724 message_writers.out_events.write(out_event);
725
726 // Potentially send `Leave` events to the entity and all of its ancestors
727 let mut entities_to_send_leave =
728 pointer_state.get_ancestors(&hovered_entity).map_or_else(
729 || {
730 ancestors_query
731 .iter_ancestors(hovered_entity)
732 .collect::<EntityHashSet>()
733 },
734 Clone::clone,
735 );
736 entities_to_send_leave.insert(hovered_entity);
737 // Ensure we do not double send to any other entities that have already been sent to during this loop
738 entities_to_send_leave.retain(|entity| !sent_leave.contains(&(pointer_id, *entity)));
739 if !entities_to_send_leave.is_empty() {
740 // Fetch the currently hovered entities and their ancestors
741 let new_hovered_entities = get_hovered_entities(&hover_map, &pointer_id);
742 let new_hovered_ancestors =
743 hovered_entity_ancestors.get_ancestors_union(&new_hovered_entities);
744 let union = new_hovered_entities
745 .union(&new_hovered_ancestors)
746 .copied()
747 .collect::<EntityHashSet>();
748 // Keep entities and ancestors that are not going to continue to be hovered over
749 entities_to_send_leave.retain(|entity| !union.contains(entity));
750 // Send `Leave` events for those entities.
751 // Note that `Leave` events send without propagation; we manually calculated
752 // which ancestors should receive one.
753 for leave_event in entities_to_send_leave.iter().map(|entity| {
754 Pointer::new_without_propagate(
755 pointer_id,
756 location.clone(),
757 Leave {
758 hit: hit.clone(),
759 was_in_bounds: is_directly_hovered(
760 &previous_hover_map.0,
761 &pointer_id,
762 entity,
763 ),
764 },
765 *entity,
766 )
767 }) {
768 let entity = leave_event.entity;
769 commands.trigger(leave_event.clone());
770 message_writers.leave_events.write(leave_event);
771 sent_leave.insert((pointer_id, entity));
772 }
773 }
774
775 // Possibly send DragLeave events
776 for button in PointerButton::iter() {
777 let state = pointer_state.get_mut(pointer_id, button);
778 state.dragging_over.remove(&hovered_entity);
779 for drag_target in state.dragging.keys() {
780 let drag_leave_event = Pointer::new(
781 pointer_id,
782 location.clone(),
783 DragLeave {
784 button,
785 dragged: *drag_target,
786 hit: hit.clone(),
787 },
788 hovered_entity,
789 );
790 commands.trigger(drag_leave_event.clone());
791 message_writers.drag_leave_events.write(drag_leave_event);
792 }
793 }
794 }
795 }
796
797 // Iterate all currently hovered entities for each pointer
798 for (pointer_id, hovered_entity, hit) in hover_map
799 .iter()
800 .flat_map(|(id, hashmap)| hashmap.iter().map(|data| (*id, *data.0, data.1.clone())))
801 {
802 // Continue if the pointer does not have a valid location.
803 let Some(location) = pointer_location(pointer_id) else {
804 debug!(
805 "Unable to get location for pointer {:?} during pointer over",
806 pointer_id
807 );
808 continue;
809 };
810
811 // For each button update its `dragging_over` state and possibly emit DragEnter events.
812 for button in PointerButton::iter() {
813 let state = pointer_state.get_mut(pointer_id, button);
814
815 // Only update the `dragging_over` state if there is at least one entity being dragged.
816 // Only emit DragEnter events for this `hovered_entity`, if it had no previous `dragging_over` state.
817 if !state.dragging.is_empty()
818 && state
819 .dragging_over
820 .insert(hovered_entity, hit.clone())
821 .is_none()
822 {
823 for drag_target in state.dragging.keys() {
824 let drag_enter_event = Pointer::new(
825 pointer_id,
826 location.clone(),
827 DragEnter {
828 button,
829 dragged: *drag_target,
830 hit: hit.clone(),
831 },
832 hovered_entity,
833 );
834 commands.trigger(drag_enter_event.clone());
835 message_writers.drag_enter_events.write(drag_enter_event);
836 }
837 }
838 }
839
840 // If the `hovered_entity` was not hovered by the same pointer the previous frame...
841 if !previous_hover_map
842 .get(&pointer_id)
843 .iter()
844 .any(|e| e.contains_key(&hovered_entity))
845 {
846 // Potentially send `Enter` events to the entity and all of its ancestors
847 let mut entities_to_send_enter = hovered_entity_ancestors
848 .get_ancestors(&hovered_entity)
849 .map_or_else(
850 || {
851 ancestors_query
852 .iter_ancestors(hovered_entity)
853 .collect::<EntityHashSet>()
854 },
855 Clone::clone,
856 );
857 entities_to_send_enter.insert(hovered_entity);
858 // Ensure we do not double send to any other entities that have already been sent to during this loop
859 entities_to_send_enter
860 .retain(|entity: &Entity| !sent_enter.contains(&(pointer_id, *entity)));
861 if !entities_to_send_enter.is_empty() {
862 // Fetch the previously hovered entities and their ancestors
863 let prev_hovered_entities = get_hovered_entities(&previous_hover_map, &pointer_id);
864 let prev_hovered_ancestors =
865 pointer_state.get_ancestors_union(&prev_hovered_entities);
866 let union = prev_hovered_entities
867 .union(&prev_hovered_ancestors)
868 .copied()
869 .collect::<EntityHashSet>();
870 // Keep entities and ancestors that were not hovered over previously
871 entities_to_send_enter.retain(|entity| !union.contains(entity));
872 // Send `Enter` events for those entities.
873 // Note that `Enter` events send without propagation; we manually calculated
874 // which ancestors should receive one.
875 for enter_event in entities_to_send_enter.iter().map(|entity| {
876 Pointer::new_without_propagate(
877 pointer_id,
878 location.clone(),
879 Enter {
880 hit: hit.clone(),
881 is_in_bounds: is_directly_hovered(&hover_map.0, &pointer_id, entity),
882 },
883 *entity,
884 )
885 }) {
886 let entity = enter_event.entity;
887 commands.trigger(enter_event.clone());
888 message_writers.enter_events.write(enter_event);
889 sent_enter.insert((pointer_id, entity));
890 }
891 }
892
893 // Always send Over events
894 let over_event = Pointer::new(
895 pointer_id,
896 location.clone(),
897 Over { hit: hit.clone() },
898 hovered_entity,
899 );
900 commands.trigger(over_event.clone());
901 message_writers.over_events.write(over_event);
902 }
903 }
904
905 // Update pointer_state with the current hovered entity ancestors
906 // We swap with the Local SystemParam's map, which will be rebuilt
907 // on the next invocation of `pointer_events`
908 core::mem::swap(
909 &mut hovered_entity_ancestors.0,
910 &mut pointer_state.hovered_entity_ancestors,
911 );
912
913 // Dispatch input events...
914 for PointerInput {
915 pointer_id,
916 location,
917 action,
918 } in input_events.read().cloned()
919 {
920 match action {
921 PointerAction::Press(button) => {
922 let state = pointer_state.get_mut(pointer_id, button);
923 state.clicking.retain(|_, (last_click, _)| {
924 now - *last_click <= picking_settings.multi_click_interval
925 });
926
927 // If it's a press, emit a Pressed event and mark the hovered entities as pressed
928 for (hovered_entity, hit) in hover_map
929 .get(&pointer_id)
930 .iter()
931 .flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.clone())))
932 {
933 let count = state
934 .clicking
935 .get(&hovered_entity)
936 .map_or(1, |(_, count)| count.saturating_add(1));
937 state.clicking.insert(hovered_entity, (now, count));
938 let pressed_event = Pointer::new(
939 pointer_id,
940 location.clone(),
941 Press {
942 button,
943 hit: hit.clone(),
944 count,
945 },
946 hovered_entity,
947 );
948 commands.trigger(pressed_event.clone());
949 message_writers.pressed_events.write(pressed_event);
950 // Also insert the press into the state
951 state
952 .pressing
953 .insert(hovered_entity, (location.clone(), now, hit));
954 }
955 }
956 PointerAction::Release(button) => {
957 let state = pointer_state.get_mut(pointer_id, button);
958 state.clicking.retain(|_, (last_click, _)| {
959 now - *last_click <= picking_settings.multi_click_interval
960 });
961
962 // Emit Click and Release events on all the previously hovered entities.
963 for (hovered_entity, hit) in previous_hover_map
964 .get(&pointer_id)
965 .iter()
966 .flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.clone())))
967 {
968 // If this pointer previously pressed the hovered entity, emit a Click event
969 if let Some((_, press_instant, _)) = state.pressing.get(&hovered_entity) {
970 let count = state
971 .clicking
972 .get(&hovered_entity)
973 .map_or(1, |(_, count)| *count);
974 state.clicking.insert(hovered_entity, (now, count));
975 let click_event = Pointer::new(
976 pointer_id,
977 location.clone(),
978 Click {
979 button,
980 hit: hit.clone(),
981 duration: now - *press_instant,
982 count,
983 },
984 hovered_entity,
985 );
986 commands.trigger(click_event.clone());
987 message_writers.click_events.write(click_event);
988 }
989 // Always send the Release event
990 let released_event = Pointer::new(
991 pointer_id,
992 location.clone(),
993 Release {
994 button,
995 hit: hit.clone(),
996 },
997 hovered_entity,
998 );
999 commands.trigger(released_event.clone());
1000 message_writers.released_events.write(released_event);
1001 }
1002
1003 // Then emit the drop events.
1004 for (drag_target, drag) in state.dragging.drain() {
1005 // Emit DragDrop
1006 for (dragged_over, hit) in state.dragging_over.iter() {
1007 let drag_drop_event = Pointer::new(
1008 pointer_id,
1009 location.clone(),
1010 DragDrop {
1011 button,
1012 dropped: drag_target,
1013 hit: hit.clone(),
1014 },
1015 *dragged_over,
1016 );
1017 commands.trigger(drag_drop_event.clone());
1018 message_writers.drag_drop_events.write(drag_drop_event);
1019 }
1020 // Emit DragEnd
1021 let drag_end_event = Pointer::new(
1022 pointer_id,
1023 location.clone(),
1024 DragEnd {
1025 button,
1026 distance: drag.latest_pos - drag.start_pos,
1027 },
1028 drag_target,
1029 );
1030 commands.trigger(drag_end_event.clone());
1031 message_writers.drag_end_events.write(drag_end_event);
1032 // Emit DragLeave
1033 for (dragged_over, hit) in state.dragging_over.iter() {
1034 let drag_leave_event = Pointer::new(
1035 pointer_id,
1036 location.clone(),
1037 DragLeave {
1038 button,
1039 dragged: drag_target,
1040 hit: hit.clone(),
1041 },
1042 *dragged_over,
1043 );
1044 commands.trigger(drag_leave_event.clone());
1045 message_writers.drag_leave_events.write(drag_leave_event);
1046 }
1047 }
1048
1049 // Finally, we can clear the state of everything relating to presses or drags.
1050 state.clear();
1051 }
1052 // Moved
1053 PointerAction::Move { delta } => {
1054 if delta == Vec2::ZERO {
1055 continue; // If delta is zero, the following events will not be triggered.
1056 }
1057 // Triggers during movement even if not over an entity
1058 for button in PointerButton::iter() {
1059 let state = pointer_state.get_mut(pointer_id, button);
1060
1061 // Emit DragEntry and DragStart the first time we move while pressing an entity
1062 for (press_target, (location, _, hit)) in state.pressing.iter() {
1063 if state.dragging.contains_key(press_target) {
1064 continue; // This entity is already logged as being dragged
1065 }
1066 state.dragging.insert(
1067 *press_target,
1068 DragEntry {
1069 start_pos: location.position,
1070 latest_pos: location.position,
1071 },
1072 );
1073 let drag_start_event = Pointer::new(
1074 pointer_id,
1075 location.clone(),
1076 DragStart {
1077 button,
1078 hit: hit.clone(),
1079 },
1080 *press_target,
1081 );
1082
1083 commands.trigger(drag_start_event.clone());
1084 message_writers.drag_start_events.write(drag_start_event);
1085
1086 // Insert dragging over state and emit DragEnter for hovered entities.
1087 for (hovered_entity, hit) in hover_map
1088 .get(&pointer_id)
1089 .iter()
1090 .flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.to_owned())))
1091 .filter(|(hovered_entity, _)| *hovered_entity != *press_target)
1092 {
1093 // Inserting the `dragging_over` state here ensures the `DragEnter` event won't be dispatched twice.
1094 state.dragging_over.insert(hovered_entity, hit.clone());
1095 let drag_enter_event = Pointer::new(
1096 pointer_id,
1097 location.clone(),
1098 DragEnter {
1099 button,
1100 dragged: *press_target,
1101 hit: hit.clone(),
1102 },
1103 hovered_entity,
1104 );
1105 commands.trigger(drag_enter_event.clone());
1106 message_writers.drag_enter_events.write(drag_enter_event);
1107 }
1108 }
1109
1110 // Emit Drag events to the entities we are dragging
1111 for (drag_target, drag) in state.dragging.iter_mut() {
1112 let delta = location.position - drag.latest_pos;
1113 if delta == Vec2::ZERO {
1114 continue; // No need to emit a Drag event if there is no movement
1115 }
1116 let drag_event = Pointer::new(
1117 pointer_id,
1118 location.clone(),
1119 Drag {
1120 button,
1121 distance: location.position - drag.start_pos,
1122 delta,
1123 },
1124 *drag_target,
1125 );
1126 commands.trigger(drag_event.clone());
1127 message_writers.drag_events.write(drag_event);
1128
1129 // Update drag position
1130 drag.latest_pos = location.position;
1131
1132 // Emit corresponding DragOver to the hovered entities
1133 for (hovered_entity, hit) in hover_map
1134 .get(&pointer_id)
1135 .iter()
1136 .flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.to_owned())))
1137 .filter(|(hovered_entity, _)| *hovered_entity != *drag_target)
1138 {
1139 let drag_over_event = Pointer::new(
1140 pointer_id,
1141 location.clone(),
1142 DragOver {
1143 button,
1144 dragged: *drag_target,
1145 hit: hit.clone(),
1146 },
1147 hovered_entity,
1148 );
1149 commands.trigger(drag_over_event.clone());
1150 message_writers.drag_over_events.write(drag_over_event);
1151 }
1152 }
1153 }
1154
1155 for (hovered_entity, hit) in hover_map
1156 .get(&pointer_id)
1157 .iter()
1158 .flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.to_owned())))
1159 {
1160 // Emit Move events to the entities we are hovering
1161 let move_event = Pointer::new(
1162 pointer_id,
1163 location.clone(),
1164 Move {
1165 hit: hit.clone(),
1166 delta,
1167 },
1168 hovered_entity,
1169 );
1170 commands.trigger(move_event.clone());
1171 message_writers.move_events.write(move_event);
1172 }
1173 }
1174 PointerAction::Scroll { x, y, unit, phase } => {
1175 for (hovered_entity, hit) in hover_map
1176 .get(&pointer_id)
1177 .iter()
1178 .flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.clone())))
1179 {
1180 // Emit Scroll events to the entities we are hovering
1181 let scroll_event = Pointer::new(
1182 pointer_id,
1183 location.clone(),
1184 Scroll {
1185 unit,
1186 x,
1187 y,
1188 hit: hit.clone(),
1189 phase,
1190 },
1191 hovered_entity,
1192 );
1193 commands.trigger(scroll_event.clone());
1194 message_writers.scroll_events.write(scroll_event);
1195 }
1196 }
1197 // Canceled
1198 PointerAction::Cancel => {
1199 // Emit a Cancel to the hovered entity.
1200 for (hovered_entity, hit) in hover_map
1201 .get(&pointer_id)
1202 .iter()
1203 .flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.to_owned())))
1204 {
1205 let cancel_event =
1206 Pointer::new(pointer_id, location.clone(), Cancel { hit }, hovered_entity);
1207 commands.trigger(cancel_event.clone());
1208 message_writers.cancel_events.write(cancel_event);
1209 }
1210 // Clear the state for the canceled pointer
1211 pointer_state.clear(pointer_id);
1212 }
1213 }
1214 }
1215}
1216
1217#[cfg(test)]
1218mod tests {
1219 use bevy_app::App;
1220 use bevy_camera::{Camera, ManualTextureViewHandle};
1221
1222 use crate::pointer::update_pointer_map;
1223
1224 use super::*;
1225
1226 const POINTER_ID: PointerId = PointerId::Mouse;
1227 const STUB_LOCATION: Location = Location {
1228 target: NormalizedRenderTarget::TextureView(ManualTextureViewHandle(5)),
1229 position: Vec2::new(3., 4.),
1230 };
1231
1232 fn initialize_app_for_test(app: &mut App) {
1233 // Init all the resources and messages necessary to run `pointer_events`
1234 app.init_resource::<HoverMap>()
1235 .init_resource::<PreviousHoverMap>()
1236 .init_resource::<PickingSettings>()
1237 .init_resource::<PointerState>()
1238 .add_message::<PointerInput>()
1239 .add_message::<Pointer<Cancel>>()
1240 .add_message::<Pointer<Click>>()
1241 .add_message::<Pointer<Press>>()
1242 .add_message::<Pointer<DragDrop>>()
1243 .add_message::<Pointer<DragEnd>>()
1244 .add_message::<Pointer<DragEnter>>()
1245 .add_message::<Pointer<Drag>>()
1246 .add_message::<Pointer<DragLeave>>()
1247 .add_message::<Pointer<DragOver>>()
1248 .add_message::<Pointer<DragStart>>()
1249 .add_message::<Pointer<Scroll>>()
1250 .add_message::<Pointer<Move>>()
1251 .add_message::<Pointer<Out>>()
1252 .add_message::<Pointer<Over>>()
1253 .add_message::<Pointer<Leave>>()
1254 .add_message::<Pointer<Enter>>()
1255 .add_message::<Pointer<Release>>();
1256
1257 // Initialize the pointer map resource manually with a stub location for the mouse
1258 app.world_mut()
1259 .spawn((POINTER_ID, PointerLocation::new(STUB_LOCATION)));
1260 app.world_mut().insert_resource(PointerMap::default());
1261 assert!(app
1262 .world_mut()
1263 .run_system_cached(update_pointer_map)
1264 .is_ok());
1265 }
1266
1267 fn update_hover_map_with_hovered_entities(app: &mut App, camera: Entity, entities: &[Entity]) {
1268 let mut hover_map = HoverMap::default();
1269 let mut entity_map = EntityHashMap::with_capacity(entities.len());
1270 for entity in entities {
1271 entity_map.insert(
1272 *entity,
1273 HitData {
1274 depth: 0.0,
1275 camera,
1276 position: None,
1277 normal: None,
1278 extra: None,
1279 },
1280 );
1281 }
1282 hover_map.insert(PointerId::Mouse, entity_map);
1283
1284 let previous_hover_map = app.world().resource::<HoverMap>().0.clone();
1285 app.world_mut()
1286 .insert_resource(PreviousHoverMap(previous_hover_map));
1287 app.world_mut().insert_resource(hover_map);
1288 }
1289
1290 #[test]
1291 fn enter_leave_events() {
1292 // the bool distinguishes between different *_in_bounds bool vals
1293 #[derive(Resource, Default)]
1294 struct EnterEventCounts(HashMap<(Entity, bool), usize>);
1295
1296 #[derive(Resource, Default)]
1297 struct LeaveEventCounts(HashMap<(Entity, bool), usize>);
1298
1299 fn observe_enter(event: On<Pointer<Enter>>, mut counts: ResMut<EnterEventCounts>) {
1300 *counts
1301 .0
1302 .entry((event.entity, event.event().is_in_bounds))
1303 .or_insert(0_usize) += 1;
1304 }
1305
1306 fn observe_leave(event: On<Pointer<Leave>>, mut counts: ResMut<LeaveEventCounts>) {
1307 *counts
1308 .0
1309 .entry((event.entity, event.event().was_in_bounds))
1310 .or_insert(0_usize) += 1;
1311 }
1312
1313 fn assert_msg_event_counts(app: &App, enter_count: usize, leave_count: usize) {
1314 let enter_messages = app.world().resource::<Messages<Pointer<Enter>>>();
1315 let leave_messages = app.world().resource::<Messages<Pointer<Leave>>>();
1316 assert_eq!(enter_messages.len(), enter_count);
1317 assert_eq!(leave_messages.len(), leave_count);
1318 }
1319
1320 fn assert_observer_event_counts(
1321 app: &App,
1322 entity: Entity,
1323 enter_in_bounds_counts: usize,
1324 enter_out_of_bounds_counts: usize,
1325 leave_in_bounds_counts: usize,
1326 leave_out_of_bounds_counts: usize,
1327 ) {
1328 assert_eq!(
1329 *app.world()
1330 .resource::<EnterEventCounts>()
1331 .0
1332 .get(&(entity, true))
1333 .unwrap_or(&0),
1334 enter_in_bounds_counts
1335 );
1336 assert_eq!(
1337 *app.world()
1338 .resource::<EnterEventCounts>()
1339 .0
1340 .get(&(entity, false))
1341 .unwrap_or(&0),
1342 enter_out_of_bounds_counts
1343 );
1344 assert_eq!(
1345 *app.world()
1346 .resource::<LeaveEventCounts>()
1347 .0
1348 .get(&(entity, true))
1349 .unwrap_or(&0),
1350 leave_in_bounds_counts
1351 );
1352 assert_eq!(
1353 *app.world()
1354 .resource::<LeaveEventCounts>()
1355 .0
1356 .get(&(entity, false))
1357 .unwrap_or(&0),
1358 leave_out_of_bounds_counts
1359 );
1360 }
1361
1362 let mut app = App::new();
1363 initialize_app_for_test(&mut app);
1364 app.init_resource::<EnterEventCounts>()
1365 .init_resource::<LeaveEventCounts>();
1366 let enter_messages = app.world().resource::<Messages<Pointer<Enter>>>();
1367 let leave_messages = app.world().resource::<Messages<Pointer<Leave>>>();
1368 assert_eq!(enter_messages.len(), 0);
1369 assert_eq!(leave_messages.len(), 0);
1370 // Setup test entities
1371 let camera = app.world_mut().spawn(Camera::default()).id();
1372 let child_one = app
1373 .world_mut()
1374 .spawn_empty()
1375 .observe(observe_enter)
1376 .observe(observe_leave)
1377 .id();
1378 let child_two = app
1379 .world_mut()
1380 .spawn_empty()
1381 .observe(observe_enter)
1382 .observe(observe_leave)
1383 .id();
1384 let parent = app
1385 .world_mut()
1386 .spawn_empty()
1387 .add_children(&[child_one, child_two])
1388 .observe(observe_enter)
1389 .observe(observe_leave)
1390 .id();
1391
1392 // FIRST: child_one is hovered over
1393 update_hover_map_with_hovered_entities(&mut app, camera, &[child_one]);
1394
1395 assert!(app.world_mut().run_system_cached(pointer_events).is_ok());
1396
1397 // child_one received an in_bounds `Enter` event
1398 // The parent received an indirect `Enter` event because its child was hovered into
1399 assert_msg_event_counts(&app, 2, 0);
1400 assert_observer_event_counts(&app, parent, 0, 1, 0, 0);
1401 assert_observer_event_counts(&app, child_one, 1, 0, 0, 0);
1402 assert_observer_event_counts(&app, child_two, 0, 0, 0, 0);
1403 app.world_mut().increment_change_tick();
1404 // ---
1405
1406 // SECOND: child_one is hovered out of, child_two and parent are directly hovered over
1407 update_hover_map_with_hovered_entities(&mut app, camera, &[child_two, parent]);
1408
1409 assert!(app.world_mut().run_system_cached(pointer_events).is_ok());
1410
1411 // child_one received an in_bounds `Leave` event.
1412 // child_two received an in_bounds `Enter` event.
1413 // The parent did not receive any events because it is a shared ancestor
1414 assert_msg_event_counts(&app, 3, 1);
1415 assert_observer_event_counts(&app, parent, 0, 1, 0, 0);
1416 assert_observer_event_counts(&app, child_one, 1, 0, 1, 0);
1417 assert_observer_event_counts(&app, child_two, 1, 0, 0, 0);
1418 app.world_mut().increment_change_tick();
1419 // ---
1420
1421 // THIRD: child_two is hovered out of, parent is still hovered
1422 update_hover_map_with_hovered_entities(&mut app, camera, &[parent]);
1423
1424 assert!(app.world_mut().run_system_cached(pointer_events).is_ok());
1425
1426 // child_two received an in_bounds `Leave` event.
1427 assert_msg_event_counts(&app, 3, 2);
1428 assert_observer_event_counts(&app, parent, 0, 1, 0, 0);
1429 assert_observer_event_counts(&app, child_one, 1, 0, 1, 0);
1430 assert_observer_event_counts(&app, child_two, 1, 0, 1, 0);
1431 app.world_mut().increment_change_tick();
1432 // ---
1433
1434 // FOURTH: child_two is hovered back into, parent is no longer directly hovered
1435 update_hover_map_with_hovered_entities(&mut app, camera, &[child_two]);
1436
1437 assert!(app.world_mut().run_system_cached(pointer_events).is_ok());
1438
1439 // child_two received an in_bounds `Enter` event
1440 // The parent did not receive an `Leave` event because its child is still hovered
1441 assert_msg_event_counts(&app, 4, 2);
1442 assert_observer_event_counts(&app, parent, 0, 1, 0, 0);
1443 assert_observer_event_counts(&app, child_one, 1, 0, 1, 0);
1444 assert_observer_event_counts(&app, child_two, 2, 0, 1, 0);
1445 app.world_mut().increment_change_tick();
1446 // ---
1447
1448 // FIFTH: child_two is hovered out of
1449 update_hover_map_with_hovered_entities(&mut app, camera, &[]);
1450
1451 assert!(app.world_mut().run_system_cached(pointer_events).is_ok());
1452
1453 // child_two received one in_bounds `Leave` event
1454 // The parent received one indirect `Leave` event because the pointer is no longer hovering
1455 // any of its children
1456 assert_msg_event_counts(&app, 4, 4);
1457 assert_observer_event_counts(&app, parent, 0, 1, 0, 1);
1458 assert_observer_event_counts(&app, child_one, 1, 0, 1, 0);
1459 assert_observer_event_counts(&app, child_two, 2, 0, 2, 0);
1460 app.world_mut().increment_change_tick();
1461 // ---
1462
1463 // FINAL: parent and child_one are directly hovered into
1464 update_hover_map_with_hovered_entities(&mut app, camera, &[parent, child_one]);
1465
1466 assert!(app.world_mut().run_system_cached(pointer_events).is_ok());
1467
1468 // The parent received one in_bounds `Enter` event
1469 // child_one received one in_bounds `Enter` event
1470 assert_msg_event_counts(&app, 6, 4);
1471 assert_observer_event_counts(&app, parent, 1, 1, 0, 1);
1472 assert_observer_event_counts(&app, child_one, 2, 0, 1, 0);
1473 assert_observer_event_counts(&app, child_two, 2, 0, 2, 0);
1474 app.world_mut().increment_change_tick();
1475 // ---
1476 }
1477}