Refactor and Rename to CommandNotch

This commit is contained in:
2026-03-07 23:14:31 +11:00
parent 2bf1cbad2a
commit 5d161bb214
45 changed files with 76 additions and 69 deletions

View File

@@ -0,0 +1,111 @@
import SwiftUI
import AppKit
/// A clickable field that records a keyboard shortcut when focused.
/// Click it, press a key combination, and it saves the binding.
struct HotkeyRecorderView: View {
let label: String
@Binding var binding: HotkeyBinding
@State private var isRecording = false
var body: some View {
HStack {
Text(label)
.frame(width: 140, alignment: .leading)
HotkeyRecorderField(binding: $binding, isRecording: $isRecording)
.frame(width: 120, height: 24)
.background(
RoundedRectangle(cornerRadius: 6)
.fill(isRecording ? Color.accentColor.opacity(0.15) : Color.secondary.opacity(0.1))
)
.overlay(
RoundedRectangle(cornerRadius: 6)
.stroke(isRecording ? Color.accentColor : Color.secondary.opacity(0.3), lineWidth: 1)
)
}
}
}
/// NSViewRepresentable that captures key events when focused.
struct HotkeyRecorderField: NSViewRepresentable {
@Binding var binding: HotkeyBinding
@Binding var isRecording: Bool
func makeNSView(context: Context) -> HotkeyNSView {
let view = HotkeyNSView()
view.onKeyRecorded = { newBinding in
binding = newBinding
isRecording = false
}
view.onFocusChanged = { focused in
isRecording = focused
}
return view
}
func updateNSView(_ nsView: HotkeyNSView, context: Context) {
nsView.currentLabel = binding.displayString
nsView.showRecording = isRecording
nsView.needsDisplay = true
}
}
/// The actual NSView that handles key capture.
class HotkeyNSView: NSView {
var currentLabel: String = ""
var showRecording: Bool = false
var onKeyRecorded: ((HotkeyBinding) -> Void)?
var onFocusChanged: ((Bool) -> Void)?
override var acceptsFirstResponder: Bool { true }
override func draw(_ dirtyRect: NSRect) {
let text = showRecording ? "Press keys…" : currentLabel
let attrs: [NSAttributedString.Key: Any] = [
.font: NSFont.monospacedSystemFont(ofSize: 12, weight: .medium),
.foregroundColor: showRecording ? NSColor.controlAccentColor : NSColor.labelColor
]
let str = NSAttributedString(string: text, attributes: attrs)
let size = str.size()
let point = NSPoint(
x: (bounds.width - size.width) / 2,
y: (bounds.height - size.height) / 2
)
str.draw(at: point)
}
override func mouseDown(with event: NSEvent) {
window?.makeFirstResponder(self)
}
override func becomeFirstResponder() -> Bool {
onFocusChanged?(true)
return true
}
override func resignFirstResponder() -> Bool {
onFocusChanged?(false)
return true
}
override func keyDown(with event: NSEvent) {
guard showRecording else {
super.keyDown(with: event)
return
}
let relevantFlags: NSEvent.ModifierFlags = [.command, .shift, .control, .option]
let masked = event.modifierFlags.intersection(.deviceIndependentFlagsMask).intersection(relevantFlags)
// Require at least one modifier key
guard !masked.isEmpty else { return }
let binding = HotkeyBinding(modifiers: masked.rawValue, keyCode: event.keyCode)
onKeyRecorded?(binding)
// Resign first responder after recording
window?.makeFirstResponder(nil)
}
}