4
4
#![ allow( clippy:: too_many_arguments) ]
5
5
#![ deny( missing_docs) ]
6
6
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
+ } ;
9
14
use bevy_picking_core:: backend:: prelude:: * ;
10
15
11
16
/// Commonly used imports for the [`bevy_picking_ui`](crate) crate.
@@ -23,21 +28,30 @@ impl Plugin for BevyUiBackend {
23
28
}
24
29
}
25
30
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.
27
49
pub fn ui_picking (
28
50
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 > ,
41
55
mut output : EventWriter < PointerHits > ,
42
56
) {
43
57
for ( pointer, location) in pointers. iter ( ) . filter_map ( |( pointer, pointer_location) | {
@@ -54,58 +68,94 @@ pub fn ui_picking(
54
68
} )
55
69
. map ( |loc| ( pointer, loc) )
56
70
} ) {
57
- let camera = cameras
71
+ let ( window_entity, window) = primary_window. single ( ) ;
72
+ let Some ( ( camera, ui_config) ) = cameras
58
73
. 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
65
76
} )
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
+ } ;
68
80
69
- let cursor_position = location. position ;
70
- let mut blocked = false ;
81
+ if matches ! ( ui_config, Some ( & UiCameraConfig { show_ui: false , .. } ) ) {
82
+ return ;
83
+ }
71
84
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 ;
78
87
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
+ }
80
101
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
+ }
90
109
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
+ ) ;
93
116
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
+ }
103
127
} )
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
+ }
105
155
106
156
output. send ( PointerHits {
107
157
pointer : * pointer,
108
- picks : over_list ,
158
+ picks,
109
159
order : 10 ,
110
160
} )
111
161
}
0 commit comments