Skip to content

Commit

Permalink
Line breaks
Browse files Browse the repository at this point in the history
  • Loading branch information
tevelee committed Dec 19, 2024
1 parent c563b27 commit c8e85a7
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 5 deletions.
3 changes: 2 additions & 1 deletion Sources/Flow/Internal/Layout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ struct FlowLayout: Sendable {
let breakpoints: [Int] = lineBreaker.wrapItemsToLines(
sizes: sizes.map(\.breadth),
spacings: spacings,
lineBreaks: cache.subviewsCache.enumerated().filter(\.element.shouldStartInNewLine).map(\.offset),
in: proposedSize.replacingUnspecifiedDimensions(by: .infinity).value(on: axis)
)

Expand All @@ -181,7 +182,7 @@ struct FlowLayout: Sendable {
for index in start ..< end {
let subview = subviews[index]
let size = sizes[index]
let spacing = index == start ? 0 : spacings[index] // Reset spacing for the first item in each line
let spacing = index == start || cache.subviewsCache[index - 1].isLineBreak ? 0 : spacings[index] // Reset spacing for the first item in each line
line.append((subview, cache.subviewsCache[index]), size: size, spacing: spacing)
}
lines.append(line)
Expand Down
31 changes: 27 additions & 4 deletions Sources/Flow/Internal/LineBreaking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import CoreFoundation
@usableFromInline
protocol LineBreaking {
@inlinable
func wrapItemsToLines(sizes: [CGFloat], spacings: [CGFloat], in availableSpace: CGFloat) -> [Int]
func wrapItemsToLines(sizes: [CGFloat], spacings: [CGFloat], lineBreaks: [Int], in availableSpace: CGFloat) -> [Int]
}

@usableFromInline
Expand All @@ -12,13 +12,13 @@ struct FlowLineBreaker: LineBreaking {
init() {}

@inlinable
func wrapItemsToLines(sizes: [CGFloat], spacings: [CGFloat], in availableSpace: CGFloat) -> [Int] {
func wrapItemsToLines(sizes: [CGFloat], spacings: [CGFloat], lineBreaks: [Int], in availableSpace: CGFloat) -> [Int] {
var breakpoints: [Int] = []
var currentLineSize: CGFloat = 0

for (index, size) in sizes.enumerated() {
let requiredSpace = spacings[index] + size
if currentLineSize + requiredSpace > availableSpace {
if currentLineSize + requiredSpace > availableSpace || lineBreaks.contains(index) {
breakpoints.append(index)
currentLineSize = size
} else {
Expand All @@ -40,8 +40,31 @@ struct KnuthPlassLineBreaker: LineBreaking {
@inlinable
init() {}

@inlinable
func wrapItemsToLines(sizes: [CGFloat], spacings: [CGFloat], lineBreaks: [Int], in availableSpace: CGFloat) -> [Int] {
if lineBreaks.isEmpty {
return wrapItemsToLines(sizes: sizes, spacings: spacings, in: availableSpace)
}
var result: [Int] = []
var start: Int = 0
for lineBreak in lineBreaks + [sizes.endIndex] {
let partial = wrapItemsToLines(
sizes: Array(sizes[start..<lineBreak]),
spacings: Array(spacings[start..<lineBreak]),
in: availableSpace
)
result.append(contentsOf: partial.map { $0 + start }.dropLast())
start = lineBreak
}
result.append(sizes.endIndex)
return result
}

@inlinable
func wrapItemsToLines(sizes: [CGFloat], spacings: [CGFloat], in availableSpace: CGFloat) -> [Int] {
if sizes.isEmpty {
return []
}
let count = sizes.count
var costs: [CGFloat] = Array(repeating: .infinity, count: count + 1)
var breaks: [Int?] = Array(repeating: nil, count: count + 1)
Expand All @@ -68,7 +91,7 @@ struct KnuthPlassLineBreaker: LineBreaking {
}

if breaks.compactMap({ $0 }).isEmpty {
return Array(0 ... sizes.endIndex)
return [0, sizes.endIndex]
}

var breakpoints: [Int] = []
Expand Down
1 change: 1 addition & 0 deletions Sources/Flow/Internal/Protocols.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ protocol Subview {
func sizeThatFits(_ proposal: ProposedViewSize) -> CGSize
func dimensions(_ proposal: ProposedViewSize) -> any Dimensions
func place(at position: CGPoint, anchor: UnitPoint, proposal: ProposedViewSize)
subscript<K: LayoutValueKey>(key: K.Type) -> K.Value { get }
}

extension LayoutSubview: Subview {
Expand Down
29 changes: 29 additions & 0 deletions Sources/Flow/Support.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public struct FlowLayoutCache {
var min: Size
var ideal: Size
var max: Size
var shouldStartInNewLine: Bool
var isLineBreak: Bool

@usableFromInline
init(_ subview: some Subview, axis: Axis) {
Expand All @@ -45,6 +47,8 @@ public struct FlowLayoutCache {
min = subview.dimensions(.zero).size(on: axis)
ideal = subview.dimensions(.unspecified).size(on: axis)
max = subview.dimensions(.infinity).size(on: axis)
shouldStartInNewLine = subview[ShouldStartInNewLine.self]
isLineBreak = subview[ShouldStartInNewLine.self]
}
}

Expand All @@ -58,3 +62,28 @@ public struct FlowLayoutCache {
}
}
}

public struct LineBreak: View {
public var body: some View {
Color.clear
.frame(width: 0, height: 0)
.layoutValue(key: IsLineBreak.self, value: true)
.startInNewLine()
}

public init() {}
}

struct ShouldStartInNewLine: LayoutValueKey {
static let defaultValue = false
}

struct IsLineBreak: LayoutValueKey {
static let defaultValue = false
}

extension View {
public func startInNewLine() -> some View {
layoutValue(key: ShouldStartInNewLine.self, value: true)
}
}

0 comments on commit c8e85a7

Please sign in to comment.