Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_picking/src/hover.rs
6849 views
1
//! Determines which entities are being hovered by which pointers.
2
//!
3
//! The most important type in this module is the [`HoverMap`], which maps pointers to the entities
4
//! they are hovering over.
5
6
use alloc::collections::BTreeMap;
7
use core::fmt::Debug;
8
use std::collections::HashSet;
9
10
use crate::{
11
backend::{self, HitData},
12
pointer::{PointerAction, PointerId, PointerInput, PointerInteraction, PointerPress},
13
Pickable,
14
};
15
16
use bevy_derive::{Deref, DerefMut};
17
use bevy_ecs::{entity::EntityHashSet, prelude::*};
18
use bevy_math::FloatOrd;
19
use bevy_platform::collections::HashMap;
20
use bevy_reflect::prelude::*;
21
22
type DepthSortedHits = Vec<(Entity, HitData)>;
23
24
/// Events returned from backends can be grouped with an order field. This allows picking to work
25
/// with multiple layers of rendered output to the same render target.
26
type PickLayer = FloatOrd;
27
28
/// Maps [`PickLayer`]s to the map of entities within that pick layer, sorted by depth.
29
type LayerMap = BTreeMap<PickLayer, DepthSortedHits>;
30
31
/// Maps Pointers to a [`LayerMap`]. Note this is much more complex than the [`HoverMap`] because
32
/// this data structure is used to sort entities by layer then depth for every pointer.
33
type OverMap = HashMap<PointerId, LayerMap>;
34
35
/// The source of truth for all hover state. This is used to determine what events to send, and what
36
/// state components should be in.
37
///
38
/// Maps pointers to the entities they are hovering over.
39
///
40
/// "Hovering" refers to the *hover* state, which is not the same as whether or not a picking
41
/// backend is reporting hits between a pointer and an entity. A pointer is "hovering" an entity
42
/// only if the pointer is hitting the entity (as reported by a picking backend) *and* no entities
43
/// between it and the pointer block interactions.
44
///
45
/// For example, if a pointer is hitting a UI button and a 3d mesh, but the button is in front of
46
/// the mesh, the UI button will be hovered, but the mesh will not. Unless, the [`Pickable`]
47
/// component is present with [`should_block_lower`](Pickable::should_block_lower) set to `false`.
48
///
49
/// # Advanced Users
50
///
51
/// If you want to completely replace the provided picking events or state produced by this plugin,
52
/// you can use this resource to do that. All of the event systems for picking are built *on top of*
53
/// this authoritative hover state, and you can do the same. You can also use the
54
/// [`PreviousHoverMap`] as a robust way of determining changes in hover state from the previous
55
/// update.
56
#[derive(Debug, Deref, DerefMut, Default, Resource)]
57
pub struct HoverMap(pub HashMap<PointerId, HashMap<Entity, HitData>>);
58
59
/// The previous state of the hover map, used to track changes to hover state.
60
#[derive(Debug, Deref, DerefMut, Default, Resource)]
61
pub struct PreviousHoverMap(pub HashMap<PointerId, HashMap<Entity, HitData>>);
62
63
/// Coalesces all data from inputs and backends to generate a map of the currently hovered entities.
64
/// This is the final focusing step to determine which entity the pointer is hovering over.
65
pub fn generate_hovermap(
66
// Inputs
67
pickable: Query<&Pickable>,
68
pointers: Query<&PointerId>,
69
mut pointer_hits_reader: MessageReader<backend::PointerHits>,
70
mut pointer_input_reader: MessageReader<PointerInput>,
71
// Local
72
mut over_map: Local<OverMap>,
73
// Output
74
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
91
/// Clear non-empty local maps, reusing allocated memory.
92
fn reset_maps(
93
hover_map: &mut HoverMap,
94
previous_hover_map: &mut PreviousHoverMap,
95
over_map: &mut OverMap,
96
pointers: &Query<&PointerId>,
97
) {
98
// Swap the previous and current hover maps. This results in the previous values being stored in
99
// `PreviousHoverMap`. Swapping is okay because we clear the `HoverMap` which now holds stale
100
// data. This process is done without any allocations.
101
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
// Clear pointers from the maps if they have been removed.
111
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
116
/// Build an ordered map of entities that are under each pointer
117
fn 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
153
/// Build an unsorted set of hovered entities, accounting for depth, layer, and [`Pickable`]. Note
154
/// that unlike the pointer map, this uses [`Pickable`] to determine if lower entities receive hover
155
/// focus. Often, only a single entity per pointer will be hovered.
156
fn build_hover_map(
157
pointers: &Query<&PointerId>,
158
pickable: Query<&Pickable>,
159
over_map: &Local<OverMap>,
160
// Output
161
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
// Note we reverse here to start from the highest layer first.
167
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()); // Emit events by default
177
break; // Entities block by default so we break out of the loop
178
}
179
}
180
}
181
}
182
}
183
184
/// A component that aggregates picking interaction state of this entity across all pointers.
185
///
186
/// Unlike bevy's `Interaction` component, this is an aggregate of the state of all pointers
187
/// interacting with this entity. Aggregation is done by taking the interaction with the highest
188
/// precedence.
189
///
190
/// For example, if we have an entity that is being hovered by one pointer, and pressed by another,
191
/// the entity will be considered pressed. If that entity is instead being hovered by both pointers,
192
/// it will be considered hovered.
193
#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]
194
#[reflect(Component, Default, PartialEq, Debug, Clone)]
195
pub enum PickingInteraction {
196
/// The entity is being pressed down by a pointer.
197
Pressed = 2,
198
/// The entity is being hovered by a pointer.
199
Hovered = 1,
200
/// No pointers are interacting with this entity.
201
#[default]
202
None = 0,
203
}
204
205
/// Uses [`HoverMap`] changes to update [`PointerInteraction`] and [`PickingInteraction`] components.
206
pub fn update_interactions(
207
// Input
208
hover_map: Res<HoverMap>,
209
previous_hover_map: Res<PreviousHoverMap>,
210
// Outputs
211
mut commands: Commands,
212
mut pointers: Query<(&PointerId, &PointerPress, &mut PointerInteraction)>,
213
mut interact: Query<&mut PickingInteraction>,
214
) {
215
// Create a map to hold the aggregated interaction for each entity. This is needed because we
216
// need to be able to insert the interaction component on entities if they do not exist. To do
217
// so we need to know the final aggregated interaction state to avoid the scenario where we set
218
// an entity to `Pressed`, then overwrite that with a lower precedent like `Hovered`.
219
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
// Insert a sorted list of hit entities into the pointer's interaction component.
223
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
// Take the aggregated entity states and update or insert the component if missing.
234
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
// Clear all previous hover data from pointers that are no longer hovering any entities.
243
// We do this last to preserve change detection for picking interactions.
244
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
259
/// Merge the interaction state of this entity into the aggregated map.
260
fn 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
// Only update if the new value has a higher precedence than the old value.
272
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
/// A component that allows users to use regular Bevy change detection to determine when the pointer
288
/// enters or leaves an entity. Users should insert this component on an entity to indicate interest
289
/// in knowing about hover state changes.
290
///
291
/// The component's boolean value will be `true` whenever the pointer is currently directly hovering
292
/// over the entity, or any of the entity's descendants (as defined by the [`ChildOf`]
293
/// relationship). This is consistent with the behavior of the CSS `:hover` pseudo-class, which
294
/// applies to the element and all of its descendants.
295
///
296
/// The contained boolean value is guaranteed to only be mutated when the pointer enters or leaves
297
/// the entity, allowing Bevy change detection to be used efficiently. This is in contrast to the
298
/// [`HoverMap`] resource, which is updated every frame.
299
///
300
/// Typically, a simple hoverable entity or widget will have this component added to it. More
301
/// complex widgets can have this component added to each hoverable part.
302
///
303
/// The computational cost of keeping the `Hovered` components up to date is relatively cheap, and
304
/// linear in the number of entities that have the [`Hovered`] component inserted.
305
#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]
306
#[reflect(Component, Default, PartialEq, Debug, Clone)]
307
#[component(immutable)]
308
pub struct Hovered(pub bool);
309
310
impl Hovered {
311
/// Get whether the entity is currently hovered.
312
pub fn get(&self) -> bool {
313
self.0
314
}
315
}
316
317
/// A component that allows users to use regular Bevy change detection to determine when the pointer
318
/// is directly hovering over an entity. Users should insert this component on an entity to indicate
319
/// interest in knowing about hover state changes.
320
///
321
/// This is similar to [`Hovered`] component, except that it does not include descendants in the
322
/// hover state.
323
#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]
324
#[reflect(Component, Default, PartialEq, Debug, Clone)]
325
#[component(immutable)]
326
pub struct DirectlyHovered(pub bool);
327
328
impl DirectlyHovered {
329
/// Get whether the entity is currently hovered.
330
pub fn get(&self) -> bool {
331
self.0
332
}
333
}
334
335
/// Uses [`HoverMap`] changes to update [`Hovered`] components.
336
pub 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
// Don't do any work if there's no hover map.
343
let Some(hover_map) = hover_map else { return };
344
345
// Don't bother collecting ancestors if there are no hovers.
346
if hovers.is_empty() {
347
return;
348
}
349
350
// Algorithm: for each entity having a `Hovered` component, we want to know if the current
351
// entry in the hover map is "within" (that is, in the set of descendants of) that entity. Rather
352
// than doing an expensive breadth-first traversal of children, instead start with the hovermap
353
// entry and search upwards. We can make this even cheaper by building a set of ancestors for
354
// the hovermap entry, and then testing each `Hovered` entity against that set.
355
356
// A set which contains the hovered for the current pointer entity and its ancestors. The
357
// capacity is based on the likely tree depth of the hierarchy, which is typically greater for
358
// UI (because of layout issues) than for 3D scenes. A depth of 32 is a reasonable upper bound
359
// for most use cases.
360
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 each hovered entity, it is considered "hovering" if it's in the set of hovered ancestors.
369
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
377
/// Uses [`HoverMap`] changes to update [`DirectlyHovered`] components.
378
pub fn update_is_directly_hovered(
379
hover_map: Option<Res<HoverMap>>,
380
hovers: Query<(Entity, &DirectlyHovered)>,
381
mut commands: Commands,
382
) {
383
// Don't do any work if there's no hover map.
384
let Some(hover_map) = hover_map else { return };
385
386
// Don't bother collecting ancestors if there are no hovers.
387
if hovers.is_empty() {
388
return;
389
}
390
391
if let Some(map) = hover_map.get(&PointerId::Mouse) {
392
// It's hovering if it's in the HoverMap.
393
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
// No hovered entity, reset all hovers.
401
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)]
410
mod 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
// Setup entities
421
let hovered_child = world.spawn_empty().id();
422
let hovered_entity = world.spawn(Hovered(false)).add_child(hovered_child).id();
423
424
// Setup hover map with hovered_entity hovered by mouse
425
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
// Run the system
440
assert!(world.run_system_cached(update_is_hovered).is_ok());
441
442
// Check to insure that the hovered entity has the Hovered component set to true
443
let hover = world.entity(hovered_entity).get_ref::<Hovered>().unwrap();
444
assert!(hover.get());
445
assert!(hover.is_changed());
446
447
// Now do it again, but don't change the hover map.
448
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
// Should not be changed
455
// NOTE: Test doesn't work - thinks it is always changed
456
// assert!(!hover.is_changed());
457
458
// Clear the hover map and run again.
459
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
// Setup entities
474
let hovered_entity = world.spawn(DirectlyHovered(false)).id();
475
476
// Setup hover map with hovered_entity hovered by mouse
477
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
// Run the system
492
assert!(world.run_system_cached(update_is_directly_hovered).is_ok());
493
494
// Check to insure that the hovered entity has the DirectlyHovered component set to true
495
let hover = world
496
.entity(hovered_entity)
497
.get_ref::<DirectlyHovered>()
498
.unwrap();
499
assert!(hover.get());
500
assert!(hover.is_changed());
501
502
// Now do it again, but don't change the hover map.
503
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
// Should not be changed
513
// NOTE: Test doesn't work - thinks it is always changed
514
// assert!(!hover.is_changed());
515
516
// Clear the hover map and run again.
517
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
// Setup entities
535
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
// Setup hover map with hovered_entity hovered by mouse
542
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
// Run the system
557
assert!(world.run_system_cached(update_is_directly_hovered).is_ok());
558
559
// Check to insure that the DirectlyHovered component is still false
560
let hover = world
561
.entity(hovered_entity)
562
.get_ref::<DirectlyHovered>()
563
.unwrap();
564
assert!(!hover.get());
565
assert!(hover.is_changed());
566
}
567
}
568
569