aisling 0.2.1

Embeddable terminal text effects and loaders for Rust TUI applications.
Documentation
use std::f32::consts::PI;

/// Easing functions for animation timing.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum Easing {
    #[default]
    Linear,
    InSine,
    OutSine,
    InOutSine,
    InQuad,
    OutQuad,
    InOutQuad,
    InCubic,
    OutCubic,
    InOutCubic,
    InQuart,
    OutQuart,
    InOutQuart,
    InQuint,
    OutQuint,
    InOutQuint,
    InExpo,
    OutExpo,
    InOutExpo,
    InCirc,
    OutCirc,
    InOutCirc,
    InBack,
    OutBack,
    InOutBack,
    InElastic,
    OutElastic,
    InOutElastic,
    InBounce,
    OutBounce,
    InOutBounce,
    CubicBezier(f32, f32, f32, f32),
}

impl Easing {
    pub fn apply(self, progress: f32) -> f32 {
        let x = progress.clamp(0.0, 1.0);
        match self {
            Easing::Linear => x,
            Easing::InSine => 1.0 - ((x * PI) / 2.0).cos(),
            Easing::OutSine => ((x * PI) / 2.0).sin(),
            Easing::InOutSine => -((PI * x).cos() - 1.0) / 2.0,
            Easing::InQuad => x.powi(2),
            Easing::OutQuad => 1.0 - (1.0 - x).powi(2),
            Easing::InOutQuad => {
                if x < 0.5 {
                    2.0 * x.powi(2)
                } else {
                    1.0 - (-2.0 * x + 2.0).powi(2) / 2.0
                }
            }
            Easing::InCubic => x.powi(3),
            Easing::OutCubic => 1.0 - (1.0 - x).powi(3),
            Easing::InOutCubic => {
                if x < 0.5 {
                    4.0 * x.powi(3)
                } else {
                    1.0 - (-2.0 * x + 2.0).powi(3) / 2.0
                }
            }
            Easing::InQuart => x.powi(4),
            Easing::OutQuart => 1.0 - (1.0 - x).powi(4),
            Easing::InOutQuart => {
                if x < 0.5 {
                    8.0 * x.powi(4)
                } else {
                    1.0 - (-2.0 * x + 2.0).powi(4) / 2.0
                }
            }
            Easing::InQuint => x.powi(5),
            Easing::OutQuint => 1.0 - (1.0 - x).powi(5),
            Easing::InOutQuint => {
                if x < 0.5 {
                    16.0 * x.powi(5)
                } else {
                    1.0 - (-2.0 * x + 2.0).powi(5) / 2.0
                }
            }
            Easing::InExpo => {
                if x == 0.0 {
                    0.0
                } else {
                    2.0_f32.powf(10.0 * x - 10.0)
                }
            }
            Easing::OutExpo => {
                if x == 1.0 {
                    1.0
                } else {
                    1.0 - 2.0_f32.powf(-10.0 * x)
                }
            }
            Easing::InOutExpo => {
                if x == 0.0 {
                    0.0
                } else if x == 1.0 {
                    1.0
                } else if x < 0.5 {
                    2.0_f32.powf(20.0 * x - 10.0) / 2.0
                } else {
                    (2.0 - 2.0_f32.powf(-20.0 * x + 10.0)) / 2.0
                }
            }
            Easing::InCirc => 1.0 - (1.0 - x.powi(2)).sqrt(),
            Easing::OutCirc => (1.0 - (x - 1.0).powi(2)).sqrt(),
            Easing::InOutCirc => {
                if x < 0.5 {
                    (1.0 - (1.0 - (2.0 * x).powi(2)).sqrt()) / 2.0
                } else {
                    ((1.0 - (-2.0 * x + 2.0).powi(2)).sqrt() + 1.0) / 2.0
                }
            }
            Easing::InBack => {
                let c1 = 1.70158;
                let c3 = c1 + 1.0;
                c3 * x.powi(3) - c1 * x.powi(2)
            }
            Easing::OutBack => {
                let c1 = 1.70158;
                let c3 = c1 + 1.0;
                1.0 + c3 * (x - 1.0).powi(3) + c1 * (x - 1.0).powi(2)
            }
            Easing::InOutBack => {
                let c1 = 1.70158;
                let c2 = c1 * 1.525;
                if x < 0.5 {
                    ((2.0 * x).powi(2) * ((c2 + 1.0) * 2.0 * x - c2)) / 2.0
                } else {
                    ((2.0 * x - 2.0).powi(2) * ((c2 + 1.0) * (x * 2.0 - 2.0) + c2) + 2.0) / 2.0
                }
            }
            Easing::InElastic => {
                if x == 0.0 {
                    0.0
                } else if x == 1.0 {
                    1.0
                } else {
                    let c4 = (2.0 * PI) / 3.0;
                    -2.0_f32.powf(10.0 * x - 10.0) * ((x * 10.0 - 10.75) * c4).sin()
                }
            }
            Easing::OutElastic => {
                if x == 0.0 {
                    0.0
                } else if x == 1.0 {
                    1.0
                } else {
                    let c4 = (2.0 * PI) / 3.0;
                    2.0_f32.powf(-10.0 * x) * ((x * 10.0 - 0.75) * c4).sin() + 1.0
                }
            }
            Easing::InOutElastic => {
                if x == 0.0 {
                    0.0
                } else if x == 1.0 {
                    1.0
                } else {
                    let c5 = (2.0 * PI) / 4.5;
                    if x < 0.5 {
                        -(2.0_f32.powf(20.0 * x - 10.0) * ((20.0 * x - 11.125) * c5).sin()) / 2.0
                    } else {
                        (2.0_f32.powf(-20.0 * x + 10.0) * ((20.0 * x - 11.125) * c5).sin()) / 2.0
                            + 1.0
                    }
                }
            }
            Easing::InBounce => 1.0 - Easing::OutBounce.apply(1.0 - x),
            Easing::OutBounce => out_bounce(x),
            Easing::InOutBounce => {
                if x < 0.5 {
                    (1.0 - out_bounce(1.0 - 2.0 * x)) / 2.0
                } else {
                    (1.0 + out_bounce(2.0 * x - 1.0)) / 2.0
                }
            }
            Easing::CubicBezier(x1, y1, x2, y2) => cubic_bezier(x1, y1, x2, y2, x),
        }
        .clamp(0.0, 1.0)
    }
}

fn out_bounce(x: f32) -> f32 {
    let n1 = 7.5625;
    let d1 = 2.75;
    if x < 1.0 / d1 {
        n1 * x * x
    } else if x < 2.0 / d1 {
        let x = x - 1.5 / d1;
        n1 * x * x + 0.75
    } else if x < 2.5 / d1 {
        let x = x - 2.25 / d1;
        n1 * x * x + 0.9375
    } else {
        let x = x - 2.625 / d1;
        n1 * x * x + 0.984375
    }
}

fn cubic_bezier(_x1: f32, y1: f32, _x2: f32, y2: f32, t: f32) -> f32 {
    // Frame sequencing needs a monotonic 0..1 output; sampling the y curve is
    // stable and deterministic.
    let u = 1.0 - t;
    3.0 * u * u * t * y1 + 3.0 * u * t * t * y2 + t * t * t
}