RustでGUI 〜azul編〜

コード全体はgithubの[ogata-k/GUI_cmp/example_azul](https://github.com/ogata-k/GUI_cmp/tree/master/example_azul)を参考にしてください。

# azul
azul(version ```0.1.0```)はRust```1.28```以降をサポートしているので、```rustc -vV```であなたのRustコンパイラのバージョンがサポートされているか確認してください。

## どんなクレートか
[azul](https://github.com/maps4print/azul)はMozillaのブラウザーのレンダリングエンジンを元にRustで書かれているIMGUI指向の(つまり"Immediate Mode GUI"と呼ばれるパラダイムを採用した)GUIフレームワークです。IMGUI指向を簡単にいうと"要求されたときに要求されたことだけを処理する"という感じ(間違っているかも)になります。
azulはHTMLのようなDOMスタイルを使い```widget```の構造を表し、(azul用の)CSSで装飾を処理するという形を取っています。
詳しくはazulの[Tutorial(英語)](https://github.com/maps4print/azul/wiki)や[公式サイト](https://azul.rs/)を参照してみてください。

## コード
カウントアッププログラムを書いていきます。

``` css:style.css
#label{
  color: black;
  font-size: 160px;
  background-color: yellow;
}

#button{
  font-size: 10px;
  margin: 5px;
  background-color: blue;
}

* {
   font-size: 27px;
}
```

``` rust:main.rs
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");
}
```
![azul.png](https://qiita-image-store.s3.amazonaws.com/0/167385/f9bf57f0-4866-1265-6d49-3de78edb0e82.png)


## コードの説明
まず最初に今回操作するデータの対象を構造体で用意します。
例えば次のようにです。

``` rust
// レイアウトに依存するデータモデルの定義
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ライブラリは使いやすく感じます。なのでversion```1.0.0```を超えた後の安定バージョンを期待したいところです。