kludgine_core/shape/
path.rs

1use easygpu_lyon::lyon_tessellation::path::PathEvent as LyonPathEvent;
2use easygpu_lyon::lyon_tessellation::{self};
3use figures::{Displayable, Points};
4
5use super::lyon_point;
6use crate::math::{Pixels, Point, Scale, Scaled};
7use crate::scene::Target;
8use crate::shape::{Fill, Stroke};
9use crate::Error;
10
11/// A point on a [`Path`].
12pub type Endpoint<S> = Point<f32, S>;
13/// A control point used to create curves.
14pub type ControlPoint<S> = Point<f32, S>;
15
16/// An entry in a [`Path`].
17#[derive(Debug, Clone, Copy)]
18pub enum PathEvent<S> {
19    /// Begins a path. Must be at the start.
20    Begin {
21        /// The location to begin at.
22        at: Endpoint<S>,
23    },
24    /// A straight line segment.
25    Line {
26        /// The origin of the line.
27        from: Endpoint<S>,
28        /// The end location of the line.
29        to: Endpoint<S>,
30    },
31    /// A quadratic curve (one control point).
32    Quadratic {
33        /// The origin of the curve.
34        from: Endpoint<S>,
35        /// The control point for the curve.
36        ctrl: ControlPoint<S>,
37        /// The end location of the curve.
38        to: Endpoint<S>,
39    },
40    /// A cubic curve (two control points).
41    Cubic {
42        /// The origin of the curve.
43        from: Endpoint<S>,
44        /// The first control point for the curve.
45        ctrl1: ControlPoint<S>,
46        /// The second control point for the curve.
47        ctrl2: ControlPoint<S>,
48        /// The end location of the curve.
49        to: Endpoint<S>,
50    },
51    /// Ends the path. Must be the last entry.
52    End {
53        /// The end location of the path.
54        last: Endpoint<S>,
55        /// The start location of the path.
56        first: Endpoint<S>,
57        /// Whether the path should be closed.
58        close: bool,
59    },
60}
61
62impl From<PathEvent<Pixels>> for LyonPathEvent {
63    fn from(event: PathEvent<Pixels>) -> Self {
64        match event {
65            PathEvent::Begin { at } => Self::Begin { at: lyon_point(at) },
66            PathEvent::Line { from, to } => Self::Line {
67                from: lyon_point(from),
68                to: lyon_point(to),
69            },
70            PathEvent::Quadratic { from, ctrl, to } => Self::Quadratic {
71                from: lyon_point(from),
72                ctrl: lyon_point(ctrl),
73                to: lyon_point(to),
74            },
75            PathEvent::Cubic {
76                from,
77                ctrl1,
78                ctrl2,
79                to,
80            } => Self::Cubic {
81                from: lyon_point(from),
82                ctrl1: lyon_point(ctrl1),
83                ctrl2: lyon_point(ctrl2),
84                to: lyon_point(to),
85            },
86            PathEvent::End { last, first, close } => Self::End {
87                last: lyon_point(last),
88                first: lyon_point(first),
89                close,
90            },
91        }
92    }
93}
94
95impl<U> PathEvent<U> {
96    /// Returns the path event with the new unit. Does not alter the underlying
97    /// coordinate data.
98    #[must_use]
99    pub fn cast_unit<V>(self) -> PathEvent<V> {
100        match self {
101            Self::Begin { at } => PathEvent::Begin { at: at.cast_unit() },
102            Self::Line { from, to } => PathEvent::Line {
103                from: from.cast_unit(),
104                to: to.cast_unit(),
105            },
106            Self::Quadratic { from, ctrl, to } => PathEvent::Quadratic {
107                from: from.cast_unit(),
108                ctrl: ctrl.cast_unit(),
109                to: to.cast_unit(),
110            },
111            Self::Cubic {
112                from,
113                ctrl1,
114                ctrl2,
115                to,
116            } => PathEvent::Cubic {
117                from: from.cast_unit(),
118                ctrl1: ctrl1.cast_unit(),
119                ctrl2: ctrl2.cast_unit(),
120                to: to.cast_unit(),
121            },
122            Self::End { last, first, close } => PathEvent::End {
123                last: last.cast_unit(),
124                first: first.cast_unit(),
125                close,
126            },
127        }
128    }
129}
130
131/// A geometric shape defined by a path.
132#[derive(Default, Debug, Clone)]
133pub struct Path<S> {
134    events: Vec<PathEvent<S>>,
135}
136
137impl<U> Path<U> {
138    /// Returns the path with the new unit. Does not alter the underlying
139    /// coordinate data.
140    #[must_use]
141    pub fn cast_unit<V>(self) -> Path<V> {
142        Path {
143            events: self.events.into_iter().map(PathEvent::cast_unit).collect(),
144        }
145    }
146}
147
148impl Path<Pixels> {
149    pub(crate) fn translate_and_convert_to_device(
150        &self,
151        location: Point<f32, Pixels>,
152        scene: &Target,
153    ) -> Self {
154        let location = scene.offset_point_raw(location);
155        let mut events = Vec::new();
156
157        for event in &self.events {
158            // There's a bug with async-local variables and this analysis. There is no
159            // cross-dependency on any of these parameters.
160            events.push(match event {
161                PathEvent::Begin { at } => PathEvent::Begin { at: *at + location },
162                PathEvent::Line { from, to } => PathEvent::Line {
163                    from: *from + location,
164                    to: *to + location,
165                },
166                PathEvent::End { first, last, close } => PathEvent::End {
167                    first: *first + location,
168                    last: *last + location,
169                    close: *close,
170                },
171                PathEvent::Quadratic { from, ctrl, to } => PathEvent::Quadratic {
172                    from: *from + location,
173                    ctrl: *ctrl + location,
174                    to: *to + location,
175                },
176                PathEvent::Cubic {
177                    from,
178                    ctrl1,
179                    ctrl2,
180                    to,
181                } => PathEvent::Cubic {
182                    from: *from + location,
183                    ctrl1: *ctrl1 + location,
184                    ctrl2: *ctrl2 + location,
185                    to: *to + location,
186                },
187            });
188        }
189
190        Self { events }
191    }
192}
193
194impl Path<Pixels> {
195    pub(crate) fn build(
196        &self,
197        builder: &mut easygpu_lyon::ShapeBuilder,
198        stroke: &Option<Stroke>,
199        fill: &Option<Fill>,
200    ) -> crate::Result<()> {
201        let path = self.as_lyon();
202        if let Some(fill) = fill {
203            builder.default_color = fill.color.rgba();
204            builder
205                .fill(&path, &fill.options)
206                .map_err(Error::Tessellation)?;
207        }
208
209        if let Some(stroke) = stroke {
210            builder.default_color = stroke.color.rgba();
211            builder
212                .stroke(&path, &stroke.options)
213                .map_err(Error::Tessellation)?;
214        }
215
216        Ok(())
217    }
218
219    pub(crate) fn as_lyon(&self) -> lyon_tessellation::path::Path {
220        let mut builder = lyon_tessellation::path::Path::builder();
221        for &event in &self.events {
222            builder.path_event(event.into());
223        }
224        builder.build()
225    }
226}
227
228impl<S, T> From<T> for Path<S>
229where
230    T: IntoIterator<Item = PathEvent<S>>,
231{
232    fn from(source: T) -> Self {
233        Self {
234            events: source.into_iter().collect(),
235        }
236    }
237}
238
239/// Builds a [`Path`].
240pub struct PathBuilder<S> {
241    path: Path<S>,
242    start_at: Endpoint<S>,
243    current_location: Endpoint<S>,
244    close: bool,
245}
246
247impl<S> PathBuilder<S> {
248    /// Creates a new path with the initial position `start_at`.
249    #[must_use]
250    pub fn new(start_at: Endpoint<S>) -> Self {
251        let events = vec![PathEvent::Begin { at: start_at }];
252        Self {
253            path: Path::from(events),
254            start_at,
255            current_location: start_at,
256            close: false,
257        }
258    }
259
260    /// Returns the built path.
261    #[must_use]
262    pub fn build(mut self) -> Path<S> {
263        self.path.events.push(PathEvent::End {
264            first: self.start_at,
265            last: self.current_location,
266            close: self.close,
267        });
268        self.path
269    }
270
271    /// Create a straight line from the current location to `end_at`.
272    #[must_use]
273    pub fn line_to(mut self, end_at: Endpoint<S>) -> Self {
274        self.path.events.push(PathEvent::Line {
275            from: self.current_location,
276            to: end_at,
277        });
278        self.current_location = end_at;
279        self
280    }
281
282    /// Create a quadratic curve from the current location to `end_at` using
283    /// `control` as the curve's control point.
284    #[must_use]
285    pub fn quadratic_curve_to(mut self, control: ControlPoint<S>, end_at: Endpoint<S>) -> Self {
286        self.path.events.push(PathEvent::Quadratic {
287            from: self.current_location,
288            ctrl: control,
289            to: end_at,
290        });
291        self.current_location = end_at;
292        self
293    }
294
295    /// Create a cubic curve from the current location to `end_at` using
296    /// `control1` and `control2` as the curve's control points.
297    #[must_use]
298    pub fn cubic_curve_to(
299        mut self,
300        control1: ControlPoint<S>,
301        control2: ControlPoint<S>,
302        end_at: Endpoint<S>,
303    ) -> Self {
304        self.path.events.push(PathEvent::Cubic {
305            from: self.current_location,
306            ctrl1: control1,
307            ctrl2: control2,
308            to: end_at,
309        });
310        self.current_location = end_at;
311        self
312    }
313
314    /// Closes the path, connecting the current location to the shape's starting
315    /// location.
316    #[must_use]
317    pub const fn close(mut self) -> Self {
318        self.close = true;
319        self
320    }
321}
322
323impl<Src, Dst> std::ops::Mul<Scale<f32, Src, Dst>> for Path<Src> {
324    type Output = Path<Dst>;
325
326    fn mul(self, scale: Scale<f32, Src, Dst>) -> Self::Output {
327        Self::Output {
328            events: self.events.into_iter().map(|event| event * scale).collect(),
329        }
330    }
331}
332
333impl<Src, Dst> std::ops::Mul<Scale<f32, Src, Dst>> for PathEvent<Src> {
334    type Output = PathEvent<Dst>;
335
336    fn mul(self, scale: Scale<f32, Src, Dst>) -> Self::Output {
337        match self {
338            PathEvent::Begin { at } => Self::Output::Begin { at: at * scale },
339            PathEvent::Line { from, to } => Self::Output::Line {
340                from: from * scale,
341                to: to * scale,
342            },
343            PathEvent::Quadratic { from, ctrl, to } => Self::Output::Quadratic {
344                from: from * scale,
345                ctrl: ctrl * scale,
346                to: to * scale,
347            },
348            PathEvent::Cubic {
349                from,
350                ctrl1,
351                ctrl2,
352                to,
353            } => Self::Output::Cubic {
354                from: from * scale,
355                ctrl1: ctrl1 * scale,
356                ctrl2: ctrl2 * scale,
357                to: to * scale,
358            },
359            PathEvent::End { last, first, close } => Self::Output::End {
360                last: last * scale,
361                first: first * scale,
362                close,
363            },
364        }
365    }
366}
367
368impl Displayable<f32> for Path<Pixels> {
369    type Pixels = Self;
370    type Points = Path<Points>;
371    type Scaled = Path<Scaled>;
372
373    fn to_pixels(&self, _scale: &figures::DisplayScale<f32>) -> Self::Pixels {
374        self.clone()
375    }
376
377    fn to_points(&self, scale: &figures::DisplayScale<f32>) -> Self::Points {
378        Path {
379            events: self.events.iter().map(|e| e.to_points(scale)).collect(),
380        }
381    }
382
383    fn to_scaled(&self, scale: &figures::DisplayScale<f32>) -> Self::Scaled {
384        Path {
385            events: self.events.iter().map(|e| e.to_scaled(scale)).collect(),
386        }
387    }
388}
389
390impl Displayable<f32> for Path<Points> {
391    type Pixels = Path<Pixels>;
392    type Points = Self;
393    type Scaled = Path<Scaled>;
394
395    fn to_pixels(&self, scale: &figures::DisplayScale<f32>) -> Self::Pixels {
396        Path {
397            events: self.events.iter().map(|e| e.to_pixels(scale)).collect(),
398        }
399    }
400
401    fn to_points(&self, _scale: &figures::DisplayScale<f32>) -> Self::Points {
402        self.clone()
403    }
404
405    fn to_scaled(&self, scale: &figures::DisplayScale<f32>) -> Self::Scaled {
406        Path {
407            events: self.events.iter().map(|e| e.to_scaled(scale)).collect(),
408        }
409    }
410}
411
412impl Displayable<f32> for Path<Scaled> {
413    type Pixels = Path<Pixels>;
414    type Points = Path<Points>;
415    type Scaled = Self;
416
417    fn to_pixels(&self, scale: &figures::DisplayScale<f32>) -> Self::Pixels {
418        Path {
419            events: self.events.iter().map(|e| e.to_pixels(scale)).collect(),
420        }
421    }
422
423    fn to_points(&self, scale: &figures::DisplayScale<f32>) -> Self::Points {
424        Path {
425            events: self.events.iter().map(|e| e.to_points(scale)).collect(),
426        }
427    }
428
429    fn to_scaled(&self, _scale: &figures::DisplayScale<f32>) -> Self::Scaled {
430        self.clone()
431    }
432}
433
434impl Displayable<f32> for PathEvent<Pixels> {
435    type Pixels = Self;
436    type Points = PathEvent<Points>;
437    type Scaled = PathEvent<Scaled>;
438
439    fn to_pixels(&self, _scale: &figures::DisplayScale<f32>) -> Self::Pixels {
440        *self
441    }
442
443    fn to_points(&self, scale: &figures::DisplayScale<f32>) -> Self::Points {
444        match self {
445            Self::Begin { at } => PathEvent::Begin {
446                at: at.to_points(scale),
447            },
448            Self::Line { from, to } => PathEvent::Line {
449                from: from.to_points(scale),
450                to: to.to_points(scale),
451            },
452            Self::Quadratic { from, ctrl, to } => PathEvent::Quadratic {
453                from: from.to_points(scale),
454                ctrl: ctrl.to_points(scale),
455                to: to.to_points(scale),
456            },
457            Self::Cubic {
458                from,
459                ctrl1,
460                ctrl2,
461                to,
462            } => PathEvent::Cubic {
463                from: from.to_points(scale),
464                ctrl1: ctrl1.to_points(scale),
465                ctrl2: ctrl2.to_points(scale),
466                to: to.to_points(scale),
467            },
468            Self::End { last, first, close } => PathEvent::End {
469                last: last.to_points(scale),
470                first: first.to_points(scale),
471                close: *close,
472            },
473        }
474    }
475
476    fn to_scaled(&self, scale: &figures::DisplayScale<f32>) -> Self::Scaled {
477        match self {
478            Self::Begin { at } => PathEvent::Begin {
479                at: at.to_scaled(scale),
480            },
481            Self::Line { from, to } => PathEvent::Line {
482                from: from.to_scaled(scale),
483                to: to.to_scaled(scale),
484            },
485            Self::Quadratic { from, ctrl, to } => PathEvent::Quadratic {
486                from: from.to_scaled(scale),
487                ctrl: ctrl.to_scaled(scale),
488                to: to.to_scaled(scale),
489            },
490            Self::Cubic {
491                from,
492                ctrl1,
493                ctrl2,
494                to,
495            } => PathEvent::Cubic {
496                from: from.to_scaled(scale),
497                ctrl1: ctrl1.to_scaled(scale),
498                ctrl2: ctrl2.to_scaled(scale),
499                to: to.to_scaled(scale),
500            },
501            Self::End { last, first, close } => PathEvent::End {
502                last: last.to_scaled(scale),
503                first: first.to_scaled(scale),
504                close: *close,
505            },
506        }
507    }
508}
509
510impl Displayable<f32> for PathEvent<Points> {
511    type Pixels = PathEvent<Pixels>;
512    type Points = Self;
513    type Scaled = PathEvent<Scaled>;
514
515    fn to_pixels(&self, scale: &figures::DisplayScale<f32>) -> Self::Pixels {
516        match self {
517            Self::Begin { at } => PathEvent::Begin {
518                at: at.to_pixels(scale),
519            },
520            Self::Line { from, to } => PathEvent::Line {
521                from: from.to_pixels(scale),
522                to: to.to_pixels(scale),
523            },
524            Self::Quadratic { from, ctrl, to } => PathEvent::Quadratic {
525                from: from.to_pixels(scale),
526                ctrl: ctrl.to_pixels(scale),
527                to: to.to_pixels(scale),
528            },
529            Self::Cubic {
530                from,
531                ctrl1,
532                ctrl2,
533                to,
534            } => PathEvent::Cubic {
535                from: from.to_pixels(scale),
536                ctrl1: ctrl1.to_pixels(scale),
537                ctrl2: ctrl2.to_pixels(scale),
538                to: to.to_pixels(scale),
539            },
540            Self::End { last, first, close } => PathEvent::End {
541                last: last.to_pixels(scale),
542                first: first.to_pixels(scale),
543                close: *close,
544            },
545        }
546    }
547
548    fn to_points(&self, _scale: &figures::DisplayScale<f32>) -> Self::Points {
549        *self
550    }
551
552    fn to_scaled(&self, scale: &figures::DisplayScale<f32>) -> Self::Scaled {
553        match self {
554            Self::Begin { at } => PathEvent::Begin {
555                at: at.to_scaled(scale),
556            },
557            Self::Line { from, to } => PathEvent::Line {
558                from: from.to_scaled(scale),
559                to: to.to_scaled(scale),
560            },
561            Self::Quadratic { from, ctrl, to } => PathEvent::Quadratic {
562                from: from.to_scaled(scale),
563                ctrl: ctrl.to_scaled(scale),
564                to: to.to_scaled(scale),
565            },
566            Self::Cubic {
567                from,
568                ctrl1,
569                ctrl2,
570                to,
571            } => PathEvent::Cubic {
572                from: from.to_scaled(scale),
573                ctrl1: ctrl1.to_scaled(scale),
574                ctrl2: ctrl2.to_scaled(scale),
575                to: to.to_scaled(scale),
576            },
577            Self::End { last, first, close } => PathEvent::End {
578                last: last.to_scaled(scale),
579                first: first.to_scaled(scale),
580                close: *close,
581            },
582        }
583    }
584}
585
586impl Displayable<f32> for PathEvent<Scaled> {
587    type Pixels = PathEvent<Pixels>;
588    type Points = PathEvent<Points>;
589    type Scaled = Self;
590
591    fn to_pixels(&self, scale: &figures::DisplayScale<f32>) -> Self::Pixels {
592        match self {
593            Self::Begin { at } => PathEvent::Begin {
594                at: at.to_pixels(scale),
595            },
596            Self::Line { from, to } => PathEvent::Line {
597                from: from.to_pixels(scale),
598                to: to.to_pixels(scale),
599            },
600            Self::Quadratic { from, ctrl, to } => PathEvent::Quadratic {
601                from: from.to_pixels(scale),
602                ctrl: ctrl.to_pixels(scale),
603                to: to.to_pixels(scale),
604            },
605            Self::Cubic {
606                from,
607                ctrl1,
608                ctrl2,
609                to,
610            } => PathEvent::Cubic {
611                from: from.to_pixels(scale),
612                ctrl1: ctrl1.to_pixels(scale),
613                ctrl2: ctrl2.to_pixels(scale),
614                to: to.to_pixels(scale),
615            },
616            Self::End { last, first, close } => PathEvent::End {
617                last: last.to_pixels(scale),
618                first: first.to_pixels(scale),
619                close: *close,
620            },
621        }
622    }
623
624    fn to_points(&self, scale: &figures::DisplayScale<f32>) -> Self::Points {
625        match self {
626            Self::Begin { at } => PathEvent::Begin {
627                at: at.to_points(scale),
628            },
629            Self::Line { from, to } => PathEvent::Line {
630                from: from.to_points(scale),
631                to: to.to_points(scale),
632            },
633            Self::Quadratic { from, ctrl, to } => PathEvent::Quadratic {
634                from: from.to_points(scale),
635                ctrl: ctrl.to_points(scale),
636                to: to.to_points(scale),
637            },
638            Self::Cubic {
639                from,
640                ctrl1,
641                ctrl2,
642                to,
643            } => PathEvent::Cubic {
644                from: from.to_points(scale),
645                ctrl1: ctrl1.to_points(scale),
646                ctrl2: ctrl2.to_points(scale),
647                to: to.to_points(scale),
648            },
649            Self::End { last, first, close } => PathEvent::End {
650                last: last.to_points(scale),
651                first: first.to_points(scale),
652                close: *close,
653            },
654        }
655    }
656
657    fn to_scaled(&self, _scale: &figures::DisplayScale<f32>) -> Self::Scaled {
658        *self
659    }
660}