iris_ui/
util.rs

1use crate::geom::{Bounds, Size};
2use embedded_graphics::geometry::Size as ESize;
3use embedded_graphics::mono_font::MonoFont;
4use embedded_graphics::pixelcolor::Rgb565;
5use embedded_graphics::primitives::Rectangle;
6
7pub fn calc_bounds(bounds: Bounds, font: MonoFont<'static>, title: &str) -> Bounds {
8    let fsize = &font.character_size;
9    let hpad = fsize.width;
10    let vpad = fsize.height / 2;
11    let mut width = fsize.width * title.len() as u32;
12    width += hpad * 2;
13    let mut height = fsize.height;
14    height += vpad * 2;
15    Bounds::new(bounds.x(), bounds.y(), width as i32, height as i32)
16}
17
18pub fn calc_size(font: MonoFont<'static>, title: &str) -> Size {
19    let fsize = &font.character_size;
20    let hpad = fsize.width;
21    let vpad = fsize.height / 2;
22    let mut width = fsize.width * title.len() as u32;
23    width += hpad * 2;
24    let mut height = fsize.height;
25    height += vpad * 2;
26    Size {
27        w: width as i32,
28        h: height as i32,
29    }
30}
31
32pub fn bounds_to_rect(bounds: &Bounds) -> Rectangle {
33    if bounds.is_empty() {
34        return Rectangle::zero();
35    }
36    Rectangle::new(
37        embedded_graphics::geometry::Point::new(bounds.position.x, bounds.position.y),
38        ESize::new(bounds.size.w as u32, bounds.size.h as u32),
39    )
40}
41
42/// Convert a hex character (0-9, A-F, a-f) to a number, compile-time safe
43const fn hex_char_to_digit(c: u8) -> u8 {
44    if c >= b'0' && c <= b'9' {
45        c - b'0'
46    } else if c >= b'a' && c <= b'f' {
47        c - b'a' + 10
48    } else if c >= b'A' && c <= b'F' {
49        c - b'A' + 10
50    } else {
51        // Invalid hex character triggers a compile-time error
52        panic!("Invalid hex character in hex string. Use 0-9, a-f, or A-F.");
53    }
54}
55
56/// Parse two hex characters into a u8
57const fn parse_hex_byte(s: &[u8], i: usize) -> u8 {
58    (hex_char_to_digit(s[i]) << 4) | hex_char_to_digit(s[i + 1])
59}
60
61/// Convert a hex string "#RRGGBB" or "RRGGBB" to Rgb565
62pub const fn hex_str_to_rgb565(s: &str) -> Rgb565 {
63    let bytes = s.as_bytes();
64    let start = if bytes.len() == 7 && bytes[0] == b'#' {
65        1
66    } else if bytes.len() == 6 {
67        0
68    } else {
69        panic!("Hex string must be in the format \"#RRGGBB\" or \"RRGGBB\"");
70    };
71
72    // parse 0..255 values as u16 so multiplication won't overflow in const
73    let r = parse_hex_byte(bytes, start) as u16;
74    let g = parse_hex_byte(bytes, start + 2) as u16;
75    let b = parse_hex_byte(bytes, start + 4) as u16;
76
77    // scale 8-bit -> 5/6/5 with rounding:
78    // (value * max + 127) / 255  — a common integer rounding formula.
79    let r5 = ((r * 31 + 127) / 255) as u8;
80    let g6 = ((g * 63 + 127) / 255) as u8;
81    let b5 = ((b * 31 + 127) / 255) as u8;
82
83    // Pass the scaled channels into Rgb565::new (takes r,g,b as u8).
84    Rgb565::new(r5, g6, b5)
85}