From f53efa633a77cd3985fc406ab55595797031aac2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 28 Jun 2022 14:21:57 -0700 Subject: [PATCH] initial underline support, can be improved --- TODO.md | 4 +- shaders/cell.f.glsl | 5 ++ shaders/cell.v.glsl | 19 +++++++ src/Grid.zig | 101 +++++++++++++++++++++++--------------- src/terminal/Screen.zig | 1 + src/terminal/Terminal.zig | 4 ++ 6 files changed, 93 insertions(+), 41 deletions(-) diff --git a/TODO.md b/TODO.md index d6a397497..0611a6d7f 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,7 @@ Bugs: +* Underline should use freetype underline thickness hint + Performance: * libuv allocates on every read, we should use a read buffer pool @@ -19,8 +21,6 @@ Major Features: * Line wrap * Selection, highlighting * Copy (paste is done) -* Bold -* Underline * Strikethrough * Emoji * Ligatures diff --git a/shaders/cell.f.glsl b/shaders/cell.f.glsl index d665b29d6..9f17883d1 100644 --- a/shaders/cell.f.glsl +++ b/shaders/cell.f.glsl @@ -28,6 +28,7 @@ const uint MODE_FG = 2u; const uint MODE_CURSOR_RECT = 3u; const uint MODE_CURSOR_RECT_HOLLOW = 4u; const uint MODE_CURSOR_BAR = 5u; +const uint MODE_UNDERLINE = 6u; void main() { switch (mode) { @@ -83,5 +84,9 @@ void main() { case MODE_CURSOR_BAR: out_FragColor = color; break; + + case MODE_UNDERLINE: + out_FragColor = color; + break; } } diff --git a/shaders/cell.v.glsl b/shaders/cell.v.glsl index 1cdd9e22b..5b2313c58 100644 --- a/shaders/cell.v.glsl +++ b/shaders/cell.v.glsl @@ -9,6 +9,7 @@ const uint MODE_FG = 2u; const uint MODE_CURSOR_RECT = 3u; const uint MODE_CURSOR_RECT_HOLLOW = 4u; const uint MODE_CURSOR_BAR = 5u; +const uint MODE_UNDERLINE = 6u; // The grid coordinates (x, y) where x < columns and y < rows layout (location = 0) in vec2 grid_coord; @@ -158,5 +159,23 @@ void main() { gl_Position = projection * vec4(cell_pos, 0.0, 1.0); color = bg_color_in / 255.0; break; + + case MODE_UNDERLINE: + // Make the underline a smaller version of our cell + // TODO: use real font underline thickness + vec2 underline_size = vec2(cell_size.x, cell_size.y*0.05); + + // Position our underline so that it is midway between the glyph + // baseline and the bottom of the cell. + vec2 underline_offset = vec2(cell_size.x, cell_size.y - (glyph_baseline / 2)); + + // Go to the bottom of the cell, take away the size of the + // underline, and that is our position. We also float it slightly + // above the bottom. + cell_pos = cell_pos + underline_offset - underline_size * position; + + gl_Position = projection * vec4(cell_pos, 0.0, 1.0); + color = fg_color_in / 255.0; + break; } } diff --git a/src/Grid.zig b/src/Grid.zig index 5e23d4d29..14a2a9869 100644 --- a/src/Grid.zig +++ b/src/Grid.zig @@ -271,9 +271,9 @@ pub fn updateCells(self: *Grid, term: Terminal) !void { try self.cells.ensureTotalCapacity( self.alloc, - // * 2 for background modes and cursor + // * 3 for background modes and cursor and underlines // + 1 for cursor - (term.screen.rows * term.screen.cols * 2) + 1, + (term.screen.rows * term.screen.cols * 3) + 1, ); // Build each cell @@ -308,44 +308,67 @@ pub fn updateCells(self: *Grid, term: Terminal) !void { }); } - // If the cell is empty then we draw nothing in the box. - if (cell.empty()) continue; - - // Determine our glyph styling - const style: font.Style = if (cell.attrs.bold == 1) - .bold - else - .regular; - - // Get our glyph - // TODO: if we add a glyph, I think we need to rerender the texture. - const glyph = if (self.font_atlas.getGlyph(cell.char, style)) |glyph| - glyph - else glyph: { - self.atlas_dirty = true; - break :glyph try self.font_atlas.addGlyph(self.alloc, cell.char, style); - }; - const fg = cell.fg orelse self.foreground; - self.cells.appendAssumeCapacity(.{ - .mode = 2, - .grid_col = @intCast(u16, x), - .grid_row = @intCast(u16, y), - .glyph_x = glyph.atlas_x, - .glyph_y = glyph.atlas_y, - .glyph_width = glyph.width, - .glyph_height = glyph.height, - .glyph_offset_x = glyph.offset_x, - .glyph_offset_y = glyph.offset_y, - .fg_r = fg.r, - .fg_g = fg.g, - .fg_b = fg.b, - .fg_a = 255, - .bg_r = 0, - .bg_g = 0, - .bg_b = 0, - .bg_a = 0, - }); + + // If the cell is empty then we draw nothing in the box. + if (!cell.empty()) { + // Determine our glyph styling + const style: font.Style = if (cell.attrs.bold == 1) + .bold + else + .regular; + + // Get our glyph + // TODO: if we add a glyph, I think we need to rerender the texture. + const glyph = if (self.font_atlas.getGlyph(cell.char, style)) |glyph| + glyph + else glyph: { + self.atlas_dirty = true; + break :glyph try self.font_atlas.addGlyph(self.alloc, cell.char, style); + }; + + self.cells.appendAssumeCapacity(.{ + .mode = 2, + .grid_col = @intCast(u16, x), + .grid_row = @intCast(u16, y), + .glyph_x = glyph.atlas_x, + .glyph_y = glyph.atlas_y, + .glyph_width = glyph.width, + .glyph_height = glyph.height, + .glyph_offset_x = glyph.offset_x, + .glyph_offset_y = glyph.offset_y, + .fg_r = fg.r, + .fg_g = fg.g, + .fg_b = fg.b, + .fg_a = 255, + .bg_r = 0, + .bg_g = 0, + .bg_b = 0, + .bg_a = 0, + }); + } + + if (cell.attrs.underline == 1) { + self.cells.appendAssumeCapacity(.{ + .mode = 6, // underline + .grid_col = @intCast(u16, x), + .grid_row = @intCast(u16, y), + .glyph_x = 0, + .glyph_y = 0, + .glyph_width = 0, + .glyph_height = 0, + .glyph_offset_x = 0, + .glyph_offset_y = 0, + .fg_r = fg.r, + .fg_g = fg.g, + .fg_b = fg.b, + .fg_a = 255, + .bg_r = 0, + .bg_g = 0, + .bg_b = 0, + .bg_a = 0, + }); + } } } diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index 315ee203b..5b48c913a 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -24,6 +24,7 @@ pub const Cell = struct { /// TODO: pack it attrs: struct { bold: u1 = 0, + underline: u1 = 0, inverse: u1 = 0, } = .{}, diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 4e0c982fd..5de446b9f 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -154,6 +154,10 @@ pub fn setAttribute(self: *Terminal, attr: sgr.Attribute) !void { self.cursor.pen.attrs.bold = 1; }, + .underline => { + self.cursor.pen.attrs.underline = 1; + }, + .inverse => { self.cursor.pen.attrs.inverse = 1; },