在 Rust 中構建圖形介面應用程式,GTK 是一個強大的跨平台解決方案。本文將逐步講解如何使用 GTK 函式庫,從程式碼建立基本元件到利用 Glade 視覺化設計工具簡化 UI 設計流程。同時,我們也會探討 GTK 的事件處理機制,例如按鈕點選事件,以及如何動態載入和顯示圖片。此外,文章還會分析 GTK3-RS 的物件複製特性以及常見的編譯錯誤和解決方法。最後,我們將比較 Rust 生態系統中其他的 GUI 函式庫,例如 Qt、FLTK、Druid、OrbTk、egui、imgui 和 Tauri,並提供一些選擇 GUI 函式庫的建議,幫助讀者根據專案需求做出最佳決策。

使用GTK建立圖形使用者介面

本章節將介紹如何使用GTK函式庫在Rust中建立圖形使用者介面(GUI)。首先,我們將探討如何使用程式碼建立基本的視窗和元件,接著介紹如何使用Glade工具設計UI佈局。

以程式設計方式建立UI

在GTK中,建立GUI元件需要使用特定的函式庫和資料結構。下面的程式碼範例展示瞭如何建立一個包含標籤和圖片的視窗:

// 建立一個垂直排列的GtkBox
let layout_box = Box::new(Orientation::Vertical, 0);
// [1]
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,並指定其方向為垂直(Orientation::Vertical)和元件間的間距(本例中為0)。
  2. 新增元件至GtkBox:使用add方法將標籤(label)和圖片(cat_image)新增至GtkBox中。這些元件將按照指定的方向排列。
  3. 將GtkBox新增至視窗:最後,將GtkBox新增至主視窗(window)中,並呼叫show_all方法顯示所有元件。

這個範例展示瞭如何以程式設計方式建立基本的GUI佈局。然而,當UI變得更加複雜時,這種方法可能變得難以維護。

使用Glade設計UI

為了簡化UI的設計過程,GTK提供了Glade這個視覺化設計工具。Glade允許開發者透過拖曳元件的方式設計UI,並自動生成對應的XML檔案。

下面的XML範例展示瞭如何使用Glade定義一個包含標籤和圖片的UI佈局:

<?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>

內容解密:

  • XML宣告:檔案開頭宣告了XML的版本和編碼方式。
  • GTK需求:指定了所需的GTK版本。
  • 視窗和元件定義:定義了一個GtkApplicationWindow,其中包含一個垂直排列的GtkBox,內含一個標籤和一個圖片元件。

使用Glade可以大幅簡化UI的設計和維護工作,使得開發者能夠專注於應用程式的邏輯實作。

實際應用與除錯

在實際開發中,開發者可以結合GTK的程式設計介面和Glade工具來建立和除錯GUI應用程式。GTK提供的視覺化除錯工具可以幫助開發者理解UI元件的層次結構和佈局,從而更有效地進行除錯和調整。

綜上所述,本章節介紹瞭如何使用GTK和Glade在Rust中建立圖形使用者介面,並提供了相關的程式碼範例和XML定義。這些技術和方法可以幫助開發者建立功能豐富且易於維護的GUI應用程式。

使用Glade建立GTK圖形使用者介面

在前面的章節中,我們已經學習瞭如何使用Rust程式語言和GTK函式庫來建立圖形使用者介面(GUI)。然而,手動撰寫程式碼來建立複雜的介面可能會變得相當繁瑣。幸運的是,我們可以使用Glade這個工具來簡化這個過程。

Glade簡介

Glade是一個使用者介面設計工具,允許開發者以視覺化的方式設計GTK介面。它產生一個XML檔案,描述了介面的結構和外觀。然後,我們可以在程式中載入這個XML檔案來建立介面。

建立Glade檔案

首先,我們需要安裝Glade並建立一個新的專案。然後,我們可以使用Glade的視覺化介面來設計我們的GUI。在這個例子中,我們建立了一個簡單的介面,包含了一個輸入框、一個開關和一個按鈕。

清單3-4:layout.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表示盒子中的所有子元件將具有相同的大小。

在Rust中使用Glade檔案

要使用Glade檔案,我們需要在Rust專案中加入GTK函式庫,並載入Glade檔案。

清單3-5: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函式根據這個字串建立GTK介面。
  • builder.object方法取得介面中的特定元件(在這裡是主視窗)。
  • window.set_application方法設定應用程式的主視窗。
  • window.show_all方法顯示視窗及其所有子元件。

結果

執行這個程式後,我們將看到一個GTK應用程式,其介面是由Glade檔案定義的。這種方法大大簡化了建立複雜GUI的過程,使我們能夠專注於應用程式的邏輯部分。

圖表說明

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333

title 圖表說明

rectangle "載入" as node1
rectangle "建立" as node2
rectangle "顯示" as node3

node1 --> node2
node2 --> node3

@enduml

此圖示展示了使用Glade檔案建立GTK介面的流程。Glade檔案被載入到GTK Builder中,然後Builder根據檔案中的定義建立GTK介面,最後顯示出應用程式視窗。

GTK 應用程式開發:接受輸入與按鈕點選

在GTK應用程式中新增互動功能,與開發TUI(文字使用者介面)應用程式類別似。首先,在 build_ui() 函式中為輸入欄位和按鈕新增事件處理器。

取得元件控制程式碼

使用 builder.object() 函式取得所需的元件控制程式碼,例如:

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

設定按鈕點選事件處理器

使用 button.connect_clicked() 設定按鈕點選事件的回呼函式:

button.connect_clicked(|_| {
    // 事件處理邏輯
});

內容解密:

  1. button.connect_clicked() 用於繫結按鈕的點選事件。
  2. 事件處理器是一個閉包(closure),在按鈕被點選時執行。
  3. 在閉包中,可以存取和修改元件的屬性,例如讀取輸入欄位的文字、設定輸出標籤的文字、顯示或隱藏圖片等。

處理圖片顯示

為了在按鈕點選時顯示圖片,需要在閉包中呼叫 image_output.show()。同時,為了在初始狀態下隱藏圖片,需要在 window.show_all() 之後呼叫 image_output.hide()

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

內容解密:

  1. window.show_all() 顯示視窗及其所有子元件。
  2. image_output.hide() 隱藏圖片元件。
  3. 事件處理器中的 image_output.show() 用於在按鈕點選時顯示圖片。

編譯錯誤與解決方案

嘗試編譯程式碼時,可能會遇到錯誤:

error[E0373]: closure may outlive the current function, but it borrows 'image_output', which is owned by the current function

這是因為閉包可能會在 build_ui() 函式傳回後被呼叫,但此時 image_output 變數已經超出作用域。解決方案是使用 move 關鍵字將 image_output 的所有權轉移到閉包中。但是,這樣做會導致 image_output 在閉包外部無法存取。

內容解密:

  1. GTK3-RS 是 GTK C 函式庫的 Rust 包裝器,對 GTK3-RS 物件進行 clone 操作只會複製指標,而非進行深複製。
  2. 可以透過 clone image_output 來解決所有權問題:
let image_output_clone = image_output.clone();
button.connect_clicked(move |_| {
    // ...
    image_output_clone.show();
});

讀取 gtk::Switch 狀態

要讀取 “Dead?” 開關的狀態,可以使用 is_active() 方法:

let is_dead_switch: gtk::Switch = builder.object("is_dead_switch").unwrap();
let is_dead = is_dead_switch.is_active();

內容解密:

  1. is_active() 方法傳回開關的目前狀態(是否啟用)。
  2. 可以根據開關的狀態進行相應的邏輯處理。

GTK GUI 開發實作詳解

在 catsay-gui-glade 專案中,我們利用 Glade 工具設計 GUI 佈局,並使用 GTK 函式庫將介面與 Rust 程式碼結合。以下程式碼片段展示瞭如何根據 is_dead 開關狀態動態載入不同的貓咪圖片:

let is_dead_switch: gtk::Switch = builder.object("is_dead_switch").unwrap();
let is_dead = is_dead_switch.is_active();
if is_dead {
    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. 取得開關控制項:使用 builder.object() 方法取得 Glade 中定義的 is_dead_switch 開關控制項。
  2. 判斷開關狀態:透過 is_active() 方法檢查開關是否被啟用,以此決定載入哪張圖片。
  3. 動態載入圖片:根據開關狀態,使用 set_from_file() 方法載入對應的貓咪圖片。
  4. 顯示圖片:呼叫 show() 方法將圖片顯示在介面上。

GUI 開發的替代方案

除了 GTK 之外,Rust 生態系統中還存在許多其他的 GUI 函式庫,每種都有其獨特的設計理念和優勢:

1. 根據現有 C/C++ 函式庫的 Rust 繫結

  • Qt: 跨平台的 GUI 函式庫,具有成熟的生態系統和豐富的功能。
    • Rust 繫結:RitualQMetaObject
  • FLTK: 輕量級的跨平台 GUI 函式庫。
    • Rust 繫結:fltk-rs

2. 純 Rust 實作的 GUI 函式庫

  • Druid: 以資料驅動為核心設計理念,適合開發複雜的桌面應用程式。
  • OrbTk: ReduxOS 專案的一部分,提供了一套完整的 GUI 解決方案。

3. 立即模式 GUI(Immediate-Mode GUI)

  • egui: 簡潔易用的立即模式 GUI 函式庫,適合用於遊戲或即時互動應用。
  • imgui: Dear ImGui 的 Rust 繫結,同樣採用立即模式設計。

4. 根據 Web 技術的桌面應用開發

  • Tauri: 允許使用 Web 技術(HTML/CSS/JavaScript)開發桌面應用,並透過 Rust 提供後端支援。

未來趨勢與建議

  1. 關注純 Rust GUI 函式庫的發展:隨著 Rust 生態系統的不斷成熟,純 Rust 實作的 GUI 函式庫可能會提供更好的效能和安全性。
  2. 評估跨平台支援:選擇支援多平台的 GUI 函式庫,以擴大應用的覆寫範圍。
  3. 考慮專案複雜度和開發資源:對於小型專案,可以選擇簡單易用的立即模式 GUI;對於大型專案,則可能需要更為成熟和穩定的方案。

透過本章節的學習,讀者應該能夠根據實際需求選擇合適的 GUI 開發方案,並利用 Rust 的強大功能建立高效、穩定的圖形化使用者介面。