use iced::{ Element, Length::Fill, widget::{ self, button, checkbox, column, container::bordered_box, image, number_input, row, scrollable, text, }, }; use crate::{CycleResult, NES}; #[derive(Debug, Clone)] pub struct DebuggerState { ppu_cycles: usize, cpu_cycles: usize, instructions: usize, scan_lines: usize, to_scan_line: usize, frames: usize, } #[derive(Debug, Clone)] pub enum DebuggerMessage { Run, Pause, SetPPUCycles(usize), RunPPUCycles, SetCPUCycles(usize), RunCPUCycles, SetInstructions(usize), RunInstructions, SetScanLines(usize), RunScanLines, SetToScanLine(usize), RunToScanLine, SetFrames(usize), RunFrames, } impl DebuggerState { pub fn new() -> Self { Self { ppu_cycles: 1, cpu_cycles: 1, instructions: 1, scan_lines: 1, to_scan_line: 1, frames: 1, // cpu_cycles: 1, } } pub fn view<'s>(&'s self, nes: &'s NES) -> Element<'s, DebuggerMessage> { column![ row![ button(image("./images/ic_fluent_play_24_filled.png")) .on_press(DebuggerMessage::Run), button(image("./images/ic_fluent_pause_24_filled.png")) .on_press(DebuggerMessage::Pause), ], iced::widget::rule::horizontal(2.), row![column![ text("Status"), row![ labelled("A:", text(format!("{:02X}", nes.cpu.a))), labelled("X:", text(format!("{:02X}", nes.cpu.x))), labelled("Y:", text(format!("{:02X}", nes.cpu.y))), labelled("PC:", text(format!("{:04X}", nes.cpu.pc))), labelled("Cycle:", text(format!("{}", nes.cycle))), labelled("SP:", text(format!("{:02X}", nes.cpu.sp))), ] .spacing(5.), row![ labelled("P:", text(format!("{:02X}", nes.cpu.status.0))), labelled_box("Carry", nes.cpu.status.carry()), labelled_box("Zero", nes.cpu.status.zero()), labelled_box("Interrupt", nes.cpu.status.interrupt_disable()), labelled_box("--", false), labelled_box("--", false), labelled_box("Overflow", nes.cpu.status.overflow()), labelled_box("Negative", nes.cpu.status.negative()), ] .spacing(5.), row![ text("IRQs:"), labelled_box("NMI", nes.peek_nmi()), labelled_box("Cart", false), labelled_box("Frame Counter", false), labelled_box("DMC", false), ] .spacing(5.), row![ column![ labelled("Cycle", text(nes.ppu.pixel)), labelled("Scanline", text(nes.ppu.scanline)), labelled("PPU Cycle", text(nes.ppu.cycle)), ], column![ labelled_box("Sprite 0 Hit", false), labelled_box("Sprite 0 Overflow", false), labelled_box("Vertical Blank", nes.ppu.vblank), labelled_box("Write Toggle", false), labelled_box("", false), labelled_box("Large Sprites", false), labelled_box("Vertical Write", false), labelled_box("NMI on VBlank", false), labelled_box("BG at $1000", false), labelled_box("Sprites at $1000", false), ], column![ run_type( "PPU Cycles:", self.ppu_cycles, DebuggerMessage::SetPPUCycles, DebuggerMessage::RunPPUCycles ), run_type( "CPU Cycles:", self.cpu_cycles, DebuggerMessage::SetCPUCycles, DebuggerMessage::RunCPUCycles ), run_type( "Instructions:", self.instructions, DebuggerMessage::SetInstructions, DebuggerMessage::RunInstructions ), run_type( "Scanlines:", self.scan_lines, DebuggerMessage::SetScanLines, DebuggerMessage::RunScanLines ), run_type( "To Scanline:", self.to_scan_line, DebuggerMessage::SetToScanLine, DebuggerMessage::RunToScanLine ), run_type( "Frames:", self.frames, DebuggerMessage::SetFrames, DebuggerMessage::RunFrames ), ], ] .spacing(5.), scrollable(column( nes.debug_log() .history() .into_iter() .rev() .map(|s| text(s).line_height(0.9).into()) ).spacing(0)) .width(Fill), ],], ] .width(Fill) .height(Fill) .into() } fn run_n_clock_cycles(nes: &mut NES, n: usize) { for _ in 0..n { nes.run_one_clock_cycle(); } } fn run_until(nes: &mut NES, mut f: impl FnMut(CycleResult, &NES) -> bool) { loop { if f(nes.run_one_clock_cycle(), nes) { break; } } } pub fn update(&mut self, message: DebuggerMessage, nes: &mut NES) { match message { DebuggerMessage::SetPPUCycles(n) => self.ppu_cycles = n, DebuggerMessage::SetCPUCycles(n) => self.cpu_cycles = n, DebuggerMessage::SetInstructions(n) => self.instructions = n, DebuggerMessage::SetScanLines(n) => self.scan_lines = n, DebuggerMessage::SetToScanLine(n) => self.to_scan_line = n, DebuggerMessage::SetFrames(n) => self.frames = n, DebuggerMessage::RunPPUCycles => Self::run_n_clock_cycles(nes, self.ppu_cycles), DebuggerMessage::RunCPUCycles => Self::run_n_clock_cycles(nes, self.cpu_cycles * 3), DebuggerMessage::RunInstructions => Self::run_until(nes, |c, _| c.cpu_exec), DebuggerMessage::RunScanLines => Self::run_n_clock_cycles(nes, self.scan_lines * 341), DebuggerMessage::RunToScanLine => { Self::run_until(nes, |_, n| n.ppu.scanline == self.to_scan_line) } DebuggerMessage::RunFrames => Self::run_n_clock_cycles(nes, self.frames * 341 * 261), DebuggerMessage::Run => todo!(), DebuggerMessage::Pause => todo!(), } } } fn run_type<'a, Message: Clone + 'a>( label: &'a str, val: usize, update: impl Fn(usize) -> Message + 'a, run: Message, ) -> Element<'a, Message> { row![ widget::container(text(label)).padding(2.), widget::container(number_input(val).on_input(update)).padding(2.), widget::container(button(image("./images/ic_fluent_play_24_filled.png")).on_press(run)) .padding(2.), ] .spacing(1.) .into() } pub fn labelled<'a, Message: 'a>( label: &'a str, content: impl Into>, ) -> Element<'a, Message> { row![ widget::container(text(label)).padding(2.), widget::container(content).style(bordered_box).padding(2.), ] .spacing(1.) .into() } pub fn labelled_box<'a, Message: 'a>(label: &'a str, value: bool) -> Element<'a, Message> { row![checkbox(value), widget::container(text(label)),] .spacing(1.) .into() }