From 1544a666c30792d40d15e35c43d37d74a13734da Mon Sep 17 00:00:00 2001 From: Hariharan Mudaliar Date: Wed, 3 Dec 2025 23:31:37 +0530 Subject: [PATCH] Reminder Widget dynamic positioning --- ...LockScreenReminderWidgetPanelManager.swift | 50 ++++++++++++++----- .../LockScreenTimerWidgetManager.swift | 9 ++++ 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/DynamicIsland/managers/LockScreenReminderWidgetPanelManager.swift b/DynamicIsland/managers/LockScreenReminderWidgetPanelManager.swift index 4a82398..27a0a8f 100644 --- a/DynamicIsland/managers/LockScreenReminderWidgetPanelManager.swift +++ b/DynamicIsland/managers/LockScreenReminderWidgetPanelManager.swift @@ -1,6 +1,7 @@ import AppKit import SwiftUI import SkyLightWindow +import QuartzCore @MainActor final class LockScreenReminderWidgetPanelManager { @@ -29,6 +30,20 @@ final class LockScreenReminderWidgetPanelManager { window.contentView = nil } + func refreshPosition(animated: Bool) { + guard let window, window.isVisible, let screen = NSScreen.main else { return } + let target = frame(for: window.frame.size, on: screen) + if animated { + NSAnimationContext.runAnimationGroup { context in + context.duration = 0.22 + context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + window.animator().setFrame(target, display: true) + } + } else { + window.setFrame(target, display: true) + } + } + private func render(snapshot: LockScreenReminderWidgetSnapshot, makeVisible: Bool) { guard let screen = NSScreen.main else { return } if !makeVisible, window == nil { @@ -84,26 +99,35 @@ final class LockScreenReminderWidgetPanelManager { let screenFrame = screen.frame let originX = screenFrame.midX - (size.width / 2) let musicTop = LockScreenPanelManager.shared.latestFrame?.maxY ?? (screenFrame.midY - 32) - - let defaultWeatherBottom = screenFrame.midY + (screenFrame.height * 0.14) - let weatherBottom = LockScreenWeatherPanelManager.shared.latestFrame?.minY ?? min(screenFrame.maxY - 120, max(defaultWeatherBottom, musicTop + size.height + 80)) + let timerFrame = LockScreenTimerWidgetPanelManager.shared.latestFrame let marginAboveMusic: CGFloat = 16 + let marginAboveTimer: CGFloat = 24 let marginBelowWeather: CGFloat = 16 - let constrainedWeatherBottom = max(weatherBottom, musicTop + size.height + marginAboveMusic + marginBelowWeather) - let centerY = (musicTop + constrainedWeatherBottom) / 2 - let lowerBound = screenFrame.minY + 80 - let upperBound = constrainedWeatherBottom - marginBelowWeather - size.height + let baseLowerBound = musicTop + marginAboveMusic + let timerLowerBound = timerFrame.map { $0.maxY + marginAboveTimer } + let minYFloor = screenFrame.minY + 80 + let lowerBound = max(timerLowerBound ?? baseLowerBound, minYFloor) - var proposedY = centerY - (size.height / 2) - if proposedY < lowerBound { - proposedY = lowerBound - } - if upperBound > lowerBound { - proposedY = min(proposedY, upperBound) + let defaultWeatherBottom = screenFrame.midY + (screenFrame.height * 0.14) + let minimumWeatherBottom = lowerBound + size.height + marginBelowWeather + let weatherBottom = LockScreenWeatherPanelManager.shared.latestFrame?.minY ?? min(screenFrame.maxY - 120, max(defaultWeatherBottom, minimumWeatherBottom)) + + let upperBound = weatherBottom - marginBelowWeather - size.height + let clampedUpperBound = max(upperBound, lowerBound) + + let prefersTopAlignment = timerFrame != nil + var proposedY: CGFloat + + if prefersTopAlignment { + proposedY = clampedUpperBound + } else { + proposedY = (lowerBound + clampedUpperBound) / 2 } + proposedY = max(lowerBound, min(proposedY, clampedUpperBound)) + return NSRect(x: originX, y: proposedY, width: size.width, height: size.height) } } diff --git a/DynamicIsland/managers/LockScreenTimerWidgetManager.swift b/DynamicIsland/managers/LockScreenTimerWidgetManager.swift index bfc2b65..c71716a 100644 --- a/DynamicIsland/managers/LockScreenTimerWidgetManager.swift +++ b/DynamicIsland/managers/LockScreenTimerWidgetManager.swift @@ -101,6 +101,7 @@ final class LockScreenTimerWidgetPanelManager { private var hasDelegated = false private let animator = LockScreenTimerWidgetAnimator() private var hideTask: Task? + private(set) var latestFrame: NSRect? private init() {} @@ -109,11 +110,13 @@ final class LockScreenTimerWidgetPanelManager { let window = ensureWindow() let frame = targetFrame(on: screen) window.setFrame(frame, display: true) + latestFrame = frame window.alphaValue = 1 window.orderFrontRegardless() hideTask?.cancel() hideTask = nil animator.isPresented = true + LockScreenReminderWidgetPanelManager.shared.refreshPosition(animated: true) } func hide(animated: Bool = true) { @@ -125,6 +128,8 @@ final class LockScreenTimerWidgetPanelManager { if delay == 0 { window.orderOut(nil) hideTask = nil + latestFrame = nil + LockScreenReminderWidgetPanelManager.shared.refreshPosition(animated: true) return } @@ -133,6 +138,8 @@ final class LockScreenTimerWidgetPanelManager { await MainActor.run { window?.orderOut(nil) self?.hideTask = nil + self?.latestFrame = nil + LockScreenReminderWidgetPanelManager.shared.refreshPosition(animated: true) } } } @@ -150,6 +157,8 @@ final class LockScreenTimerWidgetPanelManager { } else { window.setFrame(frame, display: true) } + latestFrame = frame + LockScreenReminderWidgetPanelManager.shared.refreshPosition(animated: animated) } func refreshRelativeToMusicPanel(animated: Bool) {