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 AppKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import SkyLightWindow
|
import SkyLightWindow
|
||||||
|
import QuartzCore
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
final class LockScreenReminderWidgetPanelManager {
|
final class LockScreenReminderWidgetPanelManager {
|
||||||
|
|
@ -29,6 +30,20 @@ final class LockScreenReminderWidgetPanelManager {
|
||||||
window.contentView = nil
|
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) {
|
private func render(snapshot: LockScreenReminderWidgetSnapshot, makeVisible: Bool) {
|
||||||
guard let screen = NSScreen.main else { return }
|
guard let screen = NSScreen.main else { return }
|
||||||
if !makeVisible, window == nil {
|
if !makeVisible, window == nil {
|
||||||
|
|
@ -84,26 +99,35 @@ final class LockScreenReminderWidgetPanelManager {
|
||||||
let screenFrame = screen.frame
|
let screenFrame = screen.frame
|
||||||
let originX = screenFrame.midX - (size.width / 2)
|
let originX = screenFrame.midX - (size.width / 2)
|
||||||
let musicTop = LockScreenPanelManager.shared.latestFrame?.maxY ?? (screenFrame.midY - 32)
|
let musicTop = LockScreenPanelManager.shared.latestFrame?.maxY ?? (screenFrame.midY - 32)
|
||||||
|
let timerFrame = LockScreenTimerWidgetPanelManager.shared.latestFrame
|
||||||
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 marginAboveMusic: CGFloat = 16
|
let marginAboveMusic: CGFloat = 16
|
||||||
|
let marginAboveTimer: CGFloat = 24
|
||||||
let marginBelowWeather: CGFloat = 16
|
let marginBelowWeather: CGFloat = 16
|
||||||
|
|
||||||
let constrainedWeatherBottom = max(weatherBottom, musicTop + size.height + marginAboveMusic + marginBelowWeather)
|
let baseLowerBound = musicTop + marginAboveMusic
|
||||||
let centerY = (musicTop + constrainedWeatherBottom) / 2
|
let timerLowerBound = timerFrame.map { $0.maxY + marginAboveTimer }
|
||||||
let lowerBound = screenFrame.minY + 80
|
let minYFloor = screenFrame.minY + 80
|
||||||
let upperBound = constrainedWeatherBottom - marginBelowWeather - size.height
|
let lowerBound = max(timerLowerBound ?? baseLowerBound, minYFloor)
|
||||||
|
|
||||||
var proposedY = centerY - (size.height / 2)
|
let defaultWeatherBottom = screenFrame.midY + (screenFrame.height * 0.14)
|
||||||
if proposedY < lowerBound {
|
let minimumWeatherBottom = lowerBound + size.height + marginBelowWeather
|
||||||
proposedY = lowerBound
|
let weatherBottom = LockScreenWeatherPanelManager.shared.latestFrame?.minY ?? min(screenFrame.maxY - 120, max(defaultWeatherBottom, minimumWeatherBottom))
|
||||||
}
|
|
||||||
if upperBound > lowerBound {
|
let upperBound = weatherBottom - marginBelowWeather - size.height
|
||||||
proposedY = min(proposedY, upperBound)
|
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)
|
return NSRect(x: originX, y: proposedY, width: size.width, height: size.height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,7 @@ final class LockScreenTimerWidgetPanelManager {
|
||||||
private var hasDelegated = false
|
private var hasDelegated = false
|
||||||
private let animator = LockScreenTimerWidgetAnimator()
|
private let animator = LockScreenTimerWidgetAnimator()
|
||||||
private var hideTask: Task<Void, Never>?
|
private var hideTask: Task<Void, Never>?
|
||||||
|
private(set) var latestFrame: NSRect?
|
||||||
|
|
||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
|
|
@ -109,11 +110,13 @@ final class LockScreenTimerWidgetPanelManager {
|
||||||
let window = ensureWindow()
|
let window = ensureWindow()
|
||||||
let frame = targetFrame(on: screen)
|
let frame = targetFrame(on: screen)
|
||||||
window.setFrame(frame, display: true)
|
window.setFrame(frame, display: true)
|
||||||
|
latestFrame = frame
|
||||||
window.alphaValue = 1
|
window.alphaValue = 1
|
||||||
window.orderFrontRegardless()
|
window.orderFrontRegardless()
|
||||||
hideTask?.cancel()
|
hideTask?.cancel()
|
||||||
hideTask = nil
|
hideTask = nil
|
||||||
animator.isPresented = true
|
animator.isPresented = true
|
||||||
|
LockScreenReminderWidgetPanelManager.shared.refreshPosition(animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func hide(animated: Bool = true) {
|
func hide(animated: Bool = true) {
|
||||||
|
|
@ -125,6 +128,8 @@ final class LockScreenTimerWidgetPanelManager {
|
||||||
if delay == 0 {
|
if delay == 0 {
|
||||||
window.orderOut(nil)
|
window.orderOut(nil)
|
||||||
hideTask = nil
|
hideTask = nil
|
||||||
|
latestFrame = nil
|
||||||
|
LockScreenReminderWidgetPanelManager.shared.refreshPosition(animated: true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,6 +138,8 @@ final class LockScreenTimerWidgetPanelManager {
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
window?.orderOut(nil)
|
window?.orderOut(nil)
|
||||||
self?.hideTask = nil
|
self?.hideTask = nil
|
||||||
|
self?.latestFrame = nil
|
||||||
|
LockScreenReminderWidgetPanelManager.shared.refreshPosition(animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -150,6 +157,8 @@ final class LockScreenTimerWidgetPanelManager {
|
||||||
} else {
|
} else {
|
||||||
window.setFrame(frame, display: true)
|
window.setFrame(frame, display: true)
|
||||||
}
|
}
|
||||||
|
latestFrame = frame
|
||||||
|
LockScreenReminderWidgetPanelManager.shared.refreshPosition(animated: animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshRelativeToMusicPanel(animated: Bool) {
|
func refreshRelativeToMusicPanel(animated: Bool) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue