1use std::{
2 borrow::Cow,
3 cell::RefCell,
4 collections::HashMap,
5 fs::File,
6 io::Write,
7 path::PathBuf,
8 rc::Rc,
9 time::{
10 Duration,
11 Instant,
12 },
13};
14
15use freya_components::{
16 cache::AssetCacher,
17 keyboard_navigator::keyboard_navigator,
18};
19use freya_core::integration::*;
20pub use freya_core::{
21 events::platform::*,
22 prelude::*,
23};
24use freya_engine::prelude::{
25 EncodedImageFormat,
26 FontCollection,
27 FontMgr,
28 SkData,
29 TypefaceFontProvider,
30 raster_n32_premul,
31};
32use ragnarok::{
33 CursorPoint,
34 EventsExecutorRunner,
35 EventsMeasurerRunner,
36 NodesState,
37};
38use torin::prelude::{
39 LayoutNode,
40 Size2D,
41};
42
43pub mod prelude {
44 pub use crate::*;
45}
46
47pub fn launch_doc(app: impl Into<FpRender>, size: Size2D, path: impl Into<PathBuf>) {
48 launch_doc_hook(app, size, path, |_| {})
49}
50
51pub fn launch_doc_hook(
52 app: impl Into<FpRender>,
53 size: Size2D,
54 path: impl Into<PathBuf>,
55 hook: impl FnOnce(&mut TestingRunner),
56) {
57 let (mut test, _) = TestingRunner::new(app, size, |_| {});
58 hook(&mut test);
59 test.render_to_file(path);
60}
61
62pub fn launch_test(app: impl Into<FpRender>) -> TestingRunner {
63 TestingRunner::new(app, Size2D::new(500., 500.), |_| {}).0
64}
65
66pub struct TestingRunner {
67 nodes_state: NodesState<NodeId>,
68 runner: Runner,
69 tree: Rc<RefCell<Tree>>,
70 size: Size2D,
71
72 accessibility: AccessibilityTree,
73
74 events_receiver: futures_channel::mpsc::UnboundedReceiver<EventsChunk>,
75 events_sender: futures_channel::mpsc::UnboundedSender<EventsChunk>,
76
77 font_manager: FontMgr,
78 font_collection: FontCollection,
79
80 platform_state: PlatformState,
81
82 ticker_sender: RenderingTickerSender,
83
84 default_fonts: Vec<Cow<'static, str>>,
85}
86
87impl TestingRunner {
88 pub fn new<T>(
89 app: impl Into<FpRender>,
90 size: Size2D,
91 hook: impl FnOnce(&mut Runner) -> T,
92 ) -> (Self, T) {
93 let (events_sender, events_receiver) = futures_channel::mpsc::unbounded();
94 let app = app.into();
95 let mut runner = Runner::new(move || keyboard_navigator(app.clone()).into_element());
96
97 runner.provide_root_context(ScreenReader::new);
98
99 let (mut ticker_sender, ticker) = RenderingTicker::new();
100 ticker_sender.set_overflow(true);
101 runner.provide_root_context(|| ticker);
102
103 let animation_clock = AnimationClock::new();
104 runner.provide_root_context(|| animation_clock.clone());
105
106 runner.provide_root_context(AssetCacher::create);
107
108 let platform_state = runner.provide_root_context(|| PlatformState {
109 focused_accessibility_id: State::create(ACCESSIBILITY_ROOT_ID),
110 focused_accessibility_node: State::create(accesskit::Node::new(
111 accesskit::Role::Window,
112 )),
113 root_size: State::create(size),
114 navigation_mode: State::create(NavigationMode::NotKeyboard),
115 });
116
117 let tree = Tree::default();
118 let tree = Rc::new(RefCell::new(tree));
119
120 let platform = Platform::new({
121 let tree = tree.clone();
122 move |user_event| {
123 match user_event {
124 UserEvent::RequestRedraw => {
125 }
127 UserEvent::FocusAccessibilityNode(strategy) => {
128 tree.borrow_mut().accessibility_diff.request_focus(strategy);
129 }
130 UserEvent::SetCursorIcon(_) => {
131 }
133 UserEvent::Erased(_) => {
134 }
136 }
137 }
138 });
139 runner.provide_root_context(|| platform);
140
141 runner.provide_root_context(|| tree.borrow().accessibility_generator.clone());
142
143 let hook_result = hook(&mut runner);
144
145 let mut font_collection = FontCollection::new();
146 let def_mgr = FontMgr::default();
147 let provider = TypefaceFontProvider::new();
148 let font_manager: FontMgr = provider.into();
149 font_collection.set_default_font_manager(def_mgr, None);
150 font_collection.set_dynamic_font_manager(font_manager.clone());
151 font_collection.paragraph_cache_mut().turn_on(false);
152
153 let mutations = runner.sync_and_update();
154 tree.borrow_mut().apply_mutations(mutations);
155 tree.borrow_mut().measure_layout(
156 size,
157 &font_collection,
158 &font_manager,
159 &events_sender,
160 1.0,
161 &default_fonts(),
162 );
163
164 let nodes_state = NodesState::default();
165 let accessibility = AccessibilityTree::default();
166
167 (
168 Self {
169 runner,
170 tree,
171 size,
172
173 accessibility,
174 platform_state,
175
176 nodes_state,
177 events_receiver,
178 events_sender,
179
180 font_manager,
181 font_collection,
182
183 ticker_sender,
184
185 default_fonts: default_fonts(),
186 },
187 hook_result,
188 )
189 }
190
191 pub fn set_fonts(&mut self, fonts: HashMap<&str, &[u8]>) {
192 let mut provider = TypefaceFontProvider::new();
193 for (font_name, font_data) in fonts {
194 let ft_type = self
195 .font_collection
196 .fallback_manager()
197 .unwrap()
198 .new_from_data(font_data, None)
199 .unwrap_or_else(|| panic!("Failed to load font {font_name}."));
200 provider.register_typeface(ft_type, Some(font_name));
201 }
202 let font_manager: FontMgr = provider.into();
203 self.font_manager = font_manager.clone();
204 self.font_collection.set_dynamic_font_manager(font_manager);
205 }
206
207 pub fn set_default_fonts(&mut self, fonts: &[Cow<'static, str>]) {
208 self.default_fonts.clear();
209 self.default_fonts.extend_from_slice(fonts);
210 self.tree.borrow_mut().layout.reset();
211 self.tree.borrow_mut().text_cache.reset();
212 self.tree.borrow_mut().measure_layout(
213 self.size,
214 &self.font_collection,
215 &self.font_manager,
216 &self.events_sender,
217 1.0,
218 &self.default_fonts,
219 );
220 self.tree.borrow_mut().accessibility_diff.clear();
221 self.accessibility.focused_id = ACCESSIBILITY_ROOT_ID;
222 self.accessibility.init(&mut self.tree.borrow_mut());
223 self.sync_and_update();
224 }
225
226 pub async fn handle_events(&mut self) {
227 self.runner.handle_events().await
228 }
229
230 pub fn handle_events_immediately(&mut self) {
231 self.runner.handle_events_immediately()
232 }
233
234 pub fn sync_and_update(&mut self) {
235 let accessibility_update = self
236 .accessibility
237 .process_updates(&mut self.tree.borrow_mut(), &self.events_sender);
238 self.platform_state
239 .focused_accessibility_id
240 .set(accessibility_update.focus);
241
242 while let Ok(Some(events_chunk)) = self.events_receiver.try_next() {
243 match events_chunk {
244 EventsChunk::Processed(processed_events) => {
245 let events_executor_adapter = EventsExecutorAdapter {
246 runner: &mut self.runner,
247 };
248 events_executor_adapter.run(&mut self.nodes_state, processed_events);
249 }
250 EventsChunk::Batch(events) => {
251 for event in events {
252 self.runner.handle_event(
253 event.node_id,
254 event.name,
255 event.data,
256 event.bubbles,
257 );
258 }
259 }
260 }
261 }
262
263 let mutations = self.runner.sync_and_update();
264 self.tree.borrow_mut().apply_mutations(mutations);
265 self.tree.borrow_mut().measure_layout(
266 self.size,
267 &self.font_collection,
268 &self.font_manager,
269 &self.events_sender,
270 1.0,
271 &self.default_fonts,
272 );
273 }
274
275 pub fn poll(&mut self, step: Duration, duration: Duration) {
278 let started = Instant::now();
279 while started.elapsed() < duration {
280 self.handle_events_immediately();
281 self.sync_and_update();
282 std::thread::sleep(step);
283 self.ticker_sender.broadcast_blocking(()).unwrap();
284 }
285 }
286
287 pub fn send_event(&mut self, platform_event: PlatformEvent) {
288 let mut events_measurer_adapter = EventsMeasurerAdapter {
289 tree: &mut self.tree.borrow_mut(),
290 scale_factor: 1.0,
291 };
292 let processed_events = events_measurer_adapter.run(
293 &mut vec![platform_event],
294 &mut self.nodes_state,
295 self.accessibility.focused_node_id(),
296 );
297 self.events_sender
298 .unbounded_send(EventsChunk::Processed(processed_events))
299 .unwrap();
300 }
301
302 pub fn move_cursor(&mut self, cursor: impl Into<CursorPoint>) {
303 self.send_event(PlatformEvent::Mouse {
304 name: MouseEventName::MouseMove,
305 cursor: cursor.into(),
306 button: Some(MouseButton::Left),
307 })
308 }
309
310 pub fn write_text(&mut self, text: impl ToString) {
311 let text = text.to_string();
312 self.send_event(PlatformEvent::Keyboard {
313 name: KeyboardEventName::KeyDown,
314 key: Key::Character(text),
315 code: Code::Unidentified,
316 modifiers: Modifiers::default(),
317 });
318 self.sync_and_update();
319 }
320
321 pub fn press_key(&mut self, key: Key) {
322 self.send_event(PlatformEvent::Keyboard {
323 name: KeyboardEventName::KeyDown,
324 key,
325 code: Code::Unidentified,
326 modifiers: Modifiers::default(),
327 });
328 self.sync_and_update();
329 }
330
331 pub fn press_cursor(&mut self, cursor: impl Into<CursorPoint>) {
332 let cursor = cursor.into();
333 self.send_event(PlatformEvent::Mouse {
334 name: MouseEventName::MouseDown,
335 cursor,
336 button: Some(MouseButton::Left),
337 });
338 self.sync_and_update();
339 }
340
341 pub fn release_cursor(&mut self, cursor: impl Into<CursorPoint>) {
342 let cursor = cursor.into();
343 self.send_event(PlatformEvent::Mouse {
344 name: MouseEventName::MouseUp,
345 cursor,
346 button: Some(MouseButton::Left),
347 });
348 self.sync_and_update();
349 }
350
351 pub fn click_cursor(&mut self, cursor: impl Into<CursorPoint>) {
352 let cursor = cursor.into();
353 self.send_event(PlatformEvent::Mouse {
354 name: MouseEventName::MouseDown,
355 cursor,
356 button: Some(MouseButton::Left),
357 });
358 self.sync_and_update();
359 self.send_event(PlatformEvent::Mouse {
360 name: MouseEventName::MouseUp,
361 cursor,
362 button: Some(MouseButton::Left),
363 });
364 self.sync_and_update();
365 }
366
367 pub fn scroll(&mut self, cursor: impl Into<CursorPoint>, scroll: impl Into<CursorPoint>) {
368 let cursor = cursor.into();
369 let scroll = scroll.into();
370 self.send_event(PlatformEvent::Wheel {
371 name: WheelEventName::Wheel,
372 scroll,
373 cursor,
374 source: WheelSource::Device,
375 });
376 self.sync_and_update();
377 }
378
379 pub fn render(&mut self) -> SkData {
380 let mut surface = raster_n32_premul((self.size.width as i32, self.size.height as i32))
381 .expect("Failed to create the surface.");
382
383 let render_pipeline = RenderPipeline {
384 font_collection: &mut self.font_collection,
385 font_manager: &self.font_manager,
386 tree: &self.tree.borrow(),
387 canvas: surface.canvas(),
388 scale_factor: 1.0,
389 background: Color::WHITE,
390 };
391 render_pipeline.render();
392
393 let image = surface.image_snapshot();
394 let mut context = surface.direct_context();
395 image
396 .encode(context.as_mut(), EncodedImageFormat::PNG, None)
397 .expect("Failed to encode the snapshot.")
398 }
399
400 pub fn render_to_file(&mut self, path: impl Into<PathBuf>) {
401 let path = path.into();
402
403 let image = self.render();
404
405 let mut snapshot_file = File::create(path).expect("Failed to create the snapshot file.");
406
407 snapshot_file
408 .write_all(&image)
409 .expect("Failed to save the snapshot file.");
410 }
411
412 pub fn find<T>(
413 &self,
414 matcher: impl Fn(TestingNode, &dyn ElementExt) -> Option<T>,
415 ) -> Option<T> {
416 let mut matched = None;
417 {
418 let tree = self.tree.borrow();
419 tree.traverse_depth(|id| {
420 if matched.is_some() {
421 return;
422 }
423 let element = tree.elements.get(&id).unwrap();
424 let node = TestingNode {
425 tree: self.tree.clone(),
426 id,
427 };
428 matched = matcher(node, element.as_ref());
429 });
430 }
431
432 matched
433 }
434
435 pub fn find_many<T>(
436 &self,
437 matcher: impl Fn(TestingNode, &dyn ElementExt) -> Option<T>,
438 ) -> Vec<T> {
439 let mut matched = Vec::new();
440 {
441 let tree = self.tree.borrow();
442 tree.traverse_depth(|id| {
443 let element = tree.elements.get(&id).unwrap();
444 let node = TestingNode {
445 tree: self.tree.clone(),
446 id,
447 };
448 if let Some(result) = matcher(node, element.as_ref()) {
449 matched.push(result);
450 }
451 });
452 }
453
454 matched
455 }
456}
457
458pub struct TestingNode {
459 tree: Rc<RefCell<Tree>>,
460 id: NodeId,
461}
462
463impl TestingNode {
464 pub fn layout(&self) -> LayoutNode {
465 self.tree.borrow().layout.get(&self.id).cloned().unwrap()
466 }
467
468 pub fn children(&self) -> Vec<Self> {
469 let children = self
470 .tree
471 .borrow()
472 .children
473 .get(&self.id)
474 .cloned()
475 .unwrap_or_default();
476
477 children
478 .into_iter()
479 .map(|child_id| Self {
480 id: child_id,
481 tree: self.tree.clone(),
482 })
483 .collect()
484 }
485
486 pub fn is_visible(&self) -> bool {
487 let layout = self.layout();
488 let effect_state = self
489 .tree
490 .borrow()
491 .effect_state
492 .get(&self.id)
493 .cloned()
494 .unwrap();
495
496 effect_state.is_visible(&self.tree.borrow().layout, &layout.area)
497 }
498
499 pub fn element(&self) -> Rc<dyn ElementExt> {
500 self.tree
501 .borrow()
502 .elements
503 .get(&self.id)
504 .cloned()
505 .expect("Element does not exist.")
506 }
507}