Skip to main content

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!(|_| {
672    APP.on_init(hn!(|args| {
673        WINDOWS_FOCUS.hook_focus_service(FOCUS.focused());
674    }));
675});
676
677app_local! {
678    static FOCUS_SV: FocusService = FocusService::new();
679}
680struct FocusService {
681    auto_highlight: Var<Option<Duration>>,
682    focus_disabled_widgets: Var<bool>,
683    focus_hidden_widgets: Var<bool>,
684
685    navigation_origin: Var<Option<WidgetId>>,
686    focused: Var<Option<InteractionPath>>,
687    return_focused: IdMap<WidgetId, Var<Option<InteractionPath>>>,
688    is_highlighting: Var<bool>,
689    alt_return: WeakVar<Option<InteractionPath>>,
690
691    commands: cmd::FocusCommands,
692
693    request: Option<FocusRequest>,
694    fallback_request: Option<FocusRequest>,
695    request_highlight: bool,
696
697    enabled_nav: FocusNavAction,
698}
699fn last_keyboard_event() -> DInstant {
700    RAW_KEY_INPUT_EVENT
701        .with(|v| v.latest().map(|a| a.timestamp))
702        .unwrap_or(DInstant::EPOCH)
703}
704impl FocusService {
705    fn new() -> Self {
706        hooks();
707        let s = Self {
708            auto_highlight: var(Some(300.ms())),
709            focus_disabled_widgets: var(true),
710            focus_hidden_widgets: var(true),
711
712            navigation_origin: var(None),
713            focused: var(None),
714            return_focused: IdMap::default(),
715            is_highlighting: var(false),
716            alt_return: WeakVar::new(),
717
718            commands: cmd::FocusCommands::new(),
719
720            request: None,
721            fallback_request: None,
722            request_highlight: false,
723
724            enabled_nav: FocusNavAction::empty(),
725        };
726        fn refresh(_: &zng_var::VarHookArgs<bool>) -> bool {
727            let mut s = FOCUS_SV.write();
728            if let Some(id) = s.focused.with(|p| p.as_ref().map(|p| p.widget_id())) {
729                tracing::trace!("focus_disabled_widgets or focus_hidden_widgets changed recovery");
730                s.focus_direct_recovery(id, None);
731            }
732            true
733        }
734        s.focus_disabled_widgets.hook(refresh).perm();
735        s.focus_hidden_widgets.hook(refresh).perm();
736        s
737    }
738
739    fn fulfill_request(&mut self, tree_hint: Option<&WidgetInfoTree>, is_service_request: bool) {
740        // resolve what request to fulfill
741        let mut request = self.request.take().or(self.fallback_request.take()).unwrap();
742
743        if mem::take(&mut self.request_highlight) {
744            // there was also a highlight request
745            request.highlight = true;
746        } else if !request.highlight
747            && let Some(dur) = self.auto_highlight.get()
748            && last_keyboard_event().elapsed() <= dur
749        {
750            // there was also keyboard interaction within the auto_highlight interval
751            tracing::trace!("last keyboard event within {dur:?}, highlight");
752            request.highlight = true;
753        }
754
755        let focus_disabled = self.focus_disabled_widgets.get();
756        let focus_hidden = self.focus_hidden_widgets.get();
757
758        // find the current focus info
759        let current_info = self
760            .focused
761            .with(|p| match p {
762                Some(p) => {
763                    if let Some(t) = &tree_hint
764                        && t.window_id() == p.window_id()
765                    {
766                        t.get(p.widget_id())
767                    } else {
768                        WINDOWS.widget_tree(p.window_id()).and_then(|t| t.get(p.widget_id()))
769                    }
770                }
771                None => None,
772            })
773            .map(|i| i.into_focus_info(focus_disabled, focus_hidden));
774
775        // search for widget
776        let find_wgt = |id| {
777            if let Some(c) = &current_info
778                && let Some(r) = c.info().tree().get(id)
779            {
780                return Some(r.into_focus_info(focus_disabled, focus_hidden));
781            }
782            if let Some(t) = &tree_hint
783                && let Some(r) = t.get(id)
784            {
785                return Some(r.into_focus_info(focus_disabled, focus_hidden));
786            }
787            WINDOWS.widget_info(id).map(|r| r.into_focus_info(focus_disabled, focus_hidden))
788        };
789
790        // navigation origin
791        let origin_info = self
792            .navigation_origin
793            .get()
794            .and_then(|id| current_info.as_ref().and_then(|i| i.info().tree().get(id)))
795            .map(|i| i.into_focus_info(focus_disabled, focus_hidden))
796            .or_else(|| current_info.clone());
797
798        // resolve the new focus
799        let mut new_info = None;
800        let mut new_origin = None;
801        match request.target {
802            FocusTarget::Direct { target } => match find_wgt(target) {
803                Some(w) => {
804                    if w.is_focusable() {
805                        tracing::trace!("focus {:?}", w.info().id());
806                        new_info = Some(w);
807                    } else {
808                        tracing::debug!("cannot focus {target}, not focusable")
809                    }
810                }
811                None => tracing::debug!("cannot focus {target}, not found"),
812            },
813            FocusTarget::DirectOrExit { target, navigation_origin } => match find_wgt(target) {
814                Some(w) => {
815                    if w.is_focusable() {
816                        tracing::trace!("focus {:?}", w.info().id());
817                        new_info = Some(w);
818                    } else {
819                        tracing::debug!("cannot focus {target}, not focusable, will try ancestors");
820                        match w.ancestors().next() {
821                            Some(actual) => {
822                                tracing::trace!("focusing ancestor {:?}", actual.info().id());
823                                new_info = Some(actual);
824                            }
825                            None => {
826                                tracing::debug!("cannot focus {target} or ancestor, none focusable in path");
827                            }
828                        }
829                        if navigation_origin {
830                            new_origin = Some(w.info().id());
831                        }
832                    }
833                }
834                None => tracing::debug!("cannot focus {target} or ancestor, not found"),
835            },
836            FocusTarget::DirectOrEnter { target, navigation_origin } => match find_wgt(target) {
837                Some(w) => {
838                    if w.is_focusable() {
839                        tracing::trace!("focus {:?}", w.info().id());
840                        new_info = Some(w);
841                    } else {
842                        tracing::debug!("cannot focus {target}, not focusable, will try descendants");
843                        match w.first_tab_descendant() {
844                            Some(actual) => {
845                                tracing::trace!("focusing descendant {:?}", actual.info().id());
846                                new_info = Some(actual);
847                            }
848                            None => {
849                                if navigation_origin {
850                                    new_origin = Some(w.info().id());
851                                }
852                                tracing::debug!("cannot focus {target} or descendants, none tab focusable in subtree");
853                            }
854                        }
855                    }
856                }
857                None => tracing::debug!("cannot focus {target} or descendants, not found"),
858            },
859            FocusTarget::DirectOrRelated { target, navigation_origin } => match find_wgt(target) {
860                Some(w) => {
861                    if w.is_focusable() {
862                        tracing::trace!("focus {:?}", w.info().id());
863                        new_info = Some(w);
864                    } else {
865                        tracing::debug!("cannot focus {target}, not focusable, will try descendants and ancestors");
866                        match w
867                            .first_tab_descendant()
868                            .map(|w| (w, false))
869                            .or_else(|| w.descendants().next().map(|w| (w, false)))
870                            .or_else(|| w.ancestors().next().map(|w| (w, true)))
871                        {
872                            Some((actual, is_ancestor)) => {
873                                if navigation_origin && is_ancestor {
874                                    new_origin = Some(w.info().id());
875                                }
876                                tracing::trace!(
877                                    "focusing {} {:?}",
878                                    if is_ancestor { "ancestor" } else { "descendant" },
879                                    actual.info().id()
880                                );
881                                new_info = Some(actual);
882                            }
883                            None => {
884                                if navigation_origin {
885                                    new_origin = Some(w.info().id());
886                                }
887                                tracing::debug!("cannot focus {target} or descendants or ancestors, none focusable")
888                            }
889                        }
890                    }
891                }
892                None => {
893                    tracing::debug!("cannot focus {target} or descendants or ancestors, not found");
894                    if !is_service_request {
895                        let focused = self.focused.get();
896                        if let Some(focused) = focused
897                            && focused.widget_id() == target
898                        {
899                            // this is is a 'recovery' request, wait one update in case the widget
900                            // is just moving, if it is really removed try the ancestors
901                            UPDATES.once_next_update("delayed-focus-recovery", move || {
902                                let mut s = FOCUS_SV.write();
903                                let focus_did_not_change = s.focused.with(|p| p.as_ref() == Some(&focused));
904                                if focus_did_not_change && s.request.is_none() {
905                                    for wgt in focused.widgets_path().iter().rev() {
906                                        if let Some(wgt) = WINDOWS.widget_info(*wgt) {
907                                            s.focus_direct_recovery(wgt.id(), Some(wgt.tree()));
908                                            break;
909                                        }
910                                    }
911                                }
912                            })
913                        }
914                    }
915                }
916            },
917            FocusTarget::Enter => match &origin_info {
918                Some(i) => {
919                    new_info = i.first_tab_descendant();
920                    tracing::trace!("enter {:?}, focus {:?}", i.info().id(), new_info.as_ref().map(|w| w.info().id()));
921                }
922                None => tracing::debug!("cannot enter focused, no current focus"),
923            },
924            FocusTarget::Exit { recursive_alt } => match &origin_info {
925                Some(i) => {
926                    let mut alt = i.self_and_ancestors().find(|s| s.is_alt_scope());
927                    let mut recursive_alt_scopes = vec![];
928                    while let Some(a) = alt.take() {
929                        if recursive_alt_scopes.contains(&a.info().id()) {
930                            tracing::error!("circular alt return focus, {recursive_alt_scopes:?}");
931                            break;
932                        }
933
934                        if let Some(r) = self.return_focused.get(&a.info().id())
935                            && let Some(r) = r.with(|p| p.as_ref().map(|p| p.widget_id()))
936                            && let Some(r) = find_wgt(r)
937                            && r.is_focusable()
938                        {
939                            // ALT has valid return
940                            if recursive_alt {
941                                recursive_alt_scopes.push(a.info().id());
942                                alt = r.self_and_ancestors().find(|s| s.is_alt_scope());
943                                if alt.is_some() {
944                                    continue;
945                                }
946                                tracing::trace!(
947                                    "exit {:?}, alt {:?}, return {:?}",
948                                    i.info().id(),
949                                    recursive_alt_scopes,
950                                    r.info().id()
951                                );
952                            } else {
953                                tracing::trace!("exit {:?}, alt {:?}, return {:?}", i.info().id(), a.info().id(), r.info().id());
954                            }
955                            new_info = Some(r);
956                        } else {
957                            // ALT has no valid return
958                            if recursive_alt {
959                                new_info = a.ancestors().find(|w| !w.in_alt_scope());
960                            } else {
961                                new_info = a.ancestors().next();
962                            }
963                            tracing::debug!(
964                                "exit {:?}, alt {:?}, no return, focus {:?}",
965                                i.info().id(),
966                                a.info().id(),
967                                new_info.as_ref().map(|w| w.info().id())
968                            );
969                        }
970                    }
971                    if new_info.is_none() {
972                        new_info = i.ancestors().next();
973                        tracing::trace!("exit {:?}, focus {:?}", i.info().id(), new_info.as_ref().map(|w| w.info().id()));
974                    }
975                }
976                None => tracing::debug!("cannot exit focused, no current focus"),
977            },
978            FocusTarget::Next => match &origin_info {
979                Some(i) => {
980                    new_info = i.next_tab(false);
981                    tracing::trace!(
982                        "next from {:?}, focus {:?}",
983                        i.info().id(),
984                        new_info.as_ref().map(|w| w.info().id())
985                    );
986                }
987                None => tracing::debug!("cannot focus next, no current focus"),
988            },
989            FocusTarget::Prev => match &origin_info {
990                Some(i) => {
991                    new_info = i.prev_tab(false);
992                    tracing::trace!(
993                        "prev from {:?}, focus {:?}",
994                        i.info().id(),
995                        new_info.as_ref().map(|w| w.info().id())
996                    );
997                }
998                None => tracing::debug!("cannot focus prev, no current focus"),
999            },
1000            FocusTarget::Up => match &origin_info {
1001                Some(i) => {
1002                    new_info = i.next_up();
1003                    tracing::trace!("up from {:?}, focus {:?}", i.info().id(), new_info.as_ref().map(|w| w.info().id()));
1004                }
1005                None => tracing::debug!("cannot focus up, no current focus"),
1006            },
1007            FocusTarget::Right => match &origin_info {
1008                Some(i) => {
1009                    new_info = i.next_right();
1010                    tracing::trace!(
1011                        "right from {:?}, focus {:?}",
1012                        i.info().id(),
1013                        new_info.as_ref().map(|w| w.info().id())
1014                    );
1015                }
1016                None => tracing::debug!("cannot focus right, no current focus"),
1017            },
1018            FocusTarget::Down => match &origin_info {
1019                Some(i) => {
1020                    new_info = i.next_down();
1021                    tracing::trace!(
1022                        "down from {:?}, focus {:?}",
1023                        i.info().id(),
1024                        new_info.as_ref().map(|w| w.info().id())
1025                    );
1026                }
1027                None => tracing::debug!("cannot focus down, no current focus"),
1028            },
1029            FocusTarget::Left => match &origin_info {
1030                Some(i) => {
1031                    new_info = i.next_left();
1032                    tracing::trace!(
1033                        "left from {:?}, focus {:?}",
1034                        i.info().id(),
1035                        new_info.as_ref().map(|w| w.info().id())
1036                    );
1037                }
1038                None => tracing::debug!("cannot focus left, no current focus"),
1039            },
1040            FocusTarget::Alt => match &origin_info {
1041                Some(i) => {
1042                    if let Some(alt) = i.self_and_ancestors().find(|w| w.is_alt_scope()) {
1043                        // Alt inside ALT scope returns focus
1044                        if let Some(r) = self.return_focused.get(&alt.info().id())
1045                            && let Some(r) = r.with(|p| p.as_ref().map(|p| p.widget_id()))
1046                            && let Some(r) = find_wgt(r)
1047                            && r.is_focusable()
1048                        {
1049                            tracing::trace!("toggle alt from alt scope, exit to return {:?}", r.info().id());
1050                            new_info = Some(r);
1051                        } else {
1052                            tracing::trace!("is in alt scope without return focus, exiting to window root focusable");
1053                            new_info = i.focus_tree().focusable_root();
1054                            tracing::trace!(
1055                                "toggle alt from alt scope, to window root {:?}",
1056                                new_info.as_ref().map(|w| w.info().id())
1057                            );
1058                        }
1059                    } else {
1060                        new_info = i.alt_scope();
1061                        tracing::trace!("alt into alt scope {:?}", new_info.as_ref().map(|w| w.info().id()));
1062                    }
1063                }
1064                None => tracing::debug!("cannot focus alt, no current focus"),
1065            },
1066        }
1067
1068        let new_info = match new_info {
1069            Some(i) => i, // new_info was selected
1070            None => match current_info.clone() {
1071                // no new_info was selected, continue with current_info but check highlight and enabled_nav changes
1072                Some(i) => i,
1073                // has no focus and continues without
1074                None => return,
1075            },
1076        };
1077
1078        let current_highlight = self.is_highlighting.get();
1079        let new_highlight = request.highlight;
1080
1081        let mut new_enabled_nav = new_info.enabled_nav();
1082
1083        let prev_focus = self.focused.get();
1084        let mut new_focus = Some(new_info.info().interaction_path());
1085
1086        if prev_focus == new_focus && current_highlight == new_highlight && self.enabled_nav == new_enabled_nav {
1087            // no change
1088            tracing::trace!("no focus change");
1089            return;
1090        }
1091
1092        if let Some(prev_info) = &current_info {
1093            // update return focus
1094            let mut update_return = |scope_path: InteractionPath| -> bool {
1095                match self.return_focused.entry(scope_path.widget_id()) {
1096                    IdEntry::Occupied(e) => {
1097                        let e = e.get();
1098                        if e.with(|p| *p != prev_focus) {
1099                            e.set(prev_focus.clone());
1100                            RETURN_FOCUS_CHANGED_EVENT.notify(ReturnFocusChangedArgs::now(scope_path, e.get(), prev_focus.clone()));
1101                            return true;
1102                        }
1103                    }
1104                    IdEntry::Vacant(e) => {
1105                        e.insert(var(prev_focus.clone()));
1106                        RETURN_FOCUS_CHANGED_EVENT.notify(ReturnFocusChangedArgs::now(scope_path, None, prev_focus.clone()));
1107                        return true;
1108                    }
1109                }
1110                false
1111            };
1112
1113            let prev_scope = prev_info.self_and_ancestors().find(|w| w.is_scope());
1114            let new_scope = new_info.self_and_ancestors().find(|w| w.is_scope());
1115
1116            if prev_scope != new_scope {
1117                debug_assert_eq!(prev_focus.as_ref().unwrap().widget_id(), prev_info.info().id());
1118
1119                if let Some(new_scope) = new_scope
1120                    && new_scope.is_alt_scope()
1121                    && !matches!(request.target, FocusTarget::Exit { .. })
1122                    && prev_scope.as_ref().map(|p| !p.is_alt_scope()).unwrap_or(true)
1123                    && prev_focus.as_ref().map(|f| !f.contains(new_scope.info().id())).unwrap_or(true)
1124                {
1125                    // focus entered ALT scope, previous focus outside is return
1126                    let set = update_return(new_scope.info().interaction_path());
1127                    if set {
1128                        tracing::trace!(
1129                            "set alt scope {:?} return focus to {:?}",
1130                            new_scope.info().id(),
1131                            prev_focus.as_ref().map(|f| f.widget_id())
1132                        );
1133                    }
1134                }
1135
1136                if let Some(scope) = prev_scope
1137                    && !scope.is_alt_scope()
1138                    && matches!(
1139                        scope.focus_info().scope_on_focus(),
1140                        FocusScopeOnFocus::LastFocused | FocusScopeOnFocus::LastFocusedIgnoreBounds
1141                    )
1142                {
1143                    // focus exited scope that remembers last focused
1144                    let set = update_return(scope.info().interaction_path());
1145                    if set {
1146                        tracing::trace!(
1147                            "set scope {:?} return focus to {:?}",
1148                            scope.info().id(),
1149                            prev_focus.as_ref().map(|f| f.widget_id())
1150                        );
1151                    }
1152                }
1153            }
1154        }
1155
1156        let cause = if is_service_request {
1157            FocusChangedCause::Request(request)
1158        } else {
1159            FocusChangedCause::Recovery
1160        };
1161
1162        if current_highlight != new_highlight {
1163            self.is_highlighting.set(new_highlight);
1164        }
1165
1166        let mut focused_changed = prev_focus != new_focus;
1167
1168        let prev_focus_window = prev_focus.as_ref().map(|p| p.window_id());
1169        let new_focus_window = new_focus.as_ref().map(|p| p.window_id());
1170
1171        let args = FocusChangedArgs::now(prev_focus, new_focus.clone(), new_highlight, cause, new_enabled_nav);
1172
1173        if new_info.is_scope() {
1174            // scopes moves focus to a child
1175            let last_focused = |id| {
1176                self.return_focused
1177                    .get(&id)
1178                    .and_then(|p| p.with(|p| p.as_ref().map(|p| p.widget_id())))
1179            };
1180
1181            // reentering single child of parent scope that cycles
1182            let is_tab_cycle_reentry = matches!(args.cause.request_target(), Some(FocusTarget::Prev | FocusTarget::Next))
1183                && match (&args.prev_focus, &args.new_focus) {
1184                    (Some(p), Some(n)) => p.contains(n.widget_id()),
1185                    _ => false,
1186                };
1187
1188            // reversed into the scope, first is last
1189            let reverse = matches!(args.cause.request_target(), Some(FocusTarget::Prev));
1190
1191            let mut pending_args = Some(args.clone());
1192            let mut scope_info = new_info;
1193            while let Some(w) = scope_info.on_focus_scope_move(last_focused, is_tab_cycle_reentry, reverse) {
1194                // scope moves focus to child
1195
1196                let prev_focus = args.new_focus.clone();
1197                new_focus = Some(w.info().interaction_path());
1198
1199                focused_changed = args.prev_focus != new_focus;
1200                if prev_focus == new_focus {
1201                    break;
1202                }
1203
1204                new_enabled_nav = w.enabled_nav();
1205
1206                tracing::trace!("on focus scope move to {:?}", new_focus.as_ref().map(|w| w.widget_id()));
1207
1208                if let Some(args) = pending_args.take() {
1209                    FOCUS_CHANGED_EVENT.notify(args);
1210                }
1211                pending_args = Some(FocusChangedArgs::now(
1212                    prev_focus,
1213                    new_focus.clone(),
1214                    new_highlight,
1215                    FocusChangedCause::ScopeGotFocus(reverse),
1216                    new_enabled_nav,
1217                ));
1218
1219                if w.is_scope() {
1220                    // recursive into nested scopes
1221                    scope_info = w;
1222                } else {
1223                    break;
1224                }
1225            }
1226            if let Some(args) = pending_args.take() {
1227                FOCUS_CHANGED_EVENT.notify(args);
1228            }
1229        } else {
1230            FOCUS_CHANGED_EVENT.notify(args);
1231        }
1232
1233        if focused_changed {
1234            self.focused.set(new_focus);
1235
1236            if prev_focus_window != new_focus_window
1237                && let Some(w) = new_focus_window
1238                && let Some(mode) = WINDOWS.mode(w)
1239                && mode.is_headed()
1240                && let Some(vars) = WINDOWS.vars(w)
1241                && matches!(vars.instance_state().get(), WindowInstanceState::Loaded { has_view: true })
1242            {
1243                tracing::trace!("focus changed to another window, from {prev_focus_window:?} to {new_focus_window:?}");
1244
1245                if prev_focus_window.is_some() {
1246                    WINDOWS_FOCUS.focus(w);
1247                } else if request.force_window_focus {
1248                    tracing::trace!("attempting to steal focus from other app");
1249                    // try to steal focus, or set critical indicator if system does not allow focus stealing
1250                    vars.focus_indicator().set(Some(FocusIndicator::Critical));
1251                    WINDOWS_FOCUS.focus(w);
1252                } else if let Some(i) = request.window_indicator {
1253                    tracing::trace!("set focus indicator {i:?}");
1254                    vars.focus_indicator().set(i);
1255                } else {
1256                    tracing::debug!("app does not have focus and force or indicator request, set info indicator");
1257                    vars.focus_indicator().set(FocusIndicator::Info);
1258                }
1259            }
1260        }
1261
1262        if self.enabled_nav != new_enabled_nav {
1263            self.enabled_nav = new_enabled_nav;
1264            tracing::trace!("update cmds {:?}", new_enabled_nav);
1265            self.commands.update_enabled(new_enabled_nav);
1266        }
1267
1268        self.navigation_origin.set(new_origin);
1269    }
1270
1271    fn fulfill_highlight_request(&mut self) {
1272        if self.request.is_some() || self.fallback_request.is_some() {
1273            // `FOCUS.focus` was requested after the highlight request in the same update pass
1274            debug_assert!(self.request_highlight);
1275            return;
1276        }
1277
1278        if !self.is_highlighting.get() {
1279            self.focused.with(|f| {
1280                if let Some(p) = f {
1281                    // does a request to focused, with highlight now
1282                    tracing::trace!("highlight request to {:?}", p.widget_id());
1283                    self.request = Some(FocusRequest::direct(p.widget_id(), true));
1284                }
1285            });
1286            if self.request.is_some() {
1287                self.fulfill_request(None, true);
1288            }
1289        }
1290    }
1291
1292    fn focus_direct_recovery(&mut self, wgt_id: WidgetId, tree_hint: Option<&WidgetInfoTree>) {
1293        let pending_request = self.request.take();
1294        let pending_fallback_request = self.fallback_request.take();
1295        self.request = Some(FocusRequest::direct_or_related(wgt_id, false, self.is_highlighting.get()));
1296        self.fulfill_request(tree_hint, false);
1297        self.request = pending_request;
1298        self.fallback_request = pending_fallback_request;
1299    }
1300}
1301
1302fn hooks() {
1303    ACCESS_FOCUS_EVENT
1304        .hook(|args| {
1305            let is_focused = FOCUS_SV.read().focused.with(|p| p.as_ref().map(|p| p.widget_id())) == Some(args.target.widget_id());
1306            if args.focus {
1307                if !is_focused {
1308                    tracing::trace!("access focus request {}", args.target.widget_id());
1309                    FOCUS.focus_widget(args.target.widget_id(), false);
1310                } else {
1311                    tracing::debug!("access focus request {} ignored, already focused", args.target.widget_id());
1312                }
1313            } else if is_focused {
1314                tracing::trace!("access focus exit request {}", args.target.widget_id());
1315                FOCUS.focus_exit(false);
1316            } else {
1317                tracing::debug!("access focus exit request {} ignored, not focused", args.target.widget_id());
1318            }
1319            true
1320        })
1321        .perm();
1322
1323    ACCESS_FOCUS_NAV_ORIGIN_EVENT
1324        .hook(|args| {
1325            let is_window_focused = FOCUS_SV.read().focused.with(|p| p.as_ref().map(|p| p.window_id())) == Some(args.target.window_id());
1326            if is_window_focused {
1327                tracing::trace!("access focus nav origin request {}", args.target.widget_id());
1328                FOCUS.navigation_origin().set(Some(args.target.widget_id()));
1329            } else {
1330                tracing::debug!(
1331                    "access focus nav origin request {} ignored, not in focused window",
1332                    args.target.widget_id()
1333                );
1334            }
1335            true
1336        })
1337        .perm();
1338
1339    MOUSE_INPUT_EVENT
1340        .hook(|args| {
1341            if args.is_mouse_down() {
1342                tracing::trace!("mouse press focus request {}", args.target.widget_id());
1343                FOCUS.focus(FocusRequest::direct_or_exit(args.target.widget_id(), true, false));
1344            }
1345            true
1346        })
1347        .perm();
1348
1349    TOUCH_INPUT_EVENT
1350        .hook(|args| {
1351            if args.is_touch_start() {
1352                tracing::trace!("touch start focus request {}", args.target.widget_id());
1353                FOCUS.focus(FocusRequest::direct_or_exit(args.target.widget_id(), true, false));
1354            }
1355            true
1356        })
1357        .perm();
1358
1359    ACCESS_CLICK_EVENT
1360        .hook(|args| {
1361            tracing::trace!("access click focus request {}", args.target.widget_id());
1362            FOCUS.focus(FocusRequest::direct_or_exit(args.target.widget_id(), true, false));
1363            true
1364        })
1365        .perm();
1366
1367    WIDGET_TREE_CHANGED_EVENT
1368        .hook(|args| {
1369            let mut s = FOCUS_SV.write();
1370            if let Some((win_id, wgt_id)) = s.focused.with(|f| f.as_ref().map(|f| (f.window_id(), f.widget_id())))
1371                && args.tree.window_id() == win_id
1372            {
1373                // on tree rebuild the focused widget can move to another slot or stop being focusable
1374                // on tree update (render) the widget can stop being focusable due to visibility change
1375                //
1376                // to correct we do a focus request and fulfill on the focused widget id.
1377                //
1378                // this is also needed to update the commands enabled status, for example, the previous widget from focused
1379                // is now collapsed, so the previous command must now be disabled
1380                tracing::trace!("tree changed recovery");
1381                s.focus_direct_recovery(wgt_id, Some(&args.tree));
1382
1383                // and check all return focus
1384                s.return_focused.retain(|scope_id, ret| {
1385                    if let Some((win_id, wgt_id)) = ret.with(|f| f.as_ref().map(|f| (f.window_id(), f.widget_id())))
1386                        && win_id == args.tree.window_id()
1387                    {
1388                        if win_id != args.tree.window_id() {
1389                            // not relevant to this event
1390                            return true;
1391                        }
1392
1393                        if let Some(scope) = args.tree.get(*scope_id) {
1394                            // scope still exists
1395
1396                            if let Some(wgt) = args.tree.get(wgt_id) {
1397                                // widget still exists
1398
1399                                let wgt_path = wgt.interaction_path();
1400                                if ret.with(|p| p.as_ref() != Some(&wgt_path)) {
1401                                    // changed
1402                                    tracing::trace!("return_focus of {scope_id} ({wgt_id}) changed");
1403
1404                                    let was_inside_scope = ret.with(|p| p.as_ref().unwrap().contains(*scope_id));
1405                                    let is_inside_scope = scope.is_ancestor(&wgt);
1406
1407                                    if was_inside_scope == is_inside_scope {
1408                                        ret.set(Some(wgt_path.clone()));
1409                                        RETURN_FOCUS_CHANGED_EVENT.notify(ReturnFocusChangedArgs::now(
1410                                            scope.interaction_path(),
1411                                            ret.get(),
1412                                            Some(wgt_path),
1413                                        ));
1414
1415                                        // retain record
1416                                        return true;
1417                                    } else {
1418                                        tracing::trace!("return_focus of {scope_id} ({wgt_id}) cannot be return anymore");
1419
1420                                        ret.set(None);
1421                                        RETURN_FOCUS_CHANGED_EVENT.notify(ReturnFocusChangedArgs::now(
1422                                            scope.interaction_path(),
1423                                            ret.get(),
1424                                            None,
1425                                        ));
1426                                    }
1427                                } else {
1428                                    // retain record
1429                                    return true;
1430                                }
1431                            } else {
1432                                tracing::trace!("return_focus of {scope_id} ({wgt_id}) no longer in focus tree");
1433                                ret.set(None);
1434                                RETURN_FOCUS_CHANGED_EVENT.notify(ReturnFocusChangedArgs::now(scope.interaction_path(), ret.get(), None));
1435                            }
1436                        }
1437                    }
1438
1439                    // retain only if has observers
1440                    ret.strong_count() > 1
1441                });
1442            }
1443            if !args.is_update {
1444                focus_info::FocusTreeData::consolidate_alt_scopes(&args.prev_tree, &args.tree);
1445            }
1446            true
1447        })
1448        .perm();
1449
1450    WINDOW_FOCUS_CHANGED_EVENT
1451        .hook(|args| {
1452            let mut s = FOCUS_SV.write();
1453            let current_focus = s.focused.with(|p| p.as_ref().map(|p| p.window_id()));
1454            if current_focus != args.new_focus {
1455                if let Some(id) = args.new_focus
1456                    && let Some(tree) = WINDOWS.widget_tree(id)
1457                {
1458                    tracing::trace!("window focus changed to {id:?}");
1459                    let tree = FocusInfoTree::new(tree, s.focus_disabled_widgets.get(), s.focus_hidden_widgets.get());
1460                    if let Some(root) = tree.focusable_root() {
1461                        let pending_request = s.request.take();
1462                        let pending_fallback_request = s.fallback_request.take();
1463                        tracing::trace!("window focus changed focus request {:?}", root.info().id());
1464                        s.request = Some(FocusRequest::direct_or_related(root.info().id(), false, s.is_highlighting.get()));
1465                        s.fulfill_request(Some(tree.tree()), false);
1466                        s.request = pending_request;
1467                        s.fallback_request = pending_fallback_request;
1468                        return true;
1469                    } else {
1470                        tracing::debug!("focused window does not have any focusable widget");
1471                    }
1472                } else {
1473                    tracing::debug!("all windows lost focus");
1474                }
1475
1476                if let Some(win_id) = current_focus {
1477                    let wgt_id = s.focused.with(|p| p.as_ref().map(|p| p.widget_id())).unwrap();
1478
1479                    // notify blur
1480                    s.focused.set(None);
1481                    s.is_highlighting.set(false);
1482                    if !s.enabled_nav.is_empty() {
1483                        s.enabled_nav = FocusNavAction::empty();
1484                        s.commands.update_enabled(FocusNavAction::empty());
1485                    }
1486
1487                    FOCUS_CHANGED_EVENT.notify(FocusChangedArgs::now(
1488                        s.focused.get(),
1489                        None,
1490                        false,
1491                        FocusChangedCause::Recovery,
1492                        s.enabled_nav,
1493                    ));
1494
1495                    let prev_tree = WINDOWS.widget_tree(win_id);
1496                    if let Some(prev_tree) = &prev_tree
1497                        && let Some(wgt) = prev_tree.get(wgt_id)
1498                        && let Some(wgt) = wgt.into_focusable(s.focus_disabled_widgets.get(), s.focus_hidden_widgets.get())
1499                        && let Some(root_scope) = wgt.self_and_ancestors().filter(|w| w.is_scope()).last()
1500                        && !root_scope.is_alt_scope()
1501                        && matches!(
1502                            root_scope.focus_info().scope_on_focus(),
1503                            FocusScopeOnFocus::LastFocused | FocusScopeOnFocus::LastFocusedIgnoreBounds
1504                        )
1505                    {
1506                        // window still open, update return_focused for window root scope
1507
1508                        let mut return_change = None;
1509                        if let Some(alt_scope) = wgt.self_and_ancestors().find(|w| w.is_alt_scope()) {
1510                            // was inside alt, the return focus for the root is the alt return, does not return inside alt
1511                            if let Some(ret) = s.return_focused.get(&alt_scope.info().id())
1512                                && let Some(path) = ret.get()
1513                            {
1514                                return_change = Some(path);
1515                            }
1516                        } else {
1517                            // normal return, last focused
1518                            return_change = s.focused.get();
1519                        }
1520
1521                        if return_change.is_some() {
1522                            let mut prev = None;
1523                            match s.return_focused.entry(root_scope.info().id()) {
1524                                IdEntry::Occupied(e) => {
1525                                    prev = e.get().get();
1526                                    e.get().set(return_change.clone());
1527                                }
1528                                IdEntry::Vacant(e) => {
1529                                    e.insert(var(return_change.clone()));
1530                                }
1531                            }
1532                            RETURN_FOCUS_CHANGED_EVENT.notify(ReturnFocusChangedArgs::now(
1533                                Some(root_scope.info().interaction_path()),
1534                                prev,
1535                                return_change,
1536                            ));
1537                        }
1538                    } else if prev_tree.is_none() {
1539                        // window closed, cleanup return_focused
1540                        s.return_focused.retain(|scope_id, v| {
1541                            if let Some(p_win_id) = v.with(|p| p.as_ref().map(|p| p.window_id()))
1542                                && p_win_id == win_id
1543                            {
1544                                // was return in closed window, can assume the scope was dropped because if it
1545                                // had moved to another window the WIDGET_TREE_CHANGED_EVENT handler would have
1546                                // updates this by the time this event happens
1547                                #[cfg(debug_assertions)]
1548                                if WINDOWS.widget_info(*scope_id).is_some() {
1549                                    tracing::error!("expected focus scope {scope_id} to not exist after window close");
1550                                }
1551                                #[cfg(not(debug_assertions))]
1552                                let _ = scope_id;
1553
1554                                return false;
1555                            }
1556                            true
1557                        });
1558                    }
1559                }
1560            }
1561            true
1562        })
1563        .perm();
1564}