Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
ec484d8574
commit
740ef6a73b
@ -14,6 +14,10 @@ import CasinoKit
|
||||
/// Design constants for the Blackjack app.
|
||||
/// Shared constants are imported from CasinoDesign; game-specific values are defined here.
|
||||
enum Design {
|
||||
// MARK: - Debug
|
||||
|
||||
/// Set to true to show layout debug borders on views
|
||||
static let showDebugBorders = false
|
||||
|
||||
// MARK: - Shared Constants (from CasinoKit)
|
||||
|
||||
@ -39,12 +43,13 @@ enum Design {
|
||||
static let cardWidthSmall: CGFloat = CasinoDesign.Size.cardWidthSmall
|
||||
static let cardOverlap: CGFloat = CasinoDesign.Size.cardOverlap * handScale // Scaled overlap
|
||||
|
||||
// Player hands container height (accommodates larger cards)
|
||||
static let playerHandsHeight: CGFloat = 180 * handScale // 270pt at 1.5x
|
||||
// Player hands container height (accommodates larger cards + labels)
|
||||
// Reduced from 180 to fit content more snugly
|
||||
static let playerHandsHeight: CGFloat = 160 * handScale // 240pt at 1.5x
|
||||
|
||||
// Hand label font sizes (scaled)
|
||||
static let handLabelFontSize: CGFloat = CasinoDesign.BaseFontSize.medium * handScale
|
||||
static let handNumberFontSize: CGFloat = CasinoDesign.BaseFontSize.small * handScale
|
||||
static let handNumberFontSize: CGFloat = CasinoDesign.BaseFontSize.medium * handScale // Same as label
|
||||
static let handValueFontSize: CGFloat = CasinoDesign.BaseFontSize.xLarge * handScale
|
||||
|
||||
// Hint font size (scaled to match hands)
|
||||
|
||||
@ -15,15 +15,11 @@ struct ActionButtonsView: View {
|
||||
@ScaledMetric(relativeTo: .headline) private var buttonFontSize: CGFloat = Design.BaseFontSize.large
|
||||
@ScaledMetric(relativeTo: .body) private var iconSize: CGFloat = Design.IconSize.large
|
||||
|
||||
// Fixed height to prevent layout shifts
|
||||
private let containerHeight: CGFloat = 120
|
||||
// Scaled container height - base 60pt, scales with accessibility
|
||||
@ScaledMetric(relativeTo: .body) private var containerHeight: CGFloat = 60
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color.clear
|
||||
.frame(height: containerHeight)
|
||||
|
||||
VStack(spacing: Design.Spacing.medium) {
|
||||
VStack(spacing: Design.Spacing.small) {
|
||||
// Primary actions
|
||||
HStack(spacing: Design.Spacing.medium) {
|
||||
switch state.currentPhase {
|
||||
@ -41,7 +37,7 @@ struct ActionButtonsView: View {
|
||||
}
|
||||
.animation(.spring(duration: Design.Animation.quick), value: state.currentPhase)
|
||||
}
|
||||
}
|
||||
.frame(minHeight: containerHeight)
|
||||
.padding(.horizontal, Design.Spacing.large)
|
||||
}
|
||||
|
||||
|
||||
@ -65,6 +65,9 @@ struct GameTableView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// Use global debug flag from Design constants
|
||||
private var showDebugBorders: Bool { Design.showDebugBorders }
|
||||
|
||||
// MARK: - Main Game View
|
||||
|
||||
@ViewBuilder
|
||||
@ -85,6 +88,7 @@ struct GameTableView: View {
|
||||
onStats: { showStats = true }
|
||||
)
|
||||
.frame(maxWidth: maxContentWidth)
|
||||
.debugBorder(showDebugBorders, color: .cyan, label: "TopBar")
|
||||
|
||||
// Card count display (when enabled)
|
||||
if settings.showCardCount {
|
||||
@ -93,6 +97,7 @@ struct GameTableView: View {
|
||||
trueCount: state.engine.trueCount
|
||||
)
|
||||
.frame(maxWidth: maxContentWidth)
|
||||
.debugBorder(showDebugBorders, color: .mint, label: "CardCount")
|
||||
}
|
||||
|
||||
// Reshuffle notification
|
||||
@ -102,17 +107,18 @@ struct GameTableView: View {
|
||||
.transition(.move(edge: .top).combined(with: .opacity))
|
||||
}
|
||||
|
||||
// Table layout
|
||||
// Table layout - fills available space
|
||||
BlackjackTableView(
|
||||
state: state,
|
||||
onPlaceBet: { placeBet(state: state) }
|
||||
)
|
||||
.frame(maxWidth: maxContentWidth)
|
||||
|
||||
Spacer()
|
||||
|
||||
// Chip selector - only shown during betting phase
|
||||
if state.currentPhase == .betting {
|
||||
Spacer()
|
||||
.debugBorder(showDebugBorders, color: .yellow, label: "ChipSpacer")
|
||||
|
||||
ChipSelectorView(
|
||||
selectedChip: $selectedChip,
|
||||
balance: state.balance,
|
||||
@ -120,14 +126,15 @@ struct GameTableView: View {
|
||||
maxBet: state.settings.maxBet
|
||||
)
|
||||
.frame(maxWidth: maxContentWidth)
|
||||
.padding(.bottom, Design.Spacing.small)
|
||||
.transition(.opacity.combined(with: .move(edge: .bottom)))
|
||||
.debugBorder(showDebugBorders, color: .pink, label: "ChipSelector")
|
||||
}
|
||||
|
||||
// Action buttons
|
||||
// Action buttons - minimal spacing during player turn
|
||||
ActionButtonsView(state: state)
|
||||
.frame(maxWidth: maxContentWidth)
|
||||
.padding(.bottom, Design.Spacing.medium)
|
||||
.padding(.bottom, Design.Spacing.small)
|
||||
.debugBorder(showDebugBorders, color: .blue, label: "ActionBtns")
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
|
||||
@ -26,8 +26,14 @@ struct BlackjackTableView: View {
|
||||
private let cardWidth: CGFloat = Design.Size.cardWidth
|
||||
private let cardSpacing: CGFloat = Design.Size.cardOverlap
|
||||
|
||||
/// Fixed height for the hint area to prevent layout shifts
|
||||
private let hintAreaHeight: CGFloat = 44
|
||||
|
||||
// Use global debug flag from Design constants
|
||||
private var showDebugBorders: Bool { Design.showDebugBorders }
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: Design.Spacing.large) {
|
||||
VStack(spacing: Design.Spacing.small) {
|
||||
// Dealer area
|
||||
DealerHandView(
|
||||
hand: state.dealerHand,
|
||||
@ -36,8 +42,11 @@ struct BlackjackTableView: View {
|
||||
cardWidth: cardWidth,
|
||||
cardSpacing: cardSpacing
|
||||
)
|
||||
.debugBorder(showDebugBorders, color: .red, label: "Dealer")
|
||||
|
||||
Spacer()
|
||||
// Flexible space between dealer and player (minimum 60pt)
|
||||
Spacer(minLength: 60)
|
||||
.debugBorder(showDebugBorders, color: .yellow, label: "Spacer")
|
||||
|
||||
// Player hands area - only show when there are cards dealt
|
||||
if state.playerHands.first?.cards.isEmpty == false {
|
||||
@ -50,10 +59,14 @@ struct BlackjackTableView: View {
|
||||
cardSpacing: cardSpacing
|
||||
)
|
||||
.transition(.opacity)
|
||||
.debugBorder(showDebugBorders, color: .green, label: "Player")
|
||||
}
|
||||
|
||||
// Betting zone (when betting)
|
||||
if state.currentPhase == .betting {
|
||||
Spacer()
|
||||
.debugBorder(showDebugBorders, color: .yellow, label: "Spacer2")
|
||||
|
||||
BettingZoneView(
|
||||
betAmount: state.currentBet,
|
||||
minBet: state.settings.minBet,
|
||||
@ -61,22 +74,29 @@ struct BlackjackTableView: View {
|
||||
onTap: onPlaceBet
|
||||
)
|
||||
.transition(.scale.combined(with: .opacity))
|
||||
.debugBorder(showDebugBorders, color: .blue, label: "BetZone")
|
||||
|
||||
// Betting hint based on count (only when card counting enabled)
|
||||
if showCardCount, let bettingHint = bettingHint {
|
||||
BettingHintView(hint: bettingHint, trueCount: state.engine.trueCount)
|
||||
.transition(.opacity)
|
||||
.debugBorder(showDebugBorders, color: .purple, label: "BetHint")
|
||||
}
|
||||
}
|
||||
|
||||
// Hint (when enabled and player turn)
|
||||
} else {
|
||||
// Fixed-height hint area to prevent layout shifts during player turn
|
||||
ZStack {
|
||||
if state.settings.showHints && isPlayerTurn, let hint = currentHint {
|
||||
HintView(hint: hint)
|
||||
.transition(.opacity)
|
||||
}
|
||||
}
|
||||
.frame(height: hintAreaHeight)
|
||||
.debugBorder(showDebugBorders, color: .orange, label: "HintArea")
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, Design.Spacing.large)
|
||||
.padding(.vertical, Design.Spacing.medium)
|
||||
.debugBorder(showDebugBorders, color: .white, label: "TableView")
|
||||
.animation(.spring(duration: Design.Animation.springDuration), value: state.currentPhase)
|
||||
}
|
||||
|
||||
|
||||
@ -19,6 +19,11 @@ struct PlayerHandsView: View {
|
||||
let cardWidth: CGFloat
|
||||
let cardSpacing: CGFloat
|
||||
|
||||
/// Total card count across all hands - used to trigger scroll when hitting
|
||||
private var totalCardCount: Int {
|
||||
hands.reduce(0) { $0 + $1.cards.count }
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
ScrollViewReader { proxy in
|
||||
@ -40,24 +45,35 @@ struct PlayerHandsView: View {
|
||||
.id(index)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, Design.Spacing.medium)
|
||||
.padding(.horizontal, Design.Spacing.large)
|
||||
.frame(minWidth: geometry.size.width)
|
||||
}
|
||||
.scrollClipDisabled()
|
||||
.scrollBounceBehavior(.basedOnSize)
|
||||
.onChange(of: activeHandIndex) { _, newIndex in
|
||||
withAnimation(.easeInOut(duration: Design.Animation.quick)) {
|
||||
proxy.scrollTo(newIndex, anchor: .center)
|
||||
scrollToHand(proxy: proxy, index: newIndex)
|
||||
}
|
||||
.onChange(of: totalCardCount) { _, _ in
|
||||
// Scroll to active hand when cards are added (hit)
|
||||
scrollToHand(proxy: proxy, index: activeHandIndex)
|
||||
}
|
||||
.onChange(of: hands.count) { _, _ in
|
||||
// Scroll to active hand when split occurs
|
||||
scrollToHand(proxy: proxy, index: activeHandIndex)
|
||||
}
|
||||
.onAppear {
|
||||
if hands.count > 1 {
|
||||
proxy.scrollTo(activeHandIndex, anchor: .center)
|
||||
}
|
||||
scrollToHand(proxy: proxy, index: activeHandIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(height: Design.Size.playerHandsHeight)
|
||||
}
|
||||
|
||||
private func scrollToHand(proxy: ScrollViewProxy, index: Int) {
|
||||
withAnimation(.easeInOut(duration: Design.Animation.quick)) {
|
||||
proxy.scrollTo(index, anchor: .center)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Single Player Hand
|
||||
|
||||
@ -76,3 +76,6 @@
|
||||
// - CloudSyncManager
|
||||
// - PersistableGameData (protocol)
|
||||
|
||||
// MARK: - Debug
|
||||
// - debugBorder(_:color:label:) View modifier
|
||||
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
//
|
||||
// DebugBorderModifier.swift
|
||||
// CasinoKit
|
||||
//
|
||||
// Debug view modifier for visualizing layout boundaries.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Debug Border Modifier
|
||||
|
||||
public extension View {
|
||||
/// Adds a colored border and label to a view for debugging layout.
|
||||
/// - Parameters:
|
||||
/// - show: Whether to show the debug border.
|
||||
/// - color: The color of the border and label.
|
||||
/// - label: The label text to display in the corner.
|
||||
/// - Returns: The view with an optional debug border overlay.
|
||||
@ViewBuilder
|
||||
func debugBorder(_ show: Bool, color: Color, label: String) -> some View {
|
||||
if show {
|
||||
self
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: CasinoDesign.CornerRadius.xSmall)
|
||||
.strokeBorder(color, lineWidth: CasinoDesign.LineWidth.thin)
|
||||
)
|
||||
.overlay(alignment: .topLeading) {
|
||||
Text(label)
|
||||
.font(.system(size: 8, weight: .bold))
|
||||
.foregroundStyle(color)
|
||||
.padding(2)
|
||||
.background(Color.black.opacity(CasinoDesign.Opacity.strong))
|
||||
}
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user