bevy_picking_core/
focus.rs

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