chirp-imgui: First prototype, using pixels demo as base
This commit is contained in:
parent
c1219e60f0
commit
e9f8d917a4
@ -1,26 +0,0 @@
|
|||||||
#![allow(unused_imports)]
|
|
||||||
use chirp::*;
|
|
||||||
#[cfg(features = "iced")]
|
|
||||||
use iced::{
|
|
||||||
executor, time, window, Alignment, Application, Command, Element, Length, Settings,
|
|
||||||
Subscription,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(features = "iced")]
|
|
||||||
fn main() -> iced::Result {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(features = "iced"))]
|
|
||||||
fn main() -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// TODO: `impl Application for Emulator {}`
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
|
||||||
struct Emulator {
|
|
||||||
mem: Bus,
|
|
||||||
cpu: CPU,
|
|
||||||
fps: f64,
|
|
||||||
ipf: usize,
|
|
||||||
}
|
|
28
src/bin/chirp-imgui/error.rs
Normal file
28
src/bin/chirp-imgui/error.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
//! Error type for chirp-imgui
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub(crate) enum Error {
|
||||||
|
/// Error originated in [`chirp`]
|
||||||
|
#[error(transparent)]
|
||||||
|
Chirp(#[from] chirp::error::Error),
|
||||||
|
/// Error originated in [`std::io`]
|
||||||
|
#[error(transparent)]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
/// Error originated in [`pixels`]
|
||||||
|
#[error(transparent)]
|
||||||
|
Pixels(#[from] pixels::Error),
|
||||||
|
/// Error originated in [`pixels`]
|
||||||
|
#[error(transparent)]
|
||||||
|
PixelsTexture(#[from] pixels::TextureError),
|
||||||
|
/// Error originated from [`winit::error::OsError`]
|
||||||
|
#[error(transparent)]
|
||||||
|
WinitOs(#[from] winit::error::OsError),
|
||||||
|
/// Error originated from [`winit::error::ExternalError`]
|
||||||
|
#[error(transparent)]
|
||||||
|
WinitExternal(#[from] winit::error::ExternalError),
|
||||||
|
/// Error originated from [`winit::error::NotSupportedError`]
|
||||||
|
#[error(transparent)]
|
||||||
|
WinitNotSupported(#[from] winit::error::NotSupportedError),
|
||||||
|
}
|
217
src/bin/chirp-imgui/gui.rs
Normal file
217
src/bin/chirp-imgui/gui.rs
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
//! Represents the Dear Imgui
|
||||||
|
//!
|
||||||
|
//! Adapted from the [ImGui-winit Example]
|
||||||
|
//!
|
||||||
|
//! [ImGui-winit Example]: https://github.com/parasyte/pixels/blob/main/examples/imgui-winit/src/gui.rs
|
||||||
|
|
||||||
|
use pixels::{wgpu, PixelsContext};
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
mod menubar;
|
||||||
|
use menubar::Menubar;
|
||||||
|
|
||||||
|
/// Holds state of GUI
|
||||||
|
pub(crate) struct Gui {
|
||||||
|
imgui: imgui::Context,
|
||||||
|
platform: imgui_winit_support::WinitPlatform,
|
||||||
|
renderer: imgui_wgpu::Renderer,
|
||||||
|
last_frame: Instant,
|
||||||
|
last_cursor: Option<imgui::MouseCursor>,
|
||||||
|
pub menubar: Menubar,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Gui {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("Gui")
|
||||||
|
.field("imgui", &self.imgui)
|
||||||
|
.field("platform", &self.platform)
|
||||||
|
.field("last_frame", &self.last_frame)
|
||||||
|
.field("last_cursor", &self.last_cursor)
|
||||||
|
.field("menubar", &self.menubar)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gui {
|
||||||
|
pub fn new(window: &winit::window::Window, pixels: &pixels::Pixels) -> Self {
|
||||||
|
// Create Dear Imgui context
|
||||||
|
let mut imgui = imgui::Context::create();
|
||||||
|
imgui.set_ini_filename(None);
|
||||||
|
|
||||||
|
// winit init
|
||||||
|
let mut platform = imgui_winit_support::WinitPlatform::init(&mut imgui);
|
||||||
|
platform.attach_window(
|
||||||
|
imgui.io_mut(),
|
||||||
|
window,
|
||||||
|
imgui_winit_support::HiDpiMode::Default,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
imgui
|
||||||
|
.fonts()
|
||||||
|
.add_font(&[imgui::FontSource::DefaultFontData {
|
||||||
|
config: Some(imgui::FontConfig {
|
||||||
|
size_pixels: 13.0,
|
||||||
|
oversample_h: 2,
|
||||||
|
oversample_v: 2,
|
||||||
|
pixel_snap_h: true,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
}]);
|
||||||
|
|
||||||
|
// Create WGPU renderer
|
||||||
|
let renderer = imgui_wgpu::Renderer::new(
|
||||||
|
&mut imgui,
|
||||||
|
pixels.device(),
|
||||||
|
pixels.queue(),
|
||||||
|
imgui_wgpu::RendererConfig {
|
||||||
|
texture_format: pixels.render_texture_format(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Return Gui context
|
||||||
|
Self {
|
||||||
|
imgui,
|
||||||
|
platform,
|
||||||
|
renderer,
|
||||||
|
last_frame: Instant::now(),
|
||||||
|
last_cursor: None,
|
||||||
|
menubar: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepare Dear ImGui.
|
||||||
|
pub(crate) fn prepare(
|
||||||
|
&mut self,
|
||||||
|
window: &winit::window::Window,
|
||||||
|
) -> Result<(), winit::error::ExternalError> {
|
||||||
|
// Prepare Dear ImGui
|
||||||
|
let now = Instant::now();
|
||||||
|
self.imgui.io_mut().update_delta_time(now - self.last_frame);
|
||||||
|
self.last_frame = now;
|
||||||
|
self.platform.prepare_frame(self.imgui.io_mut(), window)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn render(
|
||||||
|
&mut self,
|
||||||
|
window: &winit::window::Window,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
render_target: &wgpu::TextureView,
|
||||||
|
context: &PixelsContext,
|
||||||
|
) -> imgui_wgpu::RendererResult<()> {
|
||||||
|
// Start a new Dear ImGui frame and update the cursor
|
||||||
|
let ui = self.imgui.new_frame();
|
||||||
|
|
||||||
|
let mouse_cursor = ui.mouse_cursor();
|
||||||
|
if self.last_cursor != mouse_cursor {
|
||||||
|
self.last_cursor = mouse_cursor;
|
||||||
|
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...");
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render Dear ImGui with WGPU
|
||||||
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: Some("imgui"),
|
||||||
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
|
view: render_target,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Load,
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
})],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.renderer.render(
|
||||||
|
self.imgui.render(),
|
||||||
|
&context.queue,
|
||||||
|
&context.device,
|
||||||
|
&mut rpass,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle any outstanding events
|
||||||
|
pub fn handle_event(
|
||||||
|
&mut self,
|
||||||
|
window: &winit::window::Window,
|
||||||
|
event: &winit::event::Event<()>,
|
||||||
|
) {
|
||||||
|
self.platform
|
||||||
|
.handle_event(self.imgui.io_mut(), window, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shows or hides the [Menubar]. If `visible` is [None], toggles visibility.
|
||||||
|
pub fn show_menubar(&mut self, visible: Option<bool>) {
|
||||||
|
match visible {
|
||||||
|
Some(visible) => self.menubar.active = visible,
|
||||||
|
None => self.menubar.active ^= true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
62
src/bin/chirp-imgui/gui/menubar.rs
Normal file
62
src/bin/chirp-imgui/gui/menubar.rs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
//! The menubar that shows at the top of the screen
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Menubar {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
active: true,
|
||||||
|
file: Default::default(),
|
||||||
|
settings: Default::default(),
|
||||||
|
debug: Default::default(),
|
||||||
|
about: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct File {
|
||||||
|
pub(super) settings: bool,
|
||||||
|
pub(super) reset: bool,
|
||||||
|
pub(super) quit: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct Debug {
|
||||||
|
pub(super) reset: bool,
|
||||||
|
pub(super) dis: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct About {
|
||||||
|
pub(super) open: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct Settings {
|
||||||
|
pub(super) target_ipf: usize,
|
||||||
|
pub(super) quirks: chirp::Quirks,
|
||||||
|
pub(super) mode_index: usize,
|
||||||
|
pub(super) colors: [[u8; 4]; 2],
|
||||||
|
pub(super) applied: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Settings {
|
||||||
|
pub fn target_ipf(&mut self) -> &mut usize {
|
||||||
|
&mut self.target_ipf
|
||||||
|
}
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
247
src/bin/chirp-imgui/main.rs
Normal file
247
src/bin/chirp-imgui/main.rs
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
#![forbid(unsafe_code)]
|
||||||
|
#![deny(clippy::all)]
|
||||||
|
#![allow(dead_code)] // TODO: finish writing the code
|
||||||
|
use crate::gui::*;
|
||||||
|
use chirp::*;
|
||||||
|
use core::panic;
|
||||||
|
use pixels::{Pixels, SurfaceTexture};
|
||||||
|
use std::result::Result;
|
||||||
|
use winit::dpi::LogicalSize;
|
||||||
|
use winit::event::{Event, VirtualKeyCode};
|
||||||
|
use winit::event_loop::{ControlFlow, EventLoop};
|
||||||
|
use winit::window::WindowBuilder;
|
||||||
|
use winit_input_helper::WinitInputHelper;
|
||||||
|
|
||||||
|
mod error;
|
||||||
|
mod gui;
|
||||||
|
|
||||||
|
const FOREGROUND: &[u8; 4] = &0xFFFF00FF_u32.to_be_bytes();
|
||||||
|
const BACKGROUND: &[u8; 4] = &0x623701FF_u32.to_be_bytes();
|
||||||
|
|
||||||
|
/// The state of the application
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Emulator {
|
||||||
|
gui: Gui,
|
||||||
|
screen: Bus,
|
||||||
|
cpu: CPU,
|
||||||
|
ipf: usize,
|
||||||
|
colors: [[u8; 4]; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), error::Error> {
|
||||||
|
let event_loop = EventLoop::new();
|
||||||
|
let mut input = WinitInputHelper::new();
|
||||||
|
|
||||||
|
let size = LogicalSize::new(128 * 6, 64 * 6);
|
||||||
|
let window = WindowBuilder::new()
|
||||||
|
.with_title("Chirp")
|
||||||
|
.with_inner_size(size)
|
||||||
|
.with_always_on_top(true)
|
||||||
|
.build(&event_loop)?;
|
||||||
|
let mut scale_factor = window.scale_factor();
|
||||||
|
|
||||||
|
let mut pixels = {
|
||||||
|
let window_size = window.inner_size();
|
||||||
|
let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window);
|
||||||
|
Pixels::new(128, 64, surface_texture)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut emu = Emulator::new(
|
||||||
|
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!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run event loop
|
||||||
|
event_loop.run(move |event, _, control_flow| {
|
||||||
|
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
|
||||||
|
context.scaling_renderer.render(encoder, render_target);
|
||||||
|
// Render Dear ImGui
|
||||||
|
gui.render(&window, encoder, render_target, context)?;
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut handle_events = |state: &mut Emulator,
|
||||||
|
pixels: &mut Pixels,
|
||||||
|
control_flow: &mut ControlFlow|
|
||||||
|
-> Result<(), error::Error> {
|
||||||
|
state.gui.handle_event(&window, &event);
|
||||||
|
if input.update(&event) {
|
||||||
|
use VirtualKeyCode::*;
|
||||||
|
match_pressed!( match input {
|
||||||
|
Escape | input.quit() | state.gui.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()),
|
||||||
|
LAlt => state.gui.show_menubar(None),
|
||||||
|
});
|
||||||
|
state.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the scale factor
|
||||||
|
if let Some(factor) = input.scale_factor() {
|
||||||
|
scale_factor = factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize the window
|
||||||
|
if let Some(size) = input.window_resized() {
|
||||||
|
if size.width > 0 && size.height > 0 {
|
||||||
|
// Resize the surface texture
|
||||||
|
pixels.resize_surface(size.width, size.height)?;
|
||||||
|
// Resize the world
|
||||||
|
let LogicalSize { width, height } = size.to_logical(scale_factor);
|
||||||
|
pixels.resize_buffer(width, height)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.cpu.flags.debug = state.gui.wants_disassembly();
|
||||||
|
if state.gui.wants_reset() {
|
||||||
|
state.cpu.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the game loop
|
||||||
|
state.update()?;
|
||||||
|
state.draw(pixels)?;
|
||||||
|
|
||||||
|
// redraw the window
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Event::RedrawRequested(_) = event {
|
||||||
|
if let Err(e) = redraw(&mut emu.gui, &mut pixels) {
|
||||||
|
eprintln!("{e}");
|
||||||
|
*control_flow = ControlFlow::Exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = handle_events(&mut emu, &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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! match_pressed {
|
||||||
|
(match $input:ident {$($key:path $( | $cond:expr)? => $action:expr),+ $(,)?}) => {
|
||||||
|
$(
|
||||||
|
if $input.key_pressed($key) $( || $cond )? {
|
||||||
|
$action;
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! match_released {
|
||||||
|
(match $input:ident {$($key:path $( | $cond:expr)? => $action:expr),+ $(,)?}) => {
|
||||||
|
$(
|
||||||
|
if $input.key_released($key) $( || $cond )? {
|
||||||
|
$action;
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user