gpui: Add PathBuilder
based on lyon to build Path
(#22808)
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" />
This commit is contained in:
parent
706f7be5e7
commit
31fa414422
8 changed files with 449 additions and 79 deletions
70
Cargo.lock
generated
70
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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::*;
|
||||
|
|
241
crates/gpui/src/path_builder.rs
Normal file
241
crates/gpui/src/path_builder.rs
Normal file
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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>),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue