高手過招不用鼠標(biāo),一款超好用的跨平臺命令行界面庫

作者:HelloGitHub-Anthony
命令行工具是程序員的秘密武器,它們安裝簡單、啟動速度快、界面簡潔,一條指令或者快捷鍵即可完成操作,用完即走深藏不露。

而最趁手的莫過于自己親手打造的!本期 《講解開源項目》 就介紹一個讓你快速擁有完美命令行界面的跨平臺庫—— tui.rs

項目地址:https://github.com/fdehau/tui-rs
官方文檔:https://docs.rs/tui/latest/tui/index.html
你一定有過這樣的糾結(jié):我的程序需要一個界面,但使用諸如 Qt 等框架又比較繁瑣?,F(xiàn)在 tui.rs 來了,它是 Rust 下的命令行 UI 庫,不僅上手方便內(nèi)置多種組件,而且效果炫酷支持跨平臺使用。

輕松實現(xiàn)一份代碼可以無縫運(yùn)行在 Linux/Windows/Mac 之上!
接下來你不僅可以快速上手 tui.rs,還會收獲多款基于它構(gòu)建的神兵利器!
一、安裝
tui.rs 采用 Rust 語言編寫,和所有其他 Rust 依賴的安裝方法一樣,直接在 cargo.toml 中添加依賴即可:
[dependencies]
tui = "0.17"
crossterm = "0.22"
如果需要官方示例,則直接 clone 官方倉庫:
$?git?clone?http://github.com/fdehau/tui-rs.git
$?cd?tui-rs
$?cargo?run?--example?demo
二、快速入門
2.1 一覽芳容
我們主要使用 tui.rs 提供的以下模塊進(jìn)行 UI 編寫(所有 UI 元素都實現(xiàn)了 Widget 或 StatefuWidget Trait):
bakend?用于生成管理命令行的后端layout用于管理 UI 組件的布局style用于為 UI 添加樣式symbols描述繪制散點(diǎn)圖時所用點(diǎn)的樣式text用于描述帶樣式的文本widgets包含預(yù)定義的 UI 組件
如下代碼就可以實現(xiàn)一個很簡單的 tui 界面:
use?crossterm::{
????event::{self,?DisableMouseCapture,?EnableMouseCapture,?Event,?KeyCode},
????execute,
????terminal::{disable_raw_mode,?enable_raw_mode,?EnterAlternateScreen,?LeaveAlternateScreen},
};
use?std::{io,?time::Duration};
use?tui::{
????backend::{Backend,?CrosstermBackend},
????layout::{Alignment,?Constraint,?Direction,?Layout},
????style::{Color,?Modifier,?Style},
????text::{Span,?Spans,?Text},
????widgets::{Block,?Borders,?Paragraph,?Widget},
????Frame,?Terminal,
};
struct?App?{
????url:?String,?//?存放一些數(shù)據(jù)或者?UI?狀態(tài)
}
fn?main()?->?Result<(),?io::Error>?{
????//?初始化終端
????enable_raw_mode()?;
????let?mut?stdout?=?io::stdout();
????execute!(stdout,?EnterAlternateScreen,?EnableMouseCapture)?;
????let?backend?=?CrosstermBackend::new(stdout);
????let?mut?terminal?=?Terminal::new(backend)?;
????let?mut?app?=?App?{
????????url:?String::from(r"https://hellogithub.com/"),
????};
????//?渲染界面
????run_app(&mut?terminal,?app)?;
????//?恢復(fù)終端
????disable_raw_mode()?;
????execute!(
????????terminal.backend_mut(),
????????LeaveAlternateScreen,
????????DisableMouseCapture
????)?;
????terminal.show_cursor()?;
????Ok(())
}
fn?run_app(terminal:?&mut?Terminal,?mut?app:?App)?->?io::Result<()>?{
????loop?{
????????terminal.draw(|f|?ui(f,?&mut?app))?;
????????//?處理按鍵事件
????????if?crossterm::event::poll(Duration::from_secs(1))??{
????????????if?let?Event::Key(key)?=?event::read()??{
????????????????match?key.code?{
????????????????????KeyCode::Char(ch)?=>?{
????????????????????????if?'q'?==?ch?{
????????????????????????????break;
????????????????????????}
????????????????????}
????????????????????_?=>?{}
????????????????}
????????????}
????????}
????????//?處理其他邏輯
????}
????Ok(())
}
fn?ui(f:?&mut?Frame,?app:?&mut?App)?{
????//
????let?chunks?=?Layout::default()?//?首先獲取默認(rèn)構(gòu)造
????????.constraints([Constraint::Length(3),?Constraint::Min(3)].as_ref())?//?按照?3?行?和?最小?3?行的規(guī)則分割區(qū)域
????????.direction(Direction::Vertical)?//?垂直分割
????????.split(f.size());?//?分割整塊?Terminal?區(qū)域
????let?paragraph?=?Paragraph::new(Span::styled(
????????app.url.as_str(),
????????Style::default().add_modifier(Modifier::BOLD),
????))
????.block(Block::default().borders(Borders::ALL).title("HelloGitHub"))
????.alignment(tui::layout::Alignment::Left);
????f.render_widget(paragraph,?chunks[0]);
????let?paragraph?=?Paragraph::new("分享?GitHub?上有趣、入門級的開源項目")
????????.style(Style::default().bg(Color::White).fg(Color::Black))
????????.block(Block::default().borders(Borders::ALL).title("宗旨"))
????????.alignment(Alignment::Center);
????f.render_widget(paragraph,?chunks[1]);
}

