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::{entity::EntityHashSet, prelude::*};
18use bevy_math::FloatOrd;
19use bevy_platform::collections::HashMap;
20use bevy_reflect::prelude::*;
21
22type DepthSortedHits = Vec<(Entity, HitData)>;
23
24type PickLayer = FloatOrd;
27
28type LayerMap = BTreeMap<PickLayer, DepthSortedHits>;
30
31type OverMap = HashMap<PointerId, LayerMap>;
34
35#[derive(Debug, Deref, DerefMut, Default, Resource)]
57pub struct HoverMap(pub HashMap<PointerId, HashMap<Entity, HitData>>);
58
59#[derive(Debug, Deref, DerefMut, Default, Resource)]
61pub struct PreviousHoverMap(pub HashMap<PointerId, HashMap<Entity, HitData>>);
62
63pub fn generate_hovermap(
66 pickable: Query<&Pickable>,
68 pointers: Query<&PointerId>,
69 mut pointer_hits_reader: MessageReader<backend::PointerHits>,
70 mut pointer_input_reader: MessageReader<PointerInput>,
71 mut over_map: Local<OverMap>,
73 mut hover_map: ResMut<HoverMap>,
75 mut previous_hover_map: ResMut<PreviousHoverMap>,
76) {
77 reset_maps(
78 &mut hover_map,
79 &mut previous_hover_map,
80 &mut over_map,
81 &pointers,
82 );
83 build_over_map(
84 &mut pointer_hits_reader,
85 &mut over_map,
86 &mut pointer_input_reader,
87 );
88 build_hover_map(&pointers, pickable, &over_map, &mut hover_map);
89}
90
91fn reset_maps(
93 hover_map: &mut HoverMap,
94 previous_hover_map: &mut PreviousHoverMap,
95 over_map: &mut OverMap,
96 pointers: &Query<&PointerId>,
97) {
98 core::mem::swap(&mut previous_hover_map.0, &mut hover_map.0);
102
103 for entity_set in hover_map.values_mut() {
104 entity_set.clear();
105 }
106 for layer_map in over_map.values_mut() {
107 layer_map.clear();
108 }
109
110 let active_pointers: Vec<PointerId> = pointers.iter().copied().collect();
112 hover_map.retain(|pointer, _| active_pointers.contains(pointer));
113 over_map.retain(|pointer, _| active_pointers.contains(pointer));
114}
115
116fn build_over_map(
118 pointer_hit_reader: &mut MessageReader<backend::PointerHits>,
119 pointer_over_map: &mut Local<OverMap>,
120 pointer_input_reader: &mut MessageReader<PointerInput>,
121) {
122 let cancelled_pointers: HashSet<PointerId> = pointer_input_reader
123 .read()
124 .filter_map(|p| {
125 if let PointerAction::Cancel = p.action {
126 Some(p.pointer_id)
127 } else {
128 None
129 }
130 })
131 .collect();
132
133 for entities_under_pointer in pointer_hit_reader
134 .read()
135 .filter(|e| !cancelled_pointers.contains(&e.pointer))
136 {
137 let pointer = entities_under_pointer.pointer;
138 let layer_map = pointer_over_map.entry(pointer).or_default();
139 for (entity, pick_data) in entities_under_pointer.picks.iter() {
140 let layer = entities_under_pointer.order;
141 let hits = layer_map.entry(FloatOrd(layer)).or_default();
142 hits.push((*entity, pick_data.clone()));
143 }
144 }
145
146 for layers in pointer_over_map.values_mut() {
147 for hits in layers.values_mut() {
148 hits.sort_by_key(|(_, hit)| FloatOrd(hit.depth));
149 }
150 }
151}
152
153fn build_hover_map(
157 pointers: &Query<&PointerId>,
158 pickable: Query<&Pickable>,
159 over_map: &Local<OverMap>,
160 hover_map: &mut HoverMap,
162) {
163 for pointer_id in pointers.iter() {
164 let pointer_entity_set = hover_map.entry(*pointer_id).or_default();
165 if let Some(layer_map) = over_map.get(pointer_id) {
166 for (entity, pick_data) in layer_map.values().rev().flatten() {
168 if let Ok(pickable) = pickable.get(*entity) {
169 if pickable.is_hoverable {
170 pointer_entity_set.insert(*entity, pick_data.clone());
171 }
172 if pickable.should_block_lower {
173 break;
174 }
175 } else {
176 pointer_entity_set.insert(*entity, pick_data.clone()); break; }
179 }
180 }
181 }
182}
183
184#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]
194#[reflect(Component, Default, PartialEq, Debug, Clone)]
195pub enum PickingInteraction {
196 Pressed = 2,
198 Hovered = 1,
200 #[default]
202 None = 0,
203}
204
205pub fn update_interactions(
207 hover_map: Res<HoverMap>,
209 previous_hover_map: Res<PreviousHoverMap>,
210 mut commands: Commands,
212 mut pointers: Query<(&PointerId, &PointerPress, &mut PointerInteraction)>,
213 mut interact: Query<&mut PickingInteraction>,
214) {
215 let mut new_interaction_state = HashMap::<Entity, PickingInteraction>::default();
220 for (pointer, pointer_press, mut pointer_interaction) in &mut pointers {
221 if let Some(pointers_hovered_entities) = hover_map.get(pointer) {
222 let mut sorted_entities: Vec<_> = pointers_hovered_entities.clone().drain().collect();
224 sorted_entities.sort_by_key(|(_, hit)| FloatOrd(hit.depth));
225 pointer_interaction.sorted_entities = sorted_entities;
226
227 for hovered_entity in pointers_hovered_entities.iter().map(|(entity, _)| entity) {
228 merge_interaction_states(pointer_press, hovered_entity, &mut new_interaction_state);
229 }
230 }
231 }
232
233 for (&hovered_entity, &new_interaction) in new_interaction_state.iter() {
235 if let Ok(mut interaction) = interact.get_mut(hovered_entity) {
236 interaction.set_if_neq(new_interaction);
237 } else if let Ok(mut entity_commands) = commands.get_entity(hovered_entity) {
238 entity_commands.try_insert(new_interaction);
239 }
240 }
241
242 for (pointer, _, _) in &mut pointers {
245 let Some(previously_hovered_entities) = previous_hover_map.get(pointer) else {
246 continue;
247 };
248
249 for entity in previously_hovered_entities.keys() {
250 if !new_interaction_state.contains_key(entity)
251 && let Ok(mut interaction) = interact.get_mut(*entity)
252 {
253 interaction.set_if_neq(PickingInteraction::None);
254 }
255 }
256 }
257}
258
259fn merge_interaction_states(
261 pointer_press: &PointerPress,
262 hovered_entity: &Entity,
263 new_interaction_state: &mut HashMap<Entity, PickingInteraction>,
264) {
265 let new_interaction = match pointer_press.is_any_pressed() {
266 true => PickingInteraction::Pressed,
267 false => PickingInteraction::Hovered,
268 };
269
270 if let Some(old_interaction) = new_interaction_state.get_mut(hovered_entity) {
271 if *old_interaction != new_interaction
273 && matches!(
274 (*old_interaction, new_interaction),
275 (PickingInteraction::Hovered, PickingInteraction::Pressed)
276 | (PickingInteraction::None, PickingInteraction::Pressed)
277 | (PickingInteraction::None, PickingInteraction::Hovered)
278 )
279 {
280 *old_interaction = new_interaction;
281 }
282 } else {
283 new_interaction_state.insert(*hovered_entity, new_interaction);
284 }
285}
286
287#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]
306#[reflect(Component, Default, PartialEq, Debug, Clone)]
307#[component(immutable)]
308pub struct Hovered(pub bool);
309
310impl Hovered {
311 pub fn get(&self) -> bool {
313 self.0
314 }
315}
316
317#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]
324#[reflect(Component, Default, PartialEq, Debug, Clone)]
325#[component(immutable)]
326pub struct DirectlyHovered(pub bool);
327
328impl DirectlyHovered {
329 pub fn get(&self) -> bool {
331 self.0
332 }
333}
334
335pub fn update_is_hovered(
337 hover_map: Option<Res<HoverMap>>,
338 mut hovers: Query<(Entity, &Hovered)>,
339 parent_query: Query<&ChildOf>,
340 mut commands: Commands,
341) {
342 let Some(hover_map) = hover_map else { return };
344
345 if hovers.is_empty() {
347 return;
348 }
349
350 let mut hover_ancestors = EntityHashSet::with_capacity(32);
361 if let Some(map) = hover_map.get(&PointerId::Mouse) {
362 for hovered_entity in map.keys() {
363 hover_ancestors.insert(*hovered_entity);
364 hover_ancestors.extend(parent_query.iter_ancestors(*hovered_entity));
365 }
366 }
367
368 for (entity, hoverable) in hovers.iter_mut() {
370 let is_hovering = hover_ancestors.contains(&entity);
371 if hoverable.0 != is_hovering {
372 commands.entity(entity).insert(Hovered(is_hovering));
373 }
374 }
375}
376
377pub fn update_is_directly_hovered(
379 hover_map: Option<Res<HoverMap>>,
380 hovers: Query<(Entity, &DirectlyHovered)>,
381 mut commands: Commands,
382) {
383 let Some(hover_map) = hover_map else { return };
385
386 if hovers.is_empty() {
388 return;
389 }
390
391 if let Some(map) = hover_map.get(&PointerId::Mouse) {
392 for (entity, hoverable) in hovers.iter() {
394 let is_hovering = map.contains_key(&entity);
395 if hoverable.0 != is_hovering {
396 commands.entity(entity).insert(DirectlyHovered(is_hovering));
397 }
398 }
399 } else {
400 for (entity, hoverable) in hovers.iter() {
402 if hoverable.0 {
403 commands.entity(entity).insert(DirectlyHovered(false));
404 }
405 }
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use bevy_camera::Camera;
412
413 use super::*;
414
415 #[test]
416 fn update_is_hovered_memoized() {
417 let mut world = World::default();
418 let camera = world.spawn(Camera::default()).id();
419
420 let hovered_child = world.spawn_empty().id();
422 let hovered_entity = world.spawn(Hovered(false)).add_child(hovered_child).id();
423
424 let mut hover_map = HoverMap::default();
426 let mut entity_map = HashMap::new();
427 entity_map.insert(
428 hovered_child,
429 HitData {
430 depth: 0.0,
431 camera,
432 position: None,
433 normal: None,
434 },
435 );
436 hover_map.insert(PointerId::Mouse, entity_map);
437 world.insert_resource(hover_map);
438
439 assert!(world.run_system_cached(update_is_hovered).is_ok());
441
442 let hover = world.entity(hovered_entity).get_ref::<Hovered>().unwrap();
444 assert!(hover.get());
445 assert!(hover.is_changed());
446
447 world.increment_change_tick();
449
450 assert!(world.run_system_cached(update_is_hovered).is_ok());
451 let hover = world.entity(hovered_entity).get_ref::<Hovered>().unwrap();
452 assert!(hover.get());
453
454 world.insert_resource(HoverMap::default());
460 world.increment_change_tick();
461
462 assert!(world.run_system_cached(update_is_hovered).is_ok());
463 let hover = world.entity(hovered_entity).get_ref::<Hovered>().unwrap();
464 assert!(!hover.get());
465 assert!(hover.is_changed());
466 }
467
468 #[test]
469 fn update_is_hovered_direct_self() {
470 let mut world = World::default();
471 let camera = world.spawn(Camera::default()).id();
472
473 let hovered_entity = world.spawn(DirectlyHovered(false)).id();
475
476 let mut hover_map = HoverMap::default();
478 let mut entity_map = HashMap::new();
479 entity_map.insert(
480 hovered_entity,
481 HitData {
482 depth: 0.0,
483 camera,
484 position: None,
485 normal: None,
486 },
487 );
488 hover_map.insert(PointerId::Mouse, entity_map);
489 world.insert_resource(hover_map);
490
491 assert!(world.run_system_cached(update_is_directly_hovered).is_ok());
493
494 let hover = world
496 .entity(hovered_entity)
497 .get_ref::<DirectlyHovered>()
498 .unwrap();
499 assert!(hover.get());
500 assert!(hover.is_changed());
501
502 world.increment_change_tick();
504
505 assert!(world.run_system_cached(update_is_directly_hovered).is_ok());
506 let hover = world
507 .entity(hovered_entity)
508 .get_ref::<DirectlyHovered>()
509 .unwrap();
510 assert!(hover.get());
511
512 world.insert_resource(HoverMap::default());
518 world.increment_change_tick();
519
520 assert!(world.run_system_cached(update_is_directly_hovered).is_ok());
521 let hover = world
522 .entity(hovered_entity)
523 .get_ref::<DirectlyHovered>()
524 .unwrap();
525 assert!(!hover.get());
526 assert!(hover.is_changed());
527 }
528
529 #[test]
530 fn update_is_hovered_direct_child() {
531 let mut world = World::default();
532 let camera = world.spawn(Camera::default()).id();
533
534 let hovered_child = world.spawn_empty().id();
536 let hovered_entity = world
537 .spawn(DirectlyHovered(false))
538 .add_child(hovered_child)
539 .id();
540
541 let mut hover_map = HoverMap::default();
543 let mut entity_map = HashMap::new();
544 entity_map.insert(
545 hovered_child,
546 HitData {
547 depth: 0.0,
548 camera,
549 position: None,
550 normal: None,
551 },
552 );
553 hover_map.insert(PointerId::Mouse, entity_map);
554 world.insert_resource(hover_map);
555
556 assert!(world.run_system_cached(update_is_directly_hovered).is_ok());
558
559 let hover = world
561 .entity(hovered_entity)
562 .get_ref::<DirectlyHovered>()
563 .unwrap();
564 assert!(!hover.get());
565 assert!(hover.is_changed());
566 }
567}