OrbTk とは
Redox という Rust で書かれた UNIX ライク OS のサブプロジェクトとして開発されている Widget Toolkit です。
OrbTk の機能としては以下が謳われています。
- モダンな軽量API
- クロスプラットフォーム
- モジュール化されたクレート
- Entity-Component-System ライブラリ DCES に基づく
- 柔軟なイベントシステム
- 包括的なウィジェットライブラリ
- カスタムウィジェットのサポート
- テーマのサポート
- デバッグツールの統合
2019年12月時点でのバージョンは 0.3.1-alpha1 となっており、まだまだ開発途上ではありますが、マルチプラットフォームな GUI アプリケーションを簡単に作ることができます。
本記事では OrbTk の使い方を簡単に見ていきます。
プロジェクトの作成
Cargo でプロジェクトを作成します。
$ cargo new --bin orbtk-example Created binary (application) `orbtk-example` package $ cd orbtk-example
Cargo.toml
には、github のリポジトリ(開発ブランチ)から依存を追加します。
[dependencies] # orbtk = "0.3.1-alpha1" orbtk = { git = "https://github.com/redox-os/orbtk.git", branch = "develop" }
現時点のリリースバージョンは 0.3.1-alpha1
ですが、開発途上のため、頻繁に API が変更されています。
ここでは最新の開発ブランチを使うことにしました。
Hello OrbTk
最初にウインドウを表示するだけのアプリケーションを作りましょう。
main.rs
を以下のように変更します。
use orbtk::prelude::*; fn main() { Application::new() .window(|ctx| { Window::create() .title("OrbTk Example") .position((100.0, 100.0)) .size(300.0, 300.0) .child(TextBlock::create().text("Hello OrbTk").build(ctx)) .build(ctx) }) .run(); }
ウインドウを作成し、TextBlock を追加しただけの簡単なものです。
実行します。
$ cargo run
以下のようなウインドウが表示されます。
Template によるレイアウト定義
もう少し複雑な例として、Template
によるレイアウトを定義してみます。
use orbtk::prelude::*; impl Template for MainView { fn template(self, _: Entity, ctx: &mut BuildContext) -> Self { self.name("MainView").child( Stack::create() .child( TextBlock::create() .margin((0.0, 0.0, 0.0, 8.0)) .text("Stack vertical") .selector("h1") .build(ctx), ) .child( Button::create() .text("center") .horizontal_alignment("center") .build(ctx), ) .build(ctx), ) } } widget!(MainView); fn main() { Application::new() .window(|ctx| { Window::create() .title("OrbTk Example") .position((100.0, 100.0)) .size(300.0, 300.0) .child(MainView::create().build(ctx)) .build(ctx) }) .run(); }
Template
にてコンポーネントを宣言的に定義し、widget!
マクロに渡します。
ウインドウの子要素として MainView::create()
として渡すことで、定義した UI を追加します。
先ほどと同様に実行すると以下のような画面が表示されます。
イベント処理
先ほどの例にボタンのクリック時のイベントを追加してみましょう。
main.rs
を以下のように変更します。
use orbtk::prelude::*; use std::cell::Cell; #[derive(Debug, Copy, Clone)] enum Action { Increment, } #[derive(Default, AsAny)] pub struct MainViewState { count: Cell<usize>, action: Cell<Option<Action>>, } impl MainViewState { fn action(&self, action: impl Into<Option<Action>>) { self.action.set(action.into()); } } impl State for MainViewState { fn update(&mut self, _: &mut Registry, ctx: &mut Context<'_>) { if let Some(action) = self.action.get() { match action { Action::Increment => { let result = self.count.get() + 1; self.count.set(result); ctx.child("text-block").set("text", String16::from(result.to_string())); } } self.action.set(None); } } } impl Template for MainView { fn template(self, id: Entity, ctx: &mut BuildContext) -> Self { self.name("MainView").child( Stack::create() .child( TextBlock::create() .margin((0.0, 0.0, 0.0, 8.0)) .text("Stack vertical") .selector( Selector::from("text-block").id("text-block"), ) .build(ctx), ) .child( Button::create() .text("center") .horizontal_alignment("center") .on_click(move |states, _| -> bool { state(id, states).action(Action::Increment); true }) .build(ctx), ) .build(ctx), ) } } widget!(MainView<MainViewState> { counter: usize }); fn main() { Application::new() .window(|ctx| { Window::create() .title("OrbTk Example") .position((100.0, 100.0)) .size(300.0, 300.0) .child(MainView::create().build(ctx)) .build(ctx) }) .run(); } fn state<'a>(id: Entity, states: &'a mut StatesContext) -> &'a mut MainViewState { states.get_mut(id) }
実行すると以下のようにボタンクリックをカウントします。
先の例を簡単に説明していきます。
ボタンクリック時のアクションを以下の enum にて定義しています。
#[derive(Debug, Copy, Clone)] enum Action { Increment, }
このアクションは、View の状態変更時に以下のパターンマッチにて処理しています。
impl State for MainViewState { fn update(&mut self, _: &mut Registry, ctx: &mut Context<'_>) { if let Some(action) = self.action.get() { match action { Action::Increment => { let result = self.count.get() + 1; self.count.set(result); ctx.child("text-block").set("text", String16::from(result.to_string())); } } self.action.set(None); } } }
MainView には以下のようなクリックカウントとボタンクリック時のアクションを状態として定義しています。
#[derive(Default, AsAny)] pub struct MainViewState { count: Cell<usize>, action: Cell<Option<Action>>, } impl MainViewState { fn action(&self, action: impl Into<Option<Action>>) { self.action.set(action.into()); } }
ボタンには on_click
でクリック時に Increment
アクションを設定しています。
Button::create() .text("center") .horizontal_alignment("center") .on_click(move |states, _| -> bool { state(id, states).action(Action::Increment); true }) .build(ctx),
ボタンクリックにより MainViewState
にアクション内容を反映し、MainViewState
の update()
にてアクションに応じた処理を行う流れになります。
まとめ
OrbTk を使った簡単なアプリケーション作成の流れを見ました。
現時点ではドキュメントなどはほとんど整備されておらず、API の変更も頻繁です。
なので、おもちゃとして触る程度になりますが、将来的には Linux で言う GTK のような扱いになるはずなので、少しずつでもウォッチしていきたいですね。

- 作者:Jim Blandy,Jason Orendorff
- 出版社/メーカー: オライリージャパン
- 発売日: 2018/08/10
- メディア: 単行本(ソフトカバー)

- 作者:Tim McNamara
- 出版社/メーカー: Manning Publications
- 発売日: 2020/05/12
- メディア: ペーパーバック