Files
ihatepdfs/Sources/IHatePDFs/ReturnKeyCommitMonitor.swift

107 lines
3.1 KiB
Swift
Raw Normal View History

2026-06-18 16:44:19 -07:00
import AppKit
2026-06-24 17:51:26 -07:00
import IHatePDFsCore
2026-06-18 16:44:19 -07:00
import SwiftUI
extension View {
2026-06-24 17:51:26 -07:00
func commitOnPlainReturn(isEnabled: Bool = true, _ action: @escaping () -> Void) -> some View {
modifier(ReturnKeyCommitMonitor(isEnabled: isEnabled, action: action))
2026-06-18 16:44:19 -07:00
}
}
private struct ReturnKeyCommitMonitor: ViewModifier {
2026-06-24 17:51:26 -07:00
let isEnabled: Bool
2026-06-18 16:44:19 -07:00
let action: () -> Void
@State private var monitor: Any?
2026-06-24 17:51:26 -07:00
@State private var eventWindowBox = EventWindowBox()
2026-06-18 16:44:19 -07:00
func body(content: Content) -> some View {
content
2026-06-24 17:51:26 -07:00
.background(
EventWindowReader { window in
eventWindowBox.windowID = window.map(ObjectIdentifier.init)
}
)
2026-06-18 16:44:19 -07:00
.onAppear {
2026-06-24 17:51:26 -07:00
eventWindowBox.isEnabled = isEnabled
2026-06-18 16:44:19 -07:00
installMonitor()
}
2026-06-24 17:51:26 -07:00
.onChange(of: isEnabled) { value in
eventWindowBox.isEnabled = value
}
2026-06-18 16:44:19 -07:00
.onDisappear {
removeMonitor()
}
}
private func installMonitor() {
removeMonitor()
2026-06-24 17:51:26 -07:00
let eventWindowBox = eventWindowBox
2026-06-18 16:44:19 -07:00
monitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in
2026-06-24 17:51:26 -07:00
guard eventWindowBox.isEnabled,
shouldCommit(event),
eventWindowBox.windowID.map({ event.window.map(ObjectIdentifier.init) == $0 }) == true
else {
return event
}
2026-06-18 16:44:19 -07:00
action()
return nil
}
}
private func removeMonitor() {
guard let monitor else { return }
NSEvent.removeMonitor(monitor)
self.monitor = nil
}
2026-06-24 17:51:26 -07:00
private func shouldCommit(_ event: NSEvent) -> Bool {
let textView = event.window?.firstResponder as? NSTextView
let isEditableMultilineText = textView?.isEditable == true && textView?.isFieldEditor == false
return ReturnKeyCommitPolicy.shouldCommit(
keyCode: UInt16(event.keyCode),
shift: event.modifierFlags.contains(.shift),
option: event.modifierFlags.contains(.option),
command: event.modifierFlags.contains(.command),
control: event.modifierFlags.contains(.control),
isEditableMultilineText: isEditableMultilineText
)
}
}
2026-06-18 16:44:19 -07:00
2026-06-24 17:51:26 -07:00
private final class EventWindowBox {
var windowID: ObjectIdentifier?
var isEnabled = true
}
private struct EventWindowReader: NSViewRepresentable {
let onWindowChange: (NSWindow?) -> Void
func makeNSView(context: Context) -> WindowReportingView {
let view = WindowReportingView()
view.onWindowChange = onWindowChange
return view
}
func updateNSView(_ view: WindowReportingView, context: Context) {
view.onWindowChange = onWindowChange
view.reportWindow()
}
}
private final class WindowReportingView: NSView {
var onWindowChange: ((NSWindow?) -> Void)?
override func viewDidMoveToWindow() {
super.viewDidMoveToWindow()
reportWindow()
}
func reportWindow() {
DispatchQueue.main.async { [weak self] in
guard let self else { return }
onWindowChange?(window)
}
2026-06-18 16:44:19 -07:00
}
}