fix: rebuild complete popup presentation between openings

This commit is contained in:
2026-10-18 01:55:36 +02:00
parent c52c3b227e
commit dcc124e382
2 changed files with 36 additions and 49 deletions
@@ -3,30 +3,16 @@ import ConjureKit
@MainActor
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),
ActionEntry(title: "Show in Finder", symbol: "finder", color: SunsetPalette.text),
ActionEntry(title: "Reset ranking", symbol: "arrow.counterclockwise", color: SunsetPalette.text),
ActionEntry(title: "Quit application", symbol: "stop.circle", color: SunsetPalette.text),
ActionEntry(title: "Uninstall application", symbol: "trash", color: .systemRed),
ActionEntry(title: "Copy Emoji", symbol: "doc.on.doc", color: SunsetPalette.text),
ActionEntry(title: "Change source language…", symbol: "character.bubble", color: SunsetPalette.text),
ActionEntry(title: "Change target language…", symbol: "character.bubble", color: SunsetPalette.text),
ActionEntry(title: "Swap languages", symbol: "arrow.left.arrow.right", color: SunsetPalette.text),
ActionEntry(title: "MyMemory (no key)", symbol: "checkmark", color: SunsetPalette.text),
ActionEntry(title: "OpenAI · gpt-6-luna", symbol: "checkmark", color: SunsetPalette.text)
]
private var search: ActionsSearchField!
private var background: LauncherBackground!
private var entries: [ActionEntry] = []
private var visibleEntries: [Int] = []
private var availableEntries = Array(0..<6)
private var selectedEntry: Int? = 0
private var rowTopConstraints: [NSLayoutConstraint] = []
private var heightConstraint: NSLayoutConstraint?
private let empty = NSTextField(labelWithString: "No matching actions")
private let applicationLabel = NSTextField(labelWithString: "")
private var empty: NSTextField!
private var applicationLabel: NSTextField!
private var mouseMonitor: Any?
private weak var coveredPill: NSView?
private var coveredPillAlpha: CGFloat = 1
@@ -52,17 +38,39 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
init() { super.init(nibName: nil, bundle: nil) }
required init?(coder: NSCoder) { fatalError("Conjure creates actions programmatically") }
override func loadView() {
override func loadView() { preparePresentation() }
private func preparePresentation() {
search = ActionsSearchField()
background = LauncherBackground(frame: .zero)
empty = NSTextField(labelWithString: "No matching actions")
applicationLabel = NSTextField(labelWithString: "")
rowTopConstraints = []
entries = [
ActionEntry(title: "Open", symbol: "arrow.up.forward.app", color: SunsetPalette.text),
ActionEntry(title: "Favorite", symbol: "star", color: SunsetPalette.text),
ActionEntry(title: "Show in Finder", symbol: "finder", color: SunsetPalette.text),
ActionEntry(title: "Reset ranking", symbol: "arrow.counterclockwise", color: SunsetPalette.text),
ActionEntry(title: "Quit application", symbol: "stop.circle", color: SunsetPalette.text),
ActionEntry(title: "Uninstall application", symbol: "trash", color: .systemRed),
ActionEntry(title: "Copy Emoji", symbol: "doc.on.doc", color: SunsetPalette.text),
ActionEntry(title: "Change source language…", symbol: "character.bubble", color: SunsetPalette.text),
ActionEntry(title: "Change target language…", symbol: "character.bubble", color: SunsetPalette.text),
ActionEntry(title: "Swap languages", symbol: "arrow.left.arrow.right", color: SunsetPalette.text),
ActionEntry(title: "MyMemory (no key)", symbol: "checkmark", color: SunsetPalette.text),
ActionEntry(title: "OpenAI · gpt-6-luna", symbol: "checkmark", color: SunsetPalette.text)
]
view = ActionsPanelView()
view.wantsLayer = true
view.layer?.shadowColor = NSColor.black.cgColor
view.layer?.shadowOpacity = 0.35
view.layer?.shadowRadius = 14
view.layer?.shadowOffset = NSSize(width: 0, height: -4)
let content = NSView()
content.wantsLayer = true
content.layer?.cornerRadius = 18
content.layer?.masksToBounds = true
let content = NSVisualEffectView()
content.material = .hudWindow
content.blendingMode = .behindWindow
content.state = .active
roundMaterialCorners(content, radius: 18)
content.layer?.borderWidth = 0.5
background.setAccessibilityElement(false)
content.addSubview(background)
@@ -90,7 +98,7 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
empty.isHidden = true
content.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(content)
for child in [background, applicationLabel, empty, search] {
for child in [background!, applicationLabel!, empty!, search!] {
child.translatesAutoresizingMaskIntoConstraints = false
if child !== background { content.addSubview(child) }
}
@@ -169,7 +177,8 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
}
guard let window = anchor.window, let host = window.contentView else { return }
let pill = anchor.superview ?? anchor
_ = view
// Reuse behavior, not native drawing state from a differently sized popup.
if isViewLoaded { preparePresentation() } else { _ = view }
applicationLabel.stringValue = title
availableEntries = available
let openTitle = emoji ? "Copy and Paste" : "Open"
@@ -213,24 +222,6 @@ 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
@@ -265,8 +256,6 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
coveredPill?.setAccessibilityHidden(false)
if !animated {
view.removeFromSuperview()
material?.removeFromSuperview()
material = nil
coveredPill?.layer?.removeAllAnimations()
coveredPill?.alphaValue = coveredPillAlpha
coveredPill = nil
@@ -283,8 +272,6 @@ 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
}