diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-15 16:28:57 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-09-15 17:24:42 -0300 |
| commit | 1ab4c071be58033d5452f69797ca8e2c32240cff (patch) | |
| tree | afa108a1c372276da1d2c62b6c05d8f167cc81b8 /src/gui/gui.zig | |
| parent | c6ab9db0fdbcb8d16f666749194bb111218b96a8 (diff) | |
| download | pardes-1ab4c071be58033d5452f69797ca8e2c32240cff.tar.gz pardes-1ab4c071be58033d5452f69797ca8e2c32240cff.zip | |
Separate tag text geometry from physical pane grips
Diffstat (limited to 'src/gui/gui.zig')
| -rw-r--r-- | src/gui/gui.zig | 199 |
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); +} |
