chirp-imgui: Refactor menu definitions

This commit is contained in:
John 2023-04-29 18:15:19 -05:00
parent 7d5718f384
commit a16d7fe732
6 changed files with 393 additions and 177 deletions

192
src/bin/chirp-imgui/emu.rs Normal file
View File

@ -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<Path>) -> 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");
}
}

View File

@ -3,7 +3,7 @@
use thiserror::Error; use thiserror::Error;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub(crate) enum Error { pub enum Error {
/// Error originated in [`chirp`] /// Error originated in [`chirp`]
#[error(transparent)] #[error(transparent)]
Chirp(#[from] chirp::error::Error), Chirp(#[from] chirp::error::Error),

View File

@ -7,9 +7,16 @@
use pixels::{wgpu, PixelsContext}; use pixels::{wgpu, PixelsContext};
use std::time::Instant; use std::time::Instant;
mod about;
mod menubar; mod menubar;
use menubar::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 /// Holds state of GUI
pub(crate) struct Gui { pub(crate) struct Gui {
imgui: imgui::Context, imgui: imgui::Context,
@ -20,6 +27,17 @@ pub(crate) struct Gui {
pub menubar: Menubar, 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 { impl std::fmt::Debug for Gui {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Gui") f.debug_struct("Gui")
@ -43,18 +61,18 @@ impl Gui {
platform.attach_window( platform.attach_window(
imgui.io_mut(), imgui.io_mut(),
window, window,
imgui_winit_support::HiDpiMode::Default, imgui_winit_support::HiDpiMode::Locked(2.2),
); );
// Configure fonts // Configure fonts
// let dpi_scale = window.scale_factor(); let dpi_scale = window.scale_factor();
// let font_size = (13.0 * dpi_scale) as f32; let font_size = (13.0 * dpi_scale) as f32;
// imgui.io_mut().font_global_scale = (1.0 / dpi_scale) as f32; imgui.io_mut().font_global_scale = (1.0 / dpi_scale) as f32;
imgui imgui
.fonts() .fonts()
.add_font(&[imgui::FontSource::DefaultFontData { .add_font(&[imgui::FontSource::DefaultFontData {
config: Some(imgui::FontConfig { config: Some(imgui::FontConfig {
size_pixels: 13.0, size_pixels: font_size,
oversample_h: 2, oversample_h: 2,
oversample_v: 2, oversample_v: 2,
pixel_snap_h: true, pixel_snap_h: true,
@ -112,53 +130,12 @@ impl Gui {
self.platform.prepare_render(ui, window); self.platform.prepare_render(ui, window);
} }
let menu = &mut self.menubar; self.menubar.draw(ui);
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...");
};
// Draw windows and GUI elements here // 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.about_open {
if self.menubar.about.open { ui.open_popup("About");
ui.show_about_window(&mut self.menubar.about.open); self.menubar.about.about_open = false;
} }
// Render Dear ImGui with WGPU // Render Dear ImGui with WGPU
@ -201,17 +178,17 @@ impl Gui {
} }
} }
pub fn wants_reset(&mut self) -> bool { /// Query the state of the Gui through a unified interface
let reset = self.menubar.debug.reset; pub fn wants(&mut self, wants: Wants) -> bool {
self.menubar.debug.reset = false; match wants {
reset Wants::Quit => self.menubar.file.quit,
} Wants::Disasm => self.menubar.debug.dis,
Wants::Reset => {
pub fn wants_disassembly(&self) -> bool { let reset = self.menubar.debug.reset;
self.menubar.debug.dis self.menubar.debug.reset = false;
} reset
}
pub fn wants_quit(&self) -> bool { _ => unreachable!(),
self.menubar.file.quit }
} }
} }

View File

@ -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"));
});
}

View File

@ -1,12 +1,27 @@
//! The menubar that shows at the top of the screen //! The menubar that shows at the top of the screen
use super::Drawable;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Menubar { pub struct Menubar {
pub(super) active: bool, pub(super) active: bool,
pub file: File, pub file: File,
pub settings: Settings, pub settings: Settings,
pub debug: Debug, 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 { impl Default for Menubar {
@ -23,20 +38,52 @@ impl Default for Menubar {
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct File { pub struct File {
pub(super) settings: bool,
pub(super) reset: bool, pub(super) reset: bool,
pub(super) quit: 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)] #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Debug { pub struct Debug {
pub(super) reset: bool, pub(super) reset: bool,
pub(super) dis: 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)] #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct About { pub struct Help {
pub(super) open: bool, 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)] #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
@ -48,6 +95,31 @@ pub struct Settings {
pub(super) applied: bool, 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 { impl Settings {
pub fn target_ipf(&mut self) -> &mut usize { pub fn target_ipf(&mut self) -> &mut usize {
&mut self.target_ipf &mut self.target_ipf
@ -55,7 +127,6 @@ impl Settings {
pub fn quirks(&mut self) -> &mut chirp::Quirks { pub fn quirks(&mut self) -> &mut chirp::Quirks {
&mut self.quirks &mut self.quirks
} }
pub fn applied(&mut self) -> Option<(usize, chirp::Quirks)> { pub fn applied(&mut self) -> Option<(usize, chirp::Quirks)> {
self.applied.then_some((self.target_ipf, self.quirks)) self.applied.then_some((self.target_ipf, self.quirks))
} }

View File

@ -1,8 +1,13 @@
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![deny(clippy::all)] #![deny(clippy::all)]
#![allow(dead_code)] // TODO: finish writing the code #![allow(dead_code)] // TODO: finish writing the code
mod emu;
mod error;
mod gui;
use crate::emu::*;
use crate::gui::*; use crate::gui::*;
use chirp::*;
use core::panic; use core::panic;
use pixels::{Pixels, SurfaceTexture}; use pixels::{Pixels, SurfaceTexture};
use std::result::Result; use std::result::Result;
@ -12,23 +17,25 @@ use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder; use winit::window::WindowBuilder;
use winit_input_helper::WinitInputHelper; use winit_input_helper::WinitInputHelper;
mod error; // TODO: Make these configurable in the frontend
mod gui;
const FOREGROUND: &[u8; 4] = &0xFFFF00FF_u32.to_be_bytes(); const FOREGROUND: &[u8; 4] = &0xFFFF00FF_u32.to_be_bytes();
const BACKGROUND: &[u8; 4] = &0x623701FF_u32.to_be_bytes(); const BACKGROUND: &[u8; 4] = &0x623701FF_u32.to_be_bytes();
const INIT_SPEED: usize = 10;
/// The state of the application struct Application {
#[derive(Debug)]
struct Emulator {
gui: Gui, gui: Gui,
screen: Bus, emu: Emulator,
cpu: CPU,
ipf: usize,
colors: [[u8; 4]; 2],
} }
fn main() -> Result<(), error::Error> { 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 event_loop = EventLoop::new();
let mut input = WinitInputHelper::new(); let mut input = WinitInputHelper::new();
@ -46,33 +53,32 @@ fn main() -> Result<(), error::Error> {
Pixels::new(128, 64, surface_texture)? 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), Gui::new(&window, &pixels),
bus! {
Screen [0x000..0x100],
},
CPU::default(),
10,
); );
// set initial parameters // set initial parameters
*emu.gui.menubar.settings.target_ipf() = emu.cpu.flags.monotonic.unwrap_or(10); *app.gui.menubar.settings.target_ipf() = INIT_SPEED;
*emu.gui.menubar.settings.quirks() = emu.cpu.flags.quirks; *app.gui.menubar.settings.quirks() = app.emu.quirks();
if let Some(path) = std::env::args().nth(1) {
emu.cpu.load_program(path)?;
} else {
panic!("Supply a rom!");
}
// Run event loop // Run event loop
event_loop.run(move |event, _, control_flow| { 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> { let redraw = |gui: &mut Gui, pixels: &mut Pixels| -> Result<(), error::Error> {
// Prepare gui for redraw // Prepare gui for redraw
gui.prepare(&window)?; gui.prepare(&window)?;
// Render everything together // Render everything together
pixels.render_with(|encoder, render_target, context| { pixels.render_with(|encoder, render_target, context| {
// Render the world texture // Render the emulator's screen
context.scaling_renderer.render(encoder, render_target); context.scaling_renderer.render(encoder, render_target);
// Render Dear ImGui // Render Dear ImGui
gui.render(&window, encoder, render_target, context)?; gui.render(&window, encoder, render_target, context)?;
@ -81,7 +87,7 @@ fn main() -> Result<(), error::Error> {
Ok(()) Ok(())
}; };
let mut handle_events = |state: &mut Emulator, let mut handle_events = |state: &mut Application,
pixels: &mut Pixels, pixels: &mut Pixels,
control_flow: &mut ControlFlow| control_flow: &mut ControlFlow|
-> Result<(), error::Error> { -> Result<(), error::Error> {
@ -89,29 +95,30 @@ fn main() -> Result<(), error::Error> {
if input.update(&event) { if input.update(&event) {
use VirtualKeyCode::*; use VirtualKeyCode::*;
match_pressed!( match input { match_pressed!( match input {
Escape | input.quit() | state.gui.wants_quit() => { Escape | input.quit() | state.gui.wants(Wants::Quit) => {
*control_flow = ControlFlow::Exit; *control_flow = ControlFlow::Exit;
return Ok(()); return Ok(());
}, },
F1 => state.cpu.dump(), F1 => state.emu.print_registers(),
F2 => state.screen.print_screen()?, F2 => state.emu.print_screen()?,
F3 => eprintln!("TODO: Dump screen"), F3 => state.emu.dump_screen()?,
F4 => state.cpu.flags.debug(), F4 => state.emu.is_disasm(),
F5 => state.cpu.flags.pause(), F5 => state.emu.pause(),
F6 => state.cpu.singlestep(&mut state.screen)?, F6 => state.emu.singlestep()?,
F7 => state.cpu.set_break(state.cpu.pc()), F7 => state.emu.set_break(),
F8 => state.cpu.unset_break(state.cpu.pc()), F8 => state.emu.unset_break(),
Delete => state.cpu.reset(), Delete => state.emu.soft_reset(),
F11 => window.set_maximized(!window.is_maximized()), Insert => state.emu.hard_reset(),
F11 => toggle_fullscreen(),
LAlt => state.gui.show_menubar(None), LAlt => state.gui.show_menubar(None),
}); });
state.input(&input)?; state.emu.input(&input)?;
// Apply settings // Apply settings
if let Some((ipf, quirks)) = state.gui.menubar.settings.applied() { if let Some((ipf, quirks)) = state.gui.menubar.settings.applied() {
state.ipf = ipf; state.emu.ipf = ipf;
state.cpu.flags.monotonic = Some(ipf); state.emu.set_quirks(quirks);
state.cpu.flags.quirks = quirks; println!("'A");
} }
// Update the scale factor // Update the scale factor
@ -130,14 +137,14 @@ fn main() -> Result<(), error::Error> {
} }
} }
state.cpu.flags.debug = state.gui.wants_disassembly(); state.emu.set_disasm(state.gui.wants(Wants::Disasm));
if state.gui.wants_reset() { if state.gui.wants(Wants::Reset) {
state.cpu.reset(); state.emu.hard_reset();
} }
// Run the game loop // Run the game loop
state.update()?; state.emu.update()?;
state.draw(pixels)?; state.emu.draw(pixels)?;
// redraw the window // redraw the window
window.request_redraw(); window.request_redraw();
@ -146,81 +153,22 @@ fn main() -> Result<(), error::Error> {
}; };
if let Event::RedrawRequested(_) = event { 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}"); eprintln!("{e}");
*control_flow = ControlFlow::Exit; *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}"); eprintln!("{e}");
*control_flow = ControlFlow::Exit; *control_flow = ControlFlow::Exit;
} }
}); });
} }
impl Emulator { impl Application {
pub fn new(gui: Gui, mem: Bus, cpu: CPU, ipf: usize) -> Self { fn new(emu: Emulator, gui: Gui) -> Self {
Self { Self { gui, emu }
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(())
} }
} }