summaryrefslogtreecommitdiff
path: root/src/gui/gui.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/gui.zig')
-rw-r--r--src/gui/gui.zig199
1 files changed, 168 insertions, 31 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig
index 0c0a5651..f4c87ed1 100644
--- a/src/gui/gui.zig
+++ b/src/gui/gui.zig
@@ -766,7 +766,7 @@ fn emitTouchScroll(in: *Input, center: TouchNormPoint, ticks: i32, delta_y: f32,
const button: pardes.Mouse.Button = if (ticks > 0) .wheel_up else .wheel_down;
var remaining: u32 = @abs(ticks);
while (remaining > 0) : (remaining -= 1)
- in.post(.{ .mouse = .{ .button = button, .kind = .press, .col = cell.col, .row = cell.row, .body_hit = cell.body_hit } });
+ in.post(.{ .mouse = .{ .button = button, .kind = .press, .col = cell.col, .row = cell.row, .tag_hit = cell.tag_hit, .body_hit = cell.body_hit } });
in.post(.{ .touch_scroll = delta_y });
}
@@ -775,8 +775,8 @@ fn emitTouchClick(t: *Touch, in: *Input, point: TouchNormPoint, button: pardes.M
t.click_button = button;
t.click_flash_frames = touch_click_flash_max_frames;
const cell = gridCellAtDimensions(in.core, point.x * win_w, point.y * win_h, cell_w, cell_h, tagline_w);
- in.post(.{ .mouse = .{ .button = button, .kind = .press, .col = cell.col, .row = cell.row, .body_hit = cell.body_hit } });
- in.post(.{ .mouse = .{ .button = button, .kind = .release, .col = cell.col, .row = cell.row, .body_hit = cell.body_hit } });
+ in.post(.{ .mouse = .{ .button = button, .kind = .press, .col = cell.col, .row = cell.row, .tag_hit = cell.tag_hit, .body_hit = cell.body_hit } });
+ in.post(.{ .mouse = .{ .button = button, .kind = .release, .col = cell.col, .row = cell.row, .tag_hit = cell.tag_hit, .body_hit = cell.body_hit } });
}
fn normCell(norm: f32, win: f32, cell: f32) u16 {
@@ -1766,6 +1766,7 @@ const Gui = struct {
tagline_width: u32,
tagline_height: u32,
attached_layers: ?*const [pardes.MAX_PANES]pardes.BodyLayer = null,
+ attached_tag_layers: ?*const [pardes.MAX_TAG_LAYERS]pardes.TagLayer = null,
cell_w: u32,
cell_h: u32,
ascent: i32,
@@ -1894,7 +1895,7 @@ fn fontForCodepoint(g: *const Gui, cp: u32) *c.UIFont {
}
const CellLayout = struct { w: f32, h: f32, x_off: f32, y_off: f32 };
-const MouseCell = struct { col: u16, row: u16, body_hit: ?pardes.Mouse.BodyHit = null };
+const MouseCell = struct { col: u16, row: u16, body_hit: ?pardes.Mouse.BodyHit = null, tag_hit: ?pardes.TagHit = null };
const WindowGeometry = struct {
window_w: f32,
window_h: f32,
@@ -1961,16 +1962,9 @@ fn compactTaglineLayout(g: *const Gui, origin_col: f32) CellLayout {
};
}
-fn taglineLayoutForCell(
- g: *const Gui,
- core: ?*const pardes.Pardes,
- col: u16,
- row: u16,
- track: ?pardes.layout.Track,
-) CellLayout {
- if (row < pardes.TOPBAR_H) return compactTaglineLayout(g, 0);
- const p = core orelse return compactTaglineLayout(g, @floatFromInt(col));
- return compactTaglineLayout(g, pardes.taglineOriginCol(p, col, row, track));
+fn taglineLayoutForCell(g: *const Gui, col: u16) CellLayout {
+ // Canonical tag cells are physical grips; text is drawn by TagLayer.
+ return compactTaglineLayout(g, @floatFromInt(col));
}
const boxContains = pardes.layout.Box.contains;
@@ -2546,6 +2540,7 @@ fn attachedLoop(gpa: std.mem.Allocator, g: *Gui, client: *detached_client.Client
var in: Input = .{ .client = client };
g.attached_layers = &client.body_layers;
+ g.attached_tag_layers = &client.tag_layers;
var dirty = false;
var geom = windowCells(g);
try attachedResize(client, g, geom.cols, geom.rows);
@@ -2597,6 +2592,7 @@ fn paintAttached(g: *Gui, gpa: std.mem.Allocator, client: *detached_client.Clien
.cursor = if (client.cursor) |cu| .{ .x = cu.x, .y = cu.y, .bar = cu.bar } else null,
.pointer_shape = client.pointer_shape,
.body_layers = client.body_layers,
+ .tag_layers = client.tag_layers,
};
const chrome = frameChromeBg(&surface);
_ = renderFrame(
@@ -3069,6 +3065,7 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void {
.kind = .release,
.col = last.col,
.row = last.row,
+ .tag_hit = last.tag_hit,
.body_hit = last.body_hit,
} });
in.post(.pointer_leave);
@@ -3082,6 +3079,7 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void {
.kind = if (b.down) .press else .release,
.col = mc.col,
.row = mc.row,
+ .tag_hit = mc.tag_hit,
.body_hit = mc.body_hit,
.ctrl = (c.SDL_GetModState() & c.SDL_KMOD_CTRL) != 0,
},
@@ -3112,6 +3110,7 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void {
.kind = if (held != null) .drag else .motion,
.col = mc.col,
.row = mc.row,
+ .tag_hit = mc.tag_hit,
.body_hit = mc.body_hit,
} });
},
@@ -3159,9 +3158,9 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void {
} else {
g.scroll_delta = accumulateWheelDelta(g.scroll_delta, w.y);
while (g.scroll_delta >= 1) : (g.scroll_delta -= 1)
- in.post(.{ .mouse = .{ .button = .wheel_down, .kind = .press, .col = mc.col, .row = mc.row, .body_hit = mc.body_hit } });
+ in.post(.{ .mouse = .{ .button = .wheel_down, .kind = .press, .col = mc.col, .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit } });
while (g.scroll_delta <= -1) : (g.scroll_delta += 1)
- in.post(.{ .mouse = .{ .button = .wheel_up, .kind = .press, .col = mc.col, .row = mc.row, .body_hit = mc.body_hit } });
+ in.post(.{ .mouse = .{ .button = .wheel_up, .kind = .press, .col = mc.col, .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit } });
}
}
if (w.x != 0) in.post(.{ .mouse = .{
@@ -3169,6 +3168,7 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void {
.kind = .press,
.col = mc.col,
.row = mc.row,
+ .tag_hit = mc.tag_hit,
.body_hit = mc.body_hit,
} });
},
@@ -3267,6 +3267,7 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void {
.kind = if (held != null) .drag else .motion,
.col = mc.col,
.row = mc.row,
+ .tag_hit = mc.tag_hit,
.body_hit = mc.body_hit,
} });
},
@@ -3284,6 +3285,7 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void {
.kind = .release,
.col = last.col,
.row = last.row,
+ .tag_hit = last.tag_hit,
.body_hit = last.body_hit,
} });
in.post(.pointer_leave);
@@ -3296,6 +3298,7 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void {
.kind = if (ck.down) .press else .release,
.col = mc.col,
.row = mc.row,
+ .tag_hit = mc.tag_hit,
.body_hit = mc.body_hit,
} });
},
@@ -3316,6 +3319,7 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void {
.kind = .press,
.col = mc.col,
.row = mc.row,
+ .tag_hit = mc.tag_hit,
.body_hit = mc.body_hit,
} });
}
@@ -3384,7 +3388,16 @@ fn gridCellAtDimensions(
tagline_w: f32,
) MouseCell {
const row: u16 = @intFromFloat(@min(@floor(@max(y, 0) / @max(body_h, 1)), 10_000));
- return .{ .col = pardes.gridColAt(core, x, row, body_w, tagline_w), .row = row };
+ var point: MouseCell = .{ .col = @intFromFloat(@min(@floor(@max(x, 0) / @max(body_w, 1)), 10_000)), .row = row };
+ if (core) |p| if (p.presentation.pointerFractional(p.screen_w, p.screen_h, x / @max(body_w, 1), y / @max(body_h, 1))) |mapped| {
+ for (p.surface.tagLayers()) |*layer| {
+ if (layer.hitAt(mapped.x * body_w, mapped.y * body_h, body_w, body_h, tagline_w, @floatFromInt(p.row_metrics.tagline_h))) |hit| {
+ point.tag_hit = hit;
+ break;
+ }
+ }
+ };
+ return point;
}
fn gridCellAtPixels(g: *const Gui, core: ?*const pardes.Pardes, x: f32, y: f32) MouseCell {
@@ -3401,31 +3414,51 @@ fn gridCellAtPixels(g: *const Gui, core: ?*const pardes.Pardes, x: f32, y: f32)
break;
}
};
+ if (core == null) if (g.attached_tag_layers) |layers| for (layers) |*layer| {
+ if (layer.hitAt(x, y, @floatFromInt(g.cell_w), @floatFromInt(g.cell_h), @floatFromInt(g.tagline_width), @floatFromInt(g.tagline_height))) |hit| {
+ point.tag_hit = hit;
+ break;
+ }
+ };
return point;
}
-test "tagline pointer mapping follows the smaller pane-anchored grid" {
+test "tagline pointer mapping retains physical grips and carries logical text coordinates" {
const core = try pardes.Pardes.init(std.testing.allocator, .{ .tty_only = true });
defer core.deinit();
core.update(.{ .resize = .{ .cols = 60, .rows = 12 } });
core.update(.{ .command = "Newcol" });
+ core.presentation.enabled = false;
+ core.row_metrics = .{ .body_w = 10, .body_h = 20, .tagline_w = 8, .tagline_h = 12 };
+ var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
+ defer arena.deinit();
+ _ = try core.render(arena.allocator());
const r = core.rects[core.active];
const body_w: f32 = 10;
const body_h: f32 = 20;
const tag_w: f32 = 8;
const tag_y = r.y;
- const x = @as(f32, @floatFromInt(r.x)) * body_w + 3.5 * tag_w;
+ const x = @as(f32, @floatFromInt(r.x + config.GUTTER)) * body_w + 3.5 * tag_w;
const tag = gridCellAtDimensions(core, x, (@as(f32, @floatFromInt(tag_y)) + 0.5) * body_h, body_w, body_h, tag_w);
- try std.testing.expectEqual(MouseCell{ .col = r.x + 3, .row = tag_y }, tag);
+ try std.testing.expectEqual(pixelCell(x, 10), tag.col);
+ try std.testing.expectEqual(tag_y, tag.row);
+ try std.testing.expectEqual(@as(u16, 3), tag.tag_hit.?.col);
+ try std.testing.expectEqual(pardes.TagKind.pane, tag.tag_hit.?.kind);
+ const grip = gridCellAtDimensions(core, @as(f32, @floatFromInt(r.x + config.GUTTER)) * body_w - 0.1, (@as(f32, @floatFromInt(tag_y)) + 0.5) * body_h, body_w, body_h, tag_w);
+ try std.testing.expect(grip.tag_hit == null);
+ try std.testing.expectEqual(r.x + config.GUTTER - 1, grip.col);
const body = gridCellAtDimensions(core, x, (@as(f32, @floatFromInt(tag_y + 1)) + 0.5) * body_h, body_w, body_h, tag_w);
try std.testing.expectEqual(pixelCell(x, 10), body.col);
try std.testing.expectEqual(tag_y + 1, body.row);
+ try std.testing.expect(body.tag_hit == null);
const topbar = gridCellAtDimensions(core, 3.5 * tag_w, 0.5 * body_h, body_w, body_h, tag_w);
- try std.testing.expectEqual(MouseCell{ .col = 3, .row = 0 }, topbar);
+ try std.testing.expectEqual(@as(u16, 2), topbar.col);
+ try std.testing.expectEqual(@as(u16, 3), topbar.tag_hit.?.col);
+ try std.testing.expectEqual(pardes.TagKind.workspace, topbar.tag_hit.?.kind);
}
fn mapScenePoint(g: *const Gui, x: f32, y: f32, w: f32, h: f32) ?crt.Point {
@@ -3468,6 +3501,7 @@ fn refreshPresentedPointer(g: *Gui, core: *pardes.Pardes) void {
.kind = if (held != null) .drag else .motion,
.col = mc.col,
.row = mc.row,
+ .tag_hit = mc.tag_hit,
.body_hit = mc.body_hit,
} });
}
@@ -3504,6 +3538,7 @@ fn pollGamepad(g: *Gui, in: *Input) void {
.kind = if (held != null) .drag else .motion,
.col = mc.col,
.row = mc.row,
+ .tag_hit = mc.tag_hit,
.body_hit = mc.body_hit,
} });
}
@@ -3515,12 +3550,12 @@ fn pollGamepad(g: *Gui, in: *Input) void {
while (@abs(g.pad_scroll) >= 1.0) {
const button: pardes.Mouse.Button = if (g.pad_scroll < 0) .wheel_up else .wheel_down;
g.pad_scroll += if (g.pad_scroll < 0) 1.0 else -1.0;
- in.post(.{ .mouse = .{ .button = button, .kind = .press, .col = mc.col, .row = mc.row, .body_hit = mc.body_hit } });
+ in.post(.{ .mouse = .{ .button = button, .kind = .press, .col = mc.col, .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit } });
}
while (@abs(g.pad_scroll_h) >= 1.0) {
const button: pardes.Mouse.Button = if (g.pad_scroll_h < 0) .wheel_left else .wheel_right;
g.pad_scroll_h += if (g.pad_scroll_h < 0) 1.0 else -1.0;
- in.post(.{ .mouse = .{ .button = button, .kind = .press, .col = mc.col, .row = mc.row, .body_hit = mc.body_hit } });
+ in.post(.{ .mouse = .{ .button = button, .kind = .press, .col = mc.col, .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit } });
}
}
@@ -4891,7 +4926,7 @@ fn renderFrame(
while (row < surface.rows) : (row += 1) {
var col: u16 = 0;
while (col < surface.cols) : (col += 1) {
- if (bodyLayerAt(surface, col, row) != null) continue;
+ if (bodyLayerAt(surface, col, row) != null or tagLayerIn(surface.tagLayers(), col, row) != null) continue;
const batch_index = paintBatchAt(&paint_plan, col, row);
const track = paint_plan.batches[batch_index].track;
const data_effect = if (track) |active|
@@ -4920,7 +4955,7 @@ fn renderFrame(
var col: u16 = 0;
while (col < surface.cols) : (col += 1) {
if (!boxContains(track.contentBox(), col, row)) continue;
- if (bodyLayerIn(surface.previous_body_layers, col, row) != null) continue;
+ if (bodyLayerIn(surface.previous_body_layers, col, row) != null or tagLayerIn(surface.previous_tag_layers, col, row) != null) continue;
const destination = if (duplicate_under) &paint_plan.batches[0] else batch;
const logical_idx: u32 = @as(u32, row) * surface.cols + col;
destination.cell_count = std.math.add(
@@ -4954,6 +4989,21 @@ fn renderFrame(
}
}
}
+ for (surface.tagLayers()) |*layer| {
+ if (layer.cols == 0) continue;
+ const batch = &paint_plan.batches[paintBatchAt(&paint_plan, layer.viewport.x, layer.viewport.y)];
+ batch.cell_count += tagLayerCellCount(g, layer) + 1;
+ }
+ for (paint_plan.batches[1..paint_plan.len], 1..) |batch, batch_index| {
+ const track = batch.track.?;
+ const under = track.effect == .vertical and track.phase == .opening;
+ if (!under and track.phase != .closing and track.effect != .dissolve) continue;
+ for (surface.previous_tag_layers) |*layer| {
+ if (layer.cols == 0 or layer.kind != .pane or layer.serial != track.serial) continue;
+ const destination = if (under) &paint_plan.batches[0] else &paint_plan.batches[batch_index];
+ destination.cell_count += tagLayerCellCount(g, layer) + 1;
+ }
+ }
var cell_next: [pardes.MAX_PANES * 2 + 1]u32 = @splat(0);
var cell_total: u32 = 0;
for (paint_plan.batches[0..paint_plan.len], 0..) |*batch, batch_index| {
@@ -4977,7 +5027,7 @@ fn renderFrame(
while (row < surface.rows) : (row += 1) {
var col: u16 = 0;
while (col < surface.cols) : (col += 1) {
- if (bodyLayerAt(surface, col, row) != null) continue;
+ if (bodyLayerAt(surface, col, row) != null or tagLayerIn(surface.tagLayers(), col, row) != null) continue;
const batch_index = paintBatchAt(&paint_plan, col, row);
const track = paint_plan.batches[batch_index].track;
const logical_idx: u32 = @as(u32, row) * surface.cols + col;
@@ -5024,6 +5074,21 @@ fn renderFrame(
);
}
}
+ for (surface.tagLayers()) |*layer| {
+ if (layer.cols == 0) continue;
+ const batch_index = paintBatchAt(&paint_plan, layer.viewport.x, layer.viewport.y);
+ emitTagLayer(g, instances, &cell_next[batch_index], layer, win_w, win_h, paint_plan.batches[batch_index].track, page, paint_plan.len == 1, false);
+ }
+ for (paint_plan.batches[1..paint_plan.len], 1..) |batch, batch_index| {
+ const track = batch.track.?;
+ const under = track.effect == .vertical and track.phase == .opening;
+ if (!under and track.phase != .closing and track.effect != .dissolve) continue;
+ for (surface.previous_tag_layers) |*layer| {
+ if (layer.cols == 0 or layer.kind != .pane or layer.serial != track.serial) continue;
+ const destination: usize = if (under) 0 else batch_index;
+ emitTagLayer(g, instances, &cell_next[destination], layer, win_w, win_h, if (under) null else track, page, false, track.effect == .dissolve);
+ }
+ }
for (surface.bodyLayers()) |*layer| {
if (layer.rows == 0) continue;
const batch_index = paintBatchAt(&paint_plan, layer.viewport.x, layer.viewport.y);
@@ -5048,7 +5113,7 @@ fn renderFrame(
var col: u16 = 0;
while (col < surface.cols) : (col += 1) {
if (!boxContains(track.contentBox(), col, row)) continue;
- if (bodyLayerIn(surface.previous_body_layers, col, row) != null) continue;
+ if (bodyLayerIn(surface.previous_body_layers, col, row) != null or tagLayerIn(surface.previous_tag_layers, col, row) != null) continue;
const logical_idx: u32 = @as(u32, row) * surface.cols + col;
const destination: usize = if (duplicate_under) 0 else batch_index;
emitSurfaceCell(
@@ -5337,6 +5402,42 @@ test "compact column and pane underlays share focus tint without tinting the glo
try std.testing.expectEqual(chrome.tag_bg, taglineBaseRgb(core, active.x, pardes.TOPBAR_H));
}
+fn tagLayerIn(layers: []const pardes.TagLayer, col: u16, row: u16) ?*const pardes.TagLayer {
+ for (layers) |*layer| {
+ const r = layer.viewport;
+ if (layer.cols > 0 and row == r.y and col >= r.x and col - r.x < r.w) return layer;
+ }
+ return null;
+}
+
+fn tagLayerCellCount(g: *const Gui, layer: *const pardes.TagLayer) u32 {
+ const pixels = @as(u32, layer.viewport.w) * g.cell_w;
+ return @min(layer.cols, (pixels + g.tagline_width - 1) / g.tagline_width);
+}
+
+fn emitTagLayer(g: *Gui, instances: [*]CellInstance, next: *u32, layer: *const pardes.TagLayer, win_w: f32, win_h: f32, track: ?pardes.layout.Track, page: Ground, show_cursor: bool, old_layer: bool) void {
+ const bw: f32 = @floatFromInt(g.cell_w);
+ const bh: f32 = @floatFromInt(g.cell_h);
+ const tw: f32 = @floatFromInt(g.tagline_width);
+ const left = @as(f32, @floatFromInt(layer.viewport.x)) * bw;
+ const right = left + @as(f32, @floatFromInt(layer.viewport.w)) * bw;
+ const background: pardes.Cell = .{ .default = false, .style = .{ .bg = .{ .rgb = layer.bg } } };
+ emitInstance(g, instances, next.*, 0, layer.viewport.y, .{ .w = right - left, .h = bh, .x_off = left, .y_off = 0 }, win_w, win_h, track, .body, &background, old_layer, false, page);
+ next.* += 1;
+ for (0..tagLayerCellCount(g, layer)) |col| {
+ const cursor = if (layer.cursor) |cu| show_cursor and !cu.bar and cu.col == col else false;
+ emitInstance(g, instances, next.*, @intCast(col), layer.viewport.y, .{ .w = tw, .h = bh, .x_off = left, .y_off = 0 }, win_w, win_h, track, .tagline, &layer.cells[col], old_layer, cursor, page);
+ const instance = &instances[next.*];
+ const edge = right / win_w * 2 - 1;
+ if (instance.x1 > edge) {
+ const fraction = (edge - instance.x0) / (instance.x1 - instance.x0);
+ instance.u1 = instance.u0 + (instance.u1 - instance.u0) * fraction;
+ instance.x1 = edge;
+ }
+ next.* += 1;
+ }
+}
+
fn bodyLayerAt(surface: *const pardes.Surface, col: u16, row: u16) ?*const pardes.BodyLayer {
return bodyLayerIn(surface.bodyLayers(), col, row);
}
@@ -5427,7 +5528,7 @@ fn emitSurfaceCell(
// adjacent workspace, column and pane tags at TaglineSize < 100.
emitInstance(g, instances, next.*, col, row, body_layout, win_w, win_h, track, .body, &band_base, old_layer, false, page);
next.* += 1;
- const tag_layout = taglineLayoutForCell(g, core, col, row, track);
+ const tag_layout = taglineLayoutForCell(g, col);
emitInstance(g, instances, next.*, col, row, tag_layout, win_w, win_h, track, .tagline, cell, old_layer, is_cursor, page);
next.* += 1;
return;
@@ -6236,7 +6337,7 @@ fn buildOverlay(
if (transient_on) {
if (core) |p| appendPaneChrome(g, p, surface, layout, &builder);
if (surface.cursor) |cursor| {
- if (cursor.x < surface.cols and cursor.y < surface.rows and bodyLayerAt(surface, cursor.x, cursor.y) == null) {
+ if (cursor.x < surface.cols and cursor.y < surface.rows and bodyLayerAt(surface, cursor.x, cursor.y) == null and tagLayerIn(surface.tagLayers(), cursor.x, cursor.y) == null) {
const cell = surface.cells[@as(usize, cursor.y) * surface.cols + cursor.x];
const role = drawnFontRole(core, &cell, cursor.y);
const visual_height: f32 = if (role == .tagline)
@@ -6244,7 +6345,7 @@ fn buildOverlay(
else
layout.h;
const cursor_layout = if (role == .tagline)
- taglineLayoutForCell(g, core, cursor.x, cursor.y, null)
+ taglineLayoutForCell(g, cursor.x)
else
layout;
addCursorBar(&builder, cursor.x, cursor.y, cursor.bar, visual_height, cursor_layout, cursorColor(core, &cell, role));
@@ -6267,6 +6368,18 @@ fn buildOverlay(
builder.addRect(x, top, x + @max(1, width / 8), @min(bottom, top + height), overlayRgb(cursorColor(core, cell, role)));
}
};
+ if (transient_on) for (surface.tagLayers()) |*layer| {
+ if (layer.cols == 0) continue;
+ const cursor = layer.cursor orelse continue;
+ if (!cursor.bar or cursor.col >= layer.cols) continue;
+ const width: f32 = @floatFromInt(g.tagline_width);
+ const left = @as(f32, @floatFromInt(layer.viewport.x)) * layout.w;
+ const right = left + @as(f32, @floatFromInt(layer.viewport.w)) * layout.w;
+ const x = left + @as(f32, @floatFromInt(cursor.col)) * width;
+ if (x >= right) continue;
+ const top = @as(f32, @floatFromInt(layer.viewport.y)) * layout.h + @as(f32, @floatFromInt(taglineBandOffset(layer.viewport.y, builder.win_h, g.cell_h, g.tagline_height)));
+ builder.addRect(x, top, @min(right, x + @max(1, width / 8)), top + @as(f32, @floatFromInt(g.tagline_height)), overlayRgb(cursorColor(core, &layer.cells[cursor.col], .tagline)));
+ };
if (transient_on) appendTouchOverlay(g, &builder, debug_on);
return @intCast(builder.len);
}
@@ -6419,7 +6532,7 @@ fn appendPaneChrome(
// A quiet grip gives the normal-mode move box a purpose at rest.
// Insert and terminal badges retain their existing visible glyphs.
if (tag_cell.printableAscii() == ' ' and g.tagline_height >= 6 and g.tagline_width >= 3) {
- const tag_layout = taglineLayoutForCell(g, core, r.x, tag_y, null);
+ const tag_layout = fixedCellLayout(g);
const center_x = tag_layout.x_off + (@as(f32, @floatFromInt(r.x)) + @as(f32, @floatFromInt(config.GUTTER)) / 2) * tag_layout.w;
const center_y = @as(f32, @floatFromInt(tag_y)) * layout.h +
@as(f32, @floatFromInt(taglineBandOffset(tag_y, builder.win_h, g.cell_h, g.tagline_height))) +
@@ -6843,3 +6956,27 @@ test "compact body layer rendering fills recovered rows and clips the final glyp
try std.testing.expectApproxEqAbs(@as(f32, -0.4), instances[19].y1, 0.0001);
try std.testing.expectApproxEqAbs(instances[19].v0 + @as(f32, 12) / atlas_h, instances[19].v1, 0.0001);
}
+
+test "compact tag layers fill available width and clip at their physical edge" {
+ var g: Gui = undefined;
+ g.cell_w = 10;
+ g.cell_h = 20;
+ g.tagline_width = 6;
+ g.tagline_height = 12;
+ g.space_slot = .{ .u = 0, .v = 0 };
+ var cells: [6]pardes.Cell = @splat(.{});
+ const layer: pardes.TagLayer = .{ .kind = .pane, .id = 0, .serial = 7, .viewport = .{ .x = 2, .y = 3, .w = 3, .h = 1 }, .cols = 6, .cells = &cells, .bg = .{ 20, 30, 40 } };
+ var instances: [8]CellInstance = undefined;
+ var next: u32 = 0;
+ emitTagLayer(&g, &instances, &next, &layer, 100, 100, null, Ground.opaqueRgb(bg_default), false, false);
+ // Five six-pixel cells fit in the three body-column viewport.
+ try std.testing.expectEqual(@as(u32, 6), next);
+ try std.testing.expectApproxEqAbs(@as(f32, -0.6), instances[1].x0, 0.0001);
+ try std.testing.expectApproxEqAbs(@as(f32, 0), instances[5].x1, 0.0001);
+ g.tagline_width = 8;
+ next = 0;
+ emitTagLayer(&g, &instances, &next, &layer, 100, 100, null, Ground.opaqueRgb(bg_default), false, false);
+ try std.testing.expectEqual(@as(u32, 5), next);
+ try std.testing.expectApproxEqAbs(@as(f32, 0), instances[4].x1, 0.0001);
+ try std.testing.expectApproxEqAbs(@as(f32, 6) / atlas_w, instances[4].u1 - instances[4].u0, 0.0001);
+}