fui-rs 0.2.6

Retained Rust UI for native desktop and WebAssembly applications
Documentation
pub const fn rgba(red: u32, green: u32, blue: u32, alpha: u32) -> u32 {
    ((red & 0xff) << 24) | ((green & 0xff) << 16) | ((blue & 0xff) << 8) | (alpha & 0xff)
}

pub const fn rgb(red: u32, green: u32, blue: u32) -> u32 {
    rgba(red, green, blue, 0xff)
}

pub const fn with_alpha(color: u32, alpha: u32) -> u32 {
    (color & 0xffff_ff00) | (alpha & 0xff)
}

pub(crate) fn color_red(color: u32) -> u32 {
    (color >> 24) & 0xff
}

pub(crate) fn color_green(color: u32) -> u32 {
    (color >> 16) & 0xff
}

pub(crate) fn color_blue(color: u32) -> u32 {
    (color >> 8) & 0xff
}

pub(crate) fn color_alpha(color: u32) -> u32 {
    color & 0xff
}

fn clamp_unit(value: f32) -> f32 {
    value.clamp(0.0, 1.0)
}

fn hue_to_rgb(p: f32, q: f32, mut t: f32) -> f32 {
    if t < 0.0 {
        t += 1.0;
    }
    if t > 1.0 {
        t -= 1.0;
    }
    if t < 1.0 / 6.0 {
        return p + (q - p) * 6.0 * t;
    }
    if t < 0.5 {
        return q;
    }
    if t < 2.0 / 3.0 {
        return p + (q - p) * ((2.0 / 3.0) - t) * 6.0;
    }
    p
}

pub fn hsl_to_color(hue: f32, saturation: f32, lightness: f32) -> u32 {
    let normalized_hue = hue % 360.0;
    let hue = if normalized_hue < 0.0 {
        normalized_hue + 360.0
    } else {
        normalized_hue
    };
    let saturation = clamp_unit(saturation);
    let lightness = clamp_unit(lightness);
    if saturation <= 0.0 {
        let channel = (lightness * 255.0) as u32;
        return rgb(channel, channel, channel);
    }

    let hue_fraction = hue / 360.0;
    let q = if lightness < 0.5 {
        lightness * (1.0 + saturation)
    } else {
        lightness + saturation - (lightness * saturation)
    };
    let p = 2.0 * lightness - q;
    rgba(
        (clamp_unit(hue_to_rgb(p, q, hue_fraction + 1.0 / 3.0)) * 255.0) as u32,
        (clamp_unit(hue_to_rgb(p, q, hue_fraction)) * 255.0) as u32,
        (clamp_unit(hue_to_rgb(p, q, hue_fraction - 1.0 / 3.0)) * 255.0) as u32,
        0xff,
    )
}

fn mix_channel(from: u32, to: u32, amount: f32) -> u32 {
    let weight = clamp_unit(amount);
    (from as f32 + ((to as f32 - from as f32) * weight)).round() as u32
}

pub fn mix_color(from: u32, to: u32, amount: f32) -> u32 {
    rgba(
        mix_channel(color_red(from), color_red(to), amount),
        mix_channel(color_green(from), color_green(to), amount),
        mix_channel(color_blue(from), color_blue(to), amount),
        mix_channel(color_alpha(from), color_alpha(to), amount),
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn color_helpers_match_effindom_rgba_packing() {
        assert_eq!(rgb(0x12, 0x34, 0x56), 0x123456ff);
        assert_eq!(rgba(0x12, 0x34, 0x56, 0x78), 0x12345678);
        assert_eq!(with_alpha(0x123456ff, 0x78), 0x12345678);
    }

    #[test]
    fn color_helpers_mask_channels_and_interpolate() {
        assert_eq!(rgba(0x112, 0x134, 0x156, 0x178), 0x12345678);
        assert_eq!(
            mix_color(rgb(0, 0, 0), rgb(255, 255, 255), 0.5),
            rgba(128, 128, 128, 255)
        );
        assert_eq!(hsl_to_color(0.0, 1.0, 0.5), rgb(255, 0, 0));
    }
}