import AppKit import 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. @MainActor struct 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) } } }