@@ -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) ]
347370pub 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+ hovered_entity,
414+ pointer_id,
415+ location. clone ( ) ,
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+ hovered_entity,
428+ pointer_id,
429+ location. clone ( ) ,
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- hovered_entity,
685- pointer_id,
686- location. clone ( ) ,
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- hovered_entity,
699- pointer_id,
700- location. clone ( ) ,
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