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();

內容解密:

  1. 建立垂直佈局的GtkBox:使用Box::new函式建立一個垂直佈局的GtkBox,引數0表示元件之間的間距為0。
  2. 建立文字標籤和圖片元件:使用Label::new建立文字標籤,使用Image::from_file建立圖片元件,並將它們加入到layout_box中。
  3. 將佈局加入到視窗:使用window.addlayout_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>標籤設定元件的屬性,例如orientationlabel

使用GTK除錯工具

GTK提供了除錯工具,可以用來檢查UI的佈局和元件。要使用除錯工具,需要設定環境變數GTK_DEBUGinteractive,然後執行程式。

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: &gtk::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();

內容解密:

  1. 使用builder.object()函式根據Glade檔案中的ID取得對應的GTK元件。
  2. unwrap()方法用於解封裝Option型別,如果元件不存在,則程式會panic。
  3. 將取得的元件指定給相應的變數,以便後續操作。

設定按鈕點選事件處理器

為「Generate」按鈕設定點選事件的回呼函式:

button.connect_clicked(|_| {
    message_output.set_text(&format!("{}\n \\\n \\", message_input.text().as_str()));
    image_output.show();
});

內容解密:

  1. button.connect_clicked()方法用於設定按鈕點選事件的回呼函式。
  2. 回呼函式是一個閉包,讀取message_input中的文字,並設定message_output的文字。
  3. 顯示image_output圖片。

處理元件可見性

確保貓圖片在點選「Generate」按鈕前保持隱藏:

window.show_all();
image_output.hide();

內容解密:

  1. window.show_all()顯示視窗中的所有元件。
  2. image_output.hide()隱藏貓圖片。
  3. 先顯示所有元件,再隱藏圖片,以確保圖片初始狀態為隱藏。

編譯錯誤處理

編譯上述程式碼時,會遇到錯誤:

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_outputbuild_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();

內容解密:

  1. 克隆image_output控制程式碼,建立image_output_clone
  2. image_output_clone移動到閉包中,呼叫其show()方法顯示圖片。
  3. 在閉包外仍可使用原始的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();

內容解密:

  1. 取得「Dead?」開關的控制程式碼。
  2. 使用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();

程式碼解析

  1. 取得開關元件的控制權:首先,我們需要從 Glade 建立的 UI 檔案中取得 is_dead_switch 元件的控制權。這是透過 builder.object() 方法實作的。
  2. 檢查開關狀態:我們使用 is_dead_switch.is_active() 方法來檢查開關是否被啟用。
  3. 根據開關狀態載入不同的圖片:根據開關的狀態,我們使用 image_output_clone.set_from_file() 方法來載入不同的貓圖片。
  4. 顯示圖片:最後,我們呼叫 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 模組、npmwebpack

對 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 應用

本章節將逐步實作三個專案:

  1. Hello World 應用:建立一個簡單的瀏覽器 alert() 函式呼叫,展示如何將 Rust 程式碼編譯成 WebAssembly 並在瀏覽器中執行。
  2. 圖片縮放應用:開發一個前端應用,用於縮放上傳的貓圖片大小。由於圖片縮放是 CPU 密集型任務,因此適合使用 WebAssembly 實作。
  3. 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 專案。