Skip to main content

gpui_authoring/
path_builder.rs

1use anyhow::Error;
2use etagere::euclid::Vector2D;
3use lyon::geom::Angle;
4use lyon::path::traits::SvgPathBuilder;
5use lyon::path::{ArcFlags, Polygon};
6use lyon::tessellation::{
7    BuffersBuilder, FillTessellator, FillVertex, StrokeTessellator, StrokeVertex, VertexBuffers,
8};
9
10pub use lyon::math::Transform;
11pub use lyon::tessellation::{FillOptions, FillRule, StrokeOptions};
12
13use crate::{Path, Pixels, Point, point};
14
15/// Style of the PathBuilder
16pub enum PathStyle {
17    /// Stroke style
18    Stroke(StrokeOptions),
19    /// Fill style
20    Fill(FillOptions),
21}
22
23/// A [`Path`] builder.
24pub struct PathBuilder {
25    raw: lyon::path::builder::WithSvg<lyon::path::BuilderImpl>,
26    transform: Option<lyon::math::Transform>,
27    /// PathStyle of the PathBuilder
28    pub style: PathStyle,
29    dash_array: Option<Vec<Pixels>>,
30}
31
32impl From<lyon::path::Builder> for PathBuilder {
33    fn from(builder: lyon::path::Builder) -> Self {
34        Self {
35            raw: builder.with_svg(),
36            ..Default::default()
37        }
38    }
39}
40
41impl From<lyon::path::builder::WithSvg<lyon::path::BuilderImpl>> for PathBuilder {
42    fn from(raw: lyon::path::builder::WithSvg<lyon::path::BuilderImpl>) -> Self {
43        Self {
44            raw,
45            ..Default::default()
46        }
47    }
48}
49
50impl Default for PathBuilder {
51    fn default() -> Self {
52        Self {
53            raw: lyon::path::Path::builder().with_svg(),
54            style: PathStyle::Fill(FillOptions::default()),
55            transform: None,
56            dash_array: None,
57        }
58    }
59}
60
61impl PathBuilder {
62    /// Creates a new [`PathBuilder`] to build a Stroke path.
63    pub fn stroke(width: Pixels) -> Self {
64        Self {
65            style: PathStyle::Stroke(StrokeOptions::default().with_line_width(width.0)),
66            ..Self::default()
67        }
68    }
69
70    /// Creates a new [`PathBuilder`] to build a Fill path.
71    pub fn fill() -> Self {
72        Self::default()
73    }
74
75    /// Sets the style of the [`PathBuilder`].
76    pub fn with_style(self, style: PathStyle) -> Self {
77        Self { style, ..self }
78    }
79
80    /// Sets the dash array of the [`PathBuilder`].
81    ///
82    /// [MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/stroke-dasharray)
83    pub fn dash_array(mut self, dash_array: &[Pixels]) -> Self {
84        // If an odd number of values is provided, then the list of values is repeated to yield an even number of values.
85        // Thus, 5,3,2 is equivalent to 5,3,2,5,3,2.
86        let array = if dash_array.len() % 2 == 1 {
87            let mut new_dash_array = dash_array.to_vec();
88            new_dash_array.extend_from_slice(dash_array);
89            new_dash_array
90        } else {
91            dash_array.to_vec()
92        };
93
94        self.dash_array = Some(array);
95        self
96    }
97
98    /// Move the current point to the given point.
99    #[inline]
100    pub fn move_to(&mut self, to: Point<Pixels>) {
101        self.raw.move_to(to.into());
102    }
103
104    /// Draw a straight line from the current point to the given point.
105    #[inline]
106    pub fn line_to(&mut self, to: Point<Pixels>) {
107        self.raw.line_to(to.into());
108    }
109
110    /// Draw a curve from the current point to the given point, using the given control point.
111    #[inline]
112    pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
113        self.raw.quadratic_bezier_to(ctrl.into(), to.into());
114    }
115
116    /// Adds a cubic Bézier to the [`Path`] given its two control points
117    /// and its end point.
118    #[inline]
119    pub fn cubic_bezier_to(
120        &mut self,
121        to: Point<Pixels>,
122        control_a: Point<Pixels>,
123        control_b: Point<Pixels>,
124    ) {
125        self.raw
126            .cubic_bezier_to(control_a.into(), control_b.into(), to.into());
127    }
128
129    /// Adds an elliptical arc.
130    pub fn arc_to(
131        &mut self,
132        radii: Point<Pixels>,
133        x_rotation: Pixels,
134        large_arc: bool,
135        sweep: bool,
136        to: Point<Pixels>,
137    ) {
138        self.raw.arc_to(
139            radii.into(),
140            Angle::degrees(x_rotation.into()),
141            ArcFlags { large_arc, sweep },
142            to.into(),
143        );
144    }
145
146    /// Equivalent to `arc_to` in relative coordinates.
147    pub fn relative_arc_to(
148        &mut self,
149        radii: Point<Pixels>,
150        x_rotation: Pixels,
151        large_arc: bool,
152        sweep: bool,
153        to: Point<Pixels>,
154    ) {
155        self.raw.relative_arc_to(
156            radii.into(),
157            Angle::degrees(x_rotation.into()),
158            ArcFlags { large_arc, sweep },
159            to.into(),
160        );
161    }
162
163    /// Adds a polygon.
164    pub fn add_polygon(&mut self, points: &[Point<Pixels>], closed: bool) {
165        let points = points.iter().copied().map(|p| p.into()).collect::<Vec<_>>();
166        self.raw.add_polygon(Polygon {
167            points: points.as_ref(),
168            closed,
169        });
170    }
171
172    /// Close the current sub-path.
173    #[inline]
174    pub fn close(&mut self) {
175        self.raw.close();
176    }
177
178    /// Applies a transform to the path.
179    #[inline]
180    pub fn transform(&mut self, transform: Transform) {
181        self.transform = Some(transform);
182    }
183
184    /// Applies a translation to the path.
185    #[inline]
186    pub fn translate(&mut self, to: Point<Pixels>) {
187        if let Some(transform) = self.transform {
188            self.transform = Some(transform.then_translate(Vector2D::new(to.x.0, to.y.0)));
189        } else {
190            self.transform = Some(Transform::translation(to.x.0, to.y.0))
191        }
192    }
193
194    /// Applies a scale to the path.
195    #[inline]
196    pub fn scale(&mut self, scale: f32) {
197        if let Some(transform) = self.transform {
198            self.transform = Some(transform.then_scale(scale, scale));
199        } else {
200            self.transform = Some(Transform::scale(scale, scale));
201        }
202    }
203
204    /// Applies a rotation to the path.
205    ///
206    /// The `angle` is in degrees value in the range 0.0 to 360.0.
207    #[inline]
208    pub fn rotate(&mut self, angle: f32) {
209        let radians = angle.to_radians();
210        if let Some(transform) = self.transform {
211            self.transform = Some(transform.then_rotate(Angle::radians(radians)));
212        } else {
213            self.transform = Some(Transform::rotation(Angle::radians(radians)));
214        }
215    }
216
217    /// Builds into a [`Path`].
218    #[inline]
219    pub fn build(self) -> Result<Path<Pixels>, Error> {
220        let path = if let Some(transform) = self.transform {
221            self.raw.build().transformed(&transform)
222        } else {
223            self.raw.build()
224        };
225
226        match self.style {
227            PathStyle::Stroke(options) => Self::tessellate_stroke(self.dash_array, &path, &options),
228            PathStyle::Fill(options) => Self::tessellate_fill(&path, &options),
229        }
230    }
231
232    fn tessellate_fill(
233        path: &lyon::path::Path,
234        options: &FillOptions,
235    ) -> Result<Path<Pixels>, Error> {
236        // Will contain the result of the tessellation.
237        let mut buf: VertexBuffers<lyon::math::Point, u16> = VertexBuffers::new();
238        let mut tessellator = FillTessellator::new();
239
240        // Compute the tessellation.
241        tessellator.tessellate_path(
242            path,
243            options,
244            &mut BuffersBuilder::new(&mut buf, |vertex: FillVertex| vertex.position()),
245        )?;
246
247        Ok(Self::build_path(buf))
248    }
249
250    fn tessellate_stroke(
251        dash_array: Option<Vec<Pixels>>,
252        path: &lyon::path::Path,
253        options: &StrokeOptions,
254    ) -> Result<Path<Pixels>, Error> {
255        let path = if let Some(dash_array) = dash_array {
256            let measurements = lyon::algorithms::measure::PathMeasurements::from_path(path, 0.01);
257            let mut sampler = measurements
258                .create_sampler(path, lyon::algorithms::measure::SampleType::Normalized);
259            let mut builder = lyon::path::Path::builder();
260
261            let total_length = sampler.length();
262            let dash_array_len = dash_array.len();
263            let mut pos = 0.;
264            let mut dash_index = 0;
265            while pos < total_length {
266                let dash_length = dash_array[dash_index % dash_array_len].0;
267                let next_pos = (pos + dash_length).min(total_length);
268                if dash_index % 2 == 0 {
269                    let start = pos / total_length;
270                    let end = next_pos / total_length;
271                    sampler.split_range(start..end, &mut builder);
272                }
273                pos = next_pos;
274                dash_index += 1;
275            }
276
277            &builder.build()
278        } else {
279            path
280        };
281
282        // Will contain the result of the tessellation.
283        let mut buf: VertexBuffers<lyon::math::Point, u16> = VertexBuffers::new();
284        let mut tessellator = StrokeTessellator::new();
285
286        // Compute the tessellation.
287        tessellator.tessellate_path(
288            path,
289            options,
290            &mut BuffersBuilder::new(&mut buf, |vertex: StrokeVertex| vertex.position()),
291        )?;
292
293        Ok(Self::build_path(buf))
294    }
295
296    /// Builds a [`Path`] from a [`lyon::tessellation::VertexBuffers`].
297    pub fn build_path(buf: VertexBuffers<lyon::math::Point, u16>) -> Path<Pixels> {
298        if buf.vertices.is_empty() {
299            return Path::new(Point::default());
300        }
301
302        let first_point = buf.vertices[0];
303
304        let mut path = Path::new(first_point.into());
305        for i in 0..buf.indices.len() / 3 {
306            let i0 = buf.indices[i * 3] as usize;
307            let i1 = buf.indices[i * 3 + 1] as usize;
308            let i2 = buf.indices[i * 3 + 2] as usize;
309
310            let v0 = buf.vertices[i0];
311            let v1 = buf.vertices[i1];
312            let v2 = buf.vertices[i2];
313
314            path.push_triangle(
315                (v0.into(), v1.into(), v2.into()),
316                (point(0., 1.), point(0., 1.), point(0., 1.)),
317            );
318        }
319
320        path
321    }
322}