Split UI drawing code out of main update code
This commit is contained in:
parent
51e0893c04
commit
3862080cb1
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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| {
|
||||
@ -176,7 +173,11 @@ impl Widget for &mut Frontpanel<'_> {
|
||||
};
|
||||
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 {
|
||||
|
@ -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<KeyboardShortcut>, down_shortcut: Option<KeyboardShortcut>) -> Self {
|
||||
Self { state, textures, up_shortcut, down_shortcut }
|
||||
pub fn new(
|
||||
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,
|
||||
);
|
||||
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 {
|
||||
|
280
src/main.rs
280
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<AudioMessage>) -> 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();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user