From 783b8c4a997213454ef3e5114ed572019920d298 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 18 May 2025 23:08:50 +0800 Subject: [PATCH 1/6] Update OG dependency --- Package.resolved | 4 ++-- .../OpenGraph/Attribute/AsyncAttribute.swift | 6 ------ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/Package.resolved b/Package.resolved index 5d142978..b1d8f8d0 100644 --- a/Package.resolved +++ b/Package.resolved @@ -7,7 +7,7 @@ "location" : "https://github.com/OpenSwiftUIProject/DarwinPrivateFrameworks.git", "state" : { "branch" : "main", - "revision" : "2ba5179547b9cefddfd304bbda6d0d0704946fe3" + "revision" : "75edcc68e064a7f4735074626c5de5f53cf60bf1" } }, { @@ -25,7 +25,7 @@ "location" : "https://github.com/OpenSwiftUIProject/OpenGraph", "state" : { "branch" : "main", - "revision" : "732ce4507120fbee7bc43ec0c8407f4c3d431811" + "revision" : "938fe2e9cbd2402c29c333de77038c1439e7cafc" } }, { diff --git a/Sources/OpenSwiftUICore/OpenGraph/Attribute/AsyncAttribute.swift b/Sources/OpenSwiftUICore/OpenGraph/Attribute/AsyncAttribute.swift index b916d292..46d73c5e 100644 --- a/Sources/OpenSwiftUICore/OpenGraph/Attribute/AsyncAttribute.swift +++ b/Sources/OpenSwiftUICore/OpenGraph/Attribute/AsyncAttribute.swift @@ -27,9 +27,3 @@ extension Attribute { } } } - -// FIXME: Add OGChangedValueFlagsRequiresMainThread in OpenGraph - -extension OGChangedValueFlags { - static let requiresMainThread = Self(rawValue: 2) -} From 77cccbc1f9ca53de8d5037f1276baa914edfdec3 Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 19 May 2025 00:45:35 +0800 Subject: [PATCH 2/6] Add LayoutProxy implementation --- .../OpenSwiftUICore/Layout/LayoutProxy.swift | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 Sources/OpenSwiftUICore/Layout/LayoutProxy.swift diff --git a/Sources/OpenSwiftUICore/Layout/LayoutProxy.swift b/Sources/OpenSwiftUICore/Layout/LayoutProxy.swift new file mode 100644 index 00000000..36405ff2 --- /dev/null +++ b/Sources/OpenSwiftUICore/Layout/LayoutProxy.swift @@ -0,0 +1,147 @@ +// +// LayoutProxy.swift +// OpenSwiftUICore +// +// Audited for iOS 18.0 +// Status: Complete + +package import OpenGraphShims + +package struct LayoutProxyAttributes: Equatable { + @OptionalAttribute + var layoutComputer: LayoutComputer? + + @OptionalAttribute + var traitList: (any ViewList)? + + package init(layoutComputer: OptionalAttribute, traitsList: OptionalAttribute) { + _layoutComputer = layoutComputer + _traitList = traitsList + } + + package init(traitsList: OptionalAttribute) { + _layoutComputer = OptionalAttribute() + _traitList = traitsList + } + + package init(layoutComputer: OptionalAttribute) { + _layoutComputer = layoutComputer + _traitList = OptionalAttribute() + } + + package init() { + _layoutComputer = OptionalAttribute() + _traitList = OptionalAttribute() + } + + package var isEmpty: Bool { + $layoutComputer == nil && $traitList == nil + } +} + +package struct LayoutProxy: Equatable { + var context: AnyRuleContext + + var attributes: LayoutProxyAttributes + + package init(context: AnyRuleContext, attributes: LayoutProxyAttributes) { + self.context = context + self.attributes = attributes + } + + package init(context: AnyRuleContext, layoutComputer: Attribute?) { + self.context = context + self.attributes = LayoutProxyAttributes(layoutComputer: .init(layoutComputer)) + } + + package var layoutComputer: LayoutComputer { + guard let layoutComputer = attributes.$layoutComputer else { + return .defaultValue + } + return context[layoutComputer] + } + + package var traits: ViewTraitCollection? { + guard let traitList = attributes.$traitList else { + return nil + } + return context[traitList].traits + } + + package subscript(key: K.Type) -> K.Value where K: _ViewTraitKey { + traits.map { $0[key] } ?? K.defaultValue + } + + package func spacing() -> Spacing { + layoutComputer.spacing() + } + + package func idealSize() -> CGSize { + size(in: .unspecified) + } + + package func size(in proposedSize: _ProposedSize) -> CGSize { + layoutComputer.sizeThatFits(proposedSize) + } + + package func lengthThatFits(_ proposal: _ProposedSize, in direction: Axis) -> CGFloat { + layoutComputer.lengthThatFits(proposal, in: direction) + } + + package func dimensions(in proposedSize: _ProposedSize) -> ViewDimensions { + let computer = layoutComputer + return ViewDimensions( + guideComputer: computer, + size: computer.sizeThatFits(proposedSize), + proposal: _ProposedSize( + width: proposedSize.width ?? .nan, + height: proposedSize.height ?? .nan + ) + ) + } + + package func finallyPlaced(at p: _Placement, in parentSize: CGSize, layoutDirection: LayoutDirection) -> ViewGeometry { + let dimensions = dimensions(in: p.proposedSize_) + var geometry = ViewGeometry( + placement: p, + dimensions: dimensions + ) + geometry.finalizeLayoutDirection(layoutDirection, parentSize: parentSize) + return geometry + } + + package func explicitAlignment(_ k: AlignmentKey, at mySize: ViewSize) -> CGFloat? { + layoutComputer.explicitAlignment(k, at: mySize) + } + + package var layoutPriority: Double { + layoutComputer.layoutPriority() + } + + package var ignoresAutomaticPadding: Bool { + layoutComputer.ignoresAutomaticPadding() + } + + package var requiresSpacingProjection: Bool { + layoutComputer.requiresSpacingProjection() + } +} + +package struct LayoutProxyCollection: RandomAccessCollection { + var context: AnyRuleContext + + var attributes: [LayoutProxyAttributes] + + package init(context: AnyRuleContext, attributes: [LayoutProxyAttributes]) { + self.context = context + self.attributes = attributes + } + + package var startIndex: Int { .zero } + + package var endIndex: Int { attributes.endIndex } + + package subscript(index: Int) -> LayoutProxy { + LayoutProxy(context: context, attributes: attributes[index]) + } +} From 4cc4035e2a17b05384c4f4cb11bba7926f444290 Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 19 May 2025 00:48:58 +0800 Subject: [PATCH 3/6] Update documentation for LayoutProxy --- .../OpenSwiftUICore/Layout/LayoutProxy.swift | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/Sources/OpenSwiftUICore/Layout/LayoutProxy.swift b/Sources/OpenSwiftUICore/Layout/LayoutProxy.swift index 36405ff2..ccd0f507 100644 --- a/Sources/OpenSwiftUICore/Layout/LayoutProxy.swift +++ b/Sources/OpenSwiftUICore/Layout/LayoutProxy.swift @@ -7,53 +7,94 @@ package import OpenGraphShims +/// A collection of attributes that can be applied to layout calculations. +/// +/// `LayoutProxyAttributes` stores optional references to a layout computer and a view trait list, +/// which are used to compute layouts and access view traits during layout operations. package struct LayoutProxyAttributes: Equatable { + /// The layout computer used to calculate sizes and positions. @OptionalAttribute var layoutComputer: LayoutComputer? + /// The list of view traits that affect layout behavior. @OptionalAttribute var traitList: (any ViewList)? + /// Creates layout proxy attributes with the specified layout computer and traits list. + /// + /// - Parameters: + /// - layoutComputer: The optional attribute containing a layout computer. + /// - traitsList: The optional attribute containing view traits. package init(layoutComputer: OptionalAttribute, traitsList: OptionalAttribute) { _layoutComputer = layoutComputer _traitList = traitsList } + /// Creates layout proxy attributes with only traits. + /// + /// - Parameter traitsList: The optional attribute containing view traits. package init(traitsList: OptionalAttribute) { _layoutComputer = OptionalAttribute() _traitList = traitsList } + /// Creates layout proxy attributes with only a layout computer. + /// + /// - Parameter layoutComputer: The optional attribute containing a layout computer. package init(layoutComputer: OptionalAttribute) { _layoutComputer = layoutComputer _traitList = OptionalAttribute() } + /// Creates empty layout proxy attributes with no layout computer or traits. package init() { _layoutComputer = OptionalAttribute() _traitList = OptionalAttribute() } + /// Determines if this collection of attributes is empty. + /// + /// Returns `true` if neither the layout computer nor the trait list is set. package var isEmpty: Bool { $layoutComputer == nil && $traitList == nil } } +/// A proxy object used to perform layout calculations for views. +/// +/// `LayoutProxy` provides an interface to compute sizes, dimensions, and positions +/// of views during the layout process. It combines layout computers and view traits +/// to determine the final layout characteristics of a view. package struct LayoutProxy: Equatable { + /// The rule context used to resolve attribute values. var context: AnyRuleContext + /// The attributes that define the layout behavior. var attributes: LayoutProxyAttributes + /// Creates a layout proxy with the specified context and attributes. + /// + /// - Parameters: + /// - context: The rule context used to resolve attribute values. + /// - attributes: The attributes that define the layout behavior. package init(context: AnyRuleContext, attributes: LayoutProxyAttributes) { self.context = context self.attributes = attributes } + /// Creates a layout proxy with the specified context and layout computer. + /// + /// - Parameters: + /// - context: The rule context used to resolve attribute values. + /// - layoutComputer: The optional layout computer to use for calculations. package init(context: AnyRuleContext, layoutComputer: Attribute?) { self.context = context self.attributes = LayoutProxyAttributes(layoutComputer: .init(layoutComputer)) } + /// The layout computer to use for layout calculations. + /// + /// If no layout computer is explicitly provided, returns the default layout computer. package var layoutComputer: LayoutComputer { guard let layoutComputer = attributes.$layoutComputer else { return .defaultValue @@ -61,6 +102,9 @@ package struct LayoutProxy: Equatable { return context[layoutComputer] } + /// The collection of view traits associated with this layout proxy. + /// + /// Returns `nil` if no trait list is available. package var traits: ViewTraitCollection? { guard let traitList = attributes.$traitList else { return nil @@ -68,26 +112,50 @@ package struct LayoutProxy: Equatable { return context[traitList].traits } + /// Accesses a specific trait value by its key type. + /// + /// - Parameter key: The trait key type to look up. + /// - Returns: The value for the specified trait key, or the default value if the trait is not present. package subscript(key: K.Type) -> K.Value where K: _ViewTraitKey { traits.map { $0[key] } ?? K.defaultValue } + /// Returns the spacing configuration for this layout. + /// + /// - Returns: The spacing configuration derived from the layout computer. package func spacing() -> Spacing { layoutComputer.spacing() } + /// Calculates the ideal size for the view without any specific constraints. + /// + /// - Returns: The ideal size as determined by the layout computer. package func idealSize() -> CGSize { size(in: .unspecified) } + /// Calculates the size that fits within the given proposed size. + /// + /// - Parameter proposedSize: The size constraints to consider. + /// - Returns: The calculated size that fits within the constraints. package func size(in proposedSize: _ProposedSize) -> CGSize { layoutComputer.sizeThatFits(proposedSize) } + /// Calculates the length that fits within the given proposal along a specific axis. + /// + /// - Parameters: + /// - proposal: The size constraints to consider. + /// - direction: The axis along which to calculate the length. + /// - Returns: The calculated length that fits within the constraints. package func lengthThatFits(_ proposal: _ProposedSize, in direction: Axis) -> CGFloat { layoutComputer.lengthThatFits(proposal, in: direction) } + /// Calculates the view dimensions within the given proposed size. + /// + /// - Parameter proposedSize: The size constraints to consider. + /// - Returns: The calculated dimensions including size and alignment guides. package func dimensions(in proposedSize: _ProposedSize) -> ViewDimensions { let computer = layoutComputer return ViewDimensions( @@ -100,6 +168,13 @@ package struct LayoutProxy: Equatable { ) } + /// Calculates the final geometry of a view at a specific placement within a parent. + /// + /// - Parameters: + /// - p: The placement defining position and proposed size. + /// - parentSize: The size of the parent container. + /// - layoutDirection: The layout direction (left-to-right or right-to-left). + /// - Returns: The final view geometry including position and dimensions. package func finallyPlaced(at p: _Placement, in parentSize: CGSize, layoutDirection: LayoutDirection) -> ViewGeometry { let dimensions = dimensions(in: p.proposedSize_) var geometry = ViewGeometry( @@ -110,28 +185,50 @@ package struct LayoutProxy: Equatable { return geometry } + /// Returns the explicit alignment for a specific alignment key at the given size. + /// + /// - Parameters: + /// - k: The alignment key to measure. + /// - mySize: The size at which to measure the alignment. + /// - Returns: The explicit alignment position, or `nil` if not explicitly defined. package func explicitAlignment(_ k: AlignmentKey, at mySize: ViewSize) -> CGFloat? { layoutComputer.explicitAlignment(k, at: mySize) } + /// The layout priority of the view, which influences layout decisions. + /// + /// Higher values indicate higher priority during layout calculations. package var layoutPriority: Double { layoutComputer.layoutPriority() } + /// Indicates whether the view ignores automatic padding applied by container views. package var ignoresAutomaticPadding: Bool { layoutComputer.ignoresAutomaticPadding() } + /// Indicates whether the view requires spacing projection during layout. package var requiresSpacingProjection: Bool { layoutComputer.requiresSpacingProjection() } } +/// A collection of layout proxies that can be accessed by index. +/// +/// `LayoutProxyCollection` provides random access to a collection of layout proxies, +/// each representing a different view or component in a layout hierarchy. package struct LayoutProxyCollection: RandomAccessCollection { + /// The rule context used to resolve attribute values. var context: AnyRuleContext + /// The attributes for each layout proxy in the collection. var attributes: [LayoutProxyAttributes] + /// Creates a layout proxy collection with the specified context and attributes. + /// + /// - Parameters: + /// - context: The rule context used to resolve attribute values. + /// - attributes: The array of attributes for each layout proxy. package init(context: AnyRuleContext, attributes: [LayoutProxyAttributes]) { self.context = context self.attributes = attributes From 120a80f9031dcf597aa808d690b00271d382116b Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 19 May 2025 00:55:48 +0800 Subject: [PATCH 4/6] Add UnaryLayout --- .../Layout/Stack/LayoutPriorityLayout.swift | 21 ++++---- .../OpenSwiftUICore/Layout/UnaryLayout.swift | 50 +++++++++++++++++++ 2 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 Sources/OpenSwiftUICore/Layout/UnaryLayout.swift diff --git a/Sources/OpenSwiftUICore/Layout/Stack/LayoutPriorityLayout.swift b/Sources/OpenSwiftUICore/Layout/Stack/LayoutPriorityLayout.swift index 9b53962a..74002678 100644 --- a/Sources/OpenSwiftUICore/Layout/Stack/LayoutPriorityLayout.swift +++ b/Sources/OpenSwiftUICore/Layout/Stack/LayoutPriorityLayout.swift @@ -5,6 +5,8 @@ // Audited for iOS 18.0 // Status: WIP +package import Foundation + extension View { /// Sets the priority by which a parent layout should apportion space to /// this child. @@ -56,18 +58,17 @@ package struct LayoutPriorityTraitKey: _ViewTraitKey { @available(*, unavailable) extension LayoutPriorityTraitKey: Sendable {} -// FIXME -protocol UnaryLayout: ViewModifier { - static func makeViewImpl( - modifier: _GraphValue, - inputs: _ViewInputs, - body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs - ) -> _ViewOutputs -} - // FIXME package struct LayoutPriorityLayout: UnaryLayout { - static func makeViewImpl(modifier: _GraphValue, inputs: _ViewInputs, body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs) -> _ViewOutputs { + package func placement(of child: LayoutProxy, in context: PlacementContext) -> _Placement { + preconditionFailure("TODO") + } + + package func sizeThatFits(in proposedSize: _ProposedSize, context: SizeAndSpacingContext, child: LayoutProxy) -> CGSize { + preconditionFailure("TODO") + } + + package static func makeViewImpl(modifier: _GraphValue, inputs: _ViewInputs, body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs) -> _ViewOutputs { .init() } } diff --git a/Sources/OpenSwiftUICore/Layout/UnaryLayout.swift b/Sources/OpenSwiftUICore/Layout/UnaryLayout.swift new file mode 100644 index 00000000..dd3481f5 --- /dev/null +++ b/Sources/OpenSwiftUICore/Layout/UnaryLayout.swift @@ -0,0 +1,50 @@ +// +// UnaryLayout.swift +// OpenSwiftUICore +// +// Audited for iOS 18.0 +// Status: WIP + +package import Foundation + +package protocol UnaryLayout: Animatable, MultiViewModifier, PrimitiveViewModifier { + associatedtype PlacementContextType = PlacementContext + + func spacing(in context: SizeAndSpacingContext, child: LayoutProxy) -> Spacing + + func placement(of child: LayoutProxy, in context: PlacementContextType) -> _Placement + + func sizeThatFits(in proposedSize: _ProposedSize, context: SizeAndSpacingContext, child: LayoutProxy) -> CGSize + + func layoutPriority(child: LayoutProxy) -> Double + + func ignoresAutomaticPadding(child: LayoutProxy) -> Bool + + static func makeViewImpl( + modifier: _GraphValue, + inputs: _ViewInputs, + body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs + ) -> _ViewOutputs +} + +extension UnaryLayout { + package func layoutPriority(child: LayoutProxy) -> Double { + child.layoutPriority + } + + package func ignoresAutomaticPadding(child: LayoutProxy) -> Bool { + false + } + + nonisolated public static func _makeView( + modifier: _GraphValue, + inputs: _ViewInputs, + body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs + ) -> _ViewOutputs { + makeViewImpl(modifier: modifier, inputs: inputs, body: body) + } + + package func spacing(in context: SizeAndSpacingContext, child: LayoutProxy) -> Spacing { + child.spacing() + } +} From 907470c90d4e6371ea6eb94438e5e8c047d7a6cb Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 19 May 2025 01:25:19 +0800 Subject: [PATCH 5/6] Add OG dependency to fix link issue --- Package.resolved | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.resolved b/Package.resolved index b1d8f8d0..f7bd5a0f 100644 --- a/Package.resolved +++ b/Package.resolved @@ -7,7 +7,7 @@ "location" : "https://github.com/OpenSwiftUIProject/DarwinPrivateFrameworks.git", "state" : { "branch" : "main", - "revision" : "75edcc68e064a7f4735074626c5de5f53cf60bf1" + "revision" : "81e34b38e1bd8b2729cdc201c4c0f06746dd87b0" } }, { @@ -25,7 +25,7 @@ "location" : "https://github.com/OpenSwiftUIProject/OpenGraph", "state" : { "branch" : "main", - "revision" : "938fe2e9cbd2402c29c333de77038c1439e7cafc" + "revision" : "433aa3a58e5c871461328971cacb7bd09f519d9a" } }, { From 074f31d35b4da316722d3ae74656a0140708653a Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 19 May 2025 01:27:14 +0800 Subject: [PATCH 6/6] Fix Linux build issue --- Sources/OpenSwiftUICore/Layout/LayoutProxy.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/OpenSwiftUICore/Layout/LayoutProxy.swift b/Sources/OpenSwiftUICore/Layout/LayoutProxy.swift index ccd0f507..65d8efba 100644 --- a/Sources/OpenSwiftUICore/Layout/LayoutProxy.swift +++ b/Sources/OpenSwiftUICore/Layout/LayoutProxy.swift @@ -5,6 +5,7 @@ // Audited for iOS 18.0 // Status: Complete +package import Foundation package import OpenGraphShims /// A collection of attributes that can be applied to layout calculations.