Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
e1d1f29793
commit
baf9d88601
@ -2,13 +2,13 @@
|
||||
// MiniBaccaratTableView.swift
|
||||
// Baccarat
|
||||
//
|
||||
// A realistic mini baccarat table layout for single player with side bets.
|
||||
// A modern baccarat table layout with all betting options.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import CasinoKit
|
||||
|
||||
/// The semi-circular mini baccarat betting table layout.
|
||||
/// The baccarat betting table layout with main bets and side bets.
|
||||
struct MiniBaccaratTableView: View {
|
||||
@Bindable var gameState: GameState
|
||||
let selectedChip: ChipDenomination
|
||||
@ -52,7 +52,7 @@ struct MiniBaccaratTableView: View {
|
||||
// MARK: - Body
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: Design.Spacing.xSmall) {
|
||||
VStack(spacing: Design.Spacing.small) {
|
||||
// Table limits label
|
||||
Text(tableLimitsText)
|
||||
.font(.system(size: tableLimitsFontSize, weight: .bold, design: .rounded))
|
||||
@ -61,88 +61,79 @@ struct MiniBaccaratTableView: View {
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(Design.MinScaleFactor.comfortable)
|
||||
|
||||
// Main table
|
||||
ZStack {
|
||||
// Table felt background with arc shape
|
||||
TableFeltShape()
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [Color.Table.feltLight, Color.Table.feltDark],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
)
|
||||
// Main betting table
|
||||
VStack(spacing: 0) {
|
||||
// Top row: B PAIR | TIE | P PAIR
|
||||
TopBettingRow(
|
||||
bankerPairAmount: betAmount(for: .bankerPair),
|
||||
tieAmount: betAmount(for: .tie),
|
||||
playerPairAmount: betAmount(for: .playerPair),
|
||||
canBetBankerPair: canAddBet(for: .bankerPair),
|
||||
canBetTie: canAddBet(for: .tie),
|
||||
canBetPlayerPair: canAddBet(for: .playerPair),
|
||||
isBankerPairAtMax: isAtMax(for: .bankerPair),
|
||||
isTieAtMax: isAtMax(for: .tie),
|
||||
isPlayerPairAtMax: isAtMax(for: .playerPair),
|
||||
onBankerPair: { gameState.placeBet(type: .bankerPair, amount: selectedChip.rawValue) },
|
||||
onTie: { gameState.placeBet(type: .tie, amount: selectedChip.rawValue) },
|
||||
onPlayerPair: { gameState.placeBet(type: .playerPair, amount: selectedChip.rawValue) }
|
||||
)
|
||||
|
||||
// Gold border
|
||||
TableFeltShape()
|
||||
// Divider
|
||||
Rectangle()
|
||||
.fill(Color.Border.gold.opacity(0.5))
|
||||
.frame(height: 1)
|
||||
|
||||
// Middle row: BANKER | BONUS
|
||||
MainBetRow(
|
||||
title: "BANKER",
|
||||
payoutText: "0.95 : 1",
|
||||
mainBetAmount: betAmount(for: .banker),
|
||||
bonusBetAmount: betAmount(for: .dragonBonusBanker),
|
||||
isSelected: isBankerSelected,
|
||||
canBetMain: canAddBet(for: .banker),
|
||||
canBetBonus: canAddBet(for: .dragonBonusBanker),
|
||||
isMainAtMax: isAtMax(for: .banker),
|
||||
isBonusAtMax: isAtMax(for: .dragonBonusBanker),
|
||||
mainColor: Color.BettingZone.bankerDark,
|
||||
onMain: { gameState.placeBet(type: .banker, amount: selectedChip.rawValue) },
|
||||
onBonus: { gameState.placeBet(type: .dragonBonusBanker, amount: selectedChip.rawValue) }
|
||||
)
|
||||
|
||||
// Divider
|
||||
Rectangle()
|
||||
.fill(Color.Border.gold.opacity(0.5))
|
||||
.frame(height: 1)
|
||||
|
||||
// Bottom row: PLAYER | BONUS
|
||||
MainBetRow(
|
||||
title: "PLAYER",
|
||||
payoutText: "1 : 1",
|
||||
mainBetAmount: betAmount(for: .player),
|
||||
bonusBetAmount: betAmount(for: .dragonBonusPlayer),
|
||||
isSelected: isPlayerSelected,
|
||||
canBetMain: canAddBet(for: .player),
|
||||
canBetBonus: canAddBet(for: .dragonBonusPlayer),
|
||||
isMainAtMax: isAtMax(for: .player),
|
||||
isBonusAtMax: isAtMax(for: .dragonBonusPlayer),
|
||||
mainColor: Color.BettingZone.playerDark,
|
||||
onMain: { gameState.placeBet(type: .player, amount: selectedChip.rawValue) },
|
||||
onBonus: { gameState.placeBet(type: .dragonBonusPlayer, amount: selectedChip.rawValue) }
|
||||
)
|
||||
}
|
||||
.clipShape(RoundedRectangle(cornerRadius: Design.CornerRadius.large))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: Design.CornerRadius.large)
|
||||
.strokeBorder(
|
||||
LinearGradient(
|
||||
colors: [Color.Border.goldLight, Color.Border.goldDark],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
),
|
||||
lineWidth: Design.LineWidth.heavy
|
||||
lineWidth: Design.LineWidth.medium
|
||||
)
|
||||
|
||||
// Betting zones layout
|
||||
VStack(spacing: Design.Spacing.xSmall) {
|
||||
// Top row: B PAIR | TIE | P PAIR
|
||||
TopBettingRow(
|
||||
bankerPairAmount: betAmount(for: .bankerPair),
|
||||
tieAmount: betAmount(for: .tie),
|
||||
playerPairAmount: betAmount(for: .playerPair),
|
||||
canBetBankerPair: canAddBet(for: .bankerPair),
|
||||
canBetTie: canAddBet(for: .tie),
|
||||
canBetPlayerPair: canAddBet(for: .playerPair),
|
||||
isBankerPairAtMax: isAtMax(for: .bankerPair),
|
||||
isTieAtMax: isAtMax(for: .tie),
|
||||
isPlayerPairAtMax: isAtMax(for: .playerPair),
|
||||
onBankerPair: { gameState.placeBet(type: .bankerPair, amount: selectedChip.rawValue) },
|
||||
onTie: { gameState.placeBet(type: .tie, amount: selectedChip.rawValue) },
|
||||
onPlayerPair: { gameState.placeBet(type: .playerPair, amount: selectedChip.rawValue) }
|
||||
)
|
||||
.padding(.horizontal, Design.Spacing.medium)
|
||||
.padding(.top, Design.Spacing.large)
|
||||
|
||||
// Middle row: BANKER | DRAGON BONUS
|
||||
MainBetRow(
|
||||
title: "BANKER",
|
||||
payoutText: "PAYS 0.95 TO 1",
|
||||
mainBetAmount: betAmount(for: .banker),
|
||||
bonusBetAmount: betAmount(for: .dragonBonusBanker),
|
||||
isSelected: isBankerSelected,
|
||||
canBetMain: canAddBet(for: .banker),
|
||||
canBetBonus: canAddBet(for: .dragonBonusBanker),
|
||||
isMainAtMax: isAtMax(for: .banker),
|
||||
isBonusAtMax: isAtMax(for: .dragonBonusBanker),
|
||||
mainColor: Color.BettingZone.bankerLight,
|
||||
mainColorDark: Color.BettingZone.bankerDark,
|
||||
onMain: { gameState.placeBet(type: .banker, amount: selectedChip.rawValue) },
|
||||
onBonus: { gameState.placeBet(type: .dragonBonusBanker, amount: selectedChip.rawValue) }
|
||||
)
|
||||
.padding(.horizontal, Design.Spacing.medium)
|
||||
|
||||
// Bottom row: PLAYER | DRAGON BONUS
|
||||
MainBetRow(
|
||||
title: "PLAYER",
|
||||
payoutText: "PAYS 1 TO 1",
|
||||
mainBetAmount: betAmount(for: .player),
|
||||
bonusBetAmount: betAmount(for: .dragonBonusPlayer),
|
||||
isSelected: isPlayerSelected,
|
||||
canBetMain: canAddBet(for: .player),
|
||||
canBetBonus: canAddBet(for: .dragonBonusPlayer),
|
||||
isMainAtMax: isAtMax(for: .player),
|
||||
isBonusAtMax: isAtMax(for: .dragonBonusPlayer),
|
||||
mainColor: Color.BettingZone.playerLight,
|
||||
mainColorDark: Color.BettingZone.playerDark,
|
||||
onMain: { gameState.placeBet(type: .player, amount: selectedChip.rawValue) },
|
||||
onBonus: { gameState.placeBet(type: .dragonBonusPlayer, amount: selectedChip.rawValue) }
|
||||
)
|
||||
.padding(.horizontal, Design.Spacing.medium)
|
||||
.padding(.bottom, Design.Spacing.large)
|
||||
}
|
||||
}
|
||||
.aspectRatio(Design.Size.tableAspectRatio, contentMode: .fit)
|
||||
)
|
||||
.shadow(color: .black.opacity(0.3), radius: 10, y: 5)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -163,21 +154,25 @@ private struct TopBettingRow: View {
|
||||
let onTie: () -> Void
|
||||
let onPlayerPair: () -> Void
|
||||
|
||||
private let rowHeight: CGFloat = 48
|
||||
private let cornerRadius = Design.CornerRadius.small
|
||||
private let rowHeight: CGFloat = 52
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: Design.Spacing.xSmall) {
|
||||
HStack(spacing: 0) {
|
||||
// B PAIR
|
||||
PairBetZone(
|
||||
title: "B PAIR",
|
||||
betAmount: bankerPairAmount,
|
||||
isEnabled: canBetBankerPair,
|
||||
isAtMax: isBankerPairAtMax,
|
||||
color: Color.BettingZone.bankerLight,
|
||||
color: Color.BettingZone.bankerDark.opacity(0.6),
|
||||
action: onBankerPair
|
||||
)
|
||||
|
||||
// Vertical divider
|
||||
Rectangle()
|
||||
.fill(Color.Border.gold.opacity(0.5))
|
||||
.frame(width: 1)
|
||||
|
||||
// TIE
|
||||
TieBetZone(
|
||||
betAmount: tieAmount,
|
||||
@ -186,13 +181,18 @@ private struct TopBettingRow: View {
|
||||
action: onTie
|
||||
)
|
||||
|
||||
// Vertical divider
|
||||
Rectangle()
|
||||
.fill(Color.Border.gold.opacity(0.5))
|
||||
.frame(width: 1)
|
||||
|
||||
// P PAIR
|
||||
PairBetZone(
|
||||
title: "P PAIR",
|
||||
betAmount: playerPairAmount,
|
||||
isEnabled: canBetPlayerPair,
|
||||
isAtMax: isPlayerPairAtMax,
|
||||
color: Color.BettingZone.playerLight,
|
||||
color: Color.BettingZone.playerDark.opacity(0.6),
|
||||
action: onPlayerPair
|
||||
)
|
||||
}
|
||||
@ -210,9 +210,8 @@ private struct PairBetZone: View {
|
||||
let color: Color
|
||||
let action: () -> Void
|
||||
|
||||
private let cornerRadius = Design.CornerRadius.small
|
||||
private let titleFontSize: CGFloat = 11
|
||||
private let payoutFontSize: CGFloat = 9
|
||||
private let titleFontSize: CGFloat = 12
|
||||
private let payoutFontSize: CGFloat = 10
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
@ -220,33 +219,29 @@ private struct PairBetZone: View {
|
||||
} label: {
|
||||
ZStack {
|
||||
// Background
|
||||
RoundedRectangle(cornerRadius: cornerRadius)
|
||||
.fill(color.opacity(0.6))
|
||||
|
||||
// Border
|
||||
RoundedRectangle(cornerRadius: cornerRadius)
|
||||
.strokeBorder(isAtMax ? Color.Border.silver : Color.Border.gold, lineWidth: Design.LineWidth.thin)
|
||||
Rectangle()
|
||||
.fill(color)
|
||||
|
||||
// Content
|
||||
VStack(spacing: 1) {
|
||||
VStack(spacing: 2) {
|
||||
Text(title)
|
||||
.font(.system(size: titleFontSize, weight: .bold, design: .rounded))
|
||||
.font(.system(size: titleFontSize, weight: .heavy, design: .rounded))
|
||||
.foregroundStyle(.yellow)
|
||||
|
||||
Text("11:1")
|
||||
.font(.system(size: payoutFontSize, weight: .medium))
|
||||
.foregroundStyle(.white.opacity(0.8))
|
||||
Text("11 : 1")
|
||||
.font(.system(size: payoutFontSize, weight: .medium, design: .rounded))
|
||||
.foregroundStyle(.white.opacity(0.7))
|
||||
}
|
||||
|
||||
// Chip indicator
|
||||
if betAmount > 0 {
|
||||
SmallChipIndicator(amount: betAmount, isMax: isAtMax)
|
||||
.offset(x: 0, y: 12)
|
||||
ChipBadge(amount: betAmount, isMax: isAtMax)
|
||||
.offset(y: 16)
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.opacity(isEnabled ? 1.0 : 0.6)
|
||||
.opacity(isEnabled ? 1.0 : 0.5)
|
||||
.accessibilityElement(children: .ignore)
|
||||
.accessibilityLabel("\(title) bet, pays 11 to 1" + (betAmount > 0 ? ", current bet $\(betAmount)" : ""))
|
||||
.accessibilityAddTraits(.isButton)
|
||||
@ -261,9 +256,8 @@ private struct TieBetZone: View {
|
||||
let isAtMax: Bool
|
||||
let action: () -> Void
|
||||
|
||||
private let cornerRadius = Design.CornerRadius.small
|
||||
private let titleFontSize: CGFloat = 13
|
||||
private let payoutFontSize: CGFloat = 9
|
||||
private let titleFontSize: CGFloat = 14
|
||||
private let payoutFontSize: CGFloat = 10
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
@ -271,34 +265,30 @@ private struct TieBetZone: View {
|
||||
} label: {
|
||||
ZStack {
|
||||
// Background
|
||||
RoundedRectangle(cornerRadius: cornerRadius)
|
||||
Rectangle()
|
||||
.fill(isAtMax ? Color.BettingZone.tieMax : Color.BettingZone.tie)
|
||||
|
||||
// Border
|
||||
RoundedRectangle(cornerRadius: cornerRadius)
|
||||
.strokeBorder(isAtMax ? Color.Border.silver : Color.Border.gold, lineWidth: Design.LineWidth.thin)
|
||||
|
||||
// Content
|
||||
VStack(spacing: 1) {
|
||||
VStack(spacing: 2) {
|
||||
Text("TIE")
|
||||
.font(.system(size: titleFontSize, weight: .black, design: .rounded))
|
||||
.tracking(1)
|
||||
|
||||
Text("8 TO 1")
|
||||
.font(.system(size: payoutFontSize, weight: .medium))
|
||||
.opacity(0.8)
|
||||
Text("8 : 1")
|
||||
.font(.system(size: payoutFontSize, weight: .medium, design: .rounded))
|
||||
.opacity(0.7)
|
||||
}
|
||||
.foregroundStyle(.white)
|
||||
|
||||
// Chip indicator
|
||||
if betAmount > 0 {
|
||||
SmallChipIndicator(amount: betAmount, isMax: isAtMax)
|
||||
.offset(x: 0, y: 12)
|
||||
ChipBadge(amount: betAmount, isMax: isAtMax)
|
||||
.offset(y: 16)
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.opacity(isEnabled ? 1.0 : 0.6)
|
||||
.opacity(isEnabled ? 1.0 : 0.5)
|
||||
.accessibilityElement(children: .ignore)
|
||||
.accessibilityLabel("Tie bet, pays 8 to 1" + (betAmount > 0 ? ", current bet $\(betAmount)" : ""))
|
||||
.accessibilityAddTraits(.isButton)
|
||||
@ -318,16 +308,14 @@ private struct MainBetRow: View {
|
||||
let isMainAtMax: Bool
|
||||
let isBonusAtMax: Bool
|
||||
let mainColor: Color
|
||||
let mainColorDark: Color
|
||||
let onMain: () -> Void
|
||||
let onBonus: () -> Void
|
||||
|
||||
private let rowHeight: CGFloat = 55
|
||||
private let bonusWidth: CGFloat = 70
|
||||
private let cornerRadius = Design.CornerRadius.medium
|
||||
private let rowHeight: CGFloat = 65
|
||||
private let bonusWidth: CGFloat = 80
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: Design.Spacing.xSmall) {
|
||||
HStack(spacing: 0) {
|
||||
// Main bet zone (BANKER or PLAYER)
|
||||
MainBetZone(
|
||||
title: title,
|
||||
@ -336,11 +324,15 @@ private struct MainBetRow: View {
|
||||
isSelected: isSelected,
|
||||
isEnabled: canBetMain,
|
||||
isAtMax: isMainAtMax,
|
||||
colorLight: mainColor,
|
||||
colorDark: mainColorDark,
|
||||
color: mainColor,
|
||||
action: onMain
|
||||
)
|
||||
|
||||
// Vertical divider
|
||||
Rectangle()
|
||||
.fill(Color.Border.gold.opacity(0.5))
|
||||
.frame(width: 1)
|
||||
|
||||
// Dragon Bonus zone
|
||||
DragonBonusZone(
|
||||
betAmount: bonusBetAmount,
|
||||
@ -363,68 +355,59 @@ private struct MainBetZone: View {
|
||||
let isSelected: Bool
|
||||
let isEnabled: Bool
|
||||
let isAtMax: Bool
|
||||
let colorLight: Color
|
||||
let colorDark: Color
|
||||
let color: Color
|
||||
let action: () -> Void
|
||||
|
||||
private let cornerRadius = Design.CornerRadius.medium
|
||||
private let titleFontSize: CGFloat = 18
|
||||
private let payoutFontSize: CGFloat = 9
|
||||
private let titleFontSize: CGFloat = 20
|
||||
private let payoutFontSize: CGFloat = 11
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
if isEnabled { action() }
|
||||
} label: {
|
||||
ZStack {
|
||||
// Background gradient
|
||||
RoundedRectangle(cornerRadius: cornerRadius)
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: isAtMax ? [colorLight.opacity(0.5), colorDark.opacity(0.5)] : [colorLight, colorDark],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
)
|
||||
// Background
|
||||
Rectangle()
|
||||
.fill(color)
|
||||
|
||||
// Selection glow
|
||||
// Selection highlight
|
||||
if isSelected {
|
||||
RoundedRectangle(cornerRadius: cornerRadius)
|
||||
.strokeBorder(Color.yellow, lineWidth: Design.LineWidth.thick)
|
||||
.shadow(color: .yellow.opacity(0.5), radius: 6)
|
||||
Rectangle()
|
||||
.fill(Color.yellow.opacity(0.15))
|
||||
|
||||
Rectangle()
|
||||
.strokeBorder(Color.yellow, lineWidth: 3)
|
||||
}
|
||||
|
||||
// Border
|
||||
RoundedRectangle(cornerRadius: cornerRadius)
|
||||
.strokeBorder(isAtMax ? Color.Border.silver : Color.Border.gold, lineWidth: Design.LineWidth.medium)
|
||||
|
||||
// Content
|
||||
VStack(spacing: 2) {
|
||||
Text(title)
|
||||
.font(.system(size: titleFontSize, weight: .black, design: .rounded))
|
||||
.tracking(2)
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
Text(payoutText)
|
||||
.font(.system(size: payoutFontSize, weight: .medium))
|
||||
.opacity(0.8)
|
||||
}
|
||||
.foregroundStyle(.white)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.7)
|
||||
|
||||
// Chip indicator
|
||||
if betAmount > 0 {
|
||||
HStack {
|
||||
Spacer()
|
||||
VStack(spacing: 3) {
|
||||
Text(title)
|
||||
.font(.system(size: titleFontSize, weight: .black, design: .rounded))
|
||||
.tracking(2)
|
||||
|
||||
Text(payoutText)
|
||||
.font(.system(size: payoutFontSize, weight: .semibold, design: .rounded))
|
||||
.opacity(0.7)
|
||||
}
|
||||
.foregroundStyle(.white)
|
||||
|
||||
Spacer()
|
||||
|
||||
// Chip indicator
|
||||
if betAmount > 0 {
|
||||
ChipOnTableView(amount: betAmount, showMax: isAtMax)
|
||||
.padding(.trailing, Design.Spacing.small)
|
||||
.padding(.trailing, Design.Spacing.medium)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.opacity(isEnabled ? 1.0 : 0.6)
|
||||
.opacity(isEnabled ? 1.0 : 0.5)
|
||||
.accessibilityElement(children: .ignore)
|
||||
.accessibilityLabel("\(title) bet, \(payoutText)" + (isSelected ? ", selected" : "") + (betAmount > 0 ? ", current bet $\(betAmount)" : ""))
|
||||
.accessibilityLabel("\(title) bet, pays \(payoutText)" + (isSelected ? ", selected" : "") + (betAmount > 0 ? ", current bet $\(betAmount)" : ""))
|
||||
.accessibilityAddTraits(.isButton)
|
||||
}
|
||||
}
|
||||
@ -437,48 +420,58 @@ private struct DragonBonusZone: View {
|
||||
let isAtMax: Bool
|
||||
let action: () -> Void
|
||||
|
||||
private let cornerRadius = Design.CornerRadius.medium
|
||||
private let titleFontSize: CGFloat = 10
|
||||
private let diamondSize: CGFloat = 20
|
||||
private let titleFontSize: CGFloat = 9
|
||||
private let diamondSize: CGFloat = 24
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
if isEnabled { action() }
|
||||
} label: {
|
||||
ZStack {
|
||||
// Background
|
||||
RoundedRectangle(cornerRadius: cornerRadius)
|
||||
.fill(Color.purple.opacity(0.5))
|
||||
|
||||
// Border
|
||||
RoundedRectangle(cornerRadius: cornerRadius)
|
||||
.strokeBorder(isAtMax ? Color.Border.silver : Color.Border.gold, lineWidth: Design.LineWidth.thin)
|
||||
// Background - darker purple/blue tint
|
||||
Rectangle()
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [
|
||||
Color(red: 0.25, green: 0.3, blue: 0.45),
|
||||
Color(red: 0.15, green: 0.2, blue: 0.35)
|
||||
],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
)
|
||||
|
||||
// Content
|
||||
VStack(spacing: 4) {
|
||||
VStack(spacing: 5) {
|
||||
// Diamond shape
|
||||
DiamondShape()
|
||||
.fill(Color.purple.opacity(0.8))
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [Color.purple.opacity(0.8), Color.purple.opacity(0.5)],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
)
|
||||
.frame(width: diamondSize, height: diamondSize)
|
||||
.overlay(
|
||||
DiamondShape()
|
||||
.strokeBorder(Color.white.opacity(0.5), lineWidth: 1)
|
||||
.strokeBorder(Color.white.opacity(0.4), lineWidth: 1)
|
||||
)
|
||||
|
||||
Text("BONUS")
|
||||
.font(.system(size: titleFontSize, weight: .bold, design: .rounded))
|
||||
.foregroundStyle(.white)
|
||||
.foregroundStyle(.white.opacity(0.9))
|
||||
}
|
||||
|
||||
// Chip indicator
|
||||
if betAmount > 0 {
|
||||
SmallChipIndicator(amount: betAmount, isMax: isAtMax)
|
||||
.offset(y: 18)
|
||||
ChipBadge(amount: betAmount, isMax: isAtMax)
|
||||
.offset(y: 22)
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.opacity(isEnabled ? 1.0 : 0.6)
|
||||
.opacity(isEnabled ? 1.0 : 0.5)
|
||||
.accessibilityElement(children: .ignore)
|
||||
.accessibilityLabel("Dragon Bonus, pays up to 30 to 1" + (betAmount > 0 ? ", current bet $\(betAmount)" : ""))
|
||||
.accessibilityAddTraits(.isButton)
|
||||
@ -510,9 +503,9 @@ private struct DiamondShape: InsettableShape {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Small Chip Indicator
|
||||
// MARK: - Chip Badge (small indicator)
|
||||
|
||||
private struct SmallChipIndicator: View {
|
||||
private struct ChipBadge: View {
|
||||
let amount: Int
|
||||
let isMax: Bool
|
||||
|
||||
@ -520,77 +513,29 @@ private struct SmallChipIndicator: View {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(isMax ? Color.gray : Color.yellow)
|
||||
.frame(width: 18, height: 18)
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Circle()
|
||||
.strokeBorder(Color.white, lineWidth: 1)
|
||||
.frame(width: 18, height: 18)
|
||||
.strokeBorder(Color.white.opacity(0.8), lineWidth: 1)
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
if isMax {
|
||||
Text("M")
|
||||
.font(.system(size: 8, weight: .bold))
|
||||
.font(.system(size: 9, weight: .bold))
|
||||
.foregroundStyle(.white)
|
||||
} else {
|
||||
Text(amount.formatted(.number.notation(.compactName)))
|
||||
.font(.system(size: 6, weight: .bold))
|
||||
Text(formatCompact(amount))
|
||||
.font(.system(size: 7, weight: .bold))
|
||||
.foregroundStyle(.black)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Table Felt Shape
|
||||
|
||||
struct TableFeltShape: InsettableShape {
|
||||
var insetAmount: CGFloat = 0
|
||||
|
||||
private let shapeCornerRadius = Design.CornerRadius.xxLarge
|
||||
|
||||
func path(in rect: CGRect) -> Path {
|
||||
var path = Path()
|
||||
|
||||
let insetRect = rect.insetBy(dx: insetAmount, dy: insetAmount)
|
||||
let height = insetRect.height
|
||||
let cornerRadius = shapeCornerRadius
|
||||
|
||||
// Start from bottom left
|
||||
path.move(to: CGPoint(x: insetRect.minX + cornerRadius, y: insetRect.maxY))
|
||||
|
||||
// Bottom edge
|
||||
path.addLine(to: CGPoint(x: insetRect.maxX - cornerRadius, y: insetRect.maxY))
|
||||
|
||||
// Bottom right corner
|
||||
path.addQuadCurve(
|
||||
to: CGPoint(x: insetRect.maxX, y: insetRect.maxY - cornerRadius),
|
||||
control: CGPoint(x: insetRect.maxX, y: insetRect.maxY)
|
||||
)
|
||||
|
||||
// Right edge going up with curve
|
||||
path.addLine(to: CGPoint(x: insetRect.maxX, y: insetRect.minY + height * 0.3))
|
||||
|
||||
// Top arc
|
||||
path.addQuadCurve(
|
||||
to: CGPoint(x: insetRect.minX, y: insetRect.minY + height * 0.3),
|
||||
control: CGPoint(x: insetRect.midX, y: insetRect.minY - height * 0.1)
|
||||
)
|
||||
|
||||
// Left edge going down
|
||||
path.addLine(to: CGPoint(x: insetRect.minX, y: insetRect.maxY - cornerRadius))
|
||||
|
||||
// Bottom left corner
|
||||
path.addQuadCurve(
|
||||
to: CGPoint(x: insetRect.minX + cornerRadius, y: insetRect.maxY),
|
||||
control: CGPoint(x: insetRect.minX, y: insetRect.maxY)
|
||||
)
|
||||
|
||||
path.closeSubpath()
|
||||
return path
|
||||
}
|
||||
|
||||
func inset(by amount: CGFloat) -> some InsettableShape {
|
||||
var shape = self
|
||||
shape.insetAmount += amount
|
||||
return shape
|
||||
private func formatCompact(_ value: Int) -> String {
|
||||
if value >= 1000 {
|
||||
return "\(value / 1000)K"
|
||||
}
|
||||
return "\(value)"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user