Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:49:08.026Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

painting.rs

473 lines · 16.7 KB · rust
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
Served at tenant.openagents/omega Member data and write actions are omitted.