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(ScreenReader::new);
186
187 let (mut ticker_sender, ticker) = RenderingTicker::new();
188 ticker_sender.set_overflow(true);
189 runner.provide_root_context(|| ticker);
190
191 let animation_clock = runner.provide_root_context(AnimationClock::new);
192
193 runner.provide_root_context(AssetCacher::create);
194
195 let tree = Tree::default();
196 let tree = Rc::new(RefCell::new(tree));
197
198 let requested_focus_strategy: Rc<RefCell<Option<AccessibilityFocusStrategy>>> =
199 Rc::new(RefCell::new(None));
200
201 let platform = runner.provide_root_context({
202 let requested_focus_strategy = requested_focus_strategy.clone();
203 || Platform {
204 focused_accessibility_id: State::create(ACCESSIBILITY_ROOT_ID),
205 focused_accessibility_node: State::create(accesskit::Node::new(
206 accesskit::Role::Window,
207 )),
208 root_size: State::create(size),
209 scale_factor: State::create(scale_factor),
210 navigation_mode: State::create(NavigationMode::NotKeyboard),
211 preferred_theme: State::create(PreferredTheme::Light),
212 is_app_focused: State::create(true),
213 accent_color: State::create(AccentColor::default()),
214 sender: Rc::new(move |user_event| {
215 match user_event {
216 UserEvent::RequestRedraw => {
217 }
219 UserEvent::FocusAccessibilityNode(strategy) => {
220 requested_focus_strategy.borrow_mut().replace(strategy);
221 }
222 UserEvent::SetCursorIcon(_) => {
223 }
225 UserEvent::Erased(_) => {
226 }
228 }
229 }),
230 }
231 });
232
233 runner.provide_root_context(|| {
234 let clipboard: Option<Box<dyn ClipboardProvider>> = ClipboardContext::new()
235 .ok()
236 .map(|c| Box::new(c) as Box<dyn ClipboardProvider>);
237
238 State::create(clipboard)
239 });
240
241 runner.provide_root_context(|| tree.borrow().accessibility_generator.clone());
242
243 let hook_result = hook(&mut runner);
244
245 let mut font_collection = FontCollection::new();
246 let def_mgr = FontMgr::default();
247 let provider = TypefaceFontProvider::new();
248 let font_manager: FontMgr = provider.into();
249 font_collection.set_default_font_manager(def_mgr, None);
250 font_collection.set_dynamic_font_manager(font_manager.clone());
251 font_collection.paragraph_cache_mut().turn_on(false);
252
253 runner.provide_root_context(|| font_collection.clone());
254
255 let nodes_state = NodesState::default();
256 let accessibility = AccessibilityTree::default();
257
258 let mut runner = Self {
259 runner,
260 tree,
261 size,
262
263 accessibility,
264 platform,
265
266 nodes_state,
267 events_receiver,
268 events_sender,
269
270 requested_focus_strategy,
271
272 font_manager,
273 font_collection,
274
275 animation_clock,
276 ticker_sender,
277
278 default_fonts: default_fonts(),
279 scale_factor,
280 };
281
282 runner.sync_and_update();
283
284 (runner, hook_result)
285 }
286
287 pub fn set_fonts(&mut self, fonts: HashMap<&str, &[u8]>) {
288 let mut provider = TypefaceFontProvider::new();
289 for (font_name, font_data) in fonts {
290 let ft_type = self
291 .font_collection
292 .fallback_manager()
293 .unwrap()
294 .new_from_data(font_data, None)
295 .unwrap_or_else(|| panic!("Failed to load font {font_name}."));
296 provider.register_typeface(ft_type, Some(font_name));
297 }
298 let font_manager: FontMgr = provider.into();
299 self.font_manager = font_manager.clone();
300 self.font_collection.set_dynamic_font_manager(font_manager);
301 }
302
303 pub fn set_default_fonts(&mut self, fonts: &[Cow<'static, str>]) {
304 self.default_fonts.clear();
305 self.default_fonts.extend_from_slice(fonts);
306 self.tree.borrow_mut().layout.reset();
307 self.tree.borrow_mut().text_cache.reset();
308 self.tree.borrow_mut().measure_layout(
309 self.size,
310 &mut self.font_collection,
311 &self.font_manager,
312 &self.events_sender,
313 self.scale_factor,
314 &self.default_fonts,
315 );
316 self.tree.borrow_mut().accessibility_diff.clear();
317 self.accessibility.focused_id = ACCESSIBILITY_ROOT_ID;
318 self.accessibility.init(&mut self.tree.borrow_mut());
319 self.sync_and_update();
320 }
321
322 pub async fn handle_events(&mut self) {
323 self.runner.handle_events().await
324 }
325
326 pub fn handle_events_immediately(&mut self) {
327 self.runner.handle_events_immediately()
328 }
329
330 pub fn sync_and_update(&mut self) {
331 if let Some(strategy) = self.requested_focus_strategy.borrow_mut().take() {
332 self.tree
333 .borrow_mut()
334 .accessibility_diff
335 .request_focus(strategy);
336 }
337
338 while let Ok(events_chunk) = self.events_receiver.try_recv() {
339 match events_chunk {
340 EventsChunk::Processed(processed_events) => {
341 let events_executor_adapter = EventsExecutorAdapter {
342 runner: &mut self.runner,
343 };
344 events_executor_adapter.run(&mut self.nodes_state, processed_events);
345 }
346 EventsChunk::Batch(events) => {
347 for event in events {
348 self.runner.handle_event(
349 event.node_id,
350 event.name,
351 event.data,
352 event.bubbles,
353 );
354 }
355 }
356 }
357 }
358
359 let mutations = self.runner.sync_and_update();
360 self.runner.run_in(|| {
361 self.tree.borrow_mut().apply_mutations(mutations);
362 });
363 self.tree.borrow_mut().measure_layout(
364 self.size,
365 &mut self.font_collection,
366 &self.font_manager,
367 &self.events_sender,
368 self.scale_factor,
369 &self.default_fonts,
370 );
371
372 let accessibility_update = self
373 .accessibility
374 .process_updates(&mut self.tree.borrow_mut(), &self.events_sender);
375
376 self.platform
377 .focused_accessibility_id
378 .set_if_modified(accessibility_update.focus);
379 let node_id = self.accessibility.focused_node_id().unwrap();
380 let tree = self.tree.borrow();
381 let layout_node = tree.layout.get(&node_id).unwrap();
382 self.platform
383 .focused_accessibility_node
384 .set_if_modified(AccessibilityTree::create_node(node_id, layout_node, &tree));
385 }
386
387 pub fn poll(&mut self, step: Duration, duration: Duration) {
390 let started = Instant::now();
391 while started.elapsed() < duration {
392 self.handle_events_immediately();
393 self.sync_and_update();
394 std::thread::sleep(step);
395 self.ticker_sender.broadcast_blocking(()).unwrap();
396 }
397 }
398
399 pub fn poll_n(&mut self, step: Duration, times: u32) {
402 for _ in 0..times {
403 self.handle_events_immediately();
404 self.sync_and_update();
405 std::thread::sleep(step);
406 self.ticker_sender.broadcast_blocking(()).unwrap();
407 }
408 }
409
410 pub fn send_event(&mut self, platform_event: PlatformEvent) {
411 let mut events_measurer_adapter = EventsMeasurerAdapter {
412 tree: &mut self.tree.borrow_mut(),
413 scale_factor: self.scale_factor,
414 };
415 let processed_events = events_measurer_adapter.run(
416 &mut vec![platform_event],
417 &mut self.nodes_state,
418 self.accessibility.focused_node_id(),
419 );
420 self.events_sender
421 .unbounded_send(EventsChunk::Processed(processed_events))
422 .unwrap();
423 }
424
425 pub fn move_cursor(&mut self, cursor: impl Into<CursorPoint>) {
426 self.send_event(PlatformEvent::Mouse {
427 name: MouseEventName::MouseMove,
428 cursor: cursor.into(),
429 button: Some(MouseButton::Left),
430 })
431 }
432
433 pub fn write_text(&mut self, text: impl ToString) {
434 let text = text.to_string();
435 self.send_event(PlatformEvent::Keyboard {
436 name: KeyboardEventName::KeyDown,
437 key: Key::Character(text),
438 code: Code::Unidentified,
439 modifiers: Modifiers::default(),
440 });
441 self.sync_and_update();
442 }
443
444 pub fn press_key(&mut self, key: Key) {
445 self.send_event(PlatformEvent::Keyboard {
446 name: KeyboardEventName::KeyDown,
447 key,
448 code: Code::Unidentified,
449 modifiers: Modifiers::default(),
450 });
451 self.sync_and_update();
452 }
453
454 pub fn press_cursor(&mut self, cursor: impl Into<CursorPoint>) {
455 let cursor = cursor.into();
456 self.send_event(PlatformEvent::Mouse {
457 name: MouseEventName::MouseDown,
458 cursor,
459 button: Some(MouseButton::Left),
460 });
461 self.sync_and_update();
462 }
463
464 pub fn release_cursor(&mut self, cursor: impl Into<CursorPoint>) {
465 let cursor = cursor.into();
466 self.send_event(PlatformEvent::Mouse {
467 name: MouseEventName::MouseUp,
468 cursor,
469 button: Some(MouseButton::Left),
470 });
471 self.sync_and_update();
472 }
473
474 pub fn click_cursor(&mut self, cursor: impl Into<CursorPoint>) {
475 let cursor = cursor.into();
476 self.send_event(PlatformEvent::Mouse {
477 name: MouseEventName::MouseDown,
478 cursor,
479 button: Some(MouseButton::Left),
480 });
481 self.sync_and_update();
482 self.send_event(PlatformEvent::Mouse {
483 name: MouseEventName::MouseUp,
484 cursor,
485 button: Some(MouseButton::Left),
486 });
487 self.sync_and_update();
488 }
489
490 pub fn press_touch(&mut self, location: impl Into<CursorPoint>) {
491 self.send_event(PlatformEvent::Touch {
492 name: TouchEventName::TouchStart,
493 location: location.into(),
494 finger_id: 0,
495 phase: TouchPhase::Started,
496 force: None,
497 });
498 self.sync_and_update();
499 }
500
501 pub fn move_touch(&mut self, location: impl Into<CursorPoint>) {
502 self.send_event(PlatformEvent::Touch {
503 name: TouchEventName::TouchMove,
504 location: location.into(),
505 finger_id: 0,
506 phase: TouchPhase::Moved,
507 force: None,
508 });
509 self.sync_and_update();
510 }
511
512 pub fn release_touch(&mut self, location: impl Into<CursorPoint>) {
513 self.send_event(PlatformEvent::Touch {
514 name: TouchEventName::TouchEnd,
515 location: location.into(),
516 finger_id: 0,
517 phase: TouchPhase::Ended,
518 force: None,
519 });
520 self.sync_and_update();
521 }
522
523 pub fn scroll(&mut self, cursor: impl Into<CursorPoint>, scroll: impl Into<CursorPoint>) {
524 let cursor = cursor.into();
525 let scroll = scroll.into();
526 self.send_event(PlatformEvent::Wheel {
527 name: WheelEventName::Wheel,
528 scroll,
529 cursor,
530 source: WheelSource::Device,
531 });
532 self.sync_and_update();
533 }
534
535 pub fn animation_clock(&mut self) -> &mut AnimationClock {
536 &mut self.animation_clock
537 }
538
539 pub fn render(&mut self) -> SkData {
540 let mut surface = raster_n32_premul((self.size.width as i32, self.size.height as i32))
541 .expect("Failed to create the surface.");
542
543 let render_pipeline = RenderPipeline {
544 font_collection: &mut self.font_collection,
545 font_manager: &self.font_manager,
546 tree: &self.tree.borrow(),
547 canvas: surface.canvas(),
548 scale_factor: self.scale_factor,
549 background: Color::WHITE,
550 };
551 render_pipeline.render();
552
553 let image = surface.image_snapshot();
554 let mut context = surface.direct_context();
555 image
556 .encode(context.as_mut(), EncodedImageFormat::PNG, None)
557 .expect("Failed to encode the snapshot.")
558 }
559
560 pub fn render_to_file(&mut self, path: impl Into<PathBuf>) {
561 let path = path.into();
562
563 let image = self.render();
564
565 let mut snapshot_file = File::create(path).expect("Failed to create the snapshot file.");
566
567 snapshot_file
568 .write_all(&image)
569 .expect("Failed to save the snapshot file.");
570 }
571
572 pub fn find<T>(
573 &self,
574 matcher: impl Fn(TestingNode, &dyn ElementExt) -> Option<T>,
575 ) -> Option<T> {
576 let mut matched = None;
577 {
578 let tree = self.tree.borrow();
579 tree.traverse_depth(|id| {
580 if matched.is_some() {
581 return;
582 }
583 let element = tree.elements.get(&id).unwrap();
584 let node = TestingNode {
585 tree: self.tree.clone(),
586 id,
587 };
588 matched = matcher(node, element.as_ref());
589 });
590 }
591
592 matched
593 }
594
595 pub fn find_many<T>(
596 &self,
597 matcher: impl Fn(TestingNode, &dyn ElementExt) -> Option<T>,
598 ) -> Vec<T> {
599 let mut matched = Vec::new();
600 {
601 let tree = self.tree.borrow();
602 tree.traverse_depth(|id| {
603 let element = tree.elements.get(&id).unwrap();
604 let node = TestingNode {
605 tree: self.tree.clone(),
606 id,
607 };
608 if let Some(result) = matcher(node, element.as_ref()) {
609 matched.push(result);
610 }
611 });
612 }
613
614 matched
615 }
616}
617
618pub struct TestingNode {
619 tree: Rc<RefCell<Tree>>,
620 id: NodeId,
621}
622
623impl TestingNode {
624 pub fn layout(&self) -> LayoutNode {
625 self.tree.borrow().layout.get(&self.id).cloned().unwrap()
626 }
627
628 pub fn children(&self) -> Vec<Self> {
629 let children = self
630 .tree
631 .borrow()
632 .children
633 .get(&self.id)
634 .cloned()
635 .unwrap_or_default();
636
637 children
638 .into_iter()
639 .map(|child_id| Self {
640 id: child_id,
641 tree: self.tree.clone(),
642 })
643 .collect()
644 }
645
646 pub fn is_visible(&self) -> bool {
647 let layout = self.layout();
648 let effect_state = self
649 .tree
650 .borrow()
651 .effect_state
652 .get(&self.id)
653 .cloned()
654 .unwrap();
655
656 effect_state.is_visible(&self.tree.borrow().layout, &layout.area)
657 }
658
659 pub fn element(&self) -> Rc<dyn ElementExt> {
660 self.tree
661 .borrow()
662 .elements
663 .get(&self.id)
664 .cloned()
665 .expect("Element does not exist.")
666 }
667}