From 242467eb9f066f80164d534be55c2c4ebc9b5604 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 19:06:01 -0300 Subject: acme looks like acme; every theme gets acme's button, scroll column and 2px rules acme takes plan9port's own colours (acme.c iconinit, draw.h): selections #eeee9e in the body and #9eeeee in the tag, black ink; exec and look sweeps #aa0000 and #006600, white ink; a #99994c scrollbar under a #ffffea thumb; black rules; a #8888cc tag/body rule and column button. Every theme: the grip is acme's button, its tag row's full height, in one scroll column with the scrollbar (rail_px, 12 at a 17px tagline, scaled, times GripWidth, a percent, default 150; same x0/x1 to the pixel): focused clean = solid box; focused dirty = box_dirty filled to a box ring (acme's modbutton); unfocused clean = a box_border ring round the tag's ground; unfocused dirty = box_dirty a pixel inside that ring. The grid, which has no ring, marks dirty with a bold * on box_dirty in the grip's second cell and fills an unfocused grip halfway to its ring. Regions carry a dirty flag. Column buttons are the same shape, solid. 2px rules between columns and stacked panes (inside the tag band's slack), none at the window edge; a 1px tag/body rule halfway between tag and page. Theme data: tag_sel_bg, sweep_bg/fg, tag_rule, rule_px, rail_px, box_border, box_dirty. Chrome: rule_px, tag_rule, rail_px, grip_border, grip_focus_ring; a comptime guard on Chrome's layout forces a wire bump once v8 ships. Structural rules are exempt from the focus-indicator rule (docs/effects.md). Tests: acme widths; stacked rule in the slack at two tagline sizes; every grip-column pixel is band, button, ring or rule, and the button shares the scrollbar's x0/x1 (4 metrics x top/bottom x focus x dirty); the four grip states distinct in every native theme with a non-colour dirty cue in the grid; wire round-trip of the new fields. Shared files: src/colors.zig, src/surface.zig, src/body_layer.zig, src/tagline.zig, src/detached/wire.zig, docs/themes.md, docs/config.md, docs/effects.md, 21 snapshot goldens (the grip cells only), test/gui-goldens.txt. Motion flavours differ by design, and drive notice drops and pane moves --- src/detached/wire.zig | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) (limited to 'src/detached') diff --git a/src/detached/wire.zig b/src/detached/wire.zig index 2a2ccc6b..c0e17054 100644 --- a/src/detached/wire.zig +++ b/src/detached/wire.zig @@ -11,6 +11,19 @@ const limits = @import("../memory.zig").limits; /// line), and a frame carries the core's placed regions after its layers. pub const version: u16 = 8; +/// The Chrome palette is carried field by field as its declaration lists +/// them, so any change to it changes the wire: bump `version`, then this. +const chrome_layout: u64 = 0x7e3d0952317bfee; +comptime { + @setEvalBranchQuota(10_000); + var hash = std.hash.Fnv1a_64.init(); + for (@typeInfo(pardes.Chrome).@"struct".fields) |field| { + hash.update(field.name); + hash.update(@typeName(field.type)); + } + if (hash.final() != chrome_layout) @compileError(std.fmt.comptimePrint("pardes.Chrome changed (layout 0x{x}): bump wire.version, then chrome_layout", .{hash.final()})); +} + pub const Error = error{ Truncated, Overlong, BadTag, BadValue, Trailing, NoSpace }; pub const max_cols: u16 = 512; @@ -248,7 +261,10 @@ pub const Frame = struct { region.owner = try r.getByte(); region.serial = try r.getU32(); region.rect = .{ .x = try r.getU16(), .y = try r.getU16(), .w = try r.getU16(), .h = try r.getU16() }; - region.active = try r.getBool(); + const flags = try r.getByte(); + if (flags > 3) return error.BadValue; + region.active = flags & 1 != 0; + region.dirty = flags & 2 != 0; region.thumb_y = try r.getU16(); region.thumb_h = try r.getU16(); region.notice = try r.getByte(); @@ -807,7 +823,7 @@ pub fn encodeFrameLayers(out: []u8, cols: u16, rows: u16, cursor: ?Cursor, point try w.putU16(region.rect.y); try w.putU16(region.rect.w); try w.putU16(region.rect.h); - try w.putBool(region.active); + try w.putByte(@as(u8, @intFromBool(region.active)) | @as(u8, @intFromBool(region.dirty)) << 1); try w.putU16(region.thumb_y); try w.putU16(region.thumb_h); try w.putByte(region.notice); @@ -1898,10 +1914,10 @@ test "detached wire carries compact body rows and exact pointer metrics" { var tag_cells: [3]pardes.Cell = @splat(.{}); tag_cells[2] = logical[10]; const tag: pardes.Layer = .{ .kind = .column, .id = 1, .viewport = .{ .x = 0, .y = 0, .w = 2, .h = 1 }, .cols = 3, .rows = 1, .bg = .{ 11, 22, 33 }, .cells = &tag_cells, .cursor = .{ .x = 2, .y = 0, .bar = true } }; - 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, .lift = 0.5 }}; + const placed = [_]pardes.Region{.{ .kind = .rail, .owner = 3, .serial = 42, .rect = .{ .x = 0, .y = 1, .w = 2, .h = 3 }, .active = true, .dirty = true, .thumb_y = 1, .thumb_h = 2, .clip = 1, .lift = 0.5 }}; const out = try gpa.alloc(u8, frameBound(2, 4) + layersBound(&.{layer}, &.{tag}, &placed)); defer gpa.free(out); - const palette: pardes.Chrome = .{ .page = .{ 1, 2, 3 }, .border = .{ 4, 5, 6 }, .tag_focus_bg = .{ 7, 8, 9 }, .scroll_thumb = .{ 10, 11, 12 } }; + const palette: pardes.Chrome = .{ .page = .{ 1, 2, 3 }, .border = .{ 4, 5, 6 }, .tag_focus_bg = .{ 7, 8, 9 }, .scroll_thumb = .{ 10, 11, 12 }, .rule_px = 3, .tag_rule = .{ 13, 14, 15 }, .rail_px = 12, .grip_border = .{ 16, 17, 18 }, .grip_focus_ring = .{ 19, 20, 21 }, .grip_dirty = .{ 22, 23, 24 }, .lift_style = 2 }; 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; -- cgit v1.3