1use anyhow::Error;
2use etagere::euclid::Vector2D;
3use lyon::geom::Angle;
4use lyon::tessellation::{
5 BuffersBuilder, FillTessellator, FillVertex, StrokeTessellator, StrokeVertex, VertexBuffers,
6};
7
8pub use lyon::math::Transform;
9pub use lyon::tessellation::{FillOptions, FillRule, StrokeOptions};
10
11use crate::{Path, Pixels, Point, point, px};
12
13/// Style of the PathBuilder
14pub enum PathStyle {
15 /// Stroke style
16 Stroke(StrokeOptions),
17 /// Fill style
18 Fill(FillOptions),
19}
20
21/// A [`Path`] builder.
22pub struct PathBuilder {
23 raw: lyon::path::builder::WithSvg<lyon::path::BuilderImpl>,
24 transform: Option<lyon::math::Transform>,
25 /// PathStyle of the PathBuilder
26 pub style: PathStyle,
27}
28
29impl From<lyon::path::Builder> for PathBuilder {
30 fn from(builder: lyon::path::Builder) -> Self {
31 Self {
32 raw: builder.with_svg(),
33 ..Default::default()
34 }
35 }
36}
37
38impl From<lyon::path::builder::WithSvg<lyon::path::BuilderImpl>> for PathBuilder {
39 fn from(raw: lyon::path::builder::WithSvg<lyon::path::BuilderImpl>) -> Self {
40 Self {
41 raw,
42 ..Default::default()
43 }
44 }
45}
46
47impl From<lyon::math::Point> for Point<Pixels> {
48 fn from(p: lyon::math::Point) -> Self {
49 point(px(p.x), px(p.y))
50 }
51}
52
53impl From<Point<Pixels>> for lyon::math::Point {
54 fn from(p: Point<Pixels>) -> Self {
55 lyon::math::point(p.x.0, p.y.0)
56 }
57}
58
59impl Default for PathBuilder {
60 fn default() -> Self {
61 Self {
62 raw: lyon::path::Path::builder().with_svg(),
63 style: PathStyle::Fill(FillOptions::default()),
64 transform: None,
65 }
66 }
67}
68
69impl PathBuilder {
70 /// Creates a new [`PathBuilder`] to build a Stroke path.
71 pub fn stroke(width: Pixels) -> Self {
72 Self {
73 style: PathStyle::Stroke(StrokeOptions::default().with_line_width(width.0)),
74 ..Self::default()
75 }
76 }
77
78 /// Creates a new [`PathBuilder`] to build a Fill path.
79 pub fn fill() -> Self {
80 Self::default()
81 }
82
83 /// Sets the style of the [`PathBuilder`].
84 pub fn with_style(self, style: PathStyle) -> Self {
85 Self { style, ..self }
86 }
87
88 /// Move the current point to the given point.
89 #[inline]
90 pub fn move_to(&mut self, to: Point<Pixels>) {
91 self.raw.move_to(to.into());
92 }
93
94 /// Draw a straight line from the current point to the given point.
95 #[inline]
96 pub fn line_to(&mut self, to: Point<Pixels>) {
97 self.raw.line_to(to.into());
98 }
99
100 /// Draw a curve from the current point to the given point, using the given control point.
101 #[inline]
102 pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
103 self.raw.quadratic_bezier_to(ctrl.into(), to.into());
104 }
105
106 /// Adds a cubic Bézier to the [`Path`] given its two control points
107 /// and its end point.
108 #[inline]
109 pub fn cubic_bezier_to(
110 &mut self,
111 to: Point<Pixels>,
112 control_a: Point<Pixels>,
113 control_b: Point<Pixels>,
114 ) {
115 self.raw
116 .cubic_bezier_to(control_a.into(), control_b.into(), to.into());
117 }
118
119 /// Close the current sub-path.
120 #[inline]
121 pub fn close(&mut self) {
122 self.raw.close();
123 }
124
125 /// Applies a transform to the path.
126 #[inline]
127 pub fn transform(&mut self, transform: Transform) {
128 self.transform = Some(transform);
129 }
130
131 /// Applies a translation to the path.
132 #[inline]
133 pub fn translate(&mut self, to: Point<Pixels>) {
134 if let Some(transform) = self.transform {
135 self.transform = Some(transform.then_translate(Vector2D::new(to.x.0, to.y.0)));
136 } else {
137 self.transform = Some(Transform::translation(to.x.0, to.y.0))
138 }
139 }
140
141 /// Applies a scale to the path.
142 #[inline]
143 pub fn scale(&mut self, scale: f32) {
144 if let Some(transform) = self.transform {
145 self.transform = Some(transform.then_scale(scale, scale));
146 } else {
147 self.transform = Some(Transform::scale(scale, scale));
148 }
149 }
150
151 /// Applies a rotation to the path.
152 ///
153 /// The `angle` is in degrees value in the range 0.0 to 360.0.
154 #[inline]
155 pub fn rotate(&mut self, angle: f32) {
156 let radians = angle.to_radians();
157 if let Some(transform) = self.transform {
158 self.transform = Some(transform.then_rotate(Angle::radians(radians)));
159 } else {
160 self.transform = Some(Transform::rotation(Angle::radians(radians)));
161 }
162 }
163
164 /// Builds into a [`Path`].
165 #[inline]
166 pub fn build(self) -> Result<Path<Pixels>, Error> {
167 let path = if let Some(transform) = self.transform {
168 self.raw.build().transformed(&transform)
169 } else {
170 self.raw.build()
171 };
172
173 match self.style {
174 PathStyle::Stroke(options) => Self::tessellate_stroke(&path, &options),
175 PathStyle::Fill(options) => Self::tessellate_fill(&path, &options),
176 }
177 }
178
179 fn tessellate_fill(
180 path: &lyon::path::Path,
181 options: &FillOptions,
182 ) -> Result<Path<Pixels>, Error> {
183 // Will contain the result of the tessellation.
184 let mut buf: VertexBuffers<lyon::math::Point, u16> = VertexBuffers::new();
185 let mut tessellator = FillTessellator::new();
186
187 // Compute the tessellation.
188 tessellator.tessellate_path(
189 path,
190 options,
191 &mut BuffersBuilder::new(&mut buf, |vertex: FillVertex| vertex.position()),
192 )?;
193
194 Ok(Self::build_path(buf))
195 }
196
197 fn tessellate_stroke(
198 path: &lyon::path::Path,
199 options: &StrokeOptions,
200 ) -> Result<Path<Pixels>, Error> {
201 // Will contain the result of the tessellation.
202 let mut buf: VertexBuffers<lyon::math::Point, u16> = VertexBuffers::new();
203 let mut tessellator = StrokeTessellator::new();
204
205 // Compute the tessellation.
206 tessellator.tessellate_path(
207 path,
208 options,
209 &mut BuffersBuilder::new(&mut buf, |vertex: StrokeVertex| vertex.position()),
210 )?;
211
212 Ok(Self::build_path(buf))
213 }
214
215 /// Builds a [`Path`] from a [`lyon::VertexBuffers`].
216 pub fn build_path(buf: VertexBuffers<lyon::math::Point, u16>) -> Path<Pixels> {
217 if buf.vertices.is_empty() {
218 return Path::new(Point::default());
219 }
220
221 let first_point = buf.vertices[0];
222
223 let mut path = Path::new(first_point.into());
224 for i in 0..buf.indices.len() / 3 {
225 let i0 = buf.indices[i * 3] as usize;
226 let i1 = buf.indices[i * 3 + 1] as usize;
227 let i2 = buf.indices[i * 3 + 2] as usize;
228
229 let v0 = buf.vertices[i0];
230 let v1 = buf.vertices[i1];
231 let v2 = buf.vertices[i2];
232
233 path.push_triangle(
234 (v0.into(), v1.into(), v2.into()),
235 (point(0., 1.), point(0., 1.), point(0., 1.)),
236 );
237 }
238
239 path
240 }
241}