use egui::{Color32, Image, ImageSource, Response, Sense, TextureOptions, Ui, Widget}; pub const GENERIC_PALETTE: [u8; 4] = [0, 64, 128, 255]; pub fn shade(brt: u8, color: Color32) -> Color32 { color.gamma_multiply(brt as f32 / 255.0) } pub fn generic_palette(color: Color32) -> [Color32; 4] { GENERIC_PALETTE.map(|brt| shade(brt, color)) } pub const fn parse_palette(palette: u8) -> [u8; 4] { [ 0, (palette >> 2) & 0x03, (palette >> 4) & 0x03, (palette >> 6) & 0x03, ] } pub const fn parse_shades(brts: &[u8; 8]) -> [u8; 4] { [ 0, brts[0], brts[2], brts[0].saturating_add(brts[2]).saturating_add(brts[4]), ] } pub fn parse_brts(brts: &[u16; 3], color: Color32) -> [Color32; 4] { [ Color32::BLACK, shade(brts[0] as u8, color), shade(brts[1] as u8, color), shade((brts[0] + brts[1] + brts[2]) as u8, color), ] } pub fn palette_colors(palette: u8, brts: &[u8; 8], color: Color32) -> [Color32; 4] { let colors = parse_shades(brts).map(|s| shade(s, color)); parse_palette(palette).map(|p| colors[p as usize]) } pub struct Object { pub x: i16, pub lon: bool, pub ron: bool, pub parallax: i16, pub y: i16, pub data: CellData, } impl Object { pub fn parse(object: [u16; 4]) -> Self { let x = ((object[0] & 0x03ff) << 6 >> 6) as i16; let parallax = ((object[1] & 0x03ff) << 6 >> 6) as i16; let lon = object[1] & 0x8000 != 0; let ron = object[1] & 0x4000 != 0; let y = (object[2] & 0x00ff) as i16; // Y is stored as the bottom 8 bits of an i16, // so only sign extend if it's out of range. let y = if y > 224 { y << 8 >> 8 } else { y }; let data = CellData::parse(object[3]); Self { x, lon, ron, parallax, y, data, } } pub fn update(&self, source: &mut [u16; 4]) -> bool { let mut changed = false; let new_x = (self.x as u16 & 0x03ff) | (source[0] & 0xfc00); changed |= source[0] != new_x; source[0] = new_x; let new_p = if self.lon { 0x8000 } else { 0x0000 } | if self.ron { 0x4000 } else { 0x0000 } | (self.parallax as u16 & 0x3ff) | (source[1] & 0x3c00); changed |= source[1] != new_p; source[1] = new_p; let new_y = (self.y as u16 & 0x00ff) | (source[2] & 0xff00); changed |= source[2] != new_y; source[2] = new_y; if self.data.update(&mut source[3]) { changed = true; } changed } } pub struct CellData { pub palette_index: usize, pub hflip: bool, pub vflip: bool, pub char_index: usize, } impl CellData { pub fn parse(cell: u16) -> Self { let char_index = (cell & 0x7ff) as usize; let vflip = cell & 0x1000 != 0; let hflip = cell & 0x2000 != 0; let palette_index = (cell >> 14) as usize; Self { char_index, vflip, hflip, palette_index, } } pub fn update(&self, source: &mut u16) -> bool { let new_value = (self.palette_index as u16) << 14 | if self.hflip { 0x2000 } else { 0x0000 } | if self.vflip { 0x1000 } else { 0x0000 } | (self.char_index as u16 & 0x07ff) | (*source & 0x0800); let changed = *source != new_value; *source = new_value; changed } } pub fn read_char_row( char: &[u16; 8], hflip: bool, vflip: bool, row: usize, ) -> impl Iterator { let pixels = if vflip { char[7 - row] } else { char[row] }; (0..16).step_by(2).map(move |i| { let pixel = if hflip { 14 - i } else { i }; ((pixels >> pixel) & 0x3) as u8 }) } pub fn read_char_pixel(char: &[u16; 8], hflip: bool, vflip: bool, row: usize, col: usize) -> u8 { let pixels = if vflip { char[7 - row] } else { char[row] }; let pixel = if hflip { 7 - col } else { col } << 1; ((pixels >> pixel) & 0x3) as u8 } pub struct CharacterGrid<'a> { source: ImageSource<'a>, scale: f32, show_grid: bool, selected: Option, } impl<'a> CharacterGrid<'a> { pub fn new(source: impl Into>) -> Self { Self { source: source.into(), scale: 1.0, show_grid: false, selected: None, } } pub fn with_scale(self, scale: f32) -> Self { Self { scale, ..self } } pub fn with_grid(self, show_grid: bool) -> Self { Self { show_grid, ..self } } pub fn with_selected(self, selected: usize) -> Self { Self { selected: Some(selected), ..self } } pub fn show(self, ui: &mut Ui) -> Option { let start_pos = ui.cursor().min; let cell_size = 8.0 * self.scale; let res = self.ui(ui); let grid_width_cells = ((res.rect.max.x - res.rect.min.x) / cell_size).round() as usize; if res.clicked() { let click_pos = res.interact_pointer_pos()?; let grid_pos = (click_pos - start_pos) / cell_size; Some((grid_pos.y as usize * grid_width_cells) + grid_pos.x as usize) } else { None } } } impl Widget for CharacterGrid<'_> { fn ui(self, ui: &mut Ui) -> Response { let image = Image::new(self.source) .fit_to_original_size(self.scale) .texture_options(TextureOptions::NEAREST) .sense(Sense::click()); let res = ui.add(image); let cell_size = 8.0 * self.scale; let grid_width_cells = ((res.rect.max.x - res.rect.min.x) / cell_size).round() as usize; let grid_height_cells = ((res.rect.max.y - res.rect.min.y) / cell_size).round() as usize; let painter = ui.painter_at(res.rect); if self.show_grid { let stroke = ui.style().visuals.widgets.noninteractive.fg_stroke; for x in (1..grid_width_cells).map(|i| (i as f32) * cell_size) { let p1 = (res.rect.min.x + x, res.rect.min.y).into(); let p2 = (res.rect.min.x + x, res.rect.max.y).into(); painter.line(vec![p1, p2], stroke); } for y in (1..grid_height_cells).map(|i| (i as f32) * cell_size) { let p1 = (res.rect.min.x, res.rect.min.y + y).into(); let p2 = (res.rect.max.x, res.rect.min.y + y).into(); painter.line(vec![p1, p2], stroke); } } if let Some(selected) = self.selected { let x1 = (selected % grid_width_cells) as f32 * cell_size; let x2 = x1 + cell_size; let y1 = (selected / grid_width_cells) as f32 * cell_size; let y2 = y1 + cell_size; painter.line( vec![ (res.rect.min + (x1, y1).into()), (res.rect.min + (x2, y1).into()), (res.rect.min + (x2, y2).into()), (res.rect.min + (x1, y2).into()), (res.rect.min + (x1, y1).into()), ], ui.style().visuals.widgets.active.fg_stroke, ); } res } }