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 的不可靠行为)
|
// MARK: - 窗口级鼠标监控(替代 .onHover 和 background tracking area)
|
||||||
struct MouseTrackingArea: NSViewRepresentable {
|
class WindowMouseMonitor {
|
||||||
let onHoverChange: @MainActor (Bool) -> Void
|
static let shared = WindowMouseMonitor()
|
||||||
|
private var monitor: Any?
|
||||||
|
private var lastState: Bool?
|
||||||
|
|
||||||
func makeNSView(context: Context) -> MouseTrackingNSView {
|
func startMonitoring() {
|
||||||
let view = MouseTrackingNSView()
|
guard monitor == nil else { return }
|
||||||
view.onHoverChange = onHoverChange
|
monitor = NSEvent.addLocalMonitorForEvents(matching: [.mouseMoved]) { [weak self] event in
|
||||||
return view
|
self?.handleMouseMoved(event)
|
||||||
}
|
return event
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
let area = NSTrackingArea(
|
|
||||||
rect: .zero,
|
|
||||||
options: [.mouseEnteredAndExited, .activeInKeyWindow, .inVisibleRect],
|
|
||||||
owner: self,
|
|
||||||
userInfo: nil
|
|
||||||
)
|
|
||||||
addTrackingArea(area)
|
|
||||||
trackingArea = area
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func mouseEntered(with event: NSEvent) {
|
private func handleMouseMoved(_ event: NSEvent) {
|
||||||
guard !isHovering else { return }
|
guard let win = NSApplication.shared.windows.first(where: { $0.isVisible && !($0 is NSPanel) }) else { return }
|
||||||
isHovering = true
|
let mouseScreenPos = NSEvent.mouseLocation
|
||||||
onHoverChange?(true)
|
let isInside = win.frame.contains(mouseScreenPos)
|
||||||
}
|
guard isInside != lastState else { return }
|
||||||
|
lastState = isInside
|
||||||
override func mouseExited(with event: NSEvent) {
|
NSLog("[MiniPlayer] WindowMouseMonitor: mouseInside=%d", isInside)
|
||||||
guard isHovering else { return }
|
// 防抖:状态没变就不动
|
||||||
isHovering = false
|
let currentlyVisible = win.titleVisibility == .visible
|
||||||
onHoverChange?(false)
|
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 {
|
var body: some View {
|
||||||
ZStack(alignment: .topLeading) {
|
ZStack(alignment: .topLeading) {
|
||||||
Color.black.ignoresSafeArea()
|
Color.black.ignoresSafeArea()
|
||||||
@ -375,9 +350,6 @@ struct PlayerContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background(MouseTrackingArea(onHoverChange: { hovering in
|
|
||||||
setWindowTitleBarVisible(hovering)
|
|
||||||
}))
|
|
||||||
.onAppear {
|
.onAppear {
|
||||||
// 初始状态:标题栏完全隐藏(含红绿灯按钮)
|
// 初始状态:标题栏完全隐藏(含红绿灯按钮)
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||||
@ -388,6 +360,12 @@ struct PlayerContentView: View {
|
|||||||
win.standardWindowButton(.closeButton)?.isHidden = true
|
win.standardWindowButton(.closeButton)?.isHidden = true
|
||||||
win.standardWindowButton(.miniaturizeButton)?.isHidden = true
|
win.standardWindowButton(.miniaturizeButton)?.isHidden = true
|
||||||
win.standardWindowButton(.zoomButton)?.isHidden = true
|
win.standardWindowButton(.zoomButton)?.isHidden = true
|
||||||
|
|
||||||
|
// 启用 mouseMoved 事件(默认不发送)
|
||||||
|
win.acceptsMouseMovedEvents = true
|
||||||
|
|
||||||
|
// 用全局鼠标事件监听器替代 .onHover
|
||||||
|
WindowMouseMonitor.shared.startMonitoring()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: showToolbar) { _, newValue in
|
.onChange(of: showToolbar) { _, newValue in
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user