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.bottom_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.top_right() + point(-horizontal_offset, vertical_offset)); path.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); 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); }); }