From 3b1c7a731785b562f5deab1a9f8f37be58b7ec90 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 3 Nov 2024 15:17:49 +0800 Subject: [PATCH 01/12] Update ViewInputPredicate --- ...EnvironmentValues+IsVisionEnabledKey.swift | 19 +++ .../View/Input/ViewInputPredicate.swift | 134 ++++++++++++++++++ .../View/ViewInputBoolFlag.swift | 17 --- .../OpenSwiftUICore/View/ViewInputFlag.swift | 22 --- .../View/ViewInputPredicate.swift | 10 -- 5 files changed, 153 insertions(+), 49 deletions(-) create mode 100644 Sources/OpenSwiftUICore/Data/Environment/EnvironmentValues+IsVisionEnabledKey.swift create mode 100644 Sources/OpenSwiftUICore/View/Input/ViewInputPredicate.swift delete mode 100644 Sources/OpenSwiftUICore/View/ViewInputBoolFlag.swift delete mode 100644 Sources/OpenSwiftUICore/View/ViewInputFlag.swift delete mode 100644 Sources/OpenSwiftUICore/View/ViewInputPredicate.swift diff --git a/Sources/OpenSwiftUICore/Data/Environment/EnvironmentValues+IsVisionEnabledKey.swift b/Sources/OpenSwiftUICore/Data/Environment/EnvironmentValues+IsVisionEnabledKey.swift new file mode 100644 index 000000000..d67001c81 --- /dev/null +++ b/Sources/OpenSwiftUICore/Data/Environment/EnvironmentValues+IsVisionEnabledKey.swift @@ -0,0 +1,19 @@ +// +// EnvironmentValues+IsVisionEnabledKey.swift +// OpenSwiftUICore +// +// Audited for iOS 18.0 +// Status: Complete + +extension EnvironmentValues { + package var isVisionEnabled: Bool { + get { false } + set { self[IsVisionEnabledKey.self] = newValue } + } +} + +package struct IsVisionEnabledKey: EnvironmentKey { + package static let defaultValue: Bool = false + + package typealias Value = Bool +} diff --git a/Sources/OpenSwiftUICore/View/Input/ViewInputPredicate.swift b/Sources/OpenSwiftUICore/View/Input/ViewInputPredicate.swift new file mode 100644 index 000000000..4f8b2de8a --- /dev/null +++ b/Sources/OpenSwiftUICore/View/Input/ViewInputPredicate.swift @@ -0,0 +1,134 @@ +// +// ViewInputPredicate.swift +// OpenSwiftUICore +// +// Audited for iOS 18.0 +// Status: Complete + +// MARK: - ViewInputPredicate + +package protocol ViewInputPredicate { + static func evaluate(inputs: _GraphInputs) -> Bool +} + +// MARK: - ViewInputFlag + +package protocol ViewInputFlag: ViewInputPredicate, _GraphInputsModifier { + associatedtype Input: ViewInput where Input.Value: Equatable + static var value: Input.Value { get } + init() +} + +extension ViewInputFlag { + package static func evaluate(inputs: _GraphInputs) -> Bool { + inputs[Input.self] == value + } + + package static func _makeInputs(modifier _: _GraphValue, inputs: inout _GraphInputs) { + inputs[Input.self] = value + } +} + +extension ViewInput where Self: ViewInputFlag { + package typealias Input = Self +} + +// MARK: - ViewInputBoolFlag + +package protocol ViewInputBoolFlag: ViewInput, ViewInputFlag where Value == Bool {} + + +extension ViewInputBoolFlag { + @inlinable + package static var defaultValue: Bool { false } + + @inlinable + package static var value: Bool { true } +} + +// MARK: - ViewInputPredicate + Extension + +extension ViewInputPredicate { + package static prefix func ! (predicate: Self) -> some ViewInputPredicate { + InvertedViewInputPredicate() + } + + package static func || (lhs: Self, rhs: Other) -> some ViewInputPredicate where Other: ViewInputPredicate { + OrOperationViewInputPredicate() + } + + package typealias Inverted = InvertedViewInputPredicate +} + +// MARK: - InvertedViewInputPredicate + +package struct InvertedViewInputPredicate: ViewInputPredicate where Base: ViewInputPredicate { + package static func evaluate(inputs: _GraphInputs) -> Bool { + !Base.evaluate(inputs: inputs) + } +} + +extension InvertedViewInputPredicate where Base: Feature { + package static var isEnabled: Bool { + !Base.isEnabled + } +} +extension InvertedViewInputPredicate: ViewInputBoolFlag, ViewInputFlag, _GraphInputsModifier, ViewInput, GraphInput, PropertyKey where Base: ViewInputBoolFlag { + @inlinable + package static var value: Bool { false } + + @inlinable + package init() {} + + package typealias Value = Bool +} + +// MARK: - OrOperationViewInputPredicate + +package struct OrOperationViewInputPredicate: ViewInputPredicate where Left: ViewInputPredicate, Right: ViewInputPredicate { + package static func evaluate(inputs: _GraphInputs) -> Bool { + Left.evaluate(inputs: inputs) || Right.evaluate(inputs: inputs) + } + + @inlinable + package init() {} +} + +// MARK: - AndOperationViewInputPredicate + +package struct AndOperationViewInputPredicate: ViewInputPredicate where Left: ViewInputPredicate, Right: ViewInputPredicate { + package static func evaluate(inputs: _GraphInputs) -> Bool { + Left.evaluate(inputs: inputs) && Right.evaluate(inputs: inputs) + } + + @inlinable + package init() {} +} + +package struct TypesMatch: ViewInputPredicate { + package static func evaluate(inputs: _GraphInputs) -> Bool { + Left.self == Right.self + } + + @inlinable + package init() {} +} + +package struct IsVisionEnabledPredicate: ViewInputPredicate { + package static func evaluate(inputs: _GraphInputs) -> Bool { + false + } + + package init() {} +} + +extension _ViewInputs { + package var isVisionEnabled: Bool { + IsVisionEnabledPredicate.evaluate(inputs: base) + } +} +extension _ViewListInputs { + package var isVisionEnabled: Bool { + IsVisionEnabledPredicate.evaluate(inputs: base) + } +} diff --git a/Sources/OpenSwiftUICore/View/ViewInputBoolFlag.swift b/Sources/OpenSwiftUICore/View/ViewInputBoolFlag.swift deleted file mode 100644 index 386ca0769..000000000 --- a/Sources/OpenSwiftUICore/View/ViewInputBoolFlag.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// ViewInputBoolFlag.swift -// OpenSwiftUICore -// -// Audited for iOS 18.0 -// Status: WIP - -// FIXME -package protocol ViewInputBoolFlag: GraphInput /*ViewInput, ViewInputFlag where Value == Bool*/ {} - -extension ViewInputBoolFlag { - @inlinable - static var defaultValue: Bool { false } - - @inlinable - static var value: Bool { true } -} diff --git a/Sources/OpenSwiftUICore/View/ViewInputFlag.swift b/Sources/OpenSwiftUICore/View/ViewInputFlag.swift deleted file mode 100644 index a4a825c89..000000000 --- a/Sources/OpenSwiftUICore/View/ViewInputFlag.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// ViewInputFlag.swift -// OpenSwiftUI -// -// Audited for iOS 15.5 -// Status: Complete - -protocol ViewInputFlag: PrimitiveViewModifier, ViewInputPredicate, _GraphInputsModifier { - associatedtype Input: ViewInput where Input.Value: Equatable - static var value: Input.Value { get } - init() -} - -extension ViewInputFlag { - static func _makeInputs(modifier _: _GraphValue, inputs: inout _GraphInputs) { - inputs[Input.self] = value - } - - static func evaluate(inputs: _GraphInputs) -> Bool { - inputs[Input.self] == value - } -} diff --git a/Sources/OpenSwiftUICore/View/ViewInputPredicate.swift b/Sources/OpenSwiftUICore/View/ViewInputPredicate.swift deleted file mode 100644 index abae6d1dd..000000000 --- a/Sources/OpenSwiftUICore/View/ViewInputPredicate.swift +++ /dev/null @@ -1,10 +0,0 @@ -// -// ViewInputPredicate.swift -// OpenSwiftUI -// -// Audited for iOS 15.5 -// Status: Complete - -protocol ViewInputPredicate { - static func evaluate(inputs: _GraphInputs) -> Bool -} From 69e583b398da91f90e3e562f04e5edc86d881517 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 3 Nov 2024 17:26:40 +0800 Subject: [PATCH 02/12] Update InterfaceIdiom --- Package.swift | 2 +- .../View/InterfaceIdiom}/InterfaceIdiom.swift | 30 ++- .../View/InterfaceIdiom/InterfaceIdiom.swift | 239 ++++++++++++++++++ .../InterfaceIdiomPredicate.swift | 44 ++++ .../InterfaceIdiom/InterfaceIdiomTests.swift | 37 +++ .../View/InterfaceIdiomTests.swift | 51 ---- .../InterfaceIdiom/InterfaceIdiomTests.swift | 25 ++ 7 files changed, 374 insertions(+), 54 deletions(-) rename Sources/{OpenSwiftUICore/View => OpenSwiftUI/View/InterfaceIdiom}/InterfaceIdiom.swift (80%) create mode 100644 Sources/OpenSwiftUICore/View/InterfaceIdiom/InterfaceIdiom.swift create mode 100644 Sources/OpenSwiftUICore/View/InterfaceIdiom/InterfaceIdiomPredicate.swift create mode 100644 Tests/OpenSwiftUICoreTests/View/InterfaceIdiom/InterfaceIdiomTests.swift delete mode 100644 Tests/OpenSwiftUICoreTests/View/InterfaceIdiomTests.swift create mode 100644 Tests/OpenSwiftUITests/View/InterfaceIdiom/InterfaceIdiomTests.swift diff --git a/Package.swift b/Package.swift index 046b5bb9d..6bf2bd76d 100644 --- a/Package.swift +++ b/Package.swift @@ -23,7 +23,7 @@ let development = envEnable("OPENSWIFTUI_DEVELOPMENT", default: false) // Xcode use clang as linker which supports "-iframework" while SwiftPM use swiftc as linker which supports "-Fsystem" let systemFrameworkSearchFlag = isXcodeEnv ? "-iframework" : "-Fsystem" -let releaseVersion = Context.environment["OPENSWIFTUI_TARGET_RELEASE"].flatMap { Int($0) } ?? 2021 +let releaseVersion = Context.environment["OPENSWIFTUI_TARGET_RELEASE"].flatMap { Int($0) } ?? 2024 let platforms: [SupportedPlatform] = switch releaseVersion { case 2024: // iOS 18.0 [ diff --git a/Sources/OpenSwiftUICore/View/InterfaceIdiom.swift b/Sources/OpenSwiftUI/View/InterfaceIdiom/InterfaceIdiom.swift similarity index 80% rename from Sources/OpenSwiftUICore/View/InterfaceIdiom.swift rename to Sources/OpenSwiftUI/View/InterfaceIdiom/InterfaceIdiom.swift index 354f1bb3e..0c7049194 100644 --- a/Sources/OpenSwiftUICore/View/InterfaceIdiom.swift +++ b/Sources/OpenSwiftUI/View/InterfaceIdiom/InterfaceIdiom.swift @@ -2,9 +2,11 @@ // InterfaceIdiom.swift // OpenSwiftUI // -// Audited for iOS 15.5 +// Audited for iOS 15.0 // Status: Complete -// ID: 2FFD16F575FFD9B8AC17BCAE09549F2 +// ID: 2FFD16F575FFD9B8AC17BCAE09549F23 (SwiftUI) + +#if OPENSWIFTUI_RELEASE_2021 // MARK: InterfaceIdiomType @@ -112,3 +114,27 @@ extension UIUserInterfaceIdiom { } } #endif + +#elseif OPENSWIFTUI_RELEASE_2024 + +#if os(iOS) || os(tvOS) +import OpenSwiftUICore +import UIKit + +extension UIUserInterfaceIdiom { + var idiom: AnyInterfaceIdiom? { + switch rawValue { + case UIUserInterfaceIdiom.phone.rawValue: return AnyInterfaceIdiom(.phone) + case UIUserInterfaceIdiom.pad.rawValue: return AnyInterfaceIdiom(.pad) + case UIUserInterfaceIdiom.tv.rawValue: return AnyInterfaceIdiom(.tv) + case 4: return AnyInterfaceIdiom(.watch) // There is no UIUserInterfaceIdiom.watch exposed currently + case UIUserInterfaceIdiom.carPlay.rawValue: return AnyInterfaceIdiom(.carPlay) + case UIUserInterfaceIdiom.mac.rawValue: return AnyInterfaceIdiom(.mac) + case UIUserInterfaceIdiom.vision.rawValue: return AnyInterfaceIdiom(.vision) + default: return nil + } + } +} +#endif + +#endif diff --git a/Sources/OpenSwiftUICore/View/InterfaceIdiom/InterfaceIdiom.swift b/Sources/OpenSwiftUICore/View/InterfaceIdiom/InterfaceIdiom.swift new file mode 100644 index 000000000..bcd164911 --- /dev/null +++ b/Sources/OpenSwiftUICore/View/InterfaceIdiom/InterfaceIdiom.swift @@ -0,0 +1,239 @@ +// +// InterfaceIdiom.swift +// OpenSwiftUICore +// +// Audited for iOS 18.0 +// Status: Complete +// ID: 39057DDA72E946BD17E1F42CCA55F7F6 (SwiftUICore) + +#if OPENSWIFTUI_RELEASE_2024 + +// MARK: - InterfaceIdiom + +package protocol InterfaceIdiom { + static func accepts(_ type: I.Type) -> Bool where I: InterfaceIdiom + static var hashValue: InterfaceIdiomKind { get } +} + +// MARK: - InterfaceIdiomKind + +package enum InterfaceIdiomKind { + case carPlay + case clarity + case complication + case widget + case mac + case macCatalyst + case phone + case pad + case tv + case touchBar + case watch + case vision + case nokit +} + +// MARK: - InterfaceIdiom + Extensions + +extension InterfaceIdiom { + @inlinable + package static func accepts(_ type: I.Type) -> Bool where I: InterfaceIdiom { + self.self == type + } + + @inlinable + package static func accepts(_ idiom: I) -> Bool where I: InterfaceIdiom { + accepts(I.self) + } +} + +extension InterfaceIdiom where Self == CarPlayInterfaceIdiom { + @inlinable + package static var carPlay: CarPlayInterfaceIdiom { + CarPlayInterfaceIdiom() + } +} + +extension InterfaceIdiom where Self == ClarityUIInterfaceIdiom { + @inlinable + package static var clarityUI: ClarityUIInterfaceIdiom { + ClarityUIInterfaceIdiom() + } +} + +extension InterfaceIdiom where Self == ComplicationInterfaceIdiom { + @inlinable + package static var complication: ComplicationInterfaceIdiom { + ComplicationInterfaceIdiom() + } +} + +extension InterfaceIdiom where Self == WidgetInterfaceIdiom { + @inlinable + package static var widget: WidgetInterfaceIdiom { + WidgetInterfaceIdiom() + } +} + +extension InterfaceIdiom where Self == MacInterfaceIdiom { + @inlinable + package static var mac: MacInterfaceIdiom { + MacInterfaceIdiom() + } +} + +extension InterfaceIdiom where Self == MacCatalystInterfaceIdiom { + @inlinable + package static var macCatalyst: MacCatalystInterfaceIdiom { + MacCatalystInterfaceIdiom() + } +} + +extension InterfaceIdiom where Self == PhoneInterfaceIdiom { + @inlinable + package static var phone: PhoneInterfaceIdiom { + PhoneInterfaceIdiom() + } +} + +extension InterfaceIdiom where Self == PadInterfaceIdiom { + @inlinable + package static var pad: PadInterfaceIdiom { + PadInterfaceIdiom() + } +} + +extension InterfaceIdiom where Self == TVInterfaceIdiom { + @inlinable + package static var tv: TVInterfaceIdiom { + TVInterfaceIdiom() + } +} + +extension InterfaceIdiom where Self == TouchBarInterfaceIdiom { + @inlinable + package static var touchBar: TouchBarInterfaceIdiom { + TouchBarInterfaceIdiom() + } +} + +extension InterfaceIdiom where Self == WatchInterfaceIdiom { + @inlinable + package static var watch: WatchInterfaceIdiom { + WatchInterfaceIdiom() + } +} + +extension InterfaceIdiom where Self == VisionInterfaceIdiom { + @inlinable + package static var vision: VisionInterfaceIdiom { + VisionInterfaceIdiom() + } +} +extension InterfaceIdiom where Self == NoKitInterfaceIdiom { + @inlinable + package static var nokit: NoKitInterfaceIdiom { + NoKitInterfaceIdiom() + } +} + +package struct CarPlayInterfaceIdiom: InterfaceIdiom { + package static let hashValue: InterfaceIdiomKind = .carPlay +} + +package struct ClarityUIInterfaceIdiom: InterfaceIdiom { + package static let hashValue: InterfaceIdiomKind = .clarity +} + +package struct ComplicationInterfaceIdiom: InterfaceIdiom { + package static let hashValue: InterfaceIdiomKind = .complication + package static func accepts(_ type: I.Type) -> Bool where I: InterfaceIdiom { + type == WidgetInterfaceIdiom.self || type == ComplicationInterfaceIdiom.self + } +} + +package struct WidgetInterfaceIdiom: InterfaceIdiom { + package static let hashValue: InterfaceIdiomKind = .widget +} + +package struct MacInterfaceIdiom: InterfaceIdiom { + package static let hashValue: InterfaceIdiomKind = .mac +} + +package struct MacCatalystInterfaceIdiom: InterfaceIdiom { + package static let hashValue: InterfaceIdiomKind = .macCatalyst +} + +package struct PhoneInterfaceIdiom: InterfaceIdiom { + package static let hashValue: InterfaceIdiomKind = .phone +} + +package struct PadInterfaceIdiom: InterfaceIdiom { + package static let hashValue: InterfaceIdiomKind = .pad +} + +package struct TVInterfaceIdiom: InterfaceIdiom { + package static let hashValue: InterfaceIdiomKind = .tv +} + +package struct TouchBarInterfaceIdiom: InterfaceIdiom { + package static let hashValue: InterfaceIdiomKind = .touchBar +} + +package struct WatchInterfaceIdiom: InterfaceIdiom { + package static let hashValue: InterfaceIdiomKind = .watch +} + +package struct VisionInterfaceIdiom: InterfaceIdiom { + package static let hashValue: InterfaceIdiomKind = .vision +} + +package struct NoKitInterfaceIdiom: InterfaceIdiom { + package static let hashValue: InterfaceIdiomKind = .nokit +} + +package struct AnyInterfaceIdiom: Hashable { + private let base: any AnyInterfaceIdiomBox.Type + + package init(_: I) where I: InterfaceIdiom { + base = InterfaceIdiomBox.self + } + + package static func == (lhs: AnyInterfaceIdiom, rhs: AnyInterfaceIdiom) -> Bool { + lhs.base.isEqual(to: rhs.base) + } + + package func hash(into hasher: inout Hasher) { + base.hash(into: &hasher) + } + + package func accepts(_ type: I.Type) -> Bool where I: InterfaceIdiom { + base.accepts(type) + } + + package func accepts(_ idiom: I) -> Bool where I: InterfaceIdiom { + accepts(I.self) + } +} + +private protocol AnyInterfaceIdiomBox { + static func isEqual(to type: any AnyInterfaceIdiomBox.Type) -> Bool + static func accepts(_ type: I.Type) -> Bool where I: InterfaceIdiom + static func hash(into hasher: inout Hasher) +} + +private struct InterfaceIdiomBox: AnyInterfaceIdiomBox where Base: InterfaceIdiom { + static func isEqual(to type: any AnyInterfaceIdiomBox.Type) -> Bool { + type is InterfaceIdiomBox.Type + } + + static func accepts(_ type: I.Type) -> Bool where I : InterfaceIdiom { + Base.accepts(type) + } + + static func hash(into hasher: inout Hasher) { + hasher.combine(Base.hashValue) + } +} + +#endif diff --git a/Sources/OpenSwiftUICore/View/InterfaceIdiom/InterfaceIdiomPredicate.swift b/Sources/OpenSwiftUICore/View/InterfaceIdiom/InterfaceIdiomPredicate.swift new file mode 100644 index 000000000..07f250805 --- /dev/null +++ b/Sources/OpenSwiftUICore/View/InterfaceIdiom/InterfaceIdiomPredicate.swift @@ -0,0 +1,44 @@ +// +// InterfaceIdiomPredicate.swift +// OpenSwiftUICore +// +// Audited for iOS 18.0 +// Status: Complete + +package struct InterfaceIdiomPredicate: ViewInputPredicate where Idiom: InterfaceIdiom { + package init() {} + + package static func evaluate(inputs: _GraphInputs) -> Bool { + inputs.interfaceIdiom.accepts(Idiom.self) + } +} + +package struct InterfaceIdiomInput: ViewInput { + package static let defaultValue: AnyInterfaceIdiom? = nil +} + +extension _GraphInputs { + package var interfaceIdiom: AnyInterfaceIdiom { + self[InterfaceIdiomInput.self] ?? _GraphInputs.defaultInterfaceIdiom + } + + package static var defaultInterfaceIdiom: AnyInterfaceIdiom { + #if os(macOS) + if isAppKitBased() { + AnyInterfaceIdiom(.mac) + } else { + AnyInterfaceIdiom(.pad) + } + #elseif os(iOS) + AnyInterfaceIdiom(.phone) + #else + fatalError("TODO") + #endif + } +} + +extension AnyInterfaceIdiom { + package static func ~= (pattern: some InterfaceIdiom, value: AnyInterfaceIdiom) -> Bool { + value == AnyInterfaceIdiom(pattern) + } +} diff --git a/Tests/OpenSwiftUICoreTests/View/InterfaceIdiom/InterfaceIdiomTests.swift b/Tests/OpenSwiftUICoreTests/View/InterfaceIdiom/InterfaceIdiomTests.swift new file mode 100644 index 000000000..a2fb24a09 --- /dev/null +++ b/Tests/OpenSwiftUICoreTests/View/InterfaceIdiom/InterfaceIdiomTests.swift @@ -0,0 +1,37 @@ +// +// InterfaceIdiomTests.swift +// OpenSwiftUITests + +@testable import OpenSwiftUICore +import Testing + +struct InterfaceIdiomTests { + @Test + func idiomEqual() { + #expect(InterfaceIdiomKind.phone == .phone) + #expect(InterfaceIdiomKind.phone != .touchBar) + } + + @Test + func idiomAccepts() { + #expect(PhoneInterfaceIdiom.accepts(PhoneInterfaceIdiom.self) == true) + #expect(PhoneInterfaceIdiom.accepts(TouchBarInterfaceIdiom.self) == false) + + #expect(ComplicationInterfaceIdiom.accepts(WidgetInterfaceIdiom.self) == true) + #expect(ComplicationInterfaceIdiom.accepts(ComplicationInterfaceIdiom.self) == true) + + #expect(WidgetInterfaceIdiom.accepts(WidgetInterfaceIdiom.self) == true) + #expect(WidgetInterfaceIdiom.accepts(ComplicationInterfaceIdiom.self) == false) + } + + @Test + func patternMatching() { + #expect((PhoneInterfaceIdiom() ~= AnyInterfaceIdiom(.phone)) == true) + + #expect((ComplicationInterfaceIdiom() ~= AnyInterfaceIdiom(.widget)) == false) + #expect((ComplicationInterfaceIdiom() ~= AnyInterfaceIdiom(.complication)) == true) + + #expect((WidgetInterfaceIdiom() ~= AnyInterfaceIdiom(.widget)) == true) + #expect((WidgetInterfaceIdiom() ~= AnyInterfaceIdiom(.complication)) == false) + } +} diff --git a/Tests/OpenSwiftUICoreTests/View/InterfaceIdiomTests.swift b/Tests/OpenSwiftUICoreTests/View/InterfaceIdiomTests.swift deleted file mode 100644 index e8a7f2ca9..000000000 --- a/Tests/OpenSwiftUICoreTests/View/InterfaceIdiomTests.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// InterfaceIdiomTests.swift -// OpenSwiftUITests - -@testable import OpenSwiftUICore -import Testing -#if canImport(UIKit) -import UIKit -#endif - -struct InterfaceIdiomTests { - @Test - func idiomEqual() { - #expect(AnyInterfaceIdiomType.phone == .phone) - #expect(AnyInterfaceIdiomType.phone != .touchBar) - } - - @Test - func idiomAccepts() throws { - #expect(InterfaceIdiom.Phone.accepts(InterfaceIdiom.Phone.self) == true) - #expect(InterfaceIdiom.Phone.accepts(InterfaceIdiom.CarPlay.self) == false) - } - - #if os(iOS) || os(tvOS) - @Test - func interfaceIdiom() throws { - #expect(UIUserInterfaceIdiom.unspecified.idiom == nil) - #expect(UIUserInterfaceIdiom.phone.idiom == .phone) - #expect(UIUserInterfaceIdiom.pad.idiom == .pad) - #expect(UIUserInterfaceIdiom.tv.idiom == .tv) - #expect(UIUserInterfaceIdiom.carPlay.idiom == .carplay) - #expect(UIUserInterfaceIdiom(rawValue: 4)?.idiom == .watch) - if #available(iOS 14, tvOS 14, *) { - #expect(UIUserInterfaceIdiom.mac.idiom == .mac) - } - if #available(iOS 17, tvOS 17, *) { - #expect(UIUserInterfaceIdiom.vision.idiom == .vision) - } - } - - @Test - func interfaceIdiomInput() { - #expect(InterfaceIdiom.Input.defaultValue == nil) - let idiom = UIDevice.current.userInterfaceIdiom.idiom - InterfaceIdiom.Input.defaultValue = idiom - - #expect(InterfaceIdiom.Input.defaultValue == idiom) - #expect(InterfaceIdiom.Input.targetValue == .phone) - } - #endif -} diff --git a/Tests/OpenSwiftUITests/View/InterfaceIdiom/InterfaceIdiomTests.swift b/Tests/OpenSwiftUITests/View/InterfaceIdiom/InterfaceIdiomTests.swift new file mode 100644 index 000000000..26dd309e8 --- /dev/null +++ b/Tests/OpenSwiftUITests/View/InterfaceIdiom/InterfaceIdiomTests.swift @@ -0,0 +1,25 @@ +// +// InterfaceIdiomTests.swift +// OpenSwiftUITests + +@testable import OpenSwiftUI +import Testing +#if canImport(UIKit) +import UIKit +#endif + +struct InterfaceIdiomTests { + #if os(iOS) || os(tvOS) + @Test + func interfaceIdiom() throws { + #expect(UIUserInterfaceIdiom.unspecified.idiom == nil) + #expect(UIUserInterfaceIdiom.phone.idiom == AnyInterfaceIdiom(.phone)) + #expect(UIUserInterfaceIdiom.pad.idiom == AnyInterfaceIdiom(.pad)) + #expect(UIUserInterfaceIdiom.tv.idiom == AnyInterfaceIdiom(.tv)) + #expect(UIUserInterfaceIdiom.carPlay.idiom == AnyInterfaceIdiom(.carPlay)) + #expect(UIUserInterfaceIdiom(rawValue: 4)?.idiom == AnyInterfaceIdiom(.watch)) + #expect(UIUserInterfaceIdiom.carPlay.idiom == AnyInterfaceIdiom(.carPlay)) + #expect(UIUserInterfaceIdiom.vision.idiom == AnyInterfaceIdiom(.vision)) + } + #endif +} From c88614263a07fabc51137580c7a45053503d8ae0 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 3 Nov 2024 17:43:44 +0800 Subject: [PATCH 03/12] Update vision related implementation --- .../EnvironmentValues+IsVisionEnabledKey.swift | 8 +++++++- .../Graph/{GraphReusable.swift => GraphReuse.swift} | 6 +++--- .../OpenSwiftUICore/View/Input/ViewInputPredicate.swift | 4 ++++ Sources/OpenSwiftUICore/View/{ => Input}/ViewInputs.swift | 6 +++--- .../View/InterfaceIdiom/InterfaceIdiomPredicate.swift | 4 ++-- 5 files changed, 19 insertions(+), 9 deletions(-) rename Sources/OpenSwiftUICore/Graph/{GraphReusable.swift => GraphReuse.swift} (96%) rename Sources/OpenSwiftUICore/View/{ => Input}/ViewInputs.swift (98%) diff --git a/Sources/OpenSwiftUICore/Data/Environment/EnvironmentValues+IsVisionEnabledKey.swift b/Sources/OpenSwiftUICore/Data/Environment/EnvironmentValues+IsVisionEnabledKey.swift index d67001c81..76b26192c 100644 --- a/Sources/OpenSwiftUICore/Data/Environment/EnvironmentValues+IsVisionEnabledKey.swift +++ b/Sources/OpenSwiftUICore/Data/Environment/EnvironmentValues+IsVisionEnabledKey.swift @@ -7,7 +7,13 @@ extension EnvironmentValues { package var isVisionEnabled: Bool { - get { false } + get { + #if os(macOS) + false + #else + self[IsVisionEnabledKey.self] + #endif + } set { self[IsVisionEnabledKey.self] = newValue } } } diff --git a/Sources/OpenSwiftUICore/Graph/GraphReusable.swift b/Sources/OpenSwiftUICore/Graph/GraphReuse.swift similarity index 96% rename from Sources/OpenSwiftUICore/Graph/GraphReusable.swift rename to Sources/OpenSwiftUICore/Graph/GraphReuse.swift index 50921e911..d9c9ec0e1 100644 --- a/Sources/OpenSwiftUICore/Graph/GraphReusable.swift +++ b/Sources/OpenSwiftUICore/Graph/GraphReuse.swift @@ -1,10 +1,10 @@ // -// GraphReusable.swift +// GraphReuse.swift // OpenSwiftUICore // // Audited for iOS 18.0 // Status: Blocked by _GraphInputs -// ID: 3E2D3733C4CBF57EC1EA761D02CE8317 +// ID: 3E2D3733C4CBF57EC1EA761D02CE8317 (SwiftUICore) package import OpenGraphShims @@ -96,7 +96,7 @@ import Foundation import os.log #endif -private enum EnableGraphReuseLogging: UserDefaultKeyedFeature { +private struct EnableGraphReuseLogging: UserDefaultKeyedFeature { static var key: String { "org.OpenSwiftUIProject.OpenSwiftUI.GraphReuseLogging" } static var cachedValue: Bool? } diff --git a/Sources/OpenSwiftUICore/View/Input/ViewInputPredicate.swift b/Sources/OpenSwiftUICore/View/Input/ViewInputPredicate.swift index 4f8b2de8a..616d92acd 100644 --- a/Sources/OpenSwiftUICore/View/Input/ViewInputPredicate.swift +++ b/Sources/OpenSwiftUICore/View/Input/ViewInputPredicate.swift @@ -116,7 +116,11 @@ package struct TypesMatch: ViewInputPredicate { package struct IsVisionEnabledPredicate: ViewInputPredicate { package static func evaluate(inputs: _GraphInputs) -> Bool { + #if os(macOS) false + #else + inputs.interfaceIdiom.accepts(.vision) + #endif } package init() {} diff --git a/Sources/OpenSwiftUICore/View/ViewInputs.swift b/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift similarity index 98% rename from Sources/OpenSwiftUICore/View/ViewInputs.swift rename to Sources/OpenSwiftUICore/View/Input/ViewInputs.swift index 9f70f88d9..43c346c30 100644 --- a/Sources/OpenSwiftUICore/View/ViewInputs.swift +++ b/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift @@ -1,8 +1,8 @@ // // ViewInputs.swift -// OpenSwiftUI +// OpenSwiftUICore // -// Audited for iOS 15.5 +// Audited for iOS 18.0 // Status: WIP import OpenGraphShims @@ -16,7 +16,7 @@ package protocol ViewInput: GraphInput {} /// their node. Doesn't include the view itself, which is passed /// separately. public struct _ViewInputs { - private var base: _GraphInputs + package var base: _GraphInputs var preferences: PreferencesInputs var transform: Attribute var position: Attribute diff --git a/Sources/OpenSwiftUICore/View/InterfaceIdiom/InterfaceIdiomPredicate.swift b/Sources/OpenSwiftUICore/View/InterfaceIdiom/InterfaceIdiomPredicate.swift index 07f250805..f299b4c20 100644 --- a/Sources/OpenSwiftUICore/View/InterfaceIdiom/InterfaceIdiomPredicate.swift +++ b/Sources/OpenSwiftUICore/View/InterfaceIdiom/InterfaceIdiomPredicate.swift @@ -29,7 +29,7 @@ extension _GraphInputs { } else { AnyInterfaceIdiom(.pad) } - #elseif os(iOS) + #elseif os(iOS) || os(visionOS) AnyInterfaceIdiom(.phone) #else fatalError("TODO") @@ -38,7 +38,7 @@ extension _GraphInputs { } extension AnyInterfaceIdiom { - package static func ~= (pattern: some InterfaceIdiom, value: AnyInterfaceIdiom) -> Bool { + package static func ~= (pattern: some InterfaceIdiom, value: AnyInterfaceIdiom) -> Bool { value == AnyInterfaceIdiom(pattern) } } From 5e5212f05b4743439fbbfea951a08130003b02ef Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 3 Nov 2024 19:18:51 +0800 Subject: [PATCH 04/12] Update ViewInputs to 2024 --- .../Data/Environment/CachedEnvironment.swift | 18 +- .../Data/Preference/PreferencesInputs.swift | 2 +- .../OpenSwiftUICore/Graph/GraphInputs.swift | 23 +- .../Edge/SafeAreaInsets.swift | 2 +- .../internal/ViewOrigin.swift | 6 +- .../internal/ViewSize.swift | 2 +- .../Modifier/AppearanceActionModifier.swift | 2 +- .../View/Debug/ViewDebug.swift | 10 +- .../View/Graph/ViewGraph.swift | 10 +- .../ViewInputPredicate.swift | 0 .../View/Inputs/ViewInputs.swift | 297 ++++++++++++++++++ .../OpenSwiftUICore/View/ViewOutputs.swift | 2 +- 12 files changed, 331 insertions(+), 43 deletions(-) rename Sources/OpenSwiftUICore/View/{Input => Inputs}/ViewInputPredicate.swift (100%) create mode 100644 Sources/OpenSwiftUICore/View/Inputs/ViewInputs.swift diff --git a/Sources/OpenSwiftUICore/Data/Environment/CachedEnvironment.swift b/Sources/OpenSwiftUICore/Data/Environment/CachedEnvironment.swift index 6d11db538..66db15c49 100644 --- a/Sources/OpenSwiftUICore/Data/Environment/CachedEnvironment.swift +++ b/Sources/OpenSwiftUICore/Data/Environment/CachedEnvironment.swift @@ -57,13 +57,17 @@ package struct CachedEnvironment { #endif } -// func animatePosition(for inputs: _ViewInputs) -> Attribute { -// fatalError("TODO") -// } -// -// func animateSize(for inputs: _ViewInputs) -> Attribute { -// fatalError("TODO") -// } + func animatedPosition(for inputs: _ViewInputs) -> Attribute { + fatalError("TODO") + } + + func animatedSize(for inputs: _ViewInputs) -> Attribute { + fatalError("TODO") + } + + func animatedCGSize(for inputs: _ViewInputs) -> Attribute { + fatalError("TODO") + } // func resolvedForegroundStyle() {} } diff --git a/Sources/OpenSwiftUICore/Data/Preference/PreferencesInputs.swift b/Sources/OpenSwiftUICore/Data/Preference/PreferencesInputs.swift index 61d7c00a8..5fb1eacb5 100644 --- a/Sources/OpenSwiftUICore/Data/Preference/PreferencesInputs.swift +++ b/Sources/OpenSwiftUICore/Data/Preference/PreferencesInputs.swift @@ -7,7 +7,7 @@ import OpenGraphShims -struct PreferencesInputs { +package struct PreferencesInputs { private(set) var keys: PreferenceKeys var hostKeys: Attribute diff --git a/Sources/OpenSwiftUICore/Graph/GraphInputs.swift b/Sources/OpenSwiftUICore/Graph/GraphInputs.swift index 95cdf637e..168c881ca 100644 --- a/Sources/OpenSwiftUICore/Graph/GraphInputs.swift +++ b/Sources/OpenSwiftUICore/Graph/GraphInputs.swift @@ -137,7 +137,9 @@ public struct _GraphInputs { get { cachedEnvironment.wrappedValue.environment } set { cachedEnvironment.wrappedValue = CachedEnvironment(newValue) - changedDebugProperties.insert(.environment) + if !changedDebugProperties.contains(.environment) { + changedDebugProperties.formUnion(.environment) + } } } @@ -307,23 +309,4 @@ extension _GraphInputs { // return newInputs fatalError("TO BE REMOVED") } - - // MARK: - changedDebugProperties - - @inline(__always) - package func withEmptyChangedDebugPropertiesInputs(_ body: (_GraphInputs) -> R) -> R { -// var inputs = self -// inputs.changedDebugProperties = [] -// return body(inputs) - fatalError("TO BE REMOVED") - } - - // MARK: - options - - @inline(__always) - package var enableLayout: Bool { - false -// get { options.contains(.enableLayout) } - // TODO: setter - } } diff --git a/Sources/OpenSwiftUICore/Layout/LayoutAdjustments/Edge/SafeAreaInsets.swift b/Sources/OpenSwiftUICore/Layout/LayoutAdjustments/Edge/SafeAreaInsets.swift index 315ef24a6..17b754ecf 100644 --- a/Sources/OpenSwiftUICore/Layout/LayoutAdjustments/Edge/SafeAreaInsets.swift +++ b/Sources/OpenSwiftUICore/Layout/LayoutAdjustments/Edge/SafeAreaInsets.swift @@ -1,5 +1,5 @@ // TODO -struct SafeAreaInsets { +package struct SafeAreaInsets { var space: UniqueID var elements: [Element] var next: OptionalValue diff --git a/Sources/OpenSwiftUICore/Layout/LayoutFundamentals/internal/ViewOrigin.swift b/Sources/OpenSwiftUICore/Layout/LayoutFundamentals/internal/ViewOrigin.swift index ea1fe8f98..d8737a207 100644 --- a/Sources/OpenSwiftUICore/Layout/LayoutFundamentals/internal/ViewOrigin.swift +++ b/Sources/OpenSwiftUICore/Layout/LayoutFundamentals/internal/ViewOrigin.swift @@ -5,9 +5,9 @@ // Audited for iOS 15.5 // Status: Complete -import Foundation +package import Foundation -struct ViewOrigin: Equatable { +package struct ViewOrigin: Equatable { var value: CGPoint @inline(__always) @@ -15,7 +15,7 @@ struct ViewOrigin: Equatable { } extension ViewOrigin: Animatable { - var animatableData: AnimatablePair { + package var animatableData: AnimatablePair { get { .init(value.x, value.y) } set { value = .init(x: newValue.first, y: newValue.second) } } diff --git a/Sources/OpenSwiftUICore/Layout/LayoutFundamentals/internal/ViewSize.swift b/Sources/OpenSwiftUICore/Layout/LayoutFundamentals/internal/ViewSize.swift index 0df83c240..bd6d41989 100644 --- a/Sources/OpenSwiftUICore/Layout/LayoutFundamentals/internal/ViewSize.swift +++ b/Sources/OpenSwiftUICore/Layout/LayoutFundamentals/internal/ViewSize.swift @@ -7,7 +7,7 @@ import Foundation -struct ViewSize: Equatable { +package struct ViewSize: Equatable { var value: CGSize var _proposal: CGSize diff --git a/Sources/OpenSwiftUICore/Modifier/AppearanceActionModifier.swift b/Sources/OpenSwiftUICore/Modifier/AppearanceActionModifier.swift index 0a2779a25..ff027d511 100644 --- a/Sources/OpenSwiftUICore/Modifier/AppearanceActionModifier.swift +++ b/Sources/OpenSwiftUICore/Modifier/AppearanceActionModifier.swift @@ -27,7 +27,7 @@ public struct _AppearanceActionModifier: PrimitiveViewModifier { inputs: _ViewInputs, body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs ) -> _ViewOutputs { - let effect = AppearanceEffect(modifier: modifier.value, phase: inputs.phase) + let effect = AppearanceEffect(modifier: modifier.value, phase: inputs.viewPhase) let attribute = Attribute(effect) attribute.flags = [.active, .removable] return body(_Graph(), inputs) diff --git a/Sources/OpenSwiftUICore/View/Debug/ViewDebug.swift b/Sources/OpenSwiftUICore/View/Debug/ViewDebug.swift index 95cb263ca..8b947a763 100644 --- a/Sources/OpenSwiftUICore/View/Debug/ViewDebug.swift +++ b/Sources/OpenSwiftUICore/View/Debug/ViewDebug.swift @@ -148,9 +148,13 @@ extension _ViewDebug { ) -> _ViewOutputs { var inputs = inputs OGSubgraph.beginTreeElement(value: view.value, flags: 0) - var outputs = inputs.withEmptyChangedDebugPropertiesInputs { inputs in - body(view, inputs) - } + // FIXME +// var outputs = inputs.withEmptyChangedDebugPropertiesInputs { inputs in +// body(view, inputs) +// } + inputs.changedDebugProperties = [] + var outputs = body(view, inputs) + if OGSubgraph.shouldRecordTree { _ViewDebug.reallyWrap(&outputs, value: view, inputs: &inputs) OGSubgraph.endTreeElement(value: view.value) diff --git a/Sources/OpenSwiftUICore/View/Graph/ViewGraph.swift b/Sources/OpenSwiftUICore/View/Graph/ViewGraph.swift index 698e35825..6baa73cb7 100644 --- a/Sources/OpenSwiftUICore/View/Graph/ViewGraph.swift +++ b/Sources/OpenSwiftUICore/View/Graph/ViewGraph.swift @@ -199,14 +199,14 @@ package final class ViewGraph: GraphHost { #if canImport(Darwin) let outputs = self.data.globalSubgraph.apply { let graphInputs = graphInputs + var inputs = _ViewInputs( - base: graphInputs, - preferences: PreferencesInputs(hostKeys: data.$hostPreferenceKeys), - transform: $rootTransform, + graphInputs, position: $position, - containerPosition: $zeroPoint, size: $dimensions, - safeAreaInsets: OptionalAttribute() + transform: $rootTransform, + containerPosition: $zeroPoint, + hostPreferenceKeys: data.$hostPreferenceKeys ) if requestedOutputs.contains(.layout) { // FIXME diff --git a/Sources/OpenSwiftUICore/View/Input/ViewInputPredicate.swift b/Sources/OpenSwiftUICore/View/Inputs/ViewInputPredicate.swift similarity index 100% rename from Sources/OpenSwiftUICore/View/Input/ViewInputPredicate.swift rename to Sources/OpenSwiftUICore/View/Inputs/ViewInputPredicate.swift diff --git a/Sources/OpenSwiftUICore/View/Inputs/ViewInputs.swift b/Sources/OpenSwiftUICore/View/Inputs/ViewInputs.swift new file mode 100644 index 000000000..6f80d1517 --- /dev/null +++ b/Sources/OpenSwiftUICore/View/Inputs/ViewInputs.swift @@ -0,0 +1,297 @@ +// +// ViewInputs.swift +// OpenSwiftUICore +// +// Audited for RELEASE_2024 +// Status: Complete + +package import OpenGraphShims + +package typealias ViewPhase = _GraphInputs.Phase + +package protocol ViewInput: GraphInput {} + +/// The input (aka inherited) attributes supplied to each view. Most +/// view types will only actually wire a small number of these into +/// their node. Doesn't include the view itself, which is passed +/// separately. +public struct _ViewInputs { + package var base: _GraphInputs + + package var preferences: PreferencesInputs + + package var customInputs: PropertyList { + get { base.customInputs } + set { base.customInputs = newValue } + } + + package subscript(input: T.Type) -> T.Value where T : ViewInput { + get { base[input] } + set { base[input] = newValue } + } + + package subscript(input: T.Type) -> T.Value where T : ViewInput, T.Value : GraphReusable { + get { base[input] } + set { base[input] = newValue } + } + + package var time: Attribute