gpui: Add `PathBuilder` based on lyon to build `Path` (#22808)

Jason Lee created

Release Notes:

- N/A

---

Continue https://github.com/zed-industries/zed/pull/20499

We to draw more complex Path. Before this change, we only have
`line_to`, but it is not enough.

Add a new `PathBuilder` to use [lyon](https://github.com/nical/lyon) to
build more complex path.

And then with PR #22812 to enable anti-aliasing, all thing will be
perfect.
## Show case

```bash
cargo run -p gpui --example painting
```

Before:

<img width="1136" alt="image"
src="https://github.com/user-attachments/assets/0c15833a-ec95-404c-a469-24cf172cfd86"
/>

After:

<img width="1136" alt="image"
src="https://github.com/user-attachments/assets/42cfa35e-7e8f-4ef3-bb2d-b98defc62ad6"
/>

Change summary

Cargo.lock                       |  70 +++++++++
crates/editor/src/element.rs     |  48 +++---
crates/gpui/Cargo.toml           |   2 
crates/gpui/examples/gradient.rs |  14 +
crates/gpui/examples/painting.rs | 141 ++++++++++++-------
crates/gpui/src/gpui.rs          |   2 
crates/gpui/src/path_builder.rs  | 241 ++++++++++++++++++++++++++++++++++
crates/gpui/src/scene.rs         |  10 +
8 files changed, 449 insertions(+), 79 deletions(-)

Detailed changes

Cargo.lock πŸ”—

@@ -4698,6 +4698,12 @@ version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d"
 
+[[package]]
+name = "float_next_after"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8"
+
 [[package]]
 name = "flume"
 version = "0.11.1"
@@ -5426,6 +5432,7 @@ dependencies = [
  "itertools 0.14.0",
  "linkme",
  "log",
+ "lyon",
  "media",
  "metal",
  "num_cpus",
@@ -7429,6 +7436,69 @@ dependencies = [
  "url",
 ]
 
+[[package]]
+name = "lyon"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e7f9cda98b5430809e63ca5197b06c7d191bf7e26dfc467d5a3f0290e2a74f"
+dependencies = [
+ "lyon_algorithms",
+ "lyon_extra",
+ "lyon_tessellation",
+]
+
+[[package]]
+name = "lyon_algorithms"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f13c9be19d257c7d37e70608ed858e8eab4b2afcea2e3c9a622e892acbf43c08"
+dependencies = [
+ "lyon_path",
+ "num-traits",
+]
+
+[[package]]
+name = "lyon_extra"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ca94c7bf1e2557c2798989c43416822c12fc5dcc5e17cc3307ef0e71894a955"
+dependencies = [
+ "lyon_path",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "lyon_geom"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8af69edc087272df438b3ee436c4bb6d7c04aa8af665cfd398feae627dbd8570"
+dependencies = [
+ "arrayvec",
+ "euclid",
+ "num-traits",
+]
+
+[[package]]
+name = "lyon_path"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e0b8aec2f58586f6eef237985b9a9b7cb3a3aff4417c575075cf95bf925252e"
+dependencies = [
+ "lyon_geom",
+ "num-traits",
+]
+
+[[package]]
+name = "lyon_tessellation"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "579d42360a4b09846eff2feef28f538696c7d6c7439bfa65874ff3cbe0951b2c"
+dependencies = [
+ "float_next_after",
+ "lyon_path",
+ "num-traits",
+]
+
 [[package]]
 name = "mac"
 version = "0.1.1"

crates/editor/src/element.rs πŸ”—

@@ -7837,8 +7837,8 @@ impl HighlightedRange {
         };
 
         let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
-        let mut path = gpui::Path::new(first_top_right - top_curve_width);
-        path.curve_to(first_top_right + curve_height, first_top_right);
+        let mut builder = gpui::PathBuilder::fill();
+        builder.curve_to(first_top_right + curve_height, first_top_right);
 
         let mut iter = lines.iter().enumerate().peekable();
         while let Some((ix, line)) = iter.next() {
@@ -7849,42 +7849,42 @@ impl HighlightedRange {
 
                 match next_top_right.x.partial_cmp(&bottom_right.x).unwrap() {
                     Ordering::Equal => {
-                        path.line_to(bottom_right);
+                        builder.line_to(bottom_right);
                     }
                     Ordering::Less => {
                         let curve_width = curve_width(next_top_right.x, bottom_right.x);
-                        path.line_to(bottom_right - curve_height);
+                        builder.line_to(bottom_right - curve_height);
                         if self.corner_radius > Pixels::ZERO {
-                            path.curve_to(bottom_right - curve_width, bottom_right);
+                            builder.curve_to(bottom_right - curve_width, bottom_right);
                         }
-                        path.line_to(next_top_right + curve_width);
+                        builder.line_to(next_top_right + curve_width);
                         if self.corner_radius > Pixels::ZERO {
-                            path.curve_to(next_top_right + curve_height, next_top_right);
+                            builder.curve_to(next_top_right + curve_height, next_top_right);
                         }
                     }
                     Ordering::Greater => {
                         let curve_width = curve_width(bottom_right.x, next_top_right.x);
-                        path.line_to(bottom_right - curve_height);
+                        builder.line_to(bottom_right - curve_height);
                         if self.corner_radius > Pixels::ZERO {
-                            path.curve_to(bottom_right + curve_width, bottom_right);
+                            builder.curve_to(bottom_right + curve_width, bottom_right);
                         }
-                        path.line_to(next_top_right - curve_width);
+                        builder.line_to(next_top_right - curve_width);
                         if self.corner_radius > Pixels::ZERO {
-                            path.curve_to(next_top_right + curve_height, next_top_right);
+                            builder.curve_to(next_top_right + curve_height, next_top_right);
                         }
                     }
                 }
             } else {
                 let curve_width = curve_width(line.start_x, line.end_x);
-                path.line_to(bottom_right - curve_height);
+                builder.line_to(bottom_right - curve_height);
                 if self.corner_radius > Pixels::ZERO {
-                    path.curve_to(bottom_right - curve_width, bottom_right);
+                    builder.curve_to(bottom_right - curve_width, bottom_right);
                 }
 
                 let bottom_left = point(line.start_x, bottom_right.y);
-                path.line_to(bottom_left + curve_width);
+                builder.line_to(bottom_left + curve_width);
                 if self.corner_radius > Pixels::ZERO {
-                    path.curve_to(bottom_left - curve_height, bottom_left);
+                    builder.curve_to(bottom_left - curve_height, bottom_left);
                 }
             }
         }
@@ -7892,24 +7892,26 @@ impl HighlightedRange {
         if first_line.start_x > last_line.start_x {
             let curve_width = curve_width(last_line.start_x, first_line.start_x);
             let second_top_left = point(last_line.start_x, start_y + self.line_height);
-            path.line_to(second_top_left + curve_height);
+            builder.line_to(second_top_left + curve_height);
             if self.corner_radius > Pixels::ZERO {
-                path.curve_to(second_top_left + curve_width, second_top_left);
+                builder.curve_to(second_top_left + curve_width, second_top_left);
             }
             let first_bottom_left = point(first_line.start_x, second_top_left.y);
-            path.line_to(first_bottom_left - curve_width);
+            builder.line_to(first_bottom_left - curve_width);
             if self.corner_radius > Pixels::ZERO {
-                path.curve_to(first_bottom_left - curve_height, first_bottom_left);
+                builder.curve_to(first_bottom_left - curve_height, first_bottom_left);
             }
         }
 
-        path.line_to(first_top_left + curve_height);
+        builder.line_to(first_top_left + curve_height);
         if self.corner_radius > Pixels::ZERO {
-            path.curve_to(first_top_left + top_curve_width, first_top_left);
+            builder.curve_to(first_top_left + top_curve_width, first_top_left);
         }
-        path.line_to(first_top_right - top_curve_width);
+        builder.line_to(first_top_right - top_curve_width);
 
-        window.paint_path(path, self.color);
+        if let Ok(path) = builder.build() {
+            window.paint_path(path, self.color);
+        }
     }
 }
 

crates/gpui/Cargo.toml πŸ”—

@@ -108,6 +108,7 @@ thiserror.workspace = true
 util.workspace = true
 uuid.workspace = true
 waker-fn = "1.2.0"
+lyon = "1.0"
 
 [dev-dependencies]
 backtrace = "0.3"
@@ -117,6 +118,7 @@ rand.workspace = true
 util = { workspace = true, features = ["test-support"] }
 http_client = { workspace = true, features = ["test-support"] }
 unicode-segmentation.workspace = true
+lyon = { version = "1.0", features = ["extra"] }
 
 [target.'cfg(target_os = "windows")'.build-dependencies]
 embed-resource = "3.0"

crates/gpui/examples/gradient.rs πŸ”—

@@ -218,13 +218,17 @@ impl Render for GradientViewer {
                     let height = square_bounds.size.height;
                     let horizontal_offset = height;
                     let vertical_offset = px(30.);
-                    let mut path = gpui::Path::new(square_bounds.bottom_left());
-                    path.line_to(square_bounds.origin + point(horizontal_offset, vertical_offset));
-                    path.line_to(
+                    let mut builder = gpui::PathBuilder::fill();
+                    builder.move_to(square_bounds.bottom_left());
+                    builder
+                        .line_to(square_bounds.origin + point(horizontal_offset, vertical_offset));
+                    builder.line_to(
                         square_bounds.top_right() + point(-horizontal_offset, vertical_offset),
                     );
-                    path.line_to(square_bounds.bottom_right());
-                    path.line_to(square_bounds.bottom_left());
+
+                    builder.line_to(square_bounds.bottom_right());
+                    builder.line_to(square_bounds.bottom_left());
+                    let path = builder.build().unwrap();
                     window.paint_path(
                         path,
                         linear_gradient(

crates/gpui/examples/painting.rs πŸ”—

@@ -1,46 +1,62 @@
 use gpui::{
-    canvas, div, point, prelude::*, px, size, App, Application, Bounds, Context, MouseDownEvent,
-    Path, Pixels, Point, Render, Window, WindowOptions,
+    canvas, div, linear_color_stop, linear_gradient, point, prelude::*, px, rgb, size, Application,
+    Background, Bounds, ColorSpace, Context, MouseDownEvent, Path, PathBuilder, PathStyle, Pixels,
+    Point, Render, StrokeOptions, Window, WindowOptions,
 };
+
 struct PaintingViewer {
-    default_lines: Vec<Path<Pixels>>,
+    default_lines: Vec<(Path<Pixels>, Background)>,
     lines: Vec<Vec<Point<Pixels>>>,
     start: Point<Pixels>,
     _painting: bool,
 }
 
 impl PaintingViewer {
-    fn new() -> Self {
+    fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self {
         let mut lines = vec![];
 
-        // draw a line
-        let mut path = Path::new(point(px(50.), px(180.)));
-        path.line_to(point(px(100.), px(120.)));
-        // go back to close the path
-        path.line_to(point(px(100.), px(121.)));
-        path.line_to(point(px(50.), px(181.)));
-        lines.push(path);
+        // draw a Rust logo
+        let mut builder = lyon::path::Path::svg_builder();
+        lyon::extra::rust_logo::build_logo_path(&mut builder);
+        // move down the Path
+        let mut builder: PathBuilder = builder.into();
+        builder.translate(point(px(10.), px(100.)));
+        builder.scale(0.9);
+        let path = builder.build().unwrap();
+        lines.push((path, gpui::black().into()));
 
         // draw a lightening bolt ⚑
-        let mut path = Path::new(point(px(150.), px(200.)));
-        path.line_to(point(px(200.), px(125.)));
-        path.line_to(point(px(200.), px(175.)));
-        path.line_to(point(px(250.), px(100.)));
-        lines.push(path);
+        let mut builder = PathBuilder::fill();
+        builder.move_to(point(px(150.), px(200.)));
+        builder.line_to(point(px(200.), px(125.)));
+        builder.line_to(point(px(200.), px(175.)));
+        builder.line_to(point(px(250.), px(100.)));
+        let path = builder.build().unwrap();
+        lines.push((path, rgb(0x1d4ed8).into()));
 
         // draw a ⭐
-        let mut path = Path::new(point(px(350.), px(100.)));
-        path.line_to(point(px(370.), px(160.)));
-        path.line_to(point(px(430.), px(160.)));
-        path.line_to(point(px(380.), px(200.)));
-        path.line_to(point(px(400.), px(260.)));
-        path.line_to(point(px(350.), px(220.)));
-        path.line_to(point(px(300.), px(260.)));
-        path.line_to(point(px(320.), px(200.)));
-        path.line_to(point(px(270.), px(160.)));
-        path.line_to(point(px(330.), px(160.)));
-        path.line_to(point(px(350.), px(100.)));
-        lines.push(path);
+        let mut builder = PathBuilder::fill();
+        builder.move_to(point(px(350.), px(100.)));
+        builder.line_to(point(px(370.), px(160.)));
+        builder.line_to(point(px(430.), px(160.)));
+        builder.line_to(point(px(380.), px(200.)));
+        builder.line_to(point(px(400.), px(260.)));
+        builder.line_to(point(px(350.), px(220.)));
+        builder.line_to(point(px(300.), px(260.)));
+        builder.line_to(point(px(320.), px(200.)));
+        builder.line_to(point(px(270.), px(160.)));
+        builder.line_to(point(px(330.), px(160.)));
+        builder.line_to(point(px(350.), px(100.)));
+        let path = builder.build().unwrap();
+        lines.push((
+            path,
+            linear_gradient(
+                180.,
+                linear_color_stop(rgb(0xFACC15), 0.7),
+                linear_color_stop(rgb(0xD56D0C), 1.),
+            )
+            .color_space(ColorSpace::Oklab),
+        ));
 
         let square_bounds = Bounds {
             origin: point(px(450.), px(100.)),
@@ -49,18 +65,42 @@ impl PaintingViewer {
         let height = square_bounds.size.height;
         let horizontal_offset = height;
         let vertical_offset = px(30.);
-        let mut path = Path::new(square_bounds.bottom_left());
-        path.curve_to(
+        let mut builder = PathBuilder::fill();
+        builder.move_to(square_bounds.bottom_left());
+        builder.curve_to(
             square_bounds.origin + point(horizontal_offset, vertical_offset),
             square_bounds.origin + point(px(0.0), vertical_offset),
         );
-        path.line_to(square_bounds.top_right() + point(-horizontal_offset, vertical_offset));
-        path.curve_to(
+        builder.line_to(square_bounds.top_right() + point(-horizontal_offset, vertical_offset));
+        builder.curve_to(
             square_bounds.bottom_right(),
             square_bounds.top_right() + point(px(0.0), vertical_offset),
         );
-        path.line_to(square_bounds.bottom_left());
-        lines.push(path);
+        builder.line_to(square_bounds.bottom_left());
+        let path = builder.build().unwrap();
+        lines.push((
+            path,
+            linear_gradient(
+                180.,
+                linear_color_stop(gpui::blue(), 0.4),
+                linear_color_stop(gpui::red(), 1.),
+            ),
+        ));
+
+        // draw a wave
+        let options = StrokeOptions::default()
+            .with_line_width(1.)
+            .with_line_join(lyon::path::LineJoin::Bevel);
+        let mut builder = PathBuilder::stroke(px(1.)).with_style(PathStyle::Stroke(options));
+        builder.move_to(point(px(40.), px(320.)));
+        for i in 0..50 {
+            builder.line_to(point(
+                px(40.0 + i as f32 * 10.0),
+                px(320.0 + (i as f32 * 10.0).sin() * 40.0),
+            ));
+        }
+        let path = builder.build().unwrap();
+        lines.push((path, gpui::green().into()));
 
         Self {
             default_lines: lines.clone(),
@@ -115,27 +155,28 @@ impl Render for PaintingViewer {
                         canvas(
                             move |_, _, _| {},
                             move |_, _, window, _| {
-                                const STROKE_WIDTH: Pixels = px(2.0);
-                                for path in default_lines {
-                                    window.paint_path(path, gpui::black());
+
+                                for (path, color) in default_lines {
+                                    window.paint_path(path, color);
                                 }
+
                                 for points in lines {
-                                    let mut path = Path::new(points[0]);
-                                    for p in points.iter().skip(1) {
-                                        path.line_to(*p);
+                                    if points.len() < 2 {
+                                        continue;
                                     }
 
-                                    let mut last = points.last().unwrap();
-                                    for p in points.iter().rev() {
-                                        let mut offset_x = px(0.);
-                                        if last.x == p.x {
-                                            offset_x = STROKE_WIDTH;
+                                    let mut builder = PathBuilder::stroke(px(1.));
+                                    for (i, p) in points.into_iter().enumerate() {
+                                        if i == 0 {
+                                            builder.move_to(p);
+                                        } else {
+                                            builder.line_to(p);
                                         }
-                                        path.line_to(point(p.x + offset_x, p.y  + STROKE_WIDTH));
-                                        last = p;
                                     }
 
-                                    window.paint_path(path, gpui::black());
+                                    if let Ok(path) = builder.build() {
+                                        window.paint_path(path, gpui::black());
+                                    }
                                 }
                             },
                         )
@@ -185,13 +226,13 @@ impl Render for PaintingViewer {
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    Application::new().run(|cx| {
         cx.open_window(
             WindowOptions {
                 focus: true,
                 ..Default::default()
             },
-            |_, cx| cx.new(|_| PaintingViewer::new()),
+            |window, cx| cx.new(|cx| PaintingViewer::new(window, cx)),
         )
         .unwrap();
         cx.activate(true);

crates/gpui/src/gpui.rs πŸ”—

@@ -82,6 +82,7 @@ mod input;
 mod interactive;
 mod key_dispatch;
 mod keymap;
+mod path_builder;
 mod platform;
 pub mod prelude;
 mod scene;
@@ -135,6 +136,7 @@ pub use input::*;
 pub use interactive::*;
 use key_dispatch::*;
 pub use keymap::*;
+pub use path_builder::*;
 pub use platform::*;
 pub use refineable::*;
 pub use scene::*;

crates/gpui/src/path_builder.rs πŸ”—

@@ -0,0 +1,241 @@
+use anyhow::Error;
+use etagere::euclid::Vector2D;
+use lyon::geom::Angle;
+use lyon::tessellation::{
+    BuffersBuilder, FillTessellator, FillVertex, StrokeTessellator, StrokeVertex, VertexBuffers,
+};
+
+pub use lyon::math::Transform;
+pub use lyon::tessellation::{FillOptions, FillRule, StrokeOptions};
+
+use crate::{point, px, Path, Pixels, Point};
+
+/// Style of the PathBuilder
+pub enum PathStyle {
+    /// Stroke style
+    Stroke(StrokeOptions),
+    /// Fill style
+    Fill(FillOptions),
+}
+
+/// A [`Path`] builder.
+pub struct PathBuilder {
+    raw: lyon::path::builder::WithSvg<lyon::path::BuilderImpl>,
+    transform: Option<lyon::math::Transform>,
+    /// PathStyle of the PathBuilder
+    pub style: PathStyle,
+}
+
+impl From<lyon::path::Builder> for PathBuilder {
+    fn from(builder: lyon::path::Builder) -> Self {
+        Self {
+            raw: builder.with_svg(),
+            ..Default::default()
+        }
+    }
+}
+
+impl From<lyon::path::builder::WithSvg<lyon::path::BuilderImpl>> for PathBuilder {
+    fn from(raw: lyon::path::builder::WithSvg<lyon::path::BuilderImpl>) -> Self {
+        Self {
+            raw,
+            ..Default::default()
+        }
+    }
+}
+
+impl From<lyon::math::Point> for Point<Pixels> {
+    fn from(p: lyon::math::Point) -> Self {
+        point(px(p.x), px(p.y))
+    }
+}
+
+impl From<Point<Pixels>> for lyon::math::Point {
+    fn from(p: Point<Pixels>) -> Self {
+        lyon::math::point(p.x.0, p.y.0)
+    }
+}
+
+impl Default for PathBuilder {
+    fn default() -> Self {
+        Self {
+            raw: lyon::path::Path::builder().with_svg(),
+            style: PathStyle::Fill(FillOptions::default()),
+            transform: None,
+        }
+    }
+}
+
+impl PathBuilder {
+    /// Creates a new [`PathBuilder`] to build a Stroke path.
+    pub fn stroke(width: Pixels) -> Self {
+        Self {
+            style: PathStyle::Stroke(StrokeOptions::default().with_line_width(width.0)),
+            ..Self::default()
+        }
+    }
+
+    /// Creates a new [`PathBuilder`] to build a Fill path.
+    pub fn fill() -> Self {
+        Self::default()
+    }
+
+    /// Sets the style of the [`PathBuilder`].
+    pub fn with_style(self, style: PathStyle) -> Self {
+        Self { style, ..self }
+    }
+
+    /// Move the current point to the given point.
+    #[inline]
+    pub fn move_to(&mut self, to: Point<Pixels>) {
+        self.raw.move_to(to.into());
+    }
+
+    /// Draw a straight line from the current point to the given point.
+    #[inline]
+    pub fn line_to(&mut self, to: Point<Pixels>) {
+        self.raw.line_to(to.into());
+    }
+
+    /// Draw a curve from the current point to the given point, using the given control point.
+    #[inline]
+    pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
+        self.raw.quadratic_bezier_to(ctrl.into(), to.into());
+    }
+
+    /// Adds a cubic BΓ©zier to the [`Path`] given its two control points
+    /// and its end point.
+    #[inline]
+    pub fn cubic_bezier_to(
+        &mut self,
+        to: Point<Pixels>,
+        control_a: Point<Pixels>,
+        control_b: Point<Pixels>,
+    ) {
+        self.raw
+            .cubic_bezier_to(control_a.into(), control_b.into(), to.into());
+    }
+
+    /// Close the current sub-path.
+    #[inline]
+    pub fn close(&mut self) {
+        self.raw.close();
+    }
+
+    /// Applies a transform to the path.
+    #[inline]
+    pub fn transform(&mut self, transform: Transform) {
+        self.transform = Some(transform);
+    }
+
+    /// Applies a translation to the path.
+    #[inline]
+    pub fn translate(&mut self, to: Point<Pixels>) {
+        if let Some(transform) = self.transform {
+            self.transform = Some(transform.then_translate(Vector2D::new(to.x.0, to.y.0)));
+        } else {
+            self.transform = Some(Transform::translation(to.x.0, to.y.0))
+        }
+    }
+
+    /// Applies a scale to the path.
+    #[inline]
+    pub fn scale(&mut self, scale: f32) {
+        if let Some(transform) = self.transform {
+            self.transform = Some(transform.then_scale(scale, scale));
+        } else {
+            self.transform = Some(Transform::scale(scale, scale));
+        }
+    }
+
+    /// Applies a rotation to the path.
+    ///
+    /// The `angle` is in degrees value in the range 0.0 to 360.0.
+    #[inline]
+    pub fn rotate(&mut self, angle: f32) {
+        let radians = angle.to_radians();
+        if let Some(transform) = self.transform {
+            self.transform = Some(transform.then_rotate(Angle::radians(radians)));
+        } else {
+            self.transform = Some(Transform::rotation(Angle::radians(radians)));
+        }
+    }
+
+    /// Builds into a [`Path`].
+    #[inline]
+    pub fn build(self) -> Result<Path<Pixels>, Error> {
+        let path = if let Some(transform) = self.transform {
+            self.raw.build().transformed(&transform)
+        } else {
+            self.raw.build()
+        };
+
+        match self.style {
+            PathStyle::Stroke(options) => Self::tessellate_stroke(&path, &options),
+            PathStyle::Fill(options) => Self::tessellate_fill(&path, &options),
+        }
+    }
+
+    fn tessellate_fill(
+        path: &lyon::path::Path,
+        options: &FillOptions,
+    ) -> Result<Path<Pixels>, Error> {
+        // Will contain the result of the tessellation.
+        let mut buf: VertexBuffers<lyon::math::Point, u16> = VertexBuffers::new();
+        let mut tessellator = FillTessellator::new();
+
+        // Compute the tessellation.
+        tessellator.tessellate_path(
+            path,
+            options,
+            &mut BuffersBuilder::new(&mut buf, |vertex: FillVertex| vertex.position()),
+        )?;
+
+        Ok(Self::build_path(buf))
+    }
+
+    fn tessellate_stroke(
+        path: &lyon::path::Path,
+        options: &StrokeOptions,
+    ) -> Result<Path<Pixels>, Error> {
+        // Will contain the result of the tessellation.
+        let mut buf: VertexBuffers<lyon::math::Point, u16> = VertexBuffers::new();
+        let mut tessellator = StrokeTessellator::new();
+
+        // Compute the tessellation.
+        tessellator.tessellate_path(
+            path,
+            options,
+            &mut BuffersBuilder::new(&mut buf, |vertex: StrokeVertex| vertex.position()),
+        )?;
+
+        Ok(Self::build_path(buf))
+    }
+
+    /// Builds a [`Path`] from a [`lyon::VertexBuffers`].
+    pub fn build_path(buf: VertexBuffers<lyon::math::Point, u16>) -> Path<Pixels> {
+        if buf.vertices.is_empty() {
+            return Path::new(Point::default());
+        }
+
+        let first_point = buf.vertices[0];
+
+        let mut path = Path::new(first_point.into());
+        for i in 0..buf.indices.len() / 3 {
+            let i0 = buf.indices[i * 3] as usize;
+            let i1 = buf.indices[i * 3 + 1] as usize;
+            let i2 = buf.indices[i * 3 + 2] as usize;
+
+            let v0 = buf.vertices[i0];
+            let v1 = buf.vertices[i1];
+            let v2 = buf.vertices[i2];
+
+            path.push_triangle(
+                (v0.into(), v1.into(), v2.into()),
+                (point(0., 1.), point(0., 1.), point(0., 1.)),
+            );
+        }
+
+        path
+    }
+}

crates/gpui/src/scene.rs πŸ”—

@@ -715,6 +715,13 @@ impl Path<Pixels> {
         }
     }
 
+    /// Move the start, current point to the given point.
+    pub fn move_to(&mut self, to: Point<Pixels>) {
+        self.contour_count += 1;
+        self.start = to;
+        self.current = to;
+    }
+
     /// Draw a straight line from the current point to the given point.
     pub fn line_to(&mut self, to: Point<Pixels>) {
         self.contour_count += 1;
@@ -744,7 +751,8 @@ impl Path<Pixels> {
         self.current = to;
     }
 
-    fn push_triangle(
+    /// Push a triangle to the Path.
+    pub fn push_triangle(
         &mut self,
         xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
         st: (Point<f32>, Point<f32>, Point<f32>),