kludgine_core/shape/
mod.rs

1mod batch;
2mod circle;
3mod fill;
4mod geometry;
5mod path;
6mod stroke;
7
8use circle::Circle;
9use easygpu_lyon::lyon_tessellation;
10use figures::{Displayable, Figure, Points, Rectlike, Scale};
11use geometry::ShapeGeometry;
12
13pub use self::batch::*;
14pub use self::fill::*;
15pub use self::path::*;
16pub use self::stroke::*;
17use crate::math::{Pixels, Point, Rect, Scaled};
18use crate::scene::{Element, Target};
19
20/// A 2d shape.
21#[derive(Debug, Clone)]
22pub struct Shape<Unit> {
23    geometry: ShapeGeometry<Unit>,
24    stroke: Option<Stroke>,
25    fill: Option<Fill>,
26}
27
28impl<Unit> Default for Shape<Unit> {
29    fn default() -> Self {
30        Self {
31            geometry: ShapeGeometry::Empty,
32            stroke: None,
33            fill: None,
34        }
35    }
36}
37
38impl<Unit> Shape<Unit> {
39    /// Returns a rectangle.
40    pub fn rect(rect: impl Into<Rect<f32, Unit>>) -> Self {
41        let rect = rect.into().as_extents();
42        let path = PathBuilder::new(Point::from_figures(rect.origin.x(), rect.origin.y()))
43            .line_to(Point::from_figures(rect.extent.x(), rect.origin.y()))
44            .line_to(Point::from_figures(rect.extent.x(), rect.extent.y()))
45            .line_to(Point::from_figures(rect.origin.x(), rect.extent.y()))
46            .close()
47            .build();
48
49        Self {
50            geometry: ShapeGeometry::Path(path),
51            stroke: None,
52            fill: None,
53        }
54    }
55
56    /// Returns a circle with `center` and `radius`.
57    #[must_use]
58    pub const fn circle(center: Point<f32, Unit>, radius: Figure<f32, Unit>) -> Self {
59        Self {
60            geometry: ShapeGeometry::Circle(Circle { center, radius }),
61            stroke: None,
62            fill: None,
63        }
64    }
65
66    /// Returns a closed polygon created with `points`.
67    #[must_use]
68    pub fn polygon(points: impl IntoIterator<Item = Point<f32, Unit>>) -> Self {
69        let mut points = points.into_iter();
70        if let Some(start) = points.next() {
71            let mut builder = PathBuilder::new(start);
72            for point in points {
73                builder = builder.line_to(point);
74            }
75
76            Self {
77                geometry: ShapeGeometry::Path(builder.close().build()),
78                stroke: None,
79                fill: None,
80            }
81        } else {
82            Self::default()
83        }
84    }
85
86    /// Builder-style function. Set `fill` and returns self.
87    #[must_use]
88    pub const fn fill(mut self, fill: Fill) -> Self {
89        self.fill = Some(fill);
90        self
91    }
92
93    /// Builder-style function. Set `stroke` and returns self.
94    #[must_use]
95    pub const fn stroke(mut self, stroke: Stroke) -> Self {
96        self.stroke = Some(stroke);
97        self
98    }
99
100    /// Returns the shape with the geometry casted to the unit provided. This
101    /// does not change the underlying shape data at all.
102    #[must_use]
103    pub fn cast_unit<U>(self) -> Shape<U> {
104        Shape {
105            geometry: self.geometry.cast_unit(),
106            fill: self.fill,
107            stroke: self.stroke,
108        }
109    }
110}
111
112impl<Unit> Shape<Unit>
113where
114    Self: Displayable<f32, Pixels = Shape<Pixels>>,
115{
116    /// Renders the shape within `scene`. Uses the coordinates in the shape
117    /// without translation.
118    pub fn render(&self, scene: &Target) {
119        self.render_at(&Point::<f32, Pixels>::default(), scene);
120    }
121
122    /// Renders the shape at `location` within `scene`.
123    pub fn render_at(
124        &self,
125        location: &impl Displayable<f32, Pixels = Point<f32, Pixels>>,
126        scene: &Target,
127    ) {
128        let location = location.to_pixels(scene.scale());
129        let pixel_shape = self.to_pixels(scene.scale());
130        let translated = pixel_shape.convert_from_user_to_device(location, scene);
131        scene.push_element(Element::Shape(translated));
132    }
133}
134
135impl Shape<Pixels> {
136    fn convert_from_user_to_device(&self, location: Point<f32, Pixels>, scene: &Target) -> Self {
137        Self {
138            geometry: self
139                .geometry
140                .translate_and_convert_to_device(location, scene),
141            fill: self.fill.clone(),
142            stroke: self.stroke.clone(),
143        }
144    }
145}
146
147impl Shape<Pixels> {
148    pub(crate) fn build(&self, builder: &mut easygpu_lyon::ShapeBuilder) -> crate::Result<()> {
149        self.geometry.build(builder, &self.stroke, &self.fill)
150    }
151}
152
153impl<Src, Dst> std::ops::Mul<Scale<f32, Src, Dst>> for Shape<Src> {
154    type Output = Shape<Dst>;
155
156    fn mul(self, scale: Scale<f32, Src, Dst>) -> Self::Output {
157        Self::Output {
158            geometry: self.geometry * scale,
159            fill: self.fill,
160            stroke: self.stroke,
161        }
162    }
163}
164
165impl Displayable<f32> for Shape<Pixels> {
166    type Pixels = Self;
167    type Points = Shape<Points>;
168    type Scaled = Shape<Scaled>;
169
170    fn to_pixels(&self, _scale: &figures::DisplayScale<f32>) -> Self::Pixels {
171        self.clone()
172    }
173
174    fn to_points(&self, scale: &figures::DisplayScale<f32>) -> Self::Points {
175        Shape {
176            geometry: self.geometry.to_points(scale),
177            stroke: self.stroke.clone(),
178            fill: self.fill.clone(),
179        }
180    }
181
182    fn to_scaled(&self, scale: &figures::DisplayScale<f32>) -> Self::Scaled {
183        Shape {
184            geometry: self.geometry.to_scaled(scale),
185            stroke: self.stroke.clone(),
186            fill: self.fill.clone(),
187        }
188    }
189}
190
191impl Displayable<f32> for Shape<Points> {
192    type Pixels = Shape<Pixels>;
193    type Points = Self;
194    type Scaled = Shape<Scaled>;
195
196    fn to_pixels(&self, scale: &figures::DisplayScale<f32>) -> Self::Pixels {
197        Shape {
198            geometry: self.geometry.to_pixels(scale),
199            stroke: self.stroke.clone(),
200            fill: self.fill.clone(),
201        }
202    }
203
204    fn to_points(&self, _scale: &figures::DisplayScale<f32>) -> Self::Points {
205        self.clone()
206    }
207
208    fn to_scaled(&self, scale: &figures::DisplayScale<f32>) -> Self::Scaled {
209        Shape {
210            geometry: self.geometry.to_scaled(scale),
211            stroke: self.stroke.clone(),
212            fill: self.fill.clone(),
213        }
214    }
215}
216
217impl Displayable<f32> for Shape<Scaled> {
218    type Pixels = Shape<Pixels>;
219    type Points = Shape<Points>;
220    type Scaled = Self;
221
222    fn to_pixels(&self, scale: &figures::DisplayScale<f32>) -> Self::Pixels {
223        Shape {
224            geometry: self.geometry.to_pixels(scale),
225            stroke: self.stroke.clone(),
226            fill: self.fill.clone(),
227        }
228    }
229
230    fn to_points(&self, scale: &figures::DisplayScale<f32>) -> Self::Points {
231        Shape {
232            geometry: self.geometry.to_points(scale),
233            stroke: self.stroke.clone(),
234            fill: self.fill.clone(),
235        }
236    }
237
238    fn to_scaled(&self, _scale: &figures::DisplayScale<f32>) -> Self::Scaled {
239        self.clone()
240    }
241}
242
243const fn lyon_point<T>(pt: Point<f32, T>) -> lyon_tessellation::math::Point {
244    lyon_tessellation::math::Point::new(pt.x, pt.y)
245}