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

This commit is contained in:
Matt Bruce 2025-12-31 09:57:45 -06:00
parent e1655ce20c
commit ca742eb73f

View File

@ -30,22 +30,34 @@ struct CardStackView: View {
/// This prevents width changes during dealing by pre-allocating placeholder space. /// This prevents width changes during dealing by pre-allocating placeholder space.
let minimumCardSlots: Int let minimumCardSlots: Int
/// Whether placeholders should use staggered (overlapped) layout like dealt cards.
/// - `true`: Placeholders overlap using `cardSpacing` (use for bordered containers like player hand)
/// - `false`: Placeholders side-by-side (use for borderless containers like dealer hand)
let staggeredPlaceholders: Bool
/// Number of additional placeholders needed to reach minimum slots. /// Number of additional placeholders needed to reach minimum slots.
private var placeholdersNeeded: Int { private var placeholdersNeeded: Int {
max(0, minimumCardSlots - cards.count) max(0, minimumCardSlots - cards.count)
} }
/// Spacing to use for empty placeholder state
private var placeholderSpacing: CGFloat {
staggeredPlaceholders ? cardSpacing : Design.Spacing.small
}
/// Scaled animation duration based on dealing speed. /// Scaled animation duration based on dealing speed.
private var animationDuration: Double { private var animationDuration: Double {
Design.Animation.springDuration * dealingSpeed Design.Animation.springDuration * dealingSpeed
} }
var body: some View { var body: some View {
HStack(spacing: cards.isEmpty && placeholdersNeeded > 0 ? Design.Spacing.small : cardSpacing) { // Use staggered or side-by-side spacing based on configuration
HStack(spacing: cards.isEmpty && placeholdersNeeded > 0 ? placeholderSpacing : cardSpacing) {
if cards.isEmpty && placeholdersNeeded > 0 { if cards.isEmpty && placeholdersNeeded > 0 {
// Show placeholders when empty // Show placeholders when empty
ForEach(0..<placeholdersNeeded, id: \.self) { _ in ForEach(0..<placeholdersNeeded, id: \.self) { index in
CardPlaceholderView(width: cardWidth) CardPlaceholderView(width: cardWidth)
.zIndex(Double(index))
} }
} else { } else {
// Show actual cards // Show actual cards
@ -95,6 +107,7 @@ struct CardStackView: View {
extension CardStackView { extension CardStackView {
/// Creates a card stack for the dealer (with hole card support). /// Creates a card stack for the dealer (with hole card support).
/// Uses side-by-side placeholders since dealer area has no border.
static func dealer( static func dealer(
cards: [Card], cards: [Card],
showHoleCard: Bool, showHoleCard: Bool,
@ -116,11 +129,13 @@ extension CardStackView {
x: Design.DealAnimation.dealerOffsetX, x: Design.DealAnimation.dealerOffsetX,
y: Design.DealAnimation.dealerOffsetY y: Design.DealAnimation.dealerOffsetY
), ),
minimumCardSlots: 2 minimumCardSlots: 2,
staggeredPlaceholders: false // Side-by-side (no border to show shrink)
) )
} }
/// Creates a card stack for the player (all cards face up). /// Creates a card stack for the player (all cards face up).
/// Uses staggered placeholders to match dealt card layout (prevents container shrink).
/// - Parameter minimumCardSlots: Minimum slots to reserve (default 2 for initial deal). /// - Parameter minimumCardSlots: Minimum slots to reserve (default 2 for initial deal).
static func player( static func player(
cards: [Card], cards: [Card],
@ -143,14 +158,15 @@ extension CardStackView {
x: Design.DealAnimation.playerOffsetX, x: Design.DealAnimation.playerOffsetX,
y: Design.DealAnimation.playerOffsetY y: Design.DealAnimation.playerOffsetY
), ),
minimumCardSlots: minimumCardSlots minimumCardSlots: minimumCardSlots,
staggeredPlaceholders: true // Staggered (bordered container shows shrink)
) )
} }
} }
// MARK: - Previews // MARK: - Previews
#Preview("Empty") { #Preview("Empty - Staggered") {
ZStack { ZStack {
Color.Table.felt.ignoresSafeArea() Color.Table.felt.ignoresSafeArea()
CardStackView( CardStackView(
@ -162,7 +178,26 @@ extension CardStackView {
showCardCount: false, showCardCount: false,
isFaceUp: { _ in true }, isFaceUp: { _ in true },
dealOffset: .zero, dealOffset: .zero,
minimumCardSlots: 2 minimumCardSlots: 2,
staggeredPlaceholders: true
)
}
}
#Preview("Empty - Side by Side") {
ZStack {
Color.Table.felt.ignoresSafeArea()
CardStackView(
cards: [],
cardWidth: 60,
cardSpacing: -20,
showAnimations: true,
dealingSpeed: 1.0,
showCardCount: false,
isFaceUp: { _ in true },
dealOffset: .zero,
minimumCardSlots: 2,
staggeredPlaceholders: false
) )
} }
} }