Continue work on front panel refactor

This commit is contained in:
pjht 2024-01-25 10:34:10 -06:00
parent 8221544efb
commit 1ff2990f2e
Signed by: pjht
GPG Key ID: 7B5F6AFBEC7EE78E
6 changed files with 1277 additions and 375 deletions

984
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,8 +8,8 @@ edition = "2021"
[dependencies]
bitflags = "2.3.3"
device_query = "1.1.3"
eframe = { version = "0.22.0", features = ["ron", "persistence"] }
egui-modal = "0.2.4"
eframe = { version = "0.25.0", features = ["ron", "persistence"] }
egui-modal = "0.3.2"
enum_dispatch = "0.3.12"
env_logger = "0.10.0"
ihex = "3.0.0"

View File

@ -1 +1,481 @@
use std::path::Path;
use eframe::{
egui::{self, TextureOptions, Ui, Widget, Id, Painter},
epaint::{vec2, Pos2, Rect, TextureHandle, pos2, Color32},
};
use self::switch::SwitchState;
pub mod led;
pub mod switch;
const NULL_UV: Rect = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0));
const NULL_TINT: Color32 = Color32::WHITE;
#[derive(Copy, Clone, Default)]
struct FrontpanelState {
ad_sws: u16,
power: bool,
runstop: SwitchState,
single_step: SwitchState,
exam: SwitchState,
dep: SwitchState,
reset: SwitchState,
prot: SwitchState,
aux1: SwitchState,
aux2: SwitchState,
}
pub struct Frontpanel<'a> {
id: Id,
textures: &'a Textures,
}
impl<'a> Frontpanel<'a> {
pub fn new(id: Id, textures: &'a Textures) -> Self {
Self {
id,
textures,
}
}
}
impl Widget for Frontpanel<'_> {
fn ui(self, ui: &mut Ui) -> egui::Response {
let mut state: FrontpanelState = ui.data(|data| data.get_temp(self.id).unwrap_or_default());
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(
(800.0, 333.0).into(),
egui::Sense {
click: false,
drag: false,
focusable: false,
},
);
let fp_rect = resp.rect;
dbg!(fp_rect);
let painter = Painter::new(ui.ctx().clone(), ui.layer_id(), ui.clip_rect().intersect(resp.rect));
painter.image(self.textures.fp.id(), painter.clip_rect(), NULL_UV, NULL_TINT);
let x_scale = painter.clip_rect().width() / 800.0;
let y_scale = painter.clip_rect().height() / 333.0;
for led in &LEDS {
let mut pos = led.pos;
pos.x *= x_scale;
pos.y *= y_scale;
pos += fp_rect.left_top().to_vec2();
let led_data = 0xAAAA;
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 mut pos = switch.pos;
pos.x *= x_scale;
pos.y *= y_scale;
pos += fp_rect.left_top().to_vec2();
if i == 0 {
ui.allocate_ui_at_rect(
Rect::from_center_size(pos, vec2(32.0, 32.0)),
|ui| {
ui.add(switch::ToggleSwitch::new(&mut state.power, &sw_textures));
},
);
}
if (1..17).contains(&i) {
let bit_mask = 1 << (16 - i);
let mut sw_state = state.ad_sws & bit_mask > 0;
ui.allocate_ui_at_rect(
Rect::from_center_size(pos, vec2(32.0, 32.0)),
|ui| {
ui.add(switch::ToggleSwitch::new(&mut sw_state, &sw_textures));
},
);
state.ad_sws = (state.ad_sws & !(bit_mask)) | ((sw_state as u16) << (16 - i));
}
if (17..25).contains(&i) {
let state = match i {
17 => &mut state.runstop,
18 => &mut state.single_step,
19 => &mut state.exam,
20 => &mut state.dep,
21 => &mut state.reset,
22 => &mut state.prot,
23 => &mut state.aux1,
24 => &mut state.aux2,
_ => unreachable!(),
};
ui.allocate_ui_at_rect(
Rect::from_center_size(pos, vec2(32.0, 32.0)),
|ui| {
ui.add(switch::ThreePosSwitch::new(state, &sw_textures));
},
);
}
// todo!();
}
ui.data_mut(|data| data.insert_temp(self.id, 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,
},
];

54
src/frontpanel/led.rs Normal file
View File

@ -0,0 +1,54 @@
use eframe::{
egui::{Sense, Widget},
epaint::{pos2, vec2, Color32, Rect, TextureHandle},
};
const NULL_UV: Rect = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0));
const NULL_TINT: Color32 = Color32::WHITE;
pub struct Textures {
led_on: TextureHandle,
led_off: TextureHandle,
}
impl Textures {
pub fn new(led_on: TextureHandle, led_off: TextureHandle) -> Self {
Self { led_on, led_off }
}
}
pub struct Led<'a> {
on: bool,
textures: &'a Textures,
}
impl<'a> Led<'a> {
pub fn new(on: bool, textures: &'a Textures) -> Self {
Self { on, textures }
}
}
impl Widget for Led<'_> {
fn ui(self, ui: &mut eframe::egui::Ui) -> eframe::egui::Response {
let (resp, painter) = ui.allocate_painter(
(16.0, 16.0).into(),
Sense {
click: false,
drag: false,
focusable: false,
},
);
let texture = if self.on {
self.textures.led_on.id()
} else {
self.textures.led_off.id()
};
painter.image(
texture,
Rect::from_min_size(painter.clip_rect().left_top(), vec2(16.0, 16.0)),
NULL_UV,
NULL_TINT,
);
resp
}
}

