コンテンツにスキップ

Rustで学ぶGTK4

出典: フリー教科書『ウィキブックス(Wikibooks)』

はじめに

[編集]

GTK4とRustを組み合わせたGUIプログラミングについて、基本から実践的な例まで解説します。

開発環境のセットアップ

[編集]

必要なパッケージのインストール

[編集]
# Debian/Ubuntuの場合
sudo apt install libgtk-4-dev build-essential
sudo apt install cargo rustc

Cargo.tomlの依存関係

Cargo.toml
[dependencies]
gtk = { version = "0.6", package = "gtk4" }
gio = "0.17"
glib = "0.17"

基本的なアプリケーション構造

[編集]

最小限のGTK4アプリケーション

[編集]
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow};

fn main() {
    // アプリケーションの作成
    let app = Application::builder()
        .application_id("com.example.FirstApp")
        .build();

    // アプリケーションの起動時の処理
    app.connect_activate(build_ui);
    app.run();
}

fn build_ui(app: &Application) {
    // ウィンドウの作成
    let window = ApplicationWindow::builder()
        .application(app)
        .title("My GTK App")
        .default_width(600)
        .default_height(400)
        .build();

    window.present();
}

ウィジェットの基本

[編集]

ボタンの追加

[編集]
use gtk::{Button, Box};

fn build_ui(app: &Application) {
    let window = ApplicationWindow::builder()
        .application(app)
        .title("Button Example")
        .build();

    let button = Button::builder()
        .label("Click Me!")
        .margin_top(12)
        .margin_bottom(12)
        .margin_start(12)
        .margin_end(12)
        .build();

    // クリックイベントの処理
    button.connect_clicked(|_| {
        println!("Button clicked!");
    });

    window.set_child(Some(&button));
    window.present();
}

レイアウトの作成

[編集]
fn create_layout() -> Box {
    let box_layout = Box::builder()
        .orientation(gtk::Orientation::Vertical)
        .spacing(6)
        .build();

    let button1 = Button::with_label("Button 1");
    let button2 = Button::with_label("Button 2");

    box_layout.append(&button1);
    box_layout.append(&button2);

    box_layout
}

シグナルとイベント処理

[編集]

シグナルの接続

[編集]
use glib::clone;

fn connect_signals(button: &Button, label: &Label) {
    button.connect_clicked(clone!(@weak label => move |_| {
        label.set_text("Button was clicked!");
    }));
}

カスタムシグナル

[編集]
use glib::subclass::Signal;
use glib::signal::SignalHandlerId;

#[derive(Default)]
pub struct CustomWidget {
    count: Cell<i32>,
}

#[glib::object_subclass]
impl ObjectSubclass for CustomWidget {
    const NAME: &'static str = "CustomWidget";
    type Type = super::CustomWidget;
    type ParentType = gtk::Widget;
}

impl ObjectImpl for CustomWidget {
    fn signals() -> &'static [Signal] {
        static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
            vec![Signal::builder("count-changed")
                .param_types([i32::static_type()])
                .build()]
        });
        SIGNALS.as_ref()
    }
}

GUIコンポーネント

[編集]

エントリー(テキスト入力)

[編集]
use gtk::Entry;

fn create_entry() -> Entry {
    let entry = Entry::builder()
        .placeholder_text("Enter text...")
        .build();

    entry.connect_changed(|entry| {
        println!("Text changed: {}", entry.text());
    });

    entry
}

リスト表示

[編集]
use gtk::{ListBox, ListBoxRow};

fn create_list() -> ListBox {
    let list_box = ListBox::new();

    for i in 1..5 {
        let row = ListBoxRow::new();
        let label = Label::new(Some(&format!("Row {}", i)));
        row.set_child(Some(&label));
        list_box.append(&row);
    }

    list_box
}

データバインディング

[編集]

プロパティバインディング

[編集]
use gtk::Expression;

fn bind_properties(source: &Label, target: &Entry) {
    let expression = Expression::closure(closure!(|src: &Label| {
        src.text()
    }));

    target.bind_property("text", source, "label")
        .bidirectional()
        .build();
}

ファイル操作

[編集]

ファイル選択ダイアログ

[編集]
use gtk::FileChooserDialog;

fn show_file_dialog(window: &ApplicationWindow) {
    let dialog = FileChooserDialog::builder()
        .title("Choose a file")
        .parent(window)
        .action(gtk::FileChooserAction::Open)
        .build();

    dialog.add_button("Cancel", gtk::ResponseType::Cancel);
    dialog.add_button("Open", gtk::ResponseType::Accept);

    dialog.connect_response(|dialog, response| {
        if response == gtk::ResponseType::Accept {
            if let Some(file) = dialog.file() {
                println!("Selected file: {:?}", file.path());
            }
        }
        dialog.close();
    });

    dialog.present();
}

スタイリング

[編集]

CSSの適用

[編集]
fn apply_css(widget: &impl IsA<Widget>) {
    let css_provider = CssProvider::new();
    css_provider.load_from_data(b"
        button {
            background: linear-gradient(to bottom, #3498db, #2980b9);
            color: white;
            border-radius: 4px;
            padding: 8px;
        }
    ");

    gtk::style_context_add_provider_for_display(
        &Display::default().expect("Could not connect to display"),
        &css_provider,
        gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
    );
}

エラー処理

[編集]

エラーダイアログ

[編集]
use gtk::MessageDialog;

fn show_error(window: &ApplicationWindow, message: &str) {
    let dialog = MessageDialog::builder()
        .modal(true)
        .buttons(gtk::ButtonsType::Ok)
        .message_type(gtk::MessageType::Error)
        .text(message)
        .parent(window)
        .build();

    dialog.connect_response(|dialog, _| {
        dialog.close();
    });

    dialog.present();
}

非同期処理

[編集]

非同期タスクの実行

[編集]
use glib::MainContext;
use std::thread;

fn run_async_task<F>(f: F)
where
    F: Future<Output = ()> + 'static,
{
    let main_context = MainContext::default();
    main_context.spawn_local(f);
}

// 使用例
run_async_task(async {
    // 非同期処理
    thread::sleep(Duration::from_secs(2));
    println!("Async task completed!");
});

ベストプラクティス

[編集]

アプリケーション構造

[編集]
mod ui;
mod data;
mod config;

struct App {
    window: ApplicationWindow,
    config: Config,
    ui: UiBuilder,
}

impl App {
    fn new(app: &Application) -> Self {
        // アプリケーションの初期化
    }

    fn build_ui(&self) {
        // UIの構築
    }

    fn connect_signals(&self) {
        // シグナルの接続
    }
}

リソース管理

[編集]
use gio::Resource;

fn load_resources() {
    let resource_bytes = include_bytes!("../resources/resources.gresource");
    let resource = Resource::from_data(&resource_bytes).unwrap();
    gio::resources_register(&resource);
}

まとめ

[編集]

GTK4とRustの組み合わせは、型安全で高性能なGUIアプリケーションの開発を可能にします。このガイドで紹介した基本的な概念と実装例を基に、独自のアプリケーション開発を始めることができます。