freya_core/accessibility/
focus.rs1use keyboard_types::{
2 Key,
3 Modifiers,
4 NamedKey,
5};
6
7use crate::{
8 accessibility::id::AccessibilityId,
9 integration::{
10 ACCESSIBILITY_ROOT_ID,
11 AccessibilityGenerator,
12 },
13 platform::{
14 NavigationMode,
15 Platform,
16 },
17 prelude::{
18 AccessibilityFocusStrategy,
19 KeyboardEventData,
20 Memo,
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 = Platform::get();
54 *platform.focused_accessibility_id.peek() == self.a11y_id
55 }
56
57 pub fn is_focused_with_keyboard(&self) -> bool {
58 let platform = Platform::get();
59 *platform.focused_accessibility_id.peek() == self.a11y_id
60 && *platform.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 let is_space = matches!(event.key, Key::Character(ref s) if s == " ");
77 let is_enter = event.key == Key::Named(NamedKey::Enter);
78
79 if cfg!(target_os = "macos") {
80 let screen_reader = ScreenReader::get();
81 if screen_reader.is_on() {
82 is_space
83 && event.modifiers.contains(Modifiers::CONTROL)
84 && event.modifiers.contains(Modifiers::ALT)
85 } else {
86 is_enter || is_space
87 }
88 } else {
89 is_enter || is_space
90 }
91 }
92}
93
94pub fn use_focus() -> Focus {
95 use_hook(Focus::create)
96}
97
98#[derive(Clone, Copy, Debug, PartialEq)]
99pub enum FocusStatus {
100 Not,
101 Pointer,
102 Keyboard,
103}
104
105pub fn use_focus_status(focus: Focus) -> Memo<FocusStatus> {
106 use_memo(move || {
107 let platform = Platform::get();
108 let is_focused = *platform.focused_accessibility_id.read() == focus.a11y_id;
109 let is_keyboard = *platform.navigation_mode.read() == NavigationMode::Keyboard;
110
111 match (is_focused, is_keyboard) {
112 (true, false) => FocusStatus::Pointer,
113 (true, true) => FocusStatus::Keyboard,
114 _ => FocusStatus::Not,
115 }
116 })
117}