painting.rs

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