//! This module defines a stateful set of interaction events driven by the `PointerInput` stream //! and the hover state of each Pointer. //! //! # Usage //! //! To receive events from this module, you must use an [`Observer`] or [`EventReader`] with [`Pointer`] events. //! The simplest example, registering a callback when an entity is hovered over by a pointer, looks like this: //! //! ```rust //! # use bevy_ecs::prelude::*; //! # use bevy_picking::prelude::*; //! # let mut world = World::default(); //! world.spawn_empty() //! .observe(|trigger: Trigger>| { //! println!("I am being hovered over"); //! }); //! ``` //! //! Observers give us three important properties: //! 1. They allow for attaching event handlers to specific entities, //! 2. they allow events to bubble up the entity hierarchy, //! 3. and they allow events of different types to be called in a specific order. //! //! The order in which interaction events are received is extremely important, and you can read more //! about it on the docs for the dispatcher system: [`pointer_events`]. This system runs in //! [`PreUpdate`](bevy_app::PreUpdate) in [`PickSet::Hover`](crate::PickSet::Hover). All pointer-event //! observers resolve during the sync point between [`pointer_events`] and //! [`update_interactions`](crate::hover::update_interactions). //! //! # Events Types //! //! The events this module defines fall into a few broad categories: //! + Hovering and movement: [`Over`], [`Move`], and [`Out`]. //! + Clicking and pressing: [`Pressed`], [`Released`], and [`Click`]. //! + Dragging and dropping: [`DragStart`], [`Drag`], [`DragEnd`], [`DragEnter`], [`DragOver`], [`DragDrop`], [`DragLeave`]. //! //! When received by an observer, these events will always be wrapped by the [`Pointer`] type, which contains //! general metadata about the pointer event. use core::{fmt::Debug, time::Duration}; use bevy_ecs::{prelude::*, query::QueryData, system::SystemParam, traversal::Traversal}; use bevy_input::mouse::MouseScrollUnit; use bevy_math::Vec2; use bevy_platform_support::collections::HashMap; use bevy_platform_support::time::Instant; use bevy_reflect::prelude::*; use bevy_render::camera::NormalizedRenderTarget; use bevy_window::Window; use tracing::debug; use crate::{ backend::{prelude::PointerLocation, HitData}, hover::{HoverMap, PreviousHoverMap}, pointer::{Location, PointerAction, PointerButton, PointerId, PointerInput, PointerMap}, }; /// Stores the common data needed for all pointer events. /// /// The documentation for the [`pointer_events`] explains the events this module exposes and /// the order in which they fire. #[derive(Clone, PartialEq, Debug, Reflect, Component)] #[reflect(Component, Debug)] pub struct Pointer { /// The original target of this picking event, before bubbling pub target: Entity, /// The pointer that triggered this event pub pointer_id: PointerId, /// The location of the pointer during this event pub pointer_location: Location, /// Additional event-specific data. [`DragDrop`] for example, has an additional field to describe /// the `Entity` that is being dropped on the target. pub event: E, } /// A traversal query (i.e. it implements [`Traversal`]) intended for use with [`Pointer`] events. /// /// This will always traverse to the parent, if the entity being visited has one. Otherwise, it /// propagates to the pointer's window and stops there. #[derive(QueryData)] pub struct PointerTraversal { child_of: Option<&'static ChildOf>, window: Option<&'static Window>, } impl Traversal> for PointerTraversal where E: Debug + Clone + Reflect, { fn traverse(item: Self::Item<'_>, pointer: &Pointer) -> Option { let PointerTraversalItem { child_of, window } = item; // Send event to parent, if it has one. if let Some(child_of) = child_of { return Some(child_of.parent); }; // Otherwise, send it to the window entity (unless this is a window entity). if window.is_none() { if let NormalizedRenderTarget::Window(window_ref) = pointer.pointer_location.target { return Some(window_ref.entity()); } } None } } impl Event for Pointer where E: Debug + Clone + Reflect, { type Traversal = PointerTraversal; const AUTO_PROPAGATE: bool = true; } impl core::fmt::Display for Pointer { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_fmt(format_args!( "{:?}, {:.1?}, {:.1?}", self.pointer_id, self.pointer_location.position, self.event )) } } impl core::ops::Deref for Pointer { type Target = E; fn deref(&self) -> &Self::Target { &self.event } } impl Pointer { /// Construct a new `Pointer` event. pub fn new(id: PointerId, location: Location, target: Entity, event: E) -> Self { Self { target, pointer_id: id, pointer_location: location, event, } } } /// Fires when a pointer is canceled, and its current interaction state is dropped. #[derive(Clone, PartialEq, Debug, Reflect)] pub struct Cancel { /// Information about the picking intersection. pub hit: HitData, } /// Fires when a the pointer crosses into the bounds of the `target` entity. #[derive(Clone, PartialEq, Debug, Reflect)] pub struct Over { /// Information about the picking intersection. pub hit: HitData, } /// Fires when a the pointer crosses out of the bounds of the `target` entity. #[derive(Clone, PartialEq, Debug, Reflect)] pub struct Out { /// Information about the latest prior picking intersection. pub hit: HitData, } /// Fires when a pointer button is pressed over the `target` entity. #[derive(Clone, PartialEq, Debug, Reflect)] pub struct Pressed { /// Pointer button pressed to trigger this event. pub button: PointerButton, /// Information about the picking intersection. pub hit: HitData, } /// Fires when a pointer button is released over the `target` entity. #[derive(Clone, PartialEq, Debug, Reflect)] pub struct Released { /// Pointer button lifted to trigger this event. pub button: PointerButton, /// Information about the picking intersection. pub hit: HitData, } /// Fires when a pointer sends a pointer pressed event followed by a pointer released event, with the same /// `target` entity for both events. #[derive(Clone, PartialEq, Debug, Reflect)] pub struct Click { /// Pointer button pressed and lifted to trigger this event. pub button: PointerButton, /// Information about the picking intersection. pub hit: HitData, /// Duration between the pointer pressed and lifted for this click pub duration: Duration, } /// Fires while a pointer is moving over the `target` entity. #[derive(Clone, PartialEq, Debug, Reflect)] pub struct Move { /// Information about the picking intersection. pub hit: HitData, /// The change in position since the last move event. pub delta: Vec2, } /// Fires when the `target` entity receives a pointer pressed event followed by a pointer move event. #[derive(Clone, PartialEq, Debug, Reflect)] pub struct DragStart { /// Pointer button pressed and moved to trigger this event. pub button: PointerButton, /// Information about the picking intersection. pub hit: HitData, } /// Fires while the `target` entity is being dragged. #[derive(Clone, PartialEq, Debug, Reflect)] pub struct Drag { /// Pointer button pressed and moved to trigger this event. pub button: PointerButton, /// The total distance vector of a drag, measured from drag start to the current position. pub distance: Vec2, /// The change in position since the last drag event. pub delta: Vec2, } /// Fires when a pointer is dragging the `target` entity and a pointer released event is received. #[derive(Clone, PartialEq, Debug, Reflect)] pub struct DragEnd { /// Pointer button pressed, moved, and released to trigger this event. pub button: PointerButton, /// The vector of drag movement measured from start to final pointer position. pub distance: Vec2, } /// Fires when a pointer dragging the `dragged` entity enters the `target` entity. #[derive(Clone, PartialEq, Debug, Reflect)] pub struct DragEnter { /// Pointer button pressed to enter drag. pub button: PointerButton, /// The entity that was being dragged when the pointer entered the `target` entity. pub dragged: Entity, /// Information about the picking intersection. pub hit: HitData, } /// Fires while the `dragged` entity is being dragged over the `target` entity. #[derive(Clone, PartialEq, Debug, Reflect)] pub struct DragOver { /// Pointer button pressed while dragging over. pub button: PointerButton, /// The entity that was being dragged when the pointer was over the `target` entity. pub dragged: Entity, /// Information about the picking intersection. pub hit: HitData, } /// Fires when a pointer dragging the `dragged` entity leaves the `target` entity. #[derive(Clone, PartialEq, Debug, Reflect)] pub struct DragLeave { /// Pointer button pressed while leaving drag. pub button: PointerButton, /// The entity that was being dragged when the pointer left the `target` entity. pub dragged: Entity, /// Information about the latest prior picking intersection. pub hit: HitData, } /// Fires when a pointer drops the `dropped` entity onto the `target` entity. #[derive(Clone, PartialEq, Debug, Reflect)] pub struct DragDrop { /// Pointer button released to drop. pub button: PointerButton, /// The entity that was dropped onto the `target` entity. pub dropped: Entity, /// Information about the picking intersection. pub hit: HitData, } /// Dragging state. #[derive(Clone, PartialEq, Debug, Reflect)] pub struct DragEntry { /// The position of the pointer at drag start. pub start_pos: Vec2, /// The latest position of the pointer during this drag, used to compute deltas. pub latest_pos: Vec2, } /// Fires while a pointer is scrolling over the `target` entity. #[derive(Clone, PartialEq, Debug, Reflect)] pub struct Scroll { /// The mouse scroll unit. pub unit: MouseScrollUnit, /// The horizontal scroll value. pub x: f32, /// The vertical scroll value. pub y: f32, /// Information about the picking intersection. pub hit: HitData, } /// An entry in the cache that drives the `pointer_events` system, storing additional data /// about pointer button presses. #[derive(Debug, Clone, Default)] pub struct PointerButtonState { /// Stores the press location and start time for each button currently being pressed by the pointer. pub pressing: HashMap, /// Stores the starting and current locations for each entity currently being dragged by the pointer. pub dragging: HashMap, /// Stores the hit data for each entity currently being dragged over by the pointer. pub dragging_over: HashMap, } /// State for all pointers. #[derive(Debug, Clone, Default, Resource)] pub struct PointerState { /// Pressing and dragging state, organized by pointer and button. pub pointer_buttons: HashMap<(PointerId, PointerButton), PointerButtonState>, } impl PointerState { /// Retrieves the current state for a specific pointer and button, if it has been created. pub fn get(&self, pointer_id: PointerId, button: PointerButton) -> Option<&PointerButtonState> { self.pointer_buttons.get(&(pointer_id, button)) } /// Provides write access to the state of a pointer and button, creating it if it does not yet exist. pub fn get_mut( &mut self, pointer_id: PointerId, button: PointerButton, ) -> &mut PointerButtonState { self.pointer_buttons .entry((pointer_id, button)) .or_default() } /// Clears all the data associated with all of the buttons on a pointer. Does not free the underlying memory. pub fn clear(&mut self, pointer_id: PointerId) { for button in PointerButton::iter() { if let Some(state) = self.pointer_buttons.get_mut(&(pointer_id, button)) { state.pressing.clear(); state.dragging.clear(); state.dragging_over.clear(); } } } } /// A helper system param for accessing the picking event writers. #[derive(SystemParam)] pub struct PickingEventWriters<'w> { cancel_events: EventWriter<'w, Pointer>, click_events: EventWriter<'w, Pointer>, pressed_events: EventWriter<'w, Pointer>, drag_drop_events: EventWriter<'w, Pointer>, drag_end_events: EventWriter<'w, Pointer>, drag_enter_events: EventWriter<'w, Pointer>, drag_events: EventWriter<'w, Pointer>, drag_leave_events: EventWriter<'w, Pointer>, drag_over_events: EventWriter<'w, Pointer>, drag_start_events: EventWriter<'w, Pointer>, scroll_events: EventWriter<'w, Pointer>, move_events: EventWriter<'w, Pointer>, out_events: EventWriter<'w, Pointer>, over_events: EventWriter<'w, Pointer>, released_events: EventWriter<'w, Pointer>, } /// Dispatches interaction events to the target entities. /// /// Within a single frame, events are dispatched in the following order: /// + [`Out`] → [`DragLeave`]. /// + [`DragEnter`] → [`Over`]. /// + Any number of any of the following: /// + For each movement: [`DragStart`] → [`Drag`] → [`DragOver`] → [`Move`]. /// + For each button press: [`Pressed`] or [`Click`] → [`Released`] → [`DragDrop`] → [`DragEnd`] → [`DragLeave`]. /// + For each pointer cancellation: [`Cancel`]. /// /// Additionally, across multiple frames, the following are also strictly /// ordered by the interaction state machine: /// + When a pointer moves over the target: /// [`Over`], [`Move`], [`Out`]. /// + When a pointer presses buttons on the target: /// [`Pressed`], [`Click`], [`Released`]. /// + When a pointer drags the target: /// [`DragStart`], [`Drag`], [`DragEnd`]. /// + When a pointer drags something over the target: /// [`DragEnter`], [`DragOver`], [`DragDrop`], [`DragLeave`]. /// + When a pointer is canceled: /// No other events will follow the [`Cancel`] event for that pointer. /// /// Two events -- [`Over`] and [`Out`] -- are driven only by the [`HoverMap`]. /// The rest rely on additional data from the [`PointerInput`] event stream. To /// receive these events for a custom pointer, you must add [`PointerInput`] /// events. /// /// When the pointer goes from hovering entity A to entity B, entity A will /// receive [`Out`] and then entity B will receive [`Over`]. No entity will ever /// receive both an [`Over`] and and a [`Out`] event during the same frame. /// /// When we account for event bubbling, this is no longer true. When the hovering focus shifts /// between children, parent entities may receive redundant [`Out`] → [`Over`] pairs. /// In the context of UI, this is especially problematic. Additional hierarchy-aware /// events will be added in a future release. /// /// Both [`Click`] and [`Released`] target the entity hovered in the *previous frame*, /// rather than the current frame. This is because touch pointers hover nothing /// on the frame they are released. The end effect is that these two events can /// be received sequentially after an [`Out`] event (but always on the same frame /// as the [`Out`] event). /// /// Note: Though it is common for the [`PointerInput`] stream may contain /// multiple pointer movements and presses each frame, the hover state is /// determined only by the pointer's *final position*. Since the hover state /// ultimately determines which entities receive events, this may mean that an /// entity can receive events from before or after it was actually hovered. pub fn pointer_events( // Input mut input_events: EventReader, // ECS State pointers: Query<&PointerLocation>, pointer_map: Res, hover_map: Res, previous_hover_map: Res, mut pointer_state: ResMut, // Output mut commands: Commands, mut event_writers: PickingEventWriters, ) { // Setup utilities let now = Instant::now(); let pointer_location = |pointer_id: PointerId| { pointer_map .get_entity(pointer_id) .and_then(|entity| pointers.get(entity).ok()) .and_then(|pointer| pointer.location.clone()) }; // If the entity was hovered by a specific pointer last frame... for (pointer_id, hovered_entity, hit) in previous_hover_map .iter() .flat_map(|(id, hashmap)| hashmap.iter().map(|data| (*id, *data.0, data.1.clone()))) { // ...but is now not being hovered by that same pointer... if !hover_map .get(&pointer_id) .iter() .any(|e| e.contains_key(&hovered_entity)) { let Some(location) = pointer_location(pointer_id) else { debug!( "Unable to get location for pointer {:?} during pointer out", pointer_id ); continue; }; // Always send Out events let out_event = Pointer::new( pointer_id, location.clone(), hovered_entity, Out { hit: hit.clone() }, ); commands.trigger_targets(out_event.clone(), hovered_entity); event_writers.out_events.write(out_event); // Possibly send DragLeave events for button in PointerButton::iter() { let state = pointer_state.get_mut(pointer_id, button); state.dragging_over.remove(&hovered_entity); for drag_target in state.dragging.keys() { let drag_leave_event = Pointer::new( pointer_id, location.clone(), hovered_entity, DragLeave { button, dragged: *drag_target, hit: hit.clone(), }, ); commands.trigger_targets(drag_leave_event.clone(), hovered_entity); event_writers.drag_leave_events.write(drag_leave_event); } } } } // If the entity is hovered... for (pointer_id, hovered_entity, hit) in hover_map .iter() .flat_map(|(id, hashmap)| hashmap.iter().map(|data| (*id, *data.0, data.1.clone()))) { // ...but was not hovered last frame... if !previous_hover_map .get(&pointer_id) .iter() .any(|e| e.contains_key(&hovered_entity)) { let Some(location) = pointer_location(pointer_id) else { debug!( "Unable to get location for pointer {:?} during pointer over", pointer_id ); continue; }; // Possibly send DragEnter events for button in PointerButton::iter() { let state = pointer_state.get_mut(pointer_id, button); for drag_target in state .dragging .keys() .filter(|&&drag_target| hovered_entity != drag_target) { state.dragging_over.insert(hovered_entity, hit.clone()); let drag_enter_event = Pointer::new( pointer_id, location.clone(), hovered_entity, DragEnter { button, dragged: *drag_target, hit: hit.clone(), }, ); commands.trigger_targets(drag_enter_event.clone(), hovered_entity); event_writers.drag_enter_events.write(drag_enter_event); } } // Always send Over events let over_event = Pointer::new( pointer_id, location.clone(), hovered_entity, Over { hit: hit.clone() }, ); commands.trigger_targets(over_event.clone(), hovered_entity); event_writers.over_events.write(over_event); } } // Dispatch input events... for PointerInput { pointer_id, location, action, } in input_events.read().cloned() { match action { PointerAction::Press(button) => { let state = pointer_state.get_mut(pointer_id, button); // If it's a press, emit a Pressed event and mark the hovered entities as pressed for (hovered_entity, hit) in hover_map .get(&pointer_id) .iter() .flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.clone()))) { let pressed_event = Pointer::new( pointer_id, location.clone(), hovered_entity, Pressed { button, hit: hit.clone(), }, ); commands.trigger_targets(pressed_event.clone(), hovered_entity); event_writers.pressed_events.write(pressed_event); // Also insert the press into the state state .pressing .insert(hovered_entity, (location.clone(), now, hit)); } } PointerAction::Release(button) => { let state = pointer_state.get_mut(pointer_id, button); // Emit Click and Up events on all the previously hovered entities. for (hovered_entity, hit) in previous_hover_map .get(&pointer_id) .iter() .flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.clone()))) { // If this pointer previously pressed the hovered entity, emit a Click event if let Some((_, press_instant, _)) = state.pressing.get(&hovered_entity) { let click_event = Pointer::new( pointer_id, location.clone(), hovered_entity, Click { button, hit: hit.clone(), duration: now - *press_instant, }, ); commands.trigger_targets(click_event.clone(), hovered_entity); event_writers.click_events.write(click_event); } // Always send the Released event let released_event = Pointer::new( pointer_id, location.clone(), hovered_entity, Released { button, hit: hit.clone(), }, ); commands.trigger_targets(released_event.clone(), hovered_entity); event_writers.released_events.write(released_event); } // Then emit the drop events. for (drag_target, drag) in state.dragging.drain() { // Emit DragDrop for (dragged_over, hit) in state.dragging_over.iter() { let drag_drop_event = Pointer::new( pointer_id, location.clone(), *dragged_over, DragDrop { button, dropped: drag_target, hit: hit.clone(), }, ); commands.trigger_targets(drag_drop_event.clone(), *dragged_over); event_writers.drag_drop_events.write(drag_drop_event); } // Emit DragEnd let drag_end_event = Pointer::new( pointer_id, location.clone(), drag_target, DragEnd { button, distance: drag.latest_pos - drag.start_pos, }, ); commands.trigger_targets(drag_end_event.clone(), drag_target); event_writers.drag_end_events.write(drag_end_event); // Emit DragLeave for (dragged_over, hit) in state.dragging_over.iter() { let drag_leave_event = Pointer::new( pointer_id, location.clone(), *dragged_over, DragLeave { button, dragged: drag_target, hit: hit.clone(), }, ); commands.trigger_targets(drag_leave_event.clone(), *dragged_over); event_writers.drag_leave_events.write(drag_leave_event); } } // Finally, we can clear the state of everything relating to presses or drags. state.pressing.clear(); state.dragging.clear(); state.dragging_over.clear(); } // Moved PointerAction::Move { delta } => { if delta == Vec2::ZERO { continue; // If delta is zero, the following events will not be triggered. } // Triggers during movement even if not over an entity for button in PointerButton::iter() { let state = pointer_state.get_mut(pointer_id, button); // Emit DragEntry and DragStart the first time we move while pressing an entity for (press_target, (location, _, hit)) in state.pressing.iter() { if state.dragging.contains_key(press_target) { continue; // This entity is already logged as being dragged } state.dragging.insert( *press_target, DragEntry { start_pos: location.position, latest_pos: location.position, }, ); let drag_start_event = Pointer::new( pointer_id, location.clone(), *press_target, DragStart { button, hit: hit.clone(), }, ); commands.trigger_targets(drag_start_event.clone(), *press_target); event_writers.drag_start_events.write(drag_start_event); } // Emit Drag events to the entities we are dragging for (drag_target, drag) in state.dragging.iter_mut() { let delta = location.position - drag.latest_pos; if delta == Vec2::ZERO { continue; // No need to emit a Drag event if there is no movement } let drag_event = Pointer::new( pointer_id, location.clone(), *drag_target, Drag { button, distance: location.position - drag.start_pos, delta, }, ); commands.trigger_targets(drag_event.clone(), *drag_target); event_writers.drag_events.write(drag_event); // Update drag position drag.latest_pos = location.position; // Emit corresponding DragOver to the hovered entities for (hovered_entity, hit) in hover_map .get(&pointer_id) .iter() .flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.to_owned()))) .filter(|(hovered_entity, _)| *hovered_entity != *drag_target) { let drag_over_event = Pointer::new( pointer_id, location.clone(), hovered_entity, DragOver { button, dragged: *drag_target, hit: hit.clone(), }, ); commands.trigger_targets(drag_over_event.clone(), hovered_entity); event_writers.drag_over_events.write(drag_over_event); } } } for (hovered_entity, hit) in hover_map .get(&pointer_id) .iter() .flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.to_owned()))) { // Emit Move events to the entities we are hovering let move_event = Pointer::new( pointer_id, location.clone(), hovered_entity, Move { hit: hit.clone(), delta, }, ); commands.trigger_targets(move_event.clone(), hovered_entity); event_writers.move_events.write(move_event); } } PointerAction::Scroll { x, y, unit } => { for (hovered_entity, hit) in hover_map .get(&pointer_id) .iter() .flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.clone()))) { // Emit Scroll events to the entities we are hovering let scroll_event = Pointer::new( pointer_id, location.clone(), hovered_entity, Scroll { unit, x, y, hit: hit.clone(), }, ); commands.trigger_targets(scroll_event.clone(), hovered_entity); event_writers.scroll_events.write(scroll_event); } } // Canceled PointerAction::Cancel => { // Emit a Cancel to the hovered entity. for (hovered_entity, hit) in hover_map .get(&pointer_id) .iter() .flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.to_owned()))) { let cancel_event = Pointer::new(pointer_id, location.clone(), hovered_entity, Cancel { hit }); commands.trigger_targets(cancel_event.clone(), hovered_entity); event_writers.cancel_events.write(cancel_event); } // Clear the state for the canceled pointer pointer_state.clear(pointer_id); } } } }