From ec83812fcfab9e59513d35d9054681fa5045fb27 Mon Sep 17 00:00:00 2001 From: Flo Date: Thu, 3 Aug 2017 12:24:08 +0200 Subject: [PATCH] [ASStackLayoutSpec] Flex wrap fix and lineSpacing property (#472) * ASStackUnpositionedLayout: Take spacing into account when laying out a wrapped stack. * ASStackLayoutSpec: Add the lineSpacing property. * Update CHANGELOG.md. --- CHANGELOG.md | 2 ++ Source/Layout/ASStackLayoutSpec.h | 21 +++++++++++++++++++ Source/Layout/ASStackLayoutSpec.mm | 16 +++++++++----- .../Layout/ASStackLayoutSpecUtilities.h | 1 + .../Private/Layout/ASStackPositionedLayout.mm | 3 ++- .../Layout/ASStackUnpositionedLayout.mm | 16 ++++++++------ 6 files changed, 47 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4164075f5..9f85d5101 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASStackLayoutSpec] Add lineSpacing property working with flex wrap. [Flo Vouin](https://github.com/flovouin) +- [ASStackLayoutSpec] Fix flex wrap overflow in some cases using item spacing. [Flo Vouin](https://github.com/flovouin) - [ASNodeController] Add -nodeDidLayout callback. Allow switching retain behavior at runtime. [Scott Goodson](https://github.com/appleguy) - [ASCollectionView] Add delegate bridging and index space translation for missing UICollectionViewLayout properties. [Scott Goodson](https://github.com/appleguy) - [ASTextNode2] Add initial implementation for link handling. [Scott Goodson](https://github.com/appleguy) [#396](https://github.com/TextureGroup/Texture/pull/396) diff --git a/Source/Layout/ASStackLayoutSpec.h b/Source/Layout/ASStackLayoutSpec.h index 3ac682dc6..b4a27697e 100644 --- a/Source/Layout/ASStackLayoutSpec.h +++ b/Source/Layout/ASStackLayoutSpec.h @@ -70,6 +70,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) ASStackLayoutFlexWrap flexWrap; /** Orientation of lines along cross axis if there are multiple lines. Defaults to ASStackLayoutAlignContentStart */ @property (nonatomic, assign) ASStackLayoutAlignContent alignContent; +/** If the stack spreads on multiple lines using flexWrap, the amount of space between lines. */ +@property (nonatomic, assign) CGFloat lineSpacing; /** Whether this stack can dispatch to other threads, regardless of which thread it's running on */ @property (nonatomic, assign, getter=isConcurrent) BOOL concurrent; @@ -105,6 +107,25 @@ NS_ASSUME_NONNULL_BEGIN alignContent:(ASStackLayoutAlignContent)alignContent children:(NSArray> *)children AS_WARN_UNUSED_RESULT; +/** + @param direction The direction of the stack view (horizontal or vertical) + @param spacing The spacing between the children + @param justifyContent If no children are flexible, this describes how to fill any extra space + @param alignItems Orientation of the children along the cross axis + @param flexWrap Whether children are stacked into a single or multiple lines + @param alignContent Orientation of lines along cross axis if there are multiple lines + @param lineSpacing The spacing between lines + @param children ASLayoutElement children to be positioned. + */ ++ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction + spacing:(CGFloat)spacing + justifyContent:(ASStackLayoutJustifyContent)justifyContent + alignItems:(ASStackLayoutAlignItems)alignItems + flexWrap:(ASStackLayoutFlexWrap)flexWrap + alignContent:(ASStackLayoutAlignContent)alignContent + lineSpacing:(CGFloat)lineSpacing + children:(NSArray> *)children AS_WARN_UNUSED_RESULT; + /** * @return A stack layout spec with direction of ASStackLayoutDirectionVertical **/ diff --git a/Source/Layout/ASStackLayoutSpec.mm b/Source/Layout/ASStackLayoutSpec.mm index 4a3de0c70..fc89d6f97 100644 --- a/Source/Layout/ASStackLayoutSpec.mm +++ b/Source/Layout/ASStackLayoutSpec.mm @@ -33,17 +33,22 @@ @implementation ASStackLayoutSpec - (instancetype)init { - return [self initWithDirection:ASStackLayoutDirectionHorizontal spacing:0.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStretch flexWrap:ASStackLayoutFlexWrapNoWrap alignContent:ASStackLayoutAlignContentStart children:nil]; + return [self initWithDirection:ASStackLayoutDirectionHorizontal spacing:0.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStretch flexWrap:ASStackLayoutFlexWrapNoWrap alignContent:ASStackLayoutAlignContentStart lineSpacing:0.0 children:nil]; } + (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems children:(NSArray *)children { - return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems flexWrap:ASStackLayoutFlexWrapNoWrap alignContent:ASStackLayoutAlignContentStart children:children]; + return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems flexWrap:ASStackLayoutFlexWrapNoWrap alignContent:ASStackLayoutAlignContentStart lineSpacing: 0.0 children:children]; } + (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent children:(NSArray> *)children { - return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems flexWrap:flexWrap alignContent:alignContent children:children]; + return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems flexWrap:flexWrap alignContent:alignContent lineSpacing:0.0 children:children]; +} + ++ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent lineSpacing:(CGFloat)lineSpacing children:(NSArray> *)children +{ + return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems flexWrap:flexWrap alignContent:alignContent lineSpacing:lineSpacing children:children]; } + (instancetype)verticalStackLayoutSpec @@ -60,7 +65,7 @@ + (instancetype)horizontalStackLayoutSpec return stackLayoutSpec; } -- (instancetype)initWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent children:(NSArray *)children +- (instancetype)initWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent lineSpacing:(CGFloat)lineSpacing children:(NSArray *)children { if (!(self = [super init])) { return nil; @@ -73,6 +78,7 @@ - (instancetype)initWithDirection:(ASStackLayoutDirection)direction spacing:(CGF _justifyContent = justifyContent; _flexWrap = flexWrap; _alignContent = alignContent; + _lineSpacing = lineSpacing; [self setChildren:children]; return self; @@ -144,7 +150,7 @@ - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize return {child, style, style.size}; }); - const ASStackLayoutSpecStyle style = {.direction = _direction, .spacing = _spacing, .justifyContent = _justifyContent, .alignItems = _alignItems, .flexWrap = _flexWrap, .alignContent = _alignContent}; + const ASStackLayoutSpecStyle style = {.direction = _direction, .spacing = _spacing, .justifyContent = _justifyContent, .alignItems = _alignItems, .flexWrap = _flexWrap, .alignContent = _alignContent, .lineSpacing = _lineSpacing}; const auto unpositionedLayout = ASStackUnpositionedLayout::compute(stackChildren, style, constrainedSize, _concurrent); const auto positionedLayout = ASStackPositionedLayout::compute(unpositionedLayout, style, constrainedSize); diff --git a/Source/Private/Layout/ASStackLayoutSpecUtilities.h b/Source/Private/Layout/ASStackLayoutSpecUtilities.h index 3b677be7e..dbb2c7435 100644 --- a/Source/Private/Layout/ASStackLayoutSpecUtilities.h +++ b/Source/Private/Layout/ASStackLayoutSpecUtilities.h @@ -24,6 +24,7 @@ typedef struct { ASStackLayoutAlignItems alignItems; ASStackLayoutFlexWrap flexWrap; ASStackLayoutAlignContent alignContent; + CGFloat lineSpacing; } ASStackLayoutSpecStyle; inline CGFloat stackDimension(const ASStackLayoutDirection direction, const CGSize size) diff --git a/Source/Private/Layout/ASStackPositionedLayout.mm b/Source/Private/Layout/ASStackPositionedLayout.mm index 382e41686..9b1ba020a 100644 --- a/Source/Private/Layout/ASStackPositionedLayout.mm +++ b/Source/Private/Layout/ASStackPositionedLayout.mm @@ -160,6 +160,7 @@ static void positionItemsInLine(const ASStackUnpositionedLine &line, const auto numOfLines = lines.size(); const auto direction = style.direction; const auto alignContent = style.alignContent; + const auto lineSpacing = style.lineSpacing; const auto justifyContent = style.justifyContent; const auto crossViolation = ASStackUnpositionedLayout::computeCrossViolation(layout.crossDimensionSum, style, sizeRange); CGFloat crossOffset; @@ -171,7 +172,7 @@ static void positionItemsInLine(const ASStackUnpositionedLine &line, BOOL first = YES; for (const auto &line : lines) { if (!first) { - p = p + directionPoint(direction, 0, crossSpacing); + p = p + directionPoint(direction, 0, crossSpacing + lineSpacing); } first = NO; diff --git a/Source/Private/Layout/ASStackUnpositionedLayout.mm b/Source/Private/Layout/ASStackUnpositionedLayout.mm index 6f43a4535..8bed958aa 100644 --- a/Source/Private/Layout/ASStackUnpositionedLayout.mm +++ b/Source/Private/Layout/ASStackUnpositionedLayout.mm @@ -113,9 +113,12 @@ static void dispatchApplyIfNeeded(size_t iterationCount, BOOL forced, void(^work @param lines unpositioned lines */ -static CGFloat computeLinesCrossDimensionSum(const std::vector &lines) +static CGFloat computeLinesCrossDimensionSum(const std::vector &lines, + const ASStackLayoutSpecStyle &style) { - return std::accumulate(lines.begin(), lines.end(), 0.0, + return std::accumulate(lines.begin(), lines.end(), + // Start from default spacing between each line: + lines.empty() ? 0 : style.lineSpacing * (lines.size() - 1), [&](CGFloat x, const ASStackUnpositionedLine &l) { return x + l.crossSize; }); @@ -236,7 +239,7 @@ static void stretchLinesAlongCrossDimension(std::vector { ASDisplayNodeCAssertFalse(lines.empty()); const std::size_t numOfLines = lines.size(); - const CGFloat violation = ASStackUnpositionedLayout::computeCrossViolation(computeLinesCrossDimensionSum(lines), style, sizeRange); + const CGFloat violation = ASStackUnpositionedLayout::computeCrossViolation(computeLinesCrossDimensionSum(lines, style), style, sizeRange); // Don't stretch if the stack is single line, because the line's cross size was clamped against the stack's constrained size. const BOOL shouldStretchLines = (numOfLines > 1 && style.alignContent == ASStackLayoutAlignContentStretch @@ -648,7 +651,8 @@ static void flexLinesAlongStackDimension(std::vector &l for(auto it = items.begin(); it != items.end(); ++it) { const auto &item = *it; const CGFloat itemStackDimension = stackDimension(style.direction, item.layout.size); - const BOOL negativeViolationIfAddItem = (ASStackUnpositionedLayout::computeStackViolation(lineStackDimensionSum + itemStackDimension, style, sizeRange) < 0); + const CGFloat itemAndSpacingStackDimension = (lineItems.empty() ? 0.0 : style.spacing) + item.child.style.spacingBefore + itemStackDimension + item.child.style.spacingAfter; + const BOOL negativeViolationIfAddItem = (ASStackUnpositionedLayout::computeStackViolation(lineStackDimensionSum + itemAndSpacingStackDimension, style, sizeRange) < 0); const BOOL breakCurrentLine = negativeViolationIfAddItem && !lineItems.empty(); if (breakCurrentLine) { @@ -658,7 +662,7 @@ static void flexLinesAlongStackDimension(std::vector &l } lineItems.push_back(std::move(item)); - lineStackDimensionSum += itemStackDimension; + lineStackDimensionSum += itemAndSpacingStackDimension; } // Handle last line @@ -752,7 +756,7 @@ static void layoutItemsAlongUnconstrainedStackDimension(std::vector