//! 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 pointer_hits_reader: MessageReader<backend::PointerHits>,69mut pointer_input_reader: MessageReader<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(83&mut pointer_hits_reader,84&mut over_map,85&mut pointer_input_reader,86);87build_hover_map(&pointers, pickable, &over_map, &mut hover_map);88}8990/// Clear non-empty local maps, reusing allocated memory.91fn reset_maps(92hover_map: &mut HoverMap,93previous_hover_map: &mut PreviousHoverMap,94over_map: &mut OverMap,95pointers: &Query<&PointerId>,96) {97// Swap the previous and current hover maps. This results in the previous values being stored in98// `PreviousHoverMap`. Swapping is okay because we clear the `HoverMap` which now holds stale99// data. This process is done without any allocations.100core::mem::swap(&mut previous_hover_map.0, &mut hover_map.0);101102for entity_set in hover_map.values_mut() {103entity_set.clear();104}105for layer_map in over_map.values_mut() {106layer_map.clear();107}108109// Clear pointers from the maps if they have been removed.110let active_pointers: Vec<PointerId> = pointers.iter().copied().collect();111hover_map.retain(|pointer, _| active_pointers.contains(pointer));112over_map.retain(|pointer, _| active_pointers.contains(pointer));113}114115/// Build an ordered map of entities that are under each pointer116fn build_over_map(117pointer_hit_reader: &mut MessageReader<backend::PointerHits>,118pointer_over_map: &mut Local<OverMap>,119pointer_input_reader: &mut MessageReader<PointerInput>,120) {121let cancelled_pointers: HashSet<PointerId> = pointer_input_reader122.read()123.filter_map(|p| {124if let PointerAction::Cancel = p.action {125Some(p.pointer_id)126} else {127None128}129})130.collect();131132for entities_under_pointer in pointer_hit_reader133.read()134.filter(|e| !cancelled_pointers.contains(&e.pointer))135{136let pointer = entities_under_pointer.pointer;137let layer_map = pointer_over_map.entry(pointer).or_default();138for (entity, pick_data) in entities_under_pointer.picks.iter() {139let layer = entities_under_pointer.order;140let hits = layer_map.entry(FloatOrd(layer)).or_default();141hits.push((*entity, pick_data.clone()));142}143}144145for layers in pointer_over_map.values_mut() {146for hits in layers.values_mut() {147hits.sort_by_key(|(_, hit)| FloatOrd(hit.depth));148}149}150}151152/// Build an unsorted set of hovered entities, accounting for depth, layer, and [`Pickable`]. Note153/// that unlike the pointer map, this uses [`Pickable`] to determine if lower entities receive hover154/// focus. Often, only a single entity per pointer will be hovered.155fn build_hover_map(156pointers: &Query<&PointerId>,157pickable: Query<&Pickable>,158over_map: &Local<OverMap>,159// Output160hover_map: &mut HoverMap,161) {162for pointer_id in pointers.iter() {163let pointer_entity_set = hover_map.entry(*pointer_id).or_default();164if let Some(layer_map) = over_map.get(pointer_id) {165// Note we reverse here to start from the highest layer first.166for (entity, pick_data) in layer_map.values().rev().flatten() {167if let Ok(pickable) = pickable.get(*entity) {168if pickable.is_hoverable {169pointer_entity_set.insert(*entity, pick_data.clone());170}171if pickable.should_block_lower {172break;173}174} else {175pointer_entity_set.insert(*entity, pick_data.clone()); // Emit events by default176break; // Entities block by default so we break out of the loop177}178}179}180}181}182183/// A component that aggregates picking interaction state of this entity across all pointers.184///185/// Unlike bevy's `Interaction` component, this is an aggregate of the state of all pointers186/// interacting with this entity. Aggregation is done by taking the interaction with the highest187/// precedence.188///189/// For example, if we have an entity that is being hovered by one pointer, and pressed by another,190/// the entity will be considered pressed. If that entity is instead being hovered by both pointers,191/// it will be considered hovered.192#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]193#[reflect(Component, Default, PartialEq, Debug, Clone)]194pub enum PickingInteraction {195/// The entity is being pressed down by a pointer.196Pressed = 2,197/// The entity is being hovered by a pointer.198Hovered = 1,199/// No pointers are interacting with this entity.200#[default]201None = 0,202}203204/// Uses [`HoverMap`] changes to update [`PointerInteraction`] and [`PickingInteraction`] components.205pub fn update_interactions(206// Input207hover_map: Res<HoverMap>,208previous_hover_map: Res<PreviousHoverMap>,209// Outputs210mut commands: Commands,211mut pointers: Query<(&PointerId, &PointerPress, &mut PointerInteraction)>,212mut interact: Query<&mut PickingInteraction>,213) {214// Create a map to hold the aggregated interaction for each entity. This is needed because we215// need to be able to insert the interaction component on entities if they do not exist. To do216// so we need to know the final aggregated interaction state to avoid the scenario where we set217// an entity to `Pressed`, then overwrite that with a lower precedent like `Hovered`.218let mut new_interaction_state = HashMap::<Entity, PickingInteraction>::default();219for (pointer, pointer_press, mut pointer_interaction) in &mut pointers {220if let Some(pointers_hovered_entities) = hover_map.get(pointer) {221// Insert a sorted list of hit entities into the pointer's interaction component.222let mut sorted_entities: Vec<_> = pointers_hovered_entities.clone().drain().collect();223sorted_entities.sort_by_key(|(_, hit)| FloatOrd(hit.depth));224pointer_interaction.sorted_entities = sorted_entities;225226for hovered_entity in pointers_hovered_entities.iter().map(|(entity, _)| entity) {227merge_interaction_states(pointer_press, hovered_entity, &mut new_interaction_state);228}229}230}231232// Take the aggregated entity states and update or insert the component if missing.233for (&hovered_entity, &new_interaction) in new_interaction_state.iter() {234if let Ok(mut interaction) = interact.get_mut(hovered_entity) {235interaction.set_if_neq(new_interaction);236} else if let Ok(mut entity_commands) = commands.get_entity(hovered_entity) {237entity_commands.try_insert(new_interaction);238}239}240241// Clear all previous hover data from pointers that are no longer hovering any entities.242// We do this last to preserve change detection for picking interactions.243for (pointer, _, _) in &mut pointers {244let Some(previously_hovered_entities) = previous_hover_map.get(pointer) else {245continue;246};247248for entity in previously_hovered_entities.keys() {249if !new_interaction_state.contains_key(entity)250&& let Ok(mut interaction) = interact.get_mut(*entity)251{252interaction.set_if_neq(PickingInteraction::None);253}254}255}256}257258/// Merge the interaction state of this entity into the aggregated map.259fn merge_interaction_states(260pointer_press: &PointerPress,261hovered_entity: &Entity,262new_interaction_state: &mut HashMap<Entity, PickingInteraction>,263) {264let new_interaction = match pointer_press.is_any_pressed() {265true => PickingInteraction::Pressed,266false => PickingInteraction::Hovered,267};268269if let Some(old_interaction) = new_interaction_state.get_mut(hovered_entity) {270// Only update if the new value has a higher precedence than the old value.271if *old_interaction != new_interaction272&& matches!(273(*old_interaction, new_interaction),274(PickingInteraction::Hovered, PickingInteraction::Pressed)275| (PickingInteraction::None, PickingInteraction::Pressed)276| (PickingInteraction::None, PickingInteraction::Hovered)277)278{279*old_interaction = new_interaction;280}281} else {282new_interaction_state.insert(*hovered_entity, new_interaction);283}284}285286/// A component that allows users to use regular Bevy change detection to determine when the pointer287/// enters or leaves an entity. Users should insert this component on an entity to indicate interest288/// in knowing about hover state changes.289///290/// The component's boolean value will be `true` whenever the pointer is currently directly hovering291/// over the entity, or any of the entity's descendants (as defined by the [`ChildOf`]292/// relationship). This is consistent with the behavior of the CSS `:hover` pseudo-class, which293/// applies to the element and all of its descendants.294///295/// The contained boolean value is guaranteed to only be mutated when the pointer enters or leaves296/// the entity, allowing Bevy change detection to be used efficiently. This is in contrast to the297/// [`HoverMap`] resource, which is updated every frame.298///299/// Typically, a simple hoverable entity or widget will have this component added to it. More300/// complex widgets can have this component added to each hoverable part.301///302/// The computational cost of keeping the `Hovered` components up to date is relatively cheap, and303/// linear in the number of entities that have the [`Hovered`] component inserted.304#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]305#[reflect(Component, Default, PartialEq, Debug, Clone)]306#[component(immutable)]307pub struct Hovered(pub bool);308309impl Hovered {310/// Get whether the entity is currently hovered.311pub fn get(&self) -> bool {312self.0313}314}315316/// A component that allows users to use regular Bevy change detection to determine when the pointer317/// is directly hovering over an entity. Users should insert this component on an entity to indicate318/// interest in knowing about hover state changes.319///320/// This is similar to [`Hovered`] component, except that it does not include descendants in the321/// hover state.322#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]323#[reflect(Component, Default, PartialEq, Debug, Clone)]324#[component(immutable)]325pub struct DirectlyHovered(pub bool);326327impl DirectlyHovered {328/// Get whether the entity is currently hovered.329pub fn get(&self) -> bool {330self.0331}332}333334/// Uses [`HoverMap`] changes to update [`Hovered`] components.335pub fn update_is_hovered(336hover_map: Option<Res<HoverMap>>,337mut hovers: Query<(Entity, &Hovered)>,338parent_query: Query<&ChildOf>,339mut commands: Commands,340) {341// Don't do any work if there's no hover map.342let Some(hover_map) = hover_map else { return };343344// Don't bother collecting ancestors if there are no hovers.345if hovers.is_empty() {346return;347}348349// Algorithm: for each entity having a `Hovered` component, we want to know if the current350// entry in the hover map is "within" (that is, in the set of descendants of) that entity. Rather351// than doing an expensive breadth-first traversal of children, instead start with the hovermap352// entry and search upwards. We can make this even cheaper by building a set of ancestors for353// the hovermap entry, and then testing each `Hovered` entity against that set.354355// A set which contains the hovered for the current pointer entity and its ancestors. The356// capacity is based on the likely tree depth of the hierarchy, which is typically greater for357// UI (because of layout issues) than for 3D scenes. A depth of 32 is a reasonable upper bound358// for most use cases.359let mut hover_ancestors = EntityHashSet::with_capacity(32);360if let Some(map) = hover_map.get(&PointerId::Mouse) {361for hovered_entity in map.keys() {362hover_ancestors.insert(*hovered_entity);363hover_ancestors.extend(parent_query.iter_ancestors(*hovered_entity));364}365}366367// For each hovered entity, it is considered "hovering" if it's in the set of hovered ancestors.368for (entity, hoverable) in hovers.iter_mut() {369let is_hovering = hover_ancestors.contains(&entity);370if hoverable.0 != is_hovering {371commands.entity(entity).insert(Hovered(is_hovering));372}373}374}375376/// Uses [`HoverMap`] changes to update [`DirectlyHovered`] components.377pub fn update_is_directly_hovered(378hover_map: Option<Res<HoverMap>>,379hovers: Query<(Entity, &DirectlyHovered)>,380mut commands: Commands,381) {382// Don't do any work if there's no hover map.383let Some(hover_map) = hover_map else { return };384385// Don't bother collecting ancestors if there are no hovers.386if hovers.is_empty() {387return;388}389390if let Some(map) = hover_map.get(&PointerId::Mouse) {391// It's hovering if it's in the HoverMap.392for (entity, hoverable) in hovers.iter() {393let is_hovering = map.contains_key(&entity);394if hoverable.0 != is_hovering {395commands.entity(entity).insert(DirectlyHovered(is_hovering));396}397}398} else {399// No hovered entity, reset all hovers.400for (entity, hoverable) in hovers.iter() {401if hoverable.0 {402commands.entity(entity).insert(DirectlyHovered(false));403}404}405}406}407408#[cfg(test)]409mod tests {410use bevy_camera::Camera;411412use super::*;413414#[test]415fn update_is_hovered_memoized() {416let mut world = World::default();417let camera = world.spawn(Camera::default()).id();418419// Setup entities420let hovered_child = world.spawn_empty().id();421let hovered_entity = world.spawn(Hovered(false)).add_child(hovered_child).id();422423// Setup hover map with hovered_entity hovered by mouse424let mut hover_map = HoverMap::default();425let mut entity_map = HashMap::new();426entity_map.insert(427hovered_child,428HitData {429depth: 0.0,430camera,431position: None,432normal: None,433},434);435hover_map.insert(PointerId::Mouse, entity_map);436world.insert_resource(hover_map);437438// Run the system439assert!(world.run_system_cached(update_is_hovered).is_ok());440441// Check to insure that the hovered entity has the Hovered component set to true442let hover = world.entity(hovered_entity).get_ref::<Hovered>().unwrap();443assert!(hover.get());444assert!(hover.is_changed());445446// Now do it again, but don't change the hover map.447world.increment_change_tick();448449assert!(world.run_system_cached(update_is_hovered).is_ok());450let hover = world.entity(hovered_entity).get_ref::<Hovered>().unwrap();451assert!(hover.get());452453// Should not be changed454// NOTE: Test doesn't work - thinks it is always changed455// assert!(!hover.is_changed());456457// Clear the hover map and run again.458world.insert_resource(HoverMap::default());459world.increment_change_tick();460461assert!(world.run_system_cached(update_is_hovered).is_ok());462let hover = world.entity(hovered_entity).get_ref::<Hovered>().unwrap();463assert!(!hover.get());464assert!(hover.is_changed());465}466467#[test]468fn update_is_hovered_direct_self() {469let mut world = World::default();470let camera = world.spawn(Camera::default()).id();471472// Setup entities473let hovered_entity = world.spawn(DirectlyHovered(false)).id();474475// Setup hover map with hovered_entity hovered by mouse476let mut hover_map = HoverMap::default();477let mut entity_map = HashMap::new();478entity_map.insert(479hovered_entity,480HitData {481depth: 0.0,482camera,483position: None,484normal: None,485},486);487hover_map.insert(PointerId::Mouse, entity_map);488world.insert_resource(hover_map);489490// Run the system491assert!(world.run_system_cached(update_is_directly_hovered).is_ok());492493// Check to insure that the hovered entity has the DirectlyHovered component set to true494let hover = world495.entity(hovered_entity)496.get_ref::<DirectlyHovered>()497.unwrap();498assert!(hover.get());499assert!(hover.is_changed());500501// Now do it again, but don't change the hover map.502world.increment_change_tick();503504assert!(world.run_system_cached(update_is_directly_hovered).is_ok());505let hover = world506.entity(hovered_entity)507.get_ref::<DirectlyHovered>()508.unwrap();509assert!(hover.get());510511// Should not be changed512// NOTE: Test doesn't work - thinks it is always changed513// assert!(!hover.is_changed());514515// Clear the hover map and run again.516world.insert_resource(HoverMap::default());517world.increment_change_tick();518519assert!(world.run_system_cached(update_is_directly_hovered).is_ok());520let hover = world521.entity(hovered_entity)522.get_ref::<DirectlyHovered>()523.unwrap();524assert!(!hover.get());525assert!(hover.is_changed());526}527528#[test]529fn update_is_hovered_direct_child() {530let mut world = World::default();531let camera = world.spawn(Camera::default()).id();532533// Setup entities534let hovered_child = world.spawn_empty().id();535let hovered_entity = world536.spawn(DirectlyHovered(false))537.add_child(hovered_child)538.id();539540// Setup hover map with hovered_entity hovered by mouse541let mut hover_map = HoverMap::default();542let mut entity_map = HashMap::new();543entity_map.insert(544hovered_child,545HitData {546depth: 0.0,547camera,548position: None,549normal: None,550},551);552hover_map.insert(PointerId::Mouse, entity_map);553world.insert_resource(hover_map);554555// Run the system556assert!(world.run_system_cached(update_is_directly_hovered).is_ok());557558// Check to insure that the DirectlyHovered component is still false559let hover = world560.entity(hovered_entity)561.get_ref::<DirectlyHovered>()562.unwrap();563assert!(!hover.get());564assert!(hover.is_changed());565}566}567568569