summaryrefslogtreecommitdiff
path: root/src/macos/Sources
diff options
context:
space:
mode:
Diffstat (limited to 'src/macos/Sources')
-rw-r--r--src/macos/Sources/AppDelegate.swift18
-rw-r--r--src/macos/Sources/PardesView.swift82
2 files changed, 77 insertions, 23 deletions
diff --git a/src/macos/Sources/AppDelegate.swift b/src/macos/Sources/AppDelegate.swift
index 96f4a184..107c40fd 100644
--- a/src/macos/Sources/AppDelegate.swift
+++ b/src/macos/Sources/AppDelegate.swift
@@ -462,17 +462,27 @@ final class AppDelegate: NSObject, NSApplicationDelegate, PardesViewDelegate {
private var dressed = false
private func applyTheme() {
- let changed = view.adoptThemeBG(pardes_theme_bg())
- guard changed || !dressed else { return }
+ // Both reads happen every pump, and neither may be short-circuited by
+ // the other: `changed` has to be the OR of two adoptions, not the first
+ // one that answered true, or a Theme and a WindowOpacity in the same
+ // tick would leave one of them unworn.
+ let themeChanged = view.adoptThemeBG(pardes_theme_bg())
+ let opacityChanged = view.adoptWindowOpacity(pardes_window_opacity())
+ guard themeChanged || opacityChanged || !dressed else { return }
dressed = true
+ // A ground short of solid needs the same non-opaque window a
+ // background-less theme does — but not the blur behind it. The backdrop
+ // answers "this theme brings no background"; WindowOpacity says how
+ // much of the one it brings to paint, and what shows through the rest
+ // is the desktop, which is what asking for a translucent window means.
if let rgb = view.themeBG {
backdrop.isHidden = true
- window.isOpaque = true
+ window.isOpaque = view.backgroundAlpha >= 1
window.backgroundColor = NSColor(
srgbRed: CGFloat((rgb >> 16) & 0xFF) / 255,
green: CGFloat((rgb >> 8) & 0xFF) / 255,
blue: CGFloat(rgb & 0xFF) / 255,
- alpha: 1)
+ alpha: view.backgroundAlpha)
// ...and follow the theme into light mode, so the titlebar, the
// traffic lights and the resize cursor stop belonging to a
// different application than the grid under them. Luminance off
diff --git a/src/macos/Sources/PardesView.swift b/src/macos/Sources/PardesView.swift
index cd3ec980..5df1fd73 100644
--- a/src/macos/Sources/PardesView.swift
+++ b/src/macos/Sources/PardesView.swift
@@ -626,6 +626,32 @@ final class PardesView: NSView {
return true
}
+ /// What `WindowOpacity` asked for, as the alpha every BACKGROUND is painted
+ /// at: the ground, cell and band backgrounds, and the chrome rules over
+ /// them. Glyph ink is never scaled by it, and neither is the block cursor —
+ /// it is foreground chrome that merely happens to be carried in a cell's
+ /// background, and it has to stay visible at `WindowOpacity 0`.
+ ///
+ /// The rule is stated once for the SDL shell in shaders/ui.frag.glsl; this
+ /// is the same rule in CoreGraphics, where painting the ground at this
+ /// alpha and the glyph over it at its own coverage composites to what that
+ /// shader writes premultiplied in one pass.
+ ///
+ /// Orthogonal to `themeBG`: a theme with no background of its own drops the
+ /// ground whatever this says, and an opaque theme still honours it.
+ private(set) var backgroundAlpha: CGFloat = 1
+
+ /// Adopt the core's percentage. Returns whether anything moved, on the same
+ /// contract as adoptThemeBG: the host redresses the window only when it has.
+ @discardableResult
+ func adoptWindowOpacity(_ percent: UInt8) -> Bool {
+ let wanted = CGFloat(min(percent, 100)) / 100
+ guard wanted != backgroundAlpha else { return false }
+ backgroundAlpha = wanted
+ needsDisplay = true
+ return true
+ }
+
// The button a left-stream click actually started with. mouseDown decides
// it from the fingers on the trackpad, and mouseDragged/mouseUp must use
// the same one: a press of right followed by a release of left leaves the
@@ -792,10 +818,11 @@ final class PardesView: NSView {
// Row 0 at the top, so the drawing arithmetic reads like the grid it is.
override var isFlipped: Bool { true }
- // Opaque only while the theme brings its own background. A transparent
- // theme has none, and an opaque view over a visual-effect backdrop is a
- // grey rectangle where the blur should be.
- override var isOpaque: Bool { themeBG != nil }
+ // Opaque only while the theme brings its own background AND that background
+ // is painted solid. A transparent theme has none, and an opaque view over a
+ // visual-effect backdrop is a grey rectangle where the blur should be;
+ // a WindowOpacity below 100 has the same problem against the desktop.
+ override var isOpaque: Bool { themeBG != nil && backgroundAlpha >= 1 }
override var acceptsFirstResponder: Bool { true }
// A click that focuses the window should also land in the grid: this is a
// text surface, and having to click twice after switching apps is the kind
@@ -974,7 +1001,12 @@ final class PardesView: NSView {
// otherwise pile up on themselves.
let ground = themeBG ?? pardesDefaultBG
let clearGround = themeBG == nil
- if clearGround { ctx.clear(bounds) } else { fill(ctx, bounds, ground, 1) }
+ // AppKit does not blank a non-opaque view, so anything that leaves the
+ // ground short of solid has to clear first or last frame's pixels pile
+ // 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 frozenImages == nil { frameImages.removeAll(keepingCapacity: true) }
guard cols > 0, rows > 0, Int(count) == cols * rows,
let cells = suppliedCells ?? pardes_frame_cells()
@@ -1004,30 +1036,38 @@ final class PardesView: NSView {
// cell in the row, and it is a call across the ABI.
let bandTop = taglines.top(row: row, canvasHeight: canvasHeight)
var start = 0
- var color = resolve(cells[base], block: blockY == row && blockX == 0,
+ var block = blockY == row && blockX == 0
+ var color = resolve(cells[base], block: block,
ground: ground, clearGround: clearGround).bg
var tagline = cells[base].flags & UInt8(PARDES_CELL_TAGLINE) != 0
for col in 1...cols {
// A real color is 24 bits, so .max is a sentinel that cannot
// compare equal and therefore always flushes the last run.
+ let nextBlock = col < cols && blockY == row && blockX == col
let next: UInt32 = col == cols
? .max
- : resolve(cells[base + col], block: blockY == row && blockX == col,
+ : resolve(cells[base + col], block: nextBlock,
ground: ground, clearGround: clearGround).bg
let nextTagline = col < cols
&& cells[base + col].flags & UInt8(PARDES_CELL_TAGLINE) != 0
- if next == color && nextTagline == tagline { continue }
+ // The cursor is in the run key as well as the colour: it is the
+ // one background that stays solid under WindowOpacity, so it
+ // cannot share a fill with the cells beside it even when the
+ // reverse happened to land on their colour.
+ if next == color && nextTagline == tagline && nextBlock == block { continue }
// bgClear runs are the ground showing through, and the ground is
// already clear — painting them would be painting the hole shut.
if color != bgClear {
let bandY = tagline ? y + bandTop : y
let bandHeight = tagline ? taglines.height : cellHeight
fill(ctx, CGRect(x: CGFloat(start) * cellWidth, y: bandY,
- width: CGFloat(col - start) * cellWidth, height: bandHeight), color, 1)
+ width: CGFloat(col - start) * cellWidth, height: bandHeight),
+ color, block ? 1 : backgroundAlpha)
}
start = col
color = next
tagline = nextTagline
+ block = nextBlock
}
}
@@ -1050,7 +1090,7 @@ final class PardesView: NSView {
}
if joins {
fill(ctx, CGRect(x: 0, y: taglines.borderTop, width: bounds.width,
- height: ruleThickness), ruleColor, 1)
+ height: ruleThickness), ruleColor, backgroundAlpha)
}
}
@@ -1069,7 +1109,7 @@ final class PardesView: NSView {
clearGround: clearGround).bg
if bg != bgClear {
fill(ctx, CGRect(x: 0, y: gridBottom, width: bounds.width,
- height: canvasHeight - gridBottom), bg, 1)
+ height: canvasHeight - gridBottom), bg, backgroundAlpha)
}
break
}
@@ -1251,15 +1291,17 @@ final class PardesView: NSView {
let viewport = CGRect(x: left, y: y, width: CGFloat(values[6]) * cellWidth, height: taglines.height)
ctx.saveGState()
ctx.clip(to: viewport)
- fill(ctx, viewport, UInt32(values[10]), 1)
+ fill(ctx, viewport, UInt32(values[10]), backgroundAlpha)
for pass in 0...1 {
for col in 0..<values[0] {
var cell = cells[col]
let isCursor = !hideCursor && col == values[8]
- let style = resolve(cell, block: isCursor && values[9] == 0, ground: UInt32(values[10]), clearGround: false)
+ let block = isCursor && values[9] == 0
+ let style = resolve(cell, block: block, ground: UInt32(values[10]), clearGround: false)
let x = left + CGFloat(col) * taglines.width
if pass == 0 {
- fill(ctx, CGRect(x: x, y: y, width: taglines.width, height: taglines.height), style.bg, 1)
+ fill(ctx, CGRect(x: x, y: y, width: taglines.width, height: taglines.height),
+ style.bg, block ? 1 : backgroundAlpha)
continue
}
let text = withUnsafeBytes(of: &cell.text) { String(decoding: $0.prefix(Int(cell.len)), as: UTF8.self) }
@@ -1320,7 +1362,7 @@ final class PardesView: NSView {
height: CGFloat(value(6))*cellHeight)
ctx.saveGState()
ctx.clip(to: viewport)
- fill(ctx, viewport, themeBG ?? pardesDefaultBG, 1)
+ fill(ctx, viewport, themeBG ?? pardesDefaultBG, backgroundAlpha)
for row in 0..<rows {
let compact = row < contexts
let advance = compact ? taglines.width : cellWidth
@@ -1328,16 +1370,18 @@ final class PardesView: NSView {
let y = top + CGFloat(min(row, contexts))*taglines.height + CGFloat(max(0, row-contexts))*cellHeight
if y >= 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, 1)
+ fill(ctx, CGRect(x: left, y: y, width: viewport.width, height: height), rowStyle.bg, backgroundAlpha)
for pass in 0...1 {
for col in 0..<cols {
var cell = cells[row*cols+col]
let isCursor = !hideCursor && col == value(8) && row == value(9)
- let style = resolve(cell, block: isCursor && value(10) == 0,
+ let block = isCursor && value(10) == 0
+ let style = resolve(cell, block: block,
ground: themeBG ?? pardesDefaultBG, clearGround: false)
let x = left + CGFloat(col)*advance
if pass == 0 {
- fill(ctx, CGRect(x: x, y: y, width: advance, height: height), style.bg, 1)
+ fill(ctx, CGRect(x: x, y: y, width: advance, height: height),
+ style.bg, block ? 1 : backgroundAlpha)
continue
}
let text = withUnsafeBytes(of: &cell.text) { bytes in
@@ -1372,7 +1416,7 @@ final class PardesView: NSView {
// the next row's first one; keep the two shells on the same pixel.
let thickness = taglines.contextBorderThickness
fill(ctx, CGRect(x: left, y: top + CGFloat(row + 1)*taglines.height - thickness,
- width: viewport.width, height: thickness), border, 1)
+ width: viewport.width, height: thickness), border, backgroundAlpha)
}
}
ctx.restoreGState()