コード全体はgithubのogata-k/GUI_cmp/example_azulを参考にしてください。
azul
azul(version 0.1.0)はRust1.28以降をサポートしているので、rustc -vVであなたのRustコンパイラのバージョンがサポートされているか確認してください。
どんなクレートか
azulはMozillaのブラウザーのレンダリングエンジンを元にRustで書かれているIMGUI指向の(つまり"Immediate Mode GUI"と呼ばれるパラダイムを採用した)GUIフレームワークです。IMGUI指向を簡単にいうと"要求されたときに要求されたことだけを処理する"という感じ(間違っているかも)になります。
azulはHTMLのようなDOMスタイルを使いwidgetの構造を表し、(azul用の)CSSで装飾を処理するという形を取っています。
詳しくはazulのTutorial(英語)や公式サイトを参照してみてください。
コード
カウントアッププログラムを書いていきます。
# label{
color: black;
font-size: 160px;
background-color: yellow;
}
# button{
font-size: 10px;
margin: 5px;
background-color: blue;
}
* {
font-size: 27px;
}
extern crate azul;
use azul::{prelude::*, widgets::{label::Label, button::Button}};
use azul::window_state::WindowSize;
// レイアウトに依存するデータモデルの定義
struct DataModel {
count_num: usize,
}
// レイアウトの実装
impl Layout for DataModel {
// render関数
fn layout(&self, info: LayoutInfo<Self>) -> Dom<Self> {
// domでビルドするビルダーパターンのイメージでwidgetの作成
let label = Label::new(format!("{}", self.count_num)).dom().with_id("label");
// domにしてから関数を設定
let button = Button::with_label("カウントアップ +1").dom().with_id("button")
.with_callback(On::MouseUp, Callback(update_counter));
// HTMLのような感じでレイアウトの部品となるDomを返す
Dom::new(NodeType::Div)
.with_child(label)
.with_child(button)
}
}
// appの情報とイベントの情報を受け取って計算したあとにスクリーンに状態を伝搬する関数
fn update_counter(app_state: &mut AppState<DataModel>, _event: &mut CallbackInfo<DataModel>) -> UpdateScreen {
app_state.data.modify(|state| state.count_num += 1);
// 再描画の必要が無いときはRedrawの代わりにDontRedrawを使う
Redraw
}
fn main() {
// GUIのルートの作成
// 引数はレイアウトを決定する初期条件とログやエラー処理に関するデータ構造
let app = App::new(DataModel {count_num: 0}, AppConfig::default());
// Windowの設定
let mut window_options = WindowCreateOptions::default();
window_options.state.title = "カウントアップ".to_string();
let mut window_size = WindowSize::default();
window_size.dimensions = LogicalSize::new(400.0, 300.0); // width * height
window_options.state.size = window_size;
// CSSの設定
macro_rules! CSS_PATH { () => (concat!(env!("CARGO_MANIFEST_DIR"), "/src/style.css")) }
let css = css::override_native(include_str!(CSS_PATH!())).expect(&format!("failed: override CSS by {}", CSS_PATH!()));
app.run(Window::new(window_options, css).expect("failed: make window")).expect("failed: start running application");
}
コードの説明
まず最初に今回操作するデータの対象を構造体で用意します。
例えば次のようにです。
// レイアウトに依存するデータモデルの定義
struct DataModel {
count_num: usize,
}
このデータモデルをターゲットにレイアウトを用意(実装)して、データを扱うDOMを作成できるようにしておきます。ButtonのDOMのメソッドのマウスの状態とコールバック関数を受けるwith_callbackに見られるように、コールバック関数も簡単に設定できます。コールバック関数であるupdate_counterは発生したイベントを元に、呼び出し元が保持するデータモデルを書き換えるという形の関数です。返り値は再描画を要求するRedrawと再描画を要求しないDontRedrawの二値からなる列挙型UpdateScreenとなります。このUpdateScreenの値を見て再描画の必要性を判断することで、azulは不必要な再描画を減らすことができます。
次はmain関数です。
基本的な構造は他のよくあるGUIライブラリと同じ書き方になります。
今回appの初期条件をべた書きしています。他のファイルから読み込んだ値で初期化するという形で初期条件を与えてやってもいいと思います。
初期化したばかりのappにはwindowの設定と装飾であるcssが設定されていないのでそれぞれ設定してやります。
そして最後にappを実行して終了となります。
所感
正直、まだまだ発展途中の上に、依存するクレートが多いせいかコンパイルは重いです。また、windowのサイズなどの設定はDefault::default関数があるもののネストが少し深く、少し面倒くさいです。set〇〇のようなメソッドがほしいところです。(見つけられてないだけかもしれませんが。)
ただレイアウトや装飾に関する情報がCSSで大体記述できるのは強いと思います。IMGUI指向のレイアウトは一般に複雑にしにくいといわれていますが、このCSSによる記述が幾分か解決してくれるように感じます。
ただ、CSSに関してはうまく適用されないことも多く、まだまだ未熟だと思われます。
まとめ
まだまだ未熟な点が多く、ドキュメントもほとんど無いため実用には程遠く感じます。しかし、IMGUI指向のCSSでレイアウトが強化されたDOM型GUIライブラリは使いやすく感じます。なのでversion1.0.0を超えた後の安定バージョンを期待したいところです。
