freya_core/accessibility/
focus.rs1use 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}