From 0697f60491586c6aa21c208685c078a598cee555 Mon Sep 17 00:00:00 2001 From: Clo91eaf Date: Sat, 11 May 2024 10:42:54 +0800 Subject: [PATCH] [ui] refactor tui --- src/emulator.rs | 43 +++++--- src/tui.rs | 228 ++-------------------------------------- src/tui/selected_tab.rs | 212 +++++++++++++++++++++++++++++++++++++ 3 files changed, 249 insertions(+), 234 deletions(-) create mode 100644 src/tui/selected_tab.rs diff --git a/src/emulator.rs b/src/emulator.rs index 0226da5..83ebf8f 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -258,7 +258,7 @@ impl Emulator { /// Start executing the emulator with difftest and tui. pub fn start_diff_tui(&mut self, terminal: &mut Tui) { - self.ui.selected_tab.ui_buffer.diff.push("running".to_string()); + self.ui.selected_tab.diff_buffer.diff.push("running".to_string()); let mut last_diff = DebugInfo::default(); while !self.ui.cmd.exit { @@ -271,7 +271,7 @@ impl Emulator { self .ui .selected_tab - .ui_buffer + .diff_buffer .inst .push(format!("pc: {:#x}, inst: {}", pc, self.cpu.inst)); @@ -280,7 +280,7 @@ impl Emulator { self .ui .selected_tab - .ui_buffer + .diff_buffer .diff .push(format!("fatal pc: {:#x}, trap {:#?}", self.cpu.pc, trap)); @@ -297,16 +297,16 @@ impl Emulator { self .ui .selected_tab - .ui_buffer + .diff_buffer .cpu - .push(format!("record: true, pc: {:#x}, inst: {}", pc, self.cpu.inst)); + .push(format!("record: true, pc: {:#x}, inst: {}", pc, self.cpu.inst)); break; } None => { self .ui .selected_tab - .ui_buffer + .diff_buffer .cpu .push(format!("record: false, pc: {:#x}, inst: {}", pc, self.cpu.inst)); } @@ -329,7 +329,7 @@ impl Emulator { // should be `Exception::InstructionAccessFault`. dut.data = self.cpu.bus.read(p_addr, crate::cpu::DOUBLEWORD).unwrap(); - self.ui.selected_tab.ui_buffer.dut.push(format!( + self.ui.selected_tab.diff_buffer.dut.push(format!( "{}, data_sram: addr: {:#x}, data: {:#018x}", dut.ticks, data_sram.addr, dut.data )) @@ -345,7 +345,7 @@ impl Emulator { // should be `Exception::InstructionAccessFault`. dut.inst = self.cpu.bus.read(p_pc, crate::cpu::WORD).unwrap() as u32; - self.ui.selected_tab.ui_buffer.dut.push(format!( + self.ui.selected_tab.diff_buffer.dut.push(format!( "{}, inst_sram: pc: {:#x}, inst: {:#010x}", dut.ticks, inst_sram.addr, dut.inst )) @@ -356,7 +356,7 @@ impl Emulator { break; } } - self.ui.selected_tab.ui_buffer.dut.push(format!( + self.ui.selected_tab.diff_buffer.dut.push(format!( "{}, pc: {:#010x} wnum: {} wdata: {:#018x}", dut.ticks, dut.top.debug_pc(), @@ -366,17 +366,32 @@ impl Emulator { // ==================== diff ==================== if cpu_diff != dut_diff { - self.ui.selected_tab.ui_buffer.diff.clear(); + self.ui.selected_tab.diff_buffer.diff.clear(); self .ui .selected_tab - .ui_buffer + .diff_buffer .diff .push("difftest failed. press 'q' or 'Q' to quit. ".to_string()); - self.ui.selected_tab.ui_buffer.diff.push(format!("last: {}", last_diff)); - self.ui.selected_tab.ui_buffer.diff.push(format!("cpu : {}", cpu_diff)); - self.ui.selected_tab.ui_buffer.diff.push(format!("dut : {}", dut_diff)); + self + .ui + .selected_tab + .diff_buffer + .diff + .push(format!("last: {}", last_diff)); + self + .ui + .selected_tab + .diff_buffer + .diff + .push(format!("cpu : {}", cpu_diff)); + self + .ui + .selected_tab + .diff_buffer + .diff + .push(format!("dut : {}", dut_diff)); self.quit(terminal); diff --git a/src/tui.rs b/src/tui.rs index 490f468..e2950dc 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -1,9 +1,11 @@ use std::io::{self, stdout, Stdout}; -use std::{collections::VecDeque, fmt}; use crossterm::{execute, terminal::*}; -use ratatui::{prelude::*, style::palette::tailwind, widgets::*}; -use strum::{Display, EnumIter, FromRepr, IntoEnumIterator}; +use ratatui::{prelude::*, widgets::*}; + +mod selected_tab; +use selected_tab::{SelectedTab, SelectedTabEnum}; +use strum::IntoEnumIterator; /// A type alias for the terminal type used in this application pub type Tui = Terminal>; @@ -22,223 +24,12 @@ pub fn restore() -> io::Result<()> { Ok(()) } -const INST_BUFFER_SIZE: usize = 10; -const CPU_BUFFER_SIZE: usize = 10; -const DUT_BUFFER_SIZE: usize = 10; -const DIFF_BUFFER_SIZE: usize = 5; - -#[derive(Clone)] -pub struct InfoBuffer { - info: VecDeque, - size: usize, -} - -impl InfoBuffer { - fn new(size: usize) -> Self { - Self { - info: VecDeque::new(), - size, - } - } - - pub fn push(&mut self, info: String) { - if self.info.len() >= self.size { - self.info.pop_front(); - } - self.info.push_back(info); - } - - pub fn clear(&mut self) { - self.info.clear(); - } -} - -impl fmt::Display for InfoBuffer { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for info in &self.info { - write!(f, "{}\n", info)?; - } - Ok(()) - } -} - -#[derive(Clone)] -pub struct UIBuffer { - pub inst: InfoBuffer, - pub cpu: InfoBuffer, - pub dut: InfoBuffer, - pub diff: InfoBuffer, -} - -impl UIBuffer { - pub fn new() -> Self { - UIBuffer { - inst: InfoBuffer::new(INST_BUFFER_SIZE), - cpu: InfoBuffer::new(CPU_BUFFER_SIZE), - dut: InfoBuffer::new(DUT_BUFFER_SIZE), - diff: InfoBuffer::new(DIFF_BUFFER_SIZE), - } - } -} - #[derive(Default)] pub struct UICommand { pub r#continue: bool, pub exit: bool, } -#[derive(Default, Clone, Copy, Display, FromRepr, EnumIter)] -pub enum SelectedTabEnum { - #[default] - #[strum(to_string = "Main")] - Main, - #[strum(to_string = "Trace")] - Trace, - #[strum(to_string = "Difftest")] - Difftest, -} - -#[derive(Clone)] -pub struct SelectedTab { - pub ui_buffer: UIBuffer, - state: SelectedTabEnum, -} - -impl SelectedTab { - pub fn new() -> Self { - SelectedTab { - ui_buffer: UIBuffer::new(), - state: SelectedTabEnum::default(), - } - } -} - -impl SelectedTabEnum { - fn title(self) -> Line<'static> { - format!(" {self} ") - .fg(tailwind::SLATE.c200) - .bg(self.palette().c900) - .into() - } - - /// Get the previous tab, if there is no previous tab return the current tab. - pub fn previous(self) -> Self { - let current_index: usize = self as usize; - let previous_index = current_index.saturating_sub(1); - Self::from_repr(previous_index).unwrap_or(self) - } - - /// Get the next tab, if there is no next tab return the current tab. - pub fn next(self) -> Self { - let current_index = self as usize; - let next_index = current_index.saturating_add(1); - Self::from_repr(next_index).unwrap_or(self) - } - - const fn palette(self) -> tailwind::Palette { - match self { - Self::Main => tailwind::BLUE, - Self::Trace => tailwind::EMERALD, - Self::Difftest => tailwind::INDIGO, - } - } -} - -impl Widget for SelectedTab { - fn render(self, area: Rect, buf: &mut Buffer) { - // in a real app these might be separate widgets - match self.state { - SelectedTabEnum::Main => self.render_main(area, buf), - SelectedTabEnum::Trace => self.render_trace(area, buf), - SelectedTabEnum::Difftest => self.render_difftest(area, buf), - } - } -} - -impl SelectedTab { - fn render_main(self, area: Rect, buf: &mut Buffer) { - Paragraph::new("Welcome to the Ratatui tabs example!") - .block(self.block()) - .render(area, buf); - } - - fn render_trace(self, area: Rect, buf: &mut Buffer) { - Paragraph::new("Look! I'm different than others!") - .block(self.block()) - .render(area, buf); - } - - fn render_difftest(self, area: Rect, buf: &mut Buffer) { - // layout - let layout_vertical = Layout::default() - .direction(Direction::Vertical) - .constraints(vec![ - Constraint::Percentage(40), - Constraint::Percentage(40), - Constraint::Percentage(20), - ]) - .split(area); - - let layout_horizontal = Layout::default() - .direction(Direction::Horizontal) - .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) - .split(layout_vertical[1]); - - // render_frame - Paragraph::new(self.ui_buffer.inst.to_string()) - .block( - Block::bordered() - .title("Instructions") - .title_alignment(Alignment::Left) - .border_type(BorderType::Rounded), - ) - .style(Style::default().fg(Color::Cyan)) - .left_aligned() - .render(layout_vertical[0], buf); - - Paragraph::new(self.ui_buffer.cpu.to_string()) - .block( - Block::bordered() - .title("CPU") - .title_alignment(Alignment::Left) - .border_type(BorderType::Rounded), - ) - .style(Style::default().fg(Color::Cyan)) - .left_aligned() - .render(layout_horizontal[0], buf); - - Paragraph::new(self.ui_buffer.dut.to_string()) - .block( - Block::bordered() - .title("DUT") - .title_alignment(Alignment::Left) - .border_type(BorderType::Rounded), - ) - .style(Style::default().fg(Color::Cyan)) - .left_aligned() - .render(layout_horizontal[1], buf); - - Paragraph::new(self.ui_buffer.diff.to_string()) - .block( - Block::bordered() - .title("Difftest Status") - .title_alignment(Alignment::Center) - .border_type(BorderType::Rounded), - ) - .style(Style::default().fg(Color::Cyan)) - .centered() - .render(layout_vertical[2], buf) - } - - /// A block surrounding the tab's content - fn block(self) -> Block<'static> { - Block::bordered() - .border_set(symbols::border::PROPORTIONAL_TALL) - .padding(Padding::horizontal(1)) - .border_style(self.state.palette().c700) - } -} - pub struct UI { pub cmd: UICommand, pub selected_tab: SelectedTab, @@ -276,12 +67,9 @@ impl UI { impl Widget for &UI { fn render(self, area: Rect, buf: &mut Buffer) { let layout_vertical = Layout::default() - .direction(Direction::Vertical) - .constraints(vec![ - Constraint::Percentage(5), - Constraint::Percentage(95), - ]) - .split(area); + .direction(Direction::Vertical) + .constraints(vec![Constraint::Percentage(5), Constraint::Percentage(95)]) + .split(area); self.render_tabs(layout_vertical[0], buf); self.selected_tab.clone().render(layout_vertical[1], buf); diff --git a/src/tui/selected_tab.rs b/src/tui/selected_tab.rs new file mode 100644 index 0000000..02388a4 --- /dev/null +++ b/src/tui/selected_tab.rs @@ -0,0 +1,212 @@ +use ratatui::{prelude::*, style::palette::tailwind, widgets::*}; +use std::{collections::VecDeque, fmt}; +use strum::{Display, EnumIter, FromRepr}; + +const INST_BUFFER_SIZE: usize = 10; +const CPU_BUFFER_SIZE: usize = 10; +const DUT_BUFFER_SIZE: usize = 10; +const DIFF_BUFFER_SIZE: usize = 5; + +#[derive(Clone)] +pub struct InfoBuffer { + info: VecDeque, + size: usize, +} + +impl InfoBuffer { + fn new(size: usize) -> Self { + Self { + info: VecDeque::new(), + size, + } + } + + pub fn push(&mut self, info: String) { + if self.info.len() >= self.size { + self.info.pop_front(); + } + self.info.push_back(info); + } + + pub fn clear(&mut self) { + self.info.clear(); + } +} + +impl fmt::Display for InfoBuffer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for info in &self.info { + write!(f, "{}\n", info)?; + } + Ok(()) + } +} +#[derive(Clone)] +pub struct UIBuffer { + pub inst: InfoBuffer, + pub cpu: InfoBuffer, + pub dut: InfoBuffer, + pub diff: InfoBuffer, +} + +impl UIBuffer { + pub fn new() -> Self { + UIBuffer { + inst: InfoBuffer::new(INST_BUFFER_SIZE), + cpu: InfoBuffer::new(CPU_BUFFER_SIZE), + dut: InfoBuffer::new(DUT_BUFFER_SIZE), + diff: InfoBuffer::new(DIFF_BUFFER_SIZE), + } + } +} +#[derive(Default, Clone, Copy, Display, FromRepr, EnumIter)] +pub enum SelectedTabEnum { + #[default] + #[strum(to_string = "Main")] + Main, + #[strum(to_string = "Trace")] + Trace, + #[strum(to_string = "Difftest")] + Difftest, +} + +impl SelectedTabEnum { + pub fn title(self) -> Line<'static> { + format!(" {self} ") + .fg(tailwind::SLATE.c200) + .bg(self.palette().c900) + .into() + } + + /// Get the previous tab, if there is no previous tab return the current tab. + pub fn previous(self) -> Self { + let current_index: usize = self as usize; + let previous_index = current_index.saturating_sub(1); + Self::from_repr(previous_index).unwrap_or(self) + } + + /// Get the next tab, if there is no next tab return the current tab. + pub fn next(self) -> Self { + let current_index = self as usize; + let next_index = current_index.saturating_add(1); + Self::from_repr(next_index).unwrap_or(self) + } + + pub const fn palette(self) -> tailwind::Palette { + match self { + Self::Main => tailwind::BLUE, + Self::Trace => tailwind::EMERALD, + Self::Difftest => tailwind::INDIGO, + } + } +} + +#[derive(Clone)] +pub struct SelectedTab { + pub diff_buffer: UIBuffer, + pub state: SelectedTabEnum, +} + +impl SelectedTab { + pub fn new() -> Self { + SelectedTab { + diff_buffer: UIBuffer::new(), + state: SelectedTabEnum::default(), + } + } +} + +impl Widget for SelectedTab { + fn render(self, area: Rect, buf: &mut Buffer) { + // in a real app these might be separate widgets + match self.state { + SelectedTabEnum::Main => self.render_main(area, buf), + SelectedTabEnum::Trace => self.render_trace(area, buf), + SelectedTabEnum::Difftest => self.render_difftest(area, buf), + } + } +} + +impl SelectedTab { + fn render_main(self, area: Rect, buf: &mut Buffer) { + Paragraph::new("Welcome to the Ratatui tabs example!") + .block(self.block()) + .render(area, buf); + } + + fn render_trace(self, area: Rect, buf: &mut Buffer) { + Paragraph::new("Look! I'm different than others!") + .block(self.block()) + .render(area, buf); + } + + fn render_difftest(self, area: Rect, buf: &mut Buffer) { + // layout + let layout_vertical = Layout::default() + .direction(Direction::Vertical) + .constraints(vec![ + Constraint::Percentage(40), + Constraint::Percentage(40), + Constraint::Percentage(20), + ]) + .split(area); + + let layout_horizontal = Layout::default() + .direction(Direction::Horizontal) + .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(layout_vertical[1]); + + // render_frame + Paragraph::new(self.diff_buffer.inst.to_string()) + .block( + Block::bordered() + .title("Instructions") + .title_alignment(Alignment::Left) + .border_type(BorderType::Rounded), + ) + .style(Style::default().fg(Color::Cyan)) + .left_aligned() + .render(layout_vertical[0], buf); + + Paragraph::new(self.diff_buffer.cpu.to_string()) + .block( + Block::bordered() + .title("CPU") + .title_alignment(Alignment::Left) + .border_type(BorderType::Rounded), + ) + .style(Style::default().fg(Color::Cyan)) + .left_aligned() + .render(layout_horizontal[0], buf); + + Paragraph::new(self.diff_buffer.dut.to_string()) + .block( + Block::bordered() + .title("DUT") + .title_alignment(Alignment::Left) + .border_type(BorderType::Rounded), + ) + .style(Style::default().fg(Color::Cyan)) + .left_aligned() + .render(layout_horizontal[1], buf); + + Paragraph::new(self.diff_buffer.diff.to_string()) + .block( + Block::bordered() + .title("Difftest Status") + .title_alignment(Alignment::Center) + .border_type(BorderType::Rounded), + ) + .style(Style::default().fg(Color::Cyan)) + .centered() + .render(layout_vertical[2], buf) + } + + /// A block surrounding the tab's content + fn block(self) -> Block<'static> { + Block::bordered() + .border_set(symbols::border::PROPORTIONAL_TALL) + .padding(Padding::horizontal(1)) + .border_style(self.state.palette().c700) + } +}