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}