chart: Draw dashed lines using gpui PathBuilder (#927)

https://github.com/zed-industries/zed/pull/31678
This commit is contained in:
Floyd Wang 2025-06-09 11:55:32 +08:00 committed by GitHub
parent 01197769df
commit 44ec74f1fb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 36 additions and 83 deletions

31
Cargo.lock generated
View file

@ -623,7 +623,7 @@ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools 0.12.1", "itertools 0.10.5",
"lazy_static", "lazy_static",
"lazycell", "lazycell",
"log", "log",
@ -646,7 +646,7 @@ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools 0.13.0", "itertools 0.10.5",
"log", "log",
"prettyplease", "prettyplease",
"proc-macro2", "proc-macro2",
@ -1226,7 +1226,7 @@ dependencies = [
[[package]] [[package]]
name = "collections" name = "collections"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#06a199da4d8a147c7867f4c82ffafed4974bea24" source = "git+https://github.com/zed-industries/zed.git#b15aef4310e86aa31c2ceab74184ec7e5627a2c5"
dependencies = [ dependencies = [
"indexmap 2.7.1", "indexmap 2.7.1",
"rustc-hash 2.1.0", "rustc-hash 2.1.0",
@ -1739,7 +1739,7 @@ dependencies = [
[[package]] [[package]]
name = "derive_refineable" name = "derive_refineable"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#06a199da4d8a147c7867f4c82ffafed4974bea24" source = "git+https://github.com/zed-industries/zed.git#b15aef4310e86aa31c2ceab74184ec7e5627a2c5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2777,7 +2777,7 @@ dependencies = [
[[package]] [[package]]
name = "gpui" name = "gpui"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#06a199da4d8a147c7867f4c82ffafed4974bea24" source = "git+https://github.com/zed-industries/zed.git#b15aef4310e86aa31c2ceab74184ec7e5627a2c5"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"as-raw-xcb-connection", "as-raw-xcb-connection",
@ -2881,7 +2881,6 @@ dependencies = [
"image", "image",
"indoc", "indoc",
"itertools 0.13.0", "itertools 0.13.0",
"lyon",
"markdown", "markdown",
"markup5ever_rcdom", "markup5ever_rcdom",
"minify-html", "minify-html",
@ -2946,7 +2945,7 @@ dependencies = [
[[package]] [[package]]
name = "gpui_macros" name = "gpui_macros"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#06a199da4d8a147c7867f4c82ffafed4974bea24" source = "git+https://github.com/zed-industries/zed.git#b15aef4310e86aa31c2ceab74184ec7e5627a2c5"
dependencies = [ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
@ -3216,7 +3215,7 @@ dependencies = [
[[package]] [[package]]
name = "http_client" name = "http_client"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#06a199da4d8a147c7867f4c82ffafed4974bea24" source = "git+https://github.com/zed-industries/zed.git#b15aef4310e86aa31c2ceab74184ec7e5627a2c5"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -3233,7 +3232,7 @@ dependencies = [
[[package]] [[package]]
name = "http_client_tls" name = "http_client_tls"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#06a199da4d8a147c7867f4c82ffafed4974bea24" source = "git+https://github.com/zed-industries/zed.git#b15aef4310e86aa31c2ceab74184ec7e5627a2c5"
dependencies = [ dependencies = [
"rustls", "rustls",
"rustls-platform-verifier", "rustls-platform-verifier",
@ -4003,7 +4002,7 @@ dependencies = [
[[package]] [[package]]
name = "media" name = "media"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#06a199da4d8a147c7867f4c82ffafed4974bea24" source = "git+https://github.com/zed-industries/zed.git#b15aef4310e86aa31c2ceab74184ec7e5627a2c5"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bindgen 0.71.1", "bindgen 0.71.1",
@ -4396,7 +4395,7 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
dependencies = [ dependencies = [
"proc-macro-crate 3.2.0", "proc-macro-crate 1.3.1",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.101", "syn 2.0.101",
@ -5720,7 +5719,7 @@ dependencies = [
[[package]] [[package]]
name = "refineable" name = "refineable"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#06a199da4d8a147c7867f4c82ffafed4974bea24" source = "git+https://github.com/zed-industries/zed.git#b15aef4310e86aa31c2ceab74184ec7e5627a2c5"
dependencies = [ dependencies = [
"derive_refineable", "derive_refineable",
"workspace-hack", "workspace-hack",
@ -5830,7 +5829,7 @@ dependencies = [
[[package]] [[package]]
name = "reqwest_client" name = "reqwest_client"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#06a199da4d8a147c7867f4c82ffafed4974bea24" source = "git+https://github.com/zed-industries/zed.git#b15aef4310e86aa31c2ceab74184ec7e5627a2c5"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -6379,7 +6378,7 @@ checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a"
[[package]] [[package]]
name = "semantic_version" name = "semantic_version"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#06a199da4d8a147c7867f4c82ffafed4974bea24" source = "git+https://github.com/zed-industries/zed.git#b15aef4310e86aa31c2ceab74184ec7e5627a2c5"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"serde", "serde",
@ -6876,7 +6875,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "sum_tree" name = "sum_tree"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#06a199da4d8a147c7867f4c82ffafed4974bea24" source = "git+https://github.com/zed-industries/zed.git#b15aef4310e86aa31c2ceab74184ec7e5627a2c5"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"log", "log",
@ -8043,7 +8042,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "util" name = "util"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#06a199da4d8a147c7867f4c82ffafed4974bea24" source = "git+https://github.com/zed-industries/zed.git#b15aef4310e86aa31c2ceab74184ec7e5627a2c5"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-fs 2.1.2", "async-fs 2.1.2",

View file

@ -19,7 +19,7 @@ UI components for building fantastic desktop applications using [GPUI](https://g
Here is the first application: [Longbridge Pro](https://longbridge.com/desktop), built using GPUI Component. Here is the first application: [Longbridge Pro](https://longbridge.com/desktop), built using GPUI Component.
<img width="1763" alt="Image" src="https://i.postimg.cc/qBh6GCvp/Longbridge-Pro.png" /> <img width="1763" alt="Image" src="https://wp-cdn.4ce.cn/v2/hZRGaJm.png" />
We built multi-theme support in the application. This feature is not included in GPUI Component itself, but is based on the `Theme` feature, so it's easy to implement. We built multi-theme support in the application. This feature is not included in GPUI Component itself, but is based on the `Theme` feature, so it's easy to implement.

View file

@ -25,7 +25,6 @@ enum-iterator = "2.1.0"
futures-util = "0.3.31" futures-util = "0.3.31"
image = "0.25.1" image = "0.25.1"
itertools = "0.13.0" itertools = "0.13.0"
lyon = "1.0"
once_cell = "1.19.0" once_cell = "1.19.0"
paste = "1" paste = "1"
regex = "1" regex = "1"

View file

@ -136,7 +136,7 @@ where
Grid::new() Grid::new()
.y((0..=3).map(|i| height * i as f64 / 4.0).collect()) .y((0..=3).map(|i| height * i as f64 / 4.0).collect())
.stroke(cx.theme().border) .stroke(cx.theme().border)
.dash_array([px(4.), px(2.)]) .dash_array(&[px(4.), px(2.)])
.paint(&bounds, window); .paint(&bounds, window);
// Draw area // Draw area

View file

@ -135,7 +135,7 @@ where
Grid::new() Grid::new()
.y((0..=3).map(|i| height * i as f64 / 4.0).collect()) .y((0..=3).map(|i| height * i as f64 / 4.0).collect())
.stroke(cx.theme().border) .stroke(cx.theme().border)
.dash_array([px(4.), px(2.)]) .dash_array(&[px(4.), px(2.)])
.paint(&bounds, window); .paint(&bounds, window);
// Draw bars // Draw bars

View file

@ -128,7 +128,7 @@ where
Grid::new() Grid::new()
.y((0..=3).map(|i| height * i as f64 / 4.0).collect()) .y((0..=3).map(|i| height * i as f64 / 4.0).collect())
.stroke(cx.theme().border) .stroke(cx.theme().border)
.dash_array([px(4.), px(2.)]) .dash_array(&[px(4.), px(2.)])
.paint(&bounds, window); .paint(&bounds, window);
// Draw line // Draw line

View file

@ -1,12 +1,12 @@
use gpui::{px, Bounds, Hsla, PathBuilder, Pixels, Point, Window}; use gpui::{px, Bounds, Hsla, PathBuilder, Pixels, Point, Window};
use super::{dash_line, origin_point}; use super::origin_point;
pub struct Grid { pub struct Grid {
x: Vec<Pixels>, x: Vec<Pixels>,
y: Vec<Pixels>, y: Vec<Pixels>,
stroke: Hsla, stroke: Hsla,
dash_array: Option<[Pixels; 2]>, dash_array: Option<Vec<Pixels>>,
} }
impl Grid { impl Grid {
@ -39,8 +39,8 @@ impl Grid {
} }
/// Set the dash array of the Grid. /// Set the dash array of the Grid.
pub fn dash_array(mut self, dash_array: [Pixels; 2]) -> Self { pub fn dash_array(mut self, dash_array: &[Pixels]) -> Self {
self.dash_array = Some(dash_array); self.dash_array = Some(dash_array.to_vec());
self self
} }
@ -78,20 +78,17 @@ impl Grid {
pub fn paint(&self, bounds: &Bounds<Pixels>, window: &mut Window) { pub fn paint(&self, bounds: &Bounds<Pixels>, window: &mut Window) {
let points = self.points(bounds); let points = self.points(bounds);
if let Some(dash_array) = self.dash_array { for (start, end) in points {
for (start, end) in points { let mut builder = PathBuilder::stroke(px(1.));
if let Some(line) = dash_line(start, end, dash_array) {
window.paint_path(line, self.stroke); if let Some(dash_array) = &self.dash_array {
} builder = builder.dash_array(&dash_array);
} }
} else {
for (start, end) in points { builder.move_to(start);
let mut builder = PathBuilder::stroke(px(1.)); builder.line_to(end);
builder.move_to(start); if let Ok(line) = builder.build() {
builder.line_to(end); window.paint_path(line, self.stroke);
if let Ok(line) = builder.build() {
window.paint_path(line, self.stroke);
}
} }
} }
} }

View file

@ -9,10 +9,7 @@ pub use gpui_component_macros::IntoPlot;
use std::{fmt::Debug, ops::Add}; use std::{fmt::Debug, ops::Add};
use gpui::{ use gpui::{point, px, App, Bounds, IntoElement, Path, PathBuilder, Pixels, Point, Window};
point, px, App, Bounds, IntoElement, Path, PathBuilder, PathStyle, Pixels, Point,
StrokeOptions, Window,
};
pub use axis::{Axis, AxisText, AXIS_GAP}; pub use axis::{Axis, AxisText, AXIS_GAP};
pub use grid::Grid; pub use grid::Grid;
@ -36,45 +33,6 @@ where
point(x, y) + origin point(x, y) + origin
} }
// TODO: Move into gpui
//
// https://github.com/zed-industries/zed/pull/31678
pub fn dash_line<T>(start: Point<T>, end: Point<T>, dash_array: [T; 2]) -> Option<Path<Pixels>>
where
T: Default + Clone + Copy + Debug + PartialEq + Add<Output = T> + Into<f64>,
{
let mut path = lyon::path::Path::builder();
path.begin(lyon::geom::point(
start.x.into() as f32,
start.y.into() as f32,
));
path.line_to(lyon::geom::point(end.x.into() as f32, end.y.into() as f32));
path.end(false);
let path = path.build();
// Make path dashable.
let measure = lyon::algorithms::measure::PathMeasurements::from_path(&path, 0.01);
let mut sampler =
measure.create_sampler(&path, lyon::algorithms::measure::SampleType::Normalized);
let mut dashes = lyon::path::Path::builder();
let length = sampler.length();
let dash_length = dash_array[0].into() as f32;
let gap_length = dash_array[1].into() as f32;
let pattern_length = dash_length + gap_length;
let num_patterns = (length / pattern_length).ceil() as usize;
for i in 0..num_patterns {
let start = i as f32 * pattern_length / length;
let end = (i as f32 * pattern_length + dash_length) / length;
sampler.split_range(start..end.min(1.), &mut dashes);
}
let mut path: PathBuilder = dashes.into();
path = path.with_style(PathStyle::Stroke(
StrokeOptions::default().with_line_width(1.),
));
path.build().ok()
}
pub fn polygon<T>(points: &[Point<T>], bounds: &Bounds<Pixels>) -> Option<Path<Pixels>> pub fn polygon<T>(points: &[Point<T>], bounds: &Bounds<Pixels>) -> Option<Path<Pixels>>
where where
T: Default + Clone + Copy + Debug + Into<f64> + PartialEq, T: Default + Clone + Copy + Debug + Into<f64> + PartialEq,

View file

@ -676,7 +676,7 @@ impl Element for Scrollbar {
let margin_end = state.margin_end; let margin_end = state.margin_end;
let is_vertical = axis.is_vertical(); let is_vertical = axis.is_vertical();
window.set_cursor_style(CursorStyle::default(), Some(&state.bar_hitbox)); window.set_cursor_style(CursorStyle::default(), &state.bar_hitbox);
window.paint_layer(hitbox_bounds, |cx| { window.paint_layer(hitbox_bounds, |cx| {
cx.paint_quad(fill(state.bounds, state.bg)); cx.paint_quad(fill(state.bounds, state.bg));

View file

@ -107,7 +107,7 @@ impl RenderOnce for WindowBorder {
CursorStyle::ResizeUpRightDownLeft CursorStyle::ResizeUpRightDownLeft
} }
}, },
Some(&hitbox), &hitbox,
); );
}, },
) )