Skip to main content

ferroui_base/input/
keyboard_navigation_handler.rs

1use super::navigation::{TabNavigation, XYFocus};
2use super::{
3    FocusManager, IKeyboardNavigationHandler, InputElement, Key, KeyDeviceType, KeyEventArgs, KeyModifiers,
4    NavigationDirection, NavigationMethod,
5};
6use crate::interactivity::Interactive;
7use crate::Ref;
8use std::cell::RefCell;
9use std::rc::{Rc, Weak};
10
11type Element = Ref<InputElement>;
12
13/// Handles keyboard navigation for a window.
14pub struct KeyboardNavigationHandler {
15    this: Weak<KeyboardNavigationHandler>,
16    /// The window to which the handler belongs.
17    owner: RefCell<Option<crate::WeakRef<InputElement>>>,
18}
19
20impl KeyboardNavigationHandler {
21    /// Creates a keyboard navigation handler without an owner.
22    pub fn new() -> Rc<Self> {
23        Rc::new_cyclic(|this| Self { this: this.clone(), owner: RefCell::new(None) })
24    }
25
26    fn owner(&self) -> Option<Element> {
27        self.owner.borrow().as_ref().and_then(crate::WeakRef::upgrade)
28    }
29
30    /// Gets the next control in the specified navigation direction.
31    ///
32    /// Returns the next element in the specified direction, or `None` if
33    /// `element` was the last in the requested direction.
34    ///
35    /// Panics for directions other than next, previous, up, down, left and
36    /// right.
37    pub fn get_next(element: &Element, direction: NavigationDirection) -> Option<Element> {
38        Self::get_next_private(Some(element), None, direction, None)
39    }
40
41    fn find_custom(element: &Element) -> Option<Element> {
42        element.find_ancestor_of_type_where::<InputElement>(true, |e| e.as_custom_keyboard_navigation().is_some())
43    }
44
45    fn get_next_private(
46        element: Option<&Element>,
47        owner: Option<&Element>,
48        direction: NavigationDirection,
49        key_device_type: Option<KeyDeviceType>,
50    ) -> Option<Element> {
51        let element_or_owner = element.or(owner).expect("Either the element or the owner must be set.");
52
53        // If there's a custom keyboard navigation handler as an ancestor,
54        // use that.
55        let custom = element.and_then(Self::find_custom);
56        if let Some(custom) = &custom {
57            if let Some(ce) = Self::handle_pre_custom_navigation(custom, element_or_owner, direction) {
58                return Some(ce);
59            }
60        }
61
62        let result = match direction {
63            NavigationDirection::Next => TabNavigation::get_next_tab(element_or_owner, false),
64            NavigationDirection::Previous => TabNavigation::get_prev_tab(Some(element_or_owner), None, false),
65            NavigationDirection::Up
66            | NavigationDirection::Down
67            | NavigationDirection::Left
68            | NavigationDirection::Right => match element {
69                // We don't have a start element, so use tab navigation to
70                // find the first one.
71                None => TabNavigation::get_next_tab(element_or_owner, true),
72                Some(element) => XYFocus::try_directional_focus(direction, element, owner, None, key_device_type),
73            },
74            _ => panic!("The navigation direction {direction:?} is not supported."),
75        };
76
77        // If there wasn't a custom navigation handler as an ancestor of the
78        // current element, but there is one as an ancestor of the new
79        // element, use the custom handler to find the next element.
80        if custom.is_none() {
81            if let Some(ce) = Self::handle_post_custom_navigation(element_or_owner, result.as_ref(), direction) {
82                return Some(ce);
83            }
84        }
85
86        result
87    }
88
89    fn on_key_down(&self, _sender: &Interactive, e: &KeyEventArgs) {
90        let direction = match e.key {
91            Key::Tab => {
92                if !e.key_modifiers.contains(KeyModifiers::SHIFT) {
93                    NavigationDirection::Next
94                } else {
95                    NavigationDirection::Previous
96                }
97            }
98            Key::Left => NavigationDirection::Left,
99            Key::Right => NavigationDirection::Right,
100            Key::Up => NavigationDirection::Up,
101            Key::Down => NavigationDirection::Down,
102            _ => return,
103        };
104
105        let source = e.source().and_then(|source| source.downcast::<InputElement>().ok());
106        let current = source
107            .and_then(|source| FocusManager::get_focus_manager(&source))
108            .and_then(|focus_manager| focus_manager.get_focused_element());
109
110        e.set_handled(self.move_(current.as_ref(), direction, e.key_modifiers, Some(e.key_device_type)));
111    }
112
113    fn handle_pre_custom_navigation(
114        custom_handler: &Element,
115        element: &Element,
116        direction: NavigationDirection,
117    ) -> Option<Element> {
118        let (handled, next) = custom_handler.as_custom_keyboard_navigation()?.get_next(element, direction);
119
120        if handled {
121            if next.is_some() {
122                return next;
123            }
124
125            return match direction {
126                NavigationDirection::Next => TabNavigation::get_next_tab_outside(custom_handler),
127                NavigationDirection::Previous => TabNavigation::get_prev_tab_outside(custom_handler),
128                _ => None,
129            };
130        }
131
132        None
133    }
134
135    fn handle_post_custom_navigation(
136        element: &Element,
137        new_element: Option<&Element>,
138        direction: NavigationDirection,
139    ) -> Option<Element> {
140        let custom_handler = Self::find_custom(new_element?)?;
141        let (handled, next) = custom_handler.as_custom_keyboard_navigation()?.get_next(element, direction);
142
143        if handled {
144            next
145        } else {
146            None
147        }
148    }
149}
150
151impl IKeyboardNavigationHandler for KeyboardNavigationHandler {
152    fn set_owner(&self, owner: &Element) {
153        if self.owner.borrow().is_some() {
154            panic!("KeyboardNavigationHandler owner has already been set.");
155        }
156
157        *self.owner.borrow_mut() = Some(owner.downgrade());
158
159        let this = self.this.clone();
160        owner.add_handler(InputElement::key_down_event(), move |sender, e| {
161            if let Some(this) = this.upgrade() {
162                this.on_key_down(sender, e);
163            }
164        });
165    }
166
167    fn move_(
168        &self,
169        element: Option<&Element>,
170        direction: NavigationDirection,
171        key_modifiers: KeyModifiers,
172        device_type: Option<KeyDeviceType>,
173    ) -> bool {
174        let owner = self.owner();
175        let next = Self::get_next_private(element, owner.as_ref(), direction, device_type);
176
177        match next {
178            Some(next) => {
179                let method = if direction == NavigationDirection::Next || direction == NavigationDirection::Previous {
180                    NavigationMethod::Tab
181                } else {
182                    NavigationMethod::Directional
183                };
184
185                next.focus_with(method, key_modifiers)
186            }
187            None => false,
188        }
189    }
190}