//! Determines which entities are being hovered by which pointers.1//!2//! The most important type in this module is the [`HoverMap`], which maps pointers to the entities3//! they are hovering over.45use alloc::collections::BTreeMap;6use core::fmt::Debug;7use std::collections::HashSet;89use crate::{10backend::{self, HitData},11pointer::{PointerAction, PointerId, PointerInput, PointerInteraction, PointerPress},12Pickable,13};1415use bevy_derive::{Deref, DerefMut};16use bevy_ecs::{entity::EntityHashSet, prelude::*};17use bevy_math::FloatOrd;18use bevy_platform::collections::HashMap;19use bevy_reflect::prelude::*;2021type DepthSortedHits = Vec<(Entity, HitData)>;2223/// Events returned from backends can be grouped with an order field. This allows picking to work24/// with multiple layers of rendered output to the same render target.25type PickLayer = FloatOrd;2627/// Maps [`PickLayer`]s to the map of entities within that pick layer, sorted by depth.28type LayerMap = BTreeMap<PickLayer, DepthSortedHits>;2930/// Maps Pointers to a [`LayerMap`]. Note this is much more complex than the [`HoverMap`] because31/// this data structure is used to sort entities by layer then depth for every pointer.32type OverMap = HashMap<PointerId, LayerMap>;3334/// The source of truth for all hover state. This is used to determine what events to send, and what35/// state components should be in.36///37/// Maps pointers to the entities they are hovering over.38///39/// "Hovering" refers to the *hover* state, which is not the same as whether or not a picking40/// backend is reporting hits between a pointer and an entity. A pointer is "hovering" an entity41/// only if the pointer is hitting the entity (as reported by a picking backend) *and* no entities42/// between it and the pointer block interactions.43///44/// For example, if a pointer is hitting a UI button and a 3d mesh, but the button is in front of45/// the mesh, the UI button will be hovered, but the mesh will not. Unless, the [`Pickable`]46/// component is present with [`should_block_lower`](Pickable::should_block_lower) set to `false`.47///48/// # Advanced Users49///50/// If you want to completely replace the provided picking events or state produced by this plugin,51/// you can use this resource to do that. All of the event systems for picking are built *on top of*52/// this authoritative hover state, and you can do the same. You can also use the53/// [`PreviousHoverMap`] as a robust way of determining changes in hover state from the previous54/// update.55#[derive(Debug, Deref, DerefMut, Default, Resource)]56pub struct HoverMap(pub HashMap<PointerId, HashMap<Entity, HitData>>);5758/// The previous state of the hover map, used to track changes to hover state.59#[derive(Debug, Deref, DerefMut, Default, Resource)]60pub struct PreviousHoverMap(pub HashMap<PointerId, HashMap<Entity, HitData>>);6162/// Coalesces all data from inputs and backends to generate a map of the currently hovered entities.63/// This is the final focusing step to determine which entity the pointer is hovering over.64pub fn generate_hovermap(65// Inputs66pickable: Query<&Pickable>,67pointers: Query<&PointerId>,68mut under_pointer: EventReader<backend::PointerHits>,69mut pointer_input: EventReader<PointerInput>,70// Local71mut over_map: Local<OverMap>,72// Output73mut hover_map: ResMut<HoverMap>,74mut previous_hover_map: ResMut<PreviousHoverMap>,75) {76reset_maps(77&mut hover_map,78&mut previous_hover_map,79&mut over_map,80&pointers,81);82build_over_map(&mut under_pointer, &mut over_map, &mut pointer_input);83build_hover_map(&pointers, pickable, &over_map, &mut hover_map);84}8586/// Clear non-empty local maps, reusing allocated memory.87fn reset_maps(88hover_map: &mut HoverMap,89previous_hover_map: &mut PreviousHoverMap,90over_map: &mut OverMap,91pointers: &Query<&PointerId>,92) {93// Swap the previous and current hover maps. This results in the previous values being stored in94// `PreviousHoverMap`. Swapping is okay because we clear the `HoverMap` which now holds stale95// data. This process is done without any allocations.96core::mem::swap(&mut previous_hover_map.0, &mut hover_map.0);9798for entity_set in hover_map.values_mut() {99entity_set.clear();100}101for layer_map in over_map.values_mut() {102layer_map.clear();103}104105// Clear pointers from the maps if they have been removed.106let active_pointers: Vec<PointerId> = pointers.iter().copied().collect();107hover_map.retain(|pointer, _| active_pointers.contains(pointer));108over_map.retain(|pointer, _| active_pointers.contains(pointer));109}110111/// Build an ordered map of entities that are under each pointer112fn build_over_map(113backend_events: &mut EventReader<backend::PointerHits>,114pointer_over_map: &mut Local<OverMap>,115pointer_input: &mut EventReader<PointerInput>,116) {117let cancelled_pointers: HashSet<PointerId> = pointer_input118.read()119.filter_map(|p| {120if let PointerAction::Cancel = p.action {121Some(p.pointer_id)122} else {123None124}125})126.collect();127128for entities_under_pointer in backend_events129.read()130.filter(|e| !cancelled_pointers.contains(&e.pointer))131{132let pointer = entities_under_pointer.pointer;133let layer_map = pointer_over_map.entry(pointer).or_default();134for (entity, pick_data) in entities_under_pointer.picks.iter() {135let layer = entities_under_pointer.order;136let hits = layer_map.entry(FloatOrd(layer)).or_default();137hits.push((*entity, pick_data.clone()));138}139}140141for layers in pointer_over_map.values_mut() {142for hits in layers.values_mut() {143hits.sort_by_key(|(_, hit)| FloatOrd(hit.depth));144}145}146}147148/// Build an unsorted set of hovered entities, accounting for depth, layer, and [`Pickable`]. Note149/// that unlike the pointer map, this uses [`Pickable`] to determine if lower entities receive hover150/// focus. Often, only a single entity per pointer will be hovered.151fn build_hover_map(152pointers: &Query<&PointerId>,153pickable: Query<&Pickable>,154over_map: &Local<OverMap>,155// Output156hover_map: &mut HoverMap,157) {158for pointer_id in pointers.iter() {159let pointer_entity_set = hover_map.entry(*pointer_id).or_default();160if let Some(layer_map) = over_map.get(pointer_id) {161// Note we reverse here to start from the highest layer first.162for (entity, pick_data) in layer_map.values().rev().flatten() {163if let Ok(pickable) = pickable.get(*entity) {164if pickable.is_hoverable {165pointer_entity_set.insert(*entity, pick_data.clone());166}167if pickable.should_block_lower {168break;169}170} else {171pointer_entity_set.insert(*entity, pick_data.clone()); // Emit events by default172break; // Entities block by default so we break out of the loop173}174}175}176}177}178179/// A component that aggregates picking interaction state of this entity across all pointers.180///181/// Unlike bevy's `Interaction` component, this is an aggregate of the state of all pointers182/// interacting with this entity. Aggregation is done by taking the interaction with the highest183/// precedence.184///185/// For example, if we have an entity that is being hovered by one pointer, and pressed by another,186/// the entity will be considered pressed. If that entity is instead being hovered by both pointers,187/// it will be considered hovered.188#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]189#[reflect(Component, Default, PartialEq, Debug, Clone)]190pub enum PickingInteraction {191/// The entity is being pressed down by a pointer.192Pressed = 2,193/// The entity is being hovered by a pointer.194Hovered = 1,195/// No pointers are interacting with this entity.196#[default]197None = 0,198}199200/// Uses [`HoverMap`] changes to update [`PointerInteraction`] and [`PickingInteraction`] components.201pub fn update_interactions(202// Input203hover_map: Res<HoverMap>,204previous_hover_map: Res<PreviousHoverMap>,205// Outputs206mut commands: Commands,207mut pointers: Query<(&PointerId, &PointerPress, &mut PointerInteraction)>,208mut interact: Query<&mut PickingInteraction>,209) {210// Create a map to hold the aggregated interaction for each entity. This is needed because we211// need to be able to insert the interaction component on entities if they do not exist. To do212// so we need to know the final aggregated interaction state to avoid the scenario where we set213// an entity to `Pressed`, then overwrite that with a lower precedent like `Hovered`.214let mut new_interaction_state = HashMap::<Entity, PickingInteraction>::default();215for (pointer, pointer_press, mut pointer_interaction) in &mut pointers {216if let Some(pointers_hovered_entities) = hover_map.get(pointer) {217// Insert a sorted list of hit entities into the pointer's interaction component.218let mut sorted_entities: Vec<_> = pointers_hovered_entities.clone().drain().collect();219sorted_entities.sort_by_key(|(_, hit)| FloatOrd(hit.depth));220pointer_interaction.sorted_entities = sorted_entities;221222for hovered_entity in pointers_hovered_entities.iter().map(|(entity, _)| entity) {223merge_interaction_states(pointer_press, hovered_entity, &mut new_interaction_state);224}225}226}227228// Take the aggregated entity states and update or insert the component if missing.229for (&hovered_entity, &new_interaction) in new_interaction_state.iter() {230if let Ok(mut interaction) = interact.get_mut(hovered_entity) {231interaction.set_if_neq(new_interaction);232} else if let Ok(mut entity_commands) = commands.get_entity(hovered_entity) {233entity_commands.try_insert(new_interaction);234}235}236237// Clear all previous hover data from pointers that are no longer hovering any entities.238// We do this last to preserve change detection for picking interactions.239for (pointer, _, _) in &mut pointers {240let Some(previously_hovered_entities) = previous_hover_map.get(pointer) else {241continue;242};243244for entity in previously_hovered_entities.keys() {245if !new_interaction_state.contains_key(entity)246&& let Ok(mut interaction) = interact.get_mut(*entity)247{248interaction.set_if_neq(PickingInteraction::None);249}250}251}252}253254/// Merge the interaction state of this entity into the aggregated map.255fn merge_interaction_states(256pointer_press: &PointerPress,257hovered_entity: &Entity,258new_interaction_state: &mut HashMap<Entity, PickingInteraction>,259) {260let new_interaction = match pointer_press.is_any_pressed() {261true => PickingInteraction::Pressed,262false => PickingInteraction::Hovered,263};264265if let Some(old_interaction) = new_interaction_state.get_mut(hovered_entity) {266// Only update if the new value has a higher precedence than the old value.267if *old_interaction != new_interaction268&& matches!(269(*old_interaction, new_interaction),270(PickingInteraction::Hovered, PickingInteraction::Pressed)271| (PickingInteraction::None, PickingInteraction::Pressed)272| (PickingInteraction::None, PickingInteraction::Hovered)273)274{275*old_interaction = new_interaction;276}277} else {278new_interaction_state.insert(*hovered_entity, new_interaction);279}280}281282/// A component that allows users to use regular Bevy change detection to determine when the pointer283/// enters or leaves an entity. Users should insert this component on an entity to indicate interest284/// in knowing about hover state changes.285///286/// The component's boolean value will be `true` whenever the pointer is currently directly hovering287/// over the entity, or any of the entity's descendants (as defined by the [`ChildOf`]288/// relationship). This is consistent with the behavior of the CSS `:hover` pseudo-class, which289/// applies to the element and all of its descendants.290///291/// The contained boolean value is guaranteed to only be mutated when the pointer enters or leaves292/// the entity, allowing Bevy change detection to be used efficiently. This is in contrast to the293/// [`HoverMap`] resource, which is updated every frame.294///295/// Typically, a simple hoverable entity or widget will have this component added to it. More296/// complex widgets can have this component added to each hoverable part.297///298/// The computational cost of keeping the `Hovered` components up to date is relatively cheap, and299/// linear in the number of entities that have the [`Hovered`] component inserted.300#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]301#[reflect(Component, Default, PartialEq, Debug, Clone)]302#[component(immutable)]303pub struct Hovered(pub bool);304305impl Hovered {306/// Get whether the entity is currently hovered.307pub fn get(&self) -> bool {308self.0309}310}311312/// A component that allows users to use regular Bevy change detection to determine when the pointer313/// is directly hovering over an entity. Users should insert this component on an entity to indicate314/// interest in knowing about hover state changes.315///316/// This is similar to [`Hovered`] component, except that it does not include descendants in the317/// hover state.318#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]319#[reflect(Component, Default, PartialEq, Debug, Clone)]320#[component(immutable)]321pub struct DirectlyHovered(pub bool);322323impl DirectlyHovered {324/// Get whether the entity is currently hovered.325pub fn get(&self) -> bool {326self.0327}328}329330/// Uses [`HoverMap`] changes to update [`Hovered`] components.331pub fn update_is_hovered(332hover_map: Option<Res<HoverMap>>,333mut hovers: Query<(Entity, &Hovered)>,334parent_query: Query<&ChildOf>,335mut commands: Commands,336) {337// Don't do any work if there's no hover map.338let Some(hover_map) = hover_map else { return };339340// Don't bother collecting ancestors if there are no hovers.341if hovers.is_empty() {342return;343}344345// Algorithm: for each entity having a `Hovered` component, we want to know if the current346// entry in the hover map is "within" (that is, in the set of descendants of) that entity. Rather347// than doing an expensive breadth-first traversal of children, instead start with the hovermap348// entry and search upwards. We can make this even cheaper by building a set of ancestors for349// the hovermap entry, and then testing each `Hovered` entity against that set.350351// A set which contains the hovered for the current pointer entity and its ancestors. The352// capacity is based on the likely tree depth of the hierarchy, which is typically greater for353// UI (because of layout issues) than for 3D scenes. A depth of 32 is a reasonable upper bound354// for most use cases.355let mut hover_ancestors = EntityHashSet::with_capacity(32);356if let Some(map) = hover_map.get(&PointerId::Mouse) {357for hovered_entity in map.keys() {358hover_ancestors.insert(*hovered_entity);359hover_ancestors.extend(parent_query.iter_ancestors(*hovered_entity));360}361}362363// For each hovered entity, it is considered "hovering" if it's in the set of hovered ancestors.364for (entity, hoverable) in hovers.iter_mut() {365let is_hovering = hover_ancestors.contains(&entity);366if hoverable.0 != is_hovering {367commands.entity(entity).insert(Hovered(is_hovering));368}369}370}371372/// Uses [`HoverMap`] changes to update [`DirectlyHovered`] components.373pub fn update_is_directly_hovered(374hover_map: Option<Res<HoverMap>>,375hovers: Query<(Entity, &DirectlyHovered)>,376mut commands: Commands,377) {378// Don't do any work if there's no hover map.379let Some(hover_map) = hover_map else { return };380381// Don't bother collecting ancestors if there are no hovers.382if hovers.is_empty() {383return;384}385386if let Some(map) = hover_map.get(&PointerId::Mouse) {387// It's hovering if it's in the HoverMap.388for (entity, hoverable) in hovers.iter() {389let is_hovering = map.contains_key(&entity);390if hoverable.0 != is_hovering {391commands.entity(entity).insert(DirectlyHovered(is_hovering));392}393}394} else {395// No hovered entity, reset all hovers.396for (entity, hoverable) in hovers.iter() {397if hoverable.0 {398commands.entity(entity).insert(DirectlyHovered(false));399}400}401}402}403404#[cfg(test)]405mod tests {406use bevy_camera::Camera;407408use super::*;409410#[test]411fn update_is_hovered_memoized() {412let mut world = World::default();413let camera = world.spawn(Camera::default()).id();414415// Setup entities416let hovered_child = world.spawn_empty().id();417let hovered_entity = world.spawn(Hovered(false)).add_child(hovered_child).id();418419// Setup hover map with hovered_entity hovered by mouse420let mut hover_map = HoverMap::default();421let mut entity_map = HashMap::new();422entity_map.insert(423hovered_child,424HitData {425depth: 0.0,426camera,427position: None,428normal: None,429},430);431hover_map.insert(PointerId::Mouse, entity_map);432world.insert_resource(hover_map);433434// Run the system435assert!(world.run_system_cached(update_is_hovered).is_ok());436437// Check to insure that the hovered entity has the Hovered component set to true438let hover = world.entity(hovered_entity).get_ref::<Hovered>().unwrap();439assert!(hover.get());440assert!(hover.is_changed());441442// Now do it again, but don't change the hover map.443world.increment_change_tick();444445assert!(world.run_system_cached(update_is_hovered).is_ok());446let hover = world.entity(hovered_entity).get_ref::<Hovered>().unwrap();447assert!(hover.get());448449// Should not be changed450// NOTE: Test doesn't work - thinks it is always changed451// assert!(!hover.is_changed());452453// Clear the hover map and run again.454world.insert_resource(HoverMap::default());455world.increment_change_tick();456457assert!(world.run_system_cached(update_is_hovered).is_ok());458let hover = world.entity(hovered_entity).get_ref::<Hovered>().unwrap();459assert!(!hover.get());460assert!(hover.is_changed());461}462463#[test]464fn update_is_hovered_direct_self() {465let mut world = World::default();466let camera = world.spawn(Camera::default()).id();467468// Setup entities469let hovered_entity = world.spawn(DirectlyHovered(false)).id();470471// Setup hover map with hovered_entity hovered by mouse472let mut hover_map = HoverMap::default();473let mut entity_map = HashMap::new();474entity_map.insert(475hovered_entity,476HitData {477depth: 0.0,478camera,479position: None,480normal: None,481},482);483hover_map.insert(PointerId::Mouse, entity_map);484world.insert_resource(hover_map);485486// Run the system487assert!(world.run_system_cached(update_is_directly_hovered).is_ok());488489// Check to insure that the hovered entity has the DirectlyHovered component set to true490let hover = world491.entity(hovered_entity)492.get_ref::<DirectlyHovered>()493.unwrap();494assert!(hover.get());495assert!(hover.is_changed());496497// Now do it again, but don't change the hover map.498world.increment_change_tick();499500assert!(world.run_system_cached(update_is_directly_hovered).is_ok());501let hover = world502.entity(hovered_entity)503.get_ref::<DirectlyHovered>()504.unwrap();505assert!(hover.get());506507// Should not be changed508// NOTE: Test doesn't work - thinks it is always changed509// assert!(!hover.is_changed());510511// Clear the hover map and run again.512world.insert_resource(HoverMap::default());513world.increment_change_tick();514515assert!(world.run_system_cached(update_is_directly_hovered).is_ok());516let hover = world517.entity(hovered_entity)518.get_ref::<DirectlyHovered>()519.unwrap();520assert!(!hover.get());521assert!(hover.is_changed());522}523524#[test]525fn update_is_hovered_direct_child() {526let mut world = World::default();527let camera = world.spawn(Camera::default()).id();528529// Setup entities530let hovered_child = world.spawn_empty().id();531let hovered_entity = world532.spawn(DirectlyHovered(false))533.add_child(hovered_child)534.id();535536// Setup hover map with hovered_entity hovered by mouse537let mut hover_map = HoverMap::default();538let mut entity_map = HashMap::new();539entity_map.insert(540hovered_child,541HitData {542depth: 0.0,543camera,544position: None,545normal: None,546},547);548hover_map.insert(PointerId::Mouse, entity_map);549world.insert_resource(hover_map);550551// Run the system552assert!(world.run_system_cached(update_is_directly_hovered).is_ok());553554// Check to insure that the DirectlyHovered component is still false555let hover = world556.entity(hovered_entity)557.get_ref::<DirectlyHovered>()558.unwrap();559assert!(!hover.get());560assert!(hover.is_changed());561}562}563564565