mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 14:20:20 +00:00
Implement "space between" and "space around" options of stack layout justify content
This commit is contained in:
@@ -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 */
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 |
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
Reference in New Issue
Block a user