Rust三方库(ratatui)

TUI库

开始TUI库之前我们先介绍下常用的跨平台终端操作库crossterm。其中常见的模块有四个:

  1. cursor:为终端中的光标提供各种功能。
  2. event:给键盘、鼠标、resize终端提供事件,
  3. style:给输入提供颜色等等属性。
  4. terminal:给正在运行的终端提供功能。

cursor中常见的结构体

  • DisableBlinking:禁用终端光标闪烁。
  • EnableBlinking:开启终端光标闪烁。
  • Hide:隐藏终端的光标。
  • MoveDown:光标向下移动特定的行数。
  • MoveLeft:光标向左移动特定行数。
  • MoveRight:光标向右移动特定的列数。
  • MoveTo:移动终端到给定的行列为止。
  • MoveToColumn:沿列移动当前光标。
  • MoveToNextLine:向下移动当前光标到下一行。
  • MoveToPreviousLine:移动当前光标到上一行。
  • MoveToRow:移动状态光标到给定的行和列。
  • MoveUp:上香移动特定行数。
  • RestorePosition:保存当前光标的位置。
  • SavePosition:保存当前终端光标的位置。
  • Show:显示终端光标。

event事件

事件模块提供了从键盘、鼠标、终端resize获取事件的功能。

  • read函数当事件可用时立马返回事件负责就阻塞到事件可用为止。
  • poll函数允许检查是否事件在特定时间周期可用,换句话说调用poll不会导致事件阻塞。
    不允许从不同的线程调用这些函数或者在EventStream中组合他们。开启raw mode为了键盘事件正常工作。

默认鼠标事件是不开启的你必须通过EnableMouseCapture开启。

结构体:

  • DisableBracketedPaste:禁用括号粘贴模式。
  • DisableFocusChange:禁用focus事件发射。
  • DisableMouseCapture:禁用鼠标事件捕获。
  • EnableBracketedPaste:开启括号粘贴模式。
  • EnableFocusChange:开启focus事件发射。
  • EnableMouseCapture:开启鼠标事件捕获。
  • EventStream:Result的流。
  • KeyEvent:代表键盘事件。
  • KeyEventState:键盘事件的额外状态。
  • KeyModifiers:特殊案件修饰符(shift, control, alt, etc.).
  • KeyboardEnhancementFlags:代表特殊的标记高数兼容的终端添加额外的键盘事件信息。
  • MouseEvent:鼠标事件。
  • PopKeyboardEnhancementFlags:禁用额外的键盘事件。
  • PushKeyboardEnhancementFlags:开启kitty键盘歇息,添加额外的信息到键盘事件移除含糊不清的修饰符。
    常见枚举:
  • Event:代表一个事件。
    • FocusGained
    • FocusLost
    • Key(KeyEvent)
    • Mouse(MouseEvent)
    • Paste(String)
    • Resize(u16, u16)
  • KeyCode:按键代码
    • Backspace
    • Enter
    • Left
    • Right
    • Up
    • Down
    • Home
    • End
    • PageUp
    • PageDown
    • Tab
    • BackTab
    • Delete
    • Insert
    • F(u8)
    • Char(char)
    • Null
    • Esc
    • CapsLock
    • ScrollLock
    • NumLock
    • PrintScreen
    • Pause
    • Menu
    • KeypadBegin
    • Media(MediaKeyCode)
    • Modifier(ModifierKeyCode):案件修饰符,在一些平台(如mac),并不会报告所有的鼠标事件,ctrl+鼠标左键点击为鼠标右键点击。
  • KeyEventKind:按键事件类型。
    • Press:按键按下
    • Repeat:案件重复
    • Replace:案件替换。
    • MediaKeyCode:代表媒体key。(as part of KeyCode::Media).
  • ModifierKeyCode:代表修饰符key。(as part of KeyCode::Modifier).
    • Play
    • Pause
    • PlayPause
    • Reverse
    • Stop
    • FastForward
    • Rewind
    • TrackNext
    • TrackPrevious
    • Record
    • LowerVolume
    • RaiseVolume
    • MuteVolume
  • MouseButton:代表鼠标按键
    • Left
    • Right
    • Middle
  • MouseEventKind:鼠标事件类型
    • Down(MouseButton)
    • Up(MouseButton)
    • Drag(MouseButton)
    • Moved
    • ScrollDown
    • ScrollUp
    • ScrollLeft
    • ScrollRigh

