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