summaryrefslogtreecommitdiff
path: root/src/detached
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 14:59:21 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commit360b72ade0acfa655a3740b638a0cc8e23238a05 (patch)
tree4bc9464da2cdcfd6fe636979f0b457221ec92c3d /src/detached
parentd88e381793c6505bf3f2af43705bd2fa32f55ef6 (diff)
downloadpardes-360b72ade0acfa655a3740b638a0cc8e23238a05.tar.gz
pardes-360b72ade0acfa655a3740b638a0cc8e23238a05.zip
The GUI draws in tiers from the regions; pane chrome moves with its pane
Stage 8 of docs/render-pipeline.md. The frame is drawn in groups: tier 0, one per track in paint order (tiers 2 and 3), tier 4 (notices, then guides and the debug box), tier 5 (bar cursors), each cells, images, decor. Decor (rules, rails, thumbs, grip marks, spines, the workspace and column rules, the bottom band, notice rules, bar cursors) is whole-pixel rects through the new decor.frag over ui.vert, so it carries its track's transition and clip. A closing pane's chrome comes from Surface.previous_regions. Deleted: taglineBaseRgb, topbarPaneBorderHeight, bottomTaglinePresent, frameChromeBg, cellBackgroundIs and the rail inference, paneGripCell's scan, transient_on, PaintPlan. One cover map (coverFrame) is marked from the layers and regions once a frame. New regions column, guide and debug; Surface.chrome is the palette, carried in wire v8 (not shipped yet), so an attached GUI draws the same chrome (test). A per-instance clip replaces the vertical transition's scissor. Goldens: 01-12 byte-identical. 13-17 differ only past the grid: the image pass left its scissor at the grid's size, cutting rule ends, rail feet and the bottom band in the leftover pixels whenever a picture was on screen. 16-debug now shows the debug box, which a context-row layer had hidden. 18-mid-transition is new: virtual clock (PARDES_TEST_CLOCK in the GUI), PanelSlide Newcol with the picture, frame 6 of 12. Also: the GUI sleeps when idle instead of polling every 16 ms, and a minimized or occluded window sleeps through animation. Tracy 'gui frame build' at 200x60: 992/1015 us median before, 989/987 us after. Shared files touched: pardes.zig (one export), detached/client.zig, detached/server.zig, detached/wire.zig (all additive), gui.zig. Not touched: Messages.zig, mouse.zig, tagline.zig, colors.zig, tty.zig, host_io.zig, dump.zig, exec.zig, panes.zig.
Diffstat (limited to 'src/detached')
-rw-r--r--src/detached/client.zig4
-rw-r--r--src/detached/server.zig1
-rw-r--r--src/detached/wire.zig41
3 files changed, 37 insertions, 9 deletions
diff --git a/src/detached/client.zig b/src/detached/client.zig
index 3347f609..9fa85fb4 100644
--- a/src/detached/client.zig
+++ b/src/detached/client.zig
@@ -107,6 +107,8 @@ pub const Client = struct {
tag_layers: [pardes.MAX_TAG_LAYERS]pardes.Layer = @splat(.{}),
/// The session's placed regions, as its last frame gave them.
regions: std.ArrayListUnmanaged(pardes.Region) = .empty,
+ /// The palette its chrome is drawn in, as its last frame gave it.
+ chrome: pardes.Chrome = .{},
in: std.ArrayListUnmanaged(u8) = .empty,
out: std.ArrayListUnmanaged(u8) = .empty,
/// Bytes of `in` belonging to the message `next` returned last. Compacted at
@@ -322,7 +324,7 @@ pub const Client = struct {
try c.reshape(f.cols, f.rows);
}
try f.apply(c.grid.items);
- f.applyLayers(c.gpa, &c.body_layers, &c.tag_layers, &c.regions) catch |err| return switch (err) {
+ f.applyLayers(c.gpa, &c.body_layers, &c.tag_layers, &c.regions, &c.chrome) catch |err| return switch (err) {
error.OutOfMemory => error.Closed,
else => @as(wire.Error, @errorCast(err)),
};
diff --git a/src/detached/server.zig b/src/detached/server.zig
index 46fb8fcf..9e18e125 100644
--- a/src/detached/server.zig
+++ b/src/detached/server.zig
@@ -760,6 +760,7 @@ pub const Session = struct {
&surface.body_layers,
&surface.tag_layers,
surface.regionList(),
+ surface.chrome,
) catch |err| {
log.debug("frame {d}x{d} not encodable: {t}", .{ surface.cols, surface.rows, err });
return;
diff --git a/src/detached/wire.zig b/src/detached/wire.zig
index 684c869b..32cd5229 100644
--- a/src/detached/wire.zig
+++ b/src/detached/wire.zig
@@ -169,8 +169,9 @@ pub const Frame = struct {
nruns: u32,
runs: []const u8,
- pub fn applyLayers(f: Frame, gpa: std.mem.Allocator, layers: *[pardes.MAX_PANES]pardes.Layer, tags: *[pardes.MAX_TAG_LAYERS]pardes.Layer, regions: *std.ArrayListUnmanaged(pardes.Region)) !void {
+ pub fn applyLayers(f: Frame, gpa: std.mem.Allocator, layers: *[pardes.MAX_PANES]pardes.Layer, tags: *[pardes.MAX_TAG_LAYERS]pardes.Layer, regions: *std.ArrayListUnmanaged(pardes.Region), chrome: *pardes.Chrome) !void {
regions.clearRetainingCapacity();
+ chrome.* = .{};
for (layers) |*layer| {
gpa.free(layer.cells);
layer.* = .{};
@@ -188,6 +189,8 @@ pub const Frame = struct {
gpa.free(layer.cells);
layer.* = .{};
}
+ regions.clearRetainingCapacity();
+ chrome.* = .{};
}
if (f.layers.len == 0) return;
var r: Reader = .init(f.layers);
@@ -255,6 +258,15 @@ pub const Frame = struct {
region.clip > region.rect.w or region.thumb_y > region.rect.h or region.thumb_h > region.rect.h - region.thumb_y) return error.BadValue;
regions.appendAssumeCapacity(region);
}
+ inline for (@typeInfo(pardes.Chrome).@"struct".fields) |field| {
+ const rgb = &@field(chrome, field.name);
+ if (field.type == ?[3]u8) {
+ if (try r.getBool()) {
+ rgb.* = .{ 0, 0, 0 };
+ for (&rgb.*.?) |*channel| channel.* = try r.getByte();
+ }
+ } else for (rgb) |*channel| channel.* = try r.getByte();
+ }
try r.end();
}
@@ -697,7 +709,7 @@ pub fn encodeFramePointer(
cells: []const pardes.Cell,
prev: []const pardes.Cell,
) Error![]const u8 {
- return encodeFrameLayers(out, cols, rows, cursor, pointer_shape, cells, prev, &.{}, &.{}, &.{});
+ return encodeFrameLayers(out, cols, rows, cursor, pointer_shape, cells, prev, &.{}, &.{}, &.{}, .{});
}
pub fn layersBound(layers: []const pardes.Layer, tags: []const pardes.Layer, regions: []const pardes.Region) usize {
@@ -708,12 +720,15 @@ pub fn layersBound(layers: []const pardes.Layer, tags: []const pardes.Layer, reg
for (tags) |layer| if (layer.rows != 0) {
n += 28 + layer.cells.len * cell_max;
};
- return n + regions.len * region_len;
+ return n + regions.len * region_len + chrome_len;
}
const region_len = 1 + 1 + 4 + 8 + 1 + 2 + 2 + 1 + 2;
+/// The palette: each colour three bytes, a theme's optional ones a flag
+/// before them.
+const chrome_len = @typeInfo(pardes.Chrome).@"struct".fields.len * 4;
-pub fn encodeFrameLayers(out: []u8, cols: u16, rows: u16, cursor: ?Cursor, pointer_shape: pardes.Surface.PointerShape, cells: []const pardes.Cell, prev: []const pardes.Cell, layers: []const pardes.Layer, tags: []const pardes.Layer, regions: []const pardes.Region) Error![]const u8 {
+pub fn encodeFrameLayers(out: []u8, cols: u16, rows: u16, cursor: ?Cursor, pointer_shape: pardes.Surface.PointerShape, cells: []const pardes.Cell, prev: []const pardes.Cell, layers: []const pardes.Layer, tags: []const pardes.Layer, regions: []const pardes.Region, chrome: pardes.Chrome) Error![]const u8 {
// The protocol's ceiling, enforced by the ENCODER too, and BEFORE the
// assert below so a caller can be told rather than tripped. `max_payload`
// is derived from these two, so a larger grid is a frame this decoder
@@ -793,6 +808,13 @@ pub fn encodeFrameLayers(out: []u8, cols: u16, rows: u16, cursor: ?Cursor, point
try w.putByte(region.notice);
try w.putU16(region.clip);
}
+ inline for (@typeInfo(pardes.Chrome).@"struct".fields) |field| {
+ const rgb = @field(chrome, field.name);
+ if (field.type == ?[3]u8) {
+ try w.putBool(rgb != null);
+ if (rgb) |value| for (value) |channel| try w.putByte(channel);
+ } else for (rgb) |channel| try w.putByte(channel);
+ }
}
std.mem.writeInt(u32, w.buf[layers_len_at..][0..4], @intCast(w.n - layers_start), .little);
const nruns_at = w.n;
@@ -1871,7 +1893,8 @@ test "detached wire carries compact body rows and exact pointer metrics" {
const placed = [_]pardes.Region{.{ .kind = .rail, .owner = 3, .serial = 42, .rect = .{ .x = 0, .y = 1, .w = 2, .h = 3 }, .active = true, .thumb_y = 1, .thumb_h = 2, .clip = 1 }};
const out = try gpa.alloc(u8, frameBound(2, 4) + layersBound(&.{layer}, &.{tag}, &placed));
defer gpa.free(out);
- const encoded = try encodeFrameLayers(out, 2, 4, null, .link, &cells, &cells, &.{layer}, &.{tag}, &placed);
+ const palette: pardes.Chrome = .{ .page = .{ 1, 2, 3 }, .border = .{ 4, 5, 6 }, .tag_focus_bg = .{ 7, 8, 9 }, .scroll_thumb = .{ 10, 11, 12 } };
+ const encoded = try encodeFrameLayers(out, 2, 4, null, .link, &cells, &cells, &.{layer}, &.{tag}, &placed, palette);
const packet = (try framed(encoded)).?;
const frame = (try decodeServer(packet.tag, packet.payload)).frame;
var decoded: [pardes.MAX_PANES]pardes.Layer = @splat(.{});
@@ -1881,8 +1904,10 @@ test "detached wire carries compact body rows and exact pointer metrics" {
try frame.apply(&cells);
var regions: std.ArrayListUnmanaged(pardes.Region) = .empty;
defer regions.deinit(gpa);
- try frame.applyLayers(gpa, &decoded, &tags, &regions);
+ var chrome: pardes.Chrome = .{};
+ try frame.applyLayers(gpa, &decoded, &tags, &regions, &chrome);
try std.testing.expectEqualDeep(placed[0..], regions.items);
+ try std.testing.expectEqualDeep(palette, chrome);
try std.testing.expectEqual(@as(u16, 6), decoded[0].rows);
try std.testing.expect(decoded[0].context_separators.isSet(0));
try std.testing.expect(!decoded[0].context_separators.isSet(1));
@@ -1908,7 +1933,7 @@ test "detached wire carries compact body rows and exact pointer metrics" {
try std.testing.expect(mouse_encoded.len <= clientBound(message));
var truncated = frame;
truncated.layers = frame.layers[0 .. frame.layers.len - 1];
- try std.testing.expectError(error.Truncated, truncated.applyLayers(gpa, &decoded, &tags, &regions));
+ try std.testing.expectError(error.Truncated, truncated.applyLayers(gpa, &decoded, &tags, &regions, &chrome));
for (decoded) |item| {
try std.testing.expectEqual(@as(u16, 0), item.rows);
try std.testing.expectEqual(@as(usize, 0), item.cells.len);
@@ -1922,6 +1947,6 @@ test "detached wire carries compact body rows and exact pointer metrics" {
const no_layers = try encodeFramePointer(out, 2, 4, null, .arrow, &cells, &cells);
const cleared_packet = (try framed(no_layers)).?;
const cleared = (try decodeServer(cleared_packet.tag, cleared_packet.payload)).frame;
- try cleared.applyLayers(gpa, &decoded, &tags, &regions);
+ try cleared.applyLayers(gpa, &decoded, &tags, &regions, &chrome);
try std.testing.expectEqual(@as(u16, 0), decoded[0].rows);
}