diff --git a/crates/gpui/examples/painting.rs b/crates/gpui/examples/painting.rs index 6e5fe25dfd2b45..c91d7ca02704e0 100644 --- a/crates/gpui/examples/painting.rs +++ b/crates/gpui/examples/painting.rs @@ -1,9 +1,9 @@ use gpui::{ - canvas, div, point, prelude::*, px, size, App, AppContext, Bounds, MouseDownEvent, Path, + canvas, div, point, prelude::*, px, size, App, AppContext, Bounds, Hsla, MouseDownEvent, Path, Pixels, Point, Render, ViewContext, WindowOptions, }; struct PaintingViewer { - default_lines: Vec>, + default_lines: Vec<(Path, Hsla)>, lines: Vec>>, start: Point, _painting: bool, @@ -14,33 +14,48 @@ impl PaintingViewer { 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.))); + let mut path = Path::new(point(px(20.), px(100.))); + path.line_to(point(px(50.), px(160.))); + path.line_to(point(px(60.), px(100.))); + path.line_to(point(px(80.), px(140.))); // 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); + path.move_to(point(px(81.), px(141.))); + path.line_to(point(px(60.), px(101.))); + path.line_to(point(px(50.), px(161.))); + path.line_to(point(px(20.), px(101.))); + lines.push((path, gpui::black())); + + // draw a triangle + let mut path = Path::new(point(px(25.), px(0.))); + path.line_to(point(px(50.), px(50.))); + path.line_to(point(px(0.), px(50.))); + path.translate(point(px(100.), px(100.))); + lines.push((path, gpui::red())); // 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); + let mut path = Path::new(point(px(-50.), px(50.))); + path.line_to(point(px(0.), px(-25.))); + path.line_to(point(px(0.), px(0.))); + path.move_to(point(px(0.), px(0.))); + path.line_to(point(px(50.), px(-50.))); + path.line_to(point(px(0.), px(25.))); + path.line_to(point(px(0.), px(5.))); + path.translate(point(px(220.), px(150.))); + lines.push((path, gpui::blue())); // 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 mut path = Path::new(point(px(76.8), px(116.864))); + path.line_to(point(px(31.6608), px(142.1312))); + path.line_to(point(px(41.7408), px(91.392))); + path.line_to(point(px(3.7568), px(56.2688))); + path.line_to(point(px(55.1296), px(50.176))); + path.line_to(point(px(76.8), px(3.2))); + path.line_to(point(px(98.4704), px(50.176))); + path.line_to(point(px(149.8432), px(56.2688))); + path.line_to(point(px(111.8592), px(91.392))); + path.line_to(point(px(121.9392), px(142.1312))); + path.translate(point(px(270.), px(80.))); + lines.push((path, gpui::yellow())); let square_bounds = Bounds { origin: point(px(450.), px(100.)), @@ -59,8 +74,7 @@ impl PaintingViewer { square_bounds.lower_right(), square_bounds.upper_right() + point(px(0.0), vertical_offset), ); - path.line_to(square_bounds.lower_left()); - lines.push(path); + lines.push((path, gpui::green())); Self { default_lines: lines.clone(), @@ -115,9 +129,9 @@ impl Render for PaintingViewer { canvas( move |_, _| {}, move |_, _, cx| { - const STROKE_WIDTH: Pixels = px(2.0); - for path in default_lines { - cx.paint_path(path, gpui::black()); + const STROKE_WIDTH: Pixels = px(1.0); + for (path, color) in default_lines { + cx.paint_path(path, color); } for points in lines { let mut path = Path::new(points[0]); @@ -127,11 +141,12 @@ impl Render for PaintingViewer { 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)); + let dx = p.x - last.x; + let dy = p.y - last.y; + let distance = (dx * dx + dy * dy).0.sqrt(); + let offset_x = (STROKE_WIDTH * dy / distance).clamp(px(0.0), STROKE_WIDTH); + let offset_y = (STROKE_WIDTH * dx / distance).clamp(px(0.0), STROKE_WIDTH); + path.line_to(point(p.x + offset_x, p.y - offset_y)); last = p; } diff --git a/crates/gpui/src/geometry.rs b/crates/gpui/src/geometry.rs index 9e0b9b90140391..2972fff4176d10 100644 --- a/crates/gpui/src/geometry.rs +++ b/crates/gpui/src/geometry.rs @@ -2456,6 +2456,7 @@ impl From for Pixels { Copy, Default, Div, + Mul, Eq, Hash, Ord, diff --git a/crates/gpui/src/platform/blade/blade_renderer.rs b/crates/gpui/src/platform/blade/blade_renderer.rs index 5c37caf2cbab02..576e975882a318 100644 --- a/crates/gpui/src/platform/blade/blade_renderer.rs +++ b/crates/gpui/src/platform/blade/blade_renderer.rs @@ -5,7 +5,7 @@ use super::{BladeAtlas, PATH_TEXTURE_FORMAT}; use crate::{ AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels, GPUSpecs, Hsla, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad, - ScaledPixels, Scene, Shadow, Size, Underline, + ScaledPixels, Scene, Shadow, Size, Underline, PATH_SUBPIXEL_VARIANTS, }; use bytemuck::{Pod, Zeroable}; use collections::HashMap; @@ -622,12 +622,19 @@ impl BladeRenderer { for path in paths { let tile = &self.path_tiles[&path.id]; let tex_info = self.atlas.get_texture_info(tile.texture_id); - let origin = path.bounds.intersect(&path.content_mask.bounds).origin; + let origin = path + .bounds + .intersect(&path.content_mask.bounds) + .origin + .map(|p| (p / PATH_SUBPIXEL_VARIANTS).floor()); + let size = tile + .bounds + .size + .map(|s| s / PATH_SUBPIXEL_VARIANTS as i32) + .map(Into::into); + let sprites = [PathSprite { - bounds: Bounds { - origin: origin.map(|p| p.floor()), - size: tile.bounds.size.map(Into::into), - }, + bounds: Bounds { origin, size }, color: path.color, tile: (*tile).clone(), }]; diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index f42a2e2df7b943..1186af00160d86 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -3,6 +3,7 @@ use crate::{ point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels, Hsla, MonochromeSprite, PaintSurface, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline, + PATH_SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Result}; use block::ConcreteBlock; @@ -733,12 +734,18 @@ impl MetalRenderer { if let Some((path, tile)) = paths_and_tiles.peek() { if prev_texture_id.map_or(true, |texture_id| texture_id == tile.texture_id) { prev_texture_id = Some(tile.texture_id); - let origin = path.bounds.intersect(&path.content_mask.bounds).origin; + let origin = path + .bounds + .intersect(&path.content_mask.bounds) + .origin + .map(|p| (p / PATH_SUBPIXEL_VARIANTS).floor()); + let size = tile + .bounds + .size + .map(|s| s / PATH_SUBPIXEL_VARIANTS as i32) + .map(Into::into); sprites.push(PathSprite { - bounds: Bounds { - origin: origin.map(|p| p.floor()), - size: tile.bounds.size.map(Into::into), - }, + bounds: Bounds { origin, size }, color: path.color, tile: (*tile).clone(), }); diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 9787ec5d87f137..826e7c5c83ca0b 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -7,6 +7,9 @@ use crate::{ }; use std::{fmt::Debug, iter::Peekable, ops::Range, slice}; +/// Subpixel variants for antialiasing for Path render. +pub(crate) const PATH_SUBPIXEL_VARIANTS: f32 = 2.0; + #[allow(non_camel_case_types, unused)] pub(crate) type PathVertex_ScaledPixels = PathVertex; @@ -785,13 +788,20 @@ impl Path { .iter() .map(|vertex| vertex.scale(factor)) .collect(), - start: self.start.map(|start| start.scale(factor)), + start: self.start.scale(factor), current: self.current.scale(factor), contour_count: self.contour_count, color: self.color, } } + /// Move the current point of the path to the given point. + pub fn move_to(&mut self, to: Point) { + self.start = to; + self.current = to; + self.contour_count = 0; + } + /// Draw a straight line from the current point to the given point. pub fn line_to(&mut self, to: Point) { self.contour_count += 1; @@ -821,6 +831,17 @@ impl Path { self.current = to; } + /// Translate path by the given offset. + pub fn translate(&mut self, offset: Point) { + self.start = self.start + offset; + self.current = self.current + offset; + self.bounds.origin = self.bounds.origin + offset; + self.content_mask.bounds.origin = self.content_mask.bounds.origin + offset; + for vertex in &mut self.vertices { + vertex.xy_position = vertex.xy_position + offset; + } + } + fn push_triangle( &mut self, xy: (Point, Point, Point), diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index e4fa74f981ed5f..a2866ed48157a9 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -14,7 +14,7 @@ use crate::{ TaffyLayoutEngine, Task, TextStyle, TextStyleRefinement, TransformationMatrix, Underline, UnderlineStyle, View, VisualContext, WeakView, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations, WindowOptions, WindowParams, WindowTextSystem, - SUBPIXEL_VARIANTS, + PATH_SUBPIXEL_VARIANTS, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Context as _, Result}; use collections::{FxHashMap, FxHashSet}; @@ -2330,7 +2330,7 @@ impl<'a> WindowContext<'a> { self.window .next_frame .scene - .insert_primitive(path.scale(scale_factor)); + .insert_primitive(path.scale(scale_factor * PATH_SUBPIXEL_VARIANTS)); } /// Paint an underline into the scene for the next frame at the current z-index.