Skip to content

Instantly share code, notes, and snippets.

@uvolchyk
Last active May 2, 2025 16:05
Show Gist options
  • Save uvolchyk/3d1f53df51e7f3f472c7c0d54d84b10e to your computer and use it in GitHub Desktop.
Save uvolchyk/3d1f53df51e7f3f472c7c0d54d84b10e to your computer and use it in GitHub Desktop.
import SwiftUI
struct HarmonicButton: View {
var body: some View {
Button(
action: {},
label: {}
)
.frame(width: 240.0, height: 70.0)
.buttonStyle(HarmonicStyle())
}
}
struct HarmonicStyle: ButtonStyle {
@State private var scale: CGFloat = 1.0
@State private var speedMultiplier: Double = 1.0
@State private var amplitude: Float = 0.5
@State private var elapsedTime: Double = 0.0
private let updateInterval: Double = 0.016
func makeBody(configuration: Configuration) -> some View {
TimelineView(.periodic(from: .now, by: updateInterval / speedMultiplier)) { context in
configuration.label
.spatialWrap(Capsule(), lineWidth: 1.0)
.background {
Rectangle()
.colorEffect(ShaderLibrary.default.harmonicColorEffect(
.boundingRect, // bounding rect
.float(6), // waves count,
.float(elapsedTime), // animation clock
.float(amplitude), // amplitude
.float(configuration.isPressed ? 1.0 : 0.0) // monochrome coeff
))
}
.clipShape(Capsule())
.scaleEffect(scale)
.onChange(of: context.date) { _, _ in
elapsedTime += updateInterval * speedMultiplier
}
}
.onChange(of: configuration.isPressed) { _, newValue in
withAnimation(.spring(duration: 0.3)) {
amplitude = newValue ? 2.0 : 0.5
speedMultiplier = newValue ? 2.0 : 1.0
scale = newValue ? 0.95 : 1.0
}
}
.sensoryFeedback(.impact, trigger: configuration.isPressed)
}
}
extension View {
@ViewBuilder
func spatialWrap(
_ shape: some InsettableShape,
lineWidth: CGFloat
) -> some View {
self
.background {
shape
.strokeBorder(
LinearGradient(
gradient: Gradient(stops: [
.init(color: .white.opacity(0.4), location: 0.0),
.init(color: .white.opacity(0.0), location: 0.4),
.init(color: .white.opacity(0.0), location: 0.6),
.init(color: .white.opacity(0.1), location: 1.0),
]),
startPoint: .init(x: 0.16, y: -0.4),
endPoint: .init(x: 0.2, y: 1.5)
),
style: .init(lineWidth: lineWidth)
)
}
}
}
#include <metal_stdlib>
using namespace metal;
float3 getColor(float t) {
if (t == 0) {
return float3(0.4823529412, 0.831372549, 0.8549019608);
}
if (t == 1) {
return float3(0.4117647059, 0.4117647059, 0.8470588235);
}
if (t == 2) {
return float3(0.9411764706, 0.3137254902, 0.4117647059);
}
if (t == 3) {
return float3(0.2745098039, 0.4901960784, 0.9411764706);
}
if (t == 4) {
return float3(0.0784313725, 0.862745098, 0.862745098);
}
if (t == 5) {
return float3(0.7843137255, 0.6274509804, 0.5490196078);
}
return float3(0.0);
}
float glow(float x, float str, float dist){
return dist / pow(x, str);
}
float harmonicSDF(float2 uv, float a, float offset, float f, float phi) {
return abs((uv.y - offset) + cos(uv.x * f + phi) * a);
}
[[ stitchable ]]
half4 harmonicColorEffect(
float2 pos,
half4 color,
float4 bounds,
float wavesCount,
float time,
float amplitude,
float mixCoeff
) {
float2 uv = pos / float2(bounds.z, bounds.w);
uv -= float2(0.5, 0.5);
float a = cos(uv.x * 3.0) * amplitude * 0.2;
float offset = sin(uv.x * 12.0 + time) * a * 0.1;
float frequency = mix(3.0, 12.0, mixCoeff);
float glowWidth = mix(0.6, 0.9, mixCoeff);
float glowIntensity = mix(0.02, 0.01, mixCoeff);
float3 finalColor = float3(0.0);
for (float i = 0.0; i < wavesCount; i++) {
float phase = time + i * M_PI_F / wavesCount;
float sdfDist = glow(harmonicSDF(uv, a, offset, frequency, phase), glowWidth, glowIntensity);
float3 waveColor = mix(float3(1.0), getColor(i), mixCoeff);
finalColor += waveColor * sdfDist;
}
return half4(half3(finalColor), 1.0);
}
@uvolchyk
Copy link
Author

ScreenRecording_04-28-2025.00-44-10_1.mov

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment