Split UI drawing code out of main update code

This commit is contained in:
pjht 2024-01-30 13:01:02 -06:00
parent 51e0893c04
commit 3862080cb1
Signed by: pjht
GPG Key ID: CA239FC6934E6F3A
4 changed files with 187 additions and 152 deletions

View File

@ -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),
} }
} }

View File

@ -1,7 +1,7 @@
use std::path::Path; use std::path::Path;
use eframe::{ 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}, epaint::{vec2, Pos2, Rect, TextureHandle},
}; };
@ -117,10 +117,7 @@ impl Widget for &mut Frontpanel<'_> {
); );
let led_textures = let led_textures =
led::Textures::new(self.textures.led_on.clone(), self.textures.led_off.clone()); led::Textures::new(self.textures.led_on.clone(), self.textures.led_off.clone());
let resp = ui.allocate_response( let resp = ui.allocate_response(vec2(800.0, 333.0), Sense::click());
vec2(800.0, 333.0),
Sense::click(),
);
resp.request_focus(); resp.request_focus();
let fp_rect = resp.rect; let fp_rect = resp.rect;
ui.allocate_ui_at_rect(fp_rect, |ui| { 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 = ["Q", "W", "E", "R", "T", "Y", "U", "I"][key_offset];
let key = Key::from_name(&key).unwrap(); let key = Key::from_name(&key).unwrap();
let mods = if (i - 1) < 8 { let mods = if (i - 1) < 8 {
Modifiers::SHIFT Modifiers::SHIFT
} else { } else {
Modifiers::NONE Modifiers::NONE
}; };
let shortcut = KeyboardShortcut::new(mods, key); let shortcut = KeyboardShortcut::new(mods, key);
if ui 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() .changed()
{ {
self.state.ad_sws = self.state.ad_sws =
@ -207,7 +208,12 @@ impl Widget for &mut Frontpanel<'_> {
let up_shortcut = KeyboardShortcut::new(Modifiers::NONE, up_key); let up_shortcut = KeyboardShortcut::new(Modifiers::NONE, up_key);
let down_shortcut = KeyboardShortcut::new(Modifiers::NONE, down_key); let down_shortcut = KeyboardShortcut::new(Modifiers::NONE, down_key);
if ui 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() .changed()
{ {
let sw = match i { let sw = match i {

View File

@ -59,8 +59,18 @@ pub struct ThreePosSwitch<'a> {
} }
impl<'a> ThreePosSwitch<'a> { impl<'a> ThreePosSwitch<'a> {
pub fn new(state: &'a mut SwitchState, textures: &'a Textures, up_shortcut: Option<KeyboardShortcut>, down_shortcut: Option<KeyboardShortcut>) -> Self { pub fn new(
Self { state, textures, up_shortcut, down_shortcut } state: &'a mut SwitchState,
textures: &'a Textures,
up_shortcut: Option<KeyboardShortcut>,
down_shortcut: Option<KeyboardShortcut>,
) -> Self {
Self {
state,
textures,
up_shortcut,
down_shortcut,
}
} }
} }
@ -74,16 +84,24 @@ impl Widget for ThreePosSwitch<'_> {
NULL_TINT, NULL_TINT,
); );
let up_shortcut_pressed = self.up_shortcut.map_or(false, |shortcut| { 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| { 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| { 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| { 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; let old_state = *self.state;
if resp.dragged() { if resp.dragged() {
@ -101,7 +119,10 @@ impl Widget for ThreePosSwitch<'_> {
} else if resp.drag_released() || up_shortcut_released || down_shortcut_released { } else if resp.drag_released() || up_shortcut_released || down_shortcut_released {
*self.state = SwitchState::Neut; *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; *self.state = SwitchState::Neut;
} }
if *self.state != old_state { if *self.state != old_state {

View File

@ -9,7 +9,7 @@ use std::sync::mpsc::Sender;
use audio::{AudioMessage, AudioThread}; use audio::{AudioMessage, AudioThread};
use cpu::{MemCycle, Status, I8080}; use cpu::{MemCycle, Status, I8080};
use eframe::{ use eframe::{
egui::{self, menu, Button, Slider}, egui::{self, menu, Button, Slider, Ui},
NativeOptions, NativeOptions,
}; };
use egui_modal::Modal; use egui_modal::Modal;
@ -66,14 +66,20 @@ struct AltairEmulator {
impl AltairEmulator { impl AltairEmulator {
fn new(cc: &eframe::CreationContext<'_>, audio_tx: Sender<AudioMessage>) -> Self { fn new(cc: &eframe::CreationContext<'_>, audio_tx: Sender<AudioMessage>) -> Self {
let options = if cc.storage.unwrap().get_string("options").is_none() { 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 { } else {
eframe::get_value(cc.storage.unwrap(), "options").unwrap() eframe::get_value(cc.storage.unwrap(), "options").unwrap()
}; };
if options.mute { if options.mute {
audio_tx.send(AudioMessage::SetVolume(0.0)).unwrap(); audio_tx.send(AudioMessage::SetVolume(0.0)).unwrap();
} else { } else {
audio_tx.send(AudioMessage::SetVolume(options.volume)).unwrap(); audio_tx
.send(AudioMessage::SetVolume(options.volume))
.unwrap();
} }
let mut mem = [0; 65536]; let mut mem = [0; 65536];
let cpu = I8080::new(); let cpu = I8080::new();
@ -163,6 +169,132 @@ impl AltairEmulator {
self.cpu.finish_m_cycle(data); self.cpu.finish_m_cycle(data);
self.update_fp(); 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 { 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) { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
ctx.set_pixels_per_point(1.0); ctx.set_pixels_per_point(1.0);
// frame.set_window_size((800.0, 333.0).into()); egui::TopBottomPanel::top("menu").show(ctx, |ui| self.show_menubar(ui));
let mut disable_fp_sws = self.option_window.is_some(); egui::CentralPanel::default().show(ctx, |ui| self.show_frontpanel(ui));
egui::TopBottomPanel::top("menu").show(ctx, |ui| { if self.running {
menu::bar(ui, |ui| { self.run_cpu_cycle();
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();
}
});
let old_fan_enabled = self.options.fan_enabled; let old_fan_enabled = self.options.fan_enabled;
if let Some(option_window) = self.option_window.as_mut() { if let Some(option_window) = self.option_window.as_mut() {
if option_window.draw(ctx, &mut self.options) { if option_window.draw(ctx, &mut self.options) {
@ -310,13 +316,15 @@ impl eframe::App for AltairEmulator {
if self.options.mute { if self.options.mute {
self.audio_tx.send(AudioMessage::SetVolume(0.0)).unwrap(); self.audio_tx.send(AudioMessage::SetVolume(0.0)).unwrap();
} else { } 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 (old_fan_enabled != self.options.fan_enabled) && self.fp_state.power() {
if self.options.fan_enabled { if self.options.fan_enabled {
self.audio_tx.send(AudioMessage::FanOn).unwrap(); self.audio_tx.send(AudioMessage::FanOn).unwrap();
} else { } else {
self.audio_tx.send(AudioMessage::FanOff).unwrap(); self.audio_tx.send(AudioMessage::FanOff).unwrap();
} }
} }