Add colors to register view

This commit is contained in:
Simon Gellis 2025-02-23 00:15:05 -05:00
parent 892d48e321
commit 4a6288147d
3 changed files with 328 additions and 58 deletions

View File

@ -1,9 +1,10 @@
use std::{
fmt::Display,
fmt::{Display, UpperHex},
ops::{Bound, RangeBounds},
str::FromStr,
};
use atoi::FromRadix16;
use egui::{
ecolor::HexColor, Align, Color32, CursorIcon, Event, Frame, Key, Layout, Margin, Rect,
Response, RichText, Rounding, Sense, Shape, Stroke, TextEdit, Ui, UiBuilder, Vec2, Widget,
@ -38,7 +39,12 @@ impl UiExt for Ui {
let mut frame = Frame::group(self.style());
frame.outer_margin.top += 10.0;
frame.inner_margin.top += 2.0;
let res = self.push_id(&title, |ui| frame.show(ui, add_contents));
let res = self.push_id(&title, |ui| {
frame.show(ui, |ui| {
ui.set_min_width(ui.available_width());
add_contents(ui);
})
});
let text = RichText::new(title).background_color(self.style().visuals.panel_fill);
let old_rect = res.response.rect;
let mut text_rect = old_rect;
@ -98,11 +104,35 @@ enum Direction {
}
pub trait Number:
Copy + One + CheckedAdd + CheckedSub + Eq + Ord + Display + FromStr + Send + Sync + 'static
Copy
+ One
+ CheckedAdd
+ CheckedSub
+ Eq
+ Ord
+ Display
+ FromStr
+ FromRadix16
+ UpperHex
+ Send
+ Sync
+ 'static
{
}
impl<
T: Copy + One + CheckedAdd + CheckedSub + Eq + Ord + Display + FromStr + Send + Sync + 'static,
T: Copy
+ One
+ CheckedAdd
+ CheckedSub
+ Eq
+ Ord
+ Display
+ FromStr
+ FromRadix16
+ UpperHex
+ Send
+ Sync
+ 'static,
> Number for T
{
}
@ -113,6 +143,8 @@ pub struct NumberEdit<'a, T: Number> {
precision: usize,
min: Option<T>,
max: Option<T>,
arrows: bool,
hex: bool,
}
impl<'a, T: Number> NumberEdit<'a, T> {
@ -123,6 +155,8 @@ impl<'a, T: Number> NumberEdit<'a, T> {
precision: 3,
min: None,
max: None,
arrows: true,
hex: false,
}
}
@ -143,12 +177,35 @@ impl<'a, T: Number> NumberEdit<'a, T> {
};
Self { min, max, ..self }
}
pub fn arrows(self, arrows: bool) -> Self {
Self { arrows, ..self }
}
pub fn hex(self, hex: bool) -> Self {
Self { hex, ..self }
}
}
impl<T: Number> Widget for NumberEdit<'_, T> {
fn ui(self, ui: &mut Ui) -> Response {
let id = ui.id();
let to_string = |val: &T| format!("{val:.0$}", self.precision);
let to_string = |val: &T| {
if self.hex {
format!("{val:.0$X}", self.precision)
} else {
format!("{val:.0$}", self.precision)
}
};
let from_string = |val: &str| {
if self.hex {
let bytes = val.as_bytes();
let (result, consumed) = T::from_radix_16(bytes);
(consumed == bytes.len()).then_some(result)
} else {
val.parse::<T>().ok()
}
};
let (last_value, mut str, focus) = ui.memory(|m| {
let (lv, s) = m
@ -163,7 +220,7 @@ impl<T: Number> Widget for NumberEdit<'_, T> {
str = to_string(self.value);
stale = true;
}
let valid = str.parse().is_ok_and(|v: T| v == *self.value);
let valid = from_string(&str).is_some_and(|v: T| v == *self.value);
let mut up_pressed = false;
let mut down_pressed = false;
if focus {
@ -194,17 +251,25 @@ impl<T: Number> Widget for NumberEdit<'_, T> {
.id(id)
.margin(Margin {
left: 4.0,
right: 20.0,
right: if self.arrows { 20.0 } else { 4.0 },
top: 2.0,
bottom: 2.0,
});
let res = if valid {
let mut res = if valid {
ui.add(text)
} else {
let message = match (self.min, self.max) {
(Some(min), Some(max)) => format!("Please enter a number between {min} and {max}."),
(Some(min), None) => format!("Please enter a number greater than {min}."),
(None, Some(max)) => format!("Please enter a number less than {max}."),
(Some(min), Some(max)) => format!(
"Please enter a number between {} and {}.",
to_string(&min),
to_string(&max)
),
(Some(min), None) => {
format!("Please enter a number greater than {}.", to_string(&min))
}
(None, Some(max)) => {
format!("Please enter a number less than {}.", to_string(&max))
}
(None, None) => "Please enter a number.".to_string(),
};
ui.scope(|ui| {
@ -218,13 +283,14 @@ impl<T: Number> Widget for NumberEdit<'_, T> {
.on_hover_text(message)
};
let mut delta = None;
if self.arrows {
let arrow_left = res.rect.max.x + 4.0;
let arrow_right = res.rect.max.x + 20.0;
let arrow_top = res.rect.min.y - 2.0;
let arrow_middle = (res.rect.min.y + res.rect.max.y) / 2.0;
let arrow_bottom = res.rect.max.y + 2.0;
let mut delta = None;
let top_arrow_rect = Rect {
min: (arrow_left, arrow_top).into(),
max: (arrow_right, arrow_middle).into(),
@ -239,6 +305,7 @@ impl<T: Number> Widget for NumberEdit<'_, T> {
if draw_arrow(ui, bottom_arrow_rect, false).clicked_or_dragged() || down_pressed {
delta = Some(Direction::Down);
}
}
let in_range =
|val: &T| self.min.is_none_or(|m| &m <= val) && self.max.is_none_or(|m| &m >= val);
@ -248,12 +315,18 @@ impl<T: Number> Widget for NumberEdit<'_, T> {
Direction::Down => self.value.checked_sub(&self.increment),
};
if let Some(new_value) = value.filter(in_range) {
if *self.value != new_value {
res.mark_changed();
}
*self.value = new_value;
}
str = to_string(self.value);
stale = true;
} else if res.changed {
if let Some(new_value) = str.parse().ok().filter(in_range) {
if let Some(new_value) = from_string(&str).filter(in_range) {
if *self.value != new_value {
res.mark_changed();
}
*self.value = new_value;
}
stale = true;

View File

@ -1,20 +1,22 @@
use std::sync::Arc;
use egui::{
Align, Button, CentralPanel, Checkbox, Context, Label, Layout, ScrollArea, TextEdit, Ui,
ViewportBuilder, ViewportId,
Align, Button, CentralPanel, Checkbox, Color32, Context, Direction, Label, Layout, ScrollArea,
TextEdit, Ui, ViewportBuilder, ViewportId,
};
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
use crate::{
emulator::SimId,
memory::{MemoryClient, MemoryValue, MemoryView},
memory::{MemoryClient, MemoryRef, MemoryValue, MemoryView},
window::{
utils::{NumberEdit, UiExt},
AppWindow,
},
};
use super::utils;
pub struct RegisterWindow {
sim_id: SimId,
memory: Arc<MemoryClient>,
@ -26,7 +28,7 @@ impl RegisterWindow {
Self {
sim_id,
memory: memory.clone(),
registers: memory.watch(sim_id, 0x0005f800, 0x70),
registers: memory.watch(sim_id, 0x0005f800, 0x72),
}
}
@ -36,21 +38,21 @@ impl RegisterWindow {
let mut intenb = InterruptReg::parse(raw_intenb);
let mut intpnd = InterruptReg::parse(raw_intpnd);
ui.section("Interrupt", |ui| {
ui.vertical(|ui| {
let width = ui.available_width();
ui.with_layout(Layout::top_down_justified(Align::Center), |ui| {
TableBuilder::new(ui)
.id_salt("raw_values")
.column(Column::auto())
.column(Column::remainder())
.columns(Column::exact(width * 0.5), 2)
.cell_layout(Layout::left_to_right(Align::Max))
.body(|mut body| {
body.row(row_height, |mut row| {
row.col(|ui| {
ui.label("INTENB");
ui.add_sized(ui.available_size(), Label::new("INTENB"));
});
row.col(|ui| {
let mut text = format!("{raw_intenb:04x}");
ui.add_enabled(
false,
ui.add_sized(
ui.available_size(),
TextEdit::singleline(&mut text).horizontal_align(Align::Max),
);
});
@ -71,17 +73,17 @@ impl RegisterWindow {
ui.add_space(8.0);
TableBuilder::new(ui)
.id_salt("flags")
.column(Column::auto())
.columns(Column::remainder(), 2)
.column(Column::exact(width * 0.5))
.columns(Column::exact(width * 0.25), 2)
.cell_layout(Layout::left_to_right(Align::Center).with_main_align(Align::RIGHT))
.body(|mut body| {
body.row(row_height, |mut row| {
row.col(|_ui| {});
row.col(|ui| {
ui.add_sized([ui.available_width(), 0.0], Label::new("ENB"));
ui.add_sized(ui.available_size(), Label::new("ENB"));
});
row.col(|ui| {
ui.add_sized([ui.available_width(), 0.0], Label::new("PND"));
ui.add_sized(ui.available_size(), Label::new("PND"));
});
});
let mut add_row = |label: &str, enb: &mut bool, pnd: &mut bool| {
@ -112,7 +114,7 @@ impl RegisterWindow {
add_row("LFBEND", &mut intenb.lfbend, &mut intpnd.lfbend);
add_row("SCANERR", &mut intenb.scanerr, &mut intpnd.scanerr);
});
ui.add_space(ui.available_height());
ui.allocate_space(ui.available_size());
});
});
if intpnd.update(&mut raw_intpnd) {
@ -128,9 +130,10 @@ impl RegisterWindow {
let mut raw_dpstts = self.read_address(0x0005f820);
let mut dpstts = DisplayReg::parse(raw_dpstts);
ui.section("Display", |ui| {
let width = ui.available_width();
TableBuilder::new(ui)
.column(Column::auto())
.column(Column::remainder())
.column(Column::exact(width * 0.5))
.column(Column::exact(width * 0.5))
.cell_layout(Layout::left_to_right(Align::Max))
.body(|mut body| {
body.row(row_height, |mut row| {
@ -197,7 +200,7 @@ impl RegisterWindow {
});
});
});
ui.vertical(|ui| ui.add_space((ui.available_height() - row_height).max(0.0)));
ui.allocate_space(ui.available_size());
});
if dpstts.update(&mut raw_dpstts) {
self.memory.write(self.sim_id, 0x0005f822, &raw_dpstts);
@ -209,9 +212,10 @@ impl RegisterWindow {
let [mut raw_xpstts, raw_xpctrl] = self.read_address(0x0005f840);
let mut xpstts = DrawingReg::parse(raw_xpstts);
ui.section("Drawing", |ui| {
let width = ui.available_width();
TableBuilder::new(ui)
.column(Column::auto())
.column(Column::remainder())
.column(Column::exact(width * 0.5))
.column(Column::exact(width * 0.5))
.cell_layout(Layout::left_to_right(Align::Max))
.body(|mut body| {
body.row(row_height, |mut row| {
@ -257,7 +261,9 @@ impl RegisterWindow {
row.col(|ui| {
ui.add_enabled(
false,
NumberEdit::new(&mut xpstts.sbcount).range(0..32),
NumberEdit::new(&mut xpstts.sbcount)
.arrows(false)
.range(0..32),
);
});
});
@ -291,7 +297,7 @@ impl RegisterWindow {
});
});
});
ui.vertical(|ui| ui.add_space(ui.available_height()));
ui.allocate_space(ui.available_size());
});
if xpstts.update(&mut raw_xpstts) {
xpstts.update(&mut raw_xpstts);
@ -299,10 +305,185 @@ impl RegisterWindow {
}
}
fn read_address<T: MemoryValue>(&self, address: usize) -> T {
let index = (address - 0x0005f800) / size_of::<T>();
self.registers.borrow().read(index)
fn show_colors(&mut self, ui: &mut Ui) {
let row_height = ui.spacing().interact_size.y;
let registers = self.registers.borrow();
ui.section("Colors", |ui| {
TableBuilder::new(ui)
.column(Column::auto())
.columns(Column::remainder(), 4)
.cell_layout(Layout::left_to_right(Align::Max))
.body(|mut body| {
body.row(row_height, |mut row| {
row.col(|_ui| {});
row.col(|_ui| {});
row.col(|ui| {
ui.with_layout(
Layout::centered_and_justified(Direction::LeftToRight),
|ui| ui.label("1"),
);
});
row.col(|ui| {
ui.with_layout(
Layout::centered_and_justified(Direction::LeftToRight),
|ui| ui.label("2"),
);
});
row.col(|ui| {
ui.with_layout(
Layout::centered_and_justified(Direction::LeftToRight),
|ui| ui.label("3"),
);
});
});
let mut brts: [u16; 3] = [
read_address(&registers, 0x0005f824),
read_address(&registers, 0x0005f826),
read_address(&registers, 0x0005f828),
];
body.row(row_height, |mut row| {
let mut stale = false;
row.col(|ui| {
ui.label("BRT");
});
row.col(|_ui| {});
for brt in brts.iter_mut() {
row.col(|ui| {
if ui
.add(NumberEdit::new(brt).range(0..256).arrows(false).hex(true))
.changed()
{
stale = true;
}
});
}
if stale {
self.memory.write(self.sim_id, 0x0005f824, &brts);
}
});
body.row(row_height, |mut row| {
row.col(|_ui| {});
for shade in utils::parse_brts(&brts, Color32::RED) {
row.col(|ui| {
ui.painter().rect_filled(
ui.available_rect_before_wrap(),
0.0,
shade,
);
});
}
});
let mut palettes = read_address::<[u16; 8]>(&registers, 0x0005f860);
let mut add_row = |name: &str, address: u32, value: &mut u16| {
let mut c1 = (*value >> 2) & 0x03;
let mut c2 = (*value >> 4) & 0x03;
let mut c3 = (*value >> 6) & 0x03;
let mut stale = false;
body.row(row_height, |mut row| {
row.col(|ui| {
ui.label(name);
});
row.col(|ui| {
if ui
.add(
NumberEdit::new(value)
.range(0..256)
.arrows(false)
.hex(true),
)
.changed()
{
stale = true;
};
});
row.col(|ui| {
if ui
.add(NumberEdit::new(&mut c1).range(0..4).arrows(false))
.changed()
{
*value = (*value & 0xfff3) | (c1 << 2);
stale = true;
}
});
row.col(|ui| {
if ui
.add(NumberEdit::new(&mut c2).range(0..4).arrows(false))
.changed()
{
*value = (*value & 0xffcf) | (c2 << 4);
stale = true;
}
});
row.col(|ui| {
if ui
.add(NumberEdit::new(&mut c3).range(0..4).arrows(false))
.changed()
{
*value = (*value & 0xff3f) | (c3 << 6);
stale = true;
}
});
});
if stale {
self.memory.write(self.sim_id, address, value);
}
};
add_row("GPLT0", 0x0005f860, &mut palettes[0]);
add_row("GPLT1", 0x0005f862, &mut palettes[1]);
add_row("GPLT2", 0x0005f864, &mut palettes[2]);
add_row("GPLT3", 0x0005f866, &mut palettes[3]);
add_row("JPLT0", 0x0005f868, &mut palettes[4]);
add_row("JPLT1", 0x0005f86a, &mut palettes[5]);
add_row("JPLT2", 0x0005f86c, &mut palettes[6]);
add_row("JPLT3", 0x0005f86e, &mut palettes[7]);
body.row(row_height, |mut row| {
row.col(|ui| {
ui.label("BKCOL");
});
row.col(|ui| {
let mut bkcol: u16 = read_address(&registers, 0x0005f870);
if ui
.add(NumberEdit::new(&mut bkcol).range(0..4).arrows(false))
.changed()
{
self.memory.write(self.sim_id, 0x0005f870, &bkcol);
}
});
row.col(|_ui| {});
row.col(|ui| {
ui.label("REST");
});
row.col(|ui| {
let mut rest: u16 = read_address(&registers, 0x0005f82a);
if ui
.add(
NumberEdit::new(&mut rest)
.range(0..256)
.arrows(false)
.hex(true),
)
.changed()
{
self.memory.write(self.sim_id, 0x0005f82a, &rest);
}
});
});
});
ui.allocate_space(ui.available_size_before_wrap());
});
}
fn read_address<T: MemoryValue>(&self, address: usize) -> T {
read_address(&self.registers.borrow(), address)
}
}
fn read_address<T: MemoryValue>(registers: &MemoryRef, address: usize) -> T {
let index = (address - 0x0005f800) / size_of::<T>();
registers.read(index)
}
impl AppWindow for RegisterWindow {
@ -317,15 +498,19 @@ impl AppWindow for RegisterWindow {
fn initial_viewport(&self) -> ViewportBuilder {
ViewportBuilder::default()
.with_title(format!("Registers ({})", self.sim_id))
.with_inner_size((640.0, 480.0))
.with_inner_size((800.0, 480.0))
}
fn show(&mut self, ctx: &Context) {
CentralPanel::default().show(ctx, |ui| {
ScrollArea::vertical().show(ui, |ui| {
ui.horizontal_top(|ui| {
let width = ui.available_width() - (ui.spacing().item_spacing.x * 6.0);
StripBuilder::new(ui)
.sizes(Size::remainder(), 4)
.size(Size::exact(width * 0.2))
.size(Size::exact(width * 0.2))
.size(Size::exact(width * 0.3))
.size(Size::exact(width * 0.2))
.horizontal(|mut strip| {
strip.cell(|ui| {
self.show_interrupts(ui);
@ -340,6 +525,9 @@ impl AppWindow for RegisterWindow {
});
});
});
strip.cell(|ui| {
self.show_colors(ui);
});
});
});
});

View File

@ -28,6 +28,15 @@ pub const fn parse_shades(brts: &[u8; 8]) -> [u8; 4] {
]
}
pub fn parse_brts(brts: &[u16; 3], color: Color32) -> [Color32; 4] {
[
Color32::BLACK,
shade(brts[0] as u8, color),
shade(brts[1] as u8, color),
shade((brts[0] + brts[1] + brts[2]) as u8, color),
]
}
pub fn palette_colors(palette: u8, brts: &[u8; 8], color: Color32) -> [Color32; 4] {
let colors = parse_shades(brts).map(|s| shade(s, color));
parse_palette(palette).map(|p| colors[p as usize])