Files
conjure/Sources/Conjure/ActionPill.swift
T

84 lines
4.1 KiB
Swift

import AppKit
@MainActor
final class ActionPill: NSView {
private var themeObservation: ThemeObservation?
init(primaryButton: NSButton, actionsButton: NSButton, primaryCommand: String? = nil) {
super.init(frame: .zero)
wantsLayer = true
layer?.cornerRadius = 18
layer?.borderWidth = 0.5
layer?.borderColor = ThemePalette.accent.withAlphaComponent(0.35).cgColor
layer?.backgroundColor = ThemePalette.background.withAlphaComponent(0.25).cgColor
for button in [primaryButton, actionsButton] {
button.isBordered = false
button.font = .systemFont(ofSize: 12, weight: .medium)
button.contentTintColor = ThemePalette.text
}
primaryButton.keyEquivalent = primaryCommand ?? "\r"
primaryButton.keyEquivalentModifierMask = primaryCommand == nil ? [] : .command
actionsButton.title = "Actions"
actionsButton.keyEquivalent = "k"
actionsButton.keyEquivalentModifierMask = .command
let returnKey = primaryCommand.map { ShortcutIndicator("⌘", $0.uppercased()) } ?? ShortcutIndicator("↵")
let actionKeys = ShortcutIndicator("⌘", "K")
for control in [primaryButton, returnKey, actionsButton, actionKeys] {
control.translatesAutoresizingMaskIntoConstraints = false
addSubview(control)
}
NSLayoutConstraint.activate([
heightAnchor.constraint(equalToConstant: 36),
primaryButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 14),
primaryButton.centerYAnchor.constraint(equalTo: centerYAnchor),
returnKey.leadingAnchor.constraint(equalTo: primaryButton.trailingAnchor, constant: 8),
returnKey.centerYAnchor.constraint(equalTo: centerYAnchor),
actionsButton.leadingAnchor.constraint(equalTo: returnKey.trailingAnchor, constant: 14),
actionsButton.centerYAnchor.constraint(equalTo: centerYAnchor),
actionKeys.leadingAnchor.constraint(equalTo: actionsButton.trailingAnchor, constant: 8),
actionKeys.centerYAnchor.constraint(equalTo: centerYAnchor),
actionKeys.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12),
])
themeObservation = ThemeObservation { [weak self, weak primaryButton, weak actionsButton] in
self?.layer?.borderColor = ThemePalette.accent.withAlphaComponent(0.35).cgColor
self?.layer?.backgroundColor = ThemePalette.background.withAlphaComponent(0.25).cgColor
primaryButton?.contentTintColor = ThemePalette.text
actionsButton?.contentTintColor = ThemePalette.text
}
}
required init?(coder: NSCoder) { fatalError("Conjure creates action pills programmatically") }
}
/// Keeps palette subscriptions scoped to the existing control's lifetime.
@MainActor
final class ThemeObservation {
nonisolated(unsafe) private let token: NSObjectProtocol
init(_ apply: @escaping @MainActor () -> Void) {
ThemePalette.appearance.performAsCurrentDrawingAppearance(apply)
token = NotificationCenter.default.addObserver(
forName: ThemePalette.didChangeNotification, object: nil, queue: .main
) { _ in
MainActor.assumeIsolated { ThemePalette.appearance.performAsCurrentDrawingAppearance(apply) }
}
}
deinit { NotificationCenter.default.removeObserver(token) }
}
extension NSSearchField {
/// Recolors the current presentation without changing the query or field-editor selection.
func applyTheme() {
textColor = ThemePalette.text
if let placeholderAttributedString {
let placeholder = NSMutableAttributedString(attributedString: placeholderAttributedString)
placeholder.addAttribute(.foregroundColor, value: ThemePalette.secondaryText,
range: NSRange(location: 0, length: placeholder.length))
self.placeholderAttributedString = placeholder
}
if let editor = currentEditor() as? NSTextView {
editor.textColor = ThemePalette.text
}
}
}