use gpui::{Hsla, Pixels, Rgba};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum AnimValue {
Number(f32),
Color(Hsla),
}
impl AnimValue {
pub fn lerp(self, other: Self, t: f32) -> Self {
match (self, other) {
(AnimValue::Number(a), AnimValue::Number(b)) => AnimValue::Number(a + (b - a) * t),
(AnimValue::Color(a), AnimValue::Color(b)) => AnimValue::Color(lerp_hsla(a, b, t)),
(_, b) => b,
}
}
pub fn number(self) -> f32 {
match self {
AnimValue::Number(v) => v,
AnimValue::Color(_) => 0.0,
}
}
pub fn color(self) -> Option<Hsla> {
match self {
AnimValue::Color(c) => Some(c),
AnimValue::Number(_) => None,
}
}
pub fn is_color(self) -> bool {
matches!(self, AnimValue::Color(_))
}
}
impl From<f32> for AnimValue {
fn from(v: f32) -> Self {
AnimValue::Number(v)
}
}
impl From<f64> for AnimValue {
fn from(v: f64) -> Self {
AnimValue::Number(v as f32)
}
}
impl From<i32> for AnimValue {
fn from(v: i32) -> Self {
AnimValue::Number(v as f32)
}
}
impl From<Pixels> for AnimValue {
fn from(v: Pixels) -> Self {
AnimValue::Number(f32::from(v))
}
}
impl From<Hsla> for AnimValue {
fn from(v: Hsla) -> Self {
AnimValue::Color(v)
}
}
impl From<Rgba> for AnimValue {
fn from(v: Rgba) -> Self {
AnimValue::Color(v.into())
}
}
fn lerp_hsla(a: Hsla, b: Hsla, t: f32) -> Hsla {
let (ah, bh) = match (a.a <= 0.0, b.a <= 0.0) {
(true, false) => (b.h, b.h),
(false, true) => (a.h, a.h),
_ => (a.h, b.h),
};
let mut delta = bh - ah;
if delta > 0.5 {
delta -= 1.0;
} else if delta < -0.5 {
delta += 1.0;
}
let h = (ah + delta * t).rem_euclid(1.0);
Hsla {
h,
s: a.s + (b.s - a.s) * t,
l: a.l + (b.l - a.l) * t,
a: a.a + (b.a - a.a) * t,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
Hsla { h, s, l, a }
}
#[test]
fn numbers_interpolate_linearly() {
let v = AnimValue::from(10.0).lerp(AnimValue::from(20.0), 0.25);
assert_eq!(v.number(), 12.5);
}
#[test]
fn hue_takes_the_short_way_round() {
let a = hsla(0.9, 1.0, 0.5, 1.0);
let b = hsla(0.1, 1.0, 0.5, 1.0);
let mid = AnimValue::from(a).lerp(AnimValue::from(b), 0.5).color();
assert!((mid.unwrap().h - 0.0).abs() < 1e-5, "{mid:?}");
}
#[test]
fn transparent_endpoints_borrow_the_other_hue() {
let clear = hsla(0.0, 0.0, 0.0, 0.0);
let blue = hsla(0.6, 1.0, 0.5, 1.0);
let mid = AnimValue::from(clear)
.lerp(AnimValue::from(blue), 0.5)
.color()
.unwrap();
assert!((mid.h - 0.6).abs() < 1e-5, "{mid:?}");
assert!((mid.a - 0.5).abs() < 1e-5);
}
#[test]
fn mismatched_kinds_snap_to_the_destination() {
let v = AnimValue::from(1.0).lerp(AnimValue::from(hsla(0.5, 1.0, 0.5, 1.0)), 0.5);
assert!(v.is_color());
}
}