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 { 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 { 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 { 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); // 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(usize, M); // impl<'a, M: Memory + 'a> fmt::Display for Row { // 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::, 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::, 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::, HexEvent, iced::Renderer>(Oam(ppu)).font(Font::MONOSPACE), ) } pub fn update(&mut self, _ev: HexEvent) -> Task { 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; fn len(&self) -> usize; } // impl Buffer for BufferSlice<'_> { // fn peek(&self, val: usize) -> Option { // self.get(val).copied() // } // fn len(&self) -> usize { // self.iter().len() // } // } impl Buffer for M { fn peek(&self, val: usize) -> Option { self.peek(val) } fn len(&self) -> usize { self.len() } } pub fn hex_editor(raw: B) -> HexEditor { 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 { val: B, font_size: Option, font: Option, on_edit: Option M>>, } impl HexEditor where R: Renderer, { pub fn font(mut self, font: R::Font) -> Self { self.font = Some(font); self } } impl HexEditor 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 Widget for HexEditor where R: Renderer, { fn tag(&self) -> Tag { Tag::of::() } fn state(&self) -> State { State::new(HexEditorState::default()) } fn size(&self) -> Size { 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); 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> { None } }