From 2766d250e972ffad203693df6b98b50734eefde8 Mon Sep 17 00:00:00 2001 From: Hariharan Mudaliar Date: Wed, 24 Dec 2025 12:51:03 +0530 Subject: [PATCH] BugFix: Gesture open close even when cursor kisses the top --- DynamicIsland/extensions/PanGesture.swift | 39 ++++++++++++++++++- .../managers/LockScreenWeatherManager.swift | 2 +- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/DynamicIsland/extensions/PanGesture.swift b/DynamicIsland/extensions/PanGesture.swift index fe319b6..7f0a1c6 100644 --- a/DynamicIsland/extensions/PanGesture.swift +++ b/DynamicIsland/extensions/PanGesture.swift @@ -56,9 +56,13 @@ private struct ScrollMonitor: NSViewRepresentable { private let threshold: CGFloat private let action: (CGFloat, NSEvent.Phase) -> Void private var monitor: Any? + private var globalMonitor: Any? private var accumulated: CGFloat = 0 private var active = false private let noiseThreshold: CGFloat = 0.2 + private weak var observedView: NSView? + private let hoverInset: CGFloat = 14 + private var lastEventTimestamp: TimeInterval = 0 init(direction: PanDirection, threshold: CGFloat, action: @escaping (CGFloat, NSEvent.Phase) -> Void) { self.direction = direction @@ -68,11 +72,15 @@ private struct ScrollMonitor: NSViewRepresentable { func installMonitor(on view: NSView) { removeMonitor() - monitor = NSEvent.addLocalMonitorForEvents(matching: [.scrollWheel]) { [weak self, weak view] event in - guard let self = self, event.window === view?.window else { return event } + observedView = view + monitor = NSEvent.addLocalMonitorForEvents(matching: [.scrollWheel]) { [weak self] event in + guard let self else { return event } self.handleScroll(event) return event } + globalMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.scrollWheel]) { [weak self] event in + self?.handleScroll(event) + } } func removeMonitor() { @@ -80,11 +88,21 @@ private struct ScrollMonitor: NSViewRepresentable { NSEvent.removeMonitor(monitor) self.monitor = nil } + if let globalMonitor = globalMonitor { + NSEvent.removeMonitor(globalMonitor) + self.globalMonitor = nil + } accumulated = 0 active = false + observedView = nil + lastEventTimestamp = 0 } private func handleScroll(_ event: NSEvent) { + guard lastEventTimestamp != event.timestamp else { return } + lastEventTimestamp = event.timestamp + guard isCursorNearObservedView(using: event) else { return } + if event.phase == .ended || event.momentumPhase == .ended { if active { action(accumulated.magnitude, .ended) @@ -107,5 +125,22 @@ private struct ScrollMonitor: NSViewRepresentable { action(accumulated.magnitude, .changed) } } + + private func isCursorNearObservedView(using event: NSEvent) -> Bool { + guard let view = observedView, let window = view.window else { return false } + + let screenPoint: NSPoint + if let eventWindow = event.window { + let rect = NSRect(origin: event.locationInWindow, size: .zero) + screenPoint = eventWindow.convertToScreen(rect).origin + } else { + screenPoint = NSEvent.mouseLocation + } + + let windowPoint = window.convertPoint(fromScreen: screenPoint) + let localPoint = view.convert(windowPoint, from: nil) + let hitArea = view.bounds.insetBy(dx: -hoverInset, dy: -hoverInset) + return hitArea.contains(localPoint) + } } } diff --git a/DynamicIsland/managers/LockScreenWeatherManager.swift b/DynamicIsland/managers/LockScreenWeatherManager.swift index 86b7df3..209f0d9 100644 --- a/DynamicIsland/managers/LockScreenWeatherManager.swift +++ b/DynamicIsland/managers/LockScreenWeatherManager.swift @@ -1031,7 +1031,7 @@ private enum WeatherSymbolMapper { return "cloud.rain.fill" case 179, 182, 185, 311, 314, 317, 320, 362, 365: return "cloud.sleet.fill" - case 227, 230, 281, 284, 317, 320, 323, 326, 329, 332, 335, 338, 368, 371, 374, 377: + case 227, 230, 281, 284, 323, 326, 329, 332, 335, 338, 368, 371, 374, 377: return "cloud.snow.fill" case 200, 386, 389, 392, 395: return "cloud.bolt.rain.fill"