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