Category.swift 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. //
  2. // Category.swift
  3. // Todos
  4. //
  5. // Created by Sam Jaffe on 3/2/26.
  6. //
  7. import Foundation
  8. import SwiftUI
  9. // https://gist.github.com/peterfriese/bb2fc5df202f6a15cc807bd87ff15193
  10. // Inspired by https://cocoacasts.com/from-hex-to-uicolor-and-back-in-swift
  11. // Make Color codable. This includes support for transparency.
  12. // See https://www.digitalocean.com/community/tutorials/css-hex-code-colors-alpha-values
  13. extension Color: @retroactive Codable {
  14. #if os(macOS)
  15. fileprivate typealias UnifiedColor = NSColor
  16. #endif
  17. #if os(iOS)
  18. fileprivate typealias UnifiedColor = UIColor
  19. #endif
  20. init(hex: String) {
  21. var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
  22. hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
  23. var rgb: UInt64 = 0
  24. var r: CGFloat = 0.0
  25. var g: CGFloat = 0.0
  26. var b: CGFloat = 0.0
  27. var a: CGFloat = 1.0
  28. let length = hexSanitized.count
  29. Scanner(string: hexSanitized).scanHexInt64(&rgb)
  30. if length == 6 {
  31. r = CGFloat((rgb & 0xFF0000) >> 16) / 255.0
  32. g = CGFloat((rgb & 0x00FF00) >> 8) / 255.0
  33. b = CGFloat(rgb & 0x0000FF) / 255.0
  34. } else if length == 8 {
  35. r = CGFloat((rgb & 0xFF000000) >> 24) / 255.0
  36. g = CGFloat((rgb & 0x00FF0000) >> 16) / 255.0
  37. b = CGFloat((rgb & 0x0000FF00) >> 8) / 255.0
  38. a = CGFloat(rgb & 0x000000FF) / 255.0
  39. }
  40. self.init(.sRGB,
  41. red: Double(r),
  42. green: Double(g),
  43. blue: Double(b),
  44. opacity: Double(a))
  45. }
  46. public init(from decoder: Decoder) throws {
  47. let container = try decoder.singleValueContainer()
  48. let hex = try container.decode(String.self)
  49. self.init(hex: hex)
  50. }
  51. public func encode(to encoder: Encoder) throws {
  52. var container = encoder.singleValueContainer()
  53. try container.encode(hex)
  54. }
  55. var hex: String? {
  56. return toHex()
  57. }
  58. func toHex(alpha: Bool = false) -> String? {
  59. guard let components = UnifiedColor(self).cgColor.components, components.count >= 3 else {
  60. return nil
  61. }
  62. let r = Float(components[0])
  63. let g = Float(components[1])
  64. let b = Float(components[2])
  65. var a = Float(1.0)
  66. if components.count >= 4 {
  67. a = Float(components[3])
  68. }
  69. if alpha {
  70. return String(format: "%02lX%02lX%02lX%02lX",
  71. lroundf(r * 255),
  72. lroundf(g * 255),
  73. lroundf(b * 255),
  74. lroundf(a * 255))
  75. } else {
  76. return String(format: "%02lX%02lX%02lX",
  77. lroundf(r * 255),
  78. lroundf(g * 255),
  79. lroundf(b * 255))
  80. }
  81. }
  82. }
  83. final class Category : Identifiable, Codable {
  84. var name: String = ""
  85. var color: Color = Color(.gray)
  86. var id: String { name }
  87. init() {}
  88. init(name: String, color: Color) {
  89. self.name = name
  90. self.color = color
  91. }
  92. var valid: Bool { !name.isEmpty }
  93. }