From b9b255652f383eefefd83135269cc4c4cce63960 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 26 Jan 2022 15:52:21 +0100 Subject: [PATCH] Display squiggly underlines underneath text with diagnostics Co-Authored-By: Nathan Sobo --- crates/editor/src/element.rs | 14 +++--- crates/gpui/src/fonts.rs | 41 ++++++++++++++--- crates/gpui/src/platform/mac/renderer.rs | 43 +++++++++--------- .../gpui/src/platform/mac/shaders/shaders.h | 1 + .../src/platform/mac/shaders/shaders.metal | 30 ++++++++----- crates/gpui/src/scene.rs | 17 +++++-- crates/gpui/src/text_layout.rs | 45 ++++++++++--------- 7 files changed, 119 insertions(+), 72 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index ffbefbf035dd47f24baca28d6bb82b53f25b79c6..db67903e570c9f7ff2ff0fd4dba35610b3c617ec 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -8,7 +8,7 @@ use collections::{BTreeMap, HashMap}; use gpui::{ color::Color, elements::layout_highlighted_chunks, - fonts::HighlightStyle, + fonts::{HighlightStyle, Underline}, geometry::{ rect::RectF, vector::{vec2f, Vector2F}, @@ -540,12 +540,12 @@ impl EditorElement { .chunks(rows.clone(), Some(&style.syntax)) .map(|chunk| { let highlight = if let Some(severity) = chunk.diagnostic { - let underline = Some( - super::diagnostic_style(severity, true, style) - .message - .text - .color, - ); + let diagnostic_style = super::diagnostic_style(severity, true, style); + let underline = Some(Underline { + color: diagnostic_style.message.text.color, + thickness: 1.0.into(), + squiggly: true, + }); if let Some(mut highlight) = chunk.highlight_style { highlight.underline = underline; Some(highlight) diff --git a/crates/gpui/src/fonts.rs b/crates/gpui/src/fonts.rs index 61082f6aaed686e631a679c815c7c9515df2f6b9..2768b9f986cf1f7b11802972445630aa0b63e5e4 100644 --- a/crates/gpui/src/fonts.rs +++ b/crates/gpui/src/fonts.rs @@ -10,6 +10,7 @@ pub use font_kit::{ metrics::Metrics, properties::{Properties, Stretch, Style, Weight}, }; +use ordered_float::OrderedFloat; use serde::{de, Deserialize}; use serde_json::Value; use std::{cell::RefCell, sync::Arc}; @@ -27,14 +28,21 @@ pub struct TextStyle { pub font_id: FontId, pub font_size: f32, pub font_properties: Properties, - pub underline: Option, + pub underline: Option, } #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] pub struct HighlightStyle { pub color: Color, pub font_properties: Properties, - pub underline: Option, + pub underline: Option, +} + +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub struct Underline { + pub color: Color, + pub thickness: OrderedFloat, + pub squiggly: bool, } #[allow(non_camel_case_types)] @@ -81,7 +89,14 @@ struct HighlightStyleJson { #[serde(untagged)] enum UnderlineStyleJson { Underlined(bool), - UnderlinedWithColor(Color), + UnderlinedWithProperties { + #[serde(default)] + color: Option, + #[serde(default)] + thickness: Option, + #[serde(default)] + squiggly: bool, + }, } impl TextStyle { @@ -89,7 +104,7 @@ impl TextStyle { font_family_name: impl Into>, font_size: f32, font_properties: Properties, - underline: Option, + underline: Option, color: Color, font_cache: &FontCache, ) -> anyhow::Result { @@ -276,11 +291,23 @@ impl<'de> Deserialize<'de> for HighlightStyle { } } -fn underline_from_json(json: UnderlineStyleJson, text_color: Color) -> Option { +fn underline_from_json(json: UnderlineStyleJson, text_color: Color) -> Option { match json { UnderlineStyleJson::Underlined(false) => None, - UnderlineStyleJson::Underlined(true) => Some(text_color), - UnderlineStyleJson::UnderlinedWithColor(color) => Some(color), + UnderlineStyleJson::Underlined(true) => Some(Underline { + color: text_color, + thickness: 1.0.into(), + squiggly: false, + }), + UnderlineStyleJson::UnderlinedWithProperties { + color, + thickness, + squiggly, + } => Some(Underline { + color: color.unwrap_or(text_color), + thickness: thickness.unwrap_or(1.).into(), + squiggly, + }), } } diff --git a/crates/gpui/src/platform/mac/renderer.rs b/crates/gpui/src/platform/mac/renderer.rs index b5c563c9e47225c62c456455d76876a96d96c60c..07d425af3ebd4235a178ca479a21052d0d71b043 100644 --- a/crates/gpui/src/platform/mac/renderer.rs +++ b/crates/gpui/src/platform/mac/renderer.rs @@ -6,7 +6,7 @@ use crate::{ vector::{vec2f, vec2i, Vector2F}, }, platform, - scene::{Glyph, Icon, Image, Layer, Quad, Scene, Shadow}, + scene::{Glyph, Icon, Image, Layer, Quad, Scene, Shadow, Underline}, }; use cocoa::foundation::NSUInteger; use metal::{MTLPixelFormat, MTLResourceOptions, NSRange}; @@ -334,23 +334,23 @@ impl Renderer { drawable_size, command_encoder, ); - self.render_sprites( - layer.glyphs(), - layer.icons(), + self.render_underlines( + layer.underlines(), scale_factor, offset, drawable_size, command_encoder, ); - self.render_images( - layer.images(), + self.render_sprites( + layer.glyphs(), + layer.icons(), scale_factor, offset, drawable_size, command_encoder, ); - self.render_underlines( - layer.underlines(), + self.render_images( + layer.images(), scale_factor, offset, drawable_size, @@ -834,7 +834,7 @@ impl Renderer { fn render_underlines( &mut self, - underlines: &[Quad], + underlines: &[Underline], scale_factor: f32, offset: &mut usize, drawable_size: Vector2F, @@ -874,19 +874,22 @@ impl Renderer { (self.instances.contents() as *mut u8).offset(*offset as isize) as *mut shaders::GPUIUnderline }; - for (ix, quad) in underlines.iter().enumerate() { - let bounds = quad.bounds * scale_factor; - let shader_quad = shaders::GPUIUnderline { - origin: bounds.origin().round().to_float2(), - size: bounds.size().round().to_float2(), - thickness: 1. * scale_factor, - color: quad - .background - .unwrap_or(Color::transparent_black()) - .to_uchar4(), + for (ix, underline) in underlines.iter().enumerate() { + let origin = underline.origin * scale_factor; + let mut height = underline.thickness; + if underline.squiggly { + height *= 3.; + } + let size = vec2f(underline.width, height) * scale_factor; + let shader_underline = shaders::GPUIUnderline { + origin: origin.round().to_float2(), + size: size.round().to_float2(), + thickness: underline.thickness * scale_factor, + color: underline.color.to_uchar4(), + squiggly: underline.squiggly as u8, }; unsafe { - *(buffer_contents.offset(ix as isize)) = shader_quad; + *(buffer_contents.offset(ix as isize)) = shader_underline; } } diff --git a/crates/gpui/src/platform/mac/shaders/shaders.h b/crates/gpui/src/platform/mac/shaders/shaders.h index 2e6133fdd51ced78560e168bd33a6590102b59a3..3f5096f37c867cfa2d52f8a8c79cda439418b785 100644 --- a/crates/gpui/src/platform/mac/shaders/shaders.h +++ b/crates/gpui/src/platform/mac/shaders/shaders.h @@ -118,4 +118,5 @@ typedef struct vector_float2 size; float thickness; vector_uchar4 color; + uint8_t squiggly; } GPUIUnderline; diff --git a/crates/gpui/src/platform/mac/shaders/shaders.metal b/crates/gpui/src/platform/mac/shaders/shaders.metal index 413440a24e1c6e611cf5f351e9a6f83b620007e0..b484739edbd921a8d4d9c1466ce6d6af973d8f03 100644 --- a/crates/gpui/src/platform/mac/shaders/shaders.metal +++ b/crates/gpui/src/platform/mac/shaders/shaders.metal @@ -311,6 +311,7 @@ struct UnderlineFragmentInput { float2 size; float thickness; float4 color; + bool squiggly; }; vertex UnderlineFragmentInput underline_vertex( @@ -331,22 +332,27 @@ vertex UnderlineFragmentInput underline_vertex( underline.size, underline.thickness, coloru_to_colorf(underline.color), + underline.squiggly != 0, }; } fragment float4 underline_fragment( UnderlineFragmentInput input [[stage_in]] ) { - float half_thickness = input.thickness * 0.5; - float2 st = ((input.position.xy - input.origin) / input.size.y) - float2(0., 0.5); - float frequency = M_PI_F * 0.75; - float amplitude = 0.3; - float sine = sin(st.x * frequency) * amplitude; - float dSine = cos(st.x * frequency) * amplitude * frequency; - float distance = (st.y - sine) / sqrt(1. + dSine * dSine); - float distance_in_pixels = distance * input.size.y; - float distance_from_top_border = distance_in_pixels - half_thickness; - float distance_from_bottom_border = distance_in_pixels + half_thickness; - float alpha = saturate(0.5 - max(-distance_from_bottom_border, distance_from_top_border)); - return input.color * float4(1., 1., 1., alpha); + if (input.squiggly) { + float half_thickness = input.thickness * 0.5; + float2 st = ((input.position.xy - input.origin) / input.size.y) - float2(0., 0.5); + float frequency = (M_PI_F * (3. * input.thickness)) / 8.; + float amplitude = 1. / (2. * input.thickness); + float sine = sin(st.x * frequency) * amplitude; + float dSine = cos(st.x * frequency) * amplitude * frequency; + float distance = (st.y - sine) / sqrt(1. + dSine * dSine); + float distance_in_pixels = distance * input.size.y; + float distance_from_top_border = distance_in_pixels - half_thickness; + float distance_from_bottom_border = distance_in_pixels + half_thickness; + float alpha = saturate(0.5 - max(-distance_from_bottom_border, distance_from_top_border)); + return input.color * float4(1., 1., 1., alpha); + } else { + return input.color; + } } diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index b833ffe627d07be3ef255eef9072f4a704d56825..a5b2f6c8b81aed44801a24eec5fc06aa82f49bef 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -25,7 +25,7 @@ struct StackingContext { pub struct Layer { clip_bounds: Option, quads: Vec, - underlines: Vec, + underlines: Vec, images: Vec, shadows: Vec, glyphs: Vec, @@ -76,6 +76,15 @@ pub struct Border { pub left: bool, } +#[derive(Clone, Copy, Default, Debug)] +pub struct Underline { + pub origin: Vector2F, + pub width: f32, + pub thickness: f32, + pub color: Color, + pub squiggly: bool, +} + impl<'de> Deserialize<'de> for Border { fn deserialize(deserializer: D) -> Result where @@ -183,7 +192,7 @@ impl Scene { self.active_layer().push_image(image) } - pub fn push_underline(&mut self, underline: Quad) { + pub fn push_underline(&mut self, underline: Underline) { self.active_layer().push_underline(underline) } @@ -277,11 +286,11 @@ impl Layer { self.quads.as_slice() } - fn push_underline(&mut self, underline: Quad) { + fn push_underline(&mut self, underline: Underline) { self.underlines.push(underline); } - pub fn underlines(&self) -> &[Quad] { + pub fn underlines(&self) -> &[Underline] { self.underlines.as_slice() } diff --git a/crates/gpui/src/text_layout.rs b/crates/gpui/src/text_layout.rs index f2fca6d0a6d58f135dfa41c9fc3ea914199963bd..30a0e99502a76a772a9ce7c63de4990fc248edd2 100644 --- a/crates/gpui/src/text_layout.rs +++ b/crates/gpui/src/text_layout.rs @@ -1,6 +1,6 @@ use crate::{ color::Color, - fonts::{FontId, GlyphId}, + fonts::{FontId, GlyphId, Underline}, geometry::{ rect::RectF, vector::{vec2f, Vector2F}, @@ -28,7 +28,7 @@ pub struct TextLayoutCache { pub struct RunStyle { pub color: Color, pub font_id: FontId, - pub underline: Option, + pub underline: Option, } impl TextLayoutCache { @@ -167,7 +167,7 @@ impl<'a> Hash for CacheKeyRef<'a> { #[derive(Default, Debug)] pub struct Line { layout: Arc, - style_runs: SmallVec<[(u32, Color, Option); 32]>, + style_runs: SmallVec<[(u32, Color, Option); 32]>, } #[derive(Default, Debug)] @@ -265,14 +265,14 @@ impl Line { let mut finished_underline = None; if glyph.index >= run_end { - if let Some((run_len, run_color, run_underline_color)) = style_runs.next() { - if let Some((_, underline_color)) = underline { - if *run_underline_color != Some(underline_color) { + if let Some((run_len, run_color, run_underline)) = style_runs.next() { + if let Some((_, underline_style)) = underline { + if *run_underline != Some(underline_style) { finished_underline = underline.take(); } } - if let Some(run_underline_color) = run_underline_color { - underline.get_or_insert((glyph_origin, *run_underline_color)); + if let Some(run_underline) = run_underline { + underline.get_or_insert((glyph_origin, *run_underline)); } run_end += *run_len as usize; @@ -288,12 +288,13 @@ impl Line { continue; } - if let Some((underline_origin, underline_color)) = finished_underline { - cx.scene.push_underline(scene::Quad { - bounds: RectF::from_points(underline_origin, glyph_origin + vec2f(0., 3.)), - background: Some(underline_color), - border: Default::default(), - corner_radius: 0., + if let Some((underline_origin, underline_style)) = finished_underline { + cx.scene.push_underline(scene::Underline { + origin: underline_origin, + width: glyph_origin.x() - underline_origin.x(), + thickness: underline_style.thickness.into(), + color: underline_style.color, + squiggly: underline_style.squiggly, }); } @@ -307,14 +308,14 @@ impl Line { } } - if let Some((underline_start, underline_color)) = underline.take() { - let line_end = origin + baseline_offset + vec2f(self.layout.width, 0.); - - cx.scene.push_underline(scene::Quad { - bounds: RectF::from_points(underline_start, line_end + vec2f(0., 3.)), - background: Some(underline_color), - border: Default::default(), - corner_radius: 0., + if let Some((underline_start, underline_style)) = underline.take() { + let line_end_x = origin.x() + self.layout.width; + cx.scene.push_underline(scene::Underline { + origin: underline_start, + width: line_end_x - underline_start.x(), + color: underline_style.color, + thickness: underline_style.thickness.into(), + squiggly: underline_style.squiggly, }); } }