painting.rs

  1use gpui::{
  2    canvas, div, point, prelude::*, px, size, App, AppContext, Bounds, MouseDownEvent, Path,
  3    Pixels, Point, Render, ViewContext, WindowOptions,
  4};
  5struct PaintingViewer {
  6    default_lines: Vec<Path<Pixels>>,
  7    lines: Vec<Vec<Point<Pixels>>>,
  8    start: Point<Pixels>,
  9    _painting: bool,
 10}
 11
 12impl PaintingViewer {
 13    fn new() -> Self {
 14        let mut lines = vec![];
 15
 16        // draw a line
 17        let mut path = Path::new(point(px(50.), px(180.)));
 18        path.line_to(point(px(100.), px(120.)));
 19        // go back to close the path
 20        path.line_to(point(px(100.), px(121.)));
 21        path.line_to(point(px(50.), px(181.)));
 22        lines.push(path);
 23
 24        // draw a lightening bolt ⚡
 25        let mut path = Path::new(point(px(150.), px(200.)));
 26        path.line_to(point(px(200.), px(125.)));
 27        path.line_to(point(px(200.), px(175.)));
 28        path.line_to(point(px(250.), px(100.)));
 29        lines.push(path);
 30
 31        // draw a ⭐
 32        let mut path = Path::new(point(px(350.), px(100.)));
 33        path.line_to(point(px(370.), px(160.)));
 34        path.line_to(point(px(430.), px(160.)));
 35        path.line_to(point(px(380.), px(200.)));
 36        path.line_to(point(px(400.), px(260.)));
 37        path.line_to(point(px(350.), px(220.)));
 38        path.line_to(point(px(300.), px(260.)));
 39        path.line_to(point(px(320.), px(200.)));
 40        path.line_to(point(px(270.), px(160.)));
 41        path.line_to(point(px(330.), px(160.)));
 42        path.line_to(point(px(350.), px(100.)));
 43        lines.push(path);
 44
 45        let square_bounds = Bounds {
 46            origin: point(px(450.), px(100.)),
 47            size: size(px(200.), px(80.)),
 48        };
 49        let height = square_bounds.size.height;
 50        let horizontal_offset = height;
 51        let vertical_offset = px(30.);
 52        let mut path = Path::new(square_bounds.bottom_left());
 53        path.curve_to(
 54            square_bounds.origin + point(horizontal_offset, vertical_offset),
 55            square_bounds.origin + point(px(0.0), vertical_offset),
 56        );
 57        path.line_to(square_bounds.top_right() + point(-horizontal_offset, vertical_offset));
 58        path.curve_to(
 59            square_bounds.bottom_right(),
 60            square_bounds.top_right() + point(px(0.0), vertical_offset),
 61        );
 62        path.line_to(square_bounds.bottom_left());
 63        lines.push(path);
 64
 65        Self {
 66            default_lines: lines.clone(),
 67            lines: vec![],
 68            start: point(px(0.), px(0.)),
 69            _painting: false,
 70        }
 71    }
 72
 73    fn clear(&mut self, cx: &mut ViewContext<Self>) {
 74        self.lines.clear();
 75        cx.notify();
 76    }
 77}
 78impl Render for PaintingViewer {
 79    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
 80        let default_lines = self.default_lines.clone();
 81        let lines = self.lines.clone();
 82        div()
 83            .font_family(".SystemUIFont")
 84            .bg(gpui::white())
 85            .size_full()
 86            .p_4()
 87            .flex()
 88            .flex_col()
 89            .child(
 90                div()
 91                    .flex()
 92                    .gap_2()
 93                    .justify_between()
 94                    .items_center()
 95                    .child("Mouse down any point and drag to draw lines (Hold on shift key to draw straight lines)")
 96                    .child(
 97                        div()
 98                            .id("clear")
 99                            .child("Clean up")
100                            .bg(gpui::black())
101                            .text_color(gpui::white())
102                            .active(|this| this.opacity(0.8))
103                            .flex()
104                            .px_3()
105                            .py_1()
106                            .on_click(cx.listener(|this, _, cx| {
107                                this.clear(cx);
108                            })),
109                    ),
110            )
111            .child(
112                div()
113                    .size_full()
114                    .child(
115                        canvas(
116                            move |_, _| {},
117                            move |_, _, cx| {
118                                const STROKE_WIDTH: Pixels = px(2.0);
119                                for path in default_lines {
120                                    cx.paint_path(path, gpui::black());
121                                }
122                                for points in lines {
123                                    let mut path = Path::new(points[0]);
124                                    for p in points.iter().skip(1) {
125                                        path.line_to(*p);
126                                    }
127
128                                    let mut last = points.last().unwrap();
129                                    for p in points.iter().rev() {
130                                        let mut offset_x = px(0.);
131                                        if last.x == p.x {
132                                            offset_x = STROKE_WIDTH;
133                                        }
134                                        path.line_to(point(p.x + offset_x, p.y  + STROKE_WIDTH));
135                                        last = p;
136                                    }
137
138                                    cx.paint_path(path, gpui::black());
139                                }
140                            },
141                        )
142                        .size_full(),
143                    )
144                    .on_mouse_down(
145                        gpui::MouseButton::Left,
146                        cx.listener(|this, ev: &MouseDownEvent, _| {
147                            this._painting = true;
148                            this.start = ev.position;
149                            let path = vec![ev.position];
150                            this.lines.push(path);
151                        }),
152                    )
153                    .on_mouse_move(cx.listener(|this, ev: &gpui::MouseMoveEvent, cx| {
154                        if !this._painting {
155                            return;
156                        }
157
158                        let is_shifted = ev.modifiers.shift;
159                        let mut pos = ev.position;
160                        // When holding shift, draw a straight line
161                        if is_shifted {
162                            let dx = pos.x - this.start.x;
163                            let dy = pos.y - this.start.y;
164                            if dx.abs() > dy.abs() {
165                                pos.y = this.start.y;
166                            } else {
167                                pos.x = this.start.x;
168                            }
169                        }
170
171                        if let Some(path) = this.lines.last_mut() {
172                            path.push(pos);
173                        }
174
175                        cx.notify();
176                    }))
177                    .on_mouse_up(
178                        gpui::MouseButton::Left,
179                        cx.listener(|this, _, _| {
180                            this._painting = false;
181                        }),
182                    ),
183            )
184    }
185}
186
187fn main() {
188    App::new().run(|cx: &mut AppContext| {
189        cx.open_window(
190            WindowOptions {
191                focus: true,
192                ..Default::default()
193            },
194            |cx| cx.new_view(|_| PaintingViewer::new()),
195        )
196        .unwrap();
197        cx.activate(true);
198    });
199}