From 2accc78948fa4a18e37ab0bc405f9b2758acaa3e Mon Sep 17 00:00:00 2001 From: Ben Bridle Date: Thu, 3 Jul 2025 15:26:07 +1200 Subject: Initial commit --- src/bin/br/config.rs | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 src/bin/br/config.rs (limited to 'src/bin/br/config.rs') diff --git a/src/bin/br/config.rs b/src/bin/br/config.rs new file mode 100644 index 0000000..56d5190 --- /dev/null +++ b/src/bin/br/config.rs @@ -0,0 +1,122 @@ +use crate::*; + + +#[derive(Copy, Clone, PartialEq)] +pub enum Mode { + Graphical, + Headless, + Dynamic, +} + +impl Mode { + pub fn from_str(string: &str) -> Self { + match string { + "g" | "graphical" => Self::Graphical, + "h" | "headless" => Self::Headless, + "d" | "dynamic" => Self::Dynamic, + _ => fatal!("Invalid mode string '{string}'"), + } + } +} + + +pub fn parse_dimensions(string: &str) -> ScreenDimensions { + fn parse_inner(string: &str) -> Option { + let (w_str, h_str) = string.trim().split_once('x')?; + Some( ScreenDimensions { + width: w_str.parse().ok()?, + height: h_str.parse().ok()?, + } ) + } + let dimensions = parse_inner(string).unwrap_or_else(|| { + fatal!("Invalid dimensions string '{string}'"); + }); + if dimensions.is_zero() { + fatal!("Screen dimensions must be greater than zero"); + } + return dimensions; +} + + +pub fn parse_palette(string: &str) -> [Colour; 16] { + fn decode_ascii_hex_digit(ascii: u8) -> Option { + match ascii { + b'0'..=b'9' => Some(ascii - b'0'), + b'a'..=b'f' => Some(ascii - b'a' + 10), + b'A'..=b'F' => Some(ascii - b'A' + 10), + _ => { None } + } + } + fn parse_inner(string: &str) -> Option<[Colour; 16]> { + let mut c = Vec::new(); + for token in string.split(',') { + let mut bytes = token.bytes(); + if bytes.len() != 3 { return None; } + let r = decode_ascii_hex_digit(bytes.next().unwrap())?; + let g = decode_ascii_hex_digit(bytes.next().unwrap())?; + let b = decode_ascii_hex_digit(bytes.next().unwrap())?; + c.push(Colour::from_rgb(r*17, g*17, b*17)); + } + Some(match c.len() { + 2 => [ c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1], + c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1] ], + 3 => [ c[0], c[1], c[0], c[2], c[0], c[1], c[0], c[2], + c[0], c[1], c[0], c[2], c[0], c[1], c[0], c[2] ], + 4 => [ c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3], + c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3] ], + 8 => [ c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], + c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7] ], + 16 => [ c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], + c[8], c[9], c[10], c[11], c[12], c[13], c[14], c[15] ], + _ => return None, + }) + } + parse_inner(string).unwrap_or_else(|| { + fatal!("Invalid palette string '{string}'"); + }) +} + + +pub fn parse_metadata_colour(colour: Option) -> Option { + let c = colour?; + Some(Colour::from_rgb(c.red, c.green, c.blue)) +} + + +pub fn parse_small_icon(bytes: Option>, bg: Colour, fg: Colour) -> Option { + let rgba = sprite_data_to_rgb(&bytes?, 3, bg, fg); + match Icon::from_rgba(rgba, 24, 24) { + Ok(icon) => Some(icon), + Err(err) => unreachable!("Error while parsing small icon data: {err}"), + } +} + +pub fn parse_large_icon(bytes: Option>, bg: Colour, fg: Colour) -> Option { + let rgba = sprite_data_to_rgb(&bytes?, 8, bg, fg); + match Icon::from_rgba(rgba, 64, 64) { + Ok(icon) => Some(icon), + Err(err) => unreachable!("Error while parsing large icon data: {err}"), + } +} + +fn sprite_data_to_rgb(bytes: &[u8], size: usize, bg: Colour, fg: Colour) -> Vec { + let sprites: Vec<&[u8]> = bytes.chunks_exact(8).collect(); + let mut rgba = Vec::new(); + for sprite_row in 0..size { + for pixel_row in 0..8 { + for sprite_column in 0..size { + let sprite = &sprites[sprite_column + (sprite_row * size)]; + let row = &sprite[pixel_row]; + for bit in 0..8 { + let state = row & (0x80 >> bit); + let colour = match state != 0 { + true => fg, + false => bg, + }; + rgba.extend_from_slice(&colour.as_rgba_array()); + } + } + } + } + return rgba; +} -- cgit v1.2.3-70-g09d2