1use gpui::{
2 canvas, div, point, prelude::*, px, size, App, AppContext, Bounds, MouseDownEvent, Path,
3 Pixels, Point, Render, ViewContext, WindowOptions,
4};
5struct PaintingViewer {
6 default_lines: Vec<Path<Pixels>>,
7 lines: Vec<Vec<Point<Pixels>>>,
8 start: Point<Pixels>,
9 _painting: bool,
10}
11
12impl PaintingViewer {
13 fn new() -> Self {
14 let mut lines = vec![];
15
16 // draw a line
17 let mut path = Path::new(point(px(50.), px(180.)));
18 path.line_to(point(px(100.), px(120.)));
19 // go back to close the path
20 path.line_to(point(px(100.), px(121.)));
21 path.line_to(point(px(50.), px(181.)));
22 lines.push(path);
23
24 // draw a lightening bolt ⚡
25 let mut path = Path::new(point(px(150.), px(200.)));
26 path.line_to(point(px(200.), px(125.)));
27 path.line_to(point(px(200.), px(175.)));
28 path.line_to(point(px(250.), px(100.)));
29 lines.push(path);
30
31 // draw a ⭐
32 let mut path = Path::new(point(px(350.), px(100.)));
33 path.line_to(point(px(370.), px(160.)));
34 path.line_to(point(px(430.), px(160.)));
35 path.line_to(point(px(380.), px(200.)));
36 path.line_to(point(px(400.), px(260.)));
37 path.line_to(point(px(350.), px(220.)));
38 path.line_to(point(px(300.), px(260.)));
39 path.line_to(point(px(320.), px(200.)));
40 path.line_to(point(px(270.), px(160.)));
41 path.line_to(point(px(330.), px(160.)));
42 path.line_to(point(px(350.), px(100.)));
43 lines.push(path);
44
45 let square_bounds = Bounds {
46 origin: point(px(450.), px(100.)),
47 size: size(px(200.), px(80.)),
48 };
49 let height = square_bounds.size.height;
50 let horizontal_offset = height;
51 let vertical_offset = px(30.);
52 let mut path = Path::new(square_bounds.bottom_left());
53 path.curve_to(
54 square_bounds.origin + point(horizontal_offset, vertical_offset),
55 square_bounds.origin + point(px(0.0), vertical_offset),
56 );
57 path.line_to(square_bounds.top_right() + point(-horizontal_offset, vertical_offset));
58 path.curve_to(
59 square_bounds.bottom_right(),
60 square_bounds.top_right() + point(px(0.0), vertical_offset),
61 );
62 path.line_to(square_bounds.bottom_left());
63 lines.push(path);
64
65 Self {
66 default_lines: lines.clone(),
67 lines: vec![],
68 start: point(px(0.), px(0.)),
69 _painting: false,
70 }
71 }
72
73 fn clear(&mut self, cx: &mut ViewContext<Self>) {
74 self.lines.clear();
75 cx.notify();
76 }
77}
78impl Render for PaintingViewer {
79 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
80 let default_lines = self.default_lines.clone();
81 let lines = self.lines.clone();
82 div()
83 .font_family(".SystemUIFont")
84 .bg(gpui::white())
85 .size_full()
86 .p_4()
87 .flex()
88 .flex_col()
89 .child(
90 div()
91 .flex()
92 .gap_2()
93 .justify_between()
94 .items_center()
95 .child("Mouse down any point and drag to draw lines (Hold on shift key to draw straight lines)")
96 .child(
97 div()
98 .id("clear")
99 .child("Clean up")
100 .bg(gpui::black())
101 .text_color(gpui::white())
102 .active(|this| this.opacity(0.8))
103 .flex()
104 .px_3()
105 .py_1()
106 .on_click(cx.listener(|this, _, cx| {
107 this.clear(cx);
108 })),
109 ),
110 )
111 .child(
112 div()
113 .size_full()
114 .child(
115 canvas(
116 move |_, _| {},
117 move |_, _, cx| {
118 const STROKE_WIDTH: Pixels = px(2.0);
119 for path in default_lines {
120 cx.paint_path(path, gpui::black());
121 }
122 for points in lines {
123 let mut path = Path::new(points[0]);
124 for p in points.iter().skip(1) {
125 path.line_to(*p);
126 }
127
128 let mut last = points.last().unwrap();
129 for p in points.iter().rev() {
130 let mut offset_x = px(0.);
131 if last.x == p.x {
132 offset_x = STROKE_WIDTH;
133 }
134 path.line_to(point(p.x + offset_x, p.y + STROKE_WIDTH));
135 last = p;
136 }
137
138 cx.paint_path(path, gpui::black());
139 }
140 },
141 )
142 .size_full(),
143 )
144 .on_mouse_down(
145 gpui::MouseButton::Left,
146 cx.listener(|this, ev: &MouseDownEvent, _| {
147 this._painting = true;
148 this.start = ev.position;
149 let path = vec![ev.position];
150 this.lines.push(path);
151 }),
152 )
153 .on_mouse_move(cx.listener(|this, ev: &gpui::MouseMoveEvent, cx| {
154 if !this._painting {
155 return;
156 }
157
158 let is_shifted = ev.modifiers.shift;
159 let mut pos = ev.position;
160 // When holding shift, draw a straight line
161 if is_shifted {
162 let dx = pos.x - this.start.x;
163 let dy = pos.y - this.start.y;
164 if dx.abs() > dy.abs() {
165 pos.y = this.start.y;
166 } else {
167 pos.x = this.start.x;
168 }
169 }
170
171 if let Some(path) = this.lines.last_mut() {
172 path.push(pos);
173 }
174
175 cx.notify();
176 }))
177 .on_mouse_up(
178 gpui::MouseButton::Left,
179 cx.listener(|this, _, _| {
180 this._painting = false;
181 }),
182 ),
183 )
184 }
185}
186
187fn main() {
188 App::new().run(|cx: &mut AppContext| {
189 cx.open_window(
190 WindowOptions {
191 focus: true,
192 ..Default::default()
193 },
194 |cx| cx.new_view(|_| PaintingViewer::new()),
195 )
196 .unwrap();
197 cx.activate(true);
198 });
199}