普段使うツールをRustで作る場合、コマンドラインベースのものが多いと思います。それでも、Rustを使ってデスクトップアプリを作ることもできます。今回は、実績のあるGUIツールキットの「Tcl/Tk」をラップしたクレートを使って簡単なGUIを作成してみましょう。
GUIツールキット「Tcl/Tk」のインストール
Rustのライブラリを集約したcrates.ioを見ると、多くのGUIライブラリが存在します。Rustはさまざまな環境で動作することを想定していることもあり、「これこそGUIの定番」というものはありません。多くの選択肢があり、用途に応じて使い分けるのが正しい作法です。
WebブラウザベースのTauri、ゲーム開発と相性の良いegui、GTK+を利用するGTK-rs、Tcl/Tkをラップしたライブラリのtcl/tkなど、いろいろなクレートがあります。今回は、この中からTcl/TkをラップしたGUIライブラリの「tcl」と「tk」クレートを利用してみましょう。
このクレートは、伝統的なGUIツールキット「Tcl/Tk」のラッパーです。そのため、別途Tcl/Tkのインストールが必要となります。OSごとにTcl/Tkをインストールしましょう。
なお、Tcl/Tkについては、姉妹連載のこちらで詳しく紹介しています。
【WindowsでTcl/Tkをインストールする場合】
Windowsであれば、Rustに加えて、ActiveTclのインストールが必要になります。なお、ActiveTclをダウンロードする際には、ActiveStateのアカウントを作成する必要がありますが、無料でダウンロードできます。インストーラーをダウンロードしたら、ダブルクリックでActiveTclをインストールします。
【macOSでTcl/Tkをインストールする場合】
macOSの場合は、Homebrewを使ってTcl/Tkをインストールします。Homebrewをインストールした上で、ターミナルにて下記のコマンドを実行します。
brew install tcl-tk
簡単なテストプロジェクトを作ろう
最初に、簡単なテストプログラムを作って動作を確認してみましょう。ターミナル(WindowsならPowerShell、macOSならターミナル.app)を起動して、次のコマンドを実行しましょう。
# ディレクトリを作成してプロジェクトを初期化
mkdir hello_tk
cd hello_tk
cargo init
# クレートを追加
cargo add tcl tk
次にメインプログラム「src/main.rs」を記述しましょう。プログラムを以下のように書き換えましょう。こちらからもコピーすることができます。
use tk::*;
use tk::cmd::*;
fn main() -> TkResult<()> {
// Tkの初期化 --- (*1)
let tk = make_tk!()?;
let root = tk.root();
// ウィンドウ上にフレームを作成 --- (*2)
let content = root
.add_ttk_frame(())?
.pack(())?;
content.configure( -padding(12) )?; // 余白を設定
// ラベルを配置 --- (*3)
content
.add_label("lbl1" -text("Tkのテスト"))?
.pack(())?;
// ボタンを配置 --- (*4)
content
.add_button("btn1" -text("閉じる") -command("destroy ."))?
.pack( -side("bottom") )?;
// メインループ --- (*5)
Ok( main_loop() )
}
プログラムを実行するために、ターミナルで下記のコマンドを実行しましょう。
cargo run
なお、Windowsでエラーが出て失敗する場合、下記の点を確認してください。
(1)ActiveTclをデフォルト設定でインストールし、PATHにwish.exeのパスを追加する
(2)libclangが見つからないというエラー『 thread 'main' panicked at 'Unable to find libclang: "couldn't find any valid shared libraries matching: ['clang.dll', 'libclang.dll']』が出る場合には、Chocolateyをインストールした上で管理者権限でPowerShellを起動して以下のコマンドを実行し、最新版のLLVMをインストールしてください。
choco install llvm
(3)stdio.hがないというエラーが出る場合、Visual Studioをインストールして、「C++によるデスクトップ開発」にチェックを付けてインストールする必要があります。
それでも、エラーが解決しない場合、こちらにフォロー記事を投稿しましたのでご確認ください。
プログラムが正しくコンパイルされると、次のようなウィンドウが表示されます。ウィンドウに、ラベルとボタンが配置され、「閉じる」ボタンを押すとプログラムを終了します。
プログラムを確認してみましょう。(*1)ではTkを初期化します。(*2)ではウィンドウ上にフレームを作成して配置します。configureメソッドを使ってフレームに余白を設定します。
(*3)では、add_labelメソッドでラベルを作成して、packメソッドで配置します。(*4)では、add_buttonメソッドでボタンを作成してpackメソッドで配置します。
tkクレートのadd_labelやadd_buttonのメソッドには、ウィジェットのIDに加えて、初期パラメータを指定できます。パラメータには、-padding(…)や-text(…)などのように値を指定します。
(*5)ではメインループを実行します。これによって、ウィンドウが動作します。
単位変換プログラムを作ろう
次に、単位変換を行うプログラムを作ってみましょう。分かりやすく、単位変換プログラムの定番である、インチをセンチに変換するプログラムを作ってみましょう。
プロジェクトを初期化するために、ターミナルで下記のコマンドを実行しましょう。
mkdir unit_converter
cd unit_converter
cargo init
cargo add tcl tk
そして、メインプログラム「src/main.rs」に下記のプログラムを記述しましょう。こちらからコピーすることもできます。
use tk::*;
use tk::cmd::*;
use tcl::*;
fn main() -> TkResult<()> {
// Tkの初期化
let tk = make_tk!()?;
let root = tk.root();
root.set_wm_title("インチからセンチへの変換")?;
// ウィンドウ上にフレームを作成
let c = root
.add_ttk_frame(())?
.pack(())?;
c.configure( -padding(12) )?; // 余白を設定
// Gridレイアウトを利用してウィジェットを配置 --- (*1)
c.add_ttk_label( "inch_lbl" -text("インチ") )?
.grid( -column(1) -row(1) -sticky("ne") )?;
c.add_entry( "inch_entry" -textvariable("inch_var") )?
.grid( -column(2) -row(1) -sticky("nw") )?;
c.add_button( "calc" -text("計算") -command("calculate") )?
.grid( -column(3) -row(1) -sticky("w") )?;
c.add_ttk_label( "cm_lbl" -text("センチ") )?
.grid( -column(1) -row(2) -sticky("ne") )?;
c.add_entry( "cm_entry" -textvariable("cm_var") )?
.grid( -column(2) -row(2) -sticky("nw") )?;
// Entryに初期値を設定 --- (*2)
tk.run(("set", "inch_var", "30.0"))?;
// 計算関数を定義 --- (*3)
#[proc] fn calculate() -> TkResult<()> {
let interp = tcl_interp!();
let inch: f64 = interp.get_double("inch_var")?;
let cm = inch * 2.54;
interp.set_double("cm_var", cm);
Ok(())
}
// 定義した関数を登録 --- (*4)
unsafe{ tk.def_proc( "calculate", calculate ); }
// メインループ
Ok( main_loop() )
}
プログラムを実行するには、ターミナルで下記のコマンドを実行します。
cargo run
すると、次のようなウィンドウが表示されます。インチの右横にあるテキストボックスに数値を入力して「計算」ボタンを押すと、インチをセンチに変換してテキストボックスに表示します。
プログラムを確認してみましょう。(*1)ではGridレイアウトを利用して、各種ウィジェットを配置します。(*2)では、Entryに初期値を配置します。(*3)では、計算関数を定義します。インチの値を取得して、センチに変換して値を設定します。(*4)では、定義した関数を登録します。