Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
7baab0428f
commit
21fc2d6f5b
@ -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 = false
|
static let showDebugBorders = true
|
||||||
|
|
||||||
// MARK: - Shared Constants (from CasinoKit)
|
// MARK: - Shared Constants (from CasinoKit)
|
||||||
|
|
||||||
|
|||||||
@ -115,22 +115,20 @@ struct GameTableView: View {
|
|||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func mainContent(geometry: GeometryProxy) -> some View {
|
private func mainContent(geometry: GeometryProxy) -> some View {
|
||||||
let screenWidth = geometry.size.width
|
|
||||||
let screenHeight = geometry.size.height
|
|
||||||
// Use geometry to detect landscape on iPad (width > height and large screen)
|
// Use geometry to detect landscape on iPad (width > height and large screen)
|
||||||
let isLandscapeLayout = isLargeScreen && screenWidth > screenHeight
|
let isLandscapeLayout = isLargeScreen && geometry.size.width > geometry.size.height
|
||||||
|
|
||||||
if isLandscapeLayout {
|
if isLandscapeLayout {
|
||||||
// Landscape iPad: RoadMap on left, game content on right
|
// Landscape iPad: RoadMap on left, game content on right
|
||||||
landscapeLayout(screenWidth: screenWidth)
|
landscapeLayout
|
||||||
} else {
|
} else {
|
||||||
// Portrait or iPhone: vertical stack with RoadMap inline
|
// Portrait or iPhone: vertical stack with RoadMap inline
|
||||||
portraitLayout(screenWidth: screenWidth)
|
portraitLayout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Landscape layout with TopBar spanning full width, RoadMap grid on left below TopBar
|
/// Landscape layout with TopBar spanning full width, RoadMap grid on left below TopBar
|
||||||
private func landscapeLayout(screenWidth: CGFloat) -> some View {
|
private var landscapeLayout: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
// Top bar spans full width
|
// Top bar spans full width
|
||||||
TopBarView(
|
TopBarView(
|
||||||
@ -195,10 +193,10 @@ struct GameTableView: View {
|
|||||||
bankerValue: state.bankerHandValue,
|
bankerValue: state.bankerHandValue,
|
||||||
playerIsWinner: playerIsWinner,
|
playerIsWinner: playerIsWinner,
|
||||||
bankerIsWinner: bankerIsWinner,
|
bankerIsWinner: bankerIsWinner,
|
||||||
isTie: isTie,
|
isTie: isTie
|
||||||
screenWidth: screenWidth
|
|
||||||
)
|
)
|
||||||
.frame(maxWidth: maxContentWidth)
|
.frame(maxWidth: maxContentWidth)
|
||||||
|
.padding(.horizontal, Design.Spacing.medium)
|
||||||
.debugBorder(showDebugBorders, color: .red, label: "CardsArea")
|
.debugBorder(showDebugBorders, color: .red, label: "CardsArea")
|
||||||
|
|
||||||
Spacer(minLength: 0)
|
Spacer(minLength: 0)
|
||||||
@ -250,7 +248,7 @@ struct GameTableView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Portrait layout with RoadMap inline
|
/// Portrait layout with RoadMap inline
|
||||||
private func portraitLayout(screenWidth: CGFloat) -> some View {
|
private var portraitLayout: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
// Top bar with balance and info (from CasinoKit)
|
// Top bar with balance and info (from CasinoKit)
|
||||||
TopBarView(
|
TopBarView(
|
||||||
@ -277,10 +275,10 @@ struct GameTableView: View {
|
|||||||
bankerValue: state.bankerHandValue,
|
bankerValue: state.bankerHandValue,
|
||||||
playerIsWinner: playerIsWinner,
|
playerIsWinner: playerIsWinner,
|
||||||
bankerIsWinner: bankerIsWinner,
|
bankerIsWinner: bankerIsWinner,
|
||||||
isTie: isTie,
|
isTie: isTie
|
||||||
screenWidth: screenWidth
|
|
||||||
)
|
)
|
||||||
.frame(maxWidth: isLargeScreen ? maxContentWidth : .infinity)
|
.frame(maxWidth: isLargeScreen ? maxContentWidth : .infinity)
|
||||||
|
.padding(.horizontal, Design.Spacing.medium)
|
||||||
.debugBorder(showDebugBorders, color: .red, label: "CardsArea")
|
.debugBorder(showDebugBorders, color: .red, label: "CardsArea")
|
||||||
|
|
||||||
Spacer(minLength: minSpacerHeight)
|
Spacer(minLength: minSpacerHeight)
|
||||||
@ -290,7 +288,7 @@ struct GameTableView: View {
|
|||||||
if settings.showHistory && !state.roundHistory.isEmpty {
|
if settings.showHistory && !state.roundHistory.isEmpty {
|
||||||
RoadMapView(results: state.recentResults)
|
RoadMapView(results: state.recentResults)
|
||||||
.frame(maxWidth: isLargeScreen ? maxContentWidth : .infinity)
|
.frame(maxWidth: isLargeScreen ? maxContentWidth : .infinity)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal, Design.Spacing.medium)
|
||||||
.debugBorder(showDebugBorders, color: .orange, label: "RoadMap")
|
.debugBorder(showDebugBorders, color: .orange, label: "RoadMap")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,8 +19,6 @@ struct CardsDisplayArea: View {
|
|||||||
let playerIsWinner: Bool
|
let playerIsWinner: Bool
|
||||||
let bankerIsWinner: Bool
|
let bankerIsWinner: Bool
|
||||||
let isTie: Bool
|
let isTie: Bool
|
||||||
/// Screen width for responsive card sizing
|
|
||||||
var screenWidth: CGFloat = 400
|
|
||||||
|
|
||||||
// MARK: - Environment
|
// MARK: - Environment
|
||||||
|
|
||||||
@ -51,16 +49,6 @@ struct CardsDisplayArea: View {
|
|||||||
isLargeScreen ? Design.Spacing.xxxLarge : Design.Spacing.large
|
isLargeScreen ? Design.Spacing.xxxLarge : Design.Spacing.large
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Horizontal padding inside the container
|
|
||||||
private var containerPaddingH: CGFloat {
|
|
||||||
isLargeScreen ? Design.Spacing.xLarge : Design.Spacing.medium
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Outer horizontal padding
|
|
||||||
private var outerPaddingH: CGFloat {
|
|
||||||
isLargeScreen ? Design.Spacing.large : Design.Spacing.small
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Accessibility
|
// MARK: - Accessibility
|
||||||
|
|
||||||
private var playerHandDescription: String {
|
private var playerHandDescription: String {
|
||||||
@ -107,15 +95,14 @@ struct CardsDisplayArea: View {
|
|||||||
bankerHandSection
|
bankerHandSection
|
||||||
.debugBorder(showDebugBorders, color: .red, label: "Banker")
|
.debugBorder(showDebugBorders, color: .red, label: "Banker")
|
||||||
}
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
.padding(.top, Design.Spacing.medium)
|
.padding(.top, Design.Spacing.medium)
|
||||||
.padding(.bottom, Design.Spacing.large)
|
.padding(.bottom, Design.Spacing.large)
|
||||||
.padding(.horizontal, containerPaddingH)
|
|
||||||
.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))
|
||||||
.accessibilityHidden(true)
|
.accessibilityHidden(true)
|
||||||
)
|
)
|
||||||
.padding(.horizontal, outerPaddingH)
|
|
||||||
.debugBorder(showDebugBorders, color: .mint, label: "HandsContainer")
|
.debugBorder(showDebugBorders, color: .mint, label: "HandsContainer")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,10 +126,10 @@ struct CardsDisplayArea: View {
|
|||||||
CompactHandView(
|
CompactHandView(
|
||||||
cards: playerCards,
|
cards: playerCards,
|
||||||
cardsFaceUp: playerCardsFaceUp,
|
cardsFaceUp: playerCardsFaceUp,
|
||||||
isWinner: playerIsWinner,
|
isWinner: playerIsWinner
|
||||||
screenWidth: screenWidth
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
.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") : ""))
|
||||||
@ -166,10 +153,10 @@ struct CardsDisplayArea: View {
|
|||||||
CompactHandView(
|
CompactHandView(
|
||||||
cards: bankerCards,
|
cards: bankerCards,
|
||||||
cardsFaceUp: bankerCardsFaceUp,
|
cardsFaceUp: bankerCardsFaceUp,
|
||||||
isWinner: bankerIsWinner,
|
isWinner: bankerIsWinner
|
||||||
screenWidth: screenWidth
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
.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") : ""))
|
||||||
|
|||||||
@ -13,13 +13,25 @@ struct CompactHandView: View {
|
|||||||
let cards: [Card]
|
let cards: [Card]
|
||||||
let cardsFaceUp: [Bool]
|
let cardsFaceUp: [Bool]
|
||||||
let isWinner: Bool
|
let isWinner: Bool
|
||||||
/// Screen width passed from parent for responsive sizing
|
|
||||||
var screenWidth: CGFloat = 400
|
|
||||||
|
|
||||||
// MARK: - Environment
|
// MARK: - Environment
|
||||||
|
|
||||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||||
|
|
||||||
|
// MARK: - Constants
|
||||||
|
|
||||||
|
/// Overlap ratio relative to card width (negative = overlap)
|
||||||
|
private let overlapRatio: CGFloat = -0.45
|
||||||
|
|
||||||
|
/// Maximum number of cards in baccarat hand
|
||||||
|
private let maxCards: Int = 3
|
||||||
|
|
||||||
|
/// Padding around cards
|
||||||
|
private let containerPadding: CGFloat = Design.Spacing.xSmall
|
||||||
|
|
||||||
|
/// Placeholder spacing when no cards
|
||||||
|
private let placeholderSpacing: CGFloat = Design.Spacing.small
|
||||||
|
|
||||||
// MARK: - Computed Properties
|
// MARK: - Computed Properties
|
||||||
|
|
||||||
/// Whether we're on a large screen (iPad)
|
/// Whether we're on a large screen (iPad)
|
||||||
@ -32,55 +44,60 @@ struct CompactHandView: View {
|
|||||||
isLargeScreen ? 14 : 10
|
isLargeScreen ? 14 : 10
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Card width - larger on iPad
|
/// Calculate card width from available container width
|
||||||
private var cardWidth: CGFloat {
|
/// Formula: containerWidth = cardWidth + (cardWidth + overlap) * 2 + padding
|
||||||
isLargeScreen ? Design.Size.cardWidthTableiPad : Design.Size.cardWidthTable
|
/// Where overlap = cardWidth * overlapRatio
|
||||||
|
/// Solving: cardWidth = (containerWidth - 2*padding) / (1 + 2*(1 + overlapRatio))
|
||||||
|
private func cardWidth(for containerWidth: CGFloat) -> CGFloat {
|
||||||
|
let availableWidth = containerWidth - containerPadding * 2
|
||||||
|
let divisor = 1 + CGFloat(maxCards - 1) * (1 + overlapRatio)
|
||||||
|
return availableWidth / divisor
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Card overlap based on card width
|
||||||
|
private func cardOverlap(for cardWidth: CGFloat) -> CGFloat {
|
||||||
|
cardWidth * overlapRatio
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Card height based on aspect ratio
|
/// Card height based on aspect ratio
|
||||||
private var cardHeight: CGFloat {
|
private func cardHeight(for cardWidth: CGFloat) -> CGFloat {
|
||||||
cardWidth * CasinoDesign.Size.cardAspectRatio
|
cardWidth * CasinoDesign.Size.cardAspectRatio
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Card overlap - larger on iPad
|
|
||||||
private var cardOverlap: CGFloat {
|
|
||||||
isLargeScreen ? Design.Size.cardOverlapiPad : Design.Size.cardOverlap
|
|
||||||
}
|
|
||||||
|
|
||||||
private let placeholderSpacing: CGFloat = Design.Spacing.small
|
|
||||||
|
|
||||||
/// Fixed container width to prevent resizing during deal
|
|
||||||
private var fixedContainerWidth: CGFloat {
|
|
||||||
// Max 3 cards: first card full width + 2 more with overlap
|
|
||||||
let cardsWidth = cardWidth + (cardWidth + cardOverlap) * 2
|
|
||||||
return cardsWidth + Design.Spacing.xSmall * 2
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fixed container height
|
|
||||||
private var fixedContainerHeight: CGFloat {
|
|
||||||
cardHeight + Design.Spacing.xSmall * 2
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Body
|
// MARK: - Body
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
GeometryReader { geometry in
|
||||||
// Fixed-size container
|
let width = cardWidth(for: geometry.size.width)
|
||||||
Color.clear
|
let overlap = cardOverlap(for: width)
|
||||||
.frame(width: fixedContainerWidth, height: fixedContainerHeight)
|
let height = cardHeight(for: width)
|
||||||
|
|
||||||
// Cards content centered in fixed container
|
cardsContent(cardWidth: width, cardOverlap: overlap)
|
||||||
cardsContent
|
.frame(width: geometry.size.width, height: height + containerPadding * 2)
|
||||||
}
|
.background(winnerBorder)
|
||||||
.background(winnerBorder)
|
.overlay(alignment: .bottom) {
|
||||||
.overlay(alignment: .bottom) {
|
winBadge
|
||||||
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 var cardsContent: some View {
|
private func cardsContent(cardWidth: CGFloat, cardOverlap: CGFloat) -> 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
|
||||||
@ -99,7 +116,6 @@ struct CompactHandView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(width: fixedContainerWidth, height: fixedContainerHeight)
|
|
||||||
.animation(nil, value: cards.count) // Prevent size animation during dealing
|
.animation(nil, value: cards.count) // Prevent size animation during dealing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user