69 lines · 2.2 KB · Swift
RawDownload
import AppKitimport SwiftUI /// Lightweight NSView-based mouse tracking with local coordinates.////// Why: SwiftUI's `onHover` doesn't provide location, but we want "hover a bar to see values" on macOS.@MainActorstruct MouseLocationReader: NSViewRepresentable {    let onMoved: (CGPoint?) -> Void     func makeNSView(context: Context) -> TrackingView {        let view = TrackingView()        view.onMoved = self.onMoved        return view    }     func updateNSView(_ nsView: TrackingView, context: Context) {        nsView.onMoved = self.onMoved    }     final class TrackingView: NSView {        var onMoved: ((CGPoint?) -> Void)?        private var trackingArea: NSTrackingArea?         override var isFlipped: Bool {            true        }         override func viewDidMoveToWindow() {            super.viewDidMoveToWindow()            self.window?.acceptsMouseMovedEvents = true            self.updateTrackingAreas()        }         override func updateTrackingAreas() {            super.updateTrackingAreas()            if let trackingArea {                self.removeTrackingArea(trackingArea)            }             let options: NSTrackingArea.Options = [                // NSMenu popups aren't "key windows", so `.activeInKeyWindow` would drop events and cause hover                // state to flicker. `.activeAlways` keeps tracking stable while the menu is open.                .activeAlways,                .inVisibleRect,                .mouseEnteredAndExited,                .mouseMoved,            ]            let area = NSTrackingArea(rect: .zero, options: options, owner: self, userInfo: nil)            self.addTrackingArea(area)            self.trackingArea = area        }         override func mouseEntered(with event: NSEvent) {            super.mouseEntered(with: event)            self.onMoved?(self.convert(event.locationInWindow, from: nil))        }         override func mouseMoved(with event: NSEvent) {            super.mouseMoved(with: event)            self.onMoved?(self.convert(event.locationInWindow, from: nil))        }         override func mouseExited(with event: NSEvent) {            super.mouseExited(with: event)            self.onMoved?(nil)        }    }}