fix: 用 NSEvent.addLocalMonitorForEvents 替代 .onHover/tracking area
之前的方案(.onHover 和 .background tracking area)都不可靠: - .onHover 在 macOS 上行为不稳定 - .background() 里的 NSView 被 AVPlayerLayer 挡住收不到鼠标事件 新方案:用 NSEvent.addLocalMonitorForEvents 在应用层监听 mouseMoved 事件, 直接检测鼠标是否在窗口 frame 内,完全绕过 SwiftUI 的 hover 机制。 加上双层防抖(lastState + currentlyVisible 检查)避免重复设置。
This commit is contained in:
parent
fde83a87bc
commit
16fdd72271
@ -223,51 +223,35 @@ class PlayerLayerHostView: NSView {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 可靠的鼠标追踪(替代 SwiftUI .onHover 的不可靠行为)
|
||||
struct MouseTrackingArea: NSViewRepresentable {
|
||||
let onHoverChange: @MainActor (Bool) -> Void
|
||||
// MARK: - 窗口级鼠标监控(替代 .onHover 和 background tracking area)
|
||||
class WindowMouseMonitor {
|
||||
static let shared = WindowMouseMonitor()
|
||||
private var monitor: Any?
|
||||
private var lastState: Bool?
|
||||
|
||||
func makeNSView(context: Context) -> MouseTrackingNSView {
|
||||
let view = MouseTrackingNSView()
|
||||
view.onHoverChange = onHoverChange
|
||||
return view
|
||||
}
|
||||
|
||||
func updateNSView(_ nsView: MouseTrackingNSView, context: Context) {
|
||||
nsView.onHoverChange = onHoverChange
|
||||
}
|
||||
}
|
||||
|
||||
class MouseTrackingNSView: NSView {
|
||||
var onHoverChange: (@MainActor (Bool) -> Void)?
|
||||
private var trackingArea: NSTrackingArea?
|
||||
private var isHovering = false
|
||||
|
||||
override func updateTrackingAreas() {
|
||||
super.updateTrackingAreas()
|
||||
if let existing = trackingArea {
|
||||
removeTrackingArea(existing)
|
||||
func startMonitoring() {
|
||||
guard monitor == nil else { return }
|
||||
monitor = NSEvent.addLocalMonitorForEvents(matching: [.mouseMoved]) { [weak self] event in
|
||||
self?.handleMouseMoved(event)
|
||||
return event
|
||||
}
|
||||
let area = NSTrackingArea(
|
||||
rect: .zero,
|
||||
options: [.mouseEnteredAndExited, .activeInKeyWindow, .inVisibleRect],
|
||||
owner: self,
|
||||
userInfo: nil
|
||||
)
|
||||
addTrackingArea(area)
|
||||
trackingArea = area
|
||||
}
|
||||
|
||||
override func mouseEntered(with event: NSEvent) {
|
||||
guard !isHovering else { return }
|
||||
isHovering = true
|
||||
onHoverChange?(true)
|
||||
}
|
||||
|
||||
override func mouseExited(with event: NSEvent) {
|
||||
guard isHovering else { return }
|
||||
isHovering = false
|
||||
onHoverChange?(false)
|
||||
private func handleMouseMoved(_ event: NSEvent) {
|
||||
guard let win = NSApplication.shared.windows.first(where: { $0.isVisible && !($0 is NSPanel) }) else { return }
|
||||
let mouseScreenPos = NSEvent.mouseLocation
|
||||
let isInside = win.frame.contains(mouseScreenPos)
|
||||
guard isInside != lastState else { return }
|
||||
lastState = isInside
|
||||
NSLog("[MiniPlayer] WindowMouseMonitor: mouseInside=%d", isInside)
|
||||
// 防抖:状态没变就不动
|
||||
let currentlyVisible = win.titleVisibility == .visible
|
||||
guard currentlyVisible != isInside else { return }
|
||||
win.titlebarAppearsTransparent = !isInside
|
||||
win.titleVisibility = isInside ? .visible : .hidden
|
||||
win.standardWindowButton(.closeButton)?.isHidden = !isInside
|
||||
win.standardWindowButton(.miniaturizeButton)?.isHidden = !isInside
|
||||
win.standardWindowButton(.zoomButton)?.isHidden = !isInside
|
||||
}
|
||||
}
|
||||
|
||||
@ -289,15 +273,6 @@ struct PlayerContentView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func setWindowTitleBarVisible(_ visible: Bool) {
|
||||
guard let win = NSApplication.shared.windows.first(where: { $0.isVisible }) else { return }
|
||||
win.titlebarAppearsTransparent = !visible
|
||||
win.titleVisibility = visible ? .visible : .hidden
|
||||
win.standardWindowButton(.closeButton)?.isHidden = !visible
|
||||
win.standardWindowButton(.miniaturizeButton)?.isHidden = !visible
|
||||
win.standardWindowButton(.zoomButton)?.isHidden = !visible
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .topLeading) {
|
||||
Color.black.ignoresSafeArea()
|
||||
@ -375,9 +350,6 @@ struct PlayerContentView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.background(MouseTrackingArea(onHoverChange: { hovering in
|
||||
setWindowTitleBarVisible(hovering)
|
||||
}))
|
||||
.onAppear {
|
||||
// 初始状态:标题栏完全隐藏(含红绿灯按钮)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
@ -388,6 +360,12 @@ struct PlayerContentView: View {
|
||||
win.standardWindowButton(.closeButton)?.isHidden = true
|
||||
win.standardWindowButton(.miniaturizeButton)?.isHidden = true
|
||||
win.standardWindowButton(.zoomButton)?.isHidden = true
|
||||
|
||||
// 启用 mouseMoved 事件(默认不发送)
|
||||
win.acceptsMouseMovedEvents = true
|
||||
|
||||
// 用全局鼠标事件监听器替代 .onHover
|
||||
WindowMouseMonitor.shared.startMonitoring()
|
||||
}
|
||||
}
|
||||
.onChange(of: showToolbar) { _, newValue in
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user