use std::sync::Arc; use egui::{ Align, Button, CentralPanel, Checkbox, Color32, Context, Direction, Label, Layout, ScrollArea, TextEdit, Ui, ViewportBuilder, ViewportId, }; use egui_extras::{Column, Size, StripBuilder, TableBuilder}; use crate::{ emulator::SimId, memory::{MemoryClient, MemoryRef, MemoryValue, MemoryView}, window::{ AppWindow, utils::{NumberEdit, UiExt}, }, }; use super::utils; pub struct RegisterWindow { sim_id: SimId, memory: Arc, registers: MemoryView, } impl RegisterWindow { pub fn new(sim_id: SimId, memory: &Arc) -> Self { Self { sim_id, memory: memory.clone(), registers: memory.watch(sim_id, 0x0005f800, 0x72), } } fn show_interrupts(&mut self, ui: &mut Ui) { let row_height = self.row_height(ui); let [mut raw_intpnd, mut raw_intenb] = self.read_address(0x0005f800); let mut intenb = InterruptReg::parse(raw_intenb); let mut intpnd = InterruptReg::parse(raw_intpnd); ui.section("Interrupt", |ui| { let width = ui.available_width(); let xspace = ui.spacing().item_spacing.x; ui.with_layout(Layout::top_down_justified(Align::Center), |ui| { TableBuilder::new(ui) .id_salt("raw_values") .columns(Column::exact(width * 0.5 - xspace), 2) .cell_layout(Layout::left_to_right(Align::Max)) .body(|mut body| { body.row(row_height, |mut row| { row.col(|ui| { ui.label("INTENB"); }); row.col(|ui| { let mut text = format!("{raw_intenb:04x}"); ui.add_sized( ui.available_size(), TextEdit::singleline(&mut text).horizontal_align(Align::Max), ); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("INTPND"); }); row.col(|ui| { let mut text = format!("{raw_intpnd:04x}"); ui.add_enabled( false, TextEdit::singleline(&mut text).horizontal_align(Align::Max), ); }); }); }); ui.add_space(8.0); TableBuilder::new(ui) .id_salt("flags") .column(Column::exact(width * 0.5 - xspace)) .columns(Column::exact(width * 0.25 - xspace), 2) .cell_layout(Layout::left_to_right(Align::Center).with_main_align(Align::RIGHT)) .body(|mut body| { body.row(row_height, |mut row| { row.col(|_ui| {}); row.col(|ui| { ui.add_sized(ui.available_size(), Label::new("ENB")); }); row.col(|ui| { ui.add_sized(ui.available_size(), Label::new("PND")); }); }); let mut add_row = |label: &str, enb: &mut bool, pnd: &mut bool| { body.row(row_height, |mut row| { row.col(|ui| { ui.label(label); }); row.col(|ui| { let space = (ui.available_width() - ui.spacing().icon_width) / 2.0; ui.add_space(space); ui.checkbox(enb, ""); }); row.col(|ui| { let space = (ui.available_width() - ui.spacing().icon_width) / 2.0; ui.add_space(space); ui.checkbox(pnd, ""); }); }); }; add_row("TIMEERR", &mut intenb.timeerr, &mut intpnd.timeerr); add_row("XPEND", &mut intenb.xpend, &mut intpnd.xpend); add_row("SBHIT", &mut intenb.sbhit, &mut intpnd.sbhit); add_row("FRAMESTART", &mut intenb.framestart, &mut intpnd.framestart); add_row("GAMESTART", &mut intenb.gamestart, &mut intpnd.gamestart); add_row("RFBEND", &mut intenb.rfbend, &mut intpnd.rfbend); add_row("LFBEND", &mut intenb.lfbend, &mut intpnd.lfbend); add_row("SCANERR", &mut intenb.scanerr, &mut intpnd.scanerr); }); }); ui.allocate_space(ui.available_size()); }); if intpnd.update(&mut raw_intpnd) { self.memory.write(self.sim_id, 0x0005f800, &raw_intpnd); } if intenb.update(&mut raw_intenb) { self.memory.write(self.sim_id, 0x0005f802, &raw_intenb); } } fn show_display_status(&mut self, ui: &mut Ui) { let row_height = self.row_height(ui); let mut raw_dpstts = self.read_address(0x0005f820); let mut dpstts = DisplayReg::parse(raw_dpstts); ui.section("Display", |ui| { let width = ui.available_width(); TableBuilder::new(ui) .column(Column::exact(width * 0.5)) .column(Column::exact(width * 0.5)) .cell_layout(Layout::left_to_right(Align::Max)) .body(|mut body| { body.row(row_height, |mut row| { row.col(|ui| { ui.label("DPSTTS"); }); row.col(|ui| { let mut value_str = format!("{raw_dpstts:04x}"); ui.add_enabled( false, TextEdit::singleline(&mut value_str).horizontal_align(Align::Max), ); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.add_enabled(true, Checkbox::new(&mut dpstts.lock, "LOCK")); }); row.col(|ui| { ui.add_enabled(false, Checkbox::new(&mut dpstts.r1bsy, "R1BSY")); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.add_enabled(true, Checkbox::new(&mut dpstts.synce, "SYNCE")); }); row.col(|ui| { ui.add_enabled(false, Checkbox::new(&mut dpstts.l1bsy, "L1BSY")); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.add_enabled(true, Checkbox::new(&mut dpstts.re, "RE")); }); row.col(|ui| { ui.add_enabled(false, Checkbox::new(&mut dpstts.r0bsy, "R0BSY")); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.add_enabled(false, Checkbox::new(&mut dpstts.fclk, "FCLK")); }); row.col(|ui| { ui.add_enabled(false, Checkbox::new(&mut dpstts.l0bsy, "L0BSY")); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.add_enabled(false, Checkbox::new(&mut dpstts.scanrdy, "SCANRDY")); }); row.col(|ui| { ui.add_enabled(true, Checkbox::new(&mut dpstts.disp, "DISP")); }); }); body.row(row_height, |mut row| { row.col(|_ui| {}); row.col(|ui| { if ui .add(Button::new("DPRST").min_size(ui.available_size())) .clicked() { dpstts.dprst = true; } }); }); }); ui.allocate_space(ui.available_size()); }); if dpstts.update(&mut raw_dpstts) { self.memory.write(self.sim_id, 0x0005f822, &raw_dpstts); } } fn show_drawing_status(&mut self, ui: &mut Ui) { let row_height = self.row_height(ui); let [mut raw_xpstts, raw_xpctrl] = self.read_address(0x0005f840); let mut xpstts = DrawingReg::parse(raw_xpstts); ui.section("Drawing", |ui| { let width = ui.available_width(); TableBuilder::new(ui) .column(Column::exact(width * 0.5)) .column(Column::exact(width * 0.5)) .cell_layout(Layout::left_to_right(Align::Max)) .body(|mut body| { body.row(row_height, |mut row| { row.col(|ui| { ui.label("XPCTRL"); }); row.col(|ui| { let mut value_str = format!("{raw_xpctrl:04x}"); ui.add_enabled( false, TextEdit::singleline(&mut value_str).horizontal_align(Align::Max), ); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("XPSTTS"); }); row.col(|ui| { let mut value_str = format!("{raw_xpstts:04x}"); ui.add_enabled( false, TextEdit::singleline(&mut value_str).horizontal_align(Align::Max), ); }); }); /* body.row(row_height, |mut row| { row.col(|ui| { ui.label("SBCMP"); }); row.col(|ui| { let old_value = xpctrl.sbcmp; ui.add_enabled(true, NumberEdit::new(&mut xpctrl.sbcmp).range(0..32)); cmp_changed = xpctrl.sbcmp != old_value; }); }); */ body.row(row_height, |mut row| { row.col(|ui| { ui.label("SBCOUNT"); }); row.col(|ui| { ui.add_enabled( false, NumberEdit::new(&mut xpstts.sbcount) .arrows(false) .range(0..32), ); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.add_enabled(false, Checkbox::new(&mut xpstts.sbout, "SBOUT")); }); row.col(|ui| { ui.add_enabled(false, Checkbox::new(&mut xpstts.f1bsy, "F1BSY")); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.add_enabled(false, Checkbox::new(&mut xpstts.f0bsy, "F0BSY")); }); row.col(|ui| { ui.add_enabled(false, Checkbox::new(&mut xpstts.overtime, "OVERTIME")); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.add_enabled(true, Checkbox::new(&mut xpstts.xpen, "XPEN")); }); row.col(|ui| { if ui .add(Button::new("XPRST").min_size(ui.available_size())) .clicked() { xpstts.xprst = true; } }); }); }); ui.allocate_space(ui.available_size()); }); if xpstts.update(&mut raw_xpstts) { xpstts.update(&mut raw_xpstts); self.memory.write(self.sim_id, 0x0005f842, &raw_xpstts); } } fn show_colors(&mut self, ui: &mut Ui) { let row_height = self.row_height(ui); let registers = self.registers.borrow(); ui.section("Colors", |ui| { let width = ui.available_width(); let xspace = ui.spacing().item_spacing.x; TableBuilder::new(ui) .column(Column::exact(width * 0.2 - xspace)) .columns(Column::exact(width * 0.20 - xspace), 4) .cell_layout(Layout::left_to_right(Align::Max)) .body(|mut body| { body.row(row_height, |mut row| { row.col(|_ui| {}); row.col(|_ui| {}); row.col(|ui| { ui.with_layout( Layout::centered_and_justified(Direction::LeftToRight), |ui| ui.label("1"), ); }); row.col(|ui| { ui.with_layout( Layout::centered_and_justified(Direction::LeftToRight), |ui| ui.label("2"), ); }); row.col(|ui| { ui.with_layout( Layout::centered_and_justified(Direction::LeftToRight), |ui| ui.label("3"), ); }); }); let mut brts: [u16; 3] = [ read_address(®isters, 0x0005f824), read_address(®isters, 0x0005f826), read_address(®isters, 0x0005f828), ]; body.row(row_height, |mut row| { let mut stale = false; row.col(|ui| { ui.label("BRT"); }); row.col(|_ui| {}); for brt in brts.iter_mut() { row.col(|ui| { if ui .add(NumberEdit::new(brt).range(0..256).arrows(false).hex(true)) .changed() { stale = true; } }); } if stale { self.memory.write(self.sim_id, 0x0005f824, &brts); } }); body.row(row_height, |mut row| { row.col(|_ui| {}); for shade in utils::parse_brts(&brts, Color32::RED) { row.col(|ui| { ui.painter().rect_filled( ui.available_rect_before_wrap(), 0.0, shade, ); }); } }); let mut palettes = read_address::<[u16; 8]>(®isters, 0x0005f860); let mut add_row = |name: &str, address: u32, value: &mut u16| { let mut c1 = (*value >> 2) & 0x03; let mut c2 = (*value >> 4) & 0x03; let mut c3 = (*value >> 6) & 0x03; let mut stale = false; body.row(row_height, |mut row| { row.col(|ui| { ui.label(name); }); row.col(|ui| { if ui .add( NumberEdit::new(value) .range(0..256) .desired_width(width * 0.2) .arrows(false) .hex(true), ) .changed() { stale = true; }; }); row.col(|ui| { if ui .add( NumberEdit::new(&mut c1) .range(0..4) .desired_width(ui.available_width() - xspace), ) .changed() { *value = (*value & 0xfff3) | (c1 << 2); stale = true; } }); row.col(|ui| { if ui .add( NumberEdit::new(&mut c2) .range(0..4) .desired_width(ui.available_width() - xspace), ) .changed() { *value = (*value & 0xffcf) | (c2 << 4); stale = true; } }); row.col(|ui| { if ui .add( NumberEdit::new(&mut c3) .range(0..4) .desired_width(ui.available_width() - xspace), ) .changed() { *value = (*value & 0xff3f) | (c3 << 6); stale = true; } }); }); if stale { self.memory.write(self.sim_id, address, value); } }; add_row("GPLT0", 0x0005f860, &mut palettes[0]); add_row("GPLT1", 0x0005f862, &mut palettes[1]); add_row("GPLT2", 0x0005f864, &mut palettes[2]); add_row("GPLT3", 0x0005f866, &mut palettes[3]); add_row("JPLT0", 0x0005f868, &mut palettes[4]); add_row("JPLT1", 0x0005f86a, &mut palettes[5]); add_row("JPLT2", 0x0005f86c, &mut palettes[6]); add_row("JPLT3", 0x0005f86e, &mut palettes[7]); body.row(row_height, |mut row| { row.col(|ui| { ui.label("BKCOL"); }); row.col(|ui| { let mut bkcol: u16 = read_address(®isters, 0x0005f870); if ui .add( NumberEdit::new(&mut bkcol) .range(0..4) .desired_width(ui.available_width() - xspace), ) .changed() { self.memory.write(self.sim_id, 0x0005f870, &bkcol); } }); row.col(|_ui| {}); row.col(|ui| { ui.label("REST"); }); row.col(|ui| { let mut rest: u16 = read_address(®isters, 0x0005f82a); if ui .add( NumberEdit::new(&mut rest) .range(0..256) .arrows(false) .hex(true), ) .changed() { self.memory.write(self.sim_id, 0x0005f82a, &rest); } }); }); }); ui.allocate_space(ui.available_size_before_wrap()); }); } fn show_objects(&mut self, ui: &mut Ui) { let row_height = self.row_height(ui); ui.section("Objects", |ui| { let width = ui.available_width(); let xspace = ui.spacing().item_spacing.x; TableBuilder::new(ui) .column(Column::exact(width * 0.3 - xspace)) .column(Column::exact(width * 0.3 - xspace)) .column(Column::exact(width * 0.4 - xspace)) .cell_layout(Layout::left_to_right(Align::Center).with_main_align(Align::RIGHT)) .body(|mut body| { body.row(row_height, |mut row| { row.col(|_ui| {}); row.col(|ui| { ui.add_sized(ui.available_size(), Label::new("Start")); }); row.col(|ui| { ui.add_sized(ui.available_size(), Label::new("End")); }); }); let mut spts = self.read_address::<[u16; 4]>(0x0005f848); let prevs = std::iter::once(0u16).chain(spts.map(|i| (i + 1) & 0x03ff)); let mut changed = false; for (index, (spt, prev)) in spts.iter_mut().zip(prevs).enumerate() { body.row(row_height, |mut row| { row.col(|ui| { ui.label(format!("SPT{index}")); }); row.col(|ui| { ui.with_layout(Layout::right_to_left(Align::Center), |ui| { ui.label(prev.to_string()); }); }); row.col(|ui| { if ui.add(NumberEdit::new(spt).range(0..1024)).changed() { changed = true; } }); }); } if changed { self.memory.write(self.sim_id, 0x0005f848, &spts); } }); ui.allocate_space(ui.available_size_before_wrap()); }); } fn show_misc(&mut self, ui: &mut Ui) { let row_height = self.row_height(ui); let registers = self.registers.borrow(); ui.section("Misc.", |ui| { let width = ui.available_width(); let xspace = ui.spacing().item_spacing.x; TableBuilder::new(ui) .column(Column::exact(width * 0.5 - xspace)) .column(Column::exact(width * 0.5 - xspace)) .cell_layout(Layout::left_to_right(Align::Center).with_main_align(Align::RIGHT)) .body(|mut body| { body.row(row_height, |mut row| { row.col(|ui| { ui.label("FRMCYC"); }); row.col(|ui| { let mut frmcyc = read_address::(®isters, 0x0005f82e); ui.with_layout(Layout::right_to_left(Align::Center), |ui| { if ui.add(NumberEdit::new(&mut frmcyc).range(0..32)).changed() { self.memory.write(self.sim_id, 0x0005f82e, &frmcyc); } }); }); }); let mut cta = read_address::(®isters, 0x0005f830); body.row(row_height, |mut row| { row.col(|ui| { ui.label("CTA"); }); row.col(|ui| { ui.with_layout(Layout::right_to_left(Align::Center), |ui| { ui.add_enabled( false, NumberEdit::new(&mut cta).arrows(false).hex(true), ); }); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("CTA_L"); }); row.col(|ui| { let mut cta_l = cta & 0xff; ui.with_layout(Layout::right_to_left(Align::Center), |ui| { ui.add_enabled( false, NumberEdit::new(&mut cta_l).arrows(false).hex(true), ); }); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("CTA_R"); }); row.col(|ui| { let mut cta_r = (cta >> 8) & 0xff; ui.with_layout(Layout::right_to_left(Align::Center), |ui| { ui.add_enabled( false, NumberEdit::new(&mut cta_r).arrows(false).hex(true), ); }); }); }); }); ui.allocate_space(ui.available_size_before_wrap()); }); } fn row_height(&self, ui: &mut Ui) -> f32 { ui.spacing().interact_size.y + ui.style().visuals.selection.stroke.width } fn read_address(&self, address: usize) -> T { read_address(&self.registers.borrow(), address) } } fn read_address(registers: &MemoryRef, address: usize) -> T { let index = (address - 0x0005f800) / size_of::(); registers.read(index) } impl AppWindow for RegisterWindow { fn viewport_id(&self) -> ViewportId { ViewportId::from_hash_of(format!("registers-{}", self.sim_id)) } fn sim_id(&self) -> SimId { self.sim_id } fn initial_viewport(&self) -> ViewportBuilder { ViewportBuilder::default() .with_title(format!("Registers ({})", self.sim_id)) .with_inner_size((800.0, 480.0)) } fn show(&mut self, ctx: &Context) { CentralPanel::default().show(ctx, |ui| { ScrollArea::vertical().show(ui, |ui| { ui.horizontal_top(|ui| { let width = ui.available_width(); let xspace = ui.spacing().item_spacing.x; StripBuilder::new(ui) .size(Size::exact(width * 0.25 - xspace)) .size(Size::exact(width * 0.225 - xspace)) .size(Size::exact(width * 0.325 - xspace)) .size(Size::exact(width * 0.2 - xspace)) .horizontal(|mut strip| { strip.cell(|ui| { self.show_interrupts(ui); }); strip.strip(|nested| { nested.sizes(Size::remainder(), 2).vertical(|mut strip| { strip.cell(|ui| { self.show_display_status(ui); }); strip.cell(|ui| { self.show_drawing_status(ui); }); }); }); strip.cell(|ui| { self.show_colors(ui); }); strip.strip(|nested| { nested.sizes(Size::remainder(), 2).vertical(|mut strip| { strip.cell(|ui| { self.show_objects(ui); }); strip.cell(|ui| { self.show_misc(ui); }); }); }); }); }); }); }); } } struct InterruptReg { timeerr: bool, xpend: bool, sbhit: bool, framestart: bool, gamestart: bool, rfbend: bool, lfbend: bool, scanerr: bool, } impl InterruptReg { fn parse(value: u16) -> Self { Self { timeerr: value & 0x8000 != 0, xpend: value & 0x4000 != 0, sbhit: value & 0x2000 != 0, framestart: value & 0x0010 != 0, gamestart: value & 0x0008 != 0, rfbend: value & 0x0004 != 0, lfbend: value & 0x0002 != 0, scanerr: value & 0x0001 != 0, } } fn update(&self, value: &mut u16) -> bool { let new_value = (*value & 0x1fe0) | if self.timeerr { 0x8000 } else { 0x0000 } | if self.xpend { 0x4000 } else { 0x0000 } | if self.sbhit { 0x2000 } else { 0x0000 } | if self.framestart { 0x0010 } else { 0x0000 } | if self.gamestart { 0x0008 } else { 0x0000 } | if self.rfbend { 0x0004 } else { 0x0000 } | if self.lfbend { 0x0002 } else { 0x0000 } | if self.scanerr { 0x0001 } else { 0x0000 }; let changed = *value != new_value; *value = new_value; changed } } struct DisplayReg { lock: bool, synce: bool, re: bool, fclk: bool, scanrdy: bool, r1bsy: bool, l1bsy: bool, r0bsy: bool, l0bsy: bool, disp: bool, dprst: bool, } impl DisplayReg { fn parse(value: u16) -> Self { Self { lock: value & 0x0400 != 0, synce: value & 0x0200 != 0, re: value & 0x0100 != 0, fclk: value & 0x0080 != 0, scanrdy: value & 0x0040 != 0, r1bsy: value & 0x0020 != 0, l1bsy: value & 0x0010 != 0, r0bsy: value & 0x0008 != 0, l0bsy: value & 0x0004 != 0, disp: value & 0x0002 != 0, dprst: value & 0x0001 != 0, } } fn update(&self, value: &mut u16) -> bool { let new_value = (*value & 0xf800) | if self.lock { 0x0400 } else { 0x0000 } | if self.synce { 0x0200 } else { 0x0000 } | if self.re { 0x0100 } else { 0x0000 } | if self.fclk { 0x0080 } else { 0x0000 } | if self.scanrdy { 0x0040 } else { 0x0000 } | if self.r1bsy { 0x0020 } else { 0x0000 } | if self.l1bsy { 0x0010 } else { 0x0000 } | if self.r0bsy { 0x0008 } else { 0x0000 } | if self.l0bsy { 0x0004 } else { 0x0000 } | if self.disp { 0x0002 } else { 0x0000 } | if self.dprst { 0x0001 } else { 0x0000 }; let changed = *value != new_value; *value = new_value; changed } } struct DrawingReg { sbout: bool, sbcount: u8, overtime: bool, f1bsy: bool, f0bsy: bool, xpen: bool, xprst: bool, } impl DrawingReg { fn parse(value: u16) -> Self { Self { sbout: value & 0x8000 != 0, sbcount: (value >> 8) as u8 & 0x1f, overtime: value & 0x0010 != 0, f1bsy: value & 0x0008 != 0, f0bsy: value & 0x0004 != 0, xpen: value & 0x0002 != 0, xprst: value & 0x0001 != 0, } } fn update(&self, value: &mut u16) -> bool { let new_value = (*value & 0x60e0) | if self.sbout { 0x8000 } else { 0x0000 } | (((self.sbcount & 0x1f) as u16) << 8) | if self.overtime { 0x0010 } else { 0x0000 } | if self.f1bsy { 0x0008 } else { 0x0000 } | if self.f0bsy { 0x0004 } else { 0x0000 } | if self.xpen { 0x0002 } else { 0x0000 } | if self.xprst { 0x0001 } else { 0x0000 }; let changed = *value != new_value; *value = new_value; changed } }