1use crate::{AnimationValue, Transform};
4
5pub trait Interpolate {
7 fn interpolate(&self, to: &Self, progress: f64) -> Self;
9}
10
11pub fn lerp(from: f64, to: f64, progress: f64) -> f64 {
13 from + (to - from) * progress
14}
15
16impl Interpolate for f64 {
17 fn interpolate(&self, to: &Self, progress: f64) -> Self {
18 lerp(*self, *to, progress)
19 }
20}
21
22impl Interpolate for AnimationValue {
23 fn interpolate(&self, to: &Self, progress: f64) -> Self {
24 match (self, to) {
25 (AnimationValue::Number(from), AnimationValue::Number(to)) => {
26 AnimationValue::Number(lerp(*from, *to, progress))
27 }
28 (AnimationValue::Pixels(from), AnimationValue::Pixels(to)) => {
29 AnimationValue::Pixels(lerp(*from, *to, progress))
30 }
31 (AnimationValue::Percentage(from), AnimationValue::Percentage(to)) => {
32 AnimationValue::Percentage(lerp(*from, *to, progress))
33 }
34 (AnimationValue::Degrees(from), AnimationValue::Degrees(to)) => {
35 AnimationValue::Degrees(lerp(*from, *to, progress))
36 }
37 (AnimationValue::Radians(from), AnimationValue::Radians(to)) => {
38 AnimationValue::Radians(lerp(*from, *to, progress))
39 }
40 (AnimationValue::Transform(from), AnimationValue::Transform(to)) => {
41 AnimationValue::Transform(from.interpolate(to, progress))
42 }
43 (AnimationValue::Color(from), AnimationValue::Color(to)) => {
44 AnimationValue::Color(interpolate_color(from, to, progress))
45 }
46 _ => {
47 if progress < 0.5 {
49 self.clone()
50 } else {
51 to.clone()
52 }
53 }
54 }
55 }
56}
57
58impl Interpolate for Transform {
59 fn interpolate(&self, to: &Self, progress: f64) -> Self {
60 Transform {
61 x: interpolate_option(self.x, to.x, progress),
62 y: interpolate_option(self.y, to.y, progress),
63 z: interpolate_option(self.z, to.z, progress),
64 rotate_x: interpolate_option(self.rotate_x, to.rotate_x, progress),
65 rotate_y: interpolate_option(self.rotate_y, to.rotate_y, progress),
66 rotate_z: interpolate_option(self.rotate_z, to.rotate_z, progress),
67 scale: interpolate_option(self.scale, to.scale, progress),
68 scale_x: interpolate_option(self.scale_x, to.scale_x, progress),
69 scale_y: interpolate_option(self.scale_y, to.scale_y, progress),
70 skew_x: interpolate_option(self.skew_x, to.skew_x, progress),
71 skew_y: interpolate_option(self.skew_y, to.skew_y, progress),
72 }
73 }
74}
75
76fn interpolate_option(from: Option<f64>, to: Option<f64>, progress: f64) -> Option<f64> {
78 match (from, to) {
79 (Some(from), Some(to)) => Some(lerp(from, to, progress)),
80 (Some(from), None) => Some(lerp(from, 0.0, progress)),
81 (None, Some(to)) => Some(lerp(0.0, to, progress)),
82 (None, None) => None,
83 }
84}
85
86fn interpolate_color(from: &str, to: &str, progress: f64) -> String {
88 if progress < 0.5 {
91 from.to_string()
92 } else {
93 to.to_string()
94 }
95}
96
97pub mod color {
99 use super::lerp;
100
101 #[derive(Debug, Clone, Copy, PartialEq)]
103 pub struct Rgba {
104 pub r: f64,
106 pub g: f64,
108 pub b: f64,
110 pub a: f64,
112 }
113
114 impl Rgba {
115 pub fn new(r: f64, g: f64, b: f64, a: f64) -> Self {
117 Self {
118 r: r.clamp(0.0, 255.0),
119 g: g.clamp(0.0, 255.0),
120 b: b.clamp(0.0, 255.0),
121 a: a.clamp(0.0, 1.0),
122 }
123 }
124
125 pub fn interpolate(&self, to: &Self, progress: f64) -> Self {
127 Self {
128 r: lerp(self.r, to.r, progress),
129 g: lerp(self.g, to.g, progress),
130 b: lerp(self.b, to.b, progress),
131 a: lerp(self.a, to.a, progress),
132 }
133 }
134
135 pub fn to_css(&self) -> String {
137 if self.a < 1.0 {
138 format!("rgba({}, {}, {}, {})", self.r, self.g, self.b, self.a)
139 } else {
140 format!("rgb({}, {}, {})", self.r, self.g, self.b)
141 }
142 }
143
144 pub fn from_hex(hex: &str) -> Option<Self> {
146 let hex = hex.trim_start_matches('#');
147 if hex.len() == 6 {
148 let r = u8::from_str_radix(&hex[0..2], 16).ok()? as f64;
149 let g = u8::from_str_radix(&hex[2..4], 16).ok()? as f64;
150 let b = u8::from_str_radix(&hex[4..6], 16).ok()? as f64;
151 Some(Self::new(r, g, b, 1.0))
152 } else {
153 None
154 }
155 }
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 #[cfg(feature = "approx")]
163 use approx::assert_relative_eq;
164
165 #[cfg(feature = "approx")]
166 #[test]
167 fn test_lerp() {
168 assert_relative_eq!(lerp(0.0, 100.0, 0.0), 0.0);
169 assert_relative_eq!(lerp(0.0, 100.0, 0.5), 50.0);
170 assert_relative_eq!(lerp(0.0, 100.0, 1.0), 100.0);
171 assert_relative_eq!(lerp(50.0, 150.0, 0.25), 75.0);
172 }
173
174 #[test]
175 fn test_animation_value_interpolation() {
176 let from = AnimationValue::Number(0.0);
177 let to = AnimationValue::Number(100.0);
178
179 let mid = from.interpolate(&to, 0.5);
180 assert_eq!(mid, AnimationValue::Number(50.0));
181
182 let pixels_from = AnimationValue::Pixels(10.0);
183 let pixels_to = AnimationValue::Pixels(20.0);
184 let pixels_mid = pixels_from.interpolate(&pixels_to, 0.3);
185 assert_eq!(pixels_mid, AnimationValue::Pixels(13.0));
186 }
187
188 #[test]
189 fn test_transform_interpolation() {
190 let from = Transform {
191 x: Some(0.0),
192 y: Some(0.0),
193 scale: Some(1.0),
194 ..Default::default()
195 };
196
197 let to = Transform {
198 x: Some(100.0),
199 y: Some(50.0),
200 scale: Some(2.0),
201 ..Default::default()
202 };
203
204 let mid = from.interpolate(&to, 0.5);
205 assert_eq!(mid.x, Some(50.0));
206 assert_eq!(mid.y, Some(25.0));
207 assert_eq!(mid.scale, Some(1.5));
208 }
209
210 #[test]
211 fn test_option_interpolation() {
212 assert_eq!(interpolate_option(Some(0.0), Some(100.0), 0.5), Some(50.0));
213 assert_eq!(interpolate_option(Some(10.0), None, 0.5), Some(5.0));
214 assert_eq!(interpolate_option(None, Some(20.0), 0.5), Some(10.0));
215 assert_eq!(interpolate_option(None, None, 0.5), None);
216 }
217
218 #[cfg(feature = "approx")]
219 #[test]
220 fn test_rgba_color() {
221 use super::color::Rgba;
222
223 let red = Rgba::new(255.0, 0.0, 0.0, 1.0);
224 let blue = Rgba::new(0.0, 0.0, 255.0, 1.0);
225
226 let purple = red.interpolate(&blue, 0.5);
227 assert_relative_eq!(purple.r, 127.5, epsilon = 0.1);
228 assert_relative_eq!(purple.g, 0.0);
229 assert_relative_eq!(purple.b, 127.5, epsilon = 0.1);
230
231 assert_eq!(red.to_css(), "rgb(255, 0, 0)");
232
233 let transparent_red = Rgba::new(255.0, 0.0, 0.0, 0.5);
234 assert_eq!(transparent_red.to_css(), "rgba(255, 0, 0, 0.5)");
235 }
236
237 #[test]
238 fn test_hex_color_parsing() {
239 use super::color::Rgba;
240
241 let red = Rgba::from_hex("#ff0000").unwrap();
242 assert_eq!(red.r, 255.0);
243 assert_eq!(red.g, 0.0);
244 assert_eq!(red.b, 0.0);
245 assert_eq!(red.a, 1.0);
246
247 let green = Rgba::from_hex("00ff00").unwrap();
248 assert_eq!(green.g, 255.0);
249
250 assert!(Rgba::from_hex("invalid").is_none());
251 }
252}