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