AccentColor.swift (2466B)
1 import SwiftUI 2 3 enum ColorTheme: Int, CaseIterable, Identifiable { 4 case pilbara = 0 5 case collaroy = 1 6 7 var id: Int { rawValue } 8 9 static var displayOrder: [ColorTheme] { 10 allCases.sorted { $0.displayName < $1.displayName } 11 } 12 13 var displayName: String { 14 switch self { 15 case .pilbara: "Pilbara" 16 case .collaroy: "Collaroy" 17 } 18 } 19 20 fileprivate typealias HSB = (h: Double, s: Double, b: Double) 21 22 fileprivate var top: HSB { 23 switch self { 24 case .pilbara: (h: 0.98, s: 0.85, b: 1.00) 25 case .collaroy: (h: 0.58, s: 0.88, b: 1.00) 26 } 27 } 28 29 fileprivate var mid: HSB { 30 switch self { 31 case .pilbara: (h: 0.88, s: 0.75, b: 0.95) 32 case .collaroy: (h: 0.51, s: 0.69, b: 0.90) 33 } 34 } 35 36 fileprivate var bottom: HSB { 37 switch self { 38 case .pilbara: (h: 0.72, s: 0.65, b: 0.85) 39 case .collaroy: (h: 0.44, s: 0.50, b: 0.80) 40 } 41 } 42 } 43 44 private struct ItemAccentColorKey: Hashable { 45 let index: Int 46 let total: Int 47 let theme: ColorTheme 48 } 49 50 @MainActor 51 private enum ItemAccentColorCache { 52 static var colors: [ItemAccentColorKey: Color] = [:] 53 } 54 55 func itemColor(forIndex index: Int, total: Int, theme: ColorTheme = .pilbara) -> Color { 56 let top = theme.top 57 guard total > 1 else { return Color(hue: top.h, saturation: top.s, brightness: top.b) } 58 59 let progress = Double(index) / Double(total - 1) 60 let mid = theme.mid 61 let bottom = theme.bottom 62 63 if progress < 0.5 { 64 return interpolateHSB(from: top, to: mid, progress: progress * 2.0) 65 } else { 66 return interpolateHSB(from: mid, to: bottom, progress: (progress - 0.5) * 2.0) 67 } 68 } 69 70 @MainActor 71 func cachedItemColor(forIndex index: Int, total: Int, theme: ColorTheme = .pilbara) -> Color { 72 let key = ItemAccentColorKey(index: index, total: total, theme: theme) 73 if let cached = ItemAccentColorCache.colors[key] { 74 return cached 75 } 76 77 let computed = itemColor(forIndex: index, total: total, theme: theme) 78 ItemAccentColorCache.colors[key] = computed 79 return computed 80 } 81 82 private func interpolateHSB( 83 from: (h: Double, s: Double, b: Double), 84 to: (h: Double, s: Double, b: Double), 85 progress: Double 86 ) -> Color { 87 Color( 88 hue: from.h + (to.h - from.h) * progress, 89 saturation: from.s + (to.s - from.s) * progress, 90 brightness: from.b + (to.b - from.b) * progress 91 ) 92 }