高手过招不用鼠标,一款超好用的跨平台命令行界面库

本文介绍了tui.rs库,一个用于创建命令行界面的Rust库,支持跨平台且易于上手。通过示例展示了如何安装、快速入门及构建UI组件,同时推荐了几款基于tui.rs的实用开源工具,如实时股票查看器、文件传输工具和网络监控工具。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

18d552eda78569544175bd47cdeea25a.png

作者:HelloGitHub-Anthony

命令行工具是程序员的秘密武器,它们安装简单、启动速度快、界面简洁,一条指令或者快捷键即可完成操作,用完即走深藏不露。

e47e26a166c89f416bf6a3c0fee2484b.png

而最趁手的莫过于自己亲手打造的!本期 《讲解开源项目》 就介绍一个让你快速拥有完美命令行界面的跨平台库—— tui.rs

677476c8c17ede425c9a837a6a0d06f8.gif

项目地址:https://github.com/fdehau/tui-rs

官方文档:https://docs.rs/tui/latest/tui/index.html

你一定有过这样的纠结:我的程序需要一个界面,但使用诸如 Qt 等框架又比较繁琐。现在 tui.rs 来了,它是 Rust 下的命令行 UI 库,不仅上手方便内置多种组件,而且效果炫酷支持跨平台使用。

eeff39de9fe7d1bde7995e446d34e79b.gif

轻松实现一份代码可以无缝运行在 Linux/Windows/Mac 之上!

接下来你不仅可以快速上手 tui.rs,还会收获多款基于它构建的神兵利器!

一、安装

tui.rs 采用 Rust 语言编写,和所有其他 Rust 依赖的安装方法一样,直接在 cargo.toml 中添加依赖即可:

  1. [dependencies]
  2. tui = "0.17"
  3. crossterm = "0.22"

如果需要官方示例,则直接 clone 官方仓库:

  1. $ git clone http://github.com/fdehau/tui-rs.git
  2. $ cd tui-rs
  3. $ cargo run --example demo

二、快速入门

2.1 一览芳容

我们主要使用 tui.rs 提供的以下模块进行 UI 编写(所有 UI 元素都实现了 WidgetStatefuWidget Trait):

  • bakend  用于生成管理命令行的后端

  • layout 用于管理 UI 组件的布局

  • style 用于为 UI 添加样式

  • symbols 描述绘制散点图时所用点的样式

  • text 用于描述带样式的文本

  • widgets 包含预定义的 UI 组件

如下代码就可以实现一个很简单的 tui 界面:

  1. use crossterm::{
  2.     event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
  3.     execute,
  4.     terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
  5. };
  6. use std::{io, time::Duration};
  7. use tui::{
  8.     backend::{Backend, CrosstermBackend},
  9.     layout::{Alignment, Constraint, Direction, Layout},
  10.     style::{Color, Modifier, Style},
  11.     text::{Span, Spans, Text},
  12.     widgets::{Block, Borders, Paragraph, Widget},
  13.     Frame, Terminal,
  14. };
  15. struct App {
  16.     url: String, // 存放一些数据或者 UI 状态
  17. }
  18. fn main() -> Result<(), io::Error> {
  19.     // 初始化终端
  20.     enable_raw_mode()?;
  21.     let mut stdout = io::stdout();
  22.     execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
  23.     let backend = CrosstermBackend::new(stdout);
  24.     let mut terminal = Terminal::new(backend)?;
  25.     let mut app = App {
  26.         url: String::from(r"https://hellogithub.com/"),
  27.     };
  28.     // 渲染界面
  29.     run_app(&mut terminal, app)?;
  30.     // 恢复终端
  31.     disable_raw_mode()?;
  32.     execute!(
  33.         terminal.backend_mut(),
  34.         LeaveAlternateScreen,
  35.         DisableMouseCapture
  36.     )?;
  37.     terminal.show_cursor()?;
  38.     Ok(())
  39. }
  40. fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<()> {
  41.     loop {
  42.         terminal.draw(|f| ui(f, &mut app))?;
  43.         // 处理按键事件
  44.         if crossterm::event::poll(Duration::from_secs(1))? {
  45.             if let Event::Key(key) = event::read()? {
  46.                 match key.code {
  47.                     KeyCode::Char(ch) => {
  48.                         if 'q' == ch {
  49.                             break;
  50.                         }
  51.                     }
  52.                     _ => {}
  53.                 }
  54.             }
  55.         }
  56.         // 处理其他逻辑
  57.     }
  58.     Ok(())
  59. }
  60. fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
  61.     //
  62.     let chunks = Layout::default() // 首先获取默认构造
  63.         .constraints([Constraint::Length(3), Constraint::Min(3)].as_ref()) // 按照 3 行 和 最小 3 行的规则分割区域
  64.         .direction(Direction::Vertical) // 垂直分割
  65.         .split(f.size()); // 分割整块 Terminal 区域
  66.     let paragraph = Paragraph::new(Span::styled(
  67.         app.url.as_str(),
  68.         Style::default().add_modifier(Modifier::BOLD),
  69.     ))
  70.     .block(Block::default().borders(Borders::ALL).title("HelloGitHub"))
  71.     .alignment(tui::layout::Alignment::Left);
  72.     f.render_widget(paragraph, chunks[0]);
  73.     let paragraph = Paragraph::new("分享 GitHub 上有趣、入门级的开源项目")
  74.         .style(Style::default().bg(Color::White).fg(Color::Black))
  75.         .block(Block::default().borders(Borders::ALL).title("宗旨"))
  76.         .alignment(Alignment::Center);
  77.     f.render_widget(paragraph, chunks[1]);
  78. }
5d210892217d1c428050ba641cffd831.png

这些代码可能看起来不少,但大部分都是固定的模板,不需要我们每次的重新构思。下面,就让我们来详细了解其中的细节。

2.2 创作模板

官方通过 example 给出了使用 tui.rs 进行设计的模板,我希望各位读者在使用时也能遵守这套模板以保证程序的可读性。

一个使用 tui.rs 程序的一生大概是这样的:

35dda131f08492bbea3e94874e2f74ad.png

其模块可以大致分为:

  • app.rs 实现 App 结构体,用于处理 UI 逻辑,保存 UI 状态

  • ui.rs   实现 UI 渲染功能

但对于小型程序来讲,也可以都写在 main.rs 之中。

首先来看开始和结束部分关于 Terminal 的操作,每次运行都会保存原始 Terminal 界面内容并在一个新的窗体上运行,在结束后又会恢复到原来的 Terminal 窗体中,有效地防止了搞乱原来的窗口内容。这部分代码模板官方已经给出,基本无需修改

  1. fn main() -> Result<(), io::Error> {
  2.     // 配置 Terminal
  3.     enable_raw_mode()?; // 启动命令行的 raw 模式
  4.     let mut stdout = io::stdout();
  5.     execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; // 在一个新的界面上运行 UI,保存原终端内容,并开启鼠标捕获
  6.     let backend = CrosstermBackend::new(stdout);
  7.     let mut terminal = Terminal::new(backend)?;
  8.     // 初始化 app 资源
  9.     let mut app = App {
  10.         url: String::from(r"https://hellogithub.com/"),
  11.     };
  12.   // 程序主要逻辑循环 …… //
  13.     run_app(&mut terminal, app)?;
  14.     // 恢复 Terminal
  15.     disable_raw_mode()?; // 禁用 raw 模式
  16.     execute!(
  17.         terminal.backend_mut(),
  18.         LeaveAlternateScreen, // 恢复到原来的命令行窗口
  19.         DisableMouseCapture  // 禁用鼠标捕获
  20.     )?;
  21.     terminal.show_cursor()?; // 显示光标
  22.     Ok(())
  23. }

接下来是处理 UI 逻辑的 run_app 函数,我们在此处理诸如 用户按键、UI 状态更改等逻辑

  1. fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<()> {
  2.     loop {
  3.         // 渲染 UI
  4.         terminal.draw(|f| ui(f, &mut app))?;
  5.         // 处理按键事件
  6.         if crossterm::event::poll(Duration::from_secs(1))? { // poll 方法非阻塞轮询
  7.             if let Event::Key(key) = event::read()? { // 直接 read 如果没有事件到来则会阻塞等待
  8.                 match key.code { // 判断用户按键
  9.                     KeyCode::Char(ch) => {
  10.                         if 'q' == ch {
  11.                             break;
  12.                         }
  13.                     }
  14.                     _ => {}
  15.                 }
  16.             }
  17.         }
  18.         // 处理其他逻辑
  19.     }
  20.     Ok(())
  21. }

对于功能简单的界面来讲,这个函数作用不大。但如果我们的程序需要更新一些组件状态(比如列表选中项、用户输入、外界数据交互等)则应在此统一处理。

之后,我们会使用 terminal.draw() 方法绘制界面,其接受一个闭包:

  1. fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
  2.     // 获取分割后的窗口
  3.     let chunks = Layout::default() // 首先获取默认构造
  4.         .constraints([Constraint::Length(3), Constraint::Min(3)].as_ref()) // 按照 3 行 和 最小 3 行的规则分割区域
  5.         .direction(Direction::Vertical) // 垂直方向分割
  6.         .split(f.size()); // 分割整块 Terminal 区域
  7.     let paragraph = Paragraph::new(Span::styled(
  8.         app.url.as_str(),
  9.         Style::default().add_modifier(Modifier::BOLD),
  10.     ))
  11.     .block(Block::default().borders(Borders::ALL).title("HelloGitHub"))
  12.     .alignment(tui::layout::Alignment::Left);
  13.     f.render_widget(paragraph, chunks[0]);
  14.     let paragraph = Paragraph::new("分享 GitHub 上有趣、入门级的开源项目")
  15.         .style(Style::default().bg(Color::White).fg(Color::Black))
  16.         .block(Block::default().borders(Borders::ALL).title("宗旨"))
  17.         .alignment(Alignment::Center);
  18.     f.render_widget(paragraph, chunks[1]);
  19. }

在这里,有如下流程:

  1. 使用 Layout 按照需求给定 Constraint 切分窗体,获取 chunks,每个 chunk 也可以利用 Layout 继续进行分割

  2. 实例化组件,每个组件都实现了 default 方法,在使用时我们应该先使用 xxx::default() 获取默认对象,再利用默认对象更新组件样式。例如 Block::default().borders(Borders::ALL)Style::default().bg(Color::White) 等。这也是官方推荐做法。

  3. 使用 f.render_widget 渲染组件到窗体上,对于类似 列表 等存在状态(比如当前选中元素)的组件,则使用 f.render_stateful_widget 进行渲染

关于 tui.rs 其他内置组件的使用方法,可以查看官方的 example 文件,编写套路是一样的,可以根据需要直接复制粘贴

需要注意到是,在此我们只关心 UI 组件的显示方式和内容,有关程序逻辑的内容应放在 run_app 中处理以免打乱程序架构或影响 UI 绘制效果(你总不希望 UI 绘制到一半的时候因为进行了某些 IO 操作而卡住了对吧?)

到这里对于 tui.rs 的介绍就结束了,实际上使用 tui.rs 编写 UI 界面很简单,只要根据创作模板结合官方例子一步步构建,任何人都可以很快上手。

三、更多实用工具

下面将介绍介绍几款基于 tui.rs 构建的流行开源项目,它们无一例外是命令行工具里的“神兵利器“!

3.1 实时股票数据

支持查看不同时间维度以及交易量等数据,股票实时数据来自雅虎。

fb889987bd49356deef71e6f3fa81652.png

地址:https://github.com/tarkah/tickrs

3.2 文件传输工具

支持 SCP/SFTP/FTP/S3 功能丰富的终端文件传输工具。

dc4a5c8e0b56b1ae7c2679b8fca489c8.gif

地址:https://github.com/veeso/termscp

3.3 网络监控工具

用于按进程、连接、远程 IP、主机名显示当前网络利用率。

e5703fa442cd5c4d8a8164035f26aa51.png

地址:https://github.com/imsnif/bandwhich

限于篇幅这里就不介绍其它开源项目了,感兴趣的小伙伴可以去项目首页寻找。

四、最后

以上就是本文的所有内容,希望您从中有所收获。

最后,感谢您的阅读!!!

这里是 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。您的每个点赞、留言、分享都是对我们最大的鼓励!

👆 关注「HelloGitHub」第一时间收到更新👆

登录后您可以享受以下权益:

×
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

举报

选择你想要举报的内容(必选)
  • 内容涉黄
  • 政治相关
  • 内容抄袭
  • 涉嫌广告
  • 内容侵权
  • 侮辱谩骂
  • 样式问题
  • 其他
点击体验
DeepSeekR1满血版
程序员都在用的中文IT技术交流社区

程序员都在用的中文IT技术交流社区

专业的中文 IT 技术社区,与千万技术人共成长

专业的中文 IT 技术社区,与千万技术人共成长

关注【CSDN】视频号,行业资讯、技术分享精彩不断,直播好礼送不停!

关注【CSDN】视频号,行业资讯、技术分享精彩不断,直播好礼送不停!

客服 返回顶部