用Rust進(jìn)行TUI編程:Cursive庫(kù)
在本文中,我們將探索使用Rust進(jìn)行文本用戶(hù)界面(TUI)編程。TUI提供了一種通用的方法來(lái)創(chuàng)建具有豐富圖形用戶(hù)界面的交互式命令行應(yīng)用程序。我們將使用Cursive庫(kù),一個(gè)流行的用于構(gòu)建TUI應(yīng)用程序的Rust庫(kù)。
Cursive使用聲明式UI:用戶(hù)定義布局,然后Cursive處理事件循環(huán)。Cursive還處理大多數(shù)輸入(包括鼠標(biāo)點(diǎn)擊),并將事件轉(zhuǎn)發(fā)到當(dāng)前聚焦的視圖。用戶(hù)代碼更關(guān)注“事件”,而不是鍵盤(pán)輸入。
它非常適合更復(fù)雜的應(yīng)用程序,具有嵌套的視圖樹(shù)、菜單和彈出窗口。
圖片
創(chuàng)建項(xiàng)目
使用以下命令創(chuàng)建一個(gè)Rust新項(xiàng)目:
cargo new cursive_example
然后,將Cursive添加到Cargo.toml文件中:
[dependencies]
cursive = "0.20.0"
Cursive應(yīng)用程序的基本結(jié)構(gòu)
一個(gè)典型的Cursive應(yīng)用程序主要包括三個(gè)階段:
1,創(chuàng)建一個(gè)Cursive對(duì)象:我們從創(chuàng)建一個(gè)Cursive對(duì)象開(kāi)始。cursive::default()方法可以幫助我們完成這項(xiàng)任務(wù)。
2,配置Cursive對(duì)象:在創(chuàng)建了Cursive對(duì)象之后,我們根據(jù)應(yīng)用程序的需要對(duì)它進(jìn)行配置。
3,執(zhí)行Cursive Object:最后,我們運(yùn)行Cursive對(duì)象來(lái)啟動(dòng)應(yīng)用程序。
下面是一個(gè)最簡(jiǎn)單的Cursive應(yīng)用:
fn main() {
// 創(chuàng)建一個(gè)Cursive對(duì)象
let mut siv = cursive::default();
// 執(zhí)行Cursive對(duì)象
siv.run();
}
運(yùn)行這個(gè)程序,你會(huì)看到一個(gè)空白的應(yīng)用程序窗口。
圖片
增加退出應(yīng)用程序的方式
Cursive將用戶(hù)輸入作為事件處理,默認(rèn)情況下,許多事件被忽略。為了允許用戶(hù)通過(guò)按' q '退出應(yīng)用程序,我們可以在根Cursive對(duì)象上使用add_global_callback方法:
siv.add_global_callback('q', |s| s.quit());
此代碼片段添加了一個(gè)全局回調(diào),該回調(diào)監(jiān)聽(tīng)' q '鍵并在觸發(fā)時(shí)退出應(yīng)用程序。
Cursive視圖
視圖是Cursive應(yīng)用程序中用戶(hù)界面的核心構(gòu)建塊,它們定義在終端上顯示的內(nèi)容。視圖可以是簡(jiǎn)單的元素,比如文本,也可以是復(fù)雜的小部件,比如復(fù)選框。
要顯示文本消息,我們可以使用TextView::new("text")構(gòu)造函數(shù)。最初,屏幕是空的,所以我們需要使用add_layer創(chuàng)建一個(gè)層。add_layer的參數(shù)應(yīng)該是我們想要作為新圖層顯示的視圖。
下面是一個(gè)顯示“Hello TUI!”消息,并允許用戶(hù)通過(guò)按' q '退出應(yīng)用程序:
use cursive::views::TextView;
fn main() {
// 創(chuàng)建一個(gè)Cursive對(duì)象
let mut siv = cursive::default();
// 添加一個(gè)全局回調(diào),當(dāng)按下'q'時(shí)退出應(yīng)用程序
siv.add_global_callback('q', |s| s.quit());
// 添加一個(gè)TextView與我們的消息作為一個(gè)新的圖層
siv.add_layer(TextView::new("Hello TUI! 按<q>退出."));
// 執(zhí)行Cursive對(duì)象
siv.run();
}
運(yùn)行此程序?qū)@示“Hello TUI!”,按<q>鍵退出。
對(duì)話(huà)框
對(duì)話(huà)框通常用于在TUI應(yīng)用程序中創(chuàng)建交互式的和用戶(hù)友好的基于文本的彈出窗口。它們?cè)试S你向用戶(hù)呈現(xiàn)信息,并通過(guò)按鈕和回調(diào)收集輸入,從而增強(qiáng)用戶(hù)體驗(yàn)。
讓我們使用對(duì)話(huà)框,這是一個(gè)封裝器,封裝另一個(gè)視圖,包括標(biāo)題和選擇按鈕。而不是直接使用TextView。
Dialog::around函數(shù)直接接受一個(gè)視圖,所以我們可以直接提供TextView:
siv.add_layer(Dialog::around(TextView::new("Question 1")));
由于在文本視圖中創(chuàng)建對(duì)話(huà)框窗口是一個(gè)常見(jiàn)的任務(wù),dialog::text是一個(gè)可以直接完成此任務(wù)的函數(shù),使我們的代碼更短(并且我們不再需要導(dǎo)入cursive::views::TextView)。
siv.add_layer(Dialog::text("Empty"));
我們可以使用Dialog::title方法添加標(biāo)題。
use cursive::views::{TextView, Dialog};
fn main() {
// 創(chuàng)建一個(gè)Cursive對(duì)象
let mut siv = cursive::default();
// 添加一個(gè)全局回調(diào),當(dāng)按下'q'時(shí)退出應(yīng)用程序
siv.add_global_callback('q', |s| s.quit());
siv.add_layer(Dialog::text("did you do the thing?").title("This is the title"));
// 執(zhí)行Cursive對(duì)象
siv.run();
}
如果我們運(yùn)行這段代碼,我們將看到一個(gè)沒(méi)有按鈕的對(duì)話(huà)框窗口。
圖片
按鈕
我們的對(duì)話(huà)框看起來(lái)比單獨(dú)的TextView要好,但它仍然缺少一些動(dòng)作。我們來(lái)添加一些按鈕。
就像標(biāo)題一樣,Dialog有一個(gè)Dialog::button方法,用于添加帶有關(guān)聯(lián)動(dòng)作的按鈕。下面是如何使用Dialog::button添加按鈕:
use cursive::views::{TextView, Dialog};
fn main() {
// 創(chuàng)建一個(gè)Cursive對(duì)象
let mut siv = cursive::default();
siv.add_layer(Dialog::text("...").title("Did you do the thing?")
.button("Yes", |s| s.quit())
.button("No", |s| s.quit())
.button("Uh?", |s| s.quit()));
// 執(zhí)行Cursive對(duì)象
siv.run();
}
在這個(gè)例子中,對(duì)話(huà)框包括三個(gè)按鈕:“是”、“否”和“Uh?”,當(dāng)點(diǎn)擊時(shí),它們都有退出程序的動(dòng)作。但是,你可以通過(guò)使用自定義函數(shù)替換“|s| s.quit()”來(lái)定制操作。
運(yùn)行結(jié)果如下:
圖片
讓我們?cè)谝粋€(gè)更實(shí)際的背景下探討這個(gè)問(wèn)題:
use cursive::Cursive;
use cursive::views::Dialog;
fn main() {
let mut siv = cursive::default();
siv.add_layer(Dialog::text("This is a survey!\nPress <Next> when you're ready.")
.title("Important survey")
.button("Next", show_next));
siv.run();
}
fn show_next(_: &mut Cursive) {
// Leave this function empty for now
}
在這段代碼中,在用戶(hù)單擊“Next”之后,我們希望隱藏當(dāng)前對(duì)話(huà)框并顯示一個(gè)新對(duì)話(huà)框。我們使用Cursive::pop_layer來(lái)移除當(dāng)前圖層。
為了更好地理解pop_layer是如何工作的,讓我們分解這個(gè)過(guò)程:
use cursive::views::Dialog;
use cursive::views::TextView;
use cursive::Cursive;
fn main() {
// 創(chuàng)建一個(gè)新的Cursive實(shí)例
let mut siv = cursive::default();
// 添加一個(gè)帶有標(biāo)題、文本和按鈕的對(duì)話(huà)框圖層。
siv.add_layer(
Dialog::text("Are you of legal age?")
.title("Question 1")
// 添加一個(gè)帶有Yes回調(diào)函數(shù)的按鈕
.button("Yes", yes)
// 添加一個(gè)帶有回調(diào)No函數(shù)的按鈕。
.button("No", no),
);
siv.run(); // Start the Cursive event loop.
}
fn yes(s: &mut Cursive) {
// 移除當(dāng)前對(duì)話(huà)框?qū)? s.pop_layer();
// 添加一個(gè)帶有消息的TextView圖層
s.add_layer(TextView::new("Good! You can proceed."));
}
fn no(s: &mut Cursive) {
// 移除當(dāng)前對(duì)話(huà)框?qū)? s.pop_layer();
// 添加一個(gè)帶有消息的TextView圖層
s.add_layer(TextView::new("You can't proceed!"));
}
正如你所看到的,Dialog視圖是呈現(xiàn)TextView的一種很好的方式,但它也適用于任何其他內(nèi)容。實(shí)際上,大多數(shù)的圖層都是以一個(gè)包含其他視圖的對(duì)話(huà)框開(kāi)始。
總結(jié)
本文為使用Rust和Cursive庫(kù)構(gòu)建基于文本的用戶(hù)界面(TUI)提供了堅(jiān)實(shí)的起點(diǎn)。在此基礎(chǔ)上,你可以瀏覽文檔并深入研究更高級(jí)的TUI開(kāi)發(fā)。