Signal-iOS/SignalUI/Appearance/ColorOrGradient.swift
2021-10-19 09:39:53 -03:00

232 lines
9.1 KiB
Swift

//
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
//
import Foundation
// 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())
}
}