use std::rc::Rc; use iced::{ Element, Length::{self, Fill}, Point, Renderer, Size, Theme, advanced::{ Widget, layout::Node, widget::{ Tree, tree::{State, Tag}, }, }, mouse, widget::{ self, Canvas, Text, button, canvas::{Frame, Program}, checkbox, column, container::bordered_box, image, number_input, hex_input, row, rule::horizontal, scrollable, text, }, window::Event, }; use crate::{CycleResult, NES, PPU}; #[derive(Debug, Clone)] pub struct DebuggerState { ppu_cycles: usize, cpu_cycles: usize, instructions: usize, scan_lines: usize, to_scan_line: usize, frames: usize, breakpoint: 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, SetBreakpoint(usize), RunBreakpoint, } pub fn hex16<'a, Theme, Renderer>(val: u16) -> Text<'a, Theme, Renderer> where Theme: text::Catalog + 'a, Renderer: iced::advanced::text::Renderer, { text(format!("{val:04X}")) } pub fn hex8<'a, Theme, Renderer>(val: u8) -> Text<'a, Theme, Renderer> where Theme: text::Catalog + 'a, Renderer: iced::advanced::text::Renderer, { text(format!("{val:02X}")) } pub fn bin8<'a, Theme, Renderer>(val: u8) -> Text<'a, Theme, Renderer> where Theme: text::Catalog + 'a, Renderer: iced::advanced::text::Renderer, { text(format!("{val:08b}")) } pub fn bin32<'a, Theme, Renderer>(val: u32) -> Text<'a, Theme, Renderer> where Theme: text::Catalog + 'a, Renderer: iced::advanced::text::Renderer, { text(format!("{val:032b}")) } impl DebuggerState { pub fn new() -> Self { Self { ppu_cycles: 1, cpu_cycles: 1, instructions: 1, scan_lines: 1, to_scan_line: 1, frames: 1, breakpoint: 0xEA5A, // 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", nes.apu().peek_irq()), 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)), labelled("Frame", text(nes.ppu().frame_count)), labelled("V:", hex16(nes.ppu().background.v)), labelled("T:", hex16(nes.ppu().background.t)), labelled("X:", hex8(nes.ppu().background.x)), text(""), labelled("NT:", hex8(nes.ppu().background.cur_nametable)), labelled2( "AT:", hex8(nes.ppu().background.next_attr), hex16( 0x23C0 | (nes.ppu().background.v & 0x0C00) | ((nes.ppu().background.v >> 4) & 0x38) | ((nes.ppu().background.v >> 2) & 0x07) ) ), labelled("AT:", hex8(nes.ppu().background.cur_attr)), labelled("HI:", bin32(nes.ppu().background.cur_shift_high)), labelled("LO:", bin32(nes.ppu().background.cur_shift_low)), ], column![ labelled_box("Sprite 0 Hit", false), labelled_box("Sprite 0 Overflow", false), labelled_box("Vertical Blank", nes.ppu().vblank), labelled_box("Write Toggle", nes.ppu().background.w), text(""), labelled_box("Large Sprites", false), labelled_box("Vertical Write", nes.ppu().background.vram_column), labelled_box("NMI on VBlank", nes.ppu().nmi_on_vblank()), labelled_box("BG at $1000", nes.ppu().background.second_pattern), labelled_box("Sprites at $1000", false), ], column![ labelled_box("Even frame", nes.ppu().even), labelled_box("BG Enabled", nes.ppu().mask.enable_background), labelled_box("Sprites Enabled", nes.ppu().mask.enable_sprites), labelled_box("BG Mask", nes.ppu().mask.background_on_left_edge), labelled_box("Sprites Mask", nes.ppu().mask.sprites_on_left_edge), labelled_box("Grayscale", nes.ppu().mask.grayscale), labelled_box("Intensify Red", nes.ppu().mask.em_red), labelled_box("Intensify Green", nes.ppu().mask.em_green), labelled_box("Intensify Blue", nes.ppu().mask.em_blue), ], 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 ), run_type_hex( "To Address:", self.breakpoint, DebuggerMessage::SetBreakpoint, DebuggerMessage::RunBreakpoint ), ], ] .spacing(5.), horizontal(2), 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 { if nes.run_one_clock_cycle().dbg_int || nes.halted { break; } } } fn run_until(nes: &mut NES, mut f: impl FnMut(CycleResult, &NES) -> bool, mut count: usize) { loop { let res = nes.run_one_clock_cycle(); if res.dbg_int || f(res, nes) { count -= 1; if count <= 0 { break; } } if nes.halted { 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::SetBreakpoint(n) => self.breakpoint = 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, self.instructions) } 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, 1) } DebuggerMessage::RunFrames => Self::run_n_clock_cycles(nes, self.frames * 341 * 262), DebuggerMessage::RunBreakpoint => Self::run_until(nes, |_, nes| nes.cpu.pc as usize == self.breakpoint, 1), DebuggerMessage::Run => Self::run_until(nes, |_, nes| nes.halted, 1), 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).on_submit(run.clone())).padding(2.), widget::container(button(image("./images/ic_fluent_play_24_filled.png")).on_press(run)) .padding(2.), ] .spacing(1.) .into() } fn run_type_hex<'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(hex_input(val).on_input(update).on_submit(run.clone())).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 labelled2<'a, Message: 'a>( label: &'a str, content: impl Into>, content2: impl Into>, ) -> Element<'a, Message> { row![ widget::container(text(label)).padding(2.), widget::container(content).style(bordered_box).padding(2.), widget::container(content2).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() } #[derive(Clone, Copy)] pub enum DbgImage<'a> { NameTable(&'a PPU), PatternTable(&'a PPU), Palette(&'a PPU), } impl Program for DbgImage<'_> { type State = (); fn draw( &self, _state: &Self::State, renderer: &Renderer, _theme: &T, _bounds: iced::Rectangle, _cursor: mouse::Cursor, ) -> Vec> { // const SIZE: f32 = 2.; let mut name_table_frame = Frame::new(renderer, Size::new(256. * 4. + 260. * 2., 256. * 4.)); name_table_frame.scale(2.); // println!("Position: {:?}", cursor.position()); match self { DbgImage::NameTable(ppu) => ppu.render_name_table(&mut name_table_frame), DbgImage::PatternTable(ppu) => ppu.render_pattern_tables(&mut name_table_frame), DbgImage::Palette(ppu) => ppu.render_palette(&mut name_table_frame), }; vec![name_table_frame.into_geometry()] } } impl DbgImage<'_> { fn width(&self) -> Length { match self { DbgImage::NameTable(_) => Length::Fixed(512. * 2.), DbgImage::PatternTable(_) => Length::Fixed(16. * 8. * 2.), DbgImage::Palette(_) => Length::Fixed(40. * 2.), } } fn height(&self) -> Length { match self { DbgImage::NameTable(_) => Length::Fixed(512. * 2.), DbgImage::PatternTable(_) => Length::Fixed(16. * 8. * 2. * 2.), DbgImage::Palette(_) => Length::Fixed(80. * 2.), } } fn help(&self, cursor: Point) -> Option { match self { DbgImage::NameTable(ppu) => ppu.name_cursor_info(cursor), DbgImage::PatternTable(ppu) => ppu.pattern_cursor_info(cursor), DbgImage::Palette(ppu) => ppu.palette_cursor_info(cursor), } } } struct DbgImageSetup<'a, M, T: text::Catalog> { dbg: DbgImage<'a>, image: Canvas, M, T>, text: Text<'a, T>, padding: f32, drawn: bool, // image: DbgImage<'a>, // text: te, } // pub trait Container { // // Not ideal // fn children(&self) -> &[&dyn Widget]; // } impl<'s, Message, Theme> Widget for DbgImageSetup<'s, Message, Theme> where Theme: text::Catalog + 's, { fn size(&self) -> Size { // self. Size::new(Fill, Fill) } fn layout( &mut self, tree: &mut Tree, renderer: &Renderer, limits: &iced::advanced::layout::Limits, ) -> Node { let img_node = self.image.layout(&mut tree.children[0], renderer, limits); let txt_node = Widget::::layout( &mut self.text, &mut tree.children[1], renderer, &limits.shrink(Size::new( img_node.size().width + self.padding * 2., self.padding * 2., )), ) .move_to(Point::new( img_node.size().width + self.padding, self.padding, )); Node::with_children(limits.max(), vec![img_node, txt_node]) } fn draw( &self, tree: &Tree, renderer: &mut Renderer, theme: &Theme, style: &iced::advanced::renderer::Style, layout: iced::advanced::Layout<'_>, cursor: iced::advanced::mouse::Cursor, viewport: &iced::Rectangle, ) { self.image.draw( &tree.children[0], renderer, theme, style, layout.child(0), cursor, viewport, ); Widget::::draw( &self.text, &tree.children[1], renderer, theme, style, layout.child(1), cursor, viewport, ) } fn tag(&self) -> Tag { Tag::of::>() } fn state(&self) -> State { State::new(Rc::new(String::new())) } fn children(&self) -> Vec { vec![ Tree::new(&self.image as &dyn Widget), Tree::new(&self.text as &dyn Widget), ] } fn diff(&self, tree: &mut Tree) { tree.diff_children(&[ &self.image as &dyn Widget, &self.text as &dyn Widget, ]); } fn operate( &mut self, tree: &mut Tree, layout: iced::advanced::Layout<'_>, renderer: &Renderer, operation: &mut dyn iced::advanced::widget::Operation, ) { operation.container(None, layout.bounds()); operation.traverse(&mut |op| { self.image .operate(&mut tree.children[0], layout.child(0), renderer, op); Widget::::operate( &mut self.text, &mut tree.children[1], layout.child(1), renderer, op, ); }); } fn update( &mut self, tree: &mut Tree, event: &iced::Event, layout: iced::advanced::Layout<'_>, cursor: iced::advanced::mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn iced::advanced::Clipboard, shell: &mut iced::advanced::Shell<'_, Message>, viewport: &iced::Rectangle, ) { self.image.update( &mut tree.children[0], event, layout.child(0), cursor, renderer, clipboard, shell, viewport, ); if matches!(event, iced::Event::Mouse(mouse::Event::CursorMoved { .. })) || !self.drawn { if let Some(help) = cursor .position_in(layout.child(0).bounds()) .and_then(|pos| self.dbg.help(Point::new(pos.x / 2., pos.y / 2.))) { self.text = text(help); shell.invalidate_layout(); shell.request_redraw(); } self.drawn = true; } Widget::::update( &mut self.text, &mut tree.children[1], event, layout.child(1), cursor, renderer, clipboard, shell, viewport, ); } fn mouse_interaction( &self, _tree: &iced::advanced::widget::Tree, _layout: iced::advanced::Layout<'_>, _cursor: iced::advanced::mouse::Cursor, _viewport: &iced::Rectangle, _renderer: &Renderer, ) -> iced::advanced::mouse::Interaction { iced::advanced::mouse::Interaction::default() } fn overlay<'a>( &'a mut self, _tree: &'a mut iced::advanced::widget::Tree, _layout: iced::advanced::Layout<'a>, _renderer: &Renderer, _viewport: &iced::Rectangle, _translation: iced::Vector, ) -> Option> { None } } pub fn dbg_image<'a, Message: 'a>(img: DbgImage<'a>) -> Element<'a, Message> { Element::new(DbgImageSetup { dbg: img, image: Canvas::new(img).width(img.width()).height(img.height()), text: text(""), padding: 10., drawn: false, }) }