1use crate::memviewer::MemoryViewerState; 2use crate::{DebugRenderContext, DebugRenderFn, memviewer}; 3use egui::{Panel, UiKind, Vec2, Window}; 4use gba_core::api::GameBoyAdvanceEmulator; 5use gba_core::api::debug::GbaMemoryArea; 6use jgenesis_common::debug::Endian; 7use jgenesis_common::frontend::Color; 8use std::collections::HashMap; 9 10// Manually position windows so they don't spawn over the top panel 11const BG_PALETTES_DEFAULT_POS: [f32; 2] = [16.0, 38.0]; 12const SPRITE_PALETTES_DEFAULT_POS: [f32; 2] = [396.0, 38.0]; 13 14struct PaletteWindowState { 15 open: bool, 16 offset: usize, 17 texture: Option<egui::TextureId>, 18} 19 20impl PaletteWindowState { 21 fn new_bg() -> Self { 22 Self { open: true, offset: 0, texture: None } 23 } 24 25 fn new_obj() -> Self { 26 Self { open: true, offset: 256, texture: None } 27 } 28} 29 30struct State { 31 memory_viewer_states: HashMap<GbaMemoryArea, MemoryViewerState>, 32 bg_palette: PaletteWindowState, 33 obj_palette: PaletteWindowState, 34 palette_buffer: Box<[Color; 512]>, 35} 36 37impl State { 38 fn new() -> Self { 39 let memory_viewer_states = GbaMemoryArea::ALL 40 .into_iter() 41 .map(|area| (area, MemoryViewerState::new(area.name(), Endian::Little))) 42 .collect(); 43 44 Self { 45 memory_viewer_states, 46 bg_palette: PaletteWindowState::new_bg(), 47 obj_palette: PaletteWindowState::new_obj(), 48 palette_buffer: vec![Color::default(); 512].into_boxed_slice().try_into().unwrap(), 49 } 50 } 51} 52 53#[must_use] 54pub fn render_fn() -> Box<DebugRenderFn<GameBoyAdvanceEmulator>> { 55 let mut state = State::new(); 56 Box::new(move |ctx, emulator| render(ctx, emulator, &mut state)) 57} 58 59fn render(ctx: DebugRenderContext<'_>, emulator: &mut GameBoyAdvanceEmulator, state: &mut State) { 60 Panel::top("gba_debug_top").show_inside(ctx.egui_ui, |ui| { 61 egui::MenuBar::new().ui(ui, |ui| { 62 ui.menu_button("Memory Viewers", |ui| { 63 for area in GbaMemoryArea::ALL { 64 if area == GbaMemoryArea::CartridgeRom { 65 continue; 66 } 67 68 if ui.button(area.name()).clicked() 69 && let Some(memviewer_state) = state.memory_viewer_states.get_mut(&area) 70 { 71 memviewer_state.open_window(ui); 72 ui.close_kind(UiKind::Menu); 73 } 74 } 75 }); 76 77 ui.menu_button("Video Memory", |ui| { 78 if ui.button("BG Palettes").clicked() { 79 state.bg_palette.open = true; 80 ui.close_kind(UiKind::Menu); 81 } 82 83 if ui.button("Sprite Palettes").clicked() { 84 state.obj_palette.open = true; 85 ui.close_kind(UiKind::Menu); 86 } 87 }); 88 }); 89 }); 90 91 for area in GbaMemoryArea::ALL { 92 let Some(state) = state.memory_viewer_states.get_mut(&area) else { continue }; 93 94 let mut memory = emulator.debug().memory_view(area); 95 memviewer::render(ctx.egui_ui, memory.as_mut(), state); 96 } 97 98 emulator.debug().copy_palette_ram(state.palette_buffer.as_mut_slice()); 99 100 render_palette_window( 101 ctx.egui_ui, 102 "BG Palettes", 103 BG_PALETTES_DEFAULT_POS, 104 state.palette_buffer.as_slice(), 105 &mut state.bg_palette, 106 ); 107 render_palette_window( 108 ctx.egui_ui, 109 "Sprite Palettes", 110 SPRITE_PALETTES_DEFAULT_POS, 111 state.palette_buffer.as_slice(), 112 &mut state.obj_palette, 113 ); 114} 115 116fn render_palette_window( 117 ctx: &egui::Context, 118 title: &str, 119 default_pos: [f32; 2], 120 buffer: &[Color], 121 state: &mut PaletteWindowState, 122) { 123 Window::new(title) 124 .open(&mut state.open) 125 .default_pos(default_pos) 126 .default_size([350.0, 400.0]) 127 .show(ctx, |ui| { 128 let texture = crate::update_egui_texture( 129 ctx, 130 [16, 16], 131 &buffer[state.offset..state.offset + 256], 132 &mut state.texture, 133 ); 134 135 let mut size = ui.available_width(); 136 if ui.available_height() < size { 137 size = ui.available_height(); 138 } 139 140 ui.image((texture, Vec2::new(size, size))); 141 }); 142}