Visual_Novel_iOS/crush/Crush/Src/Components/Skin/CLSystemTokens.swift

579 lines
19 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// CLSystemTokens.swift
// Crush
//
// Created by Leon on 2025/7/11.
//
import UIKit
// MARK: - Convenience functions
extension UIColor {
static var c: SystemColor {
SystemColor()
}
}
extension UIFont {
static var t: SystemFont {
SystemFont()
}
}
@dynamicMemberLookup
struct SystemColor {
subscript(dynamicMember key: String) -> UIColor {
guard let token = CLEnumSystemToken.allCases.first(where: { "\($0)" == key }) else {
return .black
}
return CLSystemToken.color(token: token) ?? .red
}
func systemTokenValue(for key: String) -> String? {
CLEnumSystemToken.allCases.first(where: { "\($0)" == key })?.rawValue
}
}
@dynamicMemberLookup
struct SystemFont {
subscript(dynamicMember key: String) -> UIFont {
guard let token = CLEnumSystemToken.allCases.first(where: { "\($0)" == key }) else {
return .systemFont(ofSize: 14)
}
return CLSystemToken.font(token: token)
}
func systemTokenValue(for key: String) -> String? {
CLEnumSystemToken.allCases.first(where: { "\($0)" == key })?.rawValue
}
}
// MARK: - CLEnumSystemToken: Enums and Constants
enum CLEnumSystemToken: String, CaseIterable {
init?(caseName: String) {
self = CLEnumSystemToken.allCases.first(where: { "\($0)" == caseName }) ?? .cpn
}
case cpn = "color.primary.normal"
case cph = "color.primary.hover"
case cpp = "color.primary.press"
case cpd = "color.primary.disabled"
case cpvn = "color.primary.variant.normal"
case cpvh = "color.primary.variant.hover"
case cpvp = "color.primary.variant.press"
case cpvd = "color.primary.variant.disabled"
case cpgn = "color.primary.gradient.normal"
case cpgh = "color.primary.gradient.hover"
case cpgp = "color.primary.gradient.press"
case cpgd = "color.primary.gradient.disabled"
case cin = "color.important.normal"
case cih = "color.important.hover"
case cip = "color.important.press"
case cid = "color.important.disabled"
case civn = "color.important.variant.normal"
case civh = "color.important.variant.hover"
case civp = "color.important.variant.press"
case civd = "color.important.variant.disabled"
case cign = "color.important.gradient.normal"
case cigh = "color.important.gradient.hover"
case cigp = "color.important.gradient.press"
case ciopn = "color.important.onpic.normal"
case cposn = "color.positive.normal"
case cposh = "color.positive.hover"
case cposp = "color.positive.press"
case cposd = "color.positive.disabled"
case cposvn = "color.positive.variant.normal"
case cposvh = "color.positive.variant.hover"
case cposvp = "color.positive.variant.press"
case cposvd = "color.positive.variant.disabled"
case cposgn = "color.positive.gradient.normal"
case cposopn = "color.positive.onpic.normal"
case cwn = "color.warning.normal"
case cwh = "color.warning.hover"
case cwp = "color.warning.press"
case cwd = "color.warning.disabled"
case cwvn = "color.warning.variant.normal"
case cwvh = "color.warning.variant.hover"
case cwvp = "color.warning.variant.press"
case cwvd = "color.warning.variant.disabled"
case cwgn = "color.warning.gradient.normal"
case cwopn = "color.warning.onpic.normal"
case cen = "color.emphasis.normal"
case ceh = "color.emphasis.hover"
case cep = "color.emphasis.press"
case ced = "color.emphasis.disabled"
case cevn = "color.emphasis.variant.normal"
case cevh = "color.emphasis.variant.hover"
case cevp = "color.emphasis.variant.press"
case cevd = "color.emphasis.variant.disabled"
case cegn = "color.emphasis.grandient.normal"
case cegh = "color.emphasis.grandient.hover"
case cegp = "color.emphasis.grandient.press"
case cegd = "color.emphasis.grandient.disabled"
case ceopn = "color.emphasis.onpic.normal"
case cbd = "color.background.default"
case cbs = "color.background.specialmap"
case cbdi = "color.background.district"
case csbn = "color.surface.base.normal"
case csbh = "color.surface.base.hover"
case csbp = "color.surface.base.press"
case csbsn = "color.surface.base.specialmap.normal"
case csbsh = "color.surface.base.specialmap.hover"
case csbsp = "color.surface.base.specialmap.press"
case csbsd = "color.surface.base.specialmap.disabled"
case csfn = "color.surface.float.normal"
case csfh = "color.surface.float.hover"
case csfp = "color.surface.float.press"
case cstn = "color.surface.top.normal"
case csth = "color.surface.top.hover"
case cstp = "color.surface.top.press"
case cstd = "color.surface.top.disabled"
case csdn = "color.surface.district.normal"
case csdh = "color.surface.district.hover"
case csdp = "color.surface.district.press"
case csdd = "color.surface.district.disabled"
case csnn = "color.surface.nest.normal"
case csnh = "color.surface.nest.hover"
case csnp = "color.surface.nest.press"
case csnd = "color.surface.nest.disabled"
case csen = "color.surface.element.normal"
case cseh = "color.surface.element.hover"
case csep = "color.surface.element.press"
case csed = "color.surface.element.disabled"
case csedn = "color.surface.element.dark.normal"
case csedh = "color.surface.element.dark.hover"
case csedp = "color.surface.element.dark.press"
case csedd = "color.surface.element.dark.disabled"
case cseln = "color.surface.element.light.normal"
case cselh = "color.surface.element.light.hover"
case cselp = "color.surface.element.light.press"
case cseld = "color.surface.element.light.disabled"
case cswn = "color.surface.white.normal"
case cswh = "color.surface.white.hover"
case cswp = "color.surface.white.press"
case cswd = "color.surface.white.disabled"
case csbn2 = "color.surface.black.normal"
case con = "color.outline.normal"
case coh = "color.outline.hover"
case cop = "color.outline.press"
case cod = "color.outline.disabled"
case copr = "color.overlay.primary"
case cogr = "color.overlay.gradient"
case codr = "color.overlay.dark"
/// graident
case cob = "color.overlay.background"
case cobase = "color.overlay.base"
/// vip gradient
case ccvn = "color.context.vip.normal"
case ctpn = "color.txt.primary.normal"
case ctph = "color.txt.primary.hover"
case ctpp = "color.txt.primary.press"
case ctpd = "color.txt.primary.disabled"
case ctpsn = "color.txt.primary.specialmap.normal"
case ctpsh = "color.txt.primary.specialmap.hover"
case ctpsp = "color.txt.primary.specialmap.press"
case ctpsd = "color.txt.primary.specialmap.disabled"
case ctsn = "color.txt.secondary.normal"
case ctsh = "color.txt.secondary.hover"
case ctsp = "color.txt.secondary.press"
case ctsd = "color.txt.secondary.disabled"
case cttn = "color.txt.tertiary.normal"
case ctg = "color.txt.grass"
case ctd = "color.txt.disabled"
case td = "txt.display"
case tdxl = "txt.display.xl"
case tdl = "txt.display.l"
case tdm = "txt.display.m"
case tds = "txt.display.s"
case thl = "txt.headline.l"
case thm = "txt.headline.m"
case ths = "txt.headline.s"
case ttl = "txt.title.l"
case ttm = "txt.title.m"
case tts = "txt.title.s"
case tbsl = "txt.bodySemibold.l"
case tbsm = "txt.bodySemibold.m"
case tbss = "txt.bodySemibold.s"
case tbl = "txt.body.l"
case tbm = "txt.body.m"
case tbim = "txt.body.italic.m"
case tbs = "txt.body.s"
case tll = "txt.label.l"
case tlm = "txt.label.m"
case tls = "txt.label.s"
case tndxl = "txt.numDisplay.xl"
case tndl = "txt.numDisplay.l"
case tndm = "txt.numDisplay.m"
case tnds = "txt.numDisplay.s"
case tnmxl = "txt.numMonotype.xl"
case tnml = "txt.numMonotype.l"
case tnmm = "txt.numMonotype.m"
case tnms = "txt.numMonotype.s"
case tnmxs = "txt.numMonotype.xs"
case shs = "shadow.s"
case shm = "shadow.m"
case shl = "shadow.l"
case rxs = "radius.xs"
case rs = "radius.s"
case rm = "radius.m"
case rl = "radius.l"
case rxl = "radius.xl"
case rxxl = "radius.xxl"
case rr = "radius.round"
case rp = "radius.pill"
case bd = "border.divider"
case bs = "border.s"
case bm = "border.m"
case bl = "border.l"
}
func KeyForSystemTokenFrom(_ token: CLEnumSystemToken) -> String {
return token.rawValue
}
// MARK: - EPShadow
struct EPShadow {
var color: UIColor?
var opacity: Float = 0
var offset: CGSize = .zero
var radius: CGFloat = 0
}
// MARK: - EPGradient
struct EPGradient {
var direction: CGPoint = .zero
var firstColor: UIColor?
var secondColor: UIColor?
var thirdColor : UIColor?
public func toImage(size: CGSize) -> UIImage? {
guard let firstColor = firstColor?.cgColor, let secondColor = secondColor?.cgColor else {
return nil
}
// direction startPoint endPoint
let startPoint: CGPoint
let endPoint: CGPoint
switch direction {
case CGPoint(x: 0, y: -1): //
startPoint = CGPoint(x: 0.5, y: 0)
endPoint = CGPoint(x: 0.5, y: 1)
case CGPoint(x: 1, y: -1): //
startPoint = CGPoint(x: 0, y: 0)
endPoint = CGPoint(x: 1, y: 1)
default:
// direction nil 使
startPoint = CGPoint(x: 0, y: 0.5)
endPoint = CGPoint(x: 1, y: 0.5) //
}
var colors = [firstColor, secondColor]
if(colors.count == 3){
if let third = thirdColor?.cgColor{
colors = [firstColor, secondColor, third]
}
}
// gradientImageWithSize
return UIImage.gradientImageWithSize(
size: size,
colors: colors,
startPoint: startPoint,
endPoint: endPoint
)
}
public func colors() -> [UIColor]{
var colors = [UIColor]()
if let first = firstColor{
colors.append(first)
}
if let second = secondColor{
colors.append(second)
}
if let third = thirdColor{
colors.append(third)
}
return colors
}
}
// MARK: - EPTypography
struct EPTypography {
var font: UIFont?
var lineHeight: CGFloat = 0
}
// MARK: - EPBaseObject
class EPBaseObject {
static func subGetColorByTokenValues(_ values: [String]) -> UIColor? {
guard !values.isEmpty else {
preconditionFailure("Invalid color values: empty array")
return nil
}
guard let hex = values.first, hex.hasPrefix("#") else {
print("❌ Invalid color format for values: \(values)")
return nil
}
let hexString = hex.replacingOccurrences(of: "#", with: "")
var rgb: UInt64 = 0
Scanner(string: hexString).scanHexInt64(&rgb)
let red = CGFloat((rgb >> 16) & 0xFF) / 255.0
let green = CGFloat((rgb >> 8) & 0xFF) / 255.0
let blue = CGFloat(rgb & 0xFF) / 255.0
let alpha: CGFloat = values.count > 1 ? CGFloat(Float(values.last!) ?? 1.0) : 1.0
return UIColor(red: red, green: green, blue: blue, alpha: alpha)
}
}
// MARK: - CLSystemToken
class CLSystemToken {
static let baseTokens = CLBaseTokens.shared
// MARK: - Color Methods
static func color(token: CLEnumSystemToken) -> UIColor? {
let key = KeyForSystemTokenFrom(token)
let values = baseTokens.getTokensByKey(key)
return EPBaseObject.subGetColorByTokenValues(values)
}
static func darkColor(token: CLEnumSystemToken) -> UIColor? {
let key = KeyForSystemTokenFrom(token)
let values = baseTokens.getDarkTokens(key)
return EPBaseObject.subGetColorByTokenValues(values)
}
// MARK: - String Method
static func string(token: CLEnumSystemToken) -> String? {
let key = KeyForSystemTokenFrom(token)
return baseTokens.getTokenByKey(key)
}
// MARK: - Float Method
static func float(token: CLEnumSystemToken) -> CGFloat {
let key = KeyForSystemTokenFrom(token)
let value = baseTokens.getTokenByKey(key)
guard let floatValue = Float(value) else {
print("❌ Invalid float format for key: \(key)")
return 0
}
return CGFloat(floatValue)
}
// MARK: - Radius Method
static func radius(token: CLEnumSystemToken) -> CGFloat {
let key = KeyForSystemTokenFrom(token)
let value = baseTokens.getTokenByKey(key)
guard let radius = Float(value) else {
print("❌ Invalid radius format for key: \(key)")
return 0
}
// print("🎨 radius: \(key) \(radius)") // DLog print
return CGFloat(radius)
}
// MARK: - Border Method
static func border(token: CLEnumSystemToken) -> CGFloat {
let key = KeyForSystemTokenFrom(token)
let value = baseTokens.getTokenByKey(key)
guard let border = Float(value) else {
print("❌ Invalid border format for key: \(key)")
return 0
}
// print("🎨 border: \(key) \(border)") // DLog print
return CGFloat(border)
}
// MARK: - Shadow Method
static func shadow(token: CLEnumSystemToken) -> EPShadow {
let key = KeyForSystemTokenFrom(token)
let values = baseTokens.getTokensByKey(key)
guard values.count == 4, values.first?.contains(EPConfigShadowPrefix) == true else {
assertionFailure("Wrong format for shadow token: \(key)")
return EPShadow()
}
var shadow = EPShadow()
if let colorStr = values.first?.replacingOccurrences(of: EPConfigShadowPrefix, with: "") {
let color = EPBaseObject.subGetColorByTokenValues([colorStr])
shadow.color = color
}
if let opacity = Float(values[1]) {
shadow.opacity = opacity
}
let offXy = values[2].components(separatedBy: "&")
if offXy.count == 2,
let x = Float(offXy[0]), let y = Float(offXy[1]) {
shadow.offset = CGSize(width: CGFloat(x), height: CGFloat(y))
}
if let radius = Float(values[3]) {
shadow.radius = CGFloat(radius)
}
return shadow
}
// MARK: - Gradient Method
static func gradient(token: CLEnumSystemToken) -> EPGradient {
// if token == .colorContextVipNormal {
// assertionFailure("Special style rule, not used in this unified rule, please use another method")
// return gradient(token: .colorPrimaryGradientNormal)
// }
let key = KeyForSystemTokenFrom(token)
let values = baseTokens.getTokensByKey(key)
if values.count <= 2 { // || values.first?.contains(EPConfigGradientPrefix) != true
var gradient = EPGradient()
gradient.direction = CGPoint(x: 1, y: 0)
gradient.firstColor = EPBaseObject.subGetColorByTokenValues(values)
gradient.secondColor = gradient.firstColor
return gradient
}
// + 23
guard values.count == 3 || values.count == 4 else {
assertionFailure("Wrong format for gradient token: \(key)")
return EPGradient()
}
var gradient = EPGradient()
if let directionStr = values.first {
switch directionStr {
case "LTR":
gradient.direction = CGPoint(x: 1, y: 0)
case "TTB":
gradient.direction = CGPoint(x: 0, y: 1)
case "LTTRB":
gradient.direction = CGPoint(x: 1, y: 1)
default:
assertionFailure("Not handled direction: \(directionStr)")
gradient.direction = .zero
}
}
let color1Str = values[1].replacingOccurrences(of: EPConfigGradientPrefix, with: "")
let color1Array = color1Str.components(separatedBy: "&")
gradient.firstColor = EPBaseObject.subGetColorByTokenValues(color1Array)
let color2Str = values[2].replacingOccurrences(of: EPConfigGradientPrefix, with: "")
let color2Array = color2Str.components(separatedBy: "&")
gradient.secondColor = EPBaseObject.subGetColorByTokenValues(color2Array)
if(values.count == 4){
let color3Str = values[3].replacingOccurrences(of: EPConfigGradientPrefix, with: "")
let color3Array = color3Str.components(separatedBy: "&")
gradient.thirdColor = EPBaseObject.subGetColorByTokenValues(color3Array)
}
return gradient
}
// MARK: - Typography Method
static func typography(token: CLEnumSystemToken) -> EPTypography {
let key = KeyForSystemTokenFrom(token)
let values = baseTokens.getTokensByKey(key)
guard values.count == 4 else {
assertionFailure("Wrong format for typography token: \(key)")
return EPTypography()
}
var typography = EPTypography()
var fontName = values[0]
guard let fontSize = Int(values[1]),
let weight = Int(values[2]),
let lineHeight = Int(values[3]) else {
assertionFailure("Invalid typography values for token: \(key)")
return EPTypography()
}
if !fontName.contains("-Italic") {
switch weight {
case 400:
fontName += "-Regular"
case 500:
fontName += "-Medium"
case 600:
fontName += "-SemiBold"
case 700:
fontName += "-Bold"
default:
assertionFailure("Not handled font weight: \(weight)")
}
}
typography.font = UIFont(name: fontName, size: CGFloat(fontSize))
typography.lineHeight = CGFloat(lineHeight)
return typography
}
// MARK: - Font Method
static func font(token: CLEnumSystemToken) -> UIFont {
let key = KeyForSystemTokenFrom(token)
let values = baseTokens.getTokensByKey(key)
guard values.count == 4 else {
assertionFailure("Wrong format for font token: \(key)")
return UIFont.systemFont(ofSize: 14)
}
var fontName = values[0]
guard let fontSize = Int(values[1]),
let weight = Int(values[2]) else {
assertionFailure("Invalid font values for token: \(key)")
return UIFont.systemFont(ofSize: 14)
}
if(fontName .isNotBlank){
fontName = fontName.removingAllWhitespace
}
if !fontName.contains("-Italic") {
switch weight {
case 400:
fontName += "-Regular"
case 500:
fontName += "-Medium"
case 600:
fontName += "-SemiBold"
case 700:
fontName += "-Bold"
default:
assertionFailure("Not handled font weight: \(weight)")
}
}
return UIFont(name: fontName, size: CGFloat(fontSize)) ?? UIFont.systemFont(ofSize: 14)
}
}