Rust 作為一門兼具效能和安全性的現代程式語言,不僅在系統程式設計領域表現出色,在圖形介面(GUI)和網頁前端開發方面也展現出強大的潛力。本文將探討如何使用 Rust 構建 GUI 應用程式,以及如何利用 WebAssembly 技術將 Rust 程式碼帶到網頁前端。首先,我們將逐步講解如何使用 GTK 和 Glade 構建跨平台 GUI 應用,涵蓋佈局、事件處理等核心概念。接著,我們將介紹 WebAssembly 的基本原理,以及如何使用 wasm-pack 工具鏈將 Rust 程式碼編譯成可在瀏覽器中執行的 Wasm 模組。最後,我們將探討 Yew 框架在構建高效能網頁前端應用方面的優勢,並以圖片處理和 Catdex 應用為例,展示 Rust 與 WebAssembly 的整合實踐。
使用GTK建立圖形使用者介面
在GTK中建立圖形使用者介面(GUI)需要使用特定的元件和容器來佈局。以下是一個簡單的範例,展示如何使用GTK建立一個包含文字標籤和圖片的視窗。
建立GTK視窗和元件
首先,需要建立一個GTK視窗和相關的元件。以下是一個範例程式碼:
let layout_box = Box::new(Orientation::Vertical, 0); // [1]
let label = Label::new(Some("Meow!")); // 建立文字標籤
layout_box.add(&label);
// [2]
let cat_image = Image::from_file("./images/cat.png"); // 建立圖片元件
layout_box.add(&cat_image);
window.add(&layout_box); // [3]
window.show_all();
內容解密:
- 建立垂直佈局的GtkBox:使用
Box::new函式建立一個垂直佈局的GtkBox,引數0表示元件之間的間距為0。 - 建立文字標籤和圖片元件:使用
Label::new建立文字標籤,使用Image::from_file建立圖片元件,並將它們加入到layout_box中。 - 將佈局加入到視窗:使用
window.add將layout_box加入到視窗中,並顯示所有元件。
使用Glade設計UI
除了使用程式碼建立UI外,還可以使用Glade這個UI設計工具來設計UI。Glade可以產生XML檔案,描述UI的佈局和元件。
Glade XML範例
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="3.12"/>
<object class="GtkApplicationWindow" id="applicationwindow1">
<child>
<object class="GtkBox" id="box1">
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="label1">
<property name="label" translatable="yes">Meow!</property>
</object>
</child>
<child>
<object class="GtkImage" id="image1">
<property name="pixbuf">./images/cat.png</property>
</object>
</child>
</object>
</child>
</object>
</interface>
內容解密:
- 定義UI佈局:使用XML描述UI的佈局和元件,包括GtkApplicationWindow、GtkBox、GtkLabel和GtkImage。
- 設定元件屬性:使用
<property>標籤設定元件的屬性,例如orientation和label。
使用GTK除錯工具
GTK提供了除錯工具,可以用來檢查UI的佈局和元件。要使用除錯工具,需要設定環境變數GTK_DEBUG為interactive,然後執行程式。
GTK_DEBUG=interactive cargo run
這樣就可以啟動除錯工具,檢查UI的佈局和元件。
使用Glade設計GTK介面
在開發圖形使用者介面(GUI)應用程式時,設計介面是一個重要的步驟。Glade是一個強大的工具,能夠幫助開發者使用GTK+函式庫建立介面。在本章節中,我們將探討如何使用Glade設計GTK介面,並將其整合到Rust程式中。
建立Glade檔案
首先,我們需要建立一個Glade檔案來定義我們的介面。Glade檔案是一種XML格式的檔案,用於描述GTK元件的佈局和屬性。以下是一個簡單的Glade檔案範例:
<object class="GtkBox" id="message_input_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="resize_mode">immediate</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkLabel" id="message_input_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">What does the cat say:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<!-- 其他元件 -->
</object>
內容解密:
- 上述XML程式碼定義了一個
GtkBox元件,其中包含一個GtkLabel和一個GtkEntry。 visible屬性設定為True,表示該元件在介面上可見。homogeneous屬性設定為True,表示該盒子中的所有子元件將具有相同的大小。packing標籤用於定義子元件在父元件中的佈局屬性。
在Rust中使用Glade檔案
在建立了Glade檔案之後,我們需要在Rust程式中載入和使用它。首先,建立一個新的Rust專案,並新增gtk crate:
$ cargo new catsay-gui-glade
$ cargo add gtk --features v3_24
然後,將Glade檔案複製到src/資料夾中,並在main.rs中新增以下程式碼:
use gtk::prelude::*;
fn build_ui(app: >k::Application) {
let glade_src = include_str!("layout.glade");
let builder = gtk::Builder::from_string(glade_src);
let window: gtk::Window = builder.object("applicationwindow1").unwrap();
window.set_application(Some(app));
window.show_all();
}
fn main() {
let application = gtk::Application::new(
Some("com.catsay-gui-glade"),
Default::default()
);
application.connect_activate(build_ui);
application.run();
}
內容解密:
include_str!巨集用於將Glade檔案載入到一個字串變數中。gtk::Builder::from_string函式用於根據Glade檔案的字串表示建立GTK程式。builder.object方法用於取得Glade檔案中定義的特定物件(在本例中為applicationwindow1)。window.set_application方法用於將視窗與GTK應用程式關聯起來。window.show_all方法用於顯示視窗及其所有子元件。
建立互動式GTK應用程式
在GTK應用程式中新增互動功能,與開發TUI應用程式類別似。首先,在build_ui()函式中為輸入框和按鈕新增事件處理器。
取得視窗元件控制程式碼
首先,使用builder.object()函式取得所需的元件控制程式碼,就像取得ApplicationWindow一樣:
let message_input: gtk::Entry = builder.object("message_input").unwrap();
let button: gtk::Button = builder.object("generate_btn").unwrap();
let message_output: gtk::Label = builder.object("message_output").unwrap();
let image_output: gtk::Image = builder.object("image_output").unwrap();
內容解密:
- 使用
builder.object()函式根據Glade檔案中的ID取得對應的GTK元件。 unwrap()方法用於解封裝Option型別,如果元件不存在,則程式會panic。- 將取得的元件指定給相應的變數,以便後續操作。
設定按鈕點選事件處理器
為「Generate」按鈕設定點選事件的回呼函式:
button.connect_clicked(|_| {
message_output.set_text(&format!("{}\n \\\n \\", message_input.text().as_str()));
image_output.show();
});
內容解密:
button.connect_clicked()方法用於設定按鈕點選事件的回呼函式。- 回呼函式是一個閉包,讀取
message_input中的文字,並設定message_output的文字。 - 顯示
image_output圖片。
處理元件可見性
確保貓圖片在點選「Generate」按鈕前保持隱藏:
window.show_all();
image_output.hide();
內容解密:
window.show_all()顯示視窗中的所有元件。image_output.hide()隱藏貓圖片。- 先顯示所有元件,再隱藏圖片,以確保圖片初始狀態為隱藏。
編譯錯誤處理
編譯上述程式碼時,會遇到錯誤:
error[E0373]: closure may outlive the current function, but it borrows 'image_output', which is owned by the current function
錯誤原因是閉包可能在build_ui()函式傳回後被呼叫,但它借用了image_output,而image_output在build_ui()函式傳回後就超出了作用域。
解決方案:移動所有權或克隆控制程式碼
為了修復這個錯誤,可以使用move關鍵字將變數的所有權移動到閉包中,但這樣做會導致變數在閉包外不可用。因此,可以克隆image_output的控制程式碼:
let image_output_clone = image_output.clone();
button.connect_clicked(move |_| {
message_output.set_text(&format!("{}\n \\\n \\", message_input.text().as_str()));
image_output_clone.show();
});
image_output.hide();
內容解密:
- 克隆
image_output控制程式碼,建立image_output_clone。 - 將
image_output_clone移動到閉包中,呼叫其show()方法顯示圖片。 - 在閉包外仍可使用原始的
image_output變數。
新增gtk::Switch處理
最後,為「Dead?」開關新增處理邏輯:
let is_dead_switch: gtk::Switch = builder.object("is_dead_switch").unwrap();
let is_dead = is_dead_switch.is_active();
內容解密:
- 取得「Dead?」開關的控制程式碼。
- 使用
is_active()方法檢查開關是否啟用。
圖表說明
以下Plantuml圖表展示了GTK應用程式的基本架構:
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle
title Rust GTK 圖形介面開發與 WebAssembly 前端應用
package "Rust 記憶體管理" {
package "所有權系統" {
component [Owner] as owner
component [Borrower &T] as borrow
component [Mutable &mut T] as mutborrow
}
package "生命週期" {
component [Lifetime 'a] as lifetime
component [Static 'static] as static_lt
}
package "智慧指標" {
component [Box<T>] as box
component [Rc<T>] as rc
component [Arc<T>] as arc
component [RefCell<T>] as refcell
}
}
package "記憶體區域" {
component [Stack] as stack
component [Heap] as heap
}
owner --> borrow : 不可變借用
owner --> mutborrow : 可變借用
owner --> lifetime : 生命週期標註
box --> heap : 堆積分配
rc --> heap : 引用計數
arc --> heap : 原子引用計數
stack --> owner : 棧上分配
note right of owner
每個值只有一個所有者
所有者離開作用域時值被釋放
end note
@enduml此圖示說明瞭GTK應用程式開發的主要步驟,包括介面設計、程式邏輯編寫、元件佈局、事件處理等,最終呈現出具有互動功能的圖形使用者介面。
圖形使用者介面(GUI)開發的多樣選擇與實踐
在前面的章節中,我們已經使用 GTK 函式庫建立了一個簡單的圖形使用者介面(GUI)應用程式。本章將進一步探討其他可用的 GUI 函式庫和技術,並比較它們之間的差異。
從 TUI 到 GUI 的進化
本章的開頭,我們從建立一個根據文字的使用者介面(TUI)開始,逐步過渡到建立一個完整的圖形使用者介面(GUI)。在這個過程中,我們學習瞭如何使用 ncurses 函式庫來建立 TUI,以及如何使用 GTK 函式庫來建立 GUI。
使用 GTK 建立 GUI
在使用 GTK 建立 GUI 的過程中,我們首先使用程式碼直接建立 UI 元件。接著,我們學習瞭如何使用 Glade 這個 XML 編輯器來定義 UI 佈局,並將其與 Rust 程式碼結合。
let is_dead_switch: gtk::Switch = builder.object("is_dead_switch").unwrap();
if is_dead_switch.is_active() {
image_output_clone.set_from_file(Some("./images/cat_dead.png"));
} else {
image_output_clone.set_from_file(Some("./images/cat.png"));
}
image_output_clone.show();
程式碼解析
- 取得開關元件的控制權:首先,我們需要從 Glade 建立的 UI 檔案中取得
is_dead_switch元件的控制權。這是透過builder.object()方法實作的。 - 檢查開關狀態:我們使用
is_dead_switch.is_active()方法來檢查開關是否被啟用。 - 根據開關狀態載入不同的圖片:根據開關的狀態,我們使用
image_output_clone.set_from_file()方法來載入不同的貓圖片。 - 顯示圖片:最後,我們呼叫
image_output_clone.show()方法來顯示所選的圖片。
其他 GUI 函式庫的選擇
除了 GTK 之外,Rust 社群還提供了許多其他的 GUI 函式庫可供選擇。這些函式庫具有不同的設計理念和特點。
立即模式 GUI 與保留模式 GUI
GUI 函式庫大致可以分為兩類別:立即模式 GUI 和保留模式 GUI。立即模式 GUI 在每次渲染新幀時重新繪製所有元件,而保留模式 GUI 則是建立元件後由框架管理其狀態。
- 立即模式 GUI:egui 和 imgui 是立即模式 GUI 的代表。立即模式 GUI 的優點是程式碼較為簡單,但佈局管理可能較為複雜。
- 保留模式 GUI:GTK 和 Qt 是保留模式 GUI 的代表。保留模式 GUI 的優點是易於管理元件狀態,但可能需要處理更多的生命週期問題。
跨平台支援
許多 GUI 函式庫都提供了跨平台支援,使得開發者能夠在不同作業系統上執行相同的應用程式。
- 跨平台 GUI 函式庫:例如 Qt、FLTK 和 libui 等,都提供了跨平台支援。
- Rust 原生 GUI 函式庫:例如 Druid 和 OrbTk 等,是專為 Rust 語言設計的 GUI 函式庫。
網頁技術構建桌面應用
Tauri 是一個有趣的框架,它允許開發者使用網頁技術(如 HTML、CSS 和 JavaScript)來建立桌面應用程式。Tauri 將 UI 渲染交由內嵌的網頁瀏覽器引擎處理,並與根據 Rust 的核心進行互動。
高效能網頁前端:WebAssembly 的應用
隨著 Rust 語言在系統開發領域的廣泛應用,其在網頁前端開發中的潛力也逐漸被挖掘出來。透過 WebAssembly(簡稱 Wasm),開發者能夠將 Rust 程式碼編譯成可在瀏覽器中執行的格式,與 JavaScript 共同工作,從而實作高效能的網頁前端應用。
瞭解 WebAssembly
WebAssembly 是一種開放標準的二進位指令格式,執行於根據堆積疊的虛擬機器上。最初設計用於提供接近原生效能的網頁瀏覽體驗,可以視為網頁的組合語言。作為 W3C 的推薦標準,WebAssembly 已在各大主流瀏覽器中獲得實作。
WebAssembly 的設計目標是實作近乎原生的執行速度,且不強制使用垃圾回收機制(GC),使其成為 C、C++ 和 Rust 等語言的編譯目標。因此,開發者能夠使用偏好的高階程式語言編寫前端應用,並獲得可預測的效能。
為何選擇 Rust 編譯至 WebAssembly
選擇 Rust 編譯至 WebAssembly 的原因包括:
- 在瀏覽器中享受 Rust 的高階語法和低階控制能力。
- 由於 Rust 的最小化執行環境,下載的
.wasm二進位檔案體積較小,有助於節省頻寬。 - 能夠重複利用現有的豐富 Rust 函式庫資源。
- 透過
wasm-pack工具鏈,使用熟悉的前端網頁工具,如 ES6 模組、npm和webpack。
對 WebAssembly 的誤解
常見的誤解封裝括:
- WebAssembly 無意取代 JavaScript。它旨在與 JavaScript 協同工作,簡化瀏覽器中的某些任務,並在需要時提供額外的效能。
- WebAssembly 不限於瀏覽器環境。雖然最初針對瀏覽器,但 WebAssembly 執行環境可執行於任何地方,如伺服器或物聯網(IoT)裝置,這些應用同樣受益於執行環境的隔離保證。
WebAssembly 的應用場景
WebAssembly 常被用於加速 JavaScript 網頁應用中的效能瓶頸。使用 HTML、CSS 和 JavaScript 建立使用者介面(UI),而將 CPU 密集型任務或函式以 WebAssembly 編寫,計算結果再傳回 JavaScript 以進行顯示。
一些框架更進一步,允許完全使用 Rust 編寫前端應用,如 Sycamore、Yew 或 Percy。這些框架通常汲取了 React 和 Elm 等流行前端框架的設計靈感,並使用虛擬 DOM。Rust 程式碼被編譯成 Wasm,並透過找出虛擬 DOM 與真實 DOM 之間的差異,然後對真實 DOM 進行調整,從而實作渲染畫面。
專案實作:從 Hello World 到 Catdex 應用
本章節將逐步實作三個專案:
- Hello World 應用:建立一個簡單的瀏覽器 alert() 函式呼叫,展示如何將 Rust 程式碼編譯成 WebAssembly 並在瀏覽器中執行。
- 圖片縮放應用:開發一個前端應用,用於縮放上傳的貓圖片大小。由於圖片縮放是 CPU 密集型任務,因此適合使用 WebAssembly 實作。
- Catdex 應用:利用 Yew 框架建立一個單頁應用,實作新增和移除貓圖片的功能。
設定開發環境
為了簡化將 Rust 程式碼編譯成 WebAssembly 的流程,推薦使用 wasm-pack 工具。首先,從 https://rustwasm.github.io/wasm-pack/installer/ 下載並安裝 wasm-pack。在 Linux 系統中,可透過執行以下命令完成安裝:
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
內容解密:
此命令使用 curl 下載 wasm-pack 安裝指令碼,並透過管道傳輸至 sh 執行安裝。其中:
curl是一個命令列 HTTP 使用者端,用於下載檔案。-sSf引數確保curl在下載過程中顯示進度並在錯誤時失敗。| sh將下載的指令碼直接傳給 shell 執行,完成安裝程式。
安裝完成後,即可使用 wasm-pack 建立新的 WebAssembly 專案,或是將現有的 Rust 程式碼編譯成 .wasm 檔案,供網頁前端呼叫。接下來的章節將詳細介紹如何建立和設定第一個 WebAssembly 專案。