1use iced_core::{Padding, Point, Rectangle, Size, Vector, border::Radius};
2
3#[cfg(any(feature = "rgba", feature = "oklaba"))]
4use iced_core::{Border, Color, Shadow};
5
6use crate::{Interpolate, InterpolationProgress};
7
8#[cfg(all(feature = "rgba", feature = "oklaba"))]
9compile_error!("features `rgba` and `oklaba` are mutually exclusive");
10
11macro_rules! interpolate_fields {
12 ($from:ident, $to:ident, $progress:ident, $($field:ident),+ $(,)?) => {
13 Self {
14 $(
15 $field: Interpolate::interpolate_progress(
16 &$from.$field,
17 &$to.$field,
18 $progress,
19 ),
20 )+
21 }
22 };
23}
24
25impl<T> Interpolate for Vector<T>
26where
27 T: Interpolate + Clone,
28{
29 fn interpolate_progress(from: &Self, to: &Self, progress: InterpolationProgress) -> Self {
30 interpolate_fields!(from, to, progress, x, y)
31 }
32}
33
34impl<T> Interpolate for Point<T>
35where
36 T: Interpolate + Clone,
37{
38 fn interpolate_progress(from: &Self, to: &Self, progress: InterpolationProgress) -> Self {
39 interpolate_fields!(from, to, progress, x, y)
40 }
41}
42
43impl<T> Interpolate for Size<T>
44where
45 T: Interpolate + Clone,
46{
47 fn interpolate_progress(from: &Self, to: &Self, progress: InterpolationProgress) -> Self {
48 interpolate_fields!(from, to, progress, width, height)
49 }
50}
51
52impl<T> Interpolate for Rectangle<T>
53where
54 T: Interpolate + Clone,
55{
56 fn interpolate_progress(from: &Self, to: &Self, progress: InterpolationProgress) -> Self {
57 interpolate_fields!(from, to, progress, x, y, width, height)
58 }
59}
60
61impl Interpolate for Padding {
62 fn interpolate_progress(from: &Self, to: &Self, progress: InterpolationProgress) -> Self {
63 interpolate_fields!(from, to, progress, top, right, bottom, left)
64 }
65}
66
67impl Interpolate for Radius {
68 fn interpolate_progress(from: &Self, to: &Self, progress: InterpolationProgress) -> Self {
69 interpolate_fields!(
70 from,
71 to,
72 progress,
73 top_left,
74 top_right,
75 bottom_right,
76 bottom_left,
77 )
78 }
79}
80
81#[cfg(any(feature = "rgba", feature = "oklaba"))]
82impl Interpolate for Shadow {
83 fn interpolate_progress(from: &Self, to: &Self, progress: InterpolationProgress) -> Self {
84 interpolate_fields!(from, to, progress, color, offset, blur_radius)
85 }
86}
87
88#[cfg(any(feature = "rgba", feature = "oklaba"))]
89impl Interpolate for Border {
90 fn interpolate_progress(from: &Self, to: &Self, progress: InterpolationProgress) -> Self {
91 interpolate_fields!(from, to, progress, color, width, radius)
92 }
93}
94
95#[cfg(feature = "rgba")]
96impl Interpolate for Color {
97 fn interpolate_progress(from: &Self, to: &Self, progress: InterpolationProgress) -> Self {
98 interpolate_fields!(from, to, progress, r, g, b, a)
99 }
100}
101
102#[cfg(feature = "oklaba")]
103impl Interpolate for Color {
104 fn interpolate_progress(from: &Self, to: &Self, progress: InterpolationProgress) -> Self {
105 use palette::{FromColor, Mix, Oklab, Srgb};
106
107 let from_lab = Oklab::from_color(Srgb::new(from.r, from.g, from.b));
108 let to_lab = Oklab::from_color(Srgb::new(to.r, to.g, to.b));
109
110 let lab = from_lab.mix(to_lab, progress.value());
111 let rgb = Srgb::from_color(lab);
112
113 Color {
114 r: rgb.red.clamp(0.0, 1.0),
115 g: rgb.green.clamp(0.0, 1.0),
116 b: rgb.blue.clamp(0.0, 1.0),
117 a: f32::interpolate_progress(&from.a, &to.a, progress).clamp(0.0, 1.0),
118 }
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use crate::Interpolate;
125 use float_cmp::assert_approx_eq;
126 use iced_core::{Padding, Point, Rectangle, Size, Vector, border::Radius};
127
128 #[test]
129 fn interpolates_geometry_types() {
130 let vector =
131 Vector::interpolate(&Vector::new(0.0_f32, 10.0), &Vector::new(10.0, 30.0), 0.5);
132 let point = Point::interpolate(&Point::new(0.0_f32, 10.0), &Point::new(10.0, 30.0), 0.5);
133 let size = Size::interpolate(&Size::new(10.0_f32, 20.0), &Size::new(30.0, 60.0), 0.5);
134 let rectangle = Rectangle::interpolate(
135 &Rectangle::new(Point::new(0.0_f32, 10.0), Size::new(20.0, 30.0)),
136 &Rectangle::new(Point::new(10.0, 30.0), Size::new(40.0, 70.0)),
137 0.5,
138 );
139
140 assert_approx_eq!(f32, vector.x, 5.0);
141 assert_approx_eq!(f32, vector.y, 20.0);
142 assert_approx_eq!(f32, point.x, 5.0);
143 assert_approx_eq!(f32, point.y, 20.0);
144 assert_approx_eq!(f32, size.width, 20.0);
145 assert_approx_eq!(f32, size.height, 40.0);
146 assert_approx_eq!(f32, rectangle.x, 5.0);
147 assert_approx_eq!(f32, rectangle.y, 20.0);
148 assert_approx_eq!(f32, rectangle.width, 30.0);
149 assert_approx_eq!(f32, rectangle.height, 50.0);
150 }
151
152 #[test]
153 fn interpolates_padding_and_radius() {
154 let padding = Padding::interpolate(
155 &Padding {
156 top: 0.0,
157 right: 10.0,
158 bottom: 20.0,
159 left: 30.0,
160 },
161 &Padding {
162 top: 10.0,
163 right: 30.0,
164 bottom: 50.0,
165 left: 70.0,
166 },
167 0.5,
168 );
169 let radius = Radius::interpolate(
170 &Radius {
171 top_left: 0.0,
172 top_right: 10.0,
173 bottom_right: 20.0,
174 bottom_left: 30.0,
175 },
176 &Radius {
177 top_left: 10.0,
178 top_right: 30.0,
179 bottom_right: 50.0,
180 bottom_left: 70.0,
181 },
182 0.5,
183 );
184
185 assert_approx_eq!(f32, padding.top, 5.0);
186 assert_approx_eq!(f32, padding.right, 20.0);
187 assert_approx_eq!(f32, padding.bottom, 35.0);
188 assert_approx_eq!(f32, padding.left, 50.0);
189 assert_approx_eq!(f32, radius.top_left, 5.0);
190 assert_approx_eq!(f32, radius.top_right, 20.0);
191 assert_approx_eq!(f32, radius.bottom_right, 35.0);
192 assert_approx_eq!(f32, radius.bottom_left, 50.0);
193 }
194
195 #[cfg(any(feature = "rgba", feature = "oklaba"))]
196 #[test]
197 fn interpolates_border_and_shadow_fields() {
198 use iced_core::{Border, Color, Shadow};
199
200 let border = Border::interpolate(
201 &Border {
202 color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
203 width: 2.0,
204 radius: Radius::from(4.0),
205 },
206 &Border {
207 color: Color::from_rgba(1.0, 1.0, 1.0, 1.0),
208 width: 6.0,
209 radius: Radius::from(12.0),
210 },
211 0.5,
212 );
213 let shadow = Shadow::interpolate(
214 &Shadow {
215 color: Color::from_rgba(0.0, 0.0, 0.0, 0.0),
216 offset: Vector::new(0.0, 2.0),
217 blur_radius: 4.0,
218 },
219 &Shadow {
220 color: Color::from_rgba(1.0, 1.0, 1.0, 1.0),
221 offset: Vector::new(10.0, 6.0),
222 blur_radius: 12.0,
223 },
224 0.5,
225 );
226
227 assert_approx_eq!(f32, border.width, 4.0);
228 assert_approx_eq!(f32, border.radius.top_left, 8.0);
229 assert_approx_eq!(f32, border.color.a, 0.5);
230 assert_approx_eq!(f32, shadow.offset.x, 5.0);
231 assert_approx_eq!(f32, shadow.offset.y, 4.0);
232 assert_approx_eq!(f32, shadow.blur_radius, 8.0);
233 assert_approx_eq!(f32, shadow.color.a, 0.5);
234 }
235
236 #[cfg(feature = "rgba")]
237 #[test]
238 fn rgba_color_interpolates_each_channel() {
239 use iced_core::Color;
240
241 let color = Color::interpolate(
242 &Color::from_rgba(0.0, 0.2, 0.4, 0.6),
243 &Color::from_rgba(1.0, 0.6, 0.8, 1.0),
244 0.5,
245 );
246
247 assert_approx_eq!(f32, color.r, 0.5);
248 assert_approx_eq!(f32, color.g, 0.4);
249 assert_approx_eq!(f32, color.b, 0.6);
250 assert_approx_eq!(f32, color.a, 0.8);
251 }
252
253 #[cfg(feature = "oklaba")]
254 #[test]
255 fn oklaba_color_interpolation_is_finite_and_preserves_alpha_progress() {
256 use iced_core::Color;
257
258 let color = Color::interpolate(
259 &Color::from_rgba(1.0, 0.0, 0.0, 0.2),
260 &Color::from_rgba(0.0, 0.0, 1.0, 0.8),
261 0.5,
262 );
263
264 assert!(color.r.is_finite());
265 assert!(color.g.is_finite());
266 assert!(color.b.is_finite());
267 assert!((0.0..=1.0).contains(&color.r));
268 assert!((0.0..=1.0).contains(&color.g));
269 assert!((0.0..=1.0).contains(&color.b));
270 assert_approx_eq!(f32, color.a, 0.5);
271 }
272}