62 lines
2.6 KiB
Swift
62 lines
2.6 KiB
Swift
import AppKit
|
|
import CoreText
|
|
|
|
@MainActor
|
|
final class ShortcutIndicator: NSStackView {
|
|
init(_ keys: String..., backgroundColor: NSColor? = nil, fontSize: CGFloat = 12, keySize: CGFloat = 18) {
|
|
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: fontSize, 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.layer?.backgroundColor = backgroundColor?.cgColor
|
|
badge.setAccessibilityElement(false)
|
|
badge.translatesAutoresizingMaskIntoConstraints = false
|
|
NSLayoutConstraint.activate([
|
|
badge.widthAnchor.constraint(equalToConstant: keySize),
|
|
badge.heightAnchor.constraint(equalToConstant: keySize)
|
|
])
|
|
addArrangedSubview(badge)
|
|
}
|
|
}
|
|
|
|
func setSingleKey(_ key: String) {
|
|
precondition(arrangedSubviews.count == 1)
|
|
(arrangedSubviews[0] as? NSTextField)?.stringValue = key
|
|
}
|
|
|
|
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()
|
|
}
|
|
}
|