use super::easing::Easing;
use std::time::Duration;
pub trait DurationExt {
fn ms(self) -> Duration;
fn secs(self) -> Duration;
}
impl DurationExt for u64 {
fn ms(self) -> Duration {
Duration::from_millis(self)
}
fn secs(self) -> Duration {
Duration::from_secs(self)
}
}
impl DurationExt for f32 {
fn ms(self) -> Duration {
Duration::from_secs_f32(self / 1000.0)
}
fn secs(self) -> Duration {
Duration::from_secs_f32(self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AnimationState {
Idle,
Running,
Paused,
Completed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum AnimationDirection {
#[default]
Normal,
Reverse,
Alternate,
AlternateReverse,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FillMode {
#[default]
None,
Forwards,
Backwards,
Both,
}
#[derive(Debug, Clone)]
pub struct Animation {
from: f32,
to: f32,
duration: Duration,
easing: Easing,
iterations: u32,
direction: AnimationDirection,
delay: Duration,
fill_mode: FillMode,
}
impl Default for Animation {
fn default() -> Self {
Self {
from: 0.0,
to: 1.0,
duration: Duration::from_millis(300),
easing: Easing::default(),
iterations: 1,
direction: AnimationDirection::default(),
delay: Duration::ZERO,
fill_mode: FillMode::default(),
}
}
}
impl Animation {
pub fn new() -> Self {
Self::default()
}
pub fn keyframes() -> Self {
Self::default()
}
pub fn from(mut self, value: f32) -> Self {
self.from = value;
self
}
pub fn to(mut self, value: f32) -> Self {
self.to = value;
self
}
pub fn values(mut self, from: f32, to: f32) -> Self {
self.from = from;
self.to = to;
self
}
pub fn duration(mut self, duration: Duration) -> Self {
self.duration = duration;
self
}
pub fn easing(mut self, easing: Easing) -> Self {
self.easing = easing;
self
}
pub fn iterations(mut self, count: u32) -> Self {
self.iterations = count;
self
}
pub fn infinite(mut self) -> Self {
self.iterations = 0;
self
}
pub fn direction(mut self, direction: AnimationDirection) -> Self {
self.direction = direction;
self
}
pub fn delay(mut self, delay: Duration) -> Self {
self.delay = delay;
self
}
pub fn fill_mode(mut self, mode: FillMode) -> Self {
self.fill_mode = mode;
self
}
pub fn get_from(&self) -> f32 {
self.from
}
pub fn get_to(&self) -> f32 {
self.to
}
pub fn get_duration(&self) -> Duration {
self.duration
}
pub fn get_easing(&self) -> Easing {
self.easing
}
pub fn get_iterations(&self) -> u32 {
self.iterations
}
pub fn get_delay(&self) -> Duration {
self.delay
}
pub fn start(&self) -> AnimationInstance {
AnimationInstance::new(self.clone())
}
}
#[derive(Debug, Clone)]
pub struct AnimationInstance {
config: Animation,
state: AnimationState,
elapsed: Duration,
current_iteration: u32,
value: f32,
}
impl AnimationInstance {
pub fn new(config: Animation) -> Self {
let initial_value = match config.fill_mode {
FillMode::Backwards | FillMode::Both => config.from,
_ => config.from,
};
Self {
config,
state: AnimationState::Idle,
elapsed: Duration::ZERO,
current_iteration: 0,
value: initial_value,
}
}
pub fn play(&mut self) {
match self.state {
AnimationState::Idle | AnimationState::Paused => {
self.state = AnimationState::Running;
}
_ => {}
}
}
pub fn pause(&mut self) {
if self.state == AnimationState::Running {
self.state = AnimationState::Paused;
}
}
pub fn reset(&mut self) {
self.elapsed = Duration::ZERO;
self.current_iteration = 0;
self.state = AnimationState::Idle;
self.value = self.config.from;
}
pub fn tick(&mut self, delta: Duration) {
if self.state != AnimationState::Running {
return;
}
self.elapsed += delta;
if self.elapsed < self.config.delay {
return;
}
let active_elapsed = self.elapsed - self.config.delay;
let duration_ms = self.config.duration.as_secs_f32() * 1000.0;
if duration_ms <= 0.0 {
self.value = self.config.to;
self.state = AnimationState::Completed;
return;
}
let total_progress_ms = active_elapsed.as_secs_f32() * 1000.0;
let iteration_progress = total_progress_ms / duration_ms;
let current_iteration = iteration_progress.floor() as u32;
if self.config.iterations > 0 && current_iteration >= self.config.iterations {
self.current_iteration = self.config.iterations;
self.state = AnimationState::Completed;
self.value = match self.config.fill_mode {
FillMode::Forwards | FillMode::Both => self.config.to,
_ => self.config.from,
};
return;
}
self.current_iteration = current_iteration;
let t = iteration_progress.fract();
let effective_t = match self.config.direction {
AnimationDirection::Normal => t,
AnimationDirection::Reverse => 1.0 - t,
AnimationDirection::Alternate => {
if current_iteration % 2 == 0 {
t
} else {
1.0 - t
}
}
AnimationDirection::AlternateReverse => {
if current_iteration % 2 == 0 {
1.0 - t
} else {
t
}
}
};
self.value = self
.config
.easing
.interpolate(self.config.from, self.config.to, effective_t);
}
pub fn get(&self) -> f32 {
self.value
}
pub fn get_i32(&self) -> i32 {
self.value.round() as i32
}
pub fn get_usize(&self) -> usize {
self.value.round().max(0.0) as usize
}
pub fn state(&self) -> AnimationState {
self.state
}
pub fn is_running(&self) -> bool {
self.state == AnimationState::Running
}
pub fn is_completed(&self) -> bool {
self.state == AnimationState::Completed
}
pub fn current_iteration(&self) -> u32 {
self.current_iteration
}
pub fn elapsed(&self) -> Duration {
self.elapsed
}
pub fn progress(&self) -> f32 {
if self.config.duration.is_zero() {
return 1.0;
}
let active_elapsed = self.elapsed.saturating_sub(self.config.delay);
let progress = active_elapsed.as_secs_f32() / self.config.duration.as_secs_f32();
if self.config.iterations == 0 {
progress.fract()
} else {
(progress / self.config.iterations as f32).min(1.0)
}
}
}
impl Animation {
pub fn fade_in(duration: Duration) -> Self {
Self::new()
.from(0.0)
.to(1.0)
.duration(duration)
.easing(Easing::EaseOut)
}
pub fn fade_out(duration: Duration) -> Self {
Self::new()
.from(1.0)
.to(0.0)
.duration(duration)
.easing(Easing::EaseIn)
}
pub fn slide(from: f32, to: f32, duration: Duration) -> Self {
Self::new()
.from(from)
.to(to)
.duration(duration)
.easing(Easing::EaseInOutCubic)
}
pub fn bounce(from: f32, to: f32, duration: Duration) -> Self {
Self::new()
.from(from)
.to(to)
.duration(duration)
.easing(Easing::EaseOutBounce)
}
pub fn pulse(duration: Duration) -> Self {
Self::new()
.from(1.0)
.to(1.2)
.duration(duration)
.direction(AnimationDirection::Alternate)
.infinite()
}
pub fn blink(duration: Duration) -> Self {
Self::new()
.from(0.0)
.to(1.0)
.duration(duration)
.direction(AnimationDirection::Alternate)
.infinite()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_animation_builder() {
let anim = Animation::new()
.from(0.0)
.to(100.0)
.duration(300.ms())
.easing(Easing::EaseOut);
assert_eq!(anim.get_from(), 0.0);
assert_eq!(anim.get_to(), 100.0);
assert_eq!(anim.get_duration(), Duration::from_millis(300));
}
#[test]
fn test_duration_ext() {
assert_eq!(300u64.ms(), Duration::from_millis(300));
assert_eq!(2u64.secs(), Duration::from_secs(2));
}
#[test]
fn test_animation_instance_start() {
let anim = Animation::new().from(0.0).to(100.0).duration(100.ms());
let mut instance = anim.start();
assert_eq!(instance.state(), AnimationState::Idle);
instance.play();
assert_eq!(instance.state(), AnimationState::Running);
}
#[test]
fn test_animation_tick() {
let anim = Animation::new()
.from(0.0)
.to(100.0)
.duration(100.ms())
.easing(Easing::Linear)
.fill_mode(FillMode::Forwards);
let mut instance = anim.start();
instance.play();
assert_eq!(instance.get(), 0.0);
instance.tick(50.ms());
assert!((instance.get() - 50.0).abs() < 1.0);
instance.tick(50.ms());
assert!(instance.is_completed());
assert!((instance.get() - 100.0).abs() < 1.0);
}
#[test]
fn test_animation_completion() {
let anim = Animation::new()
.from(0.0)
.to(100.0)
.duration(100.ms())
.iterations(1);
let mut instance = anim.start();
instance.play();
instance.tick(150.ms());
assert!(instance.is_completed());
}
#[test]
fn test_animation_infinite() {
let anim = Animation::new()
.from(0.0)
.to(100.0)
.duration(100.ms())
.infinite();
let mut instance = anim.start();
instance.play();
instance.tick(500.ms());
assert!(instance.is_running());
}
#[test]
fn test_animation_alternate() {
let anim = Animation::new()
.from(0.0)
.to(100.0)
.duration(100.ms())
.direction(AnimationDirection::Alternate)
.iterations(2);
let mut instance = anim.start();
instance.play();
instance.tick(50.ms());
let v1 = instance.get();
assert!(v1 > 0.0 && v1 < 100.0);
instance.tick(100.ms());
let v2 = instance.get();
assert!(v2 > 0.0 && v2 < 100.0);
}
#[test]
fn test_animation_delay() {
let anim = Animation::new()
.from(0.0)
.to(100.0)
.duration(100.ms())
.delay(50.ms())
.easing(Easing::Linear);
let mut instance = anim.start();
instance.play();
instance.tick(25.ms());
assert_eq!(instance.get(), 0.0);
instance.tick(75.ms()); assert!((instance.get() - 50.0).abs() < 1.0);
}
#[test]
fn test_animation_pause_resume() {
let anim = Animation::new()
.from(0.0)
.to(100.0)
.duration(100.ms())
.easing(Easing::Linear);
let mut instance = anim.start();
instance.play();
instance.tick(50.ms());
let v1 = instance.get();
instance.pause();
instance.tick(50.ms()); assert_eq!(instance.get(), v1);
instance.play();
instance.tick(25.ms());
assert!(instance.get() > v1);
}
#[test]
fn test_animation_reset() {
let anim = Animation::new().from(0.0).to(100.0).duration(100.ms());
let mut instance = anim.start();
instance.play();
instance.tick(50.ms());
instance.reset();
assert_eq!(instance.state(), AnimationState::Idle);
assert_eq!(instance.get(), 0.0);
assert_eq!(instance.elapsed(), Duration::ZERO);
}
#[test]
fn test_preset_fade_in() {
let anim = Animation::fade_in(200.ms());
assert_eq!(anim.get_from(), 0.0);
assert_eq!(anim.get_to(), 1.0);
}
#[test]
fn test_preset_pulse() {
let anim = Animation::pulse(500.ms());
assert_eq!(anim.get_iterations(), 0); }
}