// use std::time::Duration; mod tabs; mod term; use std::{io::Result, time::Duration}; use crossterm::event::{self, poll, Event, KeyCode, KeyEvent, KeyEventKind}; use ratatui::{ prelude::*, widgets::{Block, Cell, Paragraph, Row, Table, TableState}, }; use crate::cpu::Cpu; pub struct App { cpu: Cpu, running: bool, table_state: TableState, } impl App { pub fn new(cpu: Cpu) -> Self { Self { cpu, running: true, table_state: TableState::default(), } } pub fn init(&mut self) { let _ = self.cpu.reset(); } pub fn update(&mut self, terminal: &mut Terminal) -> Result<()> { self.draw(terminal)?; self.handle_events()?; self.handle_cpu()?; Ok(()) } fn handle_cpu(&mut self) -> Result<()> { if self.running { self.cpu.cycle(); } Ok(()) } /// Draw a single frame of the app. fn draw(&self, terminal: &mut Terminal) -> Result<()> { terminal .draw(|frame| { frame.render_widget(self, frame.size()); }) .unwrap(); Ok(()) } fn handle_events(&mut self) -> Result<()> { if poll(Duration::from_secs_f32(1.0 / 25.0))? { match event::read()? { Event::Key(key) if key.kind == KeyEventKind::Press => self.handle_key_press(key), _ => {} } } Ok(()) } fn handle_key_press(&mut self, key: KeyEvent) { match key.code { KeyCode::Enter => { if !self.running { self.cpu.cycle() } } KeyCode::Char(' ') => { self.running = !self.running; } _ => {} }; } } /// Implement Widget for &App rather than for App as we would otherwise have to clone or copy the /// entire app state on every frame. For this example, the app state is small enough that it doesn't /// matter, but for larger apps this can be a significant performance improvement. impl Widget for &App { fn render(self, area: Rect, buf: &mut Buffer) { let [cpu_area, memory_area] = Layout::horizontal([Constraint::Percentage(50), Constraint::Fill(1)]).areas(area); let cpu_info = format!("a: {a:#04x}, x: {x:#04x}, y: {y:#04x}, pc: {pc:#06x}, sp: {s:#04x}, sr: {p:#010b}, irq: {irq:?}, nmi: {nmi:?}", a = self.cpu.a, x = self.cpu.x, y = self.cpu.y, pc = self.cpu.pc, s = self.cpu.s, p = self.cpu.p, irq = self.cpu.irq, nmi = self.cpu.nmi); Paragraph::new(cpu_info) .block(Block::bordered().title("cpu info!")) .render(cpu_area, buf); let memory = self.cpu.memory.lock().unwrap(); let table_height = memory_area.rows().count() - 2; let rows: Vec = memory.data[0..table_height * 16] .chunks(16) .map(|chunk| { chunk .iter() .map(|content| Cell::from(Text::from(format!("{content:#02}")))) .collect::() }) .collect(); let widths = vec![Constraint::Length(2); 16]; Widget::render( Table::new(rows, widths) .header( Row::new(vec![ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", ]) .style(Style::new().bold()) // To add space between the header and the rest of the rows, specify the margin .bottom_margin(1), ) .block(Block::bordered().title("memory!")), memory_area, buf, ); } } // impl Widget for App { // fn render_title_bar(&self, area: Rect, buf: &mut Buffer) { // // let layout = Layout::horizontal([Constraint::Min(0), Constraint::Length(43)]); // // let [title] = layout.areas(area); // Span::styled("Ratatui", Style::default()).render(area, buf); // // let titles = Tab::iter().map(Tab::title); // // Tabs::new(titles) // // .style(THEME.tabs) // // .highlight_style(THEME.tabs_selected) // // .select(self.tab as usize) // // .divider("") // // .padding("", "") // // .render(tabs, buf); // } // // fn render_selected_tab(&self, area: Rect, buf: &mut Buffer) { // // match self.tab { // // Tab::About => self.about_tab.render(area, buf), // // Tab::Recipe => self.recipe_tab.render(area, buf), // // Tab::Email => self.email_tab.render(area, buf), // // Tab::Traceroute => self.traceroute_tab.render(area, buf), // // Tab::Weather => self.weather_tab.render(area, buf), // // }; // // } // }