diff --git a/src/bin/chirp-imgui/emu.rs b/src/bin/chirp-imgui/emu.rs new file mode 100644 index 0000000..76a0007 --- /dev/null +++ b/src/bin/chirp-imgui/emu.rs @@ -0,0 +1,192 @@ +//! The emulator's state, including the screen data + +use super::{error, BACKGROUND, FOREGROUND}; +use chirp::{bus, Bus, Screen, CPU}; +use pixels::Pixels; +use std::path::{Path, PathBuf}; +use winit::event::VirtualKeyCode; +use winit_input_helper::WinitInputHelper; + +/// The state of the application +#[derive(Debug)] +pub struct Emulator { + screen: Bus, + cpu: CPU, + pub ipf: usize, + pub rom: PathBuf, + pub colors: [[u8; 4]; 2], +} + +impl Emulator { + /// Constructs a new CPU, with the provided ROM loaded at 0x0200 + /// + /// # Panics + /// Panics if the provided ROM does not exist + pub fn new(ipf: usize, rom: impl AsRef) -> Self { + let screen = bus! { + Screen [0x000..0x100], + }; + let mut cpu = CPU::default(); + cpu.load_program(&rom).expect("Loaded file MUST exist."); + Self { + cpu, + ipf, + rom: rom.as_ref().into(), + screen, + colors: [*FOREGROUND, *BACKGROUND], + } + } + /// Runs a single epoch + pub fn update(&mut self) -> Result<(), error::Error> { + self.cpu.multistep(&mut self.screen, self.ipf)?; + Ok(()) + } + /// Rasterizes the screen into a [Pixels] buffer + pub fn draw(&mut self, pixels: &mut Pixels) -> Result<(), error::Error> { + if let Some(screen) = self.screen.get_region(Screen) { + let len_log2 = screen.len().ilog2() / 2; + #[allow(unused_variables)] + let (width, height) = (2u32.pow(len_log2 + 2), 2u32.pow(len_log2 + 1)); + pixels.resize_buffer(width, height)?; + for (idx, pixel) in pixels.frame_mut().iter_mut().enumerate() { + let (byte, bit, component) = (idx >> 5, (idx >> 2) % 8, idx & 0b11); + *pixel = if screen[byte] & (0x80 >> bit) > 0 { + self.colors[0][component] + } else { + self.colors[1][component] + } + } + } + Ok(()) + } + /// Processes keyboard input for the Emulator + pub fn input(&mut self, input: &WinitInputHelper) -> Result<(), error::Error> { + const KEYMAP: [VirtualKeyCode; 16] = [ + VirtualKeyCode::X, + VirtualKeyCode::Key1, + VirtualKeyCode::Key2, + VirtualKeyCode::Key3, + VirtualKeyCode::Q, + VirtualKeyCode::W, + VirtualKeyCode::E, + VirtualKeyCode::A, + VirtualKeyCode::S, + VirtualKeyCode::D, + VirtualKeyCode::Z, + VirtualKeyCode::C, + VirtualKeyCode::Key4, + VirtualKeyCode::R, + VirtualKeyCode::F, + VirtualKeyCode::V, + ]; + for (id, &key) in KEYMAP.iter().enumerate() { + if input.key_released(key) { + self.cpu.release(id)?; + } + if input.key_pressed(key) { + self.cpu.press(id)?; + } + } + Ok(()) + } + + pub fn quirks(&mut self) -> chirp::Quirks { + self.cpu.flags.quirks + } + pub fn set_quirks(&mut self, quirks: chirp::Quirks) { + self.cpu.flags.quirks = quirks + } + /// Prints the CPU registers and cycle count to stderr + pub fn print_registers(&self) { + self.cpu.dump() + } + /// Prints the screen (using the highest resolution available printer) to stdout + pub fn print_screen(&self) -> Result<(), error::Error> { + self.screen.print_screen()?; + Ok(()) + } + /// Dumps the raw screen bytes to a file named `{rom}_{cycle}.bin`, + /// or, failing that, `screen_dump.bin` + pub fn dump_screen(&self) -> Result<(), error::Error> { + let mut path = PathBuf::new().join(format!( + "{}_{}.bin", + self.rom.file_stem().unwrap_or_default().to_string_lossy(), + self.cpu.cycle() + )); + path.set_extension("bin"); + if std::fs::write( + &path, + self.screen + .get_region(Screen) + .expect("Region::Screen should exist"), + ) + .is_ok() + { + eprintln!("Saved to {}", &path.display()); + } else if std::fs::write( + "screen_dump.bin", + self.screen + .get_region(Screen) + .expect("Region::Screen should exist"), + ) + .is_ok() + { + eprintln!("Saved to screen_dump.bin"); + } else { + eprintln!("Failed to dump screen to file.") + } + Ok(()) + } + /// Sets live disassembly + pub fn set_disasm(&mut self, enabled: bool) { + self.cpu.flags.debug = enabled; + } + /// Checks live disassembly + pub fn is_disasm(&mut self) { + eprintln!( + "Live Disassembly {}abled", + if self.cpu.flags.debug { "En" } else { "Dis" } + ); + } + /// Toggles emulator pause + pub fn pause(&mut self) { + self.cpu.flags.pause(); + eprintln!("{}aused", if self.cpu.flags.pause { "P" } else { "Unp" }); + } + /// Single-steps the emulator, pausing afterward + pub fn singlestep(&mut self) -> Result<(), error::Error> { + self.cpu.singlestep(&mut self.screen)?; + Ok(()) + } + /// Sets a breakpoint at the current address + pub fn set_break(&mut self) { + self.cpu.set_break(self.cpu.pc()); + eprintln!("Set breakpoint at {}", self.cpu.pc()); + } + /// Unsets a breakpoint at the current address + pub fn unset_break(&mut self) { + self.cpu.unset_break(self.cpu.pc()); + eprintln!("Unset breakpoint at {}", self.cpu.pc()); + } + /// Soft-resets the CPU, keeping the program in memory + pub fn soft_reset(&mut self) { + self.cpu.reset(); + self.screen.clear_region(Screen); + eprintln!("Soft Reset"); + } + + /// Creates a new CPU with the current CPU's flags + pub fn hard_reset(&mut self) { + self.cpu.reset(); + self.screen.clear_region(Screen); + // keep the flags + let flags = self.cpu.flags.clone(); + // instantiate a completely new CPU, and reload the ROM from disk + self.cpu = CPU::default(); + self.cpu.flags = flags; + self.cpu + .load_program(&self.rom) + .expect("Previously loaded ROM no longer exists (was it moved?)"); + eprintln!("Hard Reset"); + } +} diff --git a/src/bin/chirp-imgui/error.rs b/src/bin/chirp-imgui/error.rs index 722f58c..22d3a29 100644 --- a/src/bin/chirp-imgui/error.rs +++ b/src/bin/chirp-imgui/error.rs @@ -3,7 +3,7 @@ use thiserror::Error; #[derive(Debug, Error)] -pub(crate) enum Error { +pub enum Error { /// Error originated in [`chirp`] #[error(transparent)] Chirp(#[from] chirp::error::Error), diff --git a/src/bin/chirp-imgui/gui.rs b/src/bin/chirp-imgui/gui.rs index 8f9427b..588ece6 100644 --- a/src/bin/chirp-imgui/gui.rs +++ b/src/bin/chirp-imgui/gui.rs @@ -7,9 +7,16 @@ use pixels::{wgpu, PixelsContext}; use std::time::Instant; +mod about; mod menubar; use menubar::Menubar; +/// Lays out the imgui widgets for a thing +pub trait Drawable { + // Lay out the ImGui widgets for this thing + fn draw(&mut self, ui: &imgui::Ui); +} + /// Holds state of GUI pub(crate) struct Gui { imgui: imgui::Context, @@ -20,6 +27,17 @@ pub(crate) struct Gui { pub menubar: Menubar, } +/// Queries the state of the [Gui] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[non_exhaustive] +pub enum Wants { + Quit, + Disasm, + SoftReset, + HardReset, + Reset, +} + impl std::fmt::Debug for Gui { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Gui") @@ -43,18 +61,18 @@ impl Gui { platform.attach_window( imgui.io_mut(), window, - imgui_winit_support::HiDpiMode::Default, + imgui_winit_support::HiDpiMode::Locked(2.2), ); // Configure fonts - // let dpi_scale = window.scale_factor(); - // let font_size = (13.0 * dpi_scale) as f32; - // imgui.io_mut().font_global_scale = (1.0 / dpi_scale) as f32; + let dpi_scale = window.scale_factor(); + let font_size = (13.0 * dpi_scale) as f32; + imgui.io_mut().font_global_scale = (1.0 / dpi_scale) as f32; imgui .fonts() .add_font(&[imgui::FontSource::DefaultFontData { config: Some(imgui::FontConfig { - size_pixels: 13.0, + size_pixels: font_size, oversample_h: 2, oversample_v: 2, pixel_snap_h: true, @@ -112,53 +130,12 @@ impl Gui { self.platform.prepare_render(ui, window); } - let menu = &mut self.menubar; - let settings_menu = || { - use chirp::Mode::*; - let settings = &mut menu.settings; - const MODES: [chirp::Mode; 3] = [Chip8, SChip, XOChip]; - if ui.combo_simple_string("Mode", &mut settings.mode_index, &MODES) { - settings.quirks = MODES[settings.mode_index].into(); - settings.applied = true; - } - settings.applied = { - ui.input_scalar("IPF", &mut settings.target_ipf) - .chars_decimal(true) - .build() - | ui.checkbox("Bin-ops don't clear vF", &mut settings.quirks.bin_ops) - | ui.checkbox("DMA doesn't modify I", &mut settings.quirks.dma_inc) - | ui.checkbox("Draw calls are instant", &mut settings.quirks.draw_wait) - | ui.checkbox("Screen wraps at edge", &mut settings.quirks.screen_wrap) - | ui.checkbox("Shift ops ignore vY", &mut settings.quirks.shift) - | ui.checkbox("Jumps behave eratically", &mut settings.quirks.stupid_jumps) - } - }; - // let file_menu = || { - // let file = &mut menu.file; - // file.quit = ui.menu_item("Quit"); - // }; - let debug_menu = || { - menu.debug.reset = ui.menu_item("Reset"); - ui.checkbox("Live Disassembly", &mut menu.debug.dis); - }; - let help_menu = || { - let about = &mut menu.about; - about.open = ui.menu_item("About..."); - }; - + self.menubar.draw(ui); // Draw windows and GUI elements here - if menu.active { - ui.main_menu_bar(|| { - //ui.menu("File", file_menu); - ui.menu("Settings", settings_menu); - ui.menu("Debug", debug_menu); - ui.menu("Help", help_menu); - }); - } - if menu.file.settings {} - if self.menubar.about.open { - ui.show_about_window(&mut self.menubar.about.open); + if self.menubar.about.about_open { + ui.open_popup("About"); + self.menubar.about.about_open = false; } // Render Dear ImGui with WGPU @@ -201,17 +178,17 @@ impl Gui { } } - pub fn wants_reset(&mut self) -> bool { - let reset = self.menubar.debug.reset; - self.menubar.debug.reset = false; - reset - } - - pub fn wants_disassembly(&self) -> bool { - self.menubar.debug.dis - } - - pub fn wants_quit(&self) -> bool { - self.menubar.file.quit + /// Query the state of the Gui through a unified interface + pub fn wants(&mut self, wants: Wants) -> bool { + match wants { + Wants::Quit => self.menubar.file.quit, + Wants::Disasm => self.menubar.debug.dis, + Wants::Reset => { + let reset = self.menubar.debug.reset; + self.menubar.debug.reset = false; + reset + } + _ => unreachable!(), + } } } diff --git a/src/bin/chirp-imgui/gui/about.rs b/src/bin/chirp-imgui/gui/about.rs new file mode 100644 index 0000000..059aaf9 --- /dev/null +++ b/src/bin/chirp-imgui/gui/about.rs @@ -0,0 +1,28 @@ +//! Information about the crate at compile time + +use imgui::Ui; + +/// Project authors +const AUTHORS: Option<&str> = option_env!("CARGO_PKG_AUTHORS"); +/// Compiled binary name +const BIN: Option<&str> = option_env!("CARGO_BIN_NAME"); +/// Crate description +const DESCRIPTION: Option<&str> = option_env!("CARGO_PKG_DESCRIPTION"); +/// Crate name +const NAME: Option<&str> = option_env!("CARGO_PKG_NAME"); +/// Crate repository +const REPO: Option<&str> = option_env!("CARGO_PKG_REPOSITORY"); +/// Crate version +const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION"); + +pub(crate) fn about(ui: &Ui) { + ui.popup("About", || { + ui.text(format!( + "{} v{}", + NAME.unwrap_or("Chirp"), + VERSION.unwrap_or("None"), + )); + ui.text(format!("Crafted by: {}", AUTHORS.unwrap_or("some people"))); + ui.text(REPO.unwrap_or("Repo unavailable")); + }); +} diff --git a/src/bin/chirp-imgui/gui/menubar.rs b/src/bin/chirp-imgui/gui/menubar.rs index 6845cbb..08f4a61 100644 --- a/src/bin/chirp-imgui/gui/menubar.rs +++ b/src/bin/chirp-imgui/gui/menubar.rs @@ -1,12 +1,27 @@ //! The menubar that shows at the top of the screen +use super::Drawable; + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Menubar { pub(super) active: bool, pub file: File, pub settings: Settings, pub debug: Debug, - pub about: About, + pub about: Help, +} + +impl Drawable for Menubar { + fn draw(&mut self, ui: &imgui::Ui) { + if self.active { + ui.main_menu_bar(|| { + self.file.draw(ui); + self.settings.draw(ui); + self.debug.draw(ui); + self.about.draw(ui); + }) + } + } } impl Default for Menubar { @@ -23,20 +38,52 @@ impl Default for Menubar { #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct File { - pub(super) settings: bool, pub(super) reset: bool, pub(super) quit: bool, } +impl Drawable for File { + fn draw(&mut self, ui: &imgui::Ui) { + ui.menu("File", || { + self.reset = ui.menu_item("Reset"); + self.quit = ui.menu_item("Quit"); + }) + } +} + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Debug { pub(super) reset: bool, pub(super) dis: bool, } +impl Drawable for Debug { + fn draw(&mut self, ui: &imgui::Ui) { + ui.menu("Debug", || { + self.reset = ui.menu_item("Reset"); + ui.checkbox("Live Disassembly", &mut self.dis); + }) + } +} + +impl Debug { + pub fn reset(&self) -> bool { + self.reset + } + pub fn dis(&self) -> bool { + self.dis + } +} + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct About { - pub(super) open: bool, +pub struct Help { + pub(super) about_open: bool, +} + +impl Drawable for Help { + fn draw(&mut self, ui: &imgui::Ui) { + ui.menu("Help", || self.about_open = ui.menu_item("About...")) + } } #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -48,6 +95,31 @@ pub struct Settings { pub(super) applied: bool, } +impl Drawable for Settings { + fn draw(&mut self, ui: &imgui::Ui) { + self.applied = false; + ui.menu("Settings", || { + use chirp::Mode::*; + const MODES: [chirp::Mode; 3] = [Chip8, SChip, XOChip]; + if ui.combo_simple_string("Mode", &mut self.mode_index, &MODES) { + self.quirks = MODES[self.mode_index].into(); + self.applied |= true; + } + self.applied |= { + ui.input_scalar("IPF", &mut self.target_ipf) + .chars_decimal(true) + .build() + | ui.checkbox("Bin-ops don't clear vF", &mut self.quirks.bin_ops) + | ui.checkbox("DMA doesn't modify I", &mut self.quirks.dma_inc) + | ui.checkbox("Draw calls are instant", &mut self.quirks.draw_wait) + | ui.checkbox("Screen wraps at edge", &mut self.quirks.screen_wrap) + | ui.checkbox("Shift ops ignore vY", &mut self.quirks.shift) + | ui.checkbox("Jumps behave eratically", &mut self.quirks.stupid_jumps) + } + }) + } +} + impl Settings { pub fn target_ipf(&mut self) -> &mut usize { &mut self.target_ipf @@ -55,7 +127,6 @@ impl Settings { pub fn quirks(&mut self) -> &mut chirp::Quirks { &mut self.quirks } - pub fn applied(&mut self) -> Option<(usize, chirp::Quirks)> { self.applied.then_some((self.target_ipf, self.quirks)) } diff --git a/src/bin/chirp-imgui/main.rs b/src/bin/chirp-imgui/main.rs index a1ff4a1..97a353e 100644 --- a/src/bin/chirp-imgui/main.rs +++ b/src/bin/chirp-imgui/main.rs @@ -1,8 +1,13 @@ #![forbid(unsafe_code)] #![deny(clippy::all)] #![allow(dead_code)] // TODO: finish writing the code + +mod emu; +mod error; +mod gui; + +use crate::emu::*; use crate::gui::*; -use chirp::*; use core::panic; use pixels::{Pixels, SurfaceTexture}; use std::result::Result; @@ -12,23 +17,25 @@ use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::WindowBuilder; use winit_input_helper::WinitInputHelper; -mod error; -mod gui; +// TODO: Make these configurable in the frontend const FOREGROUND: &[u8; 4] = &0xFFFF00FF_u32.to_be_bytes(); const BACKGROUND: &[u8; 4] = &0x623701FF_u32.to_be_bytes(); +const INIT_SPEED: usize = 10; -/// The state of the application -#[derive(Debug)] -struct Emulator { +struct Application { gui: Gui, - screen: Bus, - cpu: CPU, - ipf: usize, - colors: [[u8; 4]; 2], + emu: Emulator, } fn main() -> Result<(), error::Error> { + let rom_path; + if let Some(path) = std::env::args().nth(1) { + rom_path = path; + } else { + panic!("Supply a rom!"); + } + let event_loop = EventLoop::new(); let mut input = WinitInputHelper::new(); @@ -46,33 +53,32 @@ fn main() -> Result<(), error::Error> { Pixels::new(128, 64, surface_texture)? }; - let mut emu = Emulator::new( + let mut app = Application::new( + Emulator::new(INIT_SPEED, rom_path), Gui::new(&window, &pixels), - bus! { - Screen [0x000..0x100], - }, - CPU::default(), - 10, ); // set initial parameters - *emu.gui.menubar.settings.target_ipf() = emu.cpu.flags.monotonic.unwrap_or(10); - *emu.gui.menubar.settings.quirks() = emu.cpu.flags.quirks; - if let Some(path) = std::env::args().nth(1) { - emu.cpu.load_program(path)?; - } else { - panic!("Supply a rom!"); - } + *app.gui.menubar.settings.target_ipf() = INIT_SPEED; + *app.gui.menubar.settings.quirks() = app.emu.quirks(); // Run event loop event_loop.run(move |event, _, control_flow| { + let toggle_fullscreen = || { + window.set_fullscreen(if window.fullscreen().is_some() { + None + } else { + Some(winit::window::Fullscreen::Borderless(None)) + }) + }; + let redraw = |gui: &mut Gui, pixels: &mut Pixels| -> Result<(), error::Error> { // Prepare gui for redraw gui.prepare(&window)?; // Render everything together pixels.render_with(|encoder, render_target, context| { - // Render the world texture + // Render the emulator's screen context.scaling_renderer.render(encoder, render_target); // Render Dear ImGui gui.render(&window, encoder, render_target, context)?; @@ -81,7 +87,7 @@ fn main() -> Result<(), error::Error> { Ok(()) }; - let mut handle_events = |state: &mut Emulator, + let mut handle_events = |state: &mut Application, pixels: &mut Pixels, control_flow: &mut ControlFlow| -> Result<(), error::Error> { @@ -89,29 +95,30 @@ fn main() -> Result<(), error::Error> { if input.update(&event) { use VirtualKeyCode::*; match_pressed!( match input { - Escape | input.quit() | state.gui.wants_quit() => { + Escape | input.quit() | state.gui.wants(Wants::Quit) => { *control_flow = ControlFlow::Exit; return Ok(()); }, - F1 => state.cpu.dump(), - F2 => state.screen.print_screen()?, - F3 => eprintln!("TODO: Dump screen"), - F4 => state.cpu.flags.debug(), - F5 => state.cpu.flags.pause(), - F6 => state.cpu.singlestep(&mut state.screen)?, - F7 => state.cpu.set_break(state.cpu.pc()), - F8 => state.cpu.unset_break(state.cpu.pc()), - Delete => state.cpu.reset(), - F11 => window.set_maximized(!window.is_maximized()), + F1 => state.emu.print_registers(), + F2 => state.emu.print_screen()?, + F3 => state.emu.dump_screen()?, + F4 => state.emu.is_disasm(), + F5 => state.emu.pause(), + F6 => state.emu.singlestep()?, + F7 => state.emu.set_break(), + F8 => state.emu.unset_break(), + Delete => state.emu.soft_reset(), + Insert => state.emu.hard_reset(), + F11 => toggle_fullscreen(), LAlt => state.gui.show_menubar(None), }); - state.input(&input)?; + state.emu.input(&input)?; // Apply settings if let Some((ipf, quirks)) = state.gui.menubar.settings.applied() { - state.ipf = ipf; - state.cpu.flags.monotonic = Some(ipf); - state.cpu.flags.quirks = quirks; + state.emu.ipf = ipf; + state.emu.set_quirks(quirks); + println!("'A"); } // Update the scale factor @@ -130,14 +137,14 @@ fn main() -> Result<(), error::Error> { } } - state.cpu.flags.debug = state.gui.wants_disassembly(); - if state.gui.wants_reset() { - state.cpu.reset(); + state.emu.set_disasm(state.gui.wants(Wants::Disasm)); + if state.gui.wants(Wants::Reset) { + state.emu.hard_reset(); } // Run the game loop - state.update()?; - state.draw(pixels)?; + state.emu.update()?; + state.emu.draw(pixels)?; // redraw the window window.request_redraw(); @@ -146,81 +153,22 @@ fn main() -> Result<(), error::Error> { }; if let Event::RedrawRequested(_) = event { - if let Err(e) = redraw(&mut emu.gui, &mut pixels) { + if let Err(e) = redraw(&mut app.gui, &mut pixels) { eprintln!("{e}"); *control_flow = ControlFlow::Exit; } } - if let Err(e) = handle_events(&mut emu, &mut pixels, control_flow) { + if let Err(e) = handle_events(&mut app, &mut pixels, control_flow) { eprintln!("{e}"); *control_flow = ControlFlow::Exit; } }); } -impl Emulator { - pub fn new(gui: Gui, mem: Bus, cpu: CPU, ipf: usize) -> Self { - Self { - gui, - screen: mem, - cpu, - ipf, - colors: [*FOREGROUND, *BACKGROUND], - } - } - - pub fn update(&mut self) -> Result<(), error::Error> { - self.cpu.multistep(&mut self.screen, self.ipf)?; - Ok(()) - } - - pub fn draw(&mut self, pixels: &mut Pixels) -> Result<(), error::Error> { - if let Some(screen) = self.screen.get_region(Screen) { - let len_log2 = screen.len().ilog2() / 2; - #[allow(unused_variables)] - let (width, height) = (2u32.pow(len_log2 + 2), 2u32.pow(len_log2 + 1)); - pixels.resize_buffer(width, height)?; - for (idx, pixel) in pixels.frame_mut().iter_mut().enumerate() { - let (byte, bit, component) = (idx >> 5, (idx >> 2) % 8, idx & 0b11); - *pixel = if screen[byte] & (0x80 >> bit) > 0 { - self.colors[0][component] - } else { - self.colors[1][component] - } - } - } - Ok(()) - } - - pub fn input(&mut self, input: &WinitInputHelper) -> Result<(), error::Error> { - const KEYMAP: [VirtualKeyCode; 16] = [ - VirtualKeyCode::X, - VirtualKeyCode::Key1, - VirtualKeyCode::Key2, - VirtualKeyCode::Key3, - VirtualKeyCode::Q, - VirtualKeyCode::W, - VirtualKeyCode::E, - VirtualKeyCode::A, - VirtualKeyCode::S, - VirtualKeyCode::D, - VirtualKeyCode::Z, - VirtualKeyCode::C, - VirtualKeyCode::Key4, - VirtualKeyCode::R, - VirtualKeyCode::F, - VirtualKeyCode::V, - ]; - for (id, &key) in KEYMAP.iter().enumerate() { - if input.key_released(key) { - self.cpu.release(id)?; - } - if input.key_pressed(key) { - self.cpu.press(id)?; - } - } - Ok(()) +impl Application { + fn new(emu: Emulator, gui: Gui) -> Self { + Self { gui, emu } } }