summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
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") }