Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creating of VortexSystem.Settings #21

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Sandbox/Sandbox/PreviewViews/ConfettiView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ struct ConfettiView: View {
ZStack {
Text("Tap anywhere to create confetti.")

VortexView(.confetti.makeUniqueCopy()) {
VortexView(.confetti) {
Rectangle()
.fill(.white)
.frame(width: 16, height: 16)
.tag("square")
.tag("triangle")

Circle()
.fill(.white)
Expand Down
11 changes: 2 additions & 9 deletions Sandbox/Sandbox/PreviewViews/FirefliesView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,8 @@ struct FirefliesView: View {
Text("Drag anywhere to repel the fireflies.")
.padding(.bottom, 20)
}

VortexView(.fireflies.makeUniqueCopy()) {
Circle()
.fill(.white)
.frame(width: 32)
.blur(radius: 3)
.blendMode(.plusLighter)
.tag("circle")
}
// Show the fireflies preset using default symbols
VortexView(.fireflies)
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { value in
Expand Down
20 changes: 20 additions & 0 deletions Sources/Vortex/Helpers/Angle+Codable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// Angle+Codable.swift
// Vortex
// https://www.github.com/twostraws/Vortex
// See LICENSE for license information.
//

import SwiftUI

extension Angle: Codable {
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
self = try Angle(radians: container.decode(Double.self))
}

public func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.radians)
}
}
14 changes: 14 additions & 0 deletions Sources/Vortex/Helpers/Equatable+Identifiable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// Equatable+Identifiable.swift
// Vortex
// https://www.github.com/twostraws/Vortex
// See LICENSE for license information.
//


// Automatic conformance to Equatable for any Identifiable Type.
extension Equatable where Self: Identifiable{
public static func == (lhs: Self, rhs: Self) -> Bool {
lhs.id == rhs.id
}
}
15 changes: 15 additions & 0 deletions Sources/Vortex/Helpers/Hashable+Identifiable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// Hashable+Identifiable.swift
// Vortex
// https://www.github.com/twostraws/Vortex
// See LICENSE for license information.
//

import SwiftUI
// Automatic conformance to Hashable for any Identifiable Type.
extension Hashable where Self: Identifiable {
/// The default hash value for an Identifiable type is simply its identifier.
public func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import SwiftUI


#if !os(macOS)
extension View {
/// A tiny shim to make navigationSubtitle() do nothing everywhere
Expand All @@ -16,3 +17,4 @@ extension View {
}
}
#endif

60 changes: 43 additions & 17 deletions Sources/Vortex/Presets/Confetti.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,49 @@

import SwiftUI

extension VortexSystem {
/// A built-in effect that creates confetti only when a burst is triggered.
//extension VortexSystem {
// /// A built-in effect that creates confetti only when a burst is triggered.
// /// Relies on "triangle" and "circle" tags being present – using `Rectangle`
// /// and `Circle` with frames of 16x16 works well.
// public static let confetti: VortexSystem = VortexSystem(.confetti)
//}

extension VortexSystem.Settings {
/// These settings create confetti (only) when a burst is triggered.
/// Relies on "square" and "circle" tags being present – using `Rectangle`
/// and `Circle` with frames of 16x16 works well.
public static let confetti: VortexSystem = {
VortexSystem(
tags: ["square", "circle"],
birthRate: 0,
lifespan: 4,
speed: 0.5,
speedVariation: 0.5,
angleRange: .degrees(90),
acceleration: [0, 1],
angularSpeedVariation: [4, 4, 4],
colors: .random(.white, .red, .green, .blue, .pink, .orange, .cyan),
size: 0.5,
sizeVariation: 0.5
)
}()
public static let confetti = VortexSystem.Settings {settings in
settings.tags = ["triangle", "circle"]
settings.birthRate = 0
settings.lifespan = 4
settings.speed = 0.5
settings.speedVariation = 0.5
settings.angleRange = .degrees(90)
settings.acceleration = [0, 1]
settings.angularSpeedVariation = [4, 4, 4]
settings.colors = .random(.white, .red, .green, .blue, .pink, .orange, .cyan)
settings.size = 0.5
settings.sizeVariation = 0.5
}
}

#Preview("Confetti") {
VortexViewReader { proxy in
ZStack {
Text("Tap anywhere to create confetti.")

VortexView(.confetti)
.gesture(
/// Drag gesture is available on all platforms vs onTap which requires macOS 14+
DragGesture(minimumDistance: 0)
.onChanged { gesture in
proxy.move(to: gesture.location)
proxy.burst()
}
)
}
}
.navigationSubtitle("Demonstrates on-demand particle bursting")
.ignoresSafeArea(edges: .top)
}

40 changes: 25 additions & 15 deletions Sources/Vortex/Presets/Fire.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,31 @@

import SwiftUI

extension VortexSystem {
extension VortexSystem.Settings {
/// A built-in fire effect. Relies on a "circle" tag being present, which should be set to use
/// `.blendMode(.plusLighter)`.
public static let fire: VortexSystem = {
VortexSystem(
tags: ["circle"],
shape: .box(width: 0.1, height: 0),
birthRate: 300,
speed: 0.2,
speedVariation: 0.2,
angleRange: .degrees(10),
attractionStrength: 2,
colors: .ramp(.brown, .brown, .brown, .brown.opacity(0)),
sizeVariation: 0.5,
sizeMultiplierAtDeath: 0.1
)
}()
public static let fire = VortexSystem.Settings { settings in
settings.tags = ["circle"]
settings.shape = .box(width: 0.1, height: 0)
settings.birthRate = 300
settings.speed = 0.2
settings.speedVariation = 0.2
settings.angleRange = .degrees(10)
settings.attractionStrength = 2
settings.colors = .ramp(.brown, .brown, .brown, .brown.opacity(0))
settings.sizeVariation = 0.5
settings.sizeMultiplierAtDeath = 0.1
}
}

#Preview {
let fireSettings = VortexSystem.Settings(from: .fire ) { settings in
settings.position = [ 0.5, 1.03]
settings.shape = .box(width: 1.0, height: 0)
settings.birthRate = 600
}
VortexView(fireSettings) {
Image.circle.blendMode(.plusLighter).frame(width: 16).tag("circle")
}
.frame(width: 500, height: 500)
}
76 changes: 62 additions & 14 deletions Sources/Vortex/Presets/Fireflies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,66 @@ import SwiftUI
extension VortexSystem {
/// A built-in firefly effect. Relies on a "circle" tag being present, which should be set to use
/// `.blendMode(.plusLighter)`.
public static let fireflies: VortexSystem = {
VortexSystem(
tags: ["circle"],
shape: .ellipse(radius: 0.5),
birthRate: 200,
lifespan: 2,
speed: 0,
speedVariation: 0.25,
angleRange: .degrees(360),
colors: .ramp(.yellow, .yellow, .yellow.opacity(0)),
size: 0.01,
sizeMultiplierAtDeath: 100
)
}()
public static let fireflies: VortexSystem = .init(.fireflies)
}
extension VortexSystem.Settings {
///Built in settings for a firefly effect.
public static let fireflies = VortexSystem.Settings { settings in
settings.tags = ["circle"]
settings.shape = .ellipse(radius: 0.5)
settings.birthRate = 200
settings.lifespan = 2
settings.speed = 0
settings.speedVariation = 0.25
settings.angleRange = .degrees(360)
settings.colors = .ramp(.yellow, .yellow, .yellow.opacity(0))
settings.size = 0.01
settings.sizeMultiplierAtDeath = 100
}
}

@available(macOS 15.0, *)
#Preview("Fireflies") {
@Previewable @State var isDragging = false
/// A state value indicating whether the Option key is being held down
@Previewable @State var pressingOptionKey = false
VortexViewReader { proxy in
ZStack(alignment: .bottom) {
if isDragging {
Text("Release your drag to reset the fireflies.")
.padding(.bottom, 20)
} else {
let instructions = if !pressingOptionKey {
"Drag anywhere to repel the fireflies. Or hold the Option Key"
} else {
"Drag anywhere to attract the fireflies"
}
Text(instructions)
.padding(.bottom, 20)
}

VortexView(.fireflies)
.onModifierKeysChanged(mask: .option) { _, new in
// set the view state based whether the
// `new` EventModifiers value contains the option key
pressingOptionKey = !new.isEmpty
}
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { value in
proxy.attractTo(value.location)
proxy.particleSystem?.vortexSettings
.attractionStrength = pressingOptionKey ? 2.5 : -2
isDragging = true
}
.onEnded { _ in
proxy.particleSystem?.vortexSettings
.attractionStrength = 0
isDragging = false
}
)
}
}
.navigationSubtitle("Demonstrates use of attraction and repulsion")
.ignoresSafeArea(edges: .top)
}
94 changes: 50 additions & 44 deletions Sources/Vortex/Presets/Fireworks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,54 +11,60 @@ extension VortexSystem {
/// A built-in fireworks effect, using secondary systems that create sparkles and explosions.
/// Relies on a "circle" tag being present, which should be set to use
/// `.blendMode(.plusLighter)`.
public static let fireworks: VortexSystem = {
let sparkles = VortexSystem(
tags: ["circle"],
spawnOccasion: .onUpdate,
emissionLimit: 1,
lifespan: 0.5,
speed: 0.05,
angleRange: .degrees(90),
size: 0.05
)

let explosion = VortexSystem(
tags: ["circle"],
spawnOccasion: .onDeath,
position: [0.5, 1],
birthRate: 100_000,
emissionLimit: 500,
speed: 0.5,
speedVariation: 1,
angleRange: .degrees(360),
acceleration: [0, 1.5],
dampingFactor: 4,
colors: .randomRamp(
public static let fireworks = VortexSystem(.fireworks)
}
extension VortexSystem.Settings {
public static let fireworks = VortexSystem.Settings { settings in

var sparkles = VortexSystem.Settings { sparkle in
sparkle.tags = ["circle"]
sparkle.spawnOccasion = .onUpdate
sparkle.emissionLimit = 1
sparkle.lifespan = 0.5
sparkle.speed = 0.05
sparkle.angleRange = .degrees(180)
sparkle.size = 0.05
}

var explosions = VortexSystem.Settings { explosion in
explosion.tags = ["circle"]
explosion.spawnOccasion = .onDeath
explosion.position = [0.5, 0.5]
explosion.birthRate = 100_000
explosion.emissionLimit = 500
explosion.speed = 0.5
explosion.speedVariation = 1
explosion.angleRange = .degrees(360)
explosion.acceleration = [0, 1.5]
explosion.dampingFactor = 4
explosion.colors = .randomRamp(
[.white, .pink, .pink],
[.white, .blue, .blue],
[.white, .green, .green],
[.white, .orange, .orange],
[.white, .cyan, .cyan]
),
size: 0.15,
sizeVariation: 0.1,
sizeMultiplierAtDeath: 0
)

let mainSystem = VortexSystem(
tags: ["circle"],
secondarySystems: [sparkles, explosion],
position: [0.5, 1],
birthRate: 2,
emissionLimit: 1000,
speed: 1.5,
speedVariation: 0.75,
angleRange: .degrees(60),
dampingFactor: 2,
size: 0.15,
stretchFactor: 4
)
)
explosion.size = 0.15
explosion.sizeVariation = 0.1
explosion.sizeMultiplierAtDeath = 0
}
settings.tags = ["circle"]
settings.secondarySettings = [sparkles, explosions]
settings.position = [0.5, 1]
settings.birthRate = 2
settings.emissionLimit = 1000
settings.speed = 1.5
settings.speedVariation = 0.75
settings.angleRange = .degrees(60)
settings.dampingFactor = 2
settings.size = 0.15
settings.stretchFactor = 4
}
}

return mainSystem
}()
#Preview("Fireworks") {
VortexView(.fireworks)
.navigationSubtitle("Demonstrates multi-stage effects")
.ignoresSafeArea(edges: .top)
}
Loading