From 792c1e47104a61e79b4505e66064e39f9f7ff57d Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Sat, 16 Nov 2024 19:36:13 +0800 Subject: [PATCH] gpui: Add paint_path example (#20499) Release Notes: - N/A --- ``` cargo run -p gpui --example painting ``` I added this demo to verify the detailed support of Path drawing in GPUI. Because of, when we actually used GPUI to draw a 2D line chart, we found that the straight line Path#line_to did not support `anti-aliasing`, and the drawn line looked very bad. As shown in the demo image, if we zoom in on the image, we can clearly see that all the lines are jagged. I read and tried to make some appropriate adjustments to the functions in Path, but since I have no experience in the graphics field, I still cannot achieve anti-aliasing support so far. I don't know if I used it wrong somewhere. I checked `curve_to` and found that the curves drawn have anti-aliasing effects, as shown in the arc part of the figure below. image --- crates/gpui/examples/painting.rs | 199 +++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 crates/gpui/examples/painting.rs diff --git a/crates/gpui/examples/painting.rs b/crates/gpui/examples/painting.rs new file mode 100644 index 0000000000000000000000000000000000000000..6e5fe25dfd2b45a90ed3b0dd4682454c87d7a5a0 --- /dev/null +++ b/crates/gpui/examples/painting.rs @@ -0,0 +1,199 @@ +use gpui::{ + canvas, div, point, prelude::*, px, size, App, AppContext, Bounds, MouseDownEvent, Path, + Pixels, Point, Render, ViewContext, WindowOptions, +}; +struct PaintingViewer { + default_lines: Vec>, + lines: Vec>>, + start: Point, + _painting: bool, +} + +impl PaintingViewer { + fn new() -> 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 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); + + // 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 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 path = Path::new(square_bounds.lower_left()); + path.curve_to( + square_bounds.origin + point(horizontal_offset, vertical_offset), + square_bounds.origin + point(px(0.0), vertical_offset), + ); + path.line_to(square_bounds.upper_right() + point(-horizontal_offset, vertical_offset)); + path.curve_to( + square_bounds.lower_right(), + square_bounds.upper_right() + point(px(0.0), vertical_offset), + ); + path.line_to(square_bounds.lower_left()); + lines.push(path); + + Self { + default_lines: lines.clone(), + lines: vec![], + start: point(px(0.), px(0.)), + _painting: false, + } + } + + fn clear(&mut self, cx: &mut ViewContext) { + self.lines.clear(); + cx.notify(); + } +} +impl Render for PaintingViewer { + fn render(&mut self, cx: &mut ViewContext) -> 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 |_, _, cx| { + const STROKE_WIDTH: Pixels = px(2.0); + for path in default_lines { + cx.paint_path(path, gpui::black()); + } + for points in lines { + let mut path = Path::new(points[0]); + for p in points.iter().skip(1) { + path.line_to(*p); + } + + 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; + } + path.line_to(point(p.x + offset_x, p.y + STROKE_WIDTH)); + last = p; + } + + cx.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() { + App::new().run(|cx: &mut AppContext| { + cx.open_window( + WindowOptions { + focus: true, + ..Default::default() + }, + |cx| cx.new_view(|_| PaintingViewer::new()), + ) + .unwrap(); + cx.activate(true); + }); +}