Implement "space between" and "space around" options of stack layout justify content

This commit is contained in:
Huy Nguyen
2015-11-15 20:01:13 +02:00
parent 390d16caef
commit 1162e02b06
9 changed files with 118 additions and 7 deletions

View File

@@ -33,6 +33,21 @@ typedef NS_ENUM(NSUInteger, ASStackLayoutJustifyContent) {
On underflow, children are right/bottom-aligned within this spec's bounds. On underflow, children are right/bottom-aligned within this spec's bounds.
*/ */
ASStackLayoutJustifyContentEnd, 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 */ /** Orientation of children along cross axis */

View File

@@ -15,6 +15,7 @@
#import "ASStackLayoutSpecUtilities.h" #import "ASStackLayoutSpecUtilities.h"
#import "ASLayoutable.h" #import "ASLayoutable.h"
#import "ASLayoutOptions.h" #import "ASLayoutOptions.h"
#import "ASAssert.h"
static CGFloat crossOffset(const ASStackLayoutSpecStyle &style, static CGFloat crossOffset(const ASStackLayoutSpecStyle &style,
const ASStackUnpositionedItem &l, const ASStackUnpositionedItem &l,
@@ -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, static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style,
const CGFloat offset, const CGFloat firstChildOffset,
const CGFloat extraSpacing,
const CGFloat lastChildOffset,
const ASStackUnpositionedLayout &unpositionedLayout, const ASStackUnpositionedLayout &unpositionedLayout,
const ASSizeRange &constrainedSize) const ASSizeRange &constrainedSize)
{ {
@@ -48,12 +61,16 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style
const auto maxCrossSize = crossDimension(style.direction, constrainedSize.max); const auto maxCrossSize = crossDimension(style.direction, constrainedSize.max);
const CGFloat crossSize = MIN(MAX(minCrossSize, largestChildCrossSize), maxCrossSize); 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; BOOL first = YES;
const auto lastChild = unpositionedLayout.items.back().child;
CGFloat offset = 0;
auto stackedChildren = AS::map(unpositionedLayout.items, [&](const ASStackUnpositionedItem &l) -> ASLayout *{ 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) { if (!first) {
p = p + directionPoint(style.direction, style.spacing, 0); p = p + directionPoint(style.direction, style.spacing + extraSpacing, 0);
} }
first = NO; 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));
@@ -64,16 +81,45 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style
return {stackedChildren, crossSize}; 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, ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnpositionedLayout &unpositionedLayout,
const ASStackLayoutSpecStyle &style, const ASStackLayoutSpecStyle &style,
const ASSizeRange &constrainedSize) 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: case ASStackLayoutJustifyContentStart:
return stackedLayout(style, 0, unpositionedLayout, constrainedSize); return stackedLayout(style, 0, unpositionedLayout, constrainedSize);
case ASStackLayoutJustifyContentCenter: case ASStackLayoutJustifyContentCenter:
return stackedLayout(style, floorf(unpositionedLayout.violation / 2), unpositionedLayout, constrainedSize); return stackedLayout(style, floorf(violation / 2), unpositionedLayout, constrainedSize);
case ASStackLayoutJustifyContentEnd: 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);
}
} }
} }

View File

@@ -98,6 +98,8 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex)
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentCenter flex:NO sizeRange:kSize identifier:@"justifyCenter"]; [self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentCenter flex:NO sizeRange:kSize identifier:@"justifyCenter"];
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentEnd flex:NO sizeRange:kSize identifier:@"justifyEnd"]; [self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentEnd flex:NO sizeRange:kSize identifier:@"justifyEnd"];
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentStart flex:YES sizeRange:kSize identifier:@"flex"]; [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 - (void)testOverflowBehaviors
@@ -108,6 +110,10 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex)
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentCenter flex:NO sizeRange:kSize identifier:@"justifyCenter"]; [self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentCenter flex:NO sizeRange:kSize identifier:@"justifyCenter"];
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentEnd flex:NO sizeRange:kSize identifier:@"justifyEnd"]; [self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentEnd flex:NO sizeRange:kSize identifier:@"justifyEnd"];
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentStart flex:YES sizeRange:kSize identifier:@"flex"]; [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 - (void)testOverflowBehaviorsWhenAllFlexShrinkChildrenHaveBeenClampedToZeroButViolationStillExists
@@ -246,6 +252,50 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex)
[self testStackLayoutSpecWithStyle:style sizeRange:kVariableHeight subnodes:subnodes identifier:@"variableHeight"]; [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 - (void)testChildThatChangesCrossSizeWhenMainSizeIsFlexed
{ {
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal}; ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB