freya_core/
render_pipeline.rs

1use freya_engine::prelude::{
2    Canvas,
3    ClipOp,
4    FontCollection,
5    FontMgr,
6    SaveLayerRec,
7    SkMatrix,
8    SkPoint,
9    blur,
10};
11
12use crate::{
13    element::{
14        ClipContext,
15        RenderContext,
16    },
17    prelude::Color,
18    tree::Tree,
19};
20
21pub struct RenderPipeline<'a> {
22    pub font_collection: &'a mut FontCollection,
23    pub font_manager: &'a FontMgr,
24    pub canvas: &'a Canvas,
25    pub tree: &'a Tree,
26    pub scale_factor: f64,
27    pub background: Color,
28}
29
30impl RenderPipeline<'_> {
31    #[cfg_attr(feature = "hotpath", hotpath::measure)]
32    pub fn render(self) {
33        self.canvas.clear(self.background);
34
35        // TODO: Use incremental rendering
36        for i16 in itertools::sorted(self.tree.layers.keys()) {
37            let nodes = self.tree.layers.get(i16).unwrap();
38            'rendering: for node_id in nodes {
39                let layer = self.canvas.save();
40
41                let element = self.tree.elements.get(node_id).unwrap();
42                let text_style_state = self.tree.text_style_state.get(node_id).unwrap();
43                let layout_node = self.tree.layout.get(node_id).unwrap();
44                let effect_state = self.tree.effect_state.get(node_id);
45
46                if let Some(effect_state) = effect_state {
47                    let mut visible_area = layout_node.visible_area();
48
49                    // Transform the element area given the scale effects
50                    for id in effect_state.scales.iter() {
51                        let layout_node = self.tree.layout.get(id).unwrap();
52                        let effect = self.tree.effect_state.get(id).unwrap();
53                        let area = layout_node.visible_area();
54                        let center = area.center();
55                        let scale = effect.scale.unwrap();
56
57                        visible_area = visible_area.translate(-center.to_vector());
58                        visible_area = visible_area.scale(scale.x, scale.y);
59                        visible_area = visible_area.translate(center.to_vector());
60                    }
61
62                    hotpath::measure_block!("Element Clipping", {
63                        for clip_node_id in effect_state.clips.iter() {
64                            let clip_element = self.tree.elements.get(clip_node_id).unwrap();
65                            let clip_layout_node = self.tree.layout.get(clip_node_id).unwrap();
66                            let clip_effect = self.tree.effect_state.get(clip_node_id).unwrap();
67
68                            let mut transformed_clip_area = clip_layout_node.visible_area();
69
70                            // For every clip area that his element gets we also need to apply the effects to each one so that
71                            // we can properly assume whether this element is actually visible or not
72                            for id in clip_effect.scales.iter() {
73                                let scale_layout_node = self.tree.layout.get(id).unwrap();
74                                let scale_effect = self.tree.effect_state.get(id).unwrap();
75                                let area = scale_layout_node.visible_area();
76                                let center = area.center();
77                                let scale = scale_effect.scale.unwrap();
78
79                                transformed_clip_area =
80                                    transformed_clip_area.translate(-center.to_vector());
81                                transformed_clip_area =
82                                    transformed_clip_area.scale(scale.x, scale.y);
83                                transformed_clip_area =
84                                    transformed_clip_area.translate(center.to_vector());
85                            }
86
87                            // No need to render this element as it is completely clipped
88                            if !visible_area.intersects(&transformed_clip_area) {
89                                self.canvas.restore_to_count(layer);
90                                continue 'rendering;
91                            }
92
93                            let clip_context = ClipContext {
94                                canvas: self.canvas,
95                                visible_area: &transformed_clip_area,
96                                scale_factor: self.scale_factor,
97                            };
98
99                            clip_element.clip(clip_context);
100                        }
101                    });
102
103                    // Pass rotate effect to children
104                    for id in effect_state.rotations.iter() {
105                        let layout_node = self.tree.layout.get(id).unwrap();
106                        let effect = self.tree.effect_state.get(id).unwrap();
107                        let area = layout_node.visible_area();
108                        let mut matrix = SkMatrix::new_identity();
109                        matrix.set_rotate(
110                            effect.rotation.unwrap(),
111                            Some(SkPoint {
112                                x: area.min_x() + area.width() / 2.0,
113                                y: area.min_y() + area.height() / 2.0,
114                            }),
115                        );
116                        self.canvas.concat(&matrix);
117                    }
118
119                    let render_rect = element.render_rect(&visible_area, self.scale_factor as f32);
120
121                    // Apply inherited opacity effects
122                    for opacity in effect_state.opacities.iter() {
123                        self.canvas
124                            .save_layer_alpha_f(*render_rect.rect(), *opacity);
125                    }
126
127                    // Transform the canvas area given the scale effects
128                    for id in effect_state.scales.iter() {
129                        let layout_node = self.tree.layout.get(id).unwrap();
130                        let effect = self.tree.effect_state.get(id).unwrap();
131                        let area = layout_node.visible_area();
132                        let center = area.center();
133                        let scale = effect.scale.unwrap();
134
135                        self.canvas.translate((center.x, center.y));
136                        self.canvas.scale((scale.x, scale.y));
137                        self.canvas.translate((-center.x, -center.y));
138                    }
139                }
140
141                let render_context = RenderContext {
142                    font_collection: self.font_collection,
143                    canvas: self.canvas,
144                    layout_node,
145                    tree: self.tree,
146                    text_style_state,
147                    scale_factor: self.scale_factor,
148                };
149
150                hotpath::measure_block!("Element Render", {
151                    element.render(render_context);
152                });
153
154                if let Some(effect_state) = effect_state {
155                    let visible_area = layout_node.visible_area();
156                    let render_rect = element.render_rect(&visible_area, self.scale_factor as f32);
157                    // Apply blur effect
158                    if let Some(blur_radius) = effect_state.blur {
159                        let style = element.style();
160
161                        let image_filter = blur(
162                            (
163                                blur_radius * self.scale_factor as f32,
164                                blur_radius * self.scale_factor as f32,
165                            ),
166                            None,
167                            None,
168                            render_rect.rect(),
169                        );
170                        if let Some(image_filter) = image_filter {
171                            let rec = SaveLayerRec::default()
172                                .bounds(render_rect.rect())
173                                .backdrop(&image_filter);
174                            if style.corner_radius.is_round() {
175                                self.canvas.clip_rrect(render_rect, ClipOp::Intersect, true);
176                                self.canvas.save_layer(&rec);
177                            } else {
178                                self.canvas.save_layer(&rec);
179                            }
180                        }
181                    }
182                }
183
184                self.canvas.restore_to_count(layer);
185            }
186        }
187    }
188}