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
|
|
|
}
|
|
|
|
|
}
|