Nannou
Nannou is an open-source, creative-coding toolkit for Rust.
電子アートやデザイン等々を作成するためのライブラリです。Processingの影響を受けているみたいです。
基本的な図形の描画はもちろん、音声の出力やUIの描画もサポートするなど、機能は多岐に渡ります。
とりあえず円を出力してみる
$ cargo new hello_nannou
$ cd hello_nannou
[package]
name = "hello_nannou"
version = "0.1.0"
edition = "2018"
[dependencies]
nannou = "0.13.1"
use nannou::prelude::*;
struct Model {}
fn main() {
nannou::app(model)
.event(event)
.simple_window(view)
.run();
}
fn model(_app: &App) -> Model {
Model {}
}
fn event(_app: &App, _model: &mut Model, _event: Event) {
}
fn view(app: &App, _model: &Model, frame: Frame) {
let draw = app.draw();
draw.ellipse()
.color(STEELBLUE);
draw.to_frame(app, &frame).unwrap();
}
これを cargo run
すると...
中央に円が描画されます。
解説
fn main() {
nannou::app(model)
.event(event)
.simple_window(view)
.run();
}
main関数に注目すると、nannou::app
で得られたインスタンスに event
simple_window
run
の順でメソッドを実行しています。それぞれの引数に model
event
view
という関数を渡しています。
fn model(_app: &App) -> Model {
Model {}
}
model()
では、アプリケーションの状態を保持しておくためのModelを初期化しています。先のサンプルでは特に状態持つ必要がなかったため、空のstructのインスタンスを返しています。
fn event(_app: &App, _model: &mut Model, _event: Event) {
}
event()
では、キーボードやマウスなどのイベントを受け取ったときに呼ばれる関数です。この関数内でイベントをハンドリングして、Modelを更新することで後述するViewに対して変化を与えます。
fn view(app: &App, _model: &Model, frame: Frame) {
let draw = app.draw();
draw.ellipse()
.color(STEELBLUE);
draw.to_frame(app, &frame).unwrap();
}
そして最後に view()
です。
app.draw()
で一枚のキャンパスを模したインスタンスが取得出来ます。
draw.ellipse()
.color(STELLBLUE);
ellipse()
でキャンパスの中央に対して円を定義し、
draw.to_frame(app, &frame).unwrap();
で描画します。
簡単なアニメーションを実装する
状態によって変化を起こさない簡単なアニメーションであれば、view()
だけで実装が可能です。
fn view(app: &App, _model: &Model, frame: Frame) {
let t = app.time;
let draw = app.draw();
let x = 4.0 * t as f32;
let point = pt2(x.cos(), x.sin()) * 100.0;
draw.ellipse()
.color(STEELBLUE)
.w(200.0)
.h(200.0)
.x_y(point.x, point.y);
draw.to_frame(app, &frame).unwrap();
}
app.time
でアプリケーションが起動してから経過した時間(f32)が取得できるので、これをタイマーとしてviewを変化させています。
let x = 4.0 * t as f32;
let point = pt2(x.cos(), x.sin()) * 100.0;
取得したタイマーをもとに、sin()
, cos()
を使って二次元の点を導出して、
draw.ellipse()
.color(STEELBLUE)
.w(200.0)
.h(200.0)
.x_y(point.x, point.y);
得られた点で円の位置を決定します。これを実行することで、
円がくるくる移動します。
Eventを扱う
event(app: &App, model: &mut Model, event: Event)
はアプリケーションに対するイベントが発生するたびに呼び出されます。enum Event
は以下のような定義になっており、
pub enum Event {
WindowEvent {
id: window::Id,
simple: Option<WindowEvent>,
},
DeviceEvent(winit::event::DeviceId, winit::event::DeviceEvent),
Update(Update),
Suspended,
Resumed,
}
キーが押された、マウスが移動したなどのイベントはWindowEventでパターンマッチをすることで取得できます。
先程定義した関数event
にUp
もしくはDown
キーが押されたことを検出する機構を実装してみます。
fn event(_app: &App, _model: &mut Model, event: Event) {
match event {
Event::WindowEvent{ id: _id, simple: window_event } => {
if let Some(KeyPressed(key)) = window_event {
match key {
Key::Up => println!("Pressed Up!!!"),
Key::Down => println!("Pressed Down!!!"),
_ => {},
}
}
}
_ => {},
}
}
Modelを使ってアニメーションを変化させる
Modelに回転するスピードをもたせてみましょう。
struct Model {
speed: f32
}
fn model(_app: &App) -> Model {
Model {
speed: 4.0
}
}
Modelにspeedというフィールドを新たに定義し、model()
で初期化するように変更しました。更に、
fn view(app: &App, model: &Model, frame: Frame) {
// 省略
let x = model.speed * t as f32;
let point = pt2(x.cos(), x.sin()) * 100.0;
// 省略
}
view()
でModel
に定義したspeed
を参照するようにしました。この時点でビルドは変わらず通り、特にアニメーションは変化が無いはずです。
ここで先程追加したキー入力を出力する機構に少し手を加えます。
fn event(_app: &App, model: &mut Model, event: Event) {
match event {
Event::WindowEvent{ id: _id, simple: window_event } => {
if let Some(KeyPressed(key)) = window_event {
match key {
Key::Up => { model.speed = model.speed + 1.0 },
Key::Down => { model.speed = model.speed - 1.0 },
_ => {},
}
}
}
_ => {},
}
}
Upキーを押したときにspeedが1.0加算され、Downキーを押したときにspeedが1.0減算されるように変更しました。
この状態でビルド、実行すると...
Up, Downキーを押すたびに円が回転する速さが変わります。キーを押した時点での円の位置を考慮していないため、キーを押すたびに円の位置がズレますがそこはご愛嬌。
ソースコード全体
use nannou::prelude::*;
struct Model {
speed: f32
}
fn main() {
nannou::app(model)
.event(event)
.simple_window(view)
.run();
}
fn model(_app: &App) -> Model {
Model {
speed: 5.0
}
}
fn event(_app: &App, model: &mut Model, event: Event) {
match event {
Event::WindowEvent{ id: _id, simple: window_event } => {
if let Some(KeyPressed(key)) = window_event {
match key {
Key::Up => { model.speed = model.speed + 1.0 },
Key::Down => { model.speed = model.speed - 1.0 },
_ => {},
}
}
}
_ => {},
}
}
fn view(app: &App, model: &Model, frame: Frame) {
let t = app.time;
let draw = app.draw();
draw.background().color(BLACK);
let x = model.speed * t as f32;
let point = pt2(x.cos(), x.sin()) * 100.0;
draw.ellipse()
.color(STEELBLUE)
.w(200.0)
.h(200.0)
.x_y(point.x, point.y);
draw.to_frame(app, &frame).unwrap();
}
まとめ
描画方法、アニメーション、キーイベントの扱いなどを一通り実装してみました。
nannouでは線、多角形、円など基本的な図形はもちろん、スライダーやXYパッドなどのUIも利用可能です。
公式ドキュメントやGithubレポジトリのexamplesが非常に充実しているので、実装もサクサク進めやすいです。ぜひNannouを使ってカッコいい作品、アプリケーションを開発しましょう!