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

579 lines
19 KiB
Swift
Raw Normal View History

2025-10-09 10:29:35 +00:00
//
// 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)
}
}