diff --git a/Cargo.lock b/Cargo.lock index 527190bacae6f..015919a46c031 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,22 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ab_glyph" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" + [[package]] name = "activity_indicator" version = "0.1.0" @@ -3027,7 +3043,7 @@ dependencies = [ "self_cell", "swash", "sys-locale", - "ttf-parser", + "ttf-parser 0.21.1", "unicode-bidi", "unicode-linebreak", "unicode-script", @@ -3698,6 +3714,15 @@ dependencies = [ "signature 1.6.4", ] +[[package]] +name = "ecolor" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775cfde491852059e386c4e1deb4aef381c617dc364184c6f6afee99b87c402b" +dependencies = [ + "emath", +] + [[package]] name = "editor" version = "0.1.0" @@ -3802,6 +3827,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "emath" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1fe0049ce51d0fb414d029e668dd72eb30bc2b739bf34296ed97bd33df544f3" + [[package]] name = "embed-resource" version = "2.5.1" @@ -3924,6 +3955,27 @@ dependencies = [ "serde", ] +[[package]] +name = "epaint" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a32af8da821bd4f43f2c137e295459ee2e1661d87ca8779dfa0eaf45d870e20f" +dependencies = [ + "ab_glyph", + "ahash 0.8.11", + "ecolor", + "emath", + "epaint_default_fonts", + "nohash-hasher", + "parking_lot", +] + +[[package]] +name = "epaint_default_fonts" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "483440db0b7993cf77a20314f08311dbe95675092405518c0677aa08c151a3ea" + [[package]] name = "equivalent" version = "1.0.1" @@ -4505,7 +4557,7 @@ dependencies = [ "memmap2", "slotmap", "tinyvec", - "ttf-parser", + "ttf-parser 0.21.1", ] [[package]] @@ -5107,6 +5159,7 @@ dependencies = [ "derive_more", "embed-resource", "env_logger 0.11.5", + "epaint", "etagere", "filedescriptor", "flume", @@ -7462,6 +7515,12 @@ dependencies = [ "windows 0.58.0", ] +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nom" version = "7.1.3" @@ -8061,6 +8120,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "owned_ttf_parser" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" +dependencies = [ + "ttf-parser 0.25.0", +] + [[package]] name = "p256" version = "0.11.1" @@ -10469,7 +10537,7 @@ dependencies = [ "bytemuck", "libm", "smallvec", - "ttf-parser", + "ttf-parser 0.21.1", "unicode-bidi-mirroring", "unicode-ccc", "unicode-properties", @@ -13199,6 +13267,12 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" +[[package]] +name = "ttf-parser" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5902c5d130972a0000f60860bfbf46f7ca3db5391eddfedd1b8728bd9dc96c0e" + [[package]] name = "tungstenite" version = "0.19.0" diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 347e5502ca39d..75c41df8655c3 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -74,6 +74,7 @@ bytemuck = { version = "1", optional = true } collections.workspace = true ctor.workspace = true derive_more.workspace = true +epaint = "0.29.1" etagere = "0.2" futures.workspace = true gpui_macros.workspace = true @@ -91,7 +92,6 @@ rand = { optional = true, workspace = true } raw-window-handle = "0.6" refineable.workspace = true resvg = { version = "0.44.0", default-features = false } -usvg = { version = "0.44.0", default-features = false } schemars.workspace = true seahash = "4.1" semantic_version.workspace = true @@ -105,6 +105,7 @@ strum.workspace = true sum_tree.workspace = true taffy = "0.4.3" thiserror.workspace = true +usvg = { version = "0.44.0", default-features = false } util.workspace = true uuid.workspace = true waker-fn = "1.2.0" diff --git a/crates/gpui/examples/painting.rs b/crates/gpui/examples/painting.rs index 6e5fe25dfd2b4..6a80f761a0fd6 100644 --- a/crates/gpui/examples/painting.rs +++ b/crates/gpui/examples/painting.rs @@ -1,69 +1,83 @@ +use epaint::{pos2, Shape}; use gpui::{ canvas, div, point, prelude::*, px, size, App, AppContext, Bounds, MouseDownEvent, Path, - Pixels, Point, Render, ViewContext, WindowOptions, + PathVertex, Pixels, Point, Render, ViewContext, WindowContext, WindowOptions, }; struct PaintingViewer { - default_lines: Vec>, + default_shapes: Vec, lines: Vec>>, start: Point, _painting: bool, } +fn tessellate( + shape: epaint::Shape, + cx: &WindowContext, +) -> (Vec>, Bounds) { + let options = epaint::TessellationOptions::default(); + let mut mesh = epaint::Mesh::default(); + let rect = shape.visual_bounding_rect(); + + epaint::tessellator::Tessellator::new(cx.scale_factor(), options, [0, 0], vec![]) + .tessellate_shape(shape, &mut mesh); + + let bounds = Bounds { + origin: point(px(rect.min.x), px(rect.min.y)), + size: size(px(rect.width()), px(rect.height())), + }; + + let mut vertices = vec![]; + for vertex in mesh.vertices { + vertices.push(PathVertex::new( + point(px(vertex.pos.x), px(vertex.pos.y)), + point(vertex.uv.x, 1.0 - vertex.uv.y), + )); + } + + (vertices, bounds) +} + impl PaintingViewer { - fn new() -> Self { - let mut lines = vec![]; + fn new(_cx: &mut ViewContext) -> Self { + let mut shapes = vec![]; + let stroke = epaint::Stroke::new(1., epaint::Color32::BLACK); // 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.lower_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.upper_right() + point(-horizontal_offset, vertical_offset)); - path.curve_to( - square_bounds.lower_right(), - square_bounds.upper_right() + point(px(0.0), vertical_offset), - ); - path.line_to(square_bounds.lower_left()); - lines.push(path); + let shape = Shape::line(vec![pos2(50., 140.), pos2(100., 220.)], stroke); + shapes.push(shape); + + // draw a circle + let shape = Shape::circle_filled(pos2(300., 200.), 100., epaint::Color32::BLACK); + shapes.push(shape); + + // // draw a lightening bolt ⚡ + // let shape = Shape::closed_line( + // vec![pos2(520., 230.), pos2(620., 100.), pos2(700., 230.)], + // stroke, + // ); + // shapes.push(shape); + + // // draw a ⭐ + // let shape = Shape::closed_line( + // vec![ + // pos2(350., 100.), + // pos2(370., 160.), + // pos2(430., 160.), + // pos2(380., 200.), + // pos2(400., 260.), + // pos2(350., 220.), + // pos2(300., 260.), + // pos2(320., 200.), + // pos2(270., 160.), + // pos2(330., 160.), + // pos2(350., 100.), + // ], + // stroke, + // ); + // shapes.push(shape); Self { - default_lines: lines.clone(), + default_shapes: shapes, lines: vec![], start: point(px(0.), px(0.)), _painting: false, @@ -77,7 +91,7 @@ impl PaintingViewer { } impl Render for PaintingViewer { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let default_lines = self.default_lines.clone(); + let shapes = self.default_shapes.clone(); let lines = self.lines.clone(); div() .font_family(".SystemUIFont") @@ -115,27 +129,25 @@ impl Render for PaintingViewer { canvas( move |_, _| {}, move |_, _, cx| { - const STROKE_WIDTH: Pixels = px(2.0); - for path in default_lines { + let stroke = epaint::Stroke::new(1., epaint::Color32::BLACK); + for shape in shapes { + let mut path = Path::new(point(px(0.), px(0.))); + let (vertices, bounds) = tessellate(shape, cx); + path.set_vertices(vertices, bounds); 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; + for points in lines { + let mut path = Path::new(point(px(0.), px(0.))); + let shape = epaint::Shape::line( + points.iter().map(|p| pos2(p.x.0, p.y.0)).collect(), + stroke, + ); + let (vertices, bounds) = tessellate(shape, cx); + if vertices.len() > 0 { + path.set_vertices(vertices, bounds); + cx.paint_path(path, gpui::black()); } - - cx.paint_path(path, gpui::black()); } }, ) @@ -144,10 +156,10 @@ impl Render for PaintingViewer { .on_mouse_down( gpui::MouseButton::Left, cx.listener(|this, ev: &MouseDownEvent, _| { - this._painting = true; this.start = ev.position; - let path = vec![ev.position]; + let path = vec![]; this.lines.push(path); + this._painting = true; }), ) .on_mouse_move(cx.listener(|this, ev: &gpui::MouseMoveEvent, cx| { @@ -169,6 +181,12 @@ impl Render for PaintingViewer { } if let Some(path) = this.lines.last_mut() { + if let Some(last_pos) = path.last() { + if pos == *last_pos { + return + } + } + path.push(pos); } @@ -191,7 +209,7 @@ fn main() { focus: true, ..Default::default() }, - |cx| cx.new_view(|_| PaintingViewer::new()), + |cx| cx.new_view(PaintingViewer::new), ) .unwrap(); cx.activate(true); diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 9787ec5d87f13..4bbe45c59f131 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -857,6 +857,12 @@ impl Path { content_mask: Default::default(), }); } + + /// Force to set the vertices and bounds of the path. + pub fn set_vertices(&mut self, vertices: Vec>, bounds: Bounds) { + self.bounds = bounds; + self.vertices = vertices; + } } impl Eq for Path {} @@ -885,15 +891,26 @@ impl From> for Primitive { } } +/// A vertex in a path. #[derive(Clone, Debug)] #[repr(C)] -pub(crate) struct PathVertex { +pub struct PathVertex { pub(crate) xy_position: Point

, pub(crate) st_position: Point, pub(crate) content_mask: ContentMask

, } impl PathVertex { + /// Create a new vertex with the given position. + pub fn new(xy_position: Point, st_position: Point) -> Self { + Self { + xy_position, + st_position, + content_mask: Default::default(), + } + } + + /// Scale this vertex by the given factor. pub fn scale(&self, factor: f32) -> PathVertex { PathVertex { xy_position: self.xy_position.scale(factor),