refactor: unify custom shortcut indicators across the app
This commit is contained in:
@@ -242,7 +242,7 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
||||
private struct ActionEntry {
|
||||
let view = LauncherRowView()
|
||||
let button: NSButton
|
||||
let returnKey = NSTextField(labelWithString: "↵")
|
||||
let returnKey = ShortcutIndicator("↵")
|
||||
|
||||
init(title: String, symbol: String, color: NSColor) {
|
||||
button = NSButton(title: title, target: nil, action: nil)
|
||||
@@ -259,14 +259,6 @@ private struct ActionEntry {
|
||||
icon.contentTintColor = color
|
||||
icon.imageScaling = .scaleProportionallyDown
|
||||
icon.setAccessibilityElement(false)
|
||||
returnKey.font = .systemFont(ofSize: 14)
|
||||
returnKey.textColor = SunsetPalette.secondaryText
|
||||
returnKey.alignment = .center
|
||||
returnKey.wantsLayer = true
|
||||
returnKey.layer?.cornerRadius = 5
|
||||
returnKey.layer?.borderWidth = 1
|
||||
returnKey.layer?.borderColor = SunsetPalette.secondaryText.withAlphaComponent(0.35).cgColor
|
||||
returnKey.setAccessibilityElement(false)
|
||||
for child in [icon, button, returnKey] {
|
||||
child.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(child)
|
||||
@@ -281,9 +273,7 @@ private struct ActionEntry {
|
||||
button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
|
||||
button.trailingAnchor.constraint(equalTo: returnKey.leadingAnchor, constant: -12),
|
||||
returnKey.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -12),
|
||||
returnKey.centerYAnchor.constraint(equalTo: view.centerYAnchor),
|
||||
returnKey.widthAnchor.constraint(equalToConstant: 18),
|
||||
returnKey.heightAnchor.constraint(equalToConstant: 18)
|
||||
returnKey.centerYAnchor.constraint(equalTo: view.centerYAnchor)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import AppKit
|
||||
import ConjureKit
|
||||
import CoreText
|
||||
|
||||
@MainActor
|
||||
final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSearchFieldDelegate, NSTableViewDataSource, NSTableViewDelegate {
|
||||
@@ -268,12 +267,8 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
actionsButton.toolTip = "Actions for the selected application (Command-K)"
|
||||
actionsButton.isEnabled = false
|
||||
openButton.toolTip = "Open the selected application (Return). Use ↑ and ↓ to select."
|
||||
let returnKey = shortcutBadge("↵")
|
||||
let commandKey = shortcutBadge("⌘")
|
||||
let kKey = shortcutBadge("K")
|
||||
for badge in [returnKey, commandKey, kKey] {
|
||||
badge.font = openButton.font
|
||||
}
|
||||
let returnKey = ShortcutIndicator("↵")
|
||||
let actionKeys = ShortcutIndicator("⌘", "K")
|
||||
|
||||
let actionPill = NSVisualEffectView()
|
||||
actionPill.material = .popover
|
||||
@@ -281,7 +276,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
actionPill.state = .active
|
||||
roundMaterialCorners(actionPill, radius: 18)
|
||||
actionPill.layer?.borderWidth = 0.5
|
||||
for view in [openButton, returnKey, actionsButton, commandKey, kKey] {
|
||||
for view in [openButton, returnKey, actionsButton, actionKeys] {
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
actionPill.addSubview(view)
|
||||
}
|
||||
@@ -320,20 +315,15 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
openButton.centerYAnchor.constraint(equalTo: actionPill.centerYAnchor),
|
||||
returnKey.leadingAnchor.constraint(equalTo: openButton.trailingAnchor, constant: 8),
|
||||
actionsButton.leadingAnchor.constraint(equalTo: returnKey.trailingAnchor, constant: 14),
|
||||
commandKey.leadingAnchor.constraint(equalTo: actionsButton.trailingAnchor, constant: 8),
|
||||
commandKey.centerYAnchor.constraint(equalTo: actionPill.centerYAnchor),
|
||||
kKey.leadingAnchor.constraint(equalTo: commandKey.trailingAnchor, constant: 2),
|
||||
kKey.centerYAnchor.constraint(equalTo: actionPill.centerYAnchor),
|
||||
kKey.trailingAnchor.constraint(equalTo: actionPill.trailingAnchor, constant: -12),
|
||||
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),
|
||||
returnKey.widthAnchor.constraint(equalToConstant: 18),
|
||||
returnKey.heightAnchor.constraint(equalToConstant: 18),
|
||||
])
|
||||
window.effectiveAppearance.performAsCurrentDrawingAppearance {
|
||||
let border = SunsetPalette.secondaryText.withAlphaComponent(0.22).cgColor
|
||||
content.layer?.borderColor = border
|
||||
returnKey.layer?.borderColor = SunsetPalette.secondaryText.withAlphaComponent(0.35).cgColor
|
||||
actionPill.layer?.borderColor = SunsetPalette.accent.withAlphaComponent(0.35).cgColor
|
||||
actionPill.layer?.backgroundColor = SunsetPalette.accent.withAlphaComponent(0.08).cgColor
|
||||
}
|
||||
@@ -704,41 +694,3 @@ func roundMaterialCorners(_ view: NSVisualEffectView, radius: CGFloat) {
|
||||
view.maskImage = mask
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func shortcutBadge(_ title: String) -> NSTextField {
|
||||
let badge = ShortcutBadge(labelWithString: title)
|
||||
badge.font = .systemFont(ofSize: 14)
|
||||
badge.textColor = SunsetPalette.secondaryText
|
||||
badge.alignment = .center
|
||||
badge.wantsLayer = true
|
||||
badge.layer?.cornerRadius = 5
|
||||
badge.layer?.borderWidth = 1
|
||||
badge.layer?.borderColor = SunsetPalette.secondaryText.withAlphaComponent(0.35).cgColor
|
||||
badge.setAccessibilityElement(false)
|
||||
badge.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
badge.widthAnchor.constraint(equalToConstant: 18),
|
||||
badge.heightAnchor.constraint(equalToConstant: 18)
|
||||
])
|
||||
return badge
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private final class ShortcutBadge: NSTextField {
|
||||
// NSTextField's horizontal alignment insets otherwise make an 18-point badge 22 points wide.
|
||||
override var alignmentRectInsets: NSEdgeInsets { NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) }
|
||||
|
||||
override func draw(_ dirtyRect: NSRect) {
|
||||
guard let context = NSGraphicsContext.current?.cgContext else { return }
|
||||
let line = CTLineCreateWithAttributedString(attributedStringValue)
|
||||
let ink = CTLineGetBoundsWithOptions(line, .useGlyphPathBounds)
|
||||
context.saveGState()
|
||||
context.textMatrix = .identity
|
||||
// Center visible ink, not the font's asymmetric ascender/descender line box.
|
||||
context.translateBy(x: bounds.midX - ink.midX, y: bounds.midY + ink.midY)
|
||||
context.scaleBy(x: 1, y: -1)
|
||||
context.textPosition = .zero
|
||||
CTLineDraw(line, context)
|
||||
context.restoreGState()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import AppKit
|
||||
import CoreText
|
||||
|
||||
@MainActor
|
||||
final class ShortcutIndicator: NSStackView {
|
||||
init(_ keys: String...) {
|
||||
super.init(frame: .zero)
|
||||
orientation = .horizontal
|
||||
alignment = .centerY
|
||||
spacing = 2
|
||||
setHuggingPriority(.required, for: .horizontal)
|
||||
setHuggingPriority(.required, for: .vertical)
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
setAccessibilityElement(false)
|
||||
for key in keys {
|
||||
let badge = ShortcutKeyBadge(labelWithString: key)
|
||||
badge.font = .systemFont(ofSize: 12, weight: .medium)
|
||||
badge.textColor = SunsetPalette.secondaryText
|
||||
badge.alignment = .center
|
||||
badge.wantsLayer = true
|
||||
badge.layer?.cornerRadius = 5
|
||||
badge.layer?.borderWidth = 1
|
||||
badge.layer?.borderColor = SunsetPalette.secondaryText.withAlphaComponent(0.35).cgColor
|
||||
badge.setAccessibilityElement(false)
|
||||
badge.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
badge.widthAnchor.constraint(equalToConstant: 18),
|
||||
badge.heightAnchor.constraint(equalToConstant: 18)
|
||||
])
|
||||
addArrangedSubview(badge)
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("Conjure creates shortcut indicators programmatically") }
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private final class ShortcutKeyBadge: NSTextField {
|
||||
// NSTextField's horizontal alignment insets otherwise make an 18-point badge 22 points wide.
|
||||
override var alignmentRectInsets: NSEdgeInsets { NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) }
|
||||
|
||||
override func draw(_ dirtyRect: NSRect) {
|
||||
guard let context = NSGraphicsContext.current?.cgContext else { return }
|
||||
let line = CTLineCreateWithAttributedString(attributedStringValue)
|
||||
let ink = CTLineGetBoundsWithOptions(line, .useGlyphPathBounds)
|
||||
context.saveGState()
|
||||
context.textMatrix = .identity
|
||||
// Center visible ink, not the font's asymmetric ascender/descender line box.
|
||||
context.translateBy(x: bounds.midX - ink.midX, y: bounds.midY + ink.midY)
|
||||
context.scaleBy(x: 1, y: -1)
|
||||
context.textPosition = .zero
|
||||
CTLineDraw(line, context)
|
||||
context.restoreGState()
|
||||
}
|
||||
}
|
||||
@@ -118,9 +118,8 @@ final class UninstallReviewController: NSViewController, NSSearchFieldDelegate,
|
||||
pill.layer?.borderWidth = 0.5
|
||||
pill.layer?.borderColor = SunsetPalette.accent.withAlphaComponent(0.35).cgColor
|
||||
pill.layer?.backgroundColor = SunsetPalette.accent.withAlphaComponent(0.08).cgColor
|
||||
let commandKey = shortcutBadge("⌘")
|
||||
let returnKey = shortcutBadge("↵")
|
||||
for child in [remove, commandKey, returnKey] {
|
||||
let shortcut = ShortcutIndicator("⌘", "↵")
|
||||
for child in [remove, shortcut] {
|
||||
child.translatesAutoresizingMaskIntoConstraints = false
|
||||
pill.addSubview(child)
|
||||
}
|
||||
@@ -128,11 +127,9 @@ final class UninstallReviewController: NSViewController, NSSearchFieldDelegate,
|
||||
pill.heightAnchor.constraint(equalToConstant: 36),
|
||||
remove.leadingAnchor.constraint(equalTo: pill.leadingAnchor, constant: 14),
|
||||
remove.centerYAnchor.constraint(equalTo: pill.centerYAnchor),
|
||||
commandKey.leadingAnchor.constraint(equalTo: remove.trailingAnchor, constant: 8),
|
||||
commandKey.centerYAnchor.constraint(equalTo: pill.centerYAnchor),
|
||||
returnKey.leadingAnchor.constraint(equalTo: commandKey.trailingAnchor, constant: 2),
|
||||
returnKey.centerYAnchor.constraint(equalTo: pill.centerYAnchor),
|
||||
returnKey.trailingAnchor.constraint(equalTo: pill.trailingAnchor, constant: -12)
|
||||
shortcut.leadingAnchor.constraint(equalTo: remove.trailingAnchor, constant: 8),
|
||||
shortcut.centerYAnchor.constraint(equalTo: pill.centerYAnchor),
|
||||
shortcut.trailingAnchor.constraint(equalTo: pill.trailingAnchor, constant: -12)
|
||||
])
|
||||
let footer = NSStackView(views: [NSView(), pill])
|
||||
footer.orientation = .horizontal
|
||||
|
||||
Reference in New Issue
Block a user