From facafa308b8aee5b4f7943c741c97e2d3b669546 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 16 May 2023 15:31:52 -0500 Subject: [PATCH] more changes Signed-off-by: Matt Bruce --- .../Icon/ButtonIcon/ButtonIcon.swift | 248 ++++++++++++++++-- 1 file changed, 222 insertions(+), 26 deletions(-) diff --git a/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift b/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift index cd115e98..d432f273 100644 --- a/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift +++ b/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift @@ -7,6 +7,7 @@ import Foundation import UIKit +import VDSColorTokens @objc(VDSButtonIcon) open class ButtonIcon: Control { @@ -19,7 +20,7 @@ open class ButtonIcon: Control { public enum Kind: String, CaseIterable { case ghost, lowContrast, highContrast } - + public enum SurfaceType: String, CaseIterable { case colorFill, media } @@ -47,24 +48,85 @@ open class ButtonIcon: Control { private var centerYConstraint: NSLayoutConstraint? private var layoutGuideWidthConstraint: NSLayoutConstraint? private var layoutGuideHeightConstraint: NSLayoutConstraint? + private var currentIconName: Icon.Name? { + if let selectedIconName { + return selectedIconName + } else { + return iconName + } + } //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- open var icon = Icon() - + open var kind: Kind = .ghost { didSet { setNeedsUpdate() } } open var surfaceType: SurfaceType = .colorFill { didSet { setNeedsUpdate() } } open var iconName: Icon.Name? { didSet { setNeedsUpdate() } } + open var selectedIconName: Icon.Name? { didSet { setNeedsUpdate() } } open var size: Size = .large { didSet { setNeedsUpdate() } } open var customSize: Int? { didSet { setNeedsUpdate() }} open var floating: Bool = false { didSet { setNeedsUpdate() } } + open var fitToIcon: Bool = false { didSet { setNeedsUpdate() } } open var hideBorder: Bool = true { didSet { setNeedsUpdate() } } open var iconOffset: CGPoint = .init(x: 0, y: 0) { didSet { setNeedsUpdate() } } //-------------------------------------------------- // MARK: - Configuration //-------------------------------------------------- + private var iconColorConfig: AnyColorable { + if selectedIconName != nil { + return selectedIconColorConfig + } else { + if kind == .highContrast { + return highContrastIconColorConfig + } else { + return standardIconColorConfig + } + } + } + + private var currentConfiguration: any Configuration { + switch kind { + case .ghost: + return GhostConfiguration() + + case .lowContrast: + if surfaceType == .colorFill { + return floating ? LowContrastColorFillFloatingConfiguration() : LowContrastColorFillConfiguration() + } else { + return floating ? LowContrastMediaFloatingConfiguration() : LowContrastMediaConfiguration() + } + + case .highContrast: + return floating ? HighContrastFloatingConfiguration() : HighContrastConfiguration() + } + } + + private var standardIconColorConfig: AnyColorable = { + return ControlColorConfiguration().with { + $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .normal) + $0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted) + $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled) + }.eraseToAnyColorable() + }() + + private var highContrastIconColorConfig: AnyColorable = { + return ControlColorConfiguration().with { + $0.setSurfaceColors(VDSColor.elementsPrimaryOndark, VDSColor.elementsPrimaryOnlight, forState: .normal) + $0.setSurfaceColors(VDSColor.interactiveActiveOndark, VDSColor.interactiveActiveOnlight, forState: .highlighted) + $0.setSurfaceColors(VDSColor.interactiveDisabledOndark, VDSColor.interactiveDisabledOnlight, forState: .disabled) + }.eraseToAnyColorable() + }() + + private var selectedIconColorConfig: AnyColorable = { + return ControlColorConfiguration().with { + $0.setSurfaceColors(VDSColor.elementsBrandhighlight, VDSColor.elementsPrimaryOndark, forState: .normal) + $0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted) + $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled) + }.eraseToAnyColorable() + }() //-------------------------------------------------- // MARK: - Initializers @@ -84,25 +146,24 @@ open class ButtonIcon: Control { //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- - open override func setup() { super.setup() - + //create a layoutGuide for the icon to key off of let iconLayoutGuide = UILayoutGuide() addLayoutGuide(iconLayoutGuide) - + //add the icon addSubview(icon) - + //determines the height/width of the icon layoutGuideWidthConstraint = iconLayoutGuide.widthAnchor.constraint(equalToConstant: size.containerSize) layoutGuideHeightConstraint = iconLayoutGuide.heightAnchor.constraint(equalToConstant: size.containerSize) - + //determines the center point of the icon centerXConstraint = icon.centerXAnchor.constraint(equalTo: iconLayoutGuide.centerXAnchor, constant: 0) centerYConstraint = icon.centerYAnchor.constraint(equalTo: iconLayoutGuide.centerYAnchor, constant: 0) - + //activate the constraints NSLayoutConstraint.activate([layoutGuideWidthConstraint!, layoutGuideHeightConstraint!, @@ -130,10 +191,11 @@ open class ButtonIcon: Control { open override func updateView() { super.updateView() - + //ensure there is an icon to set - if let iconName { - icon.name = iconName + if let currentIconName { + icon.name = currentIconName + icon.color = iconColorConfig.getColor(self) icon.size = size.value icon.surface = surface icon.disabled = disabled @@ -141,13 +203,8 @@ open class ButtonIcon: Control { } else { icon.reset() } - - // colors - let bgColor = UIColor.red //backgroundColorConfiguration.getColor(self) - let borderColor = UIColor.green// borderColorConfiguration.getColor(self) - backgroundColor = bgColor - layer.borderColor = borderColor.cgColor - icon.layer.borderColor = UIColor.purple.cgColor + + icon.backgroundColor = .green setNeedsLayout() } @@ -155,30 +212,140 @@ open class ButtonIcon: Control { open override func layoutSubviews() { super.layoutSubviews() - let borderWidth = 2.0 - let cornerRadius = min(frame.width, frame.height) / 2.0 + let currentConfig = currentConfiguration + + backgroundColor = currentConfig.backgroundColorConfig.getColor(self) // calculate center point for child view with offset let childCenter = CGPoint(x: center.x + iconOffset.x, y: center.y + iconOffset.y) centerXConstraint?.constant = childCenter.x - center.x centerYConstraint?.constant = childCenter.y - center.y - // calculate the icon's container size ensuring the padding + //updating current container size var iconLayoutSize = size.containerSize if let customSize { iconLayoutSize = CGFloat(customSize) } + // check to see if this is fitToIcon + if fitToIcon && kind == .ghost { + iconLayoutSize = size.value.dimensions.width + layer.cornerRadius = 0 + } else { + layer.cornerRadius = min(frame.width, frame.height) / 2.0 + } + layoutGuideWidthConstraint?.constant = iconLayoutSize layoutGuideHeightConstraint?.constant = iconLayoutSize - //container - layer.cornerRadius = cornerRadius - layer.borderWidth = borderWidth + //border + if let borderable = currentConfig as? Borderable { + layer.borderColor = borderable.borderColorConfig.getColor(self).cgColor + layer.borderWidth = borderable.borderWidth + icon.layer.borderWidth = borderable.borderWidth + } else { + layer.borderColor = nil + layer.borderWidth = 0 + icon.layer.borderWidth = 0 + } - //icon - icon.layer.borderWidth = borderWidth + if let dropshadowable = currentConfig as? Dropshadowable { + layer.masksToBounds = false + layer.shadowColor = dropshadowable.shadowColorConfig.getColor(self).cgColor + layer.shadowOpacity = Float(dropshadowable.shadowOpacity) + layer.shadowOffset = dropshadowable.shadowOffset + layer.shadowRadius = dropshadowable.shadowRadius + layer.shadowPath = UIBezierPath(rect: bounds).cgPath + layer.shouldRasterize = true + layer.rasterizationScale = UIScreen.main.scale + } else { + layer.shadowOpacity = 0 + layer.shadowRadius = 0 + layer.shadowPath = nil + } } + + private struct GhostConfiguration: Configuration { + var kind: Kind = .ghost + var surfaceType: SurfaceType = .colorFill + var floating: Bool = false + var backgroundColorConfig: AnyColorable = { + SurfaceColorConfiguration(.clear, .clear).eraseToAnyColorable() + }() + } + + private struct LowContrastColorFillConfiguration: Configuration { + var kind: Kind = .lowContrast + var surfaceType: SurfaceType = .colorFill + var floating: Bool = false + var backgroundColorConfig: AnyColorable = { + SurfaceColorConfiguration(VDSColor.paletteGray44.withAlphaComponent(0.94), .clear).eraseToAnyColorable() + }() + } + + private struct LowContrastColorFillFloatingConfiguration: Configuration { + var kind: Kind = .lowContrast + var surfaceType: SurfaceType = .colorFill + var floating: Bool = true + var backgroundColorConfig: AnyColorable = { + SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, .clear).eraseToAnyColorable() + }() + } + + private struct LowContrastMediaConfiguration: Configuration, Borderable { + var kind: Kind = .lowContrast + var surfaceType: SurfaceType = .media + var floating: Bool = false + var backgroundColorConfig: AnyColorable = { + SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, .clear).eraseToAnyColorable() + }() + var borderWidth: CGFloat = 1.0 + var borderColorConfig: AnyColorable = { + SurfaceColorConfiguration(VDSColor.elementsLowcontrastOnlight, .clear).eraseToAnyColorable() + }() + } + + private struct LowContrastMediaFloatingConfiguration: Configuration, Dropshadowable { + var kind: Kind = .lowContrast + var surfaceType: SurfaceType = .media + var floating: Bool = true + var backgroundColorConfig: AnyColorable = { + SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, .clear).eraseToAnyColorable() + }() + var shadowColorConfig: AnyColorable = { + SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, .clear).eraseToAnyColorable() + }() + var shadowOpacity: CGFloat = 0.5 + var shadowOffset: CGSize = .init(width: 1, height: 1) + var shadowRadius: CGFloat = 2 + } + + private struct HighContrastConfiguration: Configuration { + var kind: Kind = .highContrast + var surfaceType: SurfaceType = .colorFill + var floating: Bool = false + var backgroundColorConfig: AnyColorable = { + return ControlColorConfiguration().with { + $0.setSurfaceColors(VDSColor.backgroundPrimaryDark, VDSColor.backgroundPrimaryLight, forState: .normal) + $0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted) + $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled) + }.eraseToAnyColorable() + + }() + } + + private struct HighContrastFloatingConfiguration: Configuration { + var kind: Kind = .highContrast + var surfaceType: SurfaceType = .colorFill + var floating: Bool = true + var backgroundColorConfig: AnyColorable = { + return ControlColorConfiguration().with { + $0.setSurfaceColors(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryLight, forState: .normal) + $0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted) + $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled) + }.eraseToAnyColorable() + }() + } } // MARK: AppleGuidlinesTouchable @@ -188,4 +355,33 @@ extension ButtonIcon: AppleGuidlinesTouchable { Self.acceptablyOutsideBounds(point: point, bounds: bounds) } + open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let view = super.hitTest(point, with: event) + + if view == icon { + return self + } + + return view + } + +} + +private protocol Borderable { + var borderWidth: CGFloat { get set } + var borderColorConfig: AnyColorable { get set } +} + +private protocol Dropshadowable { + var shadowColorConfig: AnyColorable { get set } + var shadowOpacity: CGFloat { get set } + var shadowOffset: CGSize { get set } + var shadowRadius: CGFloat { get set } +} + +private protocol Configuration { + var kind: ButtonIcon.Kind { get set } + var surfaceType: ButtonIcon.SurfaceType { get set } + var floating: Bool { get set } + var backgroundColorConfig: AnyColorable { get set } }