Display squiggly underlines underneath text with diagnostics
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
52594fe250
commit
b9b255652f
7 changed files with 119 additions and 72 deletions
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -118,4 +118,5 @@ typedef struct
|
|||
vector_float2 size;
|
||||
float thickness;
|
||||
vector_uchar4 color;
|
||||
uint8_t squiggly;
|
||||
} GPUIUnderline;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue