From 02c969add043d474a6cc8d4b4b4e5ef72659b320 Mon Sep 17 00:00:00 2001 From: Laszlo Teveli Date: Sun, 14 Jul 2024 12:00:00 +0200 Subject: [PATCH] Refactor --- Package.swift | 12 +++- Sources/Flow/Internal/Layout.swift | 77 ++++++++++++------------ Sources/Flow/Internal/LineBreaking.swift | 6 ++ Sources/Flow/Internal/Size.swift | 29 ++++++--- Tests/FlowTests/Utils/TestSubview.swift | 2 +- 5 files changed, 75 insertions(+), 51 deletions(-) diff --git a/Package.swift b/Package.swift index 4aa83a10..2429e53e 100644 --- a/Package.swift +++ b/Package.swift @@ -17,7 +17,15 @@ let package = Package( .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0") ], targets: [ - .target(name: "Flow", exclude: ["Example"], swiftSettings: [.swiftLanguageVersion(.v6)]), - .testTarget(name: "FlowTests", dependencies: ["Flow"], swiftSettings: [.swiftLanguageVersion(.v6)]) + .target( + name: "Flow", + exclude: ["Example"], + swiftSettings: [.swiftLanguageVersion(.v6)] + ), + .testTarget( + name: "FlowTests", + dependencies: ["Flow"], + swiftSettings: [.swiftLanguageVersion(.v6)] + ) ] ) diff --git a/Sources/Flow/Internal/Layout.swift b/Sources/Flow/Internal/Layout.swift index 2d21442a..984a1bb5 100644 --- a/Sources/Flow/Internal/Layout.swift +++ b/Sources/Flow/Internal/Layout.swift @@ -16,10 +16,10 @@ struct FlowLayout: Sendable { private struct ItemWithSpacing { var item: T var size: Size - var spacing: CGFloat = 0 + var leadingSpace: CGFloat = 0 mutating func append(_ item: Item, size: Size, spacing: CGFloat) where Self == ItemWithSpacing { - self.item.append(.init(item: item, size: size, spacing: spacing)) + self.item.append(.init(item: item, size: size, leadingSpace: spacing)) self.size = Size(breadth: self.size.breadth + spacing + size.breadth, depth: max(self.size.depth, size.depth)) } } @@ -37,11 +37,10 @@ struct FlowLayout: Sendable { guard !subviews.isEmpty else { return .zero } let lines = calculateLayout(in: proposedSize, of: subviews, cache: cache) - let spacings = lines.sum(of: \.spacing) - let size = lines + var size = lines .map(\.size) .reduce(.zero, breadth: max, depth: +) - .adding(spacings, on: .vertical) + size[.vertical] += lines.sum(of: \.leadingSpace) return CGSize(size: size, axis: axis) } @@ -54,57 +53,55 @@ struct FlowLayout: Sendable { ) { guard !subviews.isEmpty else { return } - var reversedBreadth = self.reversedBreadth var target = bounds.origin.size(on: axis) + var reversedBreadth = self.reversedBreadth + let lines = calculateLayout(in: proposal, of: subviews, cache: cache) + for line in lines { - if reversedDepth { - target.depth -= line.size.depth + line.spacing - } else { - target.depth += line.spacing - } - if reversedBreadth { - target.breadth = switch axis { - case .horizontal: bounds.maxX - case .vertical: bounds.maxY - } - } else { - target.breadth = switch axis { - case .horizontal: bounds.minX - case .vertical: bounds.minY - } - } - for item in line.item { - if reversedBreadth { - target.breadth -= item.size.breadth + item.spacing - } else { - target.breadth += item.spacing + adjust(&target, for: line, on: .vertical, reversed: reversedDepth) { target in + target.breadth = reversedBreadth ? bounds.maximumValue(on: axis) : bounds.minimumValue(on: axis) + + for item in line.item { + adjust(&target, for: item, on: .horizontal, reversed: reversedBreadth) { target in + alignAndPlace(item, in: line, at: target) + } } - alignAndPlace(item, in: line, at: target) - if !reversedBreadth { - target.breadth += item.size.breadth + + if alternatingReversedBreadth { + reversedBreadth.toggle() } } - if alternatingReversedBreadth { - reversedBreadth.toggle() - } - if !reversedDepth { - target.depth += line.size.depth - } } } + @usableFromInline func makeCache(_ subviews: some Subviews) -> FlowLayoutCache { FlowLayoutCache(subviews, axis: axis) } + private func adjust( + _ target: inout Size, + for item: ItemWithSpacing, + on axis: Axis, + reversed: Bool, + body: (inout Size) -> Void + ) { + let leadingSpace = item.leadingSpace + let size = item.size[axis] + target[axis] += reversed ? -leadingSpace-size : leadingSpace + body(&target) + target[axis] += reversed ? 0 : size + } + private func alignAndPlace( _ item: Line.Element, in line: Lines.Element, at placement: Size ) { var placement = placement - let proposedSize = ProposedViewSize(size: Size(breadth: item.size.breadth, depth: line.size.depth), axis: axis) + let size = Size(breadth: item.size.breadth, depth: line.size.depth) + let proposedSize = ProposedViewSize(size: size, axis: axis) let depth = item.size.depth if depth > 0 { placement.depth += (align(item.item.subview.dimensions(proposedSize)) / depth) * (line.size.depth - depth) @@ -167,7 +164,7 @@ struct FlowLayout: Sendable { } for index in lines.indices.dropFirst() { let spacing = self.lineSpacing ?? lineSpacings[index].distance(to: lineSpacings[index.advanced(by: -1)], along: axis.perpendicular) - lines[index].spacing = spacing + lines[index].leadingSpace = spacing } } @@ -188,7 +185,7 @@ struct FlowLayout: Sendable { .map { offset, subview in SubviewProperties( indexInLine: offset, - spacing: subview.spacing, + spacing: subview.leadingSpace, cache: subview.item.cache ) } @@ -225,7 +222,7 @@ struct FlowLayout: Sendable { if justification.isStretchingSpaces { let distributedSpace = remainingSpace / Double(count - 1) for index in line.item.indices.dropFirst() { - line.item[index].spacing += distributedSpace + line.item[index].leadingSpace += distributedSpace remainingSpace -= distributedSpace } } diff --git a/Sources/Flow/Internal/LineBreaking.swift b/Sources/Flow/Internal/LineBreaking.swift index 1300f5dd..c861df14 100644 --- a/Sources/Flow/Internal/LineBreaking.swift +++ b/Sources/Flow/Internal/LineBreaking.swift @@ -8,6 +8,9 @@ protocol LineBreaking { @usableFromInline struct FlowLineBreaker: LineBreaking { + @inlinable + init() {} + @inlinable func wrapItemsToLines(sizes: [CGFloat], spacings: [CGFloat], in availableSpace: CGFloat) -> [Int] { var breakpoints: [Int] = [] @@ -34,6 +37,9 @@ struct FlowLineBreaker: LineBreaking { @usableFromInline struct KnuthPlassLineBreaker: LineBreaking { + @inlinable + init() {} + @inlinable func wrapItemsToLines(sizes: [CGFloat], spacings: [CGFloat], in availableSpace: CGFloat) -> [Int] { let count = sizes.count diff --git a/Sources/Flow/Internal/Size.swift b/Sources/Flow/Internal/Size.swift index 2a5e662a..de7f47f9 100644 --- a/Sources/Flow/Internal/Size.swift +++ b/Sources/Flow/Internal/Size.swift @@ -17,13 +17,6 @@ struct Size: Sendable { @usableFromInline static let zero = Size(breadth: 0, depth: 0) - @usableFromInline - func adding(_ value: CGFloat, on axis: Axis) -> Size { - var size = self - size[axis] += value - return size - } - @usableFromInline subscript(axis: Axis) -> CGFloat { get { @@ -34,7 +27,8 @@ struct Size: Sendable { } } - private func keyPath(on axis: Axis) -> WritableKeyPath { + @usableFromInline + func keyPath(on axis: Axis) -> WritableKeyPath { switch axis { case .horizontal: \.breadth case .vertical: \.depth @@ -95,6 +89,7 @@ extension CGSize: FixedOrientation2DCoordinate { } } + @inlinable static var infinity: CGSize { CGSize( width: CGFloat.infinity, @@ -117,3 +112,21 @@ extension ProposedViewSize: FixedOrientation2DCoordinate { } } } + +extension CGRect { + @inlinable + func minimumValue(on axis: Axis) -> CGFloat { + switch axis { + case .horizontal: minX + case .vertical: minY + } + } + + @inlinable + func maximumValue(on axis: Axis) -> CGFloat { + switch axis { + case .horizontal: maxX + case .vertical: maxY + } + } +} diff --git a/Tests/FlowTests/Utils/TestSubview.swift b/Tests/FlowTests/Utils/TestSubview.swift index f53aa408..b1530046 100644 --- a/Tests/FlowTests/Utils/TestSubview.swift +++ b/Tests/FlowTests/Utils/TestSubview.swift @@ -2,7 +2,7 @@ import SwiftUI import XCTest @testable import Flow -final class TestSubview: Subview, CustomStringConvertible { +final class TestSubview: Flow.Subview, CustomStringConvertible { var spacing = ViewSpacing() var priority: Double = 1 var placement: (position: CGPoint, size: CGSize)?