1use crate::Lerp;
2use sequoia_core::{Color, Point, Size};
3
4#[derive(Clone, Copy, Debug, PartialEq)]
5#[non_exhaustive]
6pub enum Value {
7 F32(f32),
8 Px(f32),
9 Color(Color),
10 Point(Point),
11 Size(Size),
12}
13
14impl Value {
15 pub const fn f32(v: f32) -> Self {
16 Self::F32(v)
17 }
18 pub const fn px(v: f32) -> Self {
19 Self::Px(v)
20 }
21 pub const fn color(c: Color) -> Self {
22 Self::Color(c)
23 }
24 pub const fn point(x: f32, y: f32) -> Self {
25 Self::Point(Point::new(x, y))
26 }
27 pub const fn size(w: f32, h: f32) -> Self {
28 Self::Size(Size::new(w, h))
29 }
30
31 pub fn as_f32(&self) -> Option<f32> {
32 match self {
33 Self::F32(v) => Some(*v),
34 _ => None,
35 }
36 }
37
38 pub fn as_px(&self) -> Option<f32> {
39 match self {
40 Self::Px(v) => Some(*v),
41 _ => None,
42 }
43 }
44
45 pub fn as_pixels(&self) -> Option<f32> {
46 self.as_px()
47 }
48 pub fn as_color(&self) -> Option<Color> {
49 match self {
50 Self::Color(v) => Some(*v),
51 _ => None,
52 }
53 }
54 pub fn as_point(&self) -> Option<Point> {
55 match self {
56 Self::Point(v) => Some(*v),
57 _ => None,
58 }
59 }
60 pub fn as_size(&self) -> Option<Size> {
61 match self {
62 Self::Size(v) => Some(*v),
63 _ => None,
64 }
65 }
66
67 pub fn lerp(&self, other: &Value, t: f32) -> Value {
68 match (self, other) {
69 (Self::F32(a), Self::F32(b)) => Self::F32(a.lerp(*b, t)),
70 (Self::Px(a), Self::Px(b)) => Self::Px(a.lerp(*b, t)),
71 (Self::Color(a), Self::Color(b)) => Self::Color(a.lerp(*b, t)),
72 (Self::Point(a), Self::Point(b)) => Self::Point(a.lerp(*b, t)),
73 (Self::Size(a), Self::Size(b)) => Self::Size(a.lerp(*b, t)),
74 (a, _) => *a,
75 }
76 }
77
78 pub fn mix<'a>(samples: impl IntoIterator<Item = (&'a Value, f32)>) -> Option<Value> {
79 let mut iter = samples.into_iter().peekable();
80 let first = iter.peek().map(|(v, _)| *v)?;
81 match first {
82 Self::F32(_) => {
83 let (mut acc, mut wsum) = (0.0f32, 0.0f32);
84 for (v, w) in iter {
85 if let Self::F32(x) = v {
86 acc += x * w;
87 wsum += w;
88 }
89 }
90 (wsum > 0.0).then(|| Self::F32(acc / wsum))
91 }
92 Self::Px(_) => {
93 let (mut acc, mut wsum) = (0.0f32, 0.0f32);
94 for (v, w) in iter {
95 if let Self::Px(x) = v {
96 acc += x * w;
97 wsum += w;
98 }
99 }
100 (wsum > 0.0).then(|| Self::Px(acc / wsum))
101 }
102 Self::Color(_) => {
103 let mut acc = [0.0f32; 4];
104 let mut wsum = 0.0f32;
105 for (v, w) in iter {
106 if let Self::Color(c) = v {
107 acc[0] += c.r * w;
108 acc[1] += c.g * w;
109 acc[2] += c.b * w;
110 acc[3] += c.a * w;
111 wsum += w;
112 }
113 }
114 (wsum > 0.0).then(|| {
115 Self::Color(Color::rgba(
116 acc[0] / wsum,
117 acc[1] / wsum,
118 acc[2] / wsum,
119 acc[3] / wsum,
120 ))
121 })
122 }
123 Self::Point(_) => {
124 let (mut ax, mut ay, mut wsum) = (0.0f32, 0.0f32, 0.0f32);
125 for (v, w) in iter {
126 if let Self::Point(p) = v {
127 ax += p.x * w;
128 ay += p.y * w;
129 wsum += w;
130 }
131 }
132 (wsum > 0.0).then(|| Self::Point(Point::new(ax / wsum, ay / wsum)))
133 }
134 Self::Size(_) => {
135 let (mut aw, mut ah, mut wsum) = (0.0f32, 0.0f32, 0.0f32);
136 for (v, w) in iter {
137 if let Self::Size(s) = v {
138 aw += s.width * w;
139 ah += s.height * w;
140 wsum += w;
141 }
142 }
143 (wsum > 0.0).then(|| Self::Size(Size::new(aw / wsum, ah / wsum)))
144 }
145 }
146 }
147}
148
149impl From<f32> for Value {
150 fn from(v: f32) -> Self {
151 Self::F32(v)
152 }
153}
154impl From<Color> for Value {
155 fn from(v: Color) -> Self {
156 Self::Color(v)
157 }
158}
159impl From<Point> for Value {
160 fn from(v: Point) -> Self {
161 Self::Point(v)
162 }
163}
164impl From<Size> for Value {
165 fn from(v: Size) -> Self {
166 Self::Size(v)
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn lerp_matches_variants() {
176 let a = Value::f32(0.0);
177 let b = Value::f32(10.0);
178 assert_eq!(a.lerp(&b, 0.25), Value::f32(2.5));
179
180 let c = Value::color(Color::rgb8(0, 0, 0));
181 let d = Value::color(Color::rgb8(100, 200, 0));
182 let mid = c.lerp(&d, 0.5).as_color().unwrap();
183 let (r, g, bl) = mid.to_rgb8();
184 assert_eq!((r, g, bl), (50, 100, 0));
185 }
186
187 #[test]
188 fn lerp_holds_on_mismatched_types() {
189 let a = Value::f32(5.0);
190 let b = Value::px(10.0);
191 assert_eq!(a.lerp(&b, 0.5), Value::f32(5.0));
192 }
193
194 #[test]
195 fn mix_weights_values() {
196 let a = Value::f32(0.0);
197 let b = Value::f32(100.0);
198 assert_eq!(Value::mix([(&a, 0.75), (&b, 0.25)]), Some(Value::f32(25.0)));
199 }
200
201 #[test]
202 fn mix_skips_mismatched_and_empty_weights() {
203 let a = Value::f32(10.0);
204 let p = Value::px(99.0);
205 assert_eq!(Value::mix([(&a, 1.0), (&p, 1.0)]), Some(Value::f32(10.0)));
206 assert_eq!(Value::mix([(&a, 0.0)]), None);
207 assert_eq!(Value::mix(std::iter::empty()), None);
208 }
209
210 #[test]
211 fn mix_blends_colors_per_channel() {
212 let black = Value::color(Color::BLACK);
213 let white = Value::color(Color::WHITE);
214 let out = Value::mix([(&black, 0.5), (&white, 0.5)])
215 .unwrap()
216 .as_color()
217 .unwrap();
218 let (r, g, b) = out.to_rgb8();
219 assert!((r as i16 - 127).abs() <= 1, "r={r}");
220 assert!((g as i16 - 127).abs() <= 1, "g={g}");
221 assert!((b as i16 - 127).abs() <= 1, "b={b}");
222 }
223}