View File

@ -1,6 +1,6 @@
use eframe::{
egui::{Id, Sense, Widget},
epaint::{pos2, vec2, Color32, Rect, TextureHandle},
egui::{Sense, Widget},
epaint::{pos2, vec2, Color32, Rect, TextureHandle, TextureId},
};
const NULL_UV: Rect = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0));
@ -20,6 +20,22 @@ impl Textures {
sw_down,
}
}
pub fn get_for_state(&self, state: SwitchState) -> TextureId {
match state {
SwitchState::Up => self.sw_up.id(),
SwitchState::Neut => self.sw_neut.id(),
SwitchState::Down => self.sw_down.id(),
}
}
pub fn get_for_bool(&self, state: bool) -> TextureId {
if state {
self.sw_up.id()
} else {
self.sw_down.id()
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@ -29,51 +45,76 @@ pub enum SwitchState {
Down,
}
pub struct Switch<'a> {
impl Default for SwitchState {
fn default() -> Self {
Self::Neut
}
}
pub struct ThreePosSwitch<'a> {
state: &'a mut SwitchState,
textures: &'a Textures,
}
impl<'a> Switch<'a> {
impl<'a> ThreePosSwitch<'a> {
pub fn new(state: &'a mut SwitchState, textures: &'a Textures) -> Self {
Self { state, textures }
}
}
impl Widget for Switch<'_> {
impl Widget for ThreePosSwitch<'_> {
fn ui(self, ui: &mut eframe::egui::Ui) -> eframe::egui::Response {
let (resp, painter) = ui.allocate_painter((32.0, 32.0).into(), Sense::click());
let id = Id::new((
painter.clip_rect().left_top().x as u32,
painter.clip_rect().left_top().y as u32,
));
let texture = match self.state {
SwitchState::Up => self.textures.sw_up.id(),
SwitchState::Neut => self.textures.sw_neut.id(),
SwitchState::Down => self.textures.sw_down.id(),
};
let (resp, painter) = ui.allocate_painter((32.0, 32.0).into(), Sense::drag());
painter.image(
texture,
self.textures.get_for_state(*self.state),
Rect::from_min_size(painter.clip_rect().left_top(), vec2(32.0, 32.0)),
NULL_UV,
NULL_TINT,
);
let pointer_state = ui.ctx().input(|inp| inp.pointer.clone());
let interact_pos = pointer_state.interact_pos();
dbg!(interact_pos);
let interacted = interact_pos.map(|interact_pos| {
Rect::from_center_size(interact_pos, (10.0, 24.0).into()).contains(painter.clip_rect().left_top())
}).unwrap_or(false);
let newstate = if interacted {
if interact_pos.unwrap().y > painter.clip_rect().left_top().y {
SwitchState::Down
} else {
SwitchState::Up
if resp.drag_started() {
let interact_pos = resp.interact_pointer_pos().unwrap();
let sw_center = painter.clip_rect().left_top() + vec2(15.0, 16.0);
if Rect::from_center_size(interact_pos, (10.0, 24.0).into()).contains(sw_center) {
if interact_pos.y > sw_center.y {
*self.state = SwitchState::Down;
} else {
*self.state = SwitchState::Up;
};
}
} else {
SwitchState::Neut
};
*self.state = newstate;
} else if resp.drag_released() {
*self.state = SwitchState::Neut;
}
resp
}
}
pub struct ToggleSwitch<'a> {
state: &'a mut bool,
textures: &'a Textures,
}
impl<'a> ToggleSwitch<'a> {
pub fn new(state: &'a mut bool, textures: &'a Textures) -> Self {
Self { state, textures }
}
}
impl Widget for ToggleSwitch<'_> {
fn ui(self, ui: &mut eframe::egui::Ui) -> eframe::egui::Response {
let (resp, painter) = ui.allocate_painter((32.0, 32.0).into(), Sense::drag());
painter.image(
self.textures.get_for_bool(*self.state),
Rect::from_min_size(painter.clip_rect().left_top(), vec2(32.0, 32.0)),
NULL_UV,
NULL_TINT,
);
if resp.drag_started() {
let interact_pos = resp.interact_pointer_pos().unwrap();
let sw_center = painter.clip_rect().left_top() + vec2(15.0, 16.0);
if Rect::from_center_size(interact_pos, (10.0, 24.0).into()).contains(sw_center) {
*self.state = !*self.state;
}
}
resp
}
}

