Reminder Widget dynamic positioning

This commit is contained in:
Hariharan Mudaliar 2025-12-03 23:31:37 +05:30
commit 1544a666c3
2 changed files with 46 additions and 13 deletions

View file

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

View file

@ -101,6 +101,7 @@ final class LockScreenTimerWidgetPanelManager {
private var hasDelegated = false
private let animator = LockScreenTimerWidgetAnimator()
private var hideTask: Task<Void, Never>?
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) {