listless

A simple list app for Apple platforms
Log | Files | Refs | README | LICENSE

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 }