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, 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
27type PickLayer = FloatOrd;
30
31type LayerMap = BTreeMap<PickLayer, DepthSortedHits>;
33
34type OverMap = HashMap<PointerId, LayerMap>;
37
38#[derive(Debug, Deref, DerefMut, Default, Resource, Reflect)]
63#[reflect(Debug, Default, Resource)]
64pub struct HoverMap(pub HashMap<PointerId, EntityHashMap<HitData>>);
65
66#[derive(Debug, Deref, DerefMut, Default, Resource, Reflect)]
68#[reflect(Debug, Default, Resource)]
69pub struct PreviousHoverMap(pub HashMap<PointerId, EntityHashMap<HitData>>);
70
71#[derive(Debug, Default, Resource)]
85pub struct PointerCaptureMap(pub HashMap<PointerId, (Entity, HitData)>);
86
87impl PointerCaptureMap {
88 pub fn capture(&mut self, pointer: PointerId, entity: Entity, hit: HitData) {
91 self.0.insert(pointer, (entity, hit));
92 }
93
94 pub fn release(&mut self, pointer: PointerId) -> Option<(Entity, HitData)> {
97 self.0.remove(&pointer)
98 }
99
100 pub fn get(&self, pointer: &PointerId) -> Option<(Entity, &HitData)> {
102 self.0.get(pointer).map(|(e, h)| (*e, h))
103 }
104
105 pub fn is_captured(&self, pointer: &PointerId) -> bool {
107 self.0.contains_key(pointer)
108 }
109}
110
111pub(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
126pub(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
139pub fn generate_hovermap(
142 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 mut over_map: Local<OverMap>,
150 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 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
170fn 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
186fn 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
196fn 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 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 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
225fn 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
262fn build_hover_map(
266 pointers: &Query<&PointerId>,
267 pickable: Query<&Pickable>,
268 over_map: &Local<OverMap>,
269 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 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()); break; }
288 }
289 }
290 }
291}
292
293#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]
303#[reflect(Component, Default, PartialEq, Debug, Clone)]
304pub enum PickingInteraction {
305 Pressed = 2,
307 Hovered = 1,
309 #[default]
311 None = 0,
312}
313
314pub fn update_interactions(
316 hover_map: Res<HoverMap>,
318 previous_hover_map: Res<PreviousHoverMap>,
319 mut commands: Commands,
321 mut pointers: Query<(&PointerId, &PointerPressState, &mut PointerInteraction)>,
322 mut interact: Query<&mut PickingInteraction>,
323) {
324 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 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 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 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
368fn 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 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#[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 pub fn get(&self) -> bool {
422 self.0
423 }
424}
425
426#[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 pub fn get(&self) -> bool {
440 self.0
441 }
442}
443
444pub 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 let Some(hover_map) = hover_map else { return };
453
454 if hovers.is_empty() {
456 return;
457 }
458
459 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 (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
486pub fn update_is_directly_hovered(
488 hover_map: Option<Res<HoverMap>>,
489 hovers: Query<(Entity, &DirectlyHovered)>,
490 mut commands: Commands,
491) {
492 let Some(hover_map) = hover_map else { return };
494
495 if hovers.is_empty() {
497 return;
498 }
499
500 if let Some(map) = hover_map.get(&PointerId::Mouse) {
501 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 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 let hovered_child = world.spawn_empty().id();
531 let hovered_entity = world.spawn(Hovered(false)).add_child(hovered_child).id();
532
533 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 assert!(world.run_system_cached(update_is_hovered).is_ok());
551
552 let hover = world.entity(hovered_entity).get_ref::<Hovered>().unwrap();
554 assert!(hover.get());
555 assert!(hover.is_changed());
556
557 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 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 let hovered_entity = world.spawn(DirectlyHovered(false)).id();
585
586 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 assert!(world.run_system_cached(update_is_directly_hovered).is_ok());
604
605 let hover = world
607 .entity(hovered_entity)
608 .get_ref::<DirectlyHovered>()
609 .unwrap();
610 assert!(hover.get());
611 assert!(hover.is_changed());
612
613 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 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 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 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 assert!(world.run_system_cached(update_is_directly_hovered).is_ok());
670
671 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 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 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}