use dyn_clone::DynClone;
pub use functions::*;
pub trait Function: DynClone {
fn ease(&self, normalized_time: f64) -> f64;
}
impl<F: Function + Clone> Function for Box<F> {
#[inline]
fn ease(&self, normalized_time: f64) -> f64 {
(**self).ease(normalized_time)
}
}
#[doc(hidden)]
#[allow(missing_docs)]
pub trait FunctionClone: Function + Clone {}
impl<F: Function + Clone> FunctionClone for F {}
#[derive(Debug, Clone, Copy)]
pub enum EasingMode {
In,
Out,
InOut,
}
impl Default for EasingMode {
fn default() -> Self {
EasingMode::In
}
}
impl EasingMode {
#[inline]
fn apply<F: Fn(f64) -> f64>(&self, time: f64, f: &F) -> f64 {
let time = crate::utils::check_time(time);
match self {
EasingMode::In => f(time),
EasingMode::Out => 1.0 - f(1.0 - time),
EasingMode::InOut => {
if time < 0.5 {
f(time * 2.0) / 2.0
} else {
1.0 - f(2.0 - time * 2.0) / 2.0
}
}
}
}
}
#[derive(Debug, Clone)]
pub struct Easing<F: Fn(f64) -> f64> {
mode: EasingMode,
f: F,
}
impl<F: Fn(f64) -> f64> Easing<F> {
#[inline]
pub fn mode(mut self, mode: EasingMode) -> Self {
self.mode = mode;
self
}
}
impl<F: Fn(f64) -> f64 + Clone> Function for Easing<F> {
#[inline]
fn ease(&self, normalized_time: f64) -> f64 {
self.mode.apply(normalized_time, &self.f)
}
}
impl<F: Fn(f64) -> f64 + Clone + 'static> From<F> for Easing<F> {
#[inline]
fn from(f: F) -> Self {
functions::custom(f)
}
}
mod functions {
use super::Easing;
use std::f64::consts::PI;
#[inline]
pub fn linear() -> Easing<impl Fn(f64) -> f64 + Clone> {
custom(|t| t)
}
#[inline]
pub fn sine_ease() -> Easing<impl Fn(f64) -> f64 + Clone> {
custom(move |t| 1.0 - ((t * PI) / 2.0).cos())
}
#[inline]
pub fn pow_ease(power: f32) -> Easing<impl Fn(f64) -> f64 + Clone> {
let power = power as f64;
custom(move |t| t.powf(power))
}
#[inline]
pub fn quad_ease() -> Easing<impl Fn(f64) -> f64 + Clone> {
pow_ease(2.0)
}
#[inline]
pub fn cubic_ease() -> Easing<impl Fn(f64) -> f64 + Clone> {
pow_ease(3.0)
}
#[inline]
pub fn quart_ease() -> Easing<impl Fn(f64) -> f64 + Clone> {
pow_ease(4.0)
}
#[inline]
pub fn qunit_ease() -> Easing<impl Fn(f64) -> f64 + Clone> {
pow_ease(5.0)
}
#[inline]
pub fn expo_ease() -> Easing<impl Fn(f64) -> f64 + Clone> {
custom(|t| {
if t == 0.0 {
0.0
} else {
(2.0_f64).powf(10.0 * t - 10.0)
}
})
}
#[inline]
pub fn circle_ease() -> Easing<impl Fn(f64) -> f64 + Clone> {
custom(|t| 1.0 - (1.0 - t.powi(2)).sqrt())
}
#[inline]
pub fn back_ease(amplitude: f64) -> Easing<impl Fn(f64) -> f64 + Clone> {
custom(move |t| t.powi(3) - t * amplitude * (t * PI).sin())
}
#[inline]
pub fn elastic_ease() -> Easing<impl Fn(f64) -> f64 + Clone> {
const C4: f64 = (2.0 * PI) / 3.0;
custom(|t| {
if t == 0.0 {
0.0
} else if (1.0 - t).abs() < f64::EPSILON {
1.0
} else {
-(2.0_f64.powf(10.0 * t - 10.0) * ((t * 10.0 - 10.75) * C4).sin())
}
})
}
#[inline]
pub fn bounce_ease() -> Easing<impl Fn(f64) -> f64 + Clone> {
const N1: f64 = 7.5625;
const D1: f64 = 2.75;
custom(|t| {
let v = if t < 1.0 / D1 {
N1 * t * t
} else if t < 2.0 / D1 {
let t = t - 1.5 / D1;
N1 * t * t + 0.75
} else if t < 2.5 / D1 {
let t = t - 2.25 / D1;
N1 * t * t + 0.9375
} else {
let t = t - 2.625 / D1;
N1 * t * t + 0.984375
};
1.0 - v
})
}
#[inline]
pub fn custom<F: Fn(f64) -> f64 + Clone + 'static>(f: F) -> Easing<F> {
Easing {
mode: Default::default(),
f,
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_linear() {
let modes = [EasingMode::In, EasingMode::Out, EasingMode::InOut];
for mode in modes.iter() {
let f = linear().mode(*mode);
let v = f.ease(0.0);
assert_eq!(v, 0.0);
let v = f.ease(0.5);
assert_eq!(v, 0.5);
let v = f.ease(0.75);
assert_eq!(v, 0.75);
let v = f.ease(1.0);
assert_eq!(v, 1.0);
}
}
#[test]
fn test_quad_in() {
let f = quad_ease().mode(EasingMode::In);
let v = f.ease(0.0);
assert_eq!(v, 0.0);
let v = f.ease(0.5);
assert_eq!(v, 0.25);
let v = f.ease(0.75);
assert_eq!(v, 0.5625);
let v = f.ease(1.0);
assert_eq!(v, 1.0);
}
#[test]
fn test_quad_out() {
let f = quad_ease().mode(EasingMode::Out);
let v = f.ease(0.0);
assert_eq!(v, 0.0);
let v = f.ease(0.5);
assert_eq!(v, 0.75);
let v = f.ease(0.75);
assert_eq!(v, 0.9375);
let v = f.ease(1.0);
assert_eq!(v, 1.0);
}
#[test]
fn test_quad_in_out() {
let f = quad_ease().mode(EasingMode::InOut);
let v = f.ease(0.0);
assert_eq!(v, 0.0);
let v = f.ease(0.5);
assert_eq!(v, 0.5);
let v = f.ease(0.75);
assert_eq!(v, 0.875);
let v = f.ease(1.0);
assert_eq!(v, 1.0);
}
}