freya_hooks/
use_init_native_platform.rs1use dioxus_core::{
2 prelude::{
3 consume_context,
4 provide_context,
5 spawn,
6 },
7 use_hook,
8};
9use dioxus_hooks::use_context_provider;
10use dioxus_signals::{
11 Readable,
12 Signal,
13 Writable,
14};
15use freya_core::types::NativePlatformReceiver;
16
17use crate::use_init_asset_cacher;
18
19#[derive(Clone)]
20pub struct NavigationMark(bool);
21
22impl NavigationMark {
23 pub fn allowed(&self) -> bool {
24 self.0
25 }
26
27 pub fn set_allowed(&mut self, allowed: bool) {
28 self.0 = allowed;
29 }
30}
31
32#[derive(Clone, Copy)]
33pub struct UsePlatformEvents {
34 pub navigation_mark: Signal<NavigationMark>,
35}
36
37pub fn use_init_native_platform() -> UsePlatformEvents {
39 use_init_asset_cacher();
41
42 let navigation_mark = use_context_provider(|| Signal::new(NavigationMark(true)));
44
45 use_hook(|| {
47 let mut platform_receiver = consume_context::<NativePlatformReceiver>();
48 let platform_state = platform_receiver.borrow();
49
50 let mut preferred_theme = Signal::new(platform_state.preferred_theme);
51 let mut focused_id = Signal::new(platform_state.focused_accessibility_id);
52 let mut focused_node = Signal::new(platform_state.focused_accessibility_node.clone());
53 let mut navigation_mode = Signal::new(platform_state.navigation_mode);
54 let mut information = Signal::new(platform_state.information);
55
56 drop(platform_state);
57
58 spawn(async move {
60 while platform_receiver.changed().await.is_ok() {
61 let state = platform_receiver.borrow();
62 if *focused_id.peek() != state.focused_accessibility_id {
63 *focused_id.write() = state.focused_accessibility_id;
64 }
65
66 if *focused_node.peek() != state.focused_accessibility_node {
67 *focused_node.write() = state.focused_accessibility_node.clone();
68 }
69
70 if *preferred_theme.peek() != state.preferred_theme {
71 *preferred_theme.write() = state.preferred_theme;
72 }
73
74 if *navigation_mode.peek() != state.navigation_mode {
75 *navigation_mode.write() = state.navigation_mode;
76 }
77
78 if *information.peek() != state.information {
79 *information.write() = state.information;
80 }
81 }
82 });
83
84 provide_context(preferred_theme);
85 provide_context(navigation_mode);
86 provide_context(information);
87 provide_context(focused_id);
88 provide_context(focused_node);
89 });
90
91 UsePlatformEvents { navigation_mark }
92}
93
94#[cfg(test)]
95mod test {
96 use freya::prelude::*;
97 use freya_core::accessibility::ACCESSIBILITY_ROOT_ID;
98 use freya_testing::prelude::*;
99
100 #[tokio::test]
101 pub async fn focus_accessibility() {
102 #[allow(non_snake_case)]
103 fn OtherChild() -> Element {
104 let mut focus_manager = use_focus();
105
106 rsx!(rect {
107 a11y_id: focus_manager.attribute(),
108 width: "100%",
109 height: "50%",
110 onclick: move |_| focus_manager.request_focus(),
111 })
112 }
113
114 fn use_focus_app() -> Element {
115 rsx!(
116 rect {
117 width: "100%",
118 height: "100%",
119 OtherChild {}
120 OtherChild {}
121 }
122 )
123 }
124
125 let mut utils = launch_test_with_config(
126 use_focus_app,
127 TestingConfig::<()> {
128 size: (100.0, 100.0).into(),
129 ..TestingConfig::default()
130 },
131 );
132
133 utils.wait_for_update().await;
135 assert_eq!(utils.focus_id(), ACCESSIBILITY_ROOT_ID);
136
137 utils.click_cursor((5., 5.)).await;
139
140 utils.wait_for_update().await;
142 utils.wait_for_update().await;
143 let first_focus_id = utils.focus_id();
144 assert_ne!(first_focus_id, ACCESSIBILITY_ROOT_ID);
145
146 utils.click_cursor((5., 75.)).await;
148
149 utils.wait_for_update().await;
151 utils.wait_for_update().await;
152 let second_focus_id = utils.focus_id();
153 assert_ne!(first_focus_id, second_focus_id);
154 assert_ne!(second_focus_id, ACCESSIBILITY_ROOT_ID);
155 }
156
157 #[tokio::test]
158 pub async fn uncontrolled_focus_accessibility() {
159 #[allow(non_snake_case)]
160 fn OtherChild() -> Element {
161 let focus = use_focus();
162 rsx!(rect {
163 a11y_id: focus.attribute(),
164 width: "100%",
165 height: "50%",
166 })
167 }
168
169 fn use_focus_app() -> Element {
170 rsx!(
171 rect {
172 width: "100%",
173 height: "100%",
174 OtherChild {},
175 OtherChild {}
176 }
177 )
178 }
179
180 let mut utils = launch_test_with_config(
181 use_focus_app,
182 TestingConfig::<()> {
183 size: (100.0, 100.0).into(),
184 ..TestingConfig::default()
185 },
186 );
187
188 utils.wait_for_update().await;
190 assert_eq!(utils.focus_id(), ACCESSIBILITY_ROOT_ID);
191
192 utils.push_event(TestEvent::Keyboard {
194 name: EventName::KeyDown,
195 key: Key::Tab,
196 code: Code::Tab,
197 modifiers: Modifiers::default(),
198 });
199 utils.wait_for_update().await;
200
201 utils.wait_for_update().await;
203 utils.wait_for_update().await;
204 let first_focus_id = utils.focus_id();
205 assert_ne!(first_focus_id, ACCESSIBILITY_ROOT_ID);
206
207 utils.push_event(TestEvent::Keyboard {
209 name: EventName::KeyDown,
210 key: Key::Tab,
211 code: Code::Tab,
212 modifiers: Modifiers::default(),
213 });
214 utils.wait_for_update().await;
215
216 utils.wait_for_update().await;
217 utils.wait_for_update().await;
218 let second_focus_id = utils.focus_id();
219 assert_ne!(first_focus_id, second_focus_id);
220 assert_ne!(second_focus_id, ACCESSIBILITY_ROOT_ID);
221 }
222
223 #[tokio::test]
224 pub async fn auto_focus_accessibility() {
225 fn use_focus_app() -> Element {
226 let focus_1 = use_focus();
227 let focus_2 = use_focus();
228 rsx!(
229 rect {
230 a11y_id: focus_1.attribute(),
231 a11y_auto_focus: "true",
232 }
233 rect {
234 a11y_id: focus_2.attribute(),
235 a11y_auto_focus: "true",
236 }
237 )
238 }
239
240 let mut utils = launch_test_with_config(
241 use_focus_app,
242 TestingConfig::<()> {
243 size: (100.0, 100.0).into(),
244 ..TestingConfig::default()
245 },
246 );
247
248 utils.wait_for_update().await;
249 assert_ne!(utils.focus_id(), ACCESSIBILITY_ROOT_ID); }
251}