Skip to content

Commit e80252e

Browse files
NthTensorecoskey
authored andcommitted
Picking out order (bevyengine#16231)
Tweaks picking docs slightly for formatting and to add additional context about the ordering of `Over` and `Out` events. Also shifts `Out` to trigger before `Over` in the global event ordering. Because of how focus is tracked, we must send all `Over` and `Out` events at the same time, in a block. Originally I had `Over` precede `Out` in the global event order, because this seemed natural. However, the effect of this, when a pointer moves between entities, is to have the new entity receive `Over` before the old entity received `Out`, which several users found confusing. The new ordering (out before over globally, over before out locally per entity) should make it much easier to write hover state cleanup code.
1 parent 1fda876 commit e80252e

File tree

1 file changed

+91
-68
lines changed

1 file changed

+91
-68
lines changed

crates/bevy_picking/src/events.rs

Lines changed: 91 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -321,28 +321,51 @@ pub struct PickingEventWriters<'w> {
321321
/// Dispatches interaction events to the target entities.
322322
///
323323
/// Within a single frame, events are dispatched in the following order:
324-
/// + The sequence [`DragEnter`], [`Over`].
324+
/// + [`Out`] → [`DragLeave`].
325+
/// + [`DragEnter`] → [`Over`].
325326
/// + Any number of any of the following:
326-
/// + For each movement: The sequence [`DragStart`], [`Drag`], [`DragOver`], [`Move`].
327-
/// + For each button press: Either [`Down`], or the sequence [`Click`], [`Up`], [`DragDrop`], [`DragEnd`], [`DragLeave`].
328-
/// + For each pointer cancellation: Simply [`Cancel`].
329-
/// + Finally the sequence [`Out`], [`DragLeave`].
327+
/// + For each movement: [`DragStart`] → [`Drag`] → [`DragOver`] → [`Move`].
328+
/// + For each button press: [`Down`] or [`Click`] → [`Up`] → [`DragDrop`] → [`DragEnd`] → [`DragLeave`].
329+
/// + For each pointer cancellation: [`Cancel`].
330330
///
331-
/// Only the last event in a given sequence is garenteed to be present.
331+
/// Additionally, across multiple frames, the following are also strictly
332+
/// ordered by the interaction state machine:
333+
/// + When a pointer moves over the target:
334+
/// [`Over`], [`Move`], [`Out`].
335+
/// + When a pointer presses buttons on the target:
336+
/// [`Down`], [`Click`], [`Up`].
337+
/// + When a pointer drags the target:
338+
/// [`DragStart`], [`Drag`], [`DragEnd`].
339+
/// + When a pointer drags something over the target:
340+
/// [`DragEnter`], [`DragOver`], [`DragDrop`], [`DragLeave`].
341+
/// + When a pointer is canceled:
342+
/// No other events will follow the [`Cancel`] event for that pointer.
332343
///
333-
/// Additionally, across multiple frames, the following are also strictly ordered by the interaction state machine:
334-
/// + When a pointer moves over the target: [`Over`], [`Move`], [`Out`].
335-
/// + When a pointer presses buttons on the target: [`Down`], [`Up`], [`Click`].
336-
/// + When a pointer drags the target: [`DragStart`], [`Drag`], [`DragEnd`].
337-
/// + When a pointer drags something over the target: [`DragEnter`], [`DragOver`], [`DragDrop`], [`DragLeave`].
338-
/// + When a pointer is canceled: No other events will follow the [`Cancel`] event for that pointer.
344+
/// Two events -- [`Over`] and [`Out`] -- are driven only by the [`HoverMap`].
345+
/// The rest rely on additional data from the [`PointerInput`] event stream. To
346+
/// receive these events for a custom pointer, you must add [`PointerInput`]
347+
/// events.
339348
///
340-
/// Two events -- [`Over`] and [`Out`] -- are driven only by the [`HoverMap`]. The rest rely on additional data from the
341-
/// [`PointerInput`] event stream. To receive these events for a custom pointer, you must add [`PointerInput`] events.
349+
/// When the pointer goes from hovering entity A to entity B, entity A will
350+
/// receive [`Out`] and then entity B will receive [`Over`]. No entity will ever
351+
/// receive both an [`Over`] and and a [`Out`] event during the same frame.
342352
///
343-
/// Note: Though it is common for the [`PointerInput`] stream may contain multiple pointer movements and presses each frame,
344-
/// the hover state is determined only by the pointer's *final position*. Since the hover state ultimately determines which
345-
/// entities receive events, this may mean that an entity can receive events which occurred before it was actually hovered.
353+
/// When we account for event bubbling, this is no longer true. When focus shifts
354+
/// between children, parent entities may receive redundant [`Out`] → [`Over`] pairs.
355+
/// In the context of UI, this is especially problematic. Additional hierarchy-aware
356+
/// events will be added in a future release.
357+
///
358+
/// Both [`Click`] and [`Up`] target the entity hovered in the *previous frame*,
359+
/// rather than the current frame. This is because touch pointers hover nothing
360+
/// on the frame they are released. The end effect is that these two events can
361+
/// be received sequentally after an [`Out`] event (but always on the same frame
362+
/// as the [`Out`] event).
363+
///
364+
/// Note: Though it is common for the [`PointerInput`] stream may contain
365+
/// multiple pointer movements and presses each frame, the hover state is
366+
/// determined only by the pointer's *final position*. Since the hover state
367+
/// ultimately determines which entities receive events, this may mean that an
368+
/// entity can receive events from before or after it was actually hovered.
346369
#[allow(clippy::too_many_arguments)]
347370
pub fn pointer_events(
348371
// Input
@@ -366,6 +389,57 @@ pub fn pointer_events(
366389
.and_then(|pointer| pointer.location.clone())
367390
};
368391

392+
// If the entity was hovered by a specific pointer last frame...
393+
for (pointer_id, hovered_entity, hit) in previous_hover_map
394+
.iter()
395+
.flat_map(|(id, hashmap)| hashmap.iter().map(|data| (*id, *data.0, data.1.clone())))
396+
{
397+
// ...but is now not being hovered by that same pointer...
398+
if !hover_map
399+
.get(&pointer_id)
400+
.iter()
401+
.any(|e| e.contains_key(&hovered_entity))
402+
{
403+
let Some(location) = pointer_location(pointer_id) else {
404+
debug!(
405+
"Unable to get location for pointer {:?} during pointer out",
406+
pointer_id
407+
);
408+
continue;
409+
};
410+
411+
// Always send Out events
412+
let out_event = Pointer::new(
413+
pointer_id,
414+
location.clone(),
415+
hovered_entity,
416+
Out { hit: hit.clone() },
417+
);
418+
commands.trigger_targets(out_event.clone(), hovered_entity);
419+
event_writers.out_events.send(out_event);
420+
421+
// Possibly send DragLeave events
422+
for button in PointerButton::iter() {
423+
let state = pointer_state.get_mut(pointer_id, button);
424+
state.dragging_over.remove(&hovered_entity);
425+
for drag_target in state.dragging.keys() {
426+
let drag_leave_event = Pointer::new(
427+
pointer_id,
428+
location.clone(),
429+
hovered_entity,
430+
DragLeave {
431+
button,
432+
dragged: *drag_target,
433+
hit: hit.clone(),
434+
},
435+
);
436+
commands.trigger_targets(drag_leave_event.clone(), hovered_entity);
437+
event_writers.drag_leave_events.send(drag_leave_event);
438+
}
439+
}
440+
}
441+
}
442+
369443
// If the entity is hovered...
370444
for (pointer_id, hovered_entity, hit) in hover_map
371445
.iter()
@@ -659,55 +733,4 @@ pub fn pointer_events(
659733
}
660734
}
661735
}
662-
663-
// If the entity was hovered by a specific pointer last frame...
664-
for (pointer_id, hovered_entity, hit) in previous_hover_map
665-
.iter()
666-
.flat_map(|(id, hashmap)| hashmap.iter().map(|data| (*id, *data.0, data.1.clone())))
667-
{
668-
// ...but is now not being hovered by that same pointer...
669-
if !hover_map
670-
.get(&pointer_id)
671-
.iter()
672-
.any(|e| e.contains_key(&hovered_entity))
673-
{
674-
let Some(location) = pointer_location(pointer_id) else {
675-
debug!(
676-
"Unable to get location for pointer {:?} during pointer out",
677-
pointer_id
678-
);
679-
continue;
680-
};
681-
682-
// Always send Out events
683-
let out_event = Pointer::new(
684-
pointer_id,
685-
location.clone(),
686-
hovered_entity,
687-
Out { hit: hit.clone() },
688-
);
689-
commands.trigger_targets(out_event.clone(), hovered_entity);
690-
event_writers.out_events.send(out_event);
691-
692-
// Possibly send DragLeave events
693-
for button in PointerButton::iter() {
694-
let state = pointer_state.get_mut(pointer_id, button);
695-
state.dragging_over.remove(&hovered_entity);
696-
for drag_target in state.dragging.keys() {
697-
let drag_leave_event = Pointer::new(
698-
pointer_id,
699-
location.clone(),
700-
hovered_entity,
701-
DragLeave {
702-
button,
703-
dragged: *drag_target,
704-
hit: hit.clone(),
705-
},
706-
);
707-
commands.trigger_targets(drag_leave_event.clone(), hovered_entity);
708-
event_writers.drag_leave_events.send(drag_leave_event);
709-
}
710-
}
711-
}
712-
}
713736
}

0 commit comments

Comments
 (0)