1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
use freya_engine::prelude::*;
use freya_native_core::{
    node::NodeType,
    prelude::ElementNode,
    real_dom::NodeImmutable,
    tags::TagName,
    NodeId,
};
use freya_node_state::{
    StyleState,
    TransformState,
    ViewportState,
};
use torin::prelude::{
    LayoutNode,
    Torin,
};

use super::wireframe_renderer;
use crate::{
    dom::DioxusNode,
    elements::{
        ElementUtils,
        ElementUtilsResolver,
    },
    prelude::DioxusDOM,
};

pub struct SkiaRenderer<'a> {
    pub canvas: &'a Canvas,
    pub font_collection: &'a mut FontCollection,
    pub font_manager: &'a FontMgr,
    pub matrices: Vec<(Matrix, Vec<NodeId>)>,
    pub opacities: Vec<(f32, Vec<NodeId>)>,
    pub default_fonts: &'a [String],
    pub scale_factor: f32,
}

impl SkiaRenderer<'_> {
    /// Render a node into the Skia canvas
    pub fn render(
        &mut self,
        rdom: &DioxusDOM,
        layout_node: &LayoutNode,
        node_ref: &DioxusNode,
        render_wireframe: bool,
        layout: &Torin<NodeId>,
    ) {
        let area = layout_node.visible_area();
        let node_type = &*node_ref.node_type();
        if let NodeType::Element(ElementNode { tag, .. }) = node_type {
            let Some(element_utils) = tag.utils() else {
                return;
            };

            self.canvas.save();

            let node_transform = &*node_ref.get::<TransformState>().unwrap();
            let node_style = &*node_ref.get::<StyleState>().unwrap();

            // Pass rotate effect to children
            if let Some(rotate_degs) = node_transform.rotate_degs {
                let mut matrix = Matrix::new_identity();
                matrix.set_rotate(
                    rotate_degs,
                    Some(Point {
                        x: area.min_x() + area.width() / 2.0,
                        y: area.min_y() + area.height() / 2.0,
                    }),
                );

                self.matrices.push((matrix, vec![node_ref.id()]));
            }

            // Pass opacity effect to children
            if let Some(opacity) = node_style.opacity {
                self.opacities.push((opacity, vec![node_ref.id()]));
            }

            // Apply inherited matrices
            for (matrix, nodes) in self.matrices.iter_mut() {
                if nodes.contains(&node_ref.id()) {
                    self.canvas.concat(matrix);

                    nodes.extend(node_ref.child_ids());
                }
            }

            // Apply inherited opacity effects
            for (opacity, nodes) in self.opacities.iter_mut() {
                if nodes.contains(&node_ref.id()) {
                    self.canvas.save_layer_alpha_f(
                        Rect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y()),
                        *opacity,
                    );

                    nodes.extend(node_ref.child_ids());
                }
            }

            // Clip all elements with their corresponding viewports
            let node_viewports = node_ref.get::<ViewportState>().unwrap();
            // Only clip the element iself when it's paragraph because
            // it will render the inner text spans on it's own, so if these spans overflow the paragraph,
            // It is the paragraph job to make sure they are clipped
            if !node_viewports.viewports.is_empty() && *tag == TagName::Paragraph {
                element_utils.clip(layout_node, node_ref, self.canvas, self.scale_factor);
            }

            for node_id in &node_viewports.viewports {
                let node_ref = rdom.get(*node_id).unwrap();
                let node_type = node_ref.node_type();
                let Some(element_utils) = node_type.tag().and_then(|tag| tag.utils()) else {
                    continue;
                };
                let layout_node = layout.get(*node_id).unwrap();
                element_utils.clip(layout_node, &node_ref, self.canvas, self.scale_factor);
            }

            element_utils.render(
                layout_node,
                node_ref,
                self.canvas,
                self.font_collection,
                self.font_manager,
                self.default_fonts,
                self.scale_factor,
            );

            if render_wireframe {
                wireframe_renderer::render_wireframe(self.canvas, &area);
            }

            self.canvas.restore();
        }
    }
}