Skip to content
This repository was archived by the owner on Feb 2, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions AsyncDisplayKit/Layout/ASStackLayoutDefines.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,21 @@ typedef NS_ENUM(NSUInteger, ASStackLayoutJustifyContent) {
On underflow, children are right/bottom-aligned within this spec's bounds.
*/
ASStackLayoutJustifyContentEnd,
/**
On overflow or if the stack has only 1 child, this value is identical to ASStackLayoutJustifyContentStart.
Otherwise, the starting edge of the first child is at the starting edge of the stack,
the ending edge of the last child is at the ending edge of the stack, and the remaining children
are distributed so that the spacing between any two adjacent ones is the same.
If there is a remaining space after spacing division, it is combined with the last spacing (i.e the one between the last 2 children).
*/
ASStackLayoutJustifyContentSpaceBetween,
/**
On overflow or if the stack has only 1 child, this value is identical to ASStackLayoutJustifyContentCenter.
Otherwise, children are distributed such that the spacing between any two adjacent ones is the same,
and the spacing between the first/last child and the stack edges is half the size of the spacing between children.
If there is a remaining space after spacing division, it is combined with the last spacing (i.e the one between the last child and the stack ending edge).
*/
ASStackLayoutJustifyContentSpaceAround
};

/** Orientation of children along cross axis */
Expand Down
4 changes: 4 additions & 0 deletions AsyncDisplayKit/Layout/ASStackLayoutSpec.mm
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ - (void)setChild:(id<ASLayoutable>)child forIdentifier:(NSString *)identifier

- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize
{
if (self.children.count == 0) {
return [ASLayout layoutWithLayoutableObject:self size:constrainedSize.min];
}

ASStackLayoutSpecStyle style = {.direction = _direction, .spacing = _spacing, .justifyContent = _justifyContent, .alignItems = _alignItems, .baselineRelativeArrangement = _baselineRelativeArrangement};
BOOL needsBaselinePass = _baselineRelativeArrangement || _alignItems == ASStackLayoutAlignItemsBaselineFirst || _alignItems == ASStackLayoutAlignItemsBaselineLast;

Expand Down
60 changes: 53 additions & 7 deletions AsyncDisplayKit/Private/ASStackPositionedLayout.mm
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#import "ASStackLayoutSpecUtilities.h"
#import "ASLayoutable.h"
#import "ASLayoutOptions.h"
#import "ASAssert.h"

static CGFloat crossOffset(const ASStackLayoutSpecStyle &style,
const ASStackUnpositionedItem &l,
Expand All @@ -33,8 +34,20 @@ static CGFloat crossOffset(const ASStackLayoutSpecStyle &style,
}
}

/**
* Positions children according to the stack style and positioning properties.
*
* @param style The layout style of the overall stack layout
* @param firstChildOffset Offset of the first child
* @param extraSpacing Spacing between children, in addition to spacing set to the stack's layout style
* @param lastChildOffset Offset of the last child
* @param unpositionedLayout Unpositioned children of the stack
* @param constrainedSize Constrained size of the stack
*/
static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style,
const CGFloat offset,
const CGFloat firstChildOffset,
const CGFloat extraSpacing,
const CGFloat lastChildOffset,
const ASStackUnpositionedLayout &unpositionedLayout,
const ASSizeRange &constrainedSize)
{
Expand All @@ -48,12 +61,16 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style
const auto maxCrossSize = crossDimension(style.direction, constrainedSize.max);
const CGFloat crossSize = MIN(MAX(minCrossSize, largestChildCrossSize), maxCrossSize);

CGPoint p = directionPoint(style.direction, offset, 0);
CGPoint p = directionPoint(style.direction, firstChildOffset, 0);
BOOL first = YES;
const auto lastChild = unpositionedLayout.items.back().child;
CGFloat offset = 0;

auto stackedChildren = AS::map(unpositionedLayout.items, [&](const ASStackUnpositionedItem &l) -> ASLayout *{
p = p + directionPoint(style.direction, l.child.spacingBefore, 0);
offset = (l.child == lastChild) ? lastChildOffset : 0;
p = p + directionPoint(style.direction, l.child.spacingBefore + offset, 0);
if (!first) {
p = p + directionPoint(style.direction, style.spacing, 0);
p = p + directionPoint(style.direction, style.spacing + extraSpacing, 0);
}
first = NO;
l.layout.position = p + directionPoint(style.direction, 0, crossOffset(style, l, crossSize));
Expand All @@ -64,16 +81,45 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style
return {stackedChildren, crossSize};
}

static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style,
const CGFloat firstChildOffset,
const ASStackUnpositionedLayout &unpositionedLayout,
const ASSizeRange &constrainedSize)
{
return stackedLayout(style, firstChildOffset, 0, 0, unpositionedLayout, constrainedSize);
}

ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnpositionedLayout &unpositionedLayout,
const ASStackLayoutSpecStyle &style,
const ASSizeRange &constrainedSize)
{
switch (style.justifyContent) {
const auto numOfItems = unpositionedLayout.items.size();
ASDisplayNodeCAssertTrue(numOfItems > 0);
const CGFloat violation = unpositionedLayout.violation;
ASStackLayoutJustifyContent justifyContent = style.justifyContent;

// Handle edge cases of "space between" and "space around"
if (justifyContent == ASStackLayoutJustifyContentSpaceBetween && (violation < 0 || numOfItems == 1)) {
justifyContent = ASStackLayoutJustifyContentStart;
} else if (justifyContent == ASStackLayoutJustifyContentSpaceAround && (violation < 0 || numOfItems == 1)) {
justifyContent = ASStackLayoutJustifyContentCenter;
}

switch (justifyContent) {
case ASStackLayoutJustifyContentStart:
return stackedLayout(style, 0, unpositionedLayout, constrainedSize);
case ASStackLayoutJustifyContentCenter:
return stackedLayout(style, floorf(unpositionedLayout.violation / 2), unpositionedLayout, constrainedSize);
return stackedLayout(style, floorf(violation / 2), unpositionedLayout, constrainedSize);
case ASStackLayoutJustifyContentEnd:
return stackedLayout(style, unpositionedLayout.violation, unpositionedLayout, constrainedSize);
return stackedLayout(style, violation, unpositionedLayout, constrainedSize);
case ASStackLayoutJustifyContentSpaceBetween: {
const auto numOfSpacings = numOfItems - 1;
return stackedLayout(style, 0, floorf(violation / numOfSpacings), fmodf(violation, numOfSpacings), unpositionedLayout, constrainedSize);
}
case ASStackLayoutJustifyContentSpaceAround: {
// Spacing between items are twice the spacing on the edges
CGFloat spacingUnit = floorf(violation / (numOfItems * 2));
return stackedLayout(style, spacingUnit, spacingUnit * 2, 0, unpositionedLayout, constrainedSize);
}
}
}
50 changes: 50 additions & 0 deletions AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ - (void)testUnderflowBehaviors
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentCenter flex:NO sizeRange:kSize identifier:@"justifyCenter"];
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentEnd flex:NO sizeRange:kSize identifier:@"justifyEnd"];
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentStart flex:YES sizeRange:kSize identifier:@"flex"];
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceBetween flex:NO sizeRange:kSize identifier:@"justifySpaceBetween"];
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceAround flex:NO sizeRange:kSize identifier:@"justifySpaceAround"];
}

