much better cli! more ergonomic screen struct! window screen type still broken but working on it!
This commit is contained in:
+143
-96
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user