From 0a624ce4ec8fe57138371083de2068d86065a93e Mon Sep 17 00:00:00 2001 From: Luke Zhao Date: Wed, 31 Jul 2024 14:57:31 -0700 Subject: [PATCH] Add ItemOffset component --- .../Components/Layout/Other/Offset.swift | 19 ++++--- .../Layout/Stack/StackItemOffset.swift | 55 +++++++++++++++++++ .../Core/Model/RenderNode/RenderNode.swift | 3 +- 3 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 Sources/UIComponent/Components/Layout/Stack/StackItemOffset.swift diff --git a/Sources/UIComponent/Components/Layout/Other/Offset.swift b/Sources/UIComponent/Components/Layout/Other/Offset.swift index 0b3c18c..2b23faa 100644 --- a/Sources/UIComponent/Components/Layout/Other/Offset.swift +++ b/Sources/UIComponent/Components/Layout/Other/Offset.swift @@ -38,24 +38,29 @@ public struct DynamicOffset: Component { } } -struct OffsetRenderNode: RenderNode { - typealias View = UIView +public struct OffsetRenderNode: RenderNode { + public typealias View = UIView - let content: any RenderNode - let offset: CGPoint + public let content: any RenderNode + public let offset: CGPoint + + public init(content: any RenderNode, offset: CGPoint) { + self.content = content + self.offset = offset + } /// The size of the render node, adjusted for the insets. - var size: CGSize { + public var size: CGSize { content.size } /// The content render nodes of this render node. - var children: [any RenderNode] { + public var children: [any RenderNode] { [content] } /// The positions of the content render nodes within this render node. - var positions: [CGPoint] { + public var positions: [CGPoint] { [offset] } } diff --git a/Sources/UIComponent/Components/Layout/Stack/StackItemOffset.swift b/Sources/UIComponent/Components/Layout/Stack/StackItemOffset.swift new file mode 100644 index 0000000..06b4372 --- /dev/null +++ b/Sources/UIComponent/Components/Layout/Stack/StackItemOffset.swift @@ -0,0 +1,55 @@ +// Created by Luke Zhao on 7/29/24. + +import UIKit +import simd + +public struct VStackItemOffset: Component { + public let offset: CGFloat + public let stack: VStack + public init(offset: CGFloat, stack: VStack) { + self.offset = offset + self.stack = stack + } + public func layout(_ constraint: Constraint) -> some RenderNode { + let renderNode = stack.layout(constraint) + guard !renderNode.children.isEmpty else { return OffsetRenderNode(content: renderNode, offset: .zero) } + let previousIndex = Int(offset).clamp(0, stack.children.count - 1) + let nextIndex = min(previousIndex + 1, stack.children.count - 1) + let previousPosition = renderNode.positions[previousIndex].y + let nextPosition = nextIndex == previousIndex ? renderNode.positions[previousIndex].y + renderNode.children[previousIndex].size.height + stack.spacing : renderNode.positions[nextIndex].y + let interpolatedOffset = simd_mix(previousPosition, nextPosition, offset - CGFloat(previousIndex)) + return OffsetRenderNode(content: renderNode, offset: CGPoint(x: 0, y: -interpolatedOffset)) + } +} + +public extension VStack { + func offsetByItem(_ offset: CGFloat) -> some Component { + VStackItemOffset(offset: offset, stack: self) + } +} + +public struct HStackItemOffset: Component { + public let offset: CGFloat + public let stack: HStack + public init(offset: CGFloat, stack: HStack) { + self.offset = offset + self.stack = stack + } + public func layout(_ constraint: Constraint) -> some RenderNode { + let renderNode = stack.layout(constraint) + guard !renderNode.children.isEmpty else { return OffsetRenderNode(content: renderNode, offset: .zero) } + let previousIndex = Int(offset).clamp(0, stack.children.count - 1) + let nextIndex = min(previousIndex + 1, stack.children.count - 1) + let previousPosition = renderNode.positions[previousIndex].x + let nextPosition = nextIndex == previousIndex ? renderNode.positions[previousIndex].x + renderNode.children[previousIndex].size.width + stack.spacing : renderNode.positions[nextIndex].x + let interpolatedOffset = simd_mix(previousPosition, nextPosition, offset - CGFloat(previousIndex)) + return OffsetRenderNode(content: renderNode, offset: CGPoint(x: -interpolatedOffset, y: 0)) + } +} + +public extension HStack { + func offsetByItem(_ offset: CGFloat) -> some Component { + HStackItemOffset(offset: offset, stack: self) + } +} + diff --git a/Sources/UIComponent/Core/Model/RenderNode/RenderNode.swift b/Sources/UIComponent/Core/Model/RenderNode/RenderNode.swift index 9d1305d..741ee69 100644 --- a/Sources/UIComponent/Core/Model/RenderNode/RenderNode.swift +++ b/Sources/UIComponent/Core/Model/RenderNode/RenderNode.swift @@ -156,8 +156,7 @@ extension RenderNode { let frame = adjustVisibleFrame(frame: frame) let children = visibleChildren(in: frame) for child in children { - let childFrame = CGRect(origin: child.position, size: child.renderNode.size) - let childVisibleFrame = frame.intersection(childFrame) - child.position + let childVisibleFrame = frame - child.position let childRenderables = child.renderNode._visibleRenderables(in: childVisibleFrame).map { Renderable(id: $0.renderNode.id ?? "item-\(child.index)-\($0.id)", frame: $0.frame + child.position, renderNode: $0.renderNode) }