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, memory::{MemoryMonitor, MemoryView}, vram::{VramImage, VramImageLoader, VramResource as _, VramTextureLoader}, window::{utils::UiExt as _, AppWindow}, }; use super::utils::{self, CharacterGrid}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum VramPalette { Generic, Bg0, Bg1, Bg2, Bg3, Obj0, Obj1, Obj2, Obj3, } impl VramPalette { pub const fn values() -> [VramPalette; 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 VramPalette { 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: VramPalette, index: usize, index_str: String, scale: f32, show_grid: bool, } impl CharacterDataWindow { pub fn new(sim_id: SimId, memory: &mut MemoryMonitor) -> Self { Self { sim_id, loader: Arc::new(VramTextureLoader::new(CharDataLoader::new(sim_id, memory))), brightness: memory.view(sim_id, 0x0005f824, 8), palettes: memory.view(sim_id, 0x0005f860, 16), palette: VramPalette::Generic, index: 0, index_str: "0".into(), 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.number_picker(&mut self.index_str, &mut self.index, 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 resource = CharDataResource::Character { palette: self.palette, index: self.index, }; let image = Image::new(resource.to_uri()) .maintain_aspect_ratio(true) .tint(Color32::RED) .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 VramPalette::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, Color32::RED * Color32::from_gray(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"); }); }); } fn load_palette_colors(&self) -> [u8; 4] { let Some(offset) = self.palette.offset() else { return utils::GENERIC_PALETTE; }; let palette = self.palettes.borrow().read(offset); let brightnesses = self.brightness.borrow(); let brts = brightnesses.range(0, 8); utils::parse_palette(palette, brts) } fn show_chardata(&mut self, ui: &mut Ui) { let resource = CharDataResource::CharacterData { palette: self.palette, }; let grid = CharacterGrid::new(resource.to_uri()) .with_scale(self.scale) .with_grid(self.show_grid) .with_selected(self.index); if let Some(selected) = grid.show(ui) { self.index = selected; self.index_str = selected.to_string(); } } } 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) { self.loader.begin_pass(); CentralPanel::default().show(ctx, |ui| { ui.horizontal_top(|ui| { StripBuilder::new(ui) .size(Size::relative(0.3)) .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: VramPalette, index: usize }, CharacterData { palette: VramPalette }, } struct CharDataLoader { chardata: MemoryView, brightness: MemoryView, palettes: MemoryView, } impl CharDataLoader { pub fn new(sim_id: SimId, memory: &mut MemoryMonitor) -> Self { Self { chardata: memory.view(sim_id, 0x00078000, 0x8000), brightness: memory.view(sim_id, 0x0005f824, 8), palettes: memory.view(sim_id, 0x0005f860, 16), } } fn update_character(&self, image: &mut VramImage, palette: VramPalette, 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.iter().enumerate() { for col in 0..8 { let char = (pixels >> (col * 2)) & 0x03; image.write((col, row), palette[char as usize]); } } } fn update_character_data(&self, image: &mut VramImage, palette: VramPalette) { let palette = self.load_palette(palette); let chardata = self.chardata.borrow(); for (row, pixels) in chardata.range::(0, 8 * 2048).iter().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: VramPalette) -> [u8; 4] { let Some(offset) = palette.offset() else { return utils::GENERIC_PALETTE; }; let palette = self.palettes.borrow().read(offset); let brightnesses = self.brightness.borrow(); let brts = brightnesses.range(0, 8); utils::parse_palette(palette, brts) } } impl VramImageLoader for CharDataLoader { type Resource = CharDataResource; fn id(&self) -> &str { concat!(module_path!(), "::CharDataLoader") } fn add(&self, resource: &Self::Resource) -> Option { match resource { CharDataResource::Character { palette, index } => { let mut image = VramImage::new(8, 8); self.update_character(&mut image, *palette, *index); Some(image) } CharDataResource::CharacterData { palette } => { let mut image = VramImage::new(8 * 16, 8 * 128); self.update_character_data(&mut image, *palette); Some(image) } } } fn update<'a>( &'a self, resources: impl Iterator, ) { for (resource, image) in resources { match resource { CharDataResource::Character { palette, index } => { self.update_character(image, *palette, *index) } CharDataResource::CharacterData { palette } => { self.update_character_data(image, *palette) } } } } }