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() || visual.is_render_suppressed() || !(opacity > 0.0) {
41            return;
42        }
43
44        let rect = Rect::from_size(bounds.size());
45        let mut 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        // Not upstream: the zoom of a scroll presenter, before the position.
54        if let Some(manipulation) = visual.manipulation_transform() {
55            let position = Matrix::create_translation(bounds.position().x, bounds.position().y);
56            transform = transform * (-position) * manipulation * position;
57        }
58
59        let text_options = visual.text_options();
60        let s_text_options = if text_options != TextOptions::default() {
61            context.push_text_options(text_options)
62        } else {
63            PushedState::NONE
64        };
65        let render_options = visual.render_options();
66        let s_render_options = if render_options != RenderOptions::default() {
67            context.push_render_options(render_options)
68        } else {
69            PushedState::NONE
70        };
71        let s_transform = context.push_transform(transform);
72        let s_mirror = if visual.has_mirror_transform() {
73            context.push_transform(Matrix::new(-1.0, 0.0, 0.0, 1.0, visual.bounds().width, 0.0))
74        } else {
75            PushedState::NONE
76        };
77        let s_opacity = context.push_opacity(opacity);
78        let clip_to_bounds = visual.clip_to_bounds();
79        let s_clip = if clip_to_bounds {
80            match visual.clip_to_bounds_radius() {
81                Some(radius) => context.push_rounded_clip(RoundedRect::from_corner_radius(rect, radius)),
82                None => context.push_clip(rect),
83            }
84        } else {
85            PushedState::NONE
86        };
87        let s_geometry_clip = match visual.clip() {
88            Some(clip) => context.push_geometry_clip(&clip),
89            None => PushedState::NONE,
90        };
91        let s_opacity_mask = match visual.opacity_mask() {
92            Some(mask) => context.push_opacity_mask(&mask, rect),
93            None => PushedState::NONE,
94        };
95        let effect = visual.effect();
96        let s_effect = match &effect {
97            Some(effect) => context.push_effect(effect, rect),
98            None => PushedState::NONE,
99        };
100
101        {
102            let mut total_transform = transform * parent_transform;
103            let effect_rect = match &effect {
104                Some(effect) => rect.inflate_thickness(crate::media::EffectExtensions::get_effect_output_padding(Some(&**effect))),
105                None => rect,
106            };
107            let visual_bounds = effect_rect.transform_to_aabb(total_transform);
108
109            if visual_bounds.intersects(clip_rect) {
110                visual.render(context);
111            }
112
113            if clip_to_bounds {
114                total_transform = Matrix::IDENTITY;
115                clip_rect = rect;
116            }
117
118            if let Some(children) = visual.visual_children_snapshot() {
119                if visual.has_non_uniform_z_index_children() {
120                    let mut sorted: Vec<Ref<Visual>> = children.iter().cloned().collect();
121                    // Stable, like `OrderBy`.
122                    sorted.sort_by(ZIndexComparer::compare);
123                    for child in &sorted {
124                        Self::render_core(context, child, child.bounds(), total_transform, clip_rect);
125                    }
126                } else {
127                    for child in children.iter() {
128                        Self::render_core(context, child, child.bounds(), total_transform, clip_rect);
129                    }
130                }
131            }
132        }
133
134        context.pop(s_effect);
135        context.pop(s_opacity_mask);
136        context.pop(s_geometry_clip);
137        context.pop(s_clip);
138        context.pop(s_opacity);
139        context.pop(s_mirror);
140        context.pop(s_transform);
141        context.pop(s_render_options);
142        context.pop(s_text_options);
143    }
144}