pub const ICON_SIZE: u32 = 64;
pub const GRID_COLS: [f32; 3] = [0.555, 0.715, 0.875];
pub const GRID_ROWS: [f32; 3] = [0.255, 0.5, 0.745];
pub const DOT_RADIUS: f32 = 0.082;
pub const FILL_ORDER: [usize; 9] = [6, 7, 8, 3, 4, 5, 0, 1, 2];
pub fn dot_centers() -> [(f32, f32); 9] {
let mut centers = [(0.0, 0.0); 9];
for (row, &cy) in GRID_ROWS.iter().enumerate() {
for (col, &cx) in GRID_COLS.iter().enumerate() {
centers[row * 3 + col] = (cx, cy);
}
}
centers
}
pub fn dots_filled(step: u8) -> usize {
match step {
0 => 0,
n if n >= 8 => 9,
n => n as usize,
}
}
pub fn step_fill_color(step: u8) -> [u8; 4] {
match step {
0 => EMPTY_DOT,
1 | 2 => [0x5b, 0xd1, 0x7a, 0xff], 3 => [0xe8, 0xd4, 0x4a, 0xff], 4 => [0xe8, 0x92, 0x3d, 0xff], 5 | 6 => [0xe0, 0x53, 0x3d, 0xff], 7 => [0x9c, 0x5a, 0x2e, 0xff], _ => [0xc8, 0x36, 0x2a, 0xff], }
}
pub const EMPTY_DOT: [u8; 4] = [0x3a, 0x3a, 0x38, 0xff];
pub const MARK_INK: [u8; 4] = [0xe9, 0xe7, 0xdf, 0xff];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TrayBitmap {
pub rgba: Vec<u8>,
pub width: u32,
pub height: u32,
}
pub fn render_tray(step: u8) -> TrayBitmap {
let size = ICON_SIZE;
let mut rgba = vec![0u8; (size * size * 4) as usize];
let filled = dots_filled(step);
let lit: [bool; 9] = {
let mut lit = [false; 9];
for &idx in FILL_ORDER.iter().take(filled) {
lit[idx] = true;
}
lit
};
let fill_color = step_fill_color(step);
let centers = dot_centers();
for py in 0..size {
for px in 0..size {
let u = (px as f32 + 0.5) / size as f32;
let v = (py as f32 + 0.5) / size as f32;
let mut color = [0u8; 4];
if in_c_mark(u, v) {
color = MARK_INK;
} else if let Some(dot) = dot_at(u, v, ¢ers) {
color = if lit[dot] { fill_color } else { EMPTY_DOT };
}
let offset = ((py * size + px) * 4) as usize;
rgba[offset..offset + 4].copy_from_slice(&color);
}
}
TrayBitmap {
rgba,
width: size,
height: size,
}
}
fn dot_at(u: f32, v: f32, centers: &[(f32, f32); 9]) -> Option<usize> {
centers.iter().position(|&(cx, cy)| {
let (dx, dy) = (u - cx, v - cy);
dx * dx + dy * dy <= DOT_RADIUS * DOT_RADIUS
})
}
fn in_c_mark(u: f32, v: f32) -> bool {
const CX: f32 = 0.235;
const CY: f32 = 0.5;
const OUTER: f32 = 0.205;
const INNER: f32 = 0.105;
let (dx, dy) = (u - CX, v - CY);
let dist_sq = dx * dx + dy * dy;
if !(INNER * INNER..=OUTER * OUTER).contains(&dist_sq) {
return false;
}
!(dx > 0.0 && dy.abs() < dx * 0.78)
}
#[cfg(test)]
mod tests {
use super::*;
fn filled_pixels(bmp: &TrayBitmap, color: [u8; 4]) -> usize {
bmp.rgba.chunks_exact(4).filter(|px| *px == color).count()
}
fn opaque_pixels(bmp: &TrayBitmap) -> usize {
bmp.rgba.chunks_exact(4).filter(|px| px[3] != 0).count()
}
#[test]
fn render_has_correct_dimensions_and_buffer_length() {
let bmp = render_tray(4);
assert_eq!(bmp.width, ICON_SIZE);
assert_eq!(bmp.height, ICON_SIZE);
assert_eq!(bmp.rgba.len(), (ICON_SIZE * ICON_SIZE * 4) as usize);
}
#[test]
fn render_is_deterministic_per_step() {
for step in 0..=10u8 {
assert_eq!(
render_tray(step),
render_tray(step),
"the tray glyph must be byte-identical for a given step"
);
}
}
#[test]
fn dots_filled_covers_the_scale() {
assert_eq!(dots_filled(0), 0);
assert_eq!(dots_filled(1), 1);
assert_eq!(dots_filled(7), 7);
assert_eq!(dots_filled(8), 9, "the top step fills the whole grid");
assert_eq!(dots_filled(99), 9, "saturates, never panics");
}
#[test]
fn fill_order_is_a_permutation_of_the_nine_dots() {
let mut seen = FILL_ORDER;
seen.sort_unstable();
assert_eq!(seen, [0, 1, 2, 3, 4, 5, 6, 7, 8]);
}
#[test]
fn higher_step_lights_at_least_as_many_dots_in_the_ramp() {
for step in 1..8u8 {
assert!(dots_filled(step + 1) >= dots_filled(step));
}
}
#[test]
fn idle_step_zero_lights_no_fill_color() {
let idle = render_tray(0);
assert!(
filled_pixels(&idle, MARK_INK) > 0,
"the C mark is always drawn"
);
assert!(
filled_pixels(&idle, EMPTY_DOT) > 0,
"idle shows the dim grid"
);
assert_eq!(
filled_pixels(&idle, [0x5b, 0xd1, 0x7a, 0xff]),
0,
"idle must never paint a confident (green) fill"
);
}
#[test]
fn full_step_lights_more_than_idle() {
let idle = render_tray(0);
let full = render_tray(8);
assert!(
filled_pixels(&full, EMPTY_DOT) < filled_pixels(&idle, EMPTY_DOT),
"a fuller grid must dim fewer dots than idle"
);
assert!(opaque_pixels(&full) > 0 && opaque_pixels(&idle) > 0);
}
#[test]
fn dot_centers_are_row_major() {
let centers = dot_centers();
assert_eq!(centers[0], (GRID_COLS[0], GRID_ROWS[0]));
assert_eq!(centers[4], (GRID_COLS[1], GRID_ROWS[1]));
assert_eq!(centers[8], (GRID_COLS[2], GRID_ROWS[2]));
}
}