CasinoGames/Baccarat/Views/Chips/ChipView.swift

129 lines
4.6 KiB
Swift

//
// ChipView.swift
// Baccarat
//
// A realistic casino-style betting chip.
//
import SwiftUI
/// A realistic casino-style betting chip.
struct ChipView: View {
let denomination: ChipDenomination
let size: CGFloat
var isSelected: Bool = false
init(denomination: ChipDenomination, size: CGFloat = 60, isSelected: Bool = false) {
self.denomination = denomination
self.size = size
self.isSelected = isSelected
}
// MARK: - Layout Constants
private let innerCircleRatio: CGFloat = 0.65
private let innerGradientRatio: CGFloat = 0.4
private let textSizeRatio: CGFloat = 0.25
private let selectionGlowPadding: CGFloat = 6
private let shadowOffset: CGFloat = 2
private let shadowOffsetY: CGFloat = 3
var body: some View {
ZStack {
// Base circle with gradient
Circle()
.fill(
RadialGradient(
colors: [
denomination.secondaryColor,
denomination.primaryColor,
denomination.primaryColor.opacity(Design.Opacity.heavy)
],
center: .topLeading,
startRadius: 0,
endRadius: size
)
)
// Edge stripes pattern
ChipEdgePattern(stripeColor: denomination.stripeColor)
.clipShape(.circle)
// Inner circle
Circle()
.fill(
RadialGradient(
colors: [
denomination.secondaryColor,
denomination.primaryColor
],
center: .topLeading,
startRadius: 0,
endRadius: size * innerGradientRatio
)
)
.frame(width: size * innerCircleRatio, height: size * innerCircleRatio)
// Inner border
Circle()
.strokeBorder(
denomination.stripeColor.opacity(Design.Opacity.heavy),
lineWidth: Design.LineWidth.medium
)
.frame(width: size * innerCircleRatio, height: size * innerCircleRatio)
// Denomination text
Text(denomination.displayText)
.font(.system(size: size * textSizeRatio, weight: .heavy, design: .rounded))
.foregroundStyle(denomination.stripeColor)
.shadow(color: .black.opacity(Design.Opacity.light), radius: Design.LineWidth.thin, x: 1, y: 1)
// Outer border
Circle()
.strokeBorder(
LinearGradient(
colors: [
Color.white.opacity(Design.Opacity.overlay),
Color.black.opacity(Design.Opacity.light)
],
startPoint: .topLeading,
endPoint: .bottomTrailing
),
lineWidth: Design.LineWidth.medium
)
// Selection glow
if isSelected {
Circle()
.strokeBorder(Color.yellow, lineWidth: Design.LineWidth.thick)
.frame(width: size + selectionGlowPadding, height: size + selectionGlowPadding)
}
}
.frame(width: size, height: size)
.shadow(color: .black.opacity(Design.Opacity.overlay), radius: isSelected ? Design.Shadow.radiusSmall * 2 : Design.Shadow.radiusSmall, x: shadowOffset, y: shadowOffsetY)
.scaleEffect(isSelected ? Design.Scale.selected : Design.Scale.normal)
.animation(.spring(duration: Design.Animation.selectionDuration), value: isSelected)
.accessibilityLabel(denomination.accessibilityLabel)
.accessibilityValue(isSelected ? String(localized: "Selected") : "")
.accessibilityAddTraits(.isButton)
}
}
#Preview {
ZStack {
Color.Table.preview
.ignoresSafeArea()
VStack(spacing: Design.Spacing.xxxLarge) {
HStack(spacing: Design.Spacing.xLarge) {
ForEach(ChipDenomination.allCases) { denom in
ChipView(denomination: denom, size: Design.Size.chipSelector)
}
}
ChipView(denomination: .hundred, size: 80, isSelected: true)
}
}
}