1
+ use epaint:: Vec2 ;
2
+
1
3
use crate :: { area, window, Id , IdMap , InputState , LayerId , Pos2 , Rect , Style } ;
2
4
3
5
// ----------------------------------------------------------------------------
@@ -89,6 +91,24 @@ pub struct Memory {
89
91
everything_is_visible : bool ,
90
92
}
91
93
94
+ #[ derive( Clone , Copy , Debug , Default ) ]
95
+ pub enum FocusDirection {
96
+ // Select the widget closest above the current focused widget.
97
+ Up ,
98
+ // Select the widget to the right of the current focused widget.
99
+ Right ,
100
+ // Select the widget below the current focused widget.
101
+ Down ,
102
+ // Select the widget to the left of the the current focused widget.
103
+ Left ,
104
+ // Select the previous widget that had focus.
105
+ Previous ,
106
+ // Select the next widget that wants focus.
107
+ Next ,
108
+ #[ default]
109
+ None ,
110
+ }
111
+
92
112
// ----------------------------------------------------------------------------
93
113
94
114
/// Some global options that you can read and write.
@@ -200,11 +220,11 @@ pub(crate) struct Focus {
200
220
/// If `true`, pressing tab will NOT move focus away from the current widget.
201
221
is_focus_locked : bool ,
202
222
203
- /// Set at the beginning of the frame, set to `false` when "used".
204
- pressed_tab : bool ,
223
+ /// Set when looking for widget with navigational keys like arrows, tab, shift+tab
224
+ focus_direction : FocusDirection ,
205
225
206
- /// Set at the beginning of the frame, set to `false` when "used" .
207
- pressed_shift_tab : bool ,
226
+ /// A cache of widget ids that are interested in focus with their corresponding rectangles .
227
+ focus_widgets_cache : IdMap < Rect > ,
208
228
}
209
229
210
230
impl Interaction {
@@ -236,6 +256,10 @@ impl Interaction {
236
256
}
237
257
238
258
impl Focus {
259
+ pub fn set_id ( & mut self , id : Id ) {
260
+ self . id = Some ( id) ;
261
+ }
262
+
239
263
/// Which widget currently has keyboard focus?
240
264
pub fn focused ( & self ) -> Option < Id > {
241
265
self . id
@@ -244,44 +268,48 @@ impl Focus {
244
268
fn begin_frame ( & mut self , new_input : & crate :: data:: input:: RawInput ) {
245
269
self . id_previous_frame = self . id ;
246
270
if let Some ( id) = self . id_next_frame . take ( ) {
247
- self . id = Some ( id) ;
271
+ self . set_id ( id) ;
248
272
}
249
273
250
274
#[ cfg( feature = "accesskit" ) ]
251
275
{
252
276
self . id_requested_by_accesskit = None ;
253
277
}
254
278
255
- self . pressed_tab = false ;
256
- self . pressed_shift_tab = false ;
257
- for event in & new_input. events {
258
- if matches ! (
259
- event,
260
- crate :: Event :: Key {
261
- key: crate :: Key :: Escape ,
262
- pressed: true ,
263
- modifiers: _,
264
- ..
265
- }
266
- ) {
267
- self . id = None ;
268
- self . is_focus_locked = false ;
269
- break ;
270
- }
279
+ self . focus_direction = FocusDirection :: None ;
271
280
281
+ for event in & new_input. events {
272
282
if let crate :: Event :: Key {
273
- key : crate :: Key :: Tab ,
283
+ key,
274
284
pressed : true ,
275
285
modifiers,
276
286
..
277
287
} = event
278
288
{
279
- if !self . is_focus_locked {
280
- if modifiers. shift {
281
- self . pressed_shift_tab = true ;
282
- } else {
283
- self . pressed_tab = true ;
289
+ if let Some ( cardinality) = match key {
290
+ crate :: Key :: ArrowUp => Some ( FocusDirection :: Up ) ,
291
+ crate :: Key :: ArrowRight => Some ( FocusDirection :: Right ) ,
292
+ crate :: Key :: ArrowDown => Some ( FocusDirection :: Down ) ,
293
+ crate :: Key :: ArrowLeft => Some ( FocusDirection :: Left ) ,
294
+ crate :: Key :: Tab => {
295
+ if !self . is_focus_locked {
296
+ if modifiers. shift {
297
+ Some ( FocusDirection :: Previous )
298
+ } else {
299
+ Some ( FocusDirection :: Next )
300
+ }
301
+ } else {
302
+ None
303
+ }
284
304
}
305
+ crate :: Key :: Escape => {
306
+ self . id = None ;
307
+ self . is_focus_locked = false ;
308
+ Some ( FocusDirection :: None )
309
+ }
310
+ _ => None ,
311
+ } {
312
+ self . focus_direction = cardinality;
285
313
}
286
314
}
287
315
@@ -300,6 +328,15 @@ impl Focus {
300
328
}
301
329
302
330
pub ( crate ) fn end_frame ( & mut self , used_ids : & IdMap < Rect > ) {
331
+ if !matches ! (
332
+ self . focus_direction,
333
+ FocusDirection :: None | FocusDirection :: Previous | FocusDirection :: Next
334
+ ) {
335
+ if let Some ( found_widget) = self . find_widget_in_direction ( used_ids) {
336
+ self . set_id ( found_widget) ;
337
+ }
338
+ }
339
+
303
340
if let Some ( id) = self . id {
304
341
// Allow calling `request_focus` one frame and not using it until next frame
305
342
let recently_gained_focus = self . id_previous_frame != Some ( id) ;
@@ -319,34 +356,125 @@ impl Focus {
319
356
#[ cfg( feature = "accesskit" ) ]
320
357
{
321
358
if self . id_requested_by_accesskit == Some ( id. accesskit_id ( ) ) {
322
- self . id = Some ( id) ;
359
+ self . set_id ( id) ;
323
360
self . id_requested_by_accesskit = None ;
324
361
self . give_to_next = false ;
325
- self . pressed_tab = false ;
326
- self . pressed_shift_tab = false ;
362
+ self . reset_focus ( ) ;
327
363
}
328
364
}
329
365
366
+ // The rect is updated at the end of the frame.
367
+ self . focus_widgets_cache
368
+ . entry ( id)
369
+ . or_insert ( Rect :: EVERYTHING ) ;
370
+
330
371
if self . give_to_next && !self . had_focus_last_frame ( id) {
331
- self . id = Some ( id) ;
372
+ self . set_id ( id) ;
332
373
self . give_to_next = false ;
333
374
} else if self . id == Some ( id) {
334
- if self . pressed_tab && !self . is_focus_locked {
375
+ if matches ! ( self . focus_direction , FocusDirection :: Next ) && !self . is_focus_locked {
335
376
self . id = None ;
336
377
self . give_to_next = true ;
337
- self . pressed_tab = false ;
338
- } else if self . pressed_shift_tab && !self . is_focus_locked {
378
+ self . reset_focus ( ) ;
379
+ } else if matches ! ( self . focus_direction, FocusDirection :: Previous )
380
+ && !self . is_focus_locked
381
+ {
339
382
self . id_next_frame = self . last_interested ; // frame-delay so gained_focus works
340
- self . pressed_shift_tab = false ;
383
+ self . reset_focus ( ) ;
341
384
}
342
- } else if self . pressed_tab && self . id . is_none ( ) && !self . give_to_next {
385
+ } else if matches ! ( self . focus_direction, FocusDirection :: Next )
386
+ && self . id . is_none ( )
387
+ && !self . give_to_next
388
+ {
343
389
// nothing has focus and the user pressed tab - give focus to the first widgets that wants it:
344
- self . id = Some ( id) ;
345
- self . pressed_tab = false ;
390
+ self . set_id ( id) ;
391
+ self . reset_focus ( ) ;
346
392
}
347
393
348
394
self . last_interested = Some ( id) ;
349
395
}
396
+
397
+ pub fn reset_focus ( & mut self ) {
398
+ self . focus_direction = FocusDirection :: None ;
399
+ }
400
+
401
+ pub fn find_widget_in_direction ( & mut self , new_rects : & IdMap < Rect > ) -> Option < Id > {
402
+ let Some ( focus_id) = self . id else { return None ; } ;
403
+
404
+ // Update cache with new rects
405
+ self . focus_widgets_cache . retain ( |id, old_rect| {
406
+ if let Some ( new_rect) = new_rects. get ( id) {
407
+ * old_rect = * new_rect;
408
+ true // Keep the item
409
+ } else {
410
+ false // Remove the item
411
+ }
412
+ } ) ;
413
+
414
+ let current_focus_widget_rect = self . focus_widgets_cache . get ( & focus_id) . unwrap ( ) ;
415
+ let current_widget_pos = current_focus_widget_rect. left_center ( ) ;
416
+
417
+ let mut focus_candidates: Vec < ( & Id , f32 , f32 ) > =
418
+ Vec :: with_capacity ( self . focus_widgets_cache . len ( ) ) ;
419
+
420
+ // The local cache is lagging behind the new rectangles so update them
421
+ for ( widget_id, widget_rect) in & mut self . focus_widgets_cache {
422
+ if Some ( * widget_id) == self . id {
423
+ continue ;
424
+ }
425
+
426
+ let candidate_widget_pos = widget_rect. left_center ( ) ;
427
+
428
+ let current_to_candidate = candidate_widget_pos - current_widget_pos;
429
+
430
+ // Early out if widget is not aligned with the focus direction
431
+ let aligns_with_focus_direction = match self . focus_direction {
432
+ FocusDirection :: Up => current_to_candidate. y < 0.0 ,
433
+ FocusDirection :: Right => current_to_candidate. x > 0.0 ,
434
+ FocusDirection :: Down => current_to_candidate. y > 0.0 ,
435
+ FocusDirection :: Left => current_to_candidate. x < 0.0 ,
436
+ _ => false ,
437
+ } ;
438
+
439
+ if !aligns_with_focus_direction {
440
+ continue ;
441
+ }
442
+
443
+ // Check if the widget has the right dot product and length.
444
+ let focus_direction = match self . focus_direction {
445
+ FocusDirection :: Up => Vec2 :: UP ,
446
+ FocusDirection :: Right => Vec2 :: RIGHT ,
447
+ FocusDirection :: Down => Vec2 :: DOWN ,
448
+ FocusDirection :: Left => Vec2 :: LEFT ,
449
+ _ => {
450
+ continue ;
451
+ }
452
+ } ;
453
+
454
+ let dot_current_candidate = current_to_candidate. dot ( focus_direction) ;
455
+ let distance_current_candidate = current_to_candidate. length ( ) ;
456
+
457
+ // Only interested in widgets that fall in 90 degrees to right or left from focus vector.
458
+ if dot_current_candidate >= 0.0 {
459
+ focus_candidates. push ( (
460
+ widget_id,
461
+ dot_current_candidate,
462
+ distance_current_candidate,
463
+ ) ) ;
464
+ }
465
+ }
466
+
467
+ if !focus_candidates. is_empty ( ) {
468
+ // Select widget based on highest dot product and than lowest distance.
469
+ focus_candidates. sort_by ( |( _, dot1, _) , ( _, dot2, _) | dot2. partial_cmp ( dot1) . unwrap ( ) ) ;
470
+ focus_candidates. sort_by ( |( _, _, len1) , ( _, _, len2) | len1. partial_cmp ( len2) . unwrap ( ) ) ;
471
+
472
+ let ( id, _, _) = focus_candidates[ 0 ] ;
473
+ return Some ( * id) ;
474
+ }
475
+
476
+ None
477
+ }
350
478
}
351
479
352
480
impl Memory {
@@ -430,7 +558,7 @@ impl Memory {
430
558
/// See also [`crate::Response::request_focus`].
431
559
#[ inline( always) ]
432
560
pub fn request_focus ( & mut self , id : Id ) {
433
- self . interaction . focus . id = Some ( id) ;
561
+ self . interaction . focus . set_id ( id) ;
434
562
self . interaction . focus . is_focus_locked = false ;
435
563
}
436
564
0 commit comments