zng_ext_input/
focus.rs

1//! Keyboard focus manager.
2//!
3//! # Events
4//!
5//! Events this extension provides.
6//!
7//! * [`FOCUS_CHANGED_EVENT`]
8//! * [`RETURN_FOCUS_CHANGED_EVENT`]
9//!
10//! # Services
11//!
12//! Services this extension provides.
13//!
14//! * [`FOCUS`]
15
16pub mod cmd;
17pub mod iter;
18
19mod focus_info;
20pub use focus_info::*;
21use zng_unique_id::{IdEntry, IdMap};
22
23use std::{mem, time::Duration};
24use zng_app::{
25    APP, DInstant,
26    access::{ACCESS_CLICK_EVENT, ACCESS_FOCUS_EVENT, ACCESS_FOCUS_NAV_ORIGIN_EVENT},
27    event::event,
28    event_args, hn,
29    update::UPDATES,
30    view_process::raw_events::RAW_KEY_INPUT_EVENT,
31    widget::{
32        WidgetId,
33        info::{InteractionPath, WIDGET_TREE_CHANGED_EVENT, WidgetInfoTree},
34    },
35    window::WindowId,
36};
37use zng_app_context::app_local;
38use zng_ext_window::{FocusIndicator, WINDOW_FOCUS_CHANGED_EVENT, WINDOWS, WINDOWS_FOCUS, WindowInstanceState};
39use zng_layout::unit::TimeUnits as _;
40use zng_var::{Var, WeakVar, const_var, var};
41
42use crate::{mouse::MOUSE_INPUT_EVENT, touch::TOUCH_INPUT_EVENT};
43
44event_args! {
45    /// [`FOCUS_CHANGED_EVENT`] arguments.
46    pub struct FocusChangedArgs {
47        /// Previously focused widget.
48        pub prev_focus: Option<InteractionPath>,
49
50        /// Newly focused widget.
51        pub new_focus: Option<InteractionPath>,
52
53        /// If the focused widget should visually indicate that it is focused.
54        ///
55        /// This is `true` when the focus change is caused by a key press, `false` when it is caused by a mouse click.
56        ///
57        /// Some widgets, like *text input*, may ignore this field and always indicate that they are focused.
58        pub highlight: bool,
59
60        /// What caused this event.
61        pub cause: FocusChangedCause,
62
63        /// Focus navigation actions that can move the focus away from the [`new_focus`].
64        ///
65        /// [`new_focus`]: Self::new_focus
66        pub enabled_nav: FocusNavAction,
67
68        ..
69
70        /// If is in [`prev_focus`](Self::prev_focus) or [`new_focus`](Self::new_focus).
71        fn is_in_target(&self, id: WidgetId) -> bool {
72            if let Some(prev) = &self.prev_focus
73                && prev.contains(id)
74            {
75                return true;
76            }
77            if let Some(new) = &self.new_focus
78                && new.contains(id)
79            {
80                return true;
81            }
82            false
83        }
84    }
85
86    /// [`RETURN_FOCUS_CHANGED_EVENT`] arguments.
87    pub struct ReturnFocusChangedArgs {
88        /// The scope that returns the focus when focused directly.
89        ///
90        /// Is `None` if the previous focus was the return focus of a scope that was removed.
91        pub scope: Option<InteractionPath>,
92
93        /// Previous return focus of the widget.
94        pub prev_return: Option<InteractionPath>,
95
96        /// New return focus of the widget.
97        pub new_return: Option<InteractionPath>,
98
99        ..
100
101        /// If is in [`prev_return`](Self::prev_return), [`new_return`](Self::new_return)
102        /// or [`scope`](Self::scope).
103        fn is_in_target(&self, id: WidgetId) -> bool {
104            if let Some(scope) = &self.scope
105                && scope.contains(id)
106            {
107                return true;
108            }
109            if let Some(prev_return) = &self.prev_return
110                && prev_return.contains(id)
111            {
112                return true;
113            }
114            if let Some(new_return) = &self.new_return
115                && new_return.contains(id)
116            {
117                return true;
118            }
119            false
120        }
121    }
122}
123
124impl FocusChangedArgs {
125    /// If the focus is still in the same widget, but the widget path changed.
126    pub fn is_widget_move(&self) -> bool {
127        match (&self.prev_focus, &self.new_focus) {
128            (Some(prev), Some(new)) => prev.widget_id() == new.widget_id() && prev.as_path() != new.as_path(),
129            _ => false,
130        }
131    }
132
133    /// If the focus is still in the same widget path, but some or all interactivity has changed.
134    pub fn is_enabled_change(&self) -> bool {
135        match (&self.prev_focus, &self.new_focus) {
136            (Some(prev), Some(new)) => prev.as_path() == new.as_path() && prev.disabled_index() != new.disabled_index(),
137            _ => false,
138        }
139    }
140
141    /// If the focus is still in the same widget but [`highlight`](FocusChangedArgs::highlight) changed.
142    pub fn is_highlight_changed(&self) -> bool {
143        self.prev_focus == self.new_focus
144    }
145
146    /// If `widget_id` is the new focus and was not before.
147    pub fn is_focus(&self, widget_id: WidgetId) -> bool {
148        match (&self.prev_focus, &self.new_focus) {
149            (Some(prev), Some(new)) => prev.widget_id() != widget_id && new.widget_id() == widget_id,
150            (None, Some(new)) => new.widget_id() == widget_id,
151            (_, None) => false,
152        }
153    }
154
155    /// If `widget_id` is the previous focus and is not now.
156    pub fn is_blur(&self, widget_id: WidgetId) -> bool {
157        match (&self.prev_focus, &self.new_focus) {
158            (Some(prev), Some(new)) => prev.widget_id() == widget_id && new.widget_id() != widget_id,
159            (Some(prev), None) => prev.widget_id() == widget_id,
160            (None, _) => false,
161        }
162    }
163
164    /// 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.
165    pub fn is_focus_enter(&self, widget_id: WidgetId) -> bool {
166        match (&self.prev_focus, &self.new_focus) {
167            (Some(prev), Some(new)) => !prev.contains(widget_id) && new.contains(widget_id),
168            (None, Some(new)) => new.contains(widget_id),
169            (_, None) => false,
170        }
171    }
172
173    /// If `widget_id` is the new focus or a parent of the new focus and is enabled;
174    /// and was not the focus nor the parent of the previous focus or was not enabled.
175    pub fn is_focus_enter_enabled(&self, widget_id: WidgetId) -> bool {
176        match (&self.prev_focus, &self.new_focus) {
177            (Some(prev), Some(new)) => !prev.contains_enabled(widget_id) && new.contains_enabled(widget_id),
178            (None, Some(new)) => new.contains_enabled(widget_id),
179            (_, None) => false,
180        }
181    }
182
183    /// 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.
184    pub fn is_focus_leave(&self, widget_id: WidgetId) -> bool {
185        match (&self.prev_focus, &self.new_focus) {
186            (Some(prev), Some(new)) => prev.contains(widget_id) && !new.contains(widget_id),
187            (Some(prev), None) => prev.contains(widget_id),
188            (None, _) => false,
189        }
190    }
191
192    /// If `widget_id` is the previous focus or a parent of the previous focus and was enabled;
193    /// and is not the new focus nor a parent of the new focus or is disabled.
194    pub fn is_focus_leave_enabled(&self, widget_id: WidgetId) -> bool {
195        match (&self.prev_focus, &self.new_focus) {
196            (Some(prev), Some(new)) => prev.contains_enabled(widget_id) && !new.contains_enabled(widget_id),
197            (Some(prev), None) => prev.contains_enabled(widget_id),
198            (None, _) => false,
199        }
200    }
201
202    /// If the widget is the new focus.
203    pub fn is_focused(&self, widget_id: WidgetId) -> bool {
204        self.new_focus.as_ref().map(|p| p.widget_id() == widget_id).unwrap_or(false)
205    }
206
207    /// If the widget is in the new focus path.
208    pub fn is_focus_within(&self, widget_id: WidgetId) -> bool {
209        self.new_focus.as_ref().map(|p| p.contains(widget_id)).unwrap_or(false)
210    }
211}
212
213impl ReturnFocusChangedArgs {
214    /// If the return focus is the same widget but the widget path changed and the widget is still in the same focus scope.
215    pub fn is_widget_move(&self) -> bool {
216        match (&self.prev_return, &self.new_return) {
217            (Some(prev), Some(new)) => prev.widget_id() == new.widget_id() && prev != new,
218            _ => false,
219        }
220    }
221
222    /// If [`scope`](Self::scope) is an ALT scope and `prev_return` or `new_return` if the
223    /// widget outside the scope that will be focused back when the user escapes the ALT scope.
224    pub fn is_alt_return(&self) -> bool {
225        if let Some(scope) = &self.scope {
226            match (&self.prev_return, &self.new_return) {
227                (Some(prev), None) => !prev.contains(scope.widget_id()),
228                (None, Some(new)) => !new.contains(scope.widget_id()),
229                _ => false,
230            }
231        } else {
232            false
233        }
234    }
235
236    /// if the widget was in the [`prev_return`] and is not in the [`new_return`].
237    ///
238    /// [`prev_return`]: Self::prev_return
239    /// [`new_return`]: Self::new_return
240    pub fn lost_return_focus(&self, widget_id: WidgetId) -> bool {
241        self.prev_return.as_ref().map(|p| p.contains(widget_id)).unwrap_or(false)
242            && self.new_return.as_ref().map(|p| !p.contains(widget_id)).unwrap_or(true)
243    }
244
245    /// if the widget was not in the [`prev_return`] and is in the [`new_return`].
246    ///
247    /// [`prev_return`]: Self::prev_return
248    /// [`new_return`]: Self::new_return
249    pub fn got_return_focus(&self, widget_id: WidgetId) -> bool {
250        self.prev_return.as_ref().map(|p| !p.contains(widget_id)).unwrap_or(true)
251            && self.new_return.as_ref().map(|p| p.contains(widget_id)).unwrap_or(false)
252    }
253
254    /// if the widget was the [`prev_return`] and is the [`new_return`].
255    ///
256    /// [`prev_return`]: Self::prev_return
257    /// [`new_return`]: Self::new_return
258    pub fn was_return_focus(&self, widget_id: WidgetId) -> bool {
259        self.prev_return.as_ref().map(|p| p.widget_id() == widget_id).unwrap_or(false)
260            && self.new_return.as_ref().map(|p| p.widget_id() != widget_id).unwrap_or(true)
261    }
262
263    /// if the widget was not the [`prev_return`] and is the [`new_return`].
264    ///
265    /// [`prev_return`]: Self::prev_return
266    /// [`new_return`]: Self::new_return
267    pub fn is_return_focus(&self, widget_id: WidgetId) -> bool {
268        self.prev_return.as_ref().map(|p| p.widget_id() != widget_id).unwrap_or(true)
269            && self.new_return.as_ref().map(|p| p.widget_id() == widget_id).unwrap_or(false)
270    }
271
272    /// If `widget_id` is the new return focus or a parent of the new return and was not a parent of the previous return.
273    pub fn is_return_focus_enter(&self, widget_id: WidgetId) -> bool {
274        match (&self.prev_return, &self.new_return) {
275            (Some(prev), Some(new)) => !prev.contains(widget_id) && new.contains(widget_id),
276            (None, Some(new)) => new.contains(widget_id),
277            (_, None) => false,
278        }
279    }
280
281    /// If `widget_id` is the previous return focus or a parent of the previous return and is not a parent of the new return.
282    pub fn is_return_focus_leave(&self, widget_id: WidgetId) -> bool {
283        match (&self.prev_return, &self.new_return) {
284            (Some(prev), Some(new)) => prev.contains(widget_id) && !new.contains(widget_id),
285            (Some(prev), None) => prev.contains(widget_id),
286            (None, _) => false,
287        }
288    }
289}
290
291/// The cause of a [`FOCUS_CHANGED_EVENT`].
292#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
293pub enum FocusChangedCause {
294    /// The focus changed trying to fulfill the request.
295    Request(FocusRequest),
296
297    /// A focus scope got focus causing its [`FocusScopeOnFocus`] action to execute.
298    ///
299    /// The associated `bool` indicates if the focus was reversed in.
300    ScopeGotFocus(bool),
301
302    /// A previously focused widget, was removed or moved.
303    Recovery,
304}
305impl FocusChangedCause {
306    /// Get focus request target.
307    pub fn request_target(self) -> Option<FocusTarget> {
308        match self {
309            Self::Request(r) => Some(r.target),
310            _ => None,
311        }
312    }
313}
314
315event! {
316    /// Keyboard focused widget changed event.
317    pub static FOCUS_CHANGED_EVENT: FocusChangedArgs {
318        let _ = FOCUS_SV.read();
319    };
320
321    /// Scope return focus widget changed event.
322    pub static RETURN_FOCUS_CHANGED_EVENT: ReturnFocusChangedArgs {
323        let _ = FOCUS_SV.read();
324    };
325}
326
327/// Keyboard focus service.
328pub struct FOCUS;
329impl FOCUS {
330    /// If set to a duration, starts highlighting focus when a focus change happen within the duration of
331    /// a keyboard input event.
332    ///
333    /// Default is `300.ms()`.
334    #[must_use]
335    pub fn auto_highlight(&self) -> Var<Option<Duration>> {
336        FOCUS_SV.read().auto_highlight.clone()
337    }
338
339    /// If [`DISABLED`] widgets can receive focus.
340    ///
341    /// This is `true` by default, allowing disabled widgets to receive focus can provide a better experience for users,
342    /// as the keyboard navigation stays the same, this is also of special interest for accessibility users, screen readers
343    /// tend to only vocalize the focused content.
344    ///
345    /// Widgets should use a different *focused* visual for disabled focus, it must be clear that the widget has the keyboard focus
346    /// only as a navigation waypoint and cannot provide its normal function.
347    ///
348    /// [`DISABLED`]: zng_app::widget::info::Interactivity::DISABLED
349    #[must_use]
350    pub fn focus_disabled_widgets(&self) -> Var<bool> {
351        FOCUS_SV.read().focus_disabled_widgets.clone()
352    }
353
354    /// If [`Hidden`] widgets can receive focus.
355    ///
356    /// This is `true` by default, with the expectation that hidden widgets are made visible once they receive focus, this is
357    /// particularly important to enable auto-scrolling to view, as widgets inside scroll regions that are far away from the
358    /// viewport are auto-hidden.
359    ///
360    /// Note that widgets can be explicitly made not focusable, so you can disable focus and hide a widget without needing to
361    /// disable this feature globally. Note also that this feature does not apply to collapsed widgets.
362    ///
363    /// [`Hidden`]: zng_app::widget::info::Visibility::Hidden
364    #[must_use]
365    pub fn focus_hidden_widgets(&self) -> Var<bool> {
366        FOCUS_SV.read().focus_hidden_widgets.clone()
367    }
368
369    /// Override the starting point of the next focus move.
370    ///
371    /// Focus requests that move the focus relative to the current focus will move from this widget instead
372    /// if it is found in the focused window. This widget does not need to be focusable.
373    ///
374    /// The variable is cleared every time the focus is moved. Auto focus by click or touch also sets the
375    /// navigation origin if the clicked widget is not focusable.
376    ///
377    /// If not set the [`focused`] widget is the origin.
378    ///
379    /// [`focused`]: Self::focused
380    #[must_use]
381    pub fn navigation_origin(&self) -> Var<Option<WidgetId>> {
382        FOCUS_SV.read().navigation_origin.clone()
383    }
384
385    /// Current focused widget.
386    #[must_use]
387    pub fn focused(&self) -> Var<Option<InteractionPath>> {
388        FOCUS_SV.read().focused.read_only()
389    }
390
391    /// Current return focus of a scope.
392    #[must_use]
393    pub fn return_focused(&self, scope_id: WidgetId) -> Var<Option<InteractionPath>> {
394        FOCUS_SV
395            .write()
396            .return_focused
397            .entry(scope_id)
398            .or_insert_with(|| var(None))
399            .read_only()
400    }
401
402    /// If the [`focused`] path is in the given `window_id`.
403    ///
404    /// [`focused`]: Self::focused
405    pub fn is_window_focused(&self, window_id: WindowId) -> Var<bool> {
406        self.focused().map(move |p| matches!(p, Some(p) if p.window_id() == window_id))
407    }
408
409    /// If the [`focused`] path contains the given `widget_id`.
410    ///
411    /// [`focused`]: Self::focused
412    pub fn is_focus_within(&self, widget_id: WidgetId) -> Var<bool> {
413        self.focused().map(move |p| matches!(p, Some(p) if p.contains(widget_id)))
414    }
415
416    /// If the [`focused`] path is to the given `widget_id`.
417    ///
418    /// [`focused`]: Self::focused
419    pub fn is_focused(&self, widget_id: WidgetId) -> Var<bool> {
420        self.focused().map(move |p| matches!(p, Some(p) if p.widget_id() == widget_id))
421    }
422
423    /// If the current focused widget is visually indicated.
424    #[must_use]
425    pub fn is_highlighting(&self) -> Var<bool> {
426        FOCUS_SV.read().is_highlighting.read_only()
427    }
428
429    /// Current [`return_focused`] for the focused ALT scope, or `None` when not scoped in ALT.
430    ///
431    /// [`return_focused`]: Self::return_focused
432    #[must_use]
433    pub fn alt_return(&self) -> Var<Option<InteractionPath>> {
434        {
435            let s = FOCUS_SV.read();
436            if let Some(r) = s.alt_return.upgrade() {
437                return r;
438            }
439        }
440        let focused = FOCUS_SV.read().focused.clone();
441        let r = focused.flat_map(|p| {
442            let s = FOCUS_SV.read();
443            if let Some(p) = p
444                && let Some(tree) = WINDOWS.widget_tree(p.window_id())
445                && let Some(wgt) = tree.get(p.widget_id())
446                && let Some(wgt) = wgt.into_focusable(s.focus_disabled_widgets.get(), s.focus_hidden_widgets.get())
447                && let Some(scope) = wgt.self_and_ancestors().find(|w| w.is_alt_scope())
448            {
449                drop(s);
450                return FOCUS.return_focused(scope.info().id());
451            }
452            const_var(None)
453        });
454        FOCUS_SV.write().alt_return = r.downgrade();
455        r
456    }
457
458    /// Request a focus update.
459    ///
460    /// All other focus request methods call this method.
461    pub fn focus(&self, request: FocusRequest) {
462        let mut f = FOCUS_SV.write();
463        if f.request.is_none() && f.fallback_request.is_none() {
464            UPDATES.once_update("FOCUS.focus", || {
465                FOCUS_SV.write().fulfill_request(None, true);
466            });
467        }
468        if request.fallback_only {
469            f.fallback_request = Some(request);
470        } else {
471            f.request = Some(request);
472        }
473    }
474
475    /// Schedules enabling of [`is_highlighting`] for next update.
476    ///
477    /// [`is_highlighting`]: Self::is_highlighting
478    pub fn highlight(&self) {
479        let mut f = FOCUS_SV.write();
480        if f.request_highlight {
481            return;
482        }
483
484        f.request_highlight = true;
485        if f.request.is_none() && f.fallback_request.is_none() {
486            // `focus` request might not be made
487            UPDATES.once_update("FOCUS.highlight", || {
488                FOCUS_SV.write().fulfill_highlight_request();
489            });
490        }
491    }
492
493    /// Schedules a [`highlight`] is the latest keyboard event was within the [`auto_highlight`] interval.
494    ///
495    /// [`highlight`]: Self::highlight
496    /// [`auto_highlight`]: Self::auto_highlight
497    pub fn highlight_within_auto(&self) {
498        let dur = FOCUS_SV.read().auto_highlight.get();
499        if let Some(dur) = dur
500            && last_keyboard_event().elapsed() <= dur
501        {
502            self.highlight();
503        }
504    }
505
506    /// Focus the widget if it is focusable and change the highlight.
507    ///
508    /// If the widget is not focusable the focus does not move, in this case the highlight changes
509    /// for the current focused widget.
510    ///
511    /// If the widget is in a window that does not have focus, but is open and not minimized and the app
512    /// has keyboard focus in another window; the window is focused and the request is processed when the focus event is received.
513    /// The [`FocusRequest`] type has other more advanced window focus configurations.
514    ///
515    /// This makes a [`focus`](Self::focus) request using [`FocusRequest::direct`].
516    pub fn focus_widget(&self, widget_id: impl Into<WidgetId>, highlight: bool) {
517        self.focus(FocusRequest::direct(widget_id.into(), highlight));
518    }
519
520    /// Focus the root focusable widget in the given window.
521    pub fn focus_window(&self, window_id: impl Into<WindowId>, highlight: bool) {
522        self.focus_window_impl(window_id.into(), highlight);
523    }
524    fn focus_window_impl(&self, window_id: WindowId, highlight: bool) {
525        UPDATES.once_update("FOCUS.focus_window", move || {
526            if let Some(tree) = WINDOWS.widget_tree(window_id) {
527                FOCUS.focus_widget_or_enter(tree.root().id(), false, highlight);
528            }
529        });
530    }
531
532    /// Focus the widget if it is focusable, else focus the first focusable parent, also changes the highlight.
533    ///
534    /// If the widget and no parent are focusable the focus does not move, in this case the highlight changes
535    /// for the current focused widget.
536    ///
537    /// If `navigation_origin` is `true` the `target` always becomes the [`navigation_origin`] even when it is not focusable.
538    ///
539    /// This makes a [`focus`](Self::focus) request using [`FocusRequest::direct_or_exit`].
540    ///
541    /// [`navigation_origin`]: FOCUS::navigation_origin
542    pub fn focus_widget_or_exit(&self, widget_id: impl Into<WidgetId>, navigation_origin: bool, highlight: bool) {
543        self.focus(FocusRequest::direct_or_exit(widget_id.into(), navigation_origin, highlight));
544    }
545
546    /// Focus the widget if it is focusable, else focus the first focusable descendant, also changes the highlight.
547    ///
548    /// If the widget and no child are focusable the focus does not move, in this case the highlight changes for
549    /// the current focused widget.
550    ///
551    /// If `navigation_origin` is `true` the `target` becomes the [`navigation_origin`] when it is not focusable
552    /// and has no focusable descendant.
553    ///
554    /// This makes a [`focus`](Self::focus) request [`FocusRequest::direct_or_enter`].
555    ///
556    /// [`navigation_origin`]: FOCUS::navigation_origin
557    pub fn focus_widget_or_enter(&self, widget_id: impl Into<WidgetId>, navigation_origin: bool, highlight: bool) {
558        self.focus(FocusRequest::direct_or_enter(widget_id.into(), navigation_origin, highlight));
559    }
560
561    /// Focus the widget if it is focusable, else focus the first focusable descendant, else focus the first
562    /// focusable ancestor.
563    ///
564    /// If the widget no focusable widget is found the focus does not move, in this case the highlight changes
565    /// for the current focused widget.
566    ///
567    /// If `navigation_origin` is `true` the `target` becomes the [`navigation_origin`] when it is not focusable
568    /// and has no focusable descendant.
569    ///
570    /// This makes a [`focus`](Self::focus) request using [`FocusRequest::direct_or_related`].
571    ///
572    /// [`navigation_origin`]: FOCUS::navigation_origin
573    pub fn focus_widget_or_related(&self, widget_id: impl Into<WidgetId>, navigation_origin: bool, highlight: bool) {
574        self.focus(FocusRequest::direct_or_related(widget_id.into(), navigation_origin, highlight));
575    }
576
577    /// Focus the first logical descendant that is focusable from the navigation origin or the current focus.
578    ///
579    /// Does nothing if no origin or focus is set. Continues highlighting the new focus if the current is highlighted.
580    ///
581    /// This is makes a [`focus`](Self::focus) request using [`FocusRequest::enter`].
582    pub fn focus_enter(&self) {
583        let req = FocusRequest::enter(FOCUS_SV.read().is_highlighting.get());
584        self.focus(req);
585    }
586
587    /// Focus the first logical ancestor that is focusable from the navigation origin or the current focus
588    /// or the return focus from ALT scopes.
589    ///
590    /// If `recursive_alt` is set and is exiting from an ALT scope recursively seek the return widget that is not inside any ALT scope.
591    ///
592    /// Does nothing if no origin or focus is set. Continues highlighting the new focus if the current is highlighted.
593    ///
594    /// This is makes a [`focus`](Self::focus) request using [`FocusRequest::exit`].
595    pub fn focus_exit(&self, recursive_alt: bool) {
596        let req = FocusRequest::exit(recursive_alt, FOCUS_SV.read().is_highlighting.get());
597        self.focus(req)
598    }
599
600    /// Focus the logical next widget from the navigation origin or the current focus.
601    ///
602    /// Does nothing if no origin of focus is set. Continues highlighting the new focus if the current is highlighted.
603    ///
604    /// This is makes a [`focus`](Self::focus) request using [`FocusRequest::next`].
605    pub fn focus_next(&self) {
606        let req = FocusRequest::next(FOCUS_SV.read().is_highlighting.get());
607        self.focus(req);
608    }
609
610    /// Focus the logical previous widget from the navigation origin or the current focus.
611    ///
612    /// Does nothing if no origin or focus is set. Continues highlighting the new focus if the current is highlighted.
613    ///
614    /// This is makes a [`focus`](Self::focus) request using [`FocusRequest::prev`].
615    pub fn focus_prev(&self) {
616        let req = FocusRequest::prev(FOCUS_SV.read().is_highlighting.get());
617        self.focus(req);
618    }
619
620    /// Focus the nearest upward widget from the navigation origin or the current focus.
621    ///
622    /// Does nothing if no origin or focus is set. Continues highlighting the new focus if the current is highlighted.
623    ///
624    /// This is makes a [`focus`](Self::focus) request using [`FocusRequest::up`].
625    pub fn focus_up(&self) {
626        let req = FocusRequest::up(FOCUS_SV.read().is_highlighting.get());
627        self.focus(req);
628    }
629
630    /// Focus the nearest widget to the right of the navigation origin or the current focus.
631    ///
632    /// Does nothing if no origin or focus is set. Continues highlighting the new focus if the current is highlighted.
633    ///
634    /// This is makes a [`focus`](Self::focus) request using [`FocusRequest::right`].
635    pub fn focus_right(&self) {
636        let req = FocusRequest::right(FOCUS_SV.read().is_highlighting.get());
637        self.focus(req);
638    }
639
640    /// Focus the nearest downward widget from the navigation origin or the current focus.
641    ///
642    /// Does nothing if no origin or focus is set. Continues highlighting the new focus if the current is highlighted.
643    ///
644    /// This is makes a [`focus`](Self::focus) request using [`FocusRequest::down`].
645    pub fn focus_down(&self) {
646        let req = FocusRequest::down(FOCUS_SV.read().is_highlighting.get());
647        self.focus(req);
648    }
649
650    /// Focus the nearest widget to the left of the navigation origin or the current focus.
651    ///
652    /// Does nothing if no origin or focus is set. Continues highlighting the new focus if the current is highlighted.
653    ///
654    /// This is makes a [`focus`](Self::focus) request using [`FocusRequest::left`].
655    pub fn focus_left(&self) {
656        let req = FocusRequest::left(FOCUS_SV.read().is_highlighting.get());
657        self.focus(req);
658    }
659
660    /// Focus the ALT scope from the navigation origin or the current focus or escapes the current ALT scope.
661    ///
662    /// Does nothing if no origin or focus is set. Continues highlighting the new focus if the current is highlighted.
663    ///
664    /// This is makes a [`focus`](Self::focus) request using [`FocusRequest::alt`].
665    pub fn focus_alt(&self) {
666        let req = FocusRequest::alt(FOCUS_SV.read().is_highlighting.get());
667        self.focus(req);
668    }
669}
670
671zng_env::on_process_start!(|args| {
672    if args.yield_until_app() {
673        return;
674    }
675
676    APP.on_init(hn!(|args| {
677        WINDOWS_FOCUS.hook_focus_service(FOCUS.focused());
678    }));
679});
680
681app_local! {
682    static FOCUS_SV: FocusService = FocusService::new();
683}
684struct FocusService {
685    auto_highlight: Var<Option<Duration>>,
686    focus_disabled_widgets: Var<bool>,
687    focus_hidden_widgets: Var<bool>,
688
689    navigation_origin: Var<Option<WidgetId>>,
690    focused: Var<Option<InteractionPath>>,
691    return_focused: IdMap<WidgetId, Var<Option<InteractionPath>>>,
692    is_highlighting: Var<bool>,
693    alt_return: WeakVar<Option<InteractionPath>>,
694
695    commands: cmd::FocusCommands,
696
697    request: Option<FocusRequest>,
698    fallback_request: Option<FocusRequest>,
699    request_highlight: bool,
700
701    enabled_nav: FocusNavAction,
702}
703fn last_keyboard_event() -> DInstant {
704    RAW_KEY_INPUT_EVENT
705        .with(|v| v.latest().map(|a| a.timestamp))
706        .unwrap_or(DInstant::EPOCH)
707}
708impl FocusService {
709    fn new() -> Self {
710        hooks();
711        let s = Self {
712            auto_highlight: var(Some(300.ms())),
713            focus_disabled_widgets: var(true),
714            focus_hidden_widgets: var(true),
715
716            navigation_origin: var(None),
717            focused: var(None),
718            return_focused: IdMap::default(),
719            is_highlighting: var(false),
720            alt_return: WeakVar::new(),
721
722            commands: cmd::FocusCommands::new(),
723
724            request: None,
725            fallback_request: None,
726            request_highlight: false,
727
728            enabled_nav: FocusNavAction::empty(),
729        };
730        fn refresh(_: &zng_var::VarHookArgs<bool>) -> bool {
731            let mut s = FOCUS_SV.write();
732            if let Some(id) = s.focused.with(|p| p.as_ref().map(|p| p.widget_id())) {
733                tracing::trace!("focus_disabled_widgets or focus_hidden_widgets changed recovery");
734                s.focus_direct_recovery(id, None);
735            }
736            true
737        }
738        s.focus_disabled_widgets.hook(refresh).perm();
739        s.focus_hidden_widgets.hook(refresh).perm();
740        s
741    }
742
743    fn fulfill_request(&mut self, tree_hint: Option<&WidgetInfoTree>, is_service_request: bool) {
744        // resolve what request to fulfill
745        let mut request = self.request.take().or(self.fallback_request.take()).unwrap();
746
747        if mem::take(&mut self.request_highlight) {
748            // there was also a highlight request
749            request.highlight = true;
750        } else if !request.highlight
751            && let Some(dur) = self.auto_highlight.get()
752            && last_keyboard_event().elapsed() <= dur
753        {
754            // there was also keyboard interaction within the auto_highlight interval
755            tracing::trace!("last keyboard event within {dur:?}, highlight");
756            request.highlight = true;
757        }
758
759        let focus_disabled = self.focus_disabled_widgets.get();
760        let focus_hidden = self.focus_hidden_widgets.get();
761
762        // find the current focus info
763        let current_info = self
764            .focused
765            .with(|p| match p {
766                Some(p) => {
767                    if let Some(t) = &tree_hint
768                        && t.window_id() == p.window_id()
769                    {
770                        t.get(p.widget_id())
771                    } else {
772                        WINDOWS.widget_tree(p.window_id()).and_then(|t| t.get(p.widget_id()))
773                    }
774                }
775                None => None,
776            })
777            .map(|i| i.into_focus_info(focus_disabled, focus_hidden));
778
779        // search for widget
780        let find_wgt = |id| {
781            if let Some(c) = &current_info
782                && let Some(r) = c.info().tree().get(id)
783            {
784                return Some(r.into_focus_info(focus_disabled, focus_hidden));
785            }
786            if let Some(t) = &tree_hint
787                && let Some(r) = t.get(id)
788            {
789                return Some(r.into_focus_info(focus_disabled, focus_hidden));
790            }
791            WINDOWS.widget_info(id).map(|r| r.into_focus_info(focus_disabled, focus_hidden))
792        };
793
794        // navigation origin
795        let origin_info = self
796            .navigation_origin
797            .get()
798            .and_then(|id| current_info.as_ref().and_then(|i| i.info().tree().get(id)))
799            .map(|i| i.into_focus_info(focus_disabled, focus_hidden))
800            .or_else(|| current_info.clone());
801
802        // resolve the new focus
803        let mut new_info = None;
804        let mut new_origin = None;
805        match request.target {
806            FocusTarget::Direct { target } => match find_wgt(target) {
807                Some(w) => {
808                    if w.is_focusable() {
809                        tracing::trace!("focus {:?}", w.info().id());
810                        new_info = Some(w);
811                    } else {
812                        tracing::debug!("cannot focus {target}, not focusable")
813                    }
814                }
815                None => tracing::debug!("cannot focus {target}, not found"),
816            },
817            FocusTarget::DirectOrExit { target, navigation_origin } => match find_wgt(target) {
818                Some(w) => {
819                    if w.is_focusable() {
820                        tracing::trace!("focus {:?}", w.info().id());
821                        new_info = Some(w);
822                    } else {
823                        tracing::debug!("cannot focus {target}, not focusable, will try ancestors");
824                        match w.ancestors().next() {
825                            Some(actual) => {
826                                tracing::trace!("focusing ancestor {:?}", actual.info().id());
827                                new_info = Some(actual);
828                            }
829                            None => {
830                                tracing::debug!("cannot focus {target} or ancestor, none focusable in path");
831                            }
832                        }
833                        if navigation_origin {
834                            new_origin = Some(w.info().id());
835                        }
836                    }
837                }
838                None => tracing::debug!("cannot focus {target} or ancestor, not found"),
839            },
840            FocusTarget::DirectOrEnter { target, navigation_origin } => match find_wgt(target) {
841                Some(w) => {
842                    if w.is_focusable() {
843                        tracing::trace!("focus {:?}", w.info().id());
844                        new_info = Some(w);
845                    } else {
846                        tracing::debug!("cannot focus {target}, not focusable, will try descendants");
847                        match w.first_tab_descendant() {
848                            Some(actual) => {
849                                tracing::trace!("focusing descendant {:?}", actual.info().id());
850                                new_info = Some(actual);
851                            }
852                            None => {
853                                if navigation_origin {
854                                    new_origin = Some(w.info().id());
855                                }
856                                tracing::debug!("cannot focus {target} or descendants, none tab focusable in subtree");
857                            }
858                        }
859                    }
860                }
861                None => tracing::debug!("cannot focus {target} or descendants, not found"),
862            },
863            FocusTarget::DirectOrRelated { target, navigation_origin } => match find_wgt(target) {
864                Some(w) => {
865                    if w.is_focusable() {
866                        tracing::trace!("focus {:?}", w.info().id());
867                        new_info = Some(w);
868                    } else {
869                        tracing::debug!("cannot focus {target}, not focusable, will try descendants and ancestors");
870                        match w
871                            .first_tab_descendant()
872                            .map(|w| (w, false))
873                            .or_else(|| w.descendants().next().map(|w| (w, false)))
874                            .or_else(|| w.ancestors().next().map(|w| (w, true)))
875                        {
876                            Some((actual, is_ancestor)) => {
877                                if navigation_origin && is_ancestor {
878                                    new_origin = Some(w.info().id());
879                                }
880                                tracing::trace!(
881                                    "focusing {} {:?}",
882                                    if is_ancestor { "ancestor" } else { "descendant" },
883                                    actual.info().id()
884                                );
885                                new_info = Some(actual);
886                            }
887                            None => {
888                                if navigation_origin {
889                                    new_origin = Some(w.info().id());
890                                }
891                                tracing::debug!("cannot focus {target} or descendants or ancestors, none focusable")
892                            }
893                        }
894                    }
895                }
896                None => {
897                    tracing::debug!("cannot focus {target} or descendants or ancestors, not found");
898                    if !is_service_request {
899                        let focused = self.focused.get();
900                        if let Some(focused) = focused
901                            && focused.widget_id() == target
902                        {
903                            // this is is a 'recovery' request, wait one update in case the widget
904                            // is just moving, if it is really removed try the ancestors
905                            UPDATES.once_next_update("delayed-focus-recovery", move || {
906                                let mut s = FOCUS_SV.write();
907                                let focus_did_not_change = s.focused.with(|p| p.as_ref() == Some(&focused));
908                                if focus_did_not_change && s.request.is_none() {
909                                    for wgt in focused.widgets_path().iter().rev() {
910                                        if let Some(wgt) = WINDOWS.widget_info(*wgt) {
911                                            s.focus_direct_recovery(wgt.id(), Some(wgt.tree()));
912                                            break;
913                                        }
914                                    }
915                                }
916                            })
917                        }
918                    }
919                }
920            },
921            FocusTarget::Enter => match &origin_info {
922                Some(i) => {
923                    new_info = i.first_tab_descendant();
924                    tracing::trace!("enter {:?}, focus {:?}", i.info().id(), new_info.as_ref().map(|w| w.info().id()));
925                }
926                None => tracing::debug!("cannot enter focused, no current focus"),
927            },
928            FocusTarget::Exit { recursive_alt } => match &origin_info {
929                Some(i) => {
930                    let mut alt = i.self_and_ancestors().find(|s| s.is_alt_scope());
931                    let mut recursive_alt_scopes = vec![];
932                    while let Some(a) = alt.take() {
933                        if recursive_alt_scopes.contains(&a.info().id()) {
934                            tracing::error!("circular alt return focus, {recursive_alt_scopes:?}");
935                            break;
936                        }
937
938                        if let Some(r) = self.return_focused.get(&a.info().id())
939                            && let Some(r) = r.with(|p| p.as_ref().map(|p| p.widget_id()))
940                            && let Some(r) = find_wgt(r)
941                            && r.is_focusable()
942                        {
943                            // ALT has valid return
944                            if recursive_alt {
945                                recursive_alt_scopes.push(a.info().id());
946                                alt = r.self_and_ancestors().find(|s| s.is_alt_scope());
947                                if alt.is_some() {
948                                    continue;
949                                }
950                                tracing::trace!(
951                                    "exit {:?}, alt {:?}, return {:?}",
952                                    i.info().id(),
953                                    recursive_alt_scopes,
954                                    r.info().id()
955                                );
956                            } else {
957                                tracing::trace!("exit {:?}, alt {:?}, return {:?}", i.info().id(), a.info().id(), r.info().id());
958                            }
959                            new_info = Some(r);
960                        } else {
961                            // ALT has no valid return
962                            if recursive_alt {
963                                new_info = a.ancestors().find(|w| !w.in_alt_scope());
964                            } else {
965                                new_info = a.ancestors().next();
966                            }
967                            tracing::debug!(
968                                "exit {:?}, alt {:?}, no return, focus {:?}",
969                                i.info().id(),
970                                a.info().id(),
971                                new_info.as_ref().map(|w| w.info().id())
972                            );
973                        }
974                    }
975                    if new_info.is_none() {
976                        new_info = i.ancestors().next();
977                        tracing::trace!("exit {:?}, focus {:?}", i.info().id(), new_info.as_ref().map(|w| w.info().id()));
978                    }
979                }
980                None => tracing::debug!("cannot exit focused, no current focus"),
981            },
982            FocusTarget::Next => match &origin_info {
983                Some(i) => {
984                    new_info = i.next_tab(false);
985                    tracing::trace!(
986                        "next from {:?}, focus {:?}",
987                        i.info().id(),
988                        new_info.as_ref().map(|w| w.info().id())
989                    );
990                }
991                None => tracing::debug!("cannot focus next, no current focus"),
992            },
993            FocusTarget::Prev => match &origin_info {
994                Some(i) => {
995                    new_info = i.prev_tab(false);
996                    tracing::trace!(
997                        "prev from {:?}, focus {:?}",
998                        i.info().id(),
999                        new_info.as_ref().map(|w| w.info().id())
1000                    );
1001                }
1002                None => tracing::debug!("cannot focus prev, no current focus"),
1003            },
1004            FocusTarget::Up => match &origin_info {
1005                Some(i) => {
1006                    new_info = i.next_up();
1007                    tracing::trace!("up from {:?}, focus {:?}", i.info().id(), new_info.as_ref().map(|w| w.info().id()));
1008                }
1009                None => tracing::debug!("cannot focus up, no current focus"),
1010            },
1011            FocusTarget::Right => match &origin_info {
1012                Some(i) => {
1013                    new_info = i.next_right();
1014                    tracing::trace!(
1015                        "right from {:?}, focus {:?}",
1016                        i.info().id(),
1017                        new_info.as_ref().map(|w| w.info().id())
1018                    );
1019                }
1020                None => tracing::debug!("cannot focus right, no current focus"),
1021            },
1022            FocusTarget::Down => match &origin_info {
1023                Some(i) => {
1024                    new_info = i.next_down();
1025                    tracing::trace!(
1026                        "down from {:?}, focus {:?}",
1027                        i.info().id(),
1028                        new_info.as_ref().map(|w| w.info().id())
1029                    );
1030                }
1031                None => tracing::debug!("cannot focus down, no current focus"),
1032            },
1033            FocusTarget::Left => match &origin_info {
1034                Some(i) => {
1035                    new_info = i.next_left();
1036                    tracing::trace!(
1037                        "left from {:?}, focus {:?}",
1038                        i.info().id(),
1039                        new_info.as_ref().map(|w| w.info().id())
1040                    );
1041                }
1042                None => tracing::debug!("cannot focus left, no current focus"),
1043            },
1044            FocusTarget::Alt => match &origin_info {
1045                Some(i) => {
1046                    if let Some(alt) = i.self_and_ancestors().find(|w| w.is_alt_scope()) {
1047                        // Alt inside ALT scope returns focus
1048                        if let Some(r) = self.return_focused.get(&alt.info().id())
1049                            && let Some(r) = r.with(|p| p.as_ref().map(|p| p.widget_id()))
1050                            && let Some(r) = find_wgt(r)
1051                            && r.is_focusable()
1052                        {
1053                            tracing::trace!("toggle alt from alt scope, exit to return {:?}", r.info().id());
1054                            new_info = Some(r);
1055                        } else {
1056                            tracing::trace!("is in alt scope without return focus, exiting to window root focusable");
1057                            new_info = i.focus_tree().focusable_root();
1058                            tracing::trace!(
1059                                "toggle alt from alt scope, to window root {:?}",
1060                                new_info.as_ref().map(|w| w.info().id())
1061                            );
1062                        }
1063                    } else {
1064                        new_info = i.alt_scope();
1065                        tracing::trace!("alt into alt scope {:?}", new_info.as_ref().map(|w| w.info().id()));
1066                    }
1067                }
1068                None => tracing::debug!("cannot focus alt, no current focus"),
1069            },
1070        }
1071
1072        let new_info = match new_info {
1073            Some(i) => i, // new_info was selected
1074            None => match current_info.clone() {
1075                // no new_info was selected, continue with current_info but check highlight and enabled_nav changes
1076                Some(i) => i,
1077                // has no focus and continues without
1078                None => return,
1079            },
1080        };
1081
1082        let current_highlight = self.is_highlighting.get();
1083        let new_highlight = request.highlight;
1084
1085        let mut new_enabled_nav = new_info.enabled_nav();
1086
1087        let prev_focus = self.focused.get();
1088        let mut new_focus = Some(new_info.info().interaction_path());
1089
1090        if prev_focus == new_focus && current_highlight == new_highlight && self.enabled_nav == new_enabled_nav {
1091            // no change
1092            tracing::trace!("no focus change");
1093            return;
1094        }
1095
1096        if let Some(prev_info) = &current_info {
1097            // update return focus
1098            let mut update_return = |scope_path: InteractionPath| -> bool {
1099                match self.return_focused.entry(scope_path.widget_id()) {
1100                    IdEntry::Occupied(e) => {
1101                        let e = e.get();
1102                        if e.with(|p| *p != prev_focus) {
1103                            e.set(prev_focus.clone());
1104                            RETURN_FOCUS_CHANGED_EVENT.notify(ReturnFocusChangedArgs::now(scope_path, e.get(), prev_focus.clone()));
1105                            return true;
1106                        }
1107                    }
1108                    IdEntry::Vacant(e) => {
1109                        e.insert(var(prev_focus.clone()));
1110                        RETURN_FOCUS_CHANGED_EVENT.notify(ReturnFocusChangedArgs::now(scope_path, None, prev_focus.clone()));
1111                        return true;
1112                    }
1113                }
1114                false
1115            };
1116
1117            let prev_scope = prev_info.self_and_ancestors().find(|w| w.is_scope());
1118            let new_scope = new_info.self_and_ancestors().find(|w| w.is_scope());
1119
1120            if prev_scope != new_scope {
1121                debug_assert_eq!(prev_focus.as_ref().unwrap().widget_id(), prev_info.info().id());
1122
1123                if let Some(scope) = new_scope
1124                    && scope.is_alt_scope()
1125                    && !matches!(request.target, FocusTarget::Exit { .. })
1126                {
1127                    // focus entered ALT scope, previous focus outside is return
1128                    let set = update_return(scope.info().interaction_path());
1129                    if set {
1130                        tracing::trace!(
1131                            "set alt scope {:?} return focus to {:?}",
1132                            scope.info().id(),
1133                            prev_focus.as_ref().map(|f| f.widget_id())
1134                        );
1135                    }
1136                }
1137
1138                if let Some(scope) = prev_scope
1139                    && !scope.is_alt_scope()
1140                    && matches!(
1141                        scope.focus_info().scope_on_focus(),
1142                        FocusScopeOnFocus::LastFocused | FocusScopeOnFocus::LastFocusedIgnoreBounds
1143                    )
1144                {
1145                    // focus exited scope that remembers last focused
1146                    let set = update_return(scope.info().interaction_path());
1147                    if set {
1148                        tracing::trace!(
1149                            "set scope {:?} return focus to {:?}",
1150                            scope.info().id(),
1151                            prev_focus.as_ref().map(|f| f.widget_id())
1152                        );
1153                    }
1154                }
1155            }
1156        }
1157
1158        let cause = if is_service_request {
1159            FocusChangedCause::Request(request)
1160        } else {
1161            FocusChangedCause::Recovery
1162        };
1163
1164        if current_highlight != new_highlight {
1165            self.is_highlighting.set(new_highlight);
1166        }
1167
1168        let mut focused_changed = prev_focus != new_focus;
1169
1170        let prev_focus_window = prev_focus.as_ref().map(|p| p.window_id());
1171        let new_focus_window = new_focus.as_ref().map(|p| p.window_id());
1172
1173        let args = FocusChangedArgs::now(prev_focus, new_focus.clone(), new_highlight, cause, new_enabled_nav);
1174
1175        if new_info.is_scope() {
1176            // scopes moves focus to a child
1177            let last_focused = |id| {
1178                self.return_focused
1179                    .get(&id)
1180                    .and_then(|p| p.with(|p| p.as_ref().map(|p| p.widget_id())))
1181            };
1182
1183            // reentering single child of parent scope that cycles
1184            let is_tab_cycle_reentry = matches!(args.cause.request_target(), Some(FocusTarget::Prev | FocusTarget::Next))
1185                && match (&args.prev_focus, &args.new_focus) {
1186                    (Some(p), Some(n)) => p.contains(n.widget_id()),
1187                    _ => false,
1188                };
1189
1190            // reversed into the scope, first is last
1191            let reverse = matches!(args.cause.request_target(), Some(FocusTarget::Prev));
1192
1193            let mut pending_args = Some(args.clone());
1194            let mut scope_info = new_info;
1195            while let Some(w) = scope_info.on_focus_scope_move(last_focused, is_tab_cycle_reentry, reverse) {
1196                // scope moves focus to child
1197
1198                let prev_focus = args.new_focus.clone();
1199                new_focus = Some(w.info().interaction_path());
1200
1201                focused_changed = args.prev_focus != new_focus;
1202                if prev_focus == new_focus {
1203                    break;
1204                }
1205
1206                new_enabled_nav = w.enabled_nav();
1207
1208                tracing::trace!("on focus scope move to {:?}", new_focus.as_ref().map(|w| w.widget_id()));
1209
1210                if let Some(args) = pending_args.take() {
1211                    FOCUS_CHANGED_EVENT.notify(args);
1212                }
1213                pending_args = Some(FocusChangedArgs::now(
1214                    prev_focus,
1215                    new_focus.clone(),
1216                    new_highlight,
1217                    FocusChangedCause::ScopeGotFocus(reverse),
1218                    new_enabled_nav,
1219                ));
1220
1221                if w.is_scope() {
1222                    // recursive into nested scopes
1223                    scope_info = w;
1224                } else {
1225                    break;
1226                }
1227            }
1228            if let Some(args) = pending_args.take() {
1229                FOCUS_CHANGED_EVENT.notify(args);
1230            }
1231        } else {
1232            FOCUS_CHANGED_EVENT.notify(args);
1233        }
1234
1235        if focused_changed {
1236            self.focused.set(new_focus);
1237
1238            if prev_focus_window != new_focus_window
1239                && let Some(w) = new_focus_window
1240                && let Some(mode) = WINDOWS.mode(w)
1241                && mode.is_headed()
1242                && let Some(vars) = WINDOWS.vars(w)
1243                && matches!(vars.instance_state().get(), WindowInstanceState::Loaded { has_view: true })
1244            {
1245                tracing::trace!("focus changed to another window, from {prev_focus_window:?} to {new_focus_window:?}");
1246
1247                if prev_focus_window.is_some() {
1248                    WINDOWS_FOCUS.focus(w);
1249                } else if request.force_window_focus {
1250                    tracing::trace!("attempting to steal focus from other app");
1251                    // try to steal focus, or set critical indicator if system does not allow focus stealing
1252                    vars.focus_indicator().set(Some(FocusIndicator::Critical));
1253                    WINDOWS_FOCUS.focus(w);
1254                } else if let Some(i) = request.window_indicator {
1255                    tracing::trace!("set focus indicator {i:?}");
1256                    vars.focus_indicator().set(i);
1257                } else {
1258                    tracing::debug!("app does not have focus and force or indicator request, set info indicator");
1259                    vars.focus_indicator().set(FocusIndicator::Info);
1260                }
1261            }
1262        }
1263
1264        if self.enabled_nav != new_enabled_nav {
1265            self.enabled_nav = new_enabled_nav;
1266            tracing::trace!("update cmds {:?}", new_enabled_nav);
1267            self.commands.update_enabled(new_enabled_nav);
1268        }
1269
1270        self.navigation_origin.set(new_origin);
1271    }
1272
1273    fn fulfill_highlight_request(&mut self) {
1274        if self.request.is_some() || self.fallback_request.is_some() {
1275            // `FOCUS.focus` was requested after the highlight request in the same update pass
1276            debug_assert!(self.request_highlight);
1277            return;
1278        }
1279
1280        if !self.is_highlighting.get() {
1281            self.focused.with(|f| {
1282                if let Some(p) = f {
1283                    // does a request to focused, with highlight now
1284                    tracing::trace!("highlight request to {:?}", p.widget_id());
1285                    self.request = Some(FocusRequest::direct(p.widget_id(), true));
1286                }
1287            });
1288            if self.request.is_some() {
1289                self.fulfill_request(None, true);
1290            }
1291        }
1292    }
1293
1294    fn focus_direct_recovery(&mut self, wgt_id: WidgetId, tree_hint: Option<&WidgetInfoTree>) {
1295        let pending_request = self.request.take();
1296        let pending_fallback_request = self.fallback_request.take();
1297        self.request = Some(FocusRequest::direct_or_related(wgt_id, false, self.is_highlighting.get()));
1298        self.fulfill_request(tree_hint, false);
1299        self.request = pending_request;
1300        self.fallback_request = pending_fallback_request;
1301    }
1302}
1303
1304fn hooks() {
1305    ACCESS_FOCUS_EVENT
1306        .hook(|args| {
1307            let is_focused = FOCUS_SV.read().focused.with(|p| p.as_ref().map(|p| p.widget_id())) == Some(args.target.widget_id());
1308            if args.focus {
1309                if !is_focused {
1310                    tracing::trace!("access focus request {}", args.target.widget_id());
1311                    FOCUS.focus_widget(args.target.widget_id(), false);
1312                } else {
1313                    tracing::debug!("access focus request {} ignored, already focused", args.target.widget_id());
1314                }
1315            } else if is_focused {
1316                tracing::trace!("access focus exit request {}", args.target.widget_id());
1317                FOCUS.focus_exit(false);
1318            } else {
1319                tracing::debug!("access focus exit request {} ignored, not focused", args.target.widget_id());
1320            }
1321            true
1322        })
1323        .perm();
1324
1325    ACCESS_FOCUS_NAV_ORIGIN_EVENT
1326        .hook(|args| {
1327            let is_window_focused = FOCUS_SV.read().focused.with(|p| p.as_ref().map(|p| p.window_id())) == Some(args.target.window_id());
1328            if is_window_focused {
1329                tracing::trace!("access focus nav origin request {}", args.target.widget_id());
1330                FOCUS.navigation_origin().set(Some(args.target.widget_id()));
1331            } else {
1332                tracing::debug!(
1333                    "access focus nav origin request {} ignored, not in focused window",
1334                    args.target.widget_id()
1335                );
1336            }
1337            true
1338        })
1339        .perm();
1340
1341    MOUSE_INPUT_EVENT
1342        .hook(|args| {
1343            if args.is_mouse_down() {
1344                tracing::trace!("mouse press focus request {}", args.target.widget_id());
1345                FOCUS.focus(FocusRequest::direct_or_exit(args.target.widget_id(), true, false));
1346            }
1347            true
1348        })
1349        .perm();
1350
1351    TOUCH_INPUT_EVENT
1352        .hook(|args| {
1353            if args.is_touch_start() {
1354                tracing::trace!("touch start focus request {}", args.target.widget_id());
1355                FOCUS.focus(FocusRequest::direct_or_exit(args.target.widget_id(), true, false));
1356            }
1357            true
1358        })
1359        .perm();
1360
1361    ACCESS_CLICK_EVENT
1362        .hook(|args| {
1363            tracing::trace!("access click focus request {}", args.target.widget_id());
1364            FOCUS.focus(FocusRequest::direct_or_exit(args.target.widget_id(), true, false));
1365            true
1366        })
1367        .perm();
1368
1369    WIDGET_TREE_CHANGED_EVENT
1370        .hook(|args| {
1371            let mut s = FOCUS_SV.write();
1372            if let Some((win_id, wgt_id)) = s.focused.with(|f| f.as_ref().map(|f| (f.window_id(), f.widget_id())))
1373                && args.tree.window_id() == win_id
1374            {
1375                // on tree rebuild the focused widget can move to another slot or stop being focusable
1376                // on tree update (render) the widget can stop being focusable due to visibility change
1377                //
1378                // to correct we do a focus request and fulfill on the focused widget id.
1379                //
1380                // this is also needed to update the commands enabled status, for example, the previous widget from focused
1381                // is now collapsed, so the previous command must now be disabled
1382                tracing::trace!("tree changed recovery");
1383                s.focus_direct_recovery(wgt_id, Some(&args.tree));
1384
1385                // and check all return focus
1386                s.return_focused.retain(|scope_id, ret| {
1387                    if let Some((win_id, wgt_id)) = ret.with(|f| f.as_ref().map(|f| (f.window_id(), f.widget_id())))
1388                        && win_id == args.tree.window_id()
1389                    {
1390                        if win_id != args.tree.window_id() {
1391                            // not relevant to this event
1392                            return true;
1393                        }
1394
1395                        if let Some(scope) = args.tree.get(*scope_id) {
1396                            // scope still exists
1397
1398                            if let Some(wgt) = args.tree.get(wgt_id) {
1399                                // widget still exists
1400
1401                                let wgt_path = wgt.interaction_path();
1402                                if ret.with(|p| p.as_ref() != Some(&wgt_path)) {
1403                                    // changed
1404                                    tracing::trace!("return_focus of {scope_id} ({wgt_id}) changed");
1405
1406                                    let was_inside_scope = ret.with(|p| p.as_ref().unwrap().contains(*scope_id));
1407                                    let is_inside_scope = scope.is_ancestor(&wgt);
1408
1409                                    if was_inside_scope == is_inside_scope {
1410                                        ret.set(Some(wgt_path.clone()));
1411                                        RETURN_FOCUS_CHANGED_EVENT.notify(ReturnFocusChangedArgs::now(
1412                                            scope.interaction_path(),
1413                                            ret.get(),
1414                                            Some(wgt_path),
1415                                        ));
1416
1417                                        // retain record
1418                                        return true;
1419                                    } else {
1420                                        tracing::trace!("return_focus of {scope_id} ({wgt_id}) cannot be return anymore");
1421
1422                                        ret.set(None);
1423                                        RETURN_FOCUS_CHANGED_EVENT.notify(ReturnFocusChangedArgs::now(
1424                                            scope.interaction_path(),
1425                                            ret.get(),
1426                                            None,
1427                                        ));
1428                                    }
1429                                } else {
1430                                    // retain record
1431                                    return true;
1432                                }
1433                            } else {
1434                                tracing::trace!("return_focus of {scope_id} ({wgt_id}) no longer in focus tree");
1435                                ret.set(None);
1436                                RETURN_FOCUS_CHANGED_EVENT.notify(ReturnFocusChangedArgs::now(scope.interaction_path(), ret.get(), None));
1437                            }
1438                        }
1439                    }
1440
1441                    // retain only if has observers
1442                    ret.strong_count() > 1
1443                });
1444            }
1445            if !args.is_update {
1446                focus_info::FocusTreeData::consolidate_alt_scopes(&args.prev_tree, &args.tree);
1447            }
1448            true
1449        })
1450        .perm();
1451
1452    WINDOW_FOCUS_CHANGED_EVENT
1453        .hook(|args| {
1454            let mut s = FOCUS_SV.write();
1455            let current_focus = s.focused.with(|p| p.as_ref().map(|p| p.window_id()));
1456            if current_focus != args.new_focus {
1457                if let Some(id) = args.new_focus
1458                    && let Some(tree) = WINDOWS.widget_tree(id)
1459                {
1460                    tracing::trace!("window focus changed to {id:?}");
1461                    let tree = FocusInfoTree::new(tree, s.focus_disabled_widgets.get(), s.focus_hidden_widgets.get());
1462                    if let Some(root) = tree.focusable_root() {
1463                        let pending_request = s.request.take();
1464                        let pending_fallback_request = s.fallback_request.take();
1465                        tracing::trace!("window focus changed focus request {:?}", root.info().id());
1466                        s.request = Some(FocusRequest::direct_or_related(root.info().id(), false, s.is_highlighting.get()));
1467                        s.fulfill_request(Some(tree.tree()), false);
1468                        s.request = pending_request;
1469                        s.fallback_request = pending_fallback_request;
1470                        return true;
1471                    } else {
1472                        tracing::debug!("focused window does not have any focusable widget");
1473                    }
1474                } else {
1475                    tracing::debug!("all windows lost focus");
1476                }
1477
1478                if let Some(win_id) = current_focus {
1479                    let wgt_id = s.focused.with(|p| p.as_ref().map(|p| p.widget_id())).unwrap();
1480
1481                    // notify blur
1482                    s.focused.set(None);
1483                    s.is_highlighting.set(false);
1484                    if !s.enabled_nav.is_empty() {
1485                        s.enabled_nav = FocusNavAction::empty();
1486                        s.commands.update_enabled(FocusNavAction::empty());
1487                    }
1488
1489                    FOCUS_CHANGED_EVENT.notify(FocusChangedArgs::now(
1490                        s.focused.get(),
1491                        None,
1492                        false,
1493                        FocusChangedCause::Recovery,
1494                        s.enabled_nav,
1495                    ));
1496
1497                    let prev_tree = WINDOWS.widget_tree(win_id);
1498                    if let Some(prev_tree) = &prev_tree
1499                        && let Some(wgt) = prev_tree.get(wgt_id)
1500                        && let Some(wgt) = wgt.into_focusable(s.focus_disabled_widgets.get(), s.focus_hidden_widgets.get())
1501                        && let Some(root_scope) = wgt.self_and_ancestors().filter(|w| w.is_scope()).last()
1502                        && !root_scope.is_alt_scope()
1503                        && matches!(
1504                            root_scope.focus_info().scope_on_focus(),
1505                            FocusScopeOnFocus::LastFocused | FocusScopeOnFocus::LastFocusedIgnoreBounds
1506                        )
1507                    {
1508                        // window still open, update return_focused for window root scope
1509
1510                        let mut return_change = None;
1511                        if let Some(alt_scope) = wgt.self_and_ancestors().find(|w| w.is_alt_scope()) {
1512                            // was inside alt, the return focus for the root is the alt return, does not return inside alt
1513                            if let Some(ret) = s.return_focused.get(&alt_scope.info().id())
1514                                && let Some(path) = ret.get()
1515                            {
1516                                return_change = Some(path);
1517                            }
1518                        } else {
1519                            // normal return, last focused
1520                            return_change = s.focused.get();
1521                        }
1522
1523                        if return_change.is_some() {
1524                            let mut prev = None;
1525                            match s.return_focused.entry(root_scope.info().id()) {
1526                                IdEntry::Occupied(e) => {
1527                                    prev = e.get().get();
1528                                    e.get().set(return_change.clone());
1529                                }
1530                                IdEntry::Vacant(e) => {
1531                                    e.insert(var(return_change.clone()));
1532                                }
1533                            }
1534                            RETURN_FOCUS_CHANGED_EVENT.notify(ReturnFocusChangedArgs::now(
1535                                Some(root_scope.info().interaction_path()),
1536                                prev,
1537                                return_change,
1538                            ));
1539                        }
1540                    } else if prev_tree.is_none() {
1541                        // window closed, cleanup return_focused
1542                        s.return_focused.retain(|scope_id, v| {
1543                            if let Some(p_win_id) = v.with(|p| p.as_ref().map(|p| p.window_id()))
1544                                && p_win_id == win_id
1545                            {
1546                                // was return in closed window, can assume the scope was dropped because if it
1547                                // had moved to another window the WIDGET_TREE_CHANGED_EVENT handler would have
1548                                // updates this by the time this event happens
1549                                #[cfg(debug_assertions)]
1550                                if WINDOWS.widget_info(*scope_id).is_some() {
1551                                    tracing::error!("expected focus scope {scope_id} to not exist after window close");
1552                                }
1553                                #[cfg(not(debug_assertions))]
1554                                let _ = scope_id;
1555
1556                                return false;
1557                            }
1558                            true
1559                        });
1560                    }
1561                }
1562            }
1563            true
1564        })
1565        .perm();
1566}