1use embedded_graphics_core::{
2 Pixel,
3 draw_target::DrawTarget,
4 geometry::{OriginDimensions, Point, Size},
5 pixelcolor::Rgb565,
6};
7use heapless::Vec;
8
9pub struct FramebufDrawTarget<'a> {
27 pixels: &'a mut [Rgb565],
28 width: usize,
29 height: usize,
30}
31
32impl<'a> FramebufDrawTarget<'a> {
33 #[inline]
38 pub fn new(pixels: &'a mut [Rgb565], width: usize, height: usize) -> Self {
39 Self {
40 pixels,
41 width,
42 height,
43 }
44 }
45}
46
47impl OriginDimensions for FramebufDrawTarget<'_> {
48 fn size(&self) -> Size {
49 Size::new(self.width as u32, self.height as u32)
50 }
51}
52
53impl DrawTarget for FramebufDrawTarget<'_> {
54 type Color = Rgb565;
55 type Error = core::convert::Infallible;
56
57 #[inline]
58 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
59 where
60 I: IntoIterator<Item = Pixel<Self::Color>>,
61 {
62 for Pixel(coord, color) in pixels {
63 if coord.x >= 0
64 && coord.y >= 0
65 && (coord.x as usize) < self.width
66 && (coord.y as usize) < self.height
67 {
68 let idx = coord.y as usize * self.width + coord.x as usize;
69 if idx < self.pixels.len() {
70 self.pixels[idx] = color;
71 }
72 }
73 }
74 Ok(())
75 }
76}
77
78pub fn format_u16_dec(mut value: u16, buf: &mut [u8], digits: usize) -> &str {
94 let start = buf.len().saturating_sub(digits);
95 for i in (start..buf.len()).rev() {
96 buf[i] = b'0' + (value % 10) as u8;
97 value /= 10;
98 }
99 core::str::from_utf8(&buf[start..]).unwrap_or("")
100}
101
102#[derive(Clone, Copy, Debug)]
104pub enum HudElement {
105 FillRect {
107 x: i32,
108 y: i32,
109 w: u32,
110 h: u32,
111 color: Rgb565,
112 },
113 ProgressBar {
115 x: i32,
116 y: i32,
117 w: u32,
118 h: u32,
119 value: f32,
120 fg: Rgb565,
121 bg: Rgb565,
122 },
123 Border {
125 x: i32,
126 y: i32,
127 w: u32,
128 h: u32,
129 color: Rgb565,
130 },
131 TranslucentRect {
133 x: i32,
134 y: i32,
135 w: u32,
136 h: u32,
137 color: Rgb565,
138 alpha: u8,
139 },
140 TranslucentBorder {
142 x: i32,
143 y: i32,
144 w: u32,
145 h: u32,
146 color: Rgb565,
147 alpha: u8,
148 },
149}
150
151pub struct HudLayer<const N: usize> {
164 elements: Vec<HudElement, N>,
165}
166
167impl<const N: usize> Default for HudLayer<N> {
168 fn default() -> Self {
169 Self::new()
170 }
171}
172
173impl<const N: usize> HudLayer<N> {
174 pub const fn new() -> Self {
175 Self {
176 elements: Vec::new(),
177 }
178 }
179
180 pub fn clear(&mut self) {
182 self.elements.clear();
183 }
184
185 pub fn push(&mut self, element: HudElement) -> Result<(), HudElement> {
187 self.elements.push(element)
188 }
189
190 pub fn draw<D>(&self, target: &mut D)
192 where
193 D: DrawTarget<Color = Rgb565>,
194 {
195 for elem in &self.elements {
196 match *elem {
197 HudElement::FillRect { x, y, w, h, color } => {
198 let _ = target.draw_iter(
199 rect_pixels(x, y, w, h).map(|(px, py)| Pixel(Point::new(px, py), color)),
200 );
201 }
202 HudElement::TranslucentRect {
203 x,
204 y,
205 w,
206 h,
207 color,
208 alpha: _,
209 } => {
210 let _ = target.draw_iter(
211 rect_pixels(x, y, w, h).map(|(px, py)| Pixel(Point::new(px, py), color)),
212 );
213 }
214 HudElement::ProgressBar {
215 x,
216 y,
217 w,
218 h,
219 value,
220 fg,
221 bg,
222 } => {
223 let filled = ((w as f32 * value.clamp(0.0, 1.0)) as u32).min(w);
224 if filled > 0 {
225 let _ = target.draw_iter(
226 rect_pixels(x, y, filled, h)
227 .map(|(px, py)| Pixel(Point::new(px, py), fg)),
228 );
229 }
230 if filled < w {
231 let _ = target.draw_iter(
232 rect_pixels(x + filled as i32, y, w - filled, h)
233 .map(|(px, py)| Pixel(Point::new(px, py), bg)),
234 );
235 }
236 }
237 HudElement::Border { x, y, w, h, color }
238 | HudElement::TranslucentBorder {
239 x,
240 y,
241 w,
242 h,
243 color,
244 alpha: _,
245 } => {
246 let _ = target.draw_iter(
247 border_pixels(x, y, w, h).map(|(px, py)| Pixel(Point::new(px, py), color)),
248 );
249 }
250 }
251 }
252 }
253
254 #[cfg(feature = "aa")]
256 pub fn draw_blended<D>(&self, target: &mut D)
257 where
258 D: DrawTarget<Color = Rgb565> + crate::draw::ReadPixel,
259 {
260 for elem in &self.elements {
261 match *elem {
262 HudElement::TranslucentRect {
263 x,
264 y,
265 w,
266 h,
267 color,
268 alpha,
269 } => {
270 for (px, py) in rect_pixels(x, y, w, h) {
271 let pt = Point::new(px, py);
272 let bg = target.read_pixel(pt);
273 let blended = crate::draw::fast_blend_rgb565(bg, color, alpha);
274 let _ = target.draw_iter([Pixel(pt, blended)]);
275 }
276 }
277 HudElement::TranslucentBorder {
278 x,
279 y,
280 w,
281 h,
282 color,
283 alpha,
284 } => {
285 for (px, py) in border_pixels(x, y, w, h) {
286 let pt = Point::new(px, py);
287 let bg = target.read_pixel(pt);
288 let blended = crate::draw::fast_blend_rgb565(bg, color, alpha);
289 let _ = target.draw_iter([Pixel(pt, blended)]);
290 }
291 }
292 _ => {
293 self.draw(target);
294 }
295 }
296 }
297 }
298}
299
300fn rect_pixels(x: i32, y: i32, w: u32, h: u32) -> impl Iterator<Item = (i32, i32)> {
301 (0..h as i32).flat_map(move |dy| (0..w as i32).map(move |dx| (x + dx, y + dy)))
302}
303
304fn border_pixels(x: i32, y: i32, w: u32, h: u32) -> impl Iterator<Item = (i32, i32)> {
305 let wi = w as i32;
306 let hi = h as i32;
307 (0..wi)
308 .map(move |dx| (x + dx, y))
309 .chain((0..wi).map(move |dx| (x + dx, y + hi - 1)))
310 .chain((1..hi - 1).map(move |dy| (x, y + dy)))
311 .chain((1..hi - 1).map(move |dy| (x + wi - 1, y + dy)))
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317 use embedded_graphics_core::pixelcolor::RgbColor;
318
319 struct MockTarget {
320 pixels: heapless::Vec<(i32, i32, Rgb565), 4096>,
321 }
322
323 impl MockTarget {
324 fn new() -> Self {
325 Self {
326 pixels: heapless::Vec::new(),
327 }
328 }
329 }
330
331 impl DrawTarget for MockTarget {
332 type Color = Rgb565;
333 type Error = core::convert::Infallible;
334
335 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
336 where
337 I: IntoIterator<Item = Pixel<Self::Color>>,
338 {
339 for Pixel(p, c) in pixels {
340 let _ = self.pixels.push((p.x, p.y, c));
341 }
342 Ok(())
343 }
344 }
345
346 impl embedded_graphics_core::geometry::OriginDimensions for MockTarget {
347 fn size(&self) -> embedded_graphics_core::geometry::Size {
348 embedded_graphics_core::geometry::Size::new(320, 240)
349 }
350 }
351
352 #[test]
353 fn fill_rect_pixel_count() {
354 let mut hud = HudLayer::<4>::new();
355 hud.push(HudElement::FillRect {
356 x: 0,
357 y: 0,
358 w: 3,
359 h: 2,
360 color: Rgb565::RED,
361 })
362 .unwrap();
363 let mut target = MockTarget::new();
364 hud.draw(&mut target);
365 assert_eq!(target.pixels.len(), 6);
366 }
367
368 #[test]
369 fn progress_bar_full() {
370 let mut hud = HudLayer::<4>::new();
371 hud.push(HudElement::ProgressBar {
372 x: 0,
373 y: 0,
374 w: 10,
375 h: 2,
376 value: 1.0,
377 fg: Rgb565::GREEN,
378 bg: Rgb565::BLACK,
379 })
380 .unwrap();
381 let mut target = MockTarget::new();
382 hud.draw(&mut target);
383 assert_eq!(target.pixels.len(), 20);
384 assert!(target.pixels.iter().all(|&(_, _, c)| c == Rgb565::GREEN));
385 }
386
387 #[test]
388 fn progress_bar_empty() {
389 let mut hud = HudLayer::<4>::new();
390 hud.push(HudElement::ProgressBar {
391 x: 0,
392 y: 0,
393 w: 10,
394 h: 2,
395 value: 0.0,
396 fg: Rgb565::GREEN,
397 bg: Rgb565::BLACK,
398 })
399 .unwrap();
400 let mut target = MockTarget::new();
401 hud.draw(&mut target);
402 assert_eq!(target.pixels.len(), 20);
403 assert!(target.pixels.iter().all(|&(_, _, c)| c == Rgb565::BLACK));
404 }
405
406 #[test]
407 fn border_pixel_count() {
408 let mut hud = HudLayer::<4>::new();
409 hud.push(HudElement::Border {
410 x: 0,
411 y: 0,
412 w: 4,
413 h: 3,
414 color: Rgb565::WHITE,
415 })
416 .unwrap();
417 let mut target = MockTarget::new();
418 hud.draw(&mut target);
419 assert_eq!(target.pixels.len(), 10);
421 }
422
423 #[test]
424 fn layer_full_returns_err() {
425 let mut hud = HudLayer::<1>::new();
426 hud.push(HudElement::FillRect {
427 x: 0,
428 y: 0,
429 w: 1,
430 h: 1,
431 color: Rgb565::RED,
432 })
433 .unwrap();
434 let result = hud.push(HudElement::FillRect {
435 x: 0,
436 y: 0,
437 w: 1,
438 h: 1,
439 color: Rgb565::RED,
440 });
441 assert!(result.is_err());
442 }
443
444 #[test]
445 fn clear_empties_layer() {
446 let mut hud = HudLayer::<4>::new();
447 hud.push(HudElement::FillRect {
448 x: 0,
449 y: 0,
450 w: 2,
451 h: 2,
452 color: Rgb565::RED,
453 })
454 .unwrap();
455 hud.clear();
456 let mut target = MockTarget::new();
457 hud.draw(&mut target);
458 assert_eq!(target.pixels.len(), 0);
459 }
460}