diff --git a/Package.swift b/Package.swift index 377a030a2..5ab4f5930 100644 --- a/Package.swift +++ b/Package.swift @@ -104,6 +104,16 @@ let openSwiftUIExtensionTarget = Target.target( ], swiftSettings: sharedSwiftSettings ) + +let bridgeFramework = Context.environment["OPENSWIFTUI_BRIDGE_FRAMEWORK"] ?? "SwiftUI" +let openSwiftUIBridgeTarget = Target.target( + name: "OpenSwiftUIBridge", + dependencies: [ + "OpenSwiftUI", + ], + sources: ["Bridgeable.swift", bridgeFramework], + swiftSettings: sharedSwiftSettings +) let OpenSwiftUI_SPITestTarget = Target.testTarget( name: "OpenSwiftUI_SPITests", dependencies: [ @@ -140,6 +150,15 @@ let openSwiftUICompatibilityTestTarget = Target.testTarget( exclude: ["README.md"], swiftSettings: sharedSwiftSettings ) +let openSwiftUIBridgeTestTarget = Target.testTarget( + name: "OpenSwiftUIBridgeTests", + dependencies: [ + "OpenSwiftUIBridge", + ], + exclude: ["README.md"], + sources: ["BridgeableTests.swift", bridgeFramework], + swiftSettings: sharedSwiftSettings +) let swiftBinPath = Context.environment["_"] ?? "/usr/bin/swift" let swiftBinURL = URL(fileURLWithPath: swiftBinPath) @@ -153,6 +172,7 @@ let package = Package( .library(name: "OpenSwiftUI", targets: ["OpenSwiftUI", "OpenSwiftUIExtension"]), // FIXME: This will block xcodebuild build(iOS CI) somehow // .library(name: "OpenSwiftUI_SPI", targets: ["OpenSwiftUI_SPI"]), + // .library(name: "OpenSwiftUIBridge", targets: ["OpenSwiftUIBridge"]) ], dependencies: [ .package(url: "https://github.com/apple/swift-numerics.git", from: "1.0.2"), @@ -179,12 +199,15 @@ let package = Package( .binaryTarget(name: "CoreServices", path: "PrivateFrameworks/CoreServices.xcframework"), openSwiftUICoreTarget, openSwiftUITarget, + openSwiftUIExtensionTarget, + openSwiftUIBridgeTarget, OpenSwiftUI_SPITestTarget, openSwiftUICoreTestTarget, openSwiftUITestTarget, openSwiftUICompatibilityTestTarget, + openSwiftUIBridgeTestTarget, ] ) @@ -234,6 +257,7 @@ if attributeGraphCondition { openSwiftUICoreTestTarget.addAGSettings() openSwiftUITestTarget.addAGSettings() openSwiftUICompatibilityTestTarget.addAGSettings() + openSwiftUIBridgeTestTarget.addAGSettings() } #if os(macOS) diff --git a/README.md b/README.md index 07635424f..4cdca3df6 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,15 @@ for various platforms: > So most of the core feature is only available on Apple platform built with > AttributeGraph varient. +## Products + +- OpenSwiftUI + - A SwiftUI source compatibility framework. +- OpenSwiftUIExtension + - Extensive API collections for OpenSwiftUI & SwiftUI. +- OpenSwiftUIBridge + - A bridge layer for migrating other DSL framework to OpenSwiftUI incrementally and mixing them freely. + ## License See LICENSE file - MIT diff --git a/Sources/OpenSwiftUIBridge/Bridgeable.swift b/Sources/OpenSwiftUIBridge/Bridgeable.swift new file mode 100644 index 000000000..94a6d4974 --- /dev/null +++ b/Sources/OpenSwiftUIBridge/Bridgeable.swift @@ -0,0 +1,16 @@ +// +// Bridgeable.swift +// OpenSwiftUIBridge + +import OpenSwiftUI + +/// A type that can be converted to and from its counterpart. +public protocol Bridgeable { + associatedtype Counterpart where Counterpart: Bridgeable, Counterpart.Counterpart == Self + + init(_ counterpart: Counterpart) +} + +extension Bridgeable { + public var counterpart: Self.Counterpart { .init(self) } +} diff --git a/Sources/OpenSwiftUIBridge/SwiftUI/SwiftUI.Color.swift b/Sources/OpenSwiftUIBridge/SwiftUI/SwiftUI.Color.swift new file mode 100644 index 000000000..6f8a10183 --- /dev/null +++ b/Sources/OpenSwiftUIBridge/SwiftUI/SwiftUI.Color.swift @@ -0,0 +1,78 @@ +// +// SwiftUI.Color.swift +// OpenSwiftUIBridge + +#if canImport(SwiftUI) +public import SwiftUI +public import OpenSwiftUI + +// MARK: Color + Bridgeable + +@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) +extension SwiftUI.Color: Bridgeable { + public typealias Counterpart = OpenSwiftUI.Color + + public init(_ counterpart: Counterpart) { + self.init(OpenSwiftUIColor2SwiftUIColorAdapter(base: counterpart)) + } +} + +@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) +extension OpenSwiftUI.Color: Bridgeable { + public typealias Counterpart = SwiftUI.Color + + public init(_ counterpart: Counterpart) { + self.init(SwiftUIColor2OpenSwiftUIColorAdapter(base: counterpart)) + } +} + +@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) +private struct OpenSwiftUIColor2SwiftUIColorAdapter: Hashable, SwiftUI.ShapeStyle { + private let base: OpenSwiftUI.Color + + init(base: OpenSwiftUI.Color) { + self.base = base + } + + public typealias Resolved = SwiftUI.Color.Resolved + + public func resolve(in environment: SwiftUI.EnvironmentValues) -> SwiftUI.Color.Resolved { + .init(base.resolve(in: .init(environment))) + } +} + +@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) +private struct SwiftUIColor2OpenSwiftUIColorAdapter: Hashable, OpenSwiftUI.ShapeStyle { + private let base: SwiftUI.Color + + init(base: SwiftUI.Color) { + self.base = base + } + + public typealias Resolved = OpenSwiftUI.Color.Resolved + + public func resolve(in environment: OpenSwiftUI.EnvironmentValues) -> OpenSwiftUI.Color.Resolved { + .init(base.resolve(in: .init(environment))) + } +} + +// MARK: - Color.Resolved + Bridgeable + +@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) +extension SwiftUI.Color.Resolved: Bridgeable { + public typealias Counterpart = OpenSwiftUI.Color.Resolved + + public init(_ counterpart: Counterpart) { + self.init(colorSpace: .sRGBLinear, red: counterpart.linearRed, green: counterpart.linearGreen, blue: counterpart.linearBlue) + } +} + +@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) +extension OpenSwiftUI.Color.Resolved: Bridgeable { + public typealias Counterpart = SwiftUI.Color.Resolved + + public init(_ counterpart: Counterpart) { + self.init(colorSpace: .sRGBLinear, red: counterpart.linearRed, green: counterpart.linearGreen, blue: counterpart.linearBlue) + } +} +#endif diff --git a/Sources/OpenSwiftUIBridge/SwiftUI/SwiftUI.EnvironmentValues.swift b/Sources/OpenSwiftUIBridge/SwiftUI/SwiftUI.EnvironmentValues.swift new file mode 100644 index 000000000..ed40b4169 --- /dev/null +++ b/Sources/OpenSwiftUIBridge/SwiftUI/SwiftUI.EnvironmentValues.swift @@ -0,0 +1,28 @@ +// +// SwiftUI.EnvironmentValues.swift +// OpenSwiftUIBridge + +#if canImport(SwiftUI) +public import SwiftUI +public import OpenSwiftUI + +// MARK: EnvironmentValues + Bridgeable + +extension SwiftUI.EnvironmentValues: Bridgeable { + public typealias Counterpart = OpenSwiftUI.EnvironmentValues + + public init(_ counterpart: Counterpart) { + // FIXME + self.init() + } +} + +extension OpenSwiftUI.EnvironmentValues: Bridgeable { + public typealias Counterpart = SwiftUI.EnvironmentValues + + public init(_ counterpart: Counterpart) { + // FIXME + self.init() + } +} +#endif diff --git a/Sources/OpenSwiftUIBridge/swift-cross-ui/.gitkeep b/Sources/OpenSwiftUIBridge/swift-cross-ui/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/Tests/OpenSwiftUIBridgeTests/BridgeableTests.swift b/Tests/OpenSwiftUIBridgeTests/BridgeableTests.swift new file mode 100644 index 000000000..029993ff6 --- /dev/null +++ b/Tests/OpenSwiftUIBridgeTests/BridgeableTests.swift @@ -0,0 +1,42 @@ +// +// BridgeableTests.swift +// OpenSwiftUIBridgeTests + +import Testing +import OpenSwiftUIBridge + +struct BridgeableTests { + @Test + func example() throws { + struct A1: Bridgeable, Equatable { + typealias Counterpart = A2 + var value: Int + + init(value: Int) { + self.value = value + } + + init(_ counterpart: A2) { + self.value = counterpart.value + } + } + + struct A2: Bridgeable, Equatable { + typealias Counterpart = A1 + var value: Int + + init(value: Int) { + self.value = value + } + + init(_ counterpart: A1) { + self.value = counterpart.value + } + } + let a1 = A1(value: 3) + let a2 = A2(value: 3) + + #expect(a1.counterpart == a2) + #expect(a2.counterpart == a1) + } +} diff --git a/Tests/OpenSwiftUIBridgeTests/README.md b/Tests/OpenSwiftUIBridgeTests/README.md new file mode 100644 index 000000000..e30b5022c --- /dev/null +++ b/Tests/OpenSwiftUIBridgeTests/README.md @@ -0,0 +1,7 @@ +## OpenSwiftUIBridgeTests + +Test API of OpenSwiftUIBridge + +```swift +import OpenSwiftUIBridge +``` diff --git a/Tests/OpenSwiftUIBridgeTests/SwiftUI/SwiftUI.ColorTests.swift b/Tests/OpenSwiftUIBridgeTests/SwiftUI/SwiftUI.ColorTests.swift new file mode 100644 index 000000000..74c7e0f23 --- /dev/null +++ b/Tests/OpenSwiftUIBridgeTests/SwiftUI/SwiftUI.ColorTests.swift @@ -0,0 +1,31 @@ +// +// SwiftUI.ColorTests.swift +// OpenSwiftUIBridgeTests + +#if canImport(SwiftUI) +import Testing +import SwiftUI +import OpenSwiftUI +import OpenSwiftUIBridge + +struct SwiftUI_ColorTests { + @Test + @available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) + func color() throws { + let swiftUIWhite = SwiftUI.Color.white + let openSwiftUIWhite = OpenSwiftUI.Color.white + #expect(swiftUIWhite.counterpart.resolve(in: .init()) == openSwiftUIWhite.resolve(in: .init())) + #expect(openSwiftUIWhite.counterpart.resolve(in: .init()) == swiftUIWhite.resolve(in: .init())) + } + + @Test + @available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) + func resolved() throws { + let swiftUIWhiteResolved = SwiftUI.Color.Resolved.init(red: 1, green: 1, blue: 1) + let openSwiftUIWhiteResolved = OpenSwiftUI.Color.Resolved.init(red: 1, green: 1, blue: 1) + + #expect(swiftUIWhiteResolved.counterpart == openSwiftUIWhiteResolved) + #expect(openSwiftUIWhiteResolved.counterpart == swiftUIWhiteResolved) + } +} +#endif diff --git a/Tests/OpenSwiftUIBridgeTests/SwiftUI/SwiftUI.EnvironmentValues.swift b/Tests/OpenSwiftUIBridgeTests/SwiftUI/SwiftUI.EnvironmentValues.swift new file mode 100644 index 000000000..7abb3a9e6 --- /dev/null +++ b/Tests/OpenSwiftUIBridgeTests/SwiftUI/SwiftUI.EnvironmentValues.swift @@ -0,0 +1,24 @@ +// +// SwiftUI.EnvironmentValues.swift +// OpenSwiftUIBridgeTests + +#if canImport(SwiftUI) +import Testing +import SwiftUI +import OpenSwiftUI +import OpenSwiftUIBridge + +struct SwiftUI_EnvironmentValues { + @Test + func example() throws { + var swiftUIEnviromentValues = SwiftUI.EnvironmentValues() + let openSwiftUIEnviromentValues = OpenSwiftUI.EnvironmentValues() + #expect(swiftUIEnviromentValues.colorScheme == .light) + #expect(openSwiftUIEnviromentValues.colorScheme == .light) + swiftUIEnviromentValues.colorScheme = .dark + withKnownIssue { + #expect(swiftUIEnviromentValues.counterpart.colorScheme == .dark) + } + } +} +#endif