// // IconButtons.swift // Crush // // Created by Leon on 2025/7/18. // import UIKit enum IconButtonStyle: UInt { case primary case secondary case tertiary case ghost case tertiaryRoundDark case tertiaryRoundLight } enum IconSize: Int { /// 24->21 case large = 21 /// 20 case medium = 20 /// 16 case small = 16 /// 12 case xs = 12 /// 10 case xs10 = 10 /// 8 case xxs = 8 } enum IconButtonRadius: UInt { case none case rectangle case round } // Base class: Typically use subclasses below class EPIconButton: CLButton { var style: IconButtonStyle var iconSize: IconSize var iconCode: IconCode var radius: IconButtonRadius init(style: IconButtonStyle, radius: IconButtonRadius, iconSize: IconSize, iconCode: IconCode) { self.style = style self.iconSize = iconSize self.iconCode = iconCode self.radius = radius super.init(frame: .zero) clipsToBounds = true setupRadius() setupTapExpand() setupTheme() } convenience init(radius: IconButtonRadius, iconSize: IconSize, iconCode: IconCode) { self.init(style: .primary, radius: radius, iconSize: iconSize, iconCode: iconCode) } @available(*, unavailable) override init(frame: CGRect) { fatalError("init(frame:) has not been implemented") } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func update(radius: IconButtonRadius, iconSize: IconSize, iconCode: IconCode) { self.iconSize = iconSize self.iconCode = iconCode self.radius = radius setupRadius() setupTapExpand() setupTheme() } var iconCodeValue: IconCode { get { iconCode } set { iconCode = newValue setupTheme() } } func setupTheme() { // Override in subclasses } func setupRadius() { switch radius { case .rectangle: layer.cornerRadius = 8 case .round: layer.cornerRadius = bgImageSize().height * 0.5 case .none: layer.cornerRadius = 0 break } } func setupTapExpand() { let bgImageSize = bgImageSize() let expand = 48 - bgImageSize.width touchAreaInsets = UIEdgeInsets(top: expand, left: expand, bottom: expand, right: expand) } func bgImageSize() -> CGSize { switch iconSize { case .large: return CGSize(width: 48, height: 48) case .medium: return CGSize(width: 36, height: 36) case .small: return CGSize(width: 32, height: 32) case .xs: return CGSize(width: 24, height: 24) case .xs10: return CGSize(width: 20, height: 20) case .xxs: return CGSize(width: 16, height: 16) } } func makeIcon(color: UIColor) -> UIImage { return MWIconFont.image(fromIconInt: iconCode.rawValue, size: CGSize(width: iconSize.rawValue, height: iconSize.rawValue), color: color, edgeInsets: .zero)! } func makeBGImage(colors: UIColor) -> UIImage { let bgImageSize = bgImageSize() return UIImage.withColor(color: colors, size: bgImageSize) } func makeBGCircleBGImage(color: UIColor) -> UIImage { let bgImageSize = bgImageSize() let bounds = CGRect(origin: .zero, size: bgImageSize) return UIImage.circleImage(withColor: color, bounds: bounds)! } func makeBGGradientImage(token: EPGradient) -> UIImage { var colors: [UIColor] = [] if let firstColor = token.firstColor { colors.append(firstColor) } if let secondColor = token.secondColor { colors.append(secondColor) } let bgImageSize = bgImageSize() return UIImage.gradientHImageWithSize(size: bgImageSize, colors: colors) } func setupTapExpandEdge(insets: UIEdgeInsets) { touchAreaInsets = insets } } // MARK: - EPIconPrimaryButton class EPIconPrimaryButton: EPIconButton { convenience init(radius: IconButtonRadius, iconSize: IconSize, iconCode: IconCode) { self.init(style: .primary, radius: radius, iconSize: iconSize, iconCode: iconCode) } override func setupTheme() { let normalColor = UIColor.c.ctpn // EPSystemToken.color(.txtPrimarySpecialmapNormal) let disabledColor = UIColor.c.ctpd // EPSystemToken.color(.txtPrimaryDisabled) setImage(makeIcon(color: normalColor), for: .normal) setImage(makeIcon(color: normalColor), for: .highlighted) setImage(makeIcon(color: disabledColor), for: .disabled) let normalGradient = CLSystemToken.gradient(token: .cpgn) // EPSystemToken.gradient(.primaryGradientNormal) let pressGradient = CLSystemToken.gradient(token: .cpgp) // EPSystemToken.gradient(.primaryGradientPress) let disabledGradient = CLSystemToken.gradient(token: .csed) // EPSystemToken.gradient(.surfaceElementDisabled) setBackgroundImage(makeBGGradientImage(token: normalGradient), for: .normal) setBackgroundImage(makeBGGradientImage(token: pressGradient), for: .highlighted) setBackgroundImage(makeBGGradientImage(token: disabledGradient), for: .disabled) } } // MARK: - EPIconSecondaryButton class EPIconSecondaryButton: EPIconButton { convenience init(iconSize: IconSize, iconCode: IconCode) { self.init(style: .secondary, radius: .none, iconSize: iconSize, iconCode: iconCode) } override func setupTheme() { layer.borderColor = UIColor.c.cpvn.cgColor // EPSystemToken.color(.primaryVariantNormal).cgColor let normalColor: UIColor = .c.cpvn // EPSystemToken.color(.primaryVariantNormal) let disabledColor: UIColor = .c.cpvd // EPSystemToken.color(.primaryVariantDisabled) let pressColor: UIColor = .c.cpvp // EPSystemToken.color(.primaryVariantPress) setImage(makeIcon(color: normalColor), for: .normal) setImage(makeIcon(color: disabledColor), for: .disabled) setImage(makeIcon(color: pressColor), for: .highlighted) } override func bgImageSize() -> CGSize { switch iconSize { case .large: return CGSize(width: 48, height: 48) case .medium: return CGSize(width: 36, height: 36) case .small: return CGSize(width: 32, height: 32) case .xs: return CGSize(width: 24, height: 24) default: return .zero } } } // MARK: - EPIconTertiaryButton class EPIconTertiaryButton: EPIconButton { var optionalIconColor: UIColor? { didSet { if let color = optionalIconColor { setImage(makeIcon(color: color), for: .normal) setImage(makeIcon(color: color), for: .highlighted) setImage(makeIcon(color: color), for: .selected) } } } var optionalBackgroundColor: UIColor? { didSet { if let color = optionalBackgroundColor { setBackgroundImage(makeBGImage(colors: color), for: .normal) setBackgroundImage(makeBGImage(colors: color), for: .disabled) setBackgroundImage(makeBGImage(colors: color), for: .highlighted) } } } convenience init(radius: IconButtonRadius, iconSize: IconSize, iconCode: IconCode) { self.init(style: .primary, radius: radius, iconSize: iconSize, iconCode: iconCode) } override func setupTheme() { let normalColor: UIColor = .c.ctpn // EPSystemToken.color(.txtPrimaryNormal) let disabledColor: UIColor = .c.ctd // EPSystemToken.color(.txtDisabled) setImage(makeIcon(color: normalColor), for: .normal) setImage(makeIcon(color: disabledColor), for: .disabled) setImage(makeIcon(color: normalColor), for: .highlighted) setImage(makeIcon(color: normalColor), for: .selected) let normalBgColor: UIColor = .c.csen // EPSystemToken.color(.surfaceElementNormal) let disabledBgColor: UIColor = .c.csed // EPSystemToken.color(.surfaceElementDisabled) let pressBgColor: UIColor = .c.csep // EPSystemToken.color(.surfaceElementPress) setBackgroundImage(makeBGImage(colors: normalBgColor), for: .normal) setBackgroundImage(makeBGImage(colors: disabledBgColor), for: .disabled) setBackgroundImage(makeBGImage(colors: pressBgColor), for: .highlighted) } func clearBackgroundImage() { setBackgroundImage(nil, for: .normal) } func resumeBackgroundImage() { let normalBgColor: UIColor = .c.csen // EPSystemToken.color(.surfaceElementNormal) setBackgroundImage(makeBGImage(colors: normalBgColor), for: .normal) } } // MARK: - EPIconGhostButton class EPIconGhostButton: EPIconButton { var optionalIconColor: UIColor? { didSet { if let color = optionalIconColor { setImage(makeIcon(color: color), for: .normal) setImage(makeIcon(color: color), for: .highlighted) setImage(makeIcon(color: color), for: .selected) } } } convenience init(radius: IconButtonRadius, iconSize: IconSize, iconCode: IconCode) { self.init(style: .ghost, radius: radius, iconSize: iconSize, iconCode: iconCode) } override func setupTheme() { let normalColor: UIColor = .c.ctpn // EPSystemToken.color(.txtPrimaryNormal) let disabledColor: UIColor = .c.ctd // EPSystemToken.color(.txtDisabled) setImage(makeIcon(color: normalColor), for: .normal) setImage(makeIcon(color: disabledColor), for: .disabled) setImage(makeIcon(color: normalColor), for: .highlighted) let pressBgColor: UIColor = .c.csep // EPSystemToken.color(.surfaceElementPress) setBackgroundImage(nil, for: .normal) setBackgroundImage(nil, for: .disabled) setBackgroundImage(makeBGCircleBGImage(color: pressBgColor), for: .highlighted) } } // MARK: - EPIconGhostSecondaryButton class EPIconGhostSecondaryButton: EPIconButton { convenience init(radius: IconButtonRadius, iconSize: IconSize, iconCode: IconCode) { self.init(style: .ghost, radius: .round, iconSize: iconSize, iconCode: iconCode) } override func setupTheme() { let normalColor: UIColor = .c.ctsn // EPSystemToken.color(.txtSecondaryNormal) let disabledColor: UIColor = .c.ctsd // EPSystemToken.color(.txtSecondaryDisabled) setImage(makeIcon(color: normalColor), for: .normal) setImage(makeIcon(color: disabledColor), for: .disabled) setImage(makeIcon(color: normalColor), for: .highlighted) let pressBgColor: UIColor = .c.csep // EPSystemToken.color(.surfaceElementPress) setBackgroundImage(nil, for: .normal) setBackgroundImage(nil, for: .disabled) setBackgroundImage(makeBGCircleBGImage(color: pressBgColor), for: .highlighted) } } // MARK: - EPIconContrastTertiaryButton class EPIconContrastTertiaryButton: EPIconButton { convenience init(radius: IconButtonRadius, iconSize: IconSize, iconCode: IconCode) { self.init(style: .ghost, radius: .round, iconSize: iconSize, iconCode: iconCode) } override func setupTheme() { let normalColor: UIColor = .c.ctpn // EPSystemToken.color(.txtPrimaryNormal) let disabledColor: UIColor = .c.ctd // EPSystemToken.color(.txtDisabled) setImage(makeIcon(color: normalColor), for: .normal) setImage(makeIcon(color: disabledColor), for: .disabled) setImage(makeIcon(color: normalColor), for: .highlighted) setImage(makeIcon(color: normalColor), for: .selected) let normalBgColor: UIColor = .c.csedn // EPSystemToken.color(.surfaceElementDarkNormal) let disabledBgColor: UIColor = .c.csedd // EPSystemToken.color(.surfaceElementDarkDisabled) let pressBgColor: UIColor = .c.csedp // EPSystemToken.color(.surfaceElementDarkPress) setBackgroundImage(makeBGImage(colors: normalBgColor), for: .normal) setBackgroundImage(makeBGImage(colors: disabledBgColor), for: .disabled) setBackgroundImage(makeBGImage(colors: pressBgColor), for: .highlighted) } } // MARK: - EPFloatingButton class EPFloatingButton: EPIconButton { convenience init(radius: IconButtonRadius, iconSize: IconSize, iconCode: IconCode) { self.init(style: .ghost, radius: .round, iconSize: iconSize, iconCode: iconCode) } override func setupTheme() { setupNormalMode() } func setupNormalMode() { let normalColor: UIColor = .c.ctpn // EPSystemToken.color(.txtPrimaryNormal) let disabledColor: UIColor = .c.ctpsd // EPSystemToken.color(.txtPrimarySpecialmapDisable) setImage(makeIcon(color: normalColor), for: .normal) setImage(makeIcon(color: disabledColor), for: .disabled) setImage(makeIcon(color: normalColor), for: .highlighted) let pressGradient = CLSystemToken.gradient(token: .cpgp) // EPSystemToken.gradient(.primaryGradientPress) let gradient = CLSystemToken.gradient(token: .cpgn) // EPSystemToken.gradient(.primaryGradientNormal) let disabledBgColor = UIColor.c.csfn // EPSystemToken.color(.surfaceFloatNormal) setBackgroundImage(gradient.toImage(size: CGSize(width: 10, height: 10)), for: .normal) setBackgroundImage(makeBGImage(colors: disabledBgColor), for: .disabled) setBackgroundImage(pressGradient.toImage(size: CGSize(width: 10, height: 10)), for: .highlighted) layer.removeAllAnimations() } func setupMatchingState() { let normalColor: UIColor = .c.ctpn // EPSystemToken.color(.txtPrimaryNormal) let disabledColor: UIColor = .c.ctd // EPSystemToken.color(.txtDisabled) setImage(makeIcon(color: normalColor), for: .normal) setImage(makeIcon(color: disabledColor), for: .disabled) setImage(makeIcon(color: normalColor), for: .highlighted) let pressGradient = CLSystemToken.gradient(token: .cpgp) // EPSystemToken.gradient(.positiveGradientPress) let gradient = CLSystemToken.gradient(token: .cpgn) // EPSystemToken.gradient(.positiveGradientNormal) let disabledBgColor: UIColor = .c.csfn // EPSystemToken.color(.surfaceFloatNormal) setBackgroundImage(gradient.toImage(size: CGSize(width: 10, height: 10)), for: .normal) setBackgroundImage(makeBGImage(colors: disabledBgColor), for: .disabled) setBackgroundImage(pressGradient.toImage(size: CGSize(width: 10, height: 10)), for: .highlighted) let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z") rotationAnimation.toValue = NSNumber(value: Double.pi * 2.0) rotationAnimation.duration = 1.5 rotationAnimation.isRemovedOnCompletion = false rotationAnimation.isCumulative = true rotationAnimation.repeatCount = .infinity layer.add(rotationAnimation, forKey: "rotationAnimation") } override func layoutSubviews() { super.layoutSubviews() if radius == .round { layer.cornerRadius = bounds.size.height * 0.5 } } deinit { layer.removeAllAnimations() } } // MARK: - EPIconTertiaryDarkButton class EPIconTertiaryDarkButton: EPIconButton { convenience init(radius: IconButtonRadius, iconSize: IconSize, iconCode: IconCode) { self.init(style: .tertiaryRoundDark, radius: radius, iconSize: iconSize, iconCode: iconCode) } override func setupTheme() { let normalColor: UIColor = .c.ctpsn // EPSystemToken.color(.txtPrimarySpecialmapNormal) let disabledColor: UIColor = .c.ctpd // EPSystemToken.color(.txtPrimaryDisabled) setImage(makeIcon(color: normalColor), for: .normal) setImage(makeIcon(color: disabledColor), for: .disabled) setImage(makeIcon(color: normalColor), for: .highlighted) let normalBgColor: UIColor = .c.csedn // EPSystemToken.color(.surfaceElementDarkNormal) let disabledBgColor: UIColor = .c.csedd // EPSystemToken.color(.surfaceElementDarkDisabled) let pressBgColor: UIColor = .c.csedp // EPSystemToken.color(.surfaceElementDarkPress) setBackgroundImage(makeBGImage(colors: normalBgColor), for: .normal) setBackgroundImage(makeBGImage(colors: disabledBgColor), for: .disabled) setBackgroundImage(makeBGImage(colors: pressBgColor), for: .highlighted) } func clearBackgroundImage() { setBackgroundImage(nil, for: .normal) } func resumeBackgroundImage() { let normalBgColor:UIColor = .c.csedn//EPSystemToken.color(.surfaceElementDarkNormal) setBackgroundImage(makeBGImage(colors: normalBgColor), for: .normal) } } // MARK: - EPIconTertiaryLightButton class EPIconTertiaryLightButton: EPIconButton { convenience init(radius: IconButtonRadius, iconSize: IconSize, iconCode: IconCode) { self.init(style: .tertiaryRoundLight, radius: radius, iconSize: iconSize, iconCode: iconCode) } override func setupTheme() { let normalColor: UIColor = .c.ctpsn//EPSystemToken.color(.txtPrimarySpecialmapNormal) let disabledColor: UIColor = .c.ctpsd//EPSystemToken.color(.txtPrimarySpecialmapDisable) setImage(makeIcon(color: normalColor), for: .normal) setImage(makeIcon(color: disabledColor), for: .disabled) setImage(makeIcon(color: normalColor), for: .highlighted) let normalBgColor: UIColor = .c.cseln//EPSystemToken.color(.surfaceElementLightNormal) let disabledBgColor: UIColor = .c.cseld//EPSystemToken.color(.surfaceElementLightDisabled) let pressBgColor: UIColor = .c.cselp//EPSystemToken.color(.surfaceElementLightPress) setBackgroundImage(makeBGImage(colors: normalBgColor), for: .normal) setBackgroundImage(makeBGImage(colors: disabledBgColor), for: .disabled) setBackgroundImage(makeBGImage(colors: pressBgColor), for: .highlighted) } } class EPIconDestructiveButton : EPIconButton{ override func setupTheme() { let normalColor: UIColor = .c.ctpn let disabledColor: UIColor = .c.ctpd setImage(makeIcon(color: normalColor), for: .normal) setImage(makeIcon(color: disabledColor), for: .disabled) setImage(makeIcon(color: normalColor), for: .highlighted) let normalBgColor: UIColor = .c.cin let disabledBgColor: UIColor = .c.cid let pressBgColor: UIColor = .c.cip setBackgroundImage(makeBGImage(colors: normalBgColor), for: .normal) setBackgroundImage(makeBGImage(colors: disabledBgColor), for: .disabled) setBackgroundImage(makeBGImage(colors: pressBgColor), for: .highlighted) } }