Rustでconrodを使って動く円の個数をボタンで増減させる

はじめに
======

[Rustでconrodを使って赤い円をアニメーションさせる](https://qiita.com/mokemokechicken/items/e1c55c2a0b77d6b52afd) からの続きです。

今回は、

- 動かす円の個数を動的に変更する(widget ID の配列を使う)
- ボタンによって上記を行う(UIイベントを処理する)

という辺りをやっていこうと思います。
この辺までできると割と色々と応用が効くように思います。

コード全体は[こちら](https://github.com/mokemokechicken/rust_gui_sandbox/tree/915acca35ff94f6fcc53bf38683d09112af5b9ba/src)になります。

Rustでconrodを使って動く円の個数をボタンで増減させる
==========================================
## ポイント
### 動的に増減するWidget Objectは hogehoge[] で宣言する
今回は `circles[]` を宣言しています。すると内部で`Vector` な感じで定義されるみたいです(Macroマジック)。

```rust:animation.rs
widget_ids! {
    struct Ids {
        circles[],
        button_up,
        button_down,
    }
}
```

### ButtonイベントのHandlingとWidget Objectの増減のさせかた

Buttonイベントは以下のようにHandlingするっぽいです。
なんか斬新。

```rust
for click in widget::Button::new()...set() { /*クリックされたときの処理*/ }
```

Widget Object (今回のcircles) を増減させるときは、 

```rust
ids.circles.resize(size, id_generator)
``` 
を使うようです。
この `size` を実際の円のサイズを渡せば良いです。生成されるコードを見ると、
- **5 -> 10 に増えるとき**: 後ろに5個IDが追加されているっぽい
- **10 -> 5 に減るとき**: 後ろの5個のIDが削除されているっぽい
です。

あとは、初めてのIteratorって感じで `iter_mut().enumerate()` を見つけるのに苦労したりしました...

<details><summary>animation.rs の 抜粋</summary><div>

```rust:animation.rs
    pub fn next_frame(&mut self, ref mut ui: conrod_core::UiCell) {
        self.n_frame += 1;
        let (width, height) = (ui.win_w, ui.win_h);

        for (i, ball) in self.balls.iter_mut().enumerate() {
            ball.x += ball.dx;
            ball.y += ball.dy;
            if ball.x < -width/2. || width/2. < ball.x {
                ball.dx = -ball.dx;
            }
            if ball.y < -height/2. || height/2. < ball.y {
                ball.dy = -ball.dy;
            }

            widget::Circle::fill(10.)
                .x_y(ball.x, ball.y)
                .color(conrod_core::color::RED)
                .set(self.ids.circles[i], ui);
        }

        for _click in widget::Button::new()
            .mid_top()
            .w_h(100.0, 40.0)
            .label("↑増やす")
            .label_font_size(12)
            .set(self.ids.button_up, ui) {
            self.balls.push(Ball::new(&mut self.thread_rng, width, height));
            self.ids.circles.resize(self.balls.len(), &mut ui.widget_id_generator());
        }

        for _click in widget::Button::new()
            .w_h(100.0, 40.0)
            .label("↓減らす")
            .label_font_size(12)
            .set(self.ids.button_down, ui) {
            self.balls.pop();
            self.ids.circles.resize(self.balls.len(), &mut ui.widget_id_generator());
        }
    }
```

</div></details>

### Buttonラベルに文字を使うためのFontを設定する
こんな感じで設定できるみたいです。 `find_folder` は 外部crate です。

```rust:main.rs
    let assets = find_folder::Search::KidsThenParents(3, 5).for_folder("assets").unwrap();
    let font_path = assets.join("fonts/ipam.ttf");
    ui.fonts.insert_from_file(font_path).unwrap();
```

### winit イベントを conrod イベントに変換する
winit というベースとしているライブラリのイベントを、conrod用に変換してUI系に渡す必要があるっぽいです。とりあえず、サンプルを参考に以下のように書いておくと良いみたいです。

```rust:main.rs
            // Use the `winit` backend feature to convert the winit event to a conrod one.
            if let Some(event) = event::convert_event(event.clone(), &display) {
                ui.handle_event(event);
            }
```

さいごに
======
とりあえず動いたので良しとします。
実行効率の良いコードなのか、というと円が少ない状態でもそれなりにCPUリソースを消費(10%~15%)しているので判断しづらいですが、 400x400 を 60fps で更新するとこんなものなのかなぁ、という気がしないでもないです。