564 lines
15 KiB
Rust
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,
|
|
},
|
|
];
|