diff --git a/src/audio.rs b/src/audio.rs index 1106471..5a6ce9f 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -216,7 +216,7 @@ impl AudioThread { }; } }, - AudioMessage::SetVolume(vol) => self.sl.set_global_volume(*vol / 100.0) + AudioMessage::SetVolume(vol) => self.sl.set_global_volume(*vol / 100.0), } } diff --git a/src/frontpanel.rs b/src/frontpanel.rs index 7027fb8..cad037a 100644 --- a/src/frontpanel.rs +++ b/src/frontpanel.rs @@ -1,7 +1,7 @@ use std::path::Path; use eframe::{ - egui::{self, KeyboardShortcut, Modifiers, Sense, TextureOptions, Ui, Widget, Key}, + egui::{self, Key, KeyboardShortcut, Modifiers, Sense, TextureOptions, Ui, Widget}, epaint::{vec2, Pos2, Rect, TextureHandle}, }; @@ -117,10 +117,7 @@ impl Widget for &mut Frontpanel<'_> { ); let led_textures = led::Textures::new(self.textures.led_on.clone(), self.textures.led_off.clone()); - let resp = ui.allocate_response( - vec2(800.0, 333.0), - Sense::click(), - ); + let resp = ui.allocate_response(vec2(800.0, 333.0), Sense::click()); resp.request_focus(); let fp_rect = resp.rect; ui.allocate_ui_at_rect(fp_rect, |ui| { @@ -170,13 +167,17 @@ impl Widget for &mut Frontpanel<'_> { let key = ["Q", "W", "E", "R", "T", "Y", "U", "I"][key_offset]; let key = Key::from_name(&key).unwrap(); let mods = if (i - 1) < 8 { - Modifiers::SHIFT + Modifiers::SHIFT } else { Modifiers::NONE }; let shortcut = KeyboardShortcut::new(mods, key); if ui - .add(switch::ToggleSwitch::new(&mut sw_state, &sw_textures, Some(shortcut))) + .add(switch::ToggleSwitch::new( + &mut sw_state, + &sw_textures, + Some(shortcut), + )) .changed() { self.state.ad_sws = @@ -207,7 +208,12 @@ impl Widget for &mut Frontpanel<'_> { let up_shortcut = KeyboardShortcut::new(Modifiers::NONE, up_key); let down_shortcut = KeyboardShortcut::new(Modifiers::NONE, down_key); if ui - .add(switch::ThreePosSwitch::new(state, &sw_textures, Some(up_shortcut), Some(down_shortcut))) + .add(switch::ThreePosSwitch::new( + state, + &sw_textures, + Some(up_shortcut), + Some(down_shortcut), + )) .changed() { let sw = match i { diff --git a/src/frontpanel/switch.rs b/src/frontpanel/switch.rs index d27ed9b..b528645 100644 --- a/src/frontpanel/switch.rs +++ b/src/frontpanel/switch.rs @@ -59,8 +59,18 @@ pub struct ThreePosSwitch<'a> { } impl<'a> ThreePosSwitch<'a> { - pub fn new(state: &'a mut SwitchState, textures: &'a Textures, up_shortcut: Option, down_shortcut: Option) -> Self { - Self { state, textures, up_shortcut, down_shortcut } + pub fn new( + state: &'a mut SwitchState, + textures: &'a Textures, + up_shortcut: Option, + down_shortcut: Option, + ) -> Self { + Self { + state, + textures, + up_shortcut, + down_shortcut, + } } } @@ -74,16 +84,24 @@ impl Widget for ThreePosSwitch<'_> { NULL_TINT, ); let up_shortcut_pressed = self.up_shortcut.map_or(false, |shortcut| { - ui.input_mut(|input| input.key_pressed(shortcut.logical_key) && shortcut.modifiers == input.modifiers) + ui.input_mut(|input| { + input.key_pressed(shortcut.logical_key) && shortcut.modifiers == input.modifiers + }) }); let down_shortcut_pressed = self.down_shortcut.map_or(false, |shortcut| { - ui.input_mut(|input| input.key_pressed(shortcut.logical_key) && shortcut.modifiers == input.modifiers) + ui.input_mut(|input| { + input.key_pressed(shortcut.logical_key) && shortcut.modifiers == input.modifiers + }) }); let up_shortcut_released = self.up_shortcut.map_or(false, |shortcut| { - ui.input_mut(|input| input.key_released(shortcut.logical_key) && shortcut.modifiers == input.modifiers) + ui.input_mut(|input| { + input.key_released(shortcut.logical_key) && shortcut.modifiers == input.modifiers + }) }); let down_shortcut_released = self.down_shortcut.map_or(false, |shortcut| { - ui.input_mut(|input| input.key_released(shortcut.logical_key) && shortcut.modifiers == input.modifiers) + ui.input_mut(|input| { + input.key_released(shortcut.logical_key) && shortcut.modifiers == input.modifiers + }) }); let old_state = *self.state; if resp.dragged() { @@ -101,7 +119,10 @@ impl Widget for ThreePosSwitch<'_> { } else if resp.drag_released() || up_shortcut_released || down_shortcut_released { *self.state = SwitchState::Neut; } - if *self.state != SwitchState::Neut && !resp.dragged() && ui.input(|input| input.keys_down.is_empty()) { + if *self.state != SwitchState::Neut + && !resp.dragged() + && ui.input(|input| input.keys_down.is_empty()) + { *self.state = SwitchState::Neut; } if *self.state != old_state { diff --git a/src/main.rs b/src/main.rs index 734b55a..290de02 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use std::sync::mpsc::Sender; use audio::{AudioMessage, AudioThread}; use cpu::{MemCycle, Status, I8080}; use eframe::{ - egui::{self, menu, Button, Slider}, + egui::{self, menu, Button, Slider, Ui}, NativeOptions, }; use egui_modal::Modal; @@ -66,14 +66,20 @@ struct AltairEmulator { impl AltairEmulator { fn new(cc: &eframe::CreationContext<'_>, audio_tx: Sender) -> Self { let options = if cc.storage.unwrap().get_string("options").is_none() { - Options { mute: false, fan_enabled: true, volume: 1.0 } + Options { + mute: false, + fan_enabled: true, + volume: 1.0, + } } else { eframe::get_value(cc.storage.unwrap(), "options").unwrap() }; if options.mute { audio_tx.send(AudioMessage::SetVolume(0.0)).unwrap(); } else { - audio_tx.send(AudioMessage::SetVolume(options.volume)).unwrap(); + audio_tx + .send(AudioMessage::SetVolume(options.volume)) + .unwrap(); } let mut mem = [0; 65536]; let cpu = I8080::new(); @@ -163,6 +169,132 @@ impl AltairEmulator { self.cpu.finish_m_cycle(data); self.update_fp(); } + + fn show_menubar(&mut self, ui: &mut Ui) { + menu::bar(ui, |ui| { + menu::menu_button(ui, "Edit", |ui| { + if ui + .add_enabled(self.option_window.is_none(), Button::new("Options")) + .clicked() + { + self.option_window = Some(OptionWindow::new(ui.ctx(), self.options)); + 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 } => { + for (i, &byte) in value.iter().enumerate() { + self.mem[offset as usize + i] = byte; + } + } + ihex::Record::StartLinearAddress(_) => todo!(), + _ => unimplemented!(), + }; + } + } else { + // Raw binary + todo!(); + } + } + } + }); + }); + } + + fn show_frontpanel(&mut self, ui: &mut Ui) { + let mut fp = Frontpanel::new(&self.textures, &mut self.fp_state); + ui.add(&mut fp); + let interaction = fp.interaction(); + if let Some(interaction) = interaction { + self.audio_tx.send(AudioMessage::PlaySwitchClick).unwrap(); + match interaction { + FrontpanelInteraction::ActionSwChanged(sw, state) => { + if self.fp_state.power() { + match sw { + frontpanel::ActionSwitch::RunStop => { + if state == SwitchState::Up { + self.running = false; + } else if state == SwitchState::Down { + self.running = true; + } + } + frontpanel::ActionSwitch::SingleStep => { + if state == SwitchState::Up { + self.run_cpu_cycle(); + } + } + frontpanel::ActionSwitch::Examine => { + if state == SwitchState::Up { + // Assume M1 + self.cpu.finish_m_cycle(0xC3); // JMP + self.cpu.finish_m_cycle(self.fp_state.ad_sws() as u8); + self.cpu.finish_m_cycle((self.fp_state.ad_sws() >> 8) as u8); + self.update_fp(); + } else if state == SwitchState::Down { + // Assume M1 + self.cpu.finish_m_cycle(0x0); // NOP + self.update_fp(); + } + } + frontpanel::ActionSwitch::Deposit => { + if state == SwitchState::Up { + // Assume M1 + self.mem[self.cpu.get_mem_cycle().address() as usize] = + self.fp_state.ad_sws() as u8; + self.update_fp(); + } else if state == SwitchState::Down { + // Assume M1 + self.cpu.finish_m_cycle(0x0); // NOP + self.mem[self.cpu.get_mem_cycle().address() as usize] = + self.fp_state.ad_sws() as u8; + self.update_fp(); + } + } + frontpanel::ActionSwitch::Reset => { + if state == SwitchState::Up { + self.cpu.reset(); + self.update_fp(); + } + } + frontpanel::ActionSwitch::Protect => (), + frontpanel::ActionSwitch::Aux1 => (), + frontpanel::ActionSwitch::Aux2 => (), + } + } + } + FrontpanelInteraction::AdChanged(_) => (), + FrontpanelInteraction::PowerChanged(pwr) => { + if pwr { + self.audio_tx.send(AudioMessage::FanOn).unwrap(); + self.cpu = I8080::new(); + self.update_fp(); + } else { + self.audio_tx.send(AudioMessage::FanOff).unwrap(); + self.running = false; + self.fp_state.set_status(Status::empty()); + self.fp_state.set_addr(0); + self.fp_state.set_data(0); + } + } + } + } + } } impl eframe::App for AltairEmulator { @@ -171,137 +303,11 @@ impl eframe::App for AltairEmulator { } fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { ctx.set_pixels_per_point(1.0); - // frame.set_window_size((800.0, 333.0).into()); - let mut disable_fp_sws = self.option_window.is_some(); - egui::TopBottomPanel::top("menu").show(ctx, |ui| { - menu::bar(ui, |ui| { - menu::menu_button(ui, "Edit", |ui| { - disable_fp_sws = true; - if ui - .add_enabled(self.option_window.is_none(), Button::new("Options")) - .clicked() - { - self.option_window = Some(OptionWindow::new(ctx, self.options)); - 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 } => { - for (i, &byte) in value.iter().enumerate() { - self.mem[offset as usize + i] = byte; - } - } - ihex::Record::StartLinearAddress(_) => todo!(), - _ => unimplemented!(), - }; - } - } else { - // Raw binary - todo!(); - } - } - } - }); - }); - }); - egui::CentralPanel::default().show(ctx, |ui| { - let mut fp = Frontpanel::new(&self.textures, &mut self.fp_state); - ui.add(&mut fp); - let interaction = fp.interaction(); - if let Some(interaction) = interaction { - self.audio_tx.send(AudioMessage::PlaySwitchClick).unwrap(); - match interaction { - FrontpanelInteraction::ActionSwChanged(sw, state) => { - if self.fp_state.power() { - match sw { - frontpanel::ActionSwitch::RunStop => { - if state == SwitchState::Up { - self.running = false; - } else if state == SwitchState::Down { - self.running = true; - } - } - frontpanel::ActionSwitch::SingleStep => { - if state == SwitchState::Up { - self.run_cpu_cycle(); - } - } - frontpanel::ActionSwitch::Examine => { - if state == SwitchState::Up { - // Assume M1 - self.cpu.finish_m_cycle(0xC3); // JMP - self.cpu.finish_m_cycle(self.fp_state.ad_sws() as u8); - self.cpu - .finish_m_cycle((self.fp_state.ad_sws() >> 8) as u8); - self.update_fp(); - } else if state == SwitchState::Down { - // Assume M1 - self.cpu.finish_m_cycle(0x0); // NOP - self.update_fp(); - } - } - frontpanel::ActionSwitch::Deposit => { - if state == SwitchState::Up { - // Assume M1 - self.mem[self.cpu.get_mem_cycle().address() as usize] = - self.fp_state.ad_sws() as u8; - self.update_fp(); - } else if state == SwitchState::Down { - // Assume M1 - self.cpu.finish_m_cycle(0x0); // NOP - self.mem[self.cpu.get_mem_cycle().address() as usize] = - self.fp_state.ad_sws() as u8; - self.update_fp(); - } - } - frontpanel::ActionSwitch::Reset => { - if state == SwitchState::Up { - self.cpu.reset(); - self.update_fp(); - } - } - frontpanel::ActionSwitch::Protect => (), - frontpanel::ActionSwitch::Aux1 => (), - frontpanel::ActionSwitch::Aux2 => (), - } - } - } - FrontpanelInteraction::AdChanged(_) => (), - FrontpanelInteraction::PowerChanged(pwr) => { - if pwr { - self.audio_tx.send(AudioMessage::FanOn).unwrap(); - self.cpu = I8080::new(); - self.update_fp(); - } else { - self.audio_tx.send(AudioMessage::FanOff).unwrap(); - self.running = false; - self.fp_state.set_status(Status::empty()); - self.fp_state.set_addr(0); - self.fp_state.set_data(0); - } - } - } - } - if self.running { - self.run_cpu_cycle(); - } - }); - + egui::TopBottomPanel::top("menu").show(ctx, |ui| self.show_menubar(ui)); + egui::CentralPanel::default().show(ctx, |ui| self.show_frontpanel(ui)); + if self.running { + self.run_cpu_cycle(); + } let old_fan_enabled = self.options.fan_enabled; if let Some(option_window) = self.option_window.as_mut() { if option_window.draw(ctx, &mut self.options) { @@ -310,13 +316,15 @@ impl eframe::App for AltairEmulator { if self.options.mute { self.audio_tx.send(AudioMessage::SetVolume(0.0)).unwrap(); } else { - self.audio_tx.send(AudioMessage::SetVolume(self.options.volume)).unwrap(); + self.audio_tx + .send(AudioMessage::SetVolume(self.options.volume)) + .unwrap(); } } if (old_fan_enabled != self.options.fan_enabled) && self.fp_state.power() { if self.options.fan_enabled { self.audio_tx.send(AudioMessage::FanOn).unwrap(); - } else { + } else { self.audio_tx.send(AudioMessage::FanOff).unwrap(); } }