From 1ab4c071be58033d5452f69797ca8e2c32240cff Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 15 Sep 2026 16:28:57 -0300 Subject: Separate tag text geometry from physical pane grips --- src/macos.zig | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 99 insertions(+), 3 deletions(-) (limited to 'src/macos.zig') diff --git a/src/macos.zig b/src/macos.zig index 3a1926b5..5406fcb4 100644 --- a/src/macos.zig +++ b/src/macos.zig @@ -637,6 +637,7 @@ const State = struct { core: *pardes.Pardes, started: bool = false, runtime: Runtime, + tag_cells: [pardes.MAX_TAG_LAYERS][]Cell = @splat(&.{}), layer_cells: [pardes.MAX_PANES][]Cell = @splat(&.{}), cells: []Cell = &.{}, previous_cells: []Cell = &.{}, @@ -780,6 +781,7 @@ export fn pardes_deinit() void { st.file_watches.deinit(st.gpa); if (st.cells.len > 0) st.gpa.free(st.cells); for (st.layer_cells) |cells| st.gpa.free(cells); + for (st.tag_cells) |cells| st.gpa.free(cells); if (st.previous_cells.len > 0) st.gpa.free(st.previous_cells); if (st.changed_cells.len > 0) st.gpa.free(st.changed_cells); if (st.images.len > 0) st.gpa.free(st.images); @@ -1482,7 +1484,7 @@ export fn pardes_paste(text_ptr: ?[*]const u8, len: usize) void { } export fn pardes_mouse(button_arg: c_int, kind_arg: c_int, col: u16, row: u16, mods: u32) void { - mouseWithHit(button_arg, kind_arg, col, row, mods, null); + mouseWithHit(button_arg, kind_arg, col, row, mods, null, null); } export fn pardes_mouse_pixel(button_arg: c_int, kind_arg: c_int, col: u16, row: u16, mods: u32, x: f32, y: f32, bw: f32, bh: f32, tw: f32, th: f32) void { @@ -1494,10 +1496,16 @@ export fn pardes_mouse_pixel(button_arg: c_int, kind_arg: c_int, col: u16, row: hit = layer.hitAt(point.x * bw, point.y * bh, bw, bh, tw, th); if (hit != null) break; } - mouseWithHit(button_arg, kind_arg, col, row, mods, hit); + var tag_hit: ?pardes.TagHit = null; + for (&st.core.surface.tag_layers) |*layer| { + const point = source orelse break; + tag_hit = layer.hitAt(point.x * bw, point.y * bh, bw, bh, tw, th); + if (tag_hit != null) break; + } + mouseWithHit(button_arg, kind_arg, col, row, mods, hit, tag_hit); } -fn mouseWithHit(button_arg: c_int, kind_arg: c_int, col: u16, row: u16, mods: u32, hit: ?pardes.Mouse.BodyHit) void { +fn mouseWithHit(button_arg: c_int, kind_arg: c_int, col: u16, row: u16, mods: u32, hit: ?pardes.Mouse.BodyHit, tag_hit: ?pardes.TagHit) void { const st = &(state orelse return); const button: pardes.Mouse.Button = switch (button_arg) { 0 => .left, @@ -1521,6 +1529,7 @@ fn mouseWithHit(button_arg: c_int, kind_arg: c_int, col: u16, row: u16, mods: u3 }; st.core.update(.{ .mouse = .{ .body_hit = hit, + .tag_hit = tag_hit, .button = button, .kind = kind, .col = col, @@ -1666,6 +1675,24 @@ fn clearFrame(st: *State) void { st.panel_diff_len = 0; } +fn blankTagCells(cells: []Cell, cols: u16, rows: u16, layers: []const pardes.TagLayer) void { + for (layers) |layer| { + if (layer.cols == 0 or layer.viewport.y >= rows or layer.viewport.x >= cols) continue; + const start = @as(usize, layer.viewport.y) * cols + layer.viewport.x; + const count = @min(layer.viewport.w, cols - layer.viewport.x); + for (cells[start..][0..count]) |*cell| cell.* = encodeCell(.{}); + } +} + +fn tagCursorCovered(core: *const pardes.Pardes) bool { + const cursor = core.surface.cursor orelse return false; + for (core.surface.tag_layers) |layer| { + if (layer.cols != 0 and cursor.y == layer.viewport.y and cursor.x >= layer.viewport.x and + cursor.x - layer.viewport.x < layer.viewport.w) return true; + } + return false; +} + fn presentFrame(ctx: ?*anyopaque, surface: *const pardes.Surface) void { const st = hostState(ctx); const tz = tracy.zone(@src(), "presentFrame"); @@ -1692,6 +1719,7 @@ fn presentFrame(ctx: ?*anyopaque, surface: *const pardes.Surface) void { defer tz_cells.end(); for (surface.cells, st.cells[0..count]) |cell, *out| out.* = encodeCell(cell); } + blankTagCells(st.cells[0..count], surface.cols, surface.rows, &surface.tag_layers); collectPanelDiff(st, surface, count); collectImages(st, surface); collectPanelTracks(st, surface); @@ -1722,6 +1750,7 @@ fn collectPanelDiff(st: *State, surface: *const pardes.Surface, count: usize) vo } for (surface.previous_cells, st.previous_cells[0..count]) |cell, *out| out.* = encodeCell(cell); + blankTagCells(st.previous_cells[0..count], surface.cols, surface.rows, surface.previous_tag_layers); for (surface.cell_diffs, st.changed_cells[0..count]) |diff, *out| out.* = encodeChanged(diff); st.panel_diff_len = count; @@ -1843,9 +1872,45 @@ export fn pardes_frame_rows() u16 { export fn pardes_cursor_x() i32 { const st = &(state orelse return -1); + if (tagCursorCovered(st.core)) return -1; return if (st.core.surface.cursor) |c| c.x else -1; } +export fn pardes_tag_layer_limit() u32 { + return pardes.MAX_TAG_LAYERS; +} + +export fn pardes_tag_layer_value(index: u32, field: u32) u32 { + const st = &(state orelse return 0); + if (index >= pardes.MAX_TAG_LAYERS) return 0; + const layer = &st.core.surface.tag_layers[index]; + return switch (field) { + 0 => layer.cols, + 1 => @intFromEnum(layer.kind), + 2 => layer.id, + 3 => layer.serial, + 4 => layer.viewport.x, + 5 => layer.viewport.y, + 6 => layer.viewport.w, + 7 => layer.viewport.h, + 8 => if (layer.cursor) |c| c.col else 65535, + 9 => if (layer.cursor) |c| @intFromBool(c.bar) else 0, + 10 => @as(u32, layer.bg[0]) << 16 | @as(u32, layer.bg[1]) << 8 | layer.bg[2], + else => 0, + }; +} + +export fn pardes_tag_layer_cells(index: u32) ?[*]const Cell { + const st = &(state orelse return null); + if (index >= pardes.MAX_TAG_LAYERS) return null; + const layer = &st.core.surface.tag_layers[index]; + if (layer.cols == 0) return null; + const cells = &st.tag_cells[index]; + if (cells.len != layer.cells.len) cells.* = st.gpa.realloc(cells.*, layer.cells.len) catch return null; + for (layer.cells, cells.*) |source, *out| out.* = encodeCell(source); + return cells.ptr; +} + export fn pardes_body_layer_value(index: u32, field: u32) u32 { const st = &(state orelse return 0); if (index >= pardes.MAX_PANES) return 0; @@ -1893,6 +1958,7 @@ export fn pardes_pointer_shape() u32 { export fn pardes_cursor_y() i32 { const st = &(state orelse return -1); + if (tagCursorCovered(st.core)) return -1; return if (st.core.surface.cursor) |c| c.y else -1; } @@ -2348,6 +2414,9 @@ 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_layer_limit), @TypeOf(pardes_tag_layer_limit)); + try expectSameAbi(@TypeOf(c.pardes_tag_layer_value), @TypeOf(pardes_tag_layer_value)); + try expectSameAbi(@TypeOf(c.pardes_tag_layer_cells), @TypeOf(pardes_tag_layer_cells)); try expectSameAbi(@TypeOf(c.pardes_body_layer_value), @TypeOf(pardes_body_layer_value)); try expectSameAbi(@TypeOf(c.pardes_body_layer_cells), @TypeOf(pardes_body_layer_cells)); try expectSameAbi(@TypeOf(c.pardes_take_haptic), @TypeOf(pardes_take_haptic)); @@ -2647,3 +2716,30 @@ test "mac row metrics ABI preserves PDF cell dimensions and the unrendered grid" try std.testing.expectEqual(@as(u16, 24), core.screen_h); try std.testing.expectEqualDeep(pardes.RowMetrics{ .body_w = 22, .body_h = 39, .tagline_w = 17, .tagline_h = 30 }, core.row_metrics); } + +test "mac tag layer ABI preserves logical capacity and physical grip" { + const gpa = std.testing.allocator; + const core = try pardes.Pardes.init(gpa, .{ .tty_only = true, .cols = 8, .rows = 3 }); + defer core.deinit(); + state = .{ .gpa = gpa, .threaded = undefined, .io = std.testing.io, .core = core, .runtime = .{}, .config_arena = .init(gpa), .prompt_rcs = .{} }; + defer { + for (state.?.tag_cells) |cells| gpa.free(cells); + state.?.config_arena.deinit(); + state = null; + } + var logical: [12]pardes.Cell = @splat(.{}); + logical[11].text[0] = 'Z'; + logical[11].len = 1; + logical[11].default = false; + core.surface.tag_layers[0] = .{ .kind = .pane, .id = 1, .serial = 42, .viewport = .{ .x = 2, .y = 1, .w = 6, .h = 1 }, .cols = 12, .cells = &logical, .bg = .{ 0x12, 0x34, 0x56 }, .cursor = .{ .col = 11, .bar = true } }; + defer core.surface.tag_layers[0] = .{}; + try std.testing.expectEqual(@as(u32, 12), pardes_tag_layer_value(0, 0)); + try std.testing.expectEqual(@as(u32, 0x123456), pardes_tag_layer_value(0, 10)); + try std.testing.expectEqual(@as(u32, 11), pardes_tag_layer_value(0, 8)); + try std.testing.expectEqual(@as(u8, 'Z'), pardes_tag_layer_cells(0).?[11].text[0]); + var canonical: [24]Cell = @splat(encodeCell(.{ .text = .{'G'} ++ .{0} ** 6, .len = 1, .default = false })); + blankTagCells(&canonical, 8, 3, &core.surface.tag_layers); + try std.testing.expectEqual(@as(u8, 'G'), canonical[9].text[0]); + try std.testing.expectEqual(@as(u8, 'G'), canonical[18].text[0]); + try std.testing.expectEqual(encodeCell(.{}).len, canonical[10].len); +} -- cgit v1.3