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 dioxus_native_core::prelude::ElementNode;
use dioxus_native_core::real_dom::NodeImmutable;
use dioxus_native_core::NodeId;
use dioxus_native_core::{node::NodeType, tags::TagName};
use freya_dom::prelude::DioxusNode;
use freya_engine::prelude::*;
use freya_node_state::{Style, Transform, ViewportState};
use torin::{
    geometry::Area,
    prelude::{LayoutNode, Torin},
};

use crate::elements::{render_image, render_label, render_paragraph, render_rect, render_svg};

fn clip_viewport(canvas: &Canvas, viewport: &Area) {
    canvas.clip_rect(
        Rect::new(
            viewport.min_x(),
            viewport.min_y(),
            viewport.max_x(),
            viewport.max_y(),
        ),
        ClipOp::Intersect,
        true,
    );
}

/// Render a node into the Skia canvas
#[allow(clippy::too_many_arguments)]
pub fn render_skia(
    canvas: &Canvas,
    layout_node: &LayoutNode,
    dioxus_node: &DioxusNode,
    font_collection: &mut FontCollection,
    font_manager: &FontMgr,
    render_wireframe: bool,
    matrices: &mut Vec<(Matrix, Vec<NodeId>)>,
    opacities: &mut Vec<(f32, Vec<NodeId>)>,
    default_fonts: &[String],
    layout: &Torin<NodeId>,
) {
    let area = layout_node.visible_area();
    let data = &layout_node.data;
    let node_type = &*dioxus_node.node_type();
    if let NodeType::Element(ElementNode { tag, .. }) = node_type {
        canvas.save();

        let node_transform = &*dioxus_node.get::<Transform>().unwrap();
        let node_style = &*dioxus_node.get::<Style>().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,
                }),
            );

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

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

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

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

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

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

        // Clip all elements with their corresponding viewports
        let node_viewports = dioxus_node.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 {
            clip_viewport(canvas, &layout_node.visible_area());
        }

        for viewport_id in &node_viewports.viewports {
            let viewport = layout.get(*viewport_id).unwrap().visible_area();
            clip_viewport(canvas, &viewport);
        }

        match tag {
            TagName::Rect => {
                render_rect(&area, dioxus_node, canvas, font_collection);
            }
            TagName::Label => {
                render_label(&area, data, canvas);
            }
            TagName::Paragraph => {
                render_paragraph(
                    &area,
                    data,
                    dioxus_node,
                    canvas,
                    font_collection,
                    default_fonts,
                );
            }
            TagName::Svg => {
                render_svg(&area, dioxus_node, canvas, font_manager);
            }
            TagName::Image => {
                render_image(&area, dioxus_node, canvas);
            }
            _ => {}
        }

        if render_wireframe {
            crate::wireframe::render_wireframe(canvas, &area);
        }

        canvas.restore();
    }
}