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