Last active
August 6, 2023 18:24
-
-
Save carlynorama/3e6765d4a87aaaf3fe2f69abb14764ca to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// SwiftUIStyle.swift | |
// | |
// | |
// Created by Carlyn Maw on 8/1/23. | |
// | |
import Foundation | |
//https://talk.objc.io/episodes/S01E225-view-protocols-and-shapes | |
//https://talk.objc.io/episodes/S01E343-swiftui-style-backend-library | |
//https://forums.swift.org/t/swiftui-viewbuilder-result-is-a-tupleview-how-is-apple-using-it-and-able-to-avoid-turning-things-into-anyview/28181 | |
//https://github.com/apple/swift-certificates/blob/8debe3f20df931a29d0e5834fd8101fb49feea42/Sources/X509/Verifier/AnyPolicy.swift#L41 | |
//https://forums.swift.org/t/resultbuilder-buildpartialblock-reducing-and-buildarray/66563/18 | |
//Public type "Branch" type | |
public protocol Layer { | |
associatedtype Content:Layer | |
var content: Content { get } | |
} | |
//https://stackoverflow.com/questions/33112559/protocol-doesnt-conform-to-itself | |
extension Layer { | |
//usage: | |
//let testResult:MyDesiredType = someLayer.testType() | |
func testType<T>() -> T? { | |
content as? T | |
} | |
} | |
//Actually a private type, the "Leaf", the type with something to "say" to the renderer | |
public protocol RenderableLayer { | |
var id:String { get } | |
func render() | |
typealias Content = Never | |
} | |
//Default conformance | |
extension RenderableLayer { | |
func render() { | |
print("\(self.id)") | |
} | |
} | |
//Uh-oh. Not passing through correctly in one of the RenderableLayers | |
public extension Layer where Content == Never { | |
var content: Never { fatalError("This should never be called.") } | |
} | |
//Never as Layer so RenderableLayers can be Layers. | |
extension Never: Layer { | |
public var id:String { "Never" } | |
public typealias Content = Never | |
} | |
//"Is it a passthrough or opinionated/leaf type" checker. | |
public extension Layer { | |
func _render() { | |
if let bottom = self as? RenderableLayer { | |
//print("Found a bottom \(id)") | |
bottom.render() | |
} else { | |
//print("Not yet. \(id)") | |
content._render() | |
} | |
} | |
} | |
//------------------------------------------------------------------------- | |
//MARK: The base builder | |
@resultBuilder | |
public enum LayerBuilder { | |
//Can't be "some Layer" for for loops to work. | |
// public static func buildPartialBlock<L: Layer>(first: L) -> some Layer { | |
// first | |
// } | |
public static func buildPartialBlock<L: Layer>(first: L) -> L { | |
first | |
} | |
//HAVE TO HAVE THIS if have any other buildExpressions or everything, | |
//everything gets wrapped as an optional, or array or some such nonsense. | |
//must be L and not some Layer | |
static func buildExpression<L:Layer>(_ component: L) -> L { | |
component | |
} | |
public static func buildPartialBlock<L0: Layer, L1: Layer>(accumulated: L0, next: L1) -> some Layer { | |
Tuple2Layer(first: accumulated, second: next) | |
} | |
} | |
//Data storage type. | |
struct Tuple2Layer<First:Layer, Second:Layer>: Layer, RenderableLayer { | |
var id:String { "Tuple" } | |
var first: First | |
var second: Second | |
init(first:First, second:Second) { | |
self.first = first | |
self.second = second | |
} | |
func render() { | |
first._render() | |
second._render() | |
} | |
} | |
//------------------------------------------------------------------------- | |
//End user types | |
struct Assembly<Content:Layer>:Layer { | |
var content: Content | |
public init(@LayerBuilder content: () -> Content) { | |
self.content = content() | |
} | |
} | |
struct Circle:Layer, RenderableLayer { | |
var id:String { "Circle" } | |
} | |
struct CircleWithParam:Layer, RenderableLayer { | |
let radius:Int | |
var id:String { "Circle with radius \(radius)" } | |
} | |
struct Square:Layer, RenderableLayer { | |
var id:String { "Square" } | |
} | |
struct SquareWithParam:Layer, RenderableLayer { | |
let side:Int | |
var id:String { "Square with side \(side)" } | |
} | |
struct Triangle:Layer, RenderableLayer { | |
var id:String { "Triangle" } | |
} | |
struct Trapezoid:Layer, RenderableLayer { | |
var id:String { "Trapezoid" } | |
} | |
let insert = Assembly { | |
Triangle() | |
Triangle() | |
Triangle() | |
} | |
let test = Assembly { | |
Circle() | |
Square() | |
insert | |
Circle() | |
Circle() | |
} | |
//MARK: Hello World | |
print(test) | |
test._render() | |
//------------------------------------------------------------------------- | |
//If statement without an else. | |
extension LayerBuilder { | |
// buildOptional is only for if's not for actual optionals. | |
public static func buildOptional<L:Layer>(_ component: L?) -> some Layer { | |
WrappedOptional(component) | |
} | |
//TODO: Test this further. May not have worked in SketchPad. | |
// public static func buildOptional<L: Layer>(_ component: L?) -> some Layer { | |
// ArrayLayer(from: component.map { [$0] } ?? []) | |
// } | |
} | |
struct WrappedOptional<Wrapped:Layer>:Layer, RenderableLayer { | |
var wrapped: Wrapped? | |
init(_ wrapped: Wrapped?) { | |
self.wrapped = wrapped | |
} | |
var id:String { | |
if let wrapped { | |
if let named = wrapped as? RenderableLayer { | |
return named.id | |
} | |
} | |
return "empty wrapper" | |
} | |
func render() { | |
if let wrapped { wrapped._render() } | |
} | |
} | |
let showCircle = false | |
let maybe = Assembly { | |
Square() | |
if showCircle { | |
Circle() | |
Triangle() //See two works. | |
} | |
Square() | |
} | |
//MARK: If, no else | |
print(maybe) | |
maybe._render() | |
//------------------------------------------------------------------------- | |
// Function returns a Layer? type. | |
extension LayerBuilder { | |
// buildOptional is only for if's not for actual optionals. | |
static func buildExpression<L:Layer>(_ component: L?) -> ArrayLayer<L> { | |
ArrayLayer(from: component.map { [$0] } ?? []) | |
} | |
} | |
func returnCircleMaybe() -> Circle? { | |
let tossUp = false | |
if tossUp { return Circle() } | |
else { return nil } | |
} | |
//MARK: Optional expression | |
let depends = Assembly { | |
Square() | |
returnCircleMaybe() | |
Square() | |
} | |
//------------------------------------------------------------------------- | |
// If-Else implementation | |
extension LayerBuilder { | |
static func buildEither<First: Layer, Second: Layer>(first component: First) -> _Either<First, Second> { | |
_Either<First, Second>(storage: .first(component)) | |
} | |
static func buildEither<First: Layer, Second: Layer>(second component: Second) -> _Either<First, Second> { | |
_Either<First, Second>(storage: .second(component)) | |
} | |
} | |
public struct _Either<First: Layer, Second: Layer>: Layer, RenderableLayer { | |
enum Storage { | |
case first(First) | |
case second(Second) | |
} | |
var storage: Storage | |
init(storage: Storage) { | |
self.storage = storage | |
} | |
public var id: String { | |
switch storage { | |
case .first(let storedLayer): | |
if let named = storedLayer as? RenderableLayer { | |
return named.id | |
} | |
case .second(let storedLayer): | |
if let named = storedLayer as? RenderableLayer { | |
return named.id | |
} | |
} | |
return "either" | |
} | |
public func render() { | |
switch storage { | |
case .first(let storedLayer): | |
storedLayer._render() | |
case .second(let storedLayer): | |
storedLayer._render() | |
} | |
} | |
} | |
let which = Assembly { | |
Square() | |
if showCircle { | |
Circle() | |
Triangle() | |
} else { | |
Triangle() | |
Circle() | |
} | |
Square() | |
} | |
//MARK: If with else | |
print(which) | |
which._render() | |
//------------------------------------------------------------------------- | |
// Basic for loop with ONE AND ONLY ONE Layer in it. | |
struct ArrayLayer<Element:Layer>:Layer, RenderableLayer { | |
var id: String { "ArrayLayer" } | |
var elements: [Element] | |
public init(from elements:[Element]) { | |
self.elements = elements | |
} | |
func render() { | |
for element in elements { | |
element._render() | |
} | |
} | |
} | |
extension LayerBuilder { | |
static func buildArray<L:Layer>(_ components: [L]) -> ArrayLayer<L> { | |
ArrayLayer(from: components) | |
} | |
} | |
let singularType = Assembly { | |
Square() | |
for i in 0...20 { | |
Circle() | |
//if i > 5 { break } | |
} | |
//ERROR that shows up when builder gets confused: | |
//Generic parameter 'τ_1_0' could not be inferred, | |
//beta 15.5: Underlying type for opaque result type 'some Layer' could not be inferred from return expression | |
Square() | |
} | |
////MARK: Loop Attempt | |
print(singularType) | |
singularType._render() | |
//------------------------------------------------------------------------- | |
// Accept arrays not in for-loops. | |
extension LayerBuilder { | |
//These make an array work. | |
static func buildExpression<L:Layer>(_ expression: [L]) -> ArrayLayer<L> { | |
ArrayLayer(from: expression) | |
} | |
} | |
// | |
func spewCircles() -> [Circle] { | |
var array:[Circle] = [] | |
for _ in 0...3 { | |
array.append(Circle()) | |
} | |
return array | |
} | |
let singularTypeArray = Assembly { | |
Square() | |
[Circle(), Circle(), Circle()] | |
spewCircles() | |
Square() | |
} | |
// | |
//MARK: Explicit Arrays | |
print(singularTypeArray) | |
singularTypeArray._render() | |
// | |
// | |
//------------------------------------------------------------------------- | |
//Heterogeneous Loop | |
//TODO: Is there a way, and is it desirable, to have the init instead make a TupleStack so that the storage is the same texture as other storage? | |
struct Repeating<Content:Layer>:Layer, RenderableLayer { | |
var id: String { "Repeating" } | |
//var count:Int | |
var elements: [Content] | |
public init(count:Int, @LayerBuilder content: (Int) -> Content) { | |
self.elements = [] | |
for index in 0..<count { | |
self.elements.append(content(index)) | |
} | |
} | |
func render() { | |
for element in elements { | |
element._render() | |
} | |
} | |
} | |
let multiType = Assembly { | |
Square() | |
Repeating(count: 3) { index in | |
let adjusted = index+2 | |
CircleWithParam(radius: adjusted*3) | |
SquareWithParam(side: adjusted*2) | |
} | |
Circle() | |
} | |
//MARK: Heterogeneous Loop Attempt | |
print(multiType) | |
multiType._render() | |
//TODO: Write using sequence? | |
//https://stackoverflow.com/questions/60461796/swift-way-to-c-style-for-loop-that-alters-initial-variable | |
struct IndexLoop<Content:Layer>:Layer, RenderableLayer { | |
var id: String { "Repeating" } | |
//var count:Int | |
var elements: [Content] | |
public init(from start:Int = 0, to limit:Int, by increment:Int = 1, @LayerBuilder content: (Int) -> Content) { | |
self.elements = [] | |
for index in stride(from: start, to: limit, by: increment) { | |
self.elements.append(content(index)) | |
} | |
} | |
func render() { | |
for element in elements { | |
element._render() | |
} | |
} | |
} | |
let multiType2 = Assembly { | |
Square() | |
IndexLoop(to: 3) { index in | |
let adjusted = index+2 | |
CircleWithParam(radius: adjusted*3) | |
SquareWithParam(side: adjusted*2) | |
} | |
Circle() | |
} | |
//MARK: For Loop <3 | |
print(multiType2) | |
multiType2._render() | |
//------------------------------------------------------------------------- | |
// AnyLayer | |
//TODO: Actually make sure really needed before move to SketchPad | |
//Super class that does not have a generic dependency | |
class AnyLayerBase: RenderableLayer { | |
var id: String { fatalError("AnyLayerBase should never call id") } | |
func render() { fatalError("AnyLayerBase should never call render") } | |
} | |
//Subclass that does know what it's generic is. | |
final class AnyLayerImpl<L: Layer> : AnyLayerBase { | |
let layer: L | |
init(_ layer: L) { | |
self.layer = layer | |
} | |
override var id: String { | |
if let tested = layer as? RenderableLayer { | |
return tested.id | |
} else { | |
return "AnyLayerImpl" | |
} | |
} | |
override func render() { | |
if let tested = layer as? RenderableLayer { | |
tested | |
} else { | |
layer._render() | |
} | |
} | |
} | |
//struct that can use the features of the superclass/Protocol without | |
//having a generic as part of its static type | |
struct AnyLayer: Layer, RenderableLayer { | |
let wrapped: AnyLayerBase | |
init<L: Layer>(_ layer: L) { | |
self.wrapped = AnyLayerImpl(layer) | |
} | |
var id:String { wrapped.id } | |
//Breaks it. Leave out and it works. | |
//func render() { wrapped.render() } | |
} | |
let erased = Assembly { | |
AnyLayer(Circle()) | |
AnyLayer(Trapezoid()) | |
} | |
//MARK: AnyLayer | |
print(erased) | |
erased._render() | |
//MARK: Still not used yet | |
//struct EmptyLayer:Layer, RenderableLayer { | |
//var id: String = "Empty" | |
//func render() { return } | |
//} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment