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