Display squiggly underlines underneath text with diagnostics

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Antonio Scandurra 2022-01-26 15:52:21 +01:00
parent 52594fe250
commit b9b255652f
7 changed files with 119 additions and 72 deletions

View file

@ -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)

View file

@ -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<Color>,
pub underline: Option<Underline>,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct HighlightStyle {
pub color: Color,
pub font_properties: Properties,
pub underline: Option<Color>,
pub underline: Option<Underline>,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct Underline {
pub color: Color,
pub thickness: OrderedFloat<f32>,
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<Color>,
#[serde(default)]
thickness: Option<f32>,
#[serde(default)]
squiggly: bool,
},
}
impl TextStyle {
@ -89,7 +104,7 @@ impl TextStyle {
font_family_name: impl Into<Arc<str>>,
font_size: f32,
font_properties: Properties,
underline: Option<Color>,
underline: Option<Underline>,
color: Color,
font_cache: &FontCache,
) -> anyhow::Result<Self> {
@ -276,11 +291,23 @@ impl<'de> Deserialize<'de> for HighlightStyle {
}
}
fn underline_from_json(json: UnderlineStyleJson, text_color: Color) -> Option<Color> {
fn underline_from_json(json: UnderlineStyleJson, text_color: Color) -> Option<Underline> {
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,
}),
}
}

View file

@ -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,6 +334,13 @@ impl Renderer {
drawable_size,
command_encoder,
);
self.render_underlines(
layer.underlines(),
scale_factor,
offset,
drawable_size,
command_encoder,
);
self.render_sprites(
layer.glyphs(),
layer.icons(),
@ -349,13 +356,6 @@ impl Renderer {
drawable_size,
command_encoder,
);
self.render_underlines(
layer.underlines(),
scale_factor,
offset,
drawable_size,
command_encoder,
);
}
command_encoder.end_encoding();
@ -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;
}
}

View file

@ -118,4 +118,5 @@ typedef struct
vector_float2 size;
float thickness;
vector_uchar4 color;
uint8_t squiggly;
} GPUIUnderline;

View file

@ -311,6 +311,7 @@ struct UnderlineFragmentInput {
float2 size;
float thickness;
float4 color;
bool squiggly;
};
vertex UnderlineFragmentInput underline_vertex(
@ -331,16 +332,18 @@ vertex UnderlineFragmentInput underline_vertex(
underline.size,
underline.thickness,
coloru_to_colorf(underline.color),
underline.squiggly != 0,
};
}
fragment float4 underline_fragment(
UnderlineFragmentInput input [[stage_in]]
) {
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 * 0.75;
float amplitude = 0.3;
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);
@ -349,4 +352,7 @@ fragment float4 underline_fragment(
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;
}
}

View file

@ -25,7 +25,7 @@ struct StackingContext {
pub struct Layer {
clip_bounds: Option<RectF>,
quads: Vec<Quad>,
underlines: Vec<Quad>,
underlines: Vec<Underline>,
images: Vec<Image>,
shadows: Vec<Shadow>,
glyphs: Vec<Glyph>,
@ -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<D>(deserializer: D) -> Result<Self, D::Error>
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()
}

View file

@ -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<Color>,
pub underline: Option<Underline>,
}
impl TextLayoutCache {
@ -167,7 +167,7 @@ impl<'a> Hash for CacheKeyRef<'a> {
#[derive(Default, Debug)]
pub struct Line {
layout: Arc<LineLayout>,
style_runs: SmallVec<[(u32, Color, Option<Color>); 32]>,
style_runs: SmallVec<[(u32, Color, Option<Underline>); 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,
});
}
}