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