refactor: share footer action pill across launcher tools
This commit is contained in:
@@ -65,6 +65,8 @@ Right-click the launcher background for Settings and Quit. Actions include favor
|
|||||||
|
|
||||||
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 rebuilds the complete native view and layer tree, rather than resizing a retained presentation between the footer and header. Callbacks and selection behavior stay in the controller. Layout finishes 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 rebuilds the complete native view and layer tree, rather than resizing a retained presentation between the footer and header. Callbacks and selection behavior stay in the controller. Layout finishes before the opening fade, including when switching popup types.
|
||||||
|
|
||||||
|
The launcher, emoji picker, and Translate share `ActionPill` for footer styling, alignment, spacing, and keyboard indicators: primary action with Return first, then Actions with separate Command and K keycaps. Controllers supply the buttons and retain their targets, enabled state, and popup behavior.
|
||||||
|
|
||||||
### Global shortcuts and launch at login
|
### Global shortcuts and launch at login
|
||||||
|
|
||||||
In Settings, choose a **Global shortcut destination**: **Applications**, **Conjure Emoji**, **Conjure Notes**, or **Conjure Translate**. Choose **Record Shortcut…** and press a combination containing Command, Option, or Control. For example, select **Conjure Emoji** and record Command-Shift-Space to open the emoji picker directly. **Clear** disables only the selected destination's shortcut. No shortcuts are assigned by default; existing launcher bindings are preserved.
|
In Settings, choose a **Global shortcut destination**: **Applications**, **Conjure Emoji**, **Conjure Notes**, or **Conjure Translate**. Choose **Record Shortcut…** and press a combination containing Command, Option, or Control. For example, select **Conjure Emoji** and record Command-Shift-Space to open the emoji picker directly. **Clear** disables only the selected destination's shortcut. No shortcuts are assigned by default; existing launcher bindings are preserved.
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import AppKit
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
final class ActionPill: NSView {
|
||||||
|
init(primaryButton: NSButton, actionsButton: NSButton) {
|
||||||
|
super.init(frame: .zero)
|
||||||
|
wantsLayer = true
|
||||||
|
layer?.cornerRadius = 18
|
||||||
|
layer?.borderWidth = 0.5
|
||||||
|
layer?.borderColor = SunsetPalette.accent.withAlphaComponent(0.35).cgColor
|
||||||
|
layer?.backgroundColor = SunsetPalette.background.withAlphaComponent(0.25).cgColor
|
||||||
|
for button in [primaryButton, actionsButton] {
|
||||||
|
button.isBordered = false
|
||||||
|
button.font = .systemFont(ofSize: 12, weight: .medium)
|
||||||
|
button.contentTintColor = SunsetPalette.text
|
||||||
|
}
|
||||||
|
primaryButton.keyEquivalent = "\r"
|
||||||
|
primaryButton.keyEquivalentModifierMask = []
|
||||||
|
actionsButton.title = "Actions"
|
||||||
|
actionsButton.keyEquivalent = "k"
|
||||||
|
actionsButton.keyEquivalentModifierMask = .command
|
||||||
|
let returnKey = 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),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) { fatalError("Conjure creates action pills programmatically") }
|
||||||
|
}
|
||||||
@@ -61,32 +61,17 @@ final class EmojiPickerController: NSViewController, NSCollectionViewDataSource,
|
|||||||
status.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
status.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||||
copyButton.target = self
|
copyButton.target = self
|
||||||
copyButton.action = #selector(copyAndPasteSelection)
|
copyButton.action = #selector(copyAndPasteSelection)
|
||||||
copyButton.keyEquivalent = "\r"
|
|
||||||
copyButton.isBordered = false
|
|
||||||
copyButton.font = .systemFont(ofSize: 12, weight: .medium)
|
|
||||||
copyButton.contentTintColor = SunsetPalette.text
|
|
||||||
actionsButton.target = self
|
actionsButton.target = self
|
||||||
actionsButton.action = #selector(showActions)
|
actionsButton.action = #selector(showActions)
|
||||||
actionsButton.keyEquivalent = "k"
|
let pill = ActionPill(primaryButton: copyButton, actionsButton: actionsButton)
|
||||||
actionsButton.keyEquivalentModifierMask = .command
|
|
||||||
actionsButton.isBordered = false
|
|
||||||
actionsButton.font = copyButton.font
|
|
||||||
actionsButton.contentTintColor = SunsetPalette.text
|
|
||||||
let pill = NSStackView(views: [copyButton, ShortcutIndicator("↵"), actionsButton, ShortcutIndicator("⌘", "K")])
|
|
||||||
pill.spacing = 8
|
|
||||||
pill.edgeInsets = NSEdgeInsets(top: 0, left: 14, bottom: 0, right: 12)
|
|
||||||
pill.wantsLayer = true
|
|
||||||
pill.layer?.cornerRadius = 18
|
|
||||||
pill.layer?.borderWidth = 0.5
|
|
||||||
pill.layer?.borderColor = SunsetPalette.accent.withAlphaComponent(0.35).cgColor
|
|
||||||
pill.layer?.backgroundColor = SunsetPalette.background.withAlphaComponent(0.25).cgColor
|
|
||||||
let footer = NSStackView(views: [status, NSView(), pill])
|
let footer = NSStackView(views: [status, NSView(), pill])
|
||||||
pill.heightAnchor.constraint(equalToConstant: 36).isActive = true
|
|
||||||
actions.closed = { [weak self] in
|
actions.closed = { [weak self] in
|
||||||
self?.copyButton.keyEquivalent = "\r"
|
self?.copyButton.keyEquivalent = "\r"
|
||||||
self?.focusSearch?()
|
self?.focusSearch?()
|
||||||
}
|
}
|
||||||
footer.spacing = 8
|
footer.orientation = .horizontal
|
||||||
|
footer.alignment = .centerY
|
||||||
|
footer.spacing = 12
|
||||||
for child in [category, scroll, empty, footer] {
|
for child in [category, scroll, empty, footer] {
|
||||||
child.translatesAutoresizingMaskIntoConstraints = false
|
child.translatesAutoresizingMaskIntoConstraints = false
|
||||||
view.addSubview(child)
|
view.addSubview(child)
|
||||||
@@ -101,7 +86,7 @@ final class EmojiPickerController: NSViewController, NSCollectionViewDataSource,
|
|||||||
empty.centerXAnchor.constraint(equalTo: scroll.centerXAnchor),
|
empty.centerXAnchor.constraint(equalTo: scroll.centerXAnchor),
|
||||||
empty.centerYAnchor.constraint(equalTo: scroll.centerYAnchor),
|
empty.centerYAnchor.constraint(equalTo: scroll.centerYAnchor),
|
||||||
footer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
|
footer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
|
||||||
footer.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
|
footer.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8),
|
||||||
footer.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8),
|
footer.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8),
|
||||||
footer.heightAnchor.constraint(equalToConstant: 36),
|
footer.heightAnchor.constraint(equalToConstant: 36),
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -330,36 +330,16 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
|||||||
emptyLabel.textColor = SunsetPalette.secondaryText
|
emptyLabel.textColor = SunsetPalette.secondaryText
|
||||||
emptyLabel.alignment = .center
|
emptyLabel.alignment = .center
|
||||||
|
|
||||||
openButton.isBordered = false
|
|
||||||
openButton.font = .systemFont(ofSize: 12, weight: .medium)
|
|
||||||
openButton.contentTintColor = SunsetPalette.text
|
|
||||||
openButton.keyEquivalent = "\r"
|
|
||||||
openButton.target = self
|
openButton.target = self
|
||||||
openButton.action = #selector(openSelection)
|
openButton.action = #selector(openSelection)
|
||||||
openButton.isEnabled = false
|
openButton.isEnabled = false
|
||||||
actionsButton.isBordered = false
|
|
||||||
actionsButton.title = "Actions"
|
|
||||||
actionsButton.font = .systemFont(ofSize: 12, weight: .medium)
|
|
||||||
actionsButton.contentTintColor = SunsetPalette.text
|
|
||||||
actionsButton.keyEquivalent = "k"
|
|
||||||
actionsButton.keyEquivalentModifierMask = .command
|
|
||||||
actionsButton.target = self
|
actionsButton.target = self
|
||||||
actionsButton.action = #selector(showActions)
|
actionsButton.action = #selector(showActions)
|
||||||
actionsButton.setAccessibilityLabel("Entry actions")
|
actionsButton.setAccessibilityLabel("Entry actions")
|
||||||
actionsButton.toolTip = "Actions for the selected application or command (Command-K)"
|
actionsButton.toolTip = "Actions for the selected application or command (Command-K)"
|
||||||
actionsButton.isEnabled = false
|
actionsButton.isEnabled = false
|
||||||
openButton.toolTip = "Open the selected application (Return). Use ↑ and ↓ to select."
|
openButton.toolTip = "Open the selected application (Return). Use ↑ and ↓ to select."
|
||||||
let returnKey = ShortcutIndicator("↵")
|
let actionPill = ActionPill(primaryButton: openButton, actionsButton: actionsButton)
|
||||||
let actionKeys = ShortcutIndicator("⌘", "K")
|
|
||||||
|
|
||||||
let actionPill = NSView()
|
|
||||||
actionPill.wantsLayer = true
|
|
||||||
actionPill.layer?.cornerRadius = 18
|
|
||||||
actionPill.layer?.borderWidth = 0.5
|
|
||||||
for view in [openButton, returnKey, actionsButton, actionKeys] {
|
|
||||||
view.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
actionPill.addSubview(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
let footer = NSStackView(views: [statusLabel, NSView(), actionPill])
|
let footer = NSStackView(views: [statusLabel, NSView(), actionPill])
|
||||||
footer.orientation = .horizontal
|
footer.orientation = .horizontal
|
||||||
@@ -388,22 +368,10 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
|||||||
footer.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -8),
|
footer.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -8),
|
||||||
footer.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -8),
|
footer.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -8),
|
||||||
footer.heightAnchor.constraint(equalToConstant: 36),
|
footer.heightAnchor.constraint(equalToConstant: 36),
|
||||||
actionPill.heightAnchor.constraint(equalToConstant: 36),
|
|
||||||
openButton.leadingAnchor.constraint(equalTo: actionPill.leadingAnchor, constant: 14),
|
|
||||||
openButton.centerYAnchor.constraint(equalTo: actionPill.centerYAnchor),
|
|
||||||
returnKey.leadingAnchor.constraint(equalTo: openButton.trailingAnchor, constant: 8),
|
|
||||||
actionsButton.leadingAnchor.constraint(equalTo: returnKey.trailingAnchor, constant: 14),
|
|
||||||
actionKeys.leadingAnchor.constraint(equalTo: actionsButton.trailingAnchor, constant: 8),
|
|
||||||
actionKeys.centerYAnchor.constraint(equalTo: actionPill.centerYAnchor),
|
|
||||||
actionKeys.trailingAnchor.constraint(equalTo: actionPill.trailingAnchor, constant: -12),
|
|
||||||
actionsButton.centerYAnchor.constraint(equalTo: actionPill.centerYAnchor),
|
|
||||||
returnKey.centerYAnchor.constraint(equalTo: actionPill.centerYAnchor),
|
|
||||||
])
|
])
|
||||||
window.effectiveAppearance.performAsCurrentDrawingAppearance {
|
window.effectiveAppearance.performAsCurrentDrawingAppearance {
|
||||||
let border = SunsetPalette.secondaryText.withAlphaComponent(0.22).cgColor
|
let border = SunsetPalette.secondaryText.withAlphaComponent(0.22).cgColor
|
||||||
content.layer?.borderColor = border
|
content.layer?.borderColor = border
|
||||||
actionPill.layer?.borderColor = SunsetPalette.accent.withAlphaComponent(0.35).cgColor
|
|
||||||
actionPill.layer?.backgroundColor = SunsetPalette.background.withAlphaComponent(0.25).cgColor
|
|
||||||
}
|
}
|
||||||
window.initialFirstResponder = searchField
|
window.initialFirstResponder = searchField
|
||||||
launcherViews = [header, sectionLabel, scrollView, emptyLabel, footer]
|
launcherViews = [header, sectionLabel, scrollView, emptyLabel, footer]
|
||||||
|
|||||||
@@ -102,11 +102,6 @@ final class TranslationController: NSViewController {
|
|||||||
languageButton.setAccessibilityLabel("Translation languages")
|
languageButton.setAccessibilityLabel("Translation languages")
|
||||||
actionsButton.target = self
|
actionsButton.target = self
|
||||||
actionsButton.action = #selector(showActions)
|
actionsButton.action = #selector(showActions)
|
||||||
actionsButton.keyEquivalent = "k"
|
|
||||||
actionsButton.keyEquivalentModifierMask = .command
|
|
||||||
actionsButton.isBordered = false
|
|
||||||
actionsButton.font = .systemFont(ofSize: 12, weight: .medium)
|
|
||||||
actionsButton.contentTintColor = SunsetPalette.text
|
|
||||||
actions.closed = { [weak self] in
|
actions.closed = { [weak self] in
|
||||||
self?.translateButton.keyEquivalent = "\r"
|
self?.translateButton.keyEquivalent = "\r"
|
||||||
self?.focusSearch?()
|
self?.focusSearch?()
|
||||||
@@ -122,31 +117,8 @@ final class TranslationController: NSViewController {
|
|||||||
name: Notification.Name("ConjureTranslationCredentialsChanged"), object: nil)
|
name: Notification.Name("ConjureTranslationCredentialsChanged"), object: nil)
|
||||||
translateButton.target = self
|
translateButton.target = self
|
||||||
translateButton.action = #selector(performPrimaryAction)
|
translateButton.action = #selector(performPrimaryAction)
|
||||||
translateButton.keyEquivalent = "\r"
|
|
||||||
translateButton.isBordered = false
|
|
||||||
translateButton.font = .systemFont(ofSize: 12, weight: .medium)
|
|
||||||
translateButton.contentTintColor = SunsetPalette.text
|
|
||||||
translateButton.toolTip = "Translate the input (Return). Escape dismisses Conjure."
|
translateButton.toolTip = "Translate the input (Return). Escape dismisses Conjure."
|
||||||
let returnKey = ShortcutIndicator("↵")
|
let actionPill = ActionPill(primaryButton: translateButton, actionsButton: actionsButton)
|
||||||
let actionPill = NSView()
|
|
||||||
stylePill(actionPill)
|
|
||||||
let actionsKey = ShortcutIndicator("⌘", "K")
|
|
||||||
for control in [translateButton, returnKey, actionsButton, actionsKey] {
|
|
||||||
control.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
actionPill.addSubview(control)
|
|
||||||
}
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
actionPill.heightAnchor.constraint(equalToConstant: 36),
|
|
||||||
translateButton.leadingAnchor.constraint(equalTo: actionPill.leadingAnchor, constant: 14),
|
|
||||||
translateButton.centerYAnchor.constraint(equalTo: actionPill.centerYAnchor),
|
|
||||||
returnKey.leadingAnchor.constraint(equalTo: translateButton.trailingAnchor, constant: 8),
|
|
||||||
returnKey.centerYAnchor.constraint(equalTo: actionPill.centerYAnchor),
|
|
||||||
actionsButton.leadingAnchor.constraint(equalTo: returnKey.trailingAnchor, constant: 14),
|
|
||||||
actionsButton.centerYAnchor.constraint(equalTo: actionPill.centerYAnchor),
|
|
||||||
actionsKey.leadingAnchor.constraint(equalTo: actionsButton.trailingAnchor, constant: 8),
|
|
||||||
actionsKey.centerYAnchor.constraint(equalTo: actionPill.centerYAnchor),
|
|
||||||
actionsKey.trailingAnchor.constraint(equalTo: actionPill.trailingAnchor, constant: -12),
|
|
||||||
])
|
|
||||||
let footer = NSStackView(views: [languageButton, status, settingsButton, NSView(), actionPill])
|
let footer = NSStackView(views: [languageButton, status, settingsButton, NSView(), actionPill])
|
||||||
footer.orientation = .horizontal
|
footer.orientation = .horizontal
|
||||||
footer.alignment = .centerY
|
footer.alignment = .centerY
|
||||||
|
|||||||
Reference in New Issue
Block a user