diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-21 14:55:53 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 44ce573e15c773cf5bb0d42941a143b63e44d900 (patch) | |
| tree | c2c6ecd5b298a85044cb1b68e1db8f7b6657ead7 /src/macos.zig | |
| parent | fd50bd971d6dea7eaaa4ee5436ba16b95fa25b30 (diff) | |
| download | pardes-44ce573e15c773cf5bb0d42941a143b63e44d900.tar.gz pardes-44ce573e15c773cf5bb0d42941a143b63e44d900.zip | |
Make the macOS shell a first-class host
Pty children are exec'd with their own TERM/COLORTERM/TERM_PROGRAM instead of
inheriting a .app launch's empty environment, and ttyTaken finally answers on
darwin — libproc walks the tty's foreground process group — so Escape reaches
the child and Exec stops believing every pane sits at its prompt. The
occupancy suite runs on both platforms now.
The workspace tag row moves into the native menu bar as a Builtins menu.
-Dworkspace-tag (default off for -Dplatform=macos, on everywhere else) drives
it, and Pardes.topBarHeight replaces the TOPBAR_H constant so the core stops
reserving the row.
The view pins every variable-font axis to the file's own default (Maple Mono
came up Thin otherwise), shapes ligatures, carries per-shape pointer cursors,
and draws the look-hover affordance as refracted glass. Tag rows fill edge to
edge, with the anchor box painted back on top of that fill and its mode glyph
centred on the same square. Theme accents re-saturated across the set.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Diffstat (limited to 'src/macos.zig')
| -rw-r--r-- | src/macos.zig | 58 |
1 files changed, 45 insertions, 13 deletions
diff --git a/src/macos.zig b/src/macos.zig index 79036c3e..d263cfc6 100644 --- a/src/macos.zig +++ b/src/macos.zig @@ -106,6 +106,7 @@ const color_default: u32 = 0x01000000; const color_indexed: u32 = 0x02000000; const cell_flag_default: u8 = 1; const cell_flag_tagline: u8 = 2; +const cell_flag_hover: u8 = 4; const scene_flag_crt: u32 = 1 << 0; const scene_flag_ripple: u32 = 1 << 1; const scene_flag_glitch: u32 = 1 << 2; @@ -730,6 +731,16 @@ fn initCore(runtime: ?*const Runtime, cols_arg: u16, rows_arg: u16) !void { const core = try pardes.Pardes.init(allocs.pardes, opts); errdefer core.deinit(); + // The workspace tag row is the macOS menu bar's business here (see + // docs/macos.md): the build option says whether this shell keeps the + // acme topbar or hands its commands to native chrome. The e2e harness + // opts back in with PARDES_WORKSPACE_TAG=1 — its scripts speak to a + // grid that has the row. Applied before the first resize so the layout + // has never seen it the other way. + core.settings.workspace_tag = if (std.c.getenv("PARDES_WORKSPACE_TAG")) |v| + std.mem.eql(u8, std.mem.span(v), "1") + else + @import("pardes_config").workspace_tag; core.native_images = true; var prompt_rcs = host_io.Shell.prepare(); @@ -917,7 +928,7 @@ export fn pardes_gui_tagline_font_percent() u8 { } export fn pardes_tagline_band_offset(row: u16, canvas_h: f32, cell_h: u32, tagline_h: u32) u32 { - return pardes.taglineBandOffset(row, canvas_h, cell_h, tagline_h); + return pardes.taglineBandOffset(row, canvas_h, cell_h, tagline_h, if (state) |*st| st.core.settings.workspace_tag else true); } export fn pardes_topbar_pane_border_px(cell_h: u32, tagline_h: u32) u32 { @@ -942,6 +953,11 @@ export fn pardes_topbar_pane_border_rgb() u32 { return @as(u32, rgb[0]) << 16 | @as(u32, rgb[1]) << 8 | rgb[2]; } +export fn pardes_tag_active_bg() u32 { + const st = state orelse return color_default; + const rgb = st.core.chromeTheme().tag_active_bg; + return @as(u32, rgb[0]) << 16 | @as(u32, rgb[1]) << 8 | rgb[2]; +} export fn pardes_tagline_bg() u32 { const st = state orelse return color_default; const rgb = st.core.chromeTheme().tag_bg; @@ -1756,12 +1772,18 @@ fn clearFrame(st: *State) void { st.panel_diff_len = 0; } -fn blankTagCells(cells: []Cell, cols: u16, rows: u16, layers: []const pardes.TagLayer) void { +fn blankTagCells(cells: []Cell, cols: u16, rows: u16, source: []const pardes.Cell, 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(.{}); + for (cells[start..][0..count], source[start..][0..count]) |*cell, was| { + // The tag text is the layer's to draw, but a look-hover on a tag + // word is a canonical fact: carry the hover bit through so the + // glass overlay still finds the affordance rect. + cell.* = encodeCell(.{}); + cell.flags |= @as(u8, @intFromBool(was.hover)) * cell_flag_hover; + } } } @@ -1800,7 +1822,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); + blankTagCells(st.cells[0..count], surface.cols, surface.rows, surface.cells, &surface.tag_layers); collectPanelDiff(st, surface, count); collectImages(st, surface); collectPanelTracks(st, surface); @@ -1831,7 +1853,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); + blankTagCells(st.previous_cells[0..count], surface.cols, surface.rows, surface.previous_cells, surface.previous_tag_layers); for (surface.cell_diffs, st.changed_cells[0..count]) |diff, *out| out.* = encodeChanged(diff); st.panel_diff_len = count; @@ -2395,8 +2417,8 @@ fn encodeCell(cell: pardes.Cell) Cell { .fg = encodeColor(cell.style.fg), .bg = encodeColor(cell.style.bg), .attrs = encodeAttrs(cell.style), + .flags = encodeCellFlags(cell.default, cell.style.font_role, cell.hover), .len = if (cell.default) 1 else cell.len, - .flags = encodeCellFlags(cell.default, cell.style.font_role), }; if (cell.default) out.text[0] = ' ' @@ -2418,9 +2440,10 @@ fn encodeAttrs(style: pardes.CellStyle) u16 { return attrs; } -fn encodeCellFlags(default: bool, role: pardes.FontRole) u8 { +fn encodeCellFlags(default: bool, role: pardes.FontRole, hover: bool) u8 { return @as(u8, @intFromBool(default)) * cell_flag_default | - @as(u8, @intFromBool(role == .tagline)) * cell_flag_tagline; + @as(u8, @intFromBool(role == .tagline)) * cell_flag_tagline | + @as(u8, @intFromBool(hover)) * cell_flag_hover; } fn noteScrollVelocity(st: *State, delta_rows: f32, delta_cols: f32) void { @@ -2566,7 +2589,8 @@ test "pardes.h declares every export the way it is defined" { try expectSameAbi(@TypeOf(c.pardes_animating), @TypeOf(pardes_animating)); try expectSameAbi(@TypeOf(c.pardes_gui_tagline_font_percent), @TypeOf(pardes_gui_tagline_font_percent)); try expectSameAbi(@TypeOf(c.pardes_tagline_band_offset), @TypeOf(pardes_tagline_band_offset)); - try expectSameAbi(@TypeOf(c.pardes_topbar_pane_border_px), @TypeOf(pardes_topbar_pane_border_px)); + try expectSameAbi(@TypeOf(c.pardes_topbar_pane_border_rgb), @TypeOf(pardes_topbar_pane_border_rgb)); + try expectSameAbi(@TypeOf(c.pardes_tag_active_bg), @TypeOf(pardes_tag_active_bg)); try expectSameAbi(@TypeOf(c.pardes_tagline_origin_col), @TypeOf(pardes_tagline_origin_col)); try expectSameAbi(@TypeOf(c.pardes_grid_col_at), @TypeOf(pardes_grid_col_at)); try expectSameAbi(@TypeOf(c.pardes_topbar_pane_border_rgb), @TypeOf(pardes_topbar_pane_border_rgb)); @@ -2658,6 +2682,7 @@ test "pardes.h matches the Zig boundary" { try expectEqual(@as(u32, c.PARDES_COLOR_INDEXED), color_indexed); try expectEqual(@as(u8, c.PARDES_CELL_DEFAULT), cell_flag_default); try expectEqual(@as(u8, c.PARDES_CELL_TAGLINE), cell_flag_tagline); + try expectEqual(@as(u8, c.PARDES_CELL_HOVER), cell_flag_hover); try expectEqual(@as(u16, c.PARDES_TOPBAR_H), pardes.TOPBAR_H); try expectEqual(@as(u32, c.PARDES_SCENE_CRT), scene_flag_crt); try expectEqual(@as(u32, c.PARDES_SCENE_RIPPLE), scene_flag_ripple); @@ -2723,9 +2748,10 @@ test "pardes.h matches the Zig boundary" { encodeAttrs(.{ .ul = .curly }), ); - try expectEqual(@as(u8, 0), encodeCellFlags(false, .body)); - try expectEqual(cell_flag_tagline, encodeCellFlags(false, .tagline)); - try expectEqual(cell_flag_default | cell_flag_tagline, encodeCellFlags(true, .tagline)); + try expectEqual(@as(u8, 0), encodeCellFlags(false, .body, false)); + try expectEqual(cell_flag_hover, encodeCellFlags(false, .body, true)); + try expectEqual(cell_flag_tagline, encodeCellFlags(false, .tagline, false)); + try expectEqual(cell_flag_default | cell_flag_tagline, encodeCellFlags(true, .tagline, false)); try expectEqual(pardes.config.gui_tagline_font_percent, pardes_gui_tagline_font_percent()); } @@ -2947,10 +2973,16 @@ test "mac tag layer ABI preserves logical capacity and physical grip" { 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); + // A look-hover under the layer is a canonical fact and survives the blank; + // the glyph it marks does not, because the layer redraws that text itself. + var source: [24]pardes.Cell = @splat(.{}); + source[11].hover = true; + blankTagCells(&canonical, 8, 3, &source, &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); + try std.testing.expectEqual(@as(u8, 0), canonical[10].flags & cell_flag_hover); + try std.testing.expectEqual(cell_flag_hover, canonical[11].flags & cell_flag_hover); } test "mac window blur builtin reaches the ABI without changing opacity" { |
