freya_core/accessibility/
focus.rs

1use keyboard_types::{
2    Code,
3    Modifiers,
4};
5
6use crate::{
7    accessibility::id::AccessibilityId,
8    integration::{
9        ACCESSIBILITY_ROOT_ID,
10        AccessibilityGenerator,
11    },
12    platform::{
13        NavigationMode,
14        Platform,
15    },
16    prelude::{
17        AccessibilityFocusStrategy,
18        KeyboardEventData,
19        Memo,
20        ScreenReader,
21        UserEvent,
22        consume_root_context,
23        use_hook,
24        use_memo,
25    },
26};
27
28#[derive(Clone, Copy)]
29pub struct Focus {
30    a11y_id: AccessibilityId,
31}
32
33impl Focus {
34    pub fn create() -> Self {
35        Self::new_for_id(Self::new_id())
36    }
37
38    pub fn new_for_id(a11y_id: AccessibilityId) -> Self {
39        Self { a11y_id }
40    }
41
42    pub fn new_id() -> AccessibilityId {
43        let accessibility_generator = consume_root_context::<AccessibilityGenerator>();
44        AccessibilityId(accessibility_generator.new_id())
45    }
46
47    pub fn a11y_id(&self) -> AccessibilityId {
48        self.a11y_id
49    }
50
51    pub fn is_focused(&self) -> bool {
52        let platform = Platform::get();
53        *platform.focused_accessibility_id.peek() == self.a11y_id
54    }
55
56    pub fn is_focused_with_keyboard(&self) -> bool {
57        let platform = Platform::get();
58        *platform.focused_accessibility_id.peek() == self.a11y_id
59            && *platform.navigation_mode.peek() == NavigationMode::Keyboard
60    }
61
62    pub fn request_focus(&self) {
63        Platform::get().send(UserEvent::FocusAccessibilityNode(
64            AccessibilityFocusStrategy::Node(self.a11y_id),
65        ));
66    }
67
68    pub fn request_unfocus(&self) {
69        Platform::get().send(UserEvent::FocusAccessibilityNode(
70            AccessibilityFocusStrategy::Node(ACCESSIBILITY_ROOT_ID),
71        ));
72    }
73
74    pub fn is_pressed(event: &KeyboardEventData) -> bool {
75        if cfg!(target_os = "macos") {
76            let screen_reader = ScreenReader::get();
77            if screen_reader.is_on() {
78                event.code == Code::Space
79                    && event.modifiers.contains(Modifiers::CONTROL)
80                    && event.modifiers.contains(Modifiers::ALT)
81            } else {
82                event.code == Code::Enter || event.code == Code::Space
83            }
84        } else {
85            event.code == Code::Enter || event.code == Code::Space
86        }
87    }
88}
89
90pub fn use_focus() -> Focus {
91    use_hook(Focus::create)
92}
93
94#[derive(Clone, Copy, Debug, PartialEq)]
95pub enum FocusStatus {
96    Not,
97    Pointer,
98    Keyboard,
99}
100
101pub fn use_focus_status(focus: Focus) -> Memo<FocusStatus> {
102    use_memo(move || {
103        let platform = Platform::get();
104        let is_focused = *platform.focused_accessibility_id.read() == focus.a11y_id;
105        let is_keyboard = *platform.navigation_mode.read() == NavigationMode::Keyboard;
106
107        match (is_focused, is_keyboard) {
108            (true, false) => FocusStatus::Pointer,
109            (true, true) => FocusStatus::Keyboard,
110            _ => FocusStatus::Not,
111        }
112    })
113}