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