use crate::utils::Number;
use std::f32::consts::PI;
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
pub enum Easing {
Linear,
Out,
In,
QuadIn,
QuadOut,
QuadInOut,
CubicIn,
CubicOut,
CubicInOut,
QuartIn,
QuartOut,
QuartInOut,
QuintIn,
QuintOut,
QuintInOut,
SineIn,
SineOut,
SineInOut,
ExpoIn,
ExpoOut,
ExpoInOut,
CircIn,
CircOut,
CircInOut,
ElasticIn,
ElasticOut,
ElasticHalfOut,
ElasticQuarterOut,
ElasticInOut,
BackIn,
BackOut,
BackInOut,
BounceIn,
BounceOut,
BounceInOut,
}
impl PartialEq for Easing {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Easing::Out, Easing::QuadOut) => true,
(Easing::QuadOut, Easing::Out) => true,
(Easing::In, Easing::QuadIn) => true,
(Easing::QuadIn, Easing::In) => true,
(Easing::ElasticOut, Easing::ElasticHalfOut) => true,
(Easing::ElasticHalfOut, Easing::ElasticOut) => true,
(Easing::ElasticOut, Easing::ElasticQuarterOut) => true,
(Easing::ElasticQuarterOut, Easing::ElasticOut) => true,
(Easing::ElasticHalfOut, Easing::ElasticQuarterOut) => true,
(Easing::ElasticQuarterOut, Easing::ElasticHalfOut) => true,
(x, y) => *x as u8 == *y as u8,
}
}
}
impl Easing {
pub fn get_easing(id: u8) -> Option<Easing> {
match id {
0 => Some(Easing::Linear),
1 => Some(Easing::QuadOut),
2 => Some(Easing::QuadIn),
3 => Some(Easing::QuadIn),
4 => Some(Easing::QuadOut),
5 => Some(Easing::QuadInOut),
6 => Some(Easing::CubicIn),
7 => Some(Easing::CubicOut),
8 => Some(Easing::CubicInOut),
9 => Some(Easing::QuartIn),
10 => Some(Easing::QuartOut),
11 => Some(Easing::QuartInOut),
12 => Some(Easing::QuintIn),
13 => Some(Easing::QuintOut),
14 => Some(Easing::QuintInOut),
15 => Some(Easing::SineIn),
16 => Some(Easing::SineOut),
17 => Some(Easing::SineInOut),
18 => Some(Easing::ExpoIn),
19 => Some(Easing::ExpoOut),
20 => Some(Easing::ExpoInOut),
21 => Some(Easing::CircIn),
22 => Some(Easing::CircOut),
23 => Some(Easing::CircInOut),
24 => Some(Easing::ElasticIn),
25 => Some(Easing::ElasticOut),
26 => Some(Easing::ElasticOut),
27 => Some(Easing::ElasticOut),
28 => Some(Easing::ElasticInOut),
29 => Some(Easing::BackIn),
30 => Some(Easing::BackOut),
31 => Some(Easing::BackInOut),
32 => Some(Easing::BounceIn),
33 => Some(Easing::BounceOut),
34 => Some(Easing::BounceInOut),
_ => None,
}
}
pub fn id(self) -> u8 {
self as u8
}
pub fn ease(
self,
time: i32,
start_time: i32,
end_time: i32,
from: impl Into<Number>,
to: impl Into<Number>,
) -> Option<f32>
{
let from = from.into().as_f32();
let to = to.into().as_f32();
if time < start_time || time > end_time || to < from {
return None;
}
Some(
self.calculate((time - start_time) as f32 / (end_time - start_time) as f32)
* (to - from)
+ from,
)
}
fn calculate(self, x: f32) -> f32 {
if x < f32::EPSILON {
return 0.;
}
if 1. - x < f32::EPSILON {
return 1.;
}
match self {
Easing::Linear => x,
Easing::In | Easing::QuadIn => x * x,
Easing::Out | Easing::QuadOut => Easing::In.reverse(x),
Easing::QuadInOut => Easing::In.in_out(x),
Easing::CubicIn => x * x * x,
Easing::CubicOut => Easing::CubicIn.reverse(x),
Easing::CubicInOut => Easing::CubicIn.in_out(x),
Easing::QuartIn => x * x * x * x,
Easing::QuartOut => Easing::QuartIn.reverse(x),
Easing::QuartInOut => Easing::QuartIn.in_out(x),
Easing::QuintIn => x * x * x * x * x,
Easing::QuintOut => Easing::QuintIn.reverse(x),
Easing::QuintInOut => Easing::QuintIn.in_out(x),
Easing::SineIn => 1. - (x * PI / 2.).cos(),
Easing::SineOut => Easing::SineIn.reverse(x),
Easing::SineInOut => Easing::SineIn.in_out(x),
Easing::ExpoIn => 2.0_f32.powf(10. * (x - 1.)),
Easing::ExpoOut => Easing::ExpoIn.reverse(x),
Easing::ExpoInOut => Easing::ExpoIn.in_out(x),
Easing::CircIn => 1. - (1. - x * x).sqrt(),
Easing::CircOut => Easing::CircIn.reverse(x),
Easing::CircInOut => Easing::CircOut.in_out(x),
Easing::ElasticIn => Easing::ElasticOut.reverse(x),
Easing::ElasticOut | Easing::ElasticHalfOut | Easing::ElasticQuarterOut => {
2.0_f32.powf(-10. * x) * ((x - 0.075) * 2. * PI / 0.3).sin() + 1.
}
Easing::ElasticInOut => Easing::ElasticIn.in_out(x),
Easing::BackIn => x * x * ((1.70158 + 1.) * x - 1.70158),
Easing::BackOut => Easing::BackIn.reverse(x),
Easing::BackInOut => Easing::BackIn.in_out(x),
Easing::BounceIn => Easing::BounceOut.reverse(x),
Easing::BounceOut => {
if x < 1. / 2.75 {
7.5625 * x * x
} else if x < 2. / 2.75 {
7.5625 * (x - 1.5 / 2.75) * (x - 1.5 / 2.75) + 0.75
} else if x < 2.5 / 2.75 {
7.5625 * (x - 2.5 / 2.75) * (x - 2.5 / 2.75) + 0.9375
} else {
7.5625 * (x - 2.625 / 2.75) * (x - 2.625 / 2.75) + 0.984375
}
}
Easing::BounceInOut => Easing::BounceIn.in_out(x),
}
}
fn reverse(self, x: f32) -> f32 {
1. - self.calculate(1. - x)
}
fn in_out(self, x: f32) -> f32 {
0.5 * if x < 0.5 {
self.calculate(2. * x)
} else {
2. - self.calculate(2. - 2. * x)
}
}
}
#[cfg(test)]
mod tests {
use crate::Easing;
#[test]
fn get_easing() {
assert_eq!(Easing::get_easing(0), Some(Easing::Linear));
assert_eq!(Easing::get_easing(1), Some(Easing::QuadOut));
assert_eq!(Easing::get_easing(2), Some(Easing::QuadIn));
assert_eq!(Easing::get_easing(3), Some(Easing::QuadIn));
assert_eq!(Easing::get_easing(4), Some(Easing::QuadOut));
assert_eq!(Easing::get_easing(5), Some(Easing::QuadInOut));
assert_eq!(Easing::get_easing(6), Some(Easing::CubicIn));
assert_eq!(Easing::get_easing(7), Some(Easing::CubicOut));
assert_eq!(Easing::get_easing(8), Some(Easing::CubicInOut));
assert_eq!(Easing::get_easing(9), Some(Easing::QuartIn));
assert_eq!(Easing::get_easing(10), Some(Easing::QuartOut));
assert_eq!(Easing::get_easing(11), Some(Easing::QuartInOut));
assert_eq!(Easing::get_easing(12), Some(Easing::QuintIn));
assert_eq!(Easing::get_easing(13), Some(Easing::QuintOut));
assert_eq!(Easing::get_easing(14), Some(Easing::QuintInOut));
assert_eq!(Easing::get_easing(15), Some(Easing::SineIn));
assert_eq!(Easing::get_easing(16), Some(Easing::SineOut));
assert_eq!(Easing::get_easing(17), Some(Easing::SineInOut));
assert_eq!(Easing::get_easing(18), Some(Easing::ExpoIn));
assert_eq!(Easing::get_easing(19), Some(Easing::ExpoOut));
assert_eq!(Easing::get_easing(20), Some(Easing::ExpoInOut));
assert_eq!(Easing::get_easing(21), Some(Easing::CircIn));
assert_eq!(Easing::get_easing(22), Some(Easing::CircOut));
assert_eq!(Easing::get_easing(23), Some(Easing::CircInOut));
assert_eq!(Easing::get_easing(24), Some(Easing::ElasticIn));
assert_eq!(Easing::get_easing(25), Some(Easing::ElasticOut));
assert_eq!(Easing::get_easing(26), Some(Easing::ElasticOut));
assert_eq!(Easing::get_easing(27), Some(Easing::ElasticOut));
assert_eq!(Easing::get_easing(28), Some(Easing::ElasticInOut));
assert_eq!(Easing::get_easing(29), Some(Easing::BackIn));
assert_eq!(Easing::get_easing(30), Some(Easing::BackOut));
assert_eq!(Easing::get_easing(31), Some(Easing::BackInOut));
assert_eq!(Easing::get_easing(32), Some(Easing::BounceIn));
assert_eq!(Easing::get_easing(33), Some(Easing::BounceOut));
assert_eq!(Easing::get_easing(34), Some(Easing::BounceInOut));
}
#[test]
fn easing_eq() {
assert_eq!(Easing::Out, Easing::QuadOut);
assert_eq!(Easing::QuadOut, Easing::Out);
assert_eq!(Easing::In, Easing::QuadIn);
assert_eq!(Easing::QuadIn, Easing::In);
assert_eq!(Easing::ElasticOut, Easing::ElasticHalfOut);
assert_eq!(Easing::ElasticHalfOut, Easing::ElasticOut);
assert_eq!(Easing::ElasticOut, Easing::ElasticQuarterOut);
assert_eq!(Easing::ElasticQuarterOut, Easing::ElasticOut);
assert_eq!(Easing::ElasticHalfOut, Easing::ElasticQuarterOut);
assert_eq!(Easing::ElasticQuarterOut, Easing::ElasticHalfOut);
}
#[test]
fn ease_functions() {
assert_eq!(Easing::CubicOut.ease(1, 0, 2, 0., 200.), Some(175.));
assert_eq!(Easing::QuartOut.ease(1, 0, 2, 0., 200.), Some(187.5));
assert_eq!(Easing::QuintOut.ease(1, 0, 2, 0., 200.), Some(193.75));
assert_eq!(
Easing::SineOut.ease(1, 0, 2, 0., 1.),
Some(2_f32.sqrt() / 2.)
);
assert_eq!(Easing::ExpoOut.ease(1, 0, 2, 0., 200.), Some(193.75));
assert_eq!(Easing::CircOut.ease(1, 0, 2, 0., 1.), Some(0.75_f32.sqrt()));
assert_eq!(Easing::ElasticIn.ease(1, 0, 2, 0., 200.), Some(-3.125));
assert_eq!(Easing::BackOut.ease(1, 0, 2, 0., 200.), Some(217.5395));
assert_eq!(Easing::BounceIn.ease(1, 0, 2, 0., 200.), Some(46.875));
}
#[test]
fn ease_functions_inout() {
assert_eq!(Easing::QuadInOut.ease(1, 0, 4, 0., 40.), Some(5.));
assert_eq!(Easing::QuadInOut.ease(1, 0, 2, 0., 2.), Some(1.));
assert_eq!(Easing::QuartInOut.ease(1, 0, 2, 0., 2.), Some(1.));
assert_eq!(Easing::CubicInOut.ease(1, 0, 2, 0., 2.), Some(1.));
assert_eq!(Easing::QuintInOut.ease(1, 0, 2, 0., 2.), Some(1.));
assert_eq!(Easing::SineInOut.ease(1, 0, 2, 0., 2.), Some(1.));
assert_eq!(Easing::ExpoInOut.ease(1, 0, 2, 0., 2.), Some(1.));
assert_eq!(Easing::CircInOut.ease(1, 0, 2, 0., 2.), Some(1.));
assert_eq!(Easing::ElasticInOut.ease(1, 0, 2, 0., 2.), Some(1.));
assert_eq!(Easing::BackInOut.ease(1, 0, 2, 0., 2.), Some(1.));
assert_eq!(Easing::BounceInOut.ease(1, 0, 2, 0., 2.), Some(1.));
}
#[test]
fn ease_out_of_bounds() {
assert_eq!(Easing::Linear.ease(5, 0, 4, 0., 10.), None);
assert_eq!(Easing::Linear.ease(2, 0, 4, 10., 5.), None);
}
}