Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/OpenSwiftUICore/Graphic/Color/Color.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public import CoreGraphics
///
/// ![A screenshot of a green leaf.](Color-1)
///
/// Because SwiftUI treats colors as ``View`` instances, you can also
/// Because OpenSwiftUI treats colors as ``View`` instances, you can also
/// directly add them to a view hierarchy. For example, you can layer
/// a rectangle beneath a sun image using colors defined above:
///
Expand Down
15 changes: 8 additions & 7 deletions Sources/OpenSwiftUICore/Graphic/Color/ColorResolved.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Audited for iOS 18.0
// Status: WIP

import Foundation
package import Foundation
import OpenSwiftUI_SPI

// MARK: - Color.Resolved
Expand Down Expand Up @@ -51,13 +51,14 @@

// MARK: - Color.Resolved + ResolvedPaint

extension Color.Resolved/*: ResolvedPaint*/ {
// func draw(path: Path, style: paathDrawingStyle, in context: GraphicsContext, bounds: CGRect?)

var isClear: Bool { opacity == 0 }
var isOpaque: Bool { opacity == 1 }
extension Color.Resolved: ResolvedPaint {
package func draw(path: Path, style: PathDrawingStyle, in context: GraphicsContext, bounds: CGRect?) {
// TODO

Check warning on line 56 in Sources/OpenSwiftUICore/Graphic/Color/ColorResolved.swift

View check run for this annotation

Codecov / codecov/patch

Sources/OpenSwiftUICore/Graphic/Color/ColorResolved.swift#L55-L56

Added lines #L55 - L56 were not covered by tests
}

// static leafProtobufTag: CodableResolvedPaint.Tag?
package var isClear: Bool { opacity == 0 }
package var isOpaque: Bool { opacity == 1 }
package static var leafProtobufTag: CodableResolvedPaint.Tag? { .color }
}

// MARK: - Color.Resolved + ShapeStyle
Expand Down
176 changes: 176 additions & 0 deletions Sources/OpenSwiftUICore/Graphic/Color/Paint.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
//
// Paint.swift
// OpenSwiftUICore
//
// Audited for iOS 18.0
// Status: Blocked by Gradient, Image and Shader

package import Foundation

// MARK: - ResolvedPaint

package protocol ResolvedPaint: Equatable, Animatable, ProtobufEncodableMessage {
func draw(path: Path, style: PathDrawingStyle, in context: GraphicsContext, bounds: CGRect?)
var isClear: Bool { get }
var isOpaque: Bool { get }
var resolvedGradient: ResolvedGradient? { get }
var isCALayerCompatible: Bool { get }
static var leafProtobufTag: CodableResolvedPaint.Tag? { get }
func encodePaint(to encoder: inout ProtobufEncoder) throws
}

// MARK: - ResolvedPaint + Default Implementations

extension ResolvedPaint {
package var isClear: Bool { false }
package var isOpaque: Bool { false }

Check warning on line 26 in Sources/OpenSwiftUICore/Graphic/Color/Paint.swift

View check run for this annotation

Codecov / codecov/patch

Sources/OpenSwiftUICore/Graphic/Color/Paint.swift#L25-L26

Added lines #L25 - L26 were not covered by tests
package var resolvedGradient: ResolvedGradient? { nil }
package var isCALayerCompatible: Bool { true }
package func encodePaint(to encoder: inout ProtobufEncoder) throws {
if let tag = Self.leafProtobufTag {
try encoder.messageField(tag.rawValue, self)
} else {
try encode(to: &encoder)

Check warning on line 33 in Sources/OpenSwiftUICore/Graphic/Color/Paint.swift

View check run for this annotation

Codecov / codecov/patch

Sources/OpenSwiftUICore/Graphic/Color/Paint.swift#L33

Added line #L33 was not covered by tests
}
}
}

// MARK: - AnyResolvedPaint

package class AnyResolvedPaint: Equatable {
package func draw(path: Path, style: PathDrawingStyle, in ctx: GraphicsContext, bounds: CGRect?) {}
package var protobufPaint: Any? { nil }
package var isClear: Bool { false }
package var isOpaque: Bool { false }
package var resolvedGradient: ResolvedGradient? { nil }
package var isCALayerCompatible: Bool { false }
package func isEqual(to other: AnyResolvedPaint) -> Bool { false }
package func visit<V>(_ visitor: inout V) where V : ResolvedPaintVisitor {}
package func encode(to encoder: any Encoder) throws { preconditionFailure("") }
package func encode(to encoder: inout ProtobufEncoder) throws { preconditionFailure("") }

Check warning on line 50 in Sources/OpenSwiftUICore/Graphic/Color/Paint.swift

View check run for this annotation

Codecov / codecov/patch

Sources/OpenSwiftUICore/Graphic/Color/Paint.swift#L41-L50

Added lines #L41 - L50 were not covered by tests
package static func == (lhs: AnyResolvedPaint, rhs: AnyResolvedPaint) -> Bool { lhs.isEqual(to: rhs) }
}

// MARK: - _AnyResolvedPaint

final package class _AnyResolvedPaint<P>: AnyResolvedPaint where P: ResolvedPaint {
package let paint: P
package init(_ paint: P) {
self.paint = paint
}

override package func draw(path: Path, style: PathDrawingStyle, in ctx: GraphicsContext, bounds: CGRect?) {
paint.draw(path: path, style: style, in: ctx, bounds: bounds)

Check warning on line 63 in Sources/OpenSwiftUICore/Graphic/Color/Paint.swift

View check run for this annotation

Codecov / codecov/patch

Sources/OpenSwiftUICore/Graphic/Color/Paint.swift#L62-L63

Added lines #L62 - L63 were not covered by tests
}

override package var protobufPaint: Any? {
paint

Check warning on line 67 in Sources/OpenSwiftUICore/Graphic/Color/Paint.swift

View check run for this annotation

Codecov / codecov/patch

Sources/OpenSwiftUICore/Graphic/Color/Paint.swift#L66-L67

Added lines #L66 - L67 were not covered by tests
}

override package var isClear: Bool {
paint.isClear
}

override package var isOpaque: Bool {
paint.isOpaque
}

override package var resolvedGradient: ResolvedGradient? {
paint.resolvedGradient
}

override package var isCALayerCompatible: Bool {
paint.isCALayerCompatible
}

override package func isEqual(to other: AnyResolvedPaint) -> Bool {
guard let other = other as? _AnyResolvedPaint<P> else {
return false

Check warning on line 88 in Sources/OpenSwiftUICore/Graphic/Color/Paint.swift

View check run for this annotation

Codecov / codecov/patch

Sources/OpenSwiftUICore/Graphic/Color/Paint.swift#L88

Added line #L88 was not covered by tests
}
return paint == other.paint
}

override package func visit<V>(_ visitor: inout V) where V : ResolvedPaintVisitor {
visitor.visitPaint(paint)
}

override package func encode(to encoder: inout ProtobufEncoder) throws {
try paint.encodePaint(to: &encoder)
}
}

// FIXME
extension AnyResolvedPaint: @unchecked Sendable {}
extension _AnyResolvedPaint: @unchecked Sendable {}

// MARK: - ResolvedPaintVisitor

package protocol ResolvedPaintVisitor {
mutating func visitPaint<P>(_ paint: P) where P: ResolvedPaint
}

// MARK: - CodableResolvedPaint [TODO]

package struct CodableResolvedPaint: ProtobufMessage {
package struct Tag: Equatable, ProtobufTag {
package let rawValue: UInt

package init(rawValue: UInt) {
self.rawValue = rawValue
}

package static let color: CodableResolvedPaint.Tag = .init(rawValue: 1)
package static let linearGradient: CodableResolvedPaint.Tag = .init(rawValue: 2)
package static let radialGradient: CodableResolvedPaint.Tag = .init(rawValue: 3)
package static let angularGradient: CodableResolvedPaint.Tag = .init(rawValue: 4)
package static let ellipticalGradient: CodableResolvedPaint.Tag = .init(rawValue: 5)
package static let image: CodableResolvedPaint.Tag = .init(rawValue: 6)
package static let anchorRect: CodableResolvedPaint.Tag = .init(rawValue: 7)
package static let shader: CodableResolvedPaint.Tag = .init(rawValue: 8)
package static let meshGradient: CodableResolvedPaint.Tag = .init(rawValue: 9)
}

package var base: AnyResolvedPaint

package init(_ paint: AnyResolvedPaint) {
base = paint
}

package func encode(to encoder: inout ProtobufEncoder) throws {
try base.encode(to: &encoder)
}

package init(from decoder: inout ProtobufDecoder) throws {
var base: AnyResolvedPaint?
while let field = try decoder.nextField() {
switch field.tag {
case Tag.color.rawValue:
let color: Color.Resolved = try decoder.messageField(field)
base = _AnyResolvedPaint(color)
case Tag.linearGradient.rawValue:
break // TODO

Check warning on line 151 in Sources/OpenSwiftUICore/Graphic/Color/Paint.swift

View check run for this annotation

Codecov / codecov/patch

Sources/OpenSwiftUICore/Graphic/Color/Paint.swift#L151

Added line #L151 was not covered by tests
case Tag.radialGradient.rawValue:
break // TODO

Check warning on line 153 in Sources/OpenSwiftUICore/Graphic/Color/Paint.swift

View check run for this annotation

Codecov / codecov/patch

Sources/OpenSwiftUICore/Graphic/Color/Paint.swift#L153

Added line #L153 was not covered by tests
case Tag.angularGradient.rawValue:
break // TODO

Check warning on line 155 in Sources/OpenSwiftUICore/Graphic/Color/Paint.swift

View check run for this annotation

Codecov / codecov/patch

Sources/OpenSwiftUICore/Graphic/Color/Paint.swift#L155

Added line #L155 was not covered by tests
case Tag.ellipticalGradient.rawValue:
break // TODO

Check warning on line 157 in Sources/OpenSwiftUICore/Graphic/Color/Paint.swift

View check run for this annotation

Codecov / codecov/patch

Sources/OpenSwiftUICore/Graphic/Color/Paint.swift#L157

Added line #L157 was not covered by tests
case Tag.image.rawValue:
break // TODO

Check warning on line 159 in Sources/OpenSwiftUICore/Graphic/Color/Paint.swift

View check run for this annotation

Codecov / codecov/patch

Sources/OpenSwiftUICore/Graphic/Color/Paint.swift#L159

Added line #L159 was not covered by tests
case Tag.anchorRect.rawValue:
break // TODO

Check warning on line 161 in Sources/OpenSwiftUICore/Graphic/Color/Paint.swift

View check run for this annotation

Codecov / codecov/patch

Sources/OpenSwiftUICore/Graphic/Color/Paint.swift#L161

Added line #L161 was not covered by tests
case Tag.shader.rawValue:
break // TODO

Check warning on line 163 in Sources/OpenSwiftUICore/Graphic/Color/Paint.swift

View check run for this annotation

Codecov / codecov/patch

Sources/OpenSwiftUICore/Graphic/Color/Paint.swift#L163

Added line #L163 was not covered by tests
case Tag.meshGradient.rawValue:
break // TODO

Check warning on line 165 in Sources/OpenSwiftUICore/Graphic/Color/Paint.swift

View check run for this annotation

Codecov / codecov/patch

Sources/OpenSwiftUICore/Graphic/Color/Paint.swift#L165

Added line #L165 was not covered by tests
default:
try decoder.skipField(field)

Check warning on line 167 in Sources/OpenSwiftUICore/Graphic/Color/Paint.swift

View check run for this annotation

Codecov / codecov/patch

Sources/OpenSwiftUICore/Graphic/Color/Paint.swift#L167

Added line #L167 was not covered by tests
}
}
if let base {
self.init(base)
} else {
throw ProtobufDecoder.DecodingError.failed

Check warning on line 173 in Sources/OpenSwiftUICore/Graphic/Color/Paint.swift

View check run for this annotation

Codecov / codecov/patch

Sources/OpenSwiftUICore/Graphic/Color/Paint.swift#L173

Added line #L173 was not covered by tests
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//
// ResolvedGradient.swift
// OpenSwiftUICore
//
// Audited for iOS 18.0
// Status: Empty

package struct ResolvedGradient: Equatable {
}
15 changes: 0 additions & 15 deletions Sources/OpenSwiftUICore/Graphic/ResolvedPaint.swift

This file was deleted.

2 changes: 0 additions & 2 deletions Sources/OpenSwiftUICore/Shape/FillStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,3 @@ public struct FillStyle: Equatable {
self.isAntialiased = antialiased
}
}

extension FillStyle: Sendable {}
7 changes: 7 additions & 0 deletions Sources/OpenSwiftUICore/Shape/Path/Path.swift
Original file line number Diff line number Diff line change
Expand Up @@ -444,3 +444,10 @@ extension Path: CodableByProxy {

static func unwrap(codingProxy: CodablePath) -> Path { codingProxy.base }
}

// MARK: - PathDrawingStyle

package enum PathDrawingStyle {
case fill(FillStyle)
case stroke(StrokeStyle)
}
2 changes: 0 additions & 2 deletions Sources/OpenSwiftUICore/Shape/StrokeStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,3 @@ extension StrokeStyle: Animatable {
}
}
}

extension StrokeStyle: Sendable {}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// ColorMatrixTests.swift
// OpenSwiftUITests
// OpenSwiftUICoreTests

@testable import OpenSwiftUICore
import Testing
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// ColorResolvedTests.swift
// OpenSwiftUITests
// OpenSwiftUICoreTests

#if canImport(Darwin)

Expand Down
94 changes: 94 additions & 0 deletions Tests/OpenSwiftUICoreTests/Graphics/Color/PaintTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//
// PaintTests.swift
// OpenSwiftUICoreTests

@testable import OpenSwiftUICore
import Testing
import Foundation

struct PaintTests {
@Test
func anyResolvedPaintEquality() {
let color1 = Color.Resolved(red: 1, green: 0, blue: 0, opacity: 1)
let color2 = Color.Resolved(red: 1, green: 0, blue: 0, opacity: 1)
let color3 = Color.Resolved(red: 0, green: 1, blue: 0, opacity: 1)

let paint1 = _AnyResolvedPaint(color1)
let paint2 = _AnyResolvedPaint(color2)
let paint3 = _AnyResolvedPaint(color3)

#expect(paint1 == paint2)
#expect(paint1 != paint3)
}

@Test
func resolvedPaintProperties() {
// Test with a clear color
let clearColor = Color.Resolved(red: 0, green: 0, blue: 0, opacity: 0)
let clearPaint = _AnyResolvedPaint(clearColor)

#expect(clearPaint.isClear == true)
#expect(clearPaint.isOpaque == false)
#expect(clearPaint.resolvedGradient == nil)
#expect(clearPaint.isCALayerCompatible == true)

// Test with an opaque color
let opaqueColor = Color.Resolved(red: 1, green: 1, blue: 1, opacity: 1)
let opaquePaint = _AnyResolvedPaint(opaqueColor)

#expect(opaquePaint.isClear == false)
#expect(opaquePaint.isOpaque == true)
#expect(opaquePaint.resolvedGradient == nil)
#expect(opaquePaint.isCALayerCompatible == true)
}

@Test
func codableResolvedPaintEncoding() throws {
let color = Color.Resolved(red: 1, green: 0, blue: 0, opacity: 1)
let paint = _AnyResolvedPaint(color)
let codablePaint = CodableResolvedPaint(paint)

var encoder = ProtobufEncoder()
try codablePaint.encode(to: &encoder)

let data = try ProtobufEncoder.encoding { encoder in
try codablePaint.encode(to: &encoder)
}
#expect(data.hexString == "0a0a0d0000803f250000803f")
}

@Test
func codableResolvedPaintDecoding() throws {
// Create encoded data for a red color
let color = Color.Resolved(red: 1, green: 0, blue: 0, opacity: 1)
let paint = _AnyResolvedPaint(color)
let originalCodablePaint = CodableResolvedPaint(paint)

let data = try #require(Data(hexString: "0a0a0d0000803f250000803f"))
var decoder = ProtobufDecoder(data)
let decodedPaint = try CodableResolvedPaint(from: &decoder)

#expect(originalCodablePaint.base == decodedPaint.base)
}

@Test
func resolvedPaintVisitor() {
struct TestVisitor: ResolvedPaintVisitor {
var visitedColor: Color.Resolved?

mutating func visitPaint<P>(_ paint: P) where P: ResolvedPaint {
if let colorPaint = paint as? Color.Resolved {
visitedColor = colorPaint
}
}
}

let color = Color.Resolved(red: 1, green: 0, blue: 0, opacity: 1)
let paint = _AnyResolvedPaint(color)

var visitor = TestVisitor()
paint.visit(&visitor)

#expect(visitor.visitedColor == color)
}
}
Loading