RustのGUIライブラリとその中でのgtk-rsの位置付け
今現在、RustでGUIアプリケーションを開発するためのライブラリ/フレームワークは多種多様です。
突出して優れたものがあるわけでもなく、これを使えば間違いないと言えるものはありません。このような状況では、何を使えばいいか見当がつきませんが、それでもやりたいことから多少の取捨選択はできます。
百家争鳴のライブラリ群から類型をみると、大きく分けて自前スタックのタイプと既存のライブラリのバインディングになっているものがあります。
自前スタックのタイプはウィンドウ内のボタンといったウィジェットなども含めてすべて自前で実装しているものです。icedやeguiなどがあります。これらを基礎づけているのがwinitというライブラリで、ウィンドウを作る部分を担当しています。winitはRustの対応プラットフォームの多さを活かして、デスクトップはもちろん、モバイルやWebでも動きます[1]。一方でウィンドウ以外の面倒は看ないのでシステムとの統合などの複雑なことをしようとすると機能が不足しはじめますし、ウィンドウ内での出来事、例えば日本語の描画といった処理は各GUIライブラリが実装することになり、品質がバラつきます。
既存のライブラリのバインディングはウィンドウや描画のしくみを既存のものに頼っているものです。以前記事になったtauriもこちらに分類されます。これらのライブラリは、多くはCで書かれた外部のライブラリに頼るので、ユーザにそのライブラリをインストールしてもらう必要がありますし、Web上で動かすのも難しいでしょう。しかしその分、歴史を刻んだライブラリの機能を十二分に使えます。
既存のGUIライブラリの二大巨頭といえばGTKとQtでしょう。QtはC++で書かれているからか、有力なRustバインディングがないのが現状です。もう一方のGTKには、よくメンテナンスされたライブラリがあります。それが本記事で扱うgtk-rsです。GTKにはこれまで広く使われてきたGTK 3と現在の最新版であるGTK 4がありますが、gtk-rsはそのどちらもサポートしています。最近はGTK 4が普及してきたことを受けて、GTK 4のバインディングに力が入っています[2]。さらにその上に作られたrelm4などエコシステムが広がっています。
少々前置きが長くなりましたが、成熟した既存ライブラリであるGTKを、よくメンテナンスされたラッパであるgtk-rsから使っていきましょう。Rustから使うことで安全かつ高速に動作するアプリケーションを構築できるようになります。本記事では最新版であるGTK 4を使っていきます。3とはAPIが異なる点があることに注意してください。
GTK 4のインストール
本記事ではGTK 4.
Windowsの場合はインストール手順が複雑です。以下の資料を参考に挑んでみてください。
Windows - GUI development with Rust and GTK 4
Hello, gtk-rs
何はともあれ、まずは使ってみましょう。
コンソールで以下のコマンドを叩き、gtk4の0.
$ cargo new hello-gtk $ cd hello-gtk $ cargo add [email protected] --rename gtk
ライブラリを使うときにgtk4
と書くのはくどいのでgtk
にリネームしておきます。
これでsrc/
に以下のコードを書きます。
// 1
use gtk::prelude::*;
fn main() {
// 2
let application =
gtk::Application::new(Some("com.github.keens.gtk-examples.basic"), Default::default());
// 3
application.connect_activate(build_ui);
// 4
application.run();
}
fn build_ui(application: >k::Application) {
// 5
let window = gtk::ApplicationWindow::new(application);
// 6
window.set_title(Some("First GTK Program"));
window.set_default_size(350, 70);
// 7
let button = gtk::Button::with_label("Click me!");
// 8
button.connect_clicked(|_| {
println!("Clicked!");
});
// 9
window.set_child(Some(&button));
// 10
window.show();
}
このプロジェクトを走らせてみましょう。
$ cargo run
すると、このようにボタンが1つだけ表示されたシンプルなアプリケーションが立ち上がります。
「Click me!」
main.rsの解説
上記のコードを詳しくみていきましょう。コメントに書いた番号を参照しながら追ってみてください。
1でgtk::
をインポートします。GTKはさまざまなライブラリを組み合わせて成り立ってますが、今回のようなシンプルなアプリケーションではgtk
のプレリュードのみで済んでいます。
main
の2でアプリケーションを初期化し、作るUIを3で接続し、4でアプリケーションを実行します。ここはほとんど定型文です。
build_
に移ります。アプリケーションコードの大部分はここに書くことになるでしょう。
5でアプリケーションウィンドウを作り、6でウィンドウの設定を初期化します。ここもやや定型文に近いです。
7、8、9が主要な部分です。7でボタンを作り、8で押されたときのコールバックを登録します。9で5のときに作ったwindow
にbutton
を追加します。
10で最後に5のときに作ったウィンドウを表示します。
このくらいの内容であれば元々がCで書かれたライブラリであることを意識することなくコードを書けますね。
ウィジェットをいろいろ使ってみよう
このままいくつかGTKが用意しているGUIのパーツを使ってみましょう。GUIのパーツはウィジェットと呼ばれています。GTKで使えるウィジェットは公式サイトのVisual Indexで一覧できます。
Boxでウィジェットを並べる
これから、いくつかウィジェットを含んだアプリケーションを作ってみます。そのためにウィジェットを並べる入れ物を用意しましょう。Box
というウィジェットを使うと複数のウィジェットを並べられます。
gtk::
に限らず、GTKのオブジェクトを初期化する方法はいくつか用意されています。前述したgtk::
はあまりカスタマイズしなかったのでwith_
関数で初期化しましたが、いろいろ設定をする場合はビルダーパターンで必要な設定を与えながら構築できます。gtk::
にもビルダーが用意されています。gtk::
はいくつか設定したい項目があるのでこれを使っていきましょう。以下のように設定していきます。
gtk::Box::builder()
// アイテムを縦に並べる
.orientation(gtk::Orientation::Vertical)
// アイテムを左(始端)寄せする
.halign(gtk::Align::Start)
// 見た目を整える
.spacing(6)
.margin_bottom(6)
.margin_top(6)
.margin_start(6)
.margin_end(6)
//ビルドする
.build()
内容はコメントに書いてある通りです。これらの設定は見た目を整えるためにやっているのでウィジェットの動作確認という意味では好きに変更しても差し障りありません。
さて、このボックスにウィジェットを追加していくための準備を整えましょう。build_
関数でwindow
の子に設定します。
fn build_ui(application: >k::Application) {
let window = gtk::ApplicationWindow::new(application);
window.set_title(Some("Gallery"));
window.set_default_size(350, 70);
let vbox = /* 先程のgtk::Boxのコード */;
window.set_child(Some(&vbox));
// これからここにコードを書く
window.show();
}
// これからここに関数を追加する
ここからいろいろなウィジェットをボックスに追加していきます。例として先に作ったボタンを挙げます。コードの見通しのために一度関数を作り、以下のようにボタンを作ります。
fn build_button() -> gtk::Button {
let button = gtk::Button::with_label("Click me!");
button.connect_clicked(|_| {
println!("Clicked!");
});
button
}
そしてbuild_
内では以下のようにボタンをボックスに追加します。
vbox.append(&build_button());
コード全体は以下のようになります。
fn build_ui(application: >k::Application) {
let window = gtk::ApplicationWindow::new(application);
window.set_title(Some("Gallery"));
window.set_default_size(350, 70);
let vbox = /* 先程のgtk::Boxのコード */;
window.set_child(Some(&vbox));
vbox.append(&build_button());
window.show();
}
fn build_button() -> gtk::Button {
let button = gtk::Button::with_label("Click me!");
button.connect_clicked(|_| {
println!("Clicked!");
});
button
}
以後、build_
関数に相当するもののみを掲載するので、適宜build_
内でvbox
に追加していってください。
Scale
gtk::
でスケール(スライダー)を作れます。
fn build_scale() -> gtk::Scale {
let scale = gtk::Scale::builder()
.draw_value(true)
// 1
.adjustment(
>k::Adjustment::builder()
.lower(0.0)
.upper(100.0)
.value(50.0)
.step_increment(1.0)
.page_increment(10.0)
.build(),
)
// 2
.digits(0)
.round_digits(0)
.build();
// 3
scale.connect_value_changed(|s| {
println!("value changed: {}", s.value());
});
scale
}
1でスケールの動く範囲やキーボード操作などをadjustment
で指定します。lower
とupper
で動く範囲、value
で初期値を指定します。step_
とpage_
でキーボードなどで操作したときに動く量を指定しており、矢印キーなどで操作するときはstep_
の値が、PageUp/page_
の値が使われます。
2で値の小数点以下何桁まで扱うかを制御します。digits
で表示される値の、round_
でプログラム内で扱う実際の値の桁数を変えます。
3でスケールが変更されたときに標準出力にその値を出力するようにします。後に説明しますが、スケールが変更されたときにvalue-changed
のシグナルが発行されるのでそれに接続します。
Switch
gtk::
で(トグル)スイッチを作ります。
fn build_switch() -> gtk::Switch {
let switch = gtk::Switch::builder().halign(gtk::Align::End).build();
switch.connect_active_notify(|s| println!("state changed: {:?}", s.is_active()));
switch
}
そろそろ馴れてきたかと思います。halign
で右(終端)寄せ表示のトグルスイッチを作り、変更されたら標準出力にその値を出力します。
その他のウィジェット
ここまでボタン類を紹介しましたが、その他にも入力欄や他のウィジェットを囲うフレームなどいろいろなウィジェットがあります。
fn build_password_entry() -> gtk::PasswordEntry {
gtk::PasswordEntry::new()
}
fn build_frame() -> gtk::Frame {
let frame = gtk::Frame::builder()
.label("Frame")
.child(&build_switch())
.build();
frame
}
ここまでのウェジェットを追加したアプリケーションを起動すると以下のような画面になるはずです。
スケールやスイッチはシグナルを接続しているので、動作するとターミナルに出力されます。動かしてみましょう。
まとめ
今回はgtk-rsの紹介と、軽い動作確認でした。次回でGTKのライブラリ構造やRustからの使い方を試していきます。
- 本記事で登場したコードは以下に置いておきます。
- https://
github. com/ KeenS/ gtk-examples