freya_core/accessibility/
focus.rs1use keyboard_types::{
2 Key,
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 let is_space = matches!(event.key, Key::Character(ref s) if s == " ");
76 let is_enter = event.key == Key::Enter;
77
78 if cfg!(target_os = "macos") {
79 let screen_reader = ScreenReader::get();
80 if screen_reader.is_on() {
81 is_space
82 && event.modifiers.contains(Modifiers::CONTROL)
83 && event.modifiers.contains(Modifiers::ALT)
84 } else {
85 is_enter || is_space
86 }
87 } else {
88 is_enter || is_space
89 }
90 }
91}
92
93pub fn use_focus() -> Focus {
94 use_hook(Focus::create)
95}
96
97#[derive(Clone, Copy, Debug, PartialEq)]
98pub enum FocusStatus {
99 Not,
100 Pointer,
101 Keyboard,
102}
103
104pub fn use_focus_status(focus: Focus) -> Memo<FocusStatus> {
105 use_memo(move || {
106 let platform = Platform::get();
107 let is_focused = *platform.focused_accessibility_id.read() == focus.a11y_id;
108 let is_keyboard = *platform.navigation_mode.read() == NavigationMode::Keyboard;
109
110 match (is_focused, is_keyboard) {
111 (true, false) => FocusStatus::Pointer,
112 (true, true) => FocusStatus::Keyboard,
113 _ => FocusStatus::Not,
114 }
115 })
116}