diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-16 14:46:16 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 717afaf3177a2e0925b18ae3445119efe808b129 (patch) | |
| tree | 45a2196711de78d8d1e2db6cb5e5c51271676c2f /src/macos/Sources | |
| parent | 7cbd44dd1cfe97126e7fc996214e458525d2980c (diff) | |
| download | pardes-717afaf3177a2e0925b18ae3445119efe808b129.tar.gz pardes-717afaf3177a2e0925b18ae3445119efe808b129.zip | |
Add macOS backdrop blur and preserve PDF ink opacity
Diffstat (limited to 'src/macos/Sources')
| -rw-r--r-- | src/macos/Sources/AppDelegate.swift | 24 | ||||
| -rw-r--r-- | src/macos/Sources/PardesView.swift | 16 |
2 files changed, 28 insertions, 12 deletions
diff --git a/src/macos/Sources/AppDelegate.swift b/src/macos/Sources/AppDelegate.swift index 4e91e407..cbe9cdc9 100644 --- a/src/macos/Sources/AppDelegate.swift +++ b/src/macos/Sources/AppDelegate.swift @@ -81,8 +81,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, PardesViewDelegate { // The grid and the blur are SIBLINGS in a plain container, not parent // and child. A transparent theme has to show something through the // grid, and AppKit will not blur what is behind a window unless an - // NSVisualEffectView asks it to — but the backdrop is hidden for every - // theme that brings its own background, and hiding a superview hides + // NSVisualEffectView asks it to. The backdrop can be hidden independently + // when blur is disabled or the ground is opaque; hiding a superview hides // its subviews with it. Nested, an opaque theme drew a blank window. let container = NSView(frame: NSRect(origin: .zero, size: want)) container.autoresizesSubviews = true @@ -460,6 +460,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, PardesViewDelegate { /// theme whose background happened to equal the view's starting guess /// would otherwise leave the window in AppKit's default clothes forever. private var dressed = false + private var appliedBlur: UInt8 = 0 private func applyTheme() { // Both reads happen every pump, and neither may be short-circuited by @@ -468,15 +469,17 @@ final class AppDelegate: NSObject, NSApplicationDelegate, PardesViewDelegate { // 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 } + let blur = pardes_window_blur() + let blurChanged = blur != appliedBlur + guard themeChanged || opacityChanged || blurChanged || !dressed else { return } + appliedBlur = blur + backdrop.alphaValue = CGFloat(blur) / 100 + backdrop.isHidden = blur == 0 || (view.themeBG != nil && view.backgroundAlpha >= 1) 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. + // Opacity paints the theme ground; blur controls the native material + // behind it. An opaque ground hides the backdrop without losing the + // requested strength, so lowering opacity restores the effect. if let rgb = view.themeBG { - backdrop.isHidden = true window.isOpaque = view.backgroundAlpha >= 1 window.backgroundColor = view.backgroundAlpha < 1 ? .clear : NSColor( srgbRed: CGFloat((rgb >> 16) & 0xFF) / 255, @@ -493,10 +496,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, PardesViewDelegate { window.appearance = NSAppearance(named: luma > 0.5 ? .aqua : .darkAqua) } else { // Transparent: the window stops painting anything of its own and - // the blur takes over. isOpaque false is what lets the desktop + // the optional blur sits behind it. isOpaque false lets the desktop // reach the backdrop at all — a titled window is opaque by default // and would composite over it. - backdrop.isHidden = false window.isOpaque = false window.backgroundColor = .clear window.appearance = NSAppearance(named: .darkAqua) diff --git a/src/macos/Sources/PardesView.swift b/src/macos/Sources/PardesView.swift index 7c537055..7981cf46 100644 --- a/src/macos/Sources/PardesView.swift +++ b/src/macos/Sources/PardesView.swift @@ -1446,6 +1446,7 @@ final class PardesView: NSView { let source: CGRect let body: CGRect let destination: CGRect + let paperBG: UInt32 } /// Rasterized attachments, decoded once each. The bytes the core lends are @@ -1496,7 +1497,7 @@ final class PardesView: NSView { height: CGFloat(place.dst_h) / scale) let frozen = FrozenImage( key: key, image: crop, source: source, - body: body, destination: dst) + body: body, destination: dst, paperBG: place.paper_bg) frameImages.append(frozen) drawFrozenImage(ctx, frozen) } @@ -1545,11 +1546,24 @@ final class PardesView: NSView { ctx.clip(to: placed.body) // isFlipped gives us a y-down CTM and CGImage draws +y up, so flip // about this destination rather than changing the grid arithmetic. + let isPDF = placed.paperBG != UInt32(PARDES_COLOR_DEFAULT) + if isPDF { + fillBackground(ctx, placed.destination, placed.paperBG, backgroundAlpha) + } ctx.translateBy(x: placed.destination.minX, y: placed.destination.maxY) ctx.scaleBy(x: 1, y: -1) ctx.interpolationQuality = .high let rect = CGRect(x: 0, y: 0, width: placed.destination.width, height: placed.destination.height) + if isPDF { + // Paper was painted at WindowOpacity. Content keeps the PDF's own + // coverage, so glyphs, photos and paths stay legible at zero opacity. + ctx.setAlpha(1) + ctx.setBlendMode(.normal) + ctx.draw(placed.image, in: rect) + ctx.restoreGState() + return + } // Match image.frag.glsl/backgroundLayerBlend: source coverage removes // the destination independently of WindowOpacity, then the source is // added at that opacity. Ordinary source-over would compound alpha; |
