Files
steipete--codexbar/Sources/CodexBar/MouseLocationReader.swift
T
2026-07-13 12:22:33 +08:00

70 lines
2.2 KiB
Swift

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