style

提供对文本应用属性和颜色的功能。常见结构体:

  • Attibutes:所有个能属性的bit集合。-Colors:用来表示前景和背景颜色。-ContentStyle:作用在内容上的风格。-Print:打印给定的可显示类型的命令。-PrintStyleContent:打印风格化内容的命令。-ResetColor:reset颜色为默认。-SetAttribute:设置属性集合。-SetAttributes:设置多属性集合。-SetBackgroundColor:设置背景颜色集合。-SetForegroundColor:设置外围颜色。-SetStyle:设置风格。-SetUnderlineColor:设置下划线颜色。
    枚举:

  • Attribute:代表属性。

  • Color:代表颜色。

  • Colored:代表前景或者背景颜色。ForegroundColorBackgroundColorUnderlineColor
    属性:

    Attribute Windows UNIX Notes
    Reset
    Bold
    Dim
    Italic ? ? Not widely supported, sometimes treated as inverse.
    Underlined
    SlowBlink ? ? Not widely supported, sometimes treated as inverse.
    RapidBlink ? ? Not widely supported. MS-DOS ANSI.SYS; 150+ per minute.
    Reverse
    Hidden Also known as Conceal.
    Fraktur Legible characters, but marked for deletion.
    DefaultForegroundColor ? ? Implementation specific (according to standard).
    DefaultBackgroundColor ? ? Implementation specific (according to standard).
    Framed ? ? Not widely supported.
    Encircled ? ? This should turn on the encircled attribute.
    OverLined ? ? This should draw a line at the top of the text.

    颜色:

    深色 浅色
    Light Dark
    DarkGrey Black
    Red DarkRed
    Green DarkGreen
    Yellow DarkYellow
    Blue DarkBlue
    Magenta DarkMagenta
    Cyan DarkCyan
    White Grey

终端模块

多数终端行为都可以使用命令执行。

屏幕缓冲区

屏幕缓冲区是一个用来在终端展示二维的特性和颜色数据数组。终端有不同的缓冲区可以相互切换,默认的工作屏幕为主屏幕,其他屏幕为可用屏幕。可用屏幕不同于主屏幕,它有明确的终端窗口维度,没有滚动区域。Crossterm提供了切换到可用屏幕、修改返回主屏幕的功能,在可用屏幕上执行命令的时候主屏幕保持完整。

Raw模式

有时候默认模式是不相关的,在这种情况下,我们可以关闭它。常见的默认模式开启的场景:

  • 输入不转发到屏幕。
  • 输入不在回车后处理。
  • 输入不是缓冲行行。
  • 特殊案件删除backspace和Ctrol+C将不由终端驱动处理。
  • 新行特性将不被println!处理使用write!替换。
    通过ternimal::enable_raw_mode和terminal::disable_raw_mode函数开启或者关闭。

Lazy执行

刷新字节到终端缓冲器是一个很重的系统调用。如果我们在终端中执行一些行为,我们想周期性的做这些工作,我们可以再同一时间刷新更多数据到缓冲区,Crossterm提供了queue做这个事情,你可以调用Write::flush执行写入。你可以传入一个自定义的实现了std::io::Write的buffer到队列操作中。命令在buffer中被执行,常见的buffer是std::io::stdout和std::io::stderr。队列函数返回的是他自己,你可以使用它调用另一个命令:stdout.queue(Goto(5,5)).queue(Clear(CLeareType::All))或者你可以使用队列宏queue!(stdout,MoveTo(5,5),Clear(ClearTyle::All))

直接执行

对多数应用程序来说,高效的刷新操作并不重要。在这个case下使用execute操作,这个操作将立即执行然后调用flush。你可以传递一个自定义的实现了std::io::Write的缓冲器执行execute操作。这个命令将在缓冲器执行。

ratatui

