diff --git a/src/load_bin_window.rs b/src/load_bin_window.rs new file mode 100644 index 0000000..9e3e890 --- /dev/null +++ b/src/load_bin_window.rs @@ -0,0 +1,126 @@ +use std::{fmt::Display, path::PathBuf}; + +use eframe::egui::{self, DragValue}; +use egui_modal::Modal; +use rfd::FileDialog; + +use crate::{state::EmuState, window::Window}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum BinaryType { + Raw, + IntelHex, +} + +impl Display for BinaryType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Raw => f.write_str("Raw"), + Self::IntelHex => f.write_str("Intel HEX"), + } + } +} + +pub struct LoadBinWindow { + path: Option, + ftype: BinaryType, + start_addr: u16, +} + +impl LoadBinWindow { + pub fn new(ctx: &egui::Context) -> Self { + Modal::new(ctx, "load_bin_modal").open(); + Self { + path: None, + ftype: BinaryType::Raw, + start_addr: 0, + } + } + + fn choose_file(&mut self) { + let ihex_exts = ["hex", "mcs", "int", "ihex", "ihe", "ihx"]; + let file = FileDialog::new() + .add_filter( + "Binary files", + &["bin", "img", "hex", "mcs", "int", "ihex", "ihe", "ihx"], + ) + .add_filter("All files", &["*"]) + .pick_file(); + let Some(file) = file else { + return; + }; + if file + .extension() + .map_or(false, |ext| ihex_exts.contains(&ext.to_str().unwrap_or(""))) + { + self.ftype = BinaryType::IntelHex; + } else { + self.ftype = BinaryType::Raw; + } + self.path = Some(file); + } + + fn load_file(&self, state: &mut EmuState) { + let path = self.path.as_ref().unwrap(); + match self.ftype { + BinaryType::Raw => { + let bin = std::fs::read(path).unwrap(); + state.write_binary(self.start_addr as usize, bin); + } + BinaryType::IntelHex => { + let data = std::fs::read_to_string(path).unwrap(); + for record in ihex::Reader::new(&data) { + let record = record.unwrap(); + match record { + ihex::Record::Data { offset, value } => { + state.write_binary(offset as usize, value); + } + ihex::Record::StartLinearAddress(_) => todo!(), + ihex::Record::EndOfFile => (), + _ => unimplemented!(), + }; + } + } + } + } +} + +impl Window for LoadBinWindow { + fn draw(&mut self, ctx: &egui::Context, state: &mut EmuState) -> bool { + let modal = Modal::new(ctx, "load_bin_modal"); + modal.show(|ui| { + modal.title(ui, "Load binary"); + ui.horizontal(|ui| { + if ui.button("Choose file").clicked() { + self.choose_file(); + } + ui.label(format!( + "{}", + self.path.as_ref().map_or("".into(), |x| x + .file_name() + .map_or("".into(), |x| x.to_string_lossy().to_string())) + )); + }); + egui::ComboBox::from_label("File type") + .selected_text(format!("{}", self.ftype)) + .show_ui(ui, |ui| { + ui.selectable_value(&mut self.ftype, BinaryType::Raw, "Raw"); + ui.selectable_value(&mut self.ftype, BinaryType::IntelHex, "Intel HEX"); + }); + ui.horizontal(|ui| { + ui.set_enabled(self.ftype != BinaryType::IntelHex); + ui.add(DragValue::new(&mut self.start_addr).clamp_range(0..=65535)); + ui.label("Start address"); + }); + modal.buttons(ui, |ui| { + modal.caution_button(ui, "Cancel"); + ui.add_enabled_ui(self.path.is_some(), |ui| { + if modal.suggested_button(ui, "Load").clicked() { + self.load_file(state); + } + }) + }); + }); + !modal.is_open() + } +} diff --git a/src/main.rs b/src/main.rs index 4637a66..281132d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,10 +2,11 @@ mod audio; mod card; mod cpu; mod frontpanel; -mod ram; -mod window; -mod state; +mod load_bin_window; mod option_window; +mod ram; +mod state; +mod window; use std::collections::HashMap; @@ -15,6 +16,7 @@ use eframe::{ NativeOptions, }; use frontpanel::Textures; +use load_bin_window::LoadBinWindow; use option_window::OptionWindow; use rfd::FileDialog; use serde::{Deserialize, Serialize}; @@ -56,12 +58,11 @@ struct AltairEmulator { windows: HashMap<&'static str, Box>, } - impl AltairEmulator { fn new(cc: &eframe::CreationContext<'_>) -> Self { let audio_tx = AudioThread::init(); let options = if cc.storage.unwrap().get_string("options").is_none() { - Options::default() + Options::default() } else { eframe::get_value(cc.storage.unwrap(), "options").unwrap() }; @@ -89,37 +90,8 @@ impl AltairEmulator { ui.close_menu(); } if ui.button("Load binary file").clicked() { - let ihex_exts = ["hex", "mcs", "int", "ihex", "ihe", "ihx"]; - let file = FileDialog::new() - .add_filter( - "Binary files", - &["bin", "img", "hex", "mcs", "int", "ihex", "ihe", "ihx"], - ) - .add_filter("All files", &["*"]) - .pick_file(); - if let Some(file) = file { - if file - .extension() - .map_or(false, |ext| ihex_exts.contains(&ext.to_str().unwrap_or(""))) - { - let data = std::fs::read_to_string(file).unwrap(); - for record in ihex::Reader::new(&data) { - let record = record.unwrap(); - match record { - ihex::Record::Data { offset, value } => { - self.state.write_binary(offset as usize, value); - } - ihex::Record::StartLinearAddress(_) => todo!(), - ihex::Record::EndOfFile => (), - _ => unimplemented!(), - }; - } - } else { - // Raw binary - todo!(); - } - self.state.update_fp(); - } + self.windows + .insert("load_bin_window", Box::new(LoadBinWindow::new(ui.ctx()))); } }); }); diff --git a/src/option_window.rs b/src/option_window.rs index b9f0383..1b7ecbb 100644 --- a/src/option_window.rs +++ b/src/option_window.rs @@ -1,7 +1,7 @@ use eframe::egui::{self, Slider}; use egui_modal::Modal; -use crate::{Options, state::EmuState, window::Window}; +use crate::{state::EmuState, window::Window, Options}; pub struct OptionWindow { options: Options, diff --git a/src/state.rs b/src/state.rs index a4d0aeb..7af151f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2,7 +2,12 @@ use std::sync::mpsc::Sender; use rand::RngCore; -use crate::{cpu::{I8080, Status, MemCycle}, audio::AudioMessage, Options, frontpanel::{FrontpanelState, FrontpanelInteraction, ActionSwitch, switch::SwitchState}}; +use crate::{ + audio::AudioMessage, + cpu::{MemCycle, Status, I8080}, + frontpanel::{switch::SwitchState, ActionSwitch, FrontpanelInteraction, FrontpanelState}, + Options, +}; pub struct EmuState { mem: [u8; 65536], @@ -23,7 +28,7 @@ impl EmuState { running: false, audio_tx, options, - fp_state: FrontpanelState::new() + fp_state: FrontpanelState::new(), }; slf.apply_options(); slf