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