Skip to content
Closed
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
363 changes: 0 additions & 363 deletions StikDebug.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions StikJIT/StikJITApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,8 @@ struct HeartbeatApp: App {
window.overrideUserInterfaceStyle = .dark
case "light":
window.overrideUserInterfaceStyle = .light
case "vision":
window.overrideUserInterfaceStyle = .dark
default:
window.overrideUserInterfaceStyle = .unspecified
}
Expand Down Expand Up @@ -507,6 +509,8 @@ struct HeartbeatApp: App {
window.overrideUserInterfaceStyle = .dark
case "light":
window.overrideUserInterfaceStyle = .light
case "vision":
window.overrideUserInterfaceStyle = .dark
default:
window.overrideUserInterfaceStyle = .unspecified
}
Expand Down Expand Up @@ -871,6 +875,8 @@ struct LoadingView: View {
return true
case "light":
return false
case "vision":
return true
default:
return colorScheme == .dark
}
Expand Down
182 changes: 182 additions & 0 deletions StikJIT/SwiftGlass/BlobGlassModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
//
// BlobShape.swift
// SwiftGlass
//
// Created by Ming on 21/4/2025.
//

import SwiftUI

@available(iOS 15.0, macOS 14.0, watchOS 10.0, tvOS 15.0, visionOS 1.0, *)
public struct BlobGlassModifier: ViewModifier {
// Parameters
let color: Color
let blobIntensity: CGFloat
let animationSpeed: Double
let complexity: Int
let material: Material
let gradientOpacity: Double
let shadowOpacity: Double

// Animation state
@State private var animationProgress: CGFloat = 0.0

// Initializer
public init(
color: Color,
blobIntensity: CGFloat,
animationSpeed: Double,
complexity: Int,
material: Material,
gradientOpacity: Double,
shadowOpacity: Double
) {
self.color = color
self.blobIntensity = min(max(blobIntensity, 0.1), 1.0) // Constrain between 0.1 and 1.0
self.animationSpeed = animationSpeed
self.complexity = min(max(complexity, 4), 12) // Constrain between 4 and 12
self.material = material
self.gradientOpacity = gradientOpacity
self.shadowOpacity = shadowOpacity
}

// ViewModifier Body
public func body(content: Content) -> some View {
content
// Clip content to animated blob shape
.clipShape(
BlobShape(
complexity: complexity,
animationProgress: animationProgress,
intensity: blobIntensity
)
)
// Blob glass background and effects
.background(
BlobShape(
complexity: complexity,
animationProgress: animationProgress,
intensity: blobIntensity
)
.fill(material)
.overlay(
BlobShape(
complexity: complexity,
animationProgress: animationProgress,
intensity: blobIntensity
)
.stroke(
LinearGradient(
colors: [
color.opacity(gradientOpacity),
color.opacity(0),
color.opacity(gradientOpacity),
color.opacity(0)
],
startPoint: .topLeading,
endPoint: .bottomTrailing
),
lineWidth: 1.5
)
)
.shadow(
color: color.opacity(shadowOpacity),
radius: 10,
x: 0,
y: 5
)
)
.onAppear {
// Start the continuous animation
withAnimation(
Animation
.linear(duration: 10 / animationSpeed)
.repeatForever(autoreverses: true)
) {
animationProgress = 1.0
}
}
}
}

// BlobShape
@available(iOS 15.0, macOS 14.0, watchOS 10.0, tvOS 15.0, visionOS 1.0, *)
public struct BlobShape: Shape {
// Control points for the blob's perimeter
var controlPoints: [UnitPoint]
var animationProgress: CGFloat
var intensity: CGFloat

// Initializer
public init(complexity: Int = 8, animationProgress: CGFloat = 0.0, intensity: CGFloat = 0.5) {
self.controlPoints = BlobShape.generateControlPoints(count: complexity)
self.animationProgress = animationProgress
self.intensity = intensity
}

// Animatable
public var animatableData: CGFloat {
get { animationProgress }
set { animationProgress = newValue }
}

// Path Generation
public func path(in rect: CGRect) -> Path {
let center = CGPoint(x: rect.midX, y: rect.midY)
let minDimension = min(rect.width, rect.height)
let radius = minDimension / 2

// Calculate points on the blob's perimeter
let points = controlPoints.map { unitPoint -> CGPoint in
let angle = 2 * .pi * unitPoint.x + (animationProgress * 2 * .pi)
let distortionAmount = sin(angle * 3 + animationProgress * 4) * intensity * 0.2
let blobRadius = radius * (1 + distortionAmount)
let pointX = center.x + cos(angle) * blobRadius
let pointY = center.y + sin(angle) * blobRadius
return CGPoint(x: pointX, y: pointY)
}

// Use cubic Bézier curves for smoothness
var path = Path()
guard points.count > 2 else { return path }

path.move(to: points[0])

// Calculate control points for smooth cubic Bézier curves
let count = points.count
for i in 0..<count {
let prev = points[(i - 1 + count) % count]
let curr = points[i]
let next = points[(i + 1) % count]
let next2 = points[(i + 2) % count]

// Calculate control points for smoothness
let control1 = CGPoint(
x: curr.x + (next.x - prev.x) / 6,
y: curr.y + (next.y - prev.y) / 6
)
let control2 = CGPoint(
x: next.x - (next2.x - curr.x) / 6,
y: next.y - (next2.y - curr.y) / 6
)

path.addCurve(to: next, control1: control1, control2: control2)
}

path.closeSubpath()
return path
}

// Helper: Generate evenly distributed control points around a circle
private static func generateControlPoints(count: Int) -> [UnitPoint] {
var points = [UnitPoint]()
let angleStep = 1.0 / CGFloat(count)

for i in 0..<count {
let angle = CGFloat(i) * angleStep
points.append(UnitPoint(x: angle, y: angle))
}

return points
}
}
112 changes: 112 additions & 0 deletions StikJIT/SwiftGlass/GlassBackgroundModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//
// GlassBackgroundModifier.swift
// SwiftGlass
//
// Created by Ming on 21/4/2025.
//

import SwiftUI

@available(iOS 15.0, macOS 14.0, watchOS 10.0, tvOS 15.0, visionOS 1.0, *)
public struct GlassBackgroundModifier: ViewModifier {
/// Controls when the glass effect should be displayed
public enum GlassBackgroundDisplayMode {
case always // Always show the effect
case automatic // System determines visibility based on context
}

/// Determines the gradient direction used in the glass effect's border
public enum GradientStyle {
case normal // Light at top-left and bottom-right
case reverted // Light at top-right and bottom-left
}

// Configuration properties for the glass effect
let displayMode: GlassBackgroundDisplayMode
let radius: CGFloat
let color: Color
let material: Material
let gradientOpacity: Double
let gradientStyle: GradientStyle
let strokeWidth: CGFloat
let shadowColor: Color
let shadowOpacity: Double
let shadowRadius: CGFloat
let shadowX: CGFloat
let shadowY: CGFloat

/// Creates a new glass effect modifier with the specified appearance settings
public init(
displayMode: GlassBackgroundDisplayMode,
radius: CGFloat,
color: Color,
material: Material = .ultraThinMaterial,
gradientOpacity: Double = 0.5,
gradientStyle: GradientStyle = .normal,
strokeWidth: CGFloat = 1.5,
shadowColor: Color = .white,
shadowOpacity: Double = 0.5,
shadowRadius: CGFloat? = nil,
shadowX: CGFloat = 0,
shadowY: CGFloat = 5
) {
self.displayMode = displayMode
self.radius = radius
self.color = color
self.material = material
self.gradientOpacity = gradientOpacity
self.gradientStyle = gradientStyle
self.strokeWidth = strokeWidth
self.shadowColor = shadowColor
self.shadowOpacity = shadowOpacity
self.shadowRadius = shadowRadius ?? radius
self.shadowX = shadowX
self.shadowY = shadowY
}

/// Applies the glass effect to the content view
/// Implementation uses three primary techniques:
/// 1. Material background for the frosted effect
/// 2. Gradient stroke for edge highlighting
/// 3. Shadow for depth perception
public func body(content: Content) -> some View {
content
.background(material) // Use the specified material for the frosted glass base
.cornerRadius(radius) // Rounds the corners
.overlay(
// Adds subtle gradient border for dimensional effect
RoundedRectangle(cornerRadius: radius)
.stroke(
LinearGradient(
gradient: Gradient(colors: gradientColors()),
startPoint: .topLeading,
endPoint: .bottomTrailing
),
lineWidth: strokeWidth
)
)
// Adds shadow for depth and elevation
.shadow(color: shadowColor.opacity(shadowOpacity), radius: shadowRadius, x: shadowX, y: shadowY)
}

/// Generates the gradient colors based on the selected style
/// Creates the illusion of light reflection on glass edges
private func gradientColors() -> [Color] {
switch gradientStyle {
case .normal:
return [
color.opacity(gradientOpacity),
.clear,
.clear,
color.opacity(gradientOpacity)
]
case .reverted:
return [
.clear,
color.opacity(gradientOpacity),
color.opacity(gradientOpacity),
.clear
]
}
}
}
Loading
Loading