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_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}