Merge pull request #848 from nguyenhuy/JustifyContentSpaceBetweenAndAround

Implement "space between" and "space around" justify content options for stack layout
This commit is contained in:
appleguy
2015-11-29 21:46:42 -08:00
10 changed files with 122 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.
*/
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 */

View File

@@ -137,6 +137,10 @@
- (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;

View File

@@ -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,
@@ -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)
{
@@ -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));
@@ -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);
}
}
}

View File

@@ -130,6 +130,8 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex)
[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
@@ -140,6 +142,10 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex)
[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
@@ -278,6 +284,50 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex)
[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};

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB