Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>

This commit is contained in:
Matt Bruce 2025-12-22 23:15:24 -06:00
parent 21fc2d6f5b
commit a2ecfcd580
3 changed files with 54 additions and 51 deletions

View File

@ -14,7 +14,7 @@ import CasinoKit
enum Design { enum Design {
/// Set to true to show layout debug borders on views /// Set to true to show layout debug borders on views
static let showDebugBorders = true static let showDebugBorders = false
// MARK: - Shared Constants (from CasinoKit) // MARK: - Shared Constants (from CasinoKit)

View File

@ -20,6 +20,10 @@ struct CardsDisplayArea: View {
let bankerIsWinner: Bool let bankerIsWinner: Bool
let isTie: Bool let isTie: Bool
// MARK: - State
@State private var containerWidth: CGFloat = 300
// MARK: - Environment // MARK: - Environment
@Environment(\.horizontalSizeClass) private var horizontalSizeClass @Environment(\.horizontalSizeClass) private var horizontalSizeClass
@ -83,21 +87,37 @@ struct CardsDisplayArea: View {
return visibleCards.joined(separator: ", ") + ". " + String(format: format, bankerValue) return visibleCards.joined(separator: ", ") + ". " + String(format: format, bankerValue)
} }
/// Calculate hand section width from total container width
private var handSectionWidth: CGFloat {
(containerWidth - handsSpacing) / 2
}
// MARK: - Body // MARK: - Body
var body: some View { var body: some View {
HStack(spacing: handsSpacing) { HStack(spacing: handsSpacing) {
// Player side // Player side
playerHandSection playerHandSection(width: handSectionWidth)
.debugBorder(showDebugBorders, color: .blue, label: "Player") .debugBorder(showDebugBorders, color: .blue, label: "Player")
// Banker side // Banker side
bankerHandSection bankerHandSection(width: handSectionWidth)
.debugBorder(showDebugBorders, color: .red, label: "Banker") .debugBorder(showDebugBorders, color: .red, label: "Banker")
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding(.top, Design.Spacing.medium) .padding(.top, Design.Spacing.medium)
.padding(.bottom, Design.Spacing.large) .padding(.bottom, Design.Spacing.large)
.background(
GeometryReader { geometry in
Color.clear
.onAppear {
containerWidth = geometry.size.width
}
.onChange(of: geometry.size.width) { _, newWidth in
containerWidth = newWidth
}
}
)
.background( .background(
RoundedRectangle(cornerRadius: Design.CornerRadius.xLarge) RoundedRectangle(cornerRadius: Design.CornerRadius.xLarge)
.fill(Color.black.opacity(Design.Opacity.quarter)) .fill(Color.black.opacity(Design.Opacity.quarter))
@ -108,7 +128,7 @@ struct CardsDisplayArea: View {
// MARK: - Private Views // MARK: - Private Views
private var playerHandSection: some View { private func playerHandSection(width: CGFloat) -> some View {
VStack(spacing: Design.Spacing.small) { VStack(spacing: Design.Spacing.small) {
// Label with value // Label with value
HStack(spacing: Design.Spacing.small) { HStack(spacing: Design.Spacing.small) {
@ -126,16 +146,17 @@ struct CardsDisplayArea: View {
CompactHandView( CompactHandView(
cards: playerCards, cards: playerCards,
cardsFaceUp: playerCardsFaceUp, cardsFaceUp: playerCardsFaceUp,
isWinner: playerIsWinner isWinner: playerIsWinner,
containerWidth: width
) )
} }
.frame(maxWidth: .infinity) .frame(width: width)
.accessibilityElement(children: .ignore) .accessibilityElement(children: .ignore)
.accessibilityLabel(String(localized: "Player hand")) .accessibilityLabel(String(localized: "Player hand"))
.accessibilityValue(playerHandDescription + (playerIsWinner ? ", " + String(localized: "Winner") : "")) .accessibilityValue(playerHandDescription + (playerIsWinner ? ", " + String(localized: "Winner") : ""))
} }
private var bankerHandSection: some View { private func bankerHandSection(width: CGFloat) -> some View {
VStack(spacing: Design.Spacing.small) { VStack(spacing: Design.Spacing.small) {
// Label with value // Label with value
HStack(spacing: Design.Spacing.small) { HStack(spacing: Design.Spacing.small) {
@ -153,10 +174,11 @@ struct CardsDisplayArea: View {
CompactHandView( CompactHandView(
cards: bankerCards, cards: bankerCards,
cardsFaceUp: bankerCardsFaceUp, cardsFaceUp: bankerCardsFaceUp,
isWinner: bankerIsWinner isWinner: bankerIsWinner,
containerWidth: width
) )
} }
.frame(maxWidth: .infinity) .frame(width: width)
.accessibilityElement(children: .ignore) .accessibilityElement(children: .ignore)
.accessibilityLabel(String(localized: "Banker hand")) .accessibilityLabel(String(localized: "Banker hand"))
.accessibilityValue(bankerHandDescription + (bankerIsWinner ? ", " + String(localized: "Winner") : "")) .accessibilityValue(bankerHandDescription + (bankerIsWinner ? ", " + String(localized: "Winner") : ""))

View File

@ -13,6 +13,8 @@ struct CompactHandView: View {
let cards: [Card] let cards: [Card]
let cardsFaceUp: [Bool] let cardsFaceUp: [Bool]
let isWinner: Bool let isWinner: Bool
/// Container width passed from parent for sizing
let containerWidth: CGFloat
// MARK: - Environment // MARK: - Environment
@ -26,9 +28,6 @@ struct CompactHandView: View {
/// Maximum number of cards in baccarat hand /// Maximum number of cards in baccarat hand
private let maxCards: Int = 3 private let maxCards: Int = 3
/// Padding around cards
private let containerPadding: CGFloat = Design.Spacing.xSmall
/// Placeholder spacing when no cards /// Placeholder spacing when no cards
private let placeholderSpacing: CGFloat = Design.Spacing.small private let placeholderSpacing: CGFloat = Design.Spacing.small
@ -44,60 +43,38 @@ struct CompactHandView: View {
isLargeScreen ? 14 : 10 isLargeScreen ? 14 : 10
} }
/// Calculate card width from available container width /// Card width calculated from container width
/// Formula: containerWidth = cardWidth + (cardWidth + overlap) * 2 + padding /// Formula: containerWidth = cardWidth + (cardWidth + overlap) * 2
/// Where overlap = cardWidth * overlapRatio /// Where overlap = cardWidth * overlapRatio
/// Solving: cardWidth = (containerWidth - 2*padding) / (1 + 2*(1 + overlapRatio)) private var cardWidth: CGFloat {
private func cardWidth(for containerWidth: CGFloat) -> CGFloat {
let availableWidth = containerWidth - containerPadding * 2
let divisor = 1 + CGFloat(maxCards - 1) * (1 + overlapRatio) let divisor = 1 + CGFloat(maxCards - 1) * (1 + overlapRatio)
return availableWidth / divisor return containerWidth / divisor
} }
/// Card overlap based on card width /// Card overlap based on card width
private func cardOverlap(for cardWidth: CGFloat) -> CGFloat { private var cardOverlap: CGFloat {
cardWidth * overlapRatio cardWidth * overlapRatio
} }
/// Card height based on aspect ratio /// Card height based on aspect ratio
private func cardHeight(for cardWidth: CGFloat) -> CGFloat { private var cardHeight: CGFloat {
cardWidth * CasinoDesign.Size.cardAspectRatio cardWidth * CasinoDesign.Size.cardAspectRatio
} }
// MARK: - Body // MARK: - Body
var body: some View { var body: some View {
GeometryReader { geometry in cardsContent
let width = cardWidth(for: geometry.size.width) .frame(width: containerWidth, height: cardHeight)
let overlap = cardOverlap(for: width) .background(winnerBorder)
let height = cardHeight(for: width) .overlay(alignment: .bottom) {
winBadge
cardsContent(cardWidth: width, cardOverlap: overlap) }
.frame(width: geometry.size.width, height: height + containerPadding * 2)
.background(winnerBorder)
.overlay(alignment: .bottom) {
winBadge
}
}
.aspectRatio(contentWidth / contentHeight, contentMode: .fit)
}
/// Aspect ratio helper - use base card dimensions for consistent sizing
private var contentWidth: CGFloat {
// Base card width for ratio calculation
let baseCardWidth: CGFloat = 45
let baseOverlap = baseCardWidth * overlapRatio
return baseCardWidth + (baseCardWidth + baseOverlap) * CGFloat(maxCards - 1) + containerPadding * 2
}
private var contentHeight: CGFloat {
let baseCardWidth: CGFloat = 45
return baseCardWidth * CasinoDesign.Size.cardAspectRatio + containerPadding * 2
} }
// MARK: - Private Views // MARK: - Private Views
private func cardsContent(cardWidth: CGFloat, cardOverlap: CGFloat) -> some View { private var cardsContent: some View {
HStack(spacing: cards.isEmpty ? placeholderSpacing : cardOverlap) { HStack(spacing: cards.isEmpty ? placeholderSpacing : cardOverlap) {
if cards.isEmpty { if cards.isEmpty {
// Placeholders - no overlap, just side by side // Placeholders - no overlap, just side by side
@ -152,7 +129,8 @@ struct CompactHandView: View {
CompactHandView( CompactHandView(
cards: [], cards: [],
cardsFaceUp: [], cardsFaceUp: [],
isWinner: false isWinner: false,
containerWidth: 160
) )
} }
} }
@ -166,7 +144,8 @@ struct CompactHandView: View {
Card(suit: .hearts, rank: .eight) Card(suit: .hearts, rank: .eight)
], ],
cardsFaceUp: [true, true], cardsFaceUp: [true, true],
isWinner: false isWinner: false,
containerWidth: 160
) )
} }
} }
@ -181,7 +160,8 @@ struct CompactHandView: View {
Card(suit: .clubs, rank: .two) Card(suit: .clubs, rank: .two)
], ],
cardsFaceUp: [true, true, true], cardsFaceUp: [true, true, true],
isWinner: true isWinner: true,
containerWidth: 160
) )
} }
} }
@ -195,7 +175,8 @@ struct CompactHandView: View {
Card(suit: .spades, rank: .seven) Card(suit: .spades, rank: .seven)
], ],
cardsFaceUp: [false, false], cardsFaceUp: [false, false],
isWinner: false isWinner: false,
containerWidth: 160
) )
} }
} }