Skip to main content

embedded_gui/context/
render.rs

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