Skip to main content

embedded_gui/context/
render.rs

1use crate::{
2    geometry::Rect,
3    render::RenderCtx,
4    style::{VisualState, lerp_style},
5};
6use embedded_graphics_core::pixelcolor::Rgb565;
7
8use super::*;
9
10impl<'a, const NODES: usize, const EVENTS: usize, const DIRTY: usize>
11    GuiContext<'a, NODES, EVENTS, DIRTY>
12{
13    pub fn render<D>(&self, target: &mut D) -> Result<(), D::Error>
14    where
15        D: embedded_graphics_core::draw_target::DrawTarget<Color = Rgb565>,
16    {
17        let mut ctx = RenderCtx::new(target, self.viewport);
18        ctx.set_quality(self.render_quality);
19        self.render_into(&mut ctx, 0, 0, 255)
20    }
21
22    /// Like [`GuiContext::render`], but every widget's translucency is composited
23    /// with true per-pixel alpha blending instead of ordered dithering. Requires
24    /// a readback-capable target ([`PixelRead`](crate::PixelRead)), e.g. a
25    /// [`Framebuffer`](crate::Framebuffer) used as a software back buffer.
26    pub fn render_composited<D>(&self, target: &mut D) -> Result<(), D::Error>
27    where
28        D: embedded_graphics_core::draw_target::DrawTarget<Color = Rgb565>
29            + crate::render::PixelRead,
30    {
31        let mut ctx = RenderCtx::compositing(target, self.viewport);
32        ctx.set_quality(self.render_quality);
33        self.render_into::<D, crate::render::Blend>(&mut ctx, 0, 0, 255)
34    }
35
36    pub fn render_dirty<D>(&self, target: &mut D) -> Result<(), D::Error>
37    where
38        D: embedded_graphics_core::draw_target::DrawTarget<Color = Rgb565>,
39    {
40        let slice = self.dirty.as_slice();
41        if slice.is_empty() {
42            return Ok(());
43        }
44
45        if slice.len() == 1 {
46            let mut ctx = RenderCtx::with_dirty(target, self.viewport, slice[0]);
47            ctx.set_quality(self.render_quality);
48            return self.render_into(&mut ctx, 0, 0, 255);
49        }
50
51        if let Some(bound) = self.dirty.bounding_rect() {
52            let area_bound = bound.w as u64 * bound.h as u64;
53            let sum_area: u64 = slice.iter().map(|r| r.w as u64 * r.h as u64).sum();
54            // If bounding rect area is close to sum of individual areas (overlap or cluster),
55            // render bounding rect in 1 pass to avoid multiple tree sweeps
56            if area_bound <= sum_area + (sum_area / 2) {
57                let mut ctx = RenderCtx::with_dirty(target, self.viewport, bound);
58                ctx.set_quality(self.render_quality);
59                return self.render_into(&mut ctx, 0, 0, 255);
60            }
61        }
62
63        for dirty in slice {
64            let mut ctx = RenderCtx::with_dirty(target, self.viewport, *dirty);
65            ctx.set_quality(self.render_quality);
66            self.render_into(&mut ctx, 0, 0, 255)?;
67        }
68        Ok(())
69    }
70
71    /// Renders dirty regions to a hardware display controller implementing [`crate::render::WindowedDrawTarget`].
72    /// Sets the physical controller's column/row window bounds (`set_window`) before rendering,
73    /// enabling hardware SPI/DMA transfers exclusively to the dirty sub-window.
74    pub fn render_dirty_windowed<D>(&self, target: &mut D) -> Result<(), D::Error>
75    where
76        D: embedded_graphics_core::draw_target::DrawTarget<Color = Rgb565>
77            + crate::render::WindowedDrawTarget,
78    {
79        let slice = self.dirty.as_slice();
80        if slice.is_empty() {
81            return Ok(());
82        }
83
84        for dirty in slice {
85            let eg_rect = embedded_graphics_core::primitives::Rectangle::new(
86                embedded_graphics_core::geometry::Point::new(dirty.x, dirty.y),
87                embedded_graphics_core::geometry::Size::new(dirty.w, dirty.h),
88            );
89            target.set_window(&eg_rect)?;
90            let mut ctx = RenderCtx::with_dirty(target, self.viewport, *dirty);
91            ctx.set_quality(self.render_quality);
92            self.render_into(&mut ctx, 0, 0, 255)?;
93        }
94        Ok(())
95    }
96
97    pub fn render_with_offset<D>(
98        &self,
99        target: &mut D,
100        offset_x: i32,
101        offset_y: i32,
102    ) -> Result<(), D::Error>
103    where
104        D: embedded_graphics_core::draw_target::DrawTarget<Color = Rgb565>,
105    {
106        self.render_with_offset_and_opacity(target, offset_x, offset_y, 255)
107    }
108
109    pub fn render_with_offset_and_opacity<D>(
110        &self,
111        target: &mut D,
112        offset_x: i32,
113        offset_y: i32,
114        opacity: u8,
115    ) -> Result<(), D::Error>
116    where
117        D: embedded_graphics_core::draw_target::DrawTarget<Color = Rgb565>,
118    {
119        let mut ctx = RenderCtx::new(target, self.viewport);
120        ctx.set_quality(self.render_quality);
121        self.render_into(&mut ctx, offset_x, offset_y, opacity)
122    }
123
124    pub fn render_with_offset_opacity_and_clip<D>(
125        &self,
126        target: &mut D,
127        offset_x: i32,
128        offset_y: i32,
129        opacity: u8,
130        clip: Rect,
131    ) -> Result<(), D::Error>
132    where
133        D: embedded_graphics_core::draw_target::DrawTarget<Color = Rgb565>,
134    {
135        let mut ctx = RenderCtx::new(target, self.viewport);
136        ctx.set_quality(self.render_quality);
137        let old_clip = ctx.clip();
138        ctx.set_clip(old_clip.intersection(clip));
139        self.render_into(&mut ctx, offset_x, offset_y, opacity)
140    }
141
142    pub(crate) fn render_into<D, C>(
143        &self,
144        ctx: &mut RenderCtx<'_, D, C>,
145        offset_x: i32,
146        offset_y: i32,
147        opacity: u8,
148    ) -> Result<(), D::Error>
149    where
150        D: embedded_graphics_core::draw_target::DrawTarget<Color = Rgb565>,
151        C: crate::render::Compositor<D>,
152    {
153        for node in &self.widgets {
154            if !self.effective_visible(node.id) {
155                continue;
156            }
157            let Some(base_rect) = self.absolute_rect(node.id) else {
158                continue;
159            };
160            let rect = Rect::new(
161                base_rect.x + offset_x,
162                base_rect.y + offset_y,
163                base_rect.w,
164                base_rect.h,
165            );
166            let base_clip = self.inherited_clip(node.id).unwrap_or(self.viewport);
167            let clip = Rect::new(
168                base_clip.x + offset_x,
169                base_clip.y + offset_y,
170                base_clip.w,
171                base_clip.h,
172            );
173            if rect.intersection(clip).is_empty() {
174                continue;
175            }
176            let old_clip = ctx.clip();
177            ctx.set_clip(old_clip.intersection(clip));
178            let state = if self.pressed.is_some_and(|pressed| pressed.id == node.id) {
179                VisualState::Pressed
180            } else if Some(node.id) == self.focus {
181                VisualState::Focused
182            } else if !self.effective_enabled(node.id) {
183                VisualState::Disabled
184            } else {
185                VisualState::Normal
186            };
187            let mut render_node = *node;
188            let class_style = node.style_class.and_then(|class| {
189                self.class_styles
190                    .iter()
191                    .find(|(id, _)| *id == class)
192                    .map(|(_, style)| *style)
193            });
194            let resolve_state_style = |vs: VisualState| {
195                class_style
196                    .map(|style| style.resolve(vs))
197                    .unwrap_or_else(|| render_node.style.resolve(vs))
198            };
199            let active_style = if let Some((from, to, t)) = self.state_transition_progress(node.id)
200            {
201                lerp_style(resolve_state_style(from), resolve_state_style(to), t)
202            } else {
203                resolve_state_style(state)
204            };
205            render_node.style = render_node.style.with_state_override(state, active_style);
206            if opacity < 255 {
207                let apply = |v: u8| -> u8 { ((v as u16 * opacity as u16) / 255) as u8 };
208                render_node.style.normal.opacity = apply(render_node.style.normal.opacity);
209                render_node.style.focused.opacity = apply(render_node.style.focused.opacity);
210                render_node.style.pressed.opacity = apply(render_node.style.pressed.opacity);
211                render_node.style.disabled.opacity = apply(render_node.style.disabled.opacity);
212            }
213            render_node.render_at(ctx, rect, state)?;
214            ctx.set_clip(old_clip);
215        }
216        Ok(())
217    }
218}