命令列工具是程式設計師的祕密武器,它們安裝簡單、啟動速度快、介面簡潔,一條指令或者快捷鍵即可完成操作,用完即走深藏不露。
而最趁手的莫過於自己親手打造的!本期 《講解開源專案》 就介紹一個讓你快速擁有完美命令列介面的跨平臺庫—— tui.rs
你一定有過這樣的糾結:我的程式需要一個介面,但使用諸如 Qt 等框架又比較繁瑣。現在 tui.rs 來了,它是 Rust 下的命令列 UI 庫,不僅上手方便內建多種元件,而且效果炫酷支援跨平臺使用。
輕鬆實現一份程式碼可以無縫執行在 Linux/Windows/Mac 之上!
接下來你不僅可以快速上手 tui.rs,還會收穫多款基於它構建的神兵利器!
一、安裝
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
提供的以下模組進行 UI 編寫(所有 UI 元素都實現了 Widget
或 StatefuWidget
Trait):
bakend
用於生成管理命令列的後端layout
用於管理 UI 元件的佈局style
用於為 UI 新增樣式symbols
描述繪製散點圖時所用點的樣式text
用於描述帶樣式的文字widgets
包含預定義的 UI 元件
如下程式碼就可以實現一個很簡單的 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, // 存放一些資料或者 UI 狀態
}
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)?;
// 恢復終端
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
Ok(())
}
fn run_app<B: Backend>(terminal: &mut Terminal<B>, 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<B: Backend>(f: &mut Frame<B>, app: &mut App) {
//
let chunks = Layout::default() // 首先獲取預設構造
.constraints([Constraint::Length(3), Constraint::Min(3)].as_ref()) // 按照 3 行 和 最小 3 行的規則分割區域
.direction(Direction::Vertical) // 垂直分割
.split(f.size()); // 分割整塊 Terminal 區域
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]);
}
這些程式碼可能看起來不少,但大部分都是固定的模板,不需要我們每次的重新構思。下面,就讓我們來詳細瞭解其中的細節。
2.2 創作模板
官方通過 example 給出了使用 tui.rs
進行設計的模板,我希望各位讀者在使用時也能遵守這套模板以保證程式的可讀性。
一個使用 tui.rs
程式的一生大概是這樣的:
其模組可以大致分為:
app.rs
實現 App 結構體,用於處理 UI 邏輯,儲存 UI 狀態ui.rs
實現 UI 渲染功能
但對於小型程式來講,也可以都寫在 main.rs
之中。
首先來看開始和結束部分關於 Terminal 的操作,每次執行都會儲存原始 Terminal 介面內容並在一個新的窗體上執行,在結束後又會恢復到原來的 Terminal 窗體中,有效地防止了搞亂原來的視窗內容。這部分程式碼模板官方已經給出,基本無需修改:
fn main() -> Result<(), io::Error> {
// 配置 Terminal
enable_raw_mode()?; // 啟動命令列的 raw 模式
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; // 在一個新的介面上執行 UI,儲存原終端內容,並開啟滑鼠捕獲
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
// 初始化 app 資源
let mut app = App {
url: String::from(r"https://hellogithub.com/"),
};
// 程式主要邏輯迴圈 …… //
run_app(&mut terminal, app)?;
// 恢復 Terminal
disable_raw_mode()?; // 禁用 raw 模式
execute!(
terminal.backend_mut(),
LeaveAlternateScreen, // 恢復到原來的命令列視窗
DisableMouseCapture // 禁用滑鼠捕獲
)?;
terminal.show_cursor()?; // 顯示游標
Ok(())
}
接下來是處理 UI 邏輯的 run_app
函式,我們在此處理諸如 使用者按鍵、UI 狀態更改等邏輯
fn run_app<B: Backend>(terminal: &mut Terminal<B>, 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(())
}
對於功能簡單的介面來講,這個函式作用不大。但如果我們的程式需要更新一些元件狀態(比如列表選中項、使用者輸入、外界資料互動等)則應在此統一處理。
之後,我們會使用 terminal.draw()
方法繪製介面,其接受一個閉包:
fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
// 獲取分割後的視窗
let chunks = Layout::default() // 首先獲取預設構造
.constraints([Constraint::Length(3), Constraint::Min(3)].as_ref()) // 按照 3 行 和 最小 3 行的規則分割區域
.direction(Direction::Vertical) // 垂直方向分割
.split(f.size()); // 分割整塊 Terminal 區域
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
繼續進行分割 - 例項化元件,每個元件都實現了
default
方法,在使用時我們應該先使用xxx::default()
獲取預設物件,再利用預設物件更新元件樣式。例如Block::default().borders(Borders::ALL)
、Style::default().bg(Color::White)
等。這也是官方推薦做法。 - 使用
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 實時股票資料
支援檢視不同時間維度以及交易量等資料,股票實時資料來自雅虎。
3.2 檔案傳輸工具
支援 SCP/SFTP/FTP/S3 功能豐富的終端檔案傳輸工具。
3.3 網路監控工具
用於按程式、連線、遠端 IP、主機名顯示當前網路利用率。
限於篇幅這裡就不介紹其它開源專案了,感興趣的小夥伴可以去專案首頁尋找。
四、最後
以上就是本文的所有內容,希望您從中有所收穫。
最後,感謝您的閱讀!!!
這裡是 HelloGitHub 分享 GitHub 上有趣、入門級的開源專案。您的每個點贊、留言、分享都是對我們最大的鼓勵!