fix: complete Command presentation teardown

This commit is contained in:
Valdemar Lorenzen
2026-10-16 15:36:04 +02:00
parent 586c8a7560
commit cd2e3da330
6 changed files with 115 additions and 25 deletions
@@ -192,8 +192,8 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
}
}
func close() {
guard isShown else { return }
func close(animated: Bool = true) {
guard viewIfLoaded?.superview != nil, !isClosing || !animated else { return }
transitionGeneration += 1
let generation = transitionGeneration
isClosing = true
@@ -202,13 +202,23 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
if let mouseMonitor { NSEvent.removeMonitor(mouseMonitor) }
mouseMonitor = nil
coveredPill?.setAccessibilityHidden(false)
if !animated {
view.removeFromSuperview()
coveredPill?.layer?.removeAllAnimations()
coveredPill?.alphaValue = coveredPillAlpha
coveredPill = nil
isClosing = false
closed?()
return
}
// Finish teardown even if the presenting Command releases this controller.
NSAnimationContext.runAnimationGroup { context in
context.duration = NSWorkspace.shared.accessibilityDisplayShouldReduceMotion ? 0 : 0.12
view.animator().alphaValue = 0
coveredPill?.animator().alphaValue = coveredPillAlpha
} completionHandler: { [weak self] in
} completionHandler: { [self] in
MainActor.assumeIsolated {
guard let self, self.transitionGeneration == generation, self.isClosing else { return }
guard self.transitionGeneration == generation, self.isClosing else { return }
self.view.removeFromSuperview()
self.coveredPill = nil
self.isClosing = false
+3 -3
View File
@@ -110,10 +110,10 @@ final class EmojiPickerController: NSViewController, NSCollectionViewDataSource,
@objc private func categoryChanged() { inputChanged() }
@objc func goBack() {
if actions.isShown { actions.close() } else { closed?() }
if actions.isShown { actions.close() } else { close() }
}
func close() {
actions.close()
actions.close(animated: false)
closed?()
}
override func cancelOperation(_ sender: Any?) { goBack() }
@@ -254,7 +254,7 @@ final class EmojiPickerController: NSViewController, NSCollectionViewDataSource,
var recent = defaults.stringArray(forKey: "recentEmoji") ?? []
recent.removeAll { $0 == value }
defaults.set(Array(([value] + recent).prefix(30)), forKey: "recentEmoji")
closed?()
actions.close(animated: false)
if pasting { pasteRequested?() } else { copied?() }
}
+13 -14
View File
@@ -14,6 +14,8 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
private let sectionLabel = NSTextField(labelWithString: "Applications")
private let openButton = NSButton(title: "Open Application", target: nil, action: nil)
private let history: LaunchHistory
private let defaults: UserDefaults
private let pasteboard: NSPasteboard
private var applications: [Application] = Application.commands
private var results: [Application] = []
private var calculation: CalculatorResult?
@@ -49,9 +51,12 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
}
}()
init(history: LaunchHistory = LaunchHistory(), uninstaller: ApplicationUninstaller = ApplicationUninstaller()) {
init(history: LaunchHistory = LaunchHistory(), uninstaller: ApplicationUninstaller = ApplicationUninstaller(),
defaults: UserDefaults = .standard, pasteboard: NSPasteboard = .general) {
self.history = history
self.uninstaller = uninstaller
self.defaults = defaults
self.pasteboard = pasteboard
let window = LauncherPanel(
contentRect: NSRect(x: 0, y: 0, width: 750, height: 474),
styleMask: [.borderless],
@@ -118,7 +123,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
return
}
closeCommand()
actions.close()
actions.close(animated: false)
(window as? LauncherPanel)?.beginNumberHintSession()
let frontmost = NSWorkspace.shared.frontmostApplication
if frontmost?.processIdentifier != ProcessInfo.processInfo.processIdentifier {
@@ -145,8 +150,6 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
guard !isLaunching, window?.attachedSheet == nil else { return }
if window?.isVisible == true, window?.isKeyWindow == true,
launcherFrameBeforeReview == nil, activeCommand == command {
closeCommand()
actions.close()
dismiss()
} else {
present(command: command)
@@ -156,12 +159,9 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
func dismiss(restoringFocus: Bool = true) {
guard !restoringFocus || (!isLaunching && window?.attachedSheet == nil) else { return }
if let review = uninstallReview { review.goBack(); return }
if isCommandOpen {
goBackCommand()
if restoringFocus { return }
}
closeCommand()
guard launcherFrameBeforeReview == nil else { return }
actions.close()
actions.close(animated: false)
alignmentGuides?.end()
let previous = previousApplication
previousApplication = nil
@@ -712,7 +712,6 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
guard !isLaunching, !isCommandOpen, launcherFrameBeforeReview == nil, window?.attachedSheet == nil else { return }
if let calculation {
guard tableView.selectedRow == 0, let value = calculation.copyValue else { return }
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
if pasteboard.setString(value, forType: .string) {
dismiss()
@@ -763,7 +762,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
private func pasteEmoji() {
let target = previousApplication
let changeCount = NSPasteboard.general.changeCount
let changeCount = pasteboard.changeCount
dismiss()
guard let target, !target.isTerminated,
target.processIdentifier != ProcessInfo.processInfo.processIdentifier,
@@ -774,7 +773,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
for _ in 0..<20 {
try? await Task.sleep(for: .milliseconds(25))
guard let self, self.window?.isVisible == false, !target.isTerminated,
NSPasteboard.general.changeCount == changeCount else { return }
self.pasteboard.changeCount == changeCount else { return }
guard NSWorkspace.shared.frontmostApplication?.processIdentifier == target.processIdentifier else { continue }
guard let down = CGEvent(keyboardEventSource: nil, virtualKey: 9, keyDown: true),
let up = CGEvent(keyboardEventSource: nil, virtualKey: 9, keyDown: false) else { return }
@@ -812,7 +811,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
placeholder = "Enter English text to translate…"
accessibilityLabel = "English text to translate into Japanese"
case .emoji:
let tool = EmojiPickerController()
let tool = EmojiPickerController(defaults: defaults, pasteboard: pasteboard)
emojiPicker = tool
tool.input = { [weak self] in self?.searchField.stringValue ?? "" }
tool.focusSearch = { [weak self] in self?.window?.makeFirstResponder(self?.searchField) }
@@ -847,7 +846,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
}
translation?.closed = close
emojiPicker?.closed = close
actions.close()
actions.close(animated: false)
(window as? LauncherPanel)?.resetNumberHints()
openButton.keyEquivalent = ""
searchSymbol.isHidden = true