bevy_picking/
focus.rs

1//! Determines which entities are being hovered by which pointers.
2//!
3//! The most important type in this module is the [`HoverMap`], which maps pointers to the entities
4//! they are hovering over.
5
6use alloc::collections::BTreeMap;
7use core::fmt::Debug;
8use std::collections::HashSet;
9
10use crate::{
11    backend::{self, HitData},
12    pointer::{PointerAction, PointerId, PointerInput, PointerInteraction, PointerPress},
13    PickingBehavior,
14};
15
16use bevy_derive::{Deref, DerefMut};
17use bevy_ecs::prelude::*;
18use bevy_math::FloatOrd;
19use bevy_reflect::prelude::*;
20use bevy_utils::HashMap;
21
22type DepthSortedHits = Vec<(Entity, HitData)>;
23
24/// Events returned from backends can be grouped with an order field. This allows picking to work
25/// with multiple layers of rendered output to the same render target.
26type PickLayer = FloatOrd;
27
28/// Maps [`PickLayer`]s to the map of entities within that pick layer, sorted by depth.
29type LayerMap = BTreeMap<PickLayer, DepthSortedHits>;
30
31/// Maps Pointers to a [`LayerMap`]. Note this is much more complex than the [`HoverMap`] because
32/// this data structure is used to sort entities by layer then depth for every pointer.
33type OverMap = HashMap<PointerId, LayerMap>;
34
35/// The source of truth for all hover state. This is used to determine what events to send, and what
36/// state components should be in.
37///
38/// Maps pointers to the entities they are hovering over.
39///
40/// "Hovering" refers to the *hover* state, which is not the same as whether or not a picking
41/// backend is reporting hits between a pointer and an entity. A pointer is "hovering" an entity
42/// only if the pointer is hitting the entity (as reported by a picking backend) *and* no entities
43/// between it and the pointer block interactions.
44///
45/// For example, if a pointer is hitting a UI button and a 3d mesh, but the button is in front of
46/// the mesh, the UI button will be hovered, but the mesh will not. Unless, the [`PickingBehavior`]
47/// component is present with [`should_block_lower`](PickingBehavior::should_block_lower) set to `false`.
48///
49/// # Advanced Users
50///
51/// If you want to completely replace the provided picking events or state produced by this plugin,
52/// you can use this resource to do that. All of the event systems for picking are built *on top of*
53/// this authoritative hover state, and you can do the same. You can also use the
54/// [`PreviousHoverMap`] as a robust way of determining changes in hover state from the previous
55/// update.
56#[derive(Debug, Deref, DerefMut, Default, Resource)]
57pub struct HoverMap(pub HashMap<PointerId, HashMap<Entity, HitData>>);
58
59/// The previous state of the hover map, used to track changes to hover state.
60#[derive(Debug, Deref, DerefMut, Default, Resource)]
61pub struct PreviousHoverMap(pub HashMap<PointerId, HashMap<Entity, HitData>>);
62
63/// Coalesces all data from inputs and backends to generate a map of the currently hovered entities.
64/// This is the final focusing step to determine which entity the pointer is hovering over.
65pub fn update_focus(
66    // Inputs
67    picking_behavior: Query<&PickingBehavior>,
68    pointers: Query<&PointerId>,
69    mut under_pointer: EventReader<backend::PointerHits>,
70    mut pointer_input: EventReader<PointerInput>,
71    // Local
72    mut over_map: Local<OverMap>,
73    // Output
74    mut hover_map: ResMut<HoverMap>,
75    mut previous_hover_map: ResMut<PreviousHoverMap>,
76) {
77    reset_maps(
78        &mut hover_map,
79        &mut previous_hover_map,
80        &mut over_map,
81        &pointers,
82    );
83    build_over_map(&mut under_pointer, &mut over_map, &mut pointer_input);
84    build_hover_map(&pointers, picking_behavior, &over_map, &mut hover_map);
85}
86
87/// Clear non-empty local maps, reusing allocated memory.
88fn reset_maps(
89    hover_map: &mut HoverMap,
90    previous_hover_map: &mut PreviousHoverMap,
91    over_map: &mut OverMap,
92    pointers: &Query<&PointerId>,
93) {
94    // Swap the previous and current hover maps. This results in the previous values being stored in
95    // `PreviousHoverMap`. Swapping is okay because we clear the `HoverMap` which now holds stale
96    // data. This process is done without any allocations.
97    core::mem::swap(&mut previous_hover_map.0, &mut hover_map.0);
98
99    for entity_set in hover_map.values_mut() {
100        entity_set.clear();
101    }
102    for layer_map in over_map.values_mut() {
103        layer_map.clear();
104    }
105
106    // Clear pointers from the maps if they have been removed.
107    let active_pointers: Vec<PointerId> = pointers.iter().copied().collect();
108    hover_map.retain(|pointer, _| active_pointers.contains(pointer));
109    over_map.retain(|pointer, _| active_pointers.contains(pointer));
110}
111
112/// Build an ordered map of entities that are under each pointer
113fn build_over_map(
114    backend_events: &mut EventReader<backend::PointerHits>,
115    pointer_over_map: &mut Local<OverMap>,
116    pointer_input: &mut EventReader<PointerInput>,
117) {
118    let cancelled_pointers: HashSet<PointerId> = pointer_input
119        .read()
120        .filter_map(|p| {
121            if let PointerAction::Canceled = p.action {
122                Some(p.pointer_id)
123            } else {
124                None
125            }
126        })
127        .collect();
128
129    for entities_under_pointer in backend_events
130        .read()
131        .filter(|e| !cancelled_pointers.contains(&e.pointer))
132    {
133        let pointer = entities_under_pointer.pointer;
134        let layer_map = pointer_over_map
135            .entry(pointer)
136            .or_insert_with(BTreeMap::new);
137        for (entity, pick_data) in entities_under_pointer.picks.iter() {
138            let layer = entities_under_pointer.order;
139            let hits = layer_map.entry(FloatOrd(layer)).or_default();
140            hits.push((*entity, pick_data.clone()));
141        }
142    }
143
144    for layers in pointer_over_map.values_mut() {
145        for hits in layers.values_mut() {
146            hits.sort_by_key(|(_, hit)| FloatOrd(hit.depth));
147        }
148    }
149}
150
151/// Build an unsorted set of hovered entities, accounting for depth, layer, and [`PickingBehavior`]. Note
152/// that unlike the pointer map, this uses [`PickingBehavior`] to determine if lower entities receive hover
153/// focus. Often, only a single entity per pointer will be hovered.
154fn build_hover_map(
155    pointers: &Query<&PointerId>,
156    picking_behavior: Query<&PickingBehavior>,
157    over_map: &Local<OverMap>,
158    // Output
159    hover_map: &mut HoverMap,
160) {
161    for pointer_id in pointers.iter() {
162        let pointer_entity_set = hover_map.entry(*pointer_id).or_default();
163        if let Some(layer_map) = over_map.get(pointer_id) {
164            // Note we reverse here to start from the highest layer first.
165            for (entity, pick_data) in layer_map.values().rev().flatten() {
166                if let Ok(picking_behavior) = picking_behavior.get(*entity) {
167                    if picking_behavior.is_hoverable {
168                        pointer_entity_set.insert(*entity, pick_data.clone());
169                    }
170                    if picking_behavior.should_block_lower {
171                        break;
172                    }
173                } else {
174                    pointer_entity_set.insert(*entity, pick_data.clone()); // Emit events by default
175                    break; // Entities block by default so we break out of the loop
176                }
177            }
178        }
179    }
180}
181
182/// A component that aggregates picking interaction state of this entity across all pointers.
183///
184/// Unlike bevy's `Interaction` component, this is an aggregate of the state of all pointers
185/// interacting with this entity. Aggregation is done by taking the interaction with the highest
186/// precedence.
187///
188/// For example, if we have an entity that is being hovered by one pointer, and pressed by another,
189/// the entity will be considered pressed. If that entity is instead being hovered by both pointers,
190/// it will be considered hovered.
191#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]
192#[reflect(Component, Default, PartialEq, Debug)]
193pub enum PickingInteraction {
194    /// The entity is being pressed down by a pointer.
195    Pressed = 2,
196    /// The entity is being hovered by a pointer.
197    Hovered = 1,
198    /// No pointers are interacting with this entity.
199    #[default]
200    None = 0,
201}
202
203/// Uses pointer events to update [`PointerInteraction`] and [`PickingInteraction`] components.
204pub fn update_interactions(
205    // Input
206    hover_map: Res<HoverMap>,
207    previous_hover_map: Res<PreviousHoverMap>,
208    // Outputs
209    mut commands: Commands,
210    mut pointers: Query<(&PointerId, &PointerPress, &mut PointerInteraction)>,
211    mut interact: Query<&mut PickingInteraction>,
212) {
213    // Clear all previous hover data from pointers and entities
214    for (pointer, _, mut pointer_interaction) in &mut pointers {
215        pointer_interaction.sorted_entities.clear();
216        if let Some(previously_hovered_entities) = previous_hover_map.get(pointer) {
217            for entity in previously_hovered_entities.keys() {
218                if let Ok(mut interaction) = interact.get_mut(*entity) {
219                    *interaction = PickingInteraction::None;
220                }
221            }
222        }
223    }
224
225    // Create a map to hold the aggregated interaction for each entity. This is needed because we
226    // need to be able to insert the interaction component on entities if they do not exist. To do
227    // so we need to know the final aggregated interaction state to avoid the scenario where we set
228    // an entity to `Pressed`, then overwrite that with a lower precedent like `Hovered`.
229    let mut new_interaction_state = HashMap::<Entity, PickingInteraction>::new();
230    for (pointer, pointer_press, mut pointer_interaction) in &mut pointers {
231        if let Some(pointers_hovered_entities) = hover_map.get(pointer) {
232            // Insert a sorted list of hit entities into the pointer's interaction component.
233            let mut sorted_entities: Vec<_> = pointers_hovered_entities.clone().drain().collect();
234            sorted_entities.sort_by_key(|(_entity, hit)| FloatOrd(hit.depth));
235            pointer_interaction.sorted_entities = sorted_entities;
236
237            for hovered_entity in pointers_hovered_entities.iter().map(|(entity, _)| entity) {
238                merge_interaction_states(pointer_press, hovered_entity, &mut new_interaction_state);
239            }
240        }
241    }
242
243    // Take the aggregated entity states and update or insert the component if missing.
244    for (hovered_entity, new_interaction) in new_interaction_state.drain() {
245        if let Ok(mut interaction) = interact.get_mut(hovered_entity) {
246            *interaction = new_interaction;
247        } else if let Some(mut entity_commands) = commands.get_entity(hovered_entity) {
248            entity_commands.try_insert(new_interaction);
249        }
250    }
251}
252
253/// Merge the interaction state of this entity into the aggregated map.
254fn merge_interaction_states(
255    pointer_press: &PointerPress,
256    hovered_entity: &Entity,
257    new_interaction_state: &mut HashMap<Entity, PickingInteraction>,
258) {
259    let new_interaction = match pointer_press.is_any_pressed() {
260        true => PickingInteraction::Pressed,
261        false => PickingInteraction::Hovered,
262    };
263
264    if let Some(old_interaction) = new_interaction_state.get_mut(hovered_entity) {
265        // Only update if the new value has a higher precedence than the old value.
266        if *old_interaction != new_interaction
267            && matches!(
268                (*old_interaction, new_interaction),
269                (PickingInteraction::Hovered, PickingInteraction::Pressed)
270                    | (PickingInteraction::None, PickingInteraction::Pressed)
271                    | (PickingInteraction::None, PickingInteraction::Hovered)
272            )
273        {
274            *old_interaction = new_interaction;
275        }
276    } else {
277        new_interaction_state.insert(*hovered_entity, new_interaction);
278    }
279}