diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index dbbd53b5cc..7029853074 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -297,9 +297,6 @@ 9C70F20D1CDBE9CB007D6C76 /* ASDefaultPlayButton.h in Headers */ = {isa = PBXBuildFile; fileRef = AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */; }; 9C70F20E1CDBE9E5007D6C76 /* NSArray+Diffing.h in Headers */ = {isa = PBXBuildFile; fileRef = DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C70F20F1CDBE9FF007D6C76 /* ASLayoutManager.h in Headers */ = {isa = PBXBuildFile; fileRef = B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */; }; - 9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */; }; - 9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; }; - 9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; }; 9C8898BB1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8898BA1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm */; }; 9C8898BC1C738BA800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8898BA1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm */; }; 9C8898BD1C738BB800D6B02E /* ASTextKitFontSizeAdjuster.h in Headers */ = {isa = PBXBuildFile; fileRef = A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */; }; @@ -640,7 +637,6 @@ F7CE6C991D2CDB5800BE4C15 /* ASLayoutSpecUtilities.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED451B17847A00DA7C62 /* ASLayoutSpecUtilities.h */; }; F7CE6C9A1D2CDB5800BE4C15 /* ASMultidimensionalArrayUtils.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0442850B1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h */; }; F7CE6C9B1D2CDB5800BE4C15 /* ASPendingStateController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CC3B20811C3F76D600798563 /* ASPendingStateController.h */; }; - F7CE6C9D1D2CDB5800BE4C15 /* ASStackBaselinePositionedLayout.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */; }; F7CE6C9E1D2CDB5800BE4C15 /* ASStackLayoutSpecUtilities.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED461B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h */; }; F7CE6C9F1D2CDB5800BE4C15 /* ASStackPositionedLayout.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED471B17847A00DA7C62 /* ASStackPositionedLayout.h */; }; F7CE6CA01D2CDB5800BE4C15 /* ASStackUnpositionedLayout.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACF6ED491B17847A00DA7C62 /* ASStackUnpositionedLayout.h */; }; @@ -818,7 +814,6 @@ F7CE6C991D2CDB5800BE4C15 /* ASLayoutSpecUtilities.h in CopyFiles */, F7CE6C9A1D2CDB5800BE4C15 /* ASMultidimensionalArrayUtils.h in CopyFiles */, F7CE6C9B1D2CDB5800BE4C15 /* ASPendingStateController.h in CopyFiles */, - F7CE6C9D1D2CDB5800BE4C15 /* ASStackBaselinePositionedLayout.h in CopyFiles */, F7CE6C9E1D2CDB5800BE4C15 /* ASStackLayoutSpecUtilities.h in CopyFiles */, F7CE6C9F1D2CDB5800BE4C15 /* ASStackPositionedLayout.h in CopyFiles */, F7CE6CA01D2CDB5800BE4C15 /* ASStackUnpositionedLayout.h in CopyFiles */, @@ -1072,8 +1067,6 @@ 9C6BB3B01B8CC9C200F13F52 /* ASAbsoluteLayoutElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASAbsoluteLayoutElement.h; path = AsyncDisplayKit/Layout/ASAbsoluteLayoutElement.h; sourceTree = ""; }; 9C70F2011CDA4EFA007D6C76 /* ASTraitCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTraitCollection.h; sourceTree = ""; }; 9C70F2021CDA4EFA007D6C76 /* ASTraitCollection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTraitCollection.m; sourceTree = ""; }; - 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackBaselinePositionedLayout.h; sourceTree = ""; }; - 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASStackBaselinePositionedLayout.mm; sourceTree = ""; }; 9C8898BA1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitFontSizeAdjuster.mm; path = TextKit/ASTextKitFontSizeAdjuster.mm; sourceTree = ""; }; 9CDC18CB1B910E12004965E2 /* ASLayoutElementPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutElementPrivate.h; path = AsyncDisplayKit/Layout/ASLayoutElementPrivate.h; sourceTree = ""; }; 9CFFC6BD1CCAC52B006A6476 /* ASEnvironment.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASEnvironment.mm; sourceTree = ""; }; @@ -1638,8 +1631,6 @@ 0442850C1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm */, CC3B20811C3F76D600798563 /* ASPendingStateController.h */, CC3B20821C3F76D600798563 /* ASPendingStateController.mm */, - 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */, - 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */, ACF6ED461B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h */, ACF6ED471B17847A00DA7C62 /* ASStackPositionedLayout.h */, ACF6ED481B17847A00DA7C62 /* ASStackPositionedLayout.mm */, @@ -1962,7 +1953,6 @@ 254C6B751BF94DF4003EC431 /* ASTextKitComponents.h in Headers */, B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */, 25E327571C16819500A2170C /* ASPagerNode.h in Headers */, - 9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */, 9C70F20E1CDBE9E5007D6C76 /* NSArray+Diffing.h in Headers */, 9C49C3701B853961000B0DD5 /* ASStackLayoutElement.h in Headers */, DE040EF91C2B40AC004692FF /* ASCollectionViewFlowLayoutInspector.h in Headers */, @@ -2302,7 +2292,6 @@ 9C8898BB1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */, D785F6631A74327E00291744 /* ASScrollNode.mm in Sources */, E5711A2E1C840C96009619D4 /* ASIndexedNodeContext.mm in Sources */, - 9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */, 251B8EF81BBB3D690087C538 /* ASCollectionDataController.mm in Sources */, ACF6ED301B17843500DA7C62 /* ASStackLayoutSpec.mm in Sources */, 257754BE1BEE458E00737CA5 /* ASTextKitComponents.mm in Sources */, @@ -2496,7 +2485,6 @@ 254C6B851BF94F8A003EC431 /* ASTextKitAttributes.mm in Sources */, 509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */, B35062091B010EFD0018CF92 /* ASScrollNode.mm in Sources */, - 9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */, 8BDA5FC81CDBDF95007D13B2 /* ASVideoPlayerNode.mm in Sources */, 34EFC7721B701D0300AD841F /* ASStackLayoutSpec.mm in Sources */, 34EFC7761B701D2A00AD841F /* ASStackPositionedLayout.mm in Sources */, diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.h b/AsyncDisplayKit/Layout/ASStackLayoutSpec.h index b36b590e75..d805f1e9dc 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.h @@ -59,8 +59,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) ASStackLayoutJustifyContent justifyContent; /** Orientation of children along cross axis. Defaults to ASStackLayoutAlignItemsStretch */ @property (nonatomic, assign) ASStackLayoutAlignItems alignItems; -/** If YES the vertical spacing between two views is measured from the last baseline of the top view to the top of the bottom view */ -@property (nonatomic, assign) BOOL baselineRelativeArrangement; - (instancetype)init; diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm index 156a5c9ad1..9db5a17a8d 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm @@ -11,12 +11,15 @@ #import #import +#import "ASStackLayoutSpec.h" +#import "ASDimension.h" #import "ASInternalHelpers.h" - +#import "ASLayout.h" #import "ASLayoutElement.h" #import "ASLayoutElementStylePrivate.h" #import "ASLayoutSpecUtilities.h" -#import "ASStackBaselinePositionedLayout.h" +#import "ASStackPositionedLayout.h" +#import "ASStackUnpositionedLayout.h" #import "ASThread.h" @implementation ASStackLayoutSpec @@ -111,12 +114,6 @@ _spacing = spacing; } -- (void)setBaselineRelativeArrangement:(BOOL)baselineRelativeArrangement -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _baselineRelativeArrangement = baselineRelativeArrangement; -} - - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize { NSArray *children = self.children; @@ -131,53 +128,21 @@ return {child, style, style.size}; }); - const ASStackLayoutSpecStyle style = {.direction = _direction, .spacing = _spacing, .justifyContent = _justifyContent, .alignItems = _alignItems, .baselineRelativeArrangement = _baselineRelativeArrangement}; + const ASStackLayoutSpecStyle style = {.direction = _direction, .spacing = _spacing, .justifyContent = _justifyContent, .alignItems = _alignItems}; - // First pass is to get the children into a positioned state const auto unpositionedLayout = ASStackUnpositionedLayout::compute(stackChildren, style, constrainedSize); const auto positionedLayout = ASStackPositionedLayout::compute(unpositionedLayout, style, constrainedSize); - // Figure out if a baseline pass is really needed - const BOOL directionIsVertical = (style.direction == ASStackLayoutDirectionVertical); - const BOOL needsBaselineAlignment = ASStackBaselinePositionedLayout::needsBaselineAlignment(style); - const BOOL needsBaselinePositioning = (directionIsVertical == NO || needsBaselineAlignment == YES); - - NSMutableArray *sublayouts = [NSMutableArray array]; - CGSize finalSize = CGSizeZero; - if (needsBaselinePositioning) { - // All horizontal stacks, regardless of whether or not they are baseline aligned, should go through a baseline - // computation. They could be used in another horizontal stack that is baseline aligned and will need to have - // computed the proper ascender/descender. - - // Vertical stacks do not need to go through this computation since we can easily compute ascender/descender by - // looking at their first/last child's ascender/descender. - const auto baselinePositionedLayout = ASStackBaselinePositionedLayout::compute(positionedLayout, style, constrainedSize); - - if (directionIsVertical == NO) { - self.style.ascender = baselinePositionedLayout.ascender; - self.style.descender = baselinePositionedLayout.descender; - } - - if (needsBaselineAlignment == YES) { - finalSize = directionSize(style.direction, unpositionedLayout.stackDimensionSum, baselinePositionedLayout.crossSize); - - for (const auto &l : baselinePositionedLayout.items) { - [sublayouts addObject:l.layout]; - } - } - } - - if (directionIsVertical == YES) { + if (style.direction == ASStackLayoutDirectionVertical) { self.style.ascender = stackChildren.front().style.ascender; self.style.descender = stackChildren.back().style.descender; } - if (needsBaselineAlignment == NO) { - finalSize = directionSize(style.direction, unpositionedLayout.stackDimensionSum, positionedLayout.crossSize); - - for (const auto &l : positionedLayout.items) { - [sublayouts addObject:l.layout]; - } + const CGSize finalSize = directionSize(style.direction, unpositionedLayout.stackDimensionSum, unpositionedLayout.crossSize); + + NSMutableArray *sublayouts = [NSMutableArray array]; + for (const auto &l : positionedLayout.items) { + [sublayouts addObject:l.layout]; } return [ASLayout layoutWithLayoutElement:self size:ASSizeRangeClamp(constrainedSize, finalSize) sublayouts:sublayouts]; diff --git a/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.h b/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.h deleted file mode 100644 index 9764c25d12..0000000000 --- a/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// ASStackBaselinePositionedLayout.h -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#import "ASLayout.h" -#import "ASDimension.h" -#import "ASStackPositionedLayout.h" - -struct ASStackBaselinePositionedLayout { - const std::vector items; - const CGFloat crossSize; - const CGFloat ascender; - const CGFloat descender; - - /** Given a positioned layout, computes each child position using baseline alignment. */ - static ASStackBaselinePositionedLayout compute(const ASStackPositionedLayout &positionedLayout, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &constrainedSize); - - static BOOL needsBaselineAlignment(const ASStackLayoutSpecStyle &style); -}; diff --git a/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.mm b/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.mm deleted file mode 100644 index 3dad841291..0000000000 --- a/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.mm +++ /dev/null @@ -1,172 +0,0 @@ -// -// ASStackBaselinePositionedLayout.mm -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#import "ASStackBaselinePositionedLayout.h" - -#import "ASLayoutSpecUtilities.h" -#import "ASLayoutSpec+Subclasses.h" - -#import "ASLayoutElement.h" -#import "ASLayoutElementStylePrivate.h" - -static CGFloat baselineForItem(const ASStackLayoutSpecStyle &style, - const ASStackLayoutSpecItem &l) -{ - switch (style.alignItems) { - case ASStackLayoutAlignItemsBaselineFirst: - return l.child.style.ascender; - case ASStackLayoutAlignItemsBaselineLast: - return l.layout.size.height + l.child.style.descender; - default: - return 0; - } -} - -static CGFloat baselineOffset(const ASStackLayoutSpecStyle &style, - const ASStackLayoutSpecItem &l, - const CGFloat maxAscender, - const CGFloat maxBaseline) -{ - if (style.direction == ASStackLayoutDirectionHorizontal) { - switch (style.alignItems) { - case ASStackLayoutAlignItemsBaselineFirst: - return maxAscender - l.child.style.ascender; - case ASStackLayoutAlignItemsBaselineLast: - return maxBaseline - baselineForItem(style, l); - - default: - return 0; - } - } - return 0; -} - -static CGFloat maxDimensionForItem(const ASStackLayoutSpecItem &l, - const ASStackLayoutSpecStyle &style) -{ - CGFloat maxDimension = crossDimension(style.direction, l.layout.size); - style.direction == ASStackLayoutDirectionVertical ? maxDimension += l.layout.position.x : maxDimension += l.layout.position.y; - return maxDimension; -} - -BOOL ASStackBaselinePositionedLayout::needsBaselineAlignment(const ASStackLayoutSpecStyle &style) -{ - return style.baselineRelativeArrangement || - style.alignItems == ASStackLayoutAlignItemsBaselineFirst || - style.alignItems == ASStackLayoutAlignItemsBaselineLast; -} - -ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const ASStackPositionedLayout &positionedLayout, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &constrainedSize) -{ - const auto stackedChildren = positionedLayout.items; - - /* Step 1: Look at each child and determine the distance from the top of the child node to its baseline. - For example, let's say we have the following two text nodes and want to align them to the first baseline: - - Hello! Why, hello there! How - are you today? - - The first node has a font of size 14, the second a font of size 12. The first node will have a baseline offset of - the ascender of a font of size 14, the second will have a baseline of the ascender of a font of size 12. The first - baseline will be larger so we will keep that as the max baseline. - - However, if were to align from the last baseline we'd find the max baseline by taking the height of node and adding - the font's descender (it's negative). In the case of the first node, which is only 1 line, this should be the same value as the ascender. - The second node, however, has a larger height and there will have a larger baseline offset. - */ - const auto baselineIt = std::max_element(stackedChildren.begin(), stackedChildren.end(), [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b){ - return baselineForItem(style, a) < baselineForItem(style, b); - }); - const CGFloat maxBaseline = baselineIt == stackedChildren.end() ? 0 : baselineForItem(style, *baselineIt); - - /* - Step 2: Find the max ascender for all of the children. - Imagine 3 nodes aligned horizontally, all with the same text but with font sizes of 12, 14, 16. Because it is has the largest - ascender node with font size of 16 will not need to move, the other two nodes will align to this node's baseline. The offset we will use - for each node is our computed maxAscender - node.ascender. If the 16pt node had an ascender of 10 and the 14pt node - had an ascender of 8, that means we will offset the 14pt node by 2 pts. - - Note: if we are aligning to the last baseline, then we don't need this value in our computation. However, we do want - our layoutSpec to have it so that it can be baseline aligned with another text node or baseline layout spec. - */ - const auto ascenderIt = std::max_element(stackedChildren.begin(), stackedChildren.end(), [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b){ - return a.child.style.ascender < b.child.style.ascender; - }); - const CGFloat maxAscender = ascenderIt == stackedChildren.end() ? 0 : (*ascenderIt).child.style.ascender; - - /* - Step 3: Take each child and update its layout position based on the baseline offset. - - If this is a horizontal stack, we take a positioned child and add to its y offset to align it to the maxBaseline of the children. - If this is a vertical stack, we add the child's descender to the location of the next child to position. This will ensure the - spacing between the two nodes is from the baseline, not the bounding box. - - */ - - // Only change positions of layouts this stackSpec is aligning to a baseline. Otherwise we are only here to - // compute the min/max descender/ascender for this stack spec. - if (ASStackBaselinePositionedLayout::needsBaselineAlignment(style)) { - // Adjust the positioned layout items to be positioned based on the baseline - CGPoint p = CGPointZero; - BOOL first = YES; - - for (const ASStackLayoutSpecItem &l : stackedChildren) { - ASLayoutElementStyle *layoutElementStyle = l.child.style; - - p = p + directionPoint(style.direction, layoutElementStyle.spacingBefore, 0); - - // if this is the first item use the previously computed start point otherwise add the stack spacing - p = first ? l.layout.position : p + directionPoint(style.direction, style.spacing, 0); - first = NO; - - // Find the difference between this node's baseline and the max baseline of all the children. Add this difference to the child's y position. - l.layout.position = p + CGPointMake(0, baselineOffset(style, l, maxAscender, maxBaseline)); - - // If we are a vertical stack, add the item's descender (it is negative) to the offset for the next node. This will ensure we are spacing - // node from baselines and not bounding boxes. - CGFloat spacingAfterBaseline = 0; - if (style.direction == ASStackLayoutDirectionVertical) { - spacingAfterBaseline = layoutElementStyle.descender; - } - p = p + directionPoint(style.direction, stackDimension(style.direction, l.layout.size) + layoutElementStyle.spacingAfter + spacingAfterBaseline, 0); - } - } - - /* - Step 4: Since we have been mucking with positions, there is a chance that our cross size has changed. Imagine a node with a font size of 40 - and another node with a font size of 12 but with multiple lines. We align these nodes to the first baseline, which will be the baseline of the node with - font size of 40 (max ascender). Now, we have to move the node with multiple lines down to the other node's baseline. This node with multiple lines will - extend below the first node farther than it did before aligning the baselines thus increasing the cross size. - - After finding the new cross size, we need to clamp it so that it fits within the constrained size. - - */ - const auto it = std::max_element(stackedChildren.begin(), stackedChildren.end(), - [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b) { - return maxDimensionForItem(a, style) < maxDimensionForItem(b, style); - }); - const auto largestChildCrossSize = it == stackedChildren.end() ? 0 : maxDimensionForItem(*it, style); - const auto minCrossSize = crossDimension(style.direction, constrainedSize.min); - const auto maxCrossSize = crossDimension(style.direction, constrainedSize.max); - const CGFloat crossSize = MIN(MAX(minCrossSize, largestChildCrossSize), maxCrossSize); - - /* - Step 5: finally, we must find the smallest descender (descender is negative). This is since ASBaselineLayoutSpec implements - ASLayoutElement and needs an ascender and descender to lay itself out properly. - */ - const auto descenderIt = std::max_element(stackedChildren.begin(), stackedChildren.end(), [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b){ - return a.layout.position.y + a.layout.size.height < b.layout.position.y + b.layout.size.height; - }); - const CGFloat minDescender = descenderIt == stackedChildren.end() ? 0 : (*descenderIt).child.style.descender; - - return {std::move(stackedChildren), crossSize, maxAscender, minDescender}; -} diff --git a/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h b/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h index a5067a631e..cbec39a3e7 100644 --- a/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h +++ b/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h @@ -15,7 +15,6 @@ typedef struct { CGFloat spacing; ASStackLayoutJustifyContent justifyContent; ASStackLayoutAlignItems alignItems; - BOOL baselineRelativeArrangement; } ASStackLayoutSpecStyle; inline CGFloat stackDimension(const ASStackLayoutDirection direction, const CGSize size) diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.h b/AsyncDisplayKit/Private/ASStackPositionedLayout.h index a879763297..61e97e15c3 100644 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.h +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.h @@ -15,7 +15,6 @@ /** Represents a set of laid out and positioned stack layout children. */ struct ASStackPositionedLayout { const std::vector items; - const CGFloat crossSize; /** Given an unpositioned layout, computes the positions each child should be placed at. */ static ASStackPositionedLayout compute(const ASStackUnpositionedLayout &unpositionedLayout, diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm index a7cce88570..a935aea070 100644 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm @@ -18,7 +18,8 @@ static CGFloat crossOffset(const ASStackLayoutSpecStyle &style, const ASStackLayoutSpecItem &l, - const CGFloat crossSize) + const CGFloat crossSize, + const CGFloat baseline) { switch (alignment(l.child.style.alignSelf, style.alignItems)) { case ASStackLayoutAlignItemsEnd: @@ -27,6 +28,7 @@ static CGFloat crossOffset(const ASStackLayoutSpecStyle &style, return ASFloorPixelValue((crossSize - crossDimension(style.direction, l.layout.size)) / 2); case ASStackLayoutAlignItemsBaselineFirst: case ASStackLayoutAlignItemsBaselineLast: + return baseline - ASStackUnpositionedLayout::baselineForItem(style, l); case ASStackLayoutAlignItemsStart: case ASStackLayoutAlignItemsStretch: return 0; @@ -48,21 +50,12 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style const ASStackUnpositionedLayout &unpositionedLayout, const ASSizeRange &constrainedSize) { - - // The cross dimension is the max of the childrens' cross dimensions (clamped to our constraint below). - const auto it = std::max_element(unpositionedLayout.items.begin(), unpositionedLayout.items.end(), - [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b){ - return compareCrossDimension(style.direction, a.layout.size, b.layout.size); - }); - const auto largestChildCrossSize = it == unpositionedLayout.items.end() ? 0 : crossDimension(style.direction, it->layout.size); - const auto minCrossSize = crossDimension(style.direction, constrainedSize.min); - const auto maxCrossSize = crossDimension(style.direction, constrainedSize.max); - const CGFloat crossSize = MIN(MAX(minCrossSize, largestChildCrossSize), maxCrossSize); - - CGPoint p = directionPoint(style.direction, firstChildOffset, 0); - BOOL first = YES; + CGFloat crossSize = unpositionedLayout.crossSize; + CGFloat baseline = unpositionedLayout.baseline; // Adjust the position of the unpositioned layouts to be positioned + CGPoint p = directionPoint(style.direction, firstChildOffset, 0); + BOOL first = YES; const auto stackedChildren = unpositionedLayout.items; for (const auto &l : stackedChildren) { p = p + directionPoint(style.direction, l.child.style.spacingBefore, 0); @@ -70,12 +63,12 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style p = p + directionPoint(style.direction, style.spacing + extraSpacing, 0); } first = NO; - l.layout.position = p + directionPoint(style.direction, 0, crossOffset(style, l, crossSize)); + l.layout.position = p + directionPoint(style.direction, 0, crossOffset(style, l, crossSize, baseline)); p = p + directionPoint(style.direction, stackDimension(style.direction, l.layout.size) + l.child.style.spacingAfter, 0); } - return {std::move(stackedChildren), crossSize}; + return {std::move(stackedChildren)}; } ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnpositionedLayout &unpositionedLayout, diff --git a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.h b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.h index c1607c93cb..bcab2649f0 100644 --- a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.h +++ b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.h @@ -39,9 +39,16 @@ struct ASStackUnpositionedLayout { const CGFloat stackDimensionSum; /** The amount by which stackDimensionSum violates constraints. If positive, less than min; negative, greater than max. */ const CGFloat violation; - + /** The size in the cross dimension */ + const CGFloat crossSize; + /** The baseline of the stack which baseline aligned children should align to */ + const CGFloat baseline; + /** Given a set of children, computes the unpositioned layouts for those children. */ static ASStackUnpositionedLayout compute(const std::vector &children, const ASStackLayoutSpecStyle &style, const ASSizeRange &sizeRange); + + static CGFloat baselineForItem(const ASStackLayoutSpecStyle &style, + const ASStackLayoutSpecItem &l); }; diff --git a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm index f86d5ef4ae..6fabc52dc3 100644 --- a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm @@ -49,7 +49,7 @@ static ASLayout *crossChildLayout(const ASStackLayoutSpecChild &child, const CGFloat stackMax, const CGFloat crossMin, const CGFloat crossMax, - const CGSize size) + const CGSize parentSize) { const ASStackLayoutAlignItems alignItems = alignment(child.style.alignSelf, style.alignItems); // stretched children will have a cross dimension of at least crossMin @@ -60,11 +60,14 @@ static ASLayout *crossChildLayout(const ASStackLayoutSpecChild &child, resolveCrossDimensionMaxForStretchChild(style, child, stackMax, crossMax) : crossMax); const ASSizeRange childSizeRange = directionSizeRange(style.direction, stackMin, stackMax, childCrossMin, childCrossMax); - ASLayout *layout = [child.element layoutThatFits:childSizeRange parentSize:size]; + ASLayout *layout = [child.element layoutThatFits:childSizeRange parentSize:parentSize]; ASDisplayNodeCAssertNotNil(layout, @"ASLayout returned from measureWithSizeRange: must not be nil: %@", child.element); return layout ? : [ASLayout layoutWithLayoutElement:child.element size:{0, 0}]; } +/** The threshold that determines if a violation has actually occurred. */ +static const CGFloat kViolationEpsilon = 0.01; + /** Stretches children to lay out along the cross axis according to the alignment stretch settings of the children (child.alignSelf), and the stack layout's alignment settings (style.alignItems). This does not do the actual alignment @@ -97,34 +100,95 @@ static ASLayout *crossChildLayout(const ASStackLayoutSpecChild &child, @param layouts pre-computed child layouts; modified in-place as needed @param style the layout style of the overall stack layout */ -static void stretchChildrenAlongCrossDimension(std::vector &layouts, +static void stretchChildrenAlongCrossDimension(std::vector &items, const ASStackLayoutSpecStyle &style, - const CGSize size) + const CGSize parentSize, + const CGFloat crossSize) { - // Find the maximum cross dimension size among child layouts - const auto it = std::max_element(layouts.begin(), layouts.end(), - [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b) { - return compareCrossDimension(style.direction, a.layout.size, b.layout.size); - }); - - const CGFloat childCrossMax = it == layouts.end() ? 0 : crossDimension(style.direction, it->layout.size); - for (auto &l : layouts) { - const ASStackLayoutAlignItems alignItems = alignment(l.child.style.alignSelf, style.alignItems); - - const CGFloat cross = crossDimension(style.direction, l.layout.size); - const CGFloat stack = stackDimension(style.direction, l.layout.size); - - // restretch all stretchable children along the cross axis using the new min. set their max size to childCrossMax, - // not crossMax, so that if any of them would choose a larger size just because the min size increased (weird!) - // they are forced to choose the same width as all the other children. - if (alignItems == ASStackLayoutAlignItemsStretch && std::fabs(cross - childCrossMax) > 0.01) { - l.layout = crossChildLayout(l.child, style, stack, stack, childCrossMax, childCrossMax, size); + for (auto &item : items) { + const ASStackLayoutAlignItems alignItems = alignment(item.child.style.alignSelf, style.alignItems); + if (alignItems != ASStackLayoutAlignItemsStretch) { + continue; + } + + const CGFloat cross = crossDimension(style.direction, item.layout.size); + const CGFloat stack = stackDimension(style.direction, item.layout.size); + const CGFloat violation = crossSize - cross; + + // Only stretch if violation is positive. Compare against kViolationEpsilon here to avoid stretching against a tiny violation. + if (violation > kViolationEpsilon) { + item.layout = crossChildLayout(item.child, style, stack, stack, crossSize, crossSize, parentSize); } } } -/** The threshold that determines if a violation has actually occurred. */ -static const CGFloat kViolationEpsilon = 0.01; + +static BOOL itemIsBaselineAligned(const ASStackLayoutSpecStyle &style, + const ASStackLayoutSpecItem &l) +{ + ASStackLayoutAlignItems alignItems = alignment(l.child.style.alignSelf, style.alignItems); + return alignItems == ASStackLayoutAlignItemsBaselineFirst || alignItems == ASStackLayoutAlignItemsBaselineLast; +} + +CGFloat ASStackUnpositionedLayout::baselineForItem(const ASStackLayoutSpecStyle &style, + const ASStackLayoutSpecItem &l) +{ + switch (alignment(l.child.style.alignSelf, style.alignItems)) { + case ASStackLayoutAlignItemsBaselineFirst: + return l.child.style.ascender; + case ASStackLayoutAlignItemsBaselineLast: + return crossDimension(style.direction, l.layout.size) + l.child.style.descender; + default: + return 0; + } +} + +/** + * Finds cross dimension size and baseline of the stack. + * https://www.w3.org/TR/css-flexbox-1/#algo-cross-line + * + * @param layout the unpositioned layout + * @param style the layout style of the overall stack layout + * @param sizeRange the range of allowable sizes for the stack layout component + * @param crossSize result of the cross size + * @param baseline result of the stack baseline + */ +static void computeCrossSizeAndBaseline(const std::vector &items, + const ASStackLayoutSpecStyle &style, + const ASSizeRange &sizeRange, + CGFloat &crossSize, + CGFloat &baseline) +{ + const auto minCrossSize = crossDimension(style.direction, sizeRange.min); + const auto maxCrossSize = crossDimension(style.direction, sizeRange.max); + + // Step 1. Collect all the flex items whose align-self is baseline. Find the largest of the distances + // between each item’s baseline and its hypothetical outer cross-start edge (aka. its ascender value), + // and the largest of the distances between each item’s baseline and its hypothetical outer cross-end edge + // (aka. the opposite of its descender value, because a negative descender means the item extends below its baseline), + // and sum these two values. + // + // Step 2. Find the maximum cross dimension size among child layouts. + CGFloat maxStartToBaselineDistance = 0; + CGFloat maxBaselineToEndDistance = 0; + CGFloat maxItemCrossSize = 0; + for (const auto &item : items) { + if (itemIsBaselineAligned(style, item)) { + CGFloat baseline = ASStackUnpositionedLayout::baselineForItem(style, item); + maxStartToBaselineDistance = MAX(maxStartToBaselineDistance, baseline); + maxBaselineToEndDistance = MAX(maxBaselineToEndDistance, crossDimension(style.direction, item.layout.size) - baseline); + } else { + maxItemCrossSize = MAX(maxItemCrossSize, crossDimension(style.direction, item.layout.size)); + } + } + + // Step 3. The used cross-size of the flex line is the largest of the numbers found in the previous two steps and zero. + crossSize = MAX(maxStartToBaselineDistance + maxBaselineToEndDistance, maxItemCrossSize);; + // Clamp the cross-size to be within the stack's min and max cross-size properties. + crossSize = MIN(MAX(minCrossSize, crossSize), maxCrossSize); + + baseline = maxStartToBaselineDistance; +} /** Returns a lambda that computes the relevant flex factor based on the given violation. @@ -157,9 +221,9 @@ static inline CGFloat scaledFlexShrinkFactor(const ASStackLayoutSpecItem &item, @return A lambda capable of computing the flex shrink adjustment, if any, for a particular item. */ static std::function flexShrinkAdjustment(const std::vector &items, - const ASStackLayoutSpecStyle &style, - const CGFloat violation, - const CGFloat flexFactorSum) + const ASStackLayoutSpecStyle &style, + const CGFloat violation, + const CGFloat flexFactorSum) { const CGFloat scaledFlexShrinkFactorSum = std::accumulate(items.begin(), items.end(), 0.0, [&](CGFloat x, const ASStackLayoutSpecItem &item) { return x + scaledFlexShrinkFactor(item, style, flexFactorSum); @@ -180,8 +244,8 @@ static std::function flexShrinkAdjustmen @return A lambda capable of computing the flex grow adjustment, if any, for a particular item. */ static std::function flexGrowAdjustment(const std::vector &items, - const CGFloat violation, - const CGFloat flexFactorSum) + const CGFloat violation, + const CGFloat flexFactorSum) { // To compute the flex grow adjustment distribute the violation proportionally based on each item's flex grow factor. return [violation, flexFactorSum](const ASStackLayoutSpecItem &item) { @@ -198,9 +262,9 @@ static std::function flexGrowAdjustment( @return A lambda capable of computing the flex adjustment for a particular item. */ static std::function flexAdjustmentInViolationDirection(const std::vector &items, - const ASStackLayoutSpecStyle &style, - const CGFloat violation, - const CGFloat flexFactorSum) + const ASStackLayoutSpecStyle &style, + const CGFloat violation, + const CGFloat flexFactorSum) { if (violation > 0) { return flexGrowAdjustment(items, violation, flexFactorSum); @@ -221,7 +285,7 @@ ASDISPLAYNODE_INLINE BOOL isFlexibleInBothDirections(const ASStackLayoutSpecChil static void layoutFlexibleChildrenAtZeroSize(std::vector &items, const ASStackLayoutSpecStyle &style, const ASSizeRange &sizeRange, - const CGSize size) + const CGSize parentSize) { for (ASStackLayoutSpecItem &item : items) { if (isFlexibleInBothDirections(item.child)) { @@ -231,13 +295,13 @@ static void layoutFlexibleChildrenAtZeroSize(std::vector 0, crossDimension(style.direction, sizeRange.min), crossDimension(style.direction, sizeRange.max), - size); + parentSize); } } } /** - Computes the consumed stack dimension length for the given vector of children and stacking style. + Computes the consumed stack dimension length for the given vector of items and stacking style. stackDimensionSum <-----------------------> @@ -247,22 +311,22 @@ static void layoutFlexibleChildrenAtZeroSize(std::vector +-----+ | | +---+ +-------+ - @param children unpositioned layouts for the children of the stack spec + @param items unpositioned layouts for items @param style the layout style of the overall stack layout */ -static CGFloat computeStackDimensionSum(const std::vector &children, +static CGFloat computeStackDimensionSum(const std::vector &items, const ASStackLayoutSpecStyle &style) { // Sum up the childrens' spacing - const CGFloat childSpacingSum = std::accumulate(children.begin(), children.end(), + const CGFloat childSpacingSum = std::accumulate(items.begin(), items.end(), // Start from default spacing between each child: - children.empty() ? 0 : style.spacing * (children.size() - 1), + items.empty() ? 0 : style.spacing * (items.size() - 1), [&](CGFloat x, const ASStackLayoutSpecItem &l) { return x + l.child.style.spacingBefore + l.child.style.spacingAfter; }); // Sum up the childrens' dimensions (including spacing) in the stack direction. - const CGFloat childStackDimensionSum = std::accumulate(children.begin(), children.end(), childSpacingSum, + const CGFloat childStackDimensionSum = std::accumulate(items.begin(), items.end(), childSpacingSum, [&](CGFloat x, const ASStackLayoutSpecItem &l) { return x + stackDimension(style.direction, l.layout.size); }); @@ -344,7 +408,7 @@ ASDISPLAYNODE_INLINE BOOL useOptimizedFlexing(const std::vector &items, const ASStackLayoutSpecStyle &style, const ASSizeRange &sizeRange, - const CGSize size, + const CGSize parentSize, const BOOL useOptimizedFlexing) { const CGFloat violation = computeViolation(computeStackDimensionSum(items, style), style, sizeRange); @@ -358,14 +422,14 @@ static void flexChildrenAlongStackDimension(std::vector & if (flexFactorSum == 0) { // If optimized flexing was used then we have to clean up the unsized children and lay them out at zero size. if (useOptimizedFlexing) { - layoutFlexibleChildrenAtZeroSize(items, style, sizeRange, size); + layoutFlexibleChildrenAtZeroSize(items, style, sizeRange, parentSize); } return; } std::function flexAdjustment = flexAdjustmentInViolationDirection(items, - style, - violation, - flexFactorSum); + style, + violation, + flexFactorSum); // Compute any remaining violation to the first flexible child. const CGFloat remainingViolation = std::accumulate(items.begin(), items.end(), violation, [&](CGFloat x, const ASStackLayoutSpecItem &item) { @@ -385,7 +449,7 @@ static void flexChildrenAlongStackDimension(std::vector & MAX(flexedStackSize, 0), crossDimension(style.direction, sizeRange.min), crossDimension(style.direction, sizeRange.max), - size); + parentSize); isFirstFlex = NO; } } @@ -396,10 +460,10 @@ static void flexChildrenAlongStackDimension(std::vector & stretched. */ static std::vector layoutChildrenAlongUnconstrainedStackDimension(const std::vector &children, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange, - const CGSize size, - const BOOL useOptimizedFlexing) + const ASStackLayoutSpecStyle &style, + const ASSizeRange &sizeRange, + const CGSize parentSize, + const BOOL useOptimizedFlexing) { const CGFloat minCrossDimension = crossDimension(style.direction, sizeRange.min); const CGFloat maxCrossDimension = crossDimension(style.direction, sizeRange.max); @@ -411,11 +475,11 @@ static std::vector layoutChildrenAlongUnconstrainedStackD child, crossChildLayout(child, style, - ASDimensionResolve(child.style.flexBasis, stackDimension(style.direction, size), 0), - ASDimensionResolve(child.style.flexBasis, stackDimension(style.direction, size), INFINITY), + ASDimensionResolve(child.style.flexBasis, stackDimension(style.direction, parentSize), 0), + ASDimensionResolve(child.style.flexBasis, stackDimension(style.direction, parentSize), INFINITY), minCrossDimension, maxCrossDimension, - size) + parentSize) }; } }); @@ -425,9 +489,13 @@ ASStackUnpositionedLayout ASStackUnpositionedLayout::compute(const std::vector items = layoutChildrenAlongUnconstrainedStackDimension(children, - style, - sizeRange, - size, - optimizedFlexing); - - flexChildrenAlongStackDimension(items, style, sizeRange, size, optimizedFlexing); - stretchChildrenAlongCrossDimension(items, style, size); - + style, + sizeRange, + parentSize, + optimizedFlexing); + + // Resolve the flexible lengths (https://www.w3.org/TR/css-flexbox-1/#algo-flex) + // Determine the hypothetical cross size of each item (https://www.w3.org/TR/css-flexbox-1/#algo-cross-item) + flexChildrenAlongStackDimension(items, style, sizeRange, parentSize, optimizedFlexing); + + // Step 4. Cross Size Determination (https://www.w3.org/TR/css-flexbox-1/#cross-sizing) + // + // Calculate the cross size of the stack (https://www.w3.org/TR/css-flexbox-1/#algo-cross-line) + CGFloat crossSize; + CGFloat baseline; + computeCrossSizeAndBaseline(items, style, sizeRange, crossSize, baseline); + // Determine the used cross size of each item (https://www.w3.org/TR/css-flexbox-1/#algo-stretch) + // If the flex item has stretch alignment, redo layout + stretchChildrenAlongCrossDimension(items, style, parentSize, crossSize); + const CGFloat stackDimensionSum = computeStackDimensionSum(items, style); - return {std::move(items), stackDimensionSum, computeViolation(stackDimensionSum, style, sizeRange)}; + return {std::move(items), stackDimensionSum, computeViolation(stackDimensionSum, style, sizeRange), crossSize, baseline}; } diff --git a/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm b/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm index e82fbe7f12..3291ccdad4 100644 --- a/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm +++ b/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm @@ -15,6 +15,7 @@ #import "ASBackgroundLayoutSpec.h" #import "ASRatioLayoutSpec.h" #import "ASInsetLayoutSpec.h" +#import "ASTextNode.h" @interface ASStackLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase @end @@ -57,17 +58,21 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node) node.style.height = ASDimensionMakeWithPoints(size.height); } -- (void)testDefaultStackLayoutElementFlexProperties +static NSArray *defaultTextNodes() { - ASDisplayNode *displayNode = [[ASDisplayNode alloc] init]; + ASTextNode *textNode1 = [[ASTextNode alloc] init]; + textNode1.attributedText = [[NSAttributedString alloc] initWithString:@"Hello" + attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:20]}]; + textNode1.backgroundColor = [UIColor redColor]; + textNode1.layerBacked = YES; - XCTAssertEqual(displayNode.style.flexShrink, NO); - XCTAssertEqual(displayNode.style.flexGrow, NO); + ASTextNode *textNode2 = [[ASTextNode alloc] init]; + textNode2.attributedText = [[NSAttributedString alloc] initWithString:@"Why, hello there! How are you?" + attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:12]}]; + textNode2.backgroundColor = [UIColor blueColor]; + textNode2.layerBacked = YES; - const ASDimension unconstrainedDimension = ASDimensionAuto; - const ASDimension flexBasis = displayNode.style.flexBasis; - XCTAssertEqual(flexBasis.unit, unconstrainedDimension.unit); - XCTAssertEqual(flexBasis.value, unconstrainedDimension.value); + return @[textNode1, textNode2]; } - (void)testStackLayoutSpecWithJustify:(ASStackLayoutJustifyContent)justify @@ -85,24 +90,6 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node) [self testStackLayoutSpecWithStyle:style sizeRange:sizeRange subnodes:subnodes identifier:identifier]; } -- (void)testStackLayoutSpecWithDirection:(ASStackLayoutDirection)direction - itemsHorizontalAlignment:(ASHorizontalAlignment)horizontalAlignment - itemsVerticalAlignment:(ASVerticalAlignment)verticalAlignment - identifier:(NSString *)identifier -{ - NSArray *subnodes = defaultSubnodesWithSameSize({50, 50}, 0); - - ASStackLayoutSpec *stackLayoutSpec = [[ASStackLayoutSpec alloc] init]; - stackLayoutSpec.direction = direction; - stackLayoutSpec.children = subnodes; - stackLayoutSpec.horizontalAlignment = horizontalAlignment; - stackLayoutSpec.verticalAlignment = verticalAlignment; - - CGSize exactSize = CGSizeMake(200, 200); - static ASSizeRange kSize = ASSizeRangeMake(exactSize, exactSize); - [self testStackLayoutSpec:stackLayoutSpec sizeRange:kSize subnodes:subnodes identifier:identifier]; -} - - (void)testStackLayoutSpecWithStyle:(ASStackLayoutSpecStyle)style sizeRange:(ASSizeRange)sizeRange subnodes:(NSArray *)subnodes @@ -128,6 +115,39 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node) [self testStackLayoutSpec:stackLayoutSpec sizeRange:sizeRange subnodes:subnodes identifier:identifier]; } +- (void)testStackLayoutSpecWithDirection:(ASStackLayoutDirection)direction + itemsHorizontalAlignment:(ASHorizontalAlignment)horizontalAlignment + itemsVerticalAlignment:(ASVerticalAlignment)verticalAlignment + identifier:(NSString *)identifier +{ + NSArray *subnodes = defaultSubnodesWithSameSize({50, 50}, 0); + + ASStackLayoutSpec *stackLayoutSpec = [[ASStackLayoutSpec alloc] init]; + stackLayoutSpec.direction = direction; + stackLayoutSpec.children = subnodes; + stackLayoutSpec.horizontalAlignment = horizontalAlignment; + stackLayoutSpec.verticalAlignment = verticalAlignment; + + CGSize exactSize = CGSizeMake(200, 200); + static ASSizeRange kSize = ASSizeRangeMake(exactSize, exactSize); + [self testStackLayoutSpec:stackLayoutSpec sizeRange:kSize subnodes:subnodes identifier:identifier]; +} + +- (void)testStackLayoutSpecWithBaselineAlignment:(ASStackLayoutAlignItems)baselineAlignment + identifier:(NSString *)identifier +{ + NSAssert(baselineAlignment == ASStackLayoutAlignItemsBaselineFirst || baselineAlignment == ASStackLayoutAlignItemsBaselineLast, @"Unexpected baseline alignment"); + NSArray *textNodes = defaultTextNodes(); + textNodes[1].style.flexShrink = 1.0; + + ASStackLayoutSpec *stackLayoutSpec = [ASStackLayoutSpec horizontalStackLayoutSpec]; + stackLayoutSpec.children = textNodes; + stackLayoutSpec.alignItems = baselineAlignment; + + static ASSizeRange kSize = ASSizeRangeMake(CGSizeMake(150, 0), CGSizeMake(150, CGFLOAT_MAX)); + [self testStackLayoutSpec:stackLayoutSpec sizeRange:kSize subnodes:textNodes identifier:identifier]; +} + - (void)testStackLayoutSpec:(ASStackLayoutSpec *)stackLayoutSpec sizeRange:(ASSizeRange)sizeRange subnodes:(NSArray *)subnodes @@ -142,8 +162,22 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node) [self testLayoutSpec:layoutSpec sizeRange:sizeRange subnodes:newSubnodes identifier:identifier]; } + #pragma mark - +- (void)testDefaultStackLayoutElementFlexProperties +{ + ASDisplayNode *displayNode = [[ASDisplayNode alloc] init]; + + XCTAssertEqual(displayNode.style.flexShrink, NO); + XCTAssertEqual(displayNode.style.flexGrow, NO); + + const ASDimension unconstrainedDimension = ASDimensionAuto; + const ASDimension flexBasis = displayNode.style.flexBasis; + XCTAssertEqual(flexBasis.unit, unconstrainedDimension.unit); + XCTAssertEqual(flexBasis.value, unconstrainedDimension.value); +} + - (void)testUnderflowBehaviors { // width 300px; height 0-300px @@ -1065,4 +1099,72 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node) XCTAssertEqual(stackLayoutSpec.justifyContent, ASStackLayoutJustifyContentEnd); } +#pragma mark - Baseline alignment tests + +- (void)testBaselineAlignment +{ + [self testStackLayoutSpecWithBaselineAlignment:ASStackLayoutAlignItemsBaselineFirst identifier:@"baselineFirst"]; + [self testStackLayoutSpecWithBaselineAlignment:ASStackLayoutAlignItemsBaselineLast identifier:@"baselineLast"]; +} + +- (void)testNestedBaselineAlignments +{ + NSArray *textNodes = defaultTextNodes(); + + ASDisplayNode *stretchedNode = [[ASDisplayNode alloc] init]; + stretchedNode.layerBacked = YES; + stretchedNode.backgroundColor = [UIColor greenColor]; + stretchedNode.style.alignSelf = ASStackLayoutAlignSelfStretch; + stretchedNode.style.height = ASDimensionMake(100); + + ASStackLayoutSpec *verticalStack = [ASStackLayoutSpec verticalStackLayoutSpec]; + verticalStack.children = @[stretchedNode, textNodes[1]]; + verticalStack.style.flexShrink = 1.0; + + ASStackLayoutSpec *horizontalStack = [ASStackLayoutSpec horizontalStackLayoutSpec]; + horizontalStack.children = @[textNodes[0], verticalStack]; + horizontalStack.alignItems = ASStackLayoutAlignItemsBaselineLast; + + NSArray *subnodes = @[textNodes[0], textNodes[1], stretchedNode]; + + static ASSizeRange kSize = ASSizeRangeMake(CGSizeMake(150, 0), CGSizeMake(150, CGFLOAT_MAX)); + [self testStackLayoutSpec:horizontalStack sizeRange:kSize subnodes:subnodes identifier:nil]; +} + +- (void)testBaselineAlignmentWithSpaceBetween +{ + NSArray *textNodes = defaultTextNodes(); + + ASStackLayoutSpec *stackLayoutSpec = [ASStackLayoutSpec horizontalStackLayoutSpec]; + stackLayoutSpec.children = textNodes; + stackLayoutSpec.alignItems = ASStackLayoutAlignItemsBaselineFirst; + stackLayoutSpec.justifyContent = ASStackLayoutJustifyContentSpaceBetween; + + static ASSizeRange kSize = ASSizeRangeMake(CGSizeMake(300, 0), CGSizeMake(300, CGFLOAT_MAX)); + [self testStackLayoutSpec:stackLayoutSpec sizeRange:kSize subnodes:textNodes identifier:nil]; +} + +- (void)testBaselineAlignmentWithStretchedItem +{ + NSArray *textNodes = defaultTextNodes(); + + ASDisplayNode *stretchedNode = [[ASDisplayNode alloc] init]; + stretchedNode.layerBacked = YES; + stretchedNode.backgroundColor = [UIColor greenColor]; + stretchedNode.style.alignSelf = ASStackLayoutAlignSelfStretch; + stretchedNode.style.flexShrink = 1.0; + stretchedNode.style.flexGrow = 1.0; + + NSMutableArray *children = [NSMutableArray arrayWithArray:textNodes]; + [children insertObject:stretchedNode atIndex:1]; + + ASStackLayoutSpec *stackLayoutSpec = [ASStackLayoutSpec horizontalStackLayoutSpec]; + stackLayoutSpec.children = children; + stackLayoutSpec.alignItems = ASStackLayoutAlignItemsBaselineLast; + stackLayoutSpec.justifyContent = ASStackLayoutJustifyContentSpaceBetween; + + static ASSizeRange kSize = ASSizeRangeMake(CGSizeMake(300, 0), CGSizeMake(300, CGFLOAT_MAX)); + [self testStackLayoutSpec:stackLayoutSpec sizeRange:kSize subnodes:children identifier:nil]; +} + @end diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithSpaceBetween@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithSpaceBetween@2x.png new file mode 100644 index 0000000000..5174b92e27 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithSpaceBetween@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithStretchedItem@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithStretchedItem@2x.png new file mode 100644 index 0000000000..f3e20dbe00 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithStretchedItem@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineFirst@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineFirst@2x.png new file mode 100644 index 0000000000..e45ab35c68 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineFirst@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineLast@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineLast@2x.png new file mode 100644 index 0000000000..6cd4909902 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineLast@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNestedBaselineAlignments@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNestedBaselineAlignments@2x.png new file mode 100644 index 0000000000..67f32cb4df Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testNestedBaselineAlignments@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithSpaceBetween@2x.png b/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithSpaceBetween@2x.png new file mode 100644 index 0000000000..259778c672 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithSpaceBetween@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithStretchedItem@2x.png b/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithStretchedItem@2x.png new file mode 100644 index 0000000000..9e8286a88c Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignmentWithStretchedItem@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineFirst@2x.png b/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineFirst@2x.png new file mode 100644 index 0000000000..7f4045ee98 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineFirst@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineLast@2x.png b/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineLast@2x.png new file mode 100644 index 0000000000..c81f3e9ca4 Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testBaselineAlignment_baselineLast@2x.png differ diff --git a/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testNestedBaselineAlignments@2x.png b/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testNestedBaselineAlignments@2x.png new file mode 100644 index 0000000000..679b98a52c Binary files /dev/null and b/AsyncDisplayKitTests/ReferenceImages_iOS_10/ASStackLayoutSpecSnapshotTests/testNestedBaselineAlignments@2x.png differ