rataui常用的widget:

  • Axis:表格的x或者y轴。
  • Bar:BarChar的bar对象
  • BarChart:在单个widget上显示多个bar。
  • BarGroup:代表多个BarChart的组。
  • Borders:设置边界是否可见。
  • Cell:Cell包含在Table的行中用于展示的Text。
  • Chart:在笛卡尔坐标系下绘制一个或者更多的数据。
  • Clear:clear/reset一个确定的区域允许重写。
  • Dataset:一组数据点。
  • Gauge:任务进程状态展示widget。
  • LineGauge:单行展示的任务进度。
  • List:展示一些可以被选中的元素。
  • Paragraph:展示一些文本。
  • Block:带边界的widget块。
  • Tabs:在多个面板上下文展示可用的tab。

Axis常见属性和方法

  • title:设置title名称。
  • bounds:设置边界。
  • labels:设置label。
  • style:设置风格。
  • labels_alignment:定义标签对齐。

Block相关属性

设置外围边界线

  • Borders::LEFT:展示左边边界。

  • Borders::RIGHT:展示右边边界。

  • Border::ALL:展示包围边界。

  • Border::TOP:展示上边界。

  • Border::NONE:展示无边界。

  • Border::BOTTOM:展示下边界。

  • BorderType::Plain:展示普通边界。

  • BorderType::Double:展示双层边界。

  • BorderType::Rounded:展示圆角边界。

  • BorderType::Thick:展示细边界。

示意图:边界展示。设置风格示例:


terminal.draw(|f| {
        let size = f.size();
        let block = Block::default()
            .title("Block")
            .title_alignment(ratatui::prelude::Alignment::Center) //设置title居中对齐
            .borders(Borders::ALL) //打开边界线
            .border_type(BorderType::Rounded) //边界线周围为圆角
            .style(Style::default().bg(Color::Black).fg(Color::Yellow)); //设置block背景色为黑色,前景色为黄色
    })?;

Layout

设置组件布局。布局组成:

  • 方向(水平和垂直)
  • 限制集合(length、ratio、perccentage、min、max)
  • 边缘(水平和垂直方向)上的组件间距。
    常见方法:
  • constraints:设置布局之间的约束。
  • margin:设置layout的margin。
  • horizontal_margin:设置水平方向上的间距。
  • vertical_margin:设置垂直方向上的间距。
  • direction:设置布局的方向。
  • split:使用指定的宽度、高度、方向切分区域。

block风格设置

Bar 常见属性和方法

  • value:柱状条的值。
  • label:柱状条的label。
  • style:风格。
  • value_style:值的风格。
  • text_value:文本值。

BarGroup

BarGroup的常见属性和方法:

  • label:设置label。
  • bars:设置多个bar。

BarChar

柱状图,常见方法:

  • data:柱状条的数据。
  • block:柱状图的边框。
  • max:限制柱状图的上界。
  • bar_style:设置bar的风格。
  • bar_width:设置bar的宽度。
  • bar_gap:设置bar之间的间隔。
  • bar_set:设置bar。
  • value_style:设置bar的风格。
  • label_style:设置label的风格。
  • group_gap:设置bar组风格。
  • style:设置柱状图风格。
  • direction:设置柱状图的方向。
use crossterm::terminal::{
    disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
};
use crossterm::{event, event::Event, event::KeyCode, execute};
use ratatui::backend::CrosstermBackend;
use ratatui::prelude::{symbols, Color, Constraint, Marker, Modifier, Span, Style};
use ratatui::style::Stylize;
use ratatui::widgets::{Bar, BarChart, BarGroup, Block, Borders};
use ratatui::Terminal;
use std::error::Error;
use std::io::{self, Stdout};
use std::time::Duration;
fn main() -> Result<(), Box<dyn Error>> {
    let mut terminal = setup_terminal()?;
    run(&mut terminal)?;
    restore_terminal(&mut terminal)?;
    Ok(())
}

fn setup_terminal() -> Result<Terminal<CrosstermBackend<Stdout>>, Box<dyn Error>> {
    let mut stdout = io::stdout();
    enable_raw_mode()?;
    execute!(stdout, EnterAlternateScreen)?;
    Ok(Terminal::new(CrosstermBackend::new(stdout))?)
}

fn restore_terminal(
    terminal: &mut Terminal<CrosstermBackend<Stdout>>,
) -> Result<(), Box<dyn Error>> {
    disable_raw_mode()?;
    execute!(terminal.backend_mut(), LeaveAlternateScreen,)?;
    Ok(terminal.show_cursor()?)
}

fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<(), Box<dyn Error>> {
    let mut p = 0.4;
    Ok(loop {
        terminal.draw(|frame| {
            let g = BarChart::default()
                .block(Block::default().title("BarChart").borders(Borders::ALL))
                .style(Style::default().fg(Color::Red).bg(Color::LightCyan))
                .bar_width(3)
                .bar_gap(1)
                .group_gap(3)
                .bar_style(Style::new().yellow().on_red())
                .value_style(Style::new().red().bold())
                .label_style(Style::new().red())
                .data(&[("B0", 0), ("B1", 2), ("B2", 4), ("B3", 3)])
                .data(
                    BarGroup::default().bars(&[Bar::default().value(10), Bar::default().value(20)]),
                )
                .max(4);
            frame.render_widget(g, frame.size());
        })?;
        if event::poll(Duration::from_millis(250))? {
            if let Event::Key(key) = event::read()? {
                if KeyCode::Char('q') == key.code {
                    break;
                }
            }
        }
    })
}

barchart

Tabs常见属性和方法

  • block:设置block风格。
  • select:选中index。
  • style:设置风格。
  • highlight_style:设置高亮风格。
    示例代码:
        let tabs = Tabs::new(vec!["tab1", "tab2"]) //构建Tabs widgets
            .block(
                Block::default()
                    .borders(Borders::ALL)
                    .title("BlockTitle")
                    .title_alignment(ratatui::prelude::Alignment::Left),
            ) //设置包围block的风格,包括边界线,title和title位置
            .highlight_style(Style::default().fg(Color::Cyan).bg(Color::LightGreen)) // 设置tabs的默认前景色为Cyan,背景为LightGreen
            .select(0); //选中第一个tab
        f.render_widget(tabs, size);

Tabs风格设置

List常用函数

  • block:绘制边缘。
  • style:设置list风格。
  • highlight_style:设置高亮风格。
  • highlight_symbol:设置高亮符号。
  • repeat_hightlight_symbol:重复高亮风格。
  • start_corner:设置角起点。
  • len:List元素个数。
  • is_empty:List是否为空。

let items = [ListItem::new("Item1"), ListItem::new("Item2")];
        let lists = List::new(items)
            .block(
                Block::default()
                    .title("List title")
                    .borders(Borders::ALL)
                    .style(
                        Style::default()
                            .fg(Color::LightBlue)
                            .bg(Color::LightMagenta),
                    ),
            )
            .start_corner(Corner::BottomLeft)
            .highlight_style(Style::default().add_modifier(Modifier::BOLD))
            .highlight_symbol(">>");
        f.render_widget(lists, size);

List效果

Dataset

Dataset组件,数据点集合。常见方法:

  • name:数据点的名称。
  • data:数据点数据。
  • marker:数据点类型。常见选项为:Marker::BrailleMarker::Dot、``。
  • graph_type:图类型。常见选项为:GraphType::LineGraphType::Scatter
  • style:设置风格。

Chart

在笛卡尔坐标系上绘制。

use crossterm::terminal::{
    disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
};
use crossterm::{event, event::Event, event::KeyCode, execute};
use ratatui::backend::CrosstermBackend;
use ratatui::prelude::{symbols, Color, Constraint, Marker, Modifier, Span, Style};
use ratatui::widgets::{Axis, Block, Borders, Chart, Dataset, GraphType};
use ratatui::Terminal;
use std::error::Error;
use std::io::{self, Stdout};
use std::time::Duration;
fn main() -> Result<(), Box<dyn Error>> {
    let mut terminal = setup_terminal()?;
    run(&mut terminal)?;
    restore_terminal(&mut terminal)?;
    Ok(())
}

fn setup_terminal() -> Result<Terminal<CrosstermBackend<Stdout>>, Box<dyn Error>> {
    let mut stdout = io::stdout();
    enable_raw_mode()?;
    execute!(stdout, EnterAlternateScreen)?;
    Ok(Terminal::new(CrosstermBackend::new(stdout))?)
}

