mirror of
https://github.com/divkit/divkit.git
synced 2026-06-06 20:07:59 +00:00
Add wrap container implementation
Добавил реализацию wrap в ContainerBlock, пока без снепшотов, отсматривал свои кейсы в семпл аппе.
This commit is contained in:
@@ -30,6 +30,7 @@ public struct ContainerBlockLayout {
|
||||
let children: [ContainerBlock.Child]
|
||||
let gaps: [CGFloat]
|
||||
let layoutDirection: ContainerBlock.LayoutDirection
|
||||
let layoutMode: ContainerBlock.LayoutMode
|
||||
let axialAlignment: Alignment
|
||||
let size: CGSize
|
||||
|
||||
@@ -37,6 +38,7 @@ public struct ContainerBlockLayout {
|
||||
children: [ContainerBlock.Child],
|
||||
gaps: [CGFloat],
|
||||
layoutDirection: ContainerBlock.LayoutDirection,
|
||||
layoutMode: ContainerBlock.LayoutMode,
|
||||
axialAlignment: Alignment,
|
||||
size: CGSize
|
||||
) {
|
||||
@@ -44,12 +46,22 @@ public struct ContainerBlockLayout {
|
||||
self.children = children
|
||||
self.gaps = gaps
|
||||
self.layoutDirection = layoutDirection
|
||||
self.layoutMode = layoutMode
|
||||
self.axialAlignment = axialAlignment
|
||||
self.size = size
|
||||
self.blockFrames = calculateBlockFrames()
|
||||
}
|
||||
|
||||
private func calculateBlockFrames() -> [CGRect] {
|
||||
switch layoutMode {
|
||||
case .noWrap:
|
||||
return calculateNoWrapLayoutFrames()
|
||||
case .wrap:
|
||||
return calculateWrapLayoutFrames()
|
||||
}
|
||||
}
|
||||
|
||||
private func calculateNoWrapLayoutFrames() -> [CGRect] {
|
||||
var frames = [CGRect]()
|
||||
let gapsSize = gaps.reduce(0, +)
|
||||
var shift = CGPoint(x: 0, y: 0)
|
||||
@@ -57,10 +69,11 @@ public struct ContainerBlockLayout {
|
||||
case .horizontal:
|
||||
let horizontallyResizableBlocks = children.map { $0.content }
|
||||
.filter { $0.isHorizontallyResizable }
|
||||
let widthOfHorizontallyNonResizableBlocks = widthsOfHorizontallyNonResizableBlocks.reduce(
|
||||
0,
|
||||
+
|
||||
)
|
||||
let widthOfHorizontallyNonResizableBlocks = widthsOfHorizontallyNonResizableBlocks
|
||||
.reduce(
|
||||
0,
|
||||
+
|
||||
)
|
||||
let widthAvailableForResizableBlocks = (
|
||||
size.width - widthOfHorizontallyNonResizableBlocks - gapsSize
|
||||
)
|
||||
@@ -134,6 +147,7 @@ public struct ContainerBlockLayout {
|
||||
}
|
||||
shift.y = axialAlignment.offset(forAvailableSpace: size.height, contentSize: y)
|
||||
}
|
||||
|
||||
return frames.map {
|
||||
let frame = $0.offset(by: shift).roundedToScreenScale
|
||||
precondition(frame.isValidAndFinite)
|
||||
@@ -141,6 +155,77 @@ public struct ContainerBlockLayout {
|
||||
}
|
||||
}
|
||||
|
||||
private func calculateWrapLayoutFrames() -> [CGRect] {
|
||||
#if INTERNAL_BUILD
|
||||
assert(gaps.allSatisfy(0), "You cannot use gaps in wrap container.")
|
||||
assert(
|
||||
!children
|
||||
.contains { $0.content.isHorizontallyResizable || $0.content.isVerticallyResizable
|
||||
},
|
||||
"You cannot use resizable blocks in wrap container."
|
||||
)
|
||||
#endif
|
||||
var frames = [CGRect]()
|
||||
let buildingDirectionKeyPath: KeyPath<CGSize, CGFloat>
|
||||
let transferDirectionKeyPath: KeyPath<CGSize, CGFloat>
|
||||
|
||||
switch layoutDirection {
|
||||
case .horizontal:
|
||||
buildingDirectionKeyPath = \.width
|
||||
transferDirectionKeyPath = \.height
|
||||
case .vertical:
|
||||
buildingDirectionKeyPath = \.height
|
||||
transferDirectionKeyPath = \.width
|
||||
}
|
||||
|
||||
let groups: [[(child: ContainerBlock.Child, childSize: CGSize, lineOffset: CGFloat)]] =
|
||||
children.reduce([[]]) { result, child in
|
||||
let offset = (result.last?.last?.2 ?? 0) +
|
||||
(result.last?.last?.1[keyPath: buildingDirectionKeyPath] ?? 0)
|
||||
let childSize = child.content.size(forResizableBlockSize: .zero)
|
||||
if offset + childSize[keyPath: buildingDirectionKeyPath] >
|
||||
size[keyPath: buildingDirectionKeyPath] {
|
||||
return result + [[(child, childSize, 0)]]
|
||||
} else {
|
||||
return (result.dropLast()) + [(result.last ?? []) + [(child, childSize, offset)]]
|
||||
}
|
||||
}
|
||||
|
||||
var currentLineOffset = 0.0
|
||||
groups.forEach {
|
||||
let groupHeight = $0.map(\.childSize).map(to: transferDirectionKeyPath).max() ?? 0
|
||||
let contentLength = $0.map(\.childSize).map(to: buildingDirectionKeyPath).reduce(0, +)
|
||||
|
||||
$0.forEach {
|
||||
let alignmentSpace = groupHeight - $0.childSize[keyPath: transferDirectionKeyPath]
|
||||
let alignedLineOffset = currentLineOffset + $0.child.crossAlignment
|
||||
.offset(forAvailableSpace: alignmentSpace)
|
||||
let alignedElementOffset = $0.lineOffset.advanced(by: axialAlignment.offset(
|
||||
forAvailableSpace: size[keyPath: buildingDirectionKeyPath],
|
||||
contentSize: contentLength
|
||||
).roundedToScreenScale)
|
||||
|
||||
switch layoutDirection {
|
||||
case .horizontal:
|
||||
frames
|
||||
.append(CGRect(origin: CGPoint(
|
||||
x: alignedElementOffset,
|
||||
y: alignedLineOffset
|
||||
), size: $0.childSize))
|
||||
case .vertical:
|
||||
frames
|
||||
.append(CGRect(origin: CGPoint(
|
||||
x: alignedLineOffset,
|
||||
y: alignedElementOffset
|
||||
), size: $0.childSize))
|
||||
}
|
||||
}
|
||||
|
||||
currentLineOffset += groupHeight
|
||||
}
|
||||
return frames
|
||||
}
|
||||
|
||||
public private(set) var blockFrames: [CGRect] = []
|
||||
|
||||
public var leftInset: CGFloat {
|
||||
|
||||
@@ -18,6 +18,11 @@ public final class ContainerBlock: BlockWithLayout {
|
||||
/// Child blocks are laid out vertically one after another
|
||||
case vertical
|
||||
}
|
||||
|
||||
public enum LayoutMode {
|
||||
case wrap
|
||||
case noWrap
|
||||
}
|
||||
|
||||
public struct Child: Equatable {
|
||||
public var content: Block
|
||||
@@ -44,6 +49,7 @@ public final class ContainerBlock: BlockWithLayout {
|
||||
}
|
||||
|
||||
public let layoutDirection: LayoutDirection
|
||||
public let layoutMode: LayoutMode
|
||||
public let widthTrait: LayoutTrait
|
||||
public let heightTrait: LayoutTrait
|
||||
public let axialAlignment: Alignment
|
||||
@@ -59,6 +65,7 @@ public final class ContainerBlock: BlockWithLayout {
|
||||
|
||||
public init(
|
||||
layoutDirection: LayoutDirection,
|
||||
layoutMode: LayoutMode = .noWrap,
|
||||
widthTrait: LayoutTrait = .resizable,
|
||||
heightTrait: LayoutTrait = .intrinsic,
|
||||
axialAlignment: Alignment = .leading,
|
||||
@@ -80,6 +87,7 @@ public final class ContainerBlock: BlockWithLayout {
|
||||
}
|
||||
|
||||
self.layoutDirection = layoutDirection
|
||||
self.layoutMode = layoutMode
|
||||
self.widthTrait = widthTrait
|
||||
self.heightTrait = heightTrait
|
||||
self.axialAlignment = axialAlignment
|
||||
@@ -273,6 +281,7 @@ public final class ContainerBlock: BlockWithLayout {
|
||||
children: children,
|
||||
gaps: gaps,
|
||||
layoutDirection: layoutDirection,
|
||||
layoutMode: layoutMode,
|
||||
axialAlignment: axialAlignment,
|
||||
size: size
|
||||
)
|
||||
|
||||
@@ -6,6 +6,7 @@ import CommonCore
|
||||
extension ContainerBlock {
|
||||
public func modifying(
|
||||
layoutDirection: LayoutDirection? = nil,
|
||||
layoutMode: LayoutMode? = nil,
|
||||
widthTrait: LayoutTrait? = nil,
|
||||
heightTrait: LayoutTrait? = nil,
|
||||
axialAlignment: Alignment? = nil,
|
||||
@@ -19,6 +20,7 @@ extension ContainerBlock {
|
||||
) throws -> ContainerBlock {
|
||||
try ContainerBlock(
|
||||
layoutDirection: layoutDirection ?? self.layoutDirection,
|
||||
layoutMode: layoutMode ?? self.layoutMode,
|
||||
widthTrait: widthTrait ?? self.widthTrait,
|
||||
heightTrait: heightTrait ?? self.heightTrait,
|
||||
axialAlignment: axialAlignment ?? self.axialAlignment,
|
||||
@@ -34,6 +36,7 @@ extension ContainerBlock {
|
||||
|
||||
public convenience init(
|
||||
layoutDirection: LayoutDirection,
|
||||
layoutMode: LayoutMode = .noWrap,
|
||||
widthTrait: LayoutTrait = .resizable,
|
||||
heightTrait: LayoutTrait = .intrinsic,
|
||||
horizontalChildrenAlignment: Alignment = .leading,
|
||||
@@ -64,6 +67,7 @@ extension ContainerBlock {
|
||||
|
||||
try self.init(
|
||||
layoutDirection: layoutDirection,
|
||||
layoutMode: layoutMode,
|
||||
widthTrait: widthTrait,
|
||||
heightTrait: heightTrait,
|
||||
axialAlignment: axialAlignment,
|
||||
|
||||
+1
@@ -9,6 +9,7 @@ extension ContainerBlock: ImageRenderableBlock {
|
||||
children: children,
|
||||
gaps: gaps,
|
||||
layoutDirection: layoutDirection,
|
||||
layoutMode: layoutMode,
|
||||
axialAlignment: axialAlignment,
|
||||
size: rect.size
|
||||
).blockFrames
|
||||
|
||||
@@ -26,6 +26,7 @@ extension ContainerBlock {
|
||||
children: children,
|
||||
gaps: gaps,
|
||||
layoutDirection: layoutDirection,
|
||||
layoutMode: layoutMode,
|
||||
axialAlignment: axialAlignment,
|
||||
contentAnimation: contentAnimation,
|
||||
anchorPoint: anchorPoint,
|
||||
@@ -49,6 +50,7 @@ private final class ContainerBlockView: UIView, BlockViewProtocol, VisibleBounds
|
||||
let children: [ContainerBlock.Child]
|
||||
let gaps: [CGFloat]
|
||||
let layoutDirection: ContainerBlock.LayoutDirection
|
||||
let layoutMode: ContainerBlock.LayoutMode
|
||||
let axialAlignment: Alignment
|
||||
let contentAnimation: BlockAnimation?
|
||||
let anchorPoint: AnchorPoint
|
||||
@@ -131,6 +133,7 @@ private final class ContainerBlockView: UIView, BlockViewProtocol, VisibleBounds
|
||||
children: model.children,
|
||||
gaps: model.gaps,
|
||||
layoutDirection: model.layoutDirection,
|
||||
layoutMode: model.layoutMode,
|
||||
axialAlignment: model.axialAlignment,
|
||||
size: bounds.size
|
||||
)
|
||||
|
||||
@@ -17,6 +17,7 @@ extension ContainerBlockLayout {
|
||||
children: blocks.map { .init(content: $0, crossAlignment: crossAlignment) },
|
||||
gaps: gaps,
|
||||
layoutDirection: layoutDirection,
|
||||
layoutMode: .noWrap,
|
||||
axialAlignment: axialAlignment,
|
||||
size: size
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user