zng_ext_input/
focus.rs

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