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

This commit is contained in:
Matt Bruce 2025-12-16 21:26:34 -06:00
parent e1d1f29793
commit baf9d88601

View File

@ -2,13 +2,13 @@
// MiniBaccaratTableView.swift // MiniBaccaratTableView.swift
// Baccarat // Baccarat
// //
// A realistic mini baccarat table layout for single player with side bets. // A modern baccarat table layout with all betting options.
// //
import SwiftUI import SwiftUI
import CasinoKit import CasinoKit
/// The semi-circular mini baccarat betting table layout. /// The baccarat betting table layout with main bets and side bets.
struct MiniBaccaratTableView: View { struct MiniBaccaratTableView: View {
@Bindable var gameState: GameState @Bindable var gameState: GameState
let selectedChip: ChipDenomination let selectedChip: ChipDenomination
@ -52,7 +52,7 @@ struct MiniBaccaratTableView: View {
// MARK: - Body // MARK: - Body
var body: some View { var body: some View {
VStack(spacing: Design.Spacing.xSmall) { VStack(spacing: Design.Spacing.small) {
// Table limits label // Table limits label
Text(tableLimitsText) Text(tableLimitsText)
.font(.system(size: tableLimitsFontSize, weight: .bold, design: .rounded)) .font(.system(size: tableLimitsFontSize, weight: .bold, design: .rounded))
@ -61,88 +61,79 @@ struct MiniBaccaratTableView: View {
.lineLimit(1) .lineLimit(1)
.minimumScaleFactor(Design.MinScaleFactor.comfortable) .minimumScaleFactor(Design.MinScaleFactor.comfortable)
// Main table // Main betting table
ZStack { VStack(spacing: 0) {
// Table felt background with arc shape // Top row: B PAIR | TIE | P PAIR
TableFeltShape() TopBettingRow(
.fill( bankerPairAmount: betAmount(for: .bankerPair),
LinearGradient( tieAmount: betAmount(for: .tie),
colors: [Color.Table.feltLight, Color.Table.feltDark], playerPairAmount: betAmount(for: .playerPair),
startPoint: .top, canBetBankerPair: canAddBet(for: .bankerPair),
endPoint: .bottom canBetTie: canAddBet(for: .tie),
) canBetPlayerPair: canAddBet(for: .playerPair),
) isBankerPairAtMax: isAtMax(for: .bankerPair),
isTieAtMax: isAtMax(for: .tie),
isPlayerPairAtMax: isAtMax(for: .playerPair),
onBankerPair: { gameState.placeBet(type: .bankerPair, amount: selectedChip.rawValue) },
onTie: { gameState.placeBet(type: .tie, amount: selectedChip.rawValue) },
onPlayerPair: { gameState.placeBet(type: .playerPair, amount: selectedChip.rawValue) }
)
// Gold border // Divider
TableFeltShape() Rectangle()
.fill(Color.Border.gold.opacity(0.5))
.frame(height: 1)
// Middle row: BANKER | BONUS
MainBetRow(
title: "BANKER",
payoutText: "0.95 : 1",
mainBetAmount: betAmount(for: .banker),
bonusBetAmount: betAmount(for: .dragonBonusBanker),
isSelected: isBankerSelected,
canBetMain: canAddBet(for: .banker),
canBetBonus: canAddBet(for: .dragonBonusBanker),
isMainAtMax: isAtMax(for: .banker),
isBonusAtMax: isAtMax(for: .dragonBonusBanker),
mainColor: Color.BettingZone.bankerDark,
onMain: { gameState.placeBet(type: .banker, amount: selectedChip.rawValue) },
onBonus: { gameState.placeBet(type: .dragonBonusBanker, amount: selectedChip.rawValue) }
)
// Divider
Rectangle()
.fill(Color.Border.gold.opacity(0.5))
.frame(height: 1)
// Bottom row: PLAYER | BONUS
MainBetRow(
title: "PLAYER",
payoutText: "1 : 1",
mainBetAmount: betAmount(for: .player),
bonusBetAmount: betAmount(for: .dragonBonusPlayer),
isSelected: isPlayerSelected,
canBetMain: canAddBet(for: .player),
canBetBonus: canAddBet(for: .dragonBonusPlayer),
isMainAtMax: isAtMax(for: .player),
isBonusAtMax: isAtMax(for: .dragonBonusPlayer),
mainColor: Color.BettingZone.playerDark,
onMain: { gameState.placeBet(type: .player, amount: selectedChip.rawValue) },
onBonus: { gameState.placeBet(type: .dragonBonusPlayer, amount: selectedChip.rawValue) }
)
}
.clipShape(RoundedRectangle(cornerRadius: Design.CornerRadius.large))
.overlay(
RoundedRectangle(cornerRadius: Design.CornerRadius.large)
.strokeBorder( .strokeBorder(
LinearGradient( LinearGradient(
colors: [Color.Border.goldLight, Color.Border.goldDark], colors: [Color.Border.goldLight, Color.Border.goldDark],
startPoint: .top, startPoint: .top,
endPoint: .bottom endPoint: .bottom
), ),
lineWidth: Design.LineWidth.heavy lineWidth: Design.LineWidth.medium
) )
)
// Betting zones layout .shadow(color: .black.opacity(0.3), radius: 10, y: 5)
VStack(spacing: Design.Spacing.xSmall) {
// Top row: B PAIR | TIE | P PAIR
TopBettingRow(
bankerPairAmount: betAmount(for: .bankerPair),
tieAmount: betAmount(for: .tie),
playerPairAmount: betAmount(for: .playerPair),
canBetBankerPair: canAddBet(for: .bankerPair),
canBetTie: canAddBet(for: .tie),
canBetPlayerPair: canAddBet(for: .playerPair),
isBankerPairAtMax: isAtMax(for: .bankerPair),
isTieAtMax: isAtMax(for: .tie),
isPlayerPairAtMax: isAtMax(for: .playerPair),
onBankerPair: { gameState.placeBet(type: .bankerPair, amount: selectedChip.rawValue) },
onTie: { gameState.placeBet(type: .tie, amount: selectedChip.rawValue) },
onPlayerPair: { gameState.placeBet(type: .playerPair, amount: selectedChip.rawValue) }
)
.padding(.horizontal, Design.Spacing.medium)
.padding(.top, Design.Spacing.large)
// Middle row: BANKER | DRAGON BONUS
MainBetRow(
title: "BANKER",
payoutText: "PAYS 0.95 TO 1",
mainBetAmount: betAmount(for: .banker),
bonusBetAmount: betAmount(for: .dragonBonusBanker),
isSelected: isBankerSelected,
canBetMain: canAddBet(for: .banker),
canBetBonus: canAddBet(for: .dragonBonusBanker),
isMainAtMax: isAtMax(for: .banker),
isBonusAtMax: isAtMax(for: .dragonBonusBanker),
mainColor: Color.BettingZone.bankerLight,
mainColorDark: Color.BettingZone.bankerDark,
onMain: { gameState.placeBet(type: .banker, amount: selectedChip.rawValue) },
onBonus: { gameState.placeBet(type: .dragonBonusBanker, amount: selectedChip.rawValue) }
)
.padding(.horizontal, Design.Spacing.medium)
// Bottom row: PLAYER | DRAGON BONUS
MainBetRow(
title: "PLAYER",
payoutText: "PAYS 1 TO 1",
mainBetAmount: betAmount(for: .player),
bonusBetAmount: betAmount(for: .dragonBonusPlayer),
isSelected: isPlayerSelected,
canBetMain: canAddBet(for: .player),
canBetBonus: canAddBet(for: .dragonBonusPlayer),
isMainAtMax: isAtMax(for: .player),
isBonusAtMax: isAtMax(for: .dragonBonusPlayer),
mainColor: Color.BettingZone.playerLight,
mainColorDark: Color.BettingZone.playerDark,
onMain: { gameState.placeBet(type: .player, amount: selectedChip.rawValue) },
onBonus: { gameState.placeBet(type: .dragonBonusPlayer, amount: selectedChip.rawValue) }
)
.padding(.horizontal, Design.Spacing.medium)
.padding(.bottom, Design.Spacing.large)
}
}
.aspectRatio(Design.Size.tableAspectRatio, contentMode: .fit)
} }
} }
} }
@ -163,21 +154,25 @@ private struct TopBettingRow: View {
let onTie: () -> Void let onTie: () -> Void
let onPlayerPair: () -> Void let onPlayerPair: () -> Void
private let rowHeight: CGFloat = 48 private let rowHeight: CGFloat = 52
private let cornerRadius = Design.CornerRadius.small
var body: some View { var body: some View {
HStack(spacing: Design.Spacing.xSmall) { HStack(spacing: 0) {
// B PAIR // B PAIR
PairBetZone( PairBetZone(
title: "B PAIR", title: "B PAIR",
betAmount: bankerPairAmount, betAmount: bankerPairAmount,
isEnabled: canBetBankerPair, isEnabled: canBetBankerPair,
isAtMax: isBankerPairAtMax, isAtMax: isBankerPairAtMax,
color: Color.BettingZone.bankerLight, color: Color.BettingZone.bankerDark.opacity(0.6),
action: onBankerPair action: onBankerPair
) )
// Vertical divider
Rectangle()
.fill(Color.Border.gold.opacity(0.5))
.frame(width: 1)
// TIE // TIE
TieBetZone( TieBetZone(
betAmount: tieAmount, betAmount: tieAmount,
@ -186,13 +181,18 @@ private struct TopBettingRow: View {
action: onTie action: onTie
) )
// Vertical divider
Rectangle()
.fill(Color.Border.gold.opacity(0.5))
.frame(width: 1)
// P PAIR // P PAIR
PairBetZone( PairBetZone(
title: "P PAIR", title: "P PAIR",
betAmount: playerPairAmount, betAmount: playerPairAmount,
isEnabled: canBetPlayerPair, isEnabled: canBetPlayerPair,
isAtMax: isPlayerPairAtMax, isAtMax: isPlayerPairAtMax,
color: Color.BettingZone.playerLight, color: Color.BettingZone.playerDark.opacity(0.6),
action: onPlayerPair action: onPlayerPair
) )
} }
@ -210,9 +210,8 @@ private struct PairBetZone: View {
let color: Color let color: Color
let action: () -> Void let action: () -> Void
private let cornerRadius = Design.CornerRadius.small private let titleFontSize: CGFloat = 12
private let titleFontSize: CGFloat = 11 private let payoutFontSize: CGFloat = 10
private let payoutFontSize: CGFloat = 9
var body: some View { var body: some View {
Button { Button {
@ -220,33 +219,29 @@ private struct PairBetZone: View {
} label: { } label: {
ZStack { ZStack {
// Background // Background
RoundedRectangle(cornerRadius: cornerRadius) Rectangle()
.fill(color.opacity(0.6)) .fill(color)
// Border
RoundedRectangle(cornerRadius: cornerRadius)
.strokeBorder(isAtMax ? Color.Border.silver : Color.Border.gold, lineWidth: Design.LineWidth.thin)
// Content // Content
VStack(spacing: 1) { VStack(spacing: 2) {
Text(title) Text(title)
.font(.system(size: titleFontSize, weight: .bold, design: .rounded)) .font(.system(size: titleFontSize, weight: .heavy, design: .rounded))
.foregroundStyle(.yellow) .foregroundStyle(.yellow)
Text("11:1") Text("11 : 1")
.font(.system(size: payoutFontSize, weight: .medium)) .font(.system(size: payoutFontSize, weight: .medium, design: .rounded))
.foregroundStyle(.white.opacity(0.8)) .foregroundStyle(.white.opacity(0.7))
} }
// Chip indicator // Chip indicator
if betAmount > 0 { if betAmount > 0 {
SmallChipIndicator(amount: betAmount, isMax: isAtMax) ChipBadge(amount: betAmount, isMax: isAtMax)
.offset(x: 0, y: 12) .offset(y: 16)
} }
} }
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.opacity(isEnabled ? 1.0 : 0.6) .opacity(isEnabled ? 1.0 : 0.5)
.accessibilityElement(children: .ignore) .accessibilityElement(children: .ignore)
.accessibilityLabel("\(title) bet, pays 11 to 1" + (betAmount > 0 ? ", current bet $\(betAmount)" : "")) .accessibilityLabel("\(title) bet, pays 11 to 1" + (betAmount > 0 ? ", current bet $\(betAmount)" : ""))
.accessibilityAddTraits(.isButton) .accessibilityAddTraits(.isButton)
@ -261,9 +256,8 @@ private struct TieBetZone: View {
let isAtMax: Bool let isAtMax: Bool
let action: () -> Void let action: () -> Void
private let cornerRadius = Design.CornerRadius.small private let titleFontSize: CGFloat = 14
private let titleFontSize: CGFloat = 13 private let payoutFontSize: CGFloat = 10
private let payoutFontSize: CGFloat = 9
var body: some View { var body: some View {
Button { Button {
@ -271,34 +265,30 @@ private struct TieBetZone: View {
} label: { } label: {
ZStack { ZStack {
// Background // Background
RoundedRectangle(cornerRadius: cornerRadius) Rectangle()
.fill(isAtMax ? Color.BettingZone.tieMax : Color.BettingZone.tie) .fill(isAtMax ? Color.BettingZone.tieMax : Color.BettingZone.tie)
// Border
RoundedRectangle(cornerRadius: cornerRadius)
.strokeBorder(isAtMax ? Color.Border.silver : Color.Border.gold, lineWidth: Design.LineWidth.thin)
// Content // Content
VStack(spacing: 1) { VStack(spacing: 2) {
Text("TIE") Text("TIE")
.font(.system(size: titleFontSize, weight: .black, design: .rounded)) .font(.system(size: titleFontSize, weight: .black, design: .rounded))
.tracking(1) .tracking(1)
Text("8 TO 1") Text("8 : 1")
.font(.system(size: payoutFontSize, weight: .medium)) .font(.system(size: payoutFontSize, weight: .medium, design: .rounded))
.opacity(0.8) .opacity(0.7)
} }
.foregroundStyle(.white) .foregroundStyle(.white)
// Chip indicator // Chip indicator
if betAmount > 0 { if betAmount > 0 {
SmallChipIndicator(amount: betAmount, isMax: isAtMax) ChipBadge(amount: betAmount, isMax: isAtMax)
.offset(x: 0, y: 12) .offset(y: 16)
} }
} }
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.opacity(isEnabled ? 1.0 : 0.6) .opacity(isEnabled ? 1.0 : 0.5)
.accessibilityElement(children: .ignore) .accessibilityElement(children: .ignore)
.accessibilityLabel("Tie bet, pays 8 to 1" + (betAmount > 0 ? ", current bet $\(betAmount)" : "")) .accessibilityLabel("Tie bet, pays 8 to 1" + (betAmount > 0 ? ", current bet $\(betAmount)" : ""))
.accessibilityAddTraits(.isButton) .accessibilityAddTraits(.isButton)
@ -318,16 +308,14 @@ private struct MainBetRow: View {
let isMainAtMax: Bool let isMainAtMax: Bool
let isBonusAtMax: Bool let isBonusAtMax: Bool
let mainColor: Color let mainColor: Color
let mainColorDark: Color
let onMain: () -> Void let onMain: () -> Void
let onBonus: () -> Void let onBonus: () -> Void
private let rowHeight: CGFloat = 55 private let rowHeight: CGFloat = 65
private let bonusWidth: CGFloat = 70 private let bonusWidth: CGFloat = 80
private let cornerRadius = Design.CornerRadius.medium
var body: some View { var body: some View {
HStack(spacing: Design.Spacing.xSmall) { HStack(spacing: 0) {
// Main bet zone (BANKER or PLAYER) // Main bet zone (BANKER or PLAYER)
MainBetZone( MainBetZone(
title: title, title: title,
@ -336,11 +324,15 @@ private struct MainBetRow: View {
isSelected: isSelected, isSelected: isSelected,
isEnabled: canBetMain, isEnabled: canBetMain,
isAtMax: isMainAtMax, isAtMax: isMainAtMax,
colorLight: mainColor, color: mainColor,
colorDark: mainColorDark,
action: onMain action: onMain
) )
// Vertical divider
Rectangle()
.fill(Color.Border.gold.opacity(0.5))
.frame(width: 1)
// Dragon Bonus zone // Dragon Bonus zone
DragonBonusZone( DragonBonusZone(
betAmount: bonusBetAmount, betAmount: bonusBetAmount,
@ -363,68 +355,59 @@ private struct MainBetZone: View {
let isSelected: Bool let isSelected: Bool
let isEnabled: Bool let isEnabled: Bool
let isAtMax: Bool let isAtMax: Bool
let colorLight: Color let color: Color
let colorDark: Color
let action: () -> Void let action: () -> Void
private let cornerRadius = Design.CornerRadius.medium private let titleFontSize: CGFloat = 20
private let titleFontSize: CGFloat = 18 private let payoutFontSize: CGFloat = 11
private let payoutFontSize: CGFloat = 9
var body: some View { var body: some View {
Button { Button {
if isEnabled { action() } if isEnabled { action() }
} label: { } label: {
ZStack { ZStack {
// Background gradient // Background
RoundedRectangle(cornerRadius: cornerRadius) Rectangle()
.fill( .fill(color)
LinearGradient(
colors: isAtMax ? [colorLight.opacity(0.5), colorDark.opacity(0.5)] : [colorLight, colorDark],
startPoint: .top,
endPoint: .bottom
)
)
// Selection glow // Selection highlight
if isSelected { if isSelected {
RoundedRectangle(cornerRadius: cornerRadius) Rectangle()
.strokeBorder(Color.yellow, lineWidth: Design.LineWidth.thick) .fill(Color.yellow.opacity(0.15))
.shadow(color: .yellow.opacity(0.5), radius: 6)
}
// Border Rectangle()
RoundedRectangle(cornerRadius: cornerRadius) .strokeBorder(Color.yellow, lineWidth: 3)
.strokeBorder(isAtMax ? Color.Border.silver : Color.Border.gold, lineWidth: Design.LineWidth.medium) }
// Content // Content
VStack(spacing: 2) { HStack {
Text(title) Spacer()
.font(.system(size: titleFontSize, weight: .black, design: .rounded))
.tracking(2)
Text(payoutText) VStack(spacing: 3) {
.font(.system(size: payoutFontSize, weight: .medium)) Text(title)
.opacity(0.8) .font(.system(size: titleFontSize, weight: .black, design: .rounded))
} .tracking(2)
.foregroundStyle(.white)
.lineLimit(1)
.minimumScaleFactor(0.7)
// Chip indicator Text(payoutText)
if betAmount > 0 { .font(.system(size: payoutFontSize, weight: .semibold, design: .rounded))
HStack { .opacity(0.7)
Spacer() }
.foregroundStyle(.white)
Spacer()
// Chip indicator
if betAmount > 0 {
ChipOnTableView(amount: betAmount, showMax: isAtMax) ChipOnTableView(amount: betAmount, showMax: isAtMax)
.padding(.trailing, Design.Spacing.small) .padding(.trailing, Design.Spacing.medium)
} }
} }
} }
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.opacity(isEnabled ? 1.0 : 0.6) .opacity(isEnabled ? 1.0 : 0.5)
.accessibilityElement(children: .ignore) .accessibilityElement(children: .ignore)
.accessibilityLabel("\(title) bet, \(payoutText)" + (isSelected ? ", selected" : "") + (betAmount > 0 ? ", current bet $\(betAmount)" : "")) .accessibilityLabel("\(title) bet, pays \(payoutText)" + (isSelected ? ", selected" : "") + (betAmount > 0 ? ", current bet $\(betAmount)" : ""))
.accessibilityAddTraits(.isButton) .accessibilityAddTraits(.isButton)
} }
} }
@ -437,48 +420,58 @@ private struct DragonBonusZone: View {
let isAtMax: Bool let isAtMax: Bool
let action: () -> Void let action: () -> Void
private let cornerRadius = Design.CornerRadius.medium private let titleFontSize: CGFloat = 9
private let titleFontSize: CGFloat = 10 private let diamondSize: CGFloat = 24
private let diamondSize: CGFloat = 20
var body: some View { var body: some View {
Button { Button {
if isEnabled { action() } if isEnabled { action() }
} label: { } label: {
ZStack { ZStack {
// Background // Background - darker purple/blue tint
RoundedRectangle(cornerRadius: cornerRadius) Rectangle()
.fill(Color.purple.opacity(0.5)) .fill(
LinearGradient(
// Border colors: [
RoundedRectangle(cornerRadius: cornerRadius) Color(red: 0.25, green: 0.3, blue: 0.45),
.strokeBorder(isAtMax ? Color.Border.silver : Color.Border.gold, lineWidth: Design.LineWidth.thin) Color(red: 0.15, green: 0.2, blue: 0.35)
],
startPoint: .top,
endPoint: .bottom
)
)
// Content // Content
VStack(spacing: 4) { VStack(spacing: 5) {
// Diamond shape // Diamond shape
DiamondShape() DiamondShape()
.fill(Color.purple.opacity(0.8)) .fill(
LinearGradient(
colors: [Color.purple.opacity(0.8), Color.purple.opacity(0.5)],
startPoint: .top,
endPoint: .bottom
)
)
.frame(width: diamondSize, height: diamondSize) .frame(width: diamondSize, height: diamondSize)
.overlay( .overlay(
DiamondShape() DiamondShape()
.strokeBorder(Color.white.opacity(0.5), lineWidth: 1) .strokeBorder(Color.white.opacity(0.4), lineWidth: 1)
) )
Text("BONUS") Text("BONUS")
.font(.system(size: titleFontSize, weight: .bold, design: .rounded)) .font(.system(size: titleFontSize, weight: .bold, design: .rounded))
.foregroundStyle(.white) .foregroundStyle(.white.opacity(0.9))
} }
// Chip indicator // Chip indicator
if betAmount > 0 { if betAmount > 0 {
SmallChipIndicator(amount: betAmount, isMax: isAtMax) ChipBadge(amount: betAmount, isMax: isAtMax)
.offset(y: 18) .offset(y: 22)
} }
} }
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.opacity(isEnabled ? 1.0 : 0.6) .opacity(isEnabled ? 1.0 : 0.5)
.accessibilityElement(children: .ignore) .accessibilityElement(children: .ignore)
.accessibilityLabel("Dragon Bonus, pays up to 30 to 1" + (betAmount > 0 ? ", current bet $\(betAmount)" : "")) .accessibilityLabel("Dragon Bonus, pays up to 30 to 1" + (betAmount > 0 ? ", current bet $\(betAmount)" : ""))
.accessibilityAddTraits(.isButton) .accessibilityAddTraits(.isButton)
@ -510,9 +503,9 @@ private struct DiamondShape: InsettableShape {
} }
} }
// MARK: - Small Chip Indicator // MARK: - Chip Badge (small indicator)
private struct SmallChipIndicator: View { private struct ChipBadge: View {
let amount: Int let amount: Int
let isMax: Bool let isMax: Bool
@ -520,77 +513,29 @@ private struct SmallChipIndicator: View {
ZStack { ZStack {
Circle() Circle()
.fill(isMax ? Color.gray : Color.yellow) .fill(isMax ? Color.gray : Color.yellow)
.frame(width: 18, height: 18) .frame(width: 20, height: 20)
Circle() Circle()
.strokeBorder(Color.white, lineWidth: 1) .strokeBorder(Color.white.opacity(0.8), lineWidth: 1)
.frame(width: 18, height: 18) .frame(width: 20, height: 20)
if isMax { if isMax {
Text("M") Text("M")
.font(.system(size: 8, weight: .bold)) .font(.system(size: 9, weight: .bold))
.foregroundStyle(.white) .foregroundStyle(.white)
} else { } else {
Text(amount.formatted(.number.notation(.compactName))) Text(formatCompact(amount))
.font(.system(size: 6, weight: .bold)) .font(.system(size: 7, weight: .bold))
.foregroundStyle(.black) .foregroundStyle(.black)
} }
} }
} }
}
// MARK: - Table Felt Shape private func formatCompact(_ value: Int) -> String {
if value >= 1000 {
struct TableFeltShape: InsettableShape { return "\(value / 1000)K"
var insetAmount: CGFloat = 0 }
return "\(value)"
private let shapeCornerRadius = Design.CornerRadius.xxLarge
func path(in rect: CGRect) -> Path {
var path = Path()
let insetRect = rect.insetBy(dx: insetAmount, dy: insetAmount)
let height = insetRect.height
let cornerRadius = shapeCornerRadius
// Start from bottom left
path.move(to: CGPoint(x: insetRect.minX + cornerRadius, y: insetRect.maxY))
// Bottom edge
path.addLine(to: CGPoint(x: insetRect.maxX - cornerRadius, y: insetRect.maxY))
// Bottom right corner
path.addQuadCurve(
to: CGPoint(x: insetRect.maxX, y: insetRect.maxY - cornerRadius),
control: CGPoint(x: insetRect.maxX, y: insetRect.maxY)
)
// Right edge going up with curve
path.addLine(to: CGPoint(x: insetRect.maxX, y: insetRect.minY + height * 0.3))
// Top arc
path.addQuadCurve(
to: CGPoint(x: insetRect.minX, y: insetRect.minY + height * 0.3),
control: CGPoint(x: insetRect.midX, y: insetRect.minY - height * 0.1)
)
// Left edge going down
path.addLine(to: CGPoint(x: insetRect.minX, y: insetRect.maxY - cornerRadius))
// Bottom left corner
path.addQuadCurve(
to: CGPoint(x: insetRect.minX + cornerRadius, y: insetRect.maxY),
control: CGPoint(x: insetRect.minX, y: insetRect.maxY)
)
path.closeSubpath()
return path
}
func inset(by amount: CGFloat) -> some InsettableShape {
var shape = self
shape.insetAmount += amount
return shape
} }
} }