├── .gitignore ├── .vscode └── settings.json ├── Cargo.toml ├── README.md ├── assets ├── easing.gif ├── hello_world.gif ├── morph.gif ├── morph_text.gif └── shapes.gif ├── examples ├── Cargo.toml ├── easing.rs ├── hello_world.rs ├── morph.rs ├── morph_text.rs ├── shapes.rs └── temp.rs └── noon ├── Cargo.toml └── src ├── animation ├── builder.rs ├── color.rs ├── mod.rs ├── path.rs └── spatial.rs ├── color.rs ├── component.rs ├── consts.rs ├── ease.rs ├── geom.rs ├── lib.rs ├── object ├── arrow.rs ├── circle.rs ├── dot.rs ├── empty.rs ├── line.rs ├── mod.rs ├── rectangle.rs ├── text.rs └── triangle.rs ├── path └── mod.rs ├── scene.rs └── system.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.inlayHints.enable": false 3 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "noon", 4 | "examples", 5 | ] 6 | 7 | # Required for wgpu v0.10 feature resolution. 8 | resolver = "2" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # noon 2 | Experimental animation library with high-level APIs for vector graphics animation. 3 | 4 | ![Alt Text](./assets/shapes.gif) 5 | 6 | ## Motivation 7 | noon is an animation library that allows you to draw 2D objects and animate them with high-level commands. This project draws inspiration from [manim](https://github.com/3b1b/manim), which is used to create [educational videos about math](https://www.youtube.com/c/3blue1brown). While Python is a great language for end-users, it would be 8 | interesting to explore similar implementation in a compiled language and the possibilities that it would enable, such as real-time interactions and deploying to the web. Along the way, I became interested in Rust and tried to apply it for my learning in this project. 9 | 10 | ## Architecture 11 | Noon is designed as a thin animation-wrapper on top of an awesome existing library called 12 | [nannou](https://github.com/nannou-org/nannou). Nannou and it's dependent crate [lyon](https://github.com/nical/lyon) makes drawing custom shapes rendered in GPU much easier than it would otherwise be using Rust. 13 | 14 | To use nannou's per-frame drawing API with noon's animation commands, we cache the user commands in order to interpolate them during animation run-time. Since each animation attributes (e.g. position, size, etc.) are usually independent from one another and we can have many shapes in a scene, noon uses [Bevy ECS](https://github.com/bevyengine/bevy) to keep track of independent animation attributes. This also allows us bypass some tricky ownership-related issues that could arise when dealing with multiple references. 15 | 16 | ## Examples 17 | The following examples demonstrate the current status of this project. 18 | 19 | ![Alt Text](./assets/hello_world.gif) 20 | ```rust 21 | // cargo run --release --example hello_world 22 | 23 | fn scene(win_rect: Rect) -> Scene { 24 | // Make a blank scene 25 | let mut scene = Scene::new(win_rect); 26 | 27 | // Instantiate a rectangle with builder pattern 28 | let rect = scene 29 | .rectangle() 30 | .with_position(2.0, 0.0) 31 | .with_color(Color::random()) 32 | .make(); 33 | 34 | // Make a circle 35 | let circle = scene 36 | .circle() 37 | .with_position(-2.0, 0.0) 38 | .with_color(Color::random()) 39 | .make(); 40 | 41 | // Wait for a second 42 | scene.wait(); 43 | // Show animation for creating the rectangle with builder animation attributes 44 | scene.play(rect.show_creation()).run_time(1.5); 45 | scene.play(circle.show_creation()).run_time(1.5); 46 | // Morph circle into rectangle 47 | scene.play(circle.morph(rect)).run_time(1.5); 48 | 49 | scene 50 | } 51 | ``` 52 | 53 | ![Alt Text](./assets/morph.gif) 54 | ```rust 55 | // cargo run --release --example morph 56 | 57 | fn scene(win_rect: Rect) -> Scene { 58 | let mut scene = Scene::new(win_rect); 59 | 60 | // Make a text object 61 | let text = scene.text().with_font_size(50).with_text("Hello!").make(); 62 | let rectangle = scene.rectangle().with_position(2.0, 0.0).make(); 63 | let circle = scene.circle().with_position(-2.0, 0.0).make(); 64 | // Make a line object 65 | let line = scene.line().from(-2.0, -2.0).to(2.0, 2.0).make(); 66 | 67 | // Scene::play can receive multiple animation commands in a Vec 68 | scene 69 | .play(vec![ 70 | line.show_creation(), 71 | circle.show_creation(), 72 | rectangle.show_creation(), 73 | text.show_creation(), 74 | ]) 75 | .lag(1.0); // lag specifies the time delay between each animation 76 | 77 | // run_time specifies the duration of each animation 78 | scene 79 | .play(vec![ 80 | line.morph(circle), 81 | circle.morph(rectangle), 82 | rectangle.morph(text), 83 | ]) 84 | .run_time(2.0) 85 | .lag(2.0); 86 | 87 | scene 88 | } 89 | ``` 90 | 91 | ![Alt Text](./assets/morph_text.gif) 92 | ```rust 93 | // cargo run --release --example morph_text 94 | 95 | fn scene(win_rect: Rect) -> Scene { 96 | let mut scene = Scene::new(win_rect); 97 | 98 | // Create an empty Vec to contain multiple animations 99 | let mut morph = Vec::new(); 100 | let mut show = Vec::new(); 101 | 102 | // Each animation can be added in a for-loop 103 | for _ in 0..3 { 104 | let text2 = random_text(&mut scene, "This example shows shape transfrom"); 105 | show.push(text2.show_creation()); 106 | 107 | let text = random_text(&mut scene, "Hello World! This is some text"); 108 | show.push(text.show_creation()); 109 | 110 | morph.push(text.morph(text2)); 111 | morph.push(text2.fade_out()); 112 | } 113 | 114 | scene.play(show).run_time(3.0); 115 | scene.wait_for(0.5); 116 | scene.play(morph).run_time(3.0); 117 | 118 | scene 119 | } 120 | ``` 121 | 122 | ![Alt Text](./assets/easing.gif) 123 | ```rust 124 | // cargo run --release --example easing 125 | 126 | fn scene(win_rect: Rect) -> Scene { 127 | let mut scene = Scene::new(win_rect); 128 | 129 | let mut circles = Vec::new(); 130 | let mut show = Vec::new(); 131 | let mut to_right = Vec::new(); 132 | 133 | for i in 0..8 { 134 | let c = scene 135 | .circle() 136 | .with_position(-4.0, 2.0 - i as f32 * 0.5) 137 | .with_radius(0.2) 138 | .with_color(Color::random()) 139 | .make(); 140 | 141 | circles.push(c); 142 | show.push(c.fade_in()); 143 | to_right.push(c.move_by(8.0, 0.0)); 144 | } 145 | 146 | scene.wait(); 147 | scene.play(show).lag(0.2); 148 | 149 | // These easing functions are available for all animation attributes, 150 | // e.g. position, color, path, angle, scale, etc. 151 | let easing = [ 152 | EaseType::Linear, 153 | EaseType::Quad, 154 | EaseType::Quint, 155 | EaseType::Expo, 156 | EaseType::Sine, 157 | EaseType::Back, 158 | EaseType::Bounce, 159 | EaseType::Elastic, 160 | ]; 161 | for i in 0..8 { 162 | scene 163 | .play(to_right[i].clone()) 164 | .lag(0.5) 165 | .rate_func(easing[i]); 166 | } 167 | 168 | scene 169 | } 170 | ``` 171 | 172 | ![Alt Text](./assets/shapes.gif) 173 | ```rust 174 | // cargo run --release --example shapes 175 | 176 | fn scene(win_rect: Rect) -> Scene { 177 | let mut scene = Scene::new(win_rect); 178 | 179 | let mut animations = Vec::new(); 180 | let mut show = Vec::new(); 181 | let mut move_down = Vec::new(); 182 | 183 | for _ in 0..1000 { 184 | if noon::rand::random::() { 185 | let (x, y, w, _h, ang, color) = gen_random_values(); 186 | let circle = scene 187 | .circle() 188 | .with_position(x, y) 189 | .with_angle(ang) 190 | .with_color(color) 191 | .with_thin_stroke() 192 | .with_radius(w / 2.0) 193 | .make(); 194 | 195 | show.push(circle.show_creation()); 196 | move_down.push(circle.to_edge(Direction::Down)); 197 | 198 | let (x, y, w, _h, _ang, color) = gen_random_values(); 199 | animations.extend(vec![ 200 | circle.set_color(color), 201 | circle.move_to(x, y), 202 | circle.set_radius(w / 2.0), 203 | ]); 204 | } else { 205 | let (x, y, w, h, ang, color) = gen_random_values(); 206 | let rect = scene 207 | .rectangle() 208 | .with_position(x, y) 209 | .with_angle(ang) 210 | .with_color(color) 211 | .with_thin_stroke() 212 | .with_size(w, h) 213 | .make(); 214 | 215 | show.push(rect.show_creation()); 216 | move_down.push(rect.to_edge(Direction::Down)); 217 | 218 | let (x, y, w, _h, ang, color) = gen_random_values(); 219 | animations.extend(vec![ 220 | rect.set_color(color), 221 | rect.move_to(x, y), 222 | rect.set_size(w, h), 223 | rect.rotate(ang), 224 | ]); 225 | } 226 | } 227 | 228 | scene.wait_for(0.5); 229 | scene.play(show).run_time(1.0).lag(0.001); 230 | 231 | scene 232 | .play(animations) 233 | .run_time(3.0) 234 | .lag(0.0001) 235 | .rate_func(EaseType::Quint); 236 | 237 | scene 238 | .play(move_down) 239 | .run_time(1.0) 240 | .rate_func(EaseType::BounceOut) 241 | .lag(0.001); 242 | 243 | scene 244 | } 245 | ``` 246 | 247 | 248 | -------------------------------------------------------------------------------- /assets/easing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongkyuns/noon/3da2af643516fcc03ca01d11875830c1da0c52bc/assets/easing.gif -------------------------------------------------------------------------------- /assets/hello_world.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongkyuns/noon/3da2af643516fcc03ca01d11875830c1da0c52bc/assets/hello_world.gif -------------------------------------------------------------------------------- /assets/morph.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongkyuns/noon/3da2af643516fcc03ca01d11875830c1da0c52bc/assets/morph.gif -------------------------------------------------------------------------------- /assets/morph_text.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongkyuns/noon/3da2af643516fcc03ca01d11875830c1da0c52bc/assets/morph_text.gif -------------------------------------------------------------------------------- /assets/shapes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongkyuns/noon/3da2af643516fcc03ca01d11875830c1da0c52bc/assets/shapes.gif -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples" 3 | version = "0.1.0" 4 | authors = ["Yongkyun Shin "] 5 | edition = "2021" 6 | description = "A set examples for Noon" 7 | license = "MIT" 8 | 9 | 10 | [dependencies] 11 | noon = {version="0.1", path="../noon"} 12 | 13 | [[example]] 14 | name = "hello_world" 15 | path = "hello_world.rs" 16 | 17 | [[example]] 18 | name = "morph" 19 | path = "morph.rs" 20 | 21 | [[example]] 22 | name = "morph_text" 23 | path = "morph_text.rs" 24 | 25 | [[example]] 26 | name = "shapes" 27 | path = "shapes.rs" 28 | 29 | [[example]] 30 | name = "easing" 31 | path = "easing.rs" 32 | 33 | [[example]] 34 | name = "temp" 35 | path = "temp.rs" -------------------------------------------------------------------------------- /examples/easing.rs: -------------------------------------------------------------------------------- 1 | use noon::prelude::*; 2 | 3 | fn scene(win_rect: Rect) -> Scene { 4 | let mut scene = Scene::new(win_rect); 5 | 6 | let mut circles = Vec::new(); 7 | let mut show = Vec::new(); 8 | let mut to_right = Vec::new(); 9 | 10 | for i in 0..8 { 11 | let c = scene 12 | .circle() 13 | .with_position(-4.0, 2.0 - i as f32 * 0.5) 14 | .with_radius(0.2) 15 | .with_color(Color::random()) 16 | .make(); 17 | 18 | circles.push(c); 19 | show.push(c.fade_in()); 20 | to_right.push(c.move_by(8.0, 0.0)); 21 | // to_right.push(c.move_to(4.0, 2.0 - i as f32 * 0.5)); 22 | } 23 | 24 | scene.wait(); 25 | scene.play(show).lag(0.2); 26 | 27 | let easing = [ 28 | EaseType::Linear, 29 | EaseType::Quad, 30 | EaseType::Quint, 31 | EaseType::Expo, 32 | EaseType::Sine, 33 | EaseType::Back, 34 | EaseType::Bounce, 35 | EaseType::Elastic, 36 | ]; 37 | for i in 0..8 { 38 | scene 39 | .play(to_right[i].clone()) 40 | .lag(0.5) 41 | .rate_func(easing[i]); 42 | } 43 | 44 | scene 45 | } 46 | 47 | fn main() { 48 | noon::app(model).update(update).view(view).run(); 49 | } 50 | 51 | fn model<'a>(app: &App) -> Scene { 52 | app.new_window() 53 | .size(1920, 1080) 54 | .view(view) 55 | .build() 56 | .unwrap(); 57 | 58 | let scene = scene(app.window_rect()); 59 | scene 60 | } 61 | 62 | fn update(app: &App, scene: &mut Scene, _update: Update) { 63 | scene.update(app.time, app.window_rect()); 64 | println!("FPS = {}", app.fps()); 65 | } 66 | 67 | fn view(app: &App, scene: &mut Scene, frame: Frame) { 68 | let draw = app.draw(); 69 | draw.background().color(BLACK); 70 | scene.draw(draw.clone()); 71 | draw.to_frame(app, &frame).unwrap(); 72 | } 73 | -------------------------------------------------------------------------------- /examples/hello_world.rs: -------------------------------------------------------------------------------- 1 | use noon::prelude::*; 2 | 3 | fn scene(win_rect: Rect) -> Scene { 4 | let mut scene = Scene::new(win_rect); 5 | 6 | let rect = scene 7 | .rectangle() 8 | .with_position(2.0, 0.0) 9 | .with_color(Color::random()) 10 | .make(); 11 | 12 | let circle = scene 13 | .circle() 14 | .with_position(-2.0, 0.0) 15 | .with_color(Color::random()) 16 | .make(); 17 | 18 | scene.wait(); 19 | scene.play(rect.show_creation()).run_time(1.5); 20 | scene.play(circle.show_creation()).run_time(1.5); 21 | scene.play(circle.morph(rect)).run_time(1.5); 22 | 23 | scene 24 | } 25 | 26 | fn main() { 27 | noon::app(model).update(update).view(view).run(); 28 | } 29 | 30 | fn model<'a>(app: &App) -> Scene { 31 | app.new_window() 32 | .size(1920, 1080) 33 | .view(view) 34 | .build() 35 | .unwrap(); 36 | 37 | let scene = scene(app.window_rect()); 38 | scene 39 | } 40 | 41 | fn update(app: &App, scene: &mut Scene, _update: Update) { 42 | scene.update(app.time, app.window_rect()); 43 | println!("FPS = {}", app.fps()); 44 | } 45 | 46 | fn view(app: &App, scene: &mut Scene, frame: Frame) { 47 | let draw = app.draw(); 48 | draw.background().color(BLACK); 49 | scene.draw(draw.clone()); 50 | draw.to_frame(app, &frame).unwrap(); 51 | } 52 | -------------------------------------------------------------------------------- /examples/morph.rs: -------------------------------------------------------------------------------- 1 | use noon::prelude::*; 2 | 3 | fn scene(win_rect: Rect) -> Scene { 4 | let mut scene = Scene::new(win_rect); 5 | 6 | let text = scene.text().with_font_size(50).with_text("Hello!").make(); 7 | 8 | let rectangle = scene.rectangle().with_position(2.0, 0.0).make(); 9 | 10 | let circle = scene.circle().with_position(-2.0, 0.0).make(); 11 | 12 | let line = scene.line().from(-2.0, -2.0).to(2.0, 2.0).make(); 13 | 14 | scene 15 | .play(vec![ 16 | line.show_creation(), 17 | circle.show_creation(), 18 | rectangle.show_creation(), 19 | text.show_creation(), 20 | ]) 21 | .lag(1.0); 22 | 23 | scene 24 | .play(vec![ 25 | line.morph(circle), 26 | circle.morph(rectangle), 27 | rectangle.morph(text), 28 | ]) 29 | .run_time(2.0) 30 | .lag(2.0); 31 | 32 | scene 33 | } 34 | 35 | fn main() { 36 | noon::app(model).update(update).view(view).run(); 37 | } 38 | 39 | fn model<'a>(app: &App) -> Scene { 40 | app.new_window() 41 | .size(1920, 1080) 42 | .view(view) 43 | .build() 44 | .unwrap(); 45 | 46 | let scene = scene(app.window_rect()); 47 | scene 48 | } 49 | 50 | fn update(app: &App, scene: &mut Scene, _update: Update) { 51 | scene.update(app.time, app.window_rect()); 52 | println!("FPS = {}", app.fps()); 53 | } 54 | 55 | fn view(app: &App, scene: &mut Scene, frame: Frame) { 56 | let draw = app.draw(); 57 | draw.background().color(BLACK); 58 | scene.draw(draw.clone()); 59 | draw.to_frame(app, &frame).unwrap(); 60 | } 61 | -------------------------------------------------------------------------------- /examples/morph_text.rs: -------------------------------------------------------------------------------- 1 | use noon::prelude::*; 2 | 3 | fn scene(win_rect: Rect) -> Scene { 4 | let mut scene = Scene::new(win_rect); 5 | 6 | let mut morph = Vec::new(); 7 | let mut show = Vec::new(); 8 | 9 | for _ in 0..3 { 10 | let text2 = random_text(&mut scene, "This example shows shape transfrom"); 11 | show.push(text2.show_creation()); 12 | 13 | let text = random_text(&mut scene, "Hello World! This is some text"); 14 | show.push(text.show_creation()); 15 | 16 | morph.push(text.morph(text2)); 17 | morph.push(text2.fade_out()); 18 | } 19 | 20 | scene.play(show).run_time(3.0); 21 | scene.wait_for(0.5); 22 | scene.play(morph).run_time(3.0); 23 | 24 | scene 25 | } 26 | 27 | fn random_text(scene: &mut Scene, text: &str) -> TextId { 28 | let (x, y, _w, _h, _ang, color) = gen_random_values(); 29 | scene 30 | .text() 31 | .with_text(text) 32 | .with_font_size(20) 33 | .with_color(color) 34 | .with_position(x, y) 35 | .make() 36 | } 37 | 38 | fn gen_random_values() -> (f32, f32, f32, f32, f32, Color) { 39 | let x_lim = 4.0; 40 | let y_lim = 2.0; 41 | let x = random_range::(-x_lim, x_lim); 42 | let y = random_range::(-y_lim, y_lim); 43 | let w = random_range::(0.1, 0.3); 44 | let h = random_range::(0.1, 0.3); 45 | let ang = random_range::(0.0, 360.0); 46 | let color = Color::random(); 47 | 48 | (x, y, w, h, ang, color) 49 | } 50 | 51 | fn main() { 52 | noon::app(model).update(update).view(view).run(); 53 | } 54 | 55 | fn model<'a>(app: &App) -> Scene { 56 | // app.new_window().size(640, 480).view(view).build().unwrap(); 57 | app.new_window() 58 | .size(1920, 1080) 59 | // .key_pressed(key_pressed) 60 | // .mouse_pressed(mouse_pressed) 61 | .view(view) 62 | .build() 63 | .unwrap(); 64 | 65 | let scene = scene(app.window_rect()); 66 | scene 67 | } 68 | 69 | fn update(app: &App, scene: &mut Scene, _update: Update) { 70 | scene.update(app.time, app.window_rect()); 71 | println!("FPS = {}", app.fps()); 72 | } 73 | 74 | fn view(app: &App, scene: &mut Scene, frame: Frame) { 75 | let draw = app.draw(); 76 | draw.background().color(BLACK); 77 | scene.draw(draw.clone()); 78 | draw.to_frame(app, &frame).unwrap(); 79 | } 80 | 81 | // fn mouse_pressed(app: &App, scene: &mut Scene, _button: MouseButton) { 82 | // scene.add_circle(app.mouse.x, app.mouse.y); 83 | // } 84 | 85 | // fn key_pressed(_app: &App, _model: &mut Scene, key: Key) { 86 | // match key { 87 | // Key::Key1 => { 88 | // // model.interpolate_shortest = true; 89 | // } 90 | // Key::Key2 => { 91 | // // model.interpolate_shortest = false; 92 | // } 93 | // Key::S => { 94 | // // app.main_window() 95 | // // .capture_frame(app.exe_name().unwrap() + ".png"); 96 | // } 97 | // _other_key => {} 98 | // } 99 | // } 100 | -------------------------------------------------------------------------------- /examples/shapes.rs: -------------------------------------------------------------------------------- 1 | use noon::prelude::*; 2 | 3 | fn scene(win_rect: Rect) -> Scene { 4 | let mut scene = Scene::new(win_rect); 5 | 6 | let mut animations = Vec::new(); 7 | let mut show = Vec::new(); 8 | let mut move_down = Vec::new(); 9 | 10 | for _ in 0..1000 { 11 | if noon::rand::random::() { 12 | let (x, y, w, _h, ang, color) = gen_random_values(); 13 | let circle = scene 14 | .circle() 15 | .with_position(x, y) 16 | .with_angle(ang) 17 | .with_color(color) 18 | .with_thin_stroke() 19 | .with_radius(w / 2.0) 20 | .make(); 21 | 22 | show.push(circle.show_creation()); 23 | move_down.push(circle.to_edge(Direction::Down)); 24 | 25 | let (x, y, w, _h, _ang, color) = gen_random_values(); 26 | animations.extend(vec![ 27 | circle.set_color(color), 28 | circle.move_to(x, y), 29 | circle.set_radius(w / 2.0), 30 | ]); 31 | } else { 32 | let (x, y, w, h, ang, color) = gen_random_values(); 33 | let rect = scene 34 | .rectangle() 35 | .with_position(x, y) 36 | .with_angle(ang) 37 | .with_color(color) 38 | .with_thin_stroke() 39 | .with_size(w, h) 40 | .make(); 41 | 42 | show.push(rect.show_creation()); 43 | move_down.push(rect.to_edge(Direction::Down)); 44 | 45 | let (x, y, w, _h, ang, color) = gen_random_values(); 46 | animations.extend(vec![ 47 | rect.set_color(color), 48 | rect.move_to(x, y), 49 | rect.set_size(w, h), 50 | rect.rotate(ang), 51 | ]); 52 | } 53 | } 54 | 55 | scene.wait_for(0.5); 56 | scene.play(show).run_time(1.0).lag(0.001); 57 | 58 | scene 59 | .play(animations) 60 | .run_time(3.0) 61 | .lag(0.0001) 62 | .rate_func(EaseType::Quint); 63 | 64 | scene 65 | .play(move_down) 66 | .run_time(1.0) 67 | .rate_func(EaseType::BounceOut) 68 | .lag(0.001); 69 | 70 | scene 71 | } 72 | 73 | fn gen_random_values() -> (f32, f32, f32, f32, f32, Color) { 74 | let x_lim = 4.0; 75 | let y_lim = 2.0; 76 | let x = random_range::(-x_lim, x_lim); 77 | let y = random_range::(-y_lim, y_lim); 78 | let w = random_range::(0.1, 0.3); 79 | let h = random_range::(0.1, 0.3); 80 | let ang = random_range::(0.0, noon::TAU); 81 | let color = Color::random(); 82 | 83 | (x, y, w, h, ang, color) 84 | } 85 | 86 | fn main() { 87 | noon::app(model).update(update).view(view).run(); 88 | } 89 | 90 | fn model<'a>(app: &App) -> Scene { 91 | app.new_window() 92 | .size(1920, 1080) 93 | .view(view) 94 | .build() 95 | .unwrap(); 96 | 97 | scene(app.window_rect()) 98 | } 99 | 100 | fn update(app: &App, scene: &mut Scene, _update: Update) { 101 | scene.update(app.time, app.window_rect()); 102 | println!("FPS = {}", app.fps()); 103 | } 104 | 105 | fn view(app: &App, scene: &mut Scene, frame: Frame) { 106 | let draw = app.draw(); 107 | draw.background().color(BLACK); 108 | scene.draw(draw.clone()); 109 | draw.to_frame(app, &frame).unwrap(); 110 | // // Capture the frame! 111 | // let file_path = captured_frame_path(app, &frame); 112 | // app.main_window().capture_frame(file_path); 113 | } 114 | 115 | // fn captured_frame_path(app: &App, frame: &Frame) -> std::path::PathBuf { 116 | // // Create a path that we want to save this frame to. 117 | // app.project_path() 118 | // .expect("failed to locate `project_path`") 119 | // // Capture all frames to a directory called `//nannou/simple_capture`. 120 | // .join(app.exe_name().unwrap()) 121 | // // Name each file after the number of the frame. 122 | // .join(format!("{:03}", frame.nth())) 123 | // // The extension will be PNG. We also support tiff, bmp, gif, jpeg, webp and some others. 124 | // .with_extension("png") 125 | // } 126 | -------------------------------------------------------------------------------- /examples/temp.rs: -------------------------------------------------------------------------------- 1 | use noon::prelude::*; 2 | 3 | fn scene(win_rect: Rect) -> Scene { 4 | let mut scene = Scene::new(win_rect); 5 | 6 | // let circle = scene 7 | // .circle() 8 | // .with_position(-2.0, 0.0) 9 | // .with_color(Color::random()) 10 | // .make(); 11 | 12 | let rect = scene 13 | .rectangle() 14 | .with_position(1.0, -1.0) 15 | .with_color(Color::random()) 16 | .make(); 17 | 18 | let rect2 = scene 19 | .rectangle() 20 | .with_size(0.5, 0.5) 21 | .with_angle(noon::PI / 4.0) 22 | .with_position(-2.0, 2.0) 23 | .with_color(Color::random()) 24 | .make(); 25 | 26 | // let text = scene.text().with_text("Hello!").make(); 27 | // let line = scene.line().from(0.0, 0.0).to(1.0, 0.0).make(); 28 | // let line = scene.line().from(-0.0, 0.0000000).to(1.0, 0.0).make(); 29 | // let line = scene.line().from(2.0, 0.0).to(3.0, 1.0).make(); 30 | // let line = scene.line().from(1.0, 0.0).to(2.0, 1.0).make(); 31 | 32 | // let group = scene.group().add(line).add(text).make(); 33 | 34 | // scene.play(vec![circle.show_creation(), rect.fade_in()]); 35 | // scene.play(circle.scale(0.5)); 36 | // scene.play(circle.to_edge(Direction::Up)); 37 | // // scene.play(circle.move_to(-2.0, 4.5)); 38 | // scene.play(circle.scale(2.0)); 39 | // scene.play(circle.to_edge(Direction::Right)); 40 | 41 | // scene.play(rect.rotate(noon::PI / 4.0)); 42 | // scene.play(rect.to_edge(Direction::Up)); 43 | 44 | // scene.play(rect.scale(2.0)); 45 | // scene.play(rect.to_edge(Direction::Left)); 46 | 47 | // scene.play(vec![text.show_creation(), line.show_creation()]); 48 | 49 | // scene.play(vec![line.show_creation(), text.show_creation()]); 50 | 51 | // scene.play(group.rotate(noon::PI / 4.0)); 52 | scene.play(vec![rect.show_creation(), rect2.show_creation()]); 53 | // scene.play(rect.move_to(2.0, 2.0)); 54 | scene.play(rect.rotate(noon::PI / 4.0)); 55 | scene.play(rect.scale(0.1)); 56 | // scene.play(rect.scale_x(0.1)); 57 | // scene.play(rect.set_size(0.5, 1.0)); 58 | // scene.play(rect.move_by(-2.0, -2.0)); 59 | // scene.wait(); 60 | scene.play(rect.to_edge(Direction::Right)); 61 | // scene.play(rect.morph(rect2)); 62 | 63 | // let group = scene.group(vec![line,text]).make(); 64 | // let group = scene.group().add(line).add(text).make(); 65 | 66 | // scene.arrange(group).vertical(); 67 | // scene.arrange(group).horizontal(); 68 | // scene.play(group.rotate(noon::PI/4.0)); 69 | 70 | // scene.play(text.scale(2.0)); 71 | // scene.play(text.rotate(noon::TAU / 8.0)); 72 | // scene.play(vec![text.scale(2.0), text.rotate(noon::TAU / 8.0)]); 73 | 74 | // scene.play(text.to_edge(Direction::Up)); 75 | 76 | // scene.play(vec![text.scale(2.0), text.rotate(noon::PI / 4.0)]); 77 | // scene.play(line.show_creation()); 78 | 79 | // scene.play(vec![ 80 | // text.rotate(noon::PI / 4.0), 81 | // line.rotate(noon::PI / 4.0), 82 | // rect.rotate(noon::PI / 4.0), 83 | // ]); 84 | // scene.play(vec![ 85 | // text.to_edge(Direction::Up), 86 | // line.to_edge(Direction::Up), 87 | // rect.to_edge(Direction::Up), 88 | // ]); 89 | 90 | // scene.play(circle.move_to(-2.0, 4.5)); 91 | // scene.play(circle.scale(2.0)); 92 | // scene.play(circle.to_edge(Direction::Right)); 93 | 94 | // for i in 0..1000 { 95 | // let change = (i % 2) as f32; 96 | 97 | // scene 98 | // .play(vec![ 99 | // circle.set_radius(0.5 + change / 2.0), 100 | // rect.scale_x(0.5 + 1.5 * change), 101 | // rect.shift(LEFT), 102 | // rect.rotate(noon::PI / 4.0), 103 | // ]) 104 | // .run_time(1.0) 105 | // .rate_func(EaseType::BackOut); 106 | // } 107 | 108 | scene 109 | } 110 | 111 | fn main() { 112 | noon::app(model).update(update).view(view).run(); 113 | } 114 | 115 | fn model<'a>(app: &App) -> Scene { 116 | app.new_window() 117 | .size(1920, 1080) 118 | .view(view) 119 | .build() 120 | .unwrap(); 121 | 122 | let scene = scene(app.window_rect()); 123 | scene 124 | } 125 | 126 | fn update(app: &App, scene: &mut Scene, _update: Update) { 127 | scene.update(app.time, app.window_rect()); 128 | // println!("FPS = {}", app.fps()); 129 | } 130 | 131 | fn view(app: &App, scene: &mut Scene, frame: Frame) { 132 | let draw = app.draw(); 133 | draw.background().color(BLACK); 134 | scene.draw(draw.clone()); 135 | draw.to_frame(app, &frame).unwrap(); 136 | } 137 | -------------------------------------------------------------------------------- /noon/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "noon" 3 | version = "0.1.0" 4 | authors = ["Yongkyun Shin "] 5 | edition = "2021" 6 | license = "MIT" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | bevy_ecs="0.6" 12 | # nannou = { path = "../../nannou/nannou", version = "0.18"} 13 | nannou = { git = "https://github.com/yongkyuns/nannou.git", version = "0.18"} 14 | # nannou="0.18" 15 | cgmath = { version = "0.17", features = ["serde"] } 16 | pennereq = "0.3" 17 | 18 | [dev-dependencies] 19 | -------------------------------------------------------------------------------- /noon/src/animation/builder.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub struct AnimBuilder<'a> { 4 | scene: &'a mut Scene, 5 | animations: Vec, 6 | run_time: f32, 7 | rate_func: EaseType, 8 | lag: f32, 9 | #[allow(dead_code)] 10 | repeat: usize, 11 | start_time: Option, 12 | } 13 | 14 | impl<'a> AnimBuilder<'a> { 15 | pub fn new(scene: &'a mut Scene, animations: Vec) -> Self { 16 | let rate_func = EaseType::Quad; 17 | // for ta in animations.iter() { 18 | // if ta.action == Action::ShowCreation { 19 | // rate_func = EaseType::Quad; 20 | // break; 21 | // } 22 | // } 23 | AnimBuilder { 24 | scene, 25 | animations, 26 | run_time: 1.0, 27 | rate_func, 28 | lag: 0.0, 29 | repeat: 0, 30 | start_time: None, 31 | } 32 | } 33 | pub fn start_time(mut self, time: f32) -> Self { 34 | self.start_time = Some(time); 35 | self 36 | } 37 | pub fn run_time(mut self, duration: f32) -> Self { 38 | self.run_time = duration; 39 | self 40 | } 41 | pub fn rate_func(mut self, rate_func: EaseType) -> Self { 42 | self.rate_func = rate_func; 43 | self 44 | } 45 | pub fn lag(mut self, lag: f32) -> Self { 46 | self.lag = lag; 47 | self 48 | } 49 | } 50 | 51 | impl<'a> Drop for AnimBuilder<'a> { 52 | fn drop(&mut self) { 53 | let Self { 54 | run_time, 55 | animations, 56 | rate_func, 57 | lag, 58 | start_time, 59 | .. 60 | } = self; 61 | 62 | let mut t = if let Some(time) = start_time { 63 | *time 64 | } else { 65 | self.scene.event_time 66 | }; 67 | for animation in animations.into_iter() { 68 | animation.set_properties(t, *run_time, *rate_func); 69 | animation.clone().insert_animation(&mut self.scene.world); 70 | t += *lag; 71 | } 72 | self.scene.event_time = t - *lag + *run_time; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /noon/src/animation/color.rs: -------------------------------------------------------------------------------- 1 | use crate::{Color, ColorExtension}; 2 | 3 | use super::*; 4 | 5 | pub trait WithStrokeWeight: WithId { 6 | fn set_stroke_weight(&self, weight: f32) -> EntityAnimations { 7 | EntityAnimations { 8 | entity: self.id(), 9 | animations: Animation::to(StrokeWeight(weight)).into(), 10 | } 11 | } 12 | fn set_stroke_weight_from(&self, entity: impl Into) -> EntityAnimations { 13 | EntityAnimations { 14 | entity: self.id(), 15 | animations: Animation::::to_target(entity.into()).into(), 16 | } 17 | } 18 | } 19 | 20 | pub trait WithStroke: WithId { 21 | fn set_stroke_color(&self, color: Color) -> EntityAnimations { 22 | EntityAnimations { 23 | entity: self.id(), 24 | animations: Animation::to(StrokeColor(color)).into(), 25 | } 26 | } 27 | fn set_stroke_color_from(&self, entity: impl Into) -> EntityAnimations { 28 | EntityAnimations { 29 | entity: self.id(), 30 | animations: Animation::::to_target(entity.into()).into(), 31 | } 32 | } 33 | } 34 | 35 | pub trait WithFill: WithId { 36 | fn set_fill_color(&self, color: Color) -> EntityAnimations { 37 | EntityAnimations { 38 | entity: self.id(), 39 | animations: Animation::to(FillColor(color)).into(), 40 | } 41 | } 42 | fn set_fill_color_from(&self, entity: impl Into) -> EntityAnimations { 43 | EntityAnimations { 44 | entity: self.id(), 45 | animations: Animation::::to_target(entity.into()).into(), 46 | } 47 | } 48 | } 49 | 50 | pub trait WithColor: WithId { 51 | fn set_color(&self, color: Color) -> EntityAnimations { 52 | EntityAnimations { 53 | entity: self.id(), 54 | animations: vec![ 55 | Animation::to(StrokeColor(color.brighten())).into(), 56 | Animation::to(FillColor(color)).into(), 57 | ], 58 | } 59 | } 60 | fn set_color_from(&self, entity: impl Into) -> EntityAnimations { 61 | let entity: Entity = entity.into(); 62 | EntityAnimations { 63 | entity: self.id(), 64 | animations: vec![ 65 | Animation::::to_target(entity).into(), 66 | Animation::::to_target(entity).into(), 67 | ], 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /noon/src/animation/mod.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Mul}; 2 | 3 | use bevy_ecs::{ 4 | entity::Entity, 5 | prelude::{Component, Res, World}, 6 | }; 7 | 8 | // use crate::prelude::*; 9 | use crate::{ 10 | prelude::Direction, Angle, Bounds, EaseType, FillColor, FontSize, Interpolate, Opacity, Path, 11 | PathCompletion, Position, Scale, Scene, Size, StrokeColor, StrokeWeight, Value, Vector, 12 | }; 13 | 14 | mod builder; 15 | mod color; 16 | mod path; 17 | mod spatial; 18 | 19 | pub use builder::AnimBuilder; 20 | pub use color::*; 21 | pub use path::*; 22 | pub use spatial::*; 23 | 24 | /// Trait to indicate whether an object contains [Entity]. If it does, 25 | /// the said object qualifies as a valid object to be inserted to the 26 | /// [bevy_ecs]. 27 | pub trait WithId { 28 | fn id(&self) -> Entity; 29 | } 30 | 31 | /// Convenience struct to contain more than one [Animation] and implement 32 | /// any related functionalities. 33 | #[derive(Component)] 34 | pub struct Animations(pub Vec>); 35 | 36 | /// Basic structure to describe an animation. 37 | #[derive(Component, Debug, Clone)] 38 | pub struct Animation { 39 | /// Initial state of the animation. If `None`, will be initialized 40 | /// with current state when the time reaches `start_time`. 41 | pub(crate) begin: Option, 42 | /// Final state of the animation. The final state may contain an 43 | /// absolute value, or a relative value with respect to the 44 | /// initialized `begin` state 45 | pub(crate) end: Value, 46 | /// Duration of animation in seconds. 47 | pub(crate) duration: f32, 48 | /// Time at which animation should begin. 49 | pub(crate) start_time: f32, 50 | /// Easing function to be used for animation. 51 | pub(crate) rate_func: EaseType, 52 | /// If set to `false`, `duration` will be assigned by user 53 | /// through [Scene](crate::Scene)'s `play` function 54 | pub(crate) init_duration: bool, 55 | /// If set to `false`, `start_time` will be assigned by user 56 | /// through [Scene](crate::Scene)'s `play` function 57 | pub(crate) init_start_time: bool, 58 | /// If set to `false`, `rate_func` will be assigned by user 59 | /// through [Scene](crate::Scene)'s `play` function 60 | pub(crate) init_rate_func: bool, 61 | } 62 | 63 | impl Animation { 64 | pub fn to(to: T) -> Self { 65 | Self { 66 | begin: None, 67 | end: Value::Absolute(to), 68 | duration: 1.0, 69 | start_time: 0.0, 70 | rate_func: EaseType::Quad, 71 | init_duration: true, 72 | init_start_time: true, 73 | init_rate_func: true, 74 | } 75 | } 76 | 77 | pub fn to_target(target: Entity) -> Self { 78 | Self { 79 | begin: None, 80 | end: Value::From(target), 81 | duration: 1.0, 82 | start_time: 0.0, 83 | rate_func: Default::default(), 84 | init_duration: true, 85 | init_start_time: true, 86 | init_rate_func: true, 87 | } 88 | } 89 | 90 | pub fn by(by: T) -> Self { 91 | Self { 92 | begin: None, 93 | end: Value::Relative(by), 94 | duration: 1.0, 95 | start_time: 0.0, 96 | rate_func: Default::default(), 97 | init_duration: true, 98 | init_start_time: true, 99 | init_rate_func: true, 100 | } 101 | } 102 | 103 | pub fn times(by: T) -> Self { 104 | Self { 105 | begin: None, 106 | end: Value::Multiply(by), 107 | duration: 1.0, 108 | start_time: 0.0, 109 | rate_func: Default::default(), 110 | init_duration: true, 111 | init_start_time: true, 112 | init_rate_func: true, 113 | } 114 | } 115 | 116 | pub fn with_duration(mut self, duration: f32) -> Self { 117 | self.duration = duration; 118 | self.init_duration = false; 119 | self 120 | } 121 | 122 | pub fn with_start_time(mut self, start_time: f32) -> Self { 123 | self.start_time = start_time; 124 | self.init_start_time = false; 125 | self 126 | } 127 | 128 | pub fn with_rate_func(mut self, rate_func: EaseType) -> Self { 129 | self.rate_func = rate_func; 130 | self.init_rate_func = false; 131 | self 132 | } 133 | 134 | pub fn has_target(&self) -> Option { 135 | match self.end { 136 | Value::From(entity) => Some(entity), 137 | _ => None, 138 | } 139 | } 140 | 141 | pub fn init_from_target(&mut self, end: &T) 142 | where 143 | T: Clone, 144 | { 145 | match &self.end { 146 | Value::From(_entity) => { 147 | self.end = Value::Absolute(end.clone()); 148 | } 149 | _ => (), 150 | } 151 | } 152 | 153 | /// Update function for generic [Component]. 154 | /// 155 | /// This function does two things: 156 | /// 1. If animation hasn't started but needs to, this function 157 | /// will write to the initial status of animation from the 158 | /// current component state. 159 | /// 2. For every animation loop, this function will perform 160 | /// the interpolation between initial and final state 161 | pub fn update(&mut self, property: &mut T, progress: f32) 162 | where 163 | T: Interpolate + Component + Clone, 164 | { 165 | match (&mut self.begin, &mut self.end) { 166 | (Some(begin), Value::Absolute(to)) => *property = begin.interp(&to, progress), 167 | (None, Value::Absolute(_to)) => { 168 | self.begin = Some(property.clone()); 169 | } 170 | _ => (), 171 | } 172 | } 173 | 174 | /// This function is similar to `Self::update()`, but also 175 | /// allows relative changes to be animated, e.g. rotating by 176 | /// the specified angle [rotate()](crate::WithAngle::rotate()). 177 | /// 178 | /// If regular update is used, these relative changes will not 179 | /// perform any animation. 180 | pub fn update_with_relative(&mut self, property: &mut T, progress: f32) 181 | where 182 | T: Interpolate + Component + Clone + Add, 183 | { 184 | match (&mut self.begin, &mut self.end) { 185 | (Some(begin), Value::Absolute(to)) => *property = begin.interp(&to, progress), 186 | (None, Value::Absolute(_to)) => { 187 | self.begin = Some(property.clone()); 188 | } 189 | (None, Value::Relative(by)) => { 190 | self.begin = Some(property.clone()); 191 | self.end = Value::Absolute(property.clone() + by.clone()); 192 | } 193 | _ => (), 194 | } 195 | } 196 | 197 | /// This function is similar to `Self::update()`, but also 198 | /// allows multiplicative changes to be animated, e.g. scaling 199 | /// [scale()](crate::WithSize::scale()). 200 | /// 201 | /// If regular update is used, these multiplicative changes will not 202 | /// perform any animation. 203 | pub fn update_with_multiply(&mut self, property: &mut T, progress: f32) 204 | where 205 | T: Interpolate + Component + Clone + Mul, 206 | { 207 | match (&mut self.begin, &mut self.end) { 208 | (Some(begin), Value::Absolute(to)) => *property = begin.interp(&to, progress), 209 | (None, Value::Absolute(_to)) => { 210 | self.begin = Some(property.clone()); 211 | } 212 | (None, Value::Multiply(by)) => { 213 | self.begin = Some(property.clone()); 214 | self.end = Value::Absolute(property.clone() * by.clone()); 215 | } 216 | _ => (), 217 | } 218 | } 219 | } 220 | 221 | impl Animation { 222 | /// Update function for [Position] to be called by [System](bevy_ecs::prelude::System). 223 | /// 224 | /// This function expects current position of the object and normalized progress 225 | /// status of animation. In addition to the regular update function, this function 226 | /// also expects the edges of window frame in order to animate moving to edges. 227 | pub fn update_position( 228 | &mut self, 229 | position: &mut Position, 230 | progress: f32, 231 | bounds: &Res, 232 | size: &Size, 233 | ) { 234 | match (&mut self.begin, &mut self.end) { 235 | (Some(begin), Value::Absolute(to)) => *position = begin.interp(&to, progress), 236 | (None, Value::Absolute(_to)) => { 237 | self.begin = Some(*position); 238 | } 239 | (None, Value::Relative(by)) => { 240 | self.begin = Some(*position); 241 | self.end = Value::Absolute(*position + *by); 242 | } 243 | (None, Value::Edge(direction)) => { 244 | // println!("{:?}", position); 245 | // println!("size = {:?}", size); 246 | // println!( 247 | // "{:?}", 248 | // bounds.reduced_by(size).get_edge(*position, *direction) 249 | // ); 250 | // println!("{:?}", &*bounds); 251 | self.begin = Some(*position); 252 | self.end = Value::Absolute(bounds.reduced_by(size).get_edge(*position, *direction)); 253 | } 254 | _ => (), 255 | } 256 | } 257 | 258 | /// Animation constructor command called by [WithPosition::to_edge]. 259 | pub fn to_edge(direction: Direction) -> Self { 260 | Self { 261 | begin: None, 262 | end: Value::Edge(direction), 263 | duration: 1.0, 264 | start_time: 0.0, 265 | rate_func: Default::default(), 266 | init_duration: true, 267 | init_start_time: true, 268 | init_rate_func: true, 269 | } 270 | } 271 | } 272 | 273 | impl Into> for Animation 274 | where 275 | Animation: Into, 276 | { 277 | fn into(self) -> Vec { 278 | vec![self.into()] 279 | } 280 | } 281 | 282 | #[derive(Clone)] 283 | pub enum AnimationType { 284 | StrokeColor(Animation), 285 | StrokeWeight(Animation), 286 | FillColor(Animation), 287 | Position(Animation), 288 | Angle(Animation), 289 | Size(Animation), 290 | Scale(Animation), 291 | FontSize(Animation), 292 | Opacity(Animation), 293 | PathCompletion(Animation), 294 | Path(Animation), 295 | } 296 | 297 | impl Into for Animation { 298 | fn into(self) -> AnimationType { 299 | AnimationType::StrokeColor(self) 300 | } 301 | } 302 | 303 | impl Into for Animation { 304 | fn into(self) -> AnimationType { 305 | AnimationType::StrokeWeight(self) 306 | } 307 | } 308 | 309 | impl Into for Animation { 310 | fn into(self) -> AnimationType { 311 | AnimationType::FillColor(self) 312 | } 313 | } 314 | 315 | impl Into for Animation { 316 | fn into(self) -> AnimationType { 317 | AnimationType::Position(self) 318 | } 319 | } 320 | 321 | impl Into for Animation { 322 | fn into(self) -> AnimationType { 323 | AnimationType::Angle(self) 324 | } 325 | } 326 | 327 | impl Into for Animation { 328 | fn into(self) -> AnimationType { 329 | AnimationType::Scale(self) 330 | } 331 | } 332 | 333 | impl Into for Animation { 334 | fn into(self) -> AnimationType { 335 | AnimationType::Size(self) 336 | } 337 | } 338 | 339 | impl Into for Animation { 340 | fn into(self) -> AnimationType { 341 | AnimationType::FontSize(self) 342 | } 343 | } 344 | 345 | impl Into for Animation { 346 | fn into(self) -> AnimationType { 347 | AnimationType::Opacity(self) 348 | } 349 | } 350 | 351 | impl Into for Animation { 352 | fn into(self) -> AnimationType { 353 | AnimationType::PathCompletion(self) 354 | } 355 | } 356 | 357 | impl Into for Animation { 358 | fn into(self) -> AnimationType { 359 | AnimationType::Path(self) 360 | } 361 | } 362 | 363 | fn insert_animation( 364 | animation: Animation, 365 | world: &mut World, 366 | id: Entity, 367 | ) { 368 | if let Some(mut animations) = world.get_mut::>(id) { 369 | animations.0.push(animation); 370 | } else { 371 | world.entity_mut(id).insert(Animations(vec![animation])); 372 | } 373 | } 374 | 375 | fn set_properties( 376 | animation: &mut Animation, 377 | start_time: f32, 378 | duration: f32, 379 | rate_func: EaseType, 380 | ) { 381 | if animation.init_start_time { 382 | animation.start_time = start_time; 383 | } 384 | if animation.init_duration { 385 | animation.duration = duration; 386 | } 387 | if animation.init_rate_func { 388 | animation.rate_func = rate_func; 389 | } 390 | } 391 | 392 | #[derive(Clone)] 393 | pub struct EntityAnimations { 394 | pub(crate) entity: Entity, 395 | pub(crate) animations: Vec, 396 | } 397 | 398 | impl EntityAnimations { 399 | pub fn insert_animation(self, world: &mut World) { 400 | for animation in self.animations.into_iter() { 401 | match animation { 402 | AnimationType::StrokeColor(animation) => { 403 | insert_animation(animation, world, self.entity); 404 | } 405 | AnimationType::StrokeWeight(animation) => { 406 | insert_animation(animation, world, self.entity); 407 | } 408 | AnimationType::FillColor(animation) => { 409 | insert_animation(animation, world, self.entity); 410 | } 411 | AnimationType::Position(animation) => { 412 | insert_animation(animation, world, self.entity); 413 | } 414 | AnimationType::Angle(animation) => { 415 | insert_animation(animation, world, self.entity); 416 | } 417 | AnimationType::Scale(animation) => { 418 | insert_animation(animation, world, self.entity); 419 | } 420 | AnimationType::Size(animation) => { 421 | insert_animation(animation, world, self.entity); 422 | } 423 | AnimationType::FontSize(animation) => { 424 | insert_animation(animation, world, self.entity); 425 | } 426 | AnimationType::Opacity(animation) => { 427 | insert_animation(animation, world, self.entity); 428 | } 429 | AnimationType::PathCompletion(animation) => { 430 | insert_animation(animation, world, self.entity); 431 | } 432 | AnimationType::Path(animation) => { 433 | insert_animation(animation, world, self.entity); 434 | } 435 | }; 436 | } 437 | } 438 | pub fn start_time(&self) -> f32 { 439 | match self.animations.get(0).unwrap() { 440 | AnimationType::StrokeColor(animation) => animation.start_time, 441 | AnimationType::StrokeWeight(animation) => animation.start_time, 442 | AnimationType::FillColor(animation) => animation.start_time, 443 | AnimationType::Position(animation) => animation.start_time, 444 | AnimationType::Angle(animation) => animation.start_time, 445 | AnimationType::Scale(animation) => animation.start_time, 446 | AnimationType::Size(animation) => animation.start_time, 447 | AnimationType::FontSize(animation) => animation.start_time, 448 | AnimationType::Opacity(animation) => animation.start_time, 449 | AnimationType::PathCompletion(animation) => animation.start_time, 450 | AnimationType::Path(animation) => animation.start_time, 451 | } 452 | } 453 | pub fn set_properties(&mut self, start_time: f32, duration: f32, rate_func: EaseType) { 454 | for animation in self.animations.iter_mut() { 455 | match animation { 456 | AnimationType::StrokeColor(ref mut animation) => { 457 | set_properties(animation, start_time, duration, rate_func); 458 | } 459 | AnimationType::StrokeWeight(ref mut animation) => { 460 | set_properties(animation, start_time, duration, rate_func); 461 | } 462 | AnimationType::FillColor(ref mut animation) => { 463 | set_properties(animation, start_time, duration, rate_func); 464 | } 465 | AnimationType::Position(ref mut animation) => { 466 | set_properties(animation, start_time, duration, rate_func); 467 | } 468 | AnimationType::Angle(ref mut animation) => { 469 | set_properties(animation, start_time, duration, rate_func); 470 | } 471 | AnimationType::Scale(ref mut animation) => { 472 | set_properties(animation, start_time, duration, rate_func); 473 | } 474 | AnimationType::Size(ref mut animation) => { 475 | set_properties(animation, start_time, duration, rate_func); 476 | } 477 | AnimationType::FontSize(ref mut animation) => { 478 | set_properties(animation, start_time, duration, rate_func); 479 | } 480 | AnimationType::Opacity(ref mut animation) => { 481 | set_properties(animation, start_time, duration, rate_func); 482 | } 483 | AnimationType::PathCompletion(ref mut animation) => { 484 | set_properties(animation, start_time, duration, rate_func); 485 | } 486 | AnimationType::Path(ref mut animation) => { 487 | set_properties(animation, start_time, duration, rate_func); 488 | } 489 | } 490 | } 491 | } 492 | } 493 | 494 | impl Into> for EntityAnimations { 495 | fn into(self) -> Vec { 496 | vec![self] 497 | } 498 | } 499 | -------------------------------------------------------------------------------- /noon/src/animation/path.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::Path; 3 | 4 | pub trait WithPath: WithId { 5 | fn show_creation(&self) -> EntityAnimations { 6 | EntityAnimations { 7 | entity: self.id(), 8 | animations: vec![ 9 | Animation::::to(Opacity::FULL) 10 | .with_duration(0.0) 11 | .into(), 12 | Animation::::to(PathCompletion(1.0)).into(), 13 | ], 14 | } 15 | } 16 | fn fade_in(&self) -> EntityAnimations { 17 | EntityAnimations { 18 | entity: self.id(), 19 | animations: vec![ 20 | Animation::::to(PathCompletion(1.0)) 21 | .with_duration(0.0) 22 | .into(), 23 | Animation::to(Opacity(1.0)).into(), 24 | ], 25 | } 26 | } 27 | fn fade_out(&self) -> EntityAnimations { 28 | EntityAnimations { 29 | entity: self.id(), 30 | animations: Animation::to(Opacity(0.0)).into(), 31 | } 32 | } 33 | fn morph(&self, entity: impl Into) -> EntityAnimations { 34 | let entity: Entity = entity.into(); 35 | EntityAnimations { 36 | entity: self.id(), 37 | animations: vec![ 38 | Animation::::to_target(entity).into(), 39 | Animation::::to_target(entity).into(), 40 | Animation::::to_target(entity).into(), 41 | Animation::::to_target(entity).into(), 42 | Animation::::to_target(entity).into(), 43 | Animation::::to_target(entity).into(), 44 | Animation::::to_target(entity).into(), 45 | ], 46 | } 47 | } 48 | } 49 | 50 | pub trait Create + Copy> { 51 | fn scene_mut(&mut self) -> &mut Scene; 52 | fn make(&mut self) -> ObjectId; 53 | fn show(&mut self) -> ObjectId { 54 | let id = self.make(); 55 | let animations = EntityAnimations { 56 | entity: id.into(), 57 | animations: vec![ 58 | Animation::to(Opacity(1.0)).into(), 59 | Animation::to(PathCompletion(1.0)).into(), 60 | ], 61 | }; 62 | 63 | AnimBuilder::new(self.scene_mut(), animations.into()).run_time(0.0); 64 | id 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /noon/src/animation/spatial.rs: -------------------------------------------------------------------------------- 1 | use crate::Scale; 2 | 3 | use super::*; 4 | 5 | pub trait WithPosition: WithId { 6 | fn move_to(&self, x: f32, y: f32) -> EntityAnimations { 7 | EntityAnimations { 8 | entity: self.id(), 9 | animations: Animation::to(Position { x, y }).into(), 10 | } 11 | } 12 | fn move_by(&self, x: f32, y: f32) -> EntityAnimations { 13 | EntityAnimations { 14 | entity: self.id(), 15 | animations: Animation::by(Position { x, y }).into(), 16 | } 17 | } 18 | fn shift(&self, direction: Vector) -> EntityAnimations { 19 | EntityAnimations { 20 | entity: self.id(), 21 | animations: Animation::by(Position { 22 | x: direction.x, 23 | y: direction.y, 24 | }) 25 | .into(), 26 | } 27 | } 28 | fn to_edge(&self, direction: Direction) -> EntityAnimations { 29 | EntityAnimations { 30 | entity: self.id(), 31 | animations: Animation::::to_edge(direction).into(), 32 | } 33 | } 34 | fn move_to_object(&self, object: impl Into) -> EntityAnimations { 35 | EntityAnimations { 36 | entity: self.id(), 37 | animations: Animation::::to_target(object.into()).into(), 38 | } 39 | } 40 | } 41 | 42 | pub trait WithAngle: WithId { 43 | fn set_angle(&self, to_radians: f32) -> EntityAnimations { 44 | EntityAnimations { 45 | entity: self.id(), 46 | animations: vec![Animation::to(Angle(to_radians)).into()], 47 | } 48 | } 49 | fn rotate(&self, by_radians: f32) -> EntityAnimations { 50 | EntityAnimations { 51 | entity: self.id(), 52 | animations: vec![Animation::by(Angle(by_radians)).into()], 53 | } 54 | } 55 | } 56 | pub trait WithSize: WithId { 57 | fn set_size(&self, width: f32, height: f32) -> EntityAnimations { 58 | EntityAnimations { 59 | entity: self.id(), 60 | animations: vec![Animation::to(Size::from(width, height)).into()], 61 | } 62 | } 63 | fn scale(&self, by: f32) -> EntityAnimations { 64 | EntityAnimations { 65 | entity: self.id(), 66 | animations: vec![Animation::times(Scale::new(by, by)).into()], 67 | } 68 | } 69 | fn scale_x(&self, x: f32) -> EntityAnimations { 70 | EntityAnimations { 71 | entity: self.id(), 72 | animations: vec![Animation::times(Scale::new(x, 1.0)).into()], 73 | } 74 | } 75 | fn scale_y(&self, y: f32) -> EntityAnimations { 76 | EntityAnimations { 77 | entity: self.id(), 78 | animations: vec![Animation::times(Scale::new(1.0, y)).into()], 79 | } 80 | } 81 | fn scale_xy(&self, x: f32, y: f32) -> EntityAnimations { 82 | EntityAnimations { 83 | entity: self.id(), 84 | animations: vec![Animation::times(Scale::new(x, y)).into()], 85 | } 86 | } 87 | } 88 | 89 | pub trait WithFontSize: WithId { 90 | fn set_font_size(&self, size: u32) -> EntityAnimations { 91 | EntityAnimations { 92 | entity: self.id(), 93 | animations: vec![Animation::to(FontSize(size)).into()], 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /noon/src/color.rs: -------------------------------------------------------------------------------- 1 | use crate::Interpolate; 2 | use nannou::color::{rgb_u32, rgba, Rgb}; 3 | use nannou::rand::{prelude::SliceRandom, thread_rng}; 4 | use std::marker::PhantomData; 5 | 6 | pub type Color = nannou::color::Rgb; 7 | 8 | impl Interpolate for Color { 9 | fn interp(&self, other: &Self, progress: f32) -> Self { 10 | let progress = progress.min(1.0).max(0.0); 11 | Self { 12 | red: self.red.interp(&other.red, progress), 13 | green: self.green.interp(&other.green, progress), 14 | blue: self.blue.interp(&other.blue, progress), 15 | standard: PhantomData, 16 | } 17 | } 18 | } 19 | 20 | impl ColorExtension for Color { 21 | fn get_color(&self) -> Color { 22 | *self 23 | } 24 | } 25 | 26 | pub trait ColorExtension { 27 | const WHITE: Color = Color { 28 | red: 255.0 / 255.0, 29 | green: 255.0 / 255.0, 30 | blue: 255.0 / 255.0, 31 | standard: PhantomData, 32 | }; 33 | const BLACK: Color = Color { 34 | red: 0.0 / 255.0, 35 | green: 0.0 / 255.0, 36 | blue: 0.0 / 255.0, 37 | standard: PhantomData, 38 | }; 39 | const RED: Color = Color { 40 | red: 255.0 / 255.0, 41 | green: 0.0 / 255.0, 42 | blue: 0.0 / 255.0, 43 | standard: PhantomData, 44 | }; 45 | const BLUE: Color = Color { 46 | red: 0.0 / 255.0, 47 | green: 0.0 / 255.0, 48 | blue: 255.0 / 255.0, 49 | standard: PhantomData, 50 | }; 51 | 52 | fn palette() -> [Color; 5] { 53 | [ 54 | rgb_from_hex(0x264653), 55 | rgb_from_hex(0x2a9d8f), 56 | rgb_from_hex(0xe9c46a), 57 | rgb_from_hex(0xf4a261), 58 | rgb_from_hex(0xe76f51), 59 | ] 60 | } 61 | fn random() -> Color { 62 | *Self::palette().choose(&mut thread_rng()).unwrap() 63 | } 64 | fn get_color(&self) -> Color; 65 | fn brighten(&self) -> Color { 66 | let mut hsv: nannou::color::Hsv = self.get_color().into_linear().into(); 67 | hsv.saturation -= 0.1; 68 | hsv.value += 0.2; 69 | hsv.into() 70 | } 71 | } 72 | 73 | pub fn rgb_from_hex(color: u32) -> Rgb { 74 | let color = rgb_u32(color); 75 | rgba( 76 | color.red as f32 / 255.0, 77 | color.green as f32 / 255.0, 78 | color.blue as f32 / 255.0, 79 | 1.0, 80 | ) 81 | .into() 82 | } 83 | -------------------------------------------------------------------------------- /noon/src/component.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::Direction; 2 | use crate::{point, Color, PixelFrame, Point, Vector, TO_PXL}; 3 | use bevy_ecs::prelude::*; 4 | use nannou::color::{IntoLinSrgba, LinSrgba}; 5 | use nannou::lyon::math as euclid; 6 | use std::ops::{Add, Mul}; 7 | 8 | pub trait Interpolate { 9 | fn interp(&self, other: &T, progress: f32) -> Self 10 | where 11 | T: Into, 12 | Self: Sized; 13 | } 14 | 15 | impl Interpolate for f32 { 16 | fn interp(&self, other: &Self, progress: f32) -> Self { 17 | self + (other - self) * progress 18 | } 19 | } 20 | 21 | impl Interpolate for u32 { 22 | fn interp(&self, other: &Self, progress: f32) -> Self { 23 | self + ((other - self) as f32 * progress) as u32 24 | } 25 | } 26 | 27 | #[derive(Component)] 28 | pub struct Name(String); 29 | 30 | #[derive(Debug, Component, Default, Clone, Copy)] 31 | pub struct Transform(pub(crate) euclid::Transform); 32 | 33 | impl Transform { 34 | pub fn new() -> Self { 35 | Self(euclid::Transform::identity()) 36 | } 37 | pub fn identity() -> Self { 38 | Self(euclid::Transform::identity()) 39 | } 40 | /// Translation. Untested 41 | pub fn translate(mut self, vector: Vector) -> Self { 42 | self.translate_mut(vector); 43 | self 44 | } 45 | /// Translation. Untested 46 | pub fn translate_mut(&mut self, vector: Vector) { 47 | *self = Self(self.0.then_translate(vector)); 48 | } 49 | /// Rotation. Untested 50 | pub fn rotate(mut self, angle: Angle) -> Self { 51 | self.rotate_mut(angle); 52 | self 53 | } 54 | /// Rotation. Untested 55 | pub fn rotate_mut(&mut self, angle: Angle) { 56 | *self = Self(self.0.then_rotate(euclid::Angle::radians(angle.0))); 57 | } 58 | /// Scale. Untested 59 | pub fn scale(mut self, scale: Scale) -> Self { 60 | self.scale_mut(scale); 61 | self 62 | } 63 | /// Scale. Untested 64 | pub fn scale_mut(&mut self, scale: Scale) { 65 | *self = Self(self.0.then_scale(scale.x, scale.y)); 66 | } 67 | pub fn transform(self, transform: Transform) -> Self { 68 | Self(self.0.then(&transform.0)) 69 | } 70 | } 71 | 72 | #[derive(Debug, Component, Default, Clone)] 73 | pub struct Children(pub(crate) Vec); 74 | 75 | impl Children { 76 | pub fn add(&mut self, entity: impl Into) { 77 | self.0.push(entity.into()); 78 | } 79 | } 80 | 81 | #[derive(Debug, Component, Default, Clone, Copy)] 82 | pub struct Scale { 83 | pub x: f32, 84 | pub y: f32, 85 | } 86 | 87 | impl Scale { 88 | pub const ONE: Self = Self { x: 1.0, y: 1.0 }; 89 | 90 | pub fn new(x: f32, y: f32) -> Self { 91 | Self { x, y } 92 | } 93 | } 94 | 95 | impl Mul for Scale { 96 | type Output = Self; 97 | fn mul(self, other: Scale) -> Self::Output { 98 | Self { 99 | x: self.x * other.x, 100 | y: self.y * other.y, 101 | } 102 | } 103 | } 104 | 105 | impl Interpolate for Scale { 106 | fn interp(&self, other: &Self, progress: f32) -> Self { 107 | Self { 108 | x: self.x.interp(&other.x, progress), 109 | y: self.y.interp(&other.y, progress), 110 | } 111 | } 112 | } 113 | 114 | #[derive(Debug, Component, Default, Clone, Copy)] 115 | pub struct Position { 116 | pub x: f32, 117 | pub y: f32, 118 | } 119 | 120 | impl Position { 121 | pub fn from_points(points: &[Point]) -> Self { 122 | let sum = points 123 | .iter() 124 | .fold(point(0.0, 0.0), |sum, &p| point(sum.x + p.x, sum.y + p.y)); 125 | Position { 126 | x: sum.x / points.len() as f32, 127 | y: sum.y / points.len() as f32, 128 | } 129 | } 130 | } 131 | 132 | impl Interpolate for Position { 133 | fn interp(&self, other: &Self, progress: f32) -> Self { 134 | Self { 135 | x: self.x.interp(&other.x, progress), 136 | y: self.y.interp(&other.y, progress), 137 | } 138 | } 139 | } 140 | 141 | impl Add for Position { 142 | type Output = Self; 143 | fn add(self, other: Self) -> Self::Output { 144 | Self { 145 | x: self.x + other.x, 146 | y: self.y + other.y, 147 | } 148 | } 149 | } 150 | 151 | impl Into for Position { 152 | fn into(self) -> Vector { 153 | Vector::new(self.x, self.y) 154 | } 155 | } 156 | 157 | impl std::fmt::Display for Position { 158 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 159 | write!(f, "(x:{:3.2}, y:{:3.2})", self.x, self.y) 160 | } 161 | } 162 | 163 | impl PixelFrame for Position { 164 | fn into_pxl_scale(&self) -> Self { 165 | Self { 166 | x: self.x * TO_PXL, 167 | y: self.y * TO_PXL, 168 | } 169 | } 170 | fn into_natural_scale(&self) -> Self { 171 | Self { 172 | x: self.x / TO_PXL, 173 | y: self.y / TO_PXL, 174 | } 175 | } 176 | } 177 | 178 | #[derive(Debug, Component, Default, Clone, Copy)] 179 | pub struct Angle(pub(crate) f32); 180 | 181 | impl Interpolate for Angle { 182 | fn interp(&self, other: &Self, progress: f32) -> Self { 183 | Self(self.0.interp(&other.0, progress)) 184 | } 185 | } 186 | 187 | impl Add for Angle { 188 | type Output = Self; 189 | fn add(self, other: Self) -> Self::Output { 190 | Self(self.0 + other.0) 191 | } 192 | } 193 | 194 | #[derive(Debug, Component, Default, Clone, Copy)] 195 | pub struct Depth(pub(crate) f32); 196 | 197 | impl Interpolate for Depth { 198 | fn interp(&self, other: &Self, progress: f32) -> Self { 199 | Self(self.0.interp(&other.0, progress)) 200 | } 201 | } 202 | 203 | #[derive(Debug, Component, Clone, Copy)] 204 | pub struct FontSize(pub(crate) u32); 205 | 206 | impl Interpolate for FontSize { 207 | fn interp(&self, other: &Self, progress: f32) -> Self { 208 | Self(self.0.interp(&other.0, progress)) 209 | } 210 | } 211 | 212 | impl Add for FontSize { 213 | type Output = Self; 214 | fn add(self, other: Self) -> Self::Output { 215 | Self(self.0 + other.0) 216 | } 217 | } 218 | 219 | #[derive(Debug, Component, Default, Clone, Copy)] 220 | pub struct StrokeWeight(pub(crate) f32); 221 | 222 | impl StrokeWeight { 223 | /// Think stroke. Normal default for shapes. 224 | pub const THICK: Self = Self(3.0); 225 | /// Thin stroke. Use it for very thin shape outline. 226 | pub const THIN: Self = Self(1.0); 227 | /// No stroke 228 | pub const NONE: Self = Self(0.0); 229 | /// Let the shape determine it's stroke width based on its size. 230 | pub const AUTO: Self = Self(-1.0); 231 | /// Determines if stroke should be drawn 232 | pub fn is_none(&self) -> bool { 233 | self.0.abs() < std::f32::EPSILON 234 | } 235 | /// Determines the stroke mode between auto or manual. 236 | pub fn is_auto(&self) -> bool { 237 | self.0 < 0.0 238 | } 239 | } 240 | 241 | impl Interpolate for StrokeWeight { 242 | fn interp(&self, other: &Self, progress: f32) -> Self { 243 | if self.is_auto() { 244 | Self::AUTO 245 | } else { 246 | let progress = progress.min(1.0).max(0.0); 247 | Self(self.0.interp(&other.0, progress)) 248 | } 249 | } 250 | } 251 | 252 | #[derive(Debug, Component, Default, Clone, Copy)] 253 | pub struct HasFill(pub(crate) bool); 254 | 255 | #[derive(Debug, Component, Default, Clone, Copy)] 256 | pub struct Opacity(pub(crate) f32); 257 | 258 | impl Opacity { 259 | pub const FULL: Self = Self(1.0); 260 | pub const HALF: Self = Self(0.5); 261 | pub const CLEAR: Self = Self(0.0); 262 | pub fn is_visible(&self) -> bool { 263 | self.0 > 0.0 264 | } 265 | } 266 | 267 | impl Interpolate for Opacity { 268 | fn interp(&self, other: &Self, progress: f32) -> Self { 269 | let progress = progress.min(1.0).max(0.0); 270 | Self(self.0.interp(&other.0, progress)) 271 | } 272 | } 273 | 274 | impl Add for Opacity { 275 | type Output = Self; 276 | fn add(self, other: Self) -> Self::Output { 277 | Self(self.0 + other.0) 278 | } 279 | } 280 | 281 | #[derive(Debug, Component, Default, Clone, Copy)] 282 | pub struct PathCompletion(pub(crate) f32); 283 | 284 | impl Interpolate for PathCompletion { 285 | fn interp(&self, other: &Self, progress: f32) -> Self { 286 | let progress = progress.min(1.0).max(0.0); 287 | Self(self.0.interp(&other.0, progress)) 288 | } 289 | } 290 | 291 | impl Add for PathCompletion { 292 | type Output = Self; 293 | fn add(self, other: Self) -> Self::Output { 294 | Self(self.0 + other.0) 295 | } 296 | } 297 | 298 | #[derive(Debug, Component, Clone, Copy)] 299 | pub struct FillColor(pub(crate) Color); 300 | 301 | impl Interpolate for FillColor { 302 | fn interp(&self, other: &Self, progress: f32) -> Self { 303 | let progress = progress.min(1.0).max(0.0); 304 | FillColor(self.0.interp(&other.0, progress)) 305 | } 306 | } 307 | 308 | impl IntoLinSrgba for FillColor { 309 | fn into_lin_srgba(self) -> LinSrgba { 310 | IntoLinSrgba::into_lin_srgba(self.0) 311 | } 312 | } 313 | 314 | #[derive(Debug, Component, Clone, Copy)] 315 | pub struct StrokeColor(pub(crate) Color); 316 | 317 | impl Interpolate for StrokeColor { 318 | fn interp(&self, other: &Self, progress: f32) -> Self { 319 | let progress = progress.min(1.0).max(0.0); 320 | StrokeColor(self.0.interp(&other.0, progress)) 321 | } 322 | } 323 | 324 | impl IntoLinSrgba for StrokeColor { 325 | fn into_lin_srgba(self) -> LinSrgba { 326 | IntoLinSrgba::into_lin_srgba(self.0) 327 | } 328 | } 329 | 330 | /// This is used as a means of storing the same [Component], 331 | /// but with a different interpretation of the contained 332 | /// value for [Animation](crate::Animation). 333 | /// 334 | /// There are 3 cases of animation for most [Component]s: 335 | /// 1. Animate to an absolute value (e.g. move to absolute position) 336 | /// 2. Animate with respect to the specified change (i.e. relative to current) 337 | /// 3. Use another object's current state as the final value 338 | #[derive(Component, Clone, Copy, Debug)] 339 | pub enum Value { 340 | /// Indicates an absolute final state for animation 341 | Absolute(C), 342 | /// Indicates a relative change to apply in animation 343 | Relative(C), 344 | /// Indicates a multiplicative change to apply in animation 345 | Multiply(C), 346 | /// Used for moving object to edges 347 | Edge(Direction), 348 | /// Contains another object's ID to query for it's information 349 | From(Entity), 350 | } 351 | 352 | #[derive(Component, Clone, Copy)] 353 | pub struct Previous(pub(crate) T); 354 | 355 | // /// Cache is used to wrap around any [Component] and keep track of 356 | // /// it's changes. 357 | // /// 358 | // /// Since `Bevy`'s ECS system does not specifically provide mechanisms 359 | // /// for querying the amount of change that occured, we need to handle 360 | // /// it via this data type. It means we need to add [Cache] for any 361 | // /// component which we intend to keep track of it's change amount, 362 | // /// but it gets the job done without too much work for now. 363 | // /// 364 | // /// Where is this actually used? Since [Path](crate::Path) and 365 | // /// [Size](crate::Size) are both [Component]s of ECS and go through 366 | // /// different animations (e.g. path changes when morphing, and size 367 | // /// changes when scaling), we need to sync them when change occurs in 368 | // /// one of them. Therefore, when size changes, scale is computed by 369 | // /// observing their delta, and applies respective changes to the path. 370 | // #[derive(Component, Clone, Copy)] 371 | // pub struct Cached { 372 | // pub(crate) before: T, 373 | // pub(crate) now: T, 374 | // } 375 | 376 | // // use nannou::math::num_traits::Float; 377 | // impl Cached 378 | // where 379 | // T: Component + Clone + Copy, 380 | // { 381 | // pub fn new(value: T) -> Self { 382 | // Self { 383 | // before: value, 384 | // now: value, 385 | // } 386 | // } 387 | // pub fn update(&mut self, value: T) { 388 | // self.before = self.now; 389 | // self.now = value; 390 | // } 391 | // // pub fn has_changed(&self) { 392 | // // self.before Float::epsilon(); 393 | // // } 394 | // } 395 | 396 | // impl Cached { 397 | // pub fn has_changed(&self) -> bool { 398 | // if ((self.before.width - self.now.width).abs() > 1.0e-4) 399 | // || ((self.before.height - self.now.height).abs() > 1.0e-4) 400 | // { 401 | // true 402 | // } else { 403 | // false 404 | // } 405 | // } 406 | // } 407 | 408 | // impl Interpolate for Cached 409 | // where 410 | // T: Interpolate + Component + Clone + Copy, 411 | // { 412 | // fn interp(&self, other: &Self, progress: f32) -> Self { 413 | // let before = self.now; 414 | // Self { 415 | // before, 416 | // now: Interpolate::interp(&self.now, &other.now, progress), 417 | // } 418 | // } 419 | // } 420 | -------------------------------------------------------------------------------- /noon/src/consts.rs: -------------------------------------------------------------------------------- 1 | use crate::Vector; 2 | 3 | pub const PXL_WIDTH: u32 = 1920; 4 | pub const PXL_HEIGHT: u32 = 1080; 5 | 6 | /// Path flattenening tolerance for normal shapes under normal condition. 7 | pub const EPS: f32 = 0.0001; 8 | 9 | /// Path flattenening tolerance for interpolation and other 10 | /// tasks where computation may be higher than usual. 11 | pub const EPS_LOW: f32 = 0.001; 12 | 13 | /// Scale conversion between noon and pixel coordinates 14 | pub const TO_PXL: f32 = 200.0; 15 | pub const ZOOM: f32 = 200.0; 16 | 17 | pub const PI: f32 = std::f32::consts::PI; 18 | 19 | pub const TAU: f32 = std::f32::consts::TAU; 20 | 21 | pub const UP: Vector = Vector::new(0.0, 1.0); 22 | pub const DOWN: Vector = Vector::new(0.0, -1.0); 23 | pub const LEFT: Vector = Vector::new(-1.0, 0.0); 24 | pub const RIGHT: Vector = Vector::new(1.0, 0.0); 25 | -------------------------------------------------------------------------------- /noon/src/ease.rs: -------------------------------------------------------------------------------- 1 | use pennereq::*; 2 | 3 | type EaseFn = fn(t: S, b: S, c: S, d: S) -> S; 4 | 5 | #[derive(Debug, Copy, Clone, PartialEq)] 6 | pub enum EaseType { 7 | Linear, 8 | Quad, 9 | QuadIn, 10 | QuadOut, 11 | Cubic, 12 | CubicIn, 13 | CubicOut, 14 | Quart, 15 | QuartIn, 16 | QuartOut, 17 | Quint, 18 | QuintIn, 19 | QuintOut, 20 | Sine, 21 | SineIn, 22 | SineOut, 23 | Expo, 24 | ExpoIn, 25 | ExpoOut, 26 | Circ, 27 | CircIn, 28 | CircOut, 29 | Elastic, 30 | ElasticIn, 31 | ElasticOut, 32 | Back, 33 | BackIn, 34 | BackOut, 35 | Bounce, 36 | BounceIn, 37 | BounceOut, 38 | } 39 | 40 | impl EaseType { 41 | pub fn calculate(&self, t: f32) -> f32 { 42 | let ease_func: EaseFn = match self { 43 | EaseType::Linear => linear::ease, 44 | EaseType::Quad => quad::ease_in_out, 45 | EaseType::QuadIn => quad::ease_in, 46 | EaseType::QuadOut => quad::ease_out, 47 | EaseType::Cubic => cubic::ease_in_out, 48 | EaseType::CubicIn => cubic::ease_in, 49 | EaseType::CubicOut => cubic::ease_out, 50 | EaseType::Quart => quart::ease_in_out, 51 | EaseType::QuartIn => quart::ease_in, 52 | EaseType::QuartOut => quart::ease_out, 53 | EaseType::Quint => quint::ease_in_out, 54 | EaseType::QuintIn => quint::ease_in, 55 | EaseType::QuintOut => quint::ease_out, 56 | EaseType::Sine => sine::ease_in_out, 57 | EaseType::SineIn => sine::ease_in, 58 | EaseType::SineOut => sine::ease_out, 59 | EaseType::Expo => expo::ease_in_out, 60 | EaseType::ExpoIn => expo::ease_in, 61 | EaseType::ExpoOut => expo::ease_out, 62 | EaseType::Circ => circ::ease_in_out, 63 | EaseType::CircIn => circ::ease_in, 64 | EaseType::CircOut => circ::ease_out, 65 | EaseType::Elastic => elastic::ease_in_out, 66 | EaseType::ElasticIn => elastic::ease_in, 67 | EaseType::ElasticOut => elastic::ease_out, 68 | EaseType::Back => back::ease_in_out, 69 | EaseType::BackIn => back::ease_in, 70 | EaseType::BackOut => back::ease_out, 71 | EaseType::Bounce => bounce::ease_in_out, 72 | EaseType::BounceIn => bounce::ease_in, 73 | EaseType::BounceOut => bounce::ease_out, 74 | }; 75 | ease_func(t, 0.0, 1.0, 1.0) 76 | } 77 | } 78 | 79 | impl Default for EaseType { 80 | fn default() -> Self { 81 | Self::Quad 82 | } 83 | } 84 | // Linear takes normalized time and returns unmodified value. 85 | // This is the default ease function in addition to pennereq crate. 86 | pub mod linear { 87 | #[inline] 88 | pub fn ease(t: T, _b: T, _c: T, _d: T) -> T { 89 | t 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /noon/src/geom.rs: -------------------------------------------------------------------------------- 1 | use crate::{Interpolate, Path, Position, TO_PXL}; 2 | use bevy_ecs::prelude::Component; 3 | pub use nannou::lyon::math::{point, Point, Vector}; 4 | use std::{marker::PhantomData, ops::Mul}; 5 | 6 | #[derive(Component, Clone, Copy, Debug)] 7 | pub enum Direction { 8 | Up, 9 | Down, 10 | Left, 11 | Right, 12 | } 13 | 14 | /// Trait for converting from native Noon scale into pixel scale 15 | pub trait PixelFrame { 16 | fn into_pxl_scale(&self) -> Self; 17 | fn into_natural_scale(&self) -> Self; 18 | } 19 | 20 | impl Interpolate for Point { 21 | fn interp(&self, other: &Self, progress: f32) -> Self { 22 | point( 23 | self.x.interp(&other.x, progress), 24 | self.y.interp(&other.y, progress), 25 | ) 26 | } 27 | } 28 | 29 | impl Into for Point { 30 | fn into(self) -> Position { 31 | Position { 32 | x: self.x, 33 | y: self.y, 34 | } 35 | } 36 | } 37 | 38 | impl PixelFrame for Point { 39 | fn into_pxl_scale(&self) -> Self { 40 | Self { 41 | x: self.x * TO_PXL, 42 | y: self.y * TO_PXL, 43 | _unit: PhantomData, 44 | } 45 | } 46 | fn into_natural_scale(&self) -> Self { 47 | Self { 48 | x: self.x / TO_PXL, 49 | y: self.y / TO_PXL, 50 | _unit: PhantomData, 51 | } 52 | } 53 | } 54 | 55 | /// Data type to represent physical size of any 2D object. 56 | #[derive(Debug, Component, Clone, Copy)] 57 | pub struct BoundingSize(pub(crate) Size); 58 | 59 | impl BoundingSize { 60 | /// Update the bounding size of the [Path], when rotated by [Angle]. 61 | pub fn from(path: &Path, angle: f32) -> BoundingSize { 62 | use nannou::lyon::algorithms::aabb::bounding_rect; 63 | let rotated = path 64 | .raw 65 | .clone() 66 | .transformed(&nannou::lyon::geom::Rotation::radians(angle)); 67 | 68 | let rect = bounding_rect(rotated.iter()); 69 | BoundingSize(Size::from(rect.width() / TO_PXL, rect.height() / TO_PXL)) 70 | } 71 | } 72 | 73 | /// Data type to represent physical size of any 2D object. 74 | #[derive(Debug, Component, Clone, Copy)] 75 | pub struct Size { 76 | pub width: f32, 77 | pub height: f32, 78 | } 79 | 80 | impl Size { 81 | /// Indicate size of zero (e.g. dot) 82 | pub const ZERO: Self = Self { 83 | width: 0.0, 84 | height: 0.0, 85 | }; 86 | pub const UNIT: Self = Self { 87 | width: 1.0, 88 | height: 1.0, 89 | }; 90 | 91 | /// Create size from radius, assuming circular shape 92 | pub fn from_radius(radius: f32) -> Self { 93 | Self { 94 | width: radius * 2.0, 95 | height: radius * 2.0, 96 | } 97 | } 98 | 99 | /// Constructor for size 100 | pub fn from(width: f32, height: f32) -> Self { 101 | Self { width, height } 102 | } 103 | 104 | /// Returns scale factor to the given input size. If the given 105 | /// input size is greater, scale factor will be greater than 1. 106 | pub fn scale_factor(&self, other: &Self) -> (f32, f32) { 107 | if self.width < std::f32::EPSILON { 108 | (1.0, other.height / self.height) 109 | } else if self.height < std::f32::EPSILON { 110 | (other.width / self.width, 1.0) 111 | } else { 112 | (other.width / self.width, other.height / self.height) 113 | } 114 | } 115 | 116 | /// Compute the size of the 2D bounding box for a given set 117 | /// of points. 118 | pub fn from_points(points: &[Point]) -> Self { 119 | if !points.is_empty() { 120 | let mut min = *points.first().unwrap(); 121 | let mut max = *points.first().unwrap(); 122 | 123 | for &p in points.iter() { 124 | if p.x < min.x { 125 | min.x = p.x; 126 | } 127 | if p.y < min.y { 128 | min.y = p.y; 129 | } 130 | if p.x > max.x { 131 | max.x = p.x; 132 | } 133 | if p.y > max.y { 134 | max.y = p.y; 135 | } 136 | } 137 | Size { 138 | width: (max.x - min.x).abs(), 139 | height: (max.y - min.y).abs(), 140 | } 141 | } else { 142 | Size::ZERO 143 | } 144 | } 145 | } 146 | 147 | impl PixelFrame for Size { 148 | fn into_pxl_scale(&self) -> Self { 149 | Self { 150 | width: self.width * TO_PXL, 151 | height: self.height * TO_PXL, 152 | } 153 | } 154 | fn into_natural_scale(&self) -> Self { 155 | Self { 156 | width: self.width / TO_PXL, 157 | height: self.height / TO_PXL, 158 | } 159 | } 160 | } 161 | 162 | impl Mul for Size { 163 | type Output = Self; 164 | fn mul(self, other: Size) -> Self::Output { 165 | Self { 166 | width: self.width * other.width, 167 | height: self.height * other.height, 168 | } 169 | } 170 | } 171 | 172 | impl Mul for Size { 173 | type Output = Self; 174 | fn mul(self, value: f32) -> Self::Output { 175 | Self { 176 | width: self.width * value, 177 | height: self.height * value, 178 | } 179 | } 180 | } 181 | 182 | impl std::fmt::Display for Size { 183 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 184 | write!(f, "(width:{:3.2}, height:{:3.2})", self.width, self.height) 185 | } 186 | } 187 | 188 | impl Interpolate for Size { 189 | fn interp(&self, other: &Self, progress: f32) -> Self { 190 | Self { 191 | width: self.width.interp(&other.width, progress), 192 | height: self.height.interp(&other.height, progress), 193 | } 194 | } 195 | } 196 | 197 | #[cfg(test)] 198 | mod tests { 199 | use super::*; 200 | #[test] 201 | fn size_from_points() { 202 | let points = vec![point(2.0, 3.0), point(4.0, 1.0), point(8.0, -3.0)]; 203 | assert_eq!(Size::from_points(&points).width, 6.0); 204 | assert_eq!(Size::from_points(&points).height, 6.0); 205 | } 206 | #[test] 207 | fn check_point_cmp() { 208 | let p1 = point(3.0, 1.0); 209 | let p2 = point(2.0, 4.0); 210 | 211 | assert_eq!(point(2.0, 1.0), p1.min(p2)); 212 | assert_eq!(point(2.0, 1.0), p2.min(p1)); 213 | assert_eq!(point(3.0, 4.0), p1.max(p2)); 214 | assert_eq!(point(3.0, 4.0), p2.max(p1)); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /noon/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod animation; 2 | // mod app; 3 | pub mod color; 4 | pub mod component; 5 | pub mod consts; 6 | pub mod ease; 7 | pub mod geom; 8 | pub mod object; 9 | pub mod path; 10 | pub mod scene; 11 | pub mod system; 12 | 13 | pub use crate::animation::{ 14 | AnimBuilder, Animation, AnimationType, Animations, Create, EntityAnimations, WithAngle, 15 | WithColor, WithFill, WithFontSize, WithId, WithPath, WithPosition, WithSize, WithStroke, 16 | WithStrokeWeight, 17 | }; 18 | 19 | pub use crate::color::{Color, ColorExtension}; 20 | pub use crate::component::{ 21 | Angle, Depth, FillColor, FontSize, HasFill, Interpolate, Name, Opacity, PathCompletion, 22 | Position, Previous, Scale, StrokeColor, StrokeWeight, Transform, Value, 23 | }; 24 | 25 | pub use crate::geom::{point, BoundingSize, PixelFrame, Point, Size, Vector}; 26 | pub use crate::path::{GetPartial, Path, PathComponent, PixelPath}; 27 | pub use consts::*; 28 | pub use ease::EaseType; 29 | pub use object::*; 30 | pub use scene::{Bounds, Scene}; 31 | pub use system::{animate, init_from_target, print, update_time, Time}; 32 | 33 | pub use nannou; 34 | pub use nannou::{app, rand}; 35 | 36 | pub mod prelude { 37 | pub use crate::animation::{ 38 | AnimBuilder, Animation, AnimationType, Animations, Create, EntityAnimations, WithAngle, 39 | WithColor, WithFill, WithFontSize, WithId, WithPath, WithPosition, WithSize, WithStroke, 40 | WithStrokeWeight, 41 | }; 42 | pub use crate::consts::*; 43 | pub use crate::{ 44 | geom::Direction, 45 | object::{CircleId, TextId}, 46 | CircleBuilder, Color, ColorExtension, EaseType, Scene, StrokeWeight, TextBuilder, 47 | }; 48 | pub use nannou::app; 49 | pub use nannou::app::ModelFn; 50 | pub use nannou::geom::Rect; 51 | pub use nannou::prelude::*; 52 | } 53 | 54 | // impl Construct for Scene { 55 | // fn construct(&mut self) { 56 | // let mut animations = Vec::new(); 57 | // let mut show = Vec::new(); 58 | // for _ in 0..2000 { 59 | // let (x, y, w, h, _ang, color) = gen_random_values(); 60 | 61 | // if nannou::rand::random::() { 62 | // let circle = self 63 | // .circle() 64 | // .with_position(x, y) 65 | // .with_color(color) 66 | // .with_radius(w / 2.0) 67 | // .make(); 68 | 69 | // let (x, y, w, _h, _ang, color) = gen_random_values(); 70 | 71 | // show.push(circle.show_creation()); 72 | 73 | // animations.extend(vec![ 74 | // circle.set_color(color), 75 | // circle.move_to(x, y), 76 | // circle.set_radius(w / 2.0), 77 | // ]); 78 | // } else { 79 | // let rect = self 80 | // .rectangle() 81 | // .with_position(x, y) 82 | // .with_color(color) 83 | // .with_size(w, h) 84 | // .make(); 85 | 86 | // let (x, y, w, h, ang, color) = gen_random_values(); 87 | 88 | // show.push(rect.show_creation()); 89 | 90 | // animations.extend(vec![ 91 | // rect.set_color(color), 92 | // rect.move_to(x, y), 93 | // rect.set_size(w, h), 94 | // rect.set_angle(ang), 95 | // ]); 96 | // } 97 | // } 98 | 99 | // self.wait(); 100 | // self.play(show).run_time(1.0).lag(0.001); 101 | 102 | // self.play(animations) 103 | // .run_time(3.0) 104 | // .lag(0.0001) 105 | // .rate_func(EaseType::Quint); 106 | // } 107 | // } 108 | 109 | // impl Construct for Scene { 110 | // fn construct(&mut self) { 111 | // let mut animations = Vec::new(); 112 | // let mut show = Vec::new(); 113 | // for _ in 0..200 { 114 | // let (x, y, w, _, _, color) = gen_random_values(); 115 | 116 | // let circle = self 117 | // .circle() 118 | // .with_position(x, y) 119 | // .with_color(color) 120 | // .with_radius(2.0 * w / 2.0) 121 | // .make(); 122 | 123 | // show.push(circle.show_creation()); 124 | 125 | // let (x, y, w, h, _, color) = gen_random_values(); 126 | 127 | // let rect = self 128 | // .rectangle() 129 | // .with_position(x, y) 130 | // .with_color(color) 131 | // .with_size(2.0 * w, 2.0 * h) 132 | // .make(); 133 | 134 | // show.push(rect.show_creation()); 135 | 136 | // // animations.push(circle.morph(rect)); 137 | // animations.push(rect.morph(circle)); 138 | 139 | // let (x, y, _, _, _, color) = gen_random_values(); 140 | // let line = self.line().from(0.0, 0.0).to(x, y).with_color(color).make(); 141 | // show.push(line.show_creation()); 142 | // } 143 | 144 | // self.wait(); 145 | // self.play(show).run_time(1.0).lag(0.001); 146 | 147 | // self.play(animations) 148 | // .run_time(10.0) 149 | // .lag(0.0001) 150 | // .rate_func(EaseType::Quint); 151 | // } 152 | // } 153 | 154 | // impl Construct for Scene { 155 | // fn construct(&mut self) { 156 | // let (x, y, _w, _h, _ang, color) = gen_random_values(); 157 | 158 | // let circle = self 159 | // .circle() 160 | // .with_position(x, y) 161 | // .with_color(color) 162 | // .with_radius(200.0 / 2.0) 163 | // .make(); 164 | 165 | // let (_x, _y, _w, _h, _ang, color) = gen_random_values(); 166 | 167 | // let text = self 168 | // .text() 169 | // // .with_text("Hello World!") 170 | // .with_text("oijaweijfowiefowijfejwofeji") 171 | // .with_font_size(50) 172 | // .with_color(color) 173 | // .with_position(-500.0, 100.0) 174 | // .make(); 175 | 176 | // let (x, y, _w, _h, _ang, color) = gen_random_values(); 177 | 178 | // let rect = self 179 | // .rectangle() 180 | // .with_position(x, y) 181 | // .with_color(color) 182 | // .with_size(150.0, 150.0) 183 | // .make(); 184 | 185 | // let line = self 186 | // .line() 187 | // .with_color(color) 188 | // .from(-600.0, -200.0) 189 | // .to(0.0, -200.0) 190 | // .make(); 191 | 192 | // self.wait(); 193 | 194 | // // self.play(vec![circle.move_to(400.0, 400.0), circle.fade_in()]); 195 | // // self.play(vec![line.show_creation(), text.show_creation()]); 196 | // self.play(line.show_creation()); 197 | // self.play(line.set_stroke_weight(10.0)); 198 | 199 | // // let (x, y, _w, _h, _ang, color) = gen_random_values(); 200 | // // let circle = self 201 | // // .circle() 202 | // // .with_position(0.0, 0.0) 203 | // // .with_color(color) 204 | // // .with_radius(200.0 / 2.0) 205 | // // .show(); 206 | 207 | // // self.wait(); 208 | // // let (x, y, _w, _h, _ang, color) = gen_random_values(); 209 | // // let rect = self 210 | // // .rectangle() 211 | // // .with_position(0.0, 0.0) 212 | // // .with_color(color) 213 | // // .with_size(150.0, 150.0) 214 | // // .show(); 215 | 216 | // // self.play(rect.show_creation()).run_time(3.0); 217 | 218 | // // self.play(line.morph(circle)).run_time(3.0); 219 | // // self.play(circle.morph(rect)).run_time(3.0); 220 | // // self.play(rect.morph(text)).run_time(10.0); 221 | // self.play(line.morph(text)).run_time(2.0); 222 | 223 | // // self.play(rect.morph(text)).run_time(5.0); 224 | // // self.play(rect.morph(text)).run_time(15.0); 225 | // // self.play(text.morph(circle)).run_time(15.0); 226 | 227 | // // self.play(vec![ 228 | // // circle.move_to_object(rect), 229 | // // circle.set_color_from(rect), 230 | // // ]) 231 | // // .rate_func(EaseType::Quint) 232 | // // .run_time(2.0); 233 | 234 | // // self.wait(); 235 | // // self.play(circle.move_to(400.0, 400.0)) 236 | // // .rate_func(EaseType::Elastic); 237 | // // self.play(circle.move_to(400.0, 400.0)) 238 | // // .run_time(1.0) 239 | // // .lag(0.0001) 240 | // // .rate_func(EaseType::Quad); 241 | // } 242 | // } 243 | 244 | // fn main() { 245 | // app::run(); 246 | // } 247 | -------------------------------------------------------------------------------- /noon/src/object/arrow.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongkyuns/noon/3da2af643516fcc03ca01d11875830c1da0c52bc/noon/src/object/arrow.rs -------------------------------------------------------------------------------- /noon/src/object/circle.rs: -------------------------------------------------------------------------------- 1 | use super::common::*; 2 | use crate::Angle; 3 | use core::f32::consts::TAU; 4 | use nannou::lyon::math::Vector; 5 | 6 | /// Component indicating a circle. Other [Component]s belonging to a circle 7 | /// is implemented in [CircleBuilder]. 8 | #[derive(Component)] 9 | pub struct Circle; 10 | 11 | impl Circle { 12 | /// Returns path for a circle. 13 | fn path(size: &Size) -> Path { 14 | let radius = size.width / 2.0; 15 | let mut builder = Path::svg_builder(); 16 | let sweep_angle = nannou::lyon::math::Angle::radians(TAU); 17 | let x_rotation = nannou::lyon::math::Angle::radians(0.0); 18 | let center = point(0.0, 0.0); 19 | let start = point(radius, 0.0); 20 | let radii = Vector::new(radius, radius); 21 | 22 | builder.move_to(start); 23 | builder.arc(center, radii, sweep_angle, x_rotation); 24 | builder.close(); 25 | 26 | Path::new(builder.build(), true) 27 | } 28 | } 29 | 30 | pub struct CircleBuilder<'a> { 31 | radius: f32, 32 | angle: Angle, 33 | stroke_weight: StrokeWeight, 34 | stroke_color: Color, 35 | fill_color: Color, 36 | position: Position, 37 | scene: &'a mut Scene, 38 | } 39 | 40 | impl<'a> CircleBuilder<'a> { 41 | fn new(scene: &'a mut Scene) -> Self { 42 | let fill_color = Color::random(); 43 | Self { 44 | radius: 0.5, 45 | stroke_weight: StrokeWeight::THICK, 46 | fill_color, 47 | stroke_color: fill_color.brighten(), 48 | position: Default::default(), 49 | angle: Default::default(), 50 | scene, 51 | } 52 | } 53 | pub fn with_radius(mut self, radius: f32) -> Self { 54 | self.radius = radius; 55 | self 56 | } 57 | pub fn with_color(mut self, color: Color) -> Self { 58 | self.fill_color = color; 59 | self.stroke_color = color.brighten(); 60 | self 61 | } 62 | } 63 | 64 | crate::angle_builder!(CircleBuilder); 65 | crate::stroke_builder!(CircleBuilder); 66 | crate::position_builder!(CircleBuilder); 67 | crate::fill_builder!(CircleBuilder); 68 | 69 | impl Create for CircleBuilder<'_> { 70 | fn scene_mut(&mut self) -> &mut Scene { 71 | &mut self.scene 72 | } 73 | fn make(&mut self) -> CircleId { 74 | let depth = self.scene.increment_counter(); 75 | let world = &mut self.scene.world; 76 | let position = self.position; 77 | let scale = Scale::ONE; 78 | let path = Circle::path(&Size::from_radius(self.radius)); 79 | let transform = Transform::identity() 80 | .scale(scale) 81 | .rotate(self.angle) 82 | .translate(position.into()); 83 | let screen_transform = self.scene.transform; 84 | 85 | let pixel_path = PixelPath( 86 | path.clone() 87 | .transform(&transform.transform(screen_transform)), 88 | ); 89 | 90 | let id = world 91 | .spawn() 92 | .insert(Circle) 93 | .insert(Size::from_radius(self.radius)) 94 | .insert(scale) 95 | .insert(self.position) 96 | .insert(self.angle) 97 | .insert(self.stroke_weight) 98 | .insert(StrokeColor(self.stroke_color)) 99 | .insert(FillColor(self.fill_color)) 100 | .insert(Opacity(0.0)) 101 | .insert(depth) 102 | .insert(PathCompletion(0.0)) 103 | .insert(path) 104 | .insert(pixel_path) 105 | .insert(transform) 106 | .insert(HasFill(true)) 107 | .id(); 108 | 109 | id.into() 110 | } 111 | } 112 | 113 | pub fn circle(scene: &mut Scene) -> CircleBuilder { 114 | CircleBuilder::new(scene) 115 | } 116 | 117 | #[derive(Debug, Copy, Clone)] 118 | pub struct CircleId(pub(crate) Entity); 119 | crate::into_entity!(CircleId); 120 | 121 | impl WithStroke for CircleId {} 122 | impl WithSize for CircleId {} 123 | impl WithFill for CircleId {} 124 | impl WithColor for CircleId {} 125 | impl WithPath for CircleId {} 126 | impl WithPosition for CircleId {} 127 | impl WithStrokeWeight for CircleId {} 128 | 129 | impl CircleId { 130 | pub fn set_radius(&self, radius: f32) -> EntityAnimations { 131 | EntityAnimations { 132 | entity: self.0, 133 | animations: Animation::to(Size::from_radius(radius)).into(), 134 | } 135 | } 136 | pub fn set_radius_from(&self, entity: impl Into) -> EntityAnimations { 137 | EntityAnimations { 138 | entity: self.0, 139 | animations: Animation::::to_target(entity.into()).into(), 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /noon/src/object/dot.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongkyuns/noon/3da2af643516fcc03ca01d11875830c1da0c52bc/noon/src/object/dot.rs -------------------------------------------------------------------------------- /noon/src/object/empty.rs: -------------------------------------------------------------------------------- 1 | use crate::component::Children; 2 | 3 | use super::common::*; 4 | 5 | #[derive(Component)] 6 | pub struct Empty; 7 | 8 | pub struct EmptyBuilder<'a> { 9 | size: Size, 10 | position: Position, 11 | angle: Angle, 12 | children: Children, 13 | scene: &'a mut Scene, 14 | } 15 | 16 | impl<'a> EmptyBuilder<'a> { 17 | fn new(scene: &'a mut Scene) -> Self { 18 | Self { 19 | size: Size { 20 | width: 1.0, 21 | height: 1.0, 22 | }, 23 | position: Default::default(), 24 | angle: Default::default(), 25 | children: Default::default(), 26 | scene, 27 | } 28 | } 29 | 30 | pub fn add(mut self, entity: impl Into) -> Self { 31 | self.children.add(entity); 32 | self 33 | } 34 | } 35 | 36 | crate::angle_builder!(EmptyBuilder); 37 | crate::position_builder!(EmptyBuilder); 38 | crate::size_builder!(EmptyBuilder); 39 | 40 | impl Create for EmptyBuilder<'_> { 41 | fn scene_mut(&mut self) -> &mut Scene { 42 | &mut self.scene 43 | } 44 | fn make(&mut self) -> EmptyId { 45 | // let depth = self.scene.increment_counter(); 46 | let world = &mut self.scene.world; 47 | let id = world 48 | .spawn() 49 | .insert(Empty) 50 | .insert(self.children.clone()) 51 | .insert(self.size) 52 | .insert(BoundingSize(self.size)) 53 | .insert(Previous(self.size)) 54 | .insert(self.position) 55 | .insert(self.angle) 56 | .id(); 57 | 58 | id.into() 59 | } 60 | } 61 | 62 | pub fn empty(scene: &mut Scene) -> EmptyBuilder { 63 | EmptyBuilder::new(scene) 64 | } 65 | 66 | #[derive(Debug, Copy, Clone)] 67 | pub struct EmptyId(pub(crate) Entity); 68 | 69 | impl WithPosition for EmptyId {} 70 | impl WithAngle for EmptyId {} 71 | impl WithSize for EmptyId {} 72 | 73 | impl WithId for EmptyId { 74 | fn id(&self) -> Entity { 75 | self.0 76 | } 77 | } 78 | 79 | impl From for Entity { 80 | fn from(id: EmptyId) -> Self { 81 | id.0 82 | } 83 | } 84 | 85 | impl From for EmptyId { 86 | fn from(id: Entity) -> Self { 87 | EmptyId(id) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /noon/src/object/line.rs: -------------------------------------------------------------------------------- 1 | use super::common::*; 2 | use nannou::lyon::path::traits::PathBuilder; 3 | 4 | #[derive(Component)] 5 | pub struct Line; 6 | 7 | impl Line { 8 | fn path(points: &[Point]) -> (Path, Position) { 9 | let centroid = Position::from_points(&points); 10 | 11 | let mut builder = Path::builder(); 12 | builder.begin( 13 | *points 14 | .get(0) 15 | .expect("Attempted to create a line with 0 points"), 16 | ); 17 | for &p in points.iter().skip(1) { 18 | builder.line_to(p); 19 | } 20 | builder.end(false); 21 | 22 | // Translate the points so that they are centered around point (0,0) 23 | let path = builder 24 | .build() 25 | .transformed(&nannou::lyon::geom::Translation::new( 26 | -centroid.x, 27 | -centroid.y, 28 | )); 29 | 30 | ( 31 | Path { 32 | raw: path, 33 | closed: false, 34 | }, 35 | centroid, 36 | ) 37 | } 38 | } 39 | 40 | pub struct LineBuilder<'a> { 41 | points: Vec, 42 | stroke_color: Color, 43 | stroke_weight: StrokeWeight, 44 | angle: Angle, 45 | scene: &'a mut Scene, 46 | } 47 | 48 | impl<'a> LineBuilder<'a> { 49 | fn new(scene: &'a mut Scene) -> Self { 50 | Self { 51 | points: Vec::new(), 52 | stroke_weight: StrokeWeight::THICK, 53 | stroke_color: Color::random(), 54 | angle: Default::default(), 55 | scene, 56 | } 57 | } 58 | pub fn from(mut self, x: f32, y: f32) -> Self { 59 | self.points = vec![point(x, y)]; 60 | self 61 | } 62 | pub fn to(mut self, x: f32, y: f32) -> Self { 63 | if !self.points.is_empty() { 64 | self.points.push(point(x, y)); 65 | } 66 | self 67 | } 68 | pub fn add(mut self, point: Point) -> Self { 69 | self.points.push(point); 70 | self 71 | } 72 | pub fn with_color(mut self, color: Color) -> Self { 73 | self.stroke_color = color; 74 | self 75 | } 76 | } 77 | 78 | crate::stroke_builder!(LineBuilder); 79 | 80 | impl Create for LineBuilder<'_> { 81 | fn scene_mut(&mut self) -> &mut Scene { 82 | &mut self.scene 83 | } 84 | fn make(&mut self) -> LineId { 85 | let depth = self.scene.increment_counter(); 86 | let world = &mut self.scene.world; 87 | 88 | let scale = Scale::ONE; 89 | let (path, position) = Line::path(&self.points); 90 | let transform = Transform::identity() 91 | .scale(scale) 92 | .rotate(self.angle) 93 | .translate(position.into()); 94 | let screen_transform = self.scene.transform; 95 | let size = Size::from_points(&self.points); 96 | 97 | let global_path = PixelPath( 98 | path.clone() 99 | .transform(&transform.transform(screen_transform)), 100 | ); 101 | 102 | let id = world 103 | .spawn() 104 | .insert(Line) 105 | .insert(size) 106 | .insert(scale) 107 | .insert(position) 108 | .insert(self.angle) 109 | .insert(self.stroke_weight) 110 | .insert(StrokeColor(self.stroke_color)) 111 | .insert(Opacity(0.0)) 112 | .insert(depth) 113 | .insert(PathCompletion(0.0)) 114 | .insert(path) 115 | .insert(global_path) 116 | .insert(transform) 117 | .insert(FillColor(Color::BLACK)) 118 | .insert(HasFill(false)) 119 | .id(); 120 | 121 | id.into() 122 | } 123 | } 124 | 125 | pub fn line(scene: &mut Scene) -> LineBuilder { 126 | LineBuilder::new(scene) 127 | } 128 | 129 | #[derive(Debug, Copy, Clone)] 130 | pub struct LineId(pub(crate) Entity); 131 | crate::into_entity!(LineId); 132 | 133 | impl WithColor for LineId {} 134 | impl WithPath for LineId {} 135 | impl WithPosition for LineId {} 136 | impl WithAngle for LineId {} 137 | impl WithStrokeWeight for LineId {} 138 | -------------------------------------------------------------------------------- /noon/src/object/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | use bevy_ecs::prelude::*; 4 | 5 | pub mod arrow; 6 | pub mod circle; 7 | pub mod dot; 8 | pub mod empty; 9 | pub mod line; 10 | pub mod rectangle; 11 | pub mod text; 12 | pub mod triangle; 13 | 14 | pub use circle::*; 15 | pub use empty::*; 16 | pub use line::*; 17 | pub use rectangle::*; 18 | pub use text::*; 19 | 20 | use crate::{Animation, Color, EntityAnimations, FillColor, Opacity, Position, Size, StrokeColor}; 21 | 22 | mod common { 23 | pub use crate::path::GetPartial; 24 | pub use crate::PixelFrame; 25 | pub use crate::{ 26 | Angle, AnimBuilder, Animation, BoundingSize, Color, ColorExtension, Create, Depth, 27 | EaseType, EntityAnimations, FillColor, FontSize, HasFill, Opacity, Path, PathCompletion, 28 | PathComponent, PixelPath, Point, Position, Previous, Scale, Scene, Size, StrokeColor, 29 | StrokeWeight, Transform, Value, WithAngle, WithColor, WithFill, WithFontSize, WithId, 30 | WithPath, WithPosition, WithSize, WithStroke, WithStrokeWeight, EPS, TO_PXL, 31 | }; 32 | pub use bevy_ecs::prelude::*; 33 | pub use nannou::color::Rgba; 34 | pub use nannou::lyon::math::point; 35 | } 36 | 37 | #[derive(Component)] 38 | pub struct Triangle; 39 | 40 | #[derive(Component)] 41 | pub struct Arrow; 42 | 43 | #[derive(Component)] 44 | pub struct Dot; 45 | 46 | #[macro_export] 47 | macro_rules! stroke_builder { 48 | ($name:ident) => { 49 | impl<'a> $name<'a> { 50 | pub fn with_stroke_weight(mut self, weight: f32) -> Self { 51 | self.stroke_weight = StrokeWeight(weight); 52 | self 53 | } 54 | pub fn with_thin_stroke(mut self) -> Self { 55 | self.stroke_weight = StrokeWeight::THIN; 56 | self 57 | } 58 | pub fn with_thick_stroke(mut self) -> Self { 59 | self.stroke_weight = StrokeWeight::THICK; 60 | self 61 | } 62 | pub fn with_stroke_color(mut self, color: Color) -> Self { 63 | self.stroke_color = color; 64 | self 65 | } 66 | } 67 | }; 68 | } 69 | 70 | #[macro_export] 71 | macro_rules! position_builder { 72 | ($name:ident) => { 73 | impl<'a> $name<'a> { 74 | pub fn with_position(mut self, x: f32, y: f32) -> Self { 75 | self.position = Position { x, y }; 76 | self 77 | } 78 | } 79 | }; 80 | } 81 | 82 | #[macro_export] 83 | macro_rules! size_builder { 84 | ($name:ident) => { 85 | impl<'a> $name<'a> { 86 | pub fn with_size(mut self, width: f32, height: f32) -> Self { 87 | self.size = Size::from(width, height); 88 | self 89 | } 90 | } 91 | }; 92 | } 93 | 94 | #[macro_export] 95 | macro_rules! angle_builder { 96 | ($name:ident) => { 97 | impl<'a> $name<'a> { 98 | pub fn with_angle(mut self, radians: f32) -> Self { 99 | self.angle = Angle(radians); 100 | self 101 | } 102 | } 103 | }; 104 | } 105 | 106 | #[macro_export] 107 | macro_rules! fill_builder { 108 | ($name:ident) => { 109 | impl<'a> $name<'a> { 110 | pub fn with_fill_color(mut self, color: Color) -> Self { 111 | self.fill_color = color; 112 | self 113 | } 114 | } 115 | }; 116 | } 117 | 118 | #[macro_export] 119 | macro_rules! into_entity { 120 | ($name:ident) => { 121 | impl WithId for $name { 122 | fn id(&self) -> Entity { 123 | self.0 124 | } 125 | } 126 | 127 | impl Into for $name { 128 | fn into(self) -> Entity { 129 | self.0 130 | } 131 | } 132 | 133 | impl From for $name { 134 | fn from(id: Entity) -> Self { 135 | $name(id) 136 | } 137 | } 138 | }; 139 | } 140 | -------------------------------------------------------------------------------- /noon/src/object/rectangle.rs: -------------------------------------------------------------------------------- 1 | use super::common::*; 2 | 3 | #[derive(Component)] 4 | pub struct Rectangle; 5 | 6 | impl Rectangle { 7 | fn path(size: &Size) -> Path { 8 | let mut builder = Path::svg_builder(); 9 | let start = point(size.width / 2.0, 0.0); 10 | builder.move_to(start); 11 | builder.line_to(point(start.x, start.y + size.height / 2.0)); 12 | builder.line_to(point(start.x - size.width, start.y + size.height / 2.0)); 13 | builder.line_to(point(start.x - size.width, start.y - size.height / 2.0)); 14 | builder.line_to(point(start.x, start.y - size.height / 2.0)); 15 | builder.line_to(point(start.x, 0.0)); 16 | builder.close(); 17 | 18 | Path::new(builder.build(), true) 19 | } 20 | } 21 | 22 | pub struct RectangleBuilder<'a> { 23 | size: Size, 24 | stroke_weight: StrokeWeight, 25 | stroke_color: Color, 26 | fill_color: Color, 27 | position: Position, 28 | angle: Angle, 29 | scene: &'a mut Scene, 30 | } 31 | 32 | impl<'a> RectangleBuilder<'a> { 33 | fn new(scene: &'a mut Scene) -> Self { 34 | let fill_color = Color::random(); 35 | Self { 36 | size: Size::UNIT, 37 | stroke_weight: StrokeWeight::THICK, 38 | fill_color, 39 | stroke_color: fill_color.brighten(), 40 | position: Default::default(), 41 | angle: Default::default(), 42 | scene, 43 | } 44 | } 45 | pub fn with_color(mut self, color: Color) -> Self { 46 | self.fill_color = color; 47 | self.stroke_color = color.brighten(); 48 | self 49 | } 50 | } 51 | 52 | crate::angle_builder!(RectangleBuilder); 53 | crate::stroke_builder!(RectangleBuilder); 54 | crate::position_builder!(RectangleBuilder); 55 | crate::fill_builder!(RectangleBuilder); 56 | crate::size_builder!(RectangleBuilder); 57 | 58 | impl Create for RectangleBuilder<'_> { 59 | fn scene_mut(&mut self) -> &mut Scene { 60 | &mut self.scene 61 | } 62 | fn make(&mut self) -> RectangleId { 63 | let depth = self.scene.increment_counter(); 64 | let world = &mut self.scene.world; 65 | let scale = Scale::ONE; 66 | let path = Rectangle::path(&self.size); 67 | let transform = Transform::identity() 68 | .scale(scale) 69 | .rotate(self.angle) 70 | .translate(self.position.into()); 71 | let screen_transform = self.scene.transform; 72 | 73 | let global_path = PixelPath( 74 | path.clone() 75 | .transform(&transform.transform(screen_transform)), 76 | ); 77 | 78 | let id = world 79 | .spawn() 80 | .insert(Rectangle) 81 | .insert(self.size) 82 | .insert(scale) 83 | .insert(self.position) 84 | .insert(self.angle) 85 | .insert(self.stroke_weight) 86 | .insert(StrokeColor(self.stroke_color)) 87 | .insert(FillColor(self.fill_color)) 88 | .insert(Opacity(0.0)) 89 | .insert(depth) 90 | .insert(PathCompletion(0.0)) 91 | .insert(path) 92 | .insert(global_path) 93 | .insert(transform) 94 | .insert(HasFill(true)) 95 | .id(); 96 | 97 | id.into() 98 | } 99 | } 100 | 101 | pub fn rectangle(scene: &mut Scene) -> RectangleBuilder { 102 | RectangleBuilder::new(scene) 103 | } 104 | 105 | #[derive(Debug, Copy, Clone)] 106 | pub struct RectangleId(pub(crate) Entity); 107 | crate::into_entity!(RectangleId); 108 | 109 | impl WithStroke for RectangleId {} 110 | impl WithFill for RectangleId {} 111 | impl WithColor for RectangleId {} 112 | impl WithPath for RectangleId {} 113 | impl WithPosition for RectangleId {} 114 | impl WithAngle for RectangleId {} 115 | impl WithSize for RectangleId {} 116 | impl WithStrokeWeight for RectangleId {} 117 | -------------------------------------------------------------------------------- /noon/src/object/text.rs: -------------------------------------------------------------------------------- 1 | use super::common::*; 2 | 3 | #[derive(Component)] 4 | pub struct Text; 5 | 6 | impl Text { 7 | fn path(text: &str, font_size: u32) -> (Path, Size) { 8 | use nannou::text; 9 | use nannou::{geom::Rect, lyon::geom}; 10 | 11 | let mut builder = Path::builder(); 12 | 13 | let rect = nannou::geom::Rect::from_w_h(1.0, 1.0); 14 | let text = text::text(text) 15 | .font_size(font_size) 16 | .no_line_wrap() 17 | .left_justify() 18 | .build(rect); 19 | 20 | for e in text.path_events() { 21 | builder.path_event(e); 22 | } 23 | 24 | let rect = text.bounding_rect(); 25 | let x = -rect.x(); 26 | let y = -rect.y(); 27 | // let scale = 1.0 / rect.w().max(rect.h()) * width; 28 | let scale = 0.01; 29 | 30 | ( 31 | Path::new( 32 | builder 33 | .build() 34 | .transformed(&geom::Transform::translation(x, y).then_scale(scale, scale)), 35 | true, 36 | ), 37 | Size::from(rect.w(), rect.h()), 38 | ) 39 | } 40 | } 41 | 42 | pub struct TextBuilder<'a> { 43 | text: String, 44 | font_size: u32, 45 | stroke_weight: StrokeWeight, 46 | stroke_color: Color, 47 | fill_color: Color, 48 | position: Position, 49 | angle: Angle, 50 | scene: &'a mut Scene, 51 | } 52 | 53 | impl<'a> TextBuilder<'a> { 54 | fn new(scene: &'a mut Scene) -> Self { 55 | let fill_color = Color::random(); 56 | Self { 57 | text: String::new(), 58 | font_size: 30, 59 | stroke_weight: StrokeWeight::THIN, 60 | fill_color, 61 | stroke_color: fill_color.brighten(), 62 | position: Default::default(), 63 | angle: Default::default(), 64 | scene, 65 | } 66 | } 67 | pub fn with_text(mut self, text: &str) -> Self { 68 | self.text = text.to_owned(); 69 | self 70 | } 71 | pub fn with_color(mut self, color: Color) -> Self { 72 | self.fill_color = color; 73 | self.stroke_color = color.brighten(); 74 | self 75 | } 76 | pub fn with_font_size(mut self, size: u32) -> Self { 77 | self.font_size = size; 78 | self 79 | } 80 | } 81 | 82 | crate::stroke_builder!(TextBuilder); 83 | crate::position_builder!(TextBuilder); 84 | crate::fill_builder!(TextBuilder); 85 | 86 | impl Create for TextBuilder<'_> { 87 | fn scene_mut(&mut self) -> &mut Scene { 88 | &mut self.scene 89 | } 90 | fn make(&mut self) -> TextId { 91 | let depth = self.scene.increment_counter(); 92 | let world = &mut self.scene.world; 93 | let position = self.position; 94 | let scale = Scale::ONE; 95 | let (path, size) = Text::path(&self.text, self.font_size); 96 | let transform = Transform::identity() 97 | .scale(scale) 98 | .rotate(self.angle) 99 | .translate(position.into()); 100 | let screen_transform = self.scene.transform; 101 | 102 | let global_path = PixelPath( 103 | path.clone() 104 | .transform(&transform.transform(screen_transform)), 105 | ); 106 | let id = world 107 | .spawn() 108 | .insert(Text) 109 | .insert(FontSize(self.font_size)) 110 | .insert(size) 111 | .insert(scale) 112 | .insert(self.position) 113 | .insert(self.angle) 114 | .insert(self.stroke_weight) 115 | .insert(StrokeColor(self.stroke_color)) 116 | .insert(FillColor(self.fill_color)) 117 | .insert(Opacity(0.0)) 118 | .insert(depth) 119 | .insert(PathCompletion(0.0)) 120 | .insert(path) 121 | .insert(global_path) 122 | .insert(transform) 123 | .insert(HasFill(true)) 124 | .id(); 125 | 126 | id.into() 127 | } 128 | } 129 | 130 | pub fn text(scene: &mut Scene) -> TextBuilder { 131 | TextBuilder::new(scene) 132 | } 133 | 134 | #[derive(Debug, Copy, Clone)] 135 | pub struct TextId(pub(crate) Entity); 136 | crate::into_entity!(TextId); 137 | 138 | impl WithFontSize for TextId {} 139 | impl WithStroke for TextId {} 140 | impl WithFill for TextId {} 141 | impl WithColor for TextId {} 142 | impl WithPath for TextId {} 143 | impl WithPosition for TextId {} 144 | impl WithAngle for TextId {} 145 | impl WithSize for TextId {} 146 | -------------------------------------------------------------------------------- /noon/src/object/triangle.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yongkyuns/noon/3da2af643516fcc03ca01d11875830c1da0c52bc/noon/src/object/triangle.rs -------------------------------------------------------------------------------- /noon/src/path/mod.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::prelude::*; 2 | use lyon::{builder::WithSvg, iterator::PathIterator, PathEvent}; 3 | use nannou::lyon::path as lyon; 4 | use nannou::lyon::{ 5 | algorithms::length::approximate_length, 6 | lyon_algorithms::walk::walk_along_path, 7 | lyon_algorithms::walk::RepeatedPattern, 8 | path::{iterator::Flattened, path::Iter}, 9 | }; 10 | 11 | // use nannou::lyon::math::Transform; 12 | use crate::Transform; 13 | 14 | use crate::{point, Interpolate, Point, Size, EPS_LOW}; 15 | 16 | /// Global path component that can be rendered to screen without any transformation 17 | #[derive(Debug, Clone, Component)] 18 | pub struct PixelPath(pub(crate) Path); 19 | 20 | /// Data type for representing a vectorized 2D path. 21 | /// 22 | /// [Path] is a thin wrapper around [lyon::Path] and mainly adds [Interpolate] 23 | /// and exposes a few convenience methods. By allowing any arbitrary path to 24 | /// be interpolated, [animate](crate::animate) can transform from one shape 25 | /// to another shape through [Animation](crate::Animation). 26 | #[derive(Debug, Clone, Component)] 27 | pub struct Path { 28 | /// Lyon path 29 | pub(crate) raw: lyon::Path, 30 | /// Boolean to indicate whether the path is closed shape or not. 31 | pub(crate) closed: bool, 32 | } 33 | 34 | impl Path { 35 | pub fn new(path: lyon::Path, closed: bool) -> Self { 36 | Self { raw: path, closed } 37 | } 38 | /// Expose `Lyon`'s [SVG path builder](WithSvg) 39 | pub fn svg_builder() -> WithSvg { 40 | lyon::Path::svg_builder() 41 | } 42 | /// Expose `Lyon`'s [path builder](lyon::path::Builder) 43 | pub fn builder() -> lyon::path::Builder { 44 | lyon::path::Builder::new() 45 | } 46 | /// Flatten the current path into series of linear line segments 47 | /// with given tolerance. 48 | /// 49 | /// Note that the returned path is an iterator. 50 | pub fn flattened(&self, tolerance: f32) -> Flattened { 51 | self.raw.iter().flattened(tolerance) 52 | } 53 | 54 | /// Provides rough size of the path 55 | pub fn size(&self) -> Size { 56 | let mut max = point(-1.0e5, -1.0e5); 57 | let mut min = point(1.0e5, 1.0e5); 58 | for e in self.flattened(EPS_LOW) { 59 | match e { 60 | PathEvent::Line { from, to } => { 61 | // Probably only need to compare with destination, i.e. `to` 62 | // Remove later 63 | min = min.min(from); 64 | max = max.max(from); 65 | min = min.min(to); 66 | max = max.max(to); 67 | } 68 | _ => (), 69 | } 70 | } 71 | Size::from_points(&vec![min, max]) 72 | } 73 | 74 | /// Apply given scale to the path. 75 | pub fn scale(&self, x: f32, y: f32) -> Self { 76 | Self::new( 77 | self.raw 78 | .clone() 79 | .transformed(&nannou::lyon::math::Transform::scale(x, y)), 80 | self.closed, 81 | ) 82 | } 83 | 84 | pub fn transform(&self, transform: &Transform) -> Self { 85 | Self::new(self.raw.clone().transformed(&transform.0), self.closed) 86 | } 87 | } 88 | 89 | impl Interpolate for Path { 90 | fn interp(&self, other: &Self, progress: f32) -> Self { 91 | let tol = EPS_LOW; 92 | // let progress = progress.min(1.0).max(0.0); 93 | 94 | if progress <= 0.00001 { 95 | self.clone() 96 | } else if progress >= 0.99999 { 97 | other.clone() 98 | } else { 99 | // 1. Calculate the length of initial and final paths (1 and 2) 100 | // 2. Iterate through each path and construct normalized distance array 101 | // 3. Combine normalized distances from both paths into a single array 102 | // 4. Walk through each path and fill-in missing points to make sizes equal 103 | // 5. Interpolate each point between initial and final path 104 | // 6. Construct Path with above points as line segments 105 | let segments_src = get_segments(&self); 106 | let segments_dst = get_segments(&other); 107 | 108 | let mut interpolated = Vec::new(); 109 | for (src, dst) in segments_src.iter().zip(segments_dst.iter()) { 110 | interpolated.push(interp_segment(src, dst, progress, tol, self.closed)); 111 | } 112 | // Path::new(interpolated.get(0).unwrap().raw.clone(), self.closed) 113 | if segments_src.len() > segments_dst.len() { 114 | for src in segments_src.iter().skip(segments_dst.len()) { 115 | interpolated.push(interp_segment( 116 | src, 117 | segments_dst.last().unwrap(), 118 | progress, 119 | tol, 120 | self.closed, 121 | )); 122 | } 123 | } else if segments_src.len() < segments_dst.len() { 124 | for dst in segments_dst.iter().skip(segments_src.len()) { 125 | interpolated.push(interp_segment( 126 | segments_src.last().unwrap(), 127 | dst, 128 | progress, 129 | tol, 130 | self.closed, 131 | )); 132 | } 133 | } 134 | 135 | merge_segments(&interpolated) 136 | } 137 | } 138 | } 139 | 140 | fn interp_segment( 141 | source: &Path, 142 | destination: &Path, 143 | progress: f32, 144 | tolerance: f32, 145 | closed: bool, 146 | ) -> Path { 147 | let src_len = get_line_lengths(source.flattened(tolerance)); 148 | let dst_len = get_line_lengths(destination.flattened(tolerance)); 149 | 150 | let mut builder = Path::svg_builder(); 151 | if src_len.len() > 1 && dst_len.len() > 1 { 152 | let normalized = normalized_distances(&src_len, &dst_len); 153 | 154 | let src_max_len = *src_len.last().unwrap(); 155 | let dst_max_len = *dst_len.last().unwrap(); 156 | 157 | let p1 = points_from_path(source.flattened(tolerance), &normalized, src_max_len); 158 | let p2 = points_from_path(destination.flattened(tolerance), &normalized, dst_max_len); 159 | 160 | p1.iter().zip(p2.iter()).for_each(|(&p1, p2)| { 161 | builder.line_to(p1.interp(p2, progress)); 162 | }); 163 | 164 | if closed { 165 | builder.close(); 166 | } 167 | } 168 | Path::new(builder.build(), closed) 169 | } 170 | 171 | fn merge_segments(paths: &[Path]) -> Path { 172 | let mut builder = Path::builder(); 173 | let mut closed = true; 174 | for path in paths { 175 | closed = path.closed; 176 | builder.concatenate(&vec![path.raw.as_slice()]); 177 | } 178 | Path::new(builder.build(), closed) 179 | } 180 | 181 | fn get_segments(path: &Path) -> Vec { 182 | let mut segments = Vec::new(); 183 | let mut path_iter = path.raw.iter(); 184 | while let Some(segment) = get_segment(&mut path_iter) { 185 | segments.push(segment); 186 | } 187 | segments 188 | } 189 | 190 | fn get_segment(path_iter: &mut Iter) -> Option { 191 | let mut builder = Path::builder(); 192 | let mut count = 0; 193 | while let Some(event) = path_iter.next() { 194 | builder.path_event(event); 195 | count += 1; 196 | if let PathEvent::End { .. } = event { 197 | break; 198 | } 199 | } 200 | if count > 0 { 201 | Some(Path::new(builder.build(), true)) 202 | } else { 203 | None 204 | } 205 | } 206 | 207 | fn points_from_path( 208 | path: Flattened, 209 | normalized_len: &[f32], 210 | total_length: f32, 211 | ) -> Vec { 212 | // Compute the delta distance between each point 213 | let lengths: Vec = normalized_len 214 | .iter() 215 | .zip(normalized_len.iter().skip(1)) 216 | .map(|(a, b)| b - a) 217 | .map(|val| val * total_length) 218 | .collect(); 219 | 220 | let mut points = Vec::new(); 221 | let mut pattern = RepeatedPattern { 222 | callback: &mut |position, _t, _d| { 223 | points.push(position); 224 | true 225 | }, 226 | intervals: &lengths, 227 | index: 0, 228 | }; 229 | 230 | walk_along_path(path.into_iter(), 0.0, &mut pattern); 231 | points 232 | } 233 | 234 | fn get_line_lengths(flattened: Flattened) -> Vec { 235 | let mut p = flattened 236 | .filter(|e| matches!(e, PathEvent::Line { .. })) 237 | .scan(0.0, |d, event| { 238 | match event { 239 | PathEvent::Line { from, to } => { 240 | *d += (to - from).length(); 241 | } 242 | _ => (), 243 | }; 244 | Some(*d) 245 | }) 246 | .collect::>(); 247 | p.insert(0, 0.0); // This is needed to correct the initial point being shifted by 1 index 248 | p 249 | } 250 | 251 | // Combine two vectors which are both monotonically increasing by normalized ordering 252 | fn normalized_distances(v1: &[f32], v2: &[f32]) -> Vec { 253 | let mut combined = Vec::new(); 254 | 255 | let s1 = *v1.last().unwrap(); 256 | let s2 = *v2.last().unwrap(); 257 | 258 | let mut v2_iter = v2.iter().peekable(); 259 | for val1 in v1.into_iter() { 260 | while let Some(val2) = v2_iter.peek() { 261 | if **val2 / s2 < val1 / s1 { 262 | combined.push(**val2 / s2); 263 | v2_iter.next(); 264 | } else { 265 | break; 266 | } 267 | } 268 | combined.push(val1 / s1); 269 | } 270 | 271 | combined 272 | } 273 | 274 | pub trait PathComponent { 275 | fn path(size: &Size) -> Path; 276 | } 277 | 278 | pub trait MeasureLength { 279 | fn approximate_length(&self, tolerance: f32) -> f32; 280 | } 281 | 282 | impl MeasureLength for Path { 283 | fn approximate_length(&self, tolerance: f32) -> f32 { 284 | approximate_length(self.raw.iter(), tolerance) 285 | } 286 | } 287 | 288 | pub trait GetPartial: MeasureLength { 289 | fn upto(&self, ratio: f32, tolerance: f32) -> Path; 290 | } 291 | 292 | impl GetPartial for Path { 293 | fn upto(&self, ratio: f32, tolerance: f32) -> Path { 294 | if ratio >= 1.0 { 295 | self.clone() 296 | } else { 297 | let ratio = ratio.max(0.0); 298 | let full_length = self.approximate_length(tolerance); 299 | let stop_at = ratio * full_length; 300 | 301 | let mut builder = Path::svg_builder(); 302 | let mut length = 0.0; 303 | 304 | for e in self.raw.iter().flattened(tolerance) { 305 | if length > stop_at { 306 | break; 307 | } 308 | match e { 309 | PathEvent::Begin { at } => { 310 | builder.move_to(at); 311 | } 312 | PathEvent::Line { from, to } => { 313 | let seg_length = (to - from).length(); 314 | let new_length = length + seg_length; 315 | if new_length > stop_at { 316 | let seg_ratio = 1.0 - (new_length - stop_at) / seg_length; 317 | builder.line_to(from.lerp(to, seg_ratio)); 318 | break; 319 | } else { 320 | length = new_length; 321 | builder.line_to(to); 322 | } 323 | } 324 | PathEvent::End { .. } => { 325 | builder.close(); 326 | } 327 | _ => (), 328 | } 329 | } 330 | Self::new(builder.build(), self.closed) 331 | } 332 | } 333 | } 334 | 335 | #[cfg(test)] 336 | mod tests { 337 | use super::*; 338 | // use nannou::geom::rect::Rect; 339 | // use nannou::lyon::path::builder::PathBuilder; 340 | use nannou::lyon::math::{point, Point}; 341 | use nannou::prelude::*; 342 | 343 | #[test] 344 | fn partial_path() { 345 | let win_rect = Rect::from_w_h(640.0, 480.0); 346 | let text = text("Hello").font_size(128).left_justify().build(win_rect); 347 | let mut builder = Path::builder(); 348 | for e in text.path_events() { 349 | builder.path_event(e); 350 | } 351 | builder.close(); 352 | let path = Path::new(builder.build(), true); 353 | let partial_path = path.upto(0.5, 0.01); 354 | 355 | println!("length = {}", partial_path.approximate_length(0.01)); 356 | } 357 | 358 | #[test] 359 | fn flatten() { 360 | let mut builder = Path::svg_builder(); 361 | builder.move_to(point(0.0, 0.0)); 362 | builder.line_to(point(10.0, 0.0)); 363 | builder.close(); 364 | let path = Path::new(builder.build(), true).upto(0.5, 0.01); 365 | for e in path.raw.iter().flattened(0.01) { 366 | match e { 367 | PathEvent::Begin { .. } => {} 368 | PathEvent::Line { from, to } => { 369 | println!("from:({},{}), to:({},{})", from.x, from.y, to.x, to.y); 370 | } 371 | PathEvent::End { .. } => {} 372 | _ => (), 373 | } 374 | } 375 | } 376 | 377 | #[test] 378 | fn iter_check() { 379 | let arr = [1, 2, 3, 4, 5]; 380 | let out = arr 381 | .iter() 382 | .zip(arr.iter().skip(1)) 383 | .scan(0, |val, a| { 384 | *val += a.0 + a.1; 385 | Some(*val) 386 | }) 387 | .collect::>(); 388 | 389 | dbg!(out); 390 | } 391 | 392 | #[test] 393 | fn length() { 394 | use nannou::lyon::algorithms::length::approximate_length; 395 | 396 | let mut builder = Path::svg_builder(); 397 | builder.move_to(point(0.0, 0.0)); 398 | builder.line_to(point(10.0, 0.0)); 399 | builder.quadratic_bezier_to(point(15.0, 5.0), point(20.0, 0.0)); 400 | builder.close(); 401 | 402 | let path = Path::new(builder.build(), true); 403 | let l = approximate_length(path.raw.iter(), 0.01); 404 | let l2 = path.approximate_length(0.01); 405 | 406 | println!("{}, {}", l, l2); 407 | } 408 | 409 | #[test] 410 | fn check_vector_ordering() { 411 | let v1 = vec![0.0, 0.3, 0.6, 0.8, 1.0]; 412 | let v2 = vec![0.2, 0.5, 0.55, 0.8, 2.0]; 413 | 414 | let out = normalized_distances(&v1, &v2); 415 | assert_eq!(*out, vec![0.0, 0.1, 0.25, 0.275, 0.3, 0.4, 0.6, 0.8, 1.0]); 416 | } 417 | 418 | #[test] 419 | fn check_walk() { 420 | let mut builder = Path::builder(); 421 | builder.begin(point(5.0, 5.0)); 422 | builder.line_to(point(5.0, 10.0)); 423 | builder.line_to(point(10.0, 10.0)); 424 | builder.line_to(point(10.0, 5.0)); 425 | builder.end(true); 426 | let path = builder.build(); 427 | 428 | let pts = vec![0.0, 2.0, 2.5, 5.0, 10.0, 20.0]; 429 | let pts: Vec = pts 430 | .iter() 431 | .zip(pts.iter().skip(1)) 432 | .map(|(a, b)| b - a) 433 | .collect(); 434 | 435 | let mut pattern = RepeatedPattern { 436 | callback: &mut |position: Point, _t, d| { 437 | println!("d = {}, x = {}, y = {}", d, position.x, position.y); 438 | true 439 | }, 440 | intervals: &pts, 441 | index: 0, 442 | }; 443 | 444 | walk_along_path(path.iter(), 0.0, &mut pattern); 445 | } 446 | #[test] 447 | fn path_size() { 448 | let mut builder = Path::svg_builder(); 449 | builder.move_to(point(-100.0, 200.0)); 450 | builder.line_to(point(100.0, 300.0)); 451 | builder.line_to(point(-300.0, 300.0)); 452 | builder.close(); 453 | let size = Path::new(builder.build(), true).size(); 454 | assert_eq!(400.0, size.width); 455 | assert_eq!(100.0, size.height); 456 | } 457 | 458 | #[test] 459 | fn path_evaluation() { 460 | let mut builder = Path::svg_builder(); 461 | builder.move_to(point(-100.0, 0.0)); 462 | builder.line_to(point(-100.0, 100.0)); 463 | builder.line_to(point(0.0, 100.0)); 464 | builder.line_to(point(0.0, 0.0)); 465 | builder.close(); 466 | builder.move_to(point(100.0, 0.0)); 467 | builder.line_to(point(100.0, 100.0)); 468 | builder.line_to(point(200.0, 100.0)); 469 | builder.move_to(point(200.0, 0.0)); 470 | builder.line_to(point(200.0, 200.0)); 471 | builder.line_to(point(300.0, 200.0)); 472 | builder.close(); 473 | let p = builder.build(); 474 | 475 | // let mut builder = Path::builder(); 476 | // builder.begin(point(-100.0, 0.0)); 477 | // builder.line_to(point(-100.0, 100.0)); 478 | // builder.line_to(point(0.0, 100.0)); 479 | // builder.line_to(point(0.0, 0.0)); 480 | // builder.close(); 481 | // builder.begin(point(100.0, 0.0)); 482 | // builder.line_to(point(100.0, 100.0)); 483 | // builder.line_to(point(200.0, 100.0)); 484 | // builder.close(); 485 | // builder.begin(point(200.0, 0.0)); 486 | // builder.line_to(point(200.0, 200.0)); 487 | // builder.line_to(point(300.0, 200.0)); 488 | // builder.close(); 489 | // let p = builder.build(); 490 | 491 | // dbg!(&p); 492 | for e in p.iter() { 493 | dbg!(&e); 494 | } 495 | } 496 | 497 | #[test] 498 | fn circle() { 499 | use nannou::lyon::math::{Angle, Vector}; 500 | let mut builder = Path::svg_builder(); 501 | 502 | let radius = 3.0; 503 | let sweep_angle = Angle::radians(-TAU); 504 | let x_rotation = Angle::radians(0.0); 505 | let center = point(0.0, 0.0); 506 | let start = point(radius, 0.0); 507 | let radii = Vector::new(radius, radius); 508 | 509 | builder.move_to(start); 510 | builder.arc(center, radii, sweep_angle, x_rotation); 511 | builder.close(); 512 | 513 | // let mut path = Path::new(builder.build()).upto(0.5, 0.01); 514 | for e in builder.build().iter() { 515 | match e { 516 | PathEvent::Begin { at } => { 517 | println!("Begin -> at:({},{})", at.x, at.y); 518 | } 519 | PathEvent::Line { from, to } => { 520 | println!( 521 | "Line -> from:({},{}), to:({},{})", 522 | from.x, from.y, to.x, to.y 523 | ); 524 | } 525 | PathEvent::Quadratic { from, to, .. } => { 526 | println!( 527 | "Quadratic -> from:({},{}), to:({},{})", 528 | from.x, from.y, to.x, to.y 529 | ); 530 | } 531 | PathEvent::Cubic { from, to, .. } => { 532 | println!( 533 | "Cubic -> from:({},{}), to:({},{})", 534 | from.x, from.y, to.x, to.y 535 | ); 536 | } 537 | PathEvent::End { .. } => { 538 | println!("End"); 539 | } 540 | } 541 | } 542 | } 543 | } 544 | -------------------------------------------------------------------------------- /noon/src/scene.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::prelude::*; 2 | use nannou::geom::Rect; 3 | 4 | use crate::component::FillColor; 5 | use crate::prelude::*; 6 | use crate::system::*; 7 | use crate::Depth; 8 | use crate::Scale; 9 | use crate::Transform; 10 | use crate::{ 11 | circle, empty, line, rectangle, text, Angle, EmptyBuilder, FontSize, LineBuilder, Opacity, 12 | Path, PathCompletion, Position, RectangleBuilder, Size, StrokeColor, 13 | }; 14 | 15 | #[derive(Debug)] 16 | pub struct Bounds(pub(crate) Rect); 17 | 18 | impl Bounds { 19 | pub fn new(rect: Rect) -> Self { 20 | let x = rect.x() / ZOOM; 21 | let y = rect.y() / ZOOM; 22 | let w = rect.w() / ZOOM; 23 | let h = rect.h() / ZOOM; 24 | Self(Rect::from_x_y_w_h(x, y, w, h)) 25 | } 26 | pub fn none() -> Self { 27 | Self(Rect::from_w_h(0.0, 0.0)) 28 | } 29 | pub fn edge_upper(&self) -> f32 { 30 | self.0.y.end 31 | } 32 | pub fn edge_lower(&self) -> f32 { 33 | self.0.y.start 34 | } 35 | pub fn edge_left(&self) -> f32 { 36 | self.0.x.start 37 | } 38 | pub fn edge_right(&self) -> f32 { 39 | self.0.x.end 40 | } 41 | pub fn get_edge(&self, now: Position, direction: Direction) -> Position { 42 | let x = now.x.min(self.edge_right()).max(self.edge_left()); 43 | let y = now.y.min(self.edge_upper()).max(self.edge_lower()); 44 | 45 | match direction { 46 | Direction::Up => Position { 47 | x, 48 | y: self.edge_upper(), 49 | }, 50 | Direction::Down => Position { 51 | x, 52 | y: self.edge_lower(), 53 | }, 54 | Direction::Left => Position { 55 | x: self.edge_left(), 56 | y, 57 | }, 58 | Direction::Right => Position { 59 | x: self.edge_right(), 60 | y, 61 | }, 62 | } 63 | } 64 | /// Provide a reduced [Bounds] from given [Size] 65 | pub fn reduced_by(&self, size: &Size) -> Self { 66 | let x_pad = size.width / 2.0; 67 | let y_pad = size.height / 2.0; 68 | Self( 69 | self.0 70 | .clone() 71 | .pad_bottom(y_pad) 72 | .pad_top(y_pad) 73 | .pad_left(x_pad) 74 | .pad_right(x_pad), 75 | ) 76 | } 77 | } 78 | 79 | pub struct Scene { 80 | pub(crate) world: World, 81 | pub(crate) updater: Schedule, 82 | pub(crate) drawer: Schedule, 83 | pub(crate) event_time: f32, 84 | pub(crate) clock_time: f32, 85 | pub(crate) creation_count: u32, 86 | pub(crate) transform: Transform, 87 | } 88 | 89 | impl Scene { 90 | pub fn new(window: Rect) -> Self { 91 | let mut world = World::new(); 92 | let transform = Transform::identity().scale(Scale::new(ZOOM, ZOOM)); 93 | let bounds = Bounds::new(window); 94 | world.insert_resource(Time::default()); 95 | world.insert_resource(bounds); 96 | world.insert_resource(transform); 97 | 98 | let mut updater = Schedule::default(); 99 | updater.add_stage( 100 | "update", 101 | SystemStage::parallel() 102 | // .with_system(update_previous::.before(Label::Init)) 103 | // Beginnning of Main systems 104 | .with_system(init_from_target::.label(Label::Init)) 105 | .with_system(init_from_target::.label(Label::Init)) 106 | .with_system(init_from_target::.label(Label::Init)) 107 | .with_system(init_from_target::.label(Label::Init)) 108 | .with_system(init_from_target::.label(Label::Init)) 109 | .with_system(init_from_target::.label(Label::Init)) 110 | .with_system(init_from_target::.label(Label::Init)) 111 | .with_system(init_from_target::.label(Label::Init)) 112 | .with_system(init_from_target::.label(Label::Init)) 113 | .with_system(init_from_target::.label(Label::Init)) 114 | // Begin main animation updates 115 | .with_system(animate_position.after(Label::Init).label(Label::Main)) 116 | .with_system(animate::.after(Label::Init).label(Label::Main)) 117 | .with_system(animate::.after(Label::Init).label(Label::Main)) 118 | .with_system( 119 | animate:: 120 | .after(Label::Init) 121 | .label(Label::Main), 122 | ) 123 | .with_system( 124 | animate_with_multiply:: 125 | .after(Label::Init) 126 | .label(Label::Main), 127 | ) 128 | .with_system( 129 | animate_with_multiply:: 130 | .after(Label::Init) 131 | .label(Label::Main), 132 | ) 133 | .with_system( 134 | animate_with_relative:: 135 | .after(Label::Init) 136 | .label(Label::Main), 137 | ) 138 | .with_system( 139 | animate_with_relative:: 140 | .after(Label::Init) 141 | .label(Label::Main), 142 | ) 143 | .with_system( 144 | animate_with_relative:: 145 | .after(Label::Init) 146 | .label(Label::Main), 147 | ) 148 | .with_system(animate_with_relative::) 149 | // Post-processing 150 | .with_system( 151 | init_from_target:: 152 | .after(Label::Main) 153 | .label(Label::Post), 154 | ) 155 | .with_system(animate::.after(Label::Main).label(Label::Post)) 156 | .with_system(update_screen_paths.after(Label::Post)) 157 | .with_system(print), 158 | ); 159 | let mut drawer = Schedule::default(); 160 | drawer.add_stage("draw", SystemStage::single_threaded().with_system(draw)); 161 | 162 | Self { 163 | world, 164 | updater, 165 | drawer, 166 | event_time: 0.5, 167 | clock_time: 0.0, 168 | creation_count: 0, 169 | transform, 170 | } 171 | } 172 | /// All objects added to [Scene] has a depth value (i.e. z value) 173 | /// associated with it in order to identify the order of occlusion. 174 | /// Therefore, we keep a running counter of objects added and derive 175 | /// depth value from it at creation. 176 | pub fn increment_counter(&mut self) -> Depth { 177 | self.creation_count += 1; 178 | Depth(self.creation_count as f32 / 10.0) 179 | } 180 | pub fn circle(&mut self) -> CircleBuilder { 181 | circle(self) 182 | } 183 | pub fn rectangle(&mut self) -> RectangleBuilder { 184 | rectangle(self) 185 | } 186 | pub fn line(&mut self) -> LineBuilder { 187 | line(self) 188 | } 189 | pub fn text(&mut self) -> TextBuilder { 190 | text(self) 191 | } 192 | pub fn group(&mut self) -> EmptyBuilder { 193 | empty(self) 194 | } 195 | 196 | // pub fn group(&mut self, objects: impl Into>) -> EmptyBuilder { 197 | // let objects: Vec = objects.into(); 198 | // let mut builder = empty(self); 199 | 200 | // for object in objects.iter() { 201 | // builder = builder.add(*object); 202 | // } 203 | // builder 204 | // } 205 | 206 | pub fn add_circle(&mut self, x: f32, y: f32) { 207 | let c = circle(self) 208 | .with_position(x, y) 209 | .with_radius(0.2) 210 | .with_color(Color::random()) 211 | .make(); 212 | let t = self.clock_time; 213 | self.play(c.show_creation()).start_time(t).run_time(0.1); 214 | } 215 | 216 | pub fn update(&mut self, now: f32, win_rect: Rect) { 217 | // let now = self.clock_time; 218 | self.world 219 | .get_resource_mut::