diff --git a/Package.swift b/Package.swift index fa9b92a6f..de62ff97f 100644 --- a/Package.swift +++ b/Package.swift @@ -92,6 +92,7 @@ let openSwiftUITarget = Target.target( name: "OpenSwiftUI", dependencies: [ "OpenSwiftUICore", + "COpenSwiftUI", .target(name: "CoreServices", condition: .when(platforms: [.iOS])), .product(name: "OpenGraphShims", package: "OpenGraph"), ], @@ -205,6 +206,16 @@ let package = Package( .define("_WASI_EMULATED_SIGNAL", .when(platforms: [.wasi])), ] ), + .target( + name: "COpenSwiftUI", + publicHeadersPath: ".", + cSettings: [ + .unsafeFlags(["-I", includePath], .when(platforms: .nonDarwinPlatforms)), + .define("__COREFOUNDATION_FORSWIFTFOUNDATIONONLY__", to: "1", .when(platforms: .nonDarwinPlatforms)), + .define("_WASI_EMULATED_SIGNAL", .when(platforms: [.wasi])), + .headerSearchPath("../OpenSwiftUI_SPI"), + ] + ), .binaryTarget(name: "CoreServices", path: "PrivateFrameworks/CoreServices.xcframework"), openSwiftUICoreTarget, openSwiftUITarget, diff --git a/Sources/COpenSwiftUI/UIKit/OpenSwiftUI+UIColor.h b/Sources/COpenSwiftUI/UIKit/OpenSwiftUI+UIColor.h new file mode 100644 index 000000000..249771dae --- /dev/null +++ b/Sources/COpenSwiftUI/UIKit/OpenSwiftUI+UIColor.h @@ -0,0 +1,33 @@ +// +// OpenSwiftUI+UIColor.h +// COpenSwiftUI +// +// Audited for iOS 18.0 +// Status: Complete + +#ifndef OpenSwiftUI_UIColor_h +#define OpenSwiftUI_UIColor_h + +#include "OpenSwiftUIBase.h" + +#if OPENSWIFTUI_TARGET_OS_IOS + +#import + +OPENSWIFTUI_ASSUME_NONNULL_BEGIN + +OPENSWIFTUI_EXPORT +BOOL _UIColorDependsOnTraitCollection(UIColor *color); + +@interface UIColor (OpenSwiftUI) + +// Workaround Swift initializer limitation +- (instancetype)initWithColor__openSwiftUI__:(UIColor *)color; + +@end + +OPENSWIFTUI_ASSUME_NONNULL_END + +#endif + +#endif /* OpenSwiftUI_UIColor_h */ diff --git a/Sources/COpenSwiftUI/UIKit/OpenSwiftUI+UIColor.m b/Sources/COpenSwiftUI/UIKit/OpenSwiftUI+UIColor.m new file mode 100644 index 000000000..adb3c3c52 --- /dev/null +++ b/Sources/COpenSwiftUI/UIKit/OpenSwiftUI+UIColor.m @@ -0,0 +1,31 @@ +// +// OpenSwiftUI+UIColor.m +// COpenSwiftUI +// +// Audited for iOS 18.0 +// Status: Complete + +#import "OpenSwiftUI+UIColor.h" + +#if OPENSWIFTUI_TARGET_OS_IOS + +BOOL _UIColorDependsOnTraitCollection(UIColor *color) { + static IMP UIColor_imp = nil; + static dispatch_once_t once; + + SEL selector = @selector(resolvedColorWithTraitCollection:); + dispatch_once(&once, ^{ + UIColor_imp = [UIColor instanceMethodForSelector:selector]; + }); + return [color methodForSelector:selector] != UIColor_imp; +} + +@implementation UIColor (OpenSwiftUI) + +- (instancetype)initWithColor__openSwiftUI__:(UIColor *)color { + self = color; +} + +@end + +#endif diff --git a/Sources/OpenSwiftUI/EventHandling/Focus/FocusedValueKey.swift b/Sources/OpenSwiftUI/EventHandling/Focus/FocusedValueKey.swift index 7b055715a..34916f197 100644 --- a/Sources/OpenSwiftUI/EventHandling/Focus/FocusedValueKey.swift +++ b/Sources/OpenSwiftUI/EventHandling/Focus/FocusedValueKey.swift @@ -5,6 +5,44 @@ // Audited for iOS 15.5 // Status: Complete +import OpenSwiftUICore + +/// A protocol for identifier types used when publishing and observing focused +/// values. +/// +/// Unlike ``EnvironmentKey``, `FocusedValueKey` has no default value +/// requirement, because the default value for a key is always `nil`. public protocol FocusedValueKey { associatedtype Value } + +public struct FocusedValues { + struct StorageOptions { + let rawValue: UInt8 + } + + var plist: PropertyList + var storageOptions: StorageOptions + var seed: VersionSeed + + @usableFromInline + internal init() { + plist = PropertyList() + storageOptions = StorageOptions(rawValue: 0) + seed = .empty + } + + /// Reads and writes values associated with a given focused value key. + public subscript(key: Key.Type) -> Key.Value? where Key: FocusedValueKey { + preconditionFailure("TODO") + } +} + +@available(*, unavailable) +extension FocusedValues: Sendable {} + +extension FocusedValues: Equatable { + public static func == (lhs: FocusedValues, rhs: FocusedValues) -> Bool { + lhs.seed.matches(rhs.seed) + } +} diff --git a/Sources/OpenSwiftUI/Integration/Graphic/UIKit/OpenSwiftUI+UIColor.swift b/Sources/OpenSwiftUI/Integration/Graphic/UIKit/OpenSwiftUI+UIColor.swift new file mode 100644 index 000000000..91792db06 --- /dev/null +++ b/Sources/OpenSwiftUI/Integration/Graphic/UIKit/OpenSwiftUI+UIColor.swift @@ -0,0 +1,175 @@ +// +// OpenSwiftUI+UIKit.swift +// OpenSwiftUI +// +// Audited for iOS 18.0 +// Status: WIP +// ID: 6DC24D5146AF4B80347A1025025F68EE (SwiftUI?) + +#if canImport(UIKit) + +public import OpenSwiftUICore +public import UIKit +import COpenSwiftUI + +// MARK: - Color + UIColor + +@available(*, deprecated, message: "Use Color(uiColor:) when converting a UIColor, or create a standard Color directly") +@available(macOS, unavailable) +extension Color { + /// Creates a color from a UIKit color. + /// + /// Use this method to create a SwiftUI color from a + /// [UIColor](https://developer.apple.com/documentation/UIKit/UIColor) instance. + /// The new color preserves the adaptability of the original. + /// For example, you can create a rectangle using + /// [link](https://developer.apple.com/documentation/uikit/uicolor/3173132-link) + /// to see how the shade adjusts to match the user's system settings: + /// + /// struct Box: View { + /// var body: some View { + /// Color(UIColor.link) + /// .frame(width: 200, height: 100) + /// } + /// } + /// + /// The `Box` view defined above automatically changes its + /// appearance when the user turns on Dark Mode. With the light and dark + /// appearances placed side by side, you can see the subtle difference + /// in shades: + /// + /// ![A side by side comparison of light and dark appearance screenshots of + /// rectangles rendered with the link color. The light variant appears on + /// the left, and the dark variant on the right.](Color-init-3) + /// + /// > Note: Use this initializer only if you need to convert an existing + /// [UIColor](https://developer.apple.com/documentation/UIKit/UIColor) to a + /// OpenSwiftUI color. Otherwise, create an OpenSwiftUI ``Color`` using an + /// initializer like ``init(_:red:green:blue:opacity:)``, or use a system + /// color like ``ShapeStyle/blue``. + /// + /// - Parameter color: A + /// [UIColor](https://developer.apple.com/documentation/UIKit/UIColor) instance + /// from which to create a color. + @_disfavoredOverload + public init(_ color: UIColor) { + self.init(uiColor: color) + } +} + +@available(macOS, unavailable) +extension Color { + /// Creates a color from a UIKit color. + /// + /// Use this method to create a SwiftUI color from a + /// [UIColor](https://developer.apple.com/documentation/UIKit/UIColor) instance. + /// The new color preserves the adaptability of the original. + /// For example, you can create a rectangle using + /// [link](https://developer.apple.com/documentation/uikit/uicolor/3173132-link) + /// to see how the shade adjusts to match the user's system settings: + /// + /// struct Box: View { + /// var body: some View { + /// Color(UIColor.link) + /// .frame(width: 200, height: 100) + /// } + /// } + /// + /// The `Box` view defined above automatically changes its + /// appearance when the user turns on Dark Mode. With the light and dark + /// appearances placed side by side, you can see the subtle difference + /// in shades: + /// + /// ![A side by side comparison of light and dark appearance screenshots of + /// rectangles rendered with the link color. The light variant appears on + /// the left, and the dark variant on the right.](Color-init-3) + /// + /// > Note: Use this initializer only if you need to convert an existing + /// [UIColor](https://developer.apple.com/documentation/UIKit/UIColor) to a + /// OpenSwiftUI color. Otherwise, create an OpenSwiftUI ``Color`` using an + /// initializer like ``init(_:red:green:blue:opacity:)``, or use a system + /// color like ``ShapeStyle/blue``. + /// + /// - Parameter color: A + /// [UIColor](https://developer.apple.com/documentation/UIKit/UIColor) instance + /// from which to create a color. + public init(uiColor: UIColor) { + self.init(provider: uiColor) + } +} + +extension UIColor: ColorProvider { + private static var dynamicColorCache: NSMapTable = NSMapTable.strongToWeakObjects() + + @available(macOS, unavailable) + convenience public init(_ color: Color) { + if let cgColor = color.provider.staticColor { + self.init(cgColor: cgColor) + } else { + let objCColor = ObjcColor(color) + let cache = Self.dynamicColorCache + if let color = cache.object(forKey: objCColor) { + self.init(color__openSwiftUI__: color) + } else { + let value: UIColor + if let kitColor = color.provider.kitColor { + value = kitColor as! UIColor + } else { + value = UIColor { trait in + // TODO: trait + let resolved = Color.Resolved.clear + return resolved.kitColor as! UIColor + } + } + self.init(color__openSwiftUI__: value) + cache.setObject(value, forKey: objCColor) + } + } + } + + package func resolve(in environment: EnvironmentValues) -> Color.Resolved { + if _UIColorDependsOnTraitCollection(self) { + let trait = UITraitCollection.current.byOverriding(with: environment, viewPhase: .init(), focusedValues: .init()) + let color = resolvedColor(with: trait) + return Color.Resolved(platformColor: color) ?? .clear + } else { + return Color.Resolved(cgColor) + } + } + + package var staticColor: CGColor? { + if _UIColorDependsOnTraitCollection(self) { + nil + } else { + cgColor + } + } +} + +public extension ColorScheme { + /// Creates a color scheme from its user interface style equivalent. + @available(macOS, unavailable) + @available(watchOS, unavailable) + init?(_ uiUserInterfaceStyle: UIUserInterfaceStyle) { + switch uiUserInterfaceStyle { + case .unspecified: return nil + case .light: self = .light + case .dark: self = .dark + @unknown default: return nil + } + } +} + +public extension UIUserInterfaceStyle { + /// Creates a user interface style from its ColorScheme equivalent. + @available(macOS, unavailable) + @available(watchOS, unavailable) + init(_ colorScheme: ColorScheme?) { + switch colorScheme { + case .light: self = .light + case .dark: self = .dark + case nil: self = .unspecified + } + } +} +#endif diff --git a/Sources/OpenSwiftUI/Integration/Graphic/UIKit/OpenSwiftUI+UITraitCollection.swift b/Sources/OpenSwiftUI/Integration/Graphic/UIKit/OpenSwiftUI+UITraitCollection.swift new file mode 100644 index 000000000..efbdb4a23 --- /dev/null +++ b/Sources/OpenSwiftUI/Integration/Graphic/UIKit/OpenSwiftUI+UITraitCollection.swift @@ -0,0 +1,21 @@ +// +// OpenSwiftUI+UITraitCollection.swift +// OpenSwiftUI +// +// Audited for iOS 18.0 +// Status: WIP +// ID: 05A2BB2D44F4D559B7E508DC5B95FFB (OpenSwiftUI?) + +#if canImport(UIKit) + +import OpenSwiftUICore +import UIKit + +extension UITraitCollection { + func byOverriding(with: EnvironmentValues, viewPhase: _GraphInputs.Phase, focusedValues: FocusedValues) -> UITraitCollection { + // TODO + self + } +} + +#endif diff --git a/Sources/OpenSwiftUICore/Graphic/Color/Color.swift b/Sources/OpenSwiftUICore/Graphic/Color/Color.swift index 205efe2a4..f0b19266e 100644 --- a/Sources/OpenSwiftUICore/Graphic/Color/Color.swift +++ b/Sources/OpenSwiftUICore/Graphic/Color/Color.swift @@ -101,7 +101,7 @@ public struct Color: Hashable, CustomStringConvertible, Sendable { /// /// You can get a /// [CGColor](https://developer.apple.com/documentation/CoreGraphics/CGColor) - /// instance from a constant SwiftUI color. This includes colors you create + /// instance from a constant OpenSwiftUI color. This includes colors you create /// from a Core Graphics color, from RGB or HSB components, or from constant /// UIKit and AppKit colors. ///