much better cli! more ergonomic screen struct! window screen type still broken but working on it!

This commit is contained in:
2024-06-30 22:49:54 -04:00
parent 8890853656
commit ac4619406d
9 changed files with 931 additions and 440 deletions
+143 -96
View File
@@ -7,116 +7,163 @@ use std::{
use serde::{Deserialize, Serialize};
use crate::memory::Mem;
use crate::video::ScreenType;
#[derive(Serialize, Deserialize, Debug)]
struct Config {
struct ConfigBuilder {
rom: Option<String>,
char_rom: Option<String>,
rom: String,
screen: ScreenType,
}
pub fn get_input(memory: &mut Mem) {
let args: Vec<String> = env::args().collect();
impl ConfigBuilder {
fn new() -> Self {
Self {
rom: None,
char_rom: None,
screen: ScreenType::default(),
}
}
match args.len() {
0 => {
println!("george-emu must be run in the terminal, don't know what went wrong here!");
exit(1)
fn build(self) -> Config {
let rom = match self.rom {
Some(rom) => rom,
None => {
println!("no rom was provided :(");
exit(1);
}
};
let char_rom = self.char_rom;
let screen = self.screen;
Config {
rom,
screen,
char_rom,
}
1 => {
let config: Config = match File::open("./george.toml") {
Ok(mut file) => {
let mut string = String::new();
file.read_to_string(&mut string).unwrap();
toml::from_str(string.as_str()).unwrap()
}
Err(_) => {
println!("couldn't find a `george.toml` in the current directory!");
exit(1);
}
};
let rom = match std::fs::File::open(config.rom) {
Ok(file) => file,
Err(error) => panic!("Couldn't open main rom! {:?}", error),
};
if let Err(error) = memory.load_rom(rom) {
println!("{:?}", error);
};
}
2 => match &args[1] as &str {
"help" => {
println!("ʕ·ᴥ·ʔ- george-emu is an emulator for george:");
println!("https://git.augustkline.com/august/george\n");
println!("commands:");
println!(" help: print this help screen");
println!(" help <command>: print help info for any command");
println!(" rom <path>: load a rom/binary from path\n");
println!("configuration:");
println!(" george-emu searches for a `george.toml` in the current directory. in `george.toml` you can specify a path for the character rom using the key `char_rom` and the main rom/binary with the key `rom`");
}
}
pub struct Config {
pub rom: String,
pub screen: ScreenType,
pub char_rom: Option<String>,
}
fn help(command: Option<String>) {
let executable: String = env::args().next().unwrap();
if let Some(command) = command {
match &command as &str {
"rom" | "--rom" | "-r" => {
println!("{executable} {command} <path>\n\nload a rom/binary from path");
exit(0);
}
"screen" | "--screen" | "-s" => {
println!("{executable} {command} <path>\n\nload a rom/binary from path");
exit(0);
}
"help" | "--help" | "-h" => {
println!("{executable} {command} <command>\n\nshow help info for a given command");
exit(0);
}
_ => {
println!(
"{:?} isn't a valid command!\n\nuse `{} help` to see all valid commands",
&args[1], &args[0]
"{command:?} isn't a valid command!\n\nuse `{executable} help` to see all valid commands",
);
exit(1);
}
},
3 => match &args[1] as &str {
"help" => match &args[2] as &str {
"rom" => {
println!("{} rom <path>\n\nload a rom/binary from path", &args[0]);
exit(0);
}
_ => {
println!(
"{:?} isn't a valid command!\n\nuse `{} help` to see all valid commands",
&args[2], &args[0]
);
exit(1);
}
},
"rom" => {
let rom = match std::fs::File::open(&args[2]) {
Ok(file) => file,
Err(error) => {
match error.kind() {
ErrorKind::NotFound => {
println!("couldn't find the rom at {:?}", &args[2]);
}
ErrorKind::PermissionDenied => {
println!(
"didn't have sufficient permissions to open the rom at {:?}",
&args[2]
);
}
_ => {
println!("something went wrong! try again in a moment? really not sure why you're getting this error");
}
}
exit(1);
}
};
if let Err(error) = memory.load_rom(rom) {
println!("oh no! this rom couldn't be loaded: {:?}", error);
exit(1);
};
}
_ => {
println!(
"{:?} isn't a valid command!\n\nuse `{} help` to see all valid commands",
&args[1], &args[0]
);
exit(1);
}
},
_ => {
println!(
"too many arguments were provided!\n\nuse `{} help` to see all valid commands",
&args[0]
);
exit(1);
}
} else {
println!("ʕ·ᴥ·ʔ- {executable} is an emulator for george:");
println!("https://git.augustkline.com/august/george\n");
println!("commands:");
println!(" help, -h, --help <command>: print help info for any command");
println!(" rom, -r, --rom <path>: load a rom/binary from path");
println!(" screen, -s, --screen <type>: use the \"terminal\" or \"window\" display type");
println!("\nconfiguration:");
println!(" george-emu searches for a `george.toml` in the current directory.\n in `george.toml` you can specify a path for the character rom using the key `char_rom` and the main rom/binary with the key `rom`");
exit(0);
}
}
pub fn get_input() -> Config {
let executable: String = env::args().next().unwrap();
let mut config = ConfigBuilder::new();
let len = env::args().len();
if len == 1 {
config = match File::open("./george.toml") {
Ok(mut file) => {
let mut string = String::new();
file.read_to_string(&mut string).unwrap();
toml::from_str(string.as_str()).unwrap()
}
Err(_) => {
println!("couldn't find a `george.toml` in the current directory!");
exit(1);
}
};
return config.build();
}
let mut args = env::args().skip(1);
while let Some(arg) = args.next() {
match &arg[..] {
"--help" | "-h" | "help" => help(args.next()),
"--rom" | "-r" | "rom" => {
if let Some(path) = args.next() {
match std::fs::File::open(&path) {
Ok(_) => {
config.rom = Some(path);
}
Err(error) => {
match error.kind() {
ErrorKind::NotFound => {
println!("couldn't find the rom at {path:?}");
}
ErrorKind::PermissionDenied => {
println!(
"didn't have sufficient permissions to open the rom at {path:?}"
);
}
_ => {
println!("something went wrong! try again in a moment? really not sure why you're getting this error");
}
}
exit(1);
}
};
} else {
println!("no rom specified!");
exit(1);
}
}
"--screen" | "-s" | "screen" => {
let kind = args.next();
match kind {
Some(kind) => match &kind as &str {
"terminal" | "Terminal" | "TERMINAL" | "t" | "term" => {}
"window" | "Window" | "WINDOW" | "w" | "win" => {
config.screen = ScreenType::Window
}
_ => {
println!("{kind:?} isn't a valid screen type!\n\nuse \"{executable} --help screen\" to see all valid screen types");
exit(1);
}
},
None => {
println!("no screen type was provided!\n\nuse \"{executable} --help screen\" to see all valid screen types");
exit(1);
}
}
}
_ => {
println!("{arg:?} isn't a valid command!\n\nuse \"{executable} help\" to see all valid commands");
exit(1);
}
}
}
config.build()
}
+119 -114
View File
@@ -1,5 +1,5 @@
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
// use minifb::{InputCallback, Key};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use minifb::{InputCallback, Key};
use crate::memory::{MemHandle, MemoryWriter};
@@ -18,56 +18,61 @@ impl Keyboard {
let mut row3 = 0;
let mut row4 = 0;
let mut row5 = 0;
if key.kind == KeyEventKind::Press || key.kind == KeyEventKind::Repeat {
match key.modifiers {
KeyModifiers::SHIFT => row2 ^= 0b1000_0000,
KeyModifiers::CONTROL => row3 ^= 0b1000_0000,
KeyModifiers::ALT => row4 ^= 0b1000_0000,
KeyModifiers::META => row5 ^= 0b1000_0000,
KeyModifiers::SUPER => row5 ^= 0b0010_0000,
_ => {}
};
match key.modifiers {
KeyModifiers::SHIFT => row2 ^= 0b1000_0000,
KeyModifiers::CONTROL => row3 ^= 0b1000_0000,
KeyModifiers::ALT => row4 ^= 0b1000_0000,
KeyModifiers::META => row5 ^= 0b1000_0000,
KeyModifiers::SUPER => row5 ^= 0b0010_0000,
_ => {}
};
match key.code {
KeyCode::Esc => row0 ^= 0b1000_0000,
KeyCode::Char('w') => row0 ^= 0b0100_0000,
KeyCode::Char('e') => row0 ^= 0b0010_0000,
KeyCode::Char('r') => row0 ^= 0b0001_0000,
KeyCode::Char('t') => row0 ^= 0b0000_1000,
KeyCode::Char('u') => row0 ^= 0b0000_0100,
KeyCode::Char('o') => row0 ^= 0b0000_0010,
KeyCode::Backspace => row0 ^= 0b0000_0001,
KeyCode::Tab => row1 ^= 0b1000_0000,
KeyCode::Char('q') => row1 ^= 0b0100_0000,
KeyCode::Char('s') => row1 ^= 0b0010_0000,
KeyCode::Char('g') => row1 ^= 0b0001_0000,
KeyCode::Char('y') => row1 ^= 0b0000_1000,
KeyCode::Char('i') => row1 ^= 0b0000_0100,
KeyCode::Char('p') => row1 ^= 0b0000_0010,
KeyCode::Enter => row1 ^= 0b0000_0001,
KeyCode::Char('d') => row2 ^= 0b0100_0000,
KeyCode::Char('v') => row2 ^= 0b0010_0000,
KeyCode::Char('h') => row2 ^= 0b0001_0000,
KeyCode::Char('k') => row2 ^= 0b0000_1000,
KeyCode::Char('\'') => row2 ^= 0b0000_0100,
KeyCode::Char('/') => row2 ^= 0b0000_0010,
KeyCode::Char('a') => row2 ^= 0b0000_0001,
KeyCode::Char('z') => row3 ^= 0b0100_0000,
KeyCode::Char('f') => row3 ^= 0b0010_0000,
KeyCode::Char('b') => row3 ^= 0b0001_0000,
KeyCode::Char('j') => row3 ^= 0b0000_1000,
KeyCode::Char('l') => row3 ^= 0b0000_0100,
KeyCode::Char('2') => row3 ^= 0b0000_0010,
KeyCode::Char('4') => row3 ^= 0b0000_0001,
KeyCode::Char('x') => row4 ^= 0b0100_0000,
KeyCode::Char('c') => row4 ^= 0b0010_0000,
KeyCode::Char('n') => row4 ^= 0b0001_0000,
KeyCode::Char('m') => row4 ^= 0b0000_1000,
KeyCode::Char(',') => row4 ^= 0b0000_0100,
KeyCode::Char('1') => row4 ^= 0b0000_0010,
KeyCode::Char('3') => row4 ^= 0b0000_0001,
KeyCode::Char(' ') => row5 ^= 0b0100_0000,
_ => {}
match key.code {
KeyCode::Esc => row0 ^= 0b1000_0000,
KeyCode::Char('w') => row0 ^= 0b0100_0000,
KeyCode::Char('e') => row0 ^= 0b0010_0000,
KeyCode::Char('r') => row0 ^= 0b0001_0000,
KeyCode::Char('t') => row0 ^= 0b0000_1000,
KeyCode::Char('u') => row0 ^= 0b0000_0100,
KeyCode::Char('o') => row0 ^= 0b0000_0010,
KeyCode::Backspace => row0 ^= 0b0000_0001,
KeyCode::Tab => row1 ^= 0b1000_0000,
KeyCode::Char('q') => row1 ^= 0b0100_0000,
KeyCode::Char('s') => row1 ^= 0b0010_0000,
KeyCode::Char('g') => row1 ^= 0b0001_0000,
KeyCode::Char('y') => row1 ^= 0b0000_1000,
KeyCode::Char('i') => row1 ^= 0b0000_0100,
KeyCode::Char('p') => row1 ^= 0b0000_0010,
KeyCode::Enter => row1 ^= 0b0000_0001,
KeyCode::Char('d') => row2 ^= 0b0100_0000,
KeyCode::Char('v') => row2 ^= 0b0010_0000,
KeyCode::Char('h') => row2 ^= 0b0001_0000,
KeyCode::Char('k') => row2 ^= 0b0000_1000,
KeyCode::Char('\'') => row2 ^= 0b0000_0100,
KeyCode::Char('/') => row2 ^= 0b0000_0010,
KeyCode::Char('a') => row2 ^= 0b0000_0001,
KeyCode::Char('z') => row3 ^= 0b0100_0000,
KeyCode::Char('f') => row3 ^= 0b0010_0000,
KeyCode::Char('b') => row3 ^= 0b0001_0000,
KeyCode::Char('j') => row3 ^= 0b0000_1000,
KeyCode::Char('l') => row3 ^= 0b0000_0100,
KeyCode::Char('2') => row3 ^= 0b0000_0010,
KeyCode::Char('4') => row3 ^= 0b0000_0001,
KeyCode::Char('x') => row4 ^= 0b0100_0000,
KeyCode::Char('c') => row4 ^= 0b0010_0000,
KeyCode::Char('n') => row4 ^= 0b0001_0000,
KeyCode::Char('m') => row4 ^= 0b0000_1000,
KeyCode::Char(',') => row4 ^= 0b0000_0100,
KeyCode::Char('1') => row4 ^= 0b0000_0010,
KeyCode::Char('3') => row4 ^= 0b0000_0001,
KeyCode::Char(' ') => row5 ^= 0b0100_0000,
_ => {
row0 = 0;
row1 = 0;
row2 = 0;
row3 = 0;
row4 = 0;
row5 = 0;
}
};
@@ -86,68 +91,68 @@ impl MemoryWriter for Keyboard {
}
}
// impl InputCallback for Keyboard {
// fn add_char(&mut self, _uni_char: u32) {}
// fn set_key_state(&mut self, key: Key, _state: bool) {
// let mut row0 = 0;
// let mut row1 = 0;
// let mut row2 = 0;
// let mut row3 = 0;
// let mut row4 = 0;
// let mut row5 = 0;
impl InputCallback for Keyboard {
fn add_char(&mut self, _uni_char: u32) {}
fn set_key_state(&mut self, key: Key, _state: bool) {
let mut row0 = 0;
let mut row1 = 0;
let mut row2 = 0;
let mut row3 = 0;
let mut row4 = 0;
let mut row5 = 0;
// match key {
// Key::Escape => row0 ^= 0b1000_0000,
// Key::W => row0 ^= 0b0100_0000,
// Key::E => row0 ^= 0b0010_0000,
// Key::R => row0 ^= 0b0001_0000,
// Key::T => row0 ^= 0b0000_1000,
// Key::U => row0 ^= 0b0000_0100,
// Key::O => row0 ^= 0b0000_0010,
// Key::Backspace => row0 ^= 0b0000_0001,
// Key::Tab => row1 ^= 0b1000_0000,
// Key::Q => row1 ^= 0b0100_0000,
// Key::S => row1 ^= 0b0010_0000,
// Key::G => row1 ^= 0b0001_0000,
// Key::Y => row1 ^= 0b0000_1000,
// Key::I => row1 ^= 0b0000_0100,
// Key::P => row1 ^= 0b0000_0010,
// Key::Enter => row1 ^= 0b0000_0001,
// Key::LeftShift | Key::RightShift => row2 ^= 0b1000_0000,
// Key::D => row2 ^= 0b0100_0000,
// Key::V => row2 ^= 0b0010_0000,
// Key::H => row2 ^= 0b0001_0000,
// Key::K => row2 ^= 0b0000_1000,
// Key::Apostrophe => row2 ^= 0b0000_0100,
// Key::Slash => row2 ^= 0b0000_0010,
// Key::A => row2 ^= 0b0000_0001,
// Key::LeftCtrl | Key::RightCtrl => row3 ^= 0b1000_0000,
// Key::Z => row3 ^= 0b0100_0000,
// Key::F => row3 ^= 0b0010_0000,
// Key::B => row3 ^= 0b0001_0000,
// Key::J => row3 ^= 0b0000_1000,
// Key::L => row3 ^= 0b0000_0100,
// Key::Key2 => row3 ^= 0b0000_0010,
// Key::Key4 => row3 ^= 0b0000_0001,
// Key::LeftAlt | Key::RightAlt => row4 ^= 0b1000_0000,
// Key::X => row4 ^= 0b0100_0000,
// Key::C => row4 ^= 0b0010_0000,
// Key::N => row4 ^= 0b0001_0000,
// Key::M => row4 ^= 0b0000_1000,
// Key::Comma => row4 ^= 0b0000_0100,
// Key::Key1 => row4 ^= 0b0000_0010,
// Key::Key3 => row4 ^= 0b0000_0001,
// Key::LeftSuper => row5 ^= 0b1000_0000,
// Key::Space => row5 ^= 0b0100_0000,
// Key::RightSuper => row5 ^= 0b0010_0000,
// _ => {}
// };
match key {
Key::Escape => row0 ^= 0b1000_0000,
Key::W => row0 ^= 0b0100_0000,
Key::E => row0 ^= 0b0010_0000,
Key::R => row0 ^= 0b0001_0000,
Key::T => row0 ^= 0b0000_1000,
Key::U => row0 ^= 0b0000_0100,
Key::O => row0 ^= 0b0000_0010,
Key::Backspace => row0 ^= 0b0000_0001,
Key::Tab => row1 ^= 0b1000_0000,
Key::Q => row1 ^= 0b0100_0000,
Key::S => row1 ^= 0b0010_0000,
Key::G => row1 ^= 0b0001_0000,
Key::Y => row1 ^= 0b0000_1000,
Key::I => row1 ^= 0b0000_0100,
Key::P => row1 ^= 0b0000_0010,
Key::Enter => row1 ^= 0b0000_0001,
Key::LeftShift | Key::RightShift => row2 ^= 0b1000_0000,
Key::D => row2 ^= 0b0100_0000,
Key::V => row2 ^= 0b0010_0000,
Key::H => row2 ^= 0b0001_0000,
Key::K => row2 ^= 0b0000_1000,
Key::Apostrophe => row2 ^= 0b0000_0100,
Key::Slash => row2 ^= 0b0000_0010,
Key::A => row2 ^= 0b0000_0001,
Key::LeftCtrl | Key::RightCtrl => row3 ^= 0b1000_0000,
Key::Z => row3 ^= 0b0100_0000,
Key::F => row3 ^= 0b0010_0000,
Key::B => row3 ^= 0b0001_0000,
Key::J => row3 ^= 0b0000_1000,
Key::L => row3 ^= 0b0000_0100,
Key::Key2 => row3 ^= 0b0000_0010,
Key::Key4 => row3 ^= 0b0000_0001,
Key::LeftAlt | Key::RightAlt => row4 ^= 0b1000_0000,
Key::X => row4 ^= 0b0100_0000,
Key::C => row4 ^= 0b0010_0000,
Key::N => row4 ^= 0b0001_0000,
Key::M => row4 ^= 0b0000_1000,
Key::Comma => row4 ^= 0b0000_0100,
Key::Key1 => row4 ^= 0b0000_0010,
Key::Key3 => row4 ^= 0b0000_0001,
Key::LeftSuper => row5 ^= 0b1000_0000,
Key::Space => row5 ^= 0b0100_0000,
Key::RightSuper => row5 ^= 0b0010_0000,
_ => {}
};
// self.memory.write(0x4400, row0);
// self.memory.write(0x4401, row1);
// self.memory.write(0x4402, row2);
// self.memory.write(0x4403, row3);
// self.memory.write(0x4404, row4);
// self.memory.write(0x4405, row5);
// }
// }
self.memory.write(0x4400, row0);
self.memory.write(0x4401, row1);
self.memory.write(0x4402, row2);
self.memory.write(0x4403, row3);
self.memory.write(0x4404, row4);
self.memory.write(0x4405, row5);
}
}
+11 -12
View File
@@ -17,7 +17,7 @@ use crossterm::execute;
use crossterm::terminal::{size, Clear, ClearType, SetSize};
// use cpu::CpuController;
use memory::MemHandle;
// use minifb::{Scale, ScaleMode, Window, WindowOptions};
use minifb::{Scale, ScaleMode, Window, WindowOptions};
use std::io::{stdout, Result};
use std::thread::{self, sleep};
use std::time::Duration;
@@ -61,9 +61,10 @@ fn main() -> Result<()> {
let mut stdout = stdout();
let (cols, rows) = size()?;
let mut memory = Mem::new();
let config = get_input();
get_input(&mut memory);
let mut memory = Mem::new();
let _ = memory.load_rom(&config.rom);
execute!(stdout, SetSize(64, 29), cursor::Hide, EnterAlternateScreen)?;
enable_raw_mode()?;
@@ -82,20 +83,18 @@ fn main() -> Result<()> {
cpu.cycle();
});
let stdout_lock = stdout.lock();
let renderer = TerminalRenderer::new(screen_memory, stdout_lock);
let mut screen = Screen::new(renderer, cpu_controller);
let mut screen = Screen::new(&config, cpu_controller, screen_memory);
loop {
sleep(Duration::from_millis(16));
screen.draw();
if event::poll(std::time::Duration::from_millis(16))? {
if let event::Event::Key(key) = event::read()? {
keyboard.read_keys(key);
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
break;
}
// if event::poll(std::time::Duration::from_millis(16))? {
if let event::Event::Key(key) = event::read()? {
keyboard.read_keys(key);
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
break;
}
// }
}
}
+9 -2
View File
@@ -1,7 +1,7 @@
use anyhow::{bail, Result};
use std::cell::RefCell;
use std::io::{self, Write};
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
@@ -71,7 +71,14 @@ impl Mem {
self.0[address as usize] = data;
}
pub fn load_rom(&mut self, rom: File) -> Result<()> {
pub fn load_rom<P>(&mut self, rom: P) -> Result<()>
where
P: AsRef<Path>,
{
let rom = match File::open(rom) {
Ok(rom) => rom,
Err(_) => bail!("rom could not be opened!"),
};
let bytes = rom.bytes();
for (address, byte) in bytes.enumerate() {
// println!("{address}");
+33 -4
View File
@@ -4,6 +4,7 @@
.org $8000
n = $01 ; temporary storage for data stack operations
key_buffer = $200
reset:
sei
@@ -27,10 +28,40 @@ cleardisplay:
cli
main:
; jmp draw
jmp cleardisplay
; jsr read_keys
; lda key_buffer
lda $4402
sta $6000
jmp main
; first, let's do the simplest case of just the letter a being pressed
; 1. check row 2 ($4402, where the a key is) for pressed keys
; 2. if any keys are pressed, check if the key is a (bit 0)
; 3. if the key is not a, store zero in the key buffer
; 4. if the key is a, store the ascii for a in the key buffer
; 5. return
; so u don't have to scroll: key_buffer = $200
; key buffer just shows the current state of the keyboard
; it is not a queue of keys to print
read_keys:
lda $4402 ; check row 2
beq check_key ; if there're any keys, check which
clear_buffer:
stz key_buffer
rts
check_key: ; if there is a key pressed, check if it's a
ror
bmi store_key
rts
store_key:
; in a sec we'll set up ascii table lookup,
; for now let's just load the ascii byte for a
lda #$61
sta key_buffer
rts
draw:
push_coords #0, #0
push_char #$af
@@ -73,8 +104,6 @@ fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1
jsr get_char_address
irq:
keyboard:
rts
isr: ; interrupt service routine
-175
View File
@@ -1,175 +0,0 @@
// use std::time::Duration;
mod tabs;
mod term;
use std::{
io::Result,
sync::{mpsc::Receiver, Arc, Mutex},
time::Duration,
};
use crossterm::event::{self, poll, Event, KeyCode, KeyEvent, KeyEventKind};
use ratatui::{
layout::Layout,
prelude::*,
widgets::{Block, Cell, Paragraph, Row, Table, TableState},
};
use crate::{
cpu::{CpuController, CpuState},
memory::{MemHandle, MemoryReader, MemoryWriter},
};
pub struct App {
cpu: CpuController,
state: Receiver<CpuState>,
memory: MemHandle,
running: bool,
table_state: TableState,
}
impl MemoryReader for App {
fn read(&self, address: u16) -> u8 {
self.memory.read(address)
}
}
impl MemoryWriter for App {
fn write(&self, address: u16, data: u8) {
self.memory.write(address, data)
}
}
impl App {
pub fn new(cpu: CpuController, memory: MemHandle, state: Receiver<CpuState>) -> Self {
Self {
cpu,
memory,
running: true,
table_state: TableState::default(),
state,
}
}
pub fn update(&mut self, terminal: &mut Terminal<impl Backend>) -> Result<()> {
self.draw(terminal)?;
self.handle_events()?;
Ok(())
}
/// Draw a single frame of the app.
fn draw(&self, terminal: &mut Terminal<impl Backend>) -> Result<()> {
terminal
.draw(|frame| {
frame.render_widget(self, frame.size());
})
.unwrap();
Ok(())
}
fn handle_events(&mut self) -> Result<()> {
if poll(Duration::from_secs_f32(1.0 / 25.0))? {
match event::read()? {
Event::Key(key) if key.kind == KeyEventKind::Press => self.handle_key_press(key),
_ => {}
}
}
Ok(())
}
fn handle_key_press(&mut self, key: KeyEvent) {
// let mut cpu = self.cpu.lock().unwrap();
match key.code {
KeyCode::Enter => {
if !self.running {
// cpu.cycle()
}
}
KeyCode::Char(' ') => {
self.running = !self.running;
match self.running {
true => self.cpu.stop(),
false => self.cpu.resume(),
}
}
_ => {}
};
}
}
/// Implement Widget for &App rather than for App as we would otherwise have to clone or copy the
/// entire app state on every frame. For this example, the app state is small enough that it doesn't
/// matter, but for larger apps this can be a significant performance improvement.
impl Widget for &App {
fn render(self, area: Rect, buf: &mut Buffer) {
self.cpu.data();
let [cpu_area, memory_area] =
Layout::horizontal([Constraint::Percentage(50), Constraint::Fill(1)]).areas(area);
let [memory_table, memory_info] =
Layout::vertical([Constraint::Fill(1), Constraint::Percentage(10)]).areas(memory_area);
let cpu = self.state.recv().unwrap();
let cpu_info = format!("a: {a:#04x}, x: {x:#04x}, y: {y:#04x}, pc: {pc:#06x}, sp: {s:#04x}, sr: {p:#010b}, irq: {irq:?}, nmi: {nmi:?}", a = cpu.a, x = cpu.x, y = cpu.y, pc = cpu.pc, s = cpu.s, p = cpu.p, irq = cpu.irq, nmi = cpu.nmi);
Paragraph::new(cpu_info)
.block(Block::bordered().title("cpu info!"))
.render(cpu_area, buf);
// let table_height = memory_table.rows().count() - 2;
// TODO: impl Iterator for MemHandle so we can get references to the underlying data from other threads without cloning
// let rows: Vec<Row> = self.memory.data()[0..table_height * 16]
// .chunks(16)
// .map(|chunk| {
// chunk
// .iter()
// .map(|content| Cell::from(Text::from(format!("{content:x}"))))
// .collect::<Row>()
// })
// .collect();
let zero = self.memory.read(0);
let rows: Vec<Row> = vec![Row::new([format!("{zero}")])];
let widths = vec![Constraint::Length(2); 16];
Widget::render(
Table::new(rows, widths)
.header(
Row::new(vec![
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e",
"f",
])
.style(Style::new().bold()), // To add space between the header and the rest of the rows, specify the margin
// .bottom_margin(1),
)
.block(Block::bordered().title_top("memory")),
memory_table,
buf,
);
Paragraph::new(format!("program counter: {:#04x}", cpu.pc))
.block(Block::bordered().title_top("info"))
.render(memory_info, buf);
}
}
// impl Widget for App {
// fn render_title_bar(&self, area: Rect, buf: &mut Buffer) {
// // let layout = Layout::horizontal([Constraint::Min(0), Constraint::Length(43)]);
// // let [title] = layout.areas(area);
// Span::styled("Ratatui", Style::default()).render(area, buf);
// // let titles = Tab::iter().map(Tab::title);
// // Tabs::new(titles)
// // .style(THEME.tabs)
// // .highlight_style(THEME.tabs_selected)
// // .select(self.tab as usize)
// // .divider("")
// // .padding("", "")
// // .render(tabs, buf);
// }
// // fn render_selected_tab(&self, area: Rect, buf: &mut Buffer) {
// // match self.tab {
// // Tab::About => self.about_tab.render(area, buf),
// // Tab::Recipe => self.recipe_tab.render(area, buf),
// // Tab::Email => self.email_tab.render(area, buf),
// // Tab::Traceroute => self.traceroute_tab.render(area, buf),
// // Tab::Weather => self.weather_tab.render(area, buf),
// // };
// // }
// }
+70 -33
View File
@@ -3,28 +3,27 @@ use crossterm::{
execute, queue,
style::{Color, PrintStyledContent, Stylize},
};
use minifb::{Scale, ScaleMode, Window, WindowOptions};
use serde::{Deserialize, Serialize};
use crate::{
cli::Config,
cpu::CpuController,
memory::{MemHandle, MemoryReader},
types::{Byte, Word},
};
use std::{
fs::File,
io::{Read, StdoutLock, Write},
io::{self, Read, Write},
path::Path,
process::exit,
sync::mpsc::Sender,
time::Instant,
};
const FG_COLOR: u32 = 0xFFCC00;
const BG_COLOR: u32 = 0x110500;
// // BGR for softbuffer
// const FG_COLOR: u32 = 0x00CCFF;
// const BG_COLOR: u32 = 0x000511;
//
pub fn get_char_bin<P>(char_rom: Option<P>) -> Vec<u8>
const WIDTH: usize = 512;
const HEIGHT: usize = 380;
pub fn get_char_bin<P>(char_rom: Option<P>) -> [u8; 0x8000]
where
P: AsRef<Path>,
{
@@ -34,9 +33,9 @@ where
let mut bin = vec![0; 0x8000];
file.read_exact(&mut bin).unwrap();
// println!("reading char rom");
bin
bin.try_into().unwrap()
}
None => include_bytes!("./roms/cozette.rom").to_vec(),
None => *include_bytes!("./roms/cozette.rom"),
}
}
@@ -44,14 +43,21 @@ trait Renderer {
fn render(&mut self) {}
}
#[derive(Serialize, Deserialize, Debug, Default)]
pub enum ScreenType {
Window,
#[default]
Terminal,
}
pub struct WindowRenderer {
char_rom: Vec<u8>,
window: Sender<Vec<u32>>,
char_rom: [u8; 0x8000],
window: Window,
memory: MemHandle,
}
impl WindowRenderer {
pub fn new<P>(memory: MemHandle, char_rom: Option<P>, window: Sender<Vec<u32>>) -> Self
pub fn new<P>(memory: MemHandle, char_rom: Option<P>, window: Window) -> Self
where
P: AsRef<Path>,
{
@@ -70,7 +76,7 @@ impl Renderer for WindowRenderer {
// based on the specifics of george's weird
// display and characters... don't fuck around w it
let mut i = 0;
let mut buffer = vec![0; 512 * 380];
let mut buffer = [0; 512 * 380];
for char_row in 0..29 {
for char_col in 0..64 {
let ascii = self.read(0x6000 + i);
@@ -89,7 +95,9 @@ impl Renderer for WindowRenderer {
}
}
let _ = self.window.send(buffer.clone());
self.window
.update_with_buffer(&buffer, WIDTH, HEIGHT)
.unwrap();
}
}
}
@@ -100,19 +108,23 @@ impl MemoryReader for WindowRenderer {
}
}
pub struct TerminalRenderer<'a> {
// pub struct TerminalRenderer<'a> {
pub struct TerminalRenderer {
memory: MemHandle,
// stdout: Stdout,
stdout: StdoutLock<'a>,
// stdout: StdoutLock<'a>,
}
impl<'a> TerminalRenderer<'a> {
pub fn new(memory: MemHandle, stdout: StdoutLock<'a>) -> Self {
Self { memory, stdout }
// impl<'a> TerminalRenderer<'a> {
impl TerminalRenderer {
// pub fn new(memory: MemHandle, stdout: StdoutLock<'a>) -> Self {
pub fn new(memory: MemHandle) -> Self {
Self { memory }
}
}
impl MemoryReader for TerminalRenderer<'_> {
// impl MemoryReader for TerminalRenderer<'_> {
impl MemoryReader for TerminalRenderer {
fn read(&self, address: Word) -> Byte {
self.memory.read(address)
}
@@ -135,10 +147,12 @@ const ASCII_LOOPUP: [&str; 256] = [
"", "", "", "", "🎁", "", "", "", "", "", "", "", "", "",
];
impl Renderer for TerminalRenderer<'_> {
// impl Renderer for TerminalRenderer<'_> {
impl Renderer for TerminalRenderer {
fn render(&mut self) {
// let now = Instant::now();
let _ = execute!(self.stdout, SavePosition);
let mut stdout = io::stdout();
let _ = execute!(stdout, SavePosition);
let mut i = 0;
for char_row in 0..29 {
for char_col in 0..64 {
@@ -148,7 +162,7 @@ impl Renderer for TerminalRenderer<'_> {
let _ = queue!(
// FG_COLOR = 0xFFCC00
// BG_COLOR = 0x110500
self.stdout,
stdout,
MoveTo(char_col, char_row),
PrintStyledContent(
char.with(Color::Rgb {
@@ -165,23 +179,46 @@ impl Renderer for TerminalRenderer<'_> {
);
}
}
let _ = self.stdout.flush();
let _ = execute!(self.stdout, RestorePosition);
let _ = stdout.flush();
let _ = execute!(stdout, RestorePosition);
// let elapsed = now.elapsed();
// println!("{elapsed:?}");
// exit(0);
}
}
pub struct Screen<'a> {
// renderer: Box<dyn Renderer>,
renderer: TerminalRenderer<'a>,
// pub struct Screen<'a> {
pub struct Screen {
renderer: Box<dyn Renderer>,
// renderer: TerminalRenderer<'a>,
controller: CpuController,
}
impl<'a> Screen<'a> {
// pub fn new(cpu_controller: CpuController, renderer: Box<dyn Renderer>) -> Self {
pub fn new(renderer: TerminalRenderer<'a>, controller: CpuController) -> Self {
impl Screen {
pub fn new(config: &Config, controller: CpuController, memory: MemHandle) -> Self {
let renderer: Box<dyn Renderer> = match config.screen {
ScreenType::Window => {
let window = Window::new(
"ʕ·ᴥ·ʔ-☆",
512,
380,
WindowOptions {
resize: true,
borderless: true,
title: true,
transparency: false,
scale: Scale::X2,
scale_mode: ScaleMode::AspectRatioStretch,
topmost: false,
none: true,
},
)
.unwrap();
Box::new(WindowRenderer::new(memory, config.char_rom.clone(), window))
}
ScreenType::Terminal => Box::new(TerminalRenderer::new(memory)),
};
Self {
renderer,
controller,