use embedded_graphics_core::{Pixel, draw_target::DrawTarget, geometry::Point, pixelcolor::Rgb565};
use heapless::Vec;
#[derive(Clone, Copy, Debug)]
pub enum HudElement {
FillRect {
x: i32,
y: i32,
w: u32,
h: u32,
color: Rgb565,
},
ProgressBar {
x: i32,
y: i32,
w: u32,
h: u32,
value: f32,
fg: Rgb565,
bg: Rgb565,
},
Border {
x: i32,
y: i32,
w: u32,
h: u32,
color: Rgb565,
},
}
pub struct HudLayer<const N: usize> {
elements: Vec<HudElement, N>,
}
impl<const N: usize> Default for HudLayer<N> {
fn default() -> Self {
Self::new()
}
}
impl<const N: usize> HudLayer<N> {
pub const fn new() -> Self {
Self {
elements: Vec::new(),
}
}
pub fn clear(&mut self) {
self.elements.clear();
}
pub fn push(&mut self, element: HudElement) -> Result<(), HudElement> {
self.elements.push(element)
}
pub fn draw<D>(&self, target: &mut D)
where
D: DrawTarget<Color = Rgb565>,
{
for elem in &self.elements {
match *elem {
HudElement::FillRect { x, y, w, h, color } => {
let _ = target.draw_iter(
rect_pixels(x, y, w, h).map(|(px, py)| Pixel(Point::new(px, py), color)),
);
}
HudElement::ProgressBar {
x,
y,
w,
h,
value,
fg,
bg,
} => {
let filled = ((w as f32 * value.clamp(0.0, 1.0)) as u32).min(w);
if filled > 0 {
let _ = target.draw_iter(
rect_pixels(x, y, filled, h)
.map(|(px, py)| Pixel(Point::new(px, py), fg)),
);
}
if filled < w {
let _ = target.draw_iter(
rect_pixels(x + filled as i32, y, w - filled, h)
.map(|(px, py)| Pixel(Point::new(px, py), bg)),
);
}
}
HudElement::Border { x, y, w, h, color } => {
let _ = target.draw_iter(
border_pixels(x, y, w, h).map(|(px, py)| Pixel(Point::new(px, py), color)),
);
}
}
}
}
}
fn rect_pixels(x: i32, y: i32, w: u32, h: u32) -> impl Iterator<Item = (i32, i32)> {
(0..h as i32).flat_map(move |dy| (0..w as i32).map(move |dx| (x + dx, y + dy)))
}
fn border_pixels(x: i32, y: i32, w: u32, h: u32) -> impl Iterator<Item = (i32, i32)> {
let wi = w as i32;
let hi = h as i32;
(0..wi)
.map(move |dx| (x + dx, y))
.chain((0..wi).map(move |dx| (x + dx, y + hi - 1)))
.chain((1..hi - 1).map(move |dy| (x, y + dy)))
.chain((1..hi - 1).map(move |dy| (x + wi - 1, y + dy)))
}
#[cfg(test)]
mod tests {
use super::*;
use embedded_graphics_core::pixelcolor::RgbColor;
struct MockTarget {
pixels: heapless::Vec<(i32, i32, Rgb565), 4096>,
}
impl MockTarget {
fn new() -> Self {
Self {
pixels: heapless::Vec::new(),
}
}
}
impl DrawTarget for MockTarget {
type Color = Rgb565;
type Error = core::convert::Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for Pixel(p, c) in pixels {
let _ = self.pixels.push((p.x, p.y, c));
}
Ok(())
}
}
impl embedded_graphics_core::geometry::OriginDimensions for MockTarget {
fn size(&self) -> embedded_graphics_core::geometry::Size {
embedded_graphics_core::geometry::Size::new(320, 240)
}
}
#[test]
fn fill_rect_pixel_count() {
let mut hud = HudLayer::<4>::new();
hud.push(HudElement::FillRect {
x: 0,
y: 0,
w: 3,
h: 2,
color: Rgb565::RED,
})
.unwrap();
let mut target = MockTarget::new();
hud.draw(&mut target);
assert_eq!(target.pixels.len(), 6);
}
#[test]
fn progress_bar_full() {
let mut hud = HudLayer::<4>::new();
hud.push(HudElement::ProgressBar {
x: 0,
y: 0,
w: 10,
h: 2,
value: 1.0,
fg: Rgb565::GREEN,
bg: Rgb565::BLACK,
})
.unwrap();
let mut target = MockTarget::new();
hud.draw(&mut target);
assert_eq!(target.pixels.len(), 20);
assert!(target.pixels.iter().all(|&(_, _, c)| c == Rgb565::GREEN));
}
#[test]
fn progress_bar_empty() {
let mut hud = HudLayer::<4>::new();
hud.push(HudElement::ProgressBar {
x: 0,
y: 0,
w: 10,
h: 2,
value: 0.0,
fg: Rgb565::GREEN,
bg: Rgb565::BLACK,
})
.unwrap();
let mut target = MockTarget::new();
hud.draw(&mut target);
assert_eq!(target.pixels.len(), 20);
assert!(target.pixels.iter().all(|&(_, _, c)| c == Rgb565::BLACK));
}
#[test]
fn border_pixel_count() {
let mut hud = HudLayer::<4>::new();
hud.push(HudElement::Border {
x: 0,
y: 0,
w: 4,
h: 3,
color: Rgb565::WHITE,
})
.unwrap();
let mut target = MockTarget::new();
hud.draw(&mut target);
assert_eq!(target.pixels.len(), 10);
}
#[test]
fn layer_full_returns_err() {
let mut hud = HudLayer::<1>::new();
hud.push(HudElement::FillRect {
x: 0,
y: 0,
w: 1,
h: 1,
color: Rgb565::RED,
})
.unwrap();
let result = hud.push(HudElement::FillRect {
x: 0,
y: 0,
w: 1,
h: 1,
color: Rgb565::RED,
});
assert!(result.is_err());
}
#[test]
fn clear_empties_layer() {
let mut hud = HudLayer::<4>::new();
hud.push(HudElement::FillRect {
x: 0,
y: 0,
w: 2,
h: 2,
color: Rgb565::RED,
})
.unwrap();
hud.clear();
let mut target = MockTarget::new();
hud.draw(&mut target);
assert_eq!(target.pixels.len(), 0);
}
}