|
| 1 | +// |
| 2 | +// Paint.swift |
| 3 | +// OpenSwiftUICore |
| 4 | +// |
| 5 | +// Audited for iOS 18.0 |
| 6 | +// Status: Blocked by Gradient, Image and Shader |
| 7 | + |
| 8 | +package import Foundation |
| 9 | + |
| 10 | +// MARK: - ResolvedPaint |
| 11 | + |
| 12 | +package protocol ResolvedPaint: Equatable, Animatable, ProtobufEncodableMessage { |
| 13 | + func draw(path: Path, style: PathDrawingStyle, in context: GraphicsContext, bounds: CGRect?) |
| 14 | + var isClear: Bool { get } |
| 15 | + var isOpaque: Bool { get } |
| 16 | + var resolvedGradient: ResolvedGradient? { get } |
| 17 | + var isCALayerCompatible: Bool { get } |
| 18 | + static var leafProtobufTag: CodableResolvedPaint.Tag? { get } |
| 19 | + func encodePaint(to encoder: inout ProtobufEncoder) throws |
| 20 | +} |
| 21 | + |
| 22 | +// MARK: - ResolvedPaint + Default Implementations |
| 23 | + |
| 24 | +extension ResolvedPaint { |
| 25 | + package var isClear: Bool { false } |
| 26 | + package var isOpaque: Bool { false } |
| 27 | + package var resolvedGradient: ResolvedGradient? { nil } |
| 28 | + package var isCALayerCompatible: Bool { true } |
| 29 | + package func encodePaint(to encoder: inout ProtobufEncoder) throws { |
| 30 | + if let tag = Self.leafProtobufTag { |
| 31 | + try encoder.messageField(tag.rawValue, self) |
| 32 | + } else { |
| 33 | + try encode(to: &encoder) |
| 34 | + } |
| 35 | + } |
| 36 | +} |
| 37 | + |
| 38 | +// MARK: - AnyResolvedPaint |
| 39 | + |
| 40 | +package class AnyResolvedPaint: Equatable { |
| 41 | + package func draw(path: Path, style: PathDrawingStyle, in ctx: GraphicsContext, bounds: CGRect?) {} |
| 42 | + package var protobufPaint: Any? { nil } |
| 43 | + package var isClear: Bool { false } |
| 44 | + package var isOpaque: Bool { false } |
| 45 | + package var resolvedGradient: ResolvedGradient? { nil } |
| 46 | + package var isCALayerCompatible: Bool { false } |
| 47 | + package func isEqual(to other: AnyResolvedPaint) -> Bool { false } |
| 48 | + package func visit<V>(_ visitor: inout V) where V : ResolvedPaintVisitor {} |
| 49 | + package func encode(to encoder: any Encoder) throws { preconditionFailure("") } |
| 50 | + package func encode(to encoder: inout ProtobufEncoder) throws { preconditionFailure("") } |
| 51 | + package static func == (lhs: AnyResolvedPaint, rhs: AnyResolvedPaint) -> Bool { lhs.isEqual(to: rhs) } |
| 52 | +} |
| 53 | + |
| 54 | +// MARK: - _AnyResolvedPaint |
| 55 | + |
| 56 | +final package class _AnyResolvedPaint<P>: AnyResolvedPaint where P: ResolvedPaint { |
| 57 | + package let paint: P |
| 58 | + package init(_ paint: P) { |
| 59 | + self.paint = paint |
| 60 | + } |
| 61 | + |
| 62 | + override package func draw(path: Path, style: PathDrawingStyle, in ctx: GraphicsContext, bounds: CGRect?) { |
| 63 | + paint.draw(path: path, style: style, in: ctx, bounds: bounds) |
| 64 | + } |
| 65 | + |
| 66 | + override package var protobufPaint: Any? { |
| 67 | + paint |
| 68 | + } |
| 69 | + |
| 70 | + override package var isClear: Bool { |
| 71 | + paint.isClear |
| 72 | + } |
| 73 | + |
| 74 | + override package var isOpaque: Bool { |
| 75 | + paint.isOpaque |
| 76 | + } |
| 77 | + |
| 78 | + override package var resolvedGradient: ResolvedGradient? { |
| 79 | + paint.resolvedGradient |
| 80 | + } |
| 81 | + |
| 82 | + override package var isCALayerCompatible: Bool { |
| 83 | + paint.isCALayerCompatible |
| 84 | + } |
| 85 | + |
| 86 | + override package func isEqual(to other: AnyResolvedPaint) -> Bool { |
| 87 | + guard let other = other as? _AnyResolvedPaint<P> else { |
| 88 | + return false |
| 89 | + } |
| 90 | + return paint == other.paint |
| 91 | + } |
| 92 | + |
| 93 | + override package func visit<V>(_ visitor: inout V) where V : ResolvedPaintVisitor { |
| 94 | + visitor.visitPaint(paint) |
| 95 | + } |
| 96 | + |
| 97 | + override package func encode(to encoder: inout ProtobufEncoder) throws { |
| 98 | + try paint.encodePaint(to: &encoder) |
| 99 | + } |
| 100 | +} |
| 101 | + |
| 102 | +// FIXME |
| 103 | +extension AnyResolvedPaint: @unchecked Sendable {} |
| 104 | +extension _AnyResolvedPaint: @unchecked Sendable {} |
| 105 | + |
| 106 | +// MARK: - ResolvedPaintVisitor |
| 107 | + |
| 108 | +package protocol ResolvedPaintVisitor { |
| 109 | + mutating func visitPaint<P>(_ paint: P) where P: ResolvedPaint |
| 110 | +} |
| 111 | + |
| 112 | +// MARK: - CodableResolvedPaint [TODO] |
| 113 | + |
| 114 | +package struct CodableResolvedPaint: ProtobufMessage { |
| 115 | + package struct Tag: Equatable, ProtobufTag { |
| 116 | + package let rawValue: UInt |
| 117 | + |
| 118 | + package init(rawValue: UInt) { |
| 119 | + self.rawValue = rawValue |
| 120 | + } |
| 121 | + |
| 122 | + package static let color: CodableResolvedPaint.Tag = .init(rawValue: 1) |
| 123 | + package static let linearGradient: CodableResolvedPaint.Tag = .init(rawValue: 2) |
| 124 | + package static let radialGradient: CodableResolvedPaint.Tag = .init(rawValue: 3) |
| 125 | + package static let angularGradient: CodableResolvedPaint.Tag = .init(rawValue: 4) |
| 126 | + package static let ellipticalGradient: CodableResolvedPaint.Tag = .init(rawValue: 5) |
| 127 | + package static let image: CodableResolvedPaint.Tag = .init(rawValue: 6) |
| 128 | + package static let anchorRect: CodableResolvedPaint.Tag = .init(rawValue: 7) |
| 129 | + package static let shader: CodableResolvedPaint.Tag = .init(rawValue: 8) |
| 130 | + package static let meshGradient: CodableResolvedPaint.Tag = .init(rawValue: 9) |
| 131 | + } |
| 132 | + |
| 133 | + package var base: AnyResolvedPaint |
| 134 | + |
| 135 | + package init(_ paint: AnyResolvedPaint) { |
| 136 | + base = paint |
| 137 | + } |
| 138 | + |
| 139 | + package func encode(to encoder: inout ProtobufEncoder) throws { |
| 140 | + try base.encode(to: &encoder) |
| 141 | + } |
| 142 | + |
| 143 | + package init(from decoder: inout ProtobufDecoder) throws { |
| 144 | + var base: AnyResolvedPaint? |
| 145 | + while let field = try decoder.nextField() { |
| 146 | + switch field.tag { |
| 147 | + case Tag.color.rawValue: |
| 148 | + let color: Color.Resolved = try decoder.messageField(field) |
| 149 | + base = _AnyResolvedPaint(color) |
| 150 | + case Tag.linearGradient.rawValue: |
| 151 | + break // TODO |
| 152 | + case Tag.radialGradient.rawValue: |
| 153 | + break // TODO |
| 154 | + case Tag.angularGradient.rawValue: |
| 155 | + break // TODO |
| 156 | + case Tag.ellipticalGradient.rawValue: |
| 157 | + break // TODO |
| 158 | + case Tag.image.rawValue: |
| 159 | + break // TODO |
| 160 | + case Tag.anchorRect.rawValue: |
| 161 | + break // TODO |
| 162 | + case Tag.shader.rawValue: |
| 163 | + break // TODO |
| 164 | + case Tag.meshGradient.rawValue: |
| 165 | + break // TODO |
| 166 | + default: |
| 167 | + try decoder.skipField(field) |
| 168 | + } |
| 169 | + } |
| 170 | + if let base { |
| 171 | + self.init(base) |
| 172 | + } else { |
| 173 | + throw ProtobufDecoder.DecodingError.failed |
| 174 | + } |
| 175 | + } |
| 176 | +} |
0 commit comments