1use std::{
37 borrow::Cow,
38 cell::RefCell,
39 collections::HashMap,
40 fs::File,
41 io::Write,
42 path::PathBuf,
43 rc::Rc,
44 time::{
45 Duration,
46 Instant,
47 },
48};
49
50use freya_clipboard::copypasta::{
51 ClipboardContext,
52 ClipboardProvider,
53};
54use freya_components::{
55 cache::AssetCacher,
56 integration::integration,
57};
58use freya_core::{
59 integration::*,
60 prelude::*,
61};
62use freya_engine::prelude::{
63 EncodedImageFormat,
64 FontCollection,
65 FontMgr,
66 SkData,
67 TypefaceFontProvider,
68 raster_n32_premul,
69};
70use ragnarok::{
71 CursorPoint,
72 EventsExecutorRunner,
73 EventsMeasurerRunner,
74 NodesState,
75};
76use torin::prelude::{
77 LayoutNode,
78 Size2D,
79};
80
81pub mod prelude {
82 pub use freya_core::{
83 events::platform::*,
84 prelude::*,
85 };
86
87 pub use crate::{
88 DocRunner,
89 TestingRunner,
90 launch_doc,
91 launch_test,
92 };
93}
94
95type DocRunnerHook = Box<dyn FnOnce(&mut TestingRunner)>;
96
97pub struct DocRunner {
98 app: AppComponent,
99 size: Size2D,
100 scale_factor: f64,
101 hook: Option<DocRunnerHook>,
102 image_path: PathBuf,
103}
104
105impl DocRunner {
106 pub fn render(self) {
107 let (mut test, _) = TestingRunner::new(self.app, self.size, |_| {}, self.scale_factor);
108 if let Some(hook) = self.hook {
109 (hook)(&mut test);
110 }
111 test.render_to_file(self.image_path);
112 }
113
114 pub fn with_hook(mut self, hook: impl FnOnce(&mut TestingRunner) + 'static) -> Self {
115 self.hook = Some(Box::new(hook));
116 self
117 }
118
119 pub fn with_image_path(mut self, image_path: PathBuf) -> Self {
120 self.image_path = image_path;
121 self
122 }
123
124 pub fn with_scale_factor(mut self, scale_factor: f64) -> Self {
125 self.scale_factor = scale_factor;
126 self
127 }
128
129 pub fn with_size(mut self, size: Size2D) -> Self {
130 self.size = size;
131 self
132 }
133}
134
135pub fn launch_doc(app: impl Into<AppComponent>, path: impl Into<PathBuf>) -> DocRunner {
136 DocRunner {
137 app: app.into(),
138 size: Size2D::new(250., 250.),
139 scale_factor: 1.0,
140 hook: None,
141 image_path: path.into(),
142 }
143}
144
145pub fn launch_test(app: impl Into<AppComponent>) -> TestingRunner {
146 TestingRunner::new(app, Size2D::new(500., 500.), |_| {}, 1.0).0
147}
148
149pub struct TestingRunner {
150 nodes_state: NodesState<NodeId>,
151 runner: Runner,
152 tree: Rc<RefCell<Tree>>,
153 size: Size2D,
154
155 accessibility: AccessibilityTree,
156
157 events_receiver: futures_channel::mpsc::UnboundedReceiver<EventsChunk>,
158 events_sender: futures_channel::mpsc::UnboundedSender<EventsChunk>,
159
160 requested_focus_strategy: Rc<RefCell<Option<AccessibilityFocusStrategy>>>,
161
162 font_manager: FontMgr,
163 font_collection: FontCollection,
164
165 platform: Platform,
166
167 animation_clock: AnimationClock,
168 ticker_sender: RenderingTickerSender,
169
170 default_fonts: Vec<Cow<'static, str>>,
171 scale_factor: f64,
172}
173
174impl TestingRunner {
175 pub fn new<T>(
176 app: impl Into<AppComponent>,
177 size: Size2D,
178 hook: impl FnOnce(&mut Runner) -> T,
179 scale_factor: f64,
180 ) -> (Self, T) {
181 let (events_sender, events_receiver) = futures_channel::mpsc::unbounded();
182 let app = app.into();
183 let mut runner = Runner::new(move || integration(app.clone()).into_element());
184
185 runner.provide_root_context(GlobalContexts::default);
186
187 runner.provide_root_context(ScreenReader::new);
188
189 let (ticker_sender, ticker) = RenderingTicker::new();
190 runner.provide_root_context(|| ticker);
191
192 let animation_clock = runner.provide_root_context(AnimationClock::new);
193
194 runner.provide_root_context(AssetCacher::create);
195
196 let tree = Tree::default();
197 let tree = Rc::new(RefCell::new(tree));
198
199 let requested_focus_strategy: Rc<RefCell<Option<AccessibilityFocusStrategy>>> =
200 Rc::new(RefCell::new(None));
201
202 let platform = runner.provide_root_context({
203 let requested_focus_strategy = requested_focus_strategy.clone();
204 || Platform {
205 focused_accessibility_id: State::create(ACCESSIBILITY_ROOT_ID),
206 focused_accessibility_node: State::create(accesskit::Node::new(
207 accesskit::Role::Window,
208 )),
209 root_size: State::create(size),
210 scale_factor: State::create(scale_factor),
211 custom_scale_factor: State::create(1.0),
212 navigation_mode: State::create(NavigationMode::NotKeyboard),
213 preferred_theme: State::create(PreferredTheme::Light),
214 is_app_focused: State::create(true),
215 accent_color: State::create(AccentColor::default()),
216 sender: Rc::new(move |user_event| {
217 match user_event {
218 UserEvent::FocusAccessibilityNode(strategy) => {
219 requested_focus_strategy.borrow_mut().replace(strategy);
220 }
221 UserEvent::RequestRedraw
222 | UserEvent::SetCursorIcon(_)
223 | UserEvent::SetCustomScaleFactor(_)
224 | UserEvent::Erased(_) => {
225 }
227 }
228 }),
229 }
230 });
231
232 runner.provide_root_context(|| {
233 let clipboard: Option<Box<dyn ClipboardProvider>> = ClipboardContext::new()
234 .ok()
235 .map(|c| Box::new(c) as Box<dyn ClipboardProvider>);
236
237 State::create(clipboard)
238 });
239
240 runner.provide_root_context(|| tree.borrow().accessibility_generator.clone());
241
242 let hook_result = hook(&mut runner);
243
244 let mut font_collection = FontCollection::new();
245 let def_mgr = FontMgr::default();
246 let provider = TypefaceFontProvider::new();
247 let font_manager: FontMgr = provider.into();
248 font_collection.set_default_font_manager(def_mgr, None);
249 font_collection.set_dynamic_font_manager(font_manager.clone());
250 font_collection.paragraph_cache_mut().turn_on(false);
251
252 runner.provide_root_context(|| font_collection.clone());
253
254 let nodes_state = NodesState::default();
255 let accessibility = AccessibilityTree::default();
256
257 let mut runner = Self {
258 runner,
259 tree,
260 size,
261
262 accessibility,
263 platform,
264
265 nodes_state,
266 events_receiver,
267 events_sender,
268
269 requested_focus_strategy,
270
271 font_manager,
272 font_collection,
273
274 animation_clock,
275 ticker_sender,
276
277 default_fonts: default_fonts(),
278 scale_factor,
279 };
280
281 runner.sync_and_update();
282
283 (runner, hook_result)
284 }
285
286 pub fn set_fonts(&mut self, fonts: HashMap<&str, &[u8]>) {
287 let mut provider = TypefaceFontProvider::new();
288 for (font_name, font_data) in fonts {
289 let ft_type = self
290 .font_collection
291 .fallback_manager()
292 .unwrap()
293 .new_from_data(font_data, None)
294 .unwrap_or_else(|| panic!("Failed to load font {font_name}."));
295 provider.register_typeface(ft_type, Some(font_name));
296 }
297 let font_manager: FontMgr = provider.into();
298 self.font_manager = font_manager.clone();
299 self.font_collection.set_dynamic_font_manager(font_manager);
300 }
301
302 pub fn set_default_fonts(&mut self, fonts: &[Cow<'static, str>]) {
303 self.default_fonts.clear();
304 self.default_fonts.extend_from_slice(fonts);
305 self.tree.borrow_mut().layout.reset();
306 self.tree.borrow_mut().text_cache.reset();
307 self.tree.borrow_mut().measure_layout(
308 self.size,
309 &mut self.font_collection,
310 &self.font_manager,
311 &self.events_sender,
312 self.scale_factor,
313 &self.default_fonts,
314 );
315 self.tree.borrow_mut().accessibility_diff.clear();
316 self.accessibility.focused_id = ACCESSIBILITY_ROOT_ID;
317 self.accessibility.init(&mut self.tree.borrow_mut(), "");
318 self.sync_and_update();
319 }
320
321 pub fn run_in<T>(&self, run: impl FnOnce() -> T) -> T {
323 self.runner.run_in(run)
324 }
325
326 pub async fn handle_events(&mut self) {
327 self.runner.handle_events().await
328 }
329
330 pub fn handle_events_immediately(&mut self) {
331 self.runner.handle_events_immediately()
332 }
333
334 pub fn sync_and_update(&mut self) {
335 if let Some(strategy) = self.requested_focus_strategy.borrow_mut().take() {
336 self.tree
337 .borrow_mut()
338 .accessibility_diff
339 .request_focus(strategy);
340 }
341
342 while let Ok(events_chunk) = self.events_receiver.try_recv() {
343 match events_chunk {
344 EventsChunk::Processed(processed_events) => {
345 let events_executor_adapter = EventsExecutorAdapter {
346 runner: &mut self.runner,
347 };
348 events_executor_adapter.run(&mut self.nodes_state, processed_events);
349 }
350 EventsChunk::Batch(events) => {
351 for event in events {
352 self.runner.handle_event(
353 event.node_id,
354 event.name,
355 event.data,
356 event.bubbles,
357 );
358 }
359 }
360 }
361 }
362
363 let mutations = self.runner.sync_and_update();
364 let result = self
365 .runner
366 .run_in(|| self.tree.borrow_mut().apply_mutations(mutations));
367 if let Some(strategy) = result.auto_focus {
368 self.requested_focus_strategy.borrow_mut().replace(strategy);
369 }
370 self.tree.borrow_mut().measure_layout(
371 self.size,
372 &mut self.font_collection,
373 &self.font_manager,
374 &self.events_sender,
375 self.scale_factor,
376 &self.default_fonts,
377 );
378
379 let accessibility_update = self.accessibility.process_updates(
380 &mut self.tree.borrow_mut(),
381 &self.events_sender,
382 "",
383 );
384
385 self.platform
386 .focused_accessibility_id
387 .set_if_modified(accessibility_update.focus);
388 let node_id = self.accessibility.focused_node_id().unwrap();
389 let tree = self.tree.borrow();
390 let layout_node = tree.layout.get(&node_id).unwrap();
391 self.platform
392 .focused_accessibility_node
393 .set_if_modified(AccessibilityTree::create_node(
394 node_id,
395 layout_node,
396 &tree,
397 "",
398 ));
399 }
400
401 pub fn poll(&mut self, step: Duration, duration: Duration) {
404 let started = Instant::now();
405 while started.elapsed() < duration {
406 self.handle_events_immediately();
407 self.sync_and_update();
408 std::thread::sleep(step);
409 self.ticker_sender.notify();
410 }
411 }
412
413 pub fn poll_n(&mut self, step: Duration, times: u32) {
416 for _ in 0..times {
417 self.handle_events_immediately();
418 self.sync_and_update();
419 std::thread::sleep(step);
420 self.ticker_sender.notify();
421 }
422 }
423
424 pub fn send_event(&mut self, platform_event: PlatformEvent) {
425 let mut events_measurer_adapter = EventsMeasurerAdapter {
426 tree: &mut self.tree.borrow_mut(),
427 scale_factor: self.scale_factor,
428 };
429 let processed_events = events_measurer_adapter.run(
430 &mut vec![platform_event],
431 &mut self.nodes_state,
432 self.accessibility.focused_node_id(),
433 );
434 self.events_sender
435 .unbounded_send(EventsChunk::Processed(processed_events))
436 .unwrap();
437 }
438
439 pub fn move_cursor(&mut self, cursor: impl Into<CursorPoint>) {
440 self.send_event(PlatformEvent::Mouse {
441 name: MouseEventName::MouseMove,
442 cursor: cursor.into(),
443 button: Some(MouseButton::Left),
444 })
445 }
446
447 pub fn write_text(&mut self, text: impl ToString) {
448 let text = text.to_string();
449 self.send_event(PlatformEvent::Keyboard {
450 name: KeyboardEventName::KeyDown,
451 key: Key::Character(text),
452 code: Code::Unidentified,
453 modifiers: Modifiers::default(),
454 });
455 self.sync_and_update();
456 }
457
458 pub fn press_key(&mut self, key: Key) {
459 self.send_event(PlatformEvent::Keyboard {
460 name: KeyboardEventName::KeyDown,
461 key,
462 code: Code::Unidentified,
463 modifiers: Modifiers::default(),
464 });
465 self.sync_and_update();
466 }
467
468 pub fn press_cursor(&mut self, cursor: impl Into<CursorPoint>) {
469 let cursor = cursor.into();
470 self.send_event(PlatformEvent::Mouse {
471 name: MouseEventName::MouseDown,
472 cursor,
473 button: Some(MouseButton::Left),
474 });
475 self.sync_and_update();
476 }
477
478 pub fn release_cursor(&mut self, cursor: impl Into<CursorPoint>) {
479 let cursor = cursor.into();
480 self.send_event(PlatformEvent::Mouse {
481 name: MouseEventName::MouseUp,
482 cursor,
483 button: Some(MouseButton::Left),
484 });
485 self.sync_and_update();
486 }
487
488 pub fn click_cursor(&mut self, cursor: impl Into<CursorPoint>) {
489 let cursor = cursor.into();
490 self.send_event(PlatformEvent::Mouse {
491 name: MouseEventName::MouseDown,
492 cursor,
493 button: Some(MouseButton::Left),
494 });
495 self.sync_and_update();
496 self.send_event(PlatformEvent::Mouse {
497 name: MouseEventName::MouseUp,
498 cursor,
499 button: Some(MouseButton::Left),
500 });
501 self.sync_and_update();
502 }
503
504 pub fn press_touch(&mut self, location: impl Into<CursorPoint>) {
505 self.send_event(PlatformEvent::Touch {
506 name: TouchEventName::TouchStart,
507 location: location.into(),
508 finger_id: 0,
509 phase: TouchPhase::Started,
510 force: None,
511 });
512 self.sync_and_update();
513 }
514
515 pub fn move_touch(&mut self, location: impl Into<CursorPoint>) {
516 self.send_event(PlatformEvent::Touch {
517 name: TouchEventName::TouchMove,
518 location: location.into(),
519 finger_id: 0,
520 phase: TouchPhase::Moved,
521 force: None,
522 });
523 self.sync_and_update();
524 }
525
526 pub fn release_touch(&mut self, location: impl Into<CursorPoint>) {
527 self.send_event(PlatformEvent::Touch {
528 name: TouchEventName::TouchEnd,
529 location: location.into(),
530 finger_id: 0,
531 phase: TouchPhase::Ended,
532 force: None,
533 });
534 self.sync_and_update();
535 }
536
537 pub fn scroll(&mut self, cursor: impl Into<CursorPoint>, scroll: impl Into<CursorPoint>) {
538 let cursor = cursor.into();
539 let scroll = scroll.into();
540 self.send_event(PlatformEvent::Wheel {
541 name: WheelEventName::Wheel,
542 scroll,
543 cursor,
544 source: WheelSource::Device,
545 });
546 self.sync_and_update();
547 self.send_event(PlatformEvent::Mouse {
549 name: MouseEventName::MouseMove,
550 cursor,
551 button: None,
552 });
553 self.sync_and_update();
554 }
555
556 pub fn animation_clock(&mut self) -> &mut AnimationClock {
557 &mut self.animation_clock
558 }
559
560 pub fn render(&mut self) -> SkData {
561 let mut surface = raster_n32_premul((self.size.width as i32, self.size.height as i32))
562 .expect("Failed to create the surface.");
563
564 let render_pipeline = RenderPipeline {
565 font_collection: &mut self.font_collection,
566 font_manager: &self.font_manager,
567 tree: &self.tree.borrow(),
568 canvas: surface.canvas(),
569 scale_factor: self.scale_factor,
570 background: Color::WHITE,
571 };
572 render_pipeline.render();
573
574 let image = surface.image_snapshot();
575 let mut context = surface.direct_context();
576 image
577 .encode(context.as_mut(), EncodedImageFormat::PNG, None)
578 .expect("Failed to encode the snapshot.")
579 }
580
581 pub fn render_to_file(&mut self, path: impl Into<PathBuf>) {
582 let path = path.into();
583
584 let image = self.render();
585
586 let mut snapshot_file = File::create(path).expect("Failed to create the snapshot file.");
587
588 snapshot_file
589 .write_all(&image)
590 .expect("Failed to save the snapshot file.");
591 }
592
593 pub fn find<T>(
594 &self,
595 matcher: impl Fn(TestingNode, &dyn ElementExt) -> Option<T>,
596 ) -> Option<T> {
597 let mut matched = None;
598 {
599 let tree = self.tree.borrow();
600 tree.traverse_depth(|id| {
601 if matched.is_some() {
602 return;
603 }
604 let element = tree.elements.get(&id).unwrap();
605 let node = TestingNode {
606 tree: self.tree.clone(),
607 id,
608 };
609 matched = matcher(node, element.as_ref());
610 });
611 }
612
613 matched
614 }
615
616 pub fn find_many<T>(
617 &self,
618 matcher: impl Fn(TestingNode, &dyn ElementExt) -> Option<T>,
619 ) -> Vec<T> {
620 let mut matched = Vec::new();
621 {
622 let tree = self.tree.borrow();
623 tree.traverse_depth(|id| {
624 let element = tree.elements.get(&id).unwrap();
625 let node = TestingNode {
626 tree: self.tree.clone(),
627 id,
628 };
629 if let Some(result) = matcher(node, element.as_ref()) {
630 matched.push(result);
631 }
632 });
633 }
634
635 matched
636 }
637}
638
639pub struct TestingNode {
640 tree: Rc<RefCell<Tree>>,
641 id: NodeId,
642}
643
644impl TestingNode {
645 pub fn layout(&self) -> LayoutNode {
646 self.tree.borrow().layout.get(&self.id).cloned().unwrap()
647 }
648
649 pub fn children(&self) -> Vec<Self> {
650 let children = self
651 .tree
652 .borrow()
653 .children
654 .get(&self.id)
655 .cloned()
656 .unwrap_or_default();
657
658 children
659 .into_iter()
660 .map(|child_id| Self {
661 id: child_id,
662 tree: self.tree.clone(),
663 })
664 .collect()
665 }
666
667 pub fn is_visible(&self) -> bool {
668 let layout = self.layout();
669 let effect_state = self
670 .tree
671 .borrow()
672 .effect_state
673 .get(&self.id)
674 .cloned()
675 .unwrap();
676
677 effect_state.is_visible(&self.tree.borrow().layout, &layout.area)
678 }
679
680 pub fn element(&self) -> Rc<dyn ElementExt> {
681 self.tree
682 .borrow()
683 .elements
684 .get(&self.id)
685 .cloned()
686 .expect("Element does not exist.")
687 }
688}