1use gpui::{Hsla, Pixels, Rgba};
9
10#[derive(Debug, Clone, Copy, PartialEq)]
12pub enum AnimValue {
13 Number(f32),
14 Color(Hsla),
15}
16
17impl AnimValue {
18 pub fn lerp(self, other: Self, t: f32) -> Self {
22 match (self, other) {
23 (AnimValue::Number(a), AnimValue::Number(b)) => AnimValue::Number(a + (b - a) * t),
24 (AnimValue::Color(a), AnimValue::Color(b)) => AnimValue::Color(lerp_hsla(a, b, t)),
25 (_, b) => b,
26 }
27 }
28
29 pub fn number(self) -> f32 {
31 match self {
32 AnimValue::Number(v) => v,
33 AnimValue::Color(_) => 0.0,
34 }
35 }
36
37 pub fn color(self) -> Option<Hsla> {
38 match self {
39 AnimValue::Color(c) => Some(c),
40 AnimValue::Number(_) => None,
41 }
42 }
43
44 pub fn is_color(self) -> bool {
45 matches!(self, AnimValue::Color(_))
46 }
47}
48
49impl From<f32> for AnimValue {
50 fn from(v: f32) -> Self {
51 AnimValue::Number(v)
52 }
53}
54
55impl From<f64> for AnimValue {
56 fn from(v: f64) -> Self {
57 AnimValue::Number(v as f32)
58 }
59}
60
61impl From<i32> for AnimValue {
62 fn from(v: i32) -> Self {
63 AnimValue::Number(v as f32)
64 }
65}
66
67impl From<Pixels> for AnimValue {
68 fn from(v: Pixels) -> Self {
69 AnimValue::Number(f32::from(v))
70 }
71}
72
73impl From<Hsla> for AnimValue {
74 fn from(v: Hsla) -> Self {
75 AnimValue::Color(v)
76 }
77}
78
79impl From<Rgba> for AnimValue {
80 fn from(v: Rgba) -> Self {
81 AnimValue::Color(v.into())
82 }
83}
84
85fn lerp_hsla(a: Hsla, b: Hsla, t: f32) -> Hsla {
92 let (ah, bh) = match (a.a <= 0.0, b.a <= 0.0) {
93 (true, false) => (b.h, b.h),
94 (false, true) => (a.h, a.h),
95 _ => (a.h, b.h),
96 };
97 let mut delta = bh - ah;
98 if delta > 0.5 {
99 delta -= 1.0;
100 } else if delta < -0.5 {
101 delta += 1.0;
102 }
103 let h = (ah + delta * t).rem_euclid(1.0);
104 Hsla {
105 h,
106 s: a.s + (b.s - a.s) * t,
107 l: a.l + (b.l - a.l) * t,
108 a: a.a + (b.a - a.a) * t,
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
117 Hsla { h, s, l, a }
118 }
119
120 #[test]
121 fn numbers_interpolate_linearly() {
122 let v = AnimValue::from(10.0).lerp(AnimValue::from(20.0), 0.25);
123 assert_eq!(v.number(), 12.5);
124 }
125
126 #[test]
127 fn hue_takes_the_short_way_round() {
128 let a = hsla(0.9, 1.0, 0.5, 1.0);
130 let b = hsla(0.1, 1.0, 0.5, 1.0);
131 let mid = AnimValue::from(a).lerp(AnimValue::from(b), 0.5).color();
132 assert!((mid.unwrap().h - 0.0).abs() < 1e-5, "{mid:?}");
133 }
134
135 #[test]
136 fn transparent_endpoints_borrow_the_other_hue() {
137 let clear = hsla(0.0, 0.0, 0.0, 0.0);
138 let blue = hsla(0.6, 1.0, 0.5, 1.0);
139 let mid = AnimValue::from(clear)
140 .lerp(AnimValue::from(blue), 0.5)
141 .color()
142 .unwrap();
143 assert!((mid.h - 0.6).abs() < 1e-5, "{mid:?}");
144 assert!((mid.a - 0.5).abs() < 1e-5);
145 }
146
147 #[test]
148 fn mismatched_kinds_snap_to_the_destination() {
149 let v = AnimValue::from(1.0).lerp(AnimValue::from(hsla(0.5, 1.0, 0.5, 1.0)), 0.5);
150 assert!(v.is_color());
151 }
152}