[Layout] Treat flex factors as true factors (#2349)
* Treat flex factors as factors * Add snapshot tests * Address comments
@ -131,8 +131,8 @@ static const CGFloat kViolationEpsilon = 0.01;
|
|||||||
*/
|
*/
|
||||||
static std::function<CGFloat(const ASStackUnpositionedItem &)> flexFactorInViolationDirection(const CGFloat violation)
|
static std::function<CGFloat(const ASStackUnpositionedItem &)> flexFactorInViolationDirection(const CGFloat violation)
|
||||||
{
|
{
|
||||||
if (fabs(violation) < kViolationEpsilon) {
|
if (std::fabs(violation) < kViolationEpsilon) {
|
||||||
return [](const ASStackUnpositionedItem &item) { return 0; };
|
return [](const ASStackUnpositionedItem &item) { return 0.0; };
|
||||||
} else if (violation > 0) {
|
} else if (violation > 0) {
|
||||||
return [](const ASStackUnpositionedItem &item) { return item.child.style.flexGrow; };
|
return [](const ASStackUnpositionedItem &item) { return item.child.style.flexGrow; };
|
||||||
} else {
|
} else {
|
||||||
@ -140,9 +140,11 @@ static std::function<CGFloat(const ASStackUnpositionedItem &)> flexFactorInViola
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline CGFloat scaledFlexShrinkFactor(const ASStackUnpositionedItem &item, const ASStackLayoutSpecStyle &style)
|
static inline CGFloat scaledFlexShrinkFactor(const ASStackUnpositionedItem &item,
|
||||||
|
const ASStackLayoutSpecStyle &style,
|
||||||
|
const CGFloat flexFactorSum)
|
||||||
{
|
{
|
||||||
return stackDimension(style.direction, item.layout.size) * item.child.style.flexShrink;
|
return stackDimension(style.direction, item.layout.size) * (item.child.style.flexShrink / flexFactorSum);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -150,20 +152,22 @@ static inline CGFloat scaledFlexShrinkFactor(const ASStackUnpositionedItem &item
|
|||||||
@param items The unpositioned items from the original unconstrained layout pass.
|
@param items The unpositioned items from the original unconstrained layout pass.
|
||||||
@param style The layout style to be applied to all children.
|
@param style The layout style to be applied to all children.
|
||||||
@param violation The amount that the stack layout violates its size range.
|
@param violation The amount that the stack layout violates its size range.
|
||||||
|
@param flexFactorSum The sum of each item's flex factor as determined by the provided violation.
|
||||||
@return A lambda capable of computing the flex shrink adjustment, if any, for a particular item.
|
@return A lambda capable of computing the flex shrink adjustment, if any, for a particular item.
|
||||||
*/
|
*/
|
||||||
static std::function<CGFloat(const ASStackUnpositionedItem &, BOOL)> flexShrinkAdjustment(const std::vector<ASStackUnpositionedItem> &items,
|
static std::function<CGFloat(const ASStackUnpositionedItem &)> flexShrinkAdjustment(const std::vector<ASStackUnpositionedItem> &items,
|
||||||
const ASStackLayoutSpecStyle &style,
|
const ASStackLayoutSpecStyle &style,
|
||||||
const CGFloat violation)
|
const CGFloat violation,
|
||||||
|
const CGFloat flexFactorSum)
|
||||||
{
|
{
|
||||||
const CGFloat scaledFlexShrinkFactorSum = std::accumulate(items.begin(), items.end(), 0, [&](CGFloat x, const ASStackUnpositionedItem &item) {
|
const CGFloat scaledFlexShrinkFactorSum = std::accumulate(items.begin(), items.end(), 0.0, [&](CGFloat x, const ASStackUnpositionedItem &item) {
|
||||||
return x + scaledFlexShrinkFactor(item, style);
|
return x + scaledFlexShrinkFactor(item, style, flexFactorSum);
|
||||||
});
|
});
|
||||||
return [style, scaledFlexShrinkFactorSum, violation](const ASStackUnpositionedItem &item, BOOL isFirstFlex) {
|
return [style, scaledFlexShrinkFactorSum, violation, flexFactorSum](const ASStackUnpositionedItem &item) {
|
||||||
const CGFloat scaledFlexShrinkFactorRatio = scaledFlexShrinkFactor(item, style) / scaledFlexShrinkFactorSum;
|
const CGFloat scaledFlexShrinkFactorRatio = scaledFlexShrinkFactor(item, style, flexFactorSum) / scaledFlexShrinkFactorSum;
|
||||||
// The item should shrink proportionally to the scaled flex shrink factor ratio computed above.
|
// The item should shrink proportionally to the scaled flex shrink factor ratio computed above.
|
||||||
// Unlike the flex grow adjustment the flex shrink adjustment needs to take the size of each item into account.
|
// Unlike the flex grow adjustment the flex shrink adjustment needs to take the size of each item into account.
|
||||||
return -fabs(scaledFlexShrinkFactorRatio * violation);
|
return -std::fabs(scaledFlexShrinkFactorRatio * violation);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,17 +178,13 @@ static std::function<CGFloat(const ASStackUnpositionedItem &, BOOL)> flexShrinkA
|
|||||||
@param flexFactorSum The sum of each item's flex factor as determined by the provided violation.
|
@param flexFactorSum The sum of each item's flex factor as determined by the provided violation.
|
||||||
@return A lambda capable of computing the flex grow adjustment, if any, for a particular item.
|
@return A lambda capable of computing the flex grow adjustment, if any, for a particular item.
|
||||||
*/
|
*/
|
||||||
static std::function<CGFloat(const ASStackUnpositionedItem &, BOOL)> flexGrowAdjustment(const std::vector<ASStackUnpositionedItem> &items,
|
static std::function<CGFloat(const ASStackUnpositionedItem &)> flexGrowAdjustment(const std::vector<ASStackUnpositionedItem> &items,
|
||||||
const CGFloat violation,
|
const CGFloat violation,
|
||||||
const CGFloat flexFactorSum)
|
const CGFloat flexFactorSum)
|
||||||
{
|
{
|
||||||
const CGFloat violationPerFlexFactor = floorf(violation / flexFactorSum);
|
|
||||||
const CGFloat remainingViolation = violation - (violationPerFlexFactor * flexFactorSum);
|
|
||||||
// To compute the flex grow adjustment distribute the violation proportionally based on each item's flex grow factor.
|
// To compute the flex grow adjustment distribute the violation proportionally based on each item's flex grow factor.
|
||||||
// If there happens to be a violation remaining make sure it is allocated to the first flexible child.
|
return [violation, flexFactorSum](const ASStackUnpositionedItem &item) {
|
||||||
return [violationPerFlexFactor, remainingViolation](const ASStackUnpositionedItem &item, BOOL isFirstFlex) {
|
return std::floor(violation * (item.child.style.flexGrow / flexFactorSum));
|
||||||
// Only apply the remaining violation for the first flexible child that has a flex grow factor.
|
|
||||||
return violationPerFlexFactor * item.child.style.flexGrow + (isFirstFlex && item.child.style.flexGrow > 0 ? remainingViolation : 0);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,15 +196,15 @@ static std::function<CGFloat(const ASStackUnpositionedItem &, BOOL)> flexGrowAdj
|
|||||||
@param flexFactorSum The sum of each item's flex factor as determined by the provided violation.
|
@param flexFactorSum The sum of each item's flex factor as determined by the provided violation.
|
||||||
@return A lambda capable of computing the flex adjustment for a particular item.
|
@return A lambda capable of computing the flex adjustment for a particular item.
|
||||||
*/
|
*/
|
||||||
static std::function<CGFloat(const ASStackUnpositionedItem &, BOOL)> flexAdjustmentInViolationDirection(const std::vector<ASStackUnpositionedItem> &items,
|
static std::function<CGFloat(const ASStackUnpositionedItem &)> flexAdjustmentInViolationDirection(const std::vector<ASStackUnpositionedItem> &items,
|
||||||
const ASStackLayoutSpecStyle &style,
|
const ASStackLayoutSpecStyle &style,
|
||||||
const CGFloat violation,
|
const CGFloat violation,
|
||||||
const CGFloat flexFactorSum)
|
const CGFloat flexFactorSum)
|
||||||
{
|
{
|
||||||
if (violation > 0) {
|
if (violation > 0) {
|
||||||
return flexGrowAdjustment(items, violation, flexFactorSum);
|
return flexGrowAdjustment(items, violation, flexFactorSum);
|
||||||
} else {
|
} else {
|
||||||
return flexShrinkAdjustment(items, style, violation);
|
return flexShrinkAdjustment(items, style, violation, flexFactorSum);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,7 +352,7 @@ static void flexChildrenAlongStackDimension(std::vector<ASStackUnpositionedItem>
|
|||||||
std::function<CGFloat(const ASStackUnpositionedItem &)> flexFactor = flexFactorInViolationDirection(violation);
|
std::function<CGFloat(const ASStackUnpositionedItem &)> flexFactor = flexFactorInViolationDirection(violation);
|
||||||
// The flex factor sum is needed to determine if flexing is necessary.
|
// The flex factor sum is needed to determine if flexing is necessary.
|
||||||
// This value is also needed if the violation is positive and flexible children need to grow, so keep it around.
|
// This value is also needed if the violation is positive and flexible children need to grow, so keep it around.
|
||||||
const CGFloat flexFactorSum = std::accumulate(items.begin(), items.end(), 0, [&](CGFloat x, const ASStackUnpositionedItem &item) {
|
const CGFloat flexFactorSum = std::accumulate(items.begin(), items.end(), 0.0, [&](CGFloat x, const ASStackUnpositionedItem &item) {
|
||||||
return x + flexFactor(item);
|
return x + flexFactor(item);
|
||||||
});
|
});
|
||||||
// If no children are able to flex then there is nothing left to do. Bail.
|
// If no children are able to flex then there is nothing left to do. Bail.
|
||||||
@ -363,17 +363,23 @@ static void flexChildrenAlongStackDimension(std::vector<ASStackUnpositionedItem>
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::function<CGFloat(const ASStackUnpositionedItem &, BOOL)> flexAdjustment = flexAdjustmentInViolationDirection(items,
|
std::function<CGFloat(const ASStackUnpositionedItem &)> flexAdjustment = flexAdjustmentInViolationDirection(items,
|
||||||
style,
|
style,
|
||||||
violation,
|
violation,
|
||||||
flexFactorSum);
|
flexFactorSum);
|
||||||
|
|
||||||
|
// Compute any remaining violation to the first flexible child.
|
||||||
|
const CGFloat remainingViolation = std::accumulate(items.begin(), items.end(), violation, [&](CGFloat x, const ASStackUnpositionedItem &item) {
|
||||||
|
return x - flexAdjustment(item);
|
||||||
|
});
|
||||||
BOOL isFirstFlex = YES;
|
BOOL isFirstFlex = YES;
|
||||||
for (ASStackUnpositionedItem &item : items) {
|
for (ASStackUnpositionedItem &item : items) {
|
||||||
const CGFloat currentFlexAdjustment = flexAdjustment(item, isFirstFlex);
|
const CGFloat currentFlexAdjustment = flexAdjustment(item);
|
||||||
// Children are consider inflexible if they do not need to make a flex adjustment.
|
// Children are consider inflexible if they do not need to make a flex adjustment.
|
||||||
if (currentFlexAdjustment != 0) {
|
if (currentFlexAdjustment != 0) {
|
||||||
const CGFloat originalStackSize = stackDimension(style.direction, item.layout.size);
|
const CGFloat originalStackSize = stackDimension(style.direction, item.layout.size);
|
||||||
const CGFloat flexedStackSize = originalStackSize + currentFlexAdjustment;
|
// Only apply the remaining violation for the first flexible child that has a flex grow factor.
|
||||||
|
const CGFloat flexedStackSize = originalStackSize + currentFlexAdjustment + (isFirstFlex && item.child.style.flexGrow > 0 ? remainingViolation : 0);
|
||||||
item.layout = crossChildLayout(item.child,
|
item.layout = crossChildLayout(item.child,
|
||||||
style,
|
style,
|
||||||
MAX(flexedStackSize, 0),
|
MAX(flexedStackSize, 0),
|
||||||
|
|||||||
@ -21,6 +21,15 @@
|
|||||||
|
|
||||||
@implementation ASStackLayoutSpecSnapshotTests
|
@implementation ASStackLayoutSpecSnapshotTests
|
||||||
|
|
||||||
|
#pragma mark - XCTestCase
|
||||||
|
|
||||||
|
- (void)setUp
|
||||||
|
{
|
||||||
|
[super setUp];
|
||||||
|
|
||||||
|
self.recordMode = NO;
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - Utility methods
|
#pragma mark - Utility methods
|
||||||
|
|
||||||
static NSArray<ASDisplayNode *> *defaultSubnodes()
|
static NSArray<ASDisplayNode *> *defaultSubnodes()
|
||||||
@ -597,13 +606,13 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
|
|||||||
[self testLayoutSpec:layoutSpec sizeRange:kSize subnodes:subnodes identifier:nil];
|
[self testLayoutSpec:layoutSpec sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testPositiveViolationIsDistributedEquallyAmongFlexibleChildren
|
- (void)testPositiveViolationIsDistributedEqually
|
||||||
{
|
{
|
||||||
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
||||||
|
|
||||||
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize({50, 50}, 0);
|
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize({50, 50}, 0);
|
||||||
subnodes[0].style.flexGrow = 0;
|
subnodes[0].style.flexGrow = 1;
|
||||||
subnodes[2].style.flexGrow = 0;
|
subnodes[2].style.flexGrow = 1;
|
||||||
|
|
||||||
// In this scenario a width of 350 results in a positive violation of 200.
|
// In this scenario a width of 350 results in a positive violation of 200.
|
||||||
// Due to each flexible subnode specifying a flex grow factor of 1 the violation will be distributed evenly.
|
// Due to each flexible subnode specifying a flex grow factor of 1 the violation will be distributed evenly.
|
||||||
@ -611,7 +620,21 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
|
|||||||
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testPositiveViolationIsDistributedProportionallyAmongFlexibleChildren
|
- (void)testPositiveViolationIsDistributedEquallyWithArbitraryFloats
|
||||||
|
{
|
||||||
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
||||||
|
|
||||||
|
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize({50, 50}, 0);
|
||||||
|
subnodes[0].style.flexGrow = 0.5;
|
||||||
|
subnodes[2].style.flexGrow = 0.5;
|
||||||
|
|
||||||
|
// In this scenario a width of 350 results in a positive violation of 200.
|
||||||
|
// Due to each flexible child component specifying a flex grow factor of 0.5 the violation will be distributed evenly.
|
||||||
|
static ASSizeRange kSize = {{350, 350}, {350, 350}};
|
||||||
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testPositiveViolationIsDistributedProportionally
|
||||||
{
|
{
|
||||||
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
|
||||||
|
|
||||||
@ -627,7 +650,23 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
|
|||||||
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testPositiveViolationIsDistributedEquallyAmongGrowingAndShrinkingFlexibleChildren
|
- (void)testPositiveViolationIsDistributedProportionallyWithArbitraryFloats
|
||||||
|
{
|
||||||
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
|
||||||
|
|
||||||
|
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize({50, 50}, 0);
|
||||||
|
subnodes[0].style.flexGrow = 0.25;
|
||||||
|
subnodes[1].style.flexGrow = 0.50;
|
||||||
|
subnodes[2].style.flexGrow = 0.25;
|
||||||
|
|
||||||
|
// In this scenario a width of 350 results in a positive violation of 200.
|
||||||
|
// The first and third child components specify a flex grow factor of 0.25 and will flex by 50.
|
||||||
|
// The second child component specifies a flex grow factor of 0.25 and will flex by 100.
|
||||||
|
static ASSizeRange kSize = {{350, 350}, {350, 350}};
|
||||||
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testPositiveViolationIsDistributedEquallyAmongMixedChildren
|
||||||
{
|
{
|
||||||
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
||||||
|
|
||||||
@ -647,7 +686,27 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
|
|||||||
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testPositiveViolationIsDistributedProportionallyAmongGrowingAndShrinkingFlexibleChildren
|
- (void)testPositiveViolationIsDistributedEquallyAmongMixedChildrenWithArbitraryFloats
|
||||||
|
{
|
||||||
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
||||||
|
|
||||||
|
const CGSize kSubnodeSize = {50, 50};
|
||||||
|
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize(kSubnodeSize, 0);
|
||||||
|
subnodes = [subnodes arrayByAddingObject:ASDisplayNodeWithBackgroundColor([UIColor yellowColor], kSubnodeSize)];
|
||||||
|
|
||||||
|
subnodes[0].style.flexShrink = 1.0;
|
||||||
|
subnodes[1].style.flexGrow = 0.5;
|
||||||
|
subnodes[2].style.flexShrink = 0.0;
|
||||||
|
subnodes[3].style.flexGrow = 0.5;
|
||||||
|
|
||||||
|
// In this scenario a width of 400 results in a positive violation of 200.
|
||||||
|
// The first and third child components specify a flex shrink factor of 1 and 0, respectively. They won't flex.
|
||||||
|
// The second and fourth child components specify a flex grow factor of 0.5 and will flex by 100.
|
||||||
|
static ASSizeRange kSize = {{400, 400}, {400, 400}};
|
||||||
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testPositiveViolationIsDistributedProportionallyAmongMixedChildren
|
||||||
{
|
{
|
||||||
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
|
||||||
|
|
||||||
@ -668,6 +727,27 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
|
|||||||
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)testPositiveViolationIsDistributedProportionallyAmongMixedChildrenWithArbitraryFloats
|
||||||
|
{
|
||||||
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
|
||||||
|
|
||||||
|
const CGSize kSubnodeSize = {50, 50};
|
||||||
|
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize(kSubnodeSize, 0);
|
||||||
|
subnodes = [subnodes arrayByAddingObject:ASDisplayNodeWithBackgroundColor([UIColor yellowColor], kSubnodeSize)];
|
||||||
|
|
||||||
|
subnodes[0].style.flexShrink = 1.0;
|
||||||
|
subnodes[1].style.flexGrow = 0.75;
|
||||||
|
subnodes[2].style.flexShrink = 0.0;
|
||||||
|
subnodes[3].style.flexGrow = 0.25;
|
||||||
|
|
||||||
|
// In this scenario a width of 400 results in a positive violation of 200.
|
||||||
|
// The first and third child components specify a flex shrink factor of 1 and 0, respectively. They won't flex.
|
||||||
|
// The second child component specifies a flex grow factor of 0.75 and will flex by 150.
|
||||||
|
// The fourth child component specifies a flex grow factor of 0.25 and will flex by 50.
|
||||||
|
static ASSizeRange kSize = {{400, 400}, {400, 400}};
|
||||||
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)testRemainingViolationIsAppliedProperlyToFirstFlexibleChild
|
- (void)testRemainingViolationIsAppliedProperlyToFirstFlexibleChild
|
||||||
{
|
{
|
||||||
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
|
||||||
@ -688,7 +768,27 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
|
|||||||
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testNegativeViolationIsDistributedProportionallyBasedOnSizeAmongFlexibleChildren
|
- (void)testRemainingViolationIsAppliedProperlyToFirstFlexibleChildWithArbitraryFloats
|
||||||
|
{
|
||||||
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
|
||||||
|
|
||||||
|
NSArray<ASDisplayNode *> *subnodes = @[
|
||||||
|
ASDisplayNodeWithBackgroundColor([UIColor greenColor], {50, 25}),
|
||||||
|
ASDisplayNodeWithBackgroundColor([UIColor blueColor], {50, 0}),
|
||||||
|
ASDisplayNodeWithBackgroundColor([UIColor redColor], {50, 100})
|
||||||
|
];
|
||||||
|
|
||||||
|
subnodes[0].style.flexGrow = 0.0;
|
||||||
|
subnodes[1].style.flexGrow = 0.5;
|
||||||
|
subnodes[2].style.flexGrow = 0.5;
|
||||||
|
|
||||||
|
// In this scenario a width of 300 results in a positive violation of 175.
|
||||||
|
// The second and third child components specify a flex grow factor of 0.5 and will flex by 88 and 87, respectively.
|
||||||
|
static ASSizeRange kSize = {{300, 300}, {300, 300}};
|
||||||
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testNegativeViolationIsDistributedBasedOnSize
|
||||||
{
|
{
|
||||||
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
||||||
|
|
||||||
@ -708,7 +808,27 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
|
|||||||
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testNegativeViolationIsDistributedProportionallyBasedOnSizeAndFlexFactorAmongFlexibleChildren
|
- (void)testNegativeViolationIsDistributedBasedOnSizeWithArbitraryFloats
|
||||||
|
{
|
||||||
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
||||||
|
|
||||||
|
NSArray<ASDisplayNode *> *subnodes = @[
|
||||||
|
ASDisplayNodeWithBackgroundColor([UIColor greenColor], {300, 50}),
|
||||||
|
ASDisplayNodeWithBackgroundColor([UIColor blueColor], {100, 50}),
|
||||||
|
ASDisplayNodeWithBackgroundColor([UIColor redColor], {200, 50})
|
||||||
|
];
|
||||||
|
|
||||||
|
subnodes[0].style.flexShrink = 0.5;
|
||||||
|
subnodes[1].style.flexShrink = 0.0;
|
||||||
|
subnodes[2].style.flexShrink = 0.5;
|
||||||
|
|
||||||
|
// In this scenario a width of 400 results in a negative violation of 200.
|
||||||
|
// The first and third child components specify a flex shrink factor of 0.5 and will flex by -120 and -80, respectively.
|
||||||
|
static ASSizeRange kSize = {{400, 400}, {400, 400}};
|
||||||
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testNegativeViolationIsDistributedBasedOnSizeAndFlexFactor
|
||||||
{
|
{
|
||||||
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
|
||||||
|
|
||||||
@ -729,7 +849,28 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
|
|||||||
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testNegativeViolationIsDistributedProportionallyBasedOnSizeAmongGrowingAndShrinkingFlexibleChildren
|
- (void)testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorWithArbitraryFloats
|
||||||
|
{
|
||||||
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
|
||||||
|
|
||||||
|
NSArray<ASDisplayNode *> *subnodes = @[
|
||||||
|
ASDisplayNodeWithBackgroundColor([UIColor greenColor], {50, 300}),
|
||||||
|
ASDisplayNodeWithBackgroundColor([UIColor blueColor], {50, 100}),
|
||||||
|
ASDisplayNodeWithBackgroundColor([UIColor redColor], {50, 200})
|
||||||
|
];
|
||||||
|
|
||||||
|
subnodes[0].style.flexShrink = 0.4;
|
||||||
|
subnodes[1].style.flexShrink = 0.2;
|
||||||
|
subnodes[2].style.flexShrink = 0.4;
|
||||||
|
|
||||||
|
// In this scenario a width of 400 results in a negative violation of 200.
|
||||||
|
// The first and third child components specify a flex shrink factor of 0.4 and will flex by -109 and -72, respectively.
|
||||||
|
// The second child component specifies a flex shrink factor of 0.2 and will flex by -18.
|
||||||
|
static ASSizeRange kSize = {{400, 400}, {400, 400}};
|
||||||
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testNegativeViolationIsDistributedBasedOnSizeAmongMixedChildrenChildren
|
||||||
{
|
{
|
||||||
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
||||||
|
|
||||||
@ -749,7 +890,27 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
|
|||||||
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testNegativeViolationIsDistributedProportionallyBasedOnSizeAndFlexFactorAmongGrowingAndShrinkingFlexibleChildren
|
- (void)testNegativeViolationIsDistributedBasedOnSizeAmongMixedChildrenWithArbitraryFloats
|
||||||
|
{
|
||||||
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
||||||
|
|
||||||
|
const CGSize kSubnodeSize = {150, 50};
|
||||||
|
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize(kSubnodeSize, 0);
|
||||||
|
subnodes = [subnodes arrayByAddingObject:ASDisplayNodeWithBackgroundColor([UIColor yellowColor], kSubnodeSize)];
|
||||||
|
|
||||||
|
subnodes[0].style.flexGrow = 1.0;
|
||||||
|
subnodes[1].style.flexShrink = 0.5;
|
||||||
|
subnodes[2].style.flexGrow = 0.0;
|
||||||
|
subnodes[3].style.flexShrink = 0.5;
|
||||||
|
|
||||||
|
// In this scenario a width of 400 results in a negative violation of 200.
|
||||||
|
// The first and third child components specify a flex grow factor of 1 and 0, respectively. They won't flex.
|
||||||
|
// The second and fourth child components specify a flex shrink factor of 0.5 and will flex by -100.
|
||||||
|
static ASSizeRange kSize = {{400, 400}, {400, 400}};
|
||||||
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorAmongMixedChildren
|
||||||
{
|
{
|
||||||
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
|
||||||
|
|
||||||
@ -773,7 +934,31 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
|
|||||||
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testNegativeViolationIsDistributedProportionallyBasedOnSizeAndFlexFactorDoesNotShrinkToZeroWidth
|
- (void)testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorAmongMixedChildrenArbitraryFloats
|
||||||
|
{
|
||||||
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
|
||||||
|
|
||||||
|
NSArray<ASDisplayNode *> *subnodes = @[
|
||||||
|
ASDisplayNodeWithBackgroundColor([UIColor greenColor], {50, 150}),
|
||||||
|
ASDisplayNodeWithBackgroundColor([UIColor blueColor], {50, 100}),
|
||||||
|
ASDisplayNodeWithBackgroundColor([UIColor redColor], {50, 150}),
|
||||||
|
ASDisplayNodeWithBackgroundColor([UIColor yellowColor], {50, 200})
|
||||||
|
];
|
||||||
|
|
||||||
|
subnodes[0].style.flexGrow = 1.0;
|
||||||
|
subnodes[1].style.flexShrink = 0.25;
|
||||||
|
subnodes[2].style.flexGrow = 0.0;
|
||||||
|
subnodes[3].style.flexShrink = 0.75;
|
||||||
|
|
||||||
|
// In this scenario a width of 400 results in a negative violation of 200.
|
||||||
|
// The first and third child components specify a flex grow factor of 1 and 0, respectively. They won't flex.
|
||||||
|
// The second child component specifies a flex shrink factor of 0.25 and will flex by -28.
|
||||||
|
// The fourth child component specifies a flex shrink factor of 0.75 and will flex by -171.
|
||||||
|
static ASSizeRange kSize = {{400, 400}, {400, 400}};
|
||||||
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorDoesNotShrinkToZero
|
||||||
{
|
{
|
||||||
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
||||||
|
|
||||||
@ -794,6 +979,27 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
|
|||||||
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)testNegativeViolationIsDistributedBasedOnSizeAndFlexFactorDoesNotShrinkToZeroWithArbitraryFloats
|
||||||
|
{
|
||||||
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
||||||
|
|
||||||
|
NSArray<ASDisplayNode *> *subnodes = @[
|
||||||
|
ASDisplayNodeWithBackgroundColor([UIColor greenColor], {300, 50}),
|
||||||
|
ASDisplayNodeWithBackgroundColor([UIColor blueColor], {100, 50}),
|
||||||
|
ASDisplayNodeWithBackgroundColor([UIColor redColor], {200, 50})
|
||||||
|
];
|
||||||
|
|
||||||
|
subnodes[0].style.flexShrink = 0.25;
|
||||||
|
subnodes[1].style.flexShrink = 0.50;
|
||||||
|
subnodes[2].style.flexShrink = 0.25;
|
||||||
|
|
||||||
|
// In this scenario a width of 400 results in a negative violation of 200.
|
||||||
|
// The first and third child components specify a flex shrink factor of 0.25 and will flex by 50.
|
||||||
|
// The second child component specifies a flex shrink factor of 0.50 and will flex by -57. It will have a width of 43.
|
||||||
|
static ASSizeRange kSize = {{400, 400}, {400, 400}};
|
||||||
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)testNestedStackLayoutStretchDoesNotViolateWidth
|
- (void)testNestedStackLayoutStretchDoesNotViolateWidth
|
||||||
{
|
{
|
||||||
ASStackLayoutSpec *stackLayoutSpec = [[ASStackLayoutSpec alloc] init]; // Default direction is horizontal
|
ASStackLayoutSpec *stackLayoutSpec = [[ASStackLayoutSpec alloc] init]; // Default direction is horizontal
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |