Skip to content

Commit

Permalink
✨ Implement animations!
Browse files Browse the repository at this point in the history
  • Loading branch information
ewen-lbh committed May 4, 2024
1 parent f219c77 commit e7ff660
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 36 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ web-sys = { version = "0.3.4", features = [
'Window',
] }
once_cell = "1.19.0"
nanoid = "0.4.0"


[dev-dependencies]
Expand Down
Binary file added examples/schedule-hell-exerpt.mp4
Binary file not shown.
52 changes: 52 additions & 0 deletions src/animation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use std::fmt::Display;

use crate::{Canvas, Context, LaterHookCondition, RenderFunction};

/// Arguments: animation progress (from 0.0 to 1.0), canvas, current ms
pub type AnimationUpdateFunction = dyn Fn(f32, &mut Canvas, usize);

pub struct Animation {
pub name: String,
// pub keyframes: Vec<Keyframe<C>>,
pub update: Box<AnimationUpdateFunction>,
}

// pub struct Keyframe<C: Default> {
// pub at: f32, // from 0 to 1
// pub action: Box<RenderFunction<C>>,
// }

impl Animation {
/// Example
/// ```
/// Animation::new("example", &|t, canvas, _| {
/// canvas.root().object("dot").fill(Fill::Translucent(Color::Red, t))
/// })
/// ```
pub fn new<N>(name: N, f: &'static AnimationUpdateFunction) -> Self
where
N: Display,
{
Self {
name: format!("{}", name),
update: Box::new(f),
}
}

// /// Example:
// /// ```
// /// animation.at(50.0, Box::new(|canvas, _| canvas.root().set_background(Color::Black)));
// /// ```
// pub fn at(&mut self, percent: f32, action: Box<RenderFunction<C>>) {
// self.keyframes.push(Keyframe {
// at: percent / 100.0,
// action,
// });
// }
}

impl From<(String, Box<AnimationUpdateFunction>)> for Animation {
fn from((name, f): (String, Box<AnimationUpdateFunction>)) -> Self {
Self { name, update: f }
}
}
6 changes: 6 additions & 0 deletions src/layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ impl Layer {
panic!("object {} already exists in layer {}", name_str, self.name);
}

self.set_object(name_str, object);
}

pub fn set_object<'a, N: Display>(&mut self, name: N, object: ColoredObject) {
let name_str = format!("{}", name);

self.objects.insert(name_str, object);
self.flush();
}
Expand Down
80 changes: 55 additions & 25 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,44 @@
pub mod animation;
pub mod audio;
pub mod canvas;
pub mod cli;
pub mod video;
pub use video::*;
mod color;
pub mod color;
pub mod examples;
mod objects;
pub mod fill;
pub mod filter;
pub mod layer;
pub mod midi;
pub mod objects;
pub mod point;
pub mod preview;
pub mod region;
pub mod sync;
pub mod video;
pub mod web;
pub use animation::*;
pub use audio::*;
pub use canvas::*;
pub use color::*;
pub use objects::*;
mod fill;
mod point;
pub use fill::*;
pub use filter::*;
pub use layer::*;
pub use midi::MidiSynchronizer;
pub use objects::*;
pub use point::*;
mod region;
pub use region::*;
mod web;
pub use web::log;
mod audio;
pub use audio::*;
mod sync;
use sync::SyncData;
pub use sync::Syncable;
mod layer;
pub use layer::*;
mod canvas;
pub use canvas::*;
mod filter;
pub use filter::*;
mod midi;
mod preview;
pub use video::*;
pub use web::log;

use indicatif::{ProgressBar, ProgressStyle};
pub use midi::MidiSynchronizer;
use nanoid::nanoid;
use std::fs::{self};
use std::path::{PathBuf};
use std::ops::{Add, Div, Range, Sub};
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::thread::{self, JoinHandle};
use std::time;
use sync::SyncData;

