use std::collections::HashMap;
use std::time::{Duration, Instant};
use super::{AnimationState, EasingFn};
use crate::style::animation::easing;
#[derive(Clone, Debug, Default)]
pub struct CssKeyframe {
pub percent: u8,
pub properties: HashMap<String, f32>,
}
impl CssKeyframe {
pub fn new(percent: u8) -> Self {
Self {
percent: percent.min(100),
properties: HashMap::new(),
}
}
pub fn set(mut self, property: &str, value: f32) -> Self {
self.properties.insert(property.to_string(), value);
self
}
pub fn get(&self, property: &str) -> Option<f32> {
self.properties.get(property).copied()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum AnimationDirection {
#[default]
Normal,
Reverse,
Alternate,
AlternateReverse,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum AnimationFillMode {
#[default]
None,
Forwards,
Backwards,
Both,
}
#[derive(Clone)]
pub struct KeyframeAnimation {
name: String,
keyframes: Vec<CssKeyframe>,
pub duration: Duration,
pub delay: Duration,
easing: EasingFn,
start_time: Option<Instant>,
state: AnimationState,
pub iterations: u32,
current_iteration: u32,
pub direction: AnimationDirection,
pub fill_mode: AnimationFillMode,
}
impl KeyframeAnimation {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
keyframes: Vec::new(),
duration: Duration::from_millis(300),
delay: Duration::ZERO,
easing: easing::linear,
start_time: None,
state: AnimationState::Pending,
iterations: 1,
current_iteration: 0,
direction: AnimationDirection::Normal,
fill_mode: AnimationFillMode::None,
}
}
pub fn keyframe(mut self, percent: u8, f: impl FnOnce(CssKeyframe) -> CssKeyframe) -> Self {
let kf = f(CssKeyframe::new(percent));
self.keyframes.push(kf);
self.keyframes.sort_by_key(|k| k.percent);
self
}
pub fn duration(mut self, duration: Duration) -> Self {
self.duration = duration;
self
}
pub fn delay(mut self, delay: Duration) -> Self {
self.delay = delay;
self
}
pub fn easing(mut self, easing: EasingFn) -> Self {
self.easing = easing;
self
}
pub fn iterations(mut self, n: u32) -> Self {
self.iterations = n;
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 fill_mode(mut self, fill_mode: AnimationFillMode) -> Self {
self.fill_mode = fill_mode;
self
}
pub fn name(&self) -> &str {
&self.name
}
pub fn start(&mut self) {
if crate::utils::prefers_reduced_motion() {
self.state = AnimationState::Completed;
self.current_iteration = self.iterations.max(1);
return;
}
self.start_time = Some(Instant::now());
self.state = AnimationState::Running;
self.current_iteration = 0;
}
pub fn pause(&mut self) {
if self.state == AnimationState::Running {
self.state = AnimationState::Paused;
}
}
pub fn resume(&mut self) {
if self.state == AnimationState::Paused {
self.state = AnimationState::Running;
}
}
pub fn reset(&mut self) {
self.start_time = None;
self.state = AnimationState::Pending;
self.current_iteration = 0;
}
pub fn is_running(&self) -> bool {
self.state == AnimationState::Running
}
pub fn is_completed(&self) -> bool {
self.state == AnimationState::Completed
}
pub fn state(&self) -> AnimationState {
self.state
}
pub fn progress(&self) -> f32 {
let Some(start) = self.start_time else {
return 0.0;
};
if self.state != AnimationState::Running {
return if self.is_completed() { 1.0 } else { 0.0 };
}
let elapsed = start.elapsed();
if elapsed < self.delay {
return 0.0;
}
let elapsed_after_delay = elapsed - self.delay;
(elapsed_after_delay.as_secs_f32() / self.duration.as_secs_f32()).clamp(0.0, 1.0)
}
pub fn get(&mut self, property: &str) -> f32 {
self.update();
if self.keyframes.is_empty() {
return 0.0;
}
let progress = self.progress();
if self.state == AnimationState::Pending
&& matches!(
self.fill_mode,
AnimationFillMode::Backwards | AnimationFillMode::Both
)
{
return self
.keyframes
.first()
.and_then(|kf| kf.get(property))
.unwrap_or(0.0);
}
if self.state == AnimationState::Completed
&& matches!(
self.fill_mode,
AnimationFillMode::Forwards | AnimationFillMode::Both
)
{
return self
.keyframes
.last()
.and_then(|kf| kf.get(property))
.unwrap_or(0.0);
}
let is_reverse = match self.direction {
AnimationDirection::Normal => false,
AnimationDirection::Reverse => true,
AnimationDirection::Alternate => self.current_iteration % 2 == 1,
AnimationDirection::AlternateReverse => self.current_iteration.is_multiple_of(2),
};
let t = (self.easing)(progress);
let percent = if is_reverse { 1.0 - t } else { t } * 100.0;
let mut prev_kf = &self.keyframes[0];
let mut next_kf = &self.keyframes[self.keyframes.len() - 1];
for kf in &self.keyframes {
if (kf.percent as f32) <= percent {
prev_kf = kf;
}
if (kf.percent as f32) >= percent {
next_kf = kf;
break;
}
}
let prev_val = prev_kf.get(property).unwrap_or(0.0);
let next_val = next_kf.get(property).unwrap_or(prev_val);
if prev_kf.percent == next_kf.percent {
return prev_val;
}
let local_t =
(percent - prev_kf.percent as f32) / (next_kf.percent as f32 - prev_kf.percent as f32);
prev_val + (next_val - prev_val) * local_t
}
fn update(&mut self) {
if self.state != AnimationState::Running {
return;
}
let Some(start) = self.start_time else {
return;
};
let elapsed = start.elapsed();
if elapsed < self.delay {
return;
}
let elapsed_after_delay = elapsed - self.delay;
let iteration_progress = elapsed_after_delay.as_secs_f32() / self.duration.as_secs_f32();
if iteration_progress >= 1.0 {
self.current_iteration += 1;
if self.iterations > 0 && self.current_iteration >= self.iterations {
self.state = AnimationState::Completed;
} else {
self.start_time = Some(Instant::now());
}
}
}
}