Rust 用 ratatui 写终端 UI:从布局、事件循环到表格渲染

test12026-11-031 次阅读

终端 UI 的基本模型

ratatui(原 tui-rs)让你在终端里画出边框、表格、列表,而不依赖任何图形库。它的核心是一个固定的事件循环:每一帧先按布局切分终端区域,再把控件画进对应区块,同时监听键盘事件更新状态。绘制是无状态的,ratatui 在底层帮你做差异刷新,只把变化的部分写给终端。

搭一个最小可运行应用

下面用 crossterm 作为后端处理终端原始模式和事件。main 函数里开启 raw 模式、进入交替屏幕,然后跑循环,直到用户按 q 退出。

use crossterm::event::{self, Event, KeyCode};
use crossterm::terminal::{enable_raw_mode, disable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen};
use ratatui::{Terminal, backend::CrosstermBackend};
use std::io;

fn main() -> io::Result<()> {
    enable_raw_mode()?;
    let mut stdout = io::stdout();
    crossterm::execute!(stdout, EnterAlternateScreen)?;
    let backend = CrosstermBackend::new(stdout);
    let mut term = Terminal::new(backend)?;

    loop {
        term.draw(|f| {
            let size = f.area();
            let block = ratatui::widgets::Block::bordered().title("状态");
            f.render_widget(block, size);
        })?;

        if let Event::Key(key) = event::read()? {
            if key.code == KeyCode::Char('q') {
                break;
            }
        }
    }

    disable_raw_mode()?;
    crossterm::execute!(io::stdout(), LeaveAlternateScreen)?;
    Ok(())
}

用 Layout 切分区域

Layout 支持按百分比或固定行高把区域切成几块。下面把屏幕分成上下两部分,上占三成,下占七成。

use ratatui::layout::{Layout, Constraint, Direction};

term.draw(|f| {
    let chunks = Layout::default()
        .direction(Direction::Vertical)
        .constraints([Constraint::Percentage(30), Constraint::Percentage(70)])
        .split(f.area());

    let top = ratatui::widgets::Block::bordered().title("顶部");
    let bottom = ratatui::widgets::Block::bordered().title("底部");
    f.render_widget(top, chunks[0]);
    f.render_widget(bottom, chunks[1]);
})?;

渲染一张表格

Table 是 ratatui 最常用的控件之一。先定义表头和行,再指定列宽,最后渲染到某个区块。数据来自普通 Vec,绘制时直接读状态。

use ratatui::widgets::{Table, Row, Cell, TableRow};

let rows = vec![
    Row::new(vec![Cell::new("1"), Cell::new("张三"), Cell::new("在线")]),
    Row::new(vec![Cell::new("2"), Cell::new("李四"), Cell::new("离线")]),
];

let table = Table::new(rows, [Constraint::Length(4), Constraint::Length(12), Constraint::Length(8)])
    .header(Row::new(vec!["ID", "名称", "状态"]));
f.render_widget(table, area);

处理键盘事件驱动状态

真实应用里要把状态提出循环外。下面用一个计数器,上下键调整选中行,q 退出。

let mut selected = 0usize;
loop {
    term.draw(|f| render_list(f, selected))?;
    if let Event::Key(key) = event::read()? {
        match key.code {
            KeyCode::Char('q') => break,
            KeyCode::Down => selected = selected.saturating_add(1),
            KeyCode::Up => selected = selected.saturating_sub(1),
            _ => {}
        }
    }
}

小结

ratatui 把终端界面拆成"布局 + 无状态绘制 + 事件循环"三步。它适合写开发工具、监控面板、聊天客户端这类需要在 SSH 环境里跑的界面。由于绘制是无状态的,状态管理完全由你自己的 Rust 变量负责,逻辑清晰也好测试。

T

test1

文章作者

终端 UI 的基本模型 ratatui(原 tui-rs)让你在终端里画出边框、表格、列表,而不依赖任何图形库。它的核...

分类
技术
发布时间
2026-11-03
字数
约 2692 字
阅读
1 次