Tusker/Tusker/Screens/Preferences/Tip Jar/ConfettiView.swift

120 lines
3.5 KiB
Swift

//
// ConfettiView.swift
// Tusker
//
// Created by Shadowfacts on 1/25/23.
// Copyright © 2023 Shadowfacts. All rights reserved.
//
import SwiftUI
struct ConfettiView: View {
private static let colors = [
Color.red,
.purple,
.blue,
.pink,
.yellow,
.green,
.teal,
.cyan,
.mint,
.indigo,
.orange,
]
@State private var size: CGSize?
@State private var startDate: Date?
var body: some View {
allConfetti
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(GeometryReader { proxy in
Color.clear
.preference(key: SizeKey.self, value: proxy.size)
.onPreferenceChange(SizeKey.self) { newValue in
self.size = newValue
self.startDate = Date()
}
})
}
@ViewBuilder
private var allConfetti: some View {
if let size {
TimelineView(.animation) { context in
ZStack {
ForEach(0..<200) { idx in
let time = context.date.timeIntervalSince(startDate!)
ConfettiPiece(shape: Rectangle(), color: Self.colors[idx % Self.colors.count], canvasSize: size, time: time)
ConfettiPiece(shape: Circle(), color: Self.colors[idx % Self.colors.count], canvasSize: size, time: time)
ConfettiPiece(shape: Triangle(), color: Self.colors[idx % Self.colors.count], canvasSize: size, time: time)
}
}
}
} else {
Color.clear
}
}
}
private struct SizeKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}
private struct ConfettiPiece<S: Shape>: View {
let shape: S
let color: Color
let canvasSize: CGSize
let time: TimeInterval
@State private var start: CGPoint = .zero
@State private var velocity: CGPoint = {
let angle = CGFloat.random(in: 0..<2) * .pi
let velocity = CGFloat.random(in: 25...250)
return CGPoint(x: cos(angle) * velocity, y: sin(angle) * velocity)
}()
private let gravity: CGFloat = 100
@State private var rotationAxis: (CGFloat, CGFloat, CGFloat) = (.random() ? 1 : 0, .random() ? 1 : 0, .random() ? 1 : 0)
@State private var rotationsPerSecond = Double.random(in: 0.5..<2)
private var x: CGFloat {
(canvasSize.width / 2) + velocity.x * time
}
private var y: CGFloat {
(canvasSize.height / 2) + velocity.y * time + 0.5 * time * time * gravity
}
private var angle: Angle {
.degrees(time * rotationsPerSecond * 360)
}
var body: some View {
shape
.frame(width: 7, height: 7)
.foregroundColor(color)
.rotation3DEffect(angle, axis: rotationAxis)
.position(x: x, y: y)
}
}
private struct Triangle: Shape {
func path(in rect: CGRect) -> Path {
var p = Path()
p.move(to: CGPoint(x: rect.midX, y: rect.minY))
p.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
p.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
p.closeSubpath()
return p
}
}
struct ConfettiView_Previews: PreviewProvider {
static var previews: some View {
ConfettiView()
}
}