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