altair_emu/src/main.rs

1155 lines
41 KiB
Rust
Raw Normal View History

2023-08-10 13:58:34 -05:00
mod card;
2023-10-12 13:11:38 -05:00
mod cpu;
2024-01-24 14:31:01 -06:00
mod frontpanel;
2023-08-10 13:58:34 -05:00
mod ram;
use std::{
path::Path,
sync::{mpsc::Sender, Arc},
thread,
time::{Duration, Instant},
};
use cpu::{MemCycle, Status, I8080};
2024-01-25 10:34:10 -06:00
use device_query::DeviceState;
2024-01-24 14:31:01 -06:00
use eframe::{
2024-01-25 10:34:10 -06:00
egui::{self, menu, Button, Id, Label, Pos2, Rect, TextureHandle, TextureOptions, Ui},
2024-01-24 14:31:01 -06:00
NativeOptions,
};
2023-08-10 13:58:34 -05:00
use egui_modal::Modal;
use log::{debug, trace, warn};
use parking_lot::Mutex;
use rand::RngCore;
2024-01-23 17:10:03 -06:00
use rfd::FileDialog;
2023-08-10 13:58:34 -05:00
use serde::{Deserialize, Serialize};
use soloud::{audio, AudioExt, LoadExt, Soloud};
2024-01-25 10:34:10 -06:00
use crate::frontpanel::{switch, Frontpanel};
2024-01-24 14:31:01 -06:00
2023-08-10 13:58:34 -05:00
#[derive(Clone, Debug)]
enum AudioMessage {
PlaySwitchClick,
}
fn main() -> Result<(), eframe::Error> {
env_logger::init();
let (main_audio_tx, main_audio_rx) = std::sync::mpsc::channel::<AudioMessage>();
let (fan_audio_tx, fan_audio_rx) = std::sync::mpsc::channel::<bool>();
let sl = Arc::new(Mutex::new(Soloud::default().unwrap()));
let fan_sl = Arc::clone(&sl);
thread::spawn(move || {
let sl = fan_sl;
let rx = fan_audio_rx;
let fan_startup = {
let mut fan_startup = audio::Wav::default();
fan_startup
.load("/home/pjht/projects/altair_emu/resources/fan1.wav")
.unwrap();
fan_startup
};
let fan_loop = {
let mut fan_loop = audio::Wav::default();
fan_loop
.load("/home/pjht/projects/altair_emu/resources/fan2.wav")
.unwrap();
fan_loop
};
let fan_shutdown = {
let mut fan_shutdown = audio::Wav::default();
fan_shutdown
.load("/home/pjht/projects/altair_emu/resources/fan3.wav")
.unwrap();
fan_shutdown
};
debug!(target: "altair_emu::fan_thread", "Fan startup length: {}s. Fan shutdown length: {}s", fan_startup.length(), fan_shutdown.length());
if fan_startup.length() != fan_shutdown.length() {
2024-01-24 10:50:35 -06:00
warn!(target: "altair_emu::fan_thread", "Fan startup sound and shutdown sounds do not have the same length! ({}s and {}s, respectively.) This may cause audio glitches.", fan_startup.length(), fan_shutdown.length());
2023-08-10 13:58:34 -05:00
}
let mut fan_shutdown_start: Option<(Instant, Duration)> = None;
2024-01-24 10:50:35 -06:00
for msg in &rx {
2023-08-10 13:58:34 -05:00
if msg {
trace!(target: "altair_emu::fan_thread", "Start fan");
let fan_startup_start = if let Some(fan_shutdown_start) = fan_shutdown_start.take()
{
trace!(target: "altair_emu::fan_thread", "Fan shutdown sound playing");
let sl = sl.lock();
sl.stop_all();
let fan_shutdown_elapsed = fan_shutdown_start.0.elapsed().as_secs_f64()
+ fan_shutdown_start.1.as_secs_f64();
if fan_shutdown_elapsed >= fan_shutdown.length() {
trace!(target: "altair_emu::fan_thread", "Shutdown elapsed > shutdown length, not actually playing");
let fan_startup_start = (Instant::now(), Duration::ZERO);
sl.play(&fan_startup);
fan_startup_start
} else {
trace!(target: "altair_emu::fan_thread", "Fan shutdown sound stopped, elapsed time {}s", fan_shutdown_elapsed);
let fan_startup_start = (
Instant::now(),
Duration::from_secs_f64(fan_shutdown.length() - fan_shutdown_elapsed),
);
let fan_startup_handle = sl.play(&fan_startup);
sl.seek(
fan_startup_handle,
fan_shutdown.length() - fan_shutdown_elapsed,
)
.unwrap();
2024-01-24 10:50:35 -06:00
drop(sl);
2023-08-10 13:58:34 -05:00
trace!(target: "altair_emu::fan_thread", "Fan startup playing at offset {}s", fan_shutdown.length() - fan_shutdown_elapsed);
fan_startup_start
}
} else {
trace!(target: "altair_emu::fan_thread", "Fan shutdown sound not playing");
let fan_startup_start = (Instant::now(), Duration::ZERO);
sl.lock().play(&fan_startup);
fan_startup_start
};
let mut msg = None;
while sl.lock().voice_count() > 0 {
if let Ok(r_msg) = rx.try_recv() {
trace!(target: "altair_emu::fan_thread", "Got message {} while waiting for end of startup sound", r_msg);
msg = Some(r_msg);
break;
}
std::thread::sleep(std::time::Duration::from_millis(10));
}
if let Some(msg) = msg {
if msg {
panic!();
} else {
trace!(target: "altair_emu::fan_thread", "Stop fan in startup");
let sl = sl.lock();
sl.stop_all();
let fan_startup_elapsed = fan_startup_start.0.elapsed().as_secs_f64()
+ fan_startup_start.1.as_secs_f64();
trace!(target: "altair_emu::fan_thread", "Fan startup sound stopped, elapsed time {}s", fan_startup_elapsed);
fan_shutdown_start = Some((
Instant::now(),
Duration::from_secs_f64(fan_startup.length() - fan_startup_elapsed),
));
let fan_shutdown_handle = sl.play(&fan_shutdown);
sl.seek(
fan_shutdown_handle,
fan_startup.length() - fan_startup_elapsed,
)
.unwrap();
2024-01-24 10:50:35 -06:00
drop(sl);
2023-08-10 13:58:34 -05:00
trace!(target: "altair_emu::fan_thread", "Fan shutdown playing at offset {}s", fan_startup.length() - fan_startup_elapsed);
}
continue;
}
let mut sl = sl.lock();
let handle = sl.play(&fan_loop);
sl.set_looping(handle, true);
2024-01-24 10:50:35 -06:00
drop(sl);
2023-08-10 13:58:34 -05:00
trace!(target: "altair_emu::fan_thread", "Fan loop started, start done");
} else {
trace!(target: "altair_emu::fan_thread", "Stop fan");
sl.lock().stop_all();
trace!(target: "altair_emu::fan_thread", "Fan sound stopped");
fan_shutdown_start = Some((Instant::now(), Duration::ZERO));
sl.lock().play(&fan_shutdown);
trace!(target: "altair_emu::fan_thread", "Fan shutdown sound playing, stop done");
}
}
});
thread::spawn(move || {
let rx = main_audio_rx;
let head_step = {
let mut head_step = audio::Wav::default();
head_step
.load("/home/pjht/projects/altair_emu/resources/click2.wav")
.unwrap();
head_step
};
2024-01-24 10:50:35 -06:00
for msg in &rx {
2023-08-10 13:58:34 -05:00
match msg {
AudioMessage::PlaySwitchClick => {
sl.lock().play(&head_step);
}
}
}
});
eframe::run_native(
"Altair 8800 Emulator",
2024-01-24 10:50:35 -06:00
NativeOptions::default(),
2023-08-10 13:58:34 -05:00
Box::new(|cc| Box::new(AltairEmulator::new(cc, main_audio_tx, fan_audio_tx))),
)
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
struct Options {
fan_enabled: bool,
}
struct AltairEmulator {
2024-01-25 10:34:10 -06:00
textures: frontpanel::Textures,
2023-08-10 13:58:34 -05:00
ad_sws: u16,
power: bool,
runstop: SwitchState,
single_step: SwitchState,
exam: SwitchState,
2024-01-24 14:31:01 -06:00
dep: switch::SwitchState,
2023-08-10 13:58:34 -05:00
reset: SwitchState,
prot: SwitchState,
aux1: SwitchState,
aux2: SwitchState,
fp_address: u16,
fp_data: u8,
fp_status: Status,
mem: [u8; 65536],
cpu: I8080,
mouse_newdown: bool,
mouse_olddown: bool,
running: bool,
main_audio_tx: Sender<AudioMessage>,
fan_audio_tx: Sender<bool>,
options: Options,
option_window: Option<OptionWindow>,
device_state: DeviceState,
kbd_newdown: bool,
kbd_olddown: bool,
}
impl AltairEmulator {
fn new(
cc: &eframe::CreationContext<'_>,
main_audio_tx: Sender<AudioMessage>,
fan_audio_tx: Sender<bool>,
) -> Self {
let options = if cc.storage.unwrap().get_string("options").is_none() {
Options { fan_enabled: true }
} else {
eframe::get_value(cc.storage.unwrap(), "options").unwrap()
};
let mut mem = [0; 65536];
let cpu = I8080::new();
rand::thread_rng().fill_bytes(&mut mem);
mem[0x0] = 0x3a;
mem[0x1] = 0x10;
mem[0x2] = 0x00;
mem[0x3] = 0x47;
mem[0x4] = 0x3a;
mem[0x5] = 0x11;
mem[0x6] = 0x00;
mem[0x7] = 0x80;
mem[0x8] = 0x32;
mem[0x9] = 0x12;
mem[0xa] = 0x00;
mem[0xb] = 0x76;
mem[0x10] = 0x4;
mem[0x11] = 0x5;
// 0: 072 020 000
2023-10-12 13:11:38 -05:00
// 3: 107
2023-08-10 13:58:34 -05:00
// 4: 072 021 000
// 7: 200
// 10: 062 022 000
// 13: 166
// 20: 004
// 21: 005
Self {
2024-01-25 10:34:10 -06:00
textures: frontpanel::Textures::new(&cc.egui_ctx),
2023-08-10 13:58:34 -05:00
ad_sws: 0x0,
power: false,
runstop: SwitchState::Neut,
single_step: SwitchState::Neut,
exam: SwitchState::Neut,
2024-01-24 14:31:01 -06:00
dep: switch::SwitchState::Neut,
2023-08-10 13:58:34 -05:00
reset: SwitchState::Neut,
prot: SwitchState::Neut,
aux1: SwitchState::Neut,
aux2: SwitchState::Neut,
mem,
fp_address: 0,
fp_data: 0,
fp_status: Status::empty(),
cpu,
mouse_newdown: false,
mouse_olddown: false,
running: false,
main_audio_tx,
fan_audio_tx,
options,
option_window: None,
device_state: DeviceState::new(),
kbd_newdown: false,
kbd_olddown: false,
}
}
fn update_fp(&mut self) {
let cycle = self.cpu.get_mem_cycle();
self.fp_status = cycle.get_status();
match cycle {
MemCycle::Fetch(a) | MemCycle::Read(a) | MemCycle::StackRead(a) => {
self.fp_address = a;
self.fp_data = self.mem[a as usize];
}
MemCycle::Write(a, _) | MemCycle::StackWrite(a, _) | MemCycle::Out(a, _) => {
self.fp_address = a;
self.fp_data = 0xff;
}
2024-01-23 16:01:14 -06:00
MemCycle::In(a) => {
self.fp_address = a;
self.fp_data = 0;
2024-01-24 14:31:01 -06:00
}
2023-08-10 13:58:34 -05:00
MemCycle::Inta(_) => todo!(),
MemCycle::Hlta(_) => {
self.fp_data = 0xff;
self.fp_address = 0xffff;
}
MemCycle::IntaHlt(_) => todo!(),
}
}
}
impl eframe::App for AltairEmulator {
fn save(&mut self, storage: &mut dyn eframe::Storage) {
eframe::set_value(storage, "options", &self.options);
}
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
fn image_topleft(pos: impl Into<Pos2>, texture: &TextureHandle, ui: &mut Ui) {
ui.allocate_ui_at_rect(Rect::from_min_size(pos.into(), texture.size_vec2()), |ui| {
2024-01-25 10:34:10 -06:00
ui.image(texture);
2023-08-10 13:58:34 -05:00
});
}
fn image_center(pos: impl Into<Pos2>, texture: &TextureHandle, ui: &mut Ui) {
ui.allocate_ui_at_rect(
Rect::from_center_size(pos.into(), texture.size_vec2()),
|ui| {
2024-01-25 10:34:10 -06:00
ui.image(texture);
2023-08-10 13:58:34 -05:00
},
);
}
ctx.set_pixels_per_point(1.0);
2024-01-25 10:34:10 -06:00
// frame.set_window_size((800.0, 333.0).into());
2023-08-10 13:58:34 -05:00
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()
{
2024-01-24 10:50:35 -06:00
self.option_window = Some(OptionWindow::new(ctx, self.options));
ui.close_menu();
2023-08-10 13:58:34 -05:00
}
2024-01-23 17:10:03 -06:00
if ui.button("Load binary file").clicked() {
let ihex_exts = ["hex", "mcs", "int", "ihex", "ihe", "ihx"];
let file = FileDialog::new()
2024-01-24 14:31:01 -06:00
.add_filter(
"Binary files",
&["bin", "img", "hex", "mcs", "int", "ihex", "ihe", "ihx"],
)
2024-01-23 17:10:03 -06:00
.add_filter("All files", &["*"])
.pick_file();
if let Some(file) = file {
2024-01-24 14:31:01 -06:00
if file.extension().map_or(false, |ext| {
ihex_exts.contains(&ext.to_str().unwrap_or(""))
}) {
2024-01-23 17:10:03 -06:00
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;
}
2024-01-24 14:31:01 -06:00
}
2024-01-23 17:10:03 -06:00
ihex::Record::StartLinearAddress(_) => todo!(),
_ => unimplemented!(),
};
}
} else {
// Raw binary
todo!();
}
}
}
2023-08-10 13:58:34 -05:00
});
});
});
egui::CentralPanel::default().show(ctx, |ui| {
2024-01-25 10:34:10 -06:00
ui.add(Label::new("Hello"));
ui.add(Frontpanel::new(Id::new("frontpanel"), &self.textures));
ui.add(Frontpanel::new(Id::new("frontpanel2"), &self.textures));
// dbg!(ui.input(|input| input.pointer.latest_pos()));
// dbg!(ui.input(|input| input.pointer.latest_pos()));
2024-01-24 14:31:01 -06:00
// let (_, fp_rect) = ui.allocate_space((800.0, 333.0).into());
// image_topleft(fp_rect.left_top(), &self.textures.fp, ui);
// for led in &LEDS {
// let led_data = match led.source {
// LedSource::Protect => 0x0,
// LedSource::Iff => 0x0,
// LedSource::Run => 0x0,
// LedSource::CpuStatus => u16::from(self.fp_status.bits()),
// LedSource::Data => u16::from(self.fp_data),
// LedSource::Address => self.fp_address,
// };
// let led_on = (led_data & led.mask) > 0;
// let texture = if led_on {
// &self.textures.led_on
// } else {
// &self.textures.led_off
// };
// image_center(led.pos + fp_rect.left_top().to_vec2(), texture, ui);
// }
// let pointer_state = ctx.input(|inp| inp.pointer.clone());
// let interact_pos = pointer_state.interact_pos();
// let mut switch_clicked = false;
// fn bool_to_texture(state: bool, textures: &Textures) -> &TextureHandle {
// if state {
// &textures.sw_up
// } else {
// &textures.sw_down
// }
// }
// fn state_to_texture(state: SwitchState, textures: &Textures) -> &TextureHandle {
// match state {
// SwitchState::Up => &textures.sw_up,
// SwitchState::Neut => &textures.sw_neut,
// SwitchState::Down => &textures.sw_down,
// }
// }
// for (i, switch) in SWITCHES.iter().enumerate() {
// let pos = switch.pos + fp_rect.left_top().to_vec2();
// let texture = match i {
// 0 => bool_to_texture(!self.power, &self.textures),
// (1..=16) => {
// bool_to_texture((self.ad_sws & (1 << (16 - i))) > 0, &self.textures)
// }
// 17 => state_to_texture(self.runstop, &self.textures),
// 18 => state_to_texture(self.single_step, &self.textures),
// 19 => state_to_texture(self.exam, &self.textures),
// 20 => state_to_texture(self.dep, &self.textures),
// 21 => state_to_texture(self.reset, &self.textures),
// 22 => state_to_texture(self.prot, &self.textures),
// 23 => state_to_texture(self.aux1, &self.textures),
// 24 => state_to_texture(self.aux2, &self.textures),
// _ => unreachable!(),
// };
// image_center(pos, texture, ui);
// let interacted = interact_pos.map(|interact_pos| {
// Rect::from_center_size(interact_pos, (10.0, 24.0).into()).contains(pos)
// && !disable_fp_sws
// });
// if pointer_state.primary_clicked() && interacted.unwrap() {
// match i {
// 0 => {
// switch_clicked = true;
// self.power = !self.power;
// if self.options.fan_enabled {
// self.fan_audio_tx.send(self.power).unwrap();
// }
// if self.power {
// self.cpu = I8080::new();
// self.update_fp();
// } else {
// self.running = false;
// self.fp_status = Status::empty();
// self.fp_data = 0;
// self.fp_address = 0;
// }
// }
// (1..=16) => {
// switch_clicked = true;
// if (self.ad_sws & (1 << (16 - i))) > 0 {
// self.ad_sws &= !(1 << (16 - i));
// } else {
// self.ad_sws |= 1 << (16 - i);
// }
// }
// _ => (),
// }
// }
// if pointer_state.primary_down() && interacted.unwrap() {
// if (17..=24).contains(&i) && self.mouse_newdown {
// switch_clicked = true;
// }
// let newstate = if interact_pos.unwrap().y > pos.y {
// SwitchState::Down
// } else {
// SwitchState::Up
// };
// match i {
// 17 => self.runstop = newstate,
// 18 => self.single_step = newstate,
// 19 => self.exam = newstate,
// 20 => self.dep = newstate,
// 21 => self.reset = newstate,
// 22 => self.prot = newstate,
// 23 => self.aux1 = newstate,
// 24 => self.aux2 = newstate,
// _ => (),
// }
// } else {
// match i {
// 17 => self.runstop = SwitchState::Neut,
// 18 => self.single_step = SwitchState::Neut,
// 19 => self.exam = SwitchState::Neut,
// 20 => self.dep = SwitchState::Neut,
// 21 => self.reset = SwitchState::Neut,
// 22 => self.prot = SwitchState::Neut,
// 23 => self.aux1 = SwitchState::Neut,
// 24 => self.aux2 = SwitchState::Neut,
// _ => (),
// }
// }
// }
// if !disable_fp_sws {
// let mut kbd_ad = None;
// let pressed_keys = self.device_state.query_keymap();
// if pressed_keys
// .iter()
// .filter(|&&k| k != Keycode::LShift && k != Keycode::RShift)
// .count()
// != 0
// {
// if self.kbd_newdown {
// self.kbd_olddown = true;
// self.kbd_newdown = false;
// } else if !self.kbd_newdown && !self.kbd_olddown {
// self.kbd_newdown = true;
// }
// } else {
// self.kbd_newdown = false;
// self.kbd_olddown = false;
// }
// if self.kbd_newdown {
// if pressed_keys.contains(&Keycode::W)
// || pressed_keys.contains(&Keycode::E)
// || pressed_keys.contains(&Keycode::R)
// || pressed_keys.contains(&Keycode::T)
// || pressed_keys.contains(&Keycode::Y)
// || pressed_keys.contains(&Keycode::U)
// || pressed_keys.contains(&Keycode::I)
// || pressed_keys.contains(&Keycode::O)
// || pressed_keys.contains(&Keycode::S)
// || pressed_keys.contains(&Keycode::D)
// || pressed_keys.contains(&Keycode::F)
// || pressed_keys.contains(&Keycode::G)
// || pressed_keys.contains(&Keycode::H)
// || pressed_keys.contains(&Keycode::J)
// || pressed_keys.contains(&Keycode::K)
// || pressed_keys.contains(&Keycode::L)
// {
// switch_clicked = true;
// }
// if pressed_keys.contains(&Keycode::LShift)
// || pressed_keys.contains(&Keycode::RShift)
// {
// if pressed_keys.contains(&Keycode::Key1) {
// kbd_ad = Some(15);
// } else if pressed_keys.contains(&Keycode::Key2) {
// kbd_ad = Some(14);
// } else if pressed_keys.contains(&Keycode::Key3) {
// kbd_ad = Some(13);
// } else if pressed_keys.contains(&Keycode::Key4) {
// kbd_ad = Some(12);
// } else if pressed_keys.contains(&Keycode::Key5) {
// kbd_ad = Some(11);
// } else if pressed_keys.contains(&Keycode::Key6) {
// kbd_ad = Some(10);
// } else if pressed_keys.contains(&Keycode::Key7) {
// kbd_ad = Some(9);
// } else if pressed_keys.contains(&Keycode::Key8) {
// kbd_ad = Some(8);
// }
// } else {
// if pressed_keys.contains(&Keycode::Key1) {
// kbd_ad = Some(7);
// } else if pressed_keys.contains(&Keycode::Key2) {
// kbd_ad = Some(6);
// } else if pressed_keys.contains(&Keycode::Key3) {
// kbd_ad = Some(5);
// } else if pressed_keys.contains(&Keycode::Key4) {
// kbd_ad = Some(4);
// } else if pressed_keys.contains(&Keycode::Key5) {
// kbd_ad = Some(3);
// } else if pressed_keys.contains(&Keycode::Key6) {
// kbd_ad = Some(2);
// } else if pressed_keys.contains(&Keycode::Key7) {
// kbd_ad = Some(1);
// } else if pressed_keys.contains(&Keycode::Key8) {
// kbd_ad = Some(0);
// }
// if pressed_keys.contains(&Keycode::Q) {
// switch_clicked = true;
// self.power = !self.power;
// if self.options.fan_enabled {
// self.fan_audio_tx.send(self.power).unwrap();
// }
// if self.power {
// self.cpu = I8080::new();
// self.update_fp();
// } else {
// self.running = false;
// self.fp_status = Status::empty();
// self.fp_data = 0;
// self.fp_address = 0;
// }
// }
// }
// }
// if pressed_keys.contains(&Keycode::W) {
// self.runstop = SwitchState::Up;
// }
// if pressed_keys.contains(&Keycode::E) {
// self.single_step = SwitchState::Up;
// }
// if pressed_keys.contains(&Keycode::R) {
// self.exam = SwitchState::Up;
// }
// if pressed_keys.contains(&Keycode::T) {
// self.dep = SwitchState::Up;
// }
// if pressed_keys.contains(&Keycode::Y) {
// self.reset = SwitchState::Up;
// }
// if pressed_keys.contains(&Keycode::U) {
// self.prot = SwitchState::Up;
// }
// if pressed_keys.contains(&Keycode::I) {
// self.aux1 = SwitchState::Up;
// }
// if pressed_keys.contains(&Keycode::O) {
// self.aux2 = SwitchState::Up;
// }
// if pressed_keys.contains(&Keycode::S) {
// self.runstop = SwitchState::Down;
// }
// if pressed_keys.contains(&Keycode::D) {
// self.single_step = SwitchState::Down;
// }
// if pressed_keys.contains(&Keycode::F) {
// self.exam = SwitchState::Down;
// }
// if pressed_keys.contains(&Keycode::G) {
// self.dep = SwitchState::Down;
// }
// if pressed_keys.contains(&Keycode::H) {
// self.reset = SwitchState::Down;
// }
// if pressed_keys.contains(&Keycode::J) {
// self.prot = SwitchState::Down;
// }
// if pressed_keys.contains(&Keycode::K) {
// self.aux1 = SwitchState::Down;
// }
// if pressed_keys.contains(&Keycode::L) {
// self.aux2 = SwitchState::Down;
// }
// if let Some(kbd_ad) = kbd_ad {
// switch_clicked = true;
// if (self.ad_sws & (1 << kbd_ad)) > 0 {
// self.ad_sws &= !(1 << kbd_ad);
// } else {
// self.ad_sws |= 1 << kbd_ad;
// }
// }
// }
// if switch_clicked {
// self.main_audio_tx
// .send(AudioMessage::PlaySwitchClick)
// .unwrap();
// }
// if pointer_state.primary_down() {
// if self.mouse_newdown {
// self.mouse_olddown = true;
// self.mouse_newdown = false;
// } else if !self.mouse_newdown && !self.mouse_olddown {
// self.mouse_newdown = true;
// }
// } else {
// self.mouse_newdown = false;
// self.mouse_olddown = false;
// }
// if switch_clicked && self.power {
// if !self.running {
// if self.exam == SwitchState::Up {
// // Assume M1
// self.cpu.finish_m_cycle(0xC3); // JMP
// self.cpu.finish_m_cycle(self.ad_sws as u8);
// self.cpu.finish_m_cycle((self.ad_sws >> 8) as u8);
// self.update_fp();
// }
// if self.exam == SwitchState::Down {
// // Assume M1
// self.cpu.finish_m_cycle(0x0); // NOP
// self.update_fp();
// }
// if self.dep == SwitchState::Up {
// // Assume M1
// self.mem[self.cpu.get_mem_cycle().address() as usize] = self.ad_sws as u8;
// self.update_fp();
// }
// if self.dep == SwitchState::Down {
// // Assume M1
// self.cpu.finish_m_cycle(0x0); // NOP
// self.mem[self.cpu.get_mem_cycle().address() as usize] = self.ad_sws as u8;
// self.update_fp();
// }
// }
// if self.runstop == SwitchState::Up {
// self.running = false;
// }
// if self.runstop == SwitchState::Down {
// self.running = true;
// }
// if self.reset == SwitchState::Up {
// self.cpu.reset();
// self.update_fp();
// }
// }
// if ((switch_clicked && self.single_step != SwitchState::Neut) || self.running)
// && self.power
// {
// let cycle = self.cpu.get_mem_cycle();
// let data = match cycle {
// MemCycle::Fetch(a) | MemCycle::Read(a) | MemCycle::StackRead(a) => {
// self.mem[a as usize]
// }
// MemCycle::Write(a, d) | MemCycle::StackWrite(a, d) => {
// self.mem[a as usize] = d;
// 0
// }
// MemCycle::In(_) => 0,
// MemCycle::Out(_, _) => 0,
// MemCycle::Inta(_) => todo!(),
// MemCycle::Hlta(_) => {
// self.running = false;
// 0
// }
// MemCycle::IntaHlt(_) => todo!(),
// };
// self.cpu.finish_m_cycle(data);
// self.update_fp();
// }
2023-08-10 13:58:34 -05:00
});
2024-01-24 14:31:01 -06:00
2023-08-10 13:58:34 -05:00
let old_fan_enabled = self.options.fan_enabled;
if let Some(option_window) = self.option_window.as_mut() {
2024-01-24 10:50:35 -06:00
if option_window.draw(ctx, &mut self.options) {
2023-08-10 13:58:34 -05:00
self.option_window = None;
}
}
if (old_fan_enabled != self.options.fan_enabled) && self.power {
self.fan_audio_tx.send(self.options.fan_enabled).unwrap();
}
if self.running {
ctx.request_repaint();
}
}
}
struct OptionWindow {
options: Options,
category: OptionsCategory,
}
#[derive(PartialEq, Eq)]
enum OptionsCategory {
General,
Cards,
}
impl OptionWindow {
fn new(ctx: &egui::Context, options: Options) -> Self {
Modal::new(ctx, "options_modal").open();
2023-10-12 13:11:38 -05:00
Self {
options,
category: OptionsCategory::General,
}
2023-08-10 13:58:34 -05:00
}
fn draw(&mut self, ctx: &egui::Context, options: &mut Options) -> bool {
let modal = Modal::new(ctx, "options_modal");
modal.show(|ui| {
modal.title(ui, "Options");
ui.horizontal(|ui| {
ui.selectable_value(&mut self.category, OptionsCategory::General, "General");
ui.selectable_value(&mut self.category, OptionsCategory::Cards, "Cards");
});
match self.category {
OptionsCategory::General => {
ui.checkbox(&mut self.options.fan_enabled, "Fan enabled");
2023-10-12 13:11:38 -05:00
}
2023-08-10 13:58:34 -05:00
OptionsCategory::Cards => {
ui.heading("TODO");
}
}
modal.buttons(ui, |ui| {
if ui.button("Apply").clicked() {
2024-01-24 10:50:35 -06:00
*options = self.options;
2023-08-10 13:58:34 -05:00
}
if modal.button(ui, "OK").clicked() {
2024-01-24 10:50:35 -06:00
*options = self.options;
2023-08-10 13:58:34 -05:00
}
modal.caution_button(ui, "Cancel");
});
});
!modal.is_open()
}
}
struct Textures {
fp: TextureHandle,
sw_up: TextureHandle,
sw_neut: TextureHandle,
sw_down: TextureHandle,
led_off: TextureHandle,
led_on: TextureHandle,
}
impl Textures {
fn new(ctx: &egui::Context) -> Self {
Self {
fp: Self::load_texture(
ctx,
"fp",
"/home/pjht/projects/altair_emu/resources/altair800.png",
)
.unwrap(),
sw_up: Self::load_texture(
ctx,
"sw_up",
"/home/pjht/projects/altair_emu/resources/Togup.png",
)
.unwrap(),
sw_neut: Self::load_texture(
ctx,
"sw_neut",
"/home/pjht/projects/altair_emu/resources/Togneut.png",
)
.unwrap(),
sw_down: Self::load_texture(
ctx,
"sw_down",
"/home/pjht/projects/altair_emu/resources/Togdown.png",
)
.unwrap(),
led_off: Self::load_texture(
ctx,
"led_off",
"/home/pjht/projects/altair_emu/resources/Led_off.png",
)
.unwrap(),
led_on: Self::load_texture(
ctx,
"led_on",
"/home/pjht/projects/altair_emu/resources/Led_on.png",
)
.unwrap(),
}
}
fn load_texture(
ctx: &egui::Context,
name: impl Into<String>,
path: impl AsRef<Path>,
) -> Result<TextureHandle, image::ImageError> {
let image = image::io::Reader::open(path.as_ref())?.decode()?;
let image = egui::ColorImage::from_rgba_unmultiplied(
[image.width() as _, image.height() as _],
image.to_rgba8().as_flat_samples().as_slice(),
);
Ok(ctx.load_texture(name, image, TextureOptions::LINEAR))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum SwitchState {
Up,
Neut,
Down,
}
#[derive(Clone, Copy, Debug)]
struct SwitchInfo {
pos: Pos2,
}
static SWITCHES: [SwitchInfo; 25] = [
SwitchInfo {
pos: Pos2::new(43.0, 234.0),
},
SwitchInfo {
pos: Pos2::new(183.0, 175.0),
},
SwitchInfo {
pos: Pos2::new(228.0, 175.0),
},
SwitchInfo {
pos: Pos2::new(257.0, 175.0),
},
SwitchInfo {
pos: Pos2::new(285.0, 175.0),
},
SwitchInfo {
pos: Pos2::new(330.0, 175.0),
},
SwitchInfo {
pos: Pos2::new(359.0, 175.0),
},
SwitchInfo {
pos: Pos2::new(388.0, 175.0),
},
SwitchInfo {
pos: Pos2::new(433.0, 175.0),
},
SwitchInfo {
pos: Pos2::new(461.0, 175.0),
},
SwitchInfo {
pos: Pos2::new(491.0, 175.0),
},
SwitchInfo {
pos: Pos2::new(536.0, 175.0),
},
SwitchInfo {
pos: Pos2::new(564.0, 175.0),
},
SwitchInfo {
pos: Pos2::new(593.0, 175.0),
},
SwitchInfo {
pos: Pos2::new(638.0, 175.0),
},
SwitchInfo {
pos: Pos2::new(667.0, 175.0),
},
SwitchInfo {
pos: Pos2::new(696.0, 175.0),
},
SwitchInfo {
pos: Pos2::new(190.0, 234.0),
},
SwitchInfo {
pos: Pos2::new(248.0, 234.0),
},
SwitchInfo {
pos: Pos2::new(305.0, 234.0),
},
SwitchInfo {
pos: Pos2::new(363.0, 234.0),
},
SwitchInfo {
pos: Pos2::new(420.0, 234.0),
},
SwitchInfo {
pos: Pos2::new(478.0, 234.0),
},
SwitchInfo {
pos: Pos2::new(535.0, 234.0),
},
SwitchInfo {
pos: Pos2::new(593.0, 234.0),
},
];
#[derive(Clone, Copy, Debug)]
enum LedSource {
Protect,
Iff,
Run,
CpuStatus,
Data,
Address,
}
struct LedInfo {
pos: Pos2,
source: LedSource,
mask: u16,
}
static LEDS: [LedInfo; 36] = [
LedInfo {
pos: Pos2::new(118.0, 58.0),
source: LedSource::Protect,
mask: 0xFF,
},
LedInfo {
pos: Pos2::new(89.0, 58.0),
source: LedSource::Iff,
mask: 0xFF,
},
LedInfo {
pos: Pos2::new(89.0, 117.0),
source: LedSource::Run,
mask: 0x0,
},
LedInfo {
pos: Pos2::new(118.0, 117.0),
source: LedSource::Iff,
mask: 0x0,
},
LedInfo {
pos: Pos2::new(149.0, 58.0),
source: LedSource::CpuStatus,
mask: 0x80,
},
LedInfo {
pos: Pos2::new(179.0, 58.0),
source: LedSource::CpuStatus,
mask: 0x40,
},
LedInfo {
pos: Pos2::new(209.0, 58.0),
source: LedSource::CpuStatus,
mask: 0x20,
},
LedInfo {
pos: Pos2::new(239.0, 58.0),
source: LedSource::CpuStatus,
mask: 0x10,
},
LedInfo {
pos: Pos2::new(269.0, 58.0),
source: LedSource::CpuStatus,
mask: 0x8,
},
LedInfo {
pos: Pos2::new(299.0, 58.0),
source: LedSource::CpuStatus,
mask: 0x4,
},
LedInfo {
pos: Pos2::new(329.0, 58.0),
source: LedSource::CpuStatus,
mask: 0x2,
},
LedInfo {
pos: Pos2::new(359.0, 58.0),
source: LedSource::CpuStatus,
mask: 0x1,
},
LedInfo {
pos: Pos2::new(461.0, 58.0),
source: LedSource::Data,
mask: 0x80,
},
LedInfo {
pos: Pos2::new(491.0, 58.0),
source: LedSource::Data,
mask: 0x40,
},
LedInfo {
pos: Pos2::new(536.0, 58.0),
source: LedSource::Data,
mask: 0x20,
},
LedInfo {
pos: Pos2::new(564.0, 58.0),
source: LedSource::Data,
mask: 0x10,
},
LedInfo {
pos: Pos2::new(593.0, 58.0),
source: LedSource::Data,
mask: 0x8,
},
LedInfo {
pos: Pos2::new(638.0, 58.0),
source: LedSource::Data,
mask: 0x4,
},
LedInfo {
pos: Pos2::new(667.0, 58.0),
source: LedSource::Data,
mask: 0x2,
},
LedInfo {
pos: Pos2::new(696.0, 58.0),
source: LedSource::Data,
mask: 0x1,
},
LedInfo {
pos: Pos2::new(182.0, 117.0),
source: LedSource::Address,
mask: 0x8000,
},
LedInfo {
pos: Pos2::new(227.0, 117.0),
source: LedSource::Address,
mask: 0x4000,
},
LedInfo {
pos: Pos2::new(256.0, 117.0),
source: LedSource::Address,
mask: 0x2000,
},
LedInfo {
pos: Pos2::new(285.0, 117.0),
source: LedSource::Address,
mask: 0x1000,
},
LedInfo {
pos: Pos2::new(330.0, 117.0),
source: LedSource::Address,
mask: 0x800,
},
LedInfo {
pos: Pos2::new(359.0, 117.0),
source: LedSource::Address,
mask: 0x400,
},
LedInfo {
pos: Pos2::new(388.0, 117.0),
source: LedSource::Address,
mask: 0x200,
},
LedInfo {
pos: Pos2::new(433.0, 117.0),
source: LedSource::Address,
mask: 0x100,
},
LedInfo {
pos: Pos2::new(461.0, 117.0),
source: LedSource::Address,
mask: 0x80,
},
LedInfo {
pos: Pos2::new(490.0, 117.0),
source: LedSource::Address,
mask: 0x40,
},
LedInfo {
pos: Pos2::new(536.0, 117.0),
source: LedSource::Address,
mask: 0x20,
},
LedInfo {
pos: Pos2::new(564.0, 117.0),
source: LedSource::Address,
mask: 0x10,
},
LedInfo {
pos: Pos2::new(593.0, 117.0),
source: LedSource::Address,
mask: 0x8,
},
LedInfo {
pos: Pos2::new(638.0, 117.0),
source: LedSource::Address,
mask: 0x4,
},
LedInfo {
pos: Pos2::new(667.0, 117.0),
source: LedSource::Address,
mask: 0x2,
},
LedInfo {
pos: Pos2::new(696.0, 117.0),
source: LedSource::Address,
mask: 0x1,
},
];