Skip to repository content473 lines · 16.7 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T05:56:07.227Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
painting.rs
1#![cfg_attr(target_family = "wasm", no_main)]
2
3use gpui::{
4 Background, Bounds, ColorSpace, Context, MouseDownEvent, Path, PathBuilder, PathStyle, Pixels,
5 Point, Render, StrokeOptions, Window, WindowOptions, canvas, div, linear_color_stop,
6 linear_gradient, point, prelude::*, px, quad, rgb, size,
7};
8use gpui_platform::application;
9
10struct PaintingViewer {
11 default_lines: Vec<(Path<Pixels>, Background)>,
12 background_quads: Vec<(Bounds<Pixels>, Background)>,
13 lines: Vec<Vec<Point<Pixels>>>,
14 start: Point<Pixels>,
15 dashed: bool,
16 _painting: bool,
17}
18
19impl PaintingViewer {
20 fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self {
21 let mut lines = vec![];
22
23 // Black squares beneath transparent paths.
24 let background_quads = vec![
25 (
26 Bounds {
27 origin: point(px(70.), px(70.)),
28 size: size(px(40.), px(40.)),
29 },
30 gpui::black().into(),
31 ),
32 (
33 Bounds {
34 origin: point(px(170.), px(70.)),
35 size: size(px(40.), px(40.)),
36 },
37 gpui::black().into(),
38 ),
39 (
40 Bounds {
41 origin: point(px(270.), px(70.)),
42 size: size(px(40.), px(40.)),
43 },
44 gpui::black().into(),
45 ),
46 (
47 Bounds {
48 origin: point(px(370.), px(70.)),
49 size: size(px(40.), px(40.)),
50 },
51 gpui::black().into(),
52 ),
53 (
54 Bounds {
55 origin: point(px(450.), px(50.)),
56 size: size(px(80.), px(80.)),
57 },
58 gpui::black().into(),
59 ),
60 ];
61
62 // 50% opaque red path that extends across black quad.
63 let mut builder = PathBuilder::fill();
64 builder.move_to(point(px(50.), px(50.)));
65 builder.line_to(point(px(130.), px(50.)));
66 builder.line_to(point(px(130.), px(130.)));
67 builder.line_to(point(px(50.), px(130.)));
68 builder.close();
69 let path = builder.build().unwrap();
70 let red = rgb(0xFF0000).alpha(0.5);
71 lines.push((path, red.into()));
72
73 // 50% opaque blue path that extends across black quad.
74 let mut builder = PathBuilder::fill();
75 builder.move_to(point(px(150.), px(50.)));
76 builder.line_to(point(px(230.), px(50.)));
77 builder.line_to(point(px(230.), px(130.)));
78 builder.line_to(point(px(150.), px(130.)));
79 builder.close();
80 let path = builder.build().unwrap();
81 let blue = rgb(0x0000FF).alpha(0.5);
82 lines.push((path, blue.into()));
83
84 // 50% opaque green path that extends across black quad.
85 let mut builder = PathBuilder::fill();
86 builder.move_to(point(px(250.), px(50.)));
87 builder.line_to(point(px(330.), px(50.)));
88 builder.line_to(point(px(330.), px(130.)));
89 builder.line_to(point(px(250.), px(130.)));
90 builder.close();
91 let path = builder.build().unwrap();
92 let green = rgb(0x00FF00).alpha(0.5);
93 lines.push((path, green.into()));
94
95 // 50% opaque black path that extends across black quad.
96 let mut builder = PathBuilder::fill();
97 builder.move_to(point(px(350.), px(50.)));
98 builder.line_to(point(px(430.), px(50.)));
99 builder.line_to(point(px(430.), px(130.)));
100 builder.line_to(point(px(350.), px(130.)));
101 builder.close();
102 let path = builder.build().unwrap();
103 let black = rgb(0x000000).alpha(0.5);
104 lines.push((path, black.into()));
105
106 // Two 50% opaque red circles overlapping - center should be darker red
107 let mut builder = PathBuilder::fill();
108 let center = point(px(530.), px(85.));
109 let radius = px(30.);
110 builder.move_to(point(center.x + radius, center.y));
111 builder.arc_to(
112 point(radius, radius),
113 px(0.),
114 false,
115 false,
116 point(center.x - radius, center.y),
117 );
118 builder.arc_to(
119 point(radius, radius),
120 px(0.),
121 false,
122 false,
123 point(center.x + radius, center.y),
124 );
125 builder.close();
126 let path = builder.build().unwrap();
127 let red1 = rgb(0xFF0000).alpha(0.5);
128 lines.push((path, red1.into()));
129
130 let mut builder = PathBuilder::fill();
131 let center = point(px(570.), px(85.));
132 let radius = px(30.);
133 builder.move_to(point(center.x + radius, center.y));
134 builder.arc_to(
135 point(radius, radius),
136 px(0.),
137 false,
138 false,
139 point(center.x - radius, center.y),
140 );
141 builder.arc_to(
142 point(radius, radius),
143 px(0.),
144 false,
145 false,
146 point(center.x + radius, center.y),
147 );
148 builder.close();
149 let path = builder.build().unwrap();
150 let red2 = rgb(0xFF0000).alpha(0.5);
151 lines.push((path, red2.into()));
152
153 // draw a Rust logo
154 let mut builder = lyon::path::Path::svg_builder();
155 lyon::extra::rust_logo::build_logo_path(&mut builder);
156 // move down the Path
157 let mut builder: PathBuilder = builder.into();
158 builder.translate(point(px(10.), px(200.)));
159 builder.scale(0.9);
160 let path = builder.build().unwrap();
161 lines.push((path, gpui::black().into()));
162
163 // draw a lightening bolt ⚡
164 let mut builder = PathBuilder::fill();
165 builder.add_polygon(
166 &[
167 point(px(150.), px(300.)),
168 point(px(200.), px(225.)),
169 point(px(200.), px(275.)),
170 point(px(250.), px(200.)),
171 ],
172 false,
173 );
174 let path = builder.build().unwrap();
175 lines.push((path, rgb(0x1d4ed8).into()));
176
177 // draw a ⭐
178 let mut builder = PathBuilder::fill();
179 builder.move_to(point(px(350.), px(200.)));
180 builder.line_to(point(px(370.), px(260.)));
181 builder.line_to(point(px(430.), px(260.)));
182 builder.line_to(point(px(380.), px(300.)));
183 builder.line_to(point(px(400.), px(360.)));
184 builder.line_to(point(px(350.), px(320.)));
185 builder.line_to(point(px(300.), px(360.)));
186 builder.line_to(point(px(320.), px(300.)));
187 builder.line_to(point(px(270.), px(260.)));
188 builder.line_to(point(px(330.), px(260.)));
189 builder.line_to(point(px(350.), px(200.)));
190 let path = builder.build().unwrap();
191 lines.push((
192 path,
193 linear_gradient(
194 180.,
195 linear_color_stop(rgb(0xFACC15), 0.7),
196 linear_color_stop(rgb(0xD56D0C), 1.),
197 )
198 .color_space(ColorSpace::Oklab),
199 ));
200
201 // draw linear gradient
202 let square_bounds = Bounds {
203 origin: point(px(450.), px(200.)),
204 size: size(px(200.), px(80.)),
205 };
206 let height = square_bounds.size.height;
207 let horizontal_offset = height;
208 let vertical_offset = px(30.);
209 let mut builder = PathBuilder::fill();
210 builder.move_to(square_bounds.bottom_left());
211 builder.curve_to(
212 square_bounds.origin + point(horizontal_offset, vertical_offset),
213 square_bounds.origin + point(px(0.0), vertical_offset),
214 );
215 builder.line_to(square_bounds.top_right() + point(-horizontal_offset, vertical_offset));
216 builder.curve_to(
217 square_bounds.bottom_right(),
218 square_bounds.top_right() + point(px(0.0), vertical_offset),
219 );
220 builder.line_to(square_bounds.bottom_left());
221 let path = builder.build().unwrap();
222 lines.push((
223 path,
224 linear_gradient(
225 180.,
226 linear_color_stop(gpui::blue(), 0.4),
227 linear_color_stop(gpui::red(), 1.),
228 ),
229 ));
230
231 // draw a pie chart
232 let center = point(px(96.), px(96.));
233 let pie_center = point(px(775.), px(255.));
234 let segments = [
235 (
236 point(px(871.), px(255.)),
237 point(px(747.), px(163.)),
238 rgb(0x1374e9),
239 ),
240 (
241 point(px(747.), px(163.)),
242 point(px(679.), px(263.)),
243 rgb(0xe13527),
244 ),
245 (
246 point(px(679.), px(263.)),
247 point(px(754.), px(349.)),
248 rgb(0x0751ce),
249 ),
250 (
251 point(px(754.), px(349.)),
252 point(px(854.), px(310.)),
253 rgb(0x209742),
254 ),
255 (
256 point(px(854.), px(310.)),
257 point(px(871.), px(255.)),
258 rgb(0xfbc10a),
259 ),
260 ];
261
262 for (start, end, color) in segments {
263 let mut builder = PathBuilder::fill();
264 builder.move_to(start);
265 builder.arc_to(center, px(0.), false, false, end);
266 builder.line_to(pie_center);
267 builder.close();
268 let path = builder.build().unwrap();
269 lines.push((path, color.into()));
270 }
271
272 // draw a wave
273 let options = StrokeOptions::default()
274 .with_line_width(1.)
275 .with_line_join(lyon::path::LineJoin::Bevel);
276 let mut builder = PathBuilder::stroke(px(1.)).with_style(PathStyle::Stroke(options));
277 builder.move_to(point(px(40.), px(420.)));
278 for i in 1..50 {
279 builder.line_to(point(
280 px(40.0 + i as f32 * 10.0),
281 px(420.0 + (i as f32 * 10.0).sin() * 40.0),
282 ));
283 }
284 let path = builder.build().unwrap();
285 lines.push((path, gpui::green().into()));
286
287 Self {
288 default_lines: lines.clone(),
289 background_quads,
290 lines: vec![],
291 start: point(px(0.), px(0.)),
292 dashed: false,
293 _painting: false,
294 }
295 }
296
297 fn clear(&mut self, cx: &mut Context<Self>) {
298 self.lines.clear();
299 cx.notify();
300 }
301}
302
303fn button(
304 text: &str,
305 cx: &mut Context<PaintingViewer>,
306 on_click: impl Fn(&mut PaintingViewer, &mut Context<PaintingViewer>) + 'static,
307) -> impl IntoElement {
308 div()
309 .id(text.to_string())
310 .child(text.to_string())
311 .bg(gpui::black())
312 .text_color(gpui::white())
313 .active(|this| this.opacity(0.8))
314 .flex()
315 .px_3()
316 .py_1()
317 .on_click(cx.listener(move |this, _, _, cx| on_click(this, cx)))
318}
319
320impl Render for PaintingViewer {
321 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
322 let default_lines = self.default_lines.clone();
323 let background_quads = self.background_quads.clone();
324 let lines = self.lines.clone();
325 let dashed = self.dashed;
326
327 div()
328 .bg(gpui::white())
329 .size_full()
330 .p_4()
331 .flex()
332 .flex_col()
333 .child(
334 div()
335 .flex()
336 .gap_2()
337 .justify_between()
338 .items_center()
339 .child("Mouse down any point and drag to draw lines (Hold on shift key to draw straight lines)")
340 .child(
341 div()
342 .flex()
343 .gap_x_2()
344 .child(button(
345 if dashed { "Solid" } else { "Dashed" },
346 cx,
347 move |this, _| this.dashed = !dashed,
348 ))
349 .child(button("Clear", cx, |this, cx| this.clear(cx))),
350 ),
351 )
352 .child(
353 div()
354 .size_full()
355 .child(
356 canvas(
357 move |_, _, _| {},
358 move |_, _, window, _| {
359 // First draw background quads
360 for (bounds, color) in background_quads.iter() {
361 window.paint_quad(quad(
362 *bounds,
363 px(0.),
364 *color,
365 px(0.),
366 gpui::transparent_black(),
367 Default::default(),
368 ));
369 }
370
371 // Then draw the default paths on top
372 for (path, color) in default_lines {
373 window.paint_path(path, color);
374 }
375
376 for points in lines {
377 if points.len() < 2 {
378 continue;
379 }
380
381 let mut builder = PathBuilder::stroke(px(1.));
382 if dashed {
383 builder = builder.dash_array(&[px(4.), px(2.)]);
384 }
385 for (i, p) in points.into_iter().enumerate() {
386 if i == 0 {
387 builder.move_to(p);
388 } else {
389 builder.line_to(p);
390 }
391 }
392
393 if let Ok(path) = builder.build() {
394 window.paint_path(path, gpui::black());
395 }
396 }
397 },
398 )
399 .size_full(),
400 )
401 .on_mouse_down(
402 gpui::MouseButton::Left,
403 cx.listener(|this, ev: &MouseDownEvent, _, _| {
404 this._painting = true;
405 this.start = ev.position;
406 let path = vec![ev.position];
407 this.lines.push(path);
408 }),
409 )
410 .on_mouse_move(cx.listener(|this, ev: &gpui::MouseMoveEvent, _, cx| {
411 if !this._painting {
412 return;
413 }
414
415 let is_shifted = ev.modifiers.shift;
416 let mut pos = ev.position;
417 // When holding shift, draw a straight line
418 if is_shifted {
419 let dx = pos.x - this.start.x;
420 let dy = pos.y - this.start.y;
421 if dx.abs() > dy.abs() {
422 pos.y = this.start.y;
423 } else {
424 pos.x = this.start.x;
425 }
426 }
427
428 if let Some(path) = this.lines.last_mut() {
429 path.push(pos);
430 }
431
432 cx.notify();
433 }))
434 .on_mouse_up(
435 gpui::MouseButton::Left,
436 cx.listener(|this, _, _, _| {
437 this._painting = false;
438 }),
439 ),
440 )
441 }
442}
443
444fn run_example() {
445 application().run(|cx| {
446 cx.open_window(
447 WindowOptions {
448 focus: true,
449 ..Default::default()
450 },
451 |window, cx| cx.new(|cx| PaintingViewer::new(window, cx)),
452 )
453 .unwrap();
454 cx.on_window_closed(|cx, _window_id| {
455 cx.quit();
456 })
457 .detach();
458 cx.activate(true);
459 });
460}
461
462#[cfg(not(target_family = "wasm"))]
463fn main() {
464 run_example();
465}
466
467#[cfg(target_family = "wasm")]
468#[wasm_bindgen::prelude::wasm_bindgen(start)]
469pub fn start() {
470 gpui_platform::web_init();
471 run_example();
472}
473