[Layout] Treat flex factors as true factors (#2349)

* Treat flex factors as factors

* Add snapshot tests

* Address comments
This commit is contained in:
Michael Schneider
2016-10-06 13:53:26 -07:00
committed by Adlai Holler
parent 124234fef8
commit 94f958c4a3
44 changed files with 256 additions and 44 deletions

View File

@@ -131,8 +131,8 @@ static const CGFloat kViolationEpsilon = 0.01;
*/
static std::function<CGFloat(const ASStackUnpositionedItem &)> flexFactorInViolationDirection(const CGFloat violation)
{
if (fabs(violation) < kViolationEpsilon) {
return [](const ASStackUnpositionedItem &item) { return 0; };
if (std::fabs(violation) < kViolationEpsilon) {
return [](const ASStackUnpositionedItem &item) { return 0.0; };
} else if (violation > 0) {
return [](const ASStackUnpositionedItem &item) { return item.child.style.flexGrow; };
} 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 style The layout style to be applied to all children.
@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.
*/
static std::function<CGFloat(const ASStackUnpositionedItem &, BOOL)> flexShrinkAdjustment(const std::vector<ASStackUnpositionedItem> &items,
const ASStackLayoutSpecStyle &style,
const CGFloat violation)
static std::function<CGFloat(const ASStackUnpositionedItem &)> flexShrinkAdjustment(const std::vector<ASStackUnpositionedItem> &items,
const ASStackLayoutSpecStyle &style,
const CGFloat violation,
const CGFloat flexFactorSum)
{
const CGFloat scaledFlexShrinkFactorSum = std::accumulate(items.begin(), items.end(), 0, [&](CGFloat x, const ASStackUnpositionedItem &item) {
return x + scaledFlexShrinkFactor(item, style);
const CGFloat scaledFlexShrinkFactorSum = std::accumulate(items.begin(), items.end(), 0.0, [&](CGFloat x, const ASStackUnpositionedItem &item) {
return x + scaledFlexShrinkFactor(item, style, flexFactorSum);
});
return [style, scaledFlexShrinkFactorSum, violation](const ASStackUnpositionedItem &item, BOOL isFirstFlex) {
const CGFloat scaledFlexShrinkFactorRatio = scaledFlexShrinkFactor(item, style) / scaledFlexShrinkFactorSum;
return [style, scaledFlexShrinkFactorSum, violation, flexFactorSum](const ASStackUnpositionedItem &item) {
const CGFloat scaledFlexShrinkFactorRatio = scaledFlexShrinkFactor(item, style, flexFactorSum) / scaledFlexShrinkFactorSum;
// 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.
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.
@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,
const CGFloat violation,
const CGFloat flexFactorSum)
static std::function<CGFloat(const ASStackUnpositionedItem &)> flexGrowAdjustment(const std::vector<ASStackUnpositionedItem> &items,
const CGFloat violation,
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.
// If there happens to be a violation remaining make sure it is allocated to the first flexible child.
return [violationPerFlexFactor, remainingViolation](const ASStackUnpositionedItem &item, BOOL isFirstFlex) {
// 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);
return [violation, flexFactorSum](const ASStackUnpositionedItem &item) {
return std::floor(violation * (item.child.style.flexGrow / flexFactorSum));
};
}
@@ -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.
@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,
const ASStackLayoutSpecStyle &style,
const CGFloat violation,
const CGFloat flexFactorSum)
static std::function<CGFloat(const ASStackUnpositionedItem &)> flexAdjustmentInViolationDirection(const std::vector<ASStackUnpositionedItem> &items,
const ASStackLayoutSpecStyle &style,
const CGFloat violation,
const CGFloat flexFactorSum)
{
if (violation > 0) {
return flexGrowAdjustment(items, violation, flexFactorSum);
} 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);
// 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.
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);
});
// 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;
}
std::function<CGFloat(const ASStackUnpositionedItem &, BOOL)> flexAdjustment = flexAdjustmentInViolationDirection(items,
style,
violation,
flexFactorSum);
std::function<CGFloat(const ASStackUnpositionedItem &)> flexAdjustment = flexAdjustmentInViolationDirection(items,
style,
violation,
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;
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.
if (currentFlexAdjustment != 0) {
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,
style,
MAX(flexedStackSize, 0),