Skip to content
This repository was archived by the owner on Mar 4, 2025. It is now read-only.

Commit ed2359e

Browse files
authored
Fix inverted bevy_ui backend (#218)
* fix inverted ui * avoid panics * Formatting
1 parent d6eca9d commit ed2359e

File tree

6 files changed

+334
-187
lines changed

6 files changed

+334
-187
lines changed

backends/bevy_picking_sprite/src/lib.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,22 @@ pub fn sprite_picking(
5252
pointer_location.location().map(|loc| (pointer, loc))
5353
}) {
5454
let mut blocked = false;
55-
let (cam_entity, camera, cam_transform) = cameras
55+
let Some((cam_entity, camera, cam_transform)) = cameras
5656
.iter()
5757
.find(|(_, camera, _)| {
5858
camera
5959
.target
6060
.normalize(Some(primary_window.single()))
6161
.unwrap()
6262
== location.target
63-
})
64-
.unwrap_or_else(|| panic!("No camera found associated with pointer {:?}.", pointer));
63+
}) else {
64+
continue;
65+
};
6566

66-
let Some(cursor_pos_world) = camera.viewport_to_world_2d(cam_transform, location.position) else {
67-
continue;
68-
};
67+
let Some(cursor_pos_world) =
68+
camera.viewport_to_world_2d(cam_transform, location.position) else {
69+
continue;
70+
};
6971

7072
let picks: Vec<(Entity, HitData)> = sorted_sprites
7173
.iter()

backends/bevy_picking_ui/src/lib.rs

Lines changed: 105 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@
44
#![allow(clippy::too_many_arguments)]
55
#![deny(missing_docs)]
66

7-
use bevy::ui::{self, FocusPolicy};
8-
use bevy::{prelude::*, render::camera::NormalizedRenderTarget, window::PrimaryWindow};
7+
use bevy::{
8+
ecs::query::WorldQuery,
9+
prelude::*,
10+
render::camera::NormalizedRenderTarget,
11+
ui::{FocusPolicy, RelativeCursorPosition, UiStack},
12+
window::PrimaryWindow,
13+
};
914
use bevy_picking_core::backend::prelude::*;
1015

1116
/// Commonly used imports for the [`bevy_picking_ui`](crate) crate.
@@ -23,21 +28,30 @@ impl Plugin for BevyUiBackend {
2328
}
2429
}
2530

