Listless/Helpers/AccentColor.swift (2466B)
1import SwiftUI
2
3enum 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
44private struct ItemAccentColorKey: Hashable {
45 let index: Int
46 let total: Int
47 let theme: ColorTheme
48}
49
50@MainActor
51private enum ItemAccentColorCache {
52 static var colors: [ItemAccentColorKey: Color] = [:]
53}
54
55func 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
71func 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
82private 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}