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