use super::easing::Easing;
use std::collections::HashMap;
use std::time::Duration;
pub trait Animatable: Clone + Send + Sync + 'static {
fn lerp(&self, target: &Self, t: f64) -> Self;
}
impl Animatable for f64 {
fn lerp(&self, target: &Self, t: f64) -> Self {
self + (target - self) * t
}
}
impl Animatable for f32 {
fn lerp(&self, target: &Self, t: f64) -> Self {
self + (target - self) * t as f32
}
}
impl Animatable for (f64, f64) {
fn lerp(&self, target: &Self, t: f64) -> Self {
(self.0.lerp(&target.0, t), self.1.lerp(&target.1, t))
}
}
impl Animatable for (f64, f64, f64, f64) {
fn lerp(&self, target: &Self, t: f64) -> Self {
(
self.0.lerp(&target.0, t),
self.1.lerp(&target.1, t),
self.2.lerp(&target.2, t),
self.3.lerp(&target.3, t),
)
}
}
#[derive(Debug, Clone)]
pub struct Tween<T: Animatable> {
pub from: T,
pub to: T,
pub duration: Duration,
pub easing: Easing,
pub delay: Duration,
}
impl<T: Animatable> Tween<T> {
pub fn new(from: T, to: T) -> Self {
Self {
from,
to,
duration: Duration::from_secs(1),
easing: Easing::default(),
delay: Duration::ZERO,
}
}
pub fn duration(mut self, d: Duration) -> Self {
self.duration = d;
self
}
pub fn easing(mut self, e: Easing) -> Self {
self.easing = e;
self
}
pub fn delay(mut self, d: Duration) -> Self {
self.delay = d;
self
}
pub fn evaluate(&self, elapsed: Duration) -> Option<T> {
if elapsed < self.delay {
return None;
}
let t = elapsed.saturating_sub(self.delay);
if t >= self.duration {
return Some(self.to.clone());
}
let progress = t.as_secs_f64() / self.duration.as_secs_f64();
let eased = self.easing.ease(progress);
Some(self.from.lerp(&self.to, eased))
}
pub fn is_complete(&self, elapsed: Duration) -> bool {
elapsed >= self.total_duration()
}
pub fn total_duration(&self) -> Duration {
self.delay + self.duration
}
}
#[derive(Debug, Clone)]
pub enum Position {
Absolute(Duration),
AfterPrevious(Duration),
AtLabel(String),
}
#[derive(Debug, Clone)]
struct TimelineEntry {
start: Duration,
duration: Duration,
id: u64,
}
#[derive(Debug, Clone)]
pub struct Timeline {
entries: Vec<TimelineEntry>,
labels: HashMap<String, Duration>,
total_duration: Duration,
repeat: u32,
yoyo: bool,
speed: f64,
next_id: u64,
}
impl Timeline {
pub fn new() -> Self {
Self {
entries: Vec::new(),
labels: HashMap::new(),
total_duration: Duration::ZERO,
repeat: 0,
yoyo: false,
speed: 1.0,
next_id: 0,
}
}
pub fn add(&mut self, duration: Duration, position: Position) -> u64 {
let start = self.resolve_position(position);
let id = self.next_id;
self.next_id += 1;
self.entries.push(TimelineEntry {
start,
duration,
id,
});
let end = start + duration;
if end > self.total_duration {
self.total_duration = end;
}
self.entries.sort_by_key(|e| e.start);
id
}
pub fn label(&mut self, name: &str) -> &mut Self {
self.labels.insert(name.to_string(), self.total_duration);
self
}
pub fn label_at(&mut self, name: &str, time: Duration) -> &mut Self {
self.labels.insert(name.to_string(), time);
self
}
pub fn repeat(mut self, count: u32) -> Self {
self.repeat = count;
self
}
pub fn yoyo(mut self, enabled: bool) -> Self {
self.yoyo = enabled;
self
}
pub fn speed(mut self, speed: f64) -> Self {
self.speed = speed;
self
}
pub fn total_duration(&self) -> Duration {
self.total_duration
}
fn resolve_position(&self, position: Position) -> Duration {
match position {
Position::Absolute(time) => time,
Position::AfterPrevious(offset) => {
if offset.as_nanos() > 0 {
self.total_duration + offset
} else {
self.total_duration.saturating_sub(offset.abs_diff(Duration::ZERO))
}
}
Position::AtLabel(label) => {
*self.labels.get(&label).unwrap_or(&Duration::ZERO)
}
}
}
}
impl Default for Timeline {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PlayDirection {
Forward,
Reverse,
}
pub struct TimelinePlayback {
timeline: Timeline,
elapsed: Duration,
playing: bool,
direction: PlayDirection,
current_repeat: u32,
}
impl TimelinePlayback {
pub fn new(timeline: Timeline) -> Self {
Self {
timeline,
elapsed: Duration::ZERO,
playing: false,
direction: PlayDirection::Forward,
current_repeat: 0,
}
}
pub fn tick(&mut self, dt: Duration) {
if !self.playing {
return;
}
let dt_scaled = dt.mul_f64(self.timeline.speed);
match self.direction {
PlayDirection::Forward => {
self.elapsed += dt_scaled;
if self.elapsed >= self.timeline.total_duration {
if self.current_repeat < self.timeline.repeat {
self.current_repeat += 1;
if self.timeline.yoyo {
self.direction = PlayDirection::Reverse;
} else {
self.elapsed = Duration::ZERO;
}
} else {
self.elapsed = self.timeline.total_duration;
self.playing = false;
}
}
}
PlayDirection::Reverse => {
if self.elapsed > dt_scaled {
self.elapsed -= dt_scaled;
} else {
self.elapsed = Duration::ZERO;
if self.current_repeat < self.timeline.repeat {
self.current_repeat += 1;
if self.timeline.yoyo {
self.direction = PlayDirection::Forward;
self.elapsed = Duration::ZERO;
}
} else {
self.playing = false;
}
}
}
}
}
pub fn progress_of(&self, entry_id: u64) -> f64 {
let entry = self.timeline.entries.iter().find(|e| e.id == entry_id);
match entry {
Some(entry) => {
if self.elapsed < entry.start {
0.0
} else if self.elapsed >= entry.start + entry.duration {
1.0
} else {
let local_time = self.elapsed.saturating_sub(entry.start);
local_time.as_secs_f64() / entry.duration.as_secs_f64()
}
}
None => 0.0,
}
}
pub fn is_active(&self, entry_id: u64) -> bool {
let entry = self.timeline.entries.iter().find(|e| e.id == entry_id);
match entry {
Some(entry) => {
self.elapsed >= entry.start && self.elapsed < entry.start + entry.duration
}
None => false,
}
}
pub fn is_complete(&self) -> bool {
!self.playing && self.elapsed >= self.timeline.total_duration
}
pub fn play(&mut self) {
self.playing = true;
}
pub fn pause(&mut self) {
self.playing = false;
}
pub fn restart(&mut self) {
self.elapsed = Duration::ZERO;
self.playing = true;
self.direction = PlayDirection::Forward;
self.current_repeat = 0;
}
pub fn reverse(&mut self) {
self.direction = match self.direction {
PlayDirection::Forward => PlayDirection::Reverse,
PlayDirection::Reverse => PlayDirection::Forward,
};
}
pub fn seek(&mut self, time: Duration) {
self.elapsed = time.min(self.timeline.total_duration);
}
pub fn elapsed(&self) -> Duration {
self.elapsed
}
pub fn is_playing(&self) -> bool {
self.playing
}
pub fn direction(&self) -> PlayDirection {
self.direction
}
}