mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Reminder Widget dynamic positioning
This commit is contained in:
parent
af46e988dc
commit
1544a666c3
2 changed files with 46 additions and 13 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue