Skip to content

Commit eb5f4a8

Browse files
authored
Add UnaryLayout and LayoutProxy (#299)
* Update OG dependency * Add LayoutProxy implementation * Update documentation for LayoutProxy * Add UnaryLayout * Add OG dependency to fix link issue * Fix Linux build issue
1 parent 73e1eb0 commit eb5f4a8

File tree

5 files changed

+308
-18
lines changed

5 files changed

+308
-18
lines changed

Package.resolved

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"location" : "https://github.com/OpenSwiftUIProject/DarwinPrivateFrameworks.git",
88
"state" : {
99
"branch" : "main",
10-
"revision" : "2ba5179547b9cefddfd304bbda6d0d0704946fe3"
10+
"revision" : "81e34b38e1bd8b2729cdc201c4c0f06746dd87b0"
1111
}
1212
},
1313
{
@@ -25,7 +25,7 @@
2525
"location" : "https://github.com/OpenSwiftUIProject/OpenGraph",
2626
"state" : {
2727
"branch" : "main",
28-
"revision" : "732ce4507120fbee7bc43ec0c8407f4c3d431811"
28+
"revision" : "433aa3a58e5c871461328971cacb7bd09f519d9a"
2929
}
3030
},
3131
{
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
//
2+
// LayoutProxy.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for iOS 18.0
6+
// Status: Complete
7+
8+
package import Foundation
9+
package import OpenGraphShims
10+
11+
/// A collection of attributes that can be applied to layout calculations.
12+
///
13+
/// `LayoutProxyAttributes` stores optional references to a layout computer and a view trait list,
14+
/// which are used to compute layouts and access view traits during layout operations.
15+
package struct LayoutProxyAttributes: Equatable {
16+
/// The layout computer used to calculate sizes and positions.
17+
@OptionalAttribute
18+
var layoutComputer: LayoutComputer?
19+
20+
/// The list of view traits that affect layout behavior.
21+
@OptionalAttribute
22+
var traitList: (any ViewList)?
23+
24+
/// Creates layout proxy attributes with the specified layout computer and traits list.
25+
///
26+
/// - Parameters:
27+
/// - layoutComputer: The optional attribute containing a layout computer.
28+
/// - traitsList: The optional attribute containing view traits.
29+
package init(layoutComputer: OptionalAttribute<LayoutComputer>, traitsList: OptionalAttribute<any ViewList>) {
30+
_layoutComputer = layoutComputer
31+
_traitList = traitsList
32+
}
33+
34+
/// Creates layout proxy attributes with only traits.
35+
///
36+
/// - Parameter traitsList: The optional attribute containing view traits.
37+
package init(traitsList: OptionalAttribute<any ViewList>) {
38+
_layoutComputer = OptionalAttribute()
39+
_traitList = traitsList
40+
}
41+
42+
/// Creates layout proxy attributes with only a layout computer.
43+
///
44+
/// - Parameter layoutComputer: The optional attribute containing a layout computer.
45+
package init(layoutComputer: OptionalAttribute<LayoutComputer>) {
46+
_layoutComputer = layoutComputer
47+
_traitList = OptionalAttribute()
48+
}
49+
50+
/// Creates empty layout proxy attributes with no layout computer or traits.
51+
package init() {
52+
_layoutComputer = OptionalAttribute()
53+
_traitList = OptionalAttribute()
54+
}
55+
56+
/// Determines if this collection of attributes is empty.
57+
///
58+
/// Returns `true` if neither the layout computer nor the trait list is set.
59+
package var isEmpty: Bool {
60+
$layoutComputer == nil && $traitList == nil
61+
}
62+
}
63+
64+
/// A proxy object used to perform layout calculations for views.
65+
///
66+
/// `LayoutProxy` provides an interface to compute sizes, dimensions, and positions
67+
/// of views during the layout process. It combines layout computers and view traits
68+
/// to determine the final layout characteristics of a view.
69+
package struct LayoutProxy: Equatable {
70+
/// The rule context used to resolve attribute values.
71+
var context: AnyRuleContext
72+
73+
/// The attributes that define the layout behavior.
74+
var attributes: LayoutProxyAttributes
75+
76+
/// Creates a layout proxy with the specified context and attributes.
77+
///
78+
/// - Parameters:
79+
/// - context: The rule context used to resolve attribute values.
80+
/// - attributes: The attributes that define the layout behavior.
81+
package init(context: AnyRuleContext, attributes: LayoutProxyAttributes) {
82+
self.context = context
83+
self.attributes = attributes
84+
}
85+
86+
/// Creates a layout proxy with the specified context and layout computer.
87+
///
88+
/// - Parameters:
89+
/// - context: The rule context used to resolve attribute values.
90+
/// - layoutComputer: The optional layout computer to use for calculations.
91+
package init(context: AnyRuleContext, layoutComputer: Attribute<LayoutComputer>?) {
92+
self.context = context
93+
self.attributes = LayoutProxyAttributes(layoutComputer: .init(layoutComputer))
94+
}
95+
96+
/// The layout computer to use for layout calculations.
97+
///
98+
/// If no layout computer is explicitly provided, returns the default layout computer.
99+
package var layoutComputer: LayoutComputer {
100+
guard let layoutComputer = attributes.$layoutComputer else {
101+
return .defaultValue
102+
}
103+
return context[layoutComputer]
104+
}
105+
106+
/// The collection of view traits associated with this layout proxy.
107+
///
108+
/// Returns `nil` if no trait list is available.
109+
package var traits: ViewTraitCollection? {
110+
guard let traitList = attributes.$traitList else {
111+
return nil
112+
}
113+
return context[traitList].traits
114+
}
115+
116+
/// Accesses a specific trait value by its key type.
117+
///
118+
/// - Parameter key: The trait key type to look up.
119+
/// - Returns: The value for the specified trait key, or the default value if the trait is not present.
120+
package subscript<K>(key: K.Type) -> K.Value where K: _ViewTraitKey {
121+
traits.map { $0[key] } ?? K.defaultValue
122+
}
123+
124+
/// Returns the spacing configuration for this layout.
125+
///
126+
/// - Returns: The spacing configuration derived from the layout computer.
127+
package func spacing() -> Spacing {
128+
layoutComputer.spacing()
129+
}
130+
131+
/// Calculates the ideal size for the view without any specific constraints.
132+
///
133+
/// - Returns: The ideal size as determined by the layout computer.
134+
package func idealSize() -> CGSize {
135+
size(in: .unspecified)
136+
}
137+
138+
/// Calculates the size that fits within the given proposed size.
139+
///
140+
/// - Parameter proposedSize: The size constraints to consider.
141+
/// - Returns: The calculated size that fits within the constraints.
142+
package func size(in proposedSize: _ProposedSize) -> CGSize {
143+
layoutComputer.sizeThatFits(proposedSize)
144+
}
145+
146+
/// Calculates the length that fits within the given proposal along a specific axis.
147+
///
148+
/// - Parameters:
149+
/// - proposal: The size constraints to consider.
150+
/// - direction: The axis along which to calculate the length.
151+
/// - Returns: The calculated length that fits within the constraints.
152+
package func lengthThatFits(_ proposal: _ProposedSize, in direction: Axis) -> CGFloat {
153+
layoutComputer.lengthThatFits(proposal, in: direction)
154+
}
155+
156+
/// Calculates the view dimensions within the given proposed size.
157+
///
158+
/// - Parameter proposedSize: The size constraints to consider.
159+
/// - Returns: The calculated dimensions including size and alignment guides.
160+
package func dimensions(in proposedSize: _ProposedSize) -> ViewDimensions {
161+
let computer = layoutComputer
162+
return ViewDimensions(
163+
guideComputer: computer,
164+
size: computer.sizeThatFits(proposedSize),
165+
proposal: _ProposedSize(
166+
width: proposedSize.width ?? .nan,
167+
height: proposedSize.height ?? .nan
168+
)
169+
)
170+
}
171+
172+
/// Calculates the final geometry of a view at a specific placement within a parent.
173+
///
174+
/// - Parameters:
175+
/// - p: The placement defining position and proposed size.
176+
/// - parentSize: The size of the parent container.
177+
/// - layoutDirection: The layout direction (left-to-right or right-to-left).
178+
/// - Returns: The final view geometry including position and dimensions.
179+
package func finallyPlaced(at p: _Placement, in parentSize: CGSize, layoutDirection: LayoutDirection) -> ViewGeometry {
180+
let dimensions = dimensions(in: p.proposedSize_)
181+
var geometry = ViewGeometry(
182+
placement: p,
183+
dimensions: dimensions
184+
)
185+
geometry.finalizeLayoutDirection(layoutDirection, parentSize: parentSize)
186+
return geometry
187+
}
188+
189+
/// Returns the explicit alignment for a specific alignment key at the given size.
190+
///
191+
/// - Parameters:
192+
/// - k: The alignment key to measure.
193+
/// - mySize: The size at which to measure the alignment.
194+
/// - Returns: The explicit alignment position, or `nil` if not explicitly defined.
195+
package func explicitAlignment(_ k: AlignmentKey, at mySize: ViewSize) -> CGFloat? {
196+
layoutComputer.explicitAlignment(k, at: mySize)
197+
}
198+
199+
/// The layout priority of the view, which influences layout decisions.
200+
///
201+
/// Higher values indicate higher priority during layout calculations.
202+
package var layoutPriority: Double {
203+
layoutComputer.layoutPriority()
204+
}
205+
206+
/// Indicates whether the view ignores automatic padding applied by container views.
207+
package var ignoresAutomaticPadding: Bool {
208+
layoutComputer.ignoresAutomaticPadding()
209+
}
210+
211+
/// Indicates whether the view requires spacing projection during layout.
212+
package var requiresSpacingProjection: Bool {
213+
layoutComputer.requiresSpacingProjection()
214+
}
215+
}
216+
217+
/// A collection of layout proxies that can be accessed by index.
218+
///
219+
/// `LayoutProxyCollection` provides random access to a collection of layout proxies,
220+
/// each representing a different view or component in a layout hierarchy.
221+
package struct LayoutProxyCollection: RandomAccessCollection {
222+
/// The rule context used to resolve attribute values.
223+
var context: AnyRuleContext
224+
225+
/// The attributes for each layout proxy in the collection.
226+
var attributes: [LayoutProxyAttributes]
227+
228+
/// Creates a layout proxy collection with the specified context and attributes.
229+
///
230+
/// - Parameters:
231+
/// - context: The rule context used to resolve attribute values.
232+
/// - attributes: The array of attributes for each layout proxy.
233+
package init(context: AnyRuleContext, attributes: [LayoutProxyAttributes]) {
234+
self.context = context
235+
self.attributes = attributes
236+
}
237+
238+
package var startIndex: Int { .zero }
239+
240+
package var endIndex: Int { attributes.endIndex }
241+
242+
package subscript(index: Int) -> LayoutProxy {
243+
LayoutProxy(context: context, attributes: attributes[index])
244+
}
245+
}

Sources/OpenSwiftUICore/Layout/Stack/LayoutPriorityLayout.swift

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
// Audited for iOS 18.0
66
// Status: WIP
77

8+
package import Foundation
9+
810
extension View {
911
/// Sets the priority by which a parent layout should apportion space to
1012
/// this child.
@@ -56,18 +58,17 @@ package struct LayoutPriorityTraitKey: _ViewTraitKey {
5658
@available(*, unavailable)
5759
extension LayoutPriorityTraitKey: Sendable {}
5860

59-
// FIXME
60-
protocol UnaryLayout: ViewModifier {
61-
static func makeViewImpl(
62-
modifier: _GraphValue<Self>,
63-
inputs: _ViewInputs,
64-
body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs
65-
) -> _ViewOutputs
66-
}
67-
6861
// FIXME
6962
package struct LayoutPriorityLayout: UnaryLayout {
70-
static func makeViewImpl(modifier: _GraphValue<LayoutPriorityLayout>, inputs: _ViewInputs, body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs) -> _ViewOutputs {
63+
package func placement(of child: LayoutProxy, in context: PlacementContext) -> _Placement {
64+
preconditionFailure("TODO")
65+
}
66+
67+
package func sizeThatFits(in proposedSize: _ProposedSize, context: SizeAndSpacingContext, child: LayoutProxy) -> CGSize {
68+
preconditionFailure("TODO")
69+
}
70+
71+
package static func makeViewImpl(modifier: _GraphValue<LayoutPriorityLayout>, inputs: _ViewInputs, body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs) -> _ViewOutputs {
7172
.init()
7273
}
7374
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// UnaryLayout.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for iOS 18.0
6+
// Status: WIP
7+
8+
package import Foundation
9+
10+
package protocol UnaryLayout: Animatable, MultiViewModifier, PrimitiveViewModifier {
11+
associatedtype PlacementContextType = PlacementContext
12+
13+
func spacing(in context: SizeAndSpacingContext, child: LayoutProxy) -> Spacing
14+
15+
func placement(of child: LayoutProxy, in context: PlacementContextType) -> _Placement
16+
17+
func sizeThatFits(in proposedSize: _ProposedSize, context: SizeAndSpacingContext, child: LayoutProxy) -> CGSize
18+
19+
func layoutPriority(child: LayoutProxy) -> Double
20+
21+
func ignoresAutomaticPadding(child: LayoutProxy) -> Bool
22+
23+
static func makeViewImpl(
24+
modifier: _GraphValue<Self>,
25+
inputs: _ViewInputs,
26+
body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs
27+
) -> _ViewOutputs
28+
}
29+
30+
extension UnaryLayout {
31+
package func layoutPriority(child: LayoutProxy) -> Double {
32+
child.layoutPriority
33+
}
34+
35+
package func ignoresAutomaticPadding(child: LayoutProxy) -> Bool {
36+
false
37+
}
38+
39+
nonisolated public static func _makeView(
40+
modifier: _GraphValue<Self>,
41+
inputs: _ViewInputs,
42+
body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs
43+
) -> _ViewOutputs {
44+
makeViewImpl(modifier: modifier, inputs: inputs, body: body)
45+
}
46+
47+
package func spacing(in context: SizeAndSpacingContext, child: LayoutProxy) -> Spacing {
48+
child.spacing()
49+
}
50+
}

Sources/OpenSwiftUICore/OpenGraph/Attribute/AsyncAttribute.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,3 @@ extension Attribute {
2727
}
2828
}
2929
}
30-
31-
// FIXME: Add OGChangedValueFlagsRequiresMainThread in OpenGraph
32-
33-
extension OGChangedValueFlags {
34-
static let requiresMainThread = Self(rawValue: 2)
35-
}

0 commit comments

Comments
 (0)