Skip to main content

bevy_picking/
hover.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, PointerPressState},
13    Pickable,
14};
15
16use bevy_derive::{Deref, DerefMut};
17use bevy_ecs::{
18    entity::{EntityHashMap, EntityHashSet},
19    prelude::*,
20};
21use bevy_math::FloatOrd;
22use bevy_platform::collections::HashMap;
23use bevy_reflect::prelude::*;
24
25type DepthSortedHits = Vec<(Entity, HitData)>;
26
27/// Events returned from backends can be grouped with an order field. This allows picking to work
28/// with multiple layers of rendered output to the same render target.
29type PickLayer = FloatOrd;
30
31/// Maps [`PickLayer`]s to the map of entities within that pick layer, sorted by depth.
32type LayerMap = BTreeMap<PickLayer, DepthSortedHits>;
33
34/// Maps Pointers to a [`LayerMap`]. Note this is much more complex than the [`HoverMap`] because
35/// this data structure is used to sort entities by layer then depth for every pointer.
36type OverMap = HashMap<PointerId, LayerMap>;
37
38/// The source of truth for all hover state. This is used to determine what events to send, and what
39/// state components should be in.
40///
41/// Maps pointers to the entities they are hovering over.
42///
43/// "Hovering" refers to the *hover* state, which is not the same as whether or not a picking
44/// backend is reporting hits between a pointer and an entity. A pointer is "hovering" an entity
45/// only if the pointer is hitting the entity (as reported by a picking backend) *and* no entities
46/// between it and the pointer block interactions.
47///
48/// For example, if a pointer is hitting a UI button and a 3d mesh, but the button is in front of
49/// the mesh, the UI button will be hovered, but the mesh will not. Unless, the [`Pickable`]
50/// component is present with [`should_block_lower`](Pickable::should_block_lower) set to `false`.
51///
52/// # Advanced Users
53///
54/// If you want to completely replace the provided picking events or state produced by this plugin,
55/// you can use this resource to do that. All of the event systems for picking are built *on top of*
56/// this authoritative hover state, and you can do the same. You can also use the
57/// [`PreviousHoverMap`] as a robust way of determining changes in hover state from the previous
58/// update.
59///
60/// See [`PointerCaptureMap`] to force a pointer to keep hovering a single entity regardless of
61/// where it physically is.
62#[derive(Debug, Deref, DerefMut, Default, Resource, Reflect)]
63#[reflect(Debug, Default, Resource)]
64pub struct HoverMap(pub HashMap<PointerId, EntityHashMap<HitData>>);
65
66/// The previous state of the hover map, used to track changes to hover state.
67#[derive(Debug, Deref, DerefMut, Default, Resource, Reflect)]
68#[reflect(Debug, Default, Resource)]
69pub struct PreviousHoverMap(pub HashMap<PointerId, EntityHashMap<HitData>>);
70
71/// Overrides the [`HoverMap`] for captured pointers, forcing a single entity to be reported as
72/// hovered regardless of where the pointer physically is.
73///
74/// When a pointer is captured by an entity, that entity is reported as the sole hover target for
75/// that pointer. This keeps interactions (like dragging a slider) working correctly even when the
76/// pointer drifts outside the entity's bounding box while a button is held.
77///
78/// Use [`PointerCaptureMap::capture`] to lock a pointer to an entity, and
79/// [`PointerCaptureMap::release`] to remove the lock. Captures are automatically cleaned up when
80/// the associated pointer entity is removed.
81///
82/// A pointer's capture is also released automatically when that pointer reports a button release
83/// or cancel action.
84#[derive(Debug, Default, Resource)]
85pub struct PointerCaptureMap(pub HashMap<PointerId, (Entity, HitData)>);
86
87impl PointerCaptureMap {
88    /// Lock `pointer` to `entity`. The provided `hit` is injected into the [`HoverMap`] for the
89    /// duration of the capture so downstream systems see consistent hit data.
90    pub fn capture(&mut self, pointer: PointerId, entity: Entity, hit: HitData) {
91        self.0.insert(pointer, (entity, hit));
92    }
93
94    /// Remove the capture for `pointer`, returning the previously captured entity and hit data if
95    /// one existed.
96    pub fn release(&mut self, pointer: PointerId) -> Option<(Entity, HitData)> {
97        self.0.remove(&pointer)
98    }
99
100    /// Return the captured `(Entity, &HitData)` for `pointer`, or `None` if not captured.
101    pub fn get(&self, pointer: &PointerId) -> Option<(Entity, &HitData)> {
102        self.0.get(pointer).map(|(e, h)| (*e, h))
103    }
104
105    /// Returns `true` if `pointer` is currently captured.
106    pub fn is_captured(&self, pointer: &PointerId) -> bool {
107        self.0.contains_key(pointer)
108    }
109}
110
111/// Gets the hovered entities for a `pointer_id` from a provided `HoverMap` inner map
112pub(crate) fn get_hovered_entities(
113    hover_map: &HashMap<PointerId, EntityHashMap<HitData>>,
114    pointer_id: &PointerId,
115) -> EntityHashSet {
116    hover_map
117        .get(pointer_id)
118        .map_or(EntityHashSet::default(), |entity_hit| {
119            entity_hit
120                .iter()
121                .map(|(&entity, _)| entity)
122                .collect::<EntityHashSet>()
123        })
124}
125
126/// Returns whether there is hit data for the given `pointer_id` and `entity`
127/// from a provided `HoverMap` inner map. This means that the entity is
128/// "directly hovered" by the `pointer_id` for the given `hover_map`
129pub(crate) fn is_directly_hovered(
130    hover_map: &HashMap<PointerId, EntityHashMap<HitData>>,
131    pointer_id: &PointerId,
132    entity: &Entity,
133) -> bool {
134    hover_map
135        .get(pointer_id)
136        .is_some_and(|hit_data_map| hit_data_map.contains_key(entity))
137}
138
139/// Coalesces all data from inputs and backends to generate a map of the currently hovered entities.
140/// This is the final focusing step to determine which entity the pointer is hovering over.
141pub fn generate_hovermap(
142    // Inputs
143    pickable: Query<&Pickable>,
144    pointers: Query<&PointerId>,
145    mut pointer_hits_reader: MessageReader<backend::PointerHits>,
146    mut pointer_input_reader: MessageReader<PointerInput>,
147    mut capture_map: ResMut<PointerCaptureMap>,
148    // Local
149    mut over_map: Local<OverMap>,
150    // Output
151    mut hover_map: ResMut<HoverMap>,
152    mut previous_hover_map: ResMut<PreviousHoverMap>,
153) {
154    reset_maps(
155        &mut hover_map,
156        &mut previous_hover_map,
157        &mut over_map,
158        &mut capture_map,
159        &pointers,
160    );
161    // Read all pointer input events once so both the capture release and hit-testing logic below
162    // can inspect them.
163    let pointer_inputs: Vec<PointerInput> = pointer_input_reader.read().cloned().collect();
164    release_captures_on_release_or_cancel(&pointer_inputs, &mut capture_map);
165    build_over_map(&mut pointer_hits_reader, &mut over_map, &pointer_inputs);
166    build_hover_map(&pointers, pickable, &over_map, &mut hover_map);
167    apply_pointer_captures(&capture_map, &mut hover_map);
168}
169
170/// Releases a pointer's capture when that pointer reports a button release or cancel
171/// action.
172fn release_captures_on_release_or_cancel(
173    pointer_inputs: &[PointerInput],
174    capture_map: &mut PointerCaptureMap,
175) {
176    for input in pointer_inputs {
177        if matches!(
178            input.action,
179            PointerAction::Release(_) | PointerAction::Cancel
180        ) {
181            capture_map.release(input.pointer_id);
182        }
183    }
184}
185
186/// For each captured pointer, replaces its hover set with a single entry pointing to the captured
187/// entity. Pointers not present in `capture_map` are left unchanged.
188fn apply_pointer_captures(capture_map: &PointerCaptureMap, hover_map: &mut HoverMap) {
189    for (pointer_id, (entity, hit_data)) in &capture_map.0 {
190        let entry = hover_map.entry(*pointer_id).or_default();
191        entry.clear();
192        entry.insert(*entity, hit_data.clone());
193    }
194}
195
196/// Clear non-empty local maps, reusing allocated memory.
197fn reset_maps(
198    hover_map: &mut HoverMap,
199    previous_hover_map: &mut PreviousHoverMap,
200    over_map: &mut OverMap,
201    capture_map: &mut PointerCaptureMap,
202    pointers: &Query<&PointerId>,
203) {
204    // Swap the previous and current hover maps. This results in the previous values being stored in
205    // `PreviousHoverMap`. Swapping is okay because we clear the `HoverMap` which now holds stale
206    // data. This process is done without any allocations.
207    core::mem::swap(&mut previous_hover_map.0, &mut hover_map.0);
208
209    for entity_set in hover_map.values_mut() {
210        entity_set.clear();
211    }
212    for layer_map in over_map.values_mut() {
213        layer_map.clear();
214    }
215
216    // Clear pointers from the maps if they have been removed.
217    let active_pointers: Vec<PointerId> = pointers.iter().copied().collect();
218    hover_map.retain(|pointer, _| active_pointers.contains(pointer));
219    over_map.retain(|pointer, _| active_pointers.contains(pointer));
220    capture_map
221        .0
222        .retain(|pointer, _| active_pointers.contains(pointer));
223}
224
225/// Build an ordered map of entities that are under each pointer
226fn build_over_map(
227    pointer_hit_reader: &mut MessageReader<backend::PointerHits>,
228    pointer_over_map: &mut Local<OverMap>,
229    pointer_inputs: &[PointerInput],
230) {
231    let cancelled_pointers: HashSet<PointerId> = pointer_inputs
232        .iter()
233        .filter_map(|p| {
234            if let PointerAction::Cancel = p.action {
235                Some(p.pointer_id)
236            } else {
237                None
238            }
239        })
240        .collect();
241
242    for entities_under_pointer in pointer_hit_reader
243        .read()
244        .filter(|e| !cancelled_pointers.contains(&e.pointer))
245    {
246        let pointer = entities_under_pointer.pointer;
247        let layer_map = pointer_over_map.entry(pointer).or_default();
248        for (entity, pick_data) in entities_under_pointer.picks.iter() {
249            let layer = entities_under_pointer.order;
250            let hits = layer_map.entry(FloatOrd(layer)).or_default();
251            hits.push((*entity, pick_data.clone()));
252        }
253    }
254
255    for layers in pointer_over_map.values_mut() {
256        for hits in layers.values_mut() {
257            hits.sort_by_key(|(_, hit)| FloatOrd(hit.depth));
258        }
259    }
260}
261
262/// Build an unsorted set of hovered entities, accounting for depth, layer, and [`Pickable`]. Note
263/// that unlike the pointer map, this uses [`Pickable`] to determine if lower entities receive hover
264/// focus. Often, only a single entity per pointer will be hovered.
265fn build_hover_map(
266    pointers: &Query<&PointerId>,
267    pickable: Query<&Pickable>,
268    over_map: &Local<OverMap>,
269    // Output
270    hover_map: &mut HoverMap,
271) {
272    for pointer_id in pointers.iter() {
273        let pointer_entity_set = hover_map.entry(*pointer_id).or_default();
274        if let Some(layer_map) = over_map.get(pointer_id) {
275            // Note we reverse here to start from the highest layer first.
276            for (entity, pick_data) in layer_map.values().rev().flatten() {
277                if let Ok(pickable) = pickable.get(*entity) {
278                    if pickable.is_hoverable {
279                        pointer_entity_set.insert(*entity, pick_data.clone());
280                    }
281                    if pickable.should_block_lower {
282                        break;
283                    }
284                } else {
285                    pointer_entity_set.insert(*entity, pick_data.clone()); // Emit events by default
286                    break; // Entities block by default so we break out of the loop
287                }
288            }
289        }
290    }
291}
292
293/// A component that aggregates picking interaction state of this entity across all pointers.
294///
295/// Unlike bevy's `Interaction` component, this is an aggregate of the state of all pointers
296/// interacting with this entity. Aggregation is done by taking the interaction with the highest
297/// precedence.
298///
299/// For example, if we have an entity that is being hovered by one pointer, and pressed by another,
300/// the entity will be considered pressed. If that entity is instead being hovered by both pointers,
301/// it will be considered hovered.
302#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]
303#[reflect(Component, Default, PartialEq, Debug, Clone)]
304pub enum PickingInteraction {
305    /// The entity is being pressed down by a pointer.
306    Pressed = 2,
307    /// The entity is being hovered by a pointer.
308    Hovered = 1,
309    /// No pointers are interacting with this entity.
310    #[default]
311    None = 0,
312}
313
314/// Uses [`HoverMap`] changes to update [`PointerInteraction`] and [`PickingInteraction`] components.
315pub fn update_interactions(
316    // Input
317    hover_map: Res<HoverMap>,
318    previous_hover_map: Res<PreviousHoverMap>,
319    // Outputs
320    mut commands: Commands,
321    mut pointers: Query<(&PointerId, &PointerPressState, &mut PointerInteraction)>,
322    mut interact: Query<&mut PickingInteraction>,
323) {
324    // Create a map to hold the aggregated interaction for each entity. This is needed because we
325    // need to be able to insert the interaction component on entities if they do not exist. To do
326    // so we need to know the final aggregated interaction state to avoid the scenario where we set
327    // an entity to `Pressed`, then overwrite that with a lower precedent like `Hovered`.
328    let mut new_interaction_state = EntityHashMap::<PickingInteraction>::default();
329    for (pointer, pointer_press, mut pointer_interaction) in &mut pointers {
330        if let Some(pointers_hovered_entities) = hover_map.get(pointer) {
331            // Insert a sorted list of hit entities into the pointer's interaction component.
332            let mut sorted_entities: Vec<_> = pointers_hovered_entities.clone().drain().collect();
333            sorted_entities.sort_by_key(|(_, hit)| FloatOrd(hit.depth));
334            pointer_interaction.sorted_entities = sorted_entities;
335
336            for hovered_entity in pointers_hovered_entities.iter().map(|(entity, _)| entity) {
337                merge_interaction_states(pointer_press, hovered_entity, &mut new_interaction_state);
338            }
339        }
340    }
341
342    // Take the aggregated entity states and update or insert the component if missing.
343    for (&hovered_entity, &new_interaction) in new_interaction_state.iter() {
344        if let Ok(mut interaction) = interact.get_mut(hovered_entity) {
345            interaction.set_if_neq(new_interaction);
346        } else if let Ok(mut entity_commands) = commands.get_entity(hovered_entity) {
347            entity_commands.try_insert(new_interaction);
348        }
349    }
350
351    // Clear all previous hover data from pointers that are no longer hovering any entities.
352    // We do this last to preserve change detection for picking interactions.
353    for (pointer, _, _) in &mut pointers {
354        let Some(previously_hovered_entities) = previous_hover_map.get(pointer) else {
355            continue;
356        };
357
358        for entity in previously_hovered_entities.keys() {
359            if !new_interaction_state.contains_key(entity)
360                && let Ok(mut interaction) = interact.get_mut(*entity)
361            {
362                interaction.set_if_neq(PickingInteraction::None);
363            }
364        }
365    }
366}
367
368/// Merge the interaction state of this entity into the aggregated map.
369fn merge_interaction_states(
370    pointer_press: &PointerPressState,
371    hovered_entity: &Entity,
372    new_interaction_state: &mut EntityHashMap<PickingInteraction>,
373) {
374    let new_interaction = match pointer_press.is_any_pressed() {
375        true => PickingInteraction::Pressed,
376        false => PickingInteraction::Hovered,
377    };
378
379    if let Some(old_interaction) = new_interaction_state.get_mut(hovered_entity) {
380        // Only update if the new value has a higher precedence than the old value.
381        if *old_interaction != new_interaction
382            && matches!(
383                (*old_interaction, new_interaction),
384                (PickingInteraction::Hovered, PickingInteraction::Pressed)
385                    | (PickingInteraction::None, PickingInteraction::Pressed)
386                    | (PickingInteraction::None, PickingInteraction::Hovered)
387            )
388        {
389            *old_interaction = new_interaction;
390        }
391    } else {
392        new_interaction_state.insert(*hovered_entity, new_interaction);
393    }
394}
395
396/// A component that allows users to use regular Bevy change detection to determine when the pointer
397/// enters or leaves an entity. Users should insert this component on an entity to indicate interest
398/// in knowing about hover state changes.
399///
400/// The component's boolean value will be `true` whenever the pointer is currently directly hovering
401/// over the entity, or any of the entity's descendants (as defined by the [`ChildOf`]
402/// relationship). This is consistent with the behavior of the CSS `:hover` pseudo-class, which
403/// applies to the element and all of its descendants.
404///
405/// The contained boolean value is guaranteed to only be mutated when the pointer enters or leaves
406/// the entity, allowing Bevy change detection to be used efficiently. This is in contrast to the
407/// [`HoverMap`] resource, which is updated every frame.
408///
409/// Typically, a simple hoverable entity or widget will have this component added to it. More
410/// complex widgets can have this component added to each hoverable part.
411///
412/// The computational cost of keeping the `Hovered` components up to date is relatively cheap, and
413/// linear in the number of entities that have the [`Hovered`] component inserted.
414#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]
415#[reflect(Component, Default, PartialEq, Debug, Clone)]
416#[component(immutable)]
417pub struct Hovered(pub bool);
418
419impl Hovered {
420    /// Get whether the entity is currently hovered.
421    pub fn get(&self) -> bool {
422        self.0
423    }
424}
425
426/// A component that allows users to use regular Bevy change detection to determine when the pointer
427/// is directly hovering over an entity. Users should insert this component on an entity to indicate
428/// interest in knowing about hover state changes.
429///
430/// This is similar to [`Hovered`] component, except that it does not include descendants in the
431/// hover state.
432#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]
433#[reflect(Component, Default, PartialEq, Debug, Clone)]
434#[component(immutable)]
435pub struct DirectlyHovered(pub bool);
436
437impl DirectlyHovered {
438    /// Get whether the entity is currently hovered.
439    pub fn get(&self) -> bool {
440        self.0
441    }
442}
443
444/// Uses [`HoverMap`] changes to update [`Hovered`] components.
445pub fn update_is_hovered(
446    hover_map: Option<Res<HoverMap>>,
447    mut hovers: Query<(Entity, &Hovered)>,
448    parent_query: Query<&ChildOf>,
449    mut commands: Commands,
450) {
451    // Don't do any work if there's no hover map.
452    let Some(hover_map) = hover_map else { return };
453
454    // Don't bother collecting ancestors if there are no hovers.
455    if hovers.is_empty() {
456        return;
457    }
458
459    // Algorithm: for each entity having a `Hovered` component, we want to know if the current
460    // entry in the hover map is "within" (that is, in the set of descendants of) that entity. Rather
461    // than doing an expensive breadth-first traversal of children, instead start with the hovermap
462    // entry and search upwards. We can make this even cheaper by building a set of ancestors for
463    // the hovermap entry, and then testing each `Hovered` entity against that set.
464
465    // A set which contains the hovered for the current pointer entity and its ancestors. The
466    // capacity is based on the likely tree depth of the hierarchy, which is typically greater for
467    // UI (because of layout issues) than for 3D scenes. A depth of 32 is a reasonable upper bound
468    // for most use cases.
469    let mut hover_ancestors = EntityHashSet::with_capacity(32);
470    if let Some(map) = hover_map.get(&PointerId::Mouse) {
471        for hovered_entity in map.keys() {
472            hover_ancestors.insert(*hovered_entity);
473            hover_ancestors.extend(parent_query.iter_ancestors(*hovered_entity));
474        }
475    }
476
477    // For each hovered entity, it is considered "hovering" if it's in the set of hovered ancestors.
478    for (entity, hoverable) in hovers.iter_mut() {
479        let is_hovering = hover_ancestors.contains(&entity);
480        if hoverable.0 != is_hovering {
481            commands.entity(entity).insert(Hovered(is_hovering));
482        }
483    }
484}
485
486/// Uses [`HoverMap`] changes to update [`DirectlyHovered`] components.
487pub fn update_is_directly_hovered(
488    hover_map: Option<Res<HoverMap>>,
489    hovers: Query<(Entity, &DirectlyHovered)>,
490    mut commands: Commands,
491) {
492    // Don't do any work if there's no hover map.
493    let Some(hover_map) = hover_map else { return };
494
495    // Don't bother collecting ancestors if there are no hovers.
496    if hovers.is_empty() {
497        return;
498    }
499
500    if let Some(map) = hover_map.get(&PointerId::Mouse) {
501        // It's hovering if it's in the HoverMap.
502        for (entity, hoverable) in hovers.iter() {
503            let is_hovering = map.contains_key(&entity);
504            if hoverable.0 != is_hovering {
505                commands.entity(entity).insert(DirectlyHovered(is_hovering));
506            }
507        }
508    } else {
509        // No hovered entity, reset all hovers.
510        for (entity, hoverable) in hovers.iter() {
511            if hoverable.0 {
512                commands.entity(entity).insert(DirectlyHovered(false));
513            }
514        }
515    }
516}
517
518#[cfg(test)]
519mod tests {
520    use bevy_camera::Camera;
521
522    use super::*;
523
524    #[test]
525    fn update_is_hovered_memoized() {
526        let mut world = World::default();
527        let camera = world.spawn(Camera::default()).id();
528
529        // Setup entities
530        let hovered_child = world.spawn_empty().id();
531        let hovered_entity = world.spawn(Hovered(false)).add_child(hovered_child).id();
532
533        // Setup hover map with hovered_entity hovered by mouse
534        let mut hover_map = HoverMap::default();
535        let mut entity_map = EntityHashMap::new();
536        entity_map.insert(
537            hovered_child,
538            HitData {
539                depth: 0.0,
540                camera,
541                position: None,
542                normal: None,
543                extra: None,
544            },
545        );
546        hover_map.insert(PointerId::Mouse, entity_map);
547        world.insert_resource(hover_map);
548
549        // Run the system
550        assert!(world.run_system_cached(update_is_hovered).is_ok());
551
552        // Check to insure that the hovered entity has the Hovered component set to true
553        let hover = world.entity(hovered_entity).get_ref::<Hovered>().unwrap();
554        assert!(hover.get());
555        assert!(hover.is_changed());
556
557        // Now do it again, but don't change the hover map.
558        world.increment_change_tick();
559
560        assert!(world.run_system_cached(update_is_hovered).is_ok());
561        let hover = world.entity(hovered_entity).get_ref::<Hovered>().unwrap();
562        assert!(hover.get());
563
564        // Should not be changed
565        // NOTE: Test doesn't work - thinks it is always changed
566        // assert!(!hover.is_changed());
567
568        // Clear the hover map and run again.
569        world.insert_resource(HoverMap::default());
570        world.increment_change_tick();
571
572        assert!(world.run_system_cached(update_is_hovered).is_ok());
573        let hover = world.entity(hovered_entity).get_ref::<Hovered>().unwrap();
574        assert!(!hover.get());
575        assert!(hover.is_changed());
576    }
577
578    #[test]
579    fn update_is_hovered_direct_self() {
580        let mut world = World::default();
581        let camera = world.spawn(Camera::default()).id();
582
583        // Setup entities
584        let hovered_entity = world.spawn(DirectlyHovered(false)).id();
585
586        // Setup hover map with hovered_entity hovered by mouse
587        let mut hover_map = HoverMap::default();
588        let mut entity_map = EntityHashMap::new();
589        entity_map.insert(
590            hovered_entity,
591            HitData {
592                depth: 0.0,
593                camera,
594                position: None,
595                normal: None,
596                extra: None,
597            },
598        );
599        hover_map.insert(PointerId::Mouse, entity_map);
600        world.insert_resource(hover_map);
601
602        // Run the system
603        assert!(world.run_system_cached(update_is_directly_hovered).is_ok());
604
605        // Check to insure that the hovered entity has the DirectlyHovered component set to true
606        let hover = world
607            .entity(hovered_entity)
608            .get_ref::<DirectlyHovered>()
609            .unwrap();
610        assert!(hover.get());
611        assert!(hover.is_changed());
612
613        // Now do it again, but don't change the hover map.
614        world.increment_change_tick();
615
616        assert!(world.run_system_cached(update_is_directly_hovered).is_ok());
617        let hover = world
618            .entity(hovered_entity)
619            .get_ref::<DirectlyHovered>()
620            .unwrap();
621        assert!(hover.get());
622
623        // Should not be changed
624        // NOTE: Test doesn't work - thinks it is always changed
625        // assert!(!hover.is_changed());
626
627        // Clear the hover map and run again.
628        world.insert_resource(HoverMap::default());
629        world.increment_change_tick();
630
631        assert!(world.run_system_cached(update_is_directly_hovered).is_ok());
632        let hover = world
633            .entity(hovered_entity)
634            .get_ref::<DirectlyHovered>()
635            .unwrap();
636        assert!(!hover.get());
637        assert!(hover.is_changed());
638    }
639
640    #[test]
641    fn update_is_hovered_direct_child() {
642        let mut world = World::default();
643        let camera = world.spawn(Camera::default()).id();
644
645        // Setup entities
646        let hovered_child = world.spawn_empty().id();
647        let hovered_entity = world
648            .spawn(DirectlyHovered(false))
649            .add_child(hovered_child)
650            .id();
651
652        // Setup hover map with hovered_entity hovered by mouse
653        let mut hover_map = HoverMap::default();
654        let mut entity_map = EntityHashMap::new();
655        entity_map.insert(
656            hovered_child,
657            HitData {
658                depth: 0.0,
659                camera,
660                position: None,
661                normal: None,
662                extra: None,
663            },
664        );
665        hover_map.insert(PointerId::Mouse, entity_map);
666        world.insert_resource(hover_map);
667
668        // Run the system
669        assert!(world.run_system_cached(update_is_directly_hovered).is_ok());
670
671        // Check to insure that the DirectlyHovered component is still false
672        let hover = world
673            .entity(hovered_entity)
674            .get_ref::<DirectlyHovered>()
675            .unwrap();
676        assert!(!hover.get());
677        assert!(hover.is_changed());
678    }
679
680    fn make_hit(camera: Entity) -> HitData {
681        HitData {
682            depth: 0.0,
683            camera,
684            position: None,
685            normal: None,
686            extra: None,
687        }
688    }
689
690    #[test]
691    fn capture_overrides_hover_map() {
692        let camera = Entity::from_bits(1);
693        let entity_a = Entity::from_bits(2);
694        let entity_b = Entity::from_bits(3);
695        let hit = make_hit(camera);
696
697        let mut hover_map = HoverMap::default();
698        let mut entity_map = EntityHashMap::new();
699        entity_map.insert(entity_a, hit.clone());
700        hover_map.insert(PointerId::Mouse, entity_map);
701
702        let mut capture_map = PointerCaptureMap::default();
703        capture_map.capture(PointerId::Mouse, entity_b, hit.clone());
704
705        apply_pointer_captures(&capture_map, &mut hover_map);
706
707        let mouse_hovered = hover_map.get(&PointerId::Mouse).unwrap();
708        assert!(
709            !mouse_hovered.contains_key(&entity_a),
710            "original hover should be evicted"
711        );
712        assert!(
713            mouse_hovered.contains_key(&entity_b),
714            "captured entity should be sole entry"
715        );
716        assert_eq!(mouse_hovered.len(), 1);
717    }
718
719    #[test]
720    fn capture_does_not_affect_uncaptured_pointers() {
721        let camera = Entity::from_bits(1);
722        let entity_a = Entity::from_bits(2);
723        let entity_b = Entity::from_bits(3);
724        let hit = make_hit(camera);
725
726        let touch_id = PointerId::Touch(0);
727
728        let mut hover_map = HoverMap::default();
729        let mut mouse_map = EntityHashMap::new();
730        mouse_map.insert(entity_a, hit.clone());
731        hover_map.insert(PointerId::Mouse, mouse_map);
732
733        let mut touch_map = EntityHashMap::new();
734        touch_map.insert(entity_b, hit.clone());
735        hover_map.insert(touch_id, touch_map);
736
737        let mut capture_map = PointerCaptureMap::default();
738        capture_map.capture(touch_id, entity_a, hit.clone());
739
740        apply_pointer_captures(&capture_map, &mut hover_map);
741
742        let mouse_hovered = hover_map.get(&PointerId::Mouse).unwrap();
743        assert!(mouse_hovered.contains_key(&entity_a));
744        assert_eq!(mouse_hovered.len(), 1);
745
746        let touch_hovered = hover_map.get(&touch_id).unwrap();
747        assert!(touch_hovered.contains_key(&entity_a));
748        assert!(!touch_hovered.contains_key(&entity_b));
749        assert_eq!(touch_hovered.len(), 1);
750    }
751
752    #[test]
753    fn capture_creates_entry_for_pointer_absent_from_hover_map() {
754        let camera = Entity::from_bits(1);
755        let entity = Entity::from_bits(2);
756        let hit = make_hit(camera);
757
758        let mut hover_map = HoverMap::default();
759
760        let mut capture_map = PointerCaptureMap::default();
761        capture_map.capture(PointerId::Mouse, entity, hit.clone());
762
763        apply_pointer_captures(&capture_map, &mut hover_map);
764
765        let mouse_hovered = hover_map.get(&PointerId::Mouse).unwrap();
766        assert!(mouse_hovered.contains_key(&entity));
767        assert_eq!(mouse_hovered.len(), 1);
768    }
769
770    #[test]
771    fn pointer_capture_map_api() {
772        let camera = Entity::from_bits(1);
773        let entity = Entity::from_bits(2);
774        let hit = make_hit(camera);
775
776        let mut map = PointerCaptureMap::default();
777
778        assert!(!map.is_captured(&PointerId::Mouse));
779        assert!(map.get(&PointerId::Mouse).is_none());
780
781        map.capture(PointerId::Mouse, entity, hit);
782        assert!(map.is_captured(&PointerId::Mouse));
783        let (captured_entity, _) = map.get(&PointerId::Mouse).unwrap();
784        assert_eq!(captured_entity, entity);
785
786        let released = map.release(PointerId::Mouse);
787        assert_eq!(released.unwrap().0, entity);
788        assert!(!map.is_captured(&PointerId::Mouse));
789        assert!(map.get(&PointerId::Mouse).is_none());
790
791        // Double release is a no-op.
792        assert!(map.release(PointerId::Mouse).is_none());
793    }
794
795    fn make_pointer_input(pointer_id: PointerId, action: PointerAction) -> PointerInput {
796        use bevy_camera::{ManualTextureViewHandle, NormalizedRenderTarget};
797        use bevy_math::Vec2;
798        PointerInput::new(
799            pointer_id,
800            crate::pointer::Location {
801                target: NormalizedRenderTarget::TextureView(ManualTextureViewHandle(5)),
802                position: Vec2::ZERO,
803            },
804            action,
805        )
806    }
807
808    #[test]
809    fn release_action_releases_capture() {
810        let camera = Entity::from_bits(1);
811        let entity = Entity::from_bits(2);
812        let hit = make_hit(camera);
813
814        let mut capture_map = PointerCaptureMap::default();
815        capture_map.capture(PointerId::Mouse, entity, hit);
816
817        let inputs = [make_pointer_input(
818            PointerId::Mouse,
819            PointerAction::Release(crate::pointer::PointerButton::Primary),
820        )];
821        release_captures_on_release_or_cancel(&inputs, &mut capture_map);
822
823        assert!(!capture_map.is_captured(&PointerId::Mouse));
824    }
825
826    #[test]
827    fn cancel_action_releases_capture() {
828        let camera = Entity::from_bits(1);
829        let entity = Entity::from_bits(2);
830        let hit = make_hit(camera);
831
832        let mut capture_map = PointerCaptureMap::default();
833        capture_map.capture(PointerId::Mouse, entity, hit);
834
835        let inputs = [make_pointer_input(PointerId::Mouse, PointerAction::Cancel)];
836        release_captures_on_release_or_cancel(&inputs, &mut capture_map);
837
838        assert!(!capture_map.is_captured(&PointerId::Mouse));
839    }
840
841    #[test]
842    fn generate_hovermap_cleans_up_capture_for_removed_pointer() {
843        let mut app = bevy_app::App::new();
844        app.init_resource::<HoverMap>()
845            .init_resource::<PreviousHoverMap>()
846            .init_resource::<PointerCaptureMap>()
847            .add_message::<PointerInput>()
848            .add_message::<backend::PointerHits>();
849        // No pointer entity is spawned, so `PointerId::Mouse` is not active.
850
851        let camera = Entity::from_bits(1);
852        let captured_entity = app.world_mut().spawn_empty().id();
853        app.world_mut().resource_mut::<PointerCaptureMap>().capture(
854            PointerId::Mouse,
855            captured_entity,
856            make_hit(camera),
857        );
858
859        assert!(app.world_mut().run_system_cached(generate_hovermap).is_ok());
860
861        assert!(!app
862            .world()
863            .resource::<PointerCaptureMap>()
864            .is_captured(&PointerId::Mouse));
865    }
866}