Skip to content

Commit 08a3f24

Browse files
authored
Adds Pointer<Enter> and Pointer<Leave> Events - Hierarchy aware versions of Pointer<Over> and Pointer<Out> (bevyengine#22991)
# Objective - Fixes bevyengine#16289 (it’s in the 0.19 milestone so I decided to attempt a solution) - To quote the current `bevy_picking` documentation: > When the hovering focus shifts between children, parent entities may receive redundant [`Out`] → [`Over`] pairs. In the context of UI, this is especially problematic. Additional hierarchy-aware events will be added in a future release This PR attempts to add a hierarchy-aware version of the [`Out`] and [`Over`] events ## Solution - `Enter` and `Leave` are two new events that function like `Out` and `Over`, but they do not auto-propagate. Instead they are sent without propagation in `pointer_events()`. To especially handle the case where children are sticking out of parents, ancestors of hovered items are stored and used when deciding who to send `Enter` and `Leave` events to. ## Testing - I tested the mesh picking (`cargo run --example mesh_picking`) example after replacing `Over` and `Out` with `Enter` and `Leave` respectively, and it works fine. - I tested the ui_drag_and_drop (`cargo run --example ui_drag_and_drop`) example as well with the replacements, and that was good too. - I wrote my own little example (code in the showcase) to ensure that counts for `Enter` and `Leave` events were as I suspect. It utilizes UI nodes, a Red parent, and one blue (although it looks indigo in the example) and one green child that overlap in the middle (the more blue looking area). As you hover over the different color areas, the event counts increase depending on what events are fired. Notably, if you hover over the indigo/green child, and then hover back to the parent, the parent’s `Enter` and `Leave` counts do *not* increase. They only increase if you leave the red area for the outside altogether. The `Over` and `Out` counts are provided to show current functionality. https://github.com/user-attachments/assets/822a88cb-2f63-4471-8011-977dbc241b4a --- ## Showcase <details> <summary>Code for Mini Example</summary> ```rust //! Demonstrates Enter, Exit, Out, and Over events use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) .run(); } #[derive(Component, Default)] struct OverCount(u32); #[derive(Component, Default)] struct EnterCount(u32); #[derive(Component, Default)] struct LeaveCount(u32); #[derive(Component, Default)] struct OutCount(u32); #[derive(Component, Default)] #[require(OverCount, EnterCount, LeaveCount, OutCount)] struct EventCounter(String); #[derive(Component)] struct TextToUpdate(Entity); fn setup(mut commands: Commands) { commands.spawn(Camera2d); commands .spawn(( Node { width: percent(100), height: percent(100), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..Default::default() }, Pickable::IGNORE, )) .with_children(|grand_parent| { let gp_text_entity = grand_parent.spawn(( Node { position_type: PositionType::Absolute, top: px(10), left: px(10), ..default() }, Text::new("Red [Over: 0, Enter: 0, Leave: 0, Out: 0]"), )).id(); let p_text_entity = grand_parent.spawn(( Node { position_type: PositionType::Absolute, top: px(40), left: px(10), ..default() }, Text::new("Green [Over: 0, Enter: 0, Leave: 0, Out: 0]"), )).id(); let b_text_entity = grand_parent.spawn(( Node { position_type: PositionType::Absolute, top: px(70), left: px(10), ..default() }, Text::new("Indigo [Over: 0, Enter: 0, Leave: 0, Out: 0]"), )).id(); grand_parent .spawn(( Node { padding: UiRect::all(Val::Percent(20.)), border: UiRect::all(Val::Px(2.)), ..Default::default() }, BackgroundColor(Color::srgba(1., 0., 0., 0.9)), EventCounter("Red".to_string()), Pickable { should_block_lower: false, is_hoverable: true, }, TextToUpdate(gp_text_entity) )) .observe(handle_over) .observe(handle_enter) .observe(handle_leave) .observe(handle_out) .with_children(|parent| { parent.spawn(( Node { position_type: PositionType::Absolute, bottom: px(-50), right: px(0), padding: UiRect::all(Val::Px(50.)), ..Default::default() }, BackgroundColor(Color::srgba(0., 1., 0., 0.9)), EventCounter("Green".to_string()), Pickable { should_block_lower: false, is_hoverable: true, }, TextToUpdate(p_text_entity) )) .observe(handle_over) .observe(handle_enter) .observe(handle_leave) .observe(handle_out); parent.spawn(( Node { position_type: PositionType::Absolute, bottom: px(-50), right: px(50), padding: UiRect::all(Val::Px(50.)), ..Default::default() }, BackgroundColor(Color::srgba(0., 0., 1., 0.9)), EventCounter("Indigo".to_string()), Pickable { should_block_lower: false, is_hoverable: true, }, TextToUpdate(b_text_entity) )) .observe(handle_over) .observe(handle_enter) .observe(handle_leave) .observe(handle_out); }); }); } fn handle_over( on_over: On<Pointer<Over>>, mut query: Query<( &EventCounter, &mut OverCount, &EnterCount, &LeaveCount, &OutCount, &TextToUpdate, )>, mut text_query: Query<&mut Text> ) { if let Ok((event_counter, mut over_count, enter_count, leave_count, out_count, text_to_update)) = query.get_mut(on_over.event_target()) { over_count.0 += 1; text_query.get_mut(text_to_update.0).unwrap().0 = format!( "{} [Over: {}, Enter: {}, Leave: {}, Out: {}]", event_counter.0, over_count.0, enter_count.0, leave_count.0, out_count.0 ); } } fn handle_enter( on_over: On<Pointer<Enter>>, mut query: Query<( &EventCounter, &OverCount, &mut EnterCount, &LeaveCount, &OutCount, &TextToUpdate, )>, mut text_query: Query<&mut Text> ) { if let Ok((event_counter, over_count, mut enter_count, leave_count, out_count, text_to_update)) = query.get_mut(on_over.event_target()) { enter_count.0 += 1; text_query.get_mut(text_to_update.0).unwrap().0 = format!( "{} [Over: {}, Enter: {}, Leave: {}, Out: {}]", event_counter.0, over_count.0, enter_count.0, leave_count.0, out_count.0 ); } } fn handle_leave( on_over: On<Pointer<Leave>>, mut query: Query<( &EventCounter, &OverCount, &EnterCount, &mut LeaveCount, &OutCount, &TextToUpdate, )>, mut text_query: Query<&mut Text> ) { if let Ok((event_counter, over_count, enter_count, mut leave_count, out_count, text_to_update)) = query.get_mut(on_over.event_target()) { leave_count.0 += 1; text_query.get_mut(text_to_update.0).unwrap().0 = format!( "{} [Over: {}, Enter: {}, Leave: {}, Out: {}]", event_counter.0, over_count.0, enter_count.0, leave_count.0, out_count.0 ); } } fn handle_out( on_over: On<Pointer<Out>>, mut query: Query<( &EventCounter, &OverCount, &EnterCount, &LeaveCount, &mut OutCount, &TextToUpdate, )>, mut text_query: Query<&mut Text> ) { if let Ok((event_counter, over_count, enter_count, leave_count, mut out_count, text_to_update)) = query.get_mut(on_over.event_target()) { out_count.0 += 1; text_query.get_mut(text_to_update.0).unwrap().0 = format!( "{} [Over: {}, Enter: {}, Leave: {}, Out: {}]", event_counter.0, over_count.0, enter_count.0, leave_count.0, out_count.0 ); } } ``` </details>
1 parent 06a444a commit 08a3f24

5 files changed

Lines changed: 617 additions & 30 deletions

File tree

0 commit comments

Comments
 (0)