View File

@ -11,9 +11,9 @@ use std::{
};
use cpu::{MemCycle, Status, I8080};
use device_query::{DeviceState, Keycode};
use device_query::DeviceState;
use eframe::{
egui::{self, menu, Button, Pos2, Rect, TextureHandle, TextureOptions, Ui},
egui::{self, menu, Button, Id, Label, Pos2, Rect, TextureHandle, TextureOptions, Ui},
NativeOptions,
};
use egui_modal::Modal;
@ -24,7 +24,7 @@ use rfd::FileDialog;
use serde::{Deserialize, Serialize};
use soloud::{audio, AudioExt, LoadExt, Soloud};
use crate::frontpanel::switch;
use crate::frontpanel::{switch, Frontpanel};
#[derive(Clone, Debug)]
enum AudioMessage {
@ -182,7 +182,7 @@ struct Options {
}
struct AltairEmulator {
textures: Textures,
textures: frontpanel::Textures,
ad_sws: u16,
power: bool,
runstop: SwitchState,
@ -253,7 +253,7 @@ impl AltairEmulator {
// 20: 004
// 21: 005
Self {
textures: Textures::new(&cc.egui_ctx),
textures: frontpanel::Textures::new(&cc.egui_ctx),
ad_sws: 0x0,
power: false,
runstop: SwitchState::Neut,
@ -315,7 +315,7 @@ impl eframe::App for AltairEmulator {
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| {
ui.image(texture, texture.size_vec2());
ui.image(texture);
});
}
@ -323,13 +323,13 @@ impl eframe::App for AltairEmulator {
ui.allocate_ui_at_rect(
Rect::from_center_size(pos.into(), texture.size_vec2()),
|ui| {
ui.image(texture, texture.size_vec2());
ui.image(texture);
},
);
}
ctx.set_pixels_per_point(1.0);
frame.set_window_size((800.0, 333.0).into());
// 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| {
@ -378,6 +378,11 @@ impl eframe::App for AltairEmulator {
});
});
egui::CentralPanel::default().show(ctx, |ui| {
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()));
// 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 {
@ -728,12 +733,6 @@ impl eframe::App for AltairEmulator {
// self.cpu.finish_m_cycle(data);
// self.update_fp();
// }
let sw_texts = switch::Textures::new(
self.textures.sw_up.clone(),
self.textures.sw_neut.clone(),
self.textures.sw_down.clone(),
);
ui.add(switch::Switch::new(&mut self.dep, &sw_texts));
});
let old_fan_enabled = self.options.fan_enabled;