Skip to content
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
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,9 @@ struct ContentView: View {
}
}

func createSnow() -> VortexSystem {
let system = VortexSystem(tags: ["circle"])
func createSnow() -> VortexSettings {
let system = VortexSettings()
system.tags = ["circle"]
system.position = [0.5, 0]
system.speed = 0.5
system.speedVariation = 0.25
Expand Down Expand Up @@ -170,7 +171,7 @@ The initializer for `VortexSystem` takes a wide range of configuration options t
The `VortexSystem` initializer parameters are:

- `tags` (`[String]`, *required*) should be the names of one or more views you're passing into a `VortexView` to render this particle system. This string array might only be *some* of the views you're passing in – you might have a secondary system that uses different tags, for example.
- `secondarySystems` (`[VortexSystem]`, defaults to an empty array) should contain all the secondary particle systems that should be attached to this primary emitter.
- `secondarySettings` (`[VortexSettings]`, defaults to an empty array) should contain all the secondary particle settings that should be attached to this primary emitter.
- `spawnOccasion` (`SpawnOccasion`, defaults to `.onBirth`) determines when this secondary system should be created. Ignored if this is your primary particle system.
- `position` (`SIMD2<Double>`, defaults to `[0.5, 0.5]`) determines the center position of this particle system.
- `shape` (`Shape`, defaults to `.point`) determines the bounds of where particles are emitted.
Expand Down
2 changes: 1 addition & 1 deletion Sandbox/Sandbox/PreviewViews/ConfettiView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ struct ConfettiView: View {
ZStack {
Text("Tap anywhere to create confetti.")

VortexView(.confetti.makeUniqueCopy()) {
VortexView(.confetti) {
Rectangle()
.fill(.white)
.frame(width: 16, height: 16)
Expand Down
2 changes: 1 addition & 1 deletion Sandbox/Sandbox/PreviewViews/FireView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ struct FireView: View {
.offset(y: 50)
}

VortexView(.fire.makeUniqueCopy()) {
VortexView(.fire) {
Circle()
.fill(.white)
.frame(width: 32)
Expand Down
2 changes: 1 addition & 1 deletion Sandbox/Sandbox/PreviewViews/FirefliesView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ struct FirefliesView: View {
.padding(.bottom, 20)
}

VortexView(.fireflies.makeUniqueCopy()) {
VortexView(.fireflies) {
Circle()
.fill(.white)
.frame(width: 32)
Expand Down
2 changes: 1 addition & 1 deletion Sandbox/Sandbox/PreviewViews/FireworksView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Vortex
/// A sample view demonstrating the built-in fireworks preset.
struct FireworksView: View {
var body: some View {
VortexView(.fireworks.makeUniqueCopy()) {
VortexView(.fireworks) {
Circle()
.fill(.white)
.frame(width: 32)
Expand Down
2 changes: 1 addition & 1 deletion Sandbox/Sandbox/PreviewViews/MagicView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Vortex
/// A sample view demonstrating the built-in magic preset.
struct MagicView: View {
var body: some View {
VortexView(.magic.makeUniqueCopy()) {
VortexView(.magic) {
Image(.sparkle)
.blendMode(.plusLighter)
.tag("sparkle")
Expand Down
2 changes: 1 addition & 1 deletion Sandbox/Sandbox/PreviewViews/RainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Vortex
/// A sample view demonstrating the built-in rain preset.
struct RainView: View {
var body: some View {
VortexView(.rain.makeUniqueCopy()) {
VortexView(.rain) {
Circle()
.fill(.white)
.frame(width: 32)
Expand Down
2 changes: 1 addition & 1 deletion Sandbox/Sandbox/PreviewViews/SmokeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Vortex
/// A sample view demonstrating the built-in smoke preset.
struct SmokeView: View {
var body: some View {
VortexView(.smoke.makeUniqueCopy()) {
VortexView(.smoke) {
Circle()
.fill(.white)
.frame(width: 64)
Expand Down
2 changes: 1 addition & 1 deletion Sandbox/Sandbox/PreviewViews/SnowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Vortex
/// A sample view demonstrating the built-in snow preset.
struct SnowView: View {
var body: some View {
VortexView(.snow.makeUniqueCopy()) {
VortexView(.snow) {
Circle()
.fill(.white)
.frame(width: 24)
Expand Down
2 changes: 1 addition & 1 deletion Sandbox/Sandbox/PreviewViews/SparkView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Vortex
/// A sample view demonstrating the built-in spark preset.
struct SparkView: View {
var body: some View {
VortexView(.spark.makeUniqueCopy()) {
VortexView(.spark) {
Circle()
.fill(.white)
.frame(width: 16)
Expand Down
4 changes: 2 additions & 2 deletions Sandbox/Sandbox/PreviewViews/SplashView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ import Vortex
struct SplashView: View {
var body: some View {
ZStack {
VortexView(.rain.makeUniqueCopy()) {
VortexView(.rain) {
Circle()
.fill(.white)
.frame(width: 32)
.tag("circle")
}

VortexView(.splash.makeUniqueCopy()) {
VortexView(.splash) {
Circle()
.fill(.white)
.frame(width: 16, height: 16)
Expand Down
6 changes: 3 additions & 3 deletions Sources/Vortex/Helpers/Array-InterpolatedColor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
//
import SwiftUI

extension Array where Element == VortexSystem.Color {
extension Array where Element == VortexSettings.Color {
/// Creates a new color by linearly interpolating between other colors in a color array.
/// - Parameter amount: How far through the array we should be reading. For example,
/// if the array contains white then black and `amount` is set to 0.5, this will return gray.
/// - Returns: A new color created by interpolating existing colors inside the array.
func lerp(by amount: Double) -> VortexSystem.Color {
func lerp(by amount: Double) -> VortexSettings.Color {
guard isEmpty == false else {
fatalError("Attempting to interpolate an empty color array.")
}
Expand All @@ -35,7 +35,7 @@ extension Array where Element == VortexSystem.Color {
let interpolatedBlue = lowerColor.blue.lerp(to: upperColor.blue, amount: interpolationFactor)
let interpolatedOpacity = lowerColor.opacity.lerp(to: upperColor.opacity, amount: interpolationFactor)

return VortexSystem.Color(
return VortexSettings.Color(
red: interpolatedRed,
green: interpolatedGreen,
blue: interpolatedBlue,
Expand Down
46 changes: 31 additions & 15 deletions Sources/Vortex/Presets/Confetti.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,39 @@

import SwiftUI

extension VortexSystem {
extension VortexSettings {
/// A built-in effect that creates 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 = {
var settings = VortexSettings()
settings.tags = ["square", "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
return settings
}()
}

@available(macOS 14.0, *) // needed for .onTapGesture
#Preview {
VortexViewReader { proxy in
ZStack {
Text("Tap anywhere to create confetti.")

VortexView(.confetti)
.onTapGesture { location in
proxy.move(to: location)
proxy.burst()
proxy.particleSystem?.lifespan = 5
}
}
}
}
26 changes: 23 additions & 3 deletions Sources/Vortex/Presets/Fire.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

import SwiftUI

extension VortexSystem {
extension VortexSettings {
/// 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(
public static let fire: VortexSettings = {
VortexSettings(
tags: ["circle"],
shape: .box(width: 0.1, height: 0),
birthRate: 300,
Expand All @@ -25,3 +25,23 @@ extension VortexSystem {
)
}()
}

#Preview("Demonstrates a modified fire preset") {
/// Here we modify the default fire settings to extend it across the bottom of the screen
let floorOnFire = {
var settings = VortexSettings(basedOn: .fire)
settings.position = [0.5, 1.02]
settings.shape = .box(width: 1.0, height: 0)
settings.birthRate = 600
return settings
}()

VortexView(floorOnFire) {
Circle()
.fill(.white)
.frame(width: 32)
.blur(radius: 3)
.blendMode(.plusLighter)
.tag("circle")
}
}
33 changes: 19 additions & 14 deletions Sources/Vortex/Presets/Fireflies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

import SwiftUI

extension VortexSystem {
extension VortexSettings {
/// 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(
public static let fireflies: VortexSettings = {
VortexSettings(
tags: ["circle"],
shape: .ellipse(radius: 0.5),
birthRate: 200,
Expand All @@ -35,20 +35,25 @@ extension VortexSystem {
@Previewable @State var pressingOptionKey = false
VortexViewReader { proxy in
ZStack(alignment: .bottom) {
if isDragging {
Text("Release your drag to reset the fireflies.")
.padding(.bottom, 20)
let instructions = if isDragging {
"Release your drag to reset the fireflies."
} else if !pressingOptionKey {
"Drag anywhere to repel the fireflies. Or hold the Option Key"
} 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)
"Drag anywhere to attract the fireflies"
}

VortexView(.fireflies)
Text(instructions)
.padding(.bottom, 20)

VortexView(.fireflies) {
Circle()
.fill(.white)
.frame(width: 32)
.blur(radius: 3)
.blendMode(.plusLighter)
.tag("circle")
}
.onModifierKeysChanged(mask: .option) { _, new in
// set the view state based on whether the
// `new` EventModifiers value contains a value (that would be the option key)
Expand Down
23 changes: 17 additions & 6 deletions Sources/Vortex/Presets/Fireworks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@

import SwiftUI

extension VortexSystem {
extension VortexSettings {
/// 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(
public static let fireworks: VortexSettings = {
let sparkles = VortexSettings(
tags: ["circle"],
spawnOccasion: .onUpdate,
emissionLimit: 1,
Expand All @@ -22,7 +22,7 @@ extension VortexSystem {
size: 0.05
)

let explosion = VortexSystem(
let explosion = VortexSettings(
tags: ["circle"],
spawnOccasion: .onDeath,
position: [0.5, 1],
Expand All @@ -45,9 +45,9 @@ extension VortexSystem {
sizeMultiplierAtDeath: 0
)

let mainSystem = VortexSystem(
let mainSystem = VortexSettings(
tags: ["circle"],
secondarySystems: [sparkles, explosion],
secondarySettings: [sparkles, explosion],
position: [0.5, 1],
birthRate: 2,
emissionLimit: 1000,
Expand All @@ -62,3 +62,14 @@ extension VortexSystem {
return mainSystem
}()
}

#Preview("Demonstrates multi-stage effects") {
VortexView(.fireworks) {
Circle()
.fill(.white)
.frame(width: 32)
.blur(radius: 5)
.blendMode(.plusLighter)
.tag("circle")
}
}
10 changes: 6 additions & 4 deletions Sources/Vortex/Presets/Magic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

import SwiftUI

extension VortexSystem {
extension VortexSettings {
/// A built-in magic effect. Relies on a "sparkle" tag being present, which should be set to use
/// `.blendMode(.plusLighter)`.
public static let magic: VortexSystem = {
VortexSystem(
public static let magic =
VortexSettings(
tags: ["sparkle"],
shape: .ring(radius: 0.5),
lifespan: 1.5,
Expand All @@ -24,5 +24,7 @@ extension VortexSystem {
sizeVariation: 0.5,
sizeMultiplierAtDeath: 0.01
)
}()
}
#Preview {
VortexView(.magic)
}
10 changes: 6 additions & 4 deletions Sources/Vortex/Presets/Rain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@

import SwiftUI

extension VortexSystem {
extension VortexSettings {
/// A built-in rain effect. Relies on a "circle" tag being present.
public static let rain: VortexSystem = {
VortexSystem(
public static let rain =
VortexSettings(
tags: ["circle"],
position: [0.5, 0 ],
shape: .box(width: 1.8, height: 0),
Expand All @@ -28,5 +28,7 @@ extension VortexSystem {
sizeVariation: 0.05,
stretchFactor: 12
)
}()
}
#Preview("Demonstrate use of 'rain' preset") {
VortexView(.rain)
}
Loading