Skip to content

Commit

Permalink
Add AnyColorBox and AnyFontBox (#291)
Browse files Browse the repository at this point in the history
* Add AnyColorBox implementation

* Add TokenDeferredToRenderer

* Implement AnyFontBox

* Add Any[Color/Font]BoxDeferredToRenderer

* Resolve linter errors

* Appease the linter
  • Loading branch information
carson-katri authored Oct 28, 2020
1 parent 94dc934 commit a5da049
Show file tree
Hide file tree
Showing 5 changed files with 469 additions and 160 deletions.
22 changes: 22 additions & 0 deletions Sources/TokamakCore/Tokens/AnyTokenBox.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2018-2020 Tokamak contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Created by Carson Katri on 10/24/2020.
//

/// Allows "late-binding tokens" to be resolved in an environment by a `Renderer` (or `TokamakCore`)
public protocol AnyTokenBox: AnyObject, Hashable {
associatedtype ResolvedValue
func resolve(in environment: EnvironmentValues) -> ResolvedValue
}
265 changes: 213 additions & 52 deletions Sources/TokamakCore/Tokens/Color.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,186 @@
// Created by Max Desiatov on 16/10/2018.
//

public struct Color: Hashable, Equatable {
public static func == (lhs: Self, rhs: Self) -> Bool {
var lightEnv = EnvironmentValues()
lightEnv.colorScheme = .light
var darkEnv = EnvironmentValues()
darkEnv.colorScheme = .dark
return lhs._evaluate(lightEnv) == rhs._evaluate(lightEnv) &&
lhs._evaluate(darkEnv) == rhs._evaluate(darkEnv)
/// Override `TokamakCore`'s default `Color` resolvers with a Renderer-specific one.
/// You can override a specific color box
/// (such as `_SystemColorBox`, or all boxes with `AnyColorBox`).
///
/// This extension makes all system colors red:
///
/// extension _SystemColorBox: AnyColorBoxDeferredToRenderer {
/// public func deferredResolve(
/// in environment: EnvironmentValues
/// ) -> AnyColorBox.ResolvedValue {
/// return .init(
/// red: 1,
/// green: 0,
/// blue: 0,
/// opacity: 1,
/// space: .sRGB
/// )
/// }
/// }
///
public protocol AnyColorBoxDeferredToRenderer: AnyColorBox {
func deferredResolve(in environment: EnvironmentValues) -> AnyColorBox.ResolvedValue
}

public class AnyColorBox: AnyTokenBox {
public struct _RGBA: Hashable, Equatable {
public let red: Double
public let green: Double
public let blue: Double
public let opacity: Double
public let space: Color.RGBColorSpace
public init(
red: Double,
green: Double,
blue: Double,
opacity: Double,
space: Color.RGBColorSpace
) {
self.red = red
self.green = green
self.blue = blue
self.opacity = opacity
self.space = space
}
}

public static func == (lhs: AnyColorBox, rhs: AnyColorBox) -> Bool { false }
public func hash(into hasher: inout Hasher) {
hasher.combine(evaluator(EnvironmentValues()))
fatalError("implement \(#function) in subclass")
}

public enum RGBColorSpace {
case sRGB
case sRGBLinear
case displayP3
public func resolve(in environment: EnvironmentValues) -> _RGBA {
fatalError("implement \(#function) in subclass")
}
}

public struct _RGBA: Hashable, Equatable {
public let red: Double
public let green: Double
public let blue: Double
public let opacity: Double
public let space: RGBColorSpace
public class _ConcreteColorBox: AnyColorBox {
public let rgba: AnyColorBox._RGBA

public static func == (lhs: _ConcreteColorBox, rhs: _ConcreteColorBox) -> Bool {
lhs.rgba == rhs.rgba
}

override public func hash(into hasher: inout Hasher) {
hasher.combine(rgba)
}

init(_ rgba: AnyColorBox._RGBA) {
self.rgba = rgba
}

let evaluator: (EnvironmentValues) -> _RGBA
override public func resolve(in environment: EnvironmentValues) -> ResolvedValue {
rgba
}
}

private init(_ evaluator: @escaping (EnvironmentValues) -> _RGBA) {
self.evaluator = evaluator
public class _EnvironmentDependentColorBox: AnyColorBox {
public let resolver: (EnvironmentValues) -> Color

public static func == (lhs: _EnvironmentDependentColorBox,
rhs: _EnvironmentDependentColorBox) -> Bool
{
lhs.resolver(EnvironmentValues()) == rhs.resolver(EnvironmentValues())
}

override public func hash(into hasher: inout Hasher) {
hasher.combine(resolver(EnvironmentValues()))
}

init(_ resolver: @escaping (EnvironmentValues) -> Color) {
self.resolver = resolver
}

override public func resolve(in environment: EnvironmentValues) -> ResolvedValue {
resolver(environment).provider.resolve(in: environment)
}
}

public class _SystemColorBox: AnyColorBox {
public enum SystemColor: Equatable, Hashable {
case clear
case black
case white
case gray
case red
case green
case blue
case orange
case yellow
case pink
case purple
case primary
case secondary
}

public let value: SystemColor

public static func == (lhs: _SystemColorBox, rhs: _SystemColorBox) -> Bool {
lhs.value == rhs.value
}

override public func hash(into hasher: inout Hasher) {
hasher.combine(value)
}

fileprivate init(_ value: SystemColor) {
self.value = value
}

override public func resolve(in environment: EnvironmentValues) -> ResolvedValue {
switch environment.colorScheme {
case .light:
switch value {
case .clear: return .init(red: 0, green: 0, blue: 0, opacity: 0, space: .sRGB)
case .black: return .init(red: 0, green: 0, blue: 0, opacity: 1, space: .sRGB)
case .white: return .init(red: 1, green: 1, blue: 1, opacity: 1, space: .sRGB)
case .gray: return .init(red: 0.55, green: 0.55, blue: 0.57, opacity: 1, space: .sRGB)
case .red: return .init(red: 1, green: 0.23, blue: 0.19, opacity: 1, space: .sRGB)
case .green: return .init(red: 0.21, green: 0.78, blue: 0.35, opacity: 1, space: .sRGB)
case .blue: return .init(red: 0.01, green: 0.48, blue: 1, opacity: 1, space: .sRGB)
case .orange: return .init(red: 1, green: 0.58, blue: 0, opacity: 1, space: .sRGB)
case .yellow: return .init(red: 1, green: 0.8, blue: 0, opacity: 1, space: .sRGB)
case .pink: return .init(red: 1, green: 0.17, blue: 0.33, opacity: 1, space: .sRGB)
case .purple: return .init(red: 0.69, green: 0.32, blue: 0.87, opacity: 1, space: .sRGB)
case .primary: return .init(red: 0, green: 0, blue: 0, opacity: 1, space: .sRGB)
case .secondary: return .init(red: 0.55, green: 0.55, blue: 0.57, opacity: 1, space: .sRGB)
}
case .dark:
switch value {
case .clear: return .init(red: 0, green: 0, blue: 0, opacity: 0, space: .sRGB)
case .black: return .init(red: 0, green: 0, blue: 0, opacity: 1, space: .sRGB)
case .white: return .init(red: 1, green: 1, blue: 1, opacity: 1, space: .sRGB)
case .gray: return .init(red: 0.55, green: 0.55, blue: 0.57, opacity: 1, space: .sRGB)
case .red: return .init(red: 1, green: 0.27, blue: 0.23, opacity: 1, space: .sRGB)
case .green: return .init(red: 0.19, green: 0.82, blue: 0.35, opacity: 1, space: .sRGB)
case .blue: return .init(red: 0.04, green: 0.52, blue: 1.00, opacity: 1, space: .sRGB)
case .orange: return .init(red: 1, green: 0.62, blue: 0.04, opacity: 1, space: .sRGB)
case .yellow: return .init(red: 1, green: 0.84, blue: 0.04, opacity: 1, space: .sRGB)
case .pink: return .init(red: 1, green: 0.22, blue: 0.37, opacity: 1, space: .sRGB)
case .purple: return .init(red: 0.75, green: 0.35, blue: 0.95, opacity: 1, space: .sRGB)
case .primary: return .init(red: 1, green: 1, blue: 1, opacity: 1, space: .sRGB)
case .secondary: return .init(red: 0.55, green: 0.55, blue: 0.57, opacity: 1, space: .sRGB)
}
}
}
}

public struct Color: Hashable, Equatable {
public static func == (lhs: Self, rhs: Self) -> Bool {
lhs.provider == rhs.provider
}

public func hash(into hasher: inout Hasher) {
hasher.combine(provider)
}

let provider: AnyColorBox

private init(_ provider: AnyColorBox) {
self.provider = provider
}

public init(
Expand All @@ -56,9 +204,9 @@ public struct Color: Hashable, Equatable {
blue: Double,
opacity: Double = 1
) {
self.init { _ in
_RGBA(red: red, green: green, blue: blue, opacity: opacity, space: colorSpace)
}
self.init(_ConcreteColorBox(
.init(red: red, green: green, blue: blue, opacity: opacity, space: colorSpace)
))
}

public init(_ colorSpace: RGBColorSpace = .sRGB, white: Double, opacity: Double = 1) {
Expand All @@ -77,43 +225,56 @@ public struct Color: Hashable, Equatable {
}

/// Create a `Color` dependent on the current `ColorScheme`.
public static func _withScheme(_ evaluator: @escaping (ColorScheme) -> Self) -> Self {
.init {
evaluator($0.colorScheme)._evaluate($0)
}
public static func _withScheme(_ resolver: @escaping (ColorScheme) -> Self) -> Self {
.init(_EnvironmentDependentColorBox {
resolver($0.colorScheme)
})
}
}

public func _evaluate(_ environment: EnvironmentValues) -> _RGBA {
evaluator(environment)
public struct _ColorProxy {
let subject: Color
public init(_ subject: Color) { self.subject = subject }
public func resolve(in environment: EnvironmentValues) -> AnyColorBox.ResolvedValue {
if let deferred = subject.provider as? AnyColorBoxDeferredToRenderer {
return deferred.deferredResolve(in: environment)
} else {
return subject.provider.resolve(in: environment)
}
}
}

extension Color {
public static let clear: Self = .init(red: 0, green: 0, blue: 0, opacity: 0)
public static let black: Self = .init(white: 0)
public static let white: Self = .init(white: 1)
public static let gray: Self = .init(white: 0.6)
public static let red: Self = .init(red: 1.00, green: 0.27, blue: 0.23)
public static let green: Self = .init(red: 0.20, green: 0.84, blue: 0.29)
public static let blue: Self = .init(red: 0.04, green: 0.52, blue: 1.00)
public static let orange: Self = .init(red: 1.00, green: 0.62, blue: 0.04)
public static let yellow: Self = .init(red: 1.00, green: 0.84, blue: 0.04)
public static let pink: Self = .init(red: 1.00, green: 0.22, blue: 0.37)
public static let purple: Self = .init(red: 0.75, green: 0.36, blue: 0.95)
public static let primary: Self = .init {
switch $0.colorScheme {
case .light:
return .init(red: 0, green: 0, blue: 0, opacity: 1, space: .sRGB)
case .dark:
return .init(red: 1, green: 1, blue: 1, opacity: 1, space: .sRGB)
}
public enum RGBColorSpace {
case sRGB
case sRGBLinear
case displayP3
}
}

public static let secondary: Self = .gray
public static let accentColor: Self = .init {
($0.accentColor ?? Self.blue)._evaluate($0)
extension Color {
private init(systemColor: _SystemColorBox.SystemColor) {
self.init(_SystemColorBox(systemColor))
}

public static let clear: Self = .init(systemColor: .clear)
public static let black: Self = .init(systemColor: .black)
public static let white: Self = .init(systemColor: .white)
public static let gray: Self = .init(systemColor: .gray)
public static let red: Self = .init(systemColor: .red)
public static let green: Self = .init(systemColor: .green)
public static let blue: Self = .init(systemColor: .blue)
public static let orange: Self = .init(systemColor: .orange)
public static let yellow: Self = .init(systemColor: .yellow)
public static let pink: Self = .init(systemColor: .pink)
public static let purple: Self = .init(systemColor: .purple)
public static let primary: Self = .init(systemColor: .primary)

public static let secondary: Self = .init(systemColor: .secondary)
public static let accentColor: Self = .init(_EnvironmentDependentColorBox {
($0.accentColor ?? Self.blue)
})

public init(_ color: UIColor) {
self = color.color
}
Expand Down
Loading

0 comments on commit a5da049

Please sign in to comment.