TUI库
开始TUI库之前我们先介绍下常用的跨平台终端操作库crossterm。其中常见的模块有四个:
- cursor:为终端中的光标提供各种功能。
- event:给键盘、鼠标、resize终端提供事件,
- style:给输入提供颜色等等属性。
- 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:代表一个事件。
FocusGainedFocusLostKey(KeyEvent)Mouse(MouseEvent)Paste(String)- Resize(u16, u16)
- KeyCode:按键代码
BackspaceEnterLeftRightUpDownHomeEndPageUpPageDownTabBackTabDeleteInsertF(u8)Char(char)NullEscCapsLockScrollLockNumLockPrintScreenPauseMenuKeypadBeginMedia(MediaKeyCode)Modifier(ModifierKeyCode):案件修饰符,在一些平台(如mac),并不会报告所有的鼠标事件,ctrl+鼠标左键点击为鼠标右键点击。
KeyEventKind:按键事件类型。Press:按键按下Repeat:案件重复Replace:案件替换。MediaKeyCode:代表媒体key。(as part of KeyCode::Media).
ModifierKeyCode:代表修饰符key。(as part of KeyCode::Modifier).PlayPausePlayPauseReverseStopFastForwardRewindTrackNextTrackPreviousRecordLowerVolumeRaiseVolumeMuteVolume
MouseButton:代表鼠标按键LeftRightMiddle
MouseEventKind:鼠标事件类型Down(MouseButton)Up(MouseButton)Drag(MouseButton)MovedScrollDownScrollUpScrollLeftScrollRigh
style
提供对文本应用属性和颜色的功能。常见结构体:
Attibutes:所有个能属性的bit集合。-Colors:用来表示前景和背景颜色。-ContentStyle:作用在内容上的风格。-Print:打印给定的可显示类型的命令。-PrintStyleContent:打印风格化内容的命令。-ResetColor:reset颜色为默认。-SetAttribute:设置属性集合。-SetAttributes:设置多属性集合。-SetBackgroundColor:设置背景颜色集合。-SetForegroundColor:设置外围颜色。-SetStyle:设置风格。-SetUnderlineColor:设置下划线颜色。
枚举:Attribute:代表属性。Color:代表颜色。Colored:代表前景或者背景颜色。ForegroundColor、BackgroundColor、UnderlineColor。
属性: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:使用指定的宽度、高度、方向切分区域。

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;
}
}
}
})
}

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);

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);

Dataset
Dataset组件,数据点集合。常见方法:
name:数据点的名称。data:数据点数据。marker:数据点类型。常见选项为:Marker::Braille、Marker::Dot、``。graph_type:图类型。常见选项为:GraphType::Line、GraphType::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);

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 {
}
}
}
})
}

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 {
}
}
}
})
}
输出风格:
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 {
}
}
}
})
}
