Skip to main content

aura_anim_core/
iced.rs

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}