Skip to main content

azul_css/props/basic/
animation.rs

1//! Basic types for SVG paths and animations.
2
3use core::fmt;
4
5use crate::impl_option;
6
7#[derive(Debug, Clone, PartialEq)]
8#[repr(C)]
9pub struct InterpolateResolver {
10    pub interpolate_func: AnimationInterpolationFunction,
11    pub parent_rect_width: f32,
12    pub parent_rect_height: f32,
13    pub current_rect_width: f32,
14    pub current_rect_height: f32,
15}
16
17#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
18#[repr(C)]
19pub struct SvgPoint {
20    pub x: f32,
21    pub y: f32,
22}
23
24#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
25#[repr(C)]
26pub struct SvgCubicCurve {
27    pub start: SvgPoint,
28    pub ctrl_1: SvgPoint,
29    pub ctrl_2: SvgPoint,
30    pub end: SvgPoint,
31}
32
33/// Represents an animation timing function.
34#[derive(Debug, Copy, Clone, PartialEq)]
35#[repr(C, u8)]
36pub enum AnimationInterpolationFunction {
37    Ease,
38    Linear,
39    EaseIn,
40    EaseOut,
41    EaseInOut,
42    CubicBezier(SvgCubicCurve),
43}
44
45#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
46#[repr(C)]
47pub struct SvgRect {
48    pub width: f32,
49    pub height: f32,
50    pub x: f32,
51    pub y: f32,
52    pub radius_top_left: f32,
53    pub radius_top_right: f32,
54    pub radius_bottom_left: f32,
55    pub radius_bottom_right: f32,
56}
57
58#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
59#[repr(C)]
60pub struct SvgVector {
61    pub x: f64,
62    pub y: f64,
63}
64
65#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
66#[repr(C)]
67pub struct SvgQuadraticCurve {
68    pub start: SvgPoint,
69    pub ctrl: SvgPoint,
70    pub end: SvgPoint,
71}
72
73// Now, add the impl blocks for each type.
74
75impl_option!(
76    SvgPoint,
77    OptionSvgPoint,
78    [Debug, Clone, PartialEq, PartialOrd]
79);
80
81impl SvgPoint {
82    #[inline]
83    pub fn distance(&self, other: Self) -> f64 {
84        let dx = other.x - self.x;
85        let dy = other.y - self.y;
86        libm::hypotf(dx, dy) as f64
87    }
88}
89
90impl SvgRect {
91    pub fn union_with(&mut self, other: &Self) {
92        let self_max_x = self.x + self.width;
93        let self_max_y = self.y + self.height;
94        let self_min_x = self.x;
95        let self_min_y = self.y;
96
97        let other_max_x = other.x + other.width;
98        let other_max_y = other.y + other.height;
99        let other_min_x = other.x;
100        let other_min_y = other.y;
101
102        let max_x = self_max_x.max(other_max_x);
103        let max_y = self_max_y.max(other_max_y);
104        let min_x = self_min_x.min(other_min_x);
105        let min_y = self_min_y.min(other_min_y);
106
107        self.x = min_x;
108        self.y = min_y;
109        self.width = max_x - min_x;
110        self.height = max_y - min_y;
111    }
112
113    /// Note: does not incorporate rounded edges!
114    /// Origin of x and y is assumed to be the top left corner
115    pub fn contains_point(&self, point: SvgPoint) -> bool {
116        point.x > self.x
117            && point.x < self.x + self.width
118            && point.y > self.y
119            && point.y < self.y + self.height
120    }
121
122    /// Expands the rect with a certain amount of padding
123    pub fn expand(
124        &self,
125        padding_top: f32,
126        padding_bottom: f32,
127        padding_left: f32,
128        padding_right: f32,
129    ) -> SvgRect {
130        SvgRect {
131            width: self.width + padding_left + padding_right,
132            height: self.height + padding_top + padding_bottom,
133            x: self.x - padding_left,
134            y: self.y - padding_top,
135            ..*self
136        }
137    }
138
139    pub fn get_center(&self) -> SvgPoint {
140        SvgPoint {
141            x: self.x + (self.width / 2.0),
142            y: self.y + (self.height / 2.0),
143        }
144    }
145}
146
147const STEP_SIZE: usize = 20;
148const STEP_SIZE_F64: f64 = 0.05;
149
150impl SvgCubicCurve {
151    pub fn reverse(&mut self) {
152        let temp = self.start;
153        self.start = self.end;
154        self.end = temp;
155        let temp = self.ctrl_1;
156        self.ctrl_1 = self.ctrl_2;
157        self.ctrl_2 = temp;
158    }
159
160    pub fn get_start(&self) -> SvgPoint {
161        self.start
162    }
163    pub fn get_end(&self) -> SvgPoint {
164        self.end
165    }
166
167    // evaluate the curve at t
168    pub fn get_x_at_t(&self, t: f64) -> f64 {
169        let c_x = 3.0 * (self.ctrl_1.x as f64 - self.start.x as f64);
170        let b_x = 3.0 * (self.ctrl_2.x as f64 - self.ctrl_1.x as f64) - c_x;
171        let a_x = self.end.x as f64 - self.start.x as f64 - c_x - b_x;
172
173        (a_x * t * t * t) + (b_x * t * t) + (c_x * t) + self.start.x as f64
174    }
175
176    pub fn get_y_at_t(&self, t: f64) -> f64 {
177        let c_y = 3.0 * (self.ctrl_1.y as f64 - self.start.y as f64);
178        let b_y = 3.0 * (self.ctrl_2.y as f64 - self.ctrl_1.y as f64) - c_y;
179        let a_y = self.end.y as f64 - self.start.y as f64 - c_y - b_y;
180
181        (a_y * t * t * t) + (b_y * t * t) + (c_y * t) + self.start.y as f64
182    }
183
184    pub fn get_length(&self) -> f64 {
185        // NOTE: this arc length parametrization is not very precise, but fast
186        let mut arc_length = 0.0;
187        let mut prev_point = self.get_start();
188
189        for i in 0..STEP_SIZE {
190            let t_next = (i + 1) as f64 * STEP_SIZE_F64;
191            let next_point = SvgPoint {
192                x: self.get_x_at_t(t_next) as f32,
193                y: self.get_y_at_t(t_next) as f32,
194            };
195            arc_length += prev_point.distance(next_point);
196            prev_point = next_point;
197        }
198
199        arc_length
200    }
201
202    pub fn get_t_at_offset(&self, offset: f64) -> f64 {
203        // step through the line until the offset is reached,
204        // then interpolate linearly between the
205        // current at the last sampled point
206        let mut arc_length = 0.0;
207        let mut t_current = 0.0;
208        let mut prev_point = self.get_start();
209
210        for i in 0..STEP_SIZE {
211            let t_next = (i + 1) as f64 * STEP_SIZE_F64;
212            let next_point = SvgPoint {
213                x: self.get_x_at_t(t_next) as f32,
214                y: self.get_y_at_t(t_next) as f32,
215            };
216
217            let distance = prev_point.distance(next_point);
218
219            arc_length += distance;
220
221            // linearly interpolate between last t and current t
222            if arc_length > offset {
223                let remaining = arc_length - offset;
224                return t_current + (remaining / distance) * STEP_SIZE_F64;
225            }
226
227            prev_point = next_point;
228            t_current = t_next;
229        }
230
231        t_current
232    }
233
234    pub fn get_tangent_vector_at_t(&self, t: f64) -> SvgVector {
235        // 1. Calculate the derivative of the bezier curve.
236        //
237        // This means that we go from 4 points to 3 points and redistribute
238        // the weights of the control points according to the formula:
239        //
240        // w'0 = 3 * (w1-w0)
241        // w'1 = 3 * (w2-w1)
242        // w'2 = 3 * (w3-w2)
243
244        let w0 = SvgPoint {
245            x: self.ctrl_1.x - self.start.x,
246            y: self.ctrl_1.y - self.start.y,
247        };
248
249        let w1 = SvgPoint {
250            x: self.ctrl_2.x - self.ctrl_1.x,
251            y: self.ctrl_2.y - self.ctrl_1.y,
252        };
253
254        let w2 = SvgPoint {
255            x: self.end.x - self.ctrl_2.x,
256            y: self.end.y - self.ctrl_2.y,
257        };
258
259        let quadratic_curve = SvgQuadraticCurve {
260            start: w0,
261            ctrl: w1,
262            end: w2,
263        };
264
265        // The first derivative of a cubic bezier curve is a quadratic
266        // bezier curve. Luckily, the first derivative is also the tangent
267        // vector (slope) of the curve. So all we need to do is to sample the
268        // quadratic curve at t
269        let tangent_vector = SvgVector {
270            x: quadratic_curve.get_x_at_t(t),
271            y: quadratic_curve.get_y_at_t(t),
272        };
273
274        tangent_vector.normalize()
275    }
276
277    pub fn get_bounds(&self) -> SvgRect {
278        let min_x = self
279            .start
280            .x
281            .min(self.end.x)
282            .min(self.ctrl_1.x)
283            .min(self.ctrl_2.x);
284        let max_x = self
285            .start
286            .x
287            .max(self.end.x)
288            .max(self.ctrl_1.x)
289            .max(self.ctrl_2.x);
290
291        let min_y = self
292            .start
293            .y
294            .min(self.end.y)
295            .min(self.ctrl_1.y)
296            .min(self.ctrl_2.y);
297        let max_y = self
298            .start
299            .y
300            .max(self.end.y)
301            .max(self.ctrl_1.y)
302            .max(self.ctrl_2.y);
303
304        let width = (max_x - min_x).abs();
305        let height = (max_y - min_y).abs();
306
307        SvgRect {
308            width,
309            height,
310            x: min_x,
311            y: min_y,
312            ..SvgRect::default()
313        }
314    }
315}
316
317impl SvgVector {
318    /// Returns the angle of the vector in degrees
319    #[inline]
320    pub fn angle_degrees(&self) -> f64 {
321        (-self.y).atan2(self.x).to_degrees()
322    }
323
324    #[inline]
325    #[must_use = "returns a new vector"]
326    pub fn normalize(&self) -> Self {
327        let tangent_length = libm::hypotf(self.x as f32, self.y as f32) as f64;
328        if tangent_length == 0.0 {
329            return Self { x: 0.0, y: 0.0 };
330        }
331        Self {
332            x: self.x / tangent_length,
333            y: self.y / tangent_length,
334        }
335    }
336
337    /// Rotate the vector 90 degrees counter-clockwise
338    #[must_use = "returns a new vector"]
339    #[inline]
340    pub fn rotate_90deg_ccw(&self) -> Self {
341        Self {
342            x: -self.y,
343            y: self.x,
344        }
345    }
346}
347
348impl SvgQuadraticCurve {
349    pub fn reverse(&mut self) {
350        let temp = self.start;
351        self.start = self.end;
352        self.end = temp;
353    }
354    pub fn get_start(&self) -> SvgPoint {
355        self.start
356    }
357    pub fn get_end(&self) -> SvgPoint {
358        self.end
359    }
360    pub fn get_bounds(&self) -> SvgRect {
361        let min_x = self.start.x.min(self.end.x).min(self.ctrl.x);
362        let max_x = self.start.x.max(self.end.x).max(self.ctrl.x);
363
364        let min_y = self.start.y.min(self.end.y).min(self.ctrl.y);
365        let max_y = self.start.y.max(self.end.y).max(self.ctrl.y);
366
367        let width = (max_x - min_x).abs();
368        let height = (max_y - min_y).abs();
369
370        SvgRect {
371            width,
372            height,
373            x: min_x,
374            y: min_y,
375            ..SvgRect::default()
376        }
377    }
378
379    pub fn get_x_at_t(&self, t: f64) -> f64 {
380        let one_minus = 1.0 - t;
381        one_minus * one_minus * self.start.x as f64
382            + 2.0 * one_minus * t * self.ctrl.x as f64
383            + t * t * self.end.x as f64
384    }
385
386    pub fn get_y_at_t(&self, t: f64) -> f64 {
387        let one_minus = 1.0 - t;
388        one_minus * one_minus * self.start.y as f64
389            + 2.0 * one_minus * t * self.ctrl.y as f64
390            + t * t * self.end.y as f64
391    }
392
393    pub fn get_length(&self) -> f64 {
394        self.to_cubic().get_length()
395    }
396
397    pub fn get_t_at_offset(&self, offset: f64) -> f64 {
398        self.to_cubic().get_t_at_offset(offset)
399    }
400
401    pub fn get_tangent_vector_at_t(&self, t: f64) -> SvgVector {
402        self.to_cubic().get_tangent_vector_at_t(t)
403    }
404
405    fn to_cubic(&self) -> SvgCubicCurve {
406        SvgCubicCurve {
407            start: self.start,
408            ctrl_1: SvgPoint {
409                x: self.start.x + (2.0 / 3.0) * (self.ctrl.x - self.start.x),
410                y: self.start.y + (2.0 / 3.0) * (self.ctrl.y - self.start.y),
411            },
412            ctrl_2: SvgPoint {
413                x: self.end.x + (2.0 / 3.0) * (self.ctrl.x - self.end.x),
414                y: self.end.y + (2.0 / 3.0) * (self.ctrl.y - self.end.y),
415            },
416            end: self.end,
417        }
418    }
419}
420
421impl AnimationInterpolationFunction {
422    pub const fn get_curve(self) -> SvgCubicCurve {
423        match self {
424            AnimationInterpolationFunction::Ease => SvgCubicCurve {
425                start: SvgPoint { x: 0.0, y: 0.0 },
426                ctrl_1: SvgPoint { x: 0.25, y: 0.1 },
427                ctrl_2: SvgPoint { x: 0.25, y: 1.0 },
428                end: SvgPoint { x: 1.0, y: 1.0 },
429            },
430            AnimationInterpolationFunction::Linear => SvgCubicCurve {
431                start: SvgPoint { x: 0.0, y: 0.0 },
432                ctrl_1: SvgPoint { x: 0.0, y: 0.0 },
433                ctrl_2: SvgPoint { x: 1.0, y: 1.0 },
434                end: SvgPoint { x: 1.0, y: 1.0 },
435            },
436            AnimationInterpolationFunction::EaseIn => SvgCubicCurve {
437                start: SvgPoint { x: 0.0, y: 0.0 },
438                ctrl_1: SvgPoint { x: 0.42, y: 0.0 },
439                ctrl_2: SvgPoint { x: 1.0, y: 1.0 },
440                end: SvgPoint { x: 1.0, y: 1.0 },
441            },
442            AnimationInterpolationFunction::EaseOut => SvgCubicCurve {
443                start: SvgPoint { x: 0.0, y: 0.0 },
444                ctrl_1: SvgPoint { x: 0.0, y: 0.0 },
445                ctrl_2: SvgPoint { x: 0.58, y: 1.0 },
446                end: SvgPoint { x: 1.0, y: 1.0 },
447            },
448            AnimationInterpolationFunction::EaseInOut => SvgCubicCurve {
449                start: SvgPoint { x: 0.0, y: 0.0 },
450                ctrl_1: SvgPoint { x: 0.42, y: 0.0 },
451                ctrl_2: SvgPoint { x: 0.58, y: 1.0 },
452                end: SvgPoint { x: 1.0, y: 1.0 },
453            },
454            AnimationInterpolationFunction::CubicBezier(c) => c,
455        }
456    }
457
458    pub fn evaluate(self, t: f64) -> f32 {
459        self.get_curve().get_y_at_t(t) as f32
460    }
461}