// // HoverButton.swift // DynamicIsland // // Created by Kraigo on 04.09.2024. // import SwiftUI struct HoverButton: View { var icon: String var iconColor: Color = .white; var scale: Image.Scale = .medium var pressEffect: PressEffect? = nil var action: () -> Void var contentTransition: ContentTransition = .symbolEffect; @State private var isHovering = false @State private var pressOffset: CGFloat = 0 @State private var wiggleAngle: Double = 0 @State private var wiggleToken: Int = 0 var body: some View { let size = CGFloat(scale == .large ? 40 : 30) Button(action: { triggerPressEffect() action() }) { Rectangle() .fill(.clear) .contentShape(Rectangle()) .frame(width: size, height: size) .overlay { Capsule() .fill(isHovering ? Color.gray.opacity(0.2) : .clear) .frame(width: size, height: size) .overlay { let baseImage = Image(systemName: icon) .foregroundColor(iconColor) .contentTransition(contentTransition) .font(scale == .large ? .largeTitle : .body) if case .wiggle = pressEffect { if #available(macOS 14.0, *) { baseImage .symbolEffect( .wiggle.byLayer, options: .nonRepeating, value: wiggleToken ) } else { baseImage } } else { baseImage } } } } .buttonStyle(PlainButtonStyle()) .offset(x: pressOffset) .rotationEffect(.degrees(wiggleAngle)) .onHover { hovering in withAnimation(.smooth(duration: 0.3)) { isHovering = hovering } } } private func triggerPressEffect() { guard let pressEffect else { return } switch pressEffect { case .nudge(let amount): withAnimation(.spring(response: 0.2, dampingFraction: 0.55)) { pressOffset = amount } DispatchQueue.main.asyncAfter(deadline: .now() + 0.16) { withAnimation(.spring(response: 0.28, dampingFraction: 0.72)) { pressOffset = 0 } } case .wiggle(let direction): guard #available(macOS 14.0, *) else { return } wiggleToken += 1 let angle: Double = direction == .clockwise ? 10 : -10 withAnimation(.spring(response: 0.18, dampingFraction: 0.5)) { wiggleAngle = angle } DispatchQueue.main.asyncAfter(deadline: .now() + 0.18) { withAnimation(.spring(response: 0.32, dampingFraction: 0.72)) { wiggleAngle = 0 } } } } enum PressEffect { case nudge(CGFloat) case wiggle(WiggleDirection) } enum WiggleDirection { case clockwise case counterClockwise } }