// // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import Foundation import UIKit // ColorOrGradientSetting is used for persistence and comparison. // ColorOrGradientValue is used for rendering. public enum ColorOrGradientSetting: Equatable, Codable { case solidColor(color: OWSColor) case themedColor(lightThemeColor: OWSColor, darkThemeColor: OWSColor) // If angleRadians = 0, gradientColor1 is N. // If angleRadians = PI / 2, gradientColor1 is E. // etc. case gradient(gradientColor1: OWSColor, gradientColor2: OWSColor, angleRadians: CGFloat) case themedGradient(lightGradientColor1: OWSColor, lightGradientColor2: OWSColor, darkGradientColor1: OWSColor, darkGradientColor2: OWSColor, angleRadians: CGFloat) private enum TypeKey: UInt, Codable { case solidColor = 0 case gradient = 1 case themedColor = 2 case themedGradient = 3 } private enum CodingKeys: String, CodingKey { case typeKey case solidColor case lightThemeColor case darkThemeColor case gradientColor1 case gradientColor2 case lightGradientColor1 case lightGradientColor2 case darkGradientColor1 case darkGradientColor2 case angleRadians } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let typeKey = try container.decode(TypeKey.self, forKey: .typeKey) switch typeKey { case .solidColor: let color = try container.decode(OWSColor.self, forKey: .solidColor) self = .solidColor(color: color) case .themedColor: let lightThemeColor = try container.decode(OWSColor.self, forKey: .lightThemeColor) let darkThemeColor = try container.decode(OWSColor.self, forKey: .darkThemeColor) self = .themedColor(lightThemeColor: lightThemeColor, darkThemeColor: darkThemeColor) case .gradient: let gradientColor1 = try container.decode(OWSColor.self, forKey: .gradientColor1) let gradientColor2 = try container.decode(OWSColor.self, forKey: .gradientColor2) let angleRadians = try container.decode(CGFloat.self, forKey: .angleRadians) self = .gradient(gradientColor1: gradientColor1, gradientColor2: gradientColor2, angleRadians: angleRadians) case .themedGradient: let lightGradientColor1 = try container.decode(OWSColor.self, forKey: .lightGradientColor1) let lightGradientColor2 = try container.decode(OWSColor.self, forKey: .lightGradientColor2) let darkGradientColor1 = try container.decode(OWSColor.self, forKey: .darkGradientColor1) let darkGradientColor2 = try container.decode(OWSColor.self, forKey: .darkGradientColor2) let angleRadians = try container.decode(CGFloat.self, forKey: .angleRadians) self = .themedGradient(lightGradientColor1: lightGradientColor1, lightGradientColor2: lightGradientColor2, darkGradientColor1: darkGradientColor1, darkGradientColor2: darkGradientColor2, angleRadians: angleRadians) } } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { case .solidColor(let solidColor): try container.encode(TypeKey.solidColor, forKey: .typeKey) try container.encode(solidColor, forKey: .solidColor) case .themedColor(let lightThemeColor, let darkThemeColor): try container.encode(TypeKey.themedColor, forKey: .typeKey) try container.encode(lightThemeColor, forKey: .lightThemeColor) try container.encode(darkThemeColor, forKey: .darkThemeColor) case .gradient(let gradientColor1, let gradientColor2, let angleRadians): try container.encode(TypeKey.gradient, forKey: .typeKey) try container.encode(gradientColor1, forKey: .gradientColor1) try container.encode(gradientColor2, forKey: .gradientColor2) try container.encode(angleRadians, forKey: .angleRadians) case .themedGradient(let lightGradientColor1, let lightGradientColor2, let darkGradientColor1, let darkGradientColor2, let angleRadians): try container.encode(TypeKey.themedGradient, forKey: .typeKey) try container.encode(lightGradientColor1, forKey: .lightGradientColor1) try container.encode(lightGradientColor2, forKey: .lightGradientColor2) try container.encode(darkGradientColor1, forKey: .darkGradientColor1) try container.encode(darkGradientColor2, forKey: .darkGradientColor2) try container.encode(angleRadians, forKey: .angleRadians) } } } // MARK: - // ColorOrGradientSetting is used for persistence and comparison. // ColorOrGradientValue is used for rendering. public enum ColorOrGradientValue: CustomStringConvertible { case transparent case solidColor(color: UIColor) // If angleRadians = 0, gradientColor1 is N. // If angleRadians = PI / 2, gradientColor1 is E. // etc. case gradient(gradientColor1: UIColor, gradientColor2: UIColor, angleRadians: CGFloat) public var description: String { switch self { case .transparent: return "[transparent]" case .solidColor(let color): return "[solidColor: \(color.asOWSColor)]" case .gradient(let gradientColor1, let gradientColor2, let angleRadians): return "[gradient gradientColor1: \(gradientColor1.asOWSColor), gradientColor2: \(gradientColor2.asOWSColor), angleRadians: \(angleRadians)]" } } } // MARK: - public enum ColorOrGradientThemeMode: Int { case auto case alwaysLight case alwaysDark } // MARK: - public extension ColorOrGradientSetting { var asValue: ColorOrGradientValue { asValue(themeMode: .auto) } func asValue(themeMode: ColorOrGradientThemeMode) -> ColorOrGradientValue { let shouldUseDarkColors: Bool = { switch themeMode { case .auto: return Theme.isDarkThemeEnabled case .alwaysDark: return true case .alwaysLight: return false } }() switch self { case .solidColor(let solidColor): return .solidColor(color: solidColor.asUIColor) case .themedColor(let lightThemeColor, let darkThemeColor): let color = (shouldUseDarkColors ? darkThemeColor : lightThemeColor) return .solidColor(color: color.asUIColor) case .gradient(let gradientColor1, let gradientColor2, let angleRadians): return .gradient(gradientColor1: gradientColor1.asUIColor, gradientColor2: gradientColor2.asUIColor, angleRadians: angleRadians) case .themedGradient(let lightGradientColor1, let lightGradientColor2, let darkGradientColor1, let darkGradientColor2, let angleRadians): let gradientColor1 = (shouldUseDarkColors ? darkGradientColor1 : lightGradientColor1) let gradientColor2 = (shouldUseDarkColors ? darkGradientColor2 : lightGradientColor2) return .gradient(gradientColor1: gradientColor1.asUIColor, gradientColor2: gradientColor2.asUIColor, angleRadians: angleRadians) } } } // MARK: - // We want a color model that... // // * ...can be safely, losslessly serialized. // * ...is Equatable. public struct OWSColor: Equatable, Codable { public let red: CGFloat public let green: CGFloat public let blue: CGFloat public init(red: CGFloat, green: CGFloat, blue: CGFloat) { self.red = red.clamp01() self.green = green.clamp01() self.blue = blue.clamp01() } public var asUIColor: UIColor { UIColor(red: red, green: green, blue: blue, alpha: 1.0) } var description: String { "[red: \(red), green: \(green), blue: \(blue)]" } } // MARK: - public extension UIColor { var asOWSColor: OWSColor { var red: CGFloat = 0 var green: CGFloat = 0 var blue: CGFloat = 0 var alpha: CGFloat = 0 self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) return OWSColor(red: red.clamp01(), green: green.clamp01(), blue: blue.clamp01()) } }