use std::cell::RefCell;
use std::cmp::Ordering;
use std::rc::{Rc, Weak};
use dinamika_cpu::{Color, Pixmap};
use crate::render;
use crate::shape::Shape;
mod action;
mod tween;
pub use action::{cascade, delay, parallel, pause, sequence, Action};
pub(crate) use tween::{new_tween, TweenObj};
use action::{duration_of, flatten};
type Plan = Rc<Vec<Rc<dyn TweenObj>>>;
pub(crate) struct TimelineState {
actions: RefCell<Vec<Action>>,
plan: RefCell<Option<Plan>>,
shapes: RefCell<Vec<Shape>>,
width: u32,
height: u32,
background: Color,
fps: f64,
}
impl TimelineState {
pub(super) fn append(&self, action: Action) {
action.mark_registered();
self.actions.borrow_mut().push(action);
self.invalidate();
}
fn plan(&self) -> Plan {
if self.plan.borrow().is_none() {
let mut tweens = Vec::new();
let mut offset = 0.0;
for a in self.actions.borrow().iter() {
offset += flatten(a, offset, &mut tweens);
}
tweens.sort_by(|a, b| a.start().partial_cmp(&b.start()).unwrap_or(Ordering::Equal));
*self.plan.borrow_mut() = Some(Rc::new(tweens));
}
self.plan.borrow().clone().unwrap()
}
fn invalidate(&self) {
*self.plan.borrow_mut() = None;
}
fn duration(&self) -> f64 {
self.actions.borrow().iter().map(duration_of).sum()
}
fn seek(&self, t: f64) {
let plan = self.plan();
for e in plan.iter().rev() {
e.reset();
}
for e in plan.iter() {
if e.start() > t {
break;
}
e.capture_from();
e.apply(t);
}
}
fn frame(&self, t: f64) -> Pixmap {
self.seek(t);
let shapes = self.shapes.borrow();
render::render_scene(self.width, self.height, self.background, &shapes)
}
fn frames(&self) -> Vec<Pixmap> {
let fps = self.fps.max(1.0);
let duration = self.duration();
let frame_count = (duration * fps).ceil() as u64 + 1;
(0..frame_count).map(|i| self.frame(i as f64 / fps)).collect()
}
}
pub struct Timeline {
inner: Rc<TimelineState>,
}
impl Timeline {
pub fn new(width: u32, height: u32, background: Color, fps: f64) -> Self {
Timeline {
inner: Rc::new(TimelineState {
actions: RefCell::new(Vec::new()),
plan: RefCell::new(None),
shapes: RefCell::new(Vec::new()),
width,
height,
background,
fps,
}),
}
}
pub(crate) fn weak(&self) -> Weak<TimelineState> {
Rc::downgrade(&self.inner)
}
pub fn pause(&self, seconds: f64) -> &Self {
self.inner.append(pause(seconds));
self
}
pub fn parallel(&self, items: impl IntoIterator<Item = Action>) -> &Self {
self.inner.append(parallel(items));
self
}
pub fn sequence(&self, items: impl IntoIterator<Item = Action>) -> &Self {
self.inner.append(sequence(items));
self
}
pub fn cascade(&self, items: impl IntoIterator<Item = Action>, gap: f64) -> &Self {
self.inner.append(cascade(items, gap));
self
}
pub(crate) fn register_shape(&self, shape: Shape) {
self.inner.shapes.borrow_mut().push(shape);
}
pub fn shapes(&self) -> Vec<Shape> {
self.inner.shapes.borrow().clone()
}
pub fn duration(&self) -> f64 {
self.inner.duration()
}
pub fn seek(&self, t: f64) {
self.inner.seek(t);
}
pub fn frame(&self, t: f64) -> Pixmap {
self.inner.frame(t)
}
pub(crate) fn frames(&self) -> Vec<Pixmap> {
self.inner.frames()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::easing::Easing;
use crate::signal::Signal;
#[test]
fn sequence_chains_durations() {
let s = Signal::new(0.0_f32);
let tl = Timeline::new(64, 64, Color::BLACK, 30.0);
tl.sequence(vec![
s.tween_to(10.0, 1.0, Easing::Linear),
s.tween_to(20.0, 1.0, Easing::Linear),
]);
assert!((tl.duration() - 2.0).abs() < 1e-6);
tl.seek(0.0);
assert!((s.get() - 0.0).abs() < 1e-3);
tl.seek(0.5);
assert!((s.get() - 5.0).abs() < 1e-3);
tl.seek(1.0);
assert!((s.get() - 10.0).abs() < 1e-3);
tl.seek(1.5);
assert!((s.get() - 15.0).abs() < 1e-3);
tl.seek(2.0);
assert!((s.get() - 20.0).abs() < 1e-3);
}
#[test]
fn parallel_runs_simultaneously() {
let a = Signal::new(0.0_f32);
let b = Signal::new(0.0_f32);
let tl = Timeline::new(64, 64, Color::BLACK, 30.0);
tl.parallel(vec![
a.tween_to(100.0, 1.0, Easing::Linear),
b.tween_to(50.0, 2.0, Easing::Linear),
]);
assert!((tl.duration() - 2.0).abs() < 1e-6);
tl.seek(1.0);
assert!((a.get() - 100.0).abs() < 1e-3, "a={}", a.get());
assert!((b.get() - 25.0).abs() < 1e-3, "b={}", b.get());
}
#[test]
fn pause_offsets_following_actions() {
let s = Signal::new(0.0_f32);
let tl = Timeline::new(64, 64, Color::BLACK, 30.0);
tl.pause(1.0);
tl.sequence(vec![s.tween_to(10.0, 1.0, Easing::Linear)]);
assert!((tl.duration() - 2.0).abs() < 1e-6);
tl.seek(1.0);
assert!((s.get() - 0.0).abs() < 1e-3);
tl.seek(1.5);
assert!((s.get() - 5.0).abs() < 1e-3);
}
#[test]
fn seek_is_reversible() {
let s = Signal::new(0.0_f32);
let tl = Timeline::new(64, 64, Color::BLACK, 30.0);
tl.sequence(vec![s.tween_to(10.0, 1.0, Easing::Linear)]);
tl.seek(1.0);
assert!((s.get() - 10.0).abs() < 1e-3);
tl.seek(0.25);
assert!((s.get() - 2.5).abs() < 1e-3, "got {}", s.get());
}
#[test]
fn cascade_inserts_pause() {
let s = Signal::new(0.0_f32);
let tl = Timeline::new(64, 64, Color::BLACK, 30.0);
tl.cascade(
vec![
s.tween_to(10.0, 1.0, Easing::Linear),
s.tween_to(20.0, 1.0, Easing::Linear),
],
0.5,
);
assert!((tl.duration() - 2.5).abs() < 1e-6);
tl.seek(1.25); assert!((s.get() - 10.0).abs() < 1e-3, "got {}", s.get());
}
}