diff --git a/Cargo.toml b/Cargo.toml index 3e36ec9..f02be25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,10 @@ zip = "0.6" governor = "0.6" sevenz-rust = "0.5" chrono = "0.4" +serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" sha2 = "0.10" +rusttype = "0.9" sdl2-sys = { git = "https://github.com/Rust-SDL2/rust-sdl2.git" } clap = { version = "4.4", features = ["derive"] } diff --git a/README.md b/README.md index 1893483..be9099d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,29 @@ # gopher64 +``` +Usage: gopher64 [OPTIONS] [GAME] + +Arguments: + [GAME] + +Options: + -f, --fullscreen + + -c, --configure-input-profile + Create a new input profile (keyboard/gamepad mappings). + -b, --bind-input-profile + Must also specify --port. Used to bind a previously created profile to a port + -l, --list-controllers + Lists connected controllers which can be used in --assign-controller + -a, --assign-controller + Must also specify --port. Used to assign a controller listed in --list-controllers to a port + -p, --port + Valid values: 1-4. To be used alongside --bind-input-profile and --assign-controller + -h, --help + Print help + -V, --version + Print version +``` ## goals 1. Performance. I want to be able to use this emulator on my laptop. @@ -16,7 +40,11 @@ ## controls -Right now, only keyboard is supported. Keys are mapped according to mupen64plus defaults: https://mupen64plus.org/wiki/index.php/KeyboardSetup#2._Default_Key_Mappings_for_SDL-Input_Plugin +Keys are mapped according to mupen64plus defaults: https://mupen64plus.org/wiki/index.php/KeyboardSetup#2._Default_Key_Mappings_for_SDL-Input_Plugin. Xbox-style controllers also have a default mapping applied. + +You can create you own mappings by running `./gopher64 --configure-input-profile my_profile`. You then bind that profile to a port: `./gopher64 --bind-input-profile my_profile --port 1` + +In order to use a controller (for example, an Xbox controller), run `./gopher64 --list-controllers` to get a list of attached controllers, and then assign it by doing `./gopher64 --assign-controller --port 1` ## contributing diff --git a/data/Roboto-Regular.ttf b/data/Roboto-Regular.ttf new file mode 100644 index 0000000..ddf4bfa Binary files /dev/null and b/data/Roboto-Regular.ttf differ diff --git a/src/device/cart.rs b/src/device/cart.rs index 975c403..beb9efe 100644 --- a/src/device/cart.rs +++ b/src/device/cart.rs @@ -96,8 +96,8 @@ pub fn process(device: &mut device::Device, channel: usize) { } pub fn format_eeprom(device: &mut device::Device) { - if device.ui.saves.eeprom.len() < EEPROM_MAX_SIZE { - device.ui.saves.eeprom.resize(EEPROM_MAX_SIZE, 0xFF) + if device.ui.saves.eeprom.0.len() < EEPROM_MAX_SIZE { + device.ui.saves.eeprom.0.resize(EEPROM_MAX_SIZE, 0xFF) } } @@ -107,7 +107,7 @@ pub fn eeprom_read_block(device: &mut device::Device, block: usize, offset: usiz format_eeprom(device); device.pif.ram[offset..offset + EEPROM_BLOCK_SIZE] - .copy_from_slice(&device.ui.saves.eeprom[address..address + EEPROM_BLOCK_SIZE]); + .copy_from_slice(&device.ui.saves.eeprom.0[address..address + EEPROM_BLOCK_SIZE]); } pub fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usize, status: usize) { @@ -115,20 +115,12 @@ pub fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usi format_eeprom(device); - device.ui.saves.eeprom[address..address + EEPROM_BLOCK_SIZE] + device.ui.saves.eeprom.0[address..address + EEPROM_BLOCK_SIZE] .copy_from_slice(&device.pif.ram[offset..offset + EEPROM_BLOCK_SIZE]); device.pif.ram[status as usize] = 0x00; - let mut save_type = ui::storage::SaveTypes::Eeprom4k; - if device - .ui - .save_type - .contains(&ui::storage::SaveTypes::Eeprom16k) - { - save_type = ui::storage::SaveTypes::Eeprom16k - } - ui::storage::write_save(&mut device.ui, save_type); + device.ui.saves.eeprom.1 = true } pub fn time2data(device: &mut device::Device, offset: usize) { diff --git a/src/device/cart_rom.rs b/src/device/cart_rom.rs index 58bcbe9..707a718 100644 --- a/src/device/cart_rom.rs +++ b/src/device/cart_rom.rs @@ -1,5 +1,4 @@ use crate::device; -use crate::ui; use sha2::{Digest, Sha256}; pub enum CicType { @@ -84,14 +83,14 @@ pub fn dma_read( device.cart.rom[(cart_addr + i) as usize] = device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap]; - device.ui.saves.romsave.insert( + device.ui.saves.romsave.0.insert( (cart_addr + i).to_string(), serde_json::to_value(device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap]) .unwrap(), ); } - ui::storage::write_save(&mut device.ui, ui::storage::SaveTypes::Romsave); + device.ui.saves.romsave.1 = true; return device::pi::calculate_cycles(device, 1, length); } @@ -131,10 +130,10 @@ pub fn init(device: &mut device::Device, rom_file: Vec) { } pub fn load_rom_save(device: &mut device::Device) { - if device.ui.saves.romsave.is_empty() { + if device.ui.saves.romsave.0.is_empty() { return; } - for (key, value) in device.ui.saves.romsave.iter() { + for (key, value) in device.ui.saves.romsave.0.iter() { device.cart.rom[key.parse::().unwrap()] = serde_json::from_value(value.clone()).unwrap() } diff --git a/src/device/mempak.rs b/src/device/mempak.rs index 802acee..fe2902b 100644 --- a/src/device/mempak.rs +++ b/src/device/mempak.rs @@ -1,12 +1,11 @@ use crate::device; -use crate::ui; pub const MEMPAK_SIZE: usize = 0x8000; pub const MPK_PAGE_SIZE: usize = 256; pub fn format_mempak(device: &mut device::Device) { - if device.ui.saves.mempak.len() < MEMPAK_SIZE * 4 { - device.ui.saves.mempak.resize(MEMPAK_SIZE * 4, 0); + if device.ui.saves.mempak.0.len() < MEMPAK_SIZE * 4 { + device.ui.saves.mempak.0.resize(MEMPAK_SIZE * 4, 0); let page_0: [u8; MPK_PAGE_SIZE] = [ /* Label area */ @@ -39,28 +38,28 @@ pub fn format_mempak(device: &mut device::Device) { let offset = i * MEMPAK_SIZE; /* Fill Page 0 with pre-initialized content */ - device.ui.saves.mempak[offset..offset + MPK_PAGE_SIZE].copy_from_slice(&page_0); + device.ui.saves.mempak.0[offset..offset + MPK_PAGE_SIZE].copy_from_slice(&page_0); /* Fill INODE page 1 and update it's checkum */ let start_page = 5; for i in 1 * MPK_PAGE_SIZE..2 * start_page { - device.ui.saves.mempak[offset + i] = 0; + device.ui.saves.mempak.0[offset + i] = 0; } for i in (1 * MPK_PAGE_SIZE + 2 * start_page..2 * MPK_PAGE_SIZE).step_by(2) { - device.ui.saves.mempak[offset + i + 0] = 0x00; - device.ui.saves.mempak[offset + i + 1] = 0x03; + device.ui.saves.mempak.0[offset + i + 0] = 0x00; + device.ui.saves.mempak.0[offset + i + 1] = 0x03; } - device.ui.saves.mempak[offset + (1 * MPK_PAGE_SIZE + 1)] = 0x71; + device.ui.saves.mempak.0[offset + (1 * MPK_PAGE_SIZE + 1)] = 0x71; /* Page 2 is identical to page 1 */ let page1 = offset + 1 * MPK_PAGE_SIZE; let page2 = offset + 2 * MPK_PAGE_SIZE; - let page1data = device.ui.saves.mempak[page1..page1 + MPK_PAGE_SIZE].to_vec(); - device.ui.saves.mempak[page2..page2 + MPK_PAGE_SIZE].copy_from_slice(&page1data); + let page1data = device.ui.saves.mempak.0[page1..page1 + MPK_PAGE_SIZE].to_vec(); + device.ui.saves.mempak.0[page2..page2 + MPK_PAGE_SIZE].copy_from_slice(&page1data); /* Remaining pages DIR+DATA (3...) are initialized with 0x00 */ for i in 3 * MPK_PAGE_SIZE..MEMPAK_SIZE - 3 * MPK_PAGE_SIZE { - device.ui.saves.mempak[offset + i] = 0; + device.ui.saves.mempak.0[offset + i] = 0; } } } @@ -72,7 +71,7 @@ pub fn read(device: &mut device::Device, channel: usize, address: u16, data: usi let offset = (channel * MEMPAK_SIZE) + address as usize; device.pif.ram[data..data + size] - .copy_from_slice(&device.ui.saves.mempak[offset..offset + size]) + .copy_from_slice(&device.ui.saves.mempak.0[offset..offset + size]) } else { for i in 0..size { device.pif.ram[data + i] = 0; @@ -85,9 +84,9 @@ pub fn write(device: &mut device::Device, channel: usize, address: u16, data: us format_mempak(device); let offset = (channel * MEMPAK_SIZE) + address as usize; - device.ui.saves.mempak[offset..offset + size] + device.ui.saves.mempak.0[offset..offset + size] .copy_from_slice(&device.pif.ram[data..data + size]); - ui::storage::write_save(&mut device.ui, ui::storage::SaveTypes::Mempak); + device.ui.saves.mempak.1 = true } } diff --git a/src/device/sram.rs b/src/device/sram.rs index 5af1058..108845d 100644 --- a/src/device/sram.rs +++ b/src/device/sram.rs @@ -28,14 +28,14 @@ pub struct Flashram { } pub fn format_sram(device: &mut device::Device) { - if device.ui.saves.sram.len() < SRAM_SIZE { - device.ui.saves.sram.resize(SRAM_SIZE, 0xFF) + if device.ui.saves.sram.0.len() < SRAM_SIZE { + device.ui.saves.sram.0.resize(SRAM_SIZE, 0xFF) } } pub fn format_flash(device: &mut device::Device) { - if device.ui.saves.flash.len() < FLASHRAM_SIZE { - device.ui.saves.flash.resize(FLASHRAM_SIZE, 0xFF) + if device.ui.saves.flash.0.len() < FLASHRAM_SIZE { + device.ui.saves.flash.0.resize(FLASHRAM_SIZE, 0xFF) } } @@ -45,7 +45,7 @@ pub fn read_mem_sram(device: &mut device::Device, address: u64) -> u32 { format_sram(device); return u32::from_be_bytes( - device.ui.saves.sram[masked_address..masked_address + 4] + device.ui.saves.sram.0[masked_address..masked_address + 4] .try_into() .unwrap(), ); @@ -86,14 +86,14 @@ pub fn write_mem_sram(device: &mut device::Device, address: u64, value: u32, mas format_sram(device); let mut data = u32::from_be_bytes( - device.ui.saves.sram[masked_address..masked_address + 4] + device.ui.saves.sram.0[masked_address..masked_address + 4] .try_into() .unwrap(), ); device::memory::masked_write_32(&mut data, value, mask); - device.ui.saves.sram[masked_address..masked_address + 4].copy_from_slice(&data.to_be_bytes()); + device.ui.saves.sram.0[masked_address..masked_address + 4].copy_from_slice(&data.to_be_bytes()); - ui::storage::write_save(&mut device.ui, ui::storage::SaveTypes::Sram); + device.ui.saves.sram.1 = true } pub fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask: u32) { @@ -142,12 +142,12 @@ pub fn dma_read_sram( format_sram(device); while i < dram_addr + length { - device.ui.saves.sram[j as usize] = device.rdram.mem[i as usize ^ device.byte_swap]; + device.ui.saves.sram.0[j as usize] = device.rdram.mem[i as usize ^ device.byte_swap]; i += 1; j += 1; } - ui::storage::write_save(&mut device.ui, ui::storage::SaveTypes::Sram); + device.ui.saves.sram.1 = true } pub fn dma_read_flash(device: &mut device::Device, cart_addr: u32, dram_addr: u32, length: u32) { @@ -192,7 +192,7 @@ pub fn dma_write_sram( format_sram(device); while i < dram_addr + length { - device.rdram.mem[i as usize ^ device.byte_swap] = device.ui.saves.sram[j as usize]; + device.rdram.mem[i as usize ^ device.byte_swap] = device.ui.saves.sram.0[j as usize]; i += 1; j += 1; } @@ -233,7 +233,7 @@ pub fn dma_write_flash( /* do actual DMA */ for i in 0..length { device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap] = - device.ui.saves.flash[(cart_addr + i) as usize]; + device.ui.saves.flash.0[(cart_addr + i) as usize]; } } else { /* other accesses are not implemented */ @@ -272,14 +272,14 @@ pub fn flashram_command(device: &mut device::Device, command: u32) { if device.flashram.mode == FlashramMode::SectorErase { let offset: usize = (device.flashram.erase_page & 0xff80) as usize * 128; for i in 0..128 * 128 { - device.ui.saves.flash[offset as usize + i] = 0xFF; + device.ui.saves.flash.0[offset as usize + i] = 0xFF; } - ui::storage::write_save(&mut device.ui, ui::storage::SaveTypes::Flash); + device.ui.saves.flash.1 = true } else if device.flashram.mode == FlashramMode::ChipErase { for i in 0..FLASHRAM_SIZE { - device.ui.saves.flash[i] = 0xFF; + device.ui.saves.flash.0[i] = 0xFF; } - ui::storage::write_save(&mut device.ui, ui::storage::SaveTypes::Flash); + device.ui.saves.flash.1 = true } else { panic!("Unexpected flash erase command") } @@ -297,9 +297,10 @@ pub fn flashram_command(device: &mut device::Device, command: u32) { /* program selected page */ let offset: usize = (command & 0xffff) as usize * 128; for i in 0..128 { - device.ui.saves.flash[(offset + i) as usize] = device.flashram.page_buf[i as usize]; + device.ui.saves.flash.0[(offset + i) as usize] = + device.flashram.page_buf[i as usize]; } - ui::storage::write_save(&mut device.ui, ui::storage::SaveTypes::Flash); + device.ui.saves.flash.1 = true; /* clear program busy flag, set program success flag, transition to status mode */ device.flashram.status &= !0x01; diff --git a/src/main.rs b/src/main.rs index 3a15a31..4b692ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,46 @@ struct Args { game: Option, #[arg(short, long)] fullscreen: bool, + #[arg( + short, + long, + value_name = "PROFILE_NAME", + help = "Create a new input profile (keyboard/gamepad mappings)." + )] + configure_input_profile: Option, + #[arg( + short, + long, + value_name = "PROFILE_NAME", + help = "Must also specify --port. Used to bind a previously created profile to a port" + )] + bind_input_profile: Option, + #[arg( + short, + long, + help = "Lists connected controllers which can be used in --assign-controller" + )] + list_controllers: bool, + #[arg( + short, + long, + value_name = "CONTROLLER_NUMBER", + help = "Must also specify --port. Used to assign a controller listed in --list-controllers to a port" + )] + assign_controller: Option, + #[arg( + short, + long, + value_name = "PORT", + help = "Valid values: 1-4. To be used alongside --bind-input-profile and --assign-controller" + )] + port: Option, + #[arg( + short = 'z', + long, + help = "Clear all input profile bindings and controller assignments" + )] + clear_input_bindings: bool, } fn swap_rom(contents: Vec) -> Vec { @@ -93,12 +133,55 @@ fn get_rom_contents(file_path: &std::path::Path) -> Vec { fn main() { let args = Args::parse(); + let mut device = device::Device::new(); + + if args.clear_input_bindings { + ui::input::clear_bindings(&mut device.ui); + return; + } + if args.port.is_some() { + let port = args.port.unwrap(); + if port < 1 || port > 4 { + println!("Port must be betwen 1 and 4"); + return; + } + } + if args.list_controllers { + ui::input::list_controllers(&mut device.ui); + return; + } + if args.assign_controller.is_some() { + if args.port.is_none() { + println!("Must specify port number"); + return; + } + ui::input::assign_controller( + &mut device.ui, + args.assign_controller.unwrap(), + args.port.unwrap(), + ); + return; + } + if args.bind_input_profile.is_some() { + if args.port.is_none() { + println!("Must specify port number"); + return; + } + ui::input::bind_input_profile( + &mut device.ui, + args.bind_input_profile.unwrap(), + args.port.unwrap(), + ); + return; + } + if args.configure_input_profile.is_some() { + ui::input::configure_input_profile(&mut device.ui, args.configure_input_profile.unwrap()); + return; + } let file_path = std::path::Path::new(args.game.as_ref().unwrap()); let rom_contents = get_rom_contents(file_path); - let mut device = device::Device::new(); - device::cart_rom::init(&mut device, rom_contents); // cart needs to come before rdram // rdram pointer is shared with parallel-rdp @@ -106,6 +189,7 @@ fn main() { ui::audio::init(&mut device.ui, 33600); ui::video::init(&mut device.ui, rdram_ptr, rdram_size, args.fullscreen); + ui::input::init(&mut device.ui); device::mi::init(&mut device); device::pif::init(&mut device); diff --git a/src/ui.rs b/src/ui.rs index 4a084d6..e8176ca 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,9 +1,13 @@ pub mod audio; +pub mod config; pub mod input; pub mod storage; pub mod video; pub struct Ui { + pub controllers: [input::Controllers; 4], + pub config_file_path: std::path::PathBuf, + pub config: config::Config, pub save_type: Vec, pub game_id: String, pub game_hash: String, @@ -12,16 +16,66 @@ pub struct Ui { pub sdl_context: Option, pub video_subsystem: Option, pub audio_subsystem: Option, + pub joystick_subsystem: Option, + pub controller_subsystem: Option, pub window: Option, pub audio_device: Option>, } +impl Drop for Ui { + fn drop(&mut self) { + storage::write_saves(self); + write_config(self); + } +} + +fn write_config(ui: &mut Ui) { + let f = std::fs::File::create(ui.config_file_path.clone()).unwrap(); + serde_json::to_writer_pretty(f, &ui.config).unwrap(); +} + impl Ui { pub fn new() -> Ui { let sdl_context = sdl2::init().unwrap(); let video_subsystem = sdl_context.video().unwrap(); let audio_subsystem = sdl_context.audio().unwrap(); + let joystick_subsystem = sdl_context.joystick().unwrap(); + let controller_subsystem = sdl_context.game_controller().unwrap(); + + let config_file_path = dirs::config_dir() + .unwrap() + .join("gopher64") + .join("config.json"); + let config_file = std::fs::read(config_file_path.clone()); + let mut config_map = config::Config::new(); + if config_file.is_ok() { + let result = serde_json::from_slice(config_file.unwrap().as_ref()); + if result.is_ok() { + config_map = result.unwrap(); + } + } + Ui { + controllers: [ + input::Controllers { + game_controller: None, + joystick: None, + }, + input::Controllers { + game_controller: None, + joystick: None, + }, + input::Controllers { + game_controller: None, + joystick: None, + }, + input::Controllers { + game_controller: None, + joystick: None, + }, + ], + config_file_path: config_file_path, + config: config_map, save_type: vec![], game_id: String::new(), game_hash: String::new(), @@ -33,15 +87,17 @@ impl Ui { romsave_file_path: std::path::PathBuf::new(), }, saves: storage::Saves { - eeprom: Vec::new(), - sram: Vec::new(), - flash: Vec::new(), - mempak: Vec::new(), - romsave: serde_json::Map::new(), + eeprom: (Vec::new(), false), + sram: (Vec::new(), false), + flash: (Vec::new(), false), + mempak: (Vec::new(), false), + romsave: (serde_json::Map::new(), false), }, sdl_context: Some(sdl_context), video_subsystem: Some(video_subsystem), audio_subsystem: Some(audio_subsystem), + joystick_subsystem: Some(joystick_subsystem), + controller_subsystem: Some(controller_subsystem), window: None, audio_device: None, } diff --git a/src/ui/config.rs b/src/ui/config.rs new file mode 100644 index 0000000..4514d0b --- /dev/null +++ b/src/ui/config.rs @@ -0,0 +1,41 @@ +use crate::ui; + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct InputProfile { + pub keys: [(bool, i32); 18], + pub controller_buttons: [(bool, i32); 14], + pub controller_axis: [(bool, i32, i16); 18], + pub joystick_buttons: [(bool, u32); 14], + pub joystick_hat: [(bool, u32, i8); 14], + pub joystick_axis: [(bool, u32, i16); 18], +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct Input { + pub input_profiles: std::collections::HashMap, + pub input_profile_binding: [String; 4], + pub controller_assignment: [Option; 4], +} +#[derive(serde::Serialize, serde::Deserialize)] +pub struct Config { + pub input: Input, +} + +impl Config { + pub fn new() -> Config { + let mut input_profiles = std::collections::HashMap::new(); + input_profiles.insert("default".to_string(), ui::input::get_default_profile()); + Config { + input: Input { + input_profile_binding: [ + "default".to_string(), + "default".to_string(), + "default".to_string(), + "default".to_string(), + ], + controller_assignment: [None, None, None, None], + input_profiles: input_profiles, + }, + } + } +} diff --git a/src/ui/input.rs b/src/ui/input.rs index 9d902c6..efefccb 100644 --- a/src/ui/input.rs +++ b/src/ui/input.rs @@ -1,7 +1,36 @@ +use std::ops::Neg; + use crate::ui; +pub const R_DPAD: usize = 0; +pub const L_DPAD: usize = 1; +pub const D_DPAD: usize = 2; +pub const U_DPAD: usize = 3; +pub const START_BUTTON: usize = 4; +pub const Z_TRIG: usize = 5; +pub const B_BUTTON: usize = 6; +pub const A_BUTTON: usize = 7; +pub const R_CBUTTON: usize = 8; +pub const L_CBUTTON: usize = 9; +pub const D_CBUTTON: usize = 10; +pub const U_CBUTTON: usize = 11; +pub const R_TRIG: usize = 12; +pub const L_TRIG: usize = 13; +pub const X_AXIS: usize = 16; +pub const Y_AXIS: usize = 24; + +pub const AXIS_LEFT: usize = 14; +pub const AXIS_RIGHT: usize = 15; +pub const AXIS_UP: usize = 16; +pub const AXIS_DOWN: usize = 17; + pub const MAX_AXIS_VALUE: f64 = 85.0; +pub struct Controllers { + pub game_controller: Option, + pub joystick: Option, +} + pub fn bound_axis(x: &mut f64, y: &mut f64) { let radius = 95.0; // this is roughly the maxium diagonal distance of the controller @@ -15,42 +44,478 @@ pub fn bound_axis(x: &mut f64, y: &mut f64) { *y *= scale_factor; } } -pub fn get(ui: &mut ui::Ui, _channel: usize) -> u32 { + +pub fn set_axis_from_joystick( + profile: &ui::config::InputProfile, + joystick: &sdl2::joystick::Joystick, +) -> (f64, f64) { + let mut x = 0.0; + let mut y = 0.0; + if profile.joystick_axis[AXIS_LEFT].0 { + let axis_position = joystick.axis(profile.joystick_axis[AXIS_LEFT].1).unwrap(); + if axis_position as isize * profile.joystick_axis[AXIS_LEFT].2 as isize > 0 { + x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64; + } + } + if profile.joystick_axis[AXIS_RIGHT].0 { + let axis_position = joystick.axis(profile.joystick_axis[AXIS_RIGHT].1).unwrap(); + if axis_position as isize * profile.joystick_axis[AXIS_RIGHT].2 as isize > 0 { + x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64; + } + } + if profile.joystick_axis[AXIS_DOWN].0 { + let axis_position = joystick.axis(profile.joystick_axis[AXIS_DOWN].1).unwrap(); + if axis_position as isize * profile.joystick_axis[AXIS_DOWN].2 as isize > 0 { + y = (axis_position as f64 * MAX_AXIS_VALUE as f64 / i16::MAX as f64).neg(); + } + } + if profile.joystick_axis[AXIS_UP].0 { + let axis_position = joystick.axis(profile.joystick_axis[AXIS_UP].1).unwrap(); + if axis_position as isize * profile.joystick_axis[AXIS_UP].2 as isize > 0 { + y = (axis_position as f64 * MAX_AXIS_VALUE as f64 / i16::MAX as f64).neg(); + } + } + return (x, y); +} + +pub fn set_axis_from_controller( + profile: &ui::config::InputProfile, + controller: &sdl2::controller::GameController, +) -> (f64, f64) { + unsafe { + let mut x = 0.0; + let mut y = 0.0; + if profile.controller_axis[AXIS_LEFT].0 { + let axis_position = + controller.axis(std::mem::transmute(profile.controller_axis[AXIS_LEFT].1)); + if axis_position as isize * profile.controller_axis[AXIS_LEFT].2 as isize > 0 { + x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64; + } + } + if profile.controller_axis[AXIS_RIGHT].0 { + let axis_position = + controller.axis(std::mem::transmute(profile.controller_axis[AXIS_RIGHT].1)); + if axis_position as isize * profile.controller_axis[AXIS_RIGHT].2 as isize > 0 { + x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64; + } + } + if profile.controller_axis[AXIS_DOWN].0 { + let axis_position = + controller.axis(std::mem::transmute(profile.controller_axis[AXIS_DOWN].1)); + if axis_position as isize * profile.controller_axis[AXIS_DOWN].2 as isize > 0 { + y = (axis_position as f64 * MAX_AXIS_VALUE as f64 / i16::MAX as f64).neg(); + } + } + if profile.controller_axis[AXIS_UP].0 { + let axis_position = + controller.axis(std::mem::transmute(profile.controller_axis[AXIS_UP].1)); + if axis_position as isize * profile.controller_axis[AXIS_UP].2 as isize > 0 { + y = (axis_position as f64 * MAX_AXIS_VALUE as f64 / i16::MAX as f64).neg(); + } + } + return (x, y); + } +} + +pub fn set_axis_from_keys( + profile: &ui::config::InputProfile, + keyboard_state: sdl2::keyboard::KeyboardState, +) -> (f64, f64) { + unsafe { + let mut x = 0.0; + let mut y = 0.0; + if profile.keys[AXIS_LEFT].0 { + if keyboard_state.is_scancode_pressed(std::mem::transmute(profile.keys[AXIS_LEFT].1)) { + x = -MAX_AXIS_VALUE + } + } + if profile.keys[AXIS_RIGHT].0 { + if keyboard_state.is_scancode_pressed(std::mem::transmute(profile.keys[AXIS_RIGHT].1)) { + x = MAX_AXIS_VALUE + } + } + if profile.keys[AXIS_DOWN].0 { + if keyboard_state.is_scancode_pressed(std::mem::transmute(profile.keys[AXIS_DOWN].1)) { + y = -MAX_AXIS_VALUE + } + } + if profile.keys[AXIS_UP].0 { + if keyboard_state.is_scancode_pressed(std::mem::transmute(profile.keys[AXIS_UP].1)) { + y = MAX_AXIS_VALUE + } + } + return (x, y); + } +} + +pub fn set_buttons_from_joystick( + profile: &ui::config::InputProfile, + i: usize, + joystick: &sdl2::joystick::Joystick, + keys: &mut u32, +) { + let profile_joystick_button = profile.joystick_buttons[i]; + if profile_joystick_button.0 { + *keys |= (joystick.button(profile_joystick_button.1).unwrap() as u32) << i; + } + + let profile_joystick_hat = profile.joystick_hat[i]; + if profile_joystick_hat.0 { + if joystick.hat(profile_joystick_hat.1).unwrap() + == unsafe { std::mem::transmute(profile_joystick_hat.2) } + { + *keys |= 1 << i; + } + } + + let profile_joystick_axis = profile.joystick_axis[i]; + if profile_joystick_axis.0 { + let axis_position = unsafe { + joystick + .axis(std::mem::transmute(profile_joystick_axis.1)) + .unwrap() + }; + if axis_position as isize * profile_joystick_axis.2 as isize > 0 { + if axis_position.saturating_abs() > i16::MAX / 2 { + *keys |= 1 << i; + } + } + } +} + +pub fn set_buttons_from_controller( + profile: &ui::config::InputProfile, + i: usize, + controller: &sdl2::controller::GameController, + keys: &mut u32, +) { + let profile_controller_button = profile.controller_buttons[i]; + if profile_controller_button.0 { + unsafe { + *keys |= + (controller.button(std::mem::transmute(profile_controller_button.1)) as u32) << i; + } + } + + let profile_controller_axis = profile.controller_axis[i]; + if profile_controller_axis.0 { + let axis_position = + unsafe { controller.axis(std::mem::transmute(profile_controller_axis.1)) }; + if axis_position as isize * profile_controller_axis.2 as isize > 0 { + if axis_position.saturating_abs() > i16::MAX / 2 { + *keys |= 1 << i; + } + } + } +} + +pub fn get(ui: &mut ui::Ui, channel: usize) -> u32 { let context = ui.sdl_context.as_mut().unwrap(); let events = context.event_pump().unwrap(); let keyboard_state = events.keyboard_state(); + let profile_name = ui.config.input.input_profile_binding[channel].clone(); + let profile = ui.config.input.input_profiles.get(&profile_name).unwrap(); let mut keys = 0; - keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::D) as u32) << 0; - keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::A) as u32) << 1; - keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::S) as u32) << 2; - keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::W) as u32) << 3; - keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::Return) as u32) << 4; - keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::Z) as u32) << 5; - keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::LCtrl) as u32) << 6; - keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::LShift) as u32) << 7; - keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::L) as u32) << 8; - keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::J) as u32) << 9; - keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::K) as u32) << 10; - keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::I) as u32) << 11; - keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::C) as u32) << 12; - keys |= (keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::X) as u32) << 13; + let controller = &ui.controllers[channel].game_controller; + let joystick = &ui.controllers[channel].joystick; + for i in 0..14 { + if profile_name != "default" || (channel == 0 && profile_name == "default") { + let profile_key = profile.keys[i]; + if profile_key.0 { + unsafe { + keys |= (keyboard_state.is_scancode_pressed(std::mem::transmute(profile_key.1)) + as u32) + << i; + } + } + } + + if joystick.is_some() { + set_buttons_from_joystick(profile, i, joystick.as_ref().unwrap(), &mut keys); + } else if controller.is_some() { + set_buttons_from_controller(profile, i, controller.as_ref().unwrap(), &mut keys); + } + } let mut x: f64 = 0.0; let mut y: f64 = 0.0; - if keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::Left) { - x = -MAX_AXIS_VALUE - } else if keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::Right) { - x = MAX_AXIS_VALUE + + if profile_name != "default" || (channel == 0 && profile_name == "default") { + (x, y) = set_axis_from_keys(profile, keyboard_state); } - if keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::Down) { - y = -MAX_AXIS_VALUE - } else if keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::Up) { - y = MAX_AXIS_VALUE + + if joystick.is_some() { + (x, y) = set_axis_from_joystick(profile, joystick.as_ref().unwrap()) + } else if controller.is_some() { + (x, y) = set_axis_from_controller(profile, controller.as_ref().unwrap()) } bound_axis(&mut x, &mut y); - keys |= (x.round() as i8 as u32) << 16; - keys |= (y.round() as i8 as u32) << 24; + keys |= (x.round() as i8 as u8 as u32) << X_AXIS; + keys |= (y.round() as i8 as u8 as u32) << Y_AXIS; return keys; } + +pub fn list_controllers(ui: &mut ui::Ui) { + let joystick_subsystem = ui.joystick_subsystem.as_ref().unwrap(); + let num_joysticks = joystick_subsystem.num_joysticks().unwrap(); + if num_joysticks == 0 { + println!("No controllers connected") + } + for i in 0..num_joysticks { + println!("{}: {}", i, joystick_subsystem.name_for_index(i).unwrap()) + } +} + +pub fn assign_controller(ui: &mut ui::Ui, controller: u32, port: usize) { + let joystick_subsystem = ui.joystick_subsystem.as_ref().unwrap(); + let num_joysticks = joystick_subsystem.num_joysticks().unwrap(); + if controller < num_joysticks { + ui.config.input.controller_assignment[port - 1] = Some( + joystick_subsystem + .device_guid(controller) + .unwrap() + .to_string(), + ); + } else { + println!("Invalid controller number") + } +} + +pub fn bind_input_profile(ui: &mut ui::Ui, profile: String, port: usize) { + if ui.config.input.input_profiles.contains_key(&profile) { + ui.config.input.input_profile_binding[port - 1] = profile; + } else { + println!("Invalid profile name") + } +} + +pub fn clear_bindings(ui: &mut ui::Ui) { + for i in 0..4 { + ui.config.input.controller_assignment[i] = None; + ui.config.input.input_profile_binding[i] = "default".to_string(); + } +} + +pub fn configure_input_profile(ui: &mut ui::Ui, profile: String) { + if profile == "default" { + println!("Profile name cannot be default"); + return; + } + let joystick_subsystem = ui.sdl_context.as_ref().unwrap().joystick().unwrap(); + let mut joysticks = vec![]; + for i in 0..joystick_subsystem.num_joysticks().unwrap() { + joysticks.push(ui.sdl_context.as_ref().unwrap().joystick().unwrap().open(i)); + } + + let mut builder = + ui.video_subsystem + .as_ref() + .unwrap() + .window("configure input profile", 640, 480); + builder.position_centered(); + let window = builder.build().unwrap(); + let mut canvas = window.into_canvas().build().unwrap(); + let font = + rusttype::Font::try_from_bytes(include_bytes!("../../data/Roboto-Regular.ttf")).unwrap(); + + let key_labels = [ + ("A", A_BUTTON), + ("B", B_BUTTON), + ("Start", START_BUTTON), + ("D Up", U_DPAD), + ("D Down", D_DPAD), + ("D Left", L_DPAD), + ("D Right", R_DPAD), + ("C Up", U_CBUTTON), + ("C Down", D_CBUTTON), + ("C Left", L_CBUTTON), + ("C Right", R_CBUTTON), + ("L", L_TRIG), + ("R", R_TRIG), + ("Z", Z_TRIG), + ("Control Stick Up", AXIS_UP), + ("Control Stick Down", AXIS_DOWN), + ("Control Stick Left", AXIS_LEFT), + ("Control Stick Right", AXIS_RIGHT), + ]; + + let mut new_keys = [(false, 0); 18]; + let mut new_joystick_buttons = [(false, 0u32); 14]; + let mut new_joystick_hat = [(false, 0u32, 0); 14]; + let mut new_joystick_axis = [(false, 0u32, 0); 18]; + + let mut last_axis_result = (false, 0, 0); + let mut event_pump = ui.sdl_context.as_ref().unwrap().event_pump().unwrap(); + for (key, value) in key_labels.iter() { + for _event in event_pump.poll_iter() {} // clear events + + ui::video::draw_text( + format!("Select binding for: {key}").as_str(), + &mut canvas, + &font, + ); + + let mut key_set = false; + while !key_set { + std::thread::sleep(std::time::Duration::from_millis(100)); + for event in event_pump.poll_iter() { + match event { + sdl2::event::Event::Window { + win_event: sdl2::event::WindowEvent::Close, + .. + } => return, + sdl2::event::Event::KeyDown { + scancode: Some(scancode), + .. + } => { + new_keys[*value] = (true, scancode as i32); + key_set = true + } + sdl2::event::Event::JoyButtonDown { button_idx, .. } => { + new_joystick_buttons[*value] = (true, button_idx as u32); + key_set = true + } + sdl2::event::Event::JoyHatMotion { hat_idx, state, .. } => { + new_joystick_hat[*value] = + unsafe { (true, hat_idx as u32, std::mem::transmute(state)) }; + key_set = true + } + sdl2::event::Event::JoyAxisMotion { + axis_idx, + value: axis_value, + .. + } => { + if axis_value.saturating_abs() > 24576 { + let result = ( + true, + axis_idx as u32, + axis_value / axis_value.saturating_abs(), + ); + if result != last_axis_result { + new_joystick_axis[*value] = result; + last_axis_result = result; + key_set = true + } + } + } + _ => {} + } + } + } + } + + let new_profile = ui::config::InputProfile { + keys: new_keys, + controller_buttons: Default::default(), + controller_axis: Default::default(), + joystick_buttons: new_joystick_buttons, + joystick_hat: new_joystick_hat, + joystick_axis: new_joystick_axis, + }; + ui.config.input.input_profiles.insert(profile, new_profile); +} + +pub fn get_default_profile() -> ui::config::InputProfile { + let mut default_controller_buttons = [(false, 0); 14]; + let mut default_controller_axis = [(false, 0, 0); 18]; + let mut default_keys = [(false, 0); 18]; + default_keys[R_DPAD] = (true, sdl2::keyboard::Scancode::D as i32); + default_keys[L_DPAD] = (true, sdl2::keyboard::Scancode::A as i32); + default_keys[D_DPAD] = (true, sdl2::keyboard::Scancode::S as i32); + default_keys[U_DPAD] = (true, sdl2::keyboard::Scancode::W as i32); + default_keys[START_BUTTON] = (true, sdl2::keyboard::Scancode::Return as i32); + default_keys[Z_TRIG] = (true, sdl2::keyboard::Scancode::Z as i32); + default_keys[B_BUTTON] = (true, sdl2::keyboard::Scancode::LCtrl as i32); + default_keys[A_BUTTON] = (true, sdl2::keyboard::Scancode::LShift as i32); + default_keys[R_CBUTTON] = (true, sdl2::keyboard::Scancode::L as i32); + default_keys[L_CBUTTON] = (true, sdl2::keyboard::Scancode::J as i32); + default_keys[D_CBUTTON] = (true, sdl2::keyboard::Scancode::K as i32); + default_keys[U_CBUTTON] = (true, sdl2::keyboard::Scancode::I as i32); + default_keys[R_TRIG] = (true, sdl2::keyboard::Scancode::C as i32); + default_keys[L_TRIG] = (true, sdl2::keyboard::Scancode::X as i32); + default_keys[AXIS_LEFT] = (true, sdl2::keyboard::Scancode::Left as i32); + default_keys[AXIS_RIGHT] = (true, sdl2::keyboard::Scancode::Right as i32); + default_keys[AXIS_UP] = (true, sdl2::keyboard::Scancode::Up as i32); + default_keys[AXIS_DOWN] = (true, sdl2::keyboard::Scancode::Down as i32); + + default_controller_buttons[R_DPAD] = (true, sdl2::controller::Button::DPadRight as i32); + default_controller_buttons[L_DPAD] = (true, sdl2::controller::Button::DPadLeft as i32); + default_controller_buttons[D_DPAD] = (true, sdl2::controller::Button::DPadDown as i32); + default_controller_buttons[U_DPAD] = (true, sdl2::controller::Button::DPadUp as i32); + default_controller_buttons[START_BUTTON] = (true, sdl2::controller::Button::Start as i32); + default_controller_axis[Z_TRIG] = (true, sdl2::controller::Axis::TriggerLeft as i32, 1); + default_controller_buttons[B_BUTTON] = (true, sdl2::controller::Button::X as i32); + default_controller_buttons[A_BUTTON] = (true, sdl2::controller::Button::A as i32); + default_controller_axis[R_CBUTTON] = (true, sdl2::controller::Axis::RightX as i32, 1); + default_controller_axis[L_CBUTTON] = (true, sdl2::controller::Axis::RightX as i32, -1); + default_controller_axis[D_CBUTTON] = (true, sdl2::controller::Axis::RightY as i32, 1); + default_controller_axis[U_CBUTTON] = (true, sdl2::controller::Axis::RightY as i32, -1); + default_controller_buttons[R_TRIG] = (true, sdl2::controller::Button::RightShoulder as i32); + default_controller_buttons[L_TRIG] = (true, sdl2::controller::Button::LeftShoulder as i32); + default_controller_axis[AXIS_LEFT] = (true, sdl2::controller::Axis::LeftX as i32, -1); + default_controller_axis[AXIS_RIGHT] = (true, sdl2::controller::Axis::LeftX as i32, 1); + default_controller_axis[AXIS_UP] = (true, sdl2::controller::Axis::LeftY as i32, -1); + default_controller_axis[AXIS_DOWN] = (true, sdl2::controller::Axis::LeftY as i32, 1); + + ui::config::InputProfile { + keys: default_keys, + controller_buttons: default_controller_buttons, + controller_axis: default_controller_axis, + joystick_buttons: Default::default(), + joystick_hat: Default::default(), + joystick_axis: Default::default(), + } +} + +pub fn init(ui: &mut ui::Ui) { + let joystick_subsystem = ui.joystick_subsystem.as_ref().unwrap(); + let mut taken = [false; 4]; + for i in 0..4 { + let controller_assignment = &ui.config.input.controller_assignment[i]; + if controller_assignment.is_some() { + let mut joystick_index = u32::MAX; + let guid = controller_assignment.clone().unwrap(); + for i in 0..joystick_subsystem.num_joysticks().unwrap() { + if joystick_subsystem.device_guid(i).unwrap().to_string() == guid + && !taken[i as usize] + { + joystick_index = i; + taken[i as usize] = true; + break; + } + } + if joystick_index < u32::MAX { + if ui.config.input.input_profile_binding[i] == "default" { + let controller_result = ui + .sdl_context + .as_ref() + .unwrap() + .game_controller() + .unwrap() + .open(joystick_index); + if controller_result.is_ok() { + ui.controllers[i].game_controller = Some(controller_result.unwrap()); + } + } + if ui.controllers[i].game_controller.is_none() { + let joystick_result = ui + .sdl_context + .as_ref() + .unwrap() + .joystick() + .unwrap() + .open(joystick_index); + if joystick_result.is_err() { + println!("could not connect joystick") + } else { + ui.controllers[i].joystick = Some(joystick_result.unwrap()); + } + } + } else { + println!("Could not bind assigned controller"); + } + } + } +} diff --git a/src/ui/storage.rs b/src/ui/storage.rs index 32586df..172eb2c 100644 --- a/src/ui/storage.rs +++ b/src/ui/storage.rs @@ -18,12 +18,14 @@ pub struct Paths { pub romsave_file_path: std::path::PathBuf, } +// the bool indicates whether the save has been written to +// if that is the case, it will be flushed to the disk when the program closes pub struct Saves { - pub eeprom: Vec, - pub sram: Vec, - pub flash: Vec, - pub mempak: Vec, - pub romsave: serde_json::Map, + pub eeprom: (Vec, bool), + pub sram: (Vec, bool), + pub flash: (Vec, bool), + pub mempak: (Vec, bool), + pub romsave: (serde_json::Map, bool), } pub fn get_save_type(game_id: &str) -> Vec { @@ -88,9 +90,7 @@ pub fn init(ui: &mut ui::Ui) { let id = ui.game_id.as_str(); ui.save_type = get_save_type(id); - let mut base_path = dirs::data_dir().unwrap(); - base_path.push("gopher64"); - base_path.push("saves"); + let base_path = dirs::data_dir().unwrap().join("gopher64").join("saves"); let result = std::fs::create_dir_all(base_path.clone()); if result.is_err() { @@ -126,23 +126,23 @@ pub fn init(ui: &mut ui::Ui) { pub fn load_saves(ui: &mut ui::Ui) { let eep = std::fs::read(&mut ui.paths.eep_file_path); if eep.is_ok() { - ui.saves.eeprom = eep.unwrap(); + ui.saves.eeprom.0 = eep.unwrap(); } let sra = std::fs::read(&mut ui.paths.sra_file_path); if sra.is_ok() { - ui.saves.sram = sra.unwrap(); + ui.saves.sram.0 = sra.unwrap(); } let fla = std::fs::read(&mut ui.paths.fla_file_path); if fla.is_ok() { - ui.saves.flash = fla.unwrap(); + ui.saves.flash.0 = fla.unwrap(); } let mempak = std::fs::read(&mut ui.paths.pak_file_path); if mempak.is_ok() { - ui.saves.mempak = mempak.unwrap(); + ui.saves.mempak.0 = mempak.unwrap(); } let romsave = std::fs::read(&mut ui.paths.romsave_file_path); if romsave.is_ok() { - ui.saves.romsave = serde_json::from_slice(romsave.unwrap().as_ref()).unwrap(); + ui.saves.romsave.0 = serde_json::from_slice(romsave.unwrap().as_ref()).unwrap(); } } @@ -151,25 +151,43 @@ pub fn write_rom_save(ui: &mut ui::Ui) { serde_json::to_writer(f, &ui.saves.romsave).unwrap(); } +pub fn write_saves(ui: &mut ui::Ui) { + if ui.saves.eeprom.1 { + write_save(ui, SaveTypes::Eeprom16k) + } + if ui.saves.sram.1 { + write_save(ui, SaveTypes::Sram) + } + if ui.saves.flash.1 { + write_save(ui, SaveTypes::Flash) + } + if ui.saves.mempak.1 { + write_save(ui, SaveTypes::Mempak) + } + if ui.saves.romsave.1 { + write_save(ui, SaveTypes::Romsave) + } +} + pub fn write_save(ui: &mut ui::Ui, save_type: SaveTypes) { let path: &std::path::Path; let data: &Vec; match save_type { SaveTypes::Eeprom4k | SaveTypes::Eeprom16k => { path = ui.paths.eep_file_path.as_ref(); - data = ui.saves.eeprom.as_ref(); + data = ui.saves.eeprom.0.as_ref(); } SaveTypes::Sram => { path = ui.paths.sra_file_path.as_ref(); - data = ui.saves.sram.as_ref(); + data = ui.saves.sram.0.as_ref(); } SaveTypes::Flash => { path = ui.paths.fla_file_path.as_ref(); - data = ui.saves.flash.as_ref(); + data = ui.saves.flash.0.as_ref(); } SaveTypes::Mempak => { path = ui.paths.pak_file_path.as_ref(); - data = ui.saves.mempak.as_ref(); + data = ui.saves.mempak.0.as_ref(); } SaveTypes::Romsave => { write_rom_save(ui); diff --git a/src/ui/video.rs b/src/ui/video.rs index 883addc..f6d120d 100644 --- a/src/ui/video.rs +++ b/src/ui/video.rs @@ -50,3 +50,36 @@ pub fn rdp_full_sync() { full_sync(); } } + +pub fn draw_text( + text: &str, + canvas: &mut sdl2::render::Canvas, + font: &rusttype::Font, +) { + let text_size = 32; + let scale = rusttype::Scale::uniform(text_size as f32); + let v_metrics = font.v_metrics(scale); + let offset = rusttype::point(10.0, 10.0 + v_metrics.ascent); + + // Clear the canvas + canvas.set_draw_color(sdl2::pixels::Color::RGB(0, 0, 0)); + canvas.clear(); + + for glyph in font.layout(text, scale, offset) { + if let Some(bb) = glyph.pixel_bounding_box() { + glyph.draw(|x, y, v| { + let x = x as i32 + bb.min.x; + let y = y as i32 + bb.min.y + (240 - text_size); + if v > 0.5 { + canvas.set_draw_color(sdl2::pixels::Color::RGB(255, 255, 255)); + canvas + .draw_point(sdl2::rect::Point::new(x, y)) + .expect("Error drawing pixel"); + } + }); + } + } + + // Present the canvas + canvas.present(); +}