animate/path/
segment.rs

1use super::FuzzyEq;
2
3/// List of all path commands.
4#[derive(Clone, Copy, PartialEq, Debug)]
5#[allow(missing_docs)]
6pub enum PathCommand {
7    MoveTo,
8    LineTo,
9    HorizontalLineTo,
10    VerticalLineTo,
11    CurveTo,
12    SmoothCurveTo,
13    Quadratic,
14    SmoothQuadratic,
15    EllipticalArc,
16    ClosePath,
17}
18
19/// Representation of the path segment.
20///
21/// If you want to change the segment type (for example MoveTo to LineTo)
22/// you should create a new segment.
23/// But you still can change points or make segment relative or absolute.
24#[allow(missing_docs)]
25#[derive(Clone, Copy, PartialEq, Debug)]
26pub enum PathSegment {
27    MoveTo {
28        abs: bool,
29        x: f64,
30        y: f64,
31    },
32    LineTo {
33        abs: bool,
34        x: f64,
35        y: f64,
36    },
37    HorizontalLineTo {
38        abs: bool,
39        x: f64,
40    },
41    VerticalLineTo {
42        abs: bool,
43        y: f64,
44    },
45    CurveTo {
46        abs: bool,
47        x1: f64,
48        y1: f64,
49        x2: f64,
50        y2: f64,
51        x: f64,
52        y: f64,
53    },
54    SmoothCurveTo {
55        abs: bool,
56        x2: f64,
57        y2: f64,
58        x: f64,
59        y: f64,
60    },
61    Quadratic {
62        abs: bool,
63        x1: f64,
64        y1: f64,
65        x: f64,
66        y: f64,
67    },
68    SmoothQuadratic {
69        abs: bool,
70        x: f64,
71        y: f64,
72    },
73    EllipticalArc {
74        abs: bool,
75        rx: f64,
76        ry: f64,
77        x_axis_rotation: f64,
78        large_arc: bool,
79        sweep: bool,
80        x: f64,
81        y: f64,
82    },
83    ClosePath {
84        abs: bool,
85    },
86}
87
88impl PathSegment {
89    /// Sets the segment absolute value.
90    pub fn set_absolute(&mut self, new_abs: bool) {
91        match *self {
92            PathSegment::MoveTo { ref mut abs, .. }
93            | PathSegment::LineTo { ref mut abs, .. }
94            | PathSegment::HorizontalLineTo { ref mut abs, .. }
95            | PathSegment::VerticalLineTo { ref mut abs, .. }
96            | PathSegment::CurveTo { ref mut abs, .. }
97            | PathSegment::SmoothCurveTo { ref mut abs, .. }
98            | PathSegment::Quadratic { ref mut abs, .. }
99            | PathSegment::SmoothQuadratic { ref mut abs, .. }
100            | PathSegment::EllipticalArc { ref mut abs, .. }
101            | PathSegment::ClosePath { ref mut abs, .. } => {
102                *abs = new_abs;
103            }
104        }
105    }
106
107    /// Returns a segment type.
108    pub fn cmd(&self) -> PathCommand {
109        match *self {
110            PathSegment::MoveTo { .. } => PathCommand::MoveTo,
111            PathSegment::LineTo { .. } => PathCommand::LineTo,
112            PathSegment::HorizontalLineTo { .. } => PathCommand::HorizontalLineTo,
113            PathSegment::VerticalLineTo { .. } => PathCommand::VerticalLineTo,
114            PathSegment::CurveTo { .. } => PathCommand::CurveTo,
115            PathSegment::SmoothCurveTo { .. } => PathCommand::SmoothCurveTo,
116            PathSegment::Quadratic { .. } => PathCommand::Quadratic,
117            PathSegment::SmoothQuadratic { .. } => PathCommand::SmoothQuadratic,
118            PathSegment::EllipticalArc { .. } => PathCommand::EllipticalArc,
119            PathSegment::ClosePath { .. } => PathCommand::ClosePath,
120        }
121    }
122
123    /// Returns `true` if the segment is absolute.
124    #[inline]
125    pub fn is_absolute(&self) -> bool {
126        match *self {
127            PathSegment::MoveTo { abs, .. }
128            | PathSegment::LineTo { abs, .. }
129            | PathSegment::HorizontalLineTo { abs, .. }
130            | PathSegment::VerticalLineTo { abs, .. }
131            | PathSegment::CurveTo { abs, .. }
132            | PathSegment::SmoothCurveTo { abs, .. }
133            | PathSegment::Quadratic { abs, .. }
134            | PathSegment::SmoothQuadratic { abs, .. }
135            | PathSegment::EllipticalArc { abs, .. }
136            | PathSegment::ClosePath { abs, .. } => abs,
137        }
138    }
139
140    #[inline]
141    /// Returns `true` if the segment is relative.
142    pub fn is_relative(&self) -> bool {
143        !self.is_absolute()
144    }
145
146    /// Returns the `x` coordinate of the segment if it has one.
147    pub fn x(&self) -> Option<f64> {
148        match *self {
149            PathSegment::MoveTo { x, .. }
150            | PathSegment::LineTo { x, .. }
151            | PathSegment::HorizontalLineTo { x, .. }
152            | PathSegment::CurveTo { x, .. }
153            | PathSegment::SmoothCurveTo { x, .. }
154            | PathSegment::Quadratic { x, .. }
155            | PathSegment::SmoothQuadratic { x, .. }
156            | PathSegment::EllipticalArc { x, .. } => Some(x),
157
158            PathSegment::VerticalLineTo { .. } | PathSegment::ClosePath { .. } => None,
159        }
160    }
161
162    /// Returns the `y` coordinate of the segment if it has one.
163    pub fn y(&self) -> Option<f64> {
164        match *self {
165            PathSegment::MoveTo { y, .. }
166            | PathSegment::LineTo { y, .. }
167            | PathSegment::VerticalLineTo { y, .. }
168            | PathSegment::CurveTo { y, .. }
169            | PathSegment::SmoothCurveTo { y, .. }
170            | PathSegment::Quadratic { y, .. }
171            | PathSegment::SmoothQuadratic { y, .. }
172            | PathSegment::EllipticalArc { y, .. } => Some(y),
173
174            PathSegment::HorizontalLineTo { .. } | PathSegment::ClosePath { .. } => None,
175        }
176    }
177}
178
179impl FuzzyEq for PathSegment {
180    fn fuzzy_eq(&self, other: &Self) -> bool {
181        use self::PathSegment as Seg;
182
183        // TODO: find a way to wrap it in macro
184        match (*self, *other) {
185            (
186                Seg::MoveTo { abs, x, y },
187                Seg::MoveTo {
188                    abs: oabs,
189                    x: ox,
190                    y: oy,
191                },
192            )
193            | (
194                Seg::LineTo { abs, x, y },
195                Seg::LineTo {
196                    abs: oabs,
197                    x: ox,
198                    y: oy,
199                },
200            )
201            | (
202                Seg::SmoothQuadratic { abs, x, y },
203                Seg::SmoothQuadratic {
204                    abs: oabs,
205                    x: ox,
206                    y: oy,
207                },
208            ) => abs == oabs && x.fuzzy_eq(&ox) && y.fuzzy_eq(&oy),
209            (Seg::HorizontalLineTo { abs, x }, Seg::HorizontalLineTo { abs: oabs, x: ox }) => {
210                abs == oabs && x.fuzzy_eq(&ox)
211            }
212            (Seg::VerticalLineTo { abs, y }, Seg::VerticalLineTo { abs: oabs, y: oy }) => {
213                abs == oabs && y.fuzzy_eq(&oy)
214            }
215            (
216                Seg::CurveTo {
217                    abs,
218                    x1,
219                    y1,
220                    x2,
221                    y2,
222                    x,
223                    y,
224                },
225                Seg::CurveTo {
226                    abs: oabs,
227                    x1: ox1,
228                    y1: oy1,
229                    x2: ox2,
230                    y2: oy2,
231                    x: ox,
232                    y: oy,
233                },
234            ) => {
235                abs == oabs
236                    && x.fuzzy_eq(&ox)
237                    && y.fuzzy_eq(&oy)
238                    && x1.fuzzy_eq(&ox1)
239                    && y1.fuzzy_eq(&oy1)
240                    && x2.fuzzy_eq(&ox2)
241                    && y2.fuzzy_eq(&oy2)
242            }
243            (
244                Seg::SmoothCurveTo { abs, x2, y2, x, y },
245                Seg::SmoothCurveTo {
246                    abs: oabs,
247                    x2: ox2,
248                    y2: oy2,
249                    x: ox,
250                    y: oy,
251                },
252            ) => {
253                abs == oabs
254                    && x.fuzzy_eq(&ox)
255                    && y.fuzzy_eq(&oy)
256                    && x2.fuzzy_eq(&ox2)
257                    && y2.fuzzy_eq(&oy2)
258            }
259            (
260                Seg::Quadratic { abs, x1, y1, x, y },
261                Seg::Quadratic {
262                    abs: oabs,
263                    x1: ox1,
264                    y1: oy1,
265                    x: ox,
266                    y: oy,
267                },
268            ) => {
269                abs == oabs
270                    && x.fuzzy_eq(&ox)
271                    && y.fuzzy_eq(&oy)
272                    && x1.fuzzy_eq(&ox1)
273                    && y1.fuzzy_eq(&oy1)
274            }
275            (
276                Seg::EllipticalArc {
277                    abs,
278                    rx,
279                    ry,
280                    x_axis_rotation,
281                    large_arc,
282                    sweep,
283                    x,
284                    y,
285                },
286                Seg::EllipticalArc {
287                    abs: oabs,
288                    rx: orx,
289                    ry: ory,
290                    x_axis_rotation: ox_axis_rotation,
291                    large_arc: olarge_arc,
292                    sweep: osweep,
293                    x: ox,
294                    y: oy,
295                },
296            ) => {
297                abs == oabs
298                    && x.fuzzy_eq(&ox)
299                    && y.fuzzy_eq(&oy)
300                    && rx.fuzzy_eq(&orx)
301                    && ry.fuzzy_eq(&ory)
302                    && x_axis_rotation.fuzzy_eq(&ox_axis_rotation)
303                    && large_arc == olarge_arc
304                    && sweep == osweep
305            }
306            (Seg::ClosePath { abs }, Seg::ClosePath { abs: oabs }) => abs == oabs,
307            _ => false,
308        }
309    }
310}
311
312#[cfg(test)]
313mod fuzzy_eq_tests {
314    use super::*;
315
316    macro_rules! test {
317        ($name:ident,  $seg1:expr, $seg2:expr) => {
318            #[test]
319            fn $name() {
320                assert!($seg1 != $seg2);
321                assert!($seg1.fuzzy_eq(&$seg2));
322            }
323        };
324    }
325
326    // TODO: find a better way
327
328    test!(
329        m,
330        PathSegment::MoveTo {
331            abs: true,
332            x: 10.0,
333            y: 10.1 + 10.2
334        },
335        PathSegment::MoveTo {
336            abs: true,
337            x: 10.0,
338            y: 20.3
339        }
340    );
341
342    test!(
343        l,
344        PathSegment::LineTo {
345            abs: true,
346            x: 10.0,
347            y: 10.1 + 10.2
348        },
349        PathSegment::LineTo {
350            abs: true,
351            x: 10.0,
352            y: 20.3
353        }
354    );
355
356    test!(
357        h,
358        PathSegment::HorizontalLineTo {
359            abs: true,
360            x: 10.1 + 10.2
361        },
362        PathSegment::HorizontalLineTo { abs: true, x: 20.3 }
363    );
364
365    test!(
366        v,
367        PathSegment::VerticalLineTo {
368            abs: true,
369            y: 10.1 + 10.2
370        },
371        PathSegment::VerticalLineTo { abs: true, y: 20.3 }
372    );
373
374    test!(
375        c,
376        PathSegment::CurveTo {
377            abs: true,
378            x1: 10.0,
379            y1: 10.1 + 10.2,
380            x2: 10.0,
381            y2: 10.0,
382            x: 10.0,
383            y: 10.0
384        },
385        PathSegment::CurveTo {
386            abs: true,
387            x1: 10.0,
388            y1: 20.3,
389            x2: 10.0,
390            y2: 10.0,
391            x: 10.0,
392            y: 10.0
393        }
394    );
395
396    test!(
397        s,
398        PathSegment::SmoothCurveTo {
399            abs: true,
400            x2: 10.0,
401            y2: 10.1 + 10.2,
402            x: 10.0,
403            y: 10.0
404        },
405        PathSegment::SmoothCurveTo {
406            abs: true,
407            x2: 10.0,
408            y2: 20.3,
409            x: 10.0,
410            y: 10.0
411        }
412    );
413
414    test!(
415        q,
416        PathSegment::Quadratic {
417            abs: true,
418            x1: 10.0,
419            y1: 10.1 + 10.2,
420            x: 10.0,
421            y: 10.0
422        },
423        PathSegment::Quadratic {
424            abs: true,
425            x1: 10.0,
426            y1: 20.3,
427            x: 10.0,
428            y: 10.0
429        }
430    );
431
432    test!(
433        t,
434        PathSegment::SmoothQuadratic {
435            abs: true,
436            x: 10.0,
437            y: 10.1 + 10.2
438        },
439        PathSegment::SmoothQuadratic {
440            abs: true,
441            x: 10.0,
442            y: 20.3
443        }
444    );
445
446    test!(
447        a,
448        PathSegment::EllipticalArc {
449            abs: true,
450            rx: 100.0,
451            ry: 100.0,
452            x_axis_rotation: 0.0,
453            large_arc: true,
454            sweep: true,
455            x: 10.1 + 10.2,
456            y: 10.0,
457        },
458        PathSegment::EllipticalArc {
459            abs: true,
460            rx: 100.0,
461            ry: 100.0,
462            x_axis_rotation: 0.0,
463            large_arc: true,
464            sweep: true,
465            x: 20.3,
466            y: 10.0,
467        }
468    );
469}