Skip to main content

ferroui_base/rendering/
immediate_renderer.rs

1use super::ZIndexComparer;
2use crate::media::{DrawingContext, PushedState, RenderOptions, TextOptions};
3use crate::{Matrix, Rect, Ref, RoundedRect, Visual};
4
5/// Renders a visual tree directly to a drawing context, without the
6/// compositor. Used for rendering visuals to bitmaps and visual brushes.
7pub struct ImmediateRenderer;
8
9impl ImmediateRenderer {
10    /// Renders a visual to a drawing context.
11    pub fn render(context: &mut DrawingContext<'_>, visual: &Ref<Visual>) {
12        Self::render_clipped(context, visual, Rect::from_size(visual.bounds().size()));
13    }
14
15    /// Renders the part of a visual inside `clip_rect` to a drawing context.
16    pub fn render_clipped(context: &mut DrawingContext<'_>, visual: &Ref<Visual>, clip_rect: Rect) {
17        let transform =
18            context.push_transform(Matrix::create_translation(-clip_rect.position().x, -clip_rect.position().y));
19        let clip = context.push_clip(clip_rect);
20        Self::render_core(
21            context,
22            visual,
23            Rect::from_size(visual.bounds().size()),
24            Matrix::IDENTITY,
25            Rect::from_size(clip_rect.size()),
26        );
27        context.pop(clip);
28        context.pop(transform);
29    }
30
31    fn render_core(
32        context: &mut DrawingContext<'_>,
33        visual: &Ref<Visual>,
34        bounds: Rect,
35        parent_transform: Matrix,
36        mut clip_rect: Rect,
37    ) {
38        let opacity = visual.opacity();
39        // NaN opacity is not rendered either (`Opacity is not > 0`).
40        if !visual.is_visible() || !(opacity > 0.0) {
41            return;
42        }
43
44        let rect = Rect::from_size(bounds.size());
45        let transform = match visual.render_transform().map(|t| t.value()) {
46            Some(rt) => {
47                let origin = visual.render_transform_origin().to_pixels(visual.bounds().size());
48                let offset = Matrix::create_translation(origin.x, origin.y);
49                (-offset) * rt * offset * Matrix::create_translation(bounds.position().x, bounds.position().y)
50            }
51            None => Matrix::create_translation(bounds.position().x, bounds.position().y),
52        };
53
54        let text_options = visual.text_options();
55        let s_text_options = if text_options != TextOptions::default() {
56            context.push_text_options(text_options)
57        } else {
58            PushedState::NONE
59        };
60        let render_options = visual.render_options();
61        let s_render_options = if render_options != RenderOptions::default() {
62            context.push_render_options(render_options)
63        } else {
64            PushedState::NONE
65        };
66        let s_transform = context.push_transform(transform);
67        let s_mirror = if visual.has_mirror_transform() {
68            context.push_transform(Matrix::new(-1.0, 0.0, 0.0, 1.0, visual.bounds().width, 0.0))
69        } else {
70            PushedState::NONE
71        };
72        let s_opacity = context.push_opacity(opacity);
73        let clip_to_bounds = visual.clip_to_bounds();
74        let s_clip = if clip_to_bounds {
75            match visual.clip_to_bounds_radius() {
76                Some(radius) => context.push_rounded_clip(RoundedRect::from_corner_radius(rect, radius)),
77                None => context.push_clip(rect),
78            }
79        } else {
80            PushedState::NONE
81        };
82        let s_geometry_clip = match visual.clip() {
83            Some(clip) => context.push_geometry_clip(&clip),
84            None => PushedState::NONE,
85        };
86        let s_opacity_mask = match visual.opacity_mask() {
87            Some(mask) => context.push_opacity_mask(&mask, rect),
88            None => PushedState::NONE,
89        };
90        let effect = visual.effect();
91        let s_effect = match &effect {
92            Some(effect) => context.push_effect(effect, rect),
93            None => PushedState::NONE,
94        };
95
96        {
97            let mut total_transform = transform * parent_transform;
98            let effect_rect = match &effect {
99                Some(effect) => rect.inflate_thickness(crate::media::EffectExtensions::get_effect_output_padding(Some(&**effect))),
100                None => rect,
101            };
102            let visual_bounds = effect_rect.transform_to_aabb(total_transform);
103
104            if visual_bounds.intersects(clip_rect) {
105                visual.render(context);
106            }
107
108            if clip_to_bounds {
109                total_transform = Matrix::IDENTITY;
110                clip_rect = rect;
111            }
112
113            if let Some(children) = visual.visual_children_snapshot() {
114                if visual.has_non_uniform_z_index_children() {
115                    let mut sorted: Vec<Ref<Visual>> = children.iter().cloned().collect();
116                    // Stable, like `OrderBy`.
117                    sorted.sort_by(ZIndexComparer::compare);
118                    for child in &sorted {
119                        Self::render_core(context, child, child.bounds(), total_transform, clip_rect);
120                    }
121                } else {
122                    for child in children.iter() {
123                        Self::render_core(context, child, child.bounds(), total_transform, clip_rect);
124                    }
125                }
126            }
127        }
128
129        context.pop(s_effect);
130        context.pop(s_opacity_mask);
131        context.pop(s_geometry_clip);
132        context.pop(s_clip);
133        context.pop(s_opacity);
134        context.pop(s_mirror);
135        context.pop(s_transform);
136        context.pop(s_render_options);
137        context.pop(s_text_options);
138    }
139}