hinoirisetr 1.6.1

A daemon to dim the screen at night
Documentation
use crate::{trace, utils::eq_ignore_case};

#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum Interpolation {
    Linear,
    CubicEaseInOut,
    Cosine,
    Exponential,
}

impl std::str::FromStr for Interpolation {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            s if eq_ignore_case(s, "linear") => Ok(Self::Linear),
            s if eq_ignore_case(s, "cubic") | eq_ignore_case(s, "cubiceaseinout") => {
                Ok(Self::CubicEaseInOut)
            },
            s if eq_ignore_case(s, "cosine") => Ok(Self::Cosine),
            s if eq_ignore_case(s, "exponential") => Ok(Self::Exponential),
            _ => Err(format!(
                "invalid value for interpolation: {s}. Valid values are: linear, cubic, cubiceaseinout, cosine, exponential"
            )),
        }
    }
}

/// Linearly interpolate between start and end by factor [0.0, 1.0]
#[must_use]
pub fn interpolate_linear(start: u16, end: u16, factor: f64) -> u16 {
    trace!("interpolate_linear({start}, {end}, {factor})");
    if end < start {
        f64::from(start - end)
            .mul_add(1.0 - factor, f64::from(end))
            .round() as u16
    } else {
        f64::from(end - start)
            .mul_add(factor, f64::from(start))
            .round() as u16
    }
}

/// Cubic interpolation
fn interpolate_cubic(start: u16, end: u16, factor: f64) -> u16 {
    trace!("interpolate_cubic({start}, {end}, {factor})");
    let factor = factor.clamp(0.0, 1.0);
    // Convert to f64 for arithmetic
    let start_f = f64::from(start);
    let end_f = f64::from(end);
    // Cubic ease-in-out: 3t^2 - 2t^3
    let t = factor;
    let smooth_t = (3.0 * t).mul_add(t, -(2.0 * t * t * t));
    // Interpolate
    let result = smooth_t.mul_add(end_f - start_f, start_f);
    // Round and clamp to u16 bounds
    result.round().max(0.0).min(f64::from(u16::MAX)) as u16
}

/// Cosine interpolation
fn interpolate_cosine(start: u16, end: u16, factor: f64) -> u16 {
    trace!("interpolate_cosine({start}, {end}, {factor})");
    let t = (1.0 - (factor.clamp(0.0, 1.0) * std::f64::consts::PI).cos()) / 2.0;
    interpolate_linear(start, end, t)
}

fn interpolate_exponential(start: u16, end: u16, factor: f64) -> u16 {
    trace!("interpolate_exponential({start}, {end}, {factor})");
    let t = factor.clamp(0.0, 1.0);
    let t = if t < 0.5 {
        0.5 * (2.0 * t).powi(3)
    } else {
        0.5f64.mul_add(1.0 - (2.0 * (1.0 - t)).powi(3), 0.5)
    };
    interpolate_linear(start, end, t)
}

/// Interpolation meta-function
pub fn interpolate_value(start: u16, end: u16, factor: f64, interpolation: Interpolation) -> u16 {
    trace!("interpolate_value({start}, {end}, {factor}, {interpolation:?})");
    match interpolation {
        Interpolation::Linear => interpolate_linear(start, end, factor),
        Interpolation::CubicEaseInOut => interpolate_cubic(start, end, factor),
        Interpolation::Cosine => interpolate_cosine(start, end, factor),
        Interpolation::Exponential => interpolate_exponential(start, end, factor),
    }
}