diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/macos-snapshots/opacity.golden | 29 | ||||
| -rw-r--r-- | test/macos-snapshots/opacity.snap | 34 | ||||
| -rw-r--r-- | test/macos_e2e.swift | 62 |
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") } |
