Commit 08a3f24
authored
Adds
# 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>Pointer<Enter> and Pointer<Leave> Events - Hierarchy aware versions of Pointer<Over> and Pointer<Out> (bevyengine#22991)1 parent 06a444a commit 08a3f24
5 files changed
Lines changed: 617 additions & 30 deletions
0 commit comments