129 lines
4.6 KiB
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)
|
|
}
|
|
}
|
|
}
|
|
|