use std::sync::Arc; use egui::{ Align, CentralPanel, Checkbox, Color32, ComboBox, Context, Image, ScrollArea, Slider, TextEdit, TextureOptions, Ui, ViewportBuilder, ViewportId, }; use egui_extras::{Column, Size, StripBuilder, TableBuilder}; use crate::{ emulator::SimId, images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader}, memory::{MemoryClient, MemoryView}, window::{ utils::{NumberEdit, UiExt}, AppWindow, }, }; use super::utils::{self, CellData, CharacterGrid}; pub struct BgMapWindow { sim_id: SimId, loader: Arc, memory: Arc, bgmaps: MemoryView, cell_index: usize, generic_palette: bool, params: ImageParams, scale: f32, show_grid: bool, } impl BgMapWindow { pub fn new(sim_id: SimId, memory: &Arc, images: &mut ImageProcessor) -> Self { let renderer = BgMapRenderer::new(sim_id, memory); let ([cell, bgmap], params) = images.add(renderer, BgMapParams::default()); let loader = ImageTextureLoader::new([("vip://cell".into(), cell), ("vip://bgmap".into(), bgmap)]); Self { sim_id, loader: Arc::new(loader), memory: memory.clone(), bgmaps: memory.watch(sim_id, 0x00020000, 0x20000), cell_index: params.cell_index, generic_palette: params.generic_palette, params, scale: 1.0, show_grid: false, } } 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("Map"); }); row.col(|ui| { let mut bgmap_index = self.cell_index / 4096; ui.add(NumberEdit::new(&mut bgmap_index).range(0..16)); if bgmap_index != self.cell_index / 4096 { self.cell_index = (bgmap_index * 4096) + (self.cell_index % 4096); } }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Cell"); }); row.col(|ui| { ui.add(NumberEdit::new(&mut self.cell_index).range(0..16 * 4096)); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Address"); }); row.col(|ui| { let address = 0x00020000 + (self.cell_index * 2); let mut address_str = format!("{address:08x}"); ui.add_enabled( false, TextEdit::singleline(&mut address_str).horizontal_align(Align::Max), ); }); }); }); let image = Image::new("vip://cell") .maintain_aspect_ratio(true) .texture_options(TextureOptions::NEAREST); ui.add(image); ui.section("Cell", |ui| { let mut data = self.bgmaps.borrow().read::(self.cell_index); let mut cell = CellData::parse(data); TableBuilder::new(ui) .column(Column::remainder()) .column(Column::remainder()) .body(|mut body| { body.row(row_height, |mut row| { row.col(|ui| { ui.label("Character"); }); row.col(|ui| { ui.add(NumberEdit::new(&mut cell.char_index).range(0..2048)); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Palette"); }); row.col(|ui| { ComboBox::from_id_salt("palette") .selected_text(format!("BG {}", cell.palette_index)) .width(ui.available_width()) .show_ui(ui, |ui| { for palette in 0..4 { ui.selectable_value( &mut cell.palette_index, palette, format!("BG {palette}"), ); } }); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.add(Checkbox::new(&mut cell.hflip, "H-flip")); }); row.col(|ui| { ui.add(Checkbox::new(&mut cell.vflip, "V-flip")); }); }); }); if cell.update(&mut data) { let address = 0x00020000 + (self.cell_index * 2); self.memory.write(self.sim_id, address as u32, &data); } }); 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"); ui.checkbox(&mut self.generic_palette, "Generic palette"); }); }); self.params.write(BgMapParams { cell_index: self.cell_index, generic_palette: self.generic_palette, }); } fn show_bgmap(&mut self, ui: &mut Ui) { let grid = CharacterGrid::new("vip://bgmap") .with_scale(self.scale) .with_grid(self.show_grid) .with_selected(self.cell_index % 4096); if let Some(selected) = grid.show(ui) { self.cell_index = (self.cell_index / 4096 * 4096) + selected; } } } impl AppWindow for BgMapWindow { fn viewport_id(&self) -> ViewportId { ViewportId::from_hash_of(format!("bgmap-{}", self.sim_id)) } fn sim_id(&self) -> SimId { self.sim_id } fn initial_viewport(&self) -> ViewportBuilder { ViewportBuilder::default() .with_title(format!("BG Map 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_bgmap(ui)); }); }) }); }); } } #[derive(Default, Clone, PartialEq, Eq)] struct BgMapParams { cell_index: usize, generic_palette: bool, } struct BgMapRenderer { chardata: MemoryView, bgmaps: MemoryView, brightness: MemoryView, palettes: MemoryView, } impl BgMapRenderer { pub fn new(sim_id: SimId, memory: &MemoryClient) -> Self { Self { chardata: memory.watch(sim_id, 0x00078000, 0x8000), bgmaps: memory.watch(sim_id, 0x00020000, 0x20000), brightness: memory.watch(sim_id, 0x0005f824, 8), palettes: memory.watch(sim_id, 0x0005f860, 16), } } fn render_bgmap(&self, image: &mut ImageBuffer, bgmap_index: usize, generic_palette: bool) { let chardata = self.chardata.borrow(); let bgmaps = self.bgmaps.borrow(); let brightness = self.brightness.borrow(); let palettes = self.palettes.borrow(); let brts = brightness.read::<[u8; 8]>(0); let colors = if generic_palette { [utils::generic_palette(Color32::RED); 4] } else { [0, 2, 4, 6].map(|i| utils::palette_colors(palettes.read(i), &brts, Color32::RED)) }; for (i, cell) in bgmaps.range::(bgmap_index * 4096, 4096).enumerate() { let CellData { char_index, vflip, hflip, palette_index, } = CellData::parse(cell); let char = chardata.read::<[u16; 8]>(char_index); let palette = &colors[palette_index]; for row in 0..8 { let y = row + (i / 64) * 8; for (col, pixel) in utils::read_char_row(&char, hflip, vflip, row).enumerate() { let x = col + (i % 64) * 8; image.write((x, y), palette[pixel as usize]); } } } } fn render_bgmap_cell(&self, image: &mut ImageBuffer, index: usize, generic_palette: bool) { let chardata = self.chardata.borrow(); let bgmaps = self.bgmaps.borrow(); let brightness = self.brightness.borrow(); let palettes = self.palettes.borrow(); let brts = brightness.read::<[u8; 8]>(0); let cell = bgmaps.read::(index); let CellData { char_index, vflip, hflip, palette_index, } = CellData::parse(cell); let char = chardata.read::<[u16; 8]>(char_index); let palette = if generic_palette { utils::generic_palette(Color32::RED) } else { utils::palette_colors(palettes.read(palette_index * 2), &brts, Color32::RED) }; for row in 0..8 { for (col, pixel) in utils::read_char_row(&char, hflip, vflip, row).enumerate() { image.write((col, row), palette[pixel as usize]); } } } } impl ImageRenderer<2> for BgMapRenderer { type Params = BgMapParams; fn sizes(&self) -> [[usize; 2]; 2] { [[8, 8], [8 * 64, 8 * 64]] } fn render(&mut self, params: &Self::Params, images: &mut [ImageBuffer; 2]) { self.render_bgmap_cell(&mut images[0], params.cell_index, params.generic_palette); self.render_bgmap( &mut images[1], params.cell_index / 4096, params.generic_palette, ); } }