pommedoro/Sources/Pommedoro/OverlayWindows.swift

155 lines
5.6 KiB
Swift

import AppKit
extension AppDelegate {
func setupWindows() {
for screen in NSScreen.screens {
let window = NSWindow(
contentRect: screen.frame,
styleMask: .borderless,
backing: .buffered,
defer: false
)
window.level = .screenSaver
window.backgroundColor = .clear
window.isOpaque = false
window.hasShadow = false
window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
window.ignoresMouseEvents = true
window.isReleasedWhenClosed = false
let gradientView = EdgeGradientView(frame: NSRect(origin: .zero, size: screen.frame.size))
gradientView.autoresizingMask = [.width, .height]
window.contentView = gradientView
window.alphaValue = 0.0
window.orderFrontRegardless()
overlayWindows.append(window)
let pillW: CGFloat = 200
let pillH: CGFloat = 80
let pillX = screen.frame.origin.x + (screen.frame.width - pillW) / 2
let pillY = screen.frame.origin.y + 40
let pillWindow = NSWindow(
contentRect: NSRect(x: pillX, y: pillY, width: pillW, height: pillH),
styleMask: .borderless,
backing: .buffered,
defer: false
)
pillWindow.level = .screenSaver
pillWindow.backgroundColor = .clear
pillWindow.isOpaque = false
pillWindow.hasShadow = false
pillWindow.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
pillWindow.ignoresMouseEvents = true
pillWindow.isReleasedWhenClosed = false
let pillView = NSView(frame: NSRect(x: 0, y: 0, width: pillW, height: pillH))
pillView.wantsLayer = true
pillView.layer?.backgroundColor = NSColor.black.withAlphaComponent(0.6).cgColor
pillView.layer?.cornerRadius = pillH / 2
let label = NSTextField(labelWithString: "00:10")
label.font = NSFont.monospacedSystemFont(ofSize: 36, weight: .bold)
label.textColor = .systemTeal
label.alignment = .center
label.frame = NSRect(x: 0, y: 14, width: pillW, height: 50)
pillView.addSubview(label)
pillWindow.contentView = pillView
pillWindow.alphaValue = 0.0
pillWindow.orderFrontRegardless()
countdownWindows.append(pillWindow)
countdownLabels.append(label)
}
}
func showCountdownPills() {
for window in countdownWindows {
if window.alphaValue < 1.0 {
NSAnimationContext.runAnimationGroup { ctx in
ctx.duration = 0.2
window.animator().alphaValue = 1.0
}
}
}
}
func hideCountdownPills() {
for window in countdownWindows { window.alphaValue = 0.0 }
}
func updateCountdownLabels() {
let seconds = max(remainingSeconds, 0)
let text = String(format: "00:%02d", seconds)
for label in countdownLabels { label.stringValue = text }
}
func triggerBlink() {
// Intensity scales across the effective countdown window (capped to work timer)
let effectiveCountdown = min(countdownDuration, workTimerDuration)
let countdownElapsed = Double(effectiveCountdown - remainingSeconds)
let countdownTotal = Double(effectiveCountdown)
let progress = min(max(countdownElapsed / countdownTotal, 0.0), 1.0)
let intensity = 0.15 + (0.60 * progress) // 0.15 at start, up to 0.75 at 0:00
let fadeIn: Double
let hold: Double
let fadeOut: Double
if remainingSeconds > 60 {
// 5:00 to 1:00: gentle, slow flashes
fadeIn = 1.0; hold = 0.8; fadeOut = 1.0
} else if remainingSeconds > 30 {
fadeIn = 0.8; hold = 0.6; fadeOut = 0.8
} else if remainingSeconds > 10 {
fadeIn = 0.5; hold = 0.4; fadeOut = 0.6
} else {
fadeIn = 0.3; hold = 0.3; fadeOut = 0.4
}
for window in overlayWindows {
guard let view = window.contentView as? EdgeGradientView else { continue }
view.intensity = CGFloat(intensity)
view.needsDisplay = true
NSAnimationContext.runAnimationGroup { ctx in
ctx.duration = fadeIn
window.animator().alphaValue = 1.0
} completionHandler: {
DispatchQueue.main.asyncAfter(deadline: .now() + hold) {
NSAnimationContext.runAnimationGroup({ ctx in
ctx.duration = fadeOut
window.animator().alphaValue = 0.0
})
}
}
}
}
func makeSolid() {
isSolid = true
for window in overlayWindows {
guard let view = window.contentView as? EdgeGradientView else { continue }
view.intensity = 0.75
view.needsDisplay = true
NSAnimationContext.runAnimationGroup { ctx in
ctx.duration = 0.3
window.animator().alphaValue = 1.0
}
}
}
func resetOverlayToGradient() {
for window in overlayWindows {
window.ignoresMouseEvents = true
let size = window.frame.size
let gradientView = EdgeGradientView(frame: NSRect(origin: .zero, size: size))
gradientView.autoresizingMask = [.width, .height]
window.contentView = gradientView
window.alphaValue = 0.0
}
}
}