這些代碼可能看起來不少,但大部分都是固定的模板,不需要我們每次的重新構(gòu)思。下面,就讓我們來詳細(xì)了解其中的細(xì)節(jié)。
2.2 創(chuàng)作模板
官方通過 example 給出了使用 tui.rs 進(jìn)行設(shè)計的模板,我希望各位讀者在使用時也能遵守這套模板以保證程序的可讀性。
一個使用 tui.rs 程序的一生大概是這樣的:

其模塊可以大致分為:
app.rs實現(xiàn) App 結(jié)構(gòu)體,用于處理 UI 邏輯,保存 UI 狀態(tài)ui.rs? 實現(xiàn) UI 渲染功能
但對于小型程序來講,也可以都寫在 main.rs 之中。
首先來看開始和結(jié)束部分關(guān)于 Terminal 的操作,每次運(yùn)行都會保存原始 Terminal 界面內(nèi)容并在一個新的窗體上運(yùn)行,在結(jié)束后又會恢復(fù)到原來的 Terminal 窗體中,有效地防止了搞亂原來的窗口內(nèi)容。這部分代碼模板官方已經(jīng)給出,基本無需修改:
fn?main()?->?Result<(),?io::Error>?{
????//?配置?Terminal
????enable_raw_mode()?;?//?啟動命令行的?raw?模式
????let?mut?stdout?=?io::stdout();
????execute!(stdout,?EnterAlternateScreen,?EnableMouseCapture)?;?//?在一個新的界面上運(yùn)行?UI,保存原終端內(nèi)容,并開啟鼠標(biāo)捕獲
????let?backend?=?CrosstermBackend::new(stdout);
????let?mut?terminal?=?Terminal::new(backend)?;
????//?初始化?app?資源
????let?mut?app?=?App?{
????????url:?String::from(r"https://hellogithub.com/"),
????};
??//?程序主要邏輯循環(huán)?……?//
????run_app(&mut?terminal,?app)?;
????//?恢復(fù)?Terminal
????disable_raw_mode()?;?//?禁用?raw?模式
????execute!(
????????terminal.backend_mut(),
????????LeaveAlternateScreen,?//?恢復(fù)到原來的命令行窗口
????????DisableMouseCapture??//?禁用鼠標(biāo)捕獲
????)?;
????terminal.show_cursor()?;?//?顯示光標(biāo)
????Ok(())
}
接下來是處理 UI 邏輯的 run_app 函數(shù),我們在此處理諸如 用戶按鍵、UI 狀態(tài)更改等邏輯
fn?run_app(terminal:?&mut?Terminal,?mut?app:?App)?->?io::Result<()>?{
????loop?{
????????//?渲染?UI
????????terminal.draw(|f|?ui(f,?&mut?app))?;
????????//?處理按鍵事件
????????if?crossterm::event::poll(Duration::from_secs(1))??{?//?poll?方法非阻塞輪詢
????????????if?let?Event::Key(key)?=?event::read()??{?//?直接?read?如果沒有事件到來則會阻塞等待
????????????????match?key.code?{?//?判斷用戶按鍵
????????????????????KeyCode::Char(ch)?=>?{
????????????????????????if?'q'?==?ch?{
????????????????????????????break;
????????????????????????}
????????????????????}
????????????????????_?=>?{}
????????????????}
????????????}
????????}
????????//?處理其他邏輯
????}
????Ok(())
}
對于功能簡單的界面來講,這個函數(shù)作用不大。但如果我們的程序需要更新一些組件狀態(tài)(比如列表選中項、用戶輸入、外界數(shù)據(jù)交互等)則應(yīng)在此統(tǒng)一處理。
之后,我們會使用 terminal.draw() 方法繪制界面,其接受一個閉包:
fn?ui(f:?&mut?Frame,?app:?&mut?App)?{
????//?獲取分割后的窗口
????let?chunks?=?Layout::default()?//?首先獲取默認(rèn)構(gòu)造
????????.constraints([Constraint::Length(3),?Constraint::Min(3)].as_ref())?//?按照?3?行?和?最小?3?行的規(guī)則分割區(qū)域
????????.direction(Direction::Vertical)?//?垂直方向分割
????????.split(f.size());?//?分割整塊?Terminal?區(qū)域
????let?paragraph?=?Paragraph::new(Span::styled(
????????app.url.as_str(),
????????Style::default().add_modifier(Modifier::BOLD),
????))
????.block(Block::default().borders(Borders::ALL).title("HelloGitHub"))
????.alignment(tui::layout::Alignment::Left);
????f.render_widget(paragraph,?chunks[0]);
????let?paragraph?=?Paragraph::new("分享?GitHub?上有趣、入門級的開源項目")
????????.style(Style::default().bg(Color::White).fg(Color::Black))
????????.block(Block::default().borders(Borders::ALL).title("宗旨"))
????????.alignment(Alignment::Center);
????f.render_widget(paragraph,?chunks[1]);
}
在這里,有如下流程:
- 使用
Layout按照需求給定Constraint切分窗體,獲取 chunks,每個 chunk 也可以利用Layout繼續(xù)進(jìn)行分割 - 實例化組件,每個組件都實現(xiàn)了
default方法,在使用時我們應(yīng)該先使用xxx::default()獲取默認(rèn)對象,再利用默認(rèn)對象更新組件樣式。例如Block::default().borders(Borders::ALL)、Style::default().bg(Color::White)等。這也是官方推薦做法。 - 使用
f.render_widget渲染組件到窗體上,對于類似 列表 等存在狀態(tài)(比如當(dāng)前選中元素)的組件,則使用f.render_stateful_widget進(jìn)行渲染
關(guān)于 tui.rs 其他內(nèi)置組件的使用方法,可以查看官方的 example 文件,編寫套路是一樣的,可以根據(jù)需要直接復(fù)制粘貼。
需要注意到是,在此我們只關(guān)心 UI 組件的顯示方式和內(nèi)容,有關(guān)程序邏輯的內(nèi)容應(yīng)放在 run_app 中處理以免打亂程序架構(gòu)或影響 UI 繪制效果(你總不希望 UI 繪制到一半的時候因為進(jìn)行了某些 IO 操作而卡住了對吧?)
到這里對于 tui.rs 的介紹就結(jié)束了,實際上使用 tui.rs 編寫 UI 界面很簡單,只要根據(jù)創(chuàng)作模板結(jié)合官方例子一步步構(gòu)建,任何人都可以很快上手。
三、更多實用工具
下面將介紹介紹幾款基于 tui.rs 構(gòu)建的流行開源項目,它們無一例外是命令行工具里的“神兵利器“!
3.1 實時股票數(shù)據(jù)
支持查看不同時間維度以及交易量等數(shù)據(jù),股票實時數(shù)據(jù)來自雅虎。

地址:https://github.com/tarkah/tickrs
3.2 文件傳輸工具
支持 SCP/SFTP/FTP/S3 功能豐富的終端文件傳輸工具。

地址:https://github.com/veeso/termscp
3.3 網(wǎng)絡(luò)監(jiān)控工具
用于按進(jìn)程、連接、遠(yuǎn)程 IP、主機(jī)名顯示當(dāng)前網(wǎng)絡(luò)利用率。

地址:https://github.com/imsnif/bandwhich
限于篇幅這里就不介紹其它開源項目了,感興趣的小伙伴可以去項目首頁尋找。
四、最后
以上就是本文的所有內(nèi)容,希望您從中有所收獲。
最后,感謝您的閱讀?。?!
這里是 HelloGitHub 分享 GitHub 上有趣、入門級的開源項目。您的每個點(diǎn)贊、留言、分享都是對我們最大的鼓勵!
?? 關(guān)注「HelloGitHub」第一時間收到更新??
