zng_ext_input/
focus.rs

1//! Keyboard focus manager.
2
3pub mod iter;
4
5mod focus_info;
6pub use focus_info::*;
7
8use zng_app::{
9    APP, AppExtension, DInstant, INSTANT,
10    access::{ACCESS_CLICK_EVENT, ACCESS_FOCUS_EVENT, ACCESS_FOCUS_NAV_ORIGIN_EVENT},
11    event::{event, event_args},
12    update::{EventUpdate, InfoUpdates, RenderUpdates, UPDATES},
13    view_process::raw_events::RAW_KEY_INPUT_EVENT,
14    widget::{
15        WidgetId,
16        info::{InteractionPath, WIDGET_INFO_CHANGED_EVENT, WidgetBoundsInfo, WidgetInfoTree},
17    },
18    window::WindowId,
19};
20
21pub mod cmd;
22use cmd::FocusCommands;
23use zng_app_context::app_local;
24use zng_ext_window::{WINDOW_FOCUS, WINDOW_FOCUS_CHANGED_EVENT, WINDOWS};
25use zng_layout::unit::{Px, PxPoint, PxRect, TimeUnits};
26use zng_unique_id::{IdEntry, IdMap};
27use zng_var::{Var, var};
28use zng_view_api::window::FrameId;
29
30use std::{mem, time::Duration};
31
32use crate::{mouse::MOUSE_INPUT_EVENT, touch::TOUCH_INPUT_EVENT};
33
34event_args! {
35    /// [`FOCUS_CHANGED_EVENT`] arguments.
36    pub struct FocusChangedArgs {
37        /// Previously focused widget.
38        pub prev_focus: Option<InteractionPath>,
39
40        /// Newly focused widget.
41        pub new_focus: Option<InteractionPath>,
42
43        /// If the focused widget should visually indicate that it is focused.
44        ///
45        /// This is `true` when the focus change is caused by a key press, `false` when it is caused by a mouse click.
46        ///
47        /// Some widgets, like *text input*, may ignore this field and always indicate that they are focused.
48        pub highlight: bool,
49
50        /// What caused this event.
51        pub cause: FocusChangedCause,
52
53        /// Focus navigation actions that can move the focus away from the [`new_focus`].
54        ///
55        /// [`new_focus`]: Self::new_focus
56        pub enabled_nav: FocusNavAction,
57
58        ..
59
60        /// The [`prev_focus`](Self::prev_focus) and [`new_focus`](Self::new_focus).
61        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
62            if let Some(prev) = &self.prev_focus {
63                list.insert_wgt(prev);
64            }
65            if let Some(new) = &self.new_focus {
66                list.insert_wgt(new);
67            }
68        }
69    }
70
71    /// [`RETURN_FOCUS_CHANGED_EVENT`] arguments.
72    pub struct ReturnFocusChangedArgs {
73        /// The scope that returns the focus when focused directly.
74        ///
75        /// Is `None` if the previous focus was the return focus of a scope that was removed.
76        pub scope: Option<InteractionPath>,
77
78        /// Previous return focus of the widget.
79        pub prev_return: Option<InteractionPath>,
80
81        /// New return focus of the widget.
82        pub new_return: Option<InteractionPath>,
83
84        ..
85
86        /// The [`prev_return`](Self::prev_return), [`new_return`](Self::new_return)
87        /// and [`scope`](Self::scope).
88        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
89            if let Some(scope) = &self.scope {
90                list.insert_wgt(scope)
91            }
92            if let Some(prev_return) = &self.prev_return {
93                list.insert_wgt(prev_return)
94            }
95            if let Some(new_return) = &self.new_return {
96                list.insert_wgt(new_return)
97            }
98        }
99    }
100}
101
102impl FocusChangedArgs {
103    /// If the focus is still in the same widget, but the widget path changed.
104    pub fn is_widget_move(&self) -> bool {
105        match (&self.prev_focus, &self.new_focus) {
106            (Some(prev), Some(new)) => prev.widget_id() == new.widget_id() && prev.as_path() != new.as_path(),
107            _ => false,
108        }
109    }
110
111    /// If the focus is still in the same widget path, but some or all interactivity has changed.
112    pub fn is_enabled_change(&self) -> bool {
113        match (&self.prev_focus, &self.new_focus) {
114            (Some(prev), Some(new)) => prev.as_path() == new.as_path() && prev.disabled_index() != new.disabled_index(),
115            _ => false,
116        }
117    }
118
119    /// If the focus is still in the same widget but [`highlight`](FocusChangedArgs::highlight) changed.
120    pub fn is_highlight_changed(&self) -> bool {
121        self.prev_focus == self.new_focus
122    }
123
124    /// If `widget_id` is the new focus and was not before.
125    pub fn is_focus(&self, widget_id: WidgetId) -> bool {
126        match (&self.prev_focus, &self.new_focus) {
127            (Some(prev), Some(new)) => prev.widget_id() != widget_id && new.widget_id() == widget_id,
128            (None, Some(new)) => new.widget_id() == widget_id,
129            (_, None) => false,
130        }
131    }
132
133    /// If `widget_id` is the previous focus and is not now.
134    pub fn is_blur(&self, widget_id: WidgetId) -> bool {
135        match (&self.prev_focus, &self.new_focus) {
136            (Some(prev), Some(new)) => prev.widget_id() == widget_id && new.widget_id() != widget_id,
137            (Some(prev), None) => prev.widget_id() == widget_id,
138            (None, _) => false,
139        }
140    }
141
142    /// If `widget_id` is the new focus or a parent of the new focus and was not the focus nor the parent of the previous focus.
143    pub fn is_focus_enter(&self, widget_id: WidgetId) -> bool {
144        match (&self.prev_focus, &self.new_focus) {
145            (Some(prev), Some(new)) => !prev.contains(widget_id) && new.contains(widget_id),
146            (None, Some(new)) => new.contains(widget_id),
147            (_, None) => false,
148        }
149    }
150
151    /// If `widget_id` is the new focus or a parent of the new focus and is enabled;
152    /// and was not the focus nor the parent of the previous focus or was not enabled.
153    pub fn is_focus_enter_enabled(&self, widget_id: WidgetId) -> bool {
154        match (&self.prev_focus, &self.new_focus) {
155            (Some(prev), Some(new)) => !prev.contains_enabled(widget_id) && new.contains_enabled(widget_id),
156            (None, Some(new)) => new.contains_enabled(widget_id),
157            (_, None) => false,
158        }
159    }
160
161    /// If `widget_id` is the previous focus or a parent of the previous focus and is not the new focus nor a parent of the new focus.
162    pub fn is_focus_leave(&self, widget_id: WidgetId) -> bool {
163        match (&self.prev_focus, &self.new_focus) {
164            (Some(prev), Some(new)) => prev.contains(widget_id) && !new.contains(widget_id),
165            (Some(prev), None) => prev.contains(widget_id),
166            (None, _) => false,
167        }
168    }
169
170    /// If `widget_id` is the previous focus or a parent of the previous focus and was enabled;
171    /// and is not the new focus nor a parent of the new focus or is disabled.
172    pub fn is_focus_leave_enabled(&self, widget_id: WidgetId) -> bool {
173        match (&self.prev_focus, &self.new_focus) {
174            (Some(prev), Some(new)) => prev.contains_enabled(widget_id) && !new.contains_enabled(widget_id),
175            (Some(prev), None) => prev.contains_enabled(widget_id),
176            (None, _) => false,
177        }
178    }
179
180    /// If the widget is the new focus.
181    pub fn is_focused(&self, widget_id: WidgetId) -> bool {
182        self.new_focus.as_ref().map(|p| p.widget_id() == widget_id).unwrap_or(false)
183    }
184
185    /// If the widget is in the new focus path.
186    pub fn is_focus_within(&self, widget_id: WidgetId) -> bool {
187        self.new_focus.as_ref().map(|p| p.contains(widget_id)).unwrap_or(false)
188    }
189}
190
191impl ReturnFocusChangedArgs {
192    /// If the return focus is the same widget but the widget path changed and the widget is still in the same focus scope.
193    pub fn is_widget_move(&self) -> bool {
194        match (&self.prev_return, &self.new_return) {
195            (Some(prev), Some(new)) => prev.widget_id() == new.widget_id() && prev != new,
196            _ => false,
197        }
198    }
199
200    /// If [`scope`](Self::scope) is an ALT scope and `prev_return` or `new_return` if the
201    /// widget outside the scope that will be focused back when the user escapes the ALT scope.
202    pub fn is_alt_return(&self) -> bool {
203        if let Some(scope) = &self.scope {
204            match (&self.prev_return, &self.new_return) {
205                (Some(prev), None) => !prev.contains(scope.widget_id()),
206                (None, Some(new)) => !new.contains(scope.widget_id()),
207                _ => false,
208            }
209        } else {
210            false
211        }
212    }
213
214    /// if the widget was in the [`prev_return`] and is not in the [`new_return`].
215    ///
216    /// [`prev_return`]: Self::prev_return
217    /// [`new_return`]: Self::new_return
218    pub fn lost_return_focus(&self, widget_id: WidgetId) -> bool {
219        self.prev_return.as_ref().map(|p| p.contains(widget_id)).unwrap_or(false)
220            && self.new_return.as_ref().map(|p| !p.contains(widget_id)).unwrap_or(true)
221    }
222
223    /// if the widget was not in the [`prev_return`] and is in the [`new_return`].
224    ///
225    /// [`prev_return`]: Self::prev_return
226    /// [`new_return`]: Self::new_return
227    pub fn got_return_focus(&self, widget_id: WidgetId) -> bool {
228        self.prev_return.as_ref().map(|p| !p.contains(widget_id)).unwrap_or(true)
229            && self.new_return.as_ref().map(|p| p.contains(widget_id)).unwrap_or(false)
230    }
231
232    /// if the widget was the [`prev_return`] and is the [`new_return`].
233    ///
234    /// [`prev_return`]: Self::prev_return
235    /// [`new_return`]: Self::new_return
236    pub fn was_return_focus(&self, widget_id: WidgetId) -> bool {
237        self.prev_return.as_ref().map(|p| p.widget_id() == widget_id).unwrap_or(false)
238            && self.new_return.as_ref().map(|p| p.widget_id() != widget_id).unwrap_or(true)
239    }
240
241    /// if the widget was not the [`prev_return`] and is the [`new_return`].
242    ///
243    /// [`prev_return`]: Self::prev_return
244    /// [`new_return`]: Self::new_return
245    pub fn is_return_focus(&self, widget_id: WidgetId) -> bool {
246        self.prev_return.as_ref().map(|p| p.widget_id() != widget_id).unwrap_or(true)
247            && self.new_return.as_ref().map(|p| p.widget_id() == widget_id).unwrap_or(false)
248    }
249
250    /// If `widget_id` is the new return focus or a parent of the new return and was not a parent of the previous return.
251    pub fn is_return_focus_enter(&self, widget_id: WidgetId) -> bool {
252        match (&self.prev_return, &self.new_return) {
253            (Some(prev), Some(new)) => !prev.contains(widget_id) && new.contains(widget_id),
254            (None, Some(new)) => new.contains(widget_id),
255            (_, None) => false,
256        }
257    }
258
259    /// If `widget_id` is the previous return focus or a parent of the previous return and is not a parent of the new return.
260    pub fn is_return_focus_leave(&self, widget_id: WidgetId) -> bool {
261        match (&self.prev_return, &self.new_return) {
262            (Some(prev), Some(new)) => prev.contains(widget_id) && !new.contains(widget_id),
263            (Some(prev), None) => prev.contains(widget_id),
264            (None, _) => false,
265        }
266    }
267}
268
269/// The cause of a [`FOCUS_CHANGED_EVENT`].
270#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
271pub enum FocusChangedCause {
272    /// The focus changed trying to fulfill the request.
273    Request(FocusRequest),
274
275    /// A focus scope got focus causing its [`FocusScopeOnFocus`] action to execute.
276    ///
277    /// The associated `bool` indicates if the focus was reversed in.
278    ScopeGotFocus(bool),
279
280    /// A previously focused widget, was removed or moved.
281    Recovery,
282}
283impl FocusChangedCause {
284    /// Get focus request target.
285    pub fn request_target(self) -> Option<FocusTarget> {
286        match self {
287            Self::Request(r) => Some(r.target),
288            _ => None,
289        }
290    }
291}
292
293event! {
294    /// Keyboard focused widget changed event.
295    ///
296    /// # Provider
297    ///
298    /// This event is provided by the [`FocusManager`] extension.
299    pub static FOCUS_CHANGED_EVENT: FocusChangedArgs;
300
301    /// Scope return focus widget changed event.
302    ///
303    /// # Provider
304    ///
305    /// This event is provided by the [`FocusManager`] extension.
306    pub static RETURN_FOCUS_CHANGED_EVENT: ReturnFocusChangedArgs;
307}
308
309/// Application extension that manages keyboard focus.
310///
311/// # Events
312///
313/// Events this extension provides.
314///
315/// * [`FOCUS_CHANGED_EVENT`]
316/// * [`RETURN_FOCUS_CHANGED_EVENT`]
317///
318/// # Services
319///
320/// Services this extension provides.
321///
322/// * [`FOCUS`]
323///
324/// # Dependencies
325///
326/// This extension requires the [`WINDOWS`] service.
327///
328/// This extension listens to the [`MOUSE_INPUT_EVENT`], [`TOUCH_INPUT_EVENT`], [`SHORTCUT_EVENT`],
329/// [`WINDOW_FOCUS_CHANGED_EVENT`] and [`WIDGET_INFO_CHANGED_EVENT`].
330///
331/// To work properly it should be added to the app after the windows manager extension.
332///
333/// # About Focus
334///
335/// See the [module level](../) documentation for an overview of the keyboard
336/// focus concepts implemented by this app extension.
337///
338/// [`SHORTCUT_EVENT`]: crate::gesture::SHORTCUT_EVENT
339/// [`WINDOWS`]: zng_ext_window::WINDOWS
340/// [`WINDOW_FOCUS_CHANGED_EVENT`]: zng_ext_window::WINDOW_FOCUS_CHANGED_EVENT
341/// [`WIDGET_INFO_CHANGED_EVENT`]: zng_app::widget::info::WIDGET_INFO_CHANGED_EVENT
342#[derive(Default)]
343pub struct FocusManager {
344    commands: Option<FocusCommands>,
345    pending_render: Option<WidgetInfoTree>,
346}
347
348impl AppExtension for FocusManager {
349    fn init(&mut self) {
350        WINDOW_FOCUS.hook_focus_service(FOCUS.focused());
351        self.commands = Some(FocusCommands::new());
352    }
353
354    fn event_preview(&mut self, update: &mut EventUpdate) {
355        if let Some(args) = WIDGET_INFO_CHANGED_EVENT.on(update) {
356            if FOCUS_SV
357                .read()
358                .focused
359                .as_ref()
360                .map(|f| f.path.window_id() == args.window_id)
361                .unwrap_or_default()
362            {
363                // we need up-to-date due to visibility or spatial movement and that is affected by both layout and render.
364                // so we delay responding to the event if a render or layout was requested when the tree was invalidated.
365                if UPDATES.is_pending_render(args.window_id) {
366                    self.pending_render = Some(args.tree.clone());
367                } else {
368                    // no visual change, update interactivity changes.
369                    self.pending_render = None;
370                    self.on_info_tree_update(args.tree.clone());
371                }
372            }
373            focus_info::FocusTreeData::consolidate_alt_scopes(&args.prev_tree, &args.tree);
374        } else if let Some(args) = ACCESS_FOCUS_EVENT.on(update) {
375            let is_focused = FOCUS.is_focused(args.widget_id).get();
376            if args.focus {
377                if !is_focused {
378                    FOCUS.focus_widget(args.widget_id, false);
379                }
380            } else if is_focused {
381                FOCUS.focus_exit();
382            }
383        } else if let Some(args) = ACCESS_FOCUS_NAV_ORIGIN_EVENT.on(update) {
384            FOCUS.navigation_origin().set(Some(args.widget_id));
385        } else {
386            self.commands.as_mut().unwrap().event_preview(update);
387        }
388    }
389
390    fn render(&mut self, _: &mut RenderUpdates, _: &mut RenderUpdates) {
391        if let Some(tree) = self.pending_render.take() {
392            self.on_info_tree_update(tree);
393        } else {
394            // update visibility or enabled commands, they may have changed if the `spatial_frame_id` changed.
395            let focus = FOCUS_SV.read();
396            let mut invalidated_cmds_or_focused = None;
397
398            if let Some(f) = &focus.focused {
399                let w_id = f.path.window_id();
400                if let Ok(tree) = WINDOWS.widget_tree(w_id)
401                    && focus.enabled_nav.needs_refresh(&tree)
402                {
403                    invalidated_cmds_or_focused = Some(tree);
404                }
405            }
406
407            if let Some(tree) = invalidated_cmds_or_focused {
408                drop(focus);
409                self.on_info_tree_update(tree);
410            }
411        }
412    }
413
414    fn event(&mut self, update: &mut EventUpdate) {
415        let mut request = None;
416
417        if let Some(args) = MOUSE_INPUT_EVENT.on(update) {
418            if args.is_mouse_down() {
419                // click
420                request = Some(FocusRequest::direct_or_exit(args.target.widget_id(), true, false));
421            }
422        } else if let Some(args) = TOUCH_INPUT_EVENT.on(update) {
423            if args.is_touch_start() {
424                // start
425                request = Some(FocusRequest::direct_or_exit(args.target.widget_id(), true, false));
426            }
427        } else if let Some(args) = ACCESS_CLICK_EVENT.on(update) {
428            // click
429            request = Some(FocusRequest::direct_or_exit(args.widget_id, true, false));
430        } else if let Some(args) = WINDOW_FOCUS_CHANGED_EVENT.on(update) {
431            // foreground window maybe changed
432            let mut focus = FOCUS_SV.write();
433            if args.new_focus.is_some()
434                && let Some(pending) = focus.pending_window_focus.take()
435                && args.is_focus(pending.window)
436            {
437                request = Some(FocusRequest::direct_or_related(
438                    pending.target,
439                    pending.nav_origin.is_some(),
440                    pending.highlight,
441                ));
442            }
443            if request.is_none()
444                && let Some(args) = focus.continue_focus()
445            {
446                self.notify(&mut focus, Some(args));
447            }
448
449            if let Some(window_id) = args.closed() {
450                for args in focus.cleanup_returns_win_closed(window_id) {
451                    RETURN_FOCUS_CHANGED_EVENT.notify(args);
452                }
453            }
454        } else if let Some(args) = RAW_KEY_INPUT_EVENT.on(update) {
455            FOCUS_SV.write().last_keyboard_event = args.timestamp;
456        }
457
458        if let Some(request) = request {
459            let mut focus = FOCUS_SV.write();
460            if !matches!(&focus.request, PendingFocusRequest::Update(_)) {
461                focus.request = PendingFocusRequest::None;
462                focus.pending_highlight = false;
463                focus.pending_window_focus = None;
464                let args = focus.fulfill_request(request, false);
465                self.notify(&mut focus, args);
466            }
467        }
468    }
469
470    fn update(&mut self) {
471        let mut focus = FOCUS_SV.write();
472        if let Some((request, is_retry)) = focus.request.take_update() {
473            focus.pending_highlight = false;
474            let args = focus.fulfill_request(request, is_retry);
475            self.notify(&mut focus, args);
476        } else if mem::take(&mut focus.pending_highlight) {
477            let args = focus.continue_focus_highlight(true);
478            self.notify(&mut focus, args);
479        }
480
481        if let Some(wgt_id) = focus.navigation_origin_var.get_new()
482            && wgt_id != focus.navigation_origin
483        {
484            focus.navigation_origin = wgt_id;
485            focus.update_enabled_nav_with_origin();
486            let commands = self.commands.as_mut().unwrap();
487            commands.update_enabled(focus.enabled_nav.nav);
488        }
489    }
490
491    fn info(&mut self, _: &mut InfoUpdates) {
492        let mut focus = FOCUS_SV.write();
493        if let Some(r) = focus.request.take_info() {
494            focus.request = PendingFocusRequest::RetryUpdate(r);
495            UPDATES.update(None);
496        }
497    }
498}
499impl FocusManager {
500    fn on_info_tree_update(&mut self, tree: WidgetInfoTree) {
501        let mut focus = FOCUS_SV.write();
502        let focus = &mut *focus;
503        focus.update_focused_center();
504
505        // widget tree rebuilt or visibility may have changed, check if focus is still valid
506        let args = focus.continue_focus();
507        self.notify(focus, args);
508
509        // cleanup return focuses.
510        for args in focus.cleanup_returns(FocusInfoTree::new(
511            tree,
512            focus.focus_disabled_widgets.get(),
513            focus.focus_hidden_widgets.get(),
514        )) {
515            RETURN_FOCUS_CHANGED_EVENT.notify(args);
516        }
517    }
518
519    fn notify(&mut self, focus: &mut FocusService, args: Option<FocusChangedArgs>) {
520        if let Some(mut args) = args {
521            if !args.highlight && args.new_focus.is_some() && focus.auto_highlight(args.timestamp) {
522                args.highlight = true;
523                focus.is_highlighting = true;
524                focus.is_highlighting_var.set(true);
525            }
526
527            // reentering single child of parent scope that cycles
528            let is_tab_cycle_reentry = matches!(args.cause.request_target(), Some(FocusTarget::Prev | FocusTarget::Next))
529                && match (&args.prev_focus, &args.new_focus) {
530                    (Some(p), Some(n)) => p.contains(n.widget_id()),
531                    _ => false,
532                };
533
534            let reverse = matches!(args.cause.request_target(), Some(FocusTarget::Prev));
535            let prev_focus = args.prev_focus.clone();
536            FOCUS_CHANGED_EVENT.notify(args);
537
538            // may have focused scope.
539            while let Some(after_args) = focus.move_after_focus(is_tab_cycle_reentry, reverse) {
540                FOCUS_CHANGED_EVENT.notify(after_args);
541            }
542
543            for return_args in focus.update_returns(prev_focus) {
544                RETURN_FOCUS_CHANGED_EVENT.notify(return_args);
545            }
546        }
547
548        let commands = self.commands.as_mut().unwrap();
549        commands.update_enabled(focus.enabled_nav.nav);
550    }
551}
552
553app_local! {
554    static FOCUS_SV: FocusService = {
555        APP.extensions().require::<FocusManager>();
556        FocusService::new()
557    };
558}
559
560/// Keyboard focus service.
561///
562/// # Provider
563///
564/// This service is provided by the [`FocusManager`] extension, it will panic if the app is not extended.
565pub struct FOCUS;
566impl FOCUS {
567    /// If set to a duration, starts highlighting focus when a focus change happen within the duration of
568    /// a keyboard input event.
569    ///
570    /// Default is `300.ms()`.
571    #[must_use]
572    pub fn auto_highlight(&self) -> Var<Option<Duration>> {
573        FOCUS_SV.read().auto_highlight.clone()
574    }
575
576    /// If [`DISABLED`] widgets can receive focus.
577    ///
578    /// This is `true` by default, allowing disabled widgets to receive focus can provide a better experience for users,
579    /// as the keyboard navigation stays the same, this is also of special interest for accessibility users, screen readers
580    /// tend to only vocalize the focused content.
581    ///
582    /// Widgets should use a different *focused* visual for disabled focus, it must be clear that the widget has the keyboard focus
583    /// only as a navigation waypoint and cannot provide its normal function.
584    ///
585    /// [`DISABLED`]: zng_app::widget::info::Interactivity::DISABLED
586    #[must_use]
587    pub fn focus_disabled_widgets(&self) -> Var<bool> {
588        FOCUS_SV.read().focus_disabled_widgets.clone()
589    }
590
591    /// If [`Hidden`] widgets can receive focus.
592    ///
593    /// This is `true` by default, with the expectation that hidden widgets are made visible once they receive focus, this is
594    /// particularly important to enable auto-scrolling to view, as widgets inside scroll regions that are far away from the
595    /// viewport are auto-hidden.
596    ///
597    /// Note that widgets can be explicitly made not focusable, so you can disable focus and hide a widget without needing to
598    /// disable this feature globally. Note also that this feature does not apply to collapsed widgets.
599    ///
600    /// [`Hidden`]: zng_app::widget::info::Visibility::Hidden
601    #[must_use]
602    pub fn focus_hidden_widgets(&self) -> Var<bool> {
603        FOCUS_SV.read().focus_hidden_widgets.clone()
604    }
605
606    /// Override the starting point of the next focus move.
607    ///
608    /// Focus requests that move the focus relative to the current focus will move from this widget instead
609    /// if it is found in the focused window. This widget does not need to be focusable.
610    ///
611    /// The variable is cleared every time the focus is moved. Auto focus by click or touch also sets the
612    /// navigation origin if the clicked widget is not focusable.
613    ///
614    /// If not set the [`focused`] widget is the origin.
615    ///
616    /// [`focused`]: Self::focused
617    #[must_use]
618    pub fn navigation_origin(&self) -> Var<Option<WidgetId>> {
619        FOCUS_SV.read().navigation_origin_var.clone()
620    }
621
622    /// Current focused widget.
623    #[must_use]
624    pub fn focused(&self) -> Var<Option<InteractionPath>> {
625        FOCUS_SV.read().focused_var.read_only()
626    }
627
628    /// Current return focus of a scope.
629    #[must_use]
630    pub fn return_focused(&self, scope_id: WidgetId) -> Var<Option<InteractionPath>> {
631        FOCUS_SV
632            .write()
633            .return_focused_var
634            .entry(scope_id)
635            .or_insert_with(|| var(None))
636            .read_only()
637    }
638
639    /// If the [`focused`] path is in the given `window_id`.
640    ///
641    /// [`focused`]: Self::focused
642    pub fn is_window_focused(&self, window_id: WindowId) -> Var<bool> {
643        self.focused().map(move |p| matches!(p, Some(p) if p.window_id() == window_id))
644    }
645
646    /// If the [`focused`] path contains the given `widget_id`.
647    ///
648    /// [`focused`]: Self::focused
649    pub fn is_focus_within(&self, widget_id: WidgetId) -> Var<bool> {
650        self.focused().map(move |p| matches!(p, Some(p) if p.contains(widget_id)))
651    }
652
653    /// If the [`focused`] path is to the given `widget_id`.
654    ///
655    /// [`focused`]: Self::focused
656    pub fn is_focused(&self, widget_id: WidgetId) -> Var<bool> {
657        self.focused().map(move |p| matches!(p, Some(p) if p.widget_id() == widget_id))
658    }
659
660    /// Current ALT return focus.
661    #[must_use]
662    pub fn alt_return(&self) -> Var<Option<InteractionPath>> {
663        FOCUS_SV.read().alt_return_var.read_only()
664    }
665
666    /// If focus is in an ALT scope.
667    #[must_use]
668    pub fn in_alt(&self) -> Var<bool> {
669        FOCUS_SV.read().alt_return_var.map(|p| p.is_some())
670    }
671
672    /// If the current focused widget is visually indicated.
673    #[must_use]
674    pub fn is_highlighting(&self) -> Var<bool> {
675        FOCUS_SV.read().is_highlighting_var.read_only()
676    }
677
678    /// Request a focus update.
679    ///
680    /// All other focus request methods call this method.
681    pub fn focus(&self, mut request: FocusRequest) {
682        let mut f = FOCUS_SV.write();
683        if !request.highlight && f.auto_highlight(INSTANT.now()) {
684            request.highlight = true;
685        }
686        f.pending_window_focus = None;
687        f.request = PendingFocusRequest::Update(request);
688        UPDATES.update(None);
689    }
690
691    /// Enables focus highlight for the current focus if the key-press allows it.
692    fn on_disabled_cmd(&self) {
693        let f = FOCUS_SV.read();
694        if f.auto_highlight.get().is_some() && !f.is_highlighting {
695            drop(f);
696            self.highlight();
697        }
698    }
699
700    /// Schedules enabling of [`is_highlighting`] for next update.
701    ///
702    /// [`is_highlighting`]: Self::is_highlighting
703    pub fn highlight(&self) {
704        let mut f = FOCUS_SV.write();
705        f.pending_highlight = true;
706        UPDATES.update(None);
707    }
708
709    /// Focus the widget if it is focusable and change the highlight.
710    ///
711    /// If the widget is not focusable the focus does not move, in this case the highlight changes
712    /// for the current focused widget.
713    ///
714    /// If the widget is in a window that does not have focus, but is open and not minimized and the app
715    /// has keyboard focus in another window; the window is focused and the request is processed when the focus event is received.
716    /// The [`FocusRequest`] type has other more advanced window focus configurations.
717    ///
718    /// This makes a [`focus`](Self::focus) request using [`FocusRequest::direct`].
719    pub fn focus_widget(&self, widget_id: impl Into<WidgetId>, highlight: bool) {
720        self.focus(FocusRequest::direct(widget_id.into(), highlight));
721    }
722
723    /// Focus the widget if it is focusable, else focus the first focusable parent, also changes the highlight.
724    ///
725    /// If the widget and no parent are focusable the focus does not move, in this case the highlight changes
726    /// for the current focused widget.
727    ///
728    /// If `navigation_origin` is `true` the `target` becomes the [`navigation_origin`] when the first focusable ancestor
729    /// is focused because the `target` is not focusable.
730    ///
731    /// This makes a [`focus`](Self::focus) request using [`FocusRequest::direct_or_exit`].
732    ///
733    /// [`navigation_origin`]: FOCUS::navigation_origin
734    pub fn focus_widget_or_exit(&self, widget_id: impl Into<WidgetId>, navigation_origin: bool, highlight: bool) {
735        self.focus(FocusRequest::direct_or_exit(widget_id.into(), navigation_origin, highlight));
736    }
737
738    /// Focus the widget if it is focusable, else focus the first focusable descendant, also changes the highlight.
739    ///
740    /// If the widget and no child are focusable the focus does not move, in this case the highlight changes for
741    /// the current focused widget.
742    ///
743    /// If `navigation_origin` is `true` the `target` becomes the [`navigation_origin`] when the first focusable descendant
744    /// is focused because the `target` is not focusable.
745    ///
746    /// This makes a [`focus`](Self::focus) request [`FocusRequest::direct_or_enter`].
747    ///
748    /// [`navigation_origin`]: FOCUS::navigation_origin
749    pub fn focus_widget_or_enter(&self, widget_id: impl Into<WidgetId>, navigation_origin: bool, highlight: bool) {
750        self.focus(FocusRequest::direct_or_enter(widget_id.into(), navigation_origin, highlight));
751    }
752
753    /// Focus the widget if it is focusable, else focus the first focusable descendant, else focus the first
754    /// focusable ancestor.
755    ///
756    /// If the widget no focusable widget is found the focus does not move, in this case the highlight changes
757    /// for the current focused widget.
758    ///
759    /// If `navigation_origin` is `true` the `target` becomes the [`navigation_origin`] when the first focusable relative
760    /// is focused because the `target` is not focusable.
761    ///
762    /// This makes a [`focus`](Self::focus) request using [`FocusRequest::direct_or_related`].
763    ///
764    /// [`navigation_origin`]: FOCUS::navigation_origin
765    pub fn focus_widget_or_related(&self, widget_id: impl Into<WidgetId>, navigation_origin: bool, highlight: bool) {
766        self.focus(FocusRequest::direct_or_related(widget_id.into(), navigation_origin, highlight));
767    }
768
769    /// Focus the first logical descendant that is focusable from the navigation origin or the current focus.
770    ///
771    /// Does nothing if no origin or focus is set. Continues highlighting the new focus if the current is highlighted.
772    ///
773    /// This is makes a [`focus`](Self::focus) request using [`FocusRequest::enter`].
774    pub fn focus_enter(&self) {
775        let req = FocusRequest::enter(FOCUS_SV.read().is_highlighting);
776        self.focus(req);
777    }
778
779    /// Focus the first logical ancestor that is focusable from the navigation origin or the current focus
780    /// or the return focus from ALT scopes.
781    ///
782    /// Does nothing if no origin or focus is set. Continues highlighting the new focus if the current is highlighted.
783    ///
784    /// This is makes a [`focus`](Self::focus) request using [`FocusRequest::exit`].
785    pub fn focus_exit(&self) {
786        let req = FocusRequest::exit(FOCUS_SV.read().is_highlighting);
787        self.focus(req)
788    }
789
790    /// Focus the logical next widget from the navigation origin or the current focus.
791    ///
792    /// Does nothing if no origin of focus is set. Continues highlighting the new focus if the current is highlighted.
793    ///
794    /// This is makes a [`focus`](Self::focus) request using [`FocusRequest::next`].
795    pub fn focus_next(&self) {
796        let req = FocusRequest::next(FOCUS_SV.read().is_highlighting);
797        self.focus(req);
798    }
799
800    /// Focus the logical previous widget from the navigation origin or the current focus.
801    ///
802    /// Does nothing if no origin or focus is set. Continues highlighting the new focus if the current is highlighted.
803    ///
804    /// This is makes a [`focus`](Self::focus) request using [`FocusRequest::prev`].
805    pub fn focus_prev(&self) {
806        let req = FocusRequest::prev(FOCUS_SV.read().is_highlighting);
807        self.focus(req);
808    }
809
810    /// Focus the nearest upward widget from the navigation origin or the current focus.
811    ///
812    /// Does nothing if no origin or focus is set. Continues highlighting the new focus if the current is highlighted.
813    ///
814    /// This is makes a [`focus`](Self::focus) request using [`FocusRequest::up`].
815    pub fn focus_up(&self) {
816        let req = FocusRequest::up(FOCUS_SV.read().is_highlighting);
817        self.focus(req);
818    }
819
820    /// Focus the nearest widget to the right of the navigation origin or the current focus.
821    ///
822    /// Does nothing if no origin or focus is set. Continues highlighting the new focus if the current is highlighted.
823    ///
824    /// This is makes a [`focus`](Self::focus) request using [`FocusRequest::right`].
825    pub fn focus_right(&self) {
826        let req = FocusRequest::right(FOCUS_SV.read().is_highlighting);
827        self.focus(req);
828    }
829
830    /// Focus the nearest downward widget from the navigation origin or the current focus.
831    ///
832    /// Does nothing if no origin or focus is set. Continues highlighting the new focus if the current is highlighted.
833    ///
834    /// This is makes a [`focus`](Self::focus) request using [`FocusRequest::down`].
835    pub fn focus_down(&self) {
836        let req = FocusRequest::down(FOCUS_SV.read().is_highlighting);
837        self.focus(req);
838    }
839
840    /// Focus the nearest widget to the left of the navigation origin or the current focus.
841    ///
842    /// Does nothing if no origin or focus is set. Continues highlighting the new focus if the current is highlighted.
843    ///
844    /// This is makes a [`focus`](Self::focus) request using [`FocusRequest::left`].
845    pub fn focus_left(&self) {
846        let req = FocusRequest::left(FOCUS_SV.read().is_highlighting);
847        self.focus(req);
848    }
849
850    /// Focus the ALT scope from the navigation origin or the current focus or escapes the current ALT scope.
851    ///
852    /// Does nothing if no origin or focus is set. Continues highlighting the new focus if the current is highlighted.
853    ///
854    /// This is makes a [`focus`](Self::focus) request using [`FocusRequest::alt`].
855    pub fn focus_alt(&self) {
856        let req = FocusRequest::alt(FOCUS_SV.read().is_highlighting);
857        self.focus(req);
858    }
859}
860
861enum PendingFocusRequest {
862    None,
863    InfoRetry(FocusRequest, DInstant),
864    Update(FocusRequest),
865    RetryUpdate(FocusRequest),
866}
867impl PendingFocusRequest {
868    fn take_update(&mut self) -> Option<(FocusRequest, bool)> {
869        match mem::replace(self, PendingFocusRequest::None) {
870            PendingFocusRequest::Update(r) => Some((r, false)),
871            PendingFocusRequest::RetryUpdate(r) => Some((r, true)),
872            r => {
873                *self = r;
874                None
875            }
876        }
877    }
878    fn take_info(&mut self) -> Option<FocusRequest> {
879        match mem::replace(self, PendingFocusRequest::None) {
880            PendingFocusRequest::InfoRetry(r, i) => {
881                if i.elapsed() < 100.ms() {
882                    Some(r)
883                } else {
884                    None
885                }
886            }
887            r => {
888                *self = r;
889                None
890            }
891        }
892    }
893}
894
895struct PendingWindowFocus {
896    window: WindowId,
897    target: WidgetId,
898    highlight: bool,
899    nav_origin: Option<WidgetId>,
900}
901
902struct FocusService {
903    auto_highlight: Var<Option<Duration>>,
904    last_keyboard_event: DInstant,
905
906    focus_disabled_widgets: Var<bool>,
907    focus_hidden_widgets: Var<bool>,
908
909    request: PendingFocusRequest,
910
911    focused_var: Var<Option<InteractionPath>>,
912    focused: Option<FocusedInfo>,
913    navigation_origin_var: Var<Option<WidgetId>>,
914    navigation_origin: Option<WidgetId>,
915
916    return_focused_var: IdMap<WidgetId, Var<Option<InteractionPath>>>,
917    return_focused: IdMap<WidgetId, InteractionPath>,
918
919    alt_return_var: Var<Option<InteractionPath>>,
920    alt_return: Option<(InteractionPath, InteractionPath)>,
921
922    is_highlighting_var: Var<bool>,
923    is_highlighting: bool,
924
925    enabled_nav: EnabledNavWithFrame,
926
927    pending_window_focus: Option<PendingWindowFocus>,
928    pending_highlight: bool,
929}
930impl FocusService {
931    #[must_use]
932    fn new() -> Self {
933        Self {
934            auto_highlight: var(Some(300.ms())),
935            last_keyboard_event: DInstant::EPOCH,
936
937            focus_disabled_widgets: var(true),
938            focus_hidden_widgets: var(true),
939
940            request: PendingFocusRequest::None,
941
942            focused_var: var(None),
943            focused: None,
944            navigation_origin_var: var(None),
945            navigation_origin: None,
946
947            return_focused_var: IdMap::default(),
948            return_focused: IdMap::default(),
949
950            alt_return_var: var(None),
951            alt_return: None,
952
953            is_highlighting_var: var(false),
954            is_highlighting: false,
955
956            enabled_nav: EnabledNavWithFrame::invalid(),
957
958            pending_window_focus: None,
959            pending_highlight: false,
960        }
961    }
962
963    fn auto_highlight(&self, timestamp: DInstant) -> bool {
964        if let Some(dur) = self.auto_highlight.get()
965            && timestamp.duration_since(self.last_keyboard_event) <= dur
966        {
967            return true;
968        }
969        false
970    }
971
972    fn update_enabled_nav_with_origin(&mut self) {
973        let mut origin = self
974            .focused
975            .as_ref()
976            .and_then(|f| WINDOWS.widget_tree(f.path.window_id()).ok()?.get(f.path.widget_id()));
977        if let Some(id) = self.navigation_origin
978            && let Some(focused) = &origin
979            && let Some(o) = focused.tree().get(id)
980        {
981            origin = Some(o);
982        }
983
984        if let Some(o) = origin {
985            let o = o.into_focus_info(self.focus_disabled_widgets.get(), self.focus_hidden_widgets.get());
986            self.enabled_nav = o.enabled_nav_with_frame();
987        } else {
988            self.enabled_nav.nav = FocusNavAction::empty();
989        }
990    }
991
992    #[must_use]
993    fn fulfill_request(&mut self, request: FocusRequest, is_info_retry: bool) -> Option<FocusChangedArgs> {
994        match request.target {
995            FocusTarget::Direct { target } => self.focus_direct(target, false, request.highlight, false, false, request),
996            FocusTarget::DirectOrExit { target, navigation_origin } => {
997                self.focus_direct(target, navigation_origin, request.highlight, false, true, request)
998            }
999            FocusTarget::DirectOrEnter { target, navigation_origin } => {
1000                self.focus_direct(target, navigation_origin, request.highlight, true, false, request)
1001            }
1002            FocusTarget::DirectOrRelated { target, navigation_origin } => {
1003                self.focus_direct(target, navigation_origin, request.highlight, true, true, request)
1004            }
1005            move_ => {
1006                let origin;
1007                let origin_tree;
1008                if let Some(o) = self.navigation_origin_var.get() {
1009                    origin = Some(o);
1010                    origin_tree = WINDOWS.focused_info();
1011                    self.navigation_origin_var.set(None);
1012                    self.navigation_origin = None;
1013                } else if let Some(prev) = &self.focused {
1014                    origin = Some(prev.path.widget_id());
1015                    origin_tree = WINDOWS.widget_tree(prev.path.window_id()).ok();
1016                } else {
1017                    origin = None;
1018                    origin_tree = None;
1019                }
1020
1021                if let Some(info) = origin_tree
1022                    && let Some(origin) = origin
1023                {
1024                    if let Some(w) = info.get(origin) {
1025                        let w = w.into_focus_info(self.focus_disabled_widgets.get(), self.focus_hidden_widgets.get());
1026                        if let Some(new_focus) = match move_ {
1027                            // tabular
1028                            FocusTarget::Next => w.next_tab(false),
1029                            FocusTarget::Prev => w.prev_tab(false),
1030                            FocusTarget::Enter => w.first_tab_descendant(),
1031                            FocusTarget::Exit => {
1032                                if self.alt_return.is_some() && (w.is_alt_scope() || w.ancestors().any(|w| w.is_alt_scope())) {
1033                                    self.new_focus_for_alt_exit(w, is_info_retry, request.highlight)
1034                                } else {
1035                                    w.ancestors().next()
1036                                }
1037                            }
1038                            // directional
1039                            FocusTarget::Up => w.next_up(),
1040                            FocusTarget::Right => w.next_right(),
1041                            FocusTarget::Down => w.next_down(),
1042                            FocusTarget::Left => w.next_left(),
1043                            // alt
1044                            FocusTarget::Alt => {
1045                                if let Some(alt) = w.alt_scope() {
1046                                    Some(alt)
1047                                } else if self.alt_return.is_some() {
1048                                    // Alt toggles when there is no alt scope.
1049
1050                                    self.new_focus_for_alt_exit(w, is_info_retry, request.highlight)
1051                                } else {
1052                                    None
1053                                }
1054                            }
1055                            // cases covered by parent match
1056                            FocusTarget::Direct { .. }
1057                            | FocusTarget::DirectOrExit { .. }
1058                            | FocusTarget::DirectOrEnter { .. }
1059                            | FocusTarget::DirectOrRelated { .. } => {
1060                                unreachable!()
1061                            }
1062                        } {
1063                            // found `new_focus`
1064                            self.enabled_nav = new_focus.enabled_nav_with_frame();
1065                            self.move_focus(
1066                                Some(FocusedInfo::new(new_focus)),
1067                                None,
1068                                request.highlight,
1069                                FocusChangedCause::Request(request),
1070                            )
1071                        } else {
1072                            // no `new_focus`, maybe update highlight and widget path.
1073                            self.continue_focus_highlight(request.highlight)
1074                        }
1075                    } else {
1076                        // widget not found
1077                        self.continue_focus_highlight(request.highlight)
1078                    }
1079                } else {
1080                    // window not found
1081                    self.continue_focus_highlight(request.highlight)
1082                }
1083            }
1084        }
1085    }
1086
1087    /// Return focus from the alt scope, handles cases when the return focus is temporarily blocked.
1088    #[must_use]
1089    fn new_focus_for_alt_exit(&mut self, prev_w: WidgetFocusInfo, is_info_retry: bool, highlight: bool) -> Option<WidgetFocusInfo> {
1090        let (_, return_path) = self.alt_return.as_ref().unwrap();
1091
1092        let return_int = return_path.interactivity();
1093        let return_id = return_path.widget_id();
1094        let info = prev_w.focus_tree();
1095
1096        let r = info.get_or_parent(return_path);
1097        if let Some(w) = &r
1098            && w.info().id() != return_id
1099            && !is_info_retry
1100            && return_int.is_blocked()
1101        {
1102            // blocked return may not have unblocked yet
1103
1104            if let Some(exists) = info.tree().get(return_id) {
1105                let exists = exists.into_focus_info(info.focus_disabled_widgets(), info.focus_hidden_widgets());
1106                if !exists.is_focusable() && exists.info().interactivity().is_blocked() {
1107                    // Still blocked. A common pattern is to set a `modal` filter on the alt-scope
1108                    // then remove the modal filter when alt-scope loses focus.
1109                    //
1110                    // Here we know that the return focus was blocked after the alt got focus, because
1111                    // blocked widgets can't before return focus, and we know that we are moving focus
1112                    // to some `r`. So we setup an info retry, the focus will move to `r` momentarily,
1113                    // exiting the alt-scope, and if it removes the modal filter the focus will return.
1114                    self.request =
1115                        PendingFocusRequest::InfoRetry(FocusRequest::direct_or_related(return_id, false, highlight), INSTANT.now());
1116                }
1117            }
1118        }
1119
1120        r
1121    }
1122
1123    /// Checks if `focused()` is still valid, if not moves focus to nearest valid.
1124    #[must_use]
1125    fn continue_focus(&mut self) -> Option<FocusChangedArgs> {
1126        if let Some(focused) = &self.focused
1127            && let Ok(true) = WINDOWS.is_focused(focused.path.window_id())
1128        {
1129            let info = WINDOWS.widget_tree(focused.path.window_id()).unwrap();
1130            if let Some(widget) = info
1131                .get(focused.path.widget_id())
1132                .map(|w| w.into_focus_info(self.focus_disabled_widgets.get(), self.focus_hidden_widgets.get()))
1133            {
1134                if widget.is_focusable() {
1135                    // :-) probably in the same place, maybe moved inside same window.
1136                    self.enabled_nav = widget.enabled_nav_with_frame();
1137                    return self.move_focus(
1138                        Some(FocusedInfo::new(widget)),
1139                        self.navigation_origin_var.get(),
1140                        self.is_highlighting,
1141                        FocusChangedCause::Recovery,
1142                    );
1143                } else {
1144                    // widget no longer focusable
1145                    if let Some(parent) = widget.parent() {
1146                        // move to nearest inside focusable parent, or parent
1147                        let new_focus = parent.nearest(focused.center, Px::MAX).unwrap_or(parent);
1148                        self.enabled_nav = new_focus.enabled_nav_with_frame();
1149                        return self.move_focus(
1150                            Some(FocusedInfo::new(new_focus)),
1151                            self.navigation_origin_var.get(),
1152                            self.is_highlighting,
1153                            FocusChangedCause::Recovery,
1154                        );
1155                    } else {
1156                        // no focusable parent or root
1157                        return self.focus_focused_window(self.is_highlighting);
1158                    }
1159                }
1160            } else {
1161                // widget not found
1162                for &parent in focused.path.ancestors().iter().rev() {
1163                    if let Some(parent) = info
1164                        .get(parent)
1165                        .and_then(|w| w.into_focusable(self.focus_disabled_widgets.get(), self.focus_hidden_widgets.get()))
1166                    {
1167                        // move to nearest inside focusable parent, or parent
1168                        let new_focus = parent.nearest(focused.center, Px::MAX).unwrap_or(parent);
1169                        self.enabled_nav = new_focus.enabled_nav_with_frame();
1170                        return self.move_focus(
1171                            Some(FocusedInfo::new(new_focus)),
1172                            self.navigation_origin_var.get(),
1173                            self.is_highlighting,
1174                            FocusChangedCause::Recovery,
1175                        );
1176                    }
1177                }
1178            }
1179        } // else window not found or not focused
1180        // else no current focus
1181        self.focus_focused_window(false)
1182    }
1183
1184    #[must_use]
1185    fn continue_focus_highlight(&mut self, highlight: bool) -> Option<FocusChangedArgs> {
1186        if let Some(mut args) = self.continue_focus() {
1187            args.highlight = highlight;
1188            self.is_highlighting = highlight;
1189            self.is_highlighting_var.set(highlight);
1190            Some(args)
1191        } else if self.is_highlighting != highlight {
1192            self.is_highlighting = highlight;
1193            self.is_highlighting_var.set(highlight);
1194            let focused = self.focused.as_ref().map(|p| p.path.clone());
1195            Some(FocusChangedArgs::now(
1196                focused.clone(),
1197                focused,
1198                highlight,
1199                FocusChangedCause::Recovery,
1200                self.enabled_nav.nav,
1201            ))
1202        } else {
1203            None
1204        }
1205    }
1206
1207    #[must_use]
1208    fn focus_direct(
1209        &mut self,
1210        widget_id: WidgetId,
1211        navigation_origin: bool,
1212        highlight: bool,
1213        fallback_to_children: bool,
1214        fallback_to_parents: bool,
1215        request: FocusRequest,
1216    ) -> Option<FocusChangedArgs> {
1217        let mut next_origin = None;
1218        let mut target = None;
1219        if let Some(w) = WINDOWS
1220            .widget_trees()
1221            .iter()
1222            .find_map(|info| info.get(widget_id))
1223            .map(|w| w.into_focus_info(self.focus_disabled_widgets.get(), self.focus_hidden_widgets.get()))
1224        {
1225            if w.is_focusable() {
1226                let enable = w.enabled_nav_with_frame();
1227                target = Some((FocusedInfo::new(w), enable));
1228            } else if fallback_to_children {
1229                let enable = if navigation_origin {
1230                    next_origin = Some(widget_id);
1231                    Some(w.enabled_nav_with_frame())
1232                } else {
1233                    None
1234                };
1235                if let Some(w) = w.descendants().next() {
1236                    let enable = enable.unwrap_or_else(|| w.enabled_nav_with_frame());
1237                    target = Some((FocusedInfo::new(w), enable));
1238                }
1239            } else if fallback_to_parents {
1240                let enable = if navigation_origin {
1241                    next_origin = Some(widget_id);
1242                    Some(w.enabled_nav_with_frame())
1243                } else {
1244                    None
1245                };
1246                if let Some(w) = w.parent() {
1247                    let enable = enable.unwrap_or_else(|| w.enabled_nav_with_frame());
1248                    target = Some((FocusedInfo::new(w), enable));
1249                }
1250            }
1251        }
1252
1253        if let Some((target, enabled_nav)) = target {
1254            if let Ok(false) = WINDOWS.is_focused(target.path.window_id()) {
1255                if request.force_window_focus || WINDOWS.focused_window_id().is_some() {
1256                    // if can steal focus from other apps or focus is already in another window of the app.
1257                    WINDOWS.focus(target.path.window_id()).unwrap();
1258                } else if request.window_indicator.is_some() {
1259                    // if app does not have focus, focus stealing is not allowed, but a request indicator can be set.
1260                    WINDOWS
1261                        .vars(target.path.window_id())
1262                        .unwrap()
1263                        .focus_indicator()
1264                        .set(request.window_indicator);
1265                }
1266
1267                // will focus when the window is focused
1268                self.pending_window_focus = Some(PendingWindowFocus {
1269                    window: target.path.window_id(),
1270                    target: target.path.widget_id(),
1271                    highlight,
1272                    nav_origin: next_origin,
1273                });
1274                self.navigation_origin = next_origin;
1275                self.navigation_origin_var.set(next_origin);
1276                None
1277            } else {
1278                self.enabled_nav = enabled_nav;
1279                self.move_focus(Some(target), next_origin, highlight, FocusChangedCause::Request(request))
1280            }
1281        } else {
1282            self.navigation_origin = next_origin;
1283            self.navigation_origin_var.set(next_origin);
1284            self.change_highlight(highlight, request)
1285        }
1286    }
1287
1288    #[must_use]
1289    fn change_highlight(&mut self, highlight: bool, request: FocusRequest) -> Option<FocusChangedArgs> {
1290        if self.is_highlighting != highlight {
1291            self.is_highlighting = highlight;
1292            self.is_highlighting_var.set(highlight);
1293            let focused = self.focused.as_ref().map(|p| p.path.clone());
1294            Some(FocusChangedArgs::now(
1295                focused.clone(),
1296                focused,
1297                highlight,
1298                FocusChangedCause::Request(request),
1299                self.enabled_nav.nav,
1300            ))
1301        } else {
1302            None
1303        }
1304    }
1305
1306    #[must_use]
1307    fn focus_focused_window(&mut self, highlight: bool) -> Option<FocusChangedArgs> {
1308        if let Some(info) = WINDOWS.focused_info() {
1309            let info = FocusInfoTree::new(info, self.focus_disabled_widgets.get(), self.focus_hidden_widgets.get());
1310            if let Some(root) = info.focusable_root() {
1311                // found focused window and it is focusable.
1312                self.enabled_nav = root.enabled_nav_with_frame();
1313                self.move_focus(Some(FocusedInfo::new(root)), None, highlight, FocusChangedCause::Recovery)
1314            } else {
1315                // has focused window but it is not focusable.
1316                self.enabled_nav = EnabledNavWithFrame::invalid();
1317                self.move_focus(None, None, false, FocusChangedCause::Recovery)
1318            }
1319        } else {
1320            // no focused window
1321            self.enabled_nav = EnabledNavWithFrame::invalid();
1322            self.move_focus(None, None, false, FocusChangedCause::Recovery)
1323        }
1324    }
1325
1326    #[must_use]
1327    fn move_focus(
1328        &mut self,
1329        new_focus: Option<FocusedInfo>,
1330        new_origin: Option<WidgetId>,
1331        highlight: bool,
1332        cause: FocusChangedCause,
1333    ) -> Option<FocusChangedArgs> {
1334        let prev_highlight = std::mem::replace(&mut self.is_highlighting, highlight);
1335        self.is_highlighting_var.set(highlight);
1336
1337        self.navigation_origin = new_origin;
1338        if self.navigation_origin_var.get() != new_origin {
1339            self.navigation_origin_var.set(new_origin);
1340        }
1341
1342        let r = if self.focused.as_ref().map(|p| &p.path) != new_focus.as_ref().map(|p| &p.path) {
1343            let new_focus = new_focus.as_ref().map(|p| p.path.clone());
1344            let args = FocusChangedArgs::now(
1345                self.focused.take().map(|p| p.path),
1346                new_focus.clone(),
1347                self.is_highlighting,
1348                cause,
1349                self.enabled_nav.nav,
1350            );
1351            self.focused_var.set(new_focus);
1352            Some(args)
1353        } else if prev_highlight != highlight {
1354            let new_focus = new_focus.as_ref().map(|p| p.path.clone());
1355            Some(FocusChangedArgs::now(
1356                new_focus.clone(),
1357                new_focus,
1358                highlight,
1359                cause,
1360                self.enabled_nav.nav,
1361            ))
1362        } else {
1363            None
1364        };
1365
1366        // can be just a center update.
1367        self.focused = new_focus;
1368
1369        r
1370    }
1371
1372    #[must_use]
1373    fn move_after_focus(&mut self, is_tab_cycle_reentry: bool, reverse: bool) -> Option<FocusChangedArgs> {
1374        if let Some(focused) = &self.focused
1375            && let Some(info) = WINDOWS.focused_info()
1376            && let Some(widget) =
1377                FocusInfoTree::new(info, self.focus_disabled_widgets.get(), self.focus_hidden_widgets.get()).get(focused.path.widget_id())
1378        {
1379            if let Some(nested) = widget.nested_window() {
1380                tracing::debug!("focus nested window {nested:?}");
1381                let _ = WINDOWS.focus(nested);
1382            } else if widget.is_scope() {
1383                let last_focused = |id| self.return_focused.get(&id).map(|p| p.as_path());
1384                if let Some(widget) = widget.on_focus_scope_move(last_focused, is_tab_cycle_reentry, reverse) {
1385                    self.enabled_nav = widget.enabled_nav_with_frame();
1386                    return self.move_focus(
1387                        Some(FocusedInfo::new(widget)),
1388                        self.navigation_origin,
1389                        self.is_highlighting,
1390                        FocusChangedCause::ScopeGotFocus(reverse),
1391                    );
1392                }
1393            }
1394        }
1395        None
1396    }
1397
1398    /// Updates `return_focused` and `alt_return` after `focused` changed.
1399    #[must_use]
1400    fn update_returns(&mut self, prev_focus: Option<InteractionPath>) -> Vec<ReturnFocusChangedArgs> {
1401        let mut r = vec![];
1402
1403        if let Some((scope, _)) = &mut self.alt_return {
1404            // if we have an `alt_return` check if is still inside an ALT.
1405
1406            let mut retain_alt = false;
1407            if let Some(new_focus) = &self.focused {
1408                if let Some(s) = new_focus.path.ancestor_path(scope.widget_id()) {
1409                    retain_alt = true; // just a focus move inside the ALT.
1410                    *scope = s.into_owned();
1411                } else if let Ok(info) = WINDOWS.widget_tree(new_focus.path.window_id())
1412                    && let Some(widget) = FocusInfoTree::new(info, self.focus_disabled_widgets.get(), self.focus_hidden_widgets.get())
1413                        .get(new_focus.path.widget_id())
1414                {
1415                    let alt_scope = if widget.is_alt_scope() {
1416                        Some(widget)
1417                    } else {
1418                        widget.scopes().find(|s| s.is_alt_scope())
1419                    };
1420
1421                    if let Some(alt_scope) = alt_scope {
1422                        // entered another ALT
1423                        retain_alt = true;
1424                        *scope = alt_scope.info().interaction_path();
1425                    }
1426                }
1427            }
1428
1429            if !retain_alt {
1430                let (scope, widget_path) = self.alt_return.take().unwrap();
1431                self.alt_return_var.set(None);
1432                r.push(ReturnFocusChangedArgs::now(scope, Some(widget_path), None));
1433            }
1434        } else if let Some(new_focus) = &self.focused {
1435            // if we don't have an `alt_return` but focused something, check if focus
1436            // moved inside an ALT.
1437
1438            if let Ok(info) = WINDOWS.widget_tree(new_focus.path.window_id())
1439                && let Some(widget) = FocusInfoTree::new(info, self.focus_disabled_widgets.get(), self.focus_hidden_widgets.get())
1440                    .get(new_focus.path.widget_id())
1441            {
1442                let alt_scope = if widget.is_alt_scope() {
1443                    Some(widget)
1444                } else {
1445                    widget.scopes().find(|s| s.is_alt_scope())
1446                };
1447                if let Some(alt_scope) = alt_scope {
1448                    let scope = alt_scope.info().interaction_path();
1449                    // entered an alt_scope.
1450
1451                    if let Some(prev) = &prev_focus {
1452                        // previous focus is the return.
1453                        r.push(ReturnFocusChangedArgs::now(scope.clone(), None, Some(prev.clone())));
1454                        self.alt_return = Some((scope, prev.clone()));
1455                        self.alt_return_var.set(prev.clone());
1456                    } else if let Some(parent) = alt_scope.parent() {
1457                        // no previous focus, ALT parent is the return.
1458                        let parent_path = parent.info().interaction_path();
1459                        r.push(ReturnFocusChangedArgs::now(scope.clone(), None, Some(parent_path.clone())));
1460                        self.alt_return = Some((scope, parent_path.clone()));
1461                        self.alt_return_var.set(parent_path);
1462                    }
1463                }
1464            }
1465        }
1466
1467        /*
1468         *   Update `return_focused`
1469         */
1470
1471        if let Some(new_focus) = &self.focused
1472            && let Ok(info) = WINDOWS.widget_tree(new_focus.path.window_id())
1473            && let Some(widget) =
1474                FocusInfoTree::new(info, self.focus_disabled_widgets.get(), self.focus_hidden_widgets.get()).get(new_focus.path.widget_id())
1475            && !widget.is_alt_scope()
1476            && widget.scopes().all(|s| !s.is_alt_scope())
1477        {
1478            // if not inside ALT, update return for each LastFocused parent scopes.
1479
1480            for scope in widget
1481                .scopes()
1482                .filter(|s| s.focus_info().scope_on_focus() == FocusScopeOnFocus::LastFocused)
1483            {
1484                let scope = scope.info().interaction_path();
1485                let path = widget.info().interaction_path();
1486                if let Some(current) = self.return_focused.get_mut(&scope.widget_id()) {
1487                    if current != &path {
1488                        let prev = std::mem::replace(current, path);
1489                        self.return_focused_var.get(&scope.widget_id()).unwrap().set(current.clone());
1490                        r.push(ReturnFocusChangedArgs::now(scope, Some(prev), Some(current.clone())));
1491                    }
1492                } else {
1493                    self.return_focused.insert(scope.widget_id(), path.clone());
1494                    match self.return_focused_var.entry(scope.widget_id()) {
1495                        IdEntry::Occupied(e) => e.get().set(Some(path.clone())),
1496                        IdEntry::Vacant(e) => {
1497                            e.insert(var(Some(path.clone())));
1498                        }
1499                    }
1500                    r.push(ReturnFocusChangedArgs::now(scope, None, Some(path)));
1501                }
1502            }
1503        }
1504
1505        r
1506    }
1507
1508    /// Cleanup `return_focused` and `alt_return` after new widget tree.
1509    #[must_use]
1510    fn cleanup_returns(&mut self, info: FocusInfoTree) -> Vec<ReturnFocusChangedArgs> {
1511        let mut r = vec![];
1512
1513        if self.return_focused_var.len() > 20 {
1514            self.return_focused_var
1515                .retain(|_, var| var.strong_count() > 1 || var.with(Option::is_some))
1516        }
1517
1518        self.return_focused.retain(|&scope_id, widget_path| {
1519            if widget_path.window_id() != info.tree().window_id() {
1520                return true; // retain, not same window.
1521            }
1522
1523            let mut retain = false;
1524
1525            if let Some(widget) = info.tree().get(widget_path.widget_id()) {
1526                if let Some(scope) = widget
1527                    .clone()
1528                    .into_focus_info(info.focus_disabled_widgets(), info.focus_hidden_widgets())
1529                    .scopes()
1530                    .find(|s| s.info().id() == scope_id)
1531                {
1532                    if scope.focus_info().scope_on_focus() == FocusScopeOnFocus::LastFocused {
1533                        retain = true; // retain, widget still exists in same scope and scope still is LastFocused.
1534
1535                        let path = widget.interaction_path();
1536                        if &path != widget_path {
1537                            // widget moved inside scope.
1538                            r.push(ReturnFocusChangedArgs::now(
1539                                scope.info().interaction_path(),
1540                                Some(widget_path.clone()),
1541                                Some(path.clone()),
1542                            ));
1543                            *widget_path = path;
1544                        }
1545                    }
1546                } else if let Some(scope) = info.get(scope_id)
1547                    && scope.focus_info().scope_on_focus() == FocusScopeOnFocus::LastFocused
1548                {
1549                    // widget not inside scope anymore, but scope still exists and is valid.
1550                    if let Some(first) = scope.first_tab_descendant() {
1551                        // LastFocused goes to the first descendant as fallback.
1552                        retain = true;
1553
1554                        let path = first.info().interaction_path();
1555                        r.push(ReturnFocusChangedArgs::now(
1556                            scope.info().interaction_path(),
1557                            Some(widget_path.clone()),
1558                            Some(path.clone()),
1559                        ));
1560                        *widget_path = path;
1561                    }
1562                }
1563            } else if let Some(parent) = info.get_or_parent(widget_path) {
1564                // widget not in window anymore, but a focusable parent is..
1565                if let Some(scope) = parent.scopes().find(|s| s.info().id() == scope_id)
1566                    && scope.focus_info().scope_on_focus() == FocusScopeOnFocus::LastFocused
1567                {
1568                    // ..and the parent is inside the scope, and the scope is still valid.
1569                    retain = true;
1570
1571                    let path = parent.info().interaction_path();
1572                    r.push(ReturnFocusChangedArgs::now(
1573                        scope.info().interaction_path(),
1574                        Some(widget_path.clone()),
1575                        Some(path.clone()),
1576                    ));
1577                    *widget_path = path;
1578                }
1579            }
1580
1581            if !retain {
1582                let scope_path = info.get(scope_id).map(|i| i.info().interaction_path());
1583
1584                if scope_path.is_some() {
1585                    match self.return_focused_var.entry(scope_id) {
1586                        IdEntry::Occupied(e) => {
1587                            if e.get().strong_count() == 1 {
1588                                e.remove();
1589                            } else {
1590                                e.get().set(None);
1591                            }
1592                        }
1593                        IdEntry::Vacant(_) => {}
1594                    }
1595                } else if let Some(var) = self.return_focused_var.remove(&scope_id)
1596                    && var.strong_count() > 1
1597                {
1598                    var.set(None);
1599                }
1600
1601                r.push(ReturnFocusChangedArgs::now(scope_path, Some(widget_path.clone()), None));
1602            }
1603            retain
1604        });
1605
1606        let mut retain_alt = true;
1607        if let Some((scope, widget_path)) = &mut self.alt_return
1608            && widget_path.window_id() == info.tree().window_id()
1609        {
1610            // we need to update alt_return
1611
1612            retain_alt = false; // will retain only if still valid
1613
1614            if let Some(widget) = info.tree().get(widget_path.widget_id()) {
1615                if !widget
1616                    .clone()
1617                    .into_focus_info(info.focus_disabled_widgets(), info.focus_hidden_widgets())
1618                    .scopes()
1619                    .any(|s| s.info().id() == scope.widget_id())
1620                {
1621                    retain_alt = true; // retain, widget still exists outside of the ALT scope.
1622
1623                    let path = widget.interaction_path();
1624                    if &path != widget_path {
1625                        // widget moved without entering the ALT scope.
1626                        r.push(ReturnFocusChangedArgs::now(
1627                            scope.clone(),
1628                            Some(widget_path.clone()),
1629                            Some(path.clone()),
1630                        ));
1631                        *widget_path = path;
1632                    }
1633                }
1634            } else if let Some(parent) = info.get_or_parent(widget_path) {
1635                // widget not in window anymore, but a focusable parent is..
1636                if !parent.scopes().any(|s| s.info().id() == scope.widget_id()) {
1637                    // ..and the parent is not inside the ALT scope.
1638                    retain_alt = true;
1639
1640                    let path = parent.info().interaction_path();
1641                    r.push(ReturnFocusChangedArgs::now(
1642                        scope.clone(),
1643                        Some(widget_path.clone()),
1644                        Some(path.clone()),
1645                    ));
1646                    *widget_path = path.clone();
1647                    self.alt_return_var.set(path);
1648                }
1649            }
1650        }
1651        if !retain_alt {
1652            let (scope_id, widget_path) = self.alt_return.take().unwrap();
1653            self.alt_return_var.set(None);
1654            r.push(ReturnFocusChangedArgs::now(scope_id, Some(widget_path), None));
1655        }
1656
1657        r
1658    }
1659
1660    /// Cleanup `return_focused` and `alt_return` after a window closed.
1661    #[must_use]
1662    fn cleanup_returns_win_closed(&mut self, window_id: WindowId) -> Vec<ReturnFocusChangedArgs> {
1663        let mut r = vec![];
1664
1665        if self
1666            .alt_return
1667            .as_ref()
1668            .map(|(_, w)| w.window_id() == window_id)
1669            .unwrap_or_default()
1670        {
1671            let (_, widget_path) = self.alt_return.take().unwrap();
1672            self.alt_return_var.set(None);
1673            r.push(ReturnFocusChangedArgs::now(None, Some(widget_path), None));
1674        }
1675
1676        self.return_focused.retain(|&scope_id, widget_path| {
1677            let retain = widget_path.window_id() != window_id;
1678
1679            if !retain {
1680                let var = self.return_focused_var.remove(&scope_id).unwrap();
1681                var.set(None);
1682
1683                r.push(ReturnFocusChangedArgs::now(None, Some(widget_path.clone()), None));
1684            }
1685
1686            retain
1687        });
1688
1689        r
1690    }
1691
1692    fn update_focused_center(&mut self) {
1693        if let Some(f) = &mut self.focused {
1694            let bounds = f.bounds_info.inner_bounds();
1695            if bounds != PxRect::zero() {
1696                f.center = bounds.center();
1697            }
1698        }
1699    }
1700}
1701
1702#[derive(Debug)]
1703struct FocusedInfo {
1704    path: InteractionPath,
1705    bounds_info: WidgetBoundsInfo,
1706    center: PxPoint,
1707}
1708impl FocusedInfo {
1709    pub fn new(focusable: WidgetFocusInfo) -> Self {
1710        FocusedInfo {
1711            path: focusable.info().interaction_path(),
1712            bounds_info: focusable.info().bounds_info(),
1713            center: focusable.info().center(),
1714        }
1715    }
1716}
1717
1718struct EnabledNavWithFrame {
1719    nav: FocusNavAction,
1720    spatial_frame_id: FrameId,
1721    visibility_id: FrameId,
1722}
1723impl EnabledNavWithFrame {
1724    fn invalid() -> Self {
1725        Self {
1726            nav: FocusNavAction::empty(),
1727            spatial_frame_id: FrameId::INVALID,
1728            visibility_id: FrameId::INVALID,
1729        }
1730    }
1731    fn needs_refresh(&self, tree: &WidgetInfoTree) -> bool {
1732        let stats = tree.stats();
1733        stats.bounds_updated_frame != self.spatial_frame_id || stats.vis_updated_frame != self.visibility_id
1734    }
1735}
1736trait EnabledNavWithFrameExt {
1737    fn enabled_nav_with_frame(&self) -> EnabledNavWithFrame;
1738}
1739impl EnabledNavWithFrameExt for WidgetFocusInfo {
1740    fn enabled_nav_with_frame(&self) -> EnabledNavWithFrame {
1741        let stats = self.info().tree().stats();
1742        EnabledNavWithFrame {
1743            nav: self.enabled_nav(),
1744            spatial_frame_id: stats.bounds_updated_frame,
1745            visibility_id: stats.vis_updated_frame,
1746        }
1747    }
1748}