Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
ed1dabfe18
commit
1ecbbbf9ee
@ -1,40 +0,0 @@
|
|||||||
//
|
|
||||||
// BrandingConfig+Baccarat.swift
|
|
||||||
// Baccarat
|
|
||||||
//
|
|
||||||
// Baccarat-specific branding configurations for AppIconView and LaunchScreenView.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import CasinoKit
|
|
||||||
|
|
||||||
extension AppIconConfig {
|
|
||||||
/// Baccarat game icon configuration.
|
|
||||||
static let baccarat = AppIconConfig(
|
|
||||||
title: "BACCARAT",
|
|
||||||
iconSymbol: "suit.spade.fill"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension LaunchScreenConfig {
|
|
||||||
/// Baccarat game launch screen configuration.
|
|
||||||
static let baccarat = LaunchScreenConfig(
|
|
||||||
title: "BACCARAT",
|
|
||||||
tagline: "The Classic Casino Card Game",
|
|
||||||
iconSymbols: ["suit.spade.fill", "suit.heart.fill"]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - For Development Preview Comparison
|
|
||||||
|
|
||||||
extension AppIconConfig {
|
|
||||||
/// Blackjack config for side-by-side comparison in dev previews.
|
|
||||||
static let blackjack = AppIconConfig(
|
|
||||||
title: "BLACKJACK",
|
|
||||||
subtitle: "21",
|
|
||||||
iconSymbol: "suit.club.fill",
|
|
||||||
primaryColor: Color(red: 0.05, green: 0.35, blue: 0.15),
|
|
||||||
secondaryColor: Color(red: 0.03, green: 0.2, blue: 0.1)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
30
Baccarat/Baccarat/Theme/BrandingConfig.swift
Normal file
30
Baccarat/Baccarat/Theme/BrandingConfig.swift
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
//
|
||||||
|
// BrandingConfig.swift
|
||||||
|
// Baccarat
|
||||||
|
//
|
||||||
|
// App-specific branding configurations for icons and launch screens.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import CasinoKit
|
||||||
|
|
||||||
|
// MARK: - App Icon Configuration
|
||||||
|
|
||||||
|
extension AppIconConfig {
|
||||||
|
/// Baccarat app icon configuration.
|
||||||
|
static let baccarat = AppIconConfig(
|
||||||
|
title: "BACCARAT",
|
||||||
|
iconSymbol: "suit.spade.fill"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Launch Screen Configuration
|
||||||
|
|
||||||
|
extension LaunchScreenConfig {
|
||||||
|
/// Baccarat launch screen configuration.
|
||||||
|
static let baccarat = LaunchScreenConfig(
|
||||||
|
title: "BACCARAT",
|
||||||
|
tagline: "The Classic Casino Card Game",
|
||||||
|
iconSymbols: ["suit.spade.fill", "suit.heart.fill"]
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,15 +1,17 @@
|
|||||||
//
|
//
|
||||||
// BrandingConfig+Blackjack.swift
|
// BrandingConfig.swift
|
||||||
// Blackjack
|
// Blackjack
|
||||||
//
|
//
|
||||||
// Blackjack-specific branding configurations for AppIconView and LaunchScreenView.
|
// App-specific branding configurations for icons and launch screens.
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import CasinoKit
|
import CasinoKit
|
||||||
|
|
||||||
|
// MARK: - App Icon Configuration
|
||||||
|
|
||||||
extension AppIconConfig {
|
extension AppIconConfig {
|
||||||
/// Blackjack game icon configuration.
|
/// Blackjack app icon configuration.
|
||||||
static let blackjack = AppIconConfig(
|
static let blackjack = AppIconConfig(
|
||||||
title: "BLACKJACK",
|
title: "BLACKJACK",
|
||||||
subtitle: "21",
|
subtitle: "21",
|
||||||
@ -19,8 +21,10 @@ extension AppIconConfig {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Launch Screen Configuration
|
||||||
|
|
||||||
extension LaunchScreenConfig {
|
extension LaunchScreenConfig {
|
||||||
/// Blackjack game launch screen configuration.
|
/// Blackjack launch screen configuration.
|
||||||
static let blackjack = LaunchScreenConfig(
|
static let blackjack = LaunchScreenConfig(
|
||||||
title: "BLACKJACK",
|
title: "BLACKJACK",
|
||||||
subtitle: "21",
|
subtitle: "21",
|
||||||
@ -30,14 +34,3 @@ extension LaunchScreenConfig {
|
|||||||
secondaryColor: Color(red: 0.03, green: 0.2, blue: 0.1)
|
secondaryColor: Color(red: 0.03, green: 0.2, blue: 0.1)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - For Development Preview Comparison
|
|
||||||
|
|
||||||
extension AppIconConfig {
|
|
||||||
/// Baccarat config for side-by-side comparison in dev previews.
|
|
||||||
static let baccarat = AppIconConfig(
|
|
||||||
title: "BACCARAT",
|
|
||||||
iconSymbol: "suit.spade.fill"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,130 +0,0 @@
|
|||||||
//
|
|
||||||
// BrandingPreviewView.swift
|
|
||||||
// Blackjack
|
|
||||||
//
|
|
||||||
// Development view for previewing and exporting app icons and launch screens.
|
|
||||||
// Access this during development to generate icon assets.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import CasinoKit
|
|
||||||
|
|
||||||
/// Preview view for app branding assets.
|
|
||||||
/// Use this during development to preview and export icons.
|
|
||||||
struct BrandingPreviewView: View {
|
|
||||||
var body: some View {
|
|
||||||
TabView {
|
|
||||||
// App Icon Preview
|
|
||||||
ScrollView {
|
|
||||||
VStack(spacing: Design.Spacing.xxxLarge) {
|
|
||||||
Text("App Icon")
|
|
||||||
.font(.largeTitle.bold())
|
|
||||||
|
|
||||||
AppIconView(config: .blackjack, size: 300)
|
|
||||||
.clipShape(.rect(cornerRadius: 300 * 0.22))
|
|
||||||
.shadow(radius: Design.Shadow.radiusXLarge)
|
|
||||||
|
|
||||||
Text("All Sizes")
|
|
||||||
.font(.title2.bold())
|
|
||||||
|
|
||||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))], spacing: Design.Spacing.xLarge) {
|
|
||||||
ForEach([180, 120, 87, 60, 40], id: \.self) { size in
|
|
||||||
VStack {
|
|
||||||
AppIconView(config: .blackjack, size: CGFloat(size))
|
|
||||||
.clipShape(.rect(cornerRadius: CGFloat(size) * 0.22))
|
|
||||||
Text("\(size)px")
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
instructionsSection
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
.tabItem {
|
|
||||||
Label("Icon", systemImage: "app.fill")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Launch Screen Preview
|
|
||||||
LaunchScreenView(config: .blackjack)
|
|
||||||
.tabItem {
|
|
||||||
Label("Launch", systemImage: "rectangle.portrait.fill")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Other Games Preview
|
|
||||||
ScrollView {
|
|
||||||
VStack(spacing: Design.Spacing.xxxLarge) {
|
|
||||||
Text("Other Game Icons")
|
|
||||||
.font(.largeTitle.bold())
|
|
||||||
|
|
||||||
HStack(spacing: Design.Spacing.xLarge) {
|
|
||||||
VStack {
|
|
||||||
AppIconView(config: .baccarat, size: 150)
|
|
||||||
.clipShape(.rect(cornerRadius: 150 * 0.22))
|
|
||||||
Text("Baccarat")
|
|
||||||
.font(.caption)
|
|
||||||
}
|
|
||||||
|
|
||||||
VStack {
|
|
||||||
AppIconView(config: .poker, size: 150)
|
|
||||||
.clipShape(.rect(cornerRadius: 150 * 0.22))
|
|
||||||
Text("Poker")
|
|
||||||
.font(.caption)
|
|
||||||
}
|
|
||||||
|
|
||||||
VStack {
|
|
||||||
AppIconView(config: .roulette, size: 150)
|
|
||||||
.clipShape(.rect(cornerRadius: 150 * 0.22))
|
|
||||||
Text("Roulette")
|
|
||||||
.font(.caption)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text("These show how the same pattern works for other games")
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
.tabItem {
|
|
||||||
Label("Others", systemImage: "square.grid.2x2")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var instructionsSection: some View {
|
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.medium) {
|
|
||||||
Text("How to Export Icons")
|
|
||||||
.font(.headline)
|
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
|
||||||
Text("Option 1: Screenshot from Preview")
|
|
||||||
.font(.subheadline.bold())
|
|
||||||
Text("• Run the preview in Xcode")
|
|
||||||
Text("• Screenshot the 1024px icon")
|
|
||||||
Text("• Use an online tool to generate all sizes")
|
|
||||||
}
|
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
|
||||||
Text("Option 2: Use IconRenderer in Code")
|
|
||||||
.font(.subheadline.bold())
|
|
||||||
Text("• Call IconRenderer.renderAppIcon(config: .blackjack)")
|
|
||||||
Text("• Save the resulting UIImage to files")
|
|
||||||
Text("• Add to Assets.xcassets/AppIcon")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
.padding()
|
|
||||||
.background(Color.gray.opacity(Design.Opacity.subtle))
|
|
||||||
.clipShape(.rect(cornerRadius: Design.CornerRadius.medium))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview {
|
|
||||||
BrandingPreviewView()
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,196 +0,0 @@
|
|||||||
//
|
|
||||||
// IconGeneratorView.swift
|
|
||||||
// Blackjack
|
|
||||||
//
|
|
||||||
// Development tool to generate and export app icon images.
|
|
||||||
// Run this view, tap the button, then find the icons in the Files app.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import CasinoKit
|
|
||||||
|
|
||||||
/// A development view that generates and saves app icon images.
|
|
||||||
/// After running, find the icons in Files app → On My iPhone → Blackjack
|
|
||||||
struct IconGeneratorView: View {
|
|
||||||
@State private var status: String = "Tap the button to generate icons"
|
|
||||||
@State private var isGenerating = false
|
|
||||||
@State private var generatedIcons: [GeneratedIcon] = []
|
|
||||||
|
|
||||||
// Development view: hardcoded sizes acceptable
|
|
||||||
private let previewSize: CGFloat = 200
|
|
||||||
private let iconCornerRadiusRatio: CGFloat = 0.22
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
NavigationStack {
|
|
||||||
ScrollView {
|
|
||||||
VStack(spacing: Design.Spacing.xxLarge) {
|
|
||||||
// Preview
|
|
||||||
AppIconView(config: .blackjack, size: previewSize)
|
|
||||||
.clipShape(.rect(cornerRadius: previewSize * iconCornerRadiusRatio))
|
|
||||||
.shadow(radius: 10)
|
|
||||||
|
|
||||||
Text("App Icon Preview")
|
|
||||||
.font(.headline)
|
|
||||||
|
|
||||||
// Generate button
|
|
||||||
Button {
|
|
||||||
Task {
|
|
||||||
await generateIcons()
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
HStack {
|
|
||||||
if isGenerating {
|
|
||||||
ProgressView()
|
|
||||||
.tint(.white)
|
|
||||||
}
|
|
||||||
Text(isGenerating ? "Generating..." : "Generate & Save Icons")
|
|
||||||
}
|
|
||||||
.font(.headline)
|
|
||||||
.foregroundStyle(.white)
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.padding()
|
|
||||||
.background(isGenerating ? Color.gray : Color.blue)
|
|
||||||
.clipShape(.rect(cornerRadius: 12))
|
|
||||||
}
|
|
||||||
.disabled(isGenerating)
|
|
||||||
.padding(.horizontal)
|
|
||||||
|
|
||||||
// Status
|
|
||||||
Text(status)
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
.padding(.horizontal)
|
|
||||||
|
|
||||||
// Generated icons
|
|
||||||
if !generatedIcons.isEmpty {
|
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
|
||||||
Text("Generated Icons:")
|
|
||||||
.font(.headline)
|
|
||||||
|
|
||||||
ForEach(generatedIcons) { icon in
|
|
||||||
HStack {
|
|
||||||
Image(systemName: "checkmark.circle.fill")
|
|
||||||
.foregroundStyle(.green)
|
|
||||||
Text(icon.filename)
|
|
||||||
.font(.caption.monospaced())
|
|
||||||
Spacer()
|
|
||||||
Text("\(Int(icon.size))px")
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
.background(Color.green.opacity(Design.Opacity.subtle))
|
|
||||||
.clipShape(.rect(cornerRadius: Design.CornerRadius.medium))
|
|
||||||
.padding(.horizontal)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instructions
|
|
||||||
instructionsSection
|
|
||||||
}
|
|
||||||
.padding(.vertical)
|
|
||||||
}
|
|
||||||
.navigationTitle("Icon Generator")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var instructionsSection: some View {
|
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
|
||||||
Text("After generating:")
|
|
||||||
.font(.headline)
|
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
|
||||||
instructionRow(number: 1, text: "Open Files app on your device/simulator")
|
|
||||||
instructionRow(number: 2, text: "Navigate to: On My iPhone → Blackjack")
|
|
||||||
instructionRow(number: 3, text: "Find the AppIcon-1024.png file")
|
|
||||||
instructionRow(number: 4, text: "AirDrop or share to your Mac")
|
|
||||||
instructionRow(number: 5, text: "Drag into Xcode's Assets.xcassets/AppIcon")
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
|
||||||
|
|
||||||
Text("Alternative: Use an online tool")
|
|
||||||
.font(.subheadline.bold())
|
|
||||||
Text("Upload the 1024px icon to appicon.co or makeappicon.com to generate all sizes automatically.")
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
.background(Color.gray.opacity(Design.Opacity.subtle))
|
|
||||||
.clipShape(.rect(cornerRadius: Design.CornerRadius.medium))
|
|
||||||
.padding(.horizontal)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func instructionRow(number: Int, text: String) -> some View {
|
|
||||||
HStack(alignment: .top, spacing: 8) {
|
|
||||||
Text("\(number).")
|
|
||||||
.font(.callout.bold())
|
|
||||||
.foregroundStyle(.blue)
|
|
||||||
Text(text)
|
|
||||||
.font(.callout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@MainActor
|
|
||||||
private func generateIcons() async {
|
|
||||||
isGenerating = true
|
|
||||||
generatedIcons = []
|
|
||||||
status = "Generating icons..."
|
|
||||||
|
|
||||||
let sizes: [(CGFloat, String)] = [
|
|
||||||
(1024, "AppIcon-1024"),
|
|
||||||
(180, "AppIcon-180"),
|
|
||||||
(120, "AppIcon-120"),
|
|
||||||
(87, "AppIcon-87"),
|
|
||||||
(80, "AppIcon-80"),
|
|
||||||
(60, "AppIcon-60"),
|
|
||||||
(40, "AppIcon-40")
|
|
||||||
]
|
|
||||||
|
|
||||||
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
|
||||||
|
|
||||||
for (size, name) in sizes {
|
|
||||||
// Render the icon
|
|
||||||
let view = AppIconView(config: .blackjack, size: size)
|
|
||||||
let renderer = ImageRenderer(content: view)
|
|
||||||
renderer.scale = 1.0
|
|
||||||
|
|
||||||
if let uiImage = renderer.uiImage,
|
|
||||||
let data = uiImage.pngData() {
|
|
||||||
let filename = "\(name).png"
|
|
||||||
let fileURL = documentsPath.appending(path: filename)
|
|
||||||
|
|
||||||
do {
|
|
||||||
try data.write(to: fileURL)
|
|
||||||
generatedIcons.append(GeneratedIcon(filename: filename, size: size))
|
|
||||||
} catch {
|
|
||||||
status = "Error saving \(filename): \(error.localizedDescription)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Small delay for UI feedback
|
|
||||||
try? await Task.sleep(for: .milliseconds(100))
|
|
||||||
}
|
|
||||||
|
|
||||||
if generatedIcons.count == sizes.count {
|
|
||||||
status = "✅ All icons saved to Documents folder!\nOpen Files app to find them."
|
|
||||||
} else {
|
|
||||||
status = "⚠️ Some icons failed to generate"
|
|
||||||
}
|
|
||||||
|
|
||||||
isGenerating = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct GeneratedIcon: Identifiable {
|
|
||||||
let id = UUID()
|
|
||||||
let filename: String
|
|
||||||
let size: CGFloat
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview {
|
|
||||||
IconGeneratorView()
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -78,6 +78,8 @@
|
|||||||
// - AppIconView, AppIconConfig
|
// - AppIconView, AppIconConfig
|
||||||
// - LaunchScreenView, LaunchScreenConfig, StaticLaunchScreenView
|
// - LaunchScreenView, LaunchScreenConfig, StaticLaunchScreenView
|
||||||
// - IconRenderer, IconExportView
|
// - IconRenderer, IconExportView
|
||||||
|
// - IconGeneratorView, GeneratedIconInfo (development tool for exporting icons)
|
||||||
|
// - BrandingPreviewView (development tool for previewing all branding assets)
|
||||||
|
|
||||||
// MARK: - Theme
|
// MARK: - Theme
|
||||||
// - CasinoTheme (protocol)
|
// - CasinoTheme (protocol)
|
||||||
|
|||||||
@ -33,27 +33,21 @@ public struct AppIconConfig: Sendable {
|
|||||||
self.accentColor = accentColor
|
self.accentColor = accentColor
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Example Preset Configurations
|
// MARK: - Example Configuration (for previews only)
|
||||||
// Game-specific presets should be defined in the respective apps as extensions.
|
|
||||||
|
|
||||||
/// Poker game icon configuration (example preset).
|
/// Example configuration for CasinoKit previews.
|
||||||
public static let poker = AppIconConfig(
|
/// Apps should define their own configs in `BrandingConfig.swift`.
|
||||||
title: "POKER",
|
public static let example = AppIconConfig(
|
||||||
iconSymbol: "suit.diamond.fill",
|
title: "CASINO",
|
||||||
accentColor: .red
|
iconSymbol: "suit.diamond.fill"
|
||||||
)
|
|
||||||
|
|
||||||
/// Roulette game icon configuration (example preset).
|
|
||||||
public static let roulette = AppIconConfig(
|
|
||||||
title: "ROULETTE",
|
|
||||||
iconSymbol: "circle.grid.3x3.fill",
|
|
||||||
primaryColor: Color(red: 0.4, green: 0.1, blue: 0.1),
|
|
||||||
secondaryColor: Color(red: 0.25, green: 0.05, blue: 0.05)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A customizable app icon view for casino games.
|
/// A customizable app icon view for casino games.
|
||||||
/// Render this view to create your app icon assets.
|
/// Render this view to create your app icon assets.
|
||||||
|
///
|
||||||
|
/// **Important**: This view generates a full-bleed square icon. iOS applies its own
|
||||||
|
/// superellipse mask, so decorative borders are inset to avoid clipping at the edges.
|
||||||
public struct AppIconView: View {
|
public struct AppIconView: View {
|
||||||
let config: AppIconConfig
|
let config: AppIconConfig
|
||||||
let size: CGFloat
|
let size: CGFloat
|
||||||
@ -63,16 +57,20 @@ public struct AppIconView: View {
|
|||||||
self.size = size
|
self.size = size
|
||||||
}
|
}
|
||||||
|
|
||||||
private var cornerRadius: CGFloat { size * 0.22 }
|
// iOS clips app icons with a superellipse mask. Inset decorative elements
|
||||||
|
// to prevent clipping. The inset is approximately 4% of the icon size.
|
||||||
|
private var borderInset: CGFloat { size * 0.04 }
|
||||||
|
private var insetCornerRadius: CGFloat { (size - borderInset * 2) * 0.18 }
|
||||||
private var iconSize: CGFloat { size * 0.35 }
|
private var iconSize: CGFloat { size * 0.35 }
|
||||||
private var titleSize: CGFloat { size * 0.12 }
|
private var titleSize: CGFloat { size * 0.12 }
|
||||||
private var subtitleSize: CGFloat { size * 0.25 }
|
private var subtitleSize: CGFloat { size * 0.25 }
|
||||||
private var borderWidth: CGFloat { size * 0.02 }
|
private var borderWidth: CGFloat { size * 0.015 }
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
// Background gradient
|
// Background gradient - full bleed, no rounded corners
|
||||||
RoundedRectangle(cornerRadius: cornerRadius)
|
// iOS will apply its own superellipse mask
|
||||||
|
Rectangle()
|
||||||
.fill(
|
.fill(
|
||||||
LinearGradient(
|
LinearGradient(
|
||||||
colors: [config.primaryColor, config.secondaryColor],
|
colors: [config.primaryColor, config.secondaryColor],
|
||||||
@ -85,8 +83,8 @@ public struct AppIconView: View {
|
|||||||
DiamondPatternOverlay(size: size)
|
DiamondPatternOverlay(size: size)
|
||||||
.opacity(0.08)
|
.opacity(0.08)
|
||||||
|
|
||||||
// Gold border
|
// Decorative border - inset to avoid iOS mask clipping
|
||||||
RoundedRectangle(cornerRadius: cornerRadius)
|
RoundedRectangle(cornerRadius: insetCornerRadius)
|
||||||
.strokeBorder(
|
.strokeBorder(
|
||||||
LinearGradient(
|
LinearGradient(
|
||||||
colors: [
|
colors: [
|
||||||
@ -99,6 +97,7 @@ public struct AppIconView: View {
|
|||||||
),
|
),
|
||||||
lineWidth: borderWidth
|
lineWidth: borderWidth
|
||||||
)
|
)
|
||||||
|
.padding(borderInset)
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
VStack(spacing: size * 0.03) {
|
VStack(spacing: size * 0.03) {
|
||||||
@ -179,24 +178,24 @@ private struct DiamondPatternOverlay: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Preview
|
// MARK: - Previews
|
||||||
|
|
||||||
#Preview("Poker Icon") {
|
#Preview("App Icon") {
|
||||||
AppIconView(config: .poker, size: 512)
|
AppIconView(config: .example, size: 512)
|
||||||
|
.clipShape(.rect(cornerRadius: 512 * 0.22))
|
||||||
.padding()
|
.padding()
|
||||||
.background(Color.gray)
|
.background(Color.gray)
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview("Roulette Icon") {
|
#Preview("All Sizes") {
|
||||||
AppIconView(config: .roulette, size: 512)
|
LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))], spacing: 20) {
|
||||||
.padding()
|
ForEach([180, 120, 87, 60, 40], id: \.self) { size in
|
||||||
.background(Color.gray)
|
VStack {
|
||||||
}
|
AppIconView(config: .example, size: CGFloat(size))
|
||||||
|
.clipShape(.rect(cornerRadius: CGFloat(size) * 0.22))
|
||||||
#Preview("All Icons") {
|
Text("\(size)px").font(.caption)
|
||||||
HStack(spacing: 20) {
|
}
|
||||||
AppIconView(config: .poker, size: 200)
|
}
|
||||||
AppIconView(config: .roulette, size: 200)
|
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.background(Color.gray)
|
.background(Color.gray)
|
||||||
|
|||||||
@ -1,18 +1,47 @@
|
|||||||
//
|
//
|
||||||
// BrandingPreviewView.swift
|
// BrandingPreviewView.swift
|
||||||
// Baccarat
|
// CasinoKit
|
||||||
//
|
//
|
||||||
// Development view for previewing and exporting app icons and launch screens.
|
// Development view for previewing and exporting app icons and launch screens.
|
||||||
// Access this during development to generate icon assets.
|
// Access this during development to generate icon assets.
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import CasinoKit
|
|
||||||
|
|
||||||
/// Preview view for app branding assets.
|
/// Preview view for app branding assets.
|
||||||
/// Use this during development to preview and export icons.
|
/// Use this during development to preview and export icons.
|
||||||
struct BrandingPreviewView: View {
|
public struct BrandingPreviewView: View {
|
||||||
var body: some View {
|
let iconConfig: AppIconConfig
|
||||||
|
let launchConfig: LaunchScreenConfig
|
||||||
|
let appName: String
|
||||||
|
|
||||||
|
/// Other game configs for comparison preview.
|
||||||
|
let otherGames: [(name: String, config: AppIconConfig)]
|
||||||
|
|
||||||
|
// Development view: fixed sizes acceptable
|
||||||
|
private let largePreviewSize: CGFloat = 300
|
||||||
|
private let comparisonIconSize: CGFloat = 150
|
||||||
|
private let iconCornerRadiusRatio: CGFloat = 0.22
|
||||||
|
|
||||||
|
/// Creates a branding preview view.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - iconConfig: The app icon configuration for this game.
|
||||||
|
/// - launchConfig: The launch screen configuration for this game.
|
||||||
|
/// - appName: The app name for display purposes.
|
||||||
|
/// - otherGames: Other game configs for side-by-side comparison (optional).
|
||||||
|
public init(
|
||||||
|
iconConfig: AppIconConfig,
|
||||||
|
launchConfig: LaunchScreenConfig,
|
||||||
|
appName: String,
|
||||||
|
otherGames: [(name: String, config: AppIconConfig)] = []
|
||||||
|
) {
|
||||||
|
self.iconConfig = iconConfig
|
||||||
|
self.launchConfig = launchConfig
|
||||||
|
self.appName = appName
|
||||||
|
self.otherGames = otherGames
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
TabView {
|
TabView {
|
||||||
// App Icon Preview
|
// App Icon Preview
|
||||||
ScrollView {
|
ScrollView {
|
||||||
@ -20,8 +49,8 @@ struct BrandingPreviewView: View {
|
|||||||
Text("App Icon")
|
Text("App Icon")
|
||||||
.font(.largeTitle.bold())
|
.font(.largeTitle.bold())
|
||||||
|
|
||||||
AppIconView(config: .baccarat, size: 300)
|
AppIconView(config: iconConfig, size: largePreviewSize)
|
||||||
.clipShape(.rect(cornerRadius: 300 * 0.22))
|
.clipShape(.rect(cornerRadius: largePreviewSize * iconCornerRadiusRatio))
|
||||||
.shadow(radius: 20)
|
.shadow(radius: 20)
|
||||||
|
|
||||||
Text("All Sizes")
|
Text("All Sizes")
|
||||||
@ -30,8 +59,8 @@ struct BrandingPreviewView: View {
|
|||||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))], spacing: 20) {
|
LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))], spacing: 20) {
|
||||||
ForEach([180, 120, 87, 60, 40], id: \.self) { size in
|
ForEach([180, 120, 87, 60, 40], id: \.self) { size in
|
||||||
VStack {
|
VStack {
|
||||||
AppIconView(config: .baccarat, size: CGFloat(size))
|
AppIconView(config: iconConfig, size: CGFloat(size))
|
||||||
.clipShape(.rect(cornerRadius: CGFloat(size) * 0.22))
|
.clipShape(.rect(cornerRadius: CGFloat(size) * iconCornerRadiusRatio))
|
||||||
Text("\(size)px")
|
Text("\(size)px")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
@ -48,48 +77,38 @@ struct BrandingPreviewView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Launch Screen Preview
|
// Launch Screen Preview
|
||||||
LaunchScreenView(config: .baccarat)
|
LaunchScreenView(config: launchConfig)
|
||||||
.tabItem {
|
.tabItem {
|
||||||
Label("Launch", systemImage: "rectangle.portrait.fill")
|
Label("Launch", systemImage: "rectangle.portrait.fill")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Other Games Preview
|
// Other Games Preview (if provided)
|
||||||
ScrollView {
|
if !otherGames.isEmpty {
|
||||||
VStack(spacing: 32) {
|
ScrollView {
|
||||||
Text("Other Game Icons")
|
VStack(spacing: 32) {
|
||||||
.font(.largeTitle.bold())
|
Text("Other Game Icons")
|
||||||
|
.font(.largeTitle.bold())
|
||||||
|
|
||||||
HStack(spacing: 20) {
|
LazyVGrid(columns: [GridItem(.adaptive(minimum: 160))], spacing: 20) {
|
||||||
VStack {
|
ForEach(otherGames, id: \.name) { game in
|
||||||
AppIconView(config: .blackjack, size: 150)
|
VStack {
|
||||||
.clipShape(.rect(cornerRadius: 150 * 0.22))
|
AppIconView(config: game.config, size: comparisonIconSize)
|
||||||
Text("Blackjack")
|
.clipShape(.rect(cornerRadius: comparisonIconSize * iconCornerRadiusRatio))
|
||||||
.font(.caption)
|
Text(game.name)
|
||||||
|
.font(.caption)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VStack {
|
Text("These show how the same pattern works for other games")
|
||||||
AppIconView(config: .poker, size: 150)
|
.font(.callout)
|
||||||
.clipShape(.rect(cornerRadius: 150 * 0.22))
|
.foregroundStyle(.secondary)
|
||||||
Text("Poker")
|
|
||||||
.font(.caption)
|
|
||||||
}
|
|
||||||
|
|
||||||
VStack {
|
|
||||||
AppIconView(config: .roulette, size: 150)
|
|
||||||
.clipShape(.rect(cornerRadius: 150 * 0.22))
|
|
||||||
Text("Roulette")
|
|
||||||
.font(.caption)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.padding()
|
||||||
Text("These show how the same pattern works for other games")
|
}
|
||||||
.font(.callout)
|
.tabItem {
|
||||||
.foregroundStyle(.secondary)
|
Label("Others", systemImage: "square.grid.2x2")
|
||||||
}
|
}
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
.tabItem {
|
|
||||||
Label("Others", systemImage: "square.grid.2x2")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,7 +129,7 @@ struct BrandingPreviewView: View {
|
|||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
Text("Option 2: Use IconRenderer in Code")
|
Text("Option 2: Use IconRenderer in Code")
|
||||||
.font(.subheadline.bold())
|
.font(.subheadline.bold())
|
||||||
Text("• Call IconRenderer.renderAppIcon(config: .baccarat)")
|
Text("• Call IconRenderer.renderAppIcon(config: .\(appName.lowercased()))")
|
||||||
Text("• Save the resulting UIImage to files")
|
Text("• Save the resulting UIImage to files")
|
||||||
Text("• Add to Assets.xcassets/AppIcon")
|
Text("• Add to Assets.xcassets/AppIcon")
|
||||||
}
|
}
|
||||||
@ -125,6 +144,9 @@ struct BrandingPreviewView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
BrandingPreviewView()
|
BrandingPreviewView(
|
||||||
|
iconConfig: .example,
|
||||||
|
launchConfig: .example,
|
||||||
|
appName: "Casino"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1,28 +1,43 @@
|
|||||||
//
|
//
|
||||||
// IconGeneratorView.swift
|
// IconGeneratorView.swift
|
||||||
// Baccarat
|
// CasinoKit
|
||||||
//
|
//
|
||||||
// Development tool to generate and export app icon images.
|
// Development tool to generate and export app icon images.
|
||||||
// Run this view, tap the button, then find the icons in the Files app.
|
// Run this view, tap the button, then find the icons in the Files app.
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import CasinoKit
|
|
||||||
|
|
||||||
/// A development view that generates and saves app icon images.
|
/// A development view that generates and saves app icon images.
|
||||||
/// After running, find the icons in Files app → On My iPhone → Baccarat
|
/// After running, find the icons in Files app → On My iPhone → [App Name]
|
||||||
struct IconGeneratorView: View {
|
public struct IconGeneratorView: View {
|
||||||
|
let config: AppIconConfig
|
||||||
|
let appName: String
|
||||||
|
|
||||||
@State private var status: String = "Tap the button to generate icons"
|
@State private var status: String = "Tap the button to generate icons"
|
||||||
@State private var isGenerating = false
|
@State private var isGenerating = false
|
||||||
@State private var generatedIcons: [GeneratedIcon] = []
|
@State private var generatedIcons: [GeneratedIconInfo] = []
|
||||||
|
|
||||||
var body: some View {
|
// Development view: fixed sizes acceptable
|
||||||
|
private let previewSize: CGFloat = 200
|
||||||
|
private let iconCornerRadiusRatio: CGFloat = 0.22
|
||||||
|
|
||||||
|
/// Creates a new icon generator view.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - config: The app icon configuration to use for rendering.
|
||||||
|
/// - appName: The app name for display in instructions (e.g., "Blackjack", "Baccarat").
|
||||||
|
public init(config: AppIconConfig, appName: String) {
|
||||||
|
self.config = config
|
||||||
|
self.appName = appName
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(spacing: 24) {
|
VStack(spacing: 24) {
|
||||||
// Preview
|
// Preview
|
||||||
AppIconView(config: .baccarat, size: 200)
|
AppIconView(config: config, size: previewSize)
|
||||||
.clipShape(.rect(cornerRadius: 200 * 0.22))
|
.clipShape(.rect(cornerRadius: previewSize * iconCornerRadiusRatio))
|
||||||
.shadow(radius: 10)
|
.shadow(radius: 10)
|
||||||
|
|
||||||
Text("App Icon Preview")
|
Text("App Icon Preview")
|
||||||
@ -99,7 +114,7 @@ struct IconGeneratorView: View {
|
|||||||
|
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
instructionRow(number: 1, text: "Open Files app on your device/simulator")
|
instructionRow(number: 1, text: "Open Files app on your device/simulator")
|
||||||
instructionRow(number: 2, text: "Navigate to: On My iPhone → Baccarat")
|
instructionRow(number: 2, text: "Navigate to: On My iPhone → \(appName)")
|
||||||
instructionRow(number: 3, text: "Find the AppIcon-1024.png file")
|
instructionRow(number: 3, text: "Find the AppIcon-1024.png file")
|
||||||
instructionRow(number: 4, text: "AirDrop or share to your Mac")
|
instructionRow(number: 4, text: "AirDrop or share to your Mac")
|
||||||
instructionRow(number: 5, text: "Drag into Xcode's Assets.xcassets/AppIcon")
|
instructionRow(number: 5, text: "Drag into Xcode's Assets.xcassets/AppIcon")
|
||||||
@ -149,7 +164,7 @@ struct IconGeneratorView: View {
|
|||||||
|
|
||||||
for (size, name) in sizes {
|
for (size, name) in sizes {
|
||||||
// Render the icon
|
// Render the icon
|
||||||
let view = AppIconView(config: .baccarat, size: size)
|
let view = AppIconView(config: config, size: size)
|
||||||
let renderer = ImageRenderer(content: view)
|
let renderer = ImageRenderer(content: view)
|
||||||
renderer.scale = 1.0
|
renderer.scale = 1.0
|
||||||
|
|
||||||
@ -160,7 +175,7 @@ struct IconGeneratorView: View {
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
try data.write(to: fileURL)
|
try data.write(to: fileURL)
|
||||||
generatedIcons.append(GeneratedIcon(filename: filename, size: size))
|
generatedIcons.append(GeneratedIconInfo(filename: filename, size: size))
|
||||||
} catch {
|
} catch {
|
||||||
status = "Error saving \(filename): \(error.localizedDescription)"
|
status = "Error saving \(filename): \(error.localizedDescription)"
|
||||||
}
|
}
|
||||||
@ -180,13 +195,18 @@ struct IconGeneratorView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GeneratedIcon: Identifiable {
|
/// Information about a generated icon file.
|
||||||
let id = UUID()
|
public struct GeneratedIconInfo: Identifiable, Sendable {
|
||||||
let filename: String
|
public let id = UUID()
|
||||||
let size: CGFloat
|
public let filename: String
|
||||||
|
public let size: CGFloat
|
||||||
|
|
||||||
|
public init(filename: String, size: CGFloat) {
|
||||||
|
self.filename = filename
|
||||||
|
self.size = size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
IconGeneratorView()
|
IconGeneratorView(config: .example, appName: "Casino")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,6 +134,6 @@ public struct IconExportView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#Preview("Icon Export") {
|
#Preview("Icon Export") {
|
||||||
IconExportView(config: .poker)
|
IconExportView(config: .example)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -38,15 +38,14 @@ public struct LaunchScreenConfig: Sendable {
|
|||||||
self.showLoadingIndicator = showLoadingIndicator
|
self.showLoadingIndicator = showLoadingIndicator
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Example Preset Configurations
|
// MARK: - Example Configuration (for previews only)
|
||||||
// Game-specific presets should be defined in the respective apps as extensions.
|
|
||||||
|
|
||||||
/// Poker game launch screen configuration (example preset).
|
/// Example configuration for CasinoKit previews.
|
||||||
public static let poker = LaunchScreenConfig(
|
/// Apps should define their own configs in `BrandingConfig.swift`.
|
||||||
title: "POKER",
|
public static let example = LaunchScreenConfig(
|
||||||
tagline: "Texas Hold'em",
|
title: "CASINO",
|
||||||
iconSymbols: ["suit.diamond.fill", "suit.club.fill"],
|
tagline: "Your Game Tagline",
|
||||||
accentColor: .red
|
iconSymbols: ["suit.diamond.fill", "suit.heart.fill"]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,13 +320,13 @@ public struct StaticLaunchScreenView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Preview
|
// MARK: - Previews
|
||||||
|
|
||||||
#Preview("Poker Launch") {
|
#Preview("Launch Screen") {
|
||||||
LaunchScreenView(config: .poker)
|
LaunchScreenView(config: .example)
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview("Static Launch") {
|
#Preview("Static Launch") {
|
||||||
StaticLaunchScreenView(config: .poker)
|
StaticLaunchScreenView(config: .example)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user