faststep 0.1.0

UIKit-inspired embedded UI framework built on embedded-graphics
Documentation
/// Easing curve used by [`Animation`].
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Curve {
    /// Linear interpolation.
    Linear,
    /// Ease-out interpolation.
    EaseOut,
    /// Smooth ease-in-out interpolation.
    EaseInOut,
}

/// Small time-based animation helper.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Animation {
    duration_ms: u32,
    elapsed_ms: u32,
    curve: Curve,
}

impl Animation {
    /// Creates a new animation with a duration and easing curve.
    pub const fn new(duration_ms: u32, curve: Curve) -> Self {
        Self {
            duration_ms,
            elapsed_ms: 0,
            curve,
        }
    }

    /// Returns `true` while the animation is still progressing.
    pub const fn is_running(&self) -> bool {
        self.elapsed_ms < self.duration_ms
    }

    /// Returns `true` once the animation has completed.
    pub const fn is_finished(&self) -> bool {
        !self.is_running()
    }

    /// Advances the animation by `dt_ms`.
    pub fn advance(&mut self, dt_ms: u32) -> bool {
        self.elapsed_ms = self.elapsed_ms.saturating_add(dt_ms).min(self.duration_ms);
        self.is_running()
    }

    /// Returns eased progress in the `0..=1000` permille range.
    pub fn progress_permille(&self) -> u16 {
        if self.duration_ms == 0 {
            return 1000;
        }

        let linear = (self.elapsed_ms.saturating_mul(1000) / self.duration_ms).min(1000) as u16;
        ease(self.curve, linear)
    }
}

fn ease(curve: Curve, t: u16) -> u16 {
    let t = i64::from(t.min(1000));
    match curve {
        Curve::Linear => t as u16,
        Curve::EaseOut => {
            let inv = 1000 - t;
            let inv2 = (inv * inv) / 1000;
            let inv3 = (inv2 * inv) / 1000;
            (1000 - inv3) as u16
        }
        Curve::EaseInOut => smoothstep(t) as u16,
    }
}

fn smoothstep(t: i64) -> i64 {
    let t2 = (t * t) / 1000;
    let t3 = (t2 * t) / 1000;
    ((3 * t2) - (2 * t3)).clamp(0, 1000)
}

/// Linearly interpolates between two `i32` values using permille progress.
pub fn lerp_i32(from: i32, to: i32, progress_permille: u16) -> i32 {
    let delta = i64::from(to - from);
    from + ((delta * i64::from(progress_permille)) / 1000) as i32
}

/// Linearly interpolates between two `u8` values using permille progress.
pub fn lerp_u8(from: u8, to: u8, progress_permille: u16) -> u8 {
    let value = lerp_i32(i32::from(from), i32::from(to), progress_permille);
    value.clamp(0, 255) as u8
}