From d9cb10ba9ee4b353c5f500c2dbc38817637ed92c Mon Sep 17 00:00:00 2001 From: Andrew Watt Date: Mon, 6 Mar 2023 18:33:57 -0800 Subject: [PATCH] Refactor ContentStorage into new files --- .../Sources/Element/ContentStorage.swift | 18 + .../Sources/Element/ElementContent.swift | 571 +++--------------- .../Element/EnvironmentAdaptingStorage.swift | 64 ++ .../Sources/Element/LayoutStorage.swift | 123 ++++ BlueprintUI/Sources/Element/LazyStorage.swift | 61 ++ .../Sources/Element/MeasurableStorage.swift | 31 + .../Element/MeasureElementStorage.swift | 39 ++ .../Sources/Element/PassthroughStorage.swift | 58 ++ BlueprintUI/Sources/Internal/Measurer.swift | 8 + 9 files changed, 484 insertions(+), 489 deletions(-) create mode 100644 BlueprintUI/Sources/Element/ContentStorage.swift create mode 100644 BlueprintUI/Sources/Element/EnvironmentAdaptingStorage.swift create mode 100644 BlueprintUI/Sources/Element/LayoutStorage.swift create mode 100644 BlueprintUI/Sources/Element/LazyStorage.swift create mode 100644 BlueprintUI/Sources/Element/MeasurableStorage.swift create mode 100644 BlueprintUI/Sources/Element/MeasureElementStorage.swift create mode 100644 BlueprintUI/Sources/Element/PassthroughStorage.swift create mode 100644 BlueprintUI/Sources/Internal/Measurer.swift diff --git a/BlueprintUI/Sources/Element/ContentStorage.swift b/BlueprintUI/Sources/Element/ContentStorage.swift new file mode 100644 index 000000000..fd7d7eeff --- /dev/null +++ b/BlueprintUI/Sources/Element/ContentStorage.swift @@ -0,0 +1,18 @@ +import CoreGraphics + +/// The implementation of an `ElementContent`. +protocol ContentStorage { + var childCount: Int { get } + + func measure( + in constraint: SizeConstraint, + environment: Environment, + cache: CacheTree + ) -> CGSize + + func performLayout( + attributes: LayoutAttributes, + environment: Environment, + cache: CacheTree + ) -> [(identifier: ElementIdentifier, node: LayoutResultNode)] +} diff --git a/BlueprintUI/Sources/Element/ElementContent.swift b/BlueprintUI/Sources/Element/ElementContent.swift index 99aecb69f..ba2da988d 100644 --- a/BlueprintUI/Sources/Element/ElementContent.swift +++ b/BlueprintUI/Sources/Element/ElementContent.swift @@ -5,24 +5,6 @@ public struct ElementContent { private let storage: ContentStorage - // - // MARK: Initialization - // - - /// Initializes a new `ElementContent` with the given layout and children. - /// - /// - parameter layout: The layout to use. - /// - parameter configure: A closure that configures the layout and adds children to the container. - public init( - layout: LayoutType, - configure: (inout Builder) -> Void = { _ in } - ) { - var builder = Builder(layout: layout) - configure(&builder) - - storage = builder - } - // MARK: Measurement & Children /// Measures the required size of this element's content. @@ -49,7 +31,7 @@ public struct ElementContent { storage.measure(in: constraint, environment: environment, cache: cache) } - fileprivate func measure(in constraint: SizeConstraint, environment: Environment, cache: CacheTree) -> CGSize { + func measure(in constraint: SizeConstraint, environment: Environment, cache: CacheTree) -> CGSize { storage.measure(in: constraint, environment: environment, cache: cache) } @@ -70,34 +52,52 @@ public struct ElementContent { } } +// MARK: - Layout storage + extension ElementContent { - /// Initializes a new `ElementContent` that will lazily create its storage during a layout and measurement pass, - /// based on the `Environment` passed to the `builder` closure. - /// - /// - parameter builder: A closure that provides the content `Element` based on the provided `SizeConstraint` - /// and `Environment`. - public init( - build builder: @escaping (SizeConstraint, Environment) -> Element - ) { - storage = LazyStorage { _, size, env in - builder(size, env) + /// Used to construct elements that have layout and children. + public struct Builder { + + typealias Child = LayoutStorage.Child + + /// The layout object that is ultimately responsible for measuring + /// and layout tasks. + public var layout: LayoutType + + /// Child elements. + var children: [Child] = [] + + /// Adds the given child element. + public mutating func add( + traits: LayoutType.Traits = LayoutType.defaultTraits, + key: AnyHashable? = nil, + element: Element + ) { + let child = Child( + traits: traits, + key: key, + content: element.content, + element: element + ) + + children.append(child) } } - init( - build builder: @escaping (LayoutPhase, SizeConstraint, Environment) -> Element + /// Initializes a new `ElementContent` with the given layout and children. + /// + /// - parameter layout: The layout to use. + /// - parameter configure: A closure that configures the layout and adds children to the container. + public init( + layout: LayoutType, + configure: (inout Builder) -> Void = { _ in } ) { - storage = LazyStorage(builder: builder) - } + var builder = Builder(layout: layout) + configure(&builder) - enum LayoutPhase { - case measurement - case layout + storage = LayoutStorage(layout: layout, children: builder.children) } -} - -extension ElementContent { /// Initializes a new `ElementContent` with the given element and layout. /// @@ -113,26 +113,60 @@ extension ElementContent { $0.add(key: key, element: child) } } +} + +// MARK: - Passthrough storage + +extension ElementContent { /// Initializes a new `ElementContent` with the given element. /// /// The given element will be used for measuring, and it will always fill the extent of the parent element. /// /// - parameter element: The single child element. + public init(child: Element) { + storage = PassthroughStorage(child: child) + } +} + +// MARK: - Lazy storage + +extension ElementContent { + + /// Initializes a new `ElementContent` that will lazily create its storage during a layout and measurement pass, + /// based on the `Environment` passed to the `builder` closure. + /// + /// - parameter builder: A closure that provides the content `Element` based on the provided `SizeConstraint` + /// and `Environment`. public init( - child: Element + build builder: @escaping (SizeConstraint, Environment) -> Element ) { - storage = PassthroughStorage(child: child) + storage = LazyStorage { _, size, env in + builder(size, env) + } + } + + init( + build builder: @escaping (LayoutPhase, SizeConstraint, Environment) -> Element + ) { + storage = LazyStorage(builder: builder) } + enum LayoutPhase { + case measurement + case layout + } +} + +// MARK: - Leaf content + +extension ElementContent { + /// Initializes a new `ElementContent` with no children that delegates to the provided `Measurable`. /// /// - parameter measurable: How to measure the `ElementContent`. public init(measurable: Measurable) { - self = ElementContent( - layout: MeasurableLayout(measurable: measurable), - configure: { _ in } - ) + self = ElementContent(measureFunction: measurable.measure(in:)) } /// Initializes a new `ElementContent` with no children that delegates to the provided measure function. @@ -161,6 +195,8 @@ extension ElementContent { } } +// MARK: - Environment adapters + extension ElementContent { /// Initializes a new `ElementContent` with the given child element, measurement caching key, and environment adapter, @@ -196,9 +232,9 @@ extension ElementContent { } } +// MARK: - Nested element measuring extension ElementContent { - /// Creates a new `ElementContent` which uses the provided element to measure its /// size, but does not place the element as a child in the final, laid out hierarchy. /// @@ -207,425 +243,8 @@ extension ElementContent { public init(measuring element: Element) { storage = MeasureElementStorage(child: element) } - - private struct MeasureElementStorage: ContentStorage { - - let child: Element - - let childCount: Int = 0 - - func measure( - in constraint: SizeConstraint, - environment: Environment, - cache: CacheTree - ) -> CGSize { - cache.get(constraint) { constraint -> CGSize in - - Logger.logMeasureStart( - object: cache.signpostRef, - description: cache.name, - constraint: constraint - ) - - defer { Logger.logMeasureEnd(object: cache.signpostRef) } - - return child.content.measure( - in: constraint, - environment: environment, - cache: cache.subcache(element: child) - ) - } - } - - func performLayout( - attributes: LayoutAttributes, - environment: Environment, - cache: CacheTree - ) -> [(identifier: ElementIdentifier, node: LayoutResultNode)] { - [] - } - } } - -fileprivate protocol ContentStorage { - var childCount: Int { get } - - func measure( - in constraint: SizeConstraint, - environment: Environment, - cache: CacheTree - ) -> CGSize - - func performLayout( - attributes: LayoutAttributes, - environment: Environment, - cache: CacheTree - ) -> [(identifier: ElementIdentifier, node: LayoutResultNode)] -} - - -extension ElementContent { - - public struct Builder: ContentStorage { - - /// The layout object that is ultimately responsible for measuring - /// and layout tasks. - public var layout: LayoutType - - /// Child elements. - fileprivate var children: [Child] = [] - - init(layout: LayoutType) { - self.layout = layout - } - - /// Adds the given child element. - public mutating func add( - traits: LayoutType.Traits = LayoutType.defaultTraits, - key: AnyHashable? = nil, - element: Element - ) { - let child = Child( - traits: traits, - key: key, - content: element.content, - element: element - ) - - children.append(child) - } - - // MARK: ContentStorage - - var childCount: Int { - children.count - } - - func measure( - in constraint: SizeConstraint, - environment: Environment, - cache: CacheTree - ) -> CGSize { - cache.get(constraint) { constraint -> CGSize in - - Logger.logMeasureStart( - object: cache.signpostRef, - description: cache.name, - constraint: constraint - ) - - defer { Logger.logMeasureEnd(object: cache.signpostRef) } - - let layoutItems = self.layoutItems(in: environment, cache: cache) - return layout.measure(in: constraint, items: layoutItems) - } - } - - func performLayout( - attributes: LayoutAttributes, - environment: Environment, - cache: CacheTree - ) -> [(identifier: ElementIdentifier, node: LayoutResultNode)] { - guard children.isEmpty == false else { - return [] - } - - let layoutItems = layoutItems(in: environment, cache: cache) - let childAttributes = layout.layout(size: attributes.bounds.size, items: layoutItems) - - var result: [(identifier: ElementIdentifier, node: LayoutResultNode)] = [] - result.reserveCapacity(children.count) - - var identifierFactory = ElementIdentifier.Factory(elementCount: children.count) - - for index in 0.. [(LayoutType.Traits, Measurable)] { - - /// **Note**: We are intentionally using our `indexedMap(...)` and not `enumerated().map(...)` - /// here; because the enumerated version is about 25% slower. Because this - /// is an extremely hot codepath; this additional performance matters, so we will - /// keep track of the index ourselves. - - children.indexedMap { index, child in - let childContent = child.content - let childCache = cache.subcache( - index: index, - of: children.count, - element: child.element - ) - let measurable = Measurer { constraint -> CGSize in - childContent.measure( - in: constraint, - environment: environment, - cache: childCache - ) - } - - return (child.traits, measurable) - } - } - - fileprivate struct Child { - - var traits: LayoutType.Traits - var key: AnyHashable? - var content: ElementContent - var element: Element - - } - } -} - - -/// A storage type that simply delegates its measurement and layout to -/// another child, without any modification. -private struct PassthroughStorage: ContentStorage { - - let childCount: Int = 1 - - var child: Element - - func measure( - in constraint: SizeConstraint, - environment: Environment, - cache: CacheTree - ) -> CGSize { - - cache.get(constraint) { constraint -> CGSize in - - Logger.logMeasureStart( - object: cache.signpostRef, - description: cache.name, - constraint: constraint - ) - - defer { Logger.logMeasureEnd(object: cache.signpostRef) } - - return child.content.measure( - in: constraint, - environment: environment, - cache: cache.subcache(element: child) - ) - } - } - - func performLayout( - attributes: LayoutAttributes, - environment: Environment, - cache: CacheTree - ) -> [(identifier: ElementIdentifier, node: LayoutResultNode)] { - - let childAttributes = LayoutAttributes(size: attributes.bounds.size) - - let identifier = ElementIdentifier(elementType: type(of: child), key: nil, count: 1) - - let node = LayoutResultNode( - element: child, - layoutAttributes: childAttributes, - environment: environment, - children: child.content.performLayout( - attributes: childAttributes, - environment: environment, - cache: cache.subcache(element: child) - ) - ) - - return [(identifier, node)] - } -} - - - -private struct EnvironmentAdaptingStorage: ContentStorage { - let childCount = 1 - - /// During measurement or layout, the environment adapter will be applied - /// to the environment before passing it - /// - var adapter: (inout Environment) -> Void - - var child: Element - - func performLayout( - attributes: LayoutAttributes, - environment: Environment, - cache: CacheTree - ) -> [(identifier: ElementIdentifier, node: LayoutResultNode)] { - let environment = adapted(environment: environment) - - let childAttributes = LayoutAttributes(size: attributes.bounds.size) - - let identifier = ElementIdentifier(elementType: type(of: child), key: nil, count: 1) - - let node = LayoutResultNode( - element: child, - layoutAttributes: childAttributes, - environment: environment, - children: child.content.performLayout( - attributes: childAttributes, - environment: environment, - cache: cache.subcache(element: child) - ) - ) - - return [(identifier, node)] - } - - func measure(in constraint: SizeConstraint, environment: Environment, cache: CacheTree) -> CGSize { - cache.get(constraint) { constraint -> CGSize in - - Logger.logMeasureStart( - object: cache.signpostRef, - description: cache.name, - constraint: constraint - ) - - defer { Logger.logMeasureEnd(object: cache.signpostRef) } - - let environment = adapted(environment: environment) - - return child.content.measure( - in: constraint, - environment: environment, - cache: cache.subcache(element: child) - ) - } - } - - private func adapted(environment: Environment) -> Environment { - var environment = environment - adapter(&environment) - return environment - } -} - -/// Content storage that defers creation of its child until measurement or layout time. -private struct LazyStorage: ContentStorage { - let childCount = 1 - - var builder: (ElementContent.LayoutPhase, SizeConstraint, Environment) -> Element - - func performLayout( - attributes: LayoutAttributes, - environment: Environment, - cache: CacheTree - ) -> [(identifier: ElementIdentifier, node: LayoutResultNode)] { - let constraint = SizeConstraint(attributes.bounds.size) - let child = buildChild(for: .layout, in: constraint, environment: environment) - let childAttributes = LayoutAttributes(size: attributes.bounds.size) - - let identifier = ElementIdentifier(elementType: type(of: child), key: nil, count: 1) - - let node = LayoutResultNode( - element: child, - layoutAttributes: childAttributes, - environment: environment, - children: child.content.performLayout( - attributes: childAttributes, - environment: environment, - cache: cache.subcache(element: child) - ) - ) - - return [(identifier, node)] - } - - func measure(in constraint: SizeConstraint, environment: Environment, cache: CacheTree) -> CGSize { - cache.get(constraint) { constraint -> CGSize in - - Logger.logMeasureStart( - object: cache.signpostRef, - description: cache.name, - constraint: constraint - ) - - defer { Logger.logMeasureEnd(object: cache.signpostRef) } - - let child = buildChild(for: .measurement, in: constraint, environment: environment) - - return child.content.measure( - in: constraint, - environment: environment, - cache: cache.subcache(element: child) - ) - } - } - - private func buildChild( - for phase: ElementContent.LayoutPhase, - in constraint: SizeConstraint, - environment: Environment - ) -> Element { - builder(phase, constraint, environment) - } -} - - -private struct MeasurableStorage: ContentStorage { - - let childCount = 0 - - let measurer: (SizeConstraint, Environment) -> CGSize - - func performLayout( - attributes: LayoutAttributes, - environment: Environment, - cache: CacheTree - ) -> [(identifier: ElementIdentifier, node: LayoutResultNode)] { - [] - } - - func measure(in constraint: SizeConstraint, environment: Environment, cache: CacheTree) -> CGSize { - cache.get(constraint) { constraint in - - Logger.logMeasureStart( - object: cache.signpostRef, - description: cache.name, - constraint: constraint - ) - - defer { Logger.logMeasureEnd(object: cache.signpostRef) } - - return measurer(constraint, environment) - } - } -} - - // All layout is ultimately performed by the `Layout` protocol – this implementations delegates to a wrapped // `SingleChildLayout` implementation for use in elements with a single child. fileprivate struct SingleChildLayoutHost: Layout { @@ -649,32 +268,6 @@ fileprivate struct SingleChildLayoutHost: Layout { } } - -// Used for empty elements with an intrinsic size -fileprivate struct MeasurableLayout: Layout { - - var measurable: Measurable - - func measure(in constraint: SizeConstraint, items: [(traits: (), content: Measurable)]) -> CGSize { - precondition(items.isEmpty) - return measurable.measure(in: constraint) - } - - func layout(size: CGSize, items: [(traits: (), content: Measurable)]) -> [LayoutAttributes] { - precondition(items.isEmpty) - return [] - } - -} - -struct Measurer: Measurable { - var _measure: (SizeConstraint) -> CGSize - func measure(in constraint: SizeConstraint) -> CGSize { - _measure(constraint) - } -} - - extension Array { /// A `map` implementation that also passes the `index` of each element in the original array. diff --git a/BlueprintUI/Sources/Element/EnvironmentAdaptingStorage.swift b/BlueprintUI/Sources/Element/EnvironmentAdaptingStorage.swift new file mode 100644 index 000000000..1d36f5f59 --- /dev/null +++ b/BlueprintUI/Sources/Element/EnvironmentAdaptingStorage.swift @@ -0,0 +1,64 @@ +import CoreGraphics + +/// Content storage that applies a change to the environment. +struct EnvironmentAdaptingStorage: ContentStorage { + let childCount = 1 + + /// During measurement or layout, the environment adapter will be applied + /// to the environment before passing it + /// + var adapter: (inout Environment) -> Void + + var child: Element + + func performLayout( + attributes: LayoutAttributes, + environment: Environment, + cache: CacheTree + ) -> [(identifier: ElementIdentifier, node: LayoutResultNode)] { + let environment = adapted(environment: environment) + + let childAttributes = LayoutAttributes(size: attributes.bounds.size) + + let identifier = ElementIdentifier(elementType: type(of: child), key: nil, count: 1) + + let node = LayoutResultNode( + element: child, + layoutAttributes: childAttributes, + environment: environment, + children: child.content.performLayout( + attributes: childAttributes, + environment: environment, + cache: cache.subcache(element: child) + ) + ) + + return [(identifier, node)] + } + + func measure(in constraint: SizeConstraint, environment: Environment, cache: CacheTree) -> CGSize { + cache.get(constraint) { constraint -> CGSize in + Logger.logMeasureStart( + object: cache.signpostRef, + description: cache.name, + constraint: constraint + ) + + defer { Logger.logMeasureEnd(object: cache.signpostRef) } + + let environment = adapted(environment: environment) + + return child.content.measure( + in: constraint, + environment: environment, + cache: cache.subcache(element: child) + ) + } + } + + private func adapted(environment: Environment) -> Environment { + var environment = environment + adapter(&environment) + return environment + } +} diff --git a/BlueprintUI/Sources/Element/LayoutStorage.swift b/BlueprintUI/Sources/Element/LayoutStorage.swift new file mode 100644 index 000000000..d4dec4080 --- /dev/null +++ b/BlueprintUI/Sources/Element/LayoutStorage.swift @@ -0,0 +1,123 @@ +import Foundation + +/// Content storage that supports layout and multiple children. +struct LayoutStorage: ContentStorage { + + var layout: LayoutType + var children: [Child] + + init(layout: LayoutType, children: [Child]) { + self.layout = layout + self.children = children + } + + // MARK: ContentStorage + + var childCount: Int { + children.count + } + + func measure( + in constraint: SizeConstraint, + environment: Environment, + cache: CacheTree + ) -> CGSize { + cache.get(constraint) { constraint -> CGSize in + Logger.logMeasureStart( + object: cache.signpostRef, + description: cache.name, + constraint: constraint + ) + defer { Logger.logMeasureEnd(object: cache.signpostRef) } + + let layoutItems = self.layoutItems(in: environment, cache: cache) + return layout.measure(in: constraint, items: layoutItems) + } + } + + func performLayout( + attributes: LayoutAttributes, + environment: Environment, + cache: CacheTree + ) -> [(identifier: ElementIdentifier, node: LayoutResultNode)] { + guard children.isEmpty == false else { + return [] + } + + let layoutItems = layoutItems(in: environment, cache: cache) + let childAttributes = layout.layout(size: attributes.bounds.size, items: layoutItems) + + var result: [(identifier: ElementIdentifier, node: LayoutResultNode)] = [] + result.reserveCapacity(children.count) + + var identifierFactory = ElementIdentifier.Factory(elementCount: children.count) + + for index in 0.. [(LayoutType.Traits, Measurable)] { + + /// **Note**: We are intentionally using our `indexedMap(...)` and not `enumerated().map(...)` + /// here; because the enumerated version is about 25% slower. Because this + /// is an extremely hot codepath; this additional performance matters, so we will + /// keep track of the index ourselves. + + children.indexedMap { index, child in + let childContent = child.content + let childCache = cache.subcache( + index: index, + of: children.count, + element: child.element + ) + let measurable = Measurer { constraint -> CGSize in + childContent.measure( + in: constraint, + environment: environment, + cache: childCache + ) + } + + return (child.traits, measurable) + } + } + + struct Child { + + var traits: LayoutType.Traits + var key: AnyHashable? + var content: ElementContent + var element: Element + + } +} diff --git a/BlueprintUI/Sources/Element/LazyStorage.swift b/BlueprintUI/Sources/Element/LazyStorage.swift new file mode 100644 index 000000000..67abda33e --- /dev/null +++ b/BlueprintUI/Sources/Element/LazyStorage.swift @@ -0,0 +1,61 @@ +import CoreGraphics + +/// Content storage that defers creation of its child until measurement or layout time. +struct LazyStorage: ContentStorage { + let childCount = 1 + + var builder: (ElementContent.LayoutPhase, SizeConstraint, Environment) -> Element + + func performLayout( + attributes: LayoutAttributes, + environment: Environment, + cache: CacheTree + ) -> [(identifier: ElementIdentifier, node: LayoutResultNode)] { + let constraint = SizeConstraint(attributes.bounds.size) + let child = buildChild(for: .layout, in: constraint, environment: environment) + let childAttributes = LayoutAttributes(size: attributes.bounds.size) + + let identifier = ElementIdentifier(elementType: type(of: child), key: nil, count: 1) + + let node = LayoutResultNode( + element: child, + layoutAttributes: childAttributes, + environment: environment, + children: child.content.performLayout( + attributes: childAttributes, + environment: environment, + cache: cache.subcache(element: child) + ) + ) + + return [(identifier, node)] + } + + func measure(in constraint: SizeConstraint, environment: Environment, cache: CacheTree) -> CGSize { + cache.get(constraint) { constraint -> CGSize in + Logger.logMeasureStart( + object: cache.signpostRef, + description: cache.name, + constraint: constraint + ) + + defer { Logger.logMeasureEnd(object: cache.signpostRef) } + + let child = buildChild(for: .measurement, in: constraint, environment: environment) + + return child.content.measure( + in: constraint, + environment: environment, + cache: cache.subcache(element: child) + ) + } + } + + private func buildChild( + for phase: ElementContent.LayoutPhase, + in constraint: SizeConstraint, + environment: Environment + ) -> Element { + builder(phase, constraint, environment) + } +} diff --git a/BlueprintUI/Sources/Element/MeasurableStorage.swift b/BlueprintUI/Sources/Element/MeasurableStorage.swift new file mode 100644 index 000000000..dfccdefb6 --- /dev/null +++ b/BlueprintUI/Sources/Element/MeasurableStorage.swift @@ -0,0 +1,31 @@ +import CoreGraphics + +/// Content storage for leaf nodes. +struct MeasurableStorage: ContentStorage { + + let childCount = 0 + + let measurer: (SizeConstraint, Environment) -> CGSize + + func performLayout( + attributes: LayoutAttributes, + environment: Environment, + cache: CacheTree + ) -> [(identifier: ElementIdentifier, node: LayoutResultNode)] { + [] + } + + func measure(in constraint: SizeConstraint, environment: Environment, cache: CacheTree) -> CGSize { + cache.get(constraint) { constraint in + Logger.logMeasureStart( + object: cache.signpostRef, + description: cache.name, + constraint: constraint + ) + + defer { Logger.logMeasureEnd(object: cache.signpostRef) } + + return measurer(constraint, environment) + } + } +} diff --git a/BlueprintUI/Sources/Element/MeasureElementStorage.swift b/BlueprintUI/Sources/Element/MeasureElementStorage.swift new file mode 100644 index 000000000..1283961cf --- /dev/null +++ b/BlueprintUI/Sources/Element/MeasureElementStorage.swift @@ -0,0 +1,39 @@ +import Foundation + +struct MeasureElementStorage: ContentStorage { + + let child: Element + + let childCount: Int = 0 + + func measure( + in constraint: SizeConstraint, + environment: Environment, + cache: CacheTree + ) -> CGSize { + cache.get(constraint) { constraint -> CGSize in + + Logger.logMeasureStart( + object: cache.signpostRef, + description: cache.name, + constraint: constraint + ) + + defer { Logger.logMeasureEnd(object: cache.signpostRef) } + + return child.content.measure( + in: constraint, + environment: environment, + cache: cache.subcache(element: child) + ) + } + } + + func performLayout( + attributes: LayoutAttributes, + environment: Environment, + cache: CacheTree + ) -> [(identifier: ElementIdentifier, node: LayoutResultNode)] { + [] + } +} diff --git a/BlueprintUI/Sources/Element/PassthroughStorage.swift b/BlueprintUI/Sources/Element/PassthroughStorage.swift new file mode 100644 index 000000000..655865498 --- /dev/null +++ b/BlueprintUI/Sources/Element/PassthroughStorage.swift @@ -0,0 +1,58 @@ +import Foundation + +/// A storage type that simply delegates its measurement and layout to +/// another child, without any modification. +struct PassthroughStorage: ContentStorage { + + let childCount: Int = 1 + + var child: Element + + func measure( + in constraint: SizeConstraint, + environment: Environment, + cache: CacheTree + ) -> CGSize { + + cache.get(constraint) { constraint -> CGSize in + + Logger.logMeasureStart( + object: cache.signpostRef, + description: cache.name, + constraint: constraint + ) + + defer { Logger.logMeasureEnd(object: cache.signpostRef) } + + return child.content.measure( + in: constraint, + environment: environment, + cache: cache.subcache(element: child) + ) + } + } + + func performLayout( + attributes: LayoutAttributes, + environment: Environment, + cache: CacheTree + ) -> [(identifier: ElementIdentifier, node: LayoutResultNode)] { + + let childAttributes = LayoutAttributes(size: attributes.bounds.size) + + let identifier = ElementIdentifier(elementType: type(of: child), key: nil, count: 1) + + let node = LayoutResultNode( + element: child, + layoutAttributes: childAttributes, + environment: environment, + children: child.content.performLayout( + attributes: childAttributes, + environment: environment, + cache: cache.subcache(element: child) + ) + ) + + return [(identifier, node)] + } +} diff --git a/BlueprintUI/Sources/Internal/Measurer.swift b/BlueprintUI/Sources/Internal/Measurer.swift new file mode 100644 index 000000000..c756bb64c --- /dev/null +++ b/BlueprintUI/Sources/Internal/Measurer.swift @@ -0,0 +1,8 @@ +import CoreGraphics + +struct Measurer: Measurable { + var _measure: (SizeConstraint) -> CGSize + func measure(in constraint: SizeConstraint) -> CGSize { + _measure(constraint) + } +}