From 84c03d0f640243d9aaa92a99aec939fbfd1e8dfb Mon Sep 17 00:00:00 2001 From: Luke Zhao Date: Tue, 23 Apr 2024 14:16:38 -0700 Subject: [PATCH] add OffsetRenderNode --- .../Components/Layout/Inset/Insets.swift | 1 - .../Components/Layout/Other/Offset.swift | 82 +++++++++++++++++++ .../Model/Component/Component+Modifier.swift | 9 +- 3 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 Sources/UIComponent/Components/Layout/Other/Offset.swift diff --git a/Sources/UIComponent/Components/Layout/Inset/Insets.swift b/Sources/UIComponent/Components/Layout/Inset/Insets.swift index ded69bc2..bc42ee0b 100644 --- a/Sources/UIComponent/Components/Layout/Inset/Insets.swift +++ b/Sources/UIComponent/Components/Layout/Inset/Insets.swift @@ -66,4 +66,3 @@ struct InsetsRenderNode: RenderNode { [CGPoint(x: insets.left, y: insets.top)] } } - diff --git a/Sources/UIComponent/Components/Layout/Other/Offset.swift b/Sources/UIComponent/Components/Layout/Other/Offset.swift new file mode 100644 index 00000000..19e9e649 --- /dev/null +++ b/Sources/UIComponent/Components/Layout/Other/Offset.swift @@ -0,0 +1,82 @@ +// +// File.swift +// +// +// Created by Luke Zhao on 4/23/24. +// + +import UIKit + +/// Wraps a content component and applies the specified offset to it. +/// Instead of creating an instance directly, use the ``Component/offset(_:)`` modifier. +public struct Offset: Component { + let content: any Component + let offset: CGPoint + + public init(content: any Component, offset: CGPoint) { + self.content = content + self.offset = offset + } + + public func layout(_ constraint: Constraint) -> some RenderNode { + OffsetRenderNode(content: content.layout(constraint), offset: offset) + } +} + +public struct DynamicOffset: Component { + let content: any Component + let offsetProvider: (Constraint) -> CGPoint + + public init(content: any Component, offsetProvider: @escaping (Constraint) -> CGPoint) { + self.content = content + self.offsetProvider = offsetProvider + } + + public func layout(_ constraint: Constraint) -> some RenderNode { + let offset = offsetProvider(constraint) + return OffsetRenderNode(content: content.layout(constraint), offset: offset) + } +} + +struct OffsetRenderNode: RenderNode { + typealias View = UIView + + let content: any RenderNode + let offset: CGPoint + + /// The size of the render node, adjusted for the insets. + var size: CGSize { + content.size + } + + /// The content render nodes of this render node. + var children: [any RenderNode] { + [content] + } + + /// The positions of the content render nodes within this render node. + var positions: [CGPoint] { + [offset] + } + + func visibleIndexes(in frame: CGRect) -> any Collection { + [0] + } + + func visibleRenderables(in frame: CGRect) -> [Renderable] { + var result = [Renderable]() + if shouldRenderView, frame.intersects(CGRect(origin: .zero, size: size)) { + result.append(Renderable(frame: CGRect(origin: .zero, size: size), renderNode: self, fallbackId: defaultReuseKey)) + } + let indexes = visibleIndexes(in: frame) + for i in indexes { + let child = children[i] + let position = positions[i] + let childRenderables = child.visibleRenderables(in: frame).map { + Renderable(frame: $0.frame + position, renderNode: $0.renderNode, fallbackId: "item-\(i)-\($0.fallbackId)") + } + result.append(contentsOf: childRenderables) + } + return result + } +} diff --git a/Sources/UIComponent/Core/Model/Component/Component+Modifier.swift b/Sources/UIComponent/Core/Model/Component/Component+Modifier.swift index 60c5e916..d90dd5cc 100644 --- a/Sources/UIComponent/Core/Model/Component/Component+Modifier.swift +++ b/Sources/UIComponent/Core/Model/Component/Component+Modifier.swift @@ -438,7 +438,7 @@ extension Component { /// - Parameter offset: The `CGPoint` value to apply as an offset. /// - Returns: A component offset by the specified point. public func offset(_ offset: CGPoint) -> some Component { - Insets(content: self, insets: UIEdgeInsets(top: offset.y, left: offset.x, bottom: -offset.y, right: -offset.x)) + Offset(content: self, offset: offset) } /// Applies an offset to the component using separate x and y values. @@ -447,17 +447,14 @@ extension Component { /// - y: The vertical offset. /// - Returns: A component offset by the specified x and y values. public func offset(x: CGFloat = 0, y: CGFloat = 0) -> some Component { - Insets(content: self, insets: UIEdgeInsets(top: y, left: x, bottom: -y, right: -x)) + Offset(content: self, offset: CGPoint(x: x, y: y)) } /// Applies a dynamic offset to the component based on constraints at layout time. /// - Parameter offsetProvider: A closure that provides a `CGPoint` based on the given `Constraint`. /// - Returns: A component that dynamically adjusts its offset based on the provided point. public func offset(_ offsetProvider: @escaping (Constraint) -> CGPoint) -> some Component { - DynamicInsets(content: self) { - let offset = offsetProvider($0) - return UIEdgeInsets(top: offset.y, left: offset.x, bottom: -offset.y, right: -offset.x) - } + DynamicOffset(content: self, offsetProvider: offsetProvider) } // MARK: - Visible insets modifiers