zed/crates/gpui/examples/painting.rs
2024-12-16 22:57:15 -07:00

199 lines
7.3 KiB
Rust

use gpui::{
canvas, div, point, prelude::*, px, size, App, AppContext, Bounds, MouseDownEvent, Path,
Pixels, Point, Render, ViewContext, WindowOptions,
};
struct PaintingViewer {
default_lines: Vec<Path<Pixels>>,
lines: Vec<Vec<Point<Pixels>>>,
start: Point<Pixels>,
_painting: bool,
}
impl PaintingViewer {
fn new() -> Self {
let mut lines = vec![];
// draw a line
let mut path = Path::new(point(px(50.), px(180.)));
path.line_to(point(px(100.), px(120.)));
// go back to close the path
path.line_to(point(px(100.), px(121.)));
path.line_to(point(px(50.), px(181.)));
lines.push(path);
// draw a lightening bolt ⚡
let mut path = Path::new(point(px(150.), px(200.)));
path.line_to(point(px(200.), px(125.)));
path.line_to(point(px(200.), px(175.)));
path.line_to(point(px(250.), px(100.)));
lines.push(path);
// draw a ⭐
let mut path = Path::new(point(px(350.), px(100.)));
path.line_to(point(px(370.), px(160.)));
path.line_to(point(px(430.), px(160.)));
path.line_to(point(px(380.), px(200.)));
path.line_to(point(px(400.), px(260.)));
path.line_to(point(px(350.), px(220.)));
path.line_to(point(px(300.), px(260.)));
path.line_to(point(px(320.), px(200.)));
path.line_to(point(px(270.), px(160.)));
path.line_to(point(px(330.), px(160.)));
path.line_to(point(px(350.), px(100.)));
lines.push(path);
let square_bounds = Bounds {
origin: point(px(450.), px(100.)),
size: size(px(200.), px(80.)),
};
let height = square_bounds.size.height;
let horizontal_offset = height;
let vertical_offset = px(30.);
let mut path = Path::new(square_bounds.bottom_left());
path.curve_to(
square_bounds.origin + point(horizontal_offset, vertical_offset),
square_bounds.origin + point(px(0.0), vertical_offset),
);
path.line_to(square_bounds.top_right() + point(-horizontal_offset, vertical_offset));
path.curve_to(
square_bounds.bottom_right(),
square_bounds.top_right() + point(px(0.0), vertical_offset),
);
path.line_to(square_bounds.bottom_left());
lines.push(path);
Self {
default_lines: lines.clone(),
lines: vec![],
start: point(px(0.), px(0.)),
_painting: false,
}
}
fn clear(&mut self, cx: &mut ViewContext<Self>) {
self.lines.clear();
cx.notify();
}
}
impl Render for PaintingViewer {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let default_lines = self.default_lines.clone();
let lines = self.lines.clone();
div()
.font_family(".SystemUIFont")
.bg(gpui::white())
.size_full()
.p_4()
.flex()
.flex_col()
.child(
div()
.flex()
.gap_2()
.justify_between()
.items_center()
.child("Mouse down any point and drag to draw lines (Hold on shift key to draw straight lines)")
.child(
div()
.id("clear")
.child("Clean up")
.bg(gpui::black())
.text_color(gpui::white())
.active(|this| this.opacity(0.8))
.flex()
.px_3()
.py_1()
.on_click(cx.listener(|this, _, cx| {
this.clear(cx);
})),
),
)
.child(
div()
.size_full()
.child(
canvas(
move |_, _| {},
move |_, _, cx| {
const STROKE_WIDTH: Pixels = px(2.0);
for path in default_lines {
cx.paint_path(path, gpui::black());
}
for points in lines {
let mut path = Path::new(points[0]);
for p in points.iter().skip(1) {
path.line_to(*p);
}
let mut last = points.last().unwrap();
for p in points.iter().rev() {
let mut offset_x = px(0.);
if last.x == p.x {
offset_x = STROKE_WIDTH;
}
path.line_to(point(p.x + offset_x, p.y + STROKE_WIDTH));
last = p;
}
cx.paint_path(path, gpui::black());
}
},
)
.size_full(),
)
.on_mouse_down(
gpui::MouseButton::Left,
cx.listener(|this, ev: &MouseDownEvent, _| {
this._painting = true;
this.start = ev.position;
let path = vec![ev.position];
this.lines.push(path);
}),
)
.on_mouse_move(cx.listener(|this, ev: &gpui::MouseMoveEvent, cx| {
if !this._painting {
return;
}
let is_shifted = ev.modifiers.shift;
let mut pos = ev.position;
// When holding shift, draw a straight line
if is_shifted {
let dx = pos.x - this.start.x;
let dy = pos.y - this.start.y;
if dx.abs() > dy.abs() {
pos.y = this.start.y;
} else {
pos.x = this.start.x;
}
}
if let Some(path) = this.lines.last_mut() {
path.push(pos);
}
cx.notify();
}))
.on_mouse_up(
gpui::MouseButton::Left,
cx.listener(|this, _, _| {
this._painting = false;
}),
),
)
}
}
fn main() {
App::new().run(|cx: &mut AppContext| {
cx.open_window(
WindowOptions {
focus: true,
..Default::default()
},
|cx| cx.new_view(|_| PaintingViewer::new()),
)
.unwrap();
cx.activate(true);
});
}