summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-16 12:00:52 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commit556c5b50bbfd4a6aaae192377d7810f610fc7d9e (patch)
treed2acef085c6e6589fbf94936702222d914abc86a /test
parent0b54c599fa5e26fb1754ca0aab53da5fa0e14af1 (diff)
downloadpardes-556c5b50bbfd4a6aaae192377d7810f610fc7d9e.tar.gz
pardes-556c5b50bbfd4a6aaae192377d7810f610fc7d9e.zip
Let WindowOpacity through on macOS
The builtin was gated on the SDL platform, so the macOS shell never registered it and the setting had nowhere to land. macOS already had transparency, but only the binary kind a theme decides — a theme with no background of its own drops the ground and the blur shows through. WindowOpacity is the graded, theme-independent version, and the window had no way to hear about it. pardes_window_opacity reports the percentage and acknowledges the request in one read, beside pardes_theme_bg: an SDL surface can arrive without an alpha channel and has to be able to refuse, while an AppKit window always composites per pixel, so there is nothing here to refuse and no rollback to perform. The view then paints the rule shaders/ui.frag.glsl states for the SDL shell: backgrounds — ground, cell and band fills, and the chrome rules over them — take the alpha, glyph ink never does, and the block cursor is exempt because it is foreground chrome that happens to be carried in a cell background. That exemption is why the cursor joins the colour in the background run key; it can no longer share a fill with the cells beside it. Two things the harness was missing fall out of testing it: it never adopted the theme background or the opacity, so a Theme or WindowOpacity in a script moved the core and never reached a pixel. draw-opacity records both ends of the alpha range, which is what makes the two-sided contract assertable — 60% reads 153..255, and 0% reads 0..255 with the ink still standing. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Diffstat (limited to 'test')
-rw-r--r--test/macos-snapshots/opacity.golden29
-rw-r--r--test/macos-snapshots/opacity.snap34
-rw-r--r--test/macos_e2e.swift62
3 files changed, 125 insertions, 0 deletions
diff --git a/test/macos-snapshots/opacity.golden b/test/macos-snapshots/opacity.golden
new file mode 100644
index 00000000..b28c6e0c
--- /dev/null
+++ b/test/macos-snapshots/opacity.golden
@@ -0,0 +1,29 @@
+== draw-opacity solid 680x396 alpha 255..255
+== snap translucent grid=80x24 cursor=4,3
+|Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Kill
+| New Tty Find Grep Joincol
+|$ /private/tmp/pardes-macos-e2e/opacity/cwd Tty Save Del Mode Filter Collapse
+| $
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== draw-opacity translucent 680x396 alpha 153..255
+== draw-opacity clear 680x396 alpha 0..255
+== draw-opacity restored 680x396 alpha 255..255
diff --git a/test/macos-snapshots/opacity.snap b/test/macos-snapshots/opacity.snap
new file mode 100644
index 00000000..0f73cb17
--- /dev/null
+++ b/test/macos-snapshots/opacity.snap
@@ -0,0 +1,34 @@
+# WindowOpacity, end to end: the builtin the core only offers to a shell that
+# declared the capability (src/builtins.zig), through pardes_window_opacity,
+# to the alpha CoreGraphics actually left in the framebuffer.
+#
+# The contract is two-sided and shaders/ui.frag.glsl states it for the SDL
+# shell: BACKGROUNDS take the percentage, glyph ink never does. `draw-opacity`
+# records both ends of the range so a regression that made the whole window
+# translucent — text included — reads as a changed maximum, not as a pass.
+start 24 80
+wait 8000 Newcol Joincol
+wait 8000 $
+stable 700 20000
+
+# Opaque is the default, and an opaque view caches without an alpha channel
+# at all.
+draw-opacity solid
+
+# Backgrounds drop to 60%; the prompt and the chrome text stay at 255.
+command WindowOpacity 60
+stable 700 20000
+snap translucent
+draw-opacity translucent
+
+# 0 is the extreme the block cursor exists to survive: every background is
+# gone and the ink is still there.
+command WindowOpacity 0
+stable 700 20000
+draw-opacity clear
+
+# ...and back, which must restore an opaque view rather than leave a window
+# that is merely painting 100% alpha into a transparent surface.
+command WindowOpacity 100
+stable 700 20000
+draw-opacity restored
diff --git a/test/macos_e2e.swift b/test/macos_e2e.swift
index 58ad5206..fbfa0e3e 100644
--- a/test/macos_e2e.swift
+++ b/test/macos_e2e.swift
@@ -270,6 +270,15 @@ private final class Driver: PardesViewDelegate {
if let view, let wanted = pardes_font_take(&fontSize) {
view.adoptFont(path: String(cString: wanted), sizeHundredths: fontSize)
}
+ // The app reads both of these every pump too (AppDelegate.applyTheme).
+ // There is no window here to dress, but the VIEW half is the same and
+ // the drawing depends on it: without this a `Theme` or a
+ // `WindowOpacity` in a script would move the core's settings and never
+ // reach a pixel, and the capture would record the old ground forever.
+ if let view {
+ view.adoptThemeBG(pardes_theme_bg())
+ view.adoptWindowOpacity(pardes_window_opacity())
+ }
}
/// Hand the run loop a slice. Main-queue work (anything the view defers)
@@ -489,6 +498,51 @@ private final class Driver: PardesViewDelegate {
output += "== draw \(label) \(w)x\(h) nonblank\n"
}
+ /// The alpha range of a drawn frame, recorded into the golden.
+ ///
+ /// The contract WindowOpacity states (shaders/ui.frag.glsl, and
+ /// PardesView.backgroundAlpha for this shell) is two-sided, so one number
+ /// could not express it: BACKGROUNDS take the setting, and glyph ink stays
+ /// opaque. A minimum below 255 is the ground letting the desktop through;
+ /// a maximum of 255 is the ink still sitting on top of it. At
+ /// `WindowOpacity 100` both ends are 255 and the view is opaque again.
+ private func drawOpacity(_ label: String) throws {
+ let view = try live()
+ let bounds = view.bounds
+ guard let rep = view.bitmapImageRepForCachingDisplay(in: bounds) else {
+ throw ScriptError(message: "draw-opacity \(label): the view would not make a bitmap for \(bounds.size)")
+ }
+ view.cacheDisplay(in: bounds, to: rep)
+ guard let data = rep.bitmapData, rep.pixelsWide > 0, rep.pixelsHigh > 0 else {
+ throw ScriptError(message: "draw-opacity \(label): cacheDisplay produced no pixels")
+ }
+ guard rep.hasAlpha, rep.bitsPerPixel == 32 else {
+ // An opaque view caches into a rep with no alpha at all, which is
+ // itself the answer at WindowOpacity 100 — say so rather than
+ // reporting a range read out of the wrong byte.
+ output += "== draw-opacity \(label) \(Int(bounds.width.rounded()))x\(Int(bounds.height.rounded())) opaque\n"
+ return
+ }
+ // Where alpha sits depends on the rep's format, and only the two
+ // AppKit actually hands back here are worth decoding.
+ let alphaFirst = rep.bitmapFormat.contains(.alphaFirst)
+ let offset = alphaFirst ? 0 : 3
+ let rowBytes = rep.bytesPerRow
+ var lowest: UInt8 = 255
+ var highest: UInt8 = 0
+ for y in 0..<rep.pixelsHigh {
+ let row = data + y * rowBytes
+ for x in 0..<rep.pixelsWide {
+ let alpha = row[x * 4 + offset]
+ if alpha < lowest { lowest = alpha }
+ if alpha > highest { highest = alpha }
+ }
+ }
+ let w = Int(bounds.width.rounded())
+ let h = Int(bounds.height.rounded())
+ output += "== draw-opacity \(label) \(w)x\(h) alpha \(lowest)..\(highest)\n"
+ }
+
// MARK: - the script
func run(source: String) throws -> String {
@@ -799,6 +853,14 @@ private final class Driver: PardesViewDelegate {
guard !rest.isEmpty else { throw ScriptError(message: "draw needs a label") }
try draw(rest)
+ // What WindowOpacity actually did to the pixels, which is the only
+ // place it can be checked: the core's cell buffer says nothing about
+ // alpha, and `draw` deliberately compares channels without knowing
+ // which one is which.
+ case "draw-opacity":
+ guard !rest.isEmpty else { throw ScriptError(message: "draw-opacity needs a label") }
+ try drawOpacity(rest)
+
case "draw-effect":
let view = try live()
guard !rest.isEmpty else { throw ScriptError(message: "draw-effect needs a label") }