altair_emu/src/frontpanel.rs
2024-01-31 15:23:48 -06:00

564 lines
15 KiB
Rust

use std::path::Path;
use eframe::{
egui::{self, Key, KeyboardShortcut, Modifiers, Sense, TextureOptions, Ui, Widget},
epaint::{vec2, Pos2, Rect, TextureHandle},
};
use crate::cpu::Status;
use self::switch::SwitchState;
pub mod led;
pub mod switch;
#[derive(Clone, Copy, Debug)]
pub enum ActionSwitch {
RunStop,
SingleStep,
Examine,
Deposit,
Reset,
Protect,
Aux1,
Aux2,
}
#[derive(Clone, Copy, Debug)]
pub enum FrontpanelInteraction {
ActionSw(ActionSwitch, SwitchState),
Power(bool),
}
#[derive(Copy, Clone, Default)]
pub struct FrontpanelState {
ad_sws: u16,
power: bool,
runstop: SwitchState,
single_step: SwitchState,
exam: SwitchState,
dep: SwitchState,
reset: SwitchState,
prot: SwitchState,
aux1: SwitchState,
aux2: SwitchState,
addr: u16,
data: u8,
status: Status,
}
impl FrontpanelState {
pub fn ad_sws(&self) -> u16 {
self.ad_sws
}
pub fn power(&self) -> bool {
self.power
}
pub fn set_addr(&mut self, addr: u16) {
self.addr = addr;
}
pub fn set_data(&mut self, data: u8) {
self.data = data;
}
pub fn set_status(&mut self, status: Status) {
self.status = status;
}
}
pub struct Frontpanel<'a> {
textures: &'a Textures,
state: &'a mut FrontpanelState,
interaction: Option<FrontpanelInteraction>,
}
impl<'a> Frontpanel<'a> {
pub fn new(textures: &'a Textures, state: &'a mut FrontpanelState) -> Self {
Self {
textures,
state,
interaction: None,
}
}
pub fn interaction(&self) -> Option<FrontpanelInteraction> {
self.interaction
}
}
impl Widget for &mut Frontpanel<'_> {
fn ui(self, ui: &mut Ui) -> egui::Response {
let sw_textures = switch::Textures::new(
self.textures.sw_up.clone(),
self.textures.sw_neut.clone(),
self.textures.sw_down.clone(),
);
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());
resp.request_focus();
let fp_rect = resp.rect;
ui.allocate_ui_at_rect(fp_rect, |ui| {
ui.image(&self.textures.fp);
for led in &LEDS {
let pos = led.pos + fp_rect.left_top().to_vec2();
let led_data = match led.source {
LedSource::Protect => 0x0,
LedSource::Iff => 0x0,
LedSource::Run => 0x0,
LedSource::CpuStatus => u16::from(self.state.status.bits()),
LedSource::Data => u16::from(self.state.data),
LedSource::Address => self.state.addr,
};
let led_on = (led_data & led.mask) > 0;
ui.allocate_ui_at_rect(Rect::from_center_size(pos, vec2(16.0, 16.0)), |ui| {
ui.add(led::Led::new(led_on, &led_textures));
});
}
// 0: Power
// 1..17: A/D left to right
// 17..25: Action left to right
for (i, switch) in SWITCHES.iter().enumerate() {
let pos = switch.pos + fp_rect.left_top().to_vec2();
if i == 0 {
ui.allocate_ui_at_rect(Rect::from_center_size(pos, vec2(11.0, 25.0)), |ui| {
let mut power_inv = !self.state.power;
if ui
.add(switch::ToggleSwitch::new(
&mut power_inv,
&sw_textures,
Some(KeyboardShortcut::new(Modifiers::NONE, Key::A)),
))
.changed()
{
self.state.power = !power_inv;
self.interaction = Some(FrontpanelInteraction::Power(self.state.power));
};
});
}
if (1..17).contains(&i) {
let bit_mask = 1 << (16 - i);
let mut sw_state = self.state.ad_sws & bit_mask > 0;
ui.allocate_ui_at_rect(Rect::from_center_size(pos, vec2(11.0, 25.0)), |ui| {
let key_offset = (i - 1) % 8;
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
} else {
Modifiers::NONE
};
let shortcut = KeyboardShortcut::new(mods, key);
if ui
.add(switch::ToggleSwitch::new(
&mut sw_state,
&sw_textures,
Some(shortcut),
))
.changed()
{
self.state.ad_sws =
(self.state.ad_sws & !(bit_mask)) | (u16::from(sw_state) << (16 - i));
};
});
}
if (17..25).contains(&i) {
let state = match i {
17 => &mut self.state.runstop,
18 => &mut self.state.single_step,
19 => &mut self.state.exam,
20 => &mut self.state.dep,
21 => &mut self.state.reset,
22 => &mut self.state.prot,
23 => &mut self.state.aux1,
24 => &mut self.state.aux2,
_ => unreachable!(),
};
ui.allocate_ui_at_rect(Rect::from_center_size(pos, vec2(11.0, 25.0)), |ui| {
let sw_offset = i - 17;
let up_key = ["S", "D", "F", "G", "H", "J", "K", "L"][sw_offset];
let down_key = ["X", "C", "V", "B", "N", "M", ",", "."][sw_offset];
let up_key = Key::from_name(up_key).unwrap();
let down_key = Key::from_name(down_key).unwrap();
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),
))
.changed()
{
let sw = match i {
17 => ActionSwitch::RunStop,
18 => ActionSwitch::SingleStep,
19 => ActionSwitch::Examine,
20 => ActionSwitch::Deposit,
21 => ActionSwitch::Reset,
22 => ActionSwitch::Protect,
23 => ActionSwitch::Aux1,
24 => ActionSwitch::Aux2,
_ => unreachable!(),
};
self.interaction = Some(FrontpanelInteraction::ActionSw(sw, *state));
};
});
}
}
});
resp
}
}
pub struct Textures {
fp: TextureHandle,
sw_up: TextureHandle,
sw_neut: TextureHandle,
sw_down: TextureHandle,
led_off: TextureHandle,
led_on: TextureHandle,
}
impl Textures {
pub 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)]
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,
},
];