ZIm/crates/gpui/src/path_builder.rs
Joseph T. Lyons 9a20843ba2
Revert "gpui: Improve path rendering & global multisample anti-aliasing" (#34722)
Reverts zed-industries/zed#29718

We've noticed some issues with Zed on Intel-based Macs where typing has
become sluggish, and git bisect has seemed to point towards this PR.
Reverting for now, until we can understand why it is causing this issue.
2025-07-18 16:03:08 +00:00

347 lines
10 KiB
Rust

use anyhow::Error;
use etagere::euclid::{Point2D, Vector2D};
use lyon::geom::Angle;
use lyon::math::{Vector, vector};
use lyon::path::traits::SvgPathBuilder;
use lyon::path::{ArcFlags, Polygon};
use lyon::tessellation::{
BuffersBuilder, FillTessellator, FillVertex, StrokeTessellator, StrokeVertex, VertexBuffers,
};
pub use lyon::math::Transform;
pub use lyon::tessellation::{FillOptions, FillRule, StrokeOptions};
use crate::{Path, Pixels, Point, point, px};
/// 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,
dash_array: Option<Vec<Pixels>>,
}
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 From<Point<Pixels>> for Vector {
fn from(p: Point<Pixels>) -> Self {
vector(p.x.0, p.y.0)
}
}
impl From<Point<Pixels>> for Point2D<f32, Pixels> {
fn from(p: Point<Pixels>) -> Self {
Point2D::new(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,
dash_array: 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 }
}
/// Sets the dash array of the [`PathBuilder`].
///
/// [MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/stroke-dasharray)
pub fn dash_array(mut self, dash_array: &[Pixels]) -> Self {
// If an odd number of values is provided, then the list of values is repeated to yield an even number of values.
// Thus, 5,3,2 is equivalent to 5,3,2,5,3,2.
let array = if dash_array.len() % 2 == 1 {
let mut new_dash_array = dash_array.to_vec();
new_dash_array.extend_from_slice(dash_array);
new_dash_array
} else {
dash_array.to_vec()
};
self.dash_array = Some(array);
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());
}
/// Adds an elliptical arc.
pub fn arc_to(
&mut self,
radii: Point<Pixels>,
x_rotation: Pixels,
large_arc: bool,
sweep: bool,
to: Point<Pixels>,
) {
self.raw.arc_to(
radii.into(),
Angle::degrees(x_rotation.into()),
ArcFlags { large_arc, sweep },
to.into(),
);
}
/// Equivalent to `arc_to` in relative coordinates.
pub fn relative_arc_to(
&mut self,
radii: Point<Pixels>,
x_rotation: Pixels,
large_arc: bool,
sweep: bool,
to: Point<Pixels>,
) {
self.raw.relative_arc_to(
radii.into(),
Angle::degrees(x_rotation.into()),
ArcFlags { large_arc, sweep },
to.into(),
);
}
/// Adds a polygon.
pub fn add_polygon(&mut self, points: &[Point<Pixels>], closed: bool) {
let points = points.iter().copied().map(|p| p.into()).collect::<Vec<_>>();
self.raw.add_polygon(Polygon {
points: points.as_ref(),
closed,
});
}
/// 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(self.dash_array, &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(
dash_array: Option<Vec<Pixels>>,
path: &lyon::path::Path,
options: &StrokeOptions,
) -> Result<Path<Pixels>, Error> {
let path = if let Some(dash_array) = dash_array {
let measurements = lyon::algorithms::measure::PathMeasurements::from_path(&path, 0.01);
let mut sampler = measurements
.create_sampler(path, lyon::algorithms::measure::SampleType::Normalized);
let mut builder = lyon::path::Path::builder();
let total_length = sampler.length();
let dash_array_len = dash_array.len();
let mut pos = 0.;
let mut dash_index = 0;
while pos < total_length {
let dash_length = dash_array[dash_index % dash_array_len].0;
let next_pos = (pos + dash_length).min(total_length);
if dash_index % 2 == 0 {
let start = pos / total_length;
let end = next_pos / total_length;
sampler.split_range(start..end, &mut builder);
}
pos = next_pos;
dash_index += 1;
}
&builder.build()
} else {
path
};
// 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
}
}