vex-tui 0.6.9

Vex TUI dashboard — ratatui-based terminal interface
Documentation
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::{Color, Modifier, Style};
use ratatui::widgets::Widget;

/// Wraps a vt100 parser to render PTY output as a ratatui widget.
pub struct VtTerminal {
    parser: vt100::Parser,
}

impl VtTerminal {
    pub fn new(cols: u16, rows: u16) -> Self {
        Self {
            parser: vt100::Parser::new(rows, cols, 0),
        }
    }

    pub fn process(&mut self, data: &[u8]) {
        self.parser.process(data);
    }

    pub fn resize(&mut self, rows: u16, cols: u16) {
        self.parser.set_size(rows, cols);
    }

    pub fn widget(&self) -> VtWidget<'_> {
        VtWidget {
            screen: self.parser.screen(),
        }
    }
}

pub struct VtWidget<'a> {
    screen: &'a vt100::Screen,
}

impl Widget for VtWidget<'_> {
    fn render(self, area: Rect, buf: &mut Buffer) {
        let rows = area.height.min(self.screen.size().0);
        let cols = area.width.min(self.screen.size().1);

        for row in 0..rows {
            for col in 0..cols {
                let cell = self.screen.cell(row, col);
                if let Some(cell) = cell {
                    let x = area.x + col;
                    let y = area.y + row;

                    if x < area.right() && y < area.bottom() {
                        let ch = cell.contents();
                        let fg = vt100_color_to_ratatui(cell.fgcolor());
                        let bg = vt100_color_to_ratatui(cell.bgcolor());
                        let mut style = Style::default().fg(fg).bg(bg);
                        if cell.bold() {
                            style = style.add_modifier(Modifier::BOLD);
                        }
                        if cell.underline() {
                            style = style.add_modifier(Modifier::UNDERLINED);
                        }
                        if cell.inverse() {
                            style = style.add_modifier(Modifier::REVERSED);
                        }

                        let buf_cell = buf.cell_mut((x, y));
                        if let Some(buf_cell) = buf_cell {
                            buf_cell.set_symbol(if ch.is_empty() { " " } else { &ch });
                            buf_cell.set_style(style);
                        }
                    }
                }
            }
        }
    }
}

fn vt100_color_to_ratatui(color: vt100::Color) -> Color {
    match color {
        vt100::Color::Default => Color::Reset,
        vt100::Color::Idx(i) => Color::Indexed(i),
        vt100::Color::Rgb(r, g, b) => Color::Rgb(r, g, b),
    }
}