600 lines
19 KiB
Rust
600 lines
19 KiB
Rust
use std::fmt::{self, Display};
|
|
|
|
use iced::{
|
|
Color, Element, Event, Font, Length, Padding, Pixels, Point, Rectangle, Size, Task, Vector,
|
|
advanced::{
|
|
Clipboard, Layout, Shell, Text, Widget,
|
|
layout::{Limits, Node},
|
|
overlay,
|
|
renderer::{Quad, Style},
|
|
text::Renderer,
|
|
widget::{
|
|
Operation, Tree,
|
|
tree::{State, Tag},
|
|
},
|
|
},
|
|
alignment::Vertical,
|
|
keyboard::{Key, key::Named},
|
|
mouse::{Button, Cursor, Interaction, ScrollDelta},
|
|
};
|
|
use nes_emu::Memory;
|
|
|
|
use nes_emu::{Mapped, PPU, PPUMMRegisters};
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
struct Cpu<'a>(&'a Mapped);
|
|
|
|
impl Memory for Cpu<'_> {
|
|
fn peek(&self, val: usize) -> Option<u8> {
|
|
self.0.peek_cpu(val as u16)
|
|
}
|
|
|
|
fn len(&self) -> usize {
|
|
0x10000
|
|
}
|
|
|
|
fn edit_ver(&self) -> usize {
|
|
self.0.cpu_edit_ver()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
struct Ppu<'a>(&'a Mapped, &'a PPU);
|
|
|
|
impl Memory for Ppu<'_> {
|
|
fn peek(&self, val: usize) -> Option<u8> {
|
|
match self.0.peek_ppu(val as u16) {
|
|
Ok(v) => Some(v),
|
|
Err(Some((PPUMMRegisters::Palette, off))) => Some(self.1.palette.ram(off as u8)),
|
|
Err(None) => None,
|
|
}
|
|
}
|
|
|
|
fn len(&self) -> usize {
|
|
0x4000
|
|
}
|
|
|
|
fn edit_ver(&self) -> usize {
|
|
self.0.ppu_edit_ver()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct Oam<'a>(pub &'a PPU);
|
|
|
|
impl Memory for Oam<'_> {
|
|
fn peek(&self, val: usize) -> Option<u8> {
|
|
Some(self.0.peek_oam(val as u8))
|
|
}
|
|
|
|
fn len(&self) -> usize {
|
|
0x100
|
|
}
|
|
|
|
fn edit_ver(&self) -> usize {
|
|
self.0.oam_edit_ver()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum HexEvent {}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct HexView {}
|
|
|
|
// struct Val(Option<u8>);
|
|
// impl fmt::Display for Val {
|
|
// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
// if let Some(val) = self.0 {
|
|
// write!(f, "{:02X}", val)
|
|
// } else {
|
|
// write!(f, "XX")
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
impl HexView {
|
|
// pub fn new() -> Self {
|
|
// Self {}
|
|
// }
|
|
|
|
// pub fn render_any<'a, M: Memory + Copy + 'a>(&self, mem: M) -> Element<'a, HexEvent> {
|
|
// struct Row<M: Memory>(usize, M);
|
|
// impl<'a, M: Memory + 'a> fmt::Display for Row<M> {
|
|
// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
// write!(f, "{}", Val(self.1.peek(self.0)))?;
|
|
// for i in 1..16 {
|
|
// write!(f, " {}", Val(self.1.peek(self.0 + i)))?;
|
|
// }
|
|
// Ok(())
|
|
// }
|
|
// }
|
|
// column![
|
|
// text!("Hex view"),
|
|
// iced::widget::scrollable(lazy(mem.edit_ver(), move |_| column(
|
|
// [
|
|
// text!(" | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F")
|
|
// .font(Font::MONOSPACE)
|
|
// .into()
|
|
// ]
|
|
// .into_iter()
|
|
// .chain((0..mem.len()).step_by(16).map(|off| {
|
|
// text!(" {off:04X} | {}", Row(off, mem))
|
|
// .font(Font::MONOSPACE)
|
|
// .into()
|
|
// }))
|
|
// )))
|
|
// .width(Fill),
|
|
// ]
|
|
// .width(Fill)
|
|
// .into()
|
|
// }
|
|
pub fn render_cpu<'a>(&self, mem: &'a Mapped) -> Element<'a, HexEvent> {
|
|
// self.render_any(Cpu(mem))
|
|
Element::new(
|
|
hex_editor::<Cpu<'a>, HexEvent, iced::Renderer>(Cpu(mem)).font(Font::MONOSPACE),
|
|
)
|
|
}
|
|
pub fn render_ppu<'a>(&self, mem: &'a Mapped, ppu: &'a PPU) -> Element<'a, HexEvent> {
|
|
// self.render_any(Ppu(mem, ppu))
|
|
Element::new(
|
|
hex_editor::<Ppu<'a>, HexEvent, iced::Renderer>(Ppu(mem, ppu)).font(Font::MONOSPACE),
|
|
)
|
|
}
|
|
pub fn render_oam<'a>(&self, ppu: &'a PPU) -> Element<'a, HexEvent> {
|
|
Element::new(
|
|
hex_editor::<Oam<'a>, HexEvent, iced::Renderer>(Oam(ppu)).font(Font::MONOSPACE),
|
|
)
|
|
}
|
|
|
|
pub fn update(&mut self, _ev: HexEvent) -> Task<HexEvent> {
|
|
todo!()
|
|
}
|
|
}
|
|
|
|
// pub struct BufferSlice<'a>(pub &'a [u8]);
|
|
|
|
// impl Deref for BufferSlice<'_> {
|
|
// type Target = [u8];
|
|
|
|
// fn deref(&self) -> &Self::Target {
|
|
// self.0
|
|
// }
|
|
// }
|
|
|
|
pub trait Buffer {
|
|
fn peek(&self, val: usize) -> Option<u8>;
|
|
fn len(&self) -> usize;
|
|
}
|
|
|
|
// impl Buffer for BufferSlice<'_> {
|
|
// fn peek(&self, val: usize) -> Option<u8> {
|
|
// self.get(val).copied()
|
|
// }
|
|
|
|
// fn len(&self) -> usize {
|
|
// self.iter().len()
|
|
// }
|
|
// }
|
|
impl<M: Memory> Buffer for M {
|
|
fn peek(&self, val: usize) -> Option<u8> {
|
|
self.peek(val)
|
|
}
|
|
|
|
fn len(&self) -> usize {
|
|
self.len()
|
|
}
|
|
}
|
|
|
|
pub fn hex_editor<B: Buffer, M, R: Renderer>(raw: B) -> HexEditor<B, M, R> {
|
|
HexEditor {
|
|
val: raw,
|
|
on_edit: None,
|
|
font: None,
|
|
font_size: None,
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
pub struct HexEdit {
|
|
position: usize,
|
|
old_value: u8,
|
|
new_value: u8,
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub struct HexEditor<B, M, R: Renderer> {
|
|
val: B,
|
|
font_size: Option<Pixels>,
|
|
font: Option<R::Font>,
|
|
on_edit: Option<Box<dyn Fn(HexEdit) -> M>>,
|
|
}
|
|
|
|
impl<B, M, R> HexEditor<B, M, R>
|
|
where
|
|
R: Renderer,
|
|
{
|
|
pub fn font(mut self, font: R::Font) -> Self {
|
|
self.font = Some(font);
|
|
self
|
|
}
|
|
}
|
|
|
|
impl<B: Buffer, M, R> HexEditor<B, M, R>
|
|
where
|
|
R: Renderer,
|
|
{
|
|
fn value_bounds(&self, renderer: &R, layout: Layout<'_>) -> Rectangle {
|
|
let size = self.font_size.unwrap_or(renderer.default_size());
|
|
layout
|
|
.bounds()
|
|
.shrink(Padding::new(0.).top(size.0 * 1.5).right(10.))
|
|
}
|
|
|
|
fn scrollbar_bounds(&self, renderer: &R, layout: Layout<'_>) -> Rectangle {
|
|
let size = self.font_size.unwrap_or(renderer.default_size());
|
|
layout.bounds().shrink(
|
|
Padding::new(0.)
|
|
.top(size.0 * 1.5)
|
|
.left(layout.bounds().width - 10.),
|
|
)
|
|
}
|
|
|
|
fn row_height(&self, renderer: &R) -> f32 {
|
|
self.font_size.unwrap_or(renderer.default_size()).0 * LINE_HEIGHT
|
|
}
|
|
|
|
fn scroll_max(&self, renderer: &R, layout: Layout<'_>) -> f32 {
|
|
let size = self.font_size.unwrap_or(renderer.default_size());
|
|
let bounds = layout.bounds().shrink(Padding::new(0.).top(size.0 * 1.5));
|
|
let rows = self.val.len().div_ceil(0x10);
|
|
(self.row_height(renderer) * rows as f32 - bounds.height).max(0.)
|
|
}
|
|
|
|
fn scroll(
|
|
&self,
|
|
state: &mut HexEditorState,
|
|
renderer: &R,
|
|
layout: Layout<'_>,
|
|
delta: &ScrollDelta,
|
|
) {
|
|
// let size = self.font_size.unwrap_or(renderer.default_size());
|
|
// let bounds = layout.bounds().shrink(Padding::new(0.).top(size.0 * 1.5));
|
|
// let rows = self.val.len().div_ceil(0x10);
|
|
let max = self.scroll_max(renderer, layout);
|
|
// println!("max: {max}, rows: {rows}");
|
|
match delta {
|
|
ScrollDelta::Lines { y, .. } => {
|
|
state.offset_y += y * self.row_height(renderer);
|
|
}
|
|
ScrollDelta::Pixels { y, .. } => {
|
|
state.offset_y += y;
|
|
}
|
|
}
|
|
if state.offset_y > 0. {
|
|
state.offset_y = 0.;
|
|
} else if state.offset_y < -max {
|
|
state.offset_y = -max;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
#[allow(dead_code)]
|
|
struct HexEditorState {
|
|
offset_y: f32,
|
|
selected: usize,
|
|
dragging: bool,
|
|
}
|
|
|
|
const LINE_HEIGHT: f32 = 1.3;
|
|
|
|
impl<B: Buffer, M, T, R> Widget<M, T, R> for HexEditor<B, M, R>
|
|
where
|
|
R: Renderer,
|
|
{
|
|
fn tag(&self) -> Tag {
|
|
Tag::of::<HexEditorState>()
|
|
}
|
|
|
|
fn state(&self) -> State {
|
|
State::new(HexEditorState::default())
|
|
}
|
|
|
|
fn size(&self) -> Size<Length> {
|
|
Size::new(Length::Fill, Length::Fill)
|
|
}
|
|
|
|
fn layout(&mut self, _tree: &mut Tree, _renderer: &R, limits: &Limits) -> Node {
|
|
Node::new(limits.max())
|
|
}
|
|
|
|
fn draw(
|
|
&self,
|
|
tree: &Tree,
|
|
renderer: &mut R,
|
|
_theme: &T,
|
|
_style: &Style,
|
|
layout: Layout<'_>,
|
|
_cursor: Cursor,
|
|
viewport: &Rectangle,
|
|
) {
|
|
let state: &HexEditorState = tree.state.downcast_ref();
|
|
let size = self.font_size.unwrap_or(renderer.default_size());
|
|
let font = self.font.unwrap_or(renderer.default_font());
|
|
|
|
// let fonts = font_system();
|
|
// let mut font_sys = fonts.write().unwrap();
|
|
// let id = font_sys.raw().db().query(&Query {
|
|
// families: &[Family::Monospace],
|
|
// ..Query::default()
|
|
// });
|
|
// let f = font_sys.raw().get_font(id.unwrap(), iced::advanced::graphics::text::cosmic_text::Weight(1)).unwrap();
|
|
// let width = f.metrics();
|
|
// println!("Width: {width:?}");
|
|
// iced::advanced::graphics::text::cosmic_text::Buffer::new(
|
|
// font_sys.raw(),
|
|
// Metrics::new(size.0, size.0),
|
|
// ).layout_runs();
|
|
// TODO: this needs to be computed from the font
|
|
let col_width = 0.6 * size.0;
|
|
|
|
let rows = self.val.len().div_ceil(0x10);
|
|
let row_label_length = rows.ilog2().div_ceil(4) as usize;
|
|
let mut draw = |v: &str, pos: Point| {
|
|
renderer.fill_text(
|
|
Text {
|
|
content: format!("{}", v),
|
|
bounds: viewport.size(),
|
|
size,
|
|
line_height: size.into(),
|
|
font,
|
|
align_x: iced::advanced::text::Alignment::Left,
|
|
align_y: Vertical::Top,
|
|
shaping: iced::advanced::text::Shaping::Basic,
|
|
wrapping: iced::advanced::text::Wrapping::None,
|
|
hint_factor: None,
|
|
},
|
|
pos,
|
|
Color::WHITE,
|
|
layout.bounds(),
|
|
);
|
|
};
|
|
draw("0", Point::new(0., 0.));
|
|
for i in 0..0x10 {
|
|
draw(
|
|
"0",
|
|
layout.position()
|
|
+ Vector::new((3 + row_label_length + 3 * i) as f32 * col_width, 0.),
|
|
);
|
|
draw(
|
|
"0",
|
|
layout.position()
|
|
+ Vector::new((4 + row_label_length + 3 * i) as f32 * col_width, 0.),
|
|
);
|
|
}
|
|
// renderer.fill_text(
|
|
// Text {
|
|
// content: format!(
|
|
// "{:width$} 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F {}",
|
|
// "",
|
|
// state.offset_y,
|
|
// width = row_label_length
|
|
// ),
|
|
// bounds: viewport.size(),
|
|
// size,
|
|
// line_height: size.into(),
|
|
// font,
|
|
// align_x: iced_core::text::Alignment::Left,
|
|
// align_y: iced_core::alignment::Vertical::Top,
|
|
// shaping: iced_core::text::Shaping::Basic,
|
|
// wrapping: iced_core::text::Wrapping::None,
|
|
// hint_factor: None,
|
|
// },
|
|
// layout.position(),
|
|
// Color::WHITE,
|
|
// layout.bounds(),
|
|
// );
|
|
struct HexV(Option<u8>);
|
|
impl Display for HexV {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self.0 {
|
|
Some(v) => write!(f, "{:02X}", v),
|
|
None => write!(f, "XX"),
|
|
}
|
|
}
|
|
}
|
|
// if rows > 0 {
|
|
// rows.ilog2()
|
|
// }
|
|
let bounds = self.value_bounds(renderer, layout);
|
|
let mut pos = bounds.position() + Vector::new(0., state.offset_y);
|
|
for row in 0..rows {
|
|
if bounds.contains(pos) || bounds.contains(pos + Vector::new(0., size.0 * LINE_HEIGHT))
|
|
{
|
|
renderer.fill_text(
|
|
Text {
|
|
content: format!(
|
|
"{:0width$X}0: {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {}",
|
|
row,
|
|
HexV(self.val.peek(row * 0x10 + 0x0)),
|
|
HexV(self.val.peek(row * 0x10 + 0x1)),
|
|
HexV(self.val.peek(row * 0x10 + 0x2)),
|
|
HexV(self.val.peek(row * 0x10 + 0x3)),
|
|
HexV(self.val.peek(row * 0x10 + 0x4)),
|
|
HexV(self.val.peek(row * 0x10 + 0x5)),
|
|
HexV(self.val.peek(row * 0x10 + 0x6)),
|
|
HexV(self.val.peek(row * 0x10 + 0x7)),
|
|
HexV(self.val.peek(row * 0x10 + 0x8)),
|
|
HexV(self.val.peek(row * 0x10 + 0x9)),
|
|
HexV(self.val.peek(row * 0x10 + 0xA)),
|
|
HexV(self.val.peek(row * 0x10 + 0xB)),
|
|
HexV(self.val.peek(row * 0x10 + 0xC)),
|
|
HexV(self.val.peek(row * 0x10 + 0xD)),
|
|
HexV(self.val.peek(row * 0x10 + 0xE)),
|
|
HexV(self.val.peek(row * 0x10 + 0xF)),
|
|
width = row_label_length,
|
|
),
|
|
bounds: viewport.size(),
|
|
size,
|
|
line_height: size.into(),
|
|
font,
|
|
align_x: iced::advanced::text::Alignment::Left,
|
|
align_y: Vertical::Top,
|
|
shaping: iced::advanced::text::Shaping::Basic,
|
|
wrapping: iced::advanced::text::Wrapping::None,
|
|
hint_factor: None,
|
|
},
|
|
pos,
|
|
Color::WHITE,
|
|
bounds,
|
|
);
|
|
}
|
|
pos += Vector::new(0., size.0 * LINE_HEIGHT);
|
|
}
|
|
|
|
let scrollbar = self.scrollbar_bounds(renderer, layout);
|
|
renderer.fill_quad(
|
|
Quad {
|
|
bounds: scrollbar,
|
|
..Quad::default()
|
|
},
|
|
Color::BLACK,
|
|
);
|
|
let pos = state.offset_y / self.scroll_max(renderer, layout);
|
|
renderer.fill_quad(
|
|
Quad {
|
|
bounds: Rectangle::new(
|
|
Point::new(scrollbar.x, scrollbar.y - pos * (scrollbar.height - 20.)),
|
|
Size::new(10., 20.),
|
|
),
|
|
..Quad::default()
|
|
},
|
|
Color::WHITE,
|
|
);
|
|
}
|
|
|
|
fn operate(
|
|
&mut self,
|
|
_tree: &mut Tree,
|
|
_layout: Layout<'_>,
|
|
_renderer: &R,
|
|
_operation: &mut dyn Operation,
|
|
) {
|
|
}
|
|
|
|
fn update(
|
|
&mut self,
|
|
tree: &mut Tree,
|
|
event: &Event,
|
|
layout: Layout<'_>,
|
|
cursor: Cursor,
|
|
renderer: &R,
|
|
_clipboard: &mut dyn Clipboard,
|
|
shell: &mut Shell<'_, M>,
|
|
_viewport: &Rectangle,
|
|
) {
|
|
// if !matches!(event, Event::Window(_)) {
|
|
// println!("Event: {:#?}", event);
|
|
// }
|
|
match event {
|
|
Event::Keyboard(iced::keyboard::Event::KeyPressed {
|
|
key: Key::Named(Named::PageUp),
|
|
..
|
|
}) => {
|
|
let state: &mut HexEditorState = tree.state.downcast_mut();
|
|
self.scroll(
|
|
state,
|
|
renderer,
|
|
layout,
|
|
&ScrollDelta::Pixels {
|
|
x: 0.,
|
|
y: self.value_bounds(renderer, layout).height,
|
|
},
|
|
);
|
|
shell.request_redraw();
|
|
shell.capture_event();
|
|
}
|
|
Event::Keyboard(iced::keyboard::Event::KeyPressed {
|
|
key: Key::Named(Named::PageDown),
|
|
..
|
|
}) => {
|
|
let state: &mut HexEditorState = tree.state.downcast_mut();
|
|
self.scroll(
|
|
state,
|
|
renderer,
|
|
layout,
|
|
&ScrollDelta::Pixels {
|
|
x: 0.,
|
|
y: -self.value_bounds(renderer, layout).height,
|
|
},
|
|
);
|
|
shell.request_redraw();
|
|
shell.capture_event();
|
|
}
|
|
Event::Mouse(iced::mouse::Event::WheelScrolled { delta }) => {
|
|
let state: &mut HexEditorState = tree.state.downcast_mut();
|
|
let bounds = self.value_bounds(renderer, layout);
|
|
if cursor.is_over(bounds) {
|
|
self.scroll(state, renderer, layout, delta);
|
|
shell.request_redraw();
|
|
shell.capture_event();
|
|
}
|
|
}
|
|
Event::Mouse(iced::mouse::Event::ButtonPressed(Button::Left)) => {
|
|
let state: &mut HexEditorState = tree.state.downcast_mut();
|
|
let bounds = self.scrollbar_bounds(renderer, layout);
|
|
if let Some(pos) = cursor.position_in(bounds) {
|
|
state.offset_y = -(pos.y / bounds.height * self.scroll_max(renderer, layout));
|
|
state.dragging = true;
|
|
shell.request_redraw();
|
|
shell.capture_event();
|
|
}
|
|
}
|
|
Event::Mouse(iced::mouse::Event::ButtonReleased(Button::Left)) => {
|
|
let state: &mut HexEditorState = tree.state.downcast_mut();
|
|
state.dragging = false;
|
|
}
|
|
// Event::Mouse(iced::mouse::Event::CursorLeft) => {
|
|
// let state: &mut HexEditorState = tree.state.downcast_mut();
|
|
// state.dragging = false;
|
|
// }
|
|
Event::Mouse(iced::mouse::Event::CursorMoved { .. }) => {
|
|
let state: &mut HexEditorState = tree.state.downcast_mut();
|
|
if state.dragging {
|
|
let bounds = self.scrollbar_bounds(renderer, layout);
|
|
if let Some(pos) = cursor.position_in(bounds) {
|
|
state.offset_y =
|
|
-(pos.y / bounds.height * self.scroll_max(renderer, layout));
|
|
shell.request_redraw();
|
|
shell.capture_event();
|
|
}
|
|
}
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
fn mouse_interaction(
|
|
&self,
|
|
_tree: &Tree,
|
|
_layout: Layout<'_>,
|
|
_cursor: Cursor,
|
|
_viewport: &Rectangle,
|
|
_renderer: &R,
|
|
) -> Interaction {
|
|
Interaction::None
|
|
}
|
|
|
|
fn overlay<'a>(
|
|
&'a mut self,
|
|
_tree: &'a mut Tree,
|
|
_layout: Layout<'a>,
|
|
_renderer: &R,
|
|
_viewport: &Rectangle,
|
|
_translation: Vector,
|
|
) -> Option<overlay::Element<'a, M, T, R>> {
|
|
None
|
|
}
|
|
}
|