1use std::{
2 collections::HashSet,
3 ops::{
4 Deref,
5 DerefMut,
6 },
7};
8
9use freya_native_core::{
10 exports::shipyard::{
11 IntoIter,
12 View,
13 },
14 prelude::NodeImmutable,
15 NodeId,
16};
17use rustc_hash::FxHashMap;
18use torin::prelude::{
19 Area,
20 LayoutNode,
21 Torin,
22};
23
24use crate::{
25 dom::{
26 CompositorDirtyNodes,
27 DioxusDOM,
28 DioxusNode,
29 },
30 elements::{
31 ElementUtils,
32 ElementUtilsResolver,
33 ElementWithUtils,
34 },
35 layers::Layers,
36 states::{
37 LayerState,
38 StyleState,
39 TransformState,
40 ViewportState,
41 },
42};
43
44#[derive(Clone, Default, Debug)]
52pub struct CompositorCache(FxHashMap<NodeId, Area>);
53
54impl Deref for CompositorCache {
55 type Target = FxHashMap<NodeId, Area>;
56
57 fn deref(&self) -> &Self::Target {
58 &self.0
59 }
60}
61
62impl DerefMut for CompositorCache {
63 fn deref_mut(&mut self) -> &mut Self::Target {
64 &mut self.0
65 }
66}
67
68#[derive(Clone, Default, Debug)]
69pub struct CompositorDirtyArea(Option<Area>);
70
71impl CompositorDirtyArea {
72 pub fn take(&mut self) -> Option<Area> {
74 self.0.take()
75 }
76
77 pub fn unite_or_insert(&mut self, other: &Area) {
79 if let Some(dirty_area) = &mut self.0 {
80 *dirty_area = dirty_area.union(other);
81 } else {
82 self.0 = Some(*other);
83 }
84 }
85
86 pub fn round_out(&mut self) {
88 if let Some(dirty_area) = &mut self.0 {
89 *dirty_area = dirty_area.round_out();
90 }
91 }
92
93 pub fn intersects(&self, other: &Area) -> bool {
95 self.0
96 .map(|dirty_area| dirty_area.intersects(other))
97 .unwrap_or_default()
98 }
99}
100
101impl Deref for CompositorDirtyArea {
102 type Target = Option<Area>;
103
104 fn deref(&self) -> &Self::Target {
105 &self.0
106 }
107}
108
109#[derive(Debug)]
110pub struct Compositor {
111 full_render: bool,
112}
113
114impl Default for Compositor {
115 fn default() -> Self {
116 Self { full_render: true }
117 }
118}
119
120impl Compositor {
121 #[inline]
122 pub fn get_drawing_area(
123 node_id: NodeId,
124 layout: &Torin<NodeId>,
125 rdom: &DioxusDOM,
126 scale_factor: f32,
127 ) -> Option<Area> {
128 let layout_node = layout.get(node_id)?;
129 let node_ref = rdom.get(node_id)?;
130 let utils = node_ref.node_type().tag()?.utils()?;
131 let style_state = node_ref.get::<StyleState>().unwrap();
132 let viewport_state = node_ref.get::<ViewportState>().unwrap();
133 let transform_state = node_ref.get::<TransformState>().unwrap();
134 utils.drawing_area_with_viewports(
135 layout_node,
136 &node_ref,
137 layout,
138 scale_factor,
139 &style_state,
140 &viewport_state,
141 &transform_state,
142 )
143 }
144
145 #[inline]
146 pub fn with_utils<T>(
147 node_id: NodeId,
148 layout: &Torin<NodeId>,
149 rdom: &DioxusDOM,
150 run: impl FnOnce(DioxusNode, ElementWithUtils, &LayoutNode) -> T,
151 ) -> Option<T> {
152 let layout_node = layout.get(node_id)?;
153 let node = rdom.get(node_id)?;
154 let utils = node.node_type().tag()?.utils()?;
155
156 Some(run(node, utils, layout_node))
157 }
158
159 #[allow(clippy::too_many_arguments)]
167 pub fn run<'a>(
168 &mut self,
169 dirty_nodes: &mut CompositorDirtyNodes,
170 dirty_area: &mut CompositorDirtyArea,
171 cache: &mut CompositorCache,
172 layers: &'a Layers,
173 dirty_layers: &'a mut Layers,
174 layout: &Torin<NodeId>,
175 rdom: &DioxusDOM,
176 scale_factor: f32,
177 ) -> &'a Layers {
178 if self.full_render {
179 rdom.raw_world().run(
180 |viewport_states: View<ViewportState>,
181 style_states: View<StyleState>,
182 transform_states: View<TransformState>| {
183 for (viewport, style_state, transform_state) in
184 (&viewport_states, &style_states, &transform_states).iter()
185 {
186 Self::with_utils(
187 viewport.node_id,
188 layout,
189 rdom,
190 |node_ref, utils, layout_node| {
191 if utils.needs_cached_area(&node_ref, transform_state, style_state)
192 {
193 let area = utils.drawing_area(
194 layout_node,
195 &node_ref,
196 layout,
197 scale_factor,
198 style_state,
199 transform_state,
200 );
201 cache.insert(viewport.node_id, area);
203 }
204 },
205 );
206 }
207 },
208 );
209 self.full_render = false;
210 return layers;
211 }
212
213 let mut skipped_nodes = HashSet::<NodeId>::new();
214
215 loop {
216 let mut any_dirty = false;
217 rdom.raw_world().run(
218 |viewport_states: View<ViewportState>,
219 layer_states: View<LayerState>,
220 style_states: View<StyleState>,
221 transform_states: View<TransformState>| {
222 for (viewport, layer_state, style_state, transform_state) in (
223 &viewport_states,
224 &layer_states,
225 &style_states,
226 &transform_states,
227 )
228 .iter()
229 {
230 if skipped_nodes.contains(&viewport.node_id) {
231 continue;
232 }
233 let skip = Self::with_utils(
234 viewport.node_id,
235 layout,
236 rdom,
237 |node_ref, utils, layout_node| {
238 let Some(area) = utils.drawing_area_with_viewports(
239 layout_node,
240 &node_ref,
241 layout,
242 scale_factor,
243 style_state,
244 viewport,
245 transform_state,
246 ) else {
247 return true;
248 };
249
250 let is_dirty = dirty_nodes.remove(&viewport.node_id);
251
252 let mut invalidated_cache_area =
254 cache.get(&viewport.node_id).and_then(|cached_area| {
255 if is_dirty || dirty_area.intersects(cached_area) {
256 Some(*cached_area)
257 } else {
258 None
259 }
260 });
261
262 let is_invalidated = is_dirty
263 || invalidated_cache_area.is_some()
264 || dirty_area.intersects(&area);
265
266 if is_invalidated {
267 dirty_layers
269 .insert_node_in_layer(viewport.node_id, layer_state.layer);
270
271 if utils.needs_cached_area(
273 &node_ref,
274 transform_state,
275 style_state,
276 ) {
277 cache.insert(viewport.node_id, area);
278 }
279
280 if is_dirty {
281 if let Some(invalidated_cache_area) =
283 invalidated_cache_area.take()
284 {
285 dirty_area.unite_or_insert(&invalidated_cache_area);
286 }
287
288 dirty_area.unite_or_insert(&area);
290
291 any_dirty = true;
293 }
294 }
295
296 is_invalidated
297 },
298 )
299 .unwrap_or(true);
300
301 if skip {
302 skipped_nodes.insert(viewport.node_id);
303 }
304 }
305 },
306 );
307
308 if !any_dirty {
309 break;
310 }
311 }
312
313 dirty_nodes.drain();
314
315 dirty_layers
316 }
317
318 pub fn reset(&mut self) {
320 self.full_render = true;
321 }
322}
323
324#[cfg(test)]
325mod test {
326 use freya::{
327 core::{
328 layers::Layers,
329 render::Compositor,
330 },
331 prelude::*,
332 };
333 use freya_testing::prelude::*;
334 use itertools::sorted;
335
336 fn run_compositor(
337 utils: &TestingHandler<()>,
338 compositor: &mut Compositor,
339 ) -> (Layers, Layers, usize) {
340 let sdom = utils.sdom();
341 let fdom = sdom.get();
342 let layout = fdom.layout();
343 let layers = fdom.layers();
344 let rdom = fdom.rdom();
345 let mut compositor_dirty_area = fdom.compositor_dirty_area();
346 let mut compositor_dirty_nodes = fdom.compositor_dirty_nodes();
347 let mut compositor_cache = fdom.compositor_cache();
348
349 let mut dirty_layers = Layers::default();
350
351 let rendering_layers = compositor.run(
353 &mut compositor_dirty_nodes,
354 &mut compositor_dirty_area,
355 &mut compositor_cache,
356 &layers,
357 &mut dirty_layers,
358 &layout,
359 rdom,
360 1.0f32,
361 );
362
363 compositor_dirty_area.take();
364 compositor_dirty_nodes.clear();
365
366 let mut painted_nodes = 0;
367 for (_, nodes) in sorted(rendering_layers.iter()) {
368 for node_id in nodes {
369 if layout.get(*node_id).is_some() {
370 painted_nodes += 1;
371 }
372 }
373 }
374
375 (layers.clone(), rendering_layers.clone(), painted_nodes)
376 }
377
378 #[tokio::test]
379 pub async fn button_drawing() {
380 fn compositor_app() -> Element {
381 let mut count = use_signal(|| 0);
382
383 rsx!(
384 rect {
385 height: "50%",
386 width: "100%",
387 main_align: "center",
388 cross_align: "center",
389 background: "rgb(0, 119, 182)",
390 color: "white",
391 shadow: "0 4 20 5 rgb(0, 0, 0, 80)",
392 label {
393 font_size: "75",
394 font_weight: "bold",
395 "{count}"
396 }
397 }
398 rect {
399 height: "50%",
400 width: "100%",
401 main_align: "center",
402 cross_align: "center",
403 direction: "horizontal",
404 Button {
405 onclick: move |_| count += 1,
406 label { "Increase" }
407 }
408 }
409 )
410 }
411
412 let mut compositor = Compositor::default();
413 let mut utils = launch_test(compositor_app);
414 let root = utils.root();
415 let label = root.get(0).get(0);
416 utils.wait_for_update().await;
417
418 assert_eq!(label.get(0).text(), Some("0"));
419
420 let (layers, rendering_layers, _) = run_compositor(&utils, &mut compositor);
421 assert_eq!(layers, rendering_layers);
423
424 utils.move_cursor((275., 375.)).await;
425
426 let (_, _, painted_nodes) = run_compositor(&utils, &mut compositor);
427
428 assert_eq!(painted_nodes, 4);
430
431 utils.click_cursor((275., 375.)).await;
432
433 assert_eq!(label.get(0).text(), Some("1"));
434 }
435
436 #[tokio::test]
437 pub async fn after_shadow_drawing() {
438 fn compositor_app() -> Element {
439 let mut height = use_signal(|| 200);
440 let mut shadow = use_signal(|| 20);
441
442 rsx!(
443 rect {
444 height: "100",
445 width: "200",
446 background: "red",
447 margin: "0 0 2 0",
448 onclick: move |_| height += 10,
449 }
450 rect {
451 height: "{height}",
452 width: "200",
453 background: "green",
454 shadow: "0 {shadow} 1 0 rgb(0, 0, 0, 0.5)",
455 margin: "0 0 2 0",
456 onclick: move |_| height -= 10,
457 }
458 rect {
459 height: "100",
460 width: "200",
461 background: "blue",
462 onclick: move |_| shadow.set(-20),
463 }
464 )
465 }
466
467 let mut compositor = Compositor::default();
468 let mut utils = launch_test(compositor_app);
469 utils.wait_for_update().await;
470
471 let (layers, rendering_layers, _) = run_compositor(&utils, &mut compositor);
472 assert_eq!(layers, rendering_layers);
474
475 utils.click_cursor((5., 5.)).await;
476
477 let (_, _, painted_nodes) = run_compositor(&utils, &mut compositor);
478
479 assert_eq!(painted_nodes, 3);
481
482 utils.click_cursor((5., 150.)).await;
483
484 let (_, _, painted_nodes) = run_compositor(&utils, &mut compositor);
485
486 assert_eq!(painted_nodes, 3);
488
489 utils.click_cursor((5., 350.)).await;
490
491 let (_, _, painted_nodes) = run_compositor(&utils, &mut compositor);
492
493 assert_eq!(painted_nodes, 4);
495
496 utils.click_cursor((5., 150.)).await;
497
498 let (_, _, painted_nodes) = run_compositor(&utils, &mut compositor);
499
500 assert_eq!(painted_nodes, 4);
502 }
503
504 #[tokio::test]
505 pub async fn paragraph_drawing() {
506 fn compositor_app() -> Element {
507 let mut msg_state = use_signal(|| true);
508 let mut shadow_state = use_signal(|| true);
509
510 let msg = if msg_state() { "12" } else { "23" };
511 let shadow = if shadow_state() {
512 "-40 0 20 black"
513 } else {
514 "none"
515 };
516
517 rsx!(
518 rect {
519 height: "200",
520 width: "200",
521 direction: "horizontal",
522 spacing: "2",
523 rect {
524 onclick: move |_| msg_state.toggle(),
525 height: "200",
526 width: "200",
527 background: "red"
528 }
529 paragraph {
530 onclick: move |_| shadow_state.toggle(),
531 text {
532 font_size: "75",
533 font_weight: "bold",
534 text_shadow: "{shadow}",
535 "{msg}"
536 }
537 }
538 }
539 )
540 }
541
542 let mut compositor = Compositor::default();
543 let mut utils = launch_test(compositor_app);
544 let root = utils.root();
545 utils.wait_for_update().await;
546
547 assert_eq!(root.get(0).get(1).get(0).get(0).text(), Some("12"));
548
549 let (layers, rendering_layers, _) = run_compositor(&utils, &mut compositor);
550 assert_eq!(layers, rendering_layers);
552
553 utils.click_cursor((5., 5.)).await;
554
555 let (_, _, painted_nodes) = run_compositor(&utils, &mut compositor);
556
557 assert_eq!(painted_nodes, 4);
559
560 utils.click_cursor((205., 5.)).await;
561
562 let (_, _, painted_nodes) = run_compositor(&utils, &mut compositor);
563
564 assert_eq!(painted_nodes, 4);
566
567 utils.click_cursor((5., 5.)).await;
568
569 let (_, _, painted_nodes) = run_compositor(&utils, &mut compositor);
570
571 assert_eq!(painted_nodes, 2);
573 }
574
575 #[tokio::test]
576 pub async fn rotated_drawing() {
577 fn compositor_app() -> Element {
578 let mut rotate = use_signal(|| 0);
579
580 rsx!(
581 rect {
582 height: "50%",
583 width: "100%",
584 main_align: "center",
585 cross_align: "center",
586 background: "rgb(0, 119, 182)",
587 color: "white",
588 shadow: "0 4 20 5 rgb(0, 0, 0, 80)",
589 label {
590 rotate: "{rotate}deg",
591 "Hello"
592 }
593 label {
594 "World"
595 }
596 }
597 rect {
598 height: "50%",
599 width: "100%",
600 main_align: "center",
601 cross_align: "center",
602 direction: "horizontal",
603 Button {
604 theme: theme_with!(ButtonTheme {
605 shadow: "0 4 5 0 rgb(0, 0, 0, 0.1)".into(),
606 padding: "8 12".into(),
607 }),
608 onclick: move |_| rotate += 1,
609 label { "Rotate" }
610 }
611 }
612 )
613 }
614
615 let mut compositor = Compositor::default();
616 let mut utils = launch_test(compositor_app);
617 utils.wait_for_update().await;
618
619 let (layers, rendering_layers, _) = run_compositor(&utils, &mut compositor);
620 assert_eq!(layers, rendering_layers);
622
623 utils.click_cursor((275., 375.)).await;
624
625 let (_, _, painted_nodes) = run_compositor(&utils, &mut compositor);
626
627 assert_eq!(painted_nodes, 4);
629 }
630
631 #[tokio::test]
632 pub async fn rotated_shadow_drawing() {
633 fn compositor_app() -> Element {
634 let mut rotate = use_signal(|| 0);
635
636 rsx!(
637 rect {
638 height: "50%",
639 width: "100%",
640 main_align: "center",
641 cross_align: "center",
642 background: "rgb(0, 119, 182)",
643 color: "white",
644 shadow: "0 4 20 5 rgb(0, 0, 0, 80)",
645 label {
646 rotate: "{rotate}deg",
647 text_shadow: "0 180 12 rgb(0, 0, 0, 240)",
648 "Hello"
649 }
650 label {
651 "World"
652 }
653 }
654 rect {
655 height: "50%",
656 width: "100%",
657 main_align: "center",
658 cross_align: "center",
659 direction: "horizontal",
660 Button {
661 theme: theme_with!(ButtonTheme {
662 shadow: "0 4 5 0 rgb(0, 0, 0, 0.1)".into(),
663 padding: "8 12".into(),
664 }),
665 onclick: move |_| rotate += 1,
666 label { "Rotate" }
667 }
668 }
669 )
670 }
671
672 let mut compositor = Compositor::default();
673 let mut utils = launch_test(compositor_app);
674 utils.wait_for_update().await;
675
676 let (layers, rendering_layers, _) = run_compositor(&utils, &mut compositor);
677 assert_eq!(layers, rendering_layers);
679
680 utils.click_cursor((275., 375.)).await;
681
682 let (_, _, painted_nodes) = run_compositor(&utils, &mut compositor);
683
684 assert_eq!(painted_nodes, 7);
686 }
687
688 #[tokio::test]
689 pub async fn scale_drawing() {
690 fn compositor_app() -> Element {
691 let mut scale = use_signal(|| 1.);
692
693 rsx!(
694 rect {
695 scale: "{scale()} {scale()}",
696 height: "50%",
697 width: "100%",
698 main_align: "center",
699 cross_align: "center",
700 background: "rgb(0, 119, 182)",
701 color: "white",
702 shadow: "0 4 20 5 rgb(0, 0, 0, 80)",
703 label {
704 text_shadow: "0 180 12 rgb(0, 0, 0, 240)",
705 "Hello"
706 }
707 label {
708 "World"
709 }
710 }
711 rect {
712 height: "50%",
713 width: "100%",
714 main_align: "center",
715 cross_align: "center",
716 direction: "horizontal",
717 Button {
718 theme: theme_with!(ButtonTheme {
719 shadow: "0 4 5 0 rgb(0, 0, 0, 0.1)".into(),
720 padding: "8 12".into(),
721 }),
722 onclick: move |_| scale += 0.1,
723 label { "More" }
724 }
725 Button {
726 theme: theme_with!(ButtonTheme {
727 shadow: "0 4 5 0 rgb(0, 0, 0, 0.1)".into(),
728 padding: "8 12".into(),
729 }),
730 onclick: move |_| scale -= 0.1,
731 label { "Less" }
732 }
733 }
734 )
735 }
736
737 let mut compositor = Compositor::default();
738 let mut utils = launch_test_with_config(
739 compositor_app,
740 TestingConfig::<()> {
741 size: (400.0, 400.0).into(),
742 ..TestingConfig::default()
743 },
744 );
745 utils.wait_for_update().await;
746
747 let (layers, rendering_layers, _) = run_compositor(&utils, &mut compositor);
748 assert_eq!(layers, rendering_layers);
750
751 utils.click_cursor((180., 310.)).await;
752 let (_, _, painted_nodes) = run_compositor(&utils, &mut compositor);
753 assert_eq!(painted_nodes, 9);
754
755 utils.click_cursor((250., 310.)).await;
756 let (_, _, painted_nodes) = run_compositor(&utils, &mut compositor);
757 assert_eq!(painted_nodes, 9);
758
759 utils.click_cursor((250., 310.)).await;
760 let (_, _, painted_nodes) = run_compositor(&utils, &mut compositor);
761 assert_eq!(painted_nodes, 7);
762
763 utils.click_cursor((250., 310.)).await;
764 let (_, _, painted_nodes) = run_compositor(&utils, &mut compositor);
765 assert_eq!(painted_nodes, 7);
766
767 utils.click_cursor((250., 310.)).await;
768 let (_, _, painted_nodes) = run_compositor(&utils, &mut compositor);
769 assert_eq!(painted_nodes, 5);
770 }
771}