- (void)testOverflowBehaviors
Expand All @@ -108,6 +110,10 @@ - (void)testOverflowBehaviors
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentCenter flex:NO sizeRange:kSize identifier:@"justifyCenter"];
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentEnd flex:NO sizeRange:kSize identifier:@"justifyEnd"];
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentStart flex:YES sizeRange:kSize identifier:@"flex"];
// On overflow, "space between" is identical to "content start"
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceBetween flex:NO sizeRange:kSize identifier:@"justifyStart"];
// On overflow, "space around" is identical to "content center"
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceAround flex:NO sizeRange:kSize identifier:@"justifyCenter"];
}

- (void)testOverflowBehaviorsWhenAllFlexShrinkChildrenHaveBeenClampedToZeroButViolationStillExists
Expand Down Expand Up @@ -246,6 +252,50 @@ - (void)testJustifiedCenterWithChildSpacing
[self testStackLayoutSpecWithStyle:style sizeRange:kVariableHeight subnodes:subnodes identifier:@"variableHeight"];
}

- (void)testJustifiedSpaceBetweenWithOneChild
{
ASStackLayoutSpecStyle style = {
.direction = ASStackLayoutDirectionHorizontal,
.justifyContent = ASStackLayoutJustifyContentSpaceBetween
};

ASStaticSizeDisplayNode *child = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
child.staticSize = {50, 50};

// width 300px; height 0-INF
static ASSizeRange kVariableHeight = {{300, 0}, {300, INFINITY}};
[self testStackLayoutSpecWithStyle:style sizeRange:kVariableHeight subnodes:@[child] identifier:nil];
}

- (void)testJustifiedSpaceAroundWithOneChild
{
ASStackLayoutSpecStyle style = {
.direction = ASStackLayoutDirectionHorizontal,
.justifyContent = ASStackLayoutJustifyContentSpaceAround
};

ASStaticSizeDisplayNode *child = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
child.staticSize = {50, 50};

// width 300px; height 0-INF
static ASSizeRange kVariableHeight = {{300, 0}, {300, INFINITY}};
[self testStackLayoutSpecWithStyle:style sizeRange:kVariableHeight subnodes:@[child] identifier:nil];
}

- (void)testJustifiedSpaceBetweenWithRemainingSpace
{
// width 301px; height 0-300px; 1px remaining
static ASSizeRange kSize = {{301, 0}, {301, 300}};
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceBetween flex:NO sizeRange:kSize identifier:nil];
}

- (void)testJustifiedSpaceAroundWithRemainingSpace
{
// width 305px; height 0-300px; 5px remaining
static ASSizeRange kSize = {{305, 0}, {305, 300}};
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceAround flex:NO sizeRange:kSize identifier:nil];
}

- (void)testChildThatChangesCrossSizeWhenMainSizeIsFlexed
{
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.