const PROGRESS_BARS_STYLE: &str =
"{spinner:.cyan} {percent:03.bold.cyan}% {msg:<30} [{bar:100.bold.blue/dim.blue}] {eta:.cyan}";
Expand Down Expand Up @@ -119,6 +124,7 @@ impl<'a, C> Context<'a, C> {
self.later_hooks.insert(
0,
LaterHook {
once: true,
when: Box::new(move |_, context, _previous_beat| {
context.frame >= current_frame + delay
}),
Expand All @@ -133,6 +139,7 @@ impl<'a, C> Context<'a, C> {
self.later_hooks.insert(
0,
LaterHook {
once: true,
when: Box::new(move |_, context, _previous_beat| context.ms >= current_ms + delay),
render_function: Box::new(render_function),
},
Expand All @@ -145,13 +152,37 @@ impl<'a, C> Context<'a, C> {
self.later_hooks.insert(
0,
LaterHook {
once: true,
when: Box::new(move |_, context, _previous_beat| {
context.beat_fractional >= current_beat as f32 + delay
}),
render_function: Box::new(render_function),
},
);
}

/// duration is in milliseconds
pub fn start_animation(&mut self, duration: usize, animation: Animation) {
let start_ms = self.ms;
let ms_range = start_ms..(start_ms + duration);

self.later_hooks.push(LaterHook {
once: false,
when: Box::new(move |_, ctx, _| ms_range.contains(&ctx.ms)),
render_function: Box::new(move |canvas, ms| {
let t = (ms - start_ms) as f32 / duration as f32;
(animation.update)(t, canvas, ms)
}),
})
}

/// duration is in milliseconds
pub fn animate(&mut self, duration: usize, f: &'static AnimationUpdateFunction) {
self.start_animation(
duration,
Animation::new(format!("unnamed animation {}", nanoid!()), f),
);
}
}

struct SpinState {
Expand Down Expand Up @@ -191,5 +222,4 @@ impl SpinState {
}
}


fn main() {}
24 changes: 15 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@ pub fn run(args: cli::Args) {
let Point(x, y) = canvas.world_region.end;
(x - 2, y - 2)
};
kicks.add_object("top left", circle_at(1, 1).color(fill));
kicks.add_object("top right", circle_at(end_x, 1).color(fill));
kicks.add_object("bottom left", circle_at(1, end_y).color(fill));
kicks.add_object("bottom right", circle_at(end_x, end_y).color(fill));
kicks.set_object("top left", circle_at(1, 1).color(fill));
kicks.set_object("top right", circle_at(end_x, 1).color(fill));
kicks.set_object("bottom left", circle_at(1, end_y).color(fill));
kicks.set_object("bottom right", circle_at(end_x, end_y).color(fill));
canvas.add_or_replace_layer(kicks);

let mut ch = Layer::new("ch");
ch.add_object("0", Object::Dot(Point(0, 0)).into());
ch.set_object("0", Object::Dot(Point(0, 0)).into());
canvas.add_or_replace_layer(ch);
})
.sync_audio_with(&args.flag_sync_with.unwrap())
Expand All @@ -71,7 +71,13 @@ pub fn run(args: cli::Args) {

canvas.layer("anchor kick").flush();

ctx.later_ms(200, &fade_out_kick_circles)
// ctx.later_ms(200, &fade_out_kick_circles)
ctx.animate(200, &|t, canvas, _| {
canvas
.layer("anchor kick")
.paint_all_objects(Fill::Translucent(Color::White, 1.0 - t));
canvas.layer("anchor kick").flush();
});
})
.on_note("bass", &|canvas, ctx| {
let mut new_layer = canvas.random_layer_within("bass", &ctx.extra.bass_pattern_at);
Expand Down Expand Up @@ -140,7 +146,7 @@ pub fn run(args: cli::Args) {
layer.objects.retain(|name, _| dots_to_keep.contains(name));

let object_name = format!("{}", ctx.ms);
layer.add_object(
layer.set_object(
&object_name,
Object::Dot(world.resized(-1, -1).random_coordinates_within().into())
.color(Fill::Solid(Color::Cyan)),
Expand All @@ -150,7 +156,7 @@ pub fn run(args: cli::Args) {
canvas.layer("ch").flush();
})
.when_remaining(10, &|canvas, _| {
canvas.root().add_object(
canvas.root().set_object(
"credits text",
Object::RawSVG(Box::new(svg::node::Text::new("by ewen-lbh"))).into(),
);
Expand All @@ -167,7 +173,7 @@ pub fn run(args: cli::Args) {
}
}

fn fade_out_kick_circles(canvas: &mut Canvas) {
fn fade_out_kick_circles(canvas: &mut Canvas, _: usize) {
canvas
.layer("anchor kick")
.paint_all_objects(Fill::Translucent(Color::White, 0.0));
Expand Down
11 changes: 9 additions & 2 deletions src/video.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ pub type CommandAction<C> = dyn Fn(String, &mut Canvas, &mut Context<C>);
/// Arguments: canvas, context, previous rendered beat, previous rendered frame
pub type HookCondition<C> = dyn Fn(&Canvas, &Context<C>, usize, usize) -> bool;

pub type LaterRenderFunction = dyn Fn(&mut Canvas);
/// Arguments: canvas, context, current milliseconds timestamp
pub type LaterRenderFunction = dyn Fn(&mut Canvas, usize);

/// Arguments: canvas, context, previous rendered beat
pub type LaterHookCondition<C> = dyn Fn(&Canvas, &Context<C>, usize) -> bool;
Expand All @@ -49,6 +50,8 @@ pub struct Hook<C> {
pub struct LaterHook<C> {
pub when: Box<LaterHookCondition<C>>,
pub render_function: Box<LaterRenderFunction>,
/// Whether the hook should be run only once
pub once: bool,
}

impl<C> std::fmt::Debug for Hook<C> {
Expand Down Expand Up @@ -561,7 +564,11 @@ impl<AdditionalContext: Default> Video<AdditionalContext> {

for (i, hook) in context.later_hooks.iter().enumerate() {
if (hook.when)(&canvas, &context, previous_rendered_beat) {
(hook.render_function)(&mut canvas);
(hook.render_function)(&mut canvas, context.ms);
if hook.once {
later_hooks_to_delete.push(i);
}
} else if !hook.once {
later_hooks_to_delete.push(i);
}
}
Expand Down

0 comments on commit e7ff660

Please sign in to comment.