george-emu/src/tui/mod.rs

151 lines
4.9 KiB
Rust

// 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<impl Backend>) -> 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<impl Backend>) -> 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<Row> = memory.data[0..table_height * 16]
.chunks(16)
.map(|chunk| {
chunk
.iter()
.map(|content| Cell::from(Text::from(format!("{content:#02}"))))
.collect::<Row>()
})
.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),
// // };
// // }
// }