26-
/// Computes the UI node entities under each pointer
31+
/// Main query for [`ui_focus_system`]
32+
#[derive(WorldQuery)]
33+
#[world_query(mutable)]
34+
pub struct NodeQuery {
35+
entity: Entity,
36+
node: &'static Node,
37+
global_transform: &'static GlobalTransform,
38+
interaction: Option<&'static mut Interaction>,
39+
relative_cursor_position: Option<&'static mut RelativeCursorPosition>,
40+
focus_policy: Option<&'static FocusPolicy>,
41+
calculated_clip: Option<&'static CalculatedClip>,
42+
computed_visibility: Option<&'static ComputedVisibility>,
43+
}
44+
45+
/// Computes the UI node entities under each pointer.
46+
///
47+
/// Bevy's [`UiStack`] orders all nodes in the order they will be rendered, which is the same order
48+
/// we need for determining picking.
2749
pub fn ui_picking(
2850
pointers: Query<(&PointerId, &PointerLocation)>,
29-
cameras: Query<(Entity, &Camera)>,
30-
primary_window: Query<Entity, With<PrimaryWindow>>,
31-
mut node_query: Query<
32-
(
33-
Entity,
34-
&ui::Node,
35-
&GlobalTransform,
36-
&FocusPolicy,
37-
Option<&CalculatedClip>,
38-
),
39-
Without<PointerId>,
40-
>,
51+
cameras: Query<(Entity, &Camera, Option<&UiCameraConfig>)>,
52+
primary_window: Query<(Entity, &Window), With<PrimaryWindow>>,
53+
ui_stack: Res<UiStack>,
54+
mut node_query: Query<NodeQuery>,
4155
mut output: EventWriter<PointerHits>,
4256
) {
4357
for (pointer, location) in pointers.iter().filter_map(|(pointer, pointer_location)| {
@@ -54,58 +68,94 @@ pub fn ui_picking(
5468
})
5569
.map(|loc| (pointer, loc))
5670
}) {
57-
let camera = cameras
71+
let (window_entity, window) = primary_window.single();
72+
let Some((camera, ui_config)) = cameras
5873
.iter()
59-
.find(|(_entity, camera)| {
60-
camera
61-
.target
62-
.normalize(Some(primary_window.single()))
63-
.unwrap()
64-
== location.target
74+
.find(|(_entity, camera, _)| {
75+
camera.target.normalize(Some(window_entity)).unwrap() == location.target
6576
})
66-
.map(|(entity, _camera)| entity)
67-
.unwrap_or_else(|| panic!("No camera found associated with pointer {:?}.", pointer));
77+
.map(|(entity, _camera, ui_config)| (entity, ui_config)) else {
78+
continue;
79+
};
6880

69-
let cursor_position = location.position;
70-
let mut blocked = false;
81+
if matches!(ui_config, Some(&UiCameraConfig { show_ui: false, .. })) {
82+
return;
83+
}
7184

72-
let over_list = node_query
73-
.iter_mut()
74-
.filter_map(|(entity, node, global_transform, focus, clip)| {
75-
if blocked {
76-
return None;
77-
}
85+
let mut cursor_position = location.position;
86+
cursor_position.y = window.resolution.height() - cursor_position.y;
7887

79-
blocked = *focus == FocusPolicy::Block;
88+
let mut hovered_nodes = ui_stack
89+
.uinodes
90+
.iter()
91+
// reverse the iterator to traverse the tree from closest nodes to furthest
92+
.rev()
93+
.filter_map(|entity| {
94+
if let Ok(node) = node_query.get_mut(*entity) {
95+
// Nodes that are not rendered should not be interactable
96+
if let Some(computed_visibility) = node.computed_visibility {
97+
if !computed_visibility.is_visible() {
98+
return None;
99+
}
100+
}
80101

81-
let position = global_transform.translation();
82-
let ui_position = position.truncate();
83-
let extents = node.size() / 2.0;
84-
let mut min = ui_position - extents;
85-
let mut max = ui_position + extents;
86-
if let Some(clip) = clip {
87-
min = min.max(clip.clip.min);
88-
max = Vec2::min(max, clip.clip.max);
89-
}
102+
let position = node.global_transform.translation();
103+
let ui_position = position.truncate();
104+
let extents = node.node.size() / 2.0;
105+
let mut min = ui_position - extents;
106+
if let Some(clip) = node.calculated_clip {
107+
min = Vec2::max(min, clip.clip.min);
108+
}
90109

91-
let contains_cursor = (min.x..max.x).contains(&cursor_position.x)
92-
&& (min.y..max.y).contains(&cursor_position.y);
110+
// The mouse position relative to the node
111+
// (0., 0.) is the top-left corner, (1., 1.) is the bottom-right corner
112+
let relative_cursor_position = Vec2::new(
113+
(cursor_position.x - min.x) / node.node.size().x,
114+
(cursor_position.y - min.y) / node.node.size().y,
115+
);
93116

94-
contains_cursor.then_some((
95-
entity,
96-
HitData {
97-
camera,
98-
depth: position.z,
99-
position: None,
100-
normal: None,
101-
},
102-
))
117+
if (0.0..1.).contains(&relative_cursor_position.x)
118+
&& (0.0..1.).contains(&relative_cursor_position.y)
119+
{
120+
Some(*entity)
121+
} else {
122+
None
123+
}
124+
} else {
125+
None
126+
}
103127
})
104-
.collect::<Vec<_>>();
128+
.collect::<Vec<Entity>>()
129+
.into_iter();
130+
131+
// As soon as a node with a `Block` focus policy is detected, the iteration will stop on it
132+
// because it "captures" the interaction.
133+
let mut iter = node_query.iter_many_mut(hovered_nodes.by_ref());
134+
let mut picks = Vec::new();
135+
let mut depth = 0.0;
136+
137+
while let Some(node) = iter.fetch_next() {
138+
picks.push((
139+
node.entity,
140+
HitData {
141+
camera,
142+
depth,
143+
position: None,
144+
normal: None,
145+
},
146+
));
147+
match node.focus_policy.unwrap_or(&FocusPolicy::Block) {
148+
FocusPolicy::Block => {
149+
break;
150+
}
151+
FocusPolicy::Pass => { /* allow the next node to be hovered/clicked */ }
152+
}
153+
depth += 0.00001; // keep depth near 0 for precision
154+
}
105155

106156
output.send(PointerHits {
107157
pointer: *pointer,
108-
picks: over_list,
158+
picks,
109159
order: 10,
110160
})
111161
}

crates/bevy_picking_core/src/event_listening.rs

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,21 @@ impl<E: IsPointerEvent> OnPointer<E> {
111111

112112
/// Get mutable access to the target entity's [`EntityCommands`] using a closure any time this
113113
/// event listener is triggered.
114-
pub fn target_commands_mut(func: fn(&ListenedEvent<E>, &mut EntityCommands)) -> Self {
114+
pub fn target_commands_mut(func: fn(&ListenedEvent<E>, EntityCommands)) -> Self {
115115
Self::run_callback(
116116
move |In(event): In<ListenedEvent<E>>, mut commands: Commands| {
117-
func(&event, &mut commands.entity(event.target));
117+
func(&event, commands.entity(event.target));
118+
Bubble::Up
119+
},
120+
)
121+
}
122+
123+
/// Get mutable access to the listener entity's [`EntityCommands`] using a closure any time this
124+
/// event listener is triggered.
125+
pub fn listener_commands_mut(func: fn(&ListenedEvent<E>, EntityCommands)) -> Self {
126+
Self::run_callback(
127+
move |In(event): In<ListenedEvent<E>>, mut commands: Commands| {
128+
func(&event, commands.entity(event.listener));
118129
Bubble::Up
119130
},
120131
)
@@ -131,6 +142,17 @@ impl<E: IsPointerEvent> OnPointer<E> {
131142
)
132143
}
133144

145+
/// Insert a bundle on the listener entity any time this event listener is triggered.
146+
pub fn listener_insert(bundle: impl Bundle + Clone) -> Self {
147+
Self::run_callback(
148+
move |In(event): In<ListenedEvent<E>>, mut commands: Commands| {
149+
let bundle = bundle.clone();
150+
commands.entity(event.listener).insert(bundle);
151+
Bubble::Up
152+
},
153+
)
154+
}
155+
134156
/// Remove a bundle from the target entity any time this event listener is triggered.
135157
pub fn target_remove<B: Bundle>() -> Self {
136158
Self::run_callback(
@@ -141,6 +163,16 @@ impl<E: IsPointerEvent> OnPointer<E> {
141163
)
142164
}
143165

166+
/// Remove a bundle from the listener entity any time this event listener is triggered.
167+
pub fn listener_remove<B: Bundle>() -> Self {
168+
Self::run_callback(
169+
move |In(event): In<ListenedEvent<E>>, mut commands: Commands| {
170+
commands.entity(event.listener).remove::<B>();
171+
Bubble::Up
172+
},
173+
)
174+
}
175+
144176
/// Get mutable access to a specific component on the target entity using a closure any time
145177
/// this event listener is triggered. If the component does not exist, an error will be logged.
146178
pub fn target_component_mut<C: Component>(func: fn(&ListenedEvent<E>, &mut C)) -> Self {
@@ -156,6 +188,21 @@ impl<E: IsPointerEvent> OnPointer<E> {
156188
)
157189
}
158190

191+
/// Get mutable access to a specific component on the listener entity using a closure any time
192+
/// this event listener is triggered. If the component does not exist, an error will be logged.
193+
pub fn listener_component_mut<C: Component>(func: fn(&ListenedEvent<E>, &mut C)) -> Self {
194+
Self::run_callback(
195+
move |In(event): In<ListenedEvent<E>>, mut query: Query<&mut C>| {
196+
if let Ok(mut component) = query.get_mut(event.listener) {
197+
func(&event, &mut component);
198+
} else {
199+
error!("Component {:?} not found on entity {:?} during pointer callback for event {:?}", std::any::type_name::<C>(), event.listener, std::any::type_name::<E>());
200+
}
201+
Bubble::Up
202+
},
203+
)
204+
}
205+
159206
/// Send an event `F` any time this event listener is triggered. `F` must implement
160207
/// `From<ListenedEvent<E>>`.
161208
pub fn send_event<F: Event + From<ListenedEvent<E>>>() -> Self {
@@ -269,7 +316,9 @@ impl<E: IsPointerEvent> EventCallbackGraph<E> {
269316
if let Some(mut event_listener) = event_listener {
270317
// If it has an event listener, we need to add it to the map
271318
listener_map.insert(this_node, (event_listener.take(), None));
272-
if let Some((_, prev_nodes_next_node)) = listener_map.get_mut(&prev_node) {
319+
if let Some((_, prev_nodes_next_node @ None)) =
320+
listener_map.get_mut(&prev_node)
321+
{
273322
if prev_node != this_node {
274323
*prev_nodes_next_node = Some(this_node);
275324
}

0 commit comments

Comments
 (0)