diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/gui/gui.zig | 10 | ||||
| -rw-r--r-- | src/macos.zig | 5 | ||||
| -rw-r--r-- | src/macos/Sources/PardesView.swift | 2 | ||||
| -rw-r--r-- | src/macos/pardes.h | 1 | ||||
| -rw-r--r-- | src/pardes.zig | 91 | ||||
| -rw-r--r-- | src/web.zig | 4 | ||||
| -rw-r--r-- | src/web/app.mjs | 11 |
7 files changed, 74 insertions, 50 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig index 590e5aa8..87e19687 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -6528,7 +6528,7 @@ fn appendPaneChrome( const body_end = body_y + r.h - pardes.BOX_H; const x0 = @as(f32, @floatFromInt(r.x)) * layout.w; const x1 = x0 + @as(f32, @floatFromInt(r.w)) * layout.w; - const tag_cells = surface.cells[@as(usize, tag_y) * surface.cols + r.x + config.GUTTER ..][0 .. r.w - config.GUTTER]; + const tag_cells = surface.cells[@as(usize, tag_y) * surface.cols + r.x + pardes.TAG_TEXT_INSET ..][0 .. r.w - pardes.TAG_TEXT_INSET]; const tag_visible = for (tag_cells) |*cell| { if (cellFontRole(cell) != .tagline) break false; } else true; @@ -6568,8 +6568,8 @@ fn appendPaneChrome( fn appendTagGrips(g: *const Gui, surface: *const pardes.Surface, layout: CellLayout, builder: *OverlayBuilder) void { if (g.tagline_height < 6 or g.tagline_width < 3) return; for (surface.tagLayers()) |layer| { - if (layer.cols == 0 or layer.kind != .pane or layer.viewport.x < config.GUTTER) continue; - const x = layer.viewport.x - config.GUTTER; + if (layer.cols == 0 or layer.kind != .pane or layer.viewport.x < pardes.TAG_TEXT_INSET) continue; + const x = layer.viewport.x - pardes.TAG_TEXT_INSET; const y = layer.viewport.y; if (x >= surface.cols or y >= surface.rows) continue; const cell = &surface.cells[@as(usize, y) * surface.cols + x]; @@ -6593,8 +6593,8 @@ test "column grips stay unmarked while pane marks retain physical centering" { g.cell_h = 20; var cells: [80]pardes.Cell = @splat(.{ .default = false, .style = .{ .fg = .{ .rgb = .{ 100, 120, 140 } } } }); var surface: pardes.Surface = .{ .cols = 20, .rows = 4, .cells = &cells }; - surface.tag_layers[0] = .{ .kind = .column, .viewport = .{ .x = 7, .y = 1, .w = 13, .h = 1 }, .cols = 20 }; - surface.tag_layers[1] = .{ .kind = .pane, .viewport = .{ .x = 7, .y = 3, .w = 13, .h = 1 }, .cols = 20 }; + surface.tag_layers[0] = .{ .kind = .column, .viewport = .{ .x = 8, .y = 1, .w = 13, .h = 1 }, .cols = 20 }; + surface.tag_layers[1] = .{ .kind = .pane, .viewport = .{ .x = 8, .y = 3, .w = 13, .h = 1 }, .cols = 20 }; var vertices: [24]OverlayVertex = undefined; var builder: OverlayBuilder = .{ .vertices = &vertices, .win_w = 200, .win_h = 80 }; appendTagGrips(&g, &surface, .{ .w = 10, .h = 20, .x_off = 0, .y_off = 0 }, &builder); diff --git a/src/macos.zig b/src/macos.zig index febb7f88..3003fd46 100644 --- a/src/macos.zig +++ b/src/macos.zig @@ -1876,6 +1876,10 @@ export fn pardes_cursor_x() i32 { return if (st.core.surface.cursor) |c| c.x else -1; } +export fn pardes_tag_text_inset() u32 { + return pardes.TAG_TEXT_INSET; +} + export fn pardes_grip_columns() u32 { return @import("config.zig").GUTTER; } @@ -2418,6 +2422,7 @@ test "pardes.h declares every export the way it is defined" { try expectSameAbi(@TypeOf(c.pardes_pointer_shape), @TypeOf(pardes_pointer_shape)); try expectSameAbi(@TypeOf(c.pardes_mouse_pixel), @TypeOf(pardes_mouse_pixel)); try expectSameAbi(@TypeOf(c.pardes_row_metrics), @TypeOf(pardes_row_metrics)); + try expectSameAbi(@TypeOf(c.pardes_tag_text_inset), @TypeOf(pardes_tag_text_inset)); try expectSameAbi(@TypeOf(c.pardes_grip_columns), @TypeOf(pardes_grip_columns)); try expectSameAbi(@TypeOf(c.pardes_tag_layer_limit), @TypeOf(pardes_tag_layer_limit)); try expectSameAbi(@TypeOf(c.pardes_tag_layer_value), @TypeOf(pardes_tag_layer_value)); diff --git a/src/macos/Sources/PardesView.swift b/src/macos/Sources/PardesView.swift index ba05ea51..e2234264 100644 --- a/src/macos/Sources/PardesView.swift +++ b/src/macos/Sources/PardesView.swift @@ -1172,7 +1172,7 @@ final class PardesView: NSView { private func drawTagGrip(_ ctx: CGContext, values: [Int], canonical: UnsafePointer<pardes_cell_s>, cols: Int, rows: Int) { let width = Int(pardes_grip_columns()) - let x = values[4] - width, y = values[5] + let x = values[4] - Int(pardes_tag_text_inset()), y = values[5] if values[1] != 2 || x < 0 || x >= cols || y >= rows || taglines.height < 6 || taglines.width < 3 { return } var cell = canonical[y * cols + x] let text = withUnsafeBytes(of: &cell.text) { String(decoding: $0.prefix(Int(cell.len)), as: UTF8.self) } diff --git a/src/macos/pardes.h b/src/macos/pardes.h index 15a76d04..48f38e58 100644 --- a/src/macos/pardes.h +++ b/src/macos/pardes.h @@ -341,6 +341,7 @@ void pardes_mouse_pixel(pardes_mouse_button_e button, pardes_mouse_kind_e kind, uint16_t col, uint16_t row, uint32_t mods, float x, float y, float body_w, float body_h, float tagline_w, float tagline_h); void pardes_row_metrics(uint16_t body_w, uint16_t body_h, uint16_t tagline_w, uint16_t tagline_h); +uint32_t pardes_tag_text_inset(void); uint32_t pardes_grip_columns(void); uint32_t pardes_tag_layer_limit(void); uint32_t pardes_tag_layer_value(uint32_t index, uint32_t field); diff --git a/src/pardes.zig b/src/pardes.zig index bd7c43e9..d115bf2d 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -84,6 +84,8 @@ pub const TOPBAR_H: u16 = 1; pub const COLBAR_H: u16 = 1; pub const TagLine = @import("tag_line.zig").Line; pub const BOX_H: u16 = 1; +pub const TAG_GAP: u16 = 1; +pub const TAG_TEXT_INSET: u16 = config.GUTTER + TAG_GAP; pub fn taglineBandOffset(row: u16, canvas_h: f32, cell_h: u32, tagline_h: u32) u32 { const spare = cell_h -| tagline_h; @@ -1005,7 +1007,7 @@ test "pane-tag Exec prefers Tty and argument builtins before shell fallback" { const pane = p.panes[0].?; try std.testing.expectEqualStrings(terminal_pane_tail, Pardes.curTail(pane)); - const tag_x = p.rects[0].x + config.GUTTER; + const tag_x = p.rects[0].x + TAG_TEXT_INSET; const tag_y = p.rects[0].y; const rendered = try p.tagText(p.scratch.allocator(), pane); @@ -1222,7 +1224,7 @@ test "Togglettymode builtin and keyboard chord share mode transitions including p.acknowledgePanelPresentation(&.{}); const tag = try p.tagText(p.scratch.allocator(), pane); - const x = p.rects[0].x + config.GUTTER + @as(u16, @intCast(std.mem.indexOf(u8, tag, "Togglettymode").?)); + const x = p.rects[0].x + TAG_TEXT_INSET + @as(u16, @intCast(std.mem.indexOf(u8, tag, "Togglettymode").?)); const y = p.rects[0].y; p.update(.{ .mouse = .{ .button = .middle, .kind = .press, .col = x, .row = y } }); p.update(.{ .mouse = .{ .button = .middle, .kind = .release, .col = x, .row = y } }); @@ -4381,8 +4383,8 @@ test "editable workspace and column tags replace selections and persist" { p.update(.{ .key = .{ .cp = 'H', .text = "Help" } }); try std.testing.expect(std.mem.startsWith(u8, p.global_tag.slice(""), "Help Joincol")); p.update(.{ .key = .{ .cp = Key.escape } }); - p.update(.{ .mouse = .{ .button = .left, .kind = .press, .col = config.GUTTER, .row = TOPBAR_H } }); - p.update(.{ .mouse = .{ .button = .left, .kind = .release, .col = config.GUTTER, .row = TOPBAR_H } }); + p.update(.{ .mouse = .{ .button = .left, .kind = .press, .col = TAG_TEXT_INSET, .row = TOPBAR_H } }); + p.update(.{ .mouse = .{ .button = .left, .kind = .release, .col = TAG_TEXT_INSET, .row = TOPBAR_H } }); p.update(.{ .key = .{ .cp = 'é', .text = "é" } }); try std.testing.expect(std.mem.startsWith(u8, p.columnTag(0), "éNew")); p.update(.{ .key = .{ .cp = Key.backspace } }); @@ -4427,8 +4429,8 @@ test "column tags render edit hover and scroll from the column edge" { var frame = std.heap.ArenaAllocator.init(std.testing.allocator); defer frame.deinit(); for (0..p.ncol) |column| { - const x = p.col_x[column] + config.GUTTER; - const width = p.col_w[column] - config.GUTTER; + const x = p.col_x[column] + TAG_TEXT_INSET; + const width = p.col_w[column] - TAG_TEXT_INSET; const initial = try p.render(frame.allocator()); for ("New", 0..) |byte, offset| try std.testing.expectEqual(byte, initial.at(x + @as(u16, @intCast(offset)), TOPBAR_H).text[0]); @@ -4481,8 +4483,8 @@ test "column tag first-cell execution keeps New local to the clicked column" { p.active = p.col_panes[1 - column][0]; p.acknowledgePanelPresentation(&.{}); const before = p.col_n; - p.update(.{ .mouse = .{ .button = .middle, .kind = .press, .col = p.col_x[column] + config.GUTTER, .row = TOPBAR_H } }); - p.update(.{ .mouse = .{ .button = .middle, .kind = .release, .col = p.col_x[column] + config.GUTTER, .row = TOPBAR_H } }); + p.update(.{ .mouse = .{ .button = .middle, .kind = .press, .col = p.col_x[column] + TAG_TEXT_INSET, .row = TOPBAR_H } }); + p.update(.{ .mouse = .{ .button = .middle, .kind = .release, .col = p.col_x[column] + TAG_TEXT_INSET, .row = TOPBAR_H } }); try std.testing.expectEqual(before[column] + 1, p.col_n[column]); try std.testing.expectEqual(before[1 - column], p.col_n[1 - column]); try std.testing.expectEqual(column, layout.findPane(p, p.active).?.col); @@ -4527,8 +4529,8 @@ test "ColumnTags reclaims the row and releases hidden header focus without losin p.acknowledgePanelPresentation(&.{}); const shown = p.rects[0]; try std.testing.expectEqual(TOPBAR_H + COLBAR_H, shown.y); - p.update(.{ .mouse = .{ .button = .left, .kind = .press, .col = config.GUTTER, .row = TOPBAR_H } }); - p.update(.{ .mouse = .{ .button = .left, .kind = .release, .col = config.GUTTER, .row = TOPBAR_H } }); + p.update(.{ .mouse = .{ .button = .left, .kind = .press, .col = TAG_TEXT_INSET, .row = TOPBAR_H } }); + p.update(.{ .mouse = .{ .button = .left, .kind = .release, .col = TAG_TEXT_INSET, .row = TOPBAR_H } }); try std.testing.expect(p.header_column != null and p.topbar_col != null); p.update(.{ .command = "ColumnTags" }); @@ -4544,8 +4546,8 @@ test "ColumnTags reclaims the row and releases hidden header focus without losin try std.testing.expect(p.settings.column_tags); try std.testing.expectEqual(shown, p.rects[0]); p.acknowledgePanelPresentation(&.{}); - p.update(.{ .mouse = .{ .button = .left, .kind = .press, .col = config.GUTTER, .row = TOPBAR_H } }); - p.update(.{ .mouse = .{ .button = .left, .kind = .release, .col = config.GUTTER, .row = TOPBAR_H } }); + p.update(.{ .mouse = .{ .button = .left, .kind = .press, .col = TAG_TEXT_INSET, .row = TOPBAR_H } }); + p.update(.{ .mouse = .{ .button = .left, .kind = .release, .col = TAG_TEXT_INSET, .row = TOPBAR_H } }); try std.testing.expect(p.header_column != null); p.update(.{ .resize = .{ .cols = 60, .rows = 2 } }); try std.testing.expect(p.settings.column_tags); @@ -6274,7 +6276,7 @@ pub const Pardes = struct { return; }; const text = p.headerText(); - const width = p.tagCapacity(if (p.header_column) |c| p.col_w[c] -| config.GUTTER else p.screen_w); + const width = p.tagCapacity(if (p.header_column) |c| p.col_w[c] -| TAG_TEXT_INSET else p.screen_w); const col = panes.File.rawDisplayCol(text, @min(cur, text.len)); if (col < p.header_scroll) p.header_scroll = col; if (col >= p.header_scroll + width) p.header_scroll = col -| (width -| 1); @@ -7377,7 +7379,7 @@ pub const Pardes = struct { pane.tag_col = @intCast(@min(edit0 + lead, end)); } if (pane.tag_saved_col == null) if (p.paneIdOf(pane)) |id| { - const width = p.tagCapacity(p.rects[id].w -| config.GUTTER); + const width = p.tagCapacity(p.rects[id].w -| TAG_TEXT_INSET); // Reveal the suffix together with its commands, not merely the // first command letter at the viewport's far right edge. pane.tag_scroll = @intCast(panes.File.displayWidth(text) + 1 -| width); @@ -11086,6 +11088,7 @@ pub const Pardes = struct { const tag_y = if (p.settings.tag_bottom) r.y + r.h -| BOX_H else r.y; const body_y = if (p.settings.tag_bottom) r.y else r.y + BOX_H; const on_tag = row >= tag_y and row < tag_y + BOX_H; + if (on_tag and col < r.x + TAG_TEXT_INSET) return null; if (!on_tag) if (body_hit) |supplied| { const mapped_hit = p.bodyHitForPane(id, supplied, false) orelse return null; const c = @as(i32, mapped_hit.col) - config.GUTTER; @@ -11093,7 +11096,7 @@ pub const Pardes = struct { return .{ .sel = .{ .state = .dragging, .c0 = c, .c1 = c, .r0 = v, .r1 = v }, .on_tag = false }; }; const tag_col = if (on_tag) p.tagColumn(.pane, id, tag_hit, false) else null; - const c: i32 = (if (tag_col) |value| @as(i32, value) else @as(i32, col) - @as(i32, r.x + config.GUTTER)) + + const c: i32 = (if (tag_col) |value| @as(i32, value) else @as(i32, col) - @as(i32, r.x + (if (on_tag) TAG_TEXT_INSET else config.GUTTER))) + (if (on_tag) @as(i32, p.panes[id].?.tag_scroll) else 0); const v: i32 = if (on_tag) 0 else @as(i32, row) - @as(i32, body_y) + @as(i32, BOX_H); return .{ @@ -11324,7 +11327,7 @@ pub const Pardes = struct { } } if (p.header_drag and m.button == config.select_button and (m.kind == .drag or m.kind == .release)) { - const x = if (p.header_column) |c| p.col_x[c] + config.GUTTER else 0; + const x = if (p.header_column) |c| p.col_x[c] + TAG_TEXT_INSET else 0; const bar = p.headerText(); p.topbar_col = @intCast(@min(bar.len, panes.File.rawAtDisplay(bar, (p.tagColumn(if (p.header_column != null) .column else .workspace, p.header_column orelse 0, p.pointer_tag_hit, p.header_drag) orelse (mcol -| x)) + p.header_scroll))); if (m.kind == .release) p.header_drag = false; @@ -11341,13 +11344,13 @@ pub const Pardes = struct { if (mrow >= TOPBAR_H) { for (0..p.ncol) |c| if (mcol >= p.col_x[c] and mcol < p.col_x[c] + p.col_w[c]) { column = c; - x = p.col_x[c] + config.GUTTER; + x = p.col_x[c] + TAG_TEXT_INSET; if (layout.findPane(p, p.active)) |active_col| { if (active_col.col != c and p.col_n[c] > 0) p.active = p.col_panes[c][0]; } break; }; - if (column == null) return; + if (column == null or mcol < x) return; } if (p.header_column != column) { p.rememberHeader(); @@ -11478,8 +11481,9 @@ pub const Pardes = struct { p.active = id; pane.scrollBy(-(@as(i32, mrow) - @as(i32, body_y))); } else if (mrow >= tag_y and mrow < tag_y + BOX_H) { + if (mcol < r.x + TAG_TEXT_INSET) return; p.active = id; - p.enterTagEdit(pane, if (p.tagColumn(.pane, id, p.pointer_tag_hit, false)) |value| @as(i32, value) else @as(i32, mcol) - @as(i32, r.x + config.GUTTER)); + p.enterTagEdit(pane, if (p.tagColumn(.pane, id, p.pointer_tag_hit, false)) |value| @as(i32, value) else @as(i32, mcol) - @as(i32, r.x + TAG_TEXT_INSET)); pane.tag_anchor = pane.tag_col; p.drag = .{ .tag = .{ .id = id } }; } else { @@ -11634,14 +11638,14 @@ pub const Pardes = struct { else @as(i32, @min(@max(mrow, r.y), r.y + body_h - 1)) - @as(i32, r.y) + @as(i32, BOX_H); } else pane.sel[b].r1 = if (pane.sel[b].r0 < BOX_H) 0 else @as(i32, mrow) - @as(i32, r.y); - if (pane.sel[b].r1 < BOX_H) pane.sel[b].c1 += pane.tag_scroll; + if (pane.sel[b].r1 < BOX_H) pane.sel[b].c1 += @as(i32, pane.tag_scroll) - TAG_GAP; if (comptime pdf_enabled) panes.Pdf.pointerUpdate(&s.pdf, p, pane, mcol, mrow); } }, .tag => |d| { const pane = p.panes[d.id] orelse return; - const c = if (p.tagColumn(.pane, d.id, p.pointer_tag_hit, true)) |value| @as(i32, value) else @as(i32, mcol) - @as(i32, p.rects[d.id].x + config.GUTTER); + const c = if (p.tagColumn(.pane, d.id, p.pointer_tag_hit, true)) |value| @as(i32, value) else @as(i32, mcol) - @as(i32, p.rects[d.id].x + TAG_TEXT_INSET); const text = p.tagText(p.scratch.allocator(), pane) catch return; pane.tag_col = @intCast(@min(text.len, panes.File.rawAtDisplay(text, @as(usize, @intCast(@max(0, c))) + pane.tag_scroll))); pane.tag_sel = pane.tag_col != pane.tag_anchor; @@ -13256,8 +13260,9 @@ pub const Pardes = struct { }; if (p.columnBarHeight() > 0) for (0..p.ncol) |c| { const grip = @min(config.GUTTER, p.col_w[c]); - const x = p.col_x[c] + grip; - const w = p.col_w[c] - grip; + const inset = @min(TAG_TEXT_INSET, p.col_w[c]); + const x = p.col_x[c] + inset; + const w = p.col_w[c] - inset; if (p.col_w[c] == 0 or p.col_x[c] >= s.cols) continue; const active_col = if (layout.findPane(p, p.active)) |f| f.col == c else false; const bg = if (active_col and p.settings.focus_tint) chrome.tag_active_bg else chrome.tag_bg; @@ -13267,6 +13272,7 @@ pub const Pardes = struct { const ink: [3]u8 = if (lum > 140) .{ 0, 0, 0 } else .{ 255, 255, 255 }; const grip_style: CellStyle = .{ .bg = .{ .rgb = grip_bg }, .fg = .{ .rgb = ink }, .font_role = .tagline }; s.fill(p.col_x[c], TOPBAR_H, grip, 1, grip_style); + s.fill(p.col_x[c] + grip, TOPBAR_H, inset - grip, 1, .{ .bg = .{ .rgb = bg }, .font_role = .tagline }); s.fill(x, TOPBAR_H, w, 1, .{ .bg = .{ .rgb = bg }, .font_role = .tagline }); const bar_scroll = if (p.topbar_col != null and p.header_column == c) p.header_scroll else 0; @@ -13291,8 +13297,8 @@ pub const Pardes = struct { }; if (p.topbar_col) |cur| { const bar = p.headerText(); - const x = if (p.header_column) |c| p.col_x[c] + config.GUTTER else 0; - const w = if (p.header_column) |c| p.col_w[c] -| config.GUTTER else s.cols; + const x = if (p.header_column) |c| p.col_x[c] + TAG_TEXT_INSET else 0; + const w = if (p.header_column) |c| p.col_w[c] -| TAG_TEXT_INSET else s.cols; const y: u16 = if (p.header_column != null) TOPBAR_H else 0; const anchor = p.header_anchor orelse cur; var col = panes.File.rawDisplayCol(bar, @min(cur, anchor)) -| p.header_scroll; @@ -13631,8 +13637,10 @@ pub const Pardes = struct { // row zero in Sel space and starts at its real first text cell. const scroll: i32 = if (row < BOX_H) pane.tag_scroll else 0; var col: i32 = if (pane.file != null and row >= BOX_H) @max(c0, @as(i32, panes.File.gutterWidth(pane))) else @max(0, c0 - scroll); - while (col <= c1 - scroll and col < tw) : (col += 1) { - const cell = s.at(tx + @as(u16, @intCast(col)), sy); + const row_x = if (row < BOX_H) r.x + @min(TAG_TEXT_INSET, r.w) else tx; + const row_width = if (row < BOX_H) r.w -| TAG_TEXT_INSET else tw; + while (col <= c1 - scroll and col < row_width) : (col += 1) { + const cell = s.at(row_x + @as(u16, @intCast(col)), sy); cell.default = false; cell.style.bg = .{ .rgb = bg }; if (fg) |ink| cell.style.fg = .{ .rgb = ink }; @@ -13645,41 +13653,45 @@ pub const Pardes = struct { for (p.panes, 0..) |slot, id| { const pane = slot orelse continue; const r = p.rects[id]; - if (r.w <= config.GUTTER or r.h == 0) continue; + if (r.w <= TAG_TEXT_INSET or r.h == 0) continue; const tag = try p.tagText(arena, pane); - const columns: u16 = @intCast(@min(std.math.maxInt(u16) - config.GUTTER, @max(p.tagCapacity(r.w - config.GUTTER), panes.File.displayWidth(tag) + 1))); + const columns: u16 = @intCast(@min(std.math.maxInt(u16) - TAG_TEXT_INSET, @max(p.tagCapacity(r.w - TAG_TEXT_INSET), panes.File.displayWidth(tag) + 1))); const layer = &p.surface.tag_layers[id]; if (layer.cells.len != columns) layer.cells = try p.gpa.realloc(layer.cells, columns); - const storage = try arena.alloc(Cell, @as(usize, columns) + config.GUTTER); + const storage = try arena.alloc(Cell, @as(usize, columns) + TAG_TEXT_INSET); @memset(storage, .{}); - var temporary: Surface = .{ .cols = columns + config.GUTTER, .rows = 1, .cells = storage }; + var temporary: Surface = .{ .cols = columns + TAG_TEXT_INSET, .rows = 1, .cells = storage }; std.mem.swap(Surface, &p.surface, &temporary); - p.paintPaneTag(arena, pane, .{ .x = 0, .y = 0, .w = columns + config.GUTTER, .h = 1 }, id, id == p.active, p.tagCapacity(r.w - config.GUTTER)) catch |err| { + p.paintPaneTag(arena, pane, .{ .x = 0, .y = 0, .w = columns + TAG_TEXT_INSET, .h = 1 }, id, id == p.active, p.tagCapacity(r.w - TAG_TEXT_INSET)) catch |err| { std.mem.swap(Surface, &p.surface, &temporary); return err; }; std.mem.swap(Surface, &p.surface, &temporary); - @memcpy(layer.cells, storage[config.GUTTER..]); + @memcpy(layer.cells, storage[TAG_TEXT_INSET..]); layer.kind = .pane; layer.id = @intCast(id); layer.serial = pane.serial; - layer.viewport = .{ .x = r.x + config.GUTTER, .y = if (p.settings.tag_bottom) r.y + r.h - BOX_H else r.y, .w = r.w - config.GUTTER, .h = BOX_H }; + layer.viewport = .{ .x = r.x + TAG_TEXT_INSET, .y = if (p.settings.tag_bottom) r.y + r.h - BOX_H else r.y, .w = r.w - TAG_TEXT_INSET, .h = BOX_H }; layer.cols = columns; layer.bg = if (id == p.active and p.settings.focus_tint) chrome.tag_active_bg else chrome.tag_bg; - layer.cursor = if (p.topbar_col == null and pane.promptAt() == null and temporary.cursor != null) .{ .col = temporary.cursor.?.x - config.GUTTER, .bar = temporary.cursor.?.bar } else null; + layer.cursor = if (p.topbar_col == null and pane.promptAt() == null and temporary.cursor != null) .{ .col = temporary.cursor.?.x - TAG_TEXT_INSET, .bar = temporary.cursor.?.bar } else null; } var text_buf: [1200]u8 = undefined; try p.renderHeaderLayer(arena, MAX_PANES + MAX_COLS, .workspace, 0, .{ .x = 0, .y = 0, .w = p.screen_w, .h = TOPBAR_H }, p.topbar(&text_buf), chrome.tag_bg, chrome.tag_fg); if (p.columnBarHeight() > 0) for (0..p.ncol) |column| { if (p.col_w[column] == 0) continue; const active = if (layout.findPane(p, p.active)) |found| found.col == column and p.settings.focus_tint else false; - try p.renderHeaderLayer(arena, MAX_PANES + column, .column, @intCast(column), .{ .x = p.col_x[column] + @min(config.GUTTER, p.col_w[column]), .y = TOPBAR_H, .w = p.col_w[column] -| config.GUTTER, .h = COLBAR_H }, p.columnTag(column), if (active) chrome.tag_active_bg else chrome.tag_bg, if (active) chrome.tag_active_fg else chrome.tag_fg); + try p.renderHeaderLayer(arena, MAX_PANES + column, .column, @intCast(column), .{ .x = p.col_x[column] + @min(TAG_TEXT_INSET, p.col_w[column]), .y = TOPBAR_H, .w = p.col_w[column] -| TAG_TEXT_INSET, .h = COLBAR_H }, p.columnTag(column), if (active) chrome.tag_active_bg else chrome.tag_bg, if (active) chrome.tag_active_fg else chrome.tag_fg); }; } fn renderHeaderLayer(p: *Pardes, arena: std.mem.Allocator, index: usize, kind: TagKind, id: u16, viewport: Rect, text: []const u8, bg: [3]u8, fg: [3]u8) !void { _ = arena; const layer = &p.surface.tag_layers[index]; + if (viewport.w == 0) { + layer.cols = 0; + return; + } const columns: u16 = @intCast(@min(std.math.maxInt(u16), @max(p.tagCapacity(viewport.w), panes.File.displayWidth(text) + 1))); if (layer.cells.len != columns) layer.cells = try p.gpa.realloc(layer.cells, columns); layer.kind = kind; @@ -13815,7 +13827,7 @@ pub const Pardes = struct { .tty => config.tag_tty, }, box_style); - try p.paintPaneTag(arena, pane, r, id, active, p.tagCapacity(tw)); + try p.paintPaneTag(arena, pane, r, id, active, p.tagCapacity(r.w -| TAG_TEXT_INSET)); // Folded panes retain their body state, but neither body pixels nor // its cursor belong outside the one-row tag rectangle. @@ -13840,8 +13852,8 @@ pub const Pardes = struct { const s = &p.surface; const th = p.theme(); const chrome = p.chromeTheme(); - const tx = r.x + config.GUTTER; - const tw = r.w - config.GUTTER; + const tx = r.x + @min(TAG_TEXT_INSET, r.w); + const tw = r.w -| TAG_TEXT_INSET; const tag_y = if (p.settings.tag_bottom) r.y + r.h -| BOX_H else r.y; const body_y = if (p.settings.tag_bottom) r.y else r.y + BOX_H; const page_rgb = th.bg orelse th.tag_bg; @@ -13851,6 +13863,7 @@ pub const Pardes = struct { const tag_style: CellStyle = .{ .fg = .{ .rgb = tag_fg }, .bg = .{ .rgb = tag_bg }, .font_role = .tagline }; // Padding belongs to the same font row but keeps its default ink: a // role must not manufacture a foreground color for blank cells. + s.fill(r.x + config.GUTTER, tag_y, @min(TAG_GAP, r.w -| config.GUTTER), BOX_H, .{ .bg = .{ .rgb = tag_bg }, .font_role = .tagline }); s.fill(tx, tag_y, tw, BOX_H, .{ .bg = .{ .rgb = tag_bg }, .font_role = .tagline, diff --git a/src/web.zig b/src/web.zig index 72a7bb05..b5a992ae 100644 --- a/src/web.zig +++ b/src/web.zig @@ -332,6 +332,10 @@ export fn pardes_cursor_x() i32 { return if (s.core.surface.cursor) |cursor| cursor.x else -1; } +export fn pardes_tag_text_inset() u32 { + return pardes.TAG_TEXT_INSET; +} + export fn pardes_grip_columns() u32 { return @import("config.zig").GUTTER; } diff --git a/src/web/app.mjs b/src/web/app.mjs index 65e1240d..477186a3 100644 --- a/src/web/app.mjs +++ b/src/web/app.mjs @@ -55,15 +55,16 @@ export class DomRenderer { const cursorBar = this.wasm.pardes_cursor_bar() !== 0; const raw = new Array(count); const tagCovered = new Uint8Array(count); - const tagGrips = new Map(); + const tagPrefixBands = new Map(); if (this.wasm.pardes_tag_layer_limit) { for (let i = 0; i < this.wasm.pardes_tag_layer_limit(); i++) { const val = field => this.wasm.pardes_tag_layer_value(i, field); if (!val(0)) continue; const gripCols = this.wasm.pardes_grip_columns?.() ?? 2; - if (val(1) !== 0 && val(4) >= gripCols && this.cellHeight) { + const textInset = this.wasm.pardes_tag_text_inset?.() ?? gripCols; + if (val(1) !== 0 && val(4) >= textInset && this.cellHeight) { const offset = this.wasm.pardes_tagline_band_offset(val(5),rows*this.cellHeight*256,Math.round(this.cellHeight*256),Math.round(this.taglineHeight*256))/256; - for (let x = val(4)-gripCols; x < val(4); x++) tagGrips.set(val(5)*cols+x,offset); + for (let x = val(4)-textInset; x < val(4); x++) tagPrefixBands.set(val(5)*cols+x,offset); } for (let x = val(4); x < Math.min(cols, val(4) + val(6)); x++) { if (val(5) < rows) tagCovered[val(5) * cols + x] = 1; @@ -83,7 +84,7 @@ export class DomRenderer { const cursor = x === cursorX && y === cursorY; const cell = this.cells[index]; cell.style.visibility = tagCovered[index] ? "hidden" : ""; - const gripOffset = tagGrips.get(index); + const gripOffset = tagPrefixBands.get(index); cell.style.height = gripOffset === undefined ? "" : `${this.taglineHeight}px`; cell.style.lineHeight = gripOffset === undefined ? "" : `${this.taglineHeight}px`; cell.style.marginTop = gripOffset === undefined ? "" : `${gripOffset}px`; @@ -121,7 +122,7 @@ export class DomRenderer { const bytes = new Uint8Array(this.wasm.memory.buffer,ptr,cols*CELL_SIZE); const offset = this.wasm.pardes_tagline_band_offset(values[5],this.rowCount*bh*256,Math.round(bh*256),Math.round(th*256))/256; const gripCols = this.wasm.pardes_grip_columns?.() ?? 2; - const gripX = values[4] - gripCols; + const gripX = values[4] - (this.wasm.pardes_tag_text_inset?.() ?? gripCols); const gripCell = canonical[values[5]*this.cols+gripX]; const grip = values[1] === 2 && gripX >= 0 && gripCell?.text === " " && th >= 6 && tw >= 3; const geometry = [bw,bh,tw,th,offset,...values,grip,gripCell?.fg].join(":"); |
