use crate::effects::easing::EasingFn;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PlayState {
Playing,
Paused,
Completed,
}
#[derive(Debug, Clone)]
pub struct Animation {
pub start: f32,
pub end: f32,
pub current: f32,
pub duration: Duration,
pub elapsed: Duration,
pub easing: EasingFn,
pub state: PlayState,
pub loop_animation: bool,
pub alternate: bool,
pub direction_forward: bool,
}
impl Animation {
pub fn new(start: f32, end: f32, duration: Duration) -> Self {
Self {
start,
end,
current: start,
duration,
elapsed: Duration::ZERO,
easing: EasingFn::Linear,
state: PlayState::Playing,
loop_animation: false,
alternate: false,
direction_forward: true,
}
}
pub fn with_easing(mut self, easing: EasingFn) -> Self {
self.easing = easing;
self
}
pub fn with_loop(mut self) -> Self {
self.loop_animation = true;
self
}
pub fn with_alternate(mut self) -> Self {
self.alternate = true;
self
}
pub fn update(&mut self, delta: Duration) {
if self.state != PlayState::Playing {
return;
}
self.elapsed += delta;
let t = if self.duration.is_zero() {
1.0
} else {
(self.elapsed.as_secs_f32() / self.duration.as_secs_f32()).min(1.0)
};
let eased = if self.direction_forward {
self.easing.apply(t)
} else {
1.0 - self.easing.apply(t)
};
self.current = self.start + (self.end - self.start) * eased;
if t >= 1.0 {
if self.loop_animation {
self.elapsed = Duration::ZERO;
if self.alternate {
self.direction_forward = !self.direction_forward;
}
} else {
self.state = PlayState::Completed;
self.current = if self.direction_forward {
self.end
} else {
self.start
};
}
}
}
pub fn progress(&self) -> f32 {
if self.duration.is_zero() {
1.0
} else {
(self.elapsed.as_secs_f32() / self.duration.as_secs_f32()).min(1.0)
}
}
pub fn restart(&mut self) {
self.elapsed = Duration::ZERO;
self.current = self.start;
self.state = PlayState::Playing;
self.direction_forward = true;
}
pub fn pause(&mut self) {
self.state = PlayState::Paused;
}
pub fn resume(&mut self) {
if self.state == PlayState::Paused {
self.state = PlayState::Playing;
}
}
}
#[derive(Debug, Clone)]
pub struct Tween {
pub from: f32,
pub to: f32,
pub value: f32,
pub progress: f32,
pub duration: Duration,
pub elapsed: Duration,
pub easing: EasingFn,
}
impl Tween {
pub fn new(from: f32, to: f32, duration: Duration) -> Self {
Self {
from,
to,
value: from,
progress: 0.0,
duration,
elapsed: Duration::ZERO,
easing: EasingFn::OutQuad,
}
}
pub fn with_easing(mut self, easing: EasingFn) -> Self {
self.easing = easing;
self
}
pub fn update(&mut self, delta: Duration) {
self.elapsed += delta;
self.progress = if self.duration.is_zero() {
1.0
} else {
(self.elapsed.as_secs_f32() / self.duration.as_secs_f32()).min(1.0)
};
let eased = self.easing.apply(self.progress);
self.value = self.from + (self.to - self.from) * eased;
}
pub fn is_complete(&self) -> bool {
self.progress >= 1.0
}
pub fn restart(&mut self) {
self.elapsed = Duration::ZERO;
self.progress = 0.0;
self.value = self.from;
}
}
#[derive(Debug, Clone)]
pub struct TimelineItem {
pub start_time: Duration,
pub animation: Animation,
pub label: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Timeline {
pub items: Vec<TimelineItem>,
pub current_time: Duration,
pub is_playing: bool,
pub loop_timeline: bool,
pub duration: Duration,
}
impl Timeline {
pub fn new() -> Self {
Self {
items: Vec::new(),
current_time: Duration::ZERO,
is_playing: false,
loop_timeline: false,
duration: Duration::ZERO,
}
}
pub fn with_loop(mut self) -> Self {
self.loop_timeline = true;
self
}
pub fn add(&mut self, start_time: Duration, animation: Animation, label: Option<String>) {
let end = start_time + animation.duration;
if end > self.duration {
self.duration = end;
}
self.items.push(TimelineItem {
start_time,
animation,
label,
});
}
pub fn play(&mut self) {
self.is_playing = true;
self.current_time = Duration::ZERO;
for item in &mut self.items {
item.animation.restart();
item.animation.state = PlayState::Paused;
}
}
pub fn pause(&mut self) {
self.is_playing = false;
for item in &mut self.items {
item.animation.pause();
}
}
pub fn update(&mut self, delta: Duration) {
if !self.is_playing {
return;
}
self.current_time += delta;
for item in &mut self.items {
if self.current_time >= item.start_time {
if item.animation.state == PlayState::Paused {
item.animation.resume();
}
let item_delta = if self.current_time - item.start_time < delta {
self.current_time - item.start_time
} else {
delta
};
item.animation.update(item_delta);
}
}
if !self.loop_timeline && self.current_time >= self.duration {
self.is_playing = false;
for item in &mut self.items {
item.animation.state = PlayState::Completed;
}
} else if self.loop_timeline && self.current_time >= self.duration {
self.current_time = Duration::ZERO;
for item in &mut self.items {
item.animation.restart();
item.animation.state = PlayState::Paused;
}
}
}
pub fn get_value(&self, index: usize) -> Option<f32> {
self.items.get(index).map(|item| item.animation.current)
}
pub fn get_label_value(&self, label: &str) -> Option<f32> {
self.items
.iter()
.find(|item| item.label.as_deref() == Some(label))
.map(|item| item.animation.current)
}
pub fn is_complete(&self) -> bool {
self.items
.iter()
.all(|item| item.animation.state == PlayState::Completed)
}
}
impl Default for Timeline {
fn default() -> Self {
Self::new()
}
}
pub struct AnimationClock {
last_tick: Instant,
total_elapsed: Duration,
tick_count: u64,
fps: f64,
}
impl AnimationClock {
pub fn new(fps: f64) -> Self {
Self {
last_tick: Instant::now(),
total_elapsed: Duration::ZERO,
tick_count: 0,
fps,
}
}
pub fn tick(&mut self) -> Duration {
let now = Instant::now();
let delta = now.duration_since(self.last_tick);
self.last_tick = now;
self.total_elapsed += delta;
self.tick_count += 1;
delta
}
pub fn frame_delay(&self) -> Duration {
Duration::from_secs_f64(1.0 / self.fps)
}
pub fn should_render(&self) -> bool {
let target_frame_time = Duration::from_secs_f64(1.0 / self.fps);
let avg = if self.tick_count == 0 {
Duration::ZERO
} else {
Duration::from_secs_f64(self.total_elapsed.as_secs_f64() / self.tick_count as f64)
};
avg <= Duration::from_secs_f64(target_frame_time.as_secs_f64() * 1.1)
}
pub fn total_elapsed(&self) -> Duration {
self.total_elapsed
}
pub fn tick_count(&self) -> u64 {
self.tick_count
}
pub fn fps(&self) -> f64 {
self.fps
}
pub fn reset(&mut self) {
self.last_tick = Instant::now();
self.total_elapsed = Duration::ZERO;
self.tick_count = 0;
}
}