diff --git a/BlueprintUI/Sources/Element/ElementContent.swift b/BlueprintUI/Sources/Element/ElementContent.swift index 6cb0f871b..ffb49fcc6 100644 --- a/BlueprintUI/Sources/Element/ElementContent.swift +++ b/BlueprintUI/Sources/Element/ElementContent.swift @@ -304,12 +304,14 @@ fileprivate struct SingleChildLayoutHost: Layo func sizeThatFits( proposal: SizeConstraint, subelements: Subelements, + environment: Environment, cache: inout Cache ) -> CGSize { precondition(subelements.count == 1) return wrapped.sizeThatFits( proposal: proposal, subelement: subelements[0], + environment: environment, cache: &cache ) } @@ -317,19 +319,21 @@ fileprivate struct SingleChildLayoutHost: Layo func placeSubelements( in size: CGSize, subelements: Subelements, + environment: Environment, cache: inout Cache ) { precondition(subelements.count == 1) wrapped.placeSubelement( in: size, subelement: subelements[0], + environment: environment, cache: &cache ) } - func makeCache(subelements: Subelements) -> Cache { + func makeCache(subelements: Subelements, environment: Environment) -> Cache { precondition(subelements.count == 1) - return wrapped.makeCache(subelement: subelements[0]) + return wrapped.makeCache(subelement: subelements[0], environment: environment) } } diff --git a/BlueprintUI/Sources/Element/LayoutStorage.swift b/BlueprintUI/Sources/Element/LayoutStorage.swift index c869b4dcf..3005955d6 100644 --- a/BlueprintUI/Sources/Element/LayoutStorage.swift +++ b/BlueprintUI/Sources/Element/LayoutStorage.swift @@ -151,12 +151,13 @@ extension LayoutStorage: CaffeinatedContentStorage { let subelements = subelements(from: context.node, environment: context.environment) var associatedCache = context.node.associatedCache { - layout.makeCache(subelements: subelements) + layout.makeCache(subelements: subelements, environment: environment) } let size = layout.sizeThatFits( proposal: proposal, subelements: subelements, + environment: context.environment, cache: &associatedCache ) @@ -174,12 +175,13 @@ extension LayoutStorage: CaffeinatedContentStorage { let subelements = subelements(from: context.node, environment: context.environment) var associatedCache = context.node.associatedCache { - layout.makeCache(subelements: subelements) + layout.makeCache(subelements: subelements, environment: environment) } layout.placeSubelements( in: frame.size, subelements: subelements, + environment: context.environment, cache: &associatedCache ) diff --git a/BlueprintUI/Sources/Layout/Aligned.swift b/BlueprintUI/Sources/Layout/Aligned.swift index c0da47885..bd37a6fc0 100644 --- a/BlueprintUI/Sources/Layout/Aligned.swift +++ b/BlueprintUI/Sources/Layout/Aligned.swift @@ -114,11 +114,21 @@ public struct Aligned: Element { return attributes } - func sizeThatFits(proposal: SizeConstraint, subelement: LayoutSubelement, cache: inout Cache) -> CGSize { + func sizeThatFits( + proposal: SizeConstraint, + subelement: Subelement, + environment: Environment, + cache: inout Cache + ) -> CGSize { subelement.sizeThatFits(proposal) } - func placeSubelement(in size: CGSize, subelement: LayoutSubelement, cache: inout ()) { + func placeSubelement( + in size: CGSize, + subelement: Subelement, + environment: Environment, + cache: inout () + ) { let x: CGFloat let y: CGFloat diff --git a/BlueprintUI/Sources/Layout/ConstrainedAspectRatio.swift b/BlueprintUI/Sources/Layout/ConstrainedAspectRatio.swift index ec442a8ed..014b723ac 100644 --- a/BlueprintUI/Sources/Layout/ConstrainedAspectRatio.swift +++ b/BlueprintUI/Sources/Layout/ConstrainedAspectRatio.swift @@ -159,7 +159,12 @@ public struct ConstrainedAspectRatio: Element { LayoutAttributes(size: size) } - func sizeThatFits(proposal: SizeConstraint, subelement: LayoutSubelement, cache: inout Cache) -> CGSize { + func sizeThatFits( + proposal: SizeConstraint, + subelement: Subelement, + environment: Environment, + cache: inout Cache + ) -> CGSize { let contentSize = subelement.sizeThatFits(proposal) return contentMode.constrain( contentSize: contentSize, @@ -168,7 +173,12 @@ public struct ConstrainedAspectRatio: Element { ) } - func placeSubelement(in size: CGSize, subelement: LayoutSubelement, cache: inout ()) { + func placeSubelement( + in size: CGSize, + subelement: Subelement, + environment: Environment, + cache: inout () + ) { subelement.place(filling: size) } } diff --git a/BlueprintUI/Sources/Layout/ConstrainedSize.swift b/BlueprintUI/Sources/Layout/ConstrainedSize.swift index d0437f052..6ac8d58f0 100644 --- a/BlueprintUI/Sources/Layout/ConstrainedSize.swift +++ b/BlueprintUI/Sources/Layout/ConstrainedSize.swift @@ -208,7 +208,12 @@ extension ConstrainedSize { LayoutAttributes(size: size) } - func sizeThatFits(proposal: SizeConstraint, subelement: LayoutSubelement, cache: inout Cache) -> CGSize { + func sizeThatFits( + proposal: SizeConstraint, + subelement: Subelement, + environment: Environment, + cache: inout Cache + ) -> CGSize { if case let .absolute(width) = width, case let .absolute(height) = height { return CGSize(width: width, height: height) } @@ -225,7 +230,12 @@ extension ConstrainedSize { ) } - func placeSubelement(in size: CGSize, subelement: LayoutSubelement, cache: inout ()) { + func placeSubelement( + in size: CGSize, + subelement: Subelement, + environment: Environment, + cache: inout () + ) { subelement.place(filling: size) } } diff --git a/BlueprintUI/Sources/Layout/EqualStack.swift b/BlueprintUI/Sources/Layout/EqualStack.swift index 25cd7a197..09673ea4e 100644 --- a/BlueprintUI/Sources/Layout/EqualStack.swift +++ b/BlueprintUI/Sources/Layout/EqualStack.swift @@ -153,7 +153,12 @@ extension EqualStack { return result } - func sizeThatFits(proposal: SizeConstraint, subelements: Subelements, cache: inout Cache) -> CGSize { + func sizeThatFits( + proposal: SizeConstraint, + subelements: Subelements, + environment: Environment, + cache: inout Cache + ) -> CGSize { guard subelements.count > 0 else { return .zero } let totalSpacing = (spacing * CGFloat(subelements.count - 1)) @@ -192,7 +197,12 @@ extension EqualStack { return totalSize } - func placeSubelements(in size: CGSize, subelements: Subelements, cache: inout ()) { + func placeSubelements( + in size: CGSize, + subelements: Subelements, + environment: Environment, + cache: inout () + ) { guard subelements.count > 0 else { return } let totalSpacing = (spacing * CGFloat(subelements.count - 1)) diff --git a/BlueprintUI/Sources/Layout/GridRow.swift b/BlueprintUI/Sources/Layout/GridRow.swift index 49ba5ca5c..e2d4b4a0f 100644 --- a/BlueprintUI/Sources/Layout/GridRow.swift +++ b/BlueprintUI/Sources/Layout/GridRow.swift @@ -130,7 +130,12 @@ extension GridRow { } } - func sizeThatFits(proposal: SizeConstraint, subelements: Subelements, cache: inout Cache) -> CGSize { + func sizeThatFits( + proposal: SizeConstraint, + subelements: Subelements, + environment: Environment, + cache: inout Cache + ) -> CGSize { guard subelements.count > 0 else { return .zero } @@ -147,7 +152,12 @@ extension GridRow { return size } - func placeSubelements(in size: CGSize, subelements: Subelements, cache: inout ()) { + func placeSubelements( + in size: CGSize, + subelements: Subelements, + environment: Environment, + cache: inout () + ) { guard subelements.count > 0 else { return } diff --git a/BlueprintUI/Sources/Layout/Hidden.swift b/BlueprintUI/Sources/Layout/Hidden.swift index a058df23e..55714ad91 100644 --- a/BlueprintUI/Sources/Layout/Hidden.swift +++ b/BlueprintUI/Sources/Layout/Hidden.swift @@ -33,11 +33,21 @@ public struct Hidden: Element { return attributes } - func sizeThatFits(proposal: SizeConstraint, subelement: LayoutSubelement, cache: inout Cache) -> CGSize { + func sizeThatFits( + proposal: SizeConstraint, + subelement: Subelement, + environment: Environment, + cache: inout Cache + ) -> CGSize { subelement.sizeThatFits(proposal) } - func placeSubelement(in size: CGSize, subelement: LayoutSubelement, cache: inout ()) { + func placeSubelement( + in size: CGSize, + subelement: Subelement, + environment: Environment, + cache: inout () + ) { subelement.attributes.isHidden = isHidden } } diff --git a/BlueprintUI/Sources/Layout/Inset.swift b/BlueprintUI/Sources/Layout/Inset.swift index 95da224a0..be4b34473 100644 --- a/BlueprintUI/Sources/Layout/Inset.swift +++ b/BlueprintUI/Sources/Layout/Inset.swift @@ -159,13 +159,23 @@ extension Inset { return LayoutAttributes(frame: frame) } - func sizeThatFits(proposal: SizeConstraint, subelement: LayoutSubelement, cache: inout ()) -> CGSize { + func sizeThatFits( + proposal: SizeConstraint, + subelement: Subelement, + environment: Environment, + cache: inout () + ) -> CGSize { let insetProposal = proposal.inset(by: edgeInsets) let childSize = subelement.sizeThatFits(insetProposal) return childSize + CGSize(width: left + right, height: top + bottom) } - func placeSubelement(in size: CGSize, subelement: LayoutSubelement, cache: inout ()) { + func placeSubelement( + in size: CGSize, + subelement: Subelement, + environment: Environment, + cache: inout () + ) { let insetSize = size.inset(by: edgeInsets) subelement.place( diff --git a/BlueprintUI/Sources/Layout/Keyed.swift b/BlueprintUI/Sources/Layout/Keyed.swift index 09ec33ffa..34dd4f381 100644 --- a/BlueprintUI/Sources/Layout/Keyed.swift +++ b/BlueprintUI/Sources/Layout/Keyed.swift @@ -70,11 +70,21 @@ public struct Keyed: Element { LayoutAttributes(size: size) } - func sizeThatFits(proposal: SizeConstraint, subelement: LayoutSubelement, cache: inout Cache) -> CGSize { + func sizeThatFits( + proposal: SizeConstraint, + subelement: Subelement, + environment: Environment, + cache: inout Cache + ) -> CGSize { subelement.sizeThatFits(proposal) } - func placeSubelement(in size: CGSize, subelement: LayoutSubelement, cache: inout ()) { + func placeSubelement( + in size: CGSize, + subelement: Subelement, + environment: Environment, + cache: inout () + ) { subelement.place(at: .zero, size: size) } } diff --git a/BlueprintUI/Sources/Layout/Layout.swift b/BlueprintUI/Sources/Layout/Layout.swift index 4dc3291a7..1217af798 100644 --- a/BlueprintUI/Sources/Layout/Layout.swift +++ b/BlueprintUI/Sources/Layout/Layout.swift @@ -7,10 +7,10 @@ import CoreGraphics /// layout container by creating a type that conforms to the ``Layout`` protocol and implementing /// its required methods: /// -/// - ``CaffeinatedLayout/sizeThatFits(proposal:subelements:cache:)`` reports the sizes of the -/// composite layout. -/// - ``CaffeinatedLayout/placeSubelements(in:subelements:cache:)`` assigns positions to the -/// container's subelements. +/// - ``CaffeinatedLayout/sizeThatFits(proposal:subelements:environment:cache:)`` reports the sizes +/// of the composite layout. +/// - ``CaffeinatedLayout/placeSubelements(in:subelements:environment:cache:)`` assigns positions to +/// the container's subelements. /// /// You can define a basic layout type with only these two methods (see note below): /// @@ -81,7 +81,7 @@ import CoreGraphics /// This enables you to measure subelements before you commit to placing them. You also assign a /// position to each subelement by calling its proxy’s ``LayoutSubelement/place(at:anchor:size:)`` /// method. Call the method on each subelement from within your implementation of the layout’s -/// ``CaffeinatedLayout/placeSubelements(in:subelements:cache:)`` method. +/// ``CaffeinatedLayout/placeSubelements(in:subelements:environment:cache:)`` method. /// /// ## Access layout traits /// @@ -151,7 +151,7 @@ public protocol CaffeinatedLayout { /// as your data storage type. Alternatively, you can refer to the data storage type directly in /// all the places where you work with the cache. /// - /// See ``makeCache(subelements:)-4kmy2`` for more information. + /// See ``makeCache(subelements:environment:)-8ciko`` for more information. associatedtype Cache = Void /// Returns the size of the composite element, given a proposed size constraint and the @@ -169,8 +169,8 @@ public protocol CaffeinatedLayout { /// /// For performance reasons, the layout engine may deduce the measurement of your container for /// some constraint values without explicitly calling - /// ``sizeThatFits(proposal:subelements:cache:)``. To ensure that the deduced value is correct, - /// your layout must follow some ground rules: + /// ``sizeThatFits(proposal:subelements:environment:cache:)``. To ensure that the deduced value + /// is correct, your layout must follow some ground rules: /// /// 1. **Given one fixed constraint axis, the element's growth along the other axis should be /// _monotonic_.** That is, an element can grow when given a larger constraint, or shrink @@ -203,13 +203,17 @@ public protocol CaffeinatedLayout { /// - subelements: A collection of proxies that represent the elements that the container /// arranges. You can use the proxies in the collection to get information about the /// subelements as you determine how much space the container needs to display them. + /// - environment: The environment of the container. You can use properties from the + /// environment when calculating the size of this container, as long as you adhere to the + /// sizing rules. /// - cache: Optional storage for calculated data that you can share among the methods of your - /// custom layout container. See ``makeCache(subelements:)-4kmy2`` for details. + /// custom layout container. See ``makeCache(subelements:environment:)-8ciko`` for details. /// - Returns: A size that indicates how much space the container needs to arrange its /// subelements. func sizeThatFits( proposal: SizeConstraint, subelements: Subelements, + environment: Environment, cache: inout Cache ) -> CGSize @@ -226,20 +230,24 @@ public protocol CaffeinatedLayout { /// Be sure that you use computations during placement that are consistent with those in your /// implementation of other protocol methods for a given set of inputs. For example, if you add /// spacing during placement, make sure your implementation of - /// ``sizeThatFits(proposal:subelements:cache:)`` accounts for the extra space. + /// ``sizeThatFits(proposal:subelements:environment:cache:)`` accounts for the extra space. /// /// - Parameters: /// - size: The region that the container's parent allocates to the container. Place all the /// container's subelements within the region. The size of this region may not match any - /// size that was returned from a call to ``sizeThatFits(proposal:subelements:cache:)``. + /// size that was returned from a call to + /// ``sizeThatFits(proposal:subelements:environment:cache:)``. /// - subelements: A collection of proxies that represent the elements that the container /// arranges. Use the proxies in the collection to get information about the subelements and /// to tell the subelements where to appear. + /// - environment: The environment of this container. You can use properties from the + /// environment to vary the placement of subelements. /// - cache: Optional storage for calculated data that you can share among the methods of your - /// custom layout container. See ``makeCache(subelements:)-4kmy2`` for details. + /// custom layout container. See ``makeCache(subelements:environment:)-8ciko`` for details. func placeSubelements( in size: CGSize, subelements: Subelements, + environment: Environment, cache: inout Cache ) @@ -252,14 +260,15 @@ public protocol CaffeinatedLayout { /// method if you don’t need a cache. /// /// However you might find a cache useful when the layout container repeats complex intermediate - /// calculations across calls to ``sizeThatFits(proposal:subelements:cache:)`` and - /// ``placeSubelements(in:subelements:cache:)``. You might be able to improve performance by - /// calculating values once and storing them in a cache. + /// calculations across calls to ``sizeThatFits(proposal:subelements:environment:cache:)`` and + /// ``placeSubelements(in:subelements:environment:cache:)``. You might be able to improve + /// performance by calculating values once and storing them in a cache. /// /// - Note: A cache's lifetime is limited to a single render pass, so you cannot use it to store - /// values across multiple calls to ``placeSubelements(in:subelements:cache:)``. A render pass - /// includes zero, one, or many calls to ``sizeThatFits(proposal:subelements:cache:)``, - /// followed by a single call to ``placeSubelements(in:subelements:cache:)``. + /// values across multiple calls to ``placeSubelements(in:subelements:environment:cache:)``. A + /// render pass includes zero, one, or many calls to + /// ``sizeThatFits(proposal:subelements:environment:cache:)``, followed by a single call to + /// ``placeSubelements(in:subelements:environment:cache:)``. /// /// Only implement a cache if profiling shows that it improves performance. /// @@ -283,11 +292,12 @@ public protocol CaffeinatedLayout { /// - Parameter subelements: A collection of proxy instances that represent the subelements that /// the container arranges. You can use the proxies in the collection to get information about /// the subelements as you calculate values to store in the cache. + /// - Parameter environment: The environment of this container. /// - Returns: Storage for calculated data that you share among the methods of your custom /// layout container. - func makeCache(subelements: Subelements) -> Cache + func makeCache(subelements: Subelements, environment: Environment) -> Cache } extension CaffeinatedLayout where Cache == () { - public func makeCache(subelements: Subelements) { () } + public func makeCache(subelements: Subelements, environment: Environment) { () } } diff --git a/BlueprintUI/Sources/Layout/LayoutSubelement.swift b/BlueprintUI/Sources/Layout/LayoutSubelement.swift index 001c87077..33e27c223 100644 --- a/BlueprintUI/Sources/Layout/LayoutSubelement.swift +++ b/BlueprintUI/Sources/Layout/LayoutSubelement.swift @@ -14,8 +14,8 @@ public typealias LayoutSubelements = [LayoutSubelement] /// Use this proxy to get information about the associated element, like its size and traits. You /// should also use the proxy to tell its corresponding element where to appear by calling the /// proxy’s ``place(at:anchor:size:)`` method. Do this once for each subview from your -/// implementation of the layout’s ``CaffeinatedLayout/placeSubelements(in:subelements:cache:)`` -/// method. +/// implementation of the layout’s +/// ``CaffeinatedLayout/placeSubelements(in:subelements:environment:cache:)`` method. /// /// - Note: The ``LayoutSubelement`` API, and its documentation, are modeled after SwiftUI's /// [LayoutSubview](https://developer.apple.com/documentation/swiftui/layoutsubview), with major @@ -55,10 +55,10 @@ public struct LayoutSubelement { /// Assigns a position and size to a subelement. /// /// Call this method from your implementation of the `Layout` protocol’s - /// ``CaffeinatedLayout/placeSubelements(in:subelements:cache:)`` method for each subelement - /// arranged by the layout. Provide a position within the container’s bounds where the - /// subelement should appear, an anchor that indicates which part of the subelement appears at - /// that point, and a size. + /// ``CaffeinatedLayout/placeSubelements(in:subelements:environment:cache:)`` method for each + /// subelement arranged by the layout. Provide a position within the container’s bounds where + /// the subelement should appear, an anchor that indicates which part of the subelement appears + /// at that point, and a size. /// /// To learn the subelement's preferred size for a given proposal before calling this method, /// you can call ``sizeThatFits(_:)`` method on the subelement. diff --git a/BlueprintUI/Sources/Layout/LayoutWriter.swift b/BlueprintUI/Sources/Layout/LayoutWriter.swift index 1eb914ab7..eb858171f 100644 --- a/BlueprintUI/Sources/Layout/LayoutWriter.swift +++ b/BlueprintUI/Sources/Layout/LayoutWriter.swift @@ -273,11 +273,21 @@ extension LayoutWriter { } } - func sizeThatFits(proposal: SizeConstraint, subelements: Subelements, cache: inout Cache) -> CGSize { + func sizeThatFits( + proposal: SizeConstraint, + subelements: Subelements, + environment: Environment, + cache: inout Cache + ) -> CGSize { builder.sizing.measure(with: builder) } - func placeSubelements(in size: CGSize, subelements: Subelements, cache: inout ()) { + func placeSubelements( + in size: CGSize, + subelements: Subelements, + environment: Environment, + cache: inout () + ) { for (child, subelement) in zip(builder.children, subelements) { subelement.place( at: child.frame.origin, diff --git a/BlueprintUI/Sources/Layout/Opacity.swift b/BlueprintUI/Sources/Layout/Opacity.swift index 03f790606..b5aca28c7 100644 --- a/BlueprintUI/Sources/Layout/Opacity.swift +++ b/BlueprintUI/Sources/Layout/Opacity.swift @@ -43,11 +43,21 @@ public struct Opacity: Element { return attributes } - func sizeThatFits(proposal: SizeConstraint, subelement: LayoutSubelement, cache: inout ()) -> CGSize { + func sizeThatFits( + proposal: SizeConstraint, + subelement: Subelement, + environment: Environment, + cache: inout () + ) -> CGSize { subelement.sizeThatFits(proposal) } - func placeSubelement(in size: CGSize, subelement: LayoutSubelement, cache: inout ()) { + func placeSubelement( + in size: CGSize, + subelement: Subelement, + environment: Environment, + cache: inout () + ) { subelement.attributes.alpha = opacity } } diff --git a/BlueprintUI/Sources/Layout/Overlay.swift b/BlueprintUI/Sources/Layout/Overlay.swift index 47ed66b6b..0694a4a9e 100644 --- a/BlueprintUI/Sources/Layout/Overlay.swift +++ b/BlueprintUI/Sources/Layout/Overlay.swift @@ -78,7 +78,12 @@ fileprivate struct OverlayLayout: Layout { ) } - func sizeThatFits(proposal: SizeConstraint, subelements: Subelements, cache: inout Cache) -> CGSize { + func sizeThatFits( + proposal: SizeConstraint, + subelements: Subelements, + environment: Environment, + cache: inout Cache + ) -> CGSize { subelements.reduce(into: CGSize.zero) { result, subelement in let measuredSize = subelement.sizeThatFits(proposal) result.width = max(result.width, measuredSize.width) @@ -86,7 +91,12 @@ fileprivate struct OverlayLayout: Layout { } } - func placeSubelements(in size: CGSize, subelements: Subelements, cache: inout ()) { + func placeSubelements( + in size: CGSize, + subelements: Subelements, + environment: Environment, + cache: inout () + ) { for subelement in subelements { subelement.place(filling: size) } diff --git a/BlueprintUI/Sources/Layout/SingleChildLayout.swift b/BlueprintUI/Sources/Layout/SingleChildLayout.swift index 47a2026ab..1958fc063 100644 --- a/BlueprintUI/Sources/Layout/SingleChildLayout.swift +++ b/BlueprintUI/Sources/Layout/SingleChildLayout.swift @@ -40,7 +40,7 @@ public protocol CaffeinatedSingleChildLayout { /// as your data storage type. Alternatively, you can refer to the data storage type directly in /// all the places where you work with the cache. /// - /// See ``makeCache(subelement:)-33y4l`` for more information. + /// See ``makeCache(subelement:environment:)-8vyl9`` for more information. associatedtype Cache = Void /// Returns the size of the element, given a proposed size constraint and the container's @@ -54,7 +54,8 @@ public protocol CaffeinatedSingleChildLayout { /// In Blueprint, parents ultimately choose the size of their children, so the actual size that /// this container is laid out in may not be a size that was returned from this method. /// - /// For more information, see ``CaffeinatedLayout/sizeThatFits(proposal:subelements:cache:)``. + /// For more information, see + /// ``CaffeinatedLayout/sizeThatFits(proposal:subelements:environment:cache:)``. /// /// - Parameters: /// - proposal: A size constraint for the container. The container's parent element that calls @@ -63,13 +64,17 @@ public protocol CaffeinatedSingleChildLayout { /// - subelement: A proxy that represents the element that the container arranges. You can use /// the proxy to get information about the subelement as you determine how much space the /// container needs to display it. + /// - environment: The environment of the container. You can use properties from the + /// environment when calculating the size of this container, as long as you adhere to the + /// sizing rules. /// - cache: Optional storage for calculated data that you can share among the methods of your - /// custom layout container. See ``makeCache(subelement:)-33y4l`` for details. + /// custom layout container. See ``makeCache(subelement:environment:)-8vyl9`` for details. /// - Returns: A size that indicates how much space the container needs to arrange its /// subelement. func sizeThatFits( proposal: SizeConstraint, subelement: Subelement, + environment: Environment, cache: inout Cache ) -> CGSize @@ -85,20 +90,24 @@ public protocol CaffeinatedSingleChildLayout { /// Be sure that you use computations during placement that are consistent with those in your /// implementation of other protocol methods for a given set of inputs. For example, if you add /// spacing during placement, make sure your implementation of - /// ``sizeThatFits(proposal:subelement:cache:)`` accounts for the extra space. + /// ``sizeThatFits(proposal:subelement:environment:cache:)`` accounts for the extra space. /// /// - Parameters: /// - size: The region that the container's parent allocates to the container. Place the /// container's subelement within the region. The size of this region may not match any size - /// that was returned from a call to ``sizeThatFits(proposal:subelement:cache:)``. + /// that was returned from a call to + /// ``sizeThatFits(proposal:subelement:environment:cache:)``. /// - subelement: A proxy that represents the element that the container arranges. Use the /// proxy to get information about the subelement and to tell the subelement where to /// appear. + /// - environment: The environment of this container. You can use properties from the + /// environment to vary the placement of the subelement. /// - cache: Optional storage for calculated data that you can share among the methods of your - /// custom layout container. See ``makeCache(subelement:)-33y4l`` for details. + /// custom layout container. See ``makeCache(subelement:environment:)-8vyl9`` for details. func placeSubelement( in size: CGSize, subelement: Subelement, + environment: Environment, cache: inout Cache ) @@ -111,27 +120,29 @@ public protocol CaffeinatedSingleChildLayout { /// method if you don’t need a cache. /// /// However you might find a cache useful when the layout container repeats complex intermediate - /// calculations across calls to ``sizeThatFits(proposal:subelement:cache:)`` and - /// ``placeSubelement(in:subelement:cache:)``. You might be able to improve performance by - /// calculating values once and storing them in a cache. + /// calculations across calls to ``sizeThatFits(proposal:subelement:environment:cache:)`` and + /// ``placeSubelement(in:subelement:environment:cache:)``. You might be able to improve + /// performance by calculating values once and storing them in a cache. /// /// - Note: A cache's lifetime is limited to a single render pass, so you cannot use it to store - /// values across multiple calls to ``placeSubelement(in:subelement:cache:)``. A render pass - /// includes zero, one, or many calls to ``sizeThatFits(proposal:subelement:cache:)``, - /// followed by a single call to ``placeSubelement(in:subelement:cache:)``. + /// values across multiple calls to ``placeSubelement(in:subelement:environment:cache:)``. A + /// render pass includes zero, one, or many calls to + /// ``sizeThatFits(proposal:subelement:environment:cache:)``, followed by a single call to + /// ``placeSubelement(in:subelement:environment:cache:)``. /// /// Only implement a cache if profiling shows that it improves performance. /// - /// For more information, see ``CaffeinatedLayout/makeCache(subelements:)-d3w``. + /// For more information, see ``CaffeinatedLayout/makeCache(subelements:environment:)-8ciko``. /// /// - Parameter subelement: A proxy that represent the subelement that the container arranges. /// You can use the proxy to get information about the subelement as you calculate values to /// store in the cache. + /// - Parameter environment: The environment of this container. /// - Returns: Storage for calculated data that you share among the methods of your custom /// layout container. - func makeCache(subelement: Subelement) -> Cache + func makeCache(subelement: Subelement, environment: Environment) -> Cache } extension CaffeinatedSingleChildLayout where Cache == () { - public func makeCache(subelement: Subelement) { () } + public func makeCache(subelement: Subelement, environment: Environment) { () } } diff --git a/BlueprintUI/Sources/Layout/Stack.swift b/BlueprintUI/Sources/Layout/Stack.swift index 66e19170e..bee61dd2b 100644 --- a/BlueprintUI/Sources/Layout/Stack.swift +++ b/BlueprintUI/Sources/Layout/Stack.swift @@ -873,7 +873,12 @@ extension LayoutSubelement { } extension StackLayout { - public func sizeThatFits(proposal: SizeConstraint, subelements: Subelements, cache: inout ()) -> CGSize { + public func sizeThatFits( + proposal: SizeConstraint, + subelements: Subelements, + environment: Environment, + cache: inout () + ) -> CGSize { guard subelements.isEmpty == false else { return .zero } let constraint = proposal.vectorConstraint(on: axis) @@ -890,8 +895,12 @@ extension StackLayout { return vector.size(axis: axis) } - public func placeSubelements(in size: CGSize, subelements: Subelements, cache: inout ()) { - + public func placeSubelements( + in size: CGSize, + subelements: Subelements, + environment: Environment, + cache: inout () + ) { let constraint = size.vectorConstraint(axis: axis) let frames = _frames(for: subelements.map(StackLayoutItem.init), in: constraint) diff --git a/BlueprintUI/Sources/Layout/Transformed.swift b/BlueprintUI/Sources/Layout/Transformed.swift index 72c80f9d6..e8fbaf036 100644 --- a/BlueprintUI/Sources/Layout/Transformed.swift +++ b/BlueprintUI/Sources/Layout/Transformed.swift @@ -57,11 +57,21 @@ public struct Transformed: Element { return attributes } - func sizeThatFits(proposal: SizeConstraint, subelement: LayoutSubelement, cache: inout Cache) -> CGSize { + func sizeThatFits( + proposal: SizeConstraint, + subelement: Subelement, + environment: Environment, + cache: inout Cache + ) -> CGSize { subelement.sizeThatFits(proposal) } - func placeSubelement(in size: CGSize, subelement: LayoutSubelement, cache: inout ()) { + func placeSubelement( + in size: CGSize, + subelement: Subelement, + environment: Environment, + cache: inout () + ) { subelement.attributes.transform = transform } } diff --git a/BlueprintUI/Sources/Layout/UserInteractionEnabled.swift b/BlueprintUI/Sources/Layout/UserInteractionEnabled.swift index b2c18cf41..10375c2f3 100644 --- a/BlueprintUI/Sources/Layout/UserInteractionEnabled.swift +++ b/BlueprintUI/Sources/Layout/UserInteractionEnabled.swift @@ -33,11 +33,21 @@ public struct UserInteractionEnabled: Element { return attributes } - func sizeThatFits(proposal: SizeConstraint, subelement: LayoutSubelement, cache: inout Cache) -> CGSize { + func sizeThatFits( + proposal: SizeConstraint, + subelement: Subelement, + environment: Environment, + cache: inout Cache + ) -> CGSize { subelement.sizeThatFits(proposal) } - func placeSubelement(in size: CGSize, subelement: LayoutSubelement, cache: inout ()) { + func placeSubelement( + in size: CGSize, + subelement: Subelement, + environment: Environment, + cache: inout () + ) { subelement.attributes.isUserInteractionEnabled = isEnabled } } diff --git a/BlueprintUI/Tests/BlueprintViewTests.swift b/BlueprintUI/Tests/BlueprintViewTests.swift index f99afe72f..ab0255923 100755 --- a/BlueprintUI/Tests/BlueprintViewTests.swift +++ b/BlueprintUI/Tests/BlueprintViewTests.swift @@ -802,11 +802,21 @@ private struct TestContainer: Element { Array(repeating: LayoutAttributes(size: .zero), count: items.count) } - func sizeThatFits(proposal: SizeConstraint, subelements: Subelements, cache: inout ()) -> CGSize { + func sizeThatFits( + proposal: SizeConstraint, + subelements: Subelements, + environment: Environment, + cache: inout () + ) -> CGSize { .zero } - func placeSubelements(in size: CGSize, subelements: Subelements, cache: inout ()) { + func placeSubelements( + in size: CGSize, + subelements: Subelements, + environment: Environment, + cache: inout () + ) { for subelement in subelements { subelement.place(in: .zero) } diff --git a/BlueprintUI/Tests/ElementContentTests.swift b/BlueprintUI/Tests/ElementContentTests.swift index de081b564..31faf9207 100755 --- a/BlueprintUI/Tests/ElementContentTests.swift +++ b/BlueprintUI/Tests/ElementContentTests.swift @@ -280,7 +280,12 @@ fileprivate struct FrameLayout: Layout { items.map { LayoutAttributes(frame: $0.traits) } } - func sizeThatFits(proposal: SizeConstraint, subelements: Subelements, cache: inout ()) -> CGSize { + func sizeThatFits( + proposal: SizeConstraint, + subelements: Subelements, + environment: Environment, + cache: inout () + ) -> CGSize { subelements.reduce(into: CGSize.zero) { result, subview in let traits = subview.traits(forLayoutType: FrameLayout.self) result.width = max(result.width, traits.maxX) @@ -288,7 +293,12 @@ fileprivate struct FrameLayout: Layout { } } - func placeSubelements(in size: CGSize, subelements: Subelements, cache: inout ()) { + func placeSubelements( + in size: CGSize, + subelements: Subelements, + environment: Environment, + cache: inout () + ) { for subelement in subelements { let frame = subelement.traits(forLayoutType: FrameLayout.self) subelement.place( @@ -324,26 +334,22 @@ private struct HalfLayout: Layout { } } - func sizeThatFits(proposal: SizeConstraint, subelements: Subelements, cache: inout ()) -> CGSize { - let halfConstraint = SizeConstraint( - width: proposal.width / 2, - height: proposal.height / 2 - ) - let measurements = subelements.map { $0.sizeThatFits(halfConstraint) } - return CGSize( - width: measurements.map(\.width).reduce(0, +), - height: measurements.map(\.height).reduce(0, +) - ) + func sizeThatFits( + proposal: SizeConstraint, + subelements: Subelements, + environment: Environment, + cache: inout () + ) -> CGSize { + fatalError("Not supported in Caffeinated Layout") } - func placeSubelements(in size: CGSize, subelements: Subelements, cache: inout ()) { - let halfConstraint = SizeConstraint(CGSize(width: size.width / 2, height: size.height / 2)) - for subelement in subelements { - subelement.place( - at: .zero, - size: subelement.sizeThatFits(halfConstraint) - ) - } + func placeSubelements( + in size: CGSize, + subelements: Subelements, + environment: Environment, + cache: inout () + ) { + fatalError("Not supported in Caffeinated Layout") } } @@ -368,21 +374,37 @@ private struct MeasureCountingLayout: Layout where WrappedLayout: layout.layout(size: size, items: items) } - func sizeThatFits(proposal: SizeConstraint, subelements: Subelements, cache: inout WrappedLayout.Cache) -> CGSize { + func sizeThatFits( + proposal: SizeConstraint, + subelements: Subelements, + environment: Environment, + cache: inout WrappedLayout.Cache + ) -> CGSize { counts.measures += 1 - return layout.sizeThatFits(proposal: proposal, subelements: subelements, cache: &cache) + return layout.sizeThatFits( + proposal: proposal, + subelements: subelements, + environment: environment, + cache: &cache + ) } func placeSubelements( in size: CGSize, subelements: Subelements, + environment: Environment, cache: inout WrappedLayout.Cache ) { - layout.placeSubelements(in: size, subelements: subelements, cache: &cache) + layout.placeSubelements( + in: size, + subelements: subelements, + environment: environment, + cache: &cache + ) } - func makeCache(subelements: LayoutSubelements) -> WrappedLayout.Cache { - layout.makeCache(subelements: subelements) + func makeCache(subelements: Subelements, environment: Environment) -> WrappedLayout.Cache { + layout.makeCache(subelements: subelements, environment: environment) } } @@ -411,11 +433,21 @@ private struct MeasureCountingSpacer: Element { [] } - func sizeThatFits(proposal: SizeConstraint, subelements: Subelements, cache: inout ()) -> CGSize { + func sizeThatFits( + proposal: SizeConstraint, + subelements: Subelements, + environment: Environment, + cache: inout () + ) -> CGSize { size } - func placeSubelements(in size: CGSize, subelements: Subelements, cache: inout ()) { + func placeSubelements( + in size: CGSize, + subelements: Subelements, + environment: Environment, + cache: inout () + ) { // No-op } } diff --git a/BlueprintUI/Tests/EnvironmentTests.swift b/BlueprintUI/Tests/EnvironmentTests.swift index 0140c22d9..a1ad794c9 100644 --- a/BlueprintUI/Tests/EnvironmentTests.swift +++ b/BlueprintUI/Tests/EnvironmentTests.swift @@ -268,11 +268,21 @@ private struct TestElement: Element { [value.layoutAttributes] } - func sizeThatFits(proposal: SizeConstraint, subelements: Subelements, cache: inout ()) -> CGSize { + func sizeThatFits( + proposal: SizeConstraint, + subelements: Subelements, + environment: Environment, + cache: inout () + ) -> CGSize { value.size } - func placeSubelements(in size: CGSize, subelements: Subelements, cache: inout ()) { + func placeSubelements( + in size: CGSize, + subelements: Subelements, + environment: Environment, + cache: inout () + ) { for subelement in subelements { subelement.place( at: value.layoutAttributes.frame.origin, diff --git a/BlueprintUI/Tests/LayoutResultNodeTests.swift b/BlueprintUI/Tests/LayoutResultNodeTests.swift index 7486bd918..9d16fe2e8 100644 --- a/BlueprintUI/Tests/LayoutResultNodeTests.swift +++ b/BlueprintUI/Tests/LayoutResultNodeTests.swift @@ -54,11 +54,21 @@ fileprivate struct AbstractElement: Element { LayoutAttributes(frame: CGRect(origin: .zero, size: size).insetBy(dx: 10, dy: 10)) } - func sizeThatFits(proposal: SizeConstraint, subelement: Subelement, cache: inout ()) -> CGSize { + func sizeThatFits( + proposal: SizeConstraint, + subelement: Subelement, + environment: Environment, + cache: inout () + ) -> CGSize { .zero } - func placeSubelement(in size: CGSize, subelement: Subelement, cache: inout ()) { + func placeSubelement( + in size: CGSize, + subelement: Subelement, + environment: Environment, + cache: inout () + ) { let frame = CGRect(origin: .zero, size: size).insetBy(dx: 10, dy: 10) subelement.place(in: frame) } diff --git a/BlueprintUICommonControls/Sources/ScrollView.swift b/BlueprintUICommonControls/Sources/ScrollView.swift index 981ed9945..964ecd51a 100644 --- a/BlueprintUICommonControls/Sources/ScrollView.swift +++ b/BlueprintUICommonControls/Sources/ScrollView.swift @@ -164,7 +164,7 @@ extension ScrollView { return contentAttributes } - func fittedSize(in proposal: SizeConstraint, subelement: LayoutSubelement) -> CGSize { + func fittedSize(in proposal: SizeConstraint, subelement: Subelement) -> CGSize { switch contentSize { case .custom(let size): return size @@ -190,7 +190,12 @@ extension ScrollView { } } - func sizeThatFits(proposal: SizeConstraint, subelement: LayoutSubelement, cache: inout Cache) -> CGSize { + func sizeThatFits( + proposal: SizeConstraint, + subelement: Subelement, + environment: Environment, + cache: inout Cache + ) -> CGSize { let adjustedProposal = proposal.inset(by: contentInset) var result = fittedSize(in: adjustedProposal, subelement: subelement) @@ -208,7 +213,12 @@ extension ScrollView { return result } - func placeSubelement(in size: CGSize, subelement: LayoutSubelement, cache: inout ()) { + func placeSubelement( + in size: CGSize, + subelement: Subelement, + environment: Environment, + cache: inout () + ) { var insetSize = size insetSize.width -= contentInset.left + contentInset.right insetSize.height -= contentInset.top + contentInset.bottom diff --git a/BlueprintUICommonControls/Tests/Sources/BoxTests.swift b/BlueprintUICommonControls/Tests/Sources/BoxTests.swift index f936341d3..3301e88b8 100644 --- a/BlueprintUICommonControls/Tests/Sources/BoxTests.swift +++ b/BlueprintUICommonControls/Tests/Sources/BoxTests.swift @@ -133,11 +133,21 @@ private struct InsettingElement: Element { LayoutAttributes(frame: CGRect(origin: .zero, size: size).insetBy(dx: 20, dy: 20)) } - func sizeThatFits(proposal: SizeConstraint, subelement: LayoutSubelement, cache: inout ()) -> CGSize { + func sizeThatFits( + proposal: SizeConstraint, + subelement: Subelement, + environment: Environment, + cache: inout () + ) -> CGSize { .zero } - func placeSubelement(in size: CGSize, subelement: LayoutSubelement, cache: inout ()) { + func placeSubelement( + in size: CGSize, + subelement: Subelement, + environment: Environment, + cache: inout () + ) { subelement.place(in: CGRect(origin: .zero, size: size).insetBy(dx: 20, dy: 20)) } }