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+ } ;
914use 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.
2749pub 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 }
0 commit comments