mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
Merge pull request #88 from mitchellh/sprite-cursor
Use procedurally generated sprites for cursors rather than shader logic
This commit is contained in:
@ -20,6 +20,10 @@ pub const Sprite = enum(u32) {
|
||||
underline_dashed,
|
||||
underline_curly,
|
||||
|
||||
cursor_rect,
|
||||
cursor_hollow_rect,
|
||||
cursor_bar,
|
||||
|
||||
// Note: we don't currently put the box drawing glyphs in here because
|
||||
// there are a LOT and I'm lazy. What I want to do is spend more time
|
||||
// studying the patterns to see if we can programmatically build our
|
||||
|
@ -16,6 +16,7 @@ const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const font = @import("../main.zig");
|
||||
const Sprite = @import("../sprite.zig").Sprite;
|
||||
|
||||
const log = std.log.scoped(.box_font);
|
||||
|
||||
@ -30,6 +31,7 @@ thickness: u32,
|
||||
|
||||
/// The thickness of a line.
|
||||
const Thickness = enum {
|
||||
super_light,
|
||||
light,
|
||||
heavy,
|
||||
|
||||
@ -38,6 +40,7 @@ const Thickness = enum {
|
||||
/// to be in pixels.
|
||||
fn height(self: Thickness, base: u32) u32 {
|
||||
return switch (self) {
|
||||
.super_light => @max(base / 2, 1),
|
||||
.light => base,
|
||||
.heavy => base * 2,
|
||||
};
|
||||
@ -303,6 +306,12 @@ fn draw(self: Box, alloc: Allocator, canvas: *font.sprite.Canvas, cp: u32) !void
|
||||
0x1fb8a => self.draw_right_three_quarters_block(canvas),
|
||||
0x1fb8b => self.draw_right_seven_eighths_block(canvas),
|
||||
|
||||
// Not official box characters but special characters we hide
|
||||
// in the high bits of a unicode codepoint.
|
||||
@enumToInt(Sprite.cursor_rect) => self.draw_cursor_rect(canvas),
|
||||
@enumToInt(Sprite.cursor_hollow_rect) => self.draw_cursor_hollow_rect(canvas),
|
||||
@enumToInt(Sprite.cursor_bar) => self.draw_cursor_bar(canvas),
|
||||
|
||||
else => return error.InvalidCodepoint,
|
||||
}
|
||||
}
|
||||
@ -2532,6 +2541,27 @@ fn draw_dash_vertical(
|
||||
self.vline(canvas, y[3], y[3] + h[3], (self.width - thick_px) / 2, thick_px);
|
||||
}
|
||||
|
||||
fn draw_cursor_rect(self: Box, canvas: *font.sprite.Canvas) void {
|
||||
const thick_px = Thickness.super_light.height(self.thickness);
|
||||
|
||||
self.rect(canvas, 0, 0, self.width - thick_px, self.height - thick_px);
|
||||
}
|
||||
|
||||
fn draw_cursor_hollow_rect(self: Box, canvas: *font.sprite.Canvas) void {
|
||||
const thick_px = Thickness.super_light.height(self.thickness);
|
||||
|
||||
self.vline(canvas, 0, self.height, 0, thick_px);
|
||||
self.vline(canvas, 0, self.height, self.width - thick_px, thick_px);
|
||||
self.hline(canvas, 0, self.width, 0, thick_px);
|
||||
self.hline(canvas, 0, self.width, self.height - thick_px, thick_px);
|
||||
}
|
||||
|
||||
fn draw_cursor_bar(self: Box, canvas: *font.sprite.Canvas) void {
|
||||
const thick_px = Thickness.light.height(self.thickness);
|
||||
|
||||
self.vline(canvas, 0, self.height - thick_px, 0, thick_px);
|
||||
}
|
||||
|
||||
fn vline_middle(self: Box, canvas: *font.sprite.Canvas, thickness: Thickness) void {
|
||||
const thick_px = thickness.height(self.thickness);
|
||||
self.vline(canvas, 0, self.height, (self.width - thick_px) / 2, thick_px);
|
||||
|
@ -95,6 +95,11 @@ const Kind = enum {
|
||||
.underline_dashed,
|
||||
.underline_curly,
|
||||
=> .underline,
|
||||
|
||||
.cursor_rect,
|
||||
.cursor_hollow_rect,
|
||||
.cursor_bar,
|
||||
=> .box,
|
||||
},
|
||||
|
||||
// Box fonts
|
||||
|
@ -71,6 +71,7 @@ const Draw = struct {
|
||||
.underline_dotted => self.drawDotted(canvas),
|
||||
.underline_dashed => self.drawDashed(canvas),
|
||||
.underline_curly => self.drawCurly(canvas),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,18 +114,7 @@ const GPUCellMode = enum(u8) {
|
||||
bg = 1,
|
||||
fg = 2,
|
||||
fg_color = 7,
|
||||
cursor_rect = 3,
|
||||
cursor_rect_hollow = 4,
|
||||
cursor_bar = 5,
|
||||
strikethrough = 8,
|
||||
|
||||
pub fn fromCursor(cursor: renderer.CursorStyle) GPUCellMode {
|
||||
return switch (cursor) {
|
||||
.box => .cursor_rect,
|
||||
.box_hollow => .cursor_rect_hollow,
|
||||
.bar => .cursor_bar,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Returns the hints that we want for this
|
||||
@ -1005,14 +994,33 @@ fn addCursor(self: *Metal, screen: *terminal.Screen) void {
|
||||
.b = 0xFF,
|
||||
};
|
||||
|
||||
const sprite: font.Sprite = switch (self.cursor_style) {
|
||||
.box => .cursor_rect,
|
||||
.box_hollow => .cursor_hollow_rect,
|
||||
.bar => .cursor_bar,
|
||||
};
|
||||
|
||||
const glyph = self.font_group.renderGlyph(
|
||||
self.alloc,
|
||||
font.sprite_index,
|
||||
@enumToInt(sprite),
|
||||
null,
|
||||
) catch |err| {
|
||||
log.warn("error rendering cursor glyph err={}", .{err});
|
||||
return;
|
||||
};
|
||||
|
||||
self.cells.appendAssumeCapacity(.{
|
||||
.mode = GPUCellMode.fromCursor(self.cursor_style),
|
||||
.mode = .fg,
|
||||
.grid_pos = .{
|
||||
@intToFloat(f32, screen.cursor.x),
|
||||
@intToFloat(f32, screen.cursor.y),
|
||||
},
|
||||
.cell_width = if (cell.attrs.wide) 2 else 1,
|
||||
.color = .{ color.r, color.g, color.b, 0xFF },
|
||||
.glyph_pos = .{ glyph.atlas_x, glyph.atlas_y },
|
||||
.glyph_size = .{ glyph.width, glyph.height },
|
||||
.glyph_offset = .{ glyph.offset_x, glyph.offset_y },
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -218,22 +218,11 @@ const GPUCellMode = enum(u8) {
|
||||
bg = 1,
|
||||
fg = 2,
|
||||
fg_color = 7,
|
||||
cursor_rect = 3,
|
||||
cursor_rect_hollow = 4,
|
||||
cursor_bar = 5,
|
||||
strikethrough = 8,
|
||||
|
||||
// Non-exhaustive because masks change it
|
||||
_,
|
||||
|
||||
pub fn fromCursor(cursor: renderer.CursorStyle) GPUCellMode {
|
||||
return switch (cursor) {
|
||||
.box => .cursor_rect,
|
||||
.box_hollow => .cursor_rect_hollow,
|
||||
.bar => .cursor_bar,
|
||||
};
|
||||
}
|
||||
|
||||
/// Apply a mask to the mode.
|
||||
pub fn mask(self: GPUCellMode, m: GPUCellMode) GPUCellMode {
|
||||
return @intToEnum(
|
||||
@ -961,19 +950,41 @@ fn addCursor(self: *OpenGL, screen: *terminal.Screen) void {
|
||||
.b = 0xFF,
|
||||
};
|
||||
|
||||
const sprite: font.Sprite = switch (self.cursor_style) {
|
||||
.box => .cursor_rect,
|
||||
.box_hollow => .cursor_hollow_rect,
|
||||
.bar => .cursor_bar,
|
||||
};
|
||||
|
||||
const glyph = self.font_group.renderGlyph(
|
||||
self.alloc,
|
||||
font.sprite_index,
|
||||
@enumToInt(sprite),
|
||||
null,
|
||||
) catch |err| {
|
||||
log.warn("error rendering cursor glyph err={}", .{err});
|
||||
return;
|
||||
};
|
||||
|
||||
self.cells.appendAssumeCapacity(.{
|
||||
.mode = GPUCellMode.fromCursor(self.cursor_style),
|
||||
.mode = .fg,
|
||||
.grid_col = @intCast(u16, screen.cursor.x),
|
||||
.grid_row = @intCast(u16, screen.cursor.y),
|
||||
.grid_width = if (cell.attrs.wide) 2 else 1,
|
||||
.fg_r = 0,
|
||||
.fg_g = 0,
|
||||
.fg_b = 0,
|
||||
.fg_a = 0,
|
||||
.bg_r = color.r,
|
||||
.bg_g = color.g,
|
||||
.bg_b = color.b,
|
||||
.bg_a = 255,
|
||||
.fg_r = color.r,
|
||||
.fg_g = color.g,
|
||||
.fg_b = color.b,
|
||||
.fg_a = 255,
|
||||
.bg_r = 0,
|
||||
.bg_g = 0,
|
||||
.bg_b = 0,
|
||||
.bg_a = 0,
|
||||
.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,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -27,9 +27,6 @@ uniform vec2 cell_size;
|
||||
const uint MODE_BG = 1u;
|
||||
const uint MODE_FG = 2u;
|
||||
const uint MODE_FG_COLOR = 7u;
|
||||
const uint MODE_CURSOR_RECT = 3u;
|
||||
const uint MODE_CURSOR_RECT_HOLLOW = 4u;
|
||||
const uint MODE_CURSOR_BAR = 5u;
|
||||
const uint MODE_STRIKETHROUGH = 8u;
|
||||
|
||||
void main() {
|
||||
@ -49,50 +46,6 @@ void main() {
|
||||
out_FragColor = texture(text_color, glyph_tex_coords);
|
||||
break;
|
||||
|
||||
case MODE_CURSOR_RECT:
|
||||
out_FragColor = color;
|
||||
break;
|
||||
|
||||
case MODE_CURSOR_RECT_HOLLOW:
|
||||
// Okay so yeah this is probably horrendously slow and a shader
|
||||
// should never do this, but we only ever render a cursor for ONE
|
||||
// rectangle so we take the slowdown for that one.
|
||||
|
||||
// Default to no color.
|
||||
out_FragColor = vec4(0., 0., 0., 0.);
|
||||
|
||||
// We subtracted one from cell size because our coordinates start at 0.
|
||||
// So a width of 50 means max pixel of 49.
|
||||
vec2 cell_size_coords = cell_size - 1;
|
||||
|
||||
// Apply padding
|
||||
vec2 padding = vec2(1.,1.);
|
||||
cell_size_coords = cell_size_coords - (padding * 2);
|
||||
vec2 screen_cell_pos_padded = screen_cell_pos + padding;
|
||||
|
||||
// Convert our frag coord to offset of this cell. We have to subtract
|
||||
// 0.5 because the frag coord is in center pixels.
|
||||
vec2 cell_frag_coord = gl_FragCoord.xy - screen_cell_pos_padded - 0.5;
|
||||
|
||||
// If the frag coords are in the bounds, then we color it.
|
||||
const float eps = 0.1;
|
||||
if (cell_frag_coord.x >= 0 && cell_frag_coord.y >= 0 &&
|
||||
cell_frag_coord.x <= cell_size_coords.x &&
|
||||
cell_frag_coord.y <= cell_size_coords.y) {
|
||||
if (abs(cell_frag_coord.x) < eps ||
|
||||
abs(cell_frag_coord.x - cell_size_coords.x) < eps ||
|
||||
abs(cell_frag_coord.y) < eps ||
|
||||
abs(cell_frag_coord.y - cell_size_coords.y) < eps) {
|
||||
out_FragColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case MODE_CURSOR_BAR:
|
||||
out_FragColor = color;
|
||||
break;
|
||||
|
||||
case MODE_STRIKETHROUGH:
|
||||
out_FragColor = color;
|
||||
break;
|
||||
|
@ -5,9 +5,6 @@ enum Mode : uint8_t {
|
||||
MODE_BG = 1u,
|
||||
MODE_FG = 2u,
|
||||
MODE_FG_COLOR = 7u,
|
||||
MODE_CURSOR_RECT = 3u,
|
||||
MODE_CURSOR_RECT_HOLLOW = 4u,
|
||||
MODE_CURSOR_BAR = 5u,
|
||||
MODE_STRIKETHROUGH = 8u,
|
||||
};
|
||||
|
||||
@ -124,33 +121,6 @@ vertex VertexOut uber_vertex(
|
||||
break;
|
||||
}
|
||||
|
||||
case MODE_CURSOR_RECT:
|
||||
// Same as background since we're taking up the whole cell.
|
||||
cell_pos = cell_pos + cell_size_scaled * position;
|
||||
|
||||
out.position = uniforms.projection_matrix * float4(cell_pos, 0.0f, 1.0);
|
||||
break;
|
||||
|
||||
case MODE_CURSOR_RECT_HOLLOW:
|
||||
// Top-left position of this cell is needed for the hollow rect.
|
||||
out.tex_coord = cell_pos;
|
||||
|
||||
// Same as background since we're taking up the whole cell.
|
||||
cell_pos = cell_pos + cell_size_scaled * position;
|
||||
out.position = uniforms.projection_matrix * float4(cell_pos, 0.0f, 1.0);
|
||||
break;
|
||||
|
||||
case MODE_CURSOR_BAR: {
|
||||
// Make the bar a smaller version of our cell
|
||||
float2 bar_size = float2(uniforms.cell_size.x * 0.2, uniforms.cell_size.y);
|
||||
|
||||
// Same as background since we're taking up the whole cell.
|
||||
cell_pos = cell_pos + bar_size * position;
|
||||
|
||||
out.position = uniforms.projection_matrix * float4(cell_pos, 0.0f, 1.0);
|
||||
break;
|
||||
}
|
||||
|
||||
case MODE_STRIKETHROUGH: {
|
||||
// Strikethrough Y value is just our thickness
|
||||
float2 strikethrough_size = float2(cell_size_scaled.x, uniforms.strikethrough_thickness);
|
||||
@ -201,47 +171,6 @@ fragment float4 uber_fragment(
|
||||
return textureColor.sample(textureSampler, coord);
|
||||
}
|
||||
|
||||
case MODE_CURSOR_RECT:
|
||||
return in.color;
|
||||
|
||||
case MODE_CURSOR_RECT_HOLLOW: {
|
||||
// Okay so yeah this is probably horrendously slow and a shader
|
||||
// should never do this, but we only ever render a cursor for ONE
|
||||
// rectangle so we take the slowdown for that one.
|
||||
|
||||
// We subtracted one from cell size because our coordinates start at 0.
|
||||
// So a width of 50 means max pixel of 49.
|
||||
float2 cell_size_coords = in.cell_size - 1;
|
||||
|
||||
// Apply padding
|
||||
float2 padding = float2(1.0f, 1.0f);
|
||||
cell_size_coords = cell_size_coords - (padding * 2);
|
||||
float2 screen_cell_pos_padded = in.tex_coord + padding;
|
||||
|
||||
// Convert our frag coord to offset of this cell. We have to subtract
|
||||
// 0.5 because the frag coord is in center pixels.
|
||||
float2 cell_frag_coord = in.position.xy - screen_cell_pos_padded - 0.5;
|
||||
|
||||
// If the frag coords are in the bounds, then we color it.
|
||||
const float eps = 0.1;
|
||||
if (cell_frag_coord.x >= 0 && cell_frag_coord.y >= 0 &&
|
||||
cell_frag_coord.x <= cell_size_coords.x &&
|
||||
cell_frag_coord.y <= cell_size_coords.y) {
|
||||
if (abs(cell_frag_coord.x) < eps ||
|
||||
abs(cell_frag_coord.x - cell_size_coords.x) < eps ||
|
||||
abs(cell_frag_coord.y) < eps ||
|
||||
abs(cell_frag_coord.y - cell_size_coords.y) < eps) {
|
||||
return in.color;
|
||||
}
|
||||
}
|
||||
|
||||
// Default to no color.
|
||||
return float4(0.0f);
|
||||
}
|
||||
|
||||
case MODE_CURSOR_BAR:
|
||||
return in.color;
|
||||
|
||||
case MODE_STRIKETHROUGH:
|
||||
return in.color;
|
||||
}
|
||||
|
@ -7,9 +7,6 @@
|
||||
const uint MODE_BG = 1u;
|
||||
const uint MODE_FG = 2u;
|
||||
const uint MODE_FG_COLOR = 7u;
|
||||
const uint MODE_CURSOR_RECT = 3u;
|
||||
const uint MODE_CURSOR_RECT_HOLLOW = 4u;
|
||||
const uint MODE_CURSOR_BAR = 5u;
|
||||
const uint MODE_STRIKETHROUGH = 8u;
|
||||
|
||||
// The grid coordinates (x, y) where x < columns and y < rows
|
||||
@ -167,36 +164,6 @@ void main() {
|
||||
color = fg_color_in / 255.;
|
||||
break;
|
||||
|
||||
case MODE_CURSOR_RECT:
|
||||
// Same as background since we're taking up the whole cell.
|
||||
cell_pos = cell_pos + cell_size_scaled * position;
|
||||
|
||||
gl_Position = projection * vec4(cell_pos, cell_z, 1.0);
|
||||
color = bg_color_in / 255.0;
|
||||
break;
|
||||
|
||||
case MODE_CURSOR_RECT_HOLLOW:
|
||||
// Top-left position of this cell is needed for the hollow rect.
|
||||
screen_cell_pos = cell_pos;
|
||||
|
||||
// Same as background since we're taking up the whole cell.
|
||||
cell_pos = cell_pos + cell_size_scaled * position;
|
||||
|
||||
gl_Position = projection * vec4(cell_pos, cell_z, 1.0);
|
||||
color = bg_color_in / 255.0;
|
||||
break;
|
||||
|
||||
case MODE_CURSOR_BAR:
|
||||
// Make the bar a smaller version of our cell
|
||||
vec2 bar_size = vec2(cell_size.x * 0.2, cell_size.y);
|
||||
|
||||
// Same as background since we're taking up the whole cell.
|
||||
cell_pos = cell_pos + bar_size * position;
|
||||
|
||||
gl_Position = projection * vec4(cell_pos, cell_z, 1.0);
|
||||
color = bg_color_in / 255.0;
|
||||
break;
|
||||
|
||||
case MODE_STRIKETHROUGH:
|
||||
// Strikethrough Y value is just our thickness
|
||||
vec2 strikethrough_size = vec2(cell_size_scaled.x, strikethrough_thickness);
|
||||
|
Reference in New Issue
Block a user