use std::{fmt::Display, sync::Arc}; use egui::{ Align, CentralPanel, Color32, ComboBox, Context, Image, ScrollArea, Slider, TextEdit, TextureOptions, Ui, Vec2, ViewportBuilder, ViewportId, }; use egui_extras::{Column, Size, StripBuilder, TableBuilder}; use serde::{Deserialize, Serialize}; use crate::{ emulator::SimId, images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader}, memory::{MemoryClient, MemoryView}, window::{ AppWindow, utils::{NumberEdit, UiExt as _}, }, }; use super::utils::{self, CharacterGrid}; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum Palette { #[default] Generic, Bg0, Bg1, Bg2, Bg3, Obj0, Obj1, Obj2, Obj3, } impl Palette { pub const fn values() -> [Palette; 9] { [ Self::Generic, Self::Bg0, Self::Bg1, Self::Bg2, Self::Bg3, Self::Obj0, Self::Obj1, Self::Obj2, Self::Obj3, ] } pub const fn offset(self) -> Option { match self { Self::Generic => None, Self::Bg0 => Some(0), Self::Bg1 => Some(2), Self::Bg2 => Some(4), Self::Bg3 => Some(6), Self::Obj0 => Some(8), Self::Obj1 => Some(10), Self::Obj2 => Some(12), Self::Obj3 => Some(14), } } } impl Display for Palette { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Generic => f.write_str("Generic"), Self::Bg0 => f.write_str("BG 0"), Self::Bg1 => f.write_str("BG 1"), Self::Bg2 => f.write_str("BG 2"), Self::Bg3 => f.write_str("BG 3"), Self::Obj0 => f.write_str("OBJ 0"), Self::Obj1 => f.write_str("OBJ 1"), Self::Obj2 => f.write_str("OBJ 2"), Self::Obj3 => f.write_str("OBJ 3"), } } } pub struct CharacterDataWindow { sim_id: SimId, loader: Arc, brightness: MemoryView, palettes: MemoryView, palette: Palette, index: usize, params: ImageParams, scale: f32, show_grid: bool, } impl CharacterDataWindow { pub fn new(sim_id: SimId, memory: &MemoryClient, images: &mut ImageProcessor) -> Self { let renderer = CharDataRenderer::new(sim_id, memory); let ([char, chardata], params) = images.add(renderer, CharDataParams::default()); let loader = ImageTextureLoader::new([ ("vip://char".into(), char), ("vip://chardata".into(), chardata), ]); Self { sim_id, loader: Arc::new(loader), brightness: memory.watch(sim_id, 0x0005f824, 8), palettes: memory.watch(sim_id, 0x0005f860, 16), palette: params.palette, index: params.index, params, scale: 4.0, show_grid: true, } } fn show_form(&mut self, ui: &mut Ui) { let row_height = ui.spacing().interact_size.y; ui.vertical(|ui| { TableBuilder::new(ui) .column(Column::auto()) .column(Column::remainder()) .body(|mut body| { body.row(row_height, |mut row| { row.col(|ui| { ui.label("Index"); }); row.col(|ui| { ui.add(NumberEdit::new(&mut self.index).range(0..2048)); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Address"); }); row.col(|ui| { let address = match self.index { 0x000..0x200 => 0x00060000 + self.index * 16, 0x200..0x400 => 0x000e0000 + (self.index - 0x200) * 16, 0x400..0x600 => 0x00160000 + (self.index - 0x400) * 16, 0x600..0x800 => 0x001e0000 + (self.index - 0x600) * 16, _ => unreachable!("can't happen"), }; let mut address_str = format!("{address:08x}"); ui.add_enabled( false, TextEdit::singleline(&mut address_str).horizontal_align(Align::Max), ); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Mirror"); }); row.col(|ui| { let mirror = 0x00078000 + (self.index * 16); let mut mirror_str = format!("{mirror:08x}"); ui.add_enabled( false, TextEdit::singleline(&mut mirror_str).horizontal_align(Align::Max), ); }); }); }); let image = Image::new("vip://char") .maintain_aspect_ratio(true) .texture_options(TextureOptions::NEAREST); ui.add(image); ui.section("Colors", |ui| { ui.horizontal(|ui| { ui.label("Palette"); ComboBox::from_id_salt("palette") .selected_text(self.palette.to_string()) .width(ui.available_width()) .show_ui(ui, |ui| { for palette in Palette::values() { ui.selectable_value( &mut self.palette, palette, palette.to_string(), ); } }); }); TableBuilder::new(ui) .columns(Column::remainder(), 4) .body(|mut body| { let palette = self.load_palette_colors(); body.row(30.0, |mut row| { for color in palette { row.col(|ui| { let rect = ui.available_rect_before_wrap(); let scale = rect.height() / rect.width(); let rect = rect.scale_from_center2(Vec2::new(scale, 1.0)); ui.painter().rect_filled(rect, 0.0, color); }); } }); }); }); ui.section("Display", |ui| { ui.horizontal(|ui| { ui.label("Scale"); ui.spacing_mut().slider_width = ui.available_width(); let slider = Slider::new(&mut self.scale, 1.0..=10.0) .step_by(1.0) .show_value(false); ui.add(slider); }); ui.checkbox(&mut self.show_grid, "Show grid"); }); }); self.params.write(CharDataParams { palette: self.palette, index: self.index, }); } fn load_palette_colors(&self) -> [Color32; 4] { let Some(offset) = self.palette.offset() else { return utils::generic_palette(Color32::RED); }; let palette = self.palettes.borrow().read(offset); let brightnesses = self.brightness.borrow(); let brts = brightnesses.read(0); utils::palette_colors(palette, &brts, Color32::RED) } fn show_chardata(&mut self, ui: &mut Ui) { let grid = CharacterGrid::new("vip://chardata") .with_scale(self.scale) .with_grid(self.show_grid) .with_selected(self.index); if let Some(selected) = grid.show(ui) { self.index = selected; } } } impl AppWindow for CharacterDataWindow { fn viewport_id(&self) -> ViewportId { ViewportId::from_hash_of(format!("chardata-{}", self.sim_id)) } fn sim_id(&self) -> SimId { self.sim_id } fn initial_viewport(&self) -> ViewportBuilder { ViewportBuilder::default() .with_title(format!("Character Data ({})", self.sim_id)) .with_inner_size((640.0, 480.0)) } fn on_init(&mut self, ctx: &Context, _render_state: &egui_wgpu::RenderState) { ctx.add_texture_loader(self.loader.clone()); } fn show(&mut self, ctx: &Context) { CentralPanel::default().show(ctx, |ui| { ui.horizontal_top(|ui| { StripBuilder::new(ui) .size(Size::relative(0.3).at_most(200.0)) .size(Size::remainder()) .horizontal(|mut strip| { strip.cell(|ui| { ScrollArea::vertical().show(ui, |ui| self.show_form(ui)); }); strip.cell(|ui| { ScrollArea::both().show(ui, |ui| self.show_chardata(ui)); }); }); }); }); } } #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] enum CharDataResource { Character { palette: Palette, index: usize }, CharacterData { palette: Palette }, } #[derive(Clone, Default, PartialEq, Eq)] struct CharDataParams { palette: Palette, index: usize, } struct CharDataRenderer { chardata: MemoryView, brightness: MemoryView, palettes: MemoryView, } impl ImageRenderer<2> for CharDataRenderer { type Params = CharDataParams; fn sizes(&self) -> [[usize; 2]; 2] { [[8, 8], [16 * 8, 128 * 8]] } fn render(&mut self, params: &Self::Params, image: &mut [ImageBuffer; 2]) { self.render_character(&mut image[0], params.palette, params.index); self.render_character_data(&mut image[1], params.palette); } } impl CharDataRenderer { pub fn new(sim_id: SimId, memory: &MemoryClient) -> Self { Self { chardata: memory.watch(sim_id, 0x00078000, 0x8000), brightness: memory.watch(sim_id, 0x0005f824, 8), palettes: memory.watch(sim_id, 0x0005f860, 16), } } fn render_character(&self, image: &mut ImageBuffer, palette: Palette, index: usize) { if index >= 2048 { return; } let palette = self.load_palette(palette); let chardata = self.chardata.borrow(); let character = chardata.range::(index * 8, 8); for (row, pixels) in character.enumerate() { for col in 0..8 { let char = (pixels >> (col * 2)) & 0x03; image.write((col, row), palette[char as usize]); } } } fn render_character_data(&self, image: &mut ImageBuffer, palette: Palette) { let palette = self.load_palette(palette); let chardata = self.chardata.borrow(); for (row, pixels) in chardata.range::(0, 8 * 2048).enumerate() { let char_index = row / 8; let row_index = row % 8; let x = (char_index % 16) * 8; let y = (char_index / 16) * 8 + row_index; for col in 0..8 { let char = (pixels >> (col * 2)) & 0x03; image.write((x + col, y), palette[char as usize]); } } } fn load_palette(&self, palette: Palette) -> [Color32; 4] { let Some(offset) = palette.offset() else { return utils::GENERIC_PALETTE.map(|p| utils::shade(p, Color32::RED)); }; let palette = self.palettes.borrow().read(offset); let brightnesses = self.brightness.borrow(); let brts = brightnesses.read(0); utils::palette_colors(palette, &brts, Color32::RED) } }