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.move_to(point(px(150.), px(200.)));
 31        builder.line_to(point(px(200.), px(125.)));
 32        builder.line_to(point(px(200.), px(175.)));
 33        builder.line_to(point(px(250.), px(100.)));
 34        let path = builder.build().unwrap();
 35        lines.push((path, rgb(0x1d4ed8).into()));
 36
 37        // draw a ⭐
 38        let mut builder = PathBuilder::fill();
 39        builder.move_to(point(px(350.), px(100.)));
 40        builder.line_to(point(px(370.), px(160.)));
 41        builder.line_to(point(px(430.), px(160.)));
 42        builder.line_to(point(px(380.), px(200.)));
 43        builder.line_to(point(px(400.), px(260.)));
 44        builder.line_to(point(px(350.), px(220.)));
 45        builder.line_to(point(px(300.), px(260.)));
 46        builder.line_to(point(px(320.), px(200.)));
 47        builder.line_to(point(px(270.), px(160.)));
 48        builder.line_to(point(px(330.), px(160.)));
 49        builder.line_to(point(px(350.), px(100.)));
 50        let path = builder.build().unwrap();
 51        lines.push((
 52            path,
 53            linear_gradient(
 54                180.,
 55                linear_color_stop(rgb(0xFACC15), 0.7),
 56                linear_color_stop(rgb(0xD56D0C), 1.),
 57            )
 58            .color_space(ColorSpace::Oklab),
 59        ));
 60
 61        let square_bounds = Bounds {
 62            origin: point(px(450.), px(100.)),
 63            size: size(px(200.), px(80.)),
 64        };
 65        let height = square_bounds.size.height;
 66        let horizontal_offset = height;
 67        let vertical_offset = px(30.);
 68        let mut builder = PathBuilder::fill();
 69        builder.move_to(square_bounds.bottom_left());
 70        builder.curve_to(
 71            square_bounds.origin + point(horizontal_offset, vertical_offset),
 72            square_bounds.origin + point(px(0.0), vertical_offset),
 73        );
 74        builder.line_to(square_bounds.top_right() + point(-horizontal_offset, vertical_offset));
 75        builder.curve_to(
 76            square_bounds.bottom_right(),
 77            square_bounds.top_right() + point(px(0.0), vertical_offset),
 78        );
 79        builder.line_to(square_bounds.bottom_left());
 80        let path = builder.build().unwrap();
 81        lines.push((
 82            path,
 83            linear_gradient(
 84                180.,
 85                linear_color_stop(gpui::blue(), 0.4),
 86                linear_color_stop(gpui::red(), 1.),
 87            ),
 88        ));
 89
 90        // draw a wave
 91        let options = StrokeOptions::default()
 92            .with_line_width(1.)
 93            .with_line_join(lyon::path::LineJoin::Bevel);
 94        let mut builder = PathBuilder::stroke(px(1.)).with_style(PathStyle::Stroke(options));
 95        builder.move_to(point(px(40.), px(320.)));
 96        for i in 0..50 {
 97            builder.line_to(point(
 98                px(40.0 + i as f32 * 10.0),
 99                px(320.0 + (i as f32 * 10.0).sin() * 40.0),
100            ));
101        }
102        let path = builder.build().unwrap();
103        lines.push((path, gpui::green().into()));
104
105        Self {
106            default_lines: lines.clone(),
107            lines: vec![],
108            start: point(px(0.), px(0.)),
109            _painting: false,
110        }
111    }
112
113    fn clear(&mut self, cx: &mut Context<Self>) {
114        self.lines.clear();
115        cx.notify();
116    }
117}
118impl Render for PaintingViewer {
119    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
120        let default_lines = self.default_lines.clone();
121        let lines = self.lines.clone();
122        div()
123            .font_family(".SystemUIFont")
124            .bg(gpui::white())
125            .size_full()
126            .p_4()
127            .flex()
128            .flex_col()
129            .child(
130                div()
131                    .flex()
132                    .gap_2()
133                    .justify_between()
134                    .items_center()
135                    .child("Mouse down any point and drag to draw lines (Hold on shift key to draw straight lines)")
136                    .child(
137                        div()
138                            .id("clear")
139                            .child("Clean up")
140                            .bg(gpui::black())
141                            .text_color(gpui::white())
142                            .active(|this| this.opacity(0.8))
143                            .flex()
144                            .px_3()
145                            .py_1()
146                            .on_click(cx.listener(|this, _, _, cx| {
147                                this.clear(cx);
148                            })),
149                    ),
150            )
151            .child(
152                div()
153                    .size_full()
154                    .child(
155                        canvas(
156                            move |_, _, _| {},
157                            move |_, _, window, _| {
158
159                                for (path, color) in default_lines {
160                                    window.paint_path(path, color);
161                                }
162
163                                for points in lines {
164                                    if points.len() < 2 {
165                                        continue;
166                                    }
167
168                                    let mut builder = PathBuilder::stroke(px(1.));
169                                    for (i, p) in points.into_iter().enumerate() {
170                                        if i == 0 {
171                                            builder.move_to(p);
172                                        } else {
173                                            builder.line_to(p);
174                                        }
175                                    }
176
177                                    if let Ok(path) = builder.build() {
178                                        window.paint_path(path, gpui::black());
179                                    }
180                                }
181                            },
182                        )
183                        .size_full(),
184                    )
185                    .on_mouse_down(
186                        gpui::MouseButton::Left,
187                        cx.listener(|this, ev: &MouseDownEvent, _, _| {
188                            this._painting = true;
189                            this.start = ev.position;
190                            let path = vec![ev.position];
191                            this.lines.push(path);
192                        }),
193                    )
194                    .on_mouse_move(cx.listener(|this, ev: &gpui::MouseMoveEvent, _, cx| {
195                        if !this._painting {
196                            return;
197                        }
198
199                        let is_shifted = ev.modifiers.shift;
200                        let mut pos = ev.position;
201                        // When holding shift, draw a straight line
202                        if is_shifted {
203                            let dx = pos.x - this.start.x;
204                            let dy = pos.y - this.start.y;
205                            if dx.abs() > dy.abs() {
206                                pos.y = this.start.y;
207                            } else {
208                                pos.x = this.start.x;
209                            }
210                        }
211
212                        if let Some(path) = this.lines.last_mut() {
213                            path.push(pos);
214                        }
215
216                        cx.notify();
217                    }))
218                    .on_mouse_up(
219                        gpui::MouseButton::Left,
220                        cx.listener(|this, _, _, _| {
221                            this._painting = false;
222                        }),
223                    ),
224            )
225    }
226}
227
228fn main() {
229    Application::new().run(|cx| {
230        cx.open_window(
231            WindowOptions {
232                focus: true,
233                ..Default::default()
234            },
235            |window, cx| cx.new(|cx| PaintingViewer::new(window, cx)),
236        )
237        .unwrap();
238        cx.activate(true);
239    });
240}