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