1
1
use crate :: { camera_config:: UiCameraConfig , CalculatedClip , Node , UiStack } ;
2
- use bevy_derive:: { Deref , DerefMut } ;
3
2
use bevy_ecs:: {
4
3
change_detection:: DetectChangesMut ,
5
4
entity:: Entity ,
@@ -32,11 +31,11 @@ use smallvec::SmallVec;
32
31
///
33
32
/// Note that you can also control the visibility of a node using the [`Display`](crate::ui_node::Display) property,
34
33
/// which fully collapses it during layout calculations.
35
- #[ derive( Component , Copy , Clone , Eq , PartialEq , Debug , Reflect , Serialize , Deserialize ) ]
34
+ #[ derive( Component , Copy , Clone , PartialEq , Debug , Reflect , Serialize , Deserialize ) ]
36
35
#[ reflect( Component , Serialize , Deserialize , PartialEq ) ]
37
36
pub enum Interaction {
38
37
/// The node has been clicked
39
- Clicked ,
38
+ Clicked ( Vec2 ) ,
40
39
/// The node has been hovered over
41
40
Hovered ,
42
41
/// Nothing has happened
@@ -53,39 +52,6 @@ impl Default for Interaction {
53
52
}
54
53
}
55
54
56
- /// A component storing the position of the mouse relative to the node, (0., 0.) being the top-left corner and (1., 1.) being the bottom-right
57
- /// If the mouse is not over the node, the value will go beyond the range of (0., 0.) to (1., 1.)
58
- /// A None value means that the cursor position is unknown.
59
- ///
60
- /// It can be used alongside interaction to get the position of the press.
61
- #[ derive(
62
- Component ,
63
- Deref ,
64
- DerefMut ,
65
- Copy ,
66
- Clone ,
67
- Default ,
68
- PartialEq ,
69
- Debug ,
70
- Reflect ,
71
- Serialize ,
72
- Deserialize ,
73
- ) ]
74
- #[ reflect( Component , Serialize , Deserialize , PartialEq ) ]
75
- pub struct RelativeCursorPosition {
76
- /// Cursor position relative to size and position of the Node.
77
- pub normalized : Option < Vec2 > ,
78
- }
79
-
80
- impl RelativeCursorPosition {
81
- /// A helper function to check if the mouse is over the node
82
- pub fn mouse_over ( & self ) -> bool {
83
- self . normalized
84
- . map ( |position| ( 0.0 ..1. ) . contains ( & position. x ) && ( 0.0 ..1. ) . contains ( & position. y ) )
85
- . unwrap_or ( false )
86
- }
87
- }
88
-
89
55
/// Describes whether the node should block interactions with lower nodes
90
56
#[ derive( Component , Copy , Clone , Eq , PartialEq , Debug , Reflect , Serialize , Deserialize ) ]
91
57
#[ reflect( Component , Serialize , Deserialize , PartialEq ) ]
@@ -120,7 +86,6 @@ pub struct NodeQuery {
120
86
node : & ' static Node ,
121
87
global_transform : & ' static GlobalTransform ,
122
88
interaction : Option < & ' static mut Interaction > ,
123
- relative_cursor_position : Option < & ' static mut RelativeCursorPosition > ,
124
89
focus_policy : Option < & ' static FocusPolicy > ,
125
90
calculated_clip : Option < & ' static CalculatedClip > ,
126
91
computed_visibility : Option < & ' static ComputedVisibility > ,
@@ -154,19 +119,17 @@ pub fn ui_focus_system(
154
119
if mouse_released {
155
120
for node in node_query. iter_mut ( ) {
156
121
if let Some ( mut interaction) = node. interaction {
157
- if * interaction == Interaction :: Clicked {
122
+ if matches ! ( * interaction, Interaction :: Clicked ( _ ) ) {
158
123
* interaction = Interaction :: None ;
159
124
}
160
125
}
161
126
}
162
127
}
163
-
164
128
let mouse_clicked =
165
129
mouse_button_input. just_pressed ( MouseButton :: Left ) || touches_input. any_just_pressed ( ) ;
166
130
167
131
let is_ui_disabled =
168
132
|camera_ui| matches ! ( camera_ui, Some ( & UiCameraConfig { show_ui: false , .. } ) ) ;
169
-
170
133
let cursor_position = camera
171
134
. iter ( )
172
135
. filter ( |( _, camera_ui) | !is_ui_disabled ( * camera_ui) )
@@ -189,113 +152,74 @@ pub fn ui_focus_system(
189
152
} )
190
153
. or_else ( || touches_input. first_pressed_position ( ) ) ;
191
154
192
- // prepare an iterator that contains all the nodes that have the cursor in their rect,
193
- // from the top node to the bottom one. this will also reset the interaction to `None`
194
- // for all nodes encountered that are no longer hovered.
195
- let mut moused_over_nodes = ui_stack
196
- . uinodes
197
- . iter ( )
198
- // reverse the iterator to traverse the tree from closest nodes to furthest
199
- . rev ( )
200
- . filter_map ( |entity| {
201
- if let Ok ( node) = node_query. get_mut ( * entity) {
202
- // Nodes that are not rendered should not be interactable
203
- if let Some ( computed_visibility) = node. computed_visibility {
204
- if !computed_visibility. is_visible ( ) {
205
- // Reset their interaction to None to avoid strange stuck state
206
- if let Some ( mut interaction) = node. interaction {
207
- // We cannot simply set the interaction to None, as that will trigger change detection repeatedly
208
- interaction. set_if_neq ( Interaction :: None ) ;
209
- }
210
-
211
- return None ;
155
+ // Iterate through all nodes from top to bottom
156
+ let mut focus_blocked = false ;
157
+ for entity in ui_stack. uinodes . iter ( ) . rev ( ) {
158
+ if let Ok ( node) = node_query. get_mut ( * entity) {
159
+ // Nodes that are not rendered should not be interactable
160
+ if let Some ( computed_visibility) = node. computed_visibility {
161
+ if !computed_visibility. is_visible ( ) {
162
+ // Reset their interaction to None to avoid strange stuck state
163
+ if let Some ( mut interaction) = node. interaction {
164
+ // We cannot simply set the interaction to None, as that will trigger change detection repeatedly
165
+ interaction. set_if_neq ( Interaction :: None ) ;
212
166
}
213
- }
214
167
215
- let position = node. global_transform . translation ( ) ;
216
- let ui_position = position. truncate ( ) ;
217
- let extents = node. node . size ( ) / 2.0 ;
218
- let mut min = ui_position - extents;
219
- if let Some ( clip) = node. calculated_clip {
220
- min = Vec2 :: max ( min, clip. clip . min ) ;
168
+ continue ;
221
169
}
170
+ }
222
171
223
- // The mouse position relative to the node
224
- // (0., 0.) is the top-left corner, (1., 1.) is the bottom-right corner
225
- let relative_cursor_position = cursor_position. map ( |cursor_position| {
226
- Vec2 :: new (
227
- ( cursor_position. x - min. x ) / node. node . size ( ) . x ,
228
- ( cursor_position. y - min. y ) / node. node . size ( ) . y ,
229
- )
230
- } ) ;
231
-
232
- // If the current cursor position is within the bounds of the node, consider it for
233
- // clicking
234
- let relative_cursor_position_component = RelativeCursorPosition {
235
- normalized : relative_cursor_position,
236
- } ;
237
-
238
- let contains_cursor = relative_cursor_position_component. mouse_over ( ) ;
239
-
240
- // Save the relative cursor position to the correct component
241
- if let Some ( mut node_relative_cursor_position_component) =
242
- node. relative_cursor_position
243
- {
244
- * node_relative_cursor_position_component = relative_cursor_position_component;
245
- }
172
+ let position = node. global_transform . translation ( ) ;
173
+ let ui_position = position. truncate ( ) ;
174
+ let extents = node. node . size ( ) / 2.0 ;
175
+ let mut min = ui_position - extents;
176
+ let mut max = ui_position + extents;
177
+ if let Some ( clip) = node. calculated_clip {
178
+ min = Vec2 :: max ( min, clip. clip . min ) ;
179
+ max = Vec2 :: max ( max, clip. clip . max ) ;
180
+ }
246
181
182
+ let contains_cursor = cursor_position
183
+ . map ( |cursor_position| {
184
+ ( min. x ..max. x ) . contains ( & cursor_position. x )
185
+ && ( min. y ..max. y ) . contains ( & cursor_position. y )
186
+ } )
187
+ . unwrap_or ( false ) ;
188
+ if let Some ( mut interaction) = node. interaction {
247
189
if contains_cursor {
248
- Some ( * entity)
249
- } else {
250
- if let Some ( mut interaction) = node. interaction {
251
- if * interaction == Interaction :: Hovered || ( cursor_position. is_none ( ) ) {
190
+ if focus_blocked {
191
+ // don't reset clicked nodes because they're handled separately
192
+ if !matches ! ( * interaction, Interaction :: Clicked ( _) ) {
252
193
interaction. set_if_neq ( Interaction :: None ) ;
253
194
}
195
+ } else if mouse_clicked {
196
+ // only consider nodes with Interaction "clickable"
197
+ if !matches ! ( * interaction, Interaction :: Clicked ( _) ) {
198
+ * interaction = Interaction :: Clicked ( Vec2 :: new (
199
+ cursor_position. unwrap ( ) . x - min. x ,
200
+ cursor_position. unwrap ( ) . y - min. y ,
201
+ ) ) ;
202
+ // if the mouse was simultaneously released, reset this Interaction in the next
203
+ // frame
204
+ if mouse_released {
205
+ state. entities_to_reset . push ( node. entity ) ;
206
+ }
207
+ }
208
+ } else if * interaction == Interaction :: None {
209
+ * interaction = Interaction :: Hovered ;
254
210
}
255
- None
211
+ } else if * interaction == Interaction :: Hovered || cursor_position. is_none ( ) {
212
+ interaction. set_if_neq ( Interaction :: None ) ;
256
213
}
257
- } else {
258
- None
259
214
}
260
- } )
261
- . collect :: < Vec < Entity > > ( )
262
- . into_iter ( ) ;
263
215
264
- // set Clicked or Hovered on top nodes. as soon as a node with a `Block` focus policy is detected,
265
- // the iteration will stop on it because it "captures" the interaction.
266
- let mut iter = node_query. iter_many_mut ( moused_over_nodes. by_ref ( ) ) ;
267
- while let Some ( node) = iter. fetch_next ( ) {
268
- if let Some ( mut interaction) = node. interaction {
269
- if mouse_clicked {
270
- // only consider nodes with Interaction "clickable"
271
- if * interaction != Interaction :: Clicked {
272
- * interaction = Interaction :: Clicked ;
273
- // if the mouse was simultaneously released, reset this Interaction in the next
274
- // frame
275
- if mouse_released {
276
- state. entities_to_reset . push ( node. entity ) ;
216
+ if contains_cursor && !focus_blocked {
217
+ match node. focus_policy . unwrap_or ( & FocusPolicy :: Block ) {
218
+ FocusPolicy :: Block => {
219
+ focus_blocked = true ;
277
220
}
221
+ FocusPolicy :: Pass => { /* allow the next node to be hovered/clicked */ }
278
222
}
279
- } else if * interaction == Interaction :: None {
280
- * interaction = Interaction :: Hovered ;
281
- }
282
- }
283
-
284
- match node. focus_policy . unwrap_or ( & FocusPolicy :: Block ) {
285
- FocusPolicy :: Block => {
286
- break ;
287
- }
288
- FocusPolicy :: Pass => { /* allow the next node to be hovered/clicked */ }
289
- }
290
- }
291
- // reset `Interaction` for the remaining lower nodes to `None`. those are the nodes that remain in
292
- // `moused_over_nodes` after the previous loop is exited.
293
- let mut iter = node_query. iter_many_mut ( moused_over_nodes) ;
294
- while let Some ( node) = iter. fetch_next ( ) {
295
- if let Some ( mut interaction) = node. interaction {
296
- // don't reset clicked nodes because they're handled separately
297
- if * interaction != Interaction :: Clicked {
298
- interaction. set_if_neq ( Interaction :: None ) ;
299
223
}
300
224
}
301
225
}
0 commit comments