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}