import AppKitimport CodexBarCoreimport SwiftUIimport Vortex @MainActorfinal class ScreenConfettiOverlayController { private static let overlayLifetime: TimeInterval = 5 private let logger = CodexBarLog.logger(LogCategories.confetti) private var windows: [NSWindow] = [] private var dismissalTask: Task<Void, Never>? func play(originInScreen origin: CGPoint?, colors: [ProviderColor]) { guard self.windows.isEmpty else { self.logger.debug("Ignoring confetti trigger while overlay is already active") return } let screens = NSScreen.screens guard !screens.isEmpty else { self.logger.error("Cannot present confetti overlay because no screens were found") return } let palette = colors.map { color in Color(red: color.red, green: color.green, blue: color.blue) } self.windows = screens.map { screen in let frame = screen.frame let localOrigin = Self.localOrigin(in: frame, from: origin) let contentView = ScreenConfettiOverlayView(origin: localOrigin, colors: palette) .allowsHitTesting(false) let hostingView = NSHostingView(rootView: contentView) hostingView.wantsLayer = true hostingView.layer?.backgroundColor = NSColor.clear.cgColor let window = ClickThroughOverlayPanel( contentRect: frame, styleMask: [.borderless, .nonactivatingPanel], backing: .buffered, defer: false, screen: screen) window.contentView = hostingView window.level = .statusBar window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary, .ignoresCycle, .stationary] window.backgroundColor = .clear window.isOpaque = false window.hasShadow = false window.ignoresMouseEvents = true window.acceptsMouseMovedEvents = false window.isMovable = false window.isReleasedWhenClosed = false window.canHide = false window.hidesOnDeactivate = false window.becomesKeyOnlyIfNeeded = false window.isExcludedFromWindowsMenu = true window.setFrame(frame, display: false) return window } self.logger.info( "Presenting confetti overlay", metadata: [ "screenCount": "\(self.windows.count)", "originKnown": origin == nil ? "0" : "1", ]) for window in self.windows { window.orderFrontRegardless() } self.dismissalTask = Task { @MainActor [weak self] in try? await Task.sleep(for: .seconds(Self.overlayLifetime)) self?.dismiss() } } func dismiss() { self.dismissalTask?.cancel() self.dismissalTask = nil guard !self.windows.isEmpty else { return } for window in self.windows { window.orderOut(nil) window.close() } self.windows.removeAll(keepingCapacity: true) } private static func localOrigin(in screenFrame: CGRect, from globalOrigin: CGPoint?) -> CGPoint { let fallback = CGPoint(x: screenFrame.maxX - 28, y: screenFrame.maxY - 8) let resolved: CGPoint = if let globalOrigin, screenFrame.contains(globalOrigin) { globalOrigin } else { fallback } let insetFrame = screenFrame.insetBy(dx: 8, dy: 8) return CGPoint( x: min(max(resolved.x, insetFrame.minX), insetFrame.maxX) - screenFrame.minX, y: min(max(resolved.y, insetFrame.minY), insetFrame.maxY) - screenFrame.minY) }} private final class ClickThroughOverlayPanel: NSPanel { override var canBecomeKey: Bool { false } override var canBecomeMain: Bool { false } override var acceptsFirstResponder: Bool { false }} private struct ScreenConfettiOverlayView: View { private static let clockwiseRotationAngles: [Double] = [270, 234, 198, 162, 126, 90] private static let counterclockwiseRotationAngles: [Double] = [90, 126, 162, 198, 234, 270] let origin: CGPoint let colors: [Color] @Environment(\.self) private var environment @State private var visiblePhaseCount = 0 var body: some View { GeometryReader { proxy in let clockwiseAngles = Array(Self.clockwiseRotationAngles.prefix(self.visiblePhaseCount).enumerated()) let counterclockwiseAngles = Array( Self.counterclockwiseRotationAngles.prefix(self.visiblePhaseCount).enumerated()) ZStack { ForEach(clockwiseAngles, id: \.offset) { index, angle in VortexView(self.makeFireworkConfettiSystem( in: proxy.size, launchAngle: angle, phaseIndex: index, lateralOffset: -12)) { RoundedRectangle(cornerRadius: 2, style: .continuous) .fill(.white) .frame(width: 10, height: 20) .tag("confetti-bar") Circle() .fill(.white) .frame(width: 9, height: 9) .tag("confetti-dot") Capsule(style: .continuous) .fill(.white) .frame(width: 8, height: 16) .rotationEffect(.degrees(30)) .tag("confetti-pill") Circle() .fill(.white) .frame(width: 6, height: 6) .blur(radius: 1) .tag("confetti-tracer") } } ForEach(counterclockwiseAngles, id: \.offset) { index, angle in VortexView(self.makeFireworkConfettiSystem( in: proxy.size, launchAngle: angle, phaseIndex: index, lateralOffset: 12)) { RoundedRectangle(cornerRadius: 2, style: .continuous) .fill(.white) .frame(width: 10, height: 20) .tag("confetti-bar") Circle() .fill(.white) .frame(width: 9, height: 9) .tag("confetti-dot") Capsule(style: .continuous) .fill(.white) .frame(width: 8, height: 16) .rotationEffect(.degrees(30)) .tag("confetti-pill") Circle() .fill(.white) .frame(width: 6, height: 6) .blur(radius: 1) .tag("confetti-tracer") } } } .ignoresSafeArea() .allowsHitTesting(false) .task { self.visiblePhaseCount = 1 for phaseCount in 2...Self.clockwiseRotationAngles.count { try? await Task.sleep(for: .milliseconds(60)) self.visiblePhaseCount = phaseCount } } } } private func makeFireworkConfettiSystem( in size: CGSize, launchAngle: Double, phaseIndex: Int, lateralOffset: CGFloat) -> VortexSystem { let canvasOrigin = self.canvasOrigin(in: size, lateralOffset: lateralOffset) let normalizedX = size.width > 0 ? canvasOrigin.x / size.width : 1 let normalizedY = size.height > 0 ? canvasOrigin.y / size.height : 0 let resolvedColors = self.colors.map { color -> VortexSystem.Color in let components = color.resolve(in: self.environment) return VortexSystem.Color( red: Double(components.red), green: Double(components.green), blue: Double(components.blue), opacity: Double(components.opacity)) } let explosion = VortexSystem( tags: ["confetti-bar", "confetti-dot", "confetti-pill"], spawnOccasion: .onDeath, shape: .point, birthRate: 24000, emissionLimit: 42, emissionDuration: 0.08, idleDuration: 10, lifespan: 4.2, speed: 0.72, speedVariation: 0.44, angleRange: .degrees(360), acceleration: [0, 0.32], dampingFactor: 0.18, angularSpeed: [0, 0, 3], angularSpeedVariation: [2, 2, 14], colors: .random(resolvedColors), size: 0.74, sizeVariation: 0.26, sizeMultiplierAtDeath: 0.94, stretchFactor: 0.82) return VortexSystem( tags: ["confetti-tracer"], secondarySystems: [explosion], position: [normalizedX, normalizedY], shape: .point, birthRate: 18, emissionLimit: 4, emissionDuration: 0.22, idleDuration: 10, lifespan: 0.58 + (Double(phaseIndex) * 0.03), speed: 1.36 + (Double(phaseIndex) * 0.04), speedVariation: 0.12, angle: .degrees(launchAngle), angleRange: .degrees(12), acceleration: [0, 0.12], dampingFactor: 0.06, angularSpeed: [0, 0, 6], angularSpeedVariation: [1, 1, 8], colors: .single(.white), size: 0.34, sizeVariation: 0.08, sizeMultiplierAtDeath: 0.4, stretchFactor: 1.3) } private func canvasOrigin(in size: CGSize, lateralOffset: CGFloat = 0) -> CGPoint { CGPoint( x: min(max(self.origin.x + lateralOffset, 0), size.width), y: min(max(size.height - self.origin.y + 18, 0), size.height)) }}
import AppKit
import CodexBarCore
import SwiftUI
import Vortex
@MainActor
final class ScreenConfettiOverlayController {
private static let overlayLifetime: TimeInterval = 5
private let logger = CodexBarLog.logger(LogCategories.confetti)
private var windows: [NSWindow] = []
private var dismissalTask: Task<Void, Never>?
func play(originInScreen origin: CGPoint?, colors: [ProviderColor]) {
guard self.windows.isEmpty else {
self.logger.debug("Ignoring confetti trigger while overlay is already active")
return
}
let screens = NSScreen.screens
guard !screens.isEmpty else {
self.logger.error("Cannot present confetti overlay because no screens were found")
return
}
let palette = colors.map { color in
Color(red: color.red, green: color.green, blue: color.blue)
}
self.windows = screens.map { screen in
let frame = screen.frame
let localOrigin = Self.localOrigin(in: frame, from: origin)
let contentView = ScreenConfettiOverlayView(origin: localOrigin, colors: palette)
.allowsHitTesting(false)
let hostingView = NSHostingView(rootView: contentView)
hostingView.wantsLayer = true
hostingView.layer?.backgroundColor = NSColor.clear.cgColor
let window = ClickThroughOverlayPanel(
contentRect: frame,
styleMask: [.borderless, .nonactivatingPanel],
backing: .buffered,
defer: false,
screen: screen)
window.contentView = hostingView
window.level = .statusBar
window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary, .ignoresCycle, .stationary]
window.backgroundColor = .clear
window.isOpaque = false
window.hasShadow = false
window.ignoresMouseEvents = true
window.acceptsMouseMovedEvents = false
window.isMovable = false
window.isReleasedWhenClosed = false
window.canHide = false
window.hidesOnDeactivate = false
window.becomesKeyOnlyIfNeeded = false
window.isExcludedFromWindowsMenu = true
window.setFrame(frame, display: false)
return window
}
self.logger.info(
"Presenting confetti overlay",
metadata: [
"screenCount": "\(self.windows.count)",
"originKnown": origin == nil ? "0" : "1",
])
for window in self.windows {
window.orderFrontRegardless()
}
self.dismissalTask = Task { @MainActor [weak self] in
try? await Task.sleep(for: .seconds(Self.overlayLifetime))
self?.dismiss()
}
}
func dismiss() {
self.dismissalTask?.cancel()
self.dismissalTask = nil
guard !self.windows.isEmpty else { return }
for window in self.windows {
window.orderOut(nil)
window.close()
}
self.windows.removeAll(keepingCapacity: true)
}
private static func localOrigin(in screenFrame: CGRect, from globalOrigin: CGPoint?) -> CGPoint {
let fallback = CGPoint(x: screenFrame.maxX - 28, y: screenFrame.maxY - 8)
let resolved: CGPoint = if let globalOrigin, screenFrame.contains(globalOrigin) {
globalOrigin
} else {
fallback
}
let insetFrame = screenFrame.insetBy(dx: 8, dy: 8)
return CGPoint(
x: min(max(resolved.x, insetFrame.minX), insetFrame.maxX) - screenFrame.minX,
y: min(max(resolved.y, insetFrame.minY), insetFrame.maxY) - screenFrame.minY)
}
}
private final class ClickThroughOverlayPanel: NSPanel {
override var canBecomeKey: Bool {
false
}
override var canBecomeMain: Bool {
false
}
override var acceptsFirstResponder: Bool {
false
}
}
private struct ScreenConfettiOverlayView: View {
private static let clockwiseRotationAngles: [Double] = [270, 234, 198, 162, 126, 90]
private static let counterclockwiseRotationAngles: [Double] = [90, 126, 162, 198, 234, 270]
let origin: CGPoint
let colors: [Color]
@Environment(\.self) private var environment
@State private var visiblePhaseCount = 0
var body: some View {
GeometryReader { proxy in
let clockwiseAngles = Array(Self.clockwiseRotationAngles.prefix(self.visiblePhaseCount).enumerated())
let counterclockwiseAngles = Array(
Self.counterclockwiseRotationAngles.prefix(self.visiblePhaseCount).enumerated())
ZStack {
ForEach(clockwiseAngles, id: \.offset) { index, angle in
VortexView(self.makeFireworkConfettiSystem(
in: proxy.size,
launchAngle: angle,
phaseIndex: index,
lateralOffset: -12))
{
RoundedRectangle(cornerRadius: 2, style: .continuous)
.fill(.white)
.frame(width: 10, height: 20)
.tag("confetti-bar")
Circle()
.fill(.white)
.frame(width: 9, height: 9)
.tag("confetti-dot")
Capsule(style: .continuous)
.fill(.white)
.frame(width: 8, height: 16)
.rotationEffect(.degrees(30))
.tag("confetti-pill")
Circle()
.fill(.white)
.frame(width: 6, height: 6)
.blur(radius: 1)
.tag("confetti-tracer")
}
}
ForEach(counterclockwiseAngles, id: \.offset) { index, angle in
VortexView(self.makeFireworkConfettiSystem(
in: proxy.size,
launchAngle: angle,
phaseIndex: index,
lateralOffset: 12))
{
RoundedRectangle(cornerRadius: 2, style: .continuous)
.fill(.white)
.frame(width: 10, height: 20)
.tag("confetti-bar")
Circle()
.fill(.white)
.frame(width: 9, height: 9)
.tag("confetti-dot")
Capsule(style: .continuous)
.fill(.white)
.frame(width: 8, height: 16)
.rotationEffect(.degrees(30))
.tag("confetti-pill")
Circle()
.fill(.white)
.frame(width: 6, height: 6)
.blur(radius: 1)
.tag("confetti-tracer")
}
}
}
.ignoresSafeArea()
.allowsHitTesting(false)
.task {
self.visiblePhaseCount = 1
for phaseCount in 2...Self.clockwiseRotationAngles.count {
try? await Task.sleep(for: .milliseconds(60))
self.visiblePhaseCount = phaseCount
}
}
}
}
private func makeFireworkConfettiSystem(
in size: CGSize,
launchAngle: Double,
phaseIndex: Int,
lateralOffset: CGFloat)
-> VortexSystem
{
let canvasOrigin = self.canvasOrigin(in: size, lateralOffset: lateralOffset)
let normalizedX = size.width > 0 ? canvasOrigin.x / size.width : 1
let normalizedY = size.height > 0 ? canvasOrigin.y / size.height : 0
let resolvedColors = self.colors.map { color -> VortexSystem.Color in
let components = color.resolve(in: self.environment)
return VortexSystem.Color(
red: Double(components.red),
green: Double(components.green),
blue: Double(components.blue),
opacity: Double(components.opacity))
}
let explosion = VortexSystem(
tags: ["confetti-bar", "confetti-dot", "confetti-pill"],
spawnOccasion: .onDeath,
shape: .point,
birthRate: 24000,
emissionLimit: 42,
emissionDuration: 0.08,
idleDuration: 10,
lifespan: 4.2,
speed: 0.72,
speedVariation: 0.44,
angleRange: .degrees(360),
acceleration: [0, 0.32],
dampingFactor: 0.18,
angularSpeed: [0, 0, 3],
angularSpeedVariation: [2, 2, 14],
colors: .random(resolvedColors),
size: 0.74,
sizeVariation: 0.26,
sizeMultiplierAtDeath: 0.94,
stretchFactor: 0.82)
return VortexSystem(
tags: ["confetti-tracer"],
secondarySystems: [explosion],
position: [normalizedX, normalizedY],
shape: .point,
birthRate: 18,
emissionLimit: 4,
emissionDuration: 0.22,
idleDuration: 10,
lifespan: 0.58 + (Double(phaseIndex) * 0.03),
speed: 1.36 + (Double(phaseIndex) * 0.04),
speedVariation: 0.12,
angle: .degrees(launchAngle),
angleRange: .degrees(12),
acceleration: [0, 0.12],
dampingFactor: 0.06,
angularSpeed: [0, 0, 6],
angularSpeedVariation: [1, 1, 8],
colors: .single(.white),
size: 0.34,
sizeVariation: 0.08,
sizeMultiplierAtDeath: 0.4,
stretchFactor: 1.3)
}
private func canvasOrigin(in size: CGSize, lateralOffset: CGFloat = 0) -> CGPoint {
CGPoint(
x: min(max(self.origin.x + lateralOffset, 0), size.width),
y: min(max(size.height - self.origin.y + 18, 0), size.height))
}
}