mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-11 13:51:01 -05:00
Add support for dashed and dotted underlines
This finishes implementation of underline styles provided by `CSI 4 : [1-5] m` escape sequence.
This commit is contained in:
parent
774eb03f4f
commit
ed5dbc1118
6 changed files with 189 additions and 84 deletions
|
@ -15,7 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
- Option `font.builtin_box_drawing` to disable the built-in font for drawing box characters
|
||||
- Track and report surface damage information to Wayland compositors
|
||||
- Escape sequence for undercurl (`CSI 4 : 3 m`)
|
||||
- Escape sequence for undercurl, dotted and dashed underlines (`CSI 4 : [3-5] m`)
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
@ -9,46 +9,121 @@ flat in vec4 color;
|
|||
|
||||
out vec4 FragColor;
|
||||
|
||||
uniform int isUndercurl;
|
||||
uniform int rectKind;
|
||||
|
||||
uniform float cellWidth;
|
||||
uniform float cellHeight;
|
||||
uniform float paddingY;
|
||||
uniform float paddingX;
|
||||
|
||||
uniform float undercurlThickness;
|
||||
uniform float underlinePosition;
|
||||
uniform float underlineThickness;
|
||||
|
||||
uniform float undercurlPosition;
|
||||
|
||||
#define UNDERCURL 1
|
||||
#define DOTTED 2
|
||||
#define DASHED 3
|
||||
|
||||
#define PI 3.1415926538
|
||||
|
||||
void main()
|
||||
{
|
||||
if (isUndercurl == 0) {
|
||||
FragColor = color;
|
||||
return;
|
||||
}
|
||||
vec4 draw_undercurl(int x, int y) {
|
||||
// We use `undercurlPosition` as an amplitude, since it's half of the descent
|
||||
// value.
|
||||
float undercurl =
|
||||
-1. * undercurlPosition / 2. * cos(float(x) * 2 * PI / cellWidth) +
|
||||
cellHeight - undercurlPosition;
|
||||
|
||||
int x = int(gl_FragCoord.x - paddingX) % int(cellWidth);
|
||||
int y = int(gl_FragCoord.y - paddingY) % int(cellHeight);
|
||||
float undercurlTop = undercurl + max((underlineThickness - 1), 0);
|
||||
float undercurlBottom = undercurl - max((underlineThickness - 1), 0);
|
||||
|
||||
// We use `undercurlPosition` as amplitude, since it's half of the descent
|
||||
// value.
|
||||
float undercurl = -1. * undercurlPosition / 2.
|
||||
* cos(float(x) * 2 * PI / float(cellWidth))
|
||||
+ cellHeight - undercurlPosition;
|
||||
// Compute resulted alpha based on distance from `gl_FragCoord.y` to the
|
||||
// cosine curve.
|
||||
float alpha = 1.;
|
||||
if (y > undercurlTop || y < undercurlBottom) {
|
||||
alpha = 1. - min(abs(undercurlTop - y), abs(undercurlBottom - y));
|
||||
}
|
||||
|
||||
// We subtract one, since curve is already 1px thick.
|
||||
float undercurl_top = undercurl + max((undercurlThickness - 1), 0);
|
||||
float undercurl_bottom = undercurl - max((undercurlThickness - 1), 0);
|
||||
|
||||
|
||||
// Compute resulted alpha based on distance from `gl_FragCoord.y` to the
|
||||
// cosine curve.
|
||||
float alpha = 1.;
|
||||
if (y > undercurl_top || y < undercurl_bottom) {
|
||||
alpha = 1. - min(abs(undercurl_top - y), abs(undercurl_bottom - y));
|
||||
}
|
||||
|
||||
// The result is an alpha mask on a rect, which leaves only curve opaque.
|
||||
FragColor = vec4(color.xyz, alpha);
|
||||
// The result is an alpha mask on a rect, which leaves only curve opaque.
|
||||
return vec4(color.rgb, alpha);
|
||||
}
|
||||
|
||||
// When the dot size increases we can use AA to make spacing look even and the
|
||||
// dots rounded.
|
||||
vec4 draw_dotted_aliased(float x, float y) {
|
||||
int dotNumber = int(x / underlineThickness);
|
||||
|
||||
float radius = underlineThickness / 2.;
|
||||
float centerY = cellHeight - underlinePosition;
|
||||
|
||||
float leftCenter = (dotNumber - dotNumber % 2) * underlineThickness + radius;
|
||||
float rightCenter = leftCenter + 2 * underlineThickness;
|
||||
|
||||
float distanceLeft = sqrt(pow(x - leftCenter, 2) + pow(y - centerY, 2));
|
||||
float distanceRight = sqrt(pow(x - rightCenter, 2) + pow(y - centerY, 2));
|
||||
|
||||
float alpha = max(1 - (min(distanceLeft, distanceRight) - radius), 0);
|
||||
return vec4(color.rgb, alpha);
|
||||
}
|
||||
|
||||
/// Draw dotted line when dot is just a single pixel.
|
||||
vec4 draw_dotted(int x, int y) {
|
||||
int cellEven = 0;
|
||||
|
||||
// Since the size of the dot and its gap combined is 2px we should ensure that
|
||||
// spacing will be even. If the cellWidth is even it'll work since we start
|
||||
// with dot and end with gap. However if cellWidth is odd, the cell will start
|
||||
// and end with a dot, creating a dash. To resolve this issue, we invert the
|
||||
// pattern every two cells.
|
||||
if (int(cellWidth) % 2 != 0) {
|
||||
cellEven = int((gl_FragCoord.x - paddingX) / cellWidth) % 2;
|
||||
}
|
||||
|
||||
// Since we use the entire descent area for dotted underlines, we limit its
|
||||
// height to a single pixel so we don't draw bars instead of dots.
|
||||
float alpha = 1. - abs(round(cellHeight - underlinePosition) - y);
|
||||
if (x % 2 != cellEven) {
|
||||
alpha = 0;
|
||||
}
|
||||
|
||||
return vec4(color.rgb, alpha);
|
||||
}
|
||||
|
||||
vec4 draw_dashed(int x) {
|
||||
// Since dashes of adjacent cells connect with each other our dash length is
|
||||
// half of the desired total length.
|
||||
int halfDashLen = int(cellWidth) / 4;
|
||||
|
||||
float alpha = 1.;
|
||||
|
||||
// Check if `x` coordinate is where we should draw gap.
|
||||
if (x > halfDashLen && x < cellWidth - halfDashLen - 1) {
|
||||
alpha = 0.;
|
||||
}
|
||||
|
||||
return vec4(color.rgb, alpha);
|
||||
}
|
||||
|
||||
void main() {
|
||||
int x = int(gl_FragCoord.x - paddingX) % int(cellWidth);
|
||||
int y = int(gl_FragCoord.y - paddingY) % int(cellHeight);
|
||||
|
||||
switch (rectKind) {
|
||||
case UNDERCURL:
|
||||
FragColor = draw_undercurl(x, y);
|
||||
break;
|
||||
case DOTTED:
|
||||
if (underlineThickness < 2) {
|
||||
FragColor = draw_dotted(x, y);
|
||||
} else {
|
||||
FragColor = draw_dotted_aliased(x, y);
|
||||
}
|
||||
break;
|
||||
case DASHED:
|
||||
FragColor = draw_dashed(x);
|
||||
break;
|
||||
default:
|
||||
FragColor = color;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,12 +23,12 @@ pub struct RenderRect {
|
|||
pub height: f32,
|
||||
pub color: Rgb,
|
||||
pub alpha: f32,
|
||||
pub is_undercurl: bool,
|
||||
pub kind: RectKind,
|
||||
}
|
||||
|
||||
impl RenderRect {
|
||||
pub fn new(x: f32, y: f32, width: f32, height: f32, color: Rgb, alpha: f32) -> Self {
|
||||
RenderRect { x, y, width, height, color, alpha, is_undercurl: false }
|
||||
RenderRect { kind: RectKind::Normal, x, y, width, height, color, alpha }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,17 @@ pub struct RenderLine {
|
|||
pub color: Rgb,
|
||||
}
|
||||
|
||||
// NOTE: These flags must be in sync with their usage in the rect.*.glsl shaders.
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum RectKind {
|
||||
Normal = 0,
|
||||
Undercurl = 1,
|
||||
DottedUnderline = 2,
|
||||
DashedUnderline = 3,
|
||||
NumKinds = 4,
|
||||
}
|
||||
|
||||
impl RenderLine {
|
||||
pub fn rects(&self, flag: Flags, metrics: &Metrics, size: &SizeInfo) -> Vec<RenderRect> {
|
||||
let mut rects = Vec::new();
|
||||
|
@ -64,7 +75,7 @@ impl RenderLine {
|
|||
end: Point<usize>,
|
||||
color: Rgb,
|
||||
) {
|
||||
let (position, thickness) = match flag {
|
||||
let (position, thickness, ty) = match flag {
|
||||
Flags::DOUBLE_UNDERLINE => {
|
||||
// Position underlines so each one has 50% of descent available.
|
||||
let top_pos = 0.25 * metrics.descent;
|
||||
|
@ -80,18 +91,29 @@ impl RenderLine {
|
|||
color,
|
||||
));
|
||||
|
||||
(bottom_pos, metrics.underline_thickness)
|
||||
(bottom_pos, metrics.underline_thickness, RectKind::Normal)
|
||||
},
|
||||
// Make undercurl occupy the entire descent area.
|
||||
Flags::UNDERCURL => (metrics.descent, metrics.descent.abs()),
|
||||
Flags::UNDERLINE => (metrics.underline_position, metrics.underline_thickness),
|
||||
Flags::STRIKEOUT => (metrics.strikeout_position, metrics.strikeout_thickness),
|
||||
Flags::UNDERCURL => (metrics.descent, metrics.descent.abs(), RectKind::Undercurl),
|
||||
Flags::UNDERLINE => {
|
||||
(metrics.underline_position, metrics.underline_thickness, RectKind::Normal)
|
||||
},
|
||||
// Make dotted occupy the entire descent area.
|
||||
Flags::DOTTED_UNDERLINE => {
|
||||
(metrics.descent, metrics.descent.abs(), RectKind::DottedUnderline)
|
||||
},
|
||||
Flags::DASHED_UNDERLINE => {
|
||||
(metrics.underline_position, metrics.underline_thickness, RectKind::DashedUnderline)
|
||||
},
|
||||
Flags::STRIKEOUT => {
|
||||
(metrics.strikeout_position, metrics.strikeout_thickness, RectKind::Normal)
|
||||
},
|
||||
_ => unimplemented!("Invalid flag for cell line drawing specified"),
|
||||
};
|
||||
|
||||
let mut rect =
|
||||
Self::create_rect(size, metrics.descent, start, end, position, thickness, color);
|
||||
rect.is_undercurl = flag == Flags::UNDERCURL;
|
||||
rect.kind = ty;
|
||||
rects.push(rect);
|
||||
}
|
||||
|
||||
|
@ -161,6 +183,8 @@ impl RenderLines {
|
|||
self.update_flag(cell, Flags::DOUBLE_UNDERLINE);
|
||||
self.update_flag(cell, Flags::STRIKEOUT);
|
||||
self.update_flag(cell, Flags::UNDERCURL);
|
||||
self.update_flag(cell, Flags::DOTTED_UNDERLINE);
|
||||
self.update_flag(cell, Flags::DASHED_UNDERLINE);
|
||||
}
|
||||
|
||||
/// Update the lines for a specific flag.
|
||||
|
@ -224,8 +248,7 @@ pub struct RectRenderer {
|
|||
|
||||
program: RectShaderProgram,
|
||||
|
||||
rect_vertices: Vec<Vertex>,
|
||||
curl_vertices: Vec<Vertex>,
|
||||
vertices: [Vec<Vertex>; 4],
|
||||
}
|
||||
|
||||
impl RectRenderer {
|
||||
|
@ -274,7 +297,7 @@ impl RectRenderer {
|
|||
gl::BindBuffer(gl::ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
Ok(Self { vao, vbo, program, rect_vertices: Vec::new(), curl_vertices: Vec::new() })
|
||||
Ok(Self { vao, vbo, program, vertices: Default::default() })
|
||||
}
|
||||
|
||||
pub fn draw(&mut self, size_info: &SizeInfo, metrics: &Metrics, rects: Vec<RenderRect>) {
|
||||
|
@ -293,43 +316,32 @@ impl RectRenderer {
|
|||
let half_height = size_info.height() / 2.;
|
||||
|
||||
// Build rect vertices vector.
|
||||
self.rect_vertices.clear();
|
||||
self.curl_vertices.clear();
|
||||
self.vertices.iter_mut().for_each(|vertices| vertices.clear());
|
||||
for rect in &rects {
|
||||
if rect.is_undercurl {
|
||||
Self::add_rect(&mut self.curl_vertices, half_width, half_height, rect);
|
||||
} else {
|
||||
Self::add_rect(&mut self.rect_vertices, half_width, half_height, rect);
|
||||
}
|
||||
Self::add_rect(&mut self.vertices[rect.kind as usize], half_width, half_height, rect);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
if !self.curl_vertices.is_empty() {
|
||||
self.program.set_undercurl(true);
|
||||
// We iterate in reverse order to draw plain rects at the end, since we want visual
|
||||
// bell or damage rects be above the lines.
|
||||
for rect_kind in (RectKind::Normal as u8..RectKind::NumKinds as u8).rev() {
|
||||
let vertices = &mut self.vertices[rect_kind as usize];
|
||||
if vertices.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.program.set_rect_kind(rect_kind as u8);
|
||||
|
||||
// Upload accumulated undercurl vertices.
|
||||
gl::BufferData(
|
||||
gl::ARRAY_BUFFER,
|
||||
(self.curl_vertices.len() * mem::size_of::<Vertex>()) as isize,
|
||||
self.curl_vertices.as_ptr() as *const _,
|
||||
(vertices.len() * mem::size_of::<Vertex>()) as isize,
|
||||
vertices.as_ptr() as *const _,
|
||||
gl::STREAM_DRAW,
|
||||
);
|
||||
|
||||
// Draw all vertices as list of triangles.
|
||||
gl::DrawArrays(gl::TRIANGLES, 0, self.curl_vertices.len() as i32);
|
||||
}
|
||||
|
||||
if !self.rect_vertices.is_empty() {
|
||||
self.program.set_undercurl(false);
|
||||
// Upload accumulated rect vertices.
|
||||
gl::BufferData(
|
||||
gl::ARRAY_BUFFER,
|
||||
(self.rect_vertices.len() * mem::size_of::<Vertex>()) as isize,
|
||||
self.rect_vertices.as_ptr() as *const _,
|
||||
gl::STREAM_DRAW,
|
||||
);
|
||||
|
||||
// Draw all vertices as list of triangles.
|
||||
gl::DrawArrays(gl::TRIANGLES, 0, self.rect_vertices.len() as i32);
|
||||
gl::DrawArrays(gl::TRIANGLES, 0, vertices.len() as i32);
|
||||
}
|
||||
|
||||
// Disable program.
|
||||
|
@ -384,10 +396,8 @@ pub struct RectShaderProgram {
|
|||
/// Shader program.
|
||||
program: ShaderProgram,
|
||||
|
||||
/// Undercurl flag.
|
||||
///
|
||||
/// Rect rendering has two modes; one for normal filled rects, and other for undercurls.
|
||||
u_is_undercurl: GLint,
|
||||
/// Kind of rect we're drawing.
|
||||
u_rect_kind: GLint,
|
||||
|
||||
/// Cell width.
|
||||
u_cell_width: GLint,
|
||||
|
@ -399,8 +409,11 @@ pub struct RectShaderProgram {
|
|||
u_padding_x: GLint,
|
||||
u_padding_y: GLint,
|
||||
|
||||
/// Undercurl thickness.
|
||||
u_undercurl_thickness: GLint,
|
||||
/// Underline position.
|
||||
u_underline_position: GLint,
|
||||
|
||||
/// Underline thickness.
|
||||
u_underline_thickness: GLint,
|
||||
|
||||
/// Undercurl position.
|
||||
u_undercurl_position: GLint,
|
||||
|
@ -411,13 +424,14 @@ impl RectShaderProgram {
|
|||
let program = ShaderProgram::new(RECT_SHADER_V, RECT_SHADER_F)?;
|
||||
|
||||
Ok(Self {
|
||||
u_is_undercurl: program.get_uniform_location(cstr!("isUndercurl"))?,
|
||||
u_rect_kind: program.get_uniform_location(cstr!("rectKind"))?,
|
||||
u_cell_width: program.get_uniform_location(cstr!("cellWidth"))?,
|
||||
u_cell_height: program.get_uniform_location(cstr!("cellHeight"))?,
|
||||
u_padding_x: program.get_uniform_location(cstr!("paddingX"))?,
|
||||
u_padding_y: program.get_uniform_location(cstr!("paddingY"))?,
|
||||
u_underline_position: program.get_uniform_location(cstr!("underlinePosition"))?,
|
||||
u_underline_thickness: program.get_uniform_location(cstr!("underlineThickness"))?,
|
||||
u_undercurl_position: program.get_uniform_location(cstr!("undercurlPosition"))?,
|
||||
u_undercurl_thickness: program.get_uniform_location(cstr!("undercurlThickness"))?,
|
||||
program,
|
||||
})
|
||||
}
|
||||
|
@ -426,22 +440,22 @@ impl RectShaderProgram {
|
|||
self.program.id()
|
||||
}
|
||||
|
||||
fn set_undercurl(&self, is_undercurl: bool) {
|
||||
let value = if is_undercurl { 1 } else { 0 };
|
||||
|
||||
fn set_rect_kind(&self, ty: u8) {
|
||||
unsafe {
|
||||
gl::Uniform1i(self.u_is_undercurl, value);
|
||||
gl::Uniform1i(self.u_rect_kind, ty as i32);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_uniforms(&self, size_info: &SizeInfo, metrics: &Metrics) {
|
||||
let position = (0.5 * metrics.descent).abs();
|
||||
let underline_position = metrics.descent.abs() - metrics.underline_position.abs();
|
||||
unsafe {
|
||||
gl::Uniform1f(self.u_cell_width, size_info.cell_width());
|
||||
gl::Uniform1f(self.u_cell_height, size_info.cell_height());
|
||||
gl::Uniform1f(self.u_padding_y, size_info.padding_y());
|
||||
gl::Uniform1f(self.u_padding_x, size_info.padding_x());
|
||||
gl::Uniform1f(self.u_undercurl_thickness, metrics.underline_thickness);
|
||||
gl::Uniform1f(self.u_underline_position, underline_position);
|
||||
gl::Uniform1f(self.u_underline_thickness, metrics.underline_thickness);
|
||||
gl::Uniform1f(self.u_undercurl_position, position);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -772,6 +772,10 @@ pub enum Attr {
|
|||
DoubleUnderline,
|
||||
/// Undercurled text.
|
||||
Undercurl,
|
||||
/// Dotted underlined text.
|
||||
DottedUnderline,
|
||||
/// Dashed underlined text.
|
||||
DashedUnderline,
|
||||
/// Blink cursor slowly.
|
||||
BlinkSlow,
|
||||
/// Blink cursor fast.
|
||||
|
@ -1332,6 +1336,8 @@ fn attrs_from_sgr_parameters(params: &mut ParamsIter<'_>) -> Vec<Option<Attr>> {
|
|||
[4, 0] => Some(Attr::CancelUnderline),
|
||||
[4, 2] => Some(Attr::DoubleUnderline),
|
||||
[4, 3] => Some(Attr::Undercurl),
|
||||
[4, 4] => Some(Attr::DottedUnderline),
|
||||
[4, 5] => Some(Attr::DashedUnderline),
|
||||
[4, ..] => Some(Attr::Underline),
|
||||
[5] => Some(Attr::BlinkSlow),
|
||||
[6] => Some(Attr::BlinkFast),
|
||||
|
|
|
@ -25,7 +25,11 @@ bitflags! {
|
|||
const LEADING_WIDE_CHAR_SPACER = 0b0000_0100_0000_0000;
|
||||
const DOUBLE_UNDERLINE = 0b0000_1000_0000_0000;
|
||||
const UNDERCURL = 0b0001_0000_0000_0000;
|
||||
const ALL_UNDERLINES = Self::UNDERLINE.bits | Self::DOUBLE_UNDERLINE.bits | Self::UNDERCURL.bits;
|
||||
const DOTTED_UNDERLINE = 0b0010_0000_0000_0000;
|
||||
const DASHED_UNDERLINE = 0b0100_0000_0000_0000;
|
||||
const ALL_UNDERLINES = Self::UNDERLINE.bits | Self::DOUBLE_UNDERLINE.bits
|
||||
| Self::UNDERCURL.bits | Self::DOTTED_UNDERLINE.bits
|
||||
| Self::DASHED_UNDERLINE.bits;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,9 +123,7 @@ impl GridCell for Cell {
|
|||
&& self.fg == Color::Named(NamedColor::Foreground)
|
||||
&& !self.flags.intersects(
|
||||
Flags::INVERSE
|
||||
| Flags::UNDERLINE
|
||||
| Flags::DOUBLE_UNDERLINE
|
||||
| Flags::UNDERCURL
|
||||
| Flags::ALL_UNDERLINES
|
||||
| Flags::STRIKEOUT
|
||||
| Flags::WRAPLINE
|
||||
| Flags::WIDE_CHAR_SPACER
|
||||
|
|
|
@ -1850,6 +1850,14 @@ impl<T: EventListener> Handler for Term<T> {
|
|||
cursor.template.flags.remove(Flags::ALL_UNDERLINES);
|
||||
cursor.template.flags.insert(Flags::UNDERCURL);
|
||||
},
|
||||
Attr::DottedUnderline => {
|
||||
cursor.template.flags.remove(Flags::ALL_UNDERLINES);
|
||||
cursor.template.flags.insert(Flags::DOTTED_UNDERLINE);
|
||||
},
|
||||
Attr::DashedUnderline => {
|
||||
cursor.template.flags.remove(Flags::ALL_UNDERLINES);
|
||||
cursor.template.flags.insert(Flags::DASHED_UNDERLINE);
|
||||
},
|
||||
Attr::CancelUnderline => cursor.template.flags.remove(Flags::ALL_UNDERLINES),
|
||||
Attr::Hidden => cursor.template.flags.insert(Flags::HIDDEN),
|
||||
Attr::CancelHidden => cursor.template.flags.remove(Flags::HIDDEN),
|
||||
|
|
Loading…
Reference in a new issue