From 7cbd44dd1cfe97126e7fc996214e458525d2980c Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Wed, 16 Sep 2026 13:20:29 -0300 Subject: Align macOS rendering with Linux and establish parity regressions --- src/macos.zig | 21 +++++- src/macos/Sources/AppDelegate.swift | 2 +- src/macos/Sources/PardesView.swift | 128 ++++++++++++++++++++++-------------- src/macos/pardes.h | 1 + 4 files changed, 102 insertions(+), 50 deletions(-) (limited to 'src') diff --git a/src/macos.zig b/src/macos.zig index 4b37255e..bbfb8c08 100644 --- a/src/macos.zig +++ b/src/macos.zig @@ -931,7 +931,7 @@ export fn pardes_grid_col_at(x: f32, row: u16, body_w: f32, tagline_w: f32) u16 export fn pardes_topbar_pane_border_rgb() u32 { const rgb = pardes.config.gui_topbar_pane_border_rgb orelse fromTheme: { const st = state orelse return color_default; - break :fromTheme st.core.chromeTheme().scroll_track; + break :fromTheme st.core.chromeTheme().border; }; return @as(u32, rgb[0]) << 16 | @as(u32, rgb[1]) << 8 | rgb[2]; } @@ -1979,6 +1979,11 @@ export fn pardes_tag_layer_value(index: u32, field: u32) u32 { 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], + 11 => @intFromBool(layer.kind == .pane and st.core.settings.tag_bottom), + 12 => rgb: { + const border = st.core.chromeTheme().border; + break :rgb @as(u32, border[0]) << 16 | @as(u32, border[1]) << 8 | border[2]; + }, else => 0, }; } @@ -2914,6 +2919,20 @@ test "mac tag layer ABI preserves logical capacity and physical grip" { 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, 0), pardes_tag_layer_value(0, 11)); + core.settings.tag_bottom = true; + try std.testing.expectEqual(@as(u32, 1), pardes_tag_layer_value(0, 11)); + core.surface.tag_layers[0].kind = .column; + try std.testing.expectEqual(@as(u32, 0), pardes_tag_layer_value(0, 11)); + core.surface.tag_layers[0].kind = .pane; + const border = core.chromeTheme().border; + const border_rgb = @as(u32, border[0]) << 16 | @as(u32, border[1]) << 8 | border[2]; + try std.testing.expectEqual(border_rgb, pardes_tag_layer_value(0, 12)); + try std.testing.expectEqual(pardes.config.gui_topbar_pane_border_rgb orelse border, .{ + @as(u8, @intCast(pardes_topbar_pane_border_rgb() >> 16)), + @as(u8, @truncate(pardes_topbar_pane_border_rgb() >> 8)), + @as(u8, @truncate(pardes_topbar_pane_border_rgb())), + }); 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 })); diff --git a/src/macos/Sources/AppDelegate.swift b/src/macos/Sources/AppDelegate.swift index 107c40fd..4e91e407 100644 --- a/src/macos/Sources/AppDelegate.swift +++ b/src/macos/Sources/AppDelegate.swift @@ -478,7 +478,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, PardesViewDelegate { if let rgb = view.themeBG { backdrop.isHidden = true window.isOpaque = view.backgroundAlpha >= 1 - window.backgroundColor = NSColor( + window.backgroundColor = view.backgroundAlpha < 1 ? .clear : NSColor( srgbRed: CGFloat((rgb >> 16) & 0xFF) / 255, green: CGFloat((rgb >> 8) & 0xFF) / 255, blue: CGFloat(rgb & 0xFF) / 255, diff --git a/src/macos/Sources/PardesView.swift b/src/macos/Sources/PardesView.swift index ba3677e7..7c537055 100644 --- a/src/macos/Sources/PardesView.swift +++ b/src/macos/Sources/PardesView.swift @@ -1006,7 +1006,7 @@ final class PardesView: NSView { // up on themselves — a translucent ground would darken toward opaque // over a few frames otherwise. if clearGround || backgroundAlpha < 1 { ctx.clear(bounds) } - if !clearGround { fill(ctx, bounds, ground, backgroundAlpha) } + if !clearGround { fillBackground(ctx, bounds, ground, backgroundAlpha) } if frozenImages == nil { frameImages.removeAll(keepingCapacity: true) } guard cols > 0, rows > 0, Int(count) == cols * rows, let cells = suppliedCells ?? pardes_frame_cells() @@ -1060,7 +1060,7 @@ final class PardesView: NSView { if color != bgClear { let bandY = tagline ? y + bandTop : y let bandHeight = tagline ? taglines.height : cellHeight - fill(ctx, CGRect(x: CGFloat(start) * cellWidth, y: bandY, + fillBackground(ctx, CGRect(x: CGFloat(start) * cellWidth, y: bandY, width: CGFloat(col - start) * cellWidth, height: bandHeight), color, block ? 1 : backgroundAlpha) } @@ -1071,29 +1071,6 @@ final class PardesView: NSView { } } - // The rule joining the topbar band to the first pane-tag band. Both were - // centred in their own rows until now, and centring two reduced-height - // bands is exactly what leaves their unused half-bands touching with a - // strip of window background showing between them — the case - // config.gui_topbar_pane_border_px exists to close. Drawn only when that - // first pane row really carries a band, so a session whose second row is - // pane BODY gets no stray line across it. - let ruleThickness = taglines.borderThickness - let ruleColor = pardes_topbar_pane_border_rgb() - if ruleThickness > 0, ruleColor != UInt32(PARDES_COLOR_DEFAULT), - rows > Int(PARDES_TOPBAR_H) { - let base = Int(PARDES_TOPBAR_H) * cols - let joins = (0.. [FrozenBodyLayer] { var result: [FrozenBodyLayer] = [] for index in 0.., cols: Int, rows: Int) { + var layers: [[Int]] = [] for index in 0.. 0 { + fillBackground(ctx, CGRect(x: 0, y: taglines.borderTop, width: bounds.width, + height: taglines.borderThickness), + pardes_topbar_pane_border_rgb(), backgroundAlpha) + } } private func gripGlyph(_ text: String, face: Face) -> GripGlyph? { @@ -1288,10 +1289,13 @@ final class PardesView: NSView { private func drawTagLayer(_ ctx: CGContext, values: [Int], cells: UnsafePointer, hideCursor: Bool) { let left = CGFloat(values[4]) * cellWidth let y = CGFloat(values[5]) * cellHeight + taglines.top(row: values[5], canvasHeight: bounds.height) - let viewport = CGRect(x: left, y: y, width: CGFloat(values[6]) * cellWidth, height: taglines.height) + // SDL gives the tag a full body-row background; only its glyphs + // and per-cell highlights use the compact band. + let viewport = CGRect(x: left, y: CGFloat(values[5]) * cellHeight, + width: CGFloat(values[6]) * cellWidth, height: cellHeight) ctx.saveGState() ctx.clip(to: viewport) - fill(ctx, viewport, UInt32(values[10]), backgroundAlpha) + fillBackground(ctx, viewport, UInt32(values[10]), backgroundAlpha) for pass in 0...1 { for col in 0..= viewport.maxY { break } - let rowStyle = resolve(cells[row*cols], block: false, ground: themeBG ?? pardesDefaultBG, clearGround: false) - fill(ctx, CGRect(x: left, y: y, width: viewport.width, height: height), rowStyle.bg, backgroundAlpha) + let rowStyle = resolve(cells[row*cols], block: false, ground: themeBG ?? pardesDefaultBG, clearGround: themeBG == nil) + fillBackground(ctx, CGRect(x: left, y: y, width: viewport.width, height: height), rowStyle.bg, backgroundAlpha) for pass in 0...1 { for col in 0..> 16) & 0xFF) / 255, - green: CGFloat((rgb >> 8) & 0xFF) / 255, - blue: CGFloat(rgb & 0xFF) / 255, - alpha: alpha) + // Theme channels, like PDF raster bytes, are sRGB. Device RGB can + // brighten them when AppKit's backing bitmap uses another profile. + ctx.setFillColor(CGColor(srgbRed: CGFloat((rgb >> 16) & 0xFF) / 255, + green: CGFloat((rgb >> 8) & 0xFF) / 255, + blue: CGFloat(rgb & 0xFF) / 255, + alpha: alpha)) + } + + /// One surface pixel has one background opacity, even when a compact + /// layer covers a canonical cell. Source-over would turn 71% into 92%, + /// then 98%, as the ground, row and cell are painted over each other. + private func fillBackground(_ ctx: CGContext, _ rect: CGRect, _ rgb: UInt32, _ alpha: CGFloat) { + ctx.saveGState() + ctx.setShouldAntialias(false) + if rgb == bgClear { + ctx.clear(rect) + } else { + ctx.setBlendMode(.copy) + fill(ctx, rect, rgb, alpha) + } + ctx.restoreGState() } private func fill(_ ctx: CGContext, _ rect: CGRect, _ rgb: UInt32, _ alpha: CGFloat) { diff --git a/src/macos/pardes.h b/src/macos/pardes.h index a2d13b1f..a2e34f53 100644 --- a/src/macos/pardes.h +++ b/src/macos/pardes.h @@ -353,6 +353,7 @@ void pardes_row_metrics(uint16_t body_w, uint16_t body_h, uint16_t tagline_w, ui uint32_t pardes_tag_text_inset(void); uint32_t pardes_grip_columns(void); uint32_t pardes_tag_layer_limit(void); +// Tag layer fields 11 and 12 carry tag-bottom placement and chrome border RGB. uint32_t pardes_tag_layer_value(uint32_t index, uint32_t field); const pardes_cell_s *pardes_tag_layer_cells(uint32_t index); uint32_t pardes_body_layer_limit(void); -- cgit v1.3