feat: animate actions panel and footer transitions
This commit is contained in:
@@ -10,16 +10,19 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
||||
private let applicationLabel = NSTextField(labelWithString: "")
|
||||
private var mouseMonitor: Any?
|
||||
private weak var coveredPill: NSView?
|
||||
private var coveredPillOpacity: Float = 1
|
||||
private var coveredPillAlpha: CGFloat = 1
|
||||
private var isClosing = false
|
||||
private var transitionGeneration = 0
|
||||
private var panel: ActionsPanelView { view as! ActionsPanelView }
|
||||
var uninstall: (() -> Void)?
|
||||
var closed: (() -> Void)?
|
||||
var isShown: Bool { viewIfLoaded?.superview != nil }
|
||||
var isShown: Bool { viewIfLoaded?.superview != nil && !isClosing }
|
||||
|
||||
init() { super.init(nibName: nil, bundle: nil) }
|
||||
required init?(coder: NSCoder) { fatalError("Conjure creates actions programmatically") }
|
||||
|
||||
override func loadView() {
|
||||
view = NSView()
|
||||
view = ActionsPanelView()
|
||||
view.wantsLayer = true
|
||||
view.layer?.shadowColor = NSColor.black.cgColor
|
||||
view.layer?.shadowOpacity = 0.35
|
||||
@@ -138,16 +141,27 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
||||
applicationLabel.stringValue = application.name
|
||||
search.stringValue = ""
|
||||
controlTextDidChange(Notification(name: NSControl.textDidChangeNotification))
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
host.addSubview(view)
|
||||
NSLayoutConstraint.activate([
|
||||
view.trailingAnchor.constraint(equalTo: pill.trailingAnchor),
|
||||
view.bottomAnchor.constraint(equalTo: pill.bottomAnchor)
|
||||
])
|
||||
coveredPill = pill
|
||||
coveredPillOpacity = pill.layer?.opacity ?? 1
|
||||
pill.layer?.opacity = 0
|
||||
transitionGeneration += 1
|
||||
isClosing = false
|
||||
panel.acceptsInteraction = true
|
||||
panel.setAccessibilityHidden(false)
|
||||
if view.superview == nil {
|
||||
view.alphaValue = 0
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
host.addSubview(view)
|
||||
NSLayoutConstraint.activate([
|
||||
view.trailingAnchor.constraint(equalTo: pill.trailingAnchor),
|
||||
view.bottomAnchor.constraint(equalTo: pill.bottomAnchor)
|
||||
])
|
||||
coveredPill = pill
|
||||
coveredPillAlpha = pill.alphaValue
|
||||
}
|
||||
pill.setAccessibilityHidden(true)
|
||||
NSAnimationContext.runAnimationGroup { context in
|
||||
context.duration = NSWorkspace.shared.accessibilityDisplayShouldReduceMotion ? 0 : 0.16
|
||||
view.animator().alphaValue = 1
|
||||
pill.animator().alphaValue = 0
|
||||
}
|
||||
window.makeFirstResponder(search)
|
||||
(search.currentEditor() as? NSTextView)?.insertionPointColor = SunsetPalette.accent
|
||||
mouseMonitor = NSEvent.addLocalMonitorForEvents(matching: [.leftMouseDown, .rightMouseDown]) { [weak self, weak pill] event in
|
||||
@@ -165,12 +179,26 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
||||
|
||||
func close() {
|
||||
guard isShown else { return }
|
||||
transitionGeneration += 1
|
||||
let generation = transitionGeneration
|
||||
isClosing = true
|
||||
panel.acceptsInteraction = false
|
||||
panel.setAccessibilityHidden(true)
|
||||
if let mouseMonitor { NSEvent.removeMonitor(mouseMonitor) }
|
||||
mouseMonitor = nil
|
||||
coveredPill?.layer?.opacity = coveredPillOpacity
|
||||
coveredPill?.setAccessibilityHidden(false)
|
||||
coveredPill = nil
|
||||
view.removeFromSuperview()
|
||||
NSAnimationContext.runAnimationGroup { context in
|
||||
context.duration = NSWorkspace.shared.accessibilityDisplayShouldReduceMotion ? 0 : 0.12
|
||||
view.animator().alphaValue = 0
|
||||
coveredPill?.animator().alphaValue = coveredPillAlpha
|
||||
} completionHandler: { [weak self] in
|
||||
MainActor.assumeIsolated {
|
||||
guard let self, self.transitionGeneration == generation, self.isClosing else { return }
|
||||
self.view.removeFromSuperview()
|
||||
self.coveredPill = nil
|
||||
self.isClosing = false
|
||||
}
|
||||
}
|
||||
closed?()
|
||||
}
|
||||
|
||||
@@ -199,6 +227,15 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private final class ActionsPanelView: NSView {
|
||||
var acceptsInteraction = true
|
||||
|
||||
override func hitTest(_ point: NSPoint) -> NSView? {
|
||||
acceptsInteraction ? super.hitTest(point) : nil
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private final class ActionIconView: NSImageView {
|
||||
// SF Symbol optical insets otherwise enlarge a 22-point icon box beyond application icon sizing.
|
||||
|
||||
Reference in New Issue
Block a user