argui-animation 0.1.0

Animation timing and interpolation for Argui interfaces
Documentation
use crate::{Duration, MotionValue, PhysicsError};

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct DecayConfig {
    pub rate: f64,
    pub rest_speed: f64,
}

impl Default for DecayConfig {
    fn default() -> Self {
        Self {
            rate: 5.0,
            rest_speed: 0.01,
        }
    }
}

impl DecayConfig {
    pub fn validate(self) -> Result<Self, PhysicsError> {
        if !self.rate.is_finite() || self.rate <= 0.0 {
            Err(PhysicsError::InvalidDecay)
        } else if !self.rest_speed.is_finite() || self.rest_speed < 0.0 {
            Err(PhysicsError::InvalidRestThreshold)
        } else {
            Ok(self)
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Decay<T> {
    value: T,
    velocity: T,
    config: DecayConfig,
    active: bool,
}

impl<T: MotionValue> Decay<T> {
    pub fn new(value: T, velocity: T, config: DecayConfig) -> Result<Self, PhysicsError> {
        let config = config.validate()?;
        let active = velocity.magnitude() > config.rest_speed;
        Ok(Self {
            value,
            velocity,
            config,
            active,
        })
    }

    #[must_use]
    pub const fn value(&self) -> T {
        self.value
    }

    #[must_use]
    pub const fn velocity(&self) -> T {
        self.velocity
    }

    #[must_use]
    pub const fn is_active(&self) -> bool {
        self.active
    }

    pub fn kick(&mut self, velocity: T) {
        self.velocity = velocity;
        self.active = velocity.magnitude() > self.config.rest_speed;
    }

    pub fn advance(&mut self, elapsed: Duration) -> bool {
        if !self.active || elapsed == Duration::ZERO {
            return false;
        }
        let decay = (-self.config.rate * elapsed.as_secs_f64()).exp();
        let distance = self.velocity.scale((1.0 - decay) / self.config.rate);
        self.value = self.value.add(distance);
        self.velocity = self.velocity.scale(decay);
        if self.velocity.magnitude() <= self.config.rest_speed {
            self.velocity = T::zero();
            self.active = false;
        }
        true
    }
}