painting.rs

  1use gpui::{
  2    Application, Background, Bounds, ColorSpace, Context, MouseDownEvent, Path, PathBuilder,
  3    PathStyle, Pixels, Point, Render, SharedString, StrokeOptions, Window, WindowOptions, canvas,
  4    div, linear_color_stop, linear_gradient, point, prelude::*, px, rgb, size,
  5};
  6
  7struct PaintingViewer {
  8    default_lines: Vec<(Path<Pixels>, Background)>,
  9    lines: Vec<Vec<Point<Pixels>>>,
 10    start: Point<Pixels>,
 11    dashed: bool,
 12    _painting: bool,
 13}
 14
 15impl PaintingViewer {
 16    fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self {
 17        let mut lines = vec![];
 18
 19        // draw a Rust logo
 20        let mut builder = lyon::path::Path::svg_builder();
 21        lyon::extra::rust_logo::build_logo_path(&mut builder);
 22        // move down the Path
 23        let mut builder: PathBuilder = builder.into();
 24        builder.translate(point(px(10.), px(100.)));
 25        builder.scale(0.9);
 26        let path = builder.build().unwrap();
 27        lines.push((path, gpui::black().into()));
 28
 29        // draw a lightening bolt ⚡
 30        let mut builder = PathBuilder::fill();
 31        builder.add_polygon(
 32            &[
 33                point(px(150.), px(200.)),
 34                point(px(200.), px(125.)),
 35                point(px(200.), px(175.)),
 36                point(px(250.), px(100.)),
 37            ],
 38            false,
 39        );
 40        let path = builder.build().unwrap();
 41        lines.push((path, rgb(0x1d4ed8).into()));
 42
 43        // draw a ⭐
 44        let mut builder = PathBuilder::fill();
 45        builder.move_to(point(px(350.), px(100.)));
 46        builder.line_to(point(px(370.), px(160.)));
 47        builder.line_to(point(px(430.), px(160.)));
 48        builder.line_to(point(px(380.), px(200.)));
 49        builder.line_to(point(px(400.), px(260.)));
 50        builder.line_to(point(px(350.), px(220.)));
 51        builder.line_to(point(px(300.), px(260.)));
 52        builder.line_to(point(px(320.), px(200.)));
 53        builder.line_to(point(px(270.), px(160.)));
 54        builder.line_to(point(px(330.), px(160.)));
 55        builder.line_to(point(px(350.), px(100.)));
 56        let path = builder.build().unwrap();
 57        lines.push((
 58            path,
 59            linear_gradient(
 60                180.,
 61                linear_color_stop(rgb(0xFACC15), 0.7),
 62                linear_color_stop(rgb(0xD56D0C), 1.),
 63            )
 64            .color_space(ColorSpace::Oklab),
 65        ));
 66
 67        // draw linear gradient
 68        let square_bounds = Bounds {
 69            origin: point(px(450.), px(100.)),
 70            size: size(px(200.), px(80.)),
 71        };
 72        let height = square_bounds.size.height;
 73        let horizontal_offset = height;
 74        let vertical_offset = px(30.);
 75        let mut builder = PathBuilder::fill();
 76        builder.move_to(square_bounds.bottom_left());
 77        builder.curve_to(
 78            square_bounds.origin + point(horizontal_offset, vertical_offset),
 79            square_bounds.origin + point(px(0.0), vertical_offset),
 80        );
 81        builder.line_to(square_bounds.top_right() + point(-horizontal_offset, vertical_offset));
 82        builder.curve_to(
 83            square_bounds.bottom_right(),
 84            square_bounds.top_right() + point(px(0.0), vertical_offset),
 85        );
 86        builder.line_to(square_bounds.bottom_left());
 87        let path = builder.build().unwrap();
 88        lines.push((
 89            path,
 90            linear_gradient(
 91                180.,
 92                linear_color_stop(gpui::blue(), 0.4),
 93                linear_color_stop(gpui::red(), 1.),
 94            ),
 95        ));
 96
 97        // draw a pie chart
 98        let center = point(px(96.), px(96.));
 99        let pie_center = point(px(775.), px(155.));
100        let segments = [
101            (
102                point(px(871.), px(155.)),
103                point(px(747.), px(63.)),
104                rgb(0x1374e9),
105            ),
106            (
107                point(px(747.), px(63.)),
108                point(px(679.), px(163.)),
109                rgb(0xe13527),
110            ),
111            (
112                point(px(679.), px(163.)),
113                point(px(754.), px(249.)),
114                rgb(0x0751ce),
115            ),
116            (
117                point(px(754.), px(249.)),
118                point(px(854.), px(210.)),
119                rgb(0x209742),
120            ),
121            (
122                point(px(854.), px(210.)),
123                point(px(871.), px(155.)),
124                rgb(0xfbc10a),
125            ),
126        ];
127
128        for (start, end, color) in segments {
129            let mut builder = PathBuilder::fill();
130            builder.move_to(start);
131            builder.arc_to(center, px(0.), false, false, end);
132            builder.line_to(pie_center);
133            builder.close();
134            let path = builder.build().unwrap();
135            lines.push((path, color.into()));
136        }
137
138        // draw a wave
139        let options = StrokeOptions::default()
140            .with_line_width(1.)
141            .with_line_join(lyon::path::LineJoin::Bevel);
142        let mut builder = PathBuilder::stroke(px(1.)).with_style(PathStyle::Stroke(options));
143        builder.move_to(point(px(40.), px(320.)));
144        for i in 1..50 {
145            builder.line_to(point(
146                px(40.0 + i as f32 * 10.0),
147                px(320.0 + (i as f32 * 10.0).sin() * 40.0),
148            ));
149        }
150        let path = builder.build().unwrap();
151        lines.push((path, gpui::green().into()));
152
153        Self {
154            default_lines: lines.clone(),
155            lines: vec![],
156            start: point(px(0.), px(0.)),
157            dashed: false,
158            _painting: false,
159        }
160    }
161
162    fn clear(&mut self, cx: &mut Context<Self>) {
163        self.lines.clear();
164        cx.notify();
165    }
166}
167
168fn button(
169    text: &str,
170    cx: &mut Context<PaintingViewer>,
171    on_click: impl Fn(&mut PaintingViewer, &mut Context<PaintingViewer>) + 'static,
172) -> impl IntoElement {
173    div()
174        .id(SharedString::from(text.to_string()))
175        .child(text.to_string())
176        .bg(gpui::black())
177        .text_color(gpui::white())
178        .active(|this| this.opacity(0.8))
179        .flex()
180        .px_3()
181        .py_1()
182        .on_click(cx.listener(move |this, _, _, cx| on_click(this, cx)))
183}
184
185impl Render for PaintingViewer {
186    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
187        let default_lines = self.default_lines.clone();
188        let lines = self.lines.clone();
189        let dashed = self.dashed;
190
191        div()
192            .font_family(".SystemUIFont")
193            .bg(gpui::white())
194            .size_full()
195            .p_4()
196            .flex()
197            .flex_col()
198            .child(
199                div()
200                    .flex()
201                    .gap_2()
202                    .justify_between()
203                    .items_center()
204                    .child("Mouse down any point and drag to draw lines (Hold on shift key to draw straight lines)")
205                    .child(
206                        div()
207                            .flex()
208                            .gap_x_2()
209                            .child(button(
210                                if dashed { "Solid" } else { "Dashed" },
211                                cx,
212                                move |this, _| this.dashed = !dashed,
213                            ))
214                            .child(button("Clear", cx, |this, cx| this.clear(cx))),
215                    ),
216            )
217            .child(
218                div()
219                    .size_full()
220                    .child(
221                        canvas(
222                            move |_, _, _| {},
223                            move |_, _, window, _| {
224                                for (path, color) in default_lines {
225                                    window.paint_path(path, color);
226                                }
227
228                                for points in lines {
229                                    if points.len() < 2 {
230                                        continue;
231                                    }
232
233                                    let mut builder = PathBuilder::stroke(px(1.));
234                                    if dashed {
235                                        builder = builder.dash_array(&[px(4.), px(2.)]);
236                                    }
237                                    for (i, p) in points.into_iter().enumerate() {
238                                        if i == 0 {
239                                            builder.move_to(p);
240                                        } else {
241                                            builder.line_to(p);
242                                        }
243                                    }
244
245                                    if let Ok(path) = builder.build() {
246                                        window.paint_path(path, gpui::black());
247                                    }
248                                }
249                            },
250                        )
251                        .size_full(),
252                    )
253                    .on_mouse_down(
254                        gpui::MouseButton::Left,
255                        cx.listener(|this, ev: &MouseDownEvent, _, _| {
256                            this._painting = true;
257                            this.start = ev.position;
258                            let path = vec![ev.position];
259                            this.lines.push(path);
260                        }),
261                    )
262                    .on_mouse_move(cx.listener(|this, ev: &gpui::MouseMoveEvent, _, cx| {
263                        if !this._painting {
264                            return;
265                        }
266
267                        let is_shifted = ev.modifiers.shift;
268                        let mut pos = ev.position;
269                        // When holding shift, draw a straight line
270                        if is_shifted {
271                            let dx = pos.x - this.start.x;
272                            let dy = pos.y - this.start.y;
273                            if dx.abs() > dy.abs() {
274                                pos.y = this.start.y;
275                            } else {
276                                pos.x = this.start.x;
277                            }
278                        }
279
280                        if let Some(path) = this.lines.last_mut() {
281                            path.push(pos);
282                        }
283
284                        cx.notify();
285                    }))
286                    .on_mouse_up(
287                        gpui::MouseButton::Left,
288                        cx.listener(|this, _, _, _| {
289                            this._painting = false;
290                        }),
291                    ),
292            )
293    }
294}
295
296fn main() {
297    Application::new().run(|cx| {
298        cx.open_window(
299            WindowOptions {
300                focus: true,
301                ..Default::default()
302            },
303            |window, cx| cx.new(|cx| PaintingViewer::new(window, cx)),
304        )
305        .unwrap();
306        cx.activate(true);
307    });
308}