summaryrefslogtreecommitdiff
path: root/src/gui
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-09 23:43:06 -0300
committerGabriel Schneider <[email protected]>2026-08-10 09:17:07 -0300
commit0d66575a3c888498c7929e2ec345133628c5d63d (patch)
tree3be80051fd505f9675b36f9b570c46a955ca3445 /src/gui
parent4b5e793f48f6e721c39ab6ddcf755604df73b643 (diff)
downloadpardes-0d66575a3c888498c7929e2ec345133628c5d63d.tar.gz
pardes-0d66575a3c888498c7929e2ec345133628c5d63d.zip
fix tab rendering - waybe wreckless
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/gui.zig116
1 files changed, 87 insertions, 29 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig
index 7a9c2135..a03e51ce 100644
--- a/src/gui/gui.zig
+++ b/src/gui/gui.zig
@@ -153,7 +153,7 @@ test "GUI animation clock is active-only and cadence gated" {
/// Web tap-vs-scroll heuristic, measured after normalized coordinates have
/// been mapped into backing pixels (12px = 8 CSS px at web_render_scale).
const touch_tap_slop_px: f32 = 12.0;
-const max_overlay_vertices: usize = touch_click_flash_vertices + max_touch_points * (1100 + max_touch_trail_points * overlay_circle_vertices);
+const max_overlay_vertices: usize = 6 + touch_click_flash_vertices + max_touch_points * (1100 + max_touch_trail_points * overlay_circle_vertices);
// Ctrl+ / Ctrl-: how far one press moves g.px, and the two sizes it stops at.
// ONE size for the whole window, not one per pane: the core lays every pane
@@ -3230,7 +3230,7 @@ fn emitScrollRows(g: *Gui, instances: [*]CellInstance, base: u32, surface: *pard
var col = x0;
while (col < x0 + bw) : (col += 1) {
const sidx: u32 = @as(u32, row) * surface.cols + col;
- emitInstance(g, instances, base + n, col, row, shifted, win_w, win_h, surface.at(col, row), sidx == cursor_idx, cursor_bar);
+ emitInstance(g, instances, base + n, col, row, shifted, win_w, win_h, surface.at(col, row), sidx == cursor_idx and !cursor_bar);
n += 1;
}
}
@@ -3240,7 +3240,7 @@ fn emitScrollRows(g: *Gui, instances: [*]CellInstance, base: u32, surface: *pard
const erow: u16 = if (g.scroll_lag > 0) y0 + bh else y0 - 1;
var i: u16 = 0;
while (i < bw) : (i += 1) {
- emitInstance(g, instances, base + n, x0 + i, erow, shifted, win_w, win_h, &g.scroll_edge[i], false, false);
+ emitInstance(g, instances, base + n, x0 + i, erow, shifted, win_w, win_h, &g.scroll_edge[i], false);
n += 1;
}
}
@@ -3579,7 +3579,8 @@ fn renderFrame(g: *Gui, gpa: std.mem.Allocator, surface: *pardes.Surface, crt_on
if (crt_on) try ensureSceneTexture(g, sw, sh);
const scene: *c.SDL_GPUTexture = if (crt_on) g.scene_tex.? else target;
- const overlay_count = buildTouchOverlay(g, sw, sh, debug_on);
+ const layout = fixedCellLayout(g);
+ const overlay_count = buildOverlay(g, surface, layout, sw, sh, debug_on);
if (overlay_count != 0) uploadOverlayGpu(g, cmd, overlay_count);
const image_count = prepareNativeImages(g, gpa, cmd, surface, sw, sh);
@@ -3610,7 +3611,6 @@ fn renderFrame(g: *Gui, gpa: std.mem.Allocator, surface: *pardes.Surface, crt_on
return;
});
const instances: [*]CellInstance = @ptrCast(@alignCast(vptr));
- const layout = fixedCellLayout(g);
const cursor_idx: u32 = if (surface.cursor) |cu| @as(u32, cu.y) * surface.cols + cu.x else cells;
const cursor_bar = if (surface.cursor) |cu| cu.bar else false;
var row: u16 = 0;
@@ -3618,7 +3618,7 @@ fn renderFrame(g: *Gui, gpa: std.mem.Allocator, surface: *pardes.Surface, crt_on
var col: u16 = 0;
while (col < surface.cols) : (col += 1) {
const idx: u32 = @as(u32, row) * surface.cols + col;
- emitInstance(g, instances, idx, col, row, layout, win_w, win_h, surface.at(col, row), idx == cursor_idx, cursor_bar);
+ emitInstance(g, instances, idx, col, row, layout, win_w, win_h, surface.at(col, row), idx == cursor_idx and !cursor_bar);
}
}
const shifted = emitScrollRows(g, instances, cells, surface, layout, win_w, win_h);
@@ -3698,15 +3698,13 @@ fn emitInstance(
win_h: f32,
cell: *const pardes.Cell,
is_cursor: bool,
- cursor_bar: bool,
) void {
- // resolve the cell style to concrete fg/bg, matching the prototype's
- // cellKey: reverse swaps, invisible hides, dim darkens. The cursor cell
- // renders as reverse (cancelling an already-reversed cell); an insert
- // cursor keeps the cell colors and draws a bar glyph instead.
+ // Resolve the cell style to concrete fg/bg. A normal block cursor reverses
+ // the cell; the insert cursor is a separate overlay, so the glyph beneath
+ // it remains in this base pass.
var fg = fg_default;
var bg = bg_default;
- var reverse = is_cursor and !cursor_bar;
+ var reverse = is_cursor;
if (!cell.default) {
const st = cell.style;
fg = switch (st.fg) {
@@ -3735,11 +3733,7 @@ fn emitInstance(
const y0 = 1.0 - (py0 / win_h) * 2.0;
const y1 = 1.0 - ((py0 + layout.h) / win_h) * 2.0;
- const grapheme = cell.grapheme();
- var cp: u32 = if (cell.default or grapheme.len == 0) ' ' else firstCp(grapheme);
- // ponytail: the insert bar is the left-eighth block glyph over the cell
- // (hides the char under it); a dedicated quad if that ever matters
- if (is_cursor and cursor_bar) cp = 0x258F;
+ const cp = cellCodepoint(cell);
const slot = if (cp == ' ') g.space_slot else ensureGlyph(g, cp);
const aw_f: f32 = @floatFromInt(atlas_w);
const ah_f: f32 = @floatFromInt(atlas_h);
@@ -3762,6 +3756,34 @@ fn emitInstance(
};
}
+fn cellCodepoint(cell: *const pardes.Cell) u32 {
+ const grapheme = cell.grapheme();
+ return if (cell.default or grapheme.len == 0) ' ' else firstCp(grapheme);
+}
+
+test "insert cursor overlays without replacing the character beneath it" {
+ const cell: pardes.Cell = .{
+ .text = .{ 'x', ' ', ' ', ' ', ' ', ' ', ' ' },
+ .len = 1,
+ .default = false,
+ };
+ try std.testing.expectEqual(@as(u32, 'x'), cellCodepoint(&cell));
+
+ var vertices: [6]OverlayVertex = undefined;
+ var builder: OverlayBuilder = .{
+ .vertices = &vertices,
+ .win_w = 100,
+ .win_h = 100,
+ };
+ addCursorBar(&builder, 2, 3, true, .{
+ .x_off = 0,
+ .y_off = 0,
+ .w = 10,
+ .h = 20,
+ });
+ try std.testing.expectEqual(@as(usize, 6), builder.len);
+}
+
fn palColor(idx: u8) [3]u8 {
const p = ghostty_vt.color.default[idx];
return .{ p.r, p.g, p.b };
@@ -4058,9 +4080,9 @@ fn renderFrameGl(g: *Gui, gpa: std.mem.Allocator, surface: *pardes.Surface, debu
);
c.glClear(c.GL_COLOR_BUFFER_BIT);
- const overlay_count = buildTouchOverlay(g, sw, sh, debug_on);
const cells: u32 = @as(u32, surface.cols) * surface.rows;
if (cells == 0) {
+ const overlay_count = buildTouchOverlay(g, sw, sh, debug_on);
drawOverlayGl(g, overlay_count);
_ = c.SDL_GL_SwapWindow(g.window);
return;
@@ -4074,6 +4096,7 @@ fn renderFrameGl(g: *Gui, gpa: std.mem.Allocator, surface: *pardes.Surface, debu
const win_w: f32 = @floatFromInt(sw);
const win_h: f32 = @floatFromInt(sh);
const layout = cellLayout(g, surface.cols, surface.rows, sw, sh);
+ const overlay_count = buildOverlay(g, surface, layout, sw, sh, debug_on);
const cursor_idx: u32 = if (surface.cursor) |cu| @as(u32, cu.y) * surface.cols + cu.x else cells;
const cursor_bar = if (surface.cursor) |cu| cu.bar else false;
var row: u16 = 0;
@@ -4081,7 +4104,7 @@ fn renderFrameGl(g: *Gui, gpa: std.mem.Allocator, surface: *pardes.Surface, debu
var col: u16 = 0;
while (col < surface.cols) : (col += 1) {
const idx: u32 = @as(u32, row) * surface.cols + col;
- emitInstance(g, g.gl_instances.ptr, idx, col, row, layout, win_w, win_h, surface.at(col, row), idx == cursor_idx, cursor_bar);
+ emitInstance(g, g.gl_instances.ptr, idx, col, row, layout, win_w, win_h, surface.at(col, row), idx == cursor_idx and !cursor_bar);
}
}
const shifted = emitScrollRows(g, g.gl_instances.ptr, cells, surface, layout, win_w, win_h);
@@ -4305,15 +4328,51 @@ fn writeCapturePpm(g: *Gui, gpa: std.mem.Allocator, pixels: []const u8, width: u
// flash HUD, alpha-blended over the grid only while the Debug builtin is on.
// =====================================================================
-fn buildTouchOverlay(g: *Gui, sw: u32, sh: u32, debug_on: bool) u32 {
- if (!debug_on) {
- g.touch.click_flash_frames = 0;
- return 0;
+fn addCursorBar(builder: *OverlayBuilder, x: u16, y: u16, bar: bool, layout: CellLayout) void {
+ if (!bar) return;
+ const x0 = layout.x_off + @as(f32, @floatFromInt(x)) * layout.w;
+ const y0 = layout.y_off + @as(f32, @floatFromInt(y)) * layout.h;
+ builder.addRect(
+ x0,
+ y0,
+ x0 + @max(1.0, layout.w / 8.0),
+ y0 + layout.h,
+ .{
+ .r = @as(f32, @floatFromInt(fg_default[0])) / 255.0,
+ .g = @as(f32, @floatFromInt(fg_default[1])) / 255.0,
+ .b = @as(f32, @floatFromInt(fg_default[2])) / 255.0,
+ .a = 1.0,
+ },
+ );
+}
+
+fn buildOverlay(g: *Gui, surface: *const pardes.Surface, layout: CellLayout, sw: u32, sh: u32, debug_on: bool) u32 {
+ if (g.overlay_vertices.len == 0 or sw == 0 or sh == 0) return 0;
+ const win_w: f32 = @floatFromInt(sw);
+ const win_h: f32 = @floatFromInt(sh);
+ var builder = OverlayBuilder{ .vertices = g.overlay_vertices, .win_w = win_w, .win_h = win_h };
+ if (surface.cursor) |cursor| {
+ if (cursor.x < surface.cols and cursor.y < surface.rows)
+ addCursorBar(&builder, cursor.x, cursor.y, cursor.bar, layout);
}
+ appendTouchOverlay(g, &builder, debug_on);
+ return @intCast(builder.len);
+}
+
+fn buildTouchOverlay(g: *Gui, sw: u32, sh: u32, debug_on: bool) u32 {
if (g.overlay_vertices.len == 0 or sw == 0 or sh == 0) return 0;
const win_w: f32 = @floatFromInt(sw);
const win_h: f32 = @floatFromInt(sh);
var builder = OverlayBuilder{ .vertices = g.overlay_vertices, .win_w = win_w, .win_h = win_h };
+ appendTouchOverlay(g, &builder, debug_on);
+ return @intCast(builder.len);
+}
+
+fn appendTouchOverlay(g: *Gui, builder: *OverlayBuilder, debug_on: bool) void {
+ if (!debug_on) {
+ g.touch.click_flash_frames = 0;
+ return;
+ }
const t = &g.touch;
if (t.click_flash_frames != 0) {
@@ -4325,7 +4384,7 @@ fn buildTouchOverlay(g: *Gui, sw: u32, sh: u32, debug_on: bool) u32 {
const stripe_w = 4.0;
const label_w = pad * 2.0 + stripe_w + 3.0 + @as(f32, @floatFromInt(label.len)) * char_step - glyph_scale;
const label_h = pad * 2.0 + 5.0 * glyph_scale;
- builder.addRect(0.0, 0.0, win_w, win_h, .{ .r = 1.0, .g = 0.78, .b = 0.18, .a = 0.055 * flash });
+ builder.addRect(0.0, 0.0, builder.win_w, builder.win_h, .{ .r = 1.0, .g = 0.78, .b = 0.18, .a = 0.055 * flash });
builder.addRect(8.0, 8.0, 8.0 + label_w, 8.0 + label_h, .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 0.54 + 0.22 * flash });
builder.addRect(8.0 + pad, 8.0 + pad, 8.0 + pad + stripe_w, 8.0 + label_h - pad, .{ .r = 1.0, .g = 0.78, .b = 0.18, .a = 0.96 });
builder.addMiniText(label[0..], 8.0 + pad + stripe_w + 3.0, 8.0 + pad, glyph_scale, .{ .r = 1.0, .g = 1.0, .b = 1.0, .a = 0.96 });
@@ -4334,8 +4393,8 @@ fn buildTouchOverlay(g: *Gui, sw: u32, sh: u32, debug_on: bool) u32 {
for (&t.points) |*tp| {
if (!tp.active) continue;
- const cx = std.math.clamp(tp.x, 0.0, 1.0) * win_w;
- const cy = std.math.clamp(tp.y, 0.0, 1.0) * win_h;
+ const cx = std.math.clamp(tp.x, 0.0, 1.0) * builder.win_w;
+ const cy = std.math.clamp(tp.y, 0.0, 1.0) * builder.win_h;
const radius = (12.0 + std.math.clamp(tp.pressure, 0.0, 1.0) * 4.0) * 4.0;
const color = touchColor(tp.id);
@@ -4346,8 +4405,8 @@ fn buildTouchOverlay(g: *Gui, sw: u32, sh: u32, debug_on: bool) u32 {
const fade = @as(f32, @floatFromInt(trail_i + 1)) / @as(f32, @floatFromInt(tp.trail_len));
var trail_color = color;
trail_color.a *= 0.42 * fade;
- const tx = std.math.clamp(sample.x, 0.0, 1.0) * win_w;
- const ty = std.math.clamp(sample.y, 0.0, 1.0) * win_h;
+ const tx = std.math.clamp(sample.x, 0.0, 1.0) * builder.win_w;
+ const ty = std.math.clamp(sample.y, 0.0, 1.0) * builder.win_h;
const trail_radius = (12.0 + std.math.clamp(sample.pressure, 0.0, 1.0) * 4.0) * 2.8;
builder.addCircle(tx, ty, trail_radius, trail_color);
}
@@ -4358,7 +4417,6 @@ fn buildTouchOverlay(g: *Gui, sw: u32, sh: u32, debug_on: bool) u32 {
builder.addCircle(cx, cy, 2.4, .{ .r = 1.0, .g = 1.0, .b = 1.0, .a = 0.92 });
builder.addIdTag(tp.id, cx, cy, color);
}
- return @intCast(builder.len);
}
fn uploadOverlayGpu(g: *Gui, cmd: *c.SDL_GPUCommandBuffer, vertex_count: u32) void {