Skip to main content

ferroui_base/input/
access_key_handler.rs

1use super::{
2    FocusManager, IAccessKeyHandler, IMainMenu, InputElement, Key, KeyEventArgs, KeyModifiers, PointerPressedEventArgs,
3};
4use crate::interactivity::{RoutedEvent, RoutedEventArgs, RoutingStrategies};
5use crate::reactive::IDisposable;
6use crate::{
7    ferro_property, ferro_routed_event, ferro_routed_event_args, AttachedProperty, FerroProperty, Ref,
8    StyledPropertyOptions, Visual, WeakRef,
9};
10use std::cell::{Cell, RefCell};
11use std::collections::VecDeque;
12use std::rc::{Rc, Weak};
13
14type Element = Ref<InputElement>;
15
16/// The inputs to an access key pressed event: raised on an element to ask
17/// which element an access key should target.
18#[derive(Clone)]
19pub struct AccessKeyPressedEventArgs {
20    base: RoutedEventArgs,
21    target: Rc<RefCell<Option<Element>>>,
22    key: String,
23}
24
25ferro_routed_event_args!(AccessKeyPressedEventArgs: RoutedEventArgs);
26
27impl AccessKeyPressedEventArgs {
28    /// Creates args for the given (normalized) access key.
29    pub fn new(key: &str) -> Self {
30        Self {
31            base: RoutedEventArgs::with_event(AccessKeyHandler::access_key_pressed_event()),
32            target: Rc::new(RefCell::new(None)),
33            key: key.to_string(),
34        }
35    }
36
37    /// The target for the access key.
38    pub fn target(&self) -> Option<Element> {
39        self.target.borrow().clone()
40    }
41
42    /// Sets the target for the access key.
43    pub fn set_target(&self, value: Option<Element>) {
44        let old = self.target.replace(value);
45        drop(old);
46    }
47
48    /// The access key which was pressed.
49    pub fn key(&self) -> &str {
50        &self.key
51    }
52}
53
54/// Information pertaining to when the access key associated with an element
55/// is pressed.
56#[derive(Clone)]
57pub struct AccessKeyEventArgs {
58    base: RoutedEventArgs,
59    key: String,
60    is_multiple: bool,
61}
62
63ferro_routed_event_args!(AccessKeyEventArgs: RoutedEventArgs);
64
65impl AccessKeyEventArgs {
66    /// Creates args for an access key. `is_multiple` tells whether the key
67    /// is not unique among the registered elements.
68    pub fn new(key: &str, is_multiple: bool) -> Self {
69        Self {
70            base: RoutedEventArgs::with_event(AccessKeyHandler::access_key_event()),
71            key: key.to_string(),
72            is_multiple,
73        }
74    }
75
76    /// The key that was pressed which caused this event to fire.
77    pub fn key(&self) -> &str {
78        &self.key
79    }
80
81    /// Whether the key pressed is not unique among the registered elements.
82    pub fn is_multiple(&self) -> bool {
83        self.is_multiple
84    }
85}
86
87/// An access key registered for an element.
88pub struct AccessKeyRegistration {
89    target: WeakRef<InputElement>,
90    key: String,
91}
92
93impl AccessKeyRegistration {
94    /// Creates a registration.
95    pub fn new(key: String, target: WeakRef<InputElement>) -> Self {
96        Self { target, key }
97    }
98
99    /// The (normalized) access key.
100    pub fn key(&self) -> &str {
101        &self.key
102    }
103
104    /// The registered element, if it is still alive.
105    pub fn get_input_element(&self) -> Option<Element> {
106        self.target.upgrade()
107    }
108}
109
110#[derive(Clone, Copy, PartialEq, Eq)]
111enum ProcessKeyResult {
112    NoMatch,
113    MoreMatches,
114    LastMatch,
115}
116
117/// Handles access keys for a window.
118pub struct AccessKeyHandler {
119    this: Weak<AccessKeyHandler>,
120    /// The registered access keys.
121    registrations: RefCell<Vec<AccessKeyRegistration>>,
122    /// The window to which the handler belongs.
123    owner: RefCell<Option<WeakRef<InputElement>>>,
124    /// Whether access keys are currently being shown.
125    showing_access_keys: Cell<bool>,
126    /// Whether to ignore the Alt KeyUp event.
127    ignore_alt_up: Cell<bool>,
128    /// Whether the AltKey is down.
129    alt_is_down: Cell<bool>,
130    /// Element to restore following AltKey taking focus.
131    restore_focus_element: RefCell<Option<WeakRef<InputElement>>>,
132    /// The window's main menu.
133    main_menu: RefCell<Option<Rc<dyn IMainMenu>>>,
134    main_menu_closed: RefCell<Option<Rc<dyn IDisposable>>>,
135}
136
137crate::ferro_static_type!(AccessKeyHandler);
138
139impl AccessKeyHandler {
140    ferro_routed_event!(
141        /// Defines the AccessKey event: raised on the element an access key
142        /// targets.
143        pub fn access_key_event() -> RoutedEvent<AccessKeyEventArgs> {
144            RoutedEvent::register::<AccessKeyHandler, _>("AccessKey", RoutingStrategies::BUBBLE)
145        }
146    );
147
148    ferro_routed_event!(
149        /// Defines the AccessKeyPressed event: raised to find the target of
150        /// an access key.
151        pub fn access_key_pressed_event() -> RoutedEvent<AccessKeyPressedEventArgs> {
152            RoutedEvent::register::<AccessKeyHandler, _>("AccessKeyPressed", RoutingStrategies::BUBBLE)
153        }
154    );
155
156}
157
158crate::ferro_properties! { impl AccessKeyHandler {
159    ferro_property!(
160        /// Defines the ShowAccessKey attached property: whether access key
161        /// markers are shown in a window.
162        pub fn show_access_key_property() -> AttachedProperty<bool> {
163            FerroProperty::register_attached_with::<AccessKeyHandler, Visual, _>(
164                "ShowAccessKey",
165                StyledPropertyOptions::new(false).inherits(true),
166            )
167        }
168    );
169} }
170
171impl AccessKeyHandler {
172    /// Creates an access key handler without an owner.
173    pub fn new() -> Rc<AccessKeyHandler> {
174        Rc::new_cyclic(|this| AccessKeyHandler {
175            this: this.clone(),
176            registrations: RefCell::new(Vec::new()),
177            owner: RefCell::new(None),
178            showing_access_keys: Cell::new(false),
179            ignore_alt_up: Cell::new(false),
180            alt_is_down: Cell::new(false),
181            restore_focus_element: RefCell::new(None),
182            main_menu: RefCell::new(None),
183            main_menu_closed: RefCell::new(None),
184        })
185    }
186
187    /// The number of registered access keys, including registrations of
188    /// elements that are no longer alive and have not been purged yet.
189    pub fn registration_count(&self) -> usize {
190        self.registrations.borrow().len()
191    }
192
193    fn owner(&self) -> Option<Element> {
194        self.owner.borrow().as_ref().and_then(WeakRef::upgrade)
195    }
196
197    /// The registered access keys with the elements they target (the ones
198    /// that are still alive). For handlers built on this one.
199    pub fn registrations(&self) -> Vec<(String, Element)> {
200        self.registrations
201            .borrow()
202            .iter()
203            .filter_map(|registration| {
204                registration.get_input_element().map(|element| (registration.key().to_owned(), element))
205            })
206            .collect()
207    }
208
209    /// Records the owner of the handler: the first part of
210    /// [`IAccessKeyHandler::set_owner`].
211    ///
212    /// This method can only be called once.
213    fn set_owner_without_handlers(&self, owner: &Element) {
214        if self.owner.borrow().is_some() {
215            panic!("AccessKeyHandler owner has already been set.");
216        }
217
218        *self.owner.borrow_mut() = Some(owner.downgrade());
219    }
220
221    /// Sets the owner of the handler as [`IAccessKeyHandler::set_owner`] does, with the key
222    /// and pointer handlers attached, and then runs `on_set_owner`: the `OnSetOwner` virtual
223    /// of the class, which does nothing in this class and which a handler built on this one
224    /// overrides (the handler of a menu item attaches its text input handler there).
225    ///
226    /// This method can only be called once.
227    pub fn set_owner_with(&self, owner: &Element, on_set_owner: impl FnOnce(&Element)) {
228        IAccessKeyHandler::set_owner(self, owner);
229
230        on_set_owner(owner);
231    }
232
233    fn set_show_access_keys(target: &Visual, value: bool) {
234        target.set_value(Self::show_access_key_property(), value)
235    }
236
237    fn source_of(e: &RoutedEventArgs) -> Option<Element> {
238        e.source().and_then(|source| source.downcast::<InputElement>().ok())
239    }
240
241    /// Called when a key is pressed in the owner window, before the event
242    /// reaches the focused element.
243    pub fn on_preview_key_down(&self, e: &KeyEventArgs) {
244        let Some(owner) = self.owner() else { return };
245        let source = Self::source_of(e);
246
247        // If the event did not originate from within the owner, ignore all
248        // keyboard events.
249        let is_focus_within_owner = Self::is_focus_within_owner(&owner, source.as_ref());
250        if !is_focus_within_owner {
251            return;
252        }
253
254        if e.key == Key::LeftAlt || e.key == Key::RightAlt {
255            self.alt_is_down.set(true);
256
257            let main_menu = self.main_menu();
258            if !main_menu.as_ref().is_some_and(|menu| menu.is_open()) {
259                let focus_manager = source.as_ref().and_then(|source| FocusManager::get_focus_manager(source));
260
261                // Save the currently focused input element.
262                if let Some(focused_element) = focus_manager.and_then(|manager| manager.get_focused_element()) {
263                    *self.restore_focus_element.borrow_mut() = Some(focused_element.downgrade());
264                }
265
266                // When Alt is pressed without a main menu, or with a closed
267                // main menu, show access key markers in the window (i.e.
268                // "_File").
269                self.showing_access_keys.set(is_focus_within_owner);
270                Self::set_show_access_keys(&owner, is_focus_within_owner);
271            } else {
272                // If the Alt key is pressed and the main menu is open, close
273                // the main menu.
274                self.close_menu(&owner);
275                self.ignore_alt_up.set(true);
276
277                let restore_element = self.restore_focus_element.borrow_mut().take().and_then(|weak| weak.upgrade());
278                if let Some(restore_element) = restore_element {
279                    restore_element.focus();
280                }
281            }
282        } else if self.alt_is_down.get() {
283            self.ignore_alt_up.set(true);
284        }
285    }
286
287    /// Called when a key is pressed in the owner window.
288    pub fn on_key_down(&self, e: &KeyEventArgs) {
289        let Some(owner) = self.owner() else { return };
290        let source = Self::source_of(e);
291
292        // If the event did not originate from within the owner, ignore all
293        // keyboard events.
294        if !Self::is_focus_within_owner(&owner, source.as_ref()) {
295            return;
296        }
297
298        if (!e.key_modifiers.contains(KeyModifiers::ALT) || e.key_modifiers.contains(KeyModifiers::CONTROL))
299            && !self.main_menu().is_some_and(|menu| menu.is_open())
300        {
301            return;
302        }
303
304        e.set_handled(self.process_key(e.key_symbol.as_deref(), source.as_ref()));
305    }
306
307    /// Handles the Alt/F10 keys being released in the window.
308    pub fn on_preview_key_up(&self, e: &KeyEventArgs) {
309        if e.key == Key::LeftAlt || e.key == Key::RightAlt {
310            self.alt_is_down.set(false);
311
312            if self.ignore_alt_up.get() {
313                self.ignore_alt_up.set(false);
314            } else if self.showing_access_keys.get() {
315                if let Some(main_menu) = self.main_menu() {
316                    main_menu.open();
317                }
318            }
319        }
320    }
321
322    /// Handles a pointer press in the window: hides the access key markers.
323    pub fn on_preview_pointer_pressed(&self, _e: &PointerPressedEventArgs) {
324        if self.showing_access_keys.get() {
325            if let Some(owner) = self.owner() {
326                Self::set_show_access_keys(&owner, false);
327            }
328        }
329    }
330
331    /// Closes the main menu and performs other bookeeping.
332    fn close_menu(&self, owner: &Element) {
333        if let Some(main_menu) = self.main_menu() {
334            main_menu.close();
335        }
336        self.showing_access_keys.set(false);
337        Self::set_show_access_keys(owner, false);
338    }
339
340    fn main_menu_closed(&self) {
341        if let Some(owner) = self.owner() {
342            Self::set_show_access_keys(&owner, false);
343        }
344    }
345
346    /// Processes an access key: raises the AccessKey event on the element
347    /// the key targets. Returns whether a target was found.
348    pub fn process_key(&self, key: Option<&str>, element: Option<&Element>) -> bool {
349        let Some(key) = key.filter(|key| !key.is_empty()) else { return false };
350        let key = Self::normalize_key(key);
351        let sender_target = Self::get_target_for_element(element, &key);
352
353        // Find the possible targets matching the access key
354        let targets = Self::sort_by_hierarchy(self.get_targets_for_key(&key, element, sender_target));
355        Self::process_key_for_targets(&key, &targets) != ProcessKeyResult::NoMatch
356    }
357
358    fn normalize_key(key: &str) -> String {
359        key.to_uppercase()
360    }
361
362    fn process_key_for_targets(key: &str, targets: &[Element]) -> ProcessKeyResult {
363        if targets.is_empty() {
364            return ProcessKeyResult::NoMatch;
365        }
366
367        let mut is_single_target = true;
368        let mut last_was_focused = false;
369        let mut effective_target: Option<&Element> = None;
370        let mut chosen_index = 0;
371
372        for (i, target) in targets.iter().enumerate() {
373            if !Self::is_targetable(target) {
374                continue;
375            }
376
377            if effective_target.is_none() {
378                effective_target = Some(target);
379                chosen_index = i;
380            } else {
381                if last_was_focused {
382                    effective_target = Some(target);
383                    chosen_index = i;
384                }
385
386                is_single_target = false;
387            }
388
389            last_was_focused = target.is_focused();
390        }
391
392        let Some(effective_target) = effective_target else { return ProcessKeyResult::NoMatch };
393
394        let args = AccessKeyEventArgs::new(key, !is_single_target);
395        effective_target.raise_event(&args);
396
397        if chosen_index == targets.len() - 1 {
398            ProcessKeyResult::LastMatch
399        } else {
400            ProcessKeyResult::MoreMatches
401        }
402    }
403
404    /// Get the list of access key targets for the sender of the keyboard
405    /// event. If sender is null, pretend key was pressed in the active
406    /// window.
407    fn get_targets_for_key(&self, key: &str, sender: Option<&Element>, sender_target: Option<Element>) -> Vec<Element> {
408        let possible_elements = self.copy_matching_and_purge_dead(key);
409        let mut final_targets = Vec::with_capacity(1);
410
411        // Go through all the possible elements, find the interesting
412        // candidates
413        for element in &possible_elements {
414            if Some(element) != sender {
415                if !Self::is_targetable(element) {
416                    continue;
417                }
418
419                if let Some(target) = Self::get_target_for_element(Some(element), key) {
420                    final_targets.push(target);
421                }
422            } else if let Some(target) = &sender_target {
423                // This is the same element that sent the event so it must be
424                // in the same scope. Just add it to the final targets
425                final_targets.push(target.clone());
426            }
427        }
428
429        final_targets
430    }
431
432    fn is_targetable(element: &InputElement) -> bool {
433        element.is_effectively_enabled() && element.is_effectively_visible()
434    }
435
436    fn copy_matching_and_purge_dead(&self, key: &str) -> Vec<Element> {
437        let mut registrations = self.registrations.borrow_mut();
438        let mut matches = Vec::with_capacity(registrations.len());
439
440        // collect live elements with matching key and remove dead elements
441        registrations.retain(|registration| match registration.get_input_element() {
442            Some(input_element) => {
443                if registration.key == key {
444                    matches.push(input_element);
445                }
446                true
447            }
448            None => false,
449        });
450
451        matches
452    }
453
454    /// Returns targeting information for the given element: raises the
455    /// AccessKeyPressed event on it and returns the target it names.
456    fn get_target_for_element(element: Option<&Element>, key: &str) -> Option<Element> {
457        let element = element?;
458
459        let args = AccessKeyPressedEventArgs::new(key);
460        element.raise_event(&args);
461        args.target()
462    }
463
464    /// Checks if the focused element is a descendent of the owner.
465    fn is_focus_within_owner(owner: &Element, source: Option<&Element>) -> bool {
466        match source {
467            Some(source) => source == owner || owner.is_visual_ancestor_of(source),
468            None => false,
469        }
470    }
471
472    /// Sorts the list of targets according to logical ancestors in the
473    /// hierarchy so that child elements, for example tab item content, are
474    /// processed before the next parent item, i.e. the next tab item.
475    fn sort_by_hierarchy(targets: Vec<Element>) -> Vec<Element> {
476        // bail out, if there are no targets to sort
477        if targets.len() <= 1 {
478            return targets;
479        }
480
481        let mut sorted: Vec<Element> = Vec::with_capacity(targets.len());
482        let mut queue: VecDeque<Element> = targets.into();
483
484        while let Some(element) = queue.pop_front() {
485            // if the element was already added, do nothing
486            if sorted.contains(&element) {
487                continue;
488            }
489
490            // add the element itself
491            sorted.push(element.clone());
492
493            // add all descendants of the element
494            sorted.extend(queue.iter().filter(|child| element.is_logical_ancestor_of(Some(child))).cloned());
495        }
496
497        sorted
498    }
499}
500
501impl IAccessKeyHandler for AccessKeyHandler {
502    fn main_menu(&self) -> Option<Rc<dyn IMainMenu>> {
503        self.main_menu.borrow().clone()
504    }
505
506    fn set_main_menu(&self, value: Option<Rc<dyn IMainMenu>>) {
507        let subscription = self.main_menu_closed.borrow_mut().take();
508        if let Some(subscription) = subscription {
509            subscription.dispose();
510        }
511
512        let old = self.main_menu.replace(value.clone());
513        drop(old);
514
515        if let Some(main_menu) = value {
516            let this = self.this.clone();
517            let subscription = main_menu.closed(Rc::new(move |_| {
518                if let Some(this) = this.upgrade() {
519                    this.main_menu_closed();
520                }
521            }));
522            *self.main_menu_closed.borrow_mut() = Some(subscription);
523        }
524    }
525
526    fn set_owner(&self, owner: &Element) {
527        self.set_owner_without_handlers(owner);
528
529        let handler = |f: fn(&AccessKeyHandler, &KeyEventArgs)| {
530            let this = self.this.clone();
531            move |_: &crate::interactivity::Interactive, e: &KeyEventArgs| {
532                if let Some(this) = this.upgrade() {
533                    f(&this, e);
534                }
535            }
536        };
537
538        owner.add_handler_with(
539            InputElement::key_down_event(),
540            handler(Self::on_preview_key_down),
541            RoutingStrategies::TUNNEL,
542            false,
543        );
544        owner.add_handler_with(
545            InputElement::key_down_event(),
546            handler(Self::on_key_down),
547            RoutingStrategies::BUBBLE,
548            false,
549        );
550        owner.add_handler_with(
551            InputElement::key_up_event(),
552            handler(Self::on_preview_key_up),
553            RoutingStrategies::TUNNEL,
554            false,
555        );
556
557        let this = self.this.clone();
558        owner.add_handler_with(
559            InputElement::pointer_pressed_event(),
560            move |_, e| {
561                if let Some(this) = this.upgrade() {
562                    this.on_preview_pointer_pressed(e);
563                }
564            },
565            RoutingStrategies::TUNNEL,
566            false,
567        );
568    }
569
570    fn register(&self, access_key: &str, element: &Element) {
571        assert!(!access_key.is_empty(), "The access key cannot be empty.");
572
573        let key = Self::normalize_key(access_key);
574        let mut registrations = self.registrations.borrow_mut();
575
576        // remove dead elements with matching key
577        registrations.retain(|registration| !(registration.key == key && registration.get_input_element().is_none()));
578
579        registrations.push(AccessKeyRegistration::new(key, element.downgrade()));
580    }
581
582    fn unregister(&self, element: &InputElement) {
583        // remove element and all dead elements
584        self.registrations.borrow_mut().retain(|registration| match registration.get_input_element() {
585            Some(input_element) => !std::ptr::eq::<InputElement>(&*input_element, element),
586            None => false,
587        });
588    }
589}