From 59e4fb38d39f0bbc4ae283ca8958d668708ff9cd Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 5 Jul 2025 12:57:38 +0800 Subject: [PATCH 1/5] Implement HVStack makeCache --- .../Layout/Stack/HVStack.swift | 25 ++++--- .../Layout/Stack/StackLayout.swift | 69 +++++++++++++++++-- 2 files changed, 79 insertions(+), 15 deletions(-) diff --git a/Sources/OpenSwiftUICore/Layout/Stack/HVStack.swift b/Sources/OpenSwiftUICore/Layout/Stack/HVStack.swift index 708676421..81125360d 100644 --- a/Sources/OpenSwiftUICore/Layout/Stack/HVStack.swift +++ b/Sources/OpenSwiftUICore/Layout/Stack/HVStack.swift @@ -2,7 +2,7 @@ // HVStack.swift // OpenSwiftUICore // -// Audited for 6.4.41 +// Audited for 6.5.4 // Status: WIP package import Foundation @@ -19,6 +19,7 @@ package protocol HVStack: Layout, _VariadicView_UnaryViewRoot where Cache == _St static var resizeChildrenWithTrailingOverflow: Bool { get } } +@available(OpenSwiftUI_v4_0, *) public struct _StackLayoutCache { var stack: StackLayout } @@ -50,7 +51,15 @@ extension HVStack { } public func makeCache(subviews: Subviews) -> Cache { - _openSwiftUIUnimplementedFailure() + Cache( + stack: StackLayout( + minorAxisAlignment: alignment.key, + uniformSpacing: spacing, + majorAxis: Self.majorAxis, + proxies: subviews, + resizeChildrenWithTrailingOverflow: Self.resizeChildrenWithTrailingOverflow + ) + ) } public func updateCache(_ cache: inout Cache, subviews: Self.Subviews) { @@ -82,19 +91,19 @@ extension HVStack { of guide: HorizontalAlignment, in bounds: CGRect, proposal: ProposedViewSize, - subviews: Self.Subviews, - cache: inout Self.Cache + subviews: Subviews, + cache: inout Cache ) -> CGFloat? { - _openSwiftUIUnimplementedFailure() + cache.stack.explicitAlignment(guide.key, in: bounds, proposal: proposal) } public func explicitAlignment( of guide: VerticalAlignment, in bounds: CGRect, proposal: ProposedViewSize, - subviews: Self.Subviews, - cache: inout Self.Cache + subviews: Subviews, + cache: inout Cache ) -> CGFloat? { - _openSwiftUIUnimplementedFailure() + cache.stack.explicitAlignment(guide.key, in: bounds, proposal: proposal) } } diff --git a/Sources/OpenSwiftUICore/Layout/Stack/StackLayout.swift b/Sources/OpenSwiftUICore/Layout/Stack/StackLayout.swift index 81c8a6cf9..05f33c12b 100644 --- a/Sources/OpenSwiftUICore/Layout/Stack/StackLayout.swift +++ b/Sources/OpenSwiftUICore/Layout/Stack/StackLayout.swift @@ -2,9 +2,9 @@ // StackLayout.swift // OpenSwiftUICore // -// Audited for 6.4.41 +// Audited for 6.5.4 // Status: WIP -// ID: 00690F480F8D293143B214DBE6D72CD0 (SwiftUICore?) +// ID: 00690F480F8D293143B214DBE6D72CD0 (SwiftUICore) import Foundation @@ -18,9 +18,9 @@ struct StackLayout { let minorAxisAlignment: AlignmentKey let uniformSpacing: CGFloat? let majorAxis: Axis - var internalSpacing: CGFloat - var lastProposedSize: ProposedViewSize - var stackSize: CGSize + var internalSpacing: CGFloat = .zero + var lastProposedSize: ProposedViewSize = .init(width: -.infinity, height: -.infinity) + var stackSize: CGSize = .zero let proxies: LayoutSubviews let resizeChildrenWithTrailingOverflow: Bool } @@ -37,8 +37,63 @@ struct StackLayout { private var children: [Child] - private func makeChildren() { - // TODO + @inline(__always) + init( + minorAxisAlignment: AlignmentKey, + uniformSpacing: CGFloat?, + majorAxis: Axis, + proxies: LayoutSubviews, + resizeChildrenWithTrailingOverflow: Bool + ) { + self.header = Header( + minorAxisAlignment: minorAxisAlignment, + uniformSpacing: uniformSpacing, + majorAxis: majorAxis, + proxies: proxies, + resizeChildrenWithTrailingOverflow: resizeChildrenWithTrailingOverflow + ) + self.children = [] + makeChildren() + } + + private mutating func makeChildren() { + children.reserveCapacity(header.proxies.count) + header.internalSpacing = .zero + let proxies = header.proxies + var internalSpacing: Double = .zero + for (index, subview) in proxies.enumerated() { + let distanceToPrevious: Double + if index == 0 { + distanceToPrevious = .zero + } else { + if let uniformSpacing = header.uniformSpacing { + distanceToPrevious = uniformSpacing + } else { + let previousSpacing = proxies[index-1].spacing + let spacing = proxies[index].spacing + distanceToPrevious = previousSpacing.distance(to: spacing, along: header.majorAxis) + } + internalSpacing += distanceToPrevious + header.internalSpacing = internalSpacing + } + children.append( + Child( + layoutPriority: proxies[index].priority, + majorAxisRangeCache: .init(), + distanceToPrevious: distanceToPrevious, + fittingOrder: index, + geometry: .invalidValue + ) + ) + } + } + + func explicitAlignment( + _ key: AlignmentKey, + in bounds: CGRect, + proposal: ProposedViewSize, + ) -> CGFloat? { + _openSwiftUIUnimplementedFailure() } } From f5b104d4fcd08219aafcd3b6c83e117d56b9ecf9 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 5 Jul 2025 13:09:03 +0800 Subject: [PATCH 2/5] Complete HVStack API --- .../Layout/Stack/HVStack.swift | 37 +++++++++++++------ .../Layout/Stack/StackLayout.swift | 21 +++++++++++ 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/Sources/OpenSwiftUICore/Layout/Stack/HVStack.swift b/Sources/OpenSwiftUICore/Layout/Stack/HVStack.swift index 81125360d..487b1ac73 100644 --- a/Sources/OpenSwiftUICore/Layout/Stack/HVStack.swift +++ b/Sources/OpenSwiftUICore/Layout/Stack/HVStack.swift @@ -3,7 +3,7 @@ // OpenSwiftUICore // // Audited for 6.5.4 -// Status: WIP +// Status: Complete package import Foundation @@ -62,29 +62,34 @@ extension HVStack { ) } - public func updateCache(_ cache: inout Cache, subviews: Self.Subviews) { - _openSwiftUIUnimplementedFailure() + public func updateCache(_ cache: inout Cache, subviews: Subviews) { + cache.stack.update( + children: subviews, + majorAxis: Self.majorAxis, + minorAxisAlignment: alignment.key, + uniformSpacing: spacing + ) } public func spacing(subviews: Subviews, cache: inout Cache) -> ViewSpacing { - _openSwiftUIUnimplementedFailure() + cache.stack.spacing() } public func sizeThatFits( proposal: ProposedViewSize, - subviews: Self.Subviews, - cache: inout Self.Cache + subviews: Subviews, + cache: inout Cache ) -> CGSize { - _openSwiftUIUnimplementedFailure() + cache.stack.sizeThatFits(proposal) } public func placeSubviews( in bounds: CGRect, proposal: ProposedViewSize, - subviews: Self.Subviews, - cache: inout Self.Cache + subviews: Subviews, + cache: inout Cache ) { - _openSwiftUIUnimplementedFailure() + cache.stack.placeSubviews(in: bounds, proposedSize: proposal) } public func explicitAlignment( @@ -94,7 +99,11 @@ extension HVStack { subviews: Subviews, cache: inout Cache ) -> CGFloat? { - cache.stack.explicitAlignment(guide.key, in: bounds, proposal: proposal) + cache.stack.explicitAlignment( + guide.key, + in: bounds, + proposal: proposal + ) } public func explicitAlignment( @@ -104,6 +113,10 @@ extension HVStack { subviews: Subviews, cache: inout Cache ) -> CGFloat? { - cache.stack.explicitAlignment(guide.key, in: bounds, proposal: proposal) + cache.stack.explicitAlignment( + guide.key, + in: bounds, + proposal: proposal + ) } } diff --git a/Sources/OpenSwiftUICore/Layout/Stack/StackLayout.swift b/Sources/OpenSwiftUICore/Layout/Stack/StackLayout.swift index 05f33c12b..e4a9da91e 100644 --- a/Sources/OpenSwiftUICore/Layout/Stack/StackLayout.swift +++ b/Sources/OpenSwiftUICore/Layout/Stack/StackLayout.swift @@ -88,6 +88,27 @@ struct StackLayout { } } + func update( + children: LayoutSubviews, + majorAxis: Axis, + minorAxisAlignment: AlignmentKey, + uniformSpacing: CGFloat? + ) { + _openSwiftUIUnimplementedFailure() + } + + func spacing() -> ViewSpacing { + _openSwiftUIUnimplementedFailure() + } + + func sizeThatFits(_ proposedSize: ProposedViewSize) -> CGSize { + _openSwiftUIUnimplementedFailure() + } + + func placeSubviews(in bounds: CGRect, proposedSize: ProposedViewSize) { + _openSwiftUIUnimplementedFailure() + } + func explicitAlignment( _ key: AlignmentKey, in bounds: CGRect, From 52a7dbccc35bc2b89de602e929e4b7a246e6088d Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 5 Jul 2025 18:55:55 +0800 Subject: [PATCH 3/5] Optimize log message --- Sources/OpenSwiftUICore/Log/Logging.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/OpenSwiftUICore/Log/Logging.swift b/Sources/OpenSwiftUICore/Log/Logging.swift index d2ba8faf9..15a7d1820 100644 --- a/Sources/OpenSwiftUICore/Log/Logging.swift +++ b/Sources/OpenSwiftUICore/Log/Logging.swift @@ -237,7 +237,7 @@ package func _openSwiftUIPlatformUnimplementedFailure(_ function: String = #func @_transparent package func _openSwiftUIUnimplementedWarning(_ function: String = #function, file: StaticString = #fileID, line: UInt = #line) { - print("[Warning]: \(function) is unimplemented") + print("\(file):\(line): Warning: \(function) is unimplemented") #if DEBUG && OPENSWIFTUI_DEVELOPMENT _openSwiftUIUnimplementedFailure(function, file: file, line: line) #endif @@ -245,7 +245,7 @@ package func _openSwiftUIUnimplementedWarning(_ function: String = #function, fi @_transparent package func _openSwiftUIPlatformUnimplementedWarning(_ function: String = #function, file: StaticString = #fileID, line: UInt = #line) { - print("[Warning]: \(function) is unimplemented on this platform") + print("\(file):\(line): Warning: \(function) is unimplemented on this platform") #if DEBUG && OPENSWIFTUI_DEVELOPMENT _openSwiftUIPlatformUnimplementedFailure(function, file: file, line: line) #endif From 0c763be60b7cc1dd9283e67ff02102f871f41305 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 5 Jul 2025 18:56:15 +0800 Subject: [PATCH 4/5] Implement StackLayout --- .../Layout/Stack/StackLayout.swift | 196 +++++++++++++++++- 1 file changed, 187 insertions(+), 9 deletions(-) diff --git a/Sources/OpenSwiftUICore/Layout/Stack/StackLayout.swift b/Sources/OpenSwiftUICore/Layout/Stack/StackLayout.swift index e4a9da91e..3ba1daf0c 100644 --- a/Sources/OpenSwiftUICore/Layout/Stack/StackLayout.swift +++ b/Sources/OpenSwiftUICore/Layout/Stack/StackLayout.swift @@ -70,7 +70,7 @@ struct StackLayout { distanceToPrevious = uniformSpacing } else { let previousSpacing = proxies[index-1].spacing - let spacing = proxies[index].spacing + let spacing = subview.spacing distanceToPrevious = previousSpacing.distance(to: spacing, along: header.majorAxis) } internalSpacing += distanceToPrevious @@ -97,24 +97,202 @@ struct StackLayout { _openSwiftUIUnimplementedFailure() } - func spacing() -> ViewSpacing { - _openSwiftUIUnimplementedFailure() + mutating func spacing() -> ViewSpacing { + _openSwiftUIUnimplementedWarning() + return .init(.zero) } - func sizeThatFits(_ proposedSize: ProposedViewSize) -> CGSize { - _openSwiftUIUnimplementedFailure() + mutating func sizeThatFits(_ proposedSize: ProposedViewSize) -> CGSize { + withUnmanagedImplementation { impl in + _openSwiftUIUnimplementedWarning() + return CGSize(width: 50, height: 50) + } } - func placeSubviews(in bounds: CGRect, proposedSize: ProposedViewSize) { - _openSwiftUIUnimplementedFailure() + mutating func placeSubviews(in bounds: CGRect, proposedSize: ProposedViewSize) { + withUnmanagedImplementation { impl in + impl.commitPlacements(in: bounds, proposedSize: proposedSize) + } } - func explicitAlignment( + mutating func explicitAlignment( _ key: AlignmentKey, in bounds: CGRect, proposal: ProposedViewSize, ) -> CGFloat? { - _openSwiftUIUnimplementedFailure() + withUnmanagedImplementation { impl in + impl.explicitAlignment( + key, + at: ViewSize(bounds.size, proposal: .init(proposal)) + ) + } } } +extension StackLayout { + @inline(__always) + private mutating func withUnmanagedImplementation( + _ body: (UnmanagedImplementation) -> V + ) -> V { + withUnsafeMutablePointer(to: &header) { headerPtr in + children.withUnsafeMutableBufferPointer { childPtr in + body(UnmanagedImplementation(header: headerPtr, children: childPtr)) + } + } + } + + private struct UnmanagedImplementation { + let header: UnsafeMutablePointer
+ let children: UnsafeMutableBufferPointer + + func commitPlacements( + in bounds: CGRect, + proposedSize: ProposedViewSize + ) { + let proposal = proposalWhenPlacing(in: .init(bounds.size, proposal: .init(proposedSize))) + placeChildren(in: proposedSize) + + // TODO: placeChildren1 + _openSwiftUIUnimplementedWarning() + } + + func spacing() -> Spacing { + _openSwiftUIUnimplementedWarning() + return .zero + } + + func explicitAlignment( + _ key: AlignmentKey, + at size: ViewSize + ) -> CGFloat? { + let proposal = proposalWhenPlacing(in: size) + placeChildren(in: proposal) + guard !children.isEmpty else { + return nil + } + let alignments = children.map { + $0.geometry.dimensions[explicit: key] + } + return key.id.combineExplicit(alignments) + } + + // WIP + func placeChildren(in proposedSize: ProposedViewSize) { + guard header.pointee.lastProposedSize != proposedSize, !children.isEmpty else { + return + } + placeChildren1(in: proposedSize) { child in + proposedSize[header.pointee.majorAxis.otherAxis] + } + if header.pointee.resizeChildrenWithTrailingOverflow { + resizeAnyChildrenWithTrailingOverflow(in: proposedSize) + } + header.pointee.lastProposedSize = proposedSize + } + + func placeChildren1( + in proposedSize: ProposedViewSize, + minorProposalForChild: (Child) -> CGFloat? + ) { + if proposedSize[header.pointee.majorAxis] != nil { + sizeChildrenGenerallyWithConcreteMajorProposal( + in: proposedSize, + minorProposalForChild: minorProposalForChild + ) + } else { + sizeChildrenIdeally( + in: proposedSize, + minorProposalForChild: minorProposalForChild + ) + } + var majorValue: CGFloat = 0.0 + var minorRange: ClosedRange = 0.0 ... 0.0 + for child in children { + let minorAxis = header.pointee.majorAxis.otherAxis + let childRange = child.geometry.frame[minorAxis] + minorRange = minorRange.union(childRange) + } + for (index, child) in children.enumerated() { + let majorAxis = header.pointee.majorAxis + let minorAxis = majorAxis.otherAxis + let majorOrigin = majorValue + child.distanceToPrevious + if !majorOrigin.isNaN { + children[index].geometry.origin[majorAxis] = majorOrigin + } + let minorOrigin = child.geometry.origin[minorAxis] - minorRange.lowerBound + if !minorOrigin.isNaN { + children[index].geometry.origin[minorAxis] = minorOrigin + } + majorValue = majorOrigin + child.geometry.dimensions.size[majorAxis] + } + header.pointee.stackSize = CGSize( + majorValue, + in: header.pointee.majorAxis, + by: minorRange.length + ) + } + + func sizeChildrenGenerallyWithConcreteMajorProposal( + in size: ProposedViewSize, + minorProposalForChild: (Child) -> CGFloat? + ) { + + } + + func sizeChildrenIdeally( + in size: ProposedViewSize, + minorProposalForChild: (Child) -> CGFloat? + ) { + guard !children.isEmpty else { + return + } + let majorOrigin: CGFloat = 0.0 + let majorAxis = header.pointee.majorAxis + for (index, child) in children.enumerated() { + let proposal = ProposedViewSize( + nil, + in: majorAxis, + by: minorProposalForChild(child) + ) + let dimensions = header.pointee.proxies[index].dimensions(in: proposal) + let minorAlignment = dimensions[header.pointee.minorAxisAlignment] + let origin = CGPoint( + majorOrigin, + in: majorAxis, + by: -minorAlignment.mappingNaN(to: .infinity) + ) + children[index].geometry = ViewGeometry( + origin: origin, + dimensions: dimensions + ) + } + } + + func proposalWhenPlacing(in size: ViewSize) -> ProposedViewSize { + let proposal = size.proposal + let axis = header.pointee.majorAxis + return if axis == .horizontal { + ProposedViewSize( + width: proposal.width, + height: proposal.height ?? size.height + ) + } else { + ProposedViewSize( + width: proposal.width ?? size.width, + height: proposal.height + ) + } + } + + func prioritize( + _ children: UnsafeMutableBufferPointer, + proposedSize: ProposedViewSize + ) { + _openSwiftUIUnimplementedWarning() + } + + func resizeAnyChildrenWithTrailingOverflow(in size: ProposedViewSize) { + _openSwiftUIUnimplementedWarning() + } + } +} From 39e2a45994d207e16b5eb65a893a52abfb281fde Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 5 Jul 2025 19:35:55 +0800 Subject: [PATCH 5/5] Update documentation and ui test Implement commitPlacements and sizeChildrenGenerallyWithConcreteMajorProposal by Copilot to support basic HVStack layout --- .../Layout/Stack/HVStackUITests.swift | 133 +++++++++ .../Layout/Stack/StackLayout.swift | 260 +++++++++++++++++- 2 files changed, 384 insertions(+), 9 deletions(-) create mode 100644 Example/OpenSwiftUIUITests/Layout/Stack/HVStackUITests.swift diff --git a/Example/OpenSwiftUIUITests/Layout/Stack/HVStackUITests.swift b/Example/OpenSwiftUIUITests/Layout/Stack/HVStackUITests.swift new file mode 100644 index 000000000..d6d6f0baa --- /dev/null +++ b/Example/OpenSwiftUIUITests/Layout/Stack/HVStackUITests.swift @@ -0,0 +1,133 @@ +// +// HVStackUITests.swift +// OpenSwiftUIUITests + +import Testing +import SnapshotTesting + +@MainActor +@Suite(.snapshots(record: .never, diffTool: diffTool)) +struct HVStackUITests { + @Test + func fixFrameForHStack() { + struct ContentView: View { + var body: some View { + HStack { + Color.red.frame(width: 50, height: 50) + Color.blue.frame(width: 50, height: 50) + } + .frame(width: 150, height: 150) + .background { Color.black } + } + } + openSwiftUIAssertSnapshot( + of: ContentView() + ) + } + + @Test + func fixFrameForVStack() { + struct ContentView: View { + var body: some View { + VStack { + Color.red.frame(width: 50, height: 50) + Color.blue.frame(width: 50, height: 50) + } + .frame(width: 150, height: 150) + .background { Color.black } + } + } + openSwiftUIAssertSnapshot( + of: ContentView() + ) + } + + @Test + func fixFrameForHVStack() { + struct ContentView: View { + var body: some View { + VStack { + Color.red.frame(width: 40, height: 40) + Color.blue.frame(width: 40, height: 40) + HStack { + Color.green.frame(width: 40, height: 40) + Color.yellow.frame(width: 40, height: 40) + } + } + .frame(width: 150, height: 150) + .background { Color.black } + } + } + withKnownIssue("Spacing is not implemented") { + openSwiftUIAssertSnapshot( + of: ContentView() + ) + } + } + + @Test + func fixElementForHVStack() { + struct ContentView: View { + var body: some View { + VStack { + Color.red.frame(width: 40, height: 40) + Color.blue.frame(width: 40, height: 40) + HStack { + Color.green.frame(width: 40, height: 40) + Color.yellow.frame(width: 40, height: 40) + } + } + .background { Color.black } + } + } + withKnownIssue("Spacing is not implemented") { + openSwiftUIAssertSnapshot( + of: ContentView() + ) + } + } + + @Test + func equalSizeForHVStack() { + struct ContentView: View { + var body: some View { + VStack { + Color.red + Color.blue + HStack { + Color.green + Color.yellow + } + } + .background { Color.black } + } + } + withKnownIssue("Proposal implmentation is not correct") { + openSwiftUIAssertSnapshot( + of: ContentView() + ) + } + } + + @Test + func layoutPriorityForHVStack() { + struct ContentView: View { + var body: some View { + VStack { + Color.red + Color.blue.layoutPriority(1) + HStack { + Color.green + Color.yellow + } + } + .background { Color.black } + } + } + withKnownIssue("Proposal implmentation is not correct") { + openSwiftUIAssertSnapshot( + of: ContentView() + ) + } + } +} diff --git a/Sources/OpenSwiftUICore/Layout/Stack/StackLayout.swift b/Sources/OpenSwiftUICore/Layout/Stack/StackLayout.swift index 3ba1daf0c..86100589a 100644 --- a/Sources/OpenSwiftUICore/Layout/Stack/StackLayout.swift +++ b/Sources/OpenSwiftUICore/Layout/Stack/StackLayout.swift @@ -8,35 +8,75 @@ import Foundation +/// A layout that arranges subviews in a linear sequence along a major axis. +/// +/// `StackLayout` provides the core implementation for stack-based layouts like +/// `HStack` and `VStack`. It manages the sizing and positioning of child views +/// along a primary axis while aligning them on the secondary axis. struct StackLayout { + /// Cache for storing major axis range calculations. private struct MajorAxisRangeCache { var min: CGFloat? var max: CGFloat? } + /// Header containing shared layout configuration and state. private struct Header { + /// The alignment key for positioning along the minor axis let minorAxisAlignment: AlignmentKey + + /// Optional uniform spacing between children let uniformSpacing: CGFloat? + + /// The primary axis of the stack layout let majorAxis: Axis + + /// Total spacing between all children var internalSpacing: CGFloat = .zero + + /// The last proposed size used for layout var lastProposedSize: ProposedViewSize = .init(width: -.infinity, height: -.infinity) + + /// The calculated size of the entire stack var stackSize: CGSize = .zero + + /// The layout subviews being arranged let proxies: LayoutSubviews + + /// Whether to resize children when they overflow let resizeChildrenWithTrailingOverflow: Bool } private var header: Header + /// Represents a single child view in the stack layout. private struct Child { + /// The layout priority of this child var layoutPriority: Double - var majorAxisRangeCache: StackLayout.MajorAxisRangeCache + + /// Cached major axis range calculations + var majorAxisRangeCache: MajorAxisRangeCache + + /// Distance from the previous child let distanceToPrevious: CGFloat + + /// Order for fitting calculations var fittingOrder: Int + + /// The calculated geometry of this child var geometry: ViewGeometry } private var children: [Child] + /// Creates a new stack layout with the specified configuration. + /// + /// - Parameters: + /// - minorAxisAlignment: Alignment along the minor axis + /// - uniformSpacing: Optional uniform spacing between children + /// - majorAxis: The primary axis of the stack + /// - proxies: The subviews to be laid out + /// - resizeChildrenWithTrailingOverflow: Whether to resize overflowing children @inline(__always) init( minorAxisAlignment: AlignmentKey, @@ -56,6 +96,7 @@ struct StackLayout { makeChildren() } + /// Initializes the child array with proper spacing calculations. private mutating func makeChildren() { children.reserveCapacity(header.proxies.count) header.internalSpacing = .zero @@ -88,6 +129,13 @@ struct StackLayout { } } + /// Updates the layout configuration with new parameters. + /// + /// - Parameters: + /// - children: The new set of layout subviews + /// - majorAxis: The primary axis for the layout + /// - minorAxisAlignment: Alignment along the minor axis + /// - uniformSpacing: Optional uniform spacing between children func update( children: LayoutSubviews, majorAxis: Axis, @@ -97,24 +145,43 @@ struct StackLayout { _openSwiftUIUnimplementedFailure() } + /// Calculates the spacing for this stack layout. + /// + /// - Returns: The computed view spacing mutating func spacing() -> ViewSpacing { _openSwiftUIUnimplementedWarning() return .init(.zero) } + /// Calculates the size that fits the given proposed size. + /// + /// - Parameter proposedSize: The proposed size constraints + /// - Returns: The computed size that fits within the constraints mutating func sizeThatFits(_ proposedSize: ProposedViewSize) -> CGSize { withUnmanagedImplementation { impl in - _openSwiftUIUnimplementedWarning() - return CGSize(width: 50, height: 50) + impl.placeChildren(in: proposedSize) + return impl.header.pointee.stackSize } } + /// Places subviews within the specified bounds. + /// + /// - Parameters: + /// - bounds: The bounds rectangle for placement + /// - proposedSize: The proposed size for layout mutating func placeSubviews(in bounds: CGRect, proposedSize: ProposedViewSize) { withUnmanagedImplementation { impl in impl.commitPlacements(in: bounds, proposedSize: proposedSize) } } + /// Calculates explicit alignment for the given key. + /// + /// - Parameters: + /// - key: The alignment key to calculate + /// - bounds: The bounds rectangle + /// - proposal: The proposed size + /// - Returns: The alignment value, or nil if not applicable mutating func explicitAlignment( _ key: AlignmentKey, in bounds: CGRect, @@ -130,6 +197,10 @@ struct StackLayout { } extension StackLayout { + /// Executes a closure with an unmanaged implementation pointer. + /// + /// - Parameter body: The closure to execute with the implementation + /// - Returns: The result of the closure @inline(__always) private mutating func withUnmanagedImplementation( _ body: (UnmanagedImplementation) -> V @@ -141,26 +212,78 @@ extension StackLayout { } } + /// Unmanaged implementation providing direct pointer access for performance. private struct UnmanagedImplementation { let header: UnsafeMutablePointer
+ let children: UnsafeMutableBufferPointer + // FIXME: [Copilot Generated] + /// Commits the final placement of children within bounds. + /// + /// - Parameters: + /// - bounds: The target bounds rectangle + /// - proposedSize: The proposed size for layout func commitPlacements( in bounds: CGRect, proposedSize: ProposedViewSize ) { - let proposal = proposalWhenPlacing(in: .init(bounds.size, proposal: .init(proposedSize))) placeChildren(in: proposedSize) - - // TODO: placeChildren1 - _openSwiftUIUnimplementedWarning() + + guard !children.isEmpty else { return } + + let majorAxis = header.pointee.majorAxis + let minorAxis = majorAxis.otherAxis + let stackSize = header.pointee.stackSize + + // Calculate offsets to center the stack within bounds + let majorOffset = bounds.origin[majorAxis] + + (bounds.size[majorAxis] - stackSize[majorAxis]) / 2 + let minorOffset = bounds.origin[minorAxis] + + (bounds.size[minorAxis] - stackSize[minorAxis]) / 2 + + // Place each child view at its calculated position + for (index, child) in children.enumerated() { + let finalOrigin = CGPoint( + child.geometry.origin[majorAxis] + majorOffset, + in: majorAxis, + by: child.geometry.origin[minorAxis] + minorOffset + ) + + let finalBounds = CGRect( + origin: finalOrigin, + size: child.geometry.dimensions.size.value + ) + + // Get the child-specific proposed size + let childProposal = ProposedViewSize( + child.geometry.dimensions.size[majorAxis], + in: majorAxis, + by: child.geometry.dimensions.size[minorAxis] + ) + + header.pointee.proxies[index].place( + at: finalBounds.origin, + anchor: .topLeading, + proposal: childProposal + ) + } } + /// Calculates the spacing for the stack. + /// + /// - Returns: The computed spacing value func spacing() -> Spacing { _openSwiftUIUnimplementedWarning() return .zero } + /// Calculates explicit alignment for a given key. + /// + /// - Parameters: + /// - key: The alignment key + /// - size: The view size context + /// - Returns: The alignment value, or nil if not applicable func explicitAlignment( _ key: AlignmentKey, at size: ViewSize @@ -176,7 +299,9 @@ extension StackLayout { return key.id.combineExplicit(alignments) } - // WIP + /// Places children according to the proposed size. + /// + /// - Parameter proposedSize: The proposed size for layout func placeChildren(in proposedSize: ProposedViewSize) { guard header.pointee.lastProposedSize != proposedSize, !children.isEmpty else { return @@ -190,6 +315,11 @@ extension StackLayout { header.pointee.lastProposedSize = proposedSize } + /// Primary child placement implementation. + /// + /// - Parameters: + /// - proposedSize: The proposed size for layout + /// - minorProposalForChild: Closure providing minor axis proposal for each child func placeChildren1( in proposedSize: ProposedViewSize, minorProposalForChild: (Child) -> CGFloat? @@ -232,13 +362,113 @@ extension StackLayout { ) } + // FIXME: [Copliot Generated] + /// Sizes children when given a concrete major axis proposal. + /// + /// This method handles the complex case where the major axis has a specific + /// size constraint, requiring distribution of available space among children + /// based on their layout priorities and flexibility. + /// + /// - Parameters: + /// - size: The proposed size with concrete major axis + /// - minorProposalForChild: Closure providing minor axis proposal for each child func sizeChildrenGenerallyWithConcreteMajorProposal( in size: ProposedViewSize, minorProposalForChild: (Child) -> CGFloat? ) { - + guard !children.isEmpty else { return } + + let majorAxis = header.pointee.majorAxis + let availableSpace = size[majorAxis]! - header.pointee.internalSpacing + + // First pass: get ideal sizes for all children + var idealSizes: [CGFloat] = [] + var totalIdealSize: CGFloat = 0 + + for (index, child) in children.enumerated() { + let proposal = ProposedViewSize( + nil, + in: majorAxis, + by: minorProposalForChild(child) + ) + let dimensions = header.pointee.proxies[index].dimensions(in: proposal) + let idealSize = dimensions.size[majorAxis] + idealSizes.append(idealSize) + totalIdealSize += idealSize + } + + // If total ideal size fits, use ideal sizes + if totalIdealSize <= availableSpace { + for (index, child) in children.enumerated() { + let proposal = ProposedViewSize( + idealSizes[index], + in: majorAxis, + by: minorProposalForChild(child) + ) + let dimensions = header.pointee.proxies[index].dimensions(in: proposal) + let minorAlignment = dimensions[header.pointee.minorAxisAlignment] + let origin = CGPoint( + 0.0, + in: majorAxis, + by: -minorAlignment.mappingNaN(to: .infinity) + ) + children[index].geometry = ViewGeometry( + origin: origin, + dimensions: dimensions + ) + } + return + } + + // Need to distribute space based on priorities and flexibility + prioritize(children, proposedSize: size) + + // Calculate distributed sizes based on layout priorities + var remainingSpace = availableSpace + var processedIndices = Set() + + // Sort by layout priority (higher priority gets preference) + let sortedIndices = children.indices.sorted { i, j in + children[i].layoutPriority > children[j].layoutPriority + } + + for index in sortedIndices { + guard !processedIndices.contains(index) else { continue } + + let child = children[index] + let remainingChildren = children.count - processedIndices.count + let targetSize = min(idealSizes[index], remainingSpace / CGFloat(remainingChildren)) + + let proposal = ProposedViewSize( + targetSize, + in: majorAxis, + by: minorProposalForChild(child) + ) + let dimensions = header.pointee.proxies[index].dimensions(in: proposal) + let actualSize = dimensions.size[majorAxis] + + let minorAlignment = dimensions[header.pointee.minorAxisAlignment] + let origin = CGPoint( + 0.0, + in: majorAxis, + by: -minorAlignment.mappingNaN(to: .infinity) + ) + + children[index].geometry = ViewGeometry( + origin: origin, + dimensions: dimensions + ) + + remainingSpace -= actualSize + processedIndices.insert(index) + } } + /// Sizes children to their ideal sizes without major axis constraints. + /// + /// - Parameters: + /// - size: The proposed size + /// - minorProposalForChild: Closure providing minor axis proposal for each child func sizeChildrenIdeally( in size: ProposedViewSize, minorProposalForChild: (Child) -> CGFloat? @@ -268,6 +498,10 @@ extension StackLayout { } } + /// Creates a proposal for placement within the given size. + /// + /// - Parameter size: The target view size + /// - Returns: The proposed size for placement func proposalWhenPlacing(in size: ViewSize) -> ProposedViewSize { let proposal = size.proposal let axis = header.pointee.majorAxis @@ -284,6 +518,11 @@ extension StackLayout { } } + /// Prioritizes children for layout calculations. + /// + /// - Parameters: + /// - children: The children to prioritize + /// - proposedSize: The proposed size context func prioritize( _ children: UnsafeMutableBufferPointer, proposedSize: ProposedViewSize @@ -291,6 +530,9 @@ extension StackLayout { _openSwiftUIUnimplementedWarning() } + /// Resizes children that have trailing overflow. + /// + /// - Parameter size: The proposed size constraint func resizeAnyChildrenWithTrailingOverflow(in size: ProposedViewSize) { _openSwiftUIUnimplementedWarning() }