1use 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 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
27type PickLayer = FloatOrd;
30
31type LayerMap = BTreeMap<PickLayer, DepthSortedHits>;
33
34type OverMap = HashMap<PointerId, LayerMap>;
37
38#[derive(Debug, Deref, DerefMut, Default, Resource)]
60pub struct HoverMap(pub HashMap<PointerId, EntityHashMap<HitData>>);
61
62#[derive(Debug, Deref, DerefMut, Default, Resource)]
64pub struct PreviousHoverMap(pub HashMap<PointerId, EntityHashMap<HitData>>);
65
66pub(crate) fn get_hovered_entities(
68 hover_map: &HashMap<PointerId, EntityHashMap<HitData>>,
69 pointer_id: &PointerId,
70) -> EntityHashSet {
71 hover_map
72 .get(pointer_id)
73 .map_or(EntityHashSet::default(), |entity_hit| {
74 entity_hit
75 .iter()
76 .map(|(&entity, _)| entity)
77 .collect::<EntityHashSet>()
78 })
79}
80
81pub(crate) fn is_directly_hovered(
85 hover_map: &HashMap<PointerId, EntityHashMap<HitData>>,
86 pointer_id: &PointerId,
87 entity: &Entity,
88) -> bool {
89 hover_map
90 .get(pointer_id)
91 .is_some_and(|hit_data_map| hit_data_map.contains_key(entity))
92}
93
94pub fn generate_hovermap(
97 pickable: Query<&Pickable>,
99 pointers: Query<&PointerId>,
100 mut pointer_hits_reader: MessageReader<backend::PointerHits>,
101 mut pointer_input_reader: MessageReader<PointerInput>,
102 mut over_map: Local<OverMap>,
104 mut hover_map: ResMut<HoverMap>,
106 mut previous_hover_map: ResMut<PreviousHoverMap>,
107) {
108 reset_maps(
109 &mut hover_map,
110 &mut previous_hover_map,
111 &mut over_map,
112 &pointers,
113 );
114 build_over_map(
115 &mut pointer_hits_reader,
116 &mut over_map,
117 &mut pointer_input_reader,
118 );
119 build_hover_map(&pointers, pickable, &over_map, &mut hover_map);
120}
121
122fn reset_maps(
124 hover_map: &mut HoverMap,
125 previous_hover_map: &mut PreviousHoverMap,
126 over_map: &mut OverMap,
127 pointers: &Query<&PointerId>,
128) {
129 core::mem::swap(&mut previous_hover_map.0, &mut hover_map.0);
133
134 for entity_set in hover_map.values_mut() {
135 entity_set.clear();
136 }
137 for layer_map in over_map.values_mut() {
138 layer_map.clear();
139 }
140
141 let active_pointers: Vec<PointerId> = pointers.iter().copied().collect();
143 hover_map.retain(|pointer, _| active_pointers.contains(pointer));
144 over_map.retain(|pointer, _| active_pointers.contains(pointer));
145}
146
147fn build_over_map(
149 pointer_hit_reader: &mut MessageReader<backend::PointerHits>,
150 pointer_over_map: &mut Local<OverMap>,
151 pointer_input_reader: &mut MessageReader<PointerInput>,
152) {
153 let cancelled_pointers: HashSet<PointerId> = pointer_input_reader
154 .read()
155 .filter_map(|p| {
156 if let PointerAction::Cancel = p.action {
157 Some(p.pointer_id)
158 } else {
159 None
160 }
161 })
162 .collect();
163
164 for entities_under_pointer in pointer_hit_reader
165 .read()
166 .filter(|e| !cancelled_pointers.contains(&e.pointer))
167 {
168 let pointer = entities_under_pointer.pointer;
169 let layer_map = pointer_over_map.entry(pointer).or_default();
170 for (entity, pick_data) in entities_under_pointer.picks.iter() {
171 let layer = entities_under_pointer.order;
172 let hits = layer_map.entry(FloatOrd(layer)).or_default();
173 hits.push((*entity, pick_data.clone()));
174 }
175 }
176
177 for layers in pointer_over_map.values_mut() {
178 for hits in layers.values_mut() {
179 hits.sort_by_key(|(_, hit)| FloatOrd(hit.depth));
180 }
181 }
182}
183
184fn build_hover_map(
188 pointers: &Query<&PointerId>,
189 pickable: Query<&Pickable>,
190 over_map: &Local<OverMap>,
191 hover_map: &mut HoverMap,
193) {
194 for pointer_id in pointers.iter() {
195 let pointer_entity_set = hover_map.entry(*pointer_id).or_default();
196 if let Some(layer_map) = over_map.get(pointer_id) {
197 for (entity, pick_data) in layer_map.values().rev().flatten() {
199 if let Ok(pickable) = pickable.get(*entity) {
200 if pickable.is_hoverable {
201 pointer_entity_set.insert(*entity, pick_data.clone());
202 }
203 if pickable.should_block_lower {
204 break;
205 }
206 } else {
207 pointer_entity_set.insert(*entity, pick_data.clone()); break; }
210 }
211 }
212 }
213}
214
215#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]
225#[reflect(Component, Default, PartialEq, Debug, Clone)]
226pub enum PickingInteraction {
227 Pressed = 2,
229 Hovered = 1,
231 #[default]
233 None = 0,
234}
235
236pub fn update_interactions(
238 hover_map: Res<HoverMap>,
240 previous_hover_map: Res<PreviousHoverMap>,
241 mut commands: Commands,
243 mut pointers: Query<(&PointerId, &PointerPress, &mut PointerInteraction)>,
244 mut interact: Query<&mut PickingInteraction>,
245) {
246 let mut new_interaction_state = EntityHashMap::<PickingInteraction>::default();
251 for (pointer, pointer_press, mut pointer_interaction) in &mut pointers {
252 if let Some(pointers_hovered_entities) = hover_map.get(pointer) {
253 let mut sorted_entities: Vec<_> = pointers_hovered_entities.clone().drain().collect();
255 sorted_entities.sort_by_key(|(_, hit)| FloatOrd(hit.depth));
256 pointer_interaction.sorted_entities = sorted_entities;
257
258 for hovered_entity in pointers_hovered_entities.iter().map(|(entity, _)| entity) {
259 merge_interaction_states(pointer_press, hovered_entity, &mut new_interaction_state);
260 }
261 }
262 }
263
264 for (&hovered_entity, &new_interaction) in new_interaction_state.iter() {
266 if let Ok(mut interaction) = interact.get_mut(hovered_entity) {
267 interaction.set_if_neq(new_interaction);
268 } else if let Ok(mut entity_commands) = commands.get_entity(hovered_entity) {
269 entity_commands.try_insert(new_interaction);
270 }
271 }
272
273 for (pointer, _, _) in &mut pointers {
276 let Some(previously_hovered_entities) = previous_hover_map.get(pointer) else {
277 continue;
278 };
279
280 for entity in previously_hovered_entities.keys() {
281 if !new_interaction_state.contains_key(entity)
282 && let Ok(mut interaction) = interact.get_mut(*entity)
283 {
284 interaction.set_if_neq(PickingInteraction::None);
285 }
286 }
287 }
288}
289
290fn merge_interaction_states(
292 pointer_press: &PointerPress,
293 hovered_entity: &Entity,
294 new_interaction_state: &mut EntityHashMap<PickingInteraction>,
295) {
296 let new_interaction = match pointer_press.is_any_pressed() {
297 true => PickingInteraction::Pressed,
298 false => PickingInteraction::Hovered,
299 };
300
301 if let Some(old_interaction) = new_interaction_state.get_mut(hovered_entity) {
302 if *old_interaction != new_interaction
304 && matches!(
305 (*old_interaction, new_interaction),
306 (PickingInteraction::Hovered, PickingInteraction::Pressed)
307 | (PickingInteraction::None, PickingInteraction::Pressed)
308 | (PickingInteraction::None, PickingInteraction::Hovered)
309 )
310 {
311 *old_interaction = new_interaction;
312 }
313 } else {
314 new_interaction_state.insert(*hovered_entity, new_interaction);
315 }
316}
317
318#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]
337#[reflect(Component, Default, PartialEq, Debug, Clone)]
338#[component(immutable)]
339pub struct Hovered(pub bool);
340
341impl Hovered {
342 pub fn get(&self) -> bool {
344 self.0
345 }
346}
347
348#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]
355#[reflect(Component, Default, PartialEq, Debug, Clone)]
356#[component(immutable)]
357pub struct DirectlyHovered(pub bool);
358
359impl DirectlyHovered {
360 pub fn get(&self) -> bool {
362 self.0
363 }
364}
365
366pub fn update_is_hovered(
368 hover_map: Option<Res<HoverMap>>,
369 mut hovers: Query<(Entity, &Hovered)>,
370 parent_query: Query<&ChildOf>,
371 mut commands: Commands,
372) {
373 let Some(hover_map) = hover_map else { return };
375
376 if hovers.is_empty() {
378 return;
379 }
380
381 let mut hover_ancestors = EntityHashSet::with_capacity(32);
392 if let Some(map) = hover_map.get(&PointerId::Mouse) {
393 for hovered_entity in map.keys() {
394 hover_ancestors.insert(*hovered_entity);
395 hover_ancestors.extend(parent_query.iter_ancestors(*hovered_entity));
396 }
397 }
398
399 for (entity, hoverable) in hovers.iter_mut() {
401 let is_hovering = hover_ancestors.contains(&entity);
402 if hoverable.0 != is_hovering {
403 commands.entity(entity).insert(Hovered(is_hovering));
404 }
405 }
406}
407
408pub fn update_is_directly_hovered(
410 hover_map: Option<Res<HoverMap>>,
411 hovers: Query<(Entity, &DirectlyHovered)>,
412 mut commands: Commands,
413) {
414 let Some(hover_map) = hover_map else { return };
416
417 if hovers.is_empty() {
419 return;
420 }
421
422 if let Some(map) = hover_map.get(&PointerId::Mouse) {
423 for (entity, hoverable) in hovers.iter() {
425 let is_hovering = map.contains_key(&entity);
426 if hoverable.0 != is_hovering {
427 commands.entity(entity).insert(DirectlyHovered(is_hovering));
428 }
429 }
430 } else {
431 for (entity, hoverable) in hovers.iter() {
433 if hoverable.0 {
434 commands.entity(entity).insert(DirectlyHovered(false));
435 }
436 }
437 }
438}
439
440#[cfg(test)]
441mod tests {
442 use bevy_camera::Camera;
443
444 use super::*;
445
446 #[test]
447 fn update_is_hovered_memoized() {
448 let mut world = World::default();
449 let camera = world.spawn(Camera::default()).id();
450
451 let hovered_child = world.spawn_empty().id();
453 let hovered_entity = world.spawn(Hovered(false)).add_child(hovered_child).id();
454
455 let mut hover_map = HoverMap::default();
457 let mut entity_map = EntityHashMap::new();
458 entity_map.insert(
459 hovered_child,
460 HitData {
461 depth: 0.0,
462 camera,
463 position: None,
464 normal: None,
465 extra: None,
466 },
467 );
468 hover_map.insert(PointerId::Mouse, entity_map);
469 world.insert_resource(hover_map);
470
471 assert!(world.run_system_cached(update_is_hovered).is_ok());
473
474 let hover = world.entity(hovered_entity).get_ref::<Hovered>().unwrap();
476 assert!(hover.get());
477 assert!(hover.is_changed());
478
479 world.increment_change_tick();
481
482 assert!(world.run_system_cached(update_is_hovered).is_ok());
483 let hover = world.entity(hovered_entity).get_ref::<Hovered>().unwrap();
484 assert!(hover.get());
485
486 world.insert_resource(HoverMap::default());
492 world.increment_change_tick();
493
494 assert!(world.run_system_cached(update_is_hovered).is_ok());
495 let hover = world.entity(hovered_entity).get_ref::<Hovered>().unwrap();
496 assert!(!hover.get());
497 assert!(hover.is_changed());
498 }
499
500 #[test]
501 fn update_is_hovered_direct_self() {
502 let mut world = World::default();
503 let camera = world.spawn(Camera::default()).id();
504
505 let hovered_entity = world.spawn(DirectlyHovered(false)).id();
507
508 let mut hover_map = HoverMap::default();
510 let mut entity_map = EntityHashMap::new();
511 entity_map.insert(
512 hovered_entity,
513 HitData {
514 depth: 0.0,
515 camera,
516 position: None,
517 normal: None,
518 extra: None,
519 },
520 );
521 hover_map.insert(PointerId::Mouse, entity_map);
522 world.insert_resource(hover_map);
523
524 assert!(world.run_system_cached(update_is_directly_hovered).is_ok());
526
527 let hover = world
529 .entity(hovered_entity)
530 .get_ref::<DirectlyHovered>()
531 .unwrap();
532 assert!(hover.get());
533 assert!(hover.is_changed());
534
535 world.increment_change_tick();
537
538 assert!(world.run_system_cached(update_is_directly_hovered).is_ok());
539 let hover = world
540 .entity(hovered_entity)
541 .get_ref::<DirectlyHovered>()
542 .unwrap();
543 assert!(hover.get());
544
545 world.insert_resource(HoverMap::default());
551 world.increment_change_tick();
552
553 assert!(world.run_system_cached(update_is_directly_hovered).is_ok());
554 let hover = world
555 .entity(hovered_entity)
556 .get_ref::<DirectlyHovered>()
557 .unwrap();
558 assert!(!hover.get());
559 assert!(hover.is_changed());
560 }
561
562 #[test]
563 fn update_is_hovered_direct_child() {
564 let mut world = World::default();
565 let camera = world.spawn(Camera::default()).id();
566
567 let hovered_child = world.spawn_empty().id();
569 let hovered_entity = world
570 .spawn(DirectlyHovered(false))
571 .add_child(hovered_child)
572 .id();
573
574 let mut hover_map = HoverMap::default();
576 let mut entity_map = EntityHashMap::new();
577 entity_map.insert(
578 hovered_child,
579 HitData {
580 depth: 0.0,
581 camera,
582 position: None,
583 normal: None,
584 extra: None,
585 },
586 );
587 hover_map.insert(PointerId::Mouse, entity_map);
588 world.insert_resource(hover_map);
589
590 assert!(world.run_system_cached(update_is_directly_hovered).is_ok());
592
593 let hover = world
595 .entity(hovered_entity)
596 .get_ref::<DirectlyHovered>()
597 .unwrap();
598 assert!(!hover.get());
599 assert!(hover.is_changed());
600 }
601}