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

This commit is contained in:
Matt Bruce 2026-01-24 16:37:07 -06:00
parent 109b2416fc
commit 32bff901c7
2 changed files with 39 additions and 74 deletions

View File

@ -103,11 +103,11 @@ struct CardsDisplayArea: View {
let percentage: CGFloat = isLandscape ? 0.175 : height < 700 ? 0.14 : 0.18
let cardWidth = height * percentage
// CompactHandView: cardWidth = containerWidth / divisor
let overlapRatio: CGFloat = -0.45
// CompactHandView uses: cardWidth = (containerWidth - 2 * spacing) / 3
// So: containerWidth = 3 * cardWidth + 2 * spacing
let spacing = Design.Spacing.medium
let maxCards: CGFloat = 3
let divisor = 1 + (maxCards - 1) * (1 + overlapRatio)
return cardWidth * divisor
return (cardWidth * maxCards) + (2 * spacing)
}
/// Current hand section width based on mode

View File

@ -2,13 +2,13 @@
// CompactHandView.swift
// Baccarat
//
// A compact view showing cards in a horizontal row with overlap.
// A compact view showing cards in a horizontal row.
//
import SwiftUI
import CasinoKit
/// A compact hand view showing cards in a row with overlap.
/// A compact hand view showing cards in a row.
struct CompactHandView: View {
let cards: [Card]
let cardsFaceUp: [Bool]
@ -34,17 +34,14 @@ struct CompactHandView: View {
// 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
/// Placeholder spacing when no cards
private let placeholderSpacing: CGFloat = Design.Spacing.small
/// Spacing for interactive layout
private let spacedGap: CGFloat = Design.Spacing.medium
/// Spacing between cards
private let cardSpacing: CGFloat = Design.Spacing.medium
// MARK: - Computed Properties
@ -59,18 +56,12 @@ struct CompactHandView: View {
}
/// Card width calculated from container width
/// Formula: containerWidth = cardWidth + (cardWidth + overlap) * 2
/// Where overlap = cardWidth * overlapRatio
/// Formula accounts for spacing between 3 cards
private var cardWidth: CGFloat {
// Use a fixed overlap ratio for sizing to keep cards large
let baseOverlapRatio: CGFloat = -0.45
let divisor = 1 + CGFloat(maxCards - 1) * (1 + baseOverlapRatio)
return containerWidth / divisor
}
/// Card overlap based on card width
private var cardOverlap: CGFloat {
cardWidth * overlapRatio
// containerWidth = 3 * cardWidth + 2 * spacing
// cardWidth = (containerWidth - 2 * spacing) / 3
let spacing = cardSpacing
return max(50, (containerWidth - 2 * spacing) / CGFloat(maxCards))
}
/// Card height based on aspect ratio
@ -78,26 +69,9 @@ struct CompactHandView: View {
cardWidth * CasinoDesign.Size.cardAspectRatio
}
/// Whether to use a spaced layout for interaction
private var useSpacedLayout: Bool {
revealStyle == .tap || revealStyle == .squeeze
}
/// The effective spacing for the card stack
private var effectiveSpacing: CGFloat {
if cards.isEmpty {
return placeholderSpacing
} else if useSpacedLayout {
return spacedGap
} else {
return cardOverlap
}
}
/// Total width required for the spaced layout
private var totalSpacedWidth: CGFloat {
let count = CGFloat(max(cards.count, 2))
return (count * cardWidth) + ((count - 1) * effectiveSpacing)
cards.isEmpty ? placeholderSpacing : cardSpacing
}
// MARK: - Body
@ -106,43 +80,34 @@ struct CompactHandView: View {
GeometryReader { geometry in
let availableWidth = geometry.size.width
Group {
if useSpacedLayout {
// Always use ScrollView in interactive modes for stable view hierarchy
ScrollViewReader { proxy in
ScrollView(.horizontal, showsIndicators: false) {
cardsContent
.padding(.horizontal, Design.Spacing.medium)
.frame(minWidth: availableWidth, alignment: .center)
.id("cards_container")
}
.scrollDisabled(cards.count < 3)
.scrollClipDisabled(true) // Prevent clipping during deal animations
.background(Color.clear)
.onChange(of: cards.count) { _, newCount in
// When 3rd card is dealt, wait for animation then scroll
if newCount == 3 {
let lastIndex = isBottom ? 4 : 5
DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
withAnimation(.spring(duration: 0.5)) {
proxy.scrollTo(lastIndex, anchor: .center)
}
}
}
}
.onChange(of: currentRevealIndex) { _, newIndex in
// Scroll to the active card during interaction
if newIndex >= 4 {
withAnimation(.spring(duration: 0.5)) {
proxy.scrollTo(newIndex, anchor: .center)
}
ScrollViewReader { proxy in
ScrollView(.horizontal, showsIndicators: false) {
cardsContent
.padding(.horizontal, Design.Spacing.medium)
.frame(minWidth: availableWidth, alignment: .center)
.id("cards_container")
}
.scrollDisabled(cards.count < 3)
.scrollClipDisabled(true) // Prevent clipping during deal animations
.background(Color.clear)
.onChange(of: cards.count) { _, newCount in
// When 3rd card is dealt, wait for animation then scroll
if newCount == 3 {
let lastIndex = isBottom ? 4 : 5
DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
withAnimation(.spring(duration: 0.5)) {
proxy.scrollTo(lastIndex, anchor: .center)
}
}
}
} else {
// Regular centered layout for non-interactive modes
cardsContent
.frame(maxWidth: .infinity, alignment: .center)
}
.onChange(of: currentRevealIndex) { _, newIndex in
// Scroll to the active card during interaction
if newIndex >= 4 {
withAnimation(.spring(duration: 0.5)) {
proxy.scrollTo(newIndex, anchor: .center)
}
}
}
}
}