fn restore_terminal(
    terminal: &mut Terminal<CrosstermBackend<Stdout>>,
) -> Result<(), Box<dyn Error>> {
    disable_raw_mode()?;
    execute!(terminal.backend_mut(), LeaveAlternateScreen,)?;
    Ok(terminal.show_cursor()?)
}

fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<(), Box<dyn Error>> {
    let mut p = 20;
    Ok(loop {
        terminal.draw(|frame| {
            let datasets = vec![
                Dataset::default()
                    .name("data1")
                    .marker(symbols::Marker::Dot)
                    .graph_type(GraphType::Scatter)
                    .style(Style::default().fg(Color::Red))
                    .data(&[(0.0, 5.0), (1.0, 6.0), (1.5, 6.434)]),
                Dataset::default()
                    .name("data2")
                    .marker(Marker::Braille)
                    .graph_type(GraphType::Line)
                    .style(Style::default().fg(Color::Magenta))
                    .data(&[(4.0, 5.0), (5.0, 8.0), (7.66, 13.5)]),
            ];
            let constrains = (Constraint::Ratio(1, 3), Constraint::Ratio(1, 4));
            let g = Chart::new(datasets)
                .block(Block::default().title("Chart"))
                .x_axis(
                    Axis::default()
                        .title(Span::styled("X Axis", Style::default().fg(Color::Red))) //x轴的title
                        .style(Style::default().fg(Color::White))
                        .bounds([0.0, 10.0]) //设置x轴范围
                        .labels(
                            ["0.0", "5.0", "10.0"] //x轴对应位置标记字符串
                                .iter()
                                .cloned()
                                .map(Span::from)
                                .collect(),
                        ),
                )
                .y_axis(
                    Axis::default()
                        .title(Span::styled("Y Axis", Style::default().fg(Color::Red)))
                        .style(Style::default().fg(Color::White))
                        .bounds([0.0, 10.0]) //y轴值范围
                        .labels(
                            ["0.0", "5.0", "10.0"]
                                .iter()
                                .cloned()
                                .map(Span::from)
                                .collect(),
                        ),
                )
                .hidden_legend_constraints(constrains);

            frame.render_widget(g, frame.size());
        })?;
        if event::poll(Duration::from_millis(250))? {
            if let Event::Key(key) = event::read()? {
                if KeyCode::Char('q') == key.code {
                    break;
                } else if KeyCode::Left == key.code {
                    p = (p - 1) % 100;
                } else if KeyCode::Right == key.code {
                    p = (p + 1) % 100;
                } else {
                }
            }
        }
    })
}```

## Table

Table由Row组成,而Row由列表构造。Row中的每个单元称为Cell,Cell可以单独定义风格、颜色。整行也可以单独定义风格。

### 常用函数

- `block`:设置表格边界
- `header`:设置表头
- `style`:设置表风格
- `highlight_symbol`:
- `highlight_style`:
- `highlight_spacing`:
- `column_spacing`:
- ``
  实现:

```rust
let table = Table::new(vec![
            // Row can be created from simple strings.
            Row::new(vec!["Row11", "Row12", "Row13"]), //从列表构建行
            // You can style the entire row.
            Row::new(vec!["Row21", "Row22", "Row23"]).style(Style::default().fg(Color::Blue)),
            // If you need more control over the styling you may need to create Cells directly
            Row::new(vec![
                Cell::from("Row31"),
                Cell::from("Row32").style(Style::default().fg(Color::Yellow)),
                Cell::from(Line::from(vec![
                    Span::raw("Row"),
                    Span::styled("33", Style::default().fg(Color::Green)),
                ])),
            ]),
            // If a Row need to display some content over multiple lines, you just have to change
            // its height.
            Row::new(vec![
                Cell::from("Row\n41"),
                Cell::from("Row\n42"),
                Cell::from("Row\n43"),
            ])
            .height(2),
        ])
        // You can set the style of the entire Table.
        .style(Style::default().fg(Color::White))
        // It has an optional header, which is simply a Row always visible at the top.
        .header(
            Row::new(vec!["Col1", "Col2", "Col3"])
                .style(Style::default().fg(Color::Yellow).bg(Color::LightCyan))
                .bottom_margin(2), //设置表头和其他行之间的间距
        )
        // As any other widget, a Table can be wrapped in a Block.
        .block(
            Block::default()
                .title("Table")
                .borders(Borders::ALL)
                .style(Style::default().fg(Color::LightMagenta)),
        )
        // 设置列宽
        .widths(&[
            Constraint::Length(5), //超过列宽的字符将被截断
            Constraint::Length(5),
            Constraint::Length(7),
        ])
        // 设置列与列之间的间隔
        .column_spacing(5)
        // 当指定方法被选中后你希望强调一行
        .highlight_style(
            Style::default()
                .add_modifier(Modifier::BOLD)
                .fg(Color::LightRed),
        )
        // ...and potentially show a symbol in front of the selection.
        .highlight_symbol(">>");
        f.render_widget(table, size);

table风格设置

Paragraph

常见的方法:

  • block:设置Paragraph的包围风格。
  • style:设置Paragraph的风格。
  • wrap:对widget设置包围配置。
  • scroll:对给定的paragraph设置scroll偏移。
  • alignment:设置文本对齐方式。

Gauge

常见方法:

  • block:进度条边框。
  • percent:设置进度条所占百分比。数字20表示占据进度条20%。
  • ratio:设置进度条所占比例,0.5表示占据进度条一半的位置。
  • label:设置进度条中显示的文字。
  • style:设置外观风格。
  • gauge_style:设置进度条风格。
  • use_unicode:使用unicode编码。
    示例:
use crossterm::terminal::{
    disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
};
use crossterm::{event, event::Event, event::KeyCode, execute, Command};
use ratatui::backend::CrosstermBackend;
use ratatui::prelude::{Color, Modifier, Style};
use ratatui::widgets::{Block, Borders, Gauge, Paragraph};
use ratatui::Terminal;
use std::error::Error;
use std::io::{self, Stdout};
use std::time::Duration;
fn main() -> Result<(), Box<dyn Error>> {
    let mut terminal = setup_terminal()?;
    run(&mut terminal)?;
    restore_terminal(&mut terminal)?;
    Ok(())
}

fn setup_terminal() -> Result<Terminal<CrosstermBackend<Stdout>>, Box<dyn Error>> {
    let mut stdout = io::stdout();
    enable_raw_mode()?;
    execute!(stdout, EnterAlternateScreen)?;
    Ok(Terminal::new(CrosstermBackend::new(stdout))?)
}

fn restore_terminal(
    terminal: &mut Terminal<CrosstermBackend<Stdout>>,
) -> Result<(), Box<dyn Error>> {
    disable_raw_mode()?;
    execute!(terminal.backend_mut(), LeaveAlternateScreen,)?;
    Ok(terminal.show_cursor()?)
}

fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<(), Box<dyn Error>> {
    let mut p = 20;
    Ok(loop {
        terminal.draw(|frame| {
            let g = Gauge::default()
                .block(
                    Block::default()
                        .title("Gauge Progress")
                        .borders(Borders::ALL),
                )
                .gauge_style(
                    Style::default()
                        .fg(Color::Gray)
                        .bg(Color::LightRed)
                        .add_modifier(Modifier::ITALIC),
                )
                .percent(p)
                .label("Gauge Bar 0x22489")
                .use_unicode(true);
            frame.render_widget(g, frame.size());
        })?;
        if event::poll(Duration::from_millis(250))? {
            if let Event::Key(key) = event::read()? {
                if KeyCode::Char('q') == key.code {
                    break;
                } else if KeyCode::Left == key.code {
                    p = (p - 1) % 100;
                } else if KeyCode::Right == key.code {
                    p = (p + 1) % 100;
                } else {
                }
            }
        }
    })
}

gauge展示效果

LineGauge

单行展示进度条。
常见方法:

  • block:进度条边框。
  • percent:设置进度条所占百分比。数字20表示占据进度条20%。
  • ratio:设置进度条所占比例,0.5表示占据进度条一半的位置。
  • line_set:设置线型宽度。
  • label:设置进度条label。
  • style:设置外观风格。
  • gauge_style:设置进度条风格。
use crossterm::terminal::{
    disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
};
use crossterm::{event, event::Event, event::KeyCode, execute};
use ratatui::backend::CrosstermBackend;
use ratatui::prelude::{symbols, Color, Constraint, Marker, Modifier, Span, Style};
use ratatui::widgets::{Block, Borders, LineGauge};
use ratatui::Terminal;
use std::error::Error;
use std::io::{self, Stdout};
use std::time::Duration;
fn main() -> Result<(), Box<dyn Error>> {
    let mut terminal = setup_terminal()?;
    run(&mut terminal)?;
    restore_terminal(&mut terminal)?;
    Ok(())
}

fn setup_terminal() -> Result<Terminal<CrosstermBackend<Stdout>>, Box<dyn Error>> {
    let mut stdout = io::stdout();
    enable_raw_mode()?;
    execute!(stdout, EnterAlternateScreen)?;
    Ok(Terminal::new(CrosstermBackend::new(stdout))?)
}

fn restore_terminal(
    terminal: &mut Terminal<CrosstermBackend<Stdout>>,
) -> Result<(), Box<dyn Error>> {
    disable_raw_mode()?;
    execute!(terminal.backend_mut(), LeaveAlternateScreen,)?;
    Ok(terminal.show_cursor()?)
}

fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<(), Box<dyn Error>> {
    let mut p = 0.4;
    Ok(loop {
        terminal.draw(|frame| {
            let g = LineGauge::default()
                .block(Block::default().borders(Borders::ALL).title("Progress"))
                .gauge_style(
                    Style::default()
                        .fg(Color::White)
                        .bg(Color::Black)
                        .add_modifier(Modifier::BOLD),
                )
                .line_set(symbols::line::THICK)
                .ratio(p);
            frame.render_widget(g, frame.size());
        })?;
        if event::poll(Duration::from_millis(250))? {
            if let Event::Key(key) = event::read()? {
                if KeyCode::Char('q') == key.code {
                    break;
                } else if (KeyCode::Left == key.code) {
                    p = (p - 0.1) % 1.;
                } else if (KeyCode::Right == key.code) {
                    p = (p + 0.1) % 1.;
                }
            }
        }
    })
}

Sparkline使用

在一行或者多行渲染走势图。常见方法:

  • block:组件边界框。
  • style:组件风格。
  • max:限制走势图的最大值。
  • bar_set:设置走势图的bar风格。
  • direction:设置走势图的方向。
use crossterm::terminal::{
    disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
};
use crossterm::{event, event::Event, event::KeyCode, execute, Command};
use ratatui::backend::CrosstermBackend;
use ratatui::prelude::{Color, Modifier, Style};
use ratatui::style::Stylize;
use ratatui::symbols::bar;
use ratatui::widgets::{Block, Borders, Gauge, Paragraph, RenderDirection, Sparkline};
use ratatui::Terminal;
use std::error::Error;
use std::io::{self, Stdout};
use std::time::Duration;
fn main() -> Result<(), Box<dyn Error>> {
    let mut terminal = setup_terminal()?;
    run(&mut terminal)?;
    restore_terminal(&mut terminal)?;
    Ok(())
}

fn setup_terminal() -> Result<Terminal<CrosstermBackend<Stdout>>, Box<dyn Error>> {
    let mut stdout = io::stdout();
    enable_raw_mode()?;
    execute!(stdout, EnterAlternateScreen)?;
    Ok(Terminal::new(CrosstermBackend::new(stdout))?)
}

fn restore_terminal(
    terminal: &mut Terminal<CrosstermBackend<Stdout>>,
) -> Result<(), Box<dyn Error>> {
    disable_raw_mode()?;
    execute!(terminal.backend_mut(), LeaveAlternateScreen,)?;
    Ok(terminal.show_cursor()?)
}

fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<(), Box<dyn Error>> {
    let mut p = 20;
    Ok(loop {
        terminal.draw(|frame| {
            let g = Sparkline::default()
                .block(
                    Block::default()
                        .title("Sparkline")
                        .borders(Borders::ALL)
                        .style(Style::default().fg(Color::Red).bg(Color::White)),
                ) //边界线为红色,背景色为白色
                .style(Style::default().fg(Color::Blue)) //走势图bar为蓝色
                .bg(Color::LightGreen) //背景为LightGreen
                .data(&[
                    1, 2, 3, 4, 5, 7, 2, 4, 3, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 8,
                ])
                .max(10) //限高为10
                .direction(RenderDirection::RightToLeft) //方向从右向左
                .bar_set(bar::NINE_LEVELS);
            frame.render_widget(g, frame.size());
        })?;
        if event::poll(Duration::from_millis(250))? {
            if let Event::Key(key) = event::read()? {
                if KeyCode::Char('q') == key.code {
                    break;
                } else if KeyCode::Left == key.code {
                    p = (p - 1) % 100;
                } else if KeyCode::Right == key.code {
                    p = (p + 1) % 100;
                } else {
                }
            }
        }
    })
}

输出风格:
sparkline

Clear

清除绘图区域的图像。 f.render_widget(block, area);

Canvas

使用点阵绘制更复杂的图,常见方法:

  • block:设置Convas的block边界。
  • x_bounds:设置x轴的范围。
  • y_bounds:设置y轴的范围。
  • paint:存储用于绘图的闭包。
  • backgrou_color:canvas的背景色。
  • marker:改变绘制图形的点形状。

常见的canvas组件

  • Canvas:绘图画布。
  • Map:地图。
  • Circle:圆形。
  • Context:保存绘图时的状态。
  • Label:在Convas上写上文字标签。
  • Line:根据给定的起点和终点绘制直线。
  • Pointers:根据给定的点绘制图像。
  • Rectangle:根据给定的点绘制矩形。
use crossterm::terminal::{
    disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
};
use crossterm::{event, event::Event, event::KeyCode, execute, Command};
use ratatui::backend::CrosstermBackend;
use ratatui::prelude::{Color, Modifier, Style};
use ratatui::symbols::Marker;
use ratatui::widgets::canvas::{self, Canvas, Line, MapResolution, Rectangle};
use ratatui::widgets::{Block, Borders};
use ratatui::Terminal;
use std::error::Error;
use std::io::{self, Stdout};
use std::time::Duration;
fn main() -> Result<(), Box<dyn Error>> {
    let mut terminal = setup_terminal()?;
    run(&mut terminal)?;
    restore_terminal(&mut terminal)?;
    Ok(())
}

fn setup_terminal() -> Result<Terminal<CrosstermBackend<Stdout>>, Box<dyn Error>> {
    let mut stdout = io::stdout();
    enable_raw_mode()?;
    execute!(stdout, EnterAlternateScreen)?;
    Ok(Terminal::new(CrosstermBackend::new(stdout))?)
}

fn restore_terminal(
    terminal: &mut Terminal<CrosstermBackend<Stdout>>,
) -> Result<(), Box<dyn Error>> {
    disable_raw_mode()?;
    execute!(terminal.backend_mut(), LeaveAlternateScreen,)?;
    Ok(terminal.show_cursor()?)
}

fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<(), Box<dyn Error>> {
    let mut p = 20;
    Ok(loop {
        terminal.draw(|frame| {
            let g = Canvas::default()
                .block(Block::default().title("Canvas").borders(Borders::ALL))
                .x_bounds([-180.0, 180.0])
                .y_bounds([-90.0, 90.0])
                .marker(Marker::Braille)
                .paint(|ctx| {
                    ctx.draw(&canvas::Map {
                        resolution: MapResolution::High,
                        color: Color::LightRed,
                    });
                    // ctx.layer()
                    ctx.draw(&Line {
                        x1: 0.0,
                        y1: 10.0,
                        x2: 10.0,
                        y2: 10.0,
                        color: Color::Blue,
                    });
                    ctx.draw(&canvas::Rectangle {
                        x: -10.,
                        y: -10.,
                        width: 20.0,
                        height: 20.0,
                        color: Color::Green,
                    });
                });
            frame.render_widget(g, frame.size());
        })?;
        if event::poll(Duration::from_millis(250))? {
            if let Event::Key(key) = event::read()? {
                if KeyCode::Char('q') == key.code {
                    break;
                } else if KeyCode::Left == key.code {
                    p = (p - 1) % 100;
                } else if KeyCode::Right == key.code {
                    p = (p + 1) % 100;
                } else {
                }
            }
        }
    })
}

绘制地图


   转载规则


《Rust三方库(ratatui)》 bleedingfight 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录