diff --git a/README.md b/README.md index f51c93b..303729e 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ The launcher, emoji picker, notes, and translation tool open immediately without Right-click the launcher background for Settings and Quit. Actions include favorites, resetting usage ranking, revealing applications in Finder, quitting them, and uninstalling them. -Action and provider popups paint a popup-sized background using the launcher window’s gradient coordinates, keeping the glows continuous without showing the results underneath. They do not retain an oversized clipped background between presentations. Layout and background invalidation finish before the opening fade, including when switching popup types. +Action and provider popups paint a popup-sized background using the launcher window’s gradient coordinates, keeping the glows continuous without showing the results underneath. Each opening creates a fresh native visual-effect backdrop; closing releases it instead of moving cached material between the footer and header. Layout finishes before the opening fade, including when switching popup types. ### Global shortcuts and launch at login diff --git a/Sources/Conjure/ApplicationActionsController.swift b/Sources/Conjure/ApplicationActionsController.swift index 0dae7ad..5cb18fd 100644 --- a/Sources/Conjure/ApplicationActionsController.swift +++ b/Sources/Conjure/ApplicationActionsController.swift @@ -5,6 +5,7 @@ import ConjureKit final class ApplicationActionsController: NSViewController, NSSearchFieldDelegate { private let search = ActionsSearchField() private let background = LauncherBackground(frame: .zero) + private var material: NSVisualEffectView? private let entries = [ ActionEntry(title: "Open", symbol: "arrow.up.forward.app", color: SunsetPalette.text), ActionEntry(title: "Favorite", symbol: "star", color: SunsetPalette.text), @@ -58,11 +59,10 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat view.layer?.shadowOpacity = 0.35 view.layer?.shadowRadius = 14 view.layer?.shadowOffset = NSSize(width: 0, height: -4) - let content = NSVisualEffectView() - content.material = .hudWindow - content.blendingMode = .behindWindow - content.state = .active - roundMaterialCorners(content, radius: 18) + let content = NSView() + content.wantsLayer = true + content.layer?.cornerRadius = 18 + content.layer?.masksToBounds = true content.layer?.borderWidth = 0.5 background.setAccessibilityElement(false) content.addSubview(background) @@ -213,6 +213,24 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat coveredPillAlpha = pill.alphaValue } coveredPill?.setAccessibilityHidden(true) + // A backdrop belongs to one presentation; do not move a cached native material + // between the footer Actions popup and the header provider popup. + material?.removeFromSuperview() + let backdrop = NSVisualEffectView() + backdrop.material = .hudWindow + backdrop.blendingMode = .behindWindow + backdrop.state = .active + roundMaterialCorners(backdrop, radius: 18) + backdrop.translatesAutoresizingMaskIntoConstraints = false + let content = background.superview! + content.addSubview(backdrop, positioned: .below, relativeTo: background) + NSLayoutConstraint.activate([ + backdrop.leadingAnchor.constraint(equalTo: content.leadingAnchor), + backdrop.trailingAnchor.constraint(equalTo: content.trailingAnchor), + backdrop.topAnchor.constraint(equalTo: content.topAnchor), + backdrop.bottomAnchor.constraint(equalTo: content.bottomAnchor), + ]) + material = backdrop host.layoutSubtreeIfNeeded() background.needsDisplay = true NSAnimationContext.runAnimationGroup { context in @@ -247,6 +265,8 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat coveredPill?.setAccessibilityHidden(false) if !animated { view.removeFromSuperview() + material?.removeFromSuperview() + material = nil coveredPill?.layer?.removeAllAnimations() coveredPill?.alphaValue = coveredPillAlpha coveredPill = nil @@ -263,6 +283,8 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat MainActor.assumeIsolated { guard self.transitionGeneration == generation, self.isClosing else { return } self.view.removeFromSuperview() + self.material?.removeFromSuperview() + self.material = nil self.coveredPill = nil self.isClosing = false }