Skip to main content

zest_widget/widget/
canvas.rs

1//! Drawable surface: an owned [`CanvasBuffer`] the host mutates plus a
2//! passive [`Canvas`] widget that blits it to the screen.
3//!
4//! Because widgets are rebuilt every frame, the *mutable* pixel state
5//! lives on the host as a [`CanvasBuffer`] — the screen owns it as a
6//! field, mutates it in `update`, and lends it to the widget by reference
7//! in `view`. The [`Canvas`] widget itself is stateless: it borrows
8//! `&'a CanvasBuffer<C>` and draws it via [`Renderer::draw_image`],
9//! mirroring [`Image`](super::image::Image).
10
11use super::Widget;
12use alloc::{vec, vec::Vec};
13use core::marker::PhantomData;
14use embedded_graphics::{pixelcolor::PixelColor, prelude::*, primitives::Rectangle};
15use zest_core::{Constraints, Length, RenderError, Renderer, TouchPhase};
16use zest_theme::Theme;
17
18/// Owned, row-major pixel buffer with simple drawing operations.
19///
20/// The host stores one as a field, mutates it in `update`, and passes
21/// `&buffer` to a [`Canvas`] widget in `view`.
22#[derive(Clone, Debug)]
23pub struct CanvasBuffer<C: PixelColor> {
24    width: u32,
25    height: u32,
26    pixels: Vec<C>,
27}
28
29impl<C: PixelColor> CanvasBuffer<C> {
30    /// Create a `width` x `height` buffer filled with `fill`.
31    #[must_use]
32    pub fn new(width: u32, height: u32, fill: C) -> Self {
33        Self {
34            width,
35            height,
36            pixels: vec![fill; (width as usize) * (height as usize)],
37        }
38    }
39
40    /// Buffer width in pixels.
41    #[must_use]
42    pub fn width(&self) -> u32 {
43        self.width
44    }
45
46    /// Buffer height in pixels.
47    #[must_use]
48    pub fn height(&self) -> u32 {
49        self.height
50    }
51
52    /// Buffer dimensions as a [`Size`].
53    #[must_use]
54    pub fn size(&self) -> Size {
55        Size::new(self.width, self.height)
56    }
57
58    /// Borrow the row-major pixel slice (e.g. for a [`Canvas`] widget).
59    #[must_use]
60    pub fn pixels(&self) -> &[C] {
61        &self.pixels
62    }
63
64    /// Overwrite every pixel with `color`.
65    pub fn clear(&mut self, color: C) {
66        for px in &mut self.pixels {
67            *px = color;
68        }
69    }
70
71    /// Set a single pixel. Out-of-bounds coordinates are ignored.
72    pub fn set_pixel(&mut self, x: i32, y: i32, color: C) {
73        if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
74            return;
75        }
76        let idx = (y as u32 * self.width + x as u32) as usize;
77        self.pixels[idx] = color;
78    }
79
80    /// Fill an axis-aligned rectangle (clipped to the buffer).
81    pub fn fill_rect(&mut self, x: i32, y: i32, w: u32, h: u32, color: C) {
82        let x0 = x.max(0);
83        let y0 = y.max(0);
84        let x1 = (x + w as i32).min(self.width as i32);
85        let y1 = (y + h as i32).min(self.height as i32);
86        let mut py = y0;
87        while py < y1 {
88            let mut px = x0;
89            while px < x1 {
90                let idx = (py as u32 * self.width + px as u32) as usize;
91                self.pixels[idx] = color;
92                px += 1;
93            }
94            py += 1;
95        }
96    }
97
98    /// Draw a line from `(x0, y0)` to `(x1, y1)` using Bresenham's
99    /// algorithm. Trig- and float-free, so it stays `no_std` friendly.
100    pub fn line(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, color: C) {
101        let dx = (x1 - x0).abs();
102        let dy = -(y1 - y0).abs();
103        let sx = if x0 < x1 { 1 } else { -1 };
104        let sy = if y0 < y1 { 1 } else { -1 };
105        let mut err = dx + dy;
106        let mut x = x0;
107        let mut y = y0;
108        loop {
109            self.set_pixel(x, y, color);
110            if x == x1 && y == y1 {
111                break;
112            }
113            let e2 = 2 * err;
114            if e2 >= dy {
115                err += dy;
116                x += sx;
117            }
118            if e2 <= dx {
119                err += dx;
120                y += sy;
121            }
122        }
123    }
124}
125
126/// Passive widget that blits a borrowed [`CanvasBuffer`] to the screen.
127///
128/// Defaults to the buffer's intrinsic size; centered when its arranged
129/// slot is larger, like [`Image`](super::image::Image).
130pub struct Canvas<'a, C: PixelColor, M: Clone> {
131    rect: Rectangle,
132    buffer: &'a CanvasBuffer<C>,
133    width: Length,
134    height: Length,
135    _phantom: PhantomData<M>,
136}
137
138impl<'a, C: PixelColor, M: Clone> Canvas<'a, C, M> {
139    /// Create a canvas widget that displays `buffer`. Position is
140    /// assigned by the parent via `arrange`.
141    pub fn new(buffer: &'a CanvasBuffer<C>) -> Self {
142        Self {
143            rect: Rectangle::zero(),
144            buffer,
145            width: Length::Shrink,
146            height: Length::Shrink,
147            _phantom: PhantomData,
148        }
149    }
150
151    /// Width sizing intent.
152    #[must_use]
153    pub fn width(mut self, width: impl Into<Length>) -> Self {
154        self.width = width.into();
155        self
156    }
157
158    /// Height sizing intent.
159    #[must_use]
160    pub fn height(mut self, height: impl Into<Length>) -> Self {
161        self.height = height.into();
162        self
163    }
164}
165
166impl<'a, C: PixelColor, M: Clone> Widget<C, M> for Canvas<'a, C, M> {
167    fn measure(&mut self, constraints: Constraints) -> Size {
168        let size = self.buffer.size();
169        let w = self.width.resolve(size.width, constraints.max.width);
170        let h = self.height.resolve(size.height, constraints.max.height);
171        constraints.clamp(Size::new(w, h))
172    }
173
174    fn preferred_size(&self) -> (Length, Length) {
175        (self.width, self.height)
176    }
177
178    fn arrange(&mut self, rect: Rectangle) {
179        self.rect = rect;
180    }
181
182    fn rect(&self) -> Rectangle {
183        self.rect
184    }
185
186    fn handle_touch(&mut self, _point: Point, _phase: TouchPhase) -> Option<M> {
187        None
188    }
189
190    fn draw<'t>(
191        &self,
192        renderer: &mut dyn Renderer<C>,
193        _theme: &Theme<'t, C>,
194    ) -> Result<(), RenderError> {
195        let size = self.buffer.size();
196        let dx = (self.rect.size.width as i32 - size.width as i32).max(0) / 2;
197        let dy = (self.rect.size.height as i32 - size.height as i32).max(0) / 2;
198        let origin = self.rect.top_left + Point::new(dx, dy);
199        renderer.draw_image(origin, size, self.buffer.pixels())
200    }
201}