
Release Notes: - N/A ---- To fix #24289 mention issue and revert PathBuilder and MSAA. I'm sorry about of this, in #22808 I was forgotten this bit of detail.  So, add `move_to` here, we can fix the selection top right corner radius issue. ## After change <img width="1383" alt="image" src="https://github.com/user-attachments/assets/28ea103c-d652-41d6-bbe0-7fd042d81e77" />
240 lines
8.8 KiB
Rust
240 lines
8.8 KiB
Rust
use gpui::{
|
|
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>, Background)>,
|
|
lines: Vec<Vec<Point<Pixels>>>,
|
|
start: Point<Pixels>,
|
|
_painting: bool,
|
|
}
|
|
|
|
impl PaintingViewer {
|
|
fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self {
|
|
let mut lines = vec![];
|
|
|
|
// 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 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 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.)),
|
|
size: size(px(200.), px(80.)),
|
|
};
|
|
let height = square_bounds.size.height;
|
|
let horizontal_offset = height;
|
|
let vertical_offset = px(30.);
|
|
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),
|
|
);
|
|
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),
|
|
);
|
|
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(),
|
|
lines: vec![],
|
|
start: point(px(0.), px(0.)),
|
|
_painting: false,
|
|
}
|
|
}
|
|
|
|
fn clear(&mut self, cx: &mut Context<Self>) {
|
|
self.lines.clear();
|
|
cx.notify();
|
|
}
|
|
}
|
|
impl Render for PaintingViewer {
|
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
|
let default_lines = self.default_lines.clone();
|
|
let lines = self.lines.clone();
|
|
div()
|
|
.font_family(".SystemUIFont")
|
|
.bg(gpui::white())
|
|
.size_full()
|
|
.p_4()
|
|
.flex()
|
|
.flex_col()
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.gap_2()
|
|
.justify_between()
|
|
.items_center()
|
|
.child("Mouse down any point and drag to draw lines (Hold on shift key to draw straight lines)")
|
|
.child(
|
|
div()
|
|
.id("clear")
|
|
.child("Clean up")
|
|
.bg(gpui::black())
|
|
.text_color(gpui::white())
|
|
.active(|this| this.opacity(0.8))
|
|
.flex()
|
|
.px_3()
|
|
.py_1()
|
|
.on_click(cx.listener(|this, _, _, cx| {
|
|
this.clear(cx);
|
|
})),
|
|
),
|
|
)
|
|
.child(
|
|
div()
|
|
.size_full()
|
|
.child(
|
|
canvas(
|
|
move |_, _, _| {},
|
|
move |_, _, window, _| {
|
|
|
|
for (path, color) in default_lines {
|
|
window.paint_path(path, color);
|
|
}
|
|
|
|
for points in lines {
|
|
if points.len() < 2 {
|
|
continue;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
if let Ok(path) = builder.build() {
|
|
window.paint_path(path, gpui::black());
|
|
}
|
|
}
|
|
},
|
|
)
|
|
.size_full(),
|
|
)
|
|
.on_mouse_down(
|
|
gpui::MouseButton::Left,
|
|
cx.listener(|this, ev: &MouseDownEvent, _, _| {
|
|
this._painting = true;
|
|
this.start = ev.position;
|
|
let path = vec![ev.position];
|
|
this.lines.push(path);
|
|
}),
|
|
)
|
|
.on_mouse_move(cx.listener(|this, ev: &gpui::MouseMoveEvent, _, cx| {
|
|
if !this._painting {
|
|
return;
|
|
}
|
|
|
|
let is_shifted = ev.modifiers.shift;
|
|
let mut pos = ev.position;
|
|
// When holding shift, draw a straight line
|
|
if is_shifted {
|
|
let dx = pos.x - this.start.x;
|
|
let dy = pos.y - this.start.y;
|
|
if dx.abs() > dy.abs() {
|
|
pos.y = this.start.y;
|
|
} else {
|
|
pos.x = this.start.x;
|
|
}
|
|
}
|
|
|
|
if let Some(path) = this.lines.last_mut() {
|
|
path.push(pos);
|
|
}
|
|
|
|
cx.notify();
|
|
}))
|
|
.on_mouse_up(
|
|
gpui::MouseButton::Left,
|
|
cx.listener(|this, _, _, _| {
|
|
this._painting = false;
|
|
}),
|
|
),
|
|
)
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
Application::new().run(|cx| {
|
|
cx.open_window(
|
|
WindowOptions {
|
|
focus: true,
|
|
..Default::default()
|
|
},
|
|
|window, cx| cx.new(|cx| PaintingViewer::new(window, cx)),
|
|
)
|
|
.unwrap();
|
|
cx.activate(true);
|
|
});
|
|
}
|