[Layout] Add support for flex factor (#2298)
* Add support for flex factor * Add snapshot tests * Respect child specified size from ASLayoutable * Add new snapshot test images * Fix rebase conflict
@ -78,7 +78,7 @@
|
|||||||
// of the button node to add a touch handler.
|
// of the button node to add a touch handler.
|
||||||
[_titleNode setLayerBacked:YES];
|
[_titleNode setLayerBacked:YES];
|
||||||
#endif
|
#endif
|
||||||
_titleNode.style.flexShrink = YES;
|
_titleNode.style.flexShrink = 1;
|
||||||
}
|
}
|
||||||
return _titleNode;
|
return _titleNode;
|
||||||
}
|
}
|
||||||
|
@ -406,7 +406,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext;
|
|||||||
return slider;
|
return slider;
|
||||||
}];
|
}];
|
||||||
|
|
||||||
_scrubberNode.style.flexShrink = YES;
|
_scrubberNode.style.flexShrink = 1;
|
||||||
|
|
||||||
[_cachedControls setObject:_scrubberNode forKey:@(ASVideoPlayerNodeControlTypeScrubber)];
|
[_cachedControls setObject:_scrubberNode forKey:@(ASVideoPlayerNodeControlTypeScrubber)];
|
||||||
}
|
}
|
||||||
@ -418,7 +418,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext;
|
|||||||
{
|
{
|
||||||
if (_controlFlexGrowSpacerSpec == nil) {
|
if (_controlFlexGrowSpacerSpec == nil) {
|
||||||
_controlFlexGrowSpacerSpec = [[ASStackLayoutSpec alloc] init];
|
_controlFlexGrowSpacerSpec = [[ASStackLayoutSpec alloc] init];
|
||||||
_controlFlexGrowSpacerSpec.style.flexGrow = YES;
|
_controlFlexGrowSpacerSpec.style.flexGrow = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
[_cachedControls setObject:_controlFlexGrowSpacerSpec forKey:@(ASVideoPlayerNodeControlTypeFlexGrowSpacer)];
|
[_cachedControls setObject:_controlFlexGrowSpacerSpec forKey:@(ASVideoPlayerNodeControlTypeFlexGrowSpacer)];
|
||||||
@ -735,7 +735,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext;
|
|||||||
_scrubberNode.style.height = ASDimensionMakeWithPoints(44.0);
|
_scrubberNode.style.height = ASDimensionMakeWithPoints(44.0);
|
||||||
|
|
||||||
ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init];
|
ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init];
|
||||||
spacer.style.flexGrow = YES;
|
spacer.style.flexGrow = 1;
|
||||||
|
|
||||||
ASStackLayoutSpec *controlbarSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal
|
ASStackLayoutSpec *controlbarSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal
|
||||||
spacing:10.0
|
spacing:10.0
|
||||||
|
@ -37,8 +37,8 @@ typedef struct ASEnvironmentStateExtensions {
|
|||||||
typedef struct ASEnvironmentLayoutOptionsState {
|
typedef struct ASEnvironmentLayoutOptionsState {
|
||||||
CGFloat spacingBefore;// = 0;
|
CGFloat spacingBefore;// = 0;
|
||||||
CGFloat spacingAfter;// = 0;
|
CGFloat spacingAfter;// = 0;
|
||||||
BOOL flexGrow;// = NO;
|
CGFloat flexGrow;// = 0;
|
||||||
BOOL flexShrink;// = NO; Default value is YES if created via ASEnvironmentLayoutOptionsStateMakeDefault
|
CGFloat flexShrink;// = 0;
|
||||||
ASDimension flexBasis;// = ASRelativeDimensionAuto;
|
ASDimension flexBasis;// = ASRelativeDimensionAuto;
|
||||||
ASStackLayoutAlignSelf alignSelf;// = ASStackLayoutAlignSelfAuto;
|
ASStackLayoutAlignSelf alignSelf;// = ASStackLayoutAlignSelfAuto;
|
||||||
CGFloat ascender;// = 0;
|
CGFloat ascender;// = 0;
|
||||||
|
@ -269,16 +269,18 @@ extern NSString * const ASLayoutableStyleLayoutPositionProperty;
|
|||||||
@property (nonatomic, assign) CGFloat spacingAfter;
|
@property (nonatomic, assign) CGFloat spacingAfter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract If the sum of childrens' stack dimensions is less than the minimum size, should this object grow?
|
* @abstract If the sum of childrens' stack dimensions is less than the minimum size, how much should this component grow?
|
||||||
* Used when attached to a stack layout.
|
* This value represents the "flex grow factor" and determines how much this component should grow in relation to any
|
||||||
|
* other flexible children.
|
||||||
*/
|
*/
|
||||||
@property (nonatomic, assign) BOOL flexGrow;
|
@property (nonatomic, assign) CGFloat flexGrow;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract If the sum of childrens' stack dimensions is greater than the maximum size, should this object shrink?
|
* @abstract If the sum of childrens' stack dimensions is greater than the maximum size, how much should this component shrink?
|
||||||
* Used when attached to a stack layout.
|
* This value represents the "flex shrink factor" and determines how much this component should shink in relation to
|
||||||
|
* other flexible children.
|
||||||
*/
|
*/
|
||||||
@property (nonatomic, assign) BOOL flexShrink;
|
@property (nonatomic, assign) CGFloat flexShrink;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract Specifies the initial size in the stack dimension for this object.
|
* @abstract Specifies the initial size in the stack dimension for this object.
|
||||||
|
@ -251,14 +251,14 @@ do {\
|
|||||||
ASLayoutableStyleCallDelegate(ASLayoutableStyleSpacingAfterProperty);
|
ASLayoutableStyleCallDelegate(ASLayoutableStyleSpacingAfterProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setFlexGrow:(BOOL)flexGrow
|
- (void)setFlexGrow:(CGFloat)flexGrow
|
||||||
{
|
{
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
_flexGrow = flexGrow;
|
_flexGrow = flexGrow;
|
||||||
ASLayoutableStyleCallDelegate(ASLayoutableStyleFlexGrowProperty);
|
ASLayoutableStyleCallDelegate(ASLayoutableStyleFlexGrowProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setFlexShrink:(BOOL)flexShrink
|
- (void)setFlexShrink:(CGFloat)flexShrink
|
||||||
{
|
{
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
_flexShrink = flexShrink;
|
_flexShrink = flexShrink;
|
||||||
|
@ -31,16 +31,18 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
@property (nonatomic, readwrite) CGFloat spacingAfter;
|
@property (nonatomic, readwrite) CGFloat spacingAfter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract If the sum of childrens' stack dimensions is less than the minimum size, should this object grow?
|
* @abstract If the sum of childrens' stack dimensions is less than the minimum size, how much should this component grow?
|
||||||
* Used when attached to a stack layout.
|
* This value represents the "flex grow factor" and determines how much this component should grow in relation to any
|
||||||
|
* other flexible children.
|
||||||
*/
|
*/
|
||||||
@property (nonatomic, readwrite) BOOL flexGrow;
|
@property (nonatomic, readwrite) CGFloat flexGrow;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract If the sum of childrens' stack dimensions is greater than the maximum size, should this object shrink?
|
* @abstract If the sum of childrens' stack dimensions is greater than the maximum size, how much should this component shrink?
|
||||||
* Used when attached to a stack layout.
|
* This value represents the "flex shrink factor" and determines how much this component should shink in relation to
|
||||||
|
* other flexible children.
|
||||||
*/
|
*/
|
||||||
@property (nonatomic, readwrite) BOOL flexShrink;
|
@property (nonatomic, readwrite) CGFloat flexShrink;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract Specifies the initial size in the stack dimension for this object.
|
* @abstract Specifies the initial size in the stack dimension for this object.
|
||||||
|
@ -15,6 +15,31 @@
|
|||||||
|
|
||||||
#import "ASLayoutSpecUtilities.h"
|
#import "ASLayoutSpecUtilities.h"
|
||||||
|
|
||||||
|
static CGFloat resolveCrossDimensionMaxForStretchChild(const ASStackLayoutSpecStyle &style,
|
||||||
|
const id<ASLayoutable>child,
|
||||||
|
const CGFloat stackMax,
|
||||||
|
const CGFloat crossMax)
|
||||||
|
{
|
||||||
|
// stretched children may have a cross direction max that is smaller than the minimum size constraint of the parent.
|
||||||
|
|
||||||
|
const CGFloat computedMax = (style.direction == ASStackLayoutDirectionVertical ?
|
||||||
|
ASLayoutableSizeResolve(child.style.size, ASLayoutableParentSizeUndefined).max.width :
|
||||||
|
ASLayoutableSizeResolve(child.style.size, ASLayoutableParentSizeUndefined).max.height);
|
||||||
|
return computedMax == INFINITY ? crossMax : computedMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
static CGFloat resolveCrossDimensionMinForStretchChild(const ASStackLayoutSpecStyle &style,
|
||||||
|
const id<ASLayoutable>child,
|
||||||
|
const CGFloat stackMax,
|
||||||
|
const CGFloat crossMin)
|
||||||
|
{
|
||||||
|
// stretched children will have a cross dimension of at least crossMin, unless they explicitly define a child size
|
||||||
|
// that is smaller than the constraint of the parent.
|
||||||
|
return (style.direction == ASStackLayoutDirectionVertical ?
|
||||||
|
ASLayoutableSizeResolve(child.style.size, ASLayoutableParentSizeUndefined).min.width :
|
||||||
|
ASLayoutableSizeResolve(child.style.size, ASLayoutableParentSizeUndefined).min.height) ?: crossMin;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Sizes the child given the parameters specified, and returns the computed layout.
|
Sizes the child given the parameters specified, and returns the computed layout.
|
||||||
*/
|
*/
|
||||||
@ -28,8 +53,11 @@ static ASLayout *crossChildLayout(const id<ASLayoutable> child,
|
|||||||
{
|
{
|
||||||
const ASStackLayoutAlignItems alignItems = alignment(child.style.alignSelf, style.alignItems);
|
const ASStackLayoutAlignItems alignItems = alignment(child.style.alignSelf, style.alignItems);
|
||||||
// stretched children will have a cross dimension of at least crossMin
|
// stretched children will have a cross dimension of at least crossMin
|
||||||
const CGFloat childCrossMin = alignItems == ASStackLayoutAlignItemsStretch ? crossMin : 0;
|
const CGFloat childCrossMin = (alignItems == ASStackLayoutAlignItemsStretch ? resolveCrossDimensionMinForStretchChild(style, child, stackMax, crossMin) : 0);
|
||||||
const ASSizeRange childSizeRange = directionSizeRange(style.direction, stackMin, stackMax, childCrossMin, crossMax);
|
const CGFloat childCrossMax = (alignItems == ASStackLayoutAlignItemsStretch ?
|
||||||
|
resolveCrossDimensionMaxForStretchChild(style, child, stackMax, crossMax) :
|
||||||
|
crossMax);
|
||||||
|
const ASSizeRange childSizeRange = directionSizeRange(style.direction, stackMin, stackMax, childCrossMin, childCrossMax);
|
||||||
ASLayout *layout = [child layoutThatFits:childSizeRange parentSize:size];
|
ASLayout *layout = [child layoutThatFits:childSizeRange parentSize:size];
|
||||||
ASDisplayNodeCAssertNotNil(layout, @"ASLayout returned from measureWithSizeRange: must not be nil: %@", child);
|
ASDisplayNodeCAssertNotNil(layout, @"ASLayout returned from measureWithSizeRange: must not be nil: %@", child);
|
||||||
return layout ? : [ASLayout layoutWithLayoutable:child size:{0, 0}];
|
return layout ? : [ASLayout layoutWithLayoutable:child size:{0, 0}];
|
||||||
@ -93,6 +121,120 @@ static void stretchChildrenAlongCrossDimension(std::vector<ASStackUnpositionedIt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The threshold that determines if a violation has actually occurred. */
|
||||||
|
static const CGFloat kViolationEpsilon = 0.01;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns a lambda that computes the relevant flex factor based on the given violation.
|
||||||
|
@param violation The amount that the stack layout violates its size range. See header for sign interpretation.
|
||||||
|
*/
|
||||||
|
static std::function<CGFloat(const ASStackUnpositionedItem &)> flexFactorInViolationDirection(const CGFloat violation)
|
||||||
|
{
|
||||||
|
if (fabs(violation) < kViolationEpsilon) {
|
||||||
|
return [](const ASStackUnpositionedItem &item) { return 0; };
|
||||||
|
} else if (violation > 0) {
|
||||||
|
return [](const ASStackUnpositionedItem &item) { return item.child.style.flexGrow; };
|
||||||
|
} else {
|
||||||
|
return [](const ASStackUnpositionedItem &item) { return item.child.style.flexShrink; };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline CGFloat scaledFlexShrinkFactor(const ASStackUnpositionedItem &item, const ASStackLayoutSpecStyle &style)
|
||||||
|
{
|
||||||
|
return stackDimension(style.direction, item.layout.size) * item.child.style.flexShrink;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns a lambda that computes a flex shrink adjustment for a given item based on the provided violation.
|
||||||
|
@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.
|
||||||
|
@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)
|
||||||
|
{
|
||||||
|
const CGFloat scaledFlexShrinkFactorSum = std::accumulate(items.begin(), items.end(), 0, [&](CGFloat x, const ASStackUnpositionedItem &item) {
|
||||||
|
return x + scaledFlexShrinkFactor(item, style);
|
||||||
|
});
|
||||||
|
return [style, scaledFlexShrinkFactorSum, violation](const ASStackUnpositionedItem &item, BOOL isFirstFlex) {
|
||||||
|
const CGFloat scaledFlexShrinkFactorRatio = scaledFlexShrinkFactor(item, style) / 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);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns a lambda that computes a flex grow adjustment for a given item based on the provided violation.
|
||||||
|
@param items The unpositioned items from the original unconstrained layout pass.
|
||||||
|
@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 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)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns a lambda that computes a flex adjustment for a given item based on the provided violation.
|
||||||
|
@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 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)
|
||||||
|
{
|
||||||
|
if (violation > 0) {
|
||||||
|
return flexGrowAdjustment(items, violation, flexFactorSum);
|
||||||
|
} else {
|
||||||
|
return flexShrinkAdjustment(items, style, violation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ASDISPLAYNODE_INLINE BOOL isFlexibleInBothDirections(id<ASLayoutable> child)
|
||||||
|
{
|
||||||
|
return child.style.flexGrow > 0 && child.style.flexShrink > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
The flexible children may have been left not laid out in the initial layout pass, so we may have to go through and size
|
||||||
|
these children at zero size so that the children layouts are at least present.
|
||||||
|
*/
|
||||||
|
static void layoutFlexibleChildrenAtZeroSize(std::vector<ASStackUnpositionedItem> &items,
|
||||||
|
const ASStackLayoutSpecStyle &style,
|
||||||
|
const ASSizeRange &sizeRange,
|
||||||
|
const CGSize size)
|
||||||
|
{
|
||||||
|
for (ASStackUnpositionedItem &item : items) {
|
||||||
|
const id<ASLayoutable> child = item.child;
|
||||||
|
if (isFlexibleInBothDirections(child)) {
|
||||||
|
item.layout = crossChildLayout(child,
|
||||||
|
style,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
crossDimension(style.direction, sizeRange.min),
|
||||||
|
crossDimension(style.direction, sizeRange.max),
|
||||||
|
size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Computes the consumed stack dimension length for the given vector of children and stacking style.
|
Computes the consumed stack dimension length for the given vector of children and stacking style.
|
||||||
|
|
||||||
@ -172,30 +314,6 @@ static CGFloat computeViolation(const CGFloat stackDimensionSum,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The threshold that determines if a violation has actually occurred. */
|
|
||||||
static const CGFloat kViolationEpsilon = 0.01;
|
|
||||||
|
|
||||||
/**
|
|
||||||
Returns a lambda that determines if the given unpositioned item's child is flexible in the direction of the violation.
|
|
||||||
|
|
||||||
@param violation the amount that the stack layout violates its size range. See header for sign interpretation.
|
|
||||||
*/
|
|
||||||
static std::function<BOOL(const ASStackUnpositionedItem &)> isFlexibleInViolationDirection(const CGFloat violation)
|
|
||||||
{
|
|
||||||
if (std::fabs(violation) < kViolationEpsilon) {
|
|
||||||
return [](const ASStackUnpositionedItem &l) { return NO; };
|
|
||||||
} else if (violation > 0) {
|
|
||||||
return [](const ASStackUnpositionedItem &l) { return l.child.style.flexGrow; };
|
|
||||||
} else {
|
|
||||||
return [](const ASStackUnpositionedItem &l) { return l.child.style.flexShrink; };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ASDISPLAYNODE_INLINE BOOL isFlexibleInBothDirections(id<ASLayoutable> child)
|
|
||||||
{
|
|
||||||
return child.style.flexGrow && child.style.flexShrink;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
If we have a single flexible (both shrinkable and growable) child, and our allowed size range is set to a specific
|
If we have a single flexible (both shrinkable and growable) child, and our allowed size range is set to a specific
|
||||||
number then we may avoid the first "intrinsic" size calculation.
|
number then we may avoid the first "intrinsic" size calculation.
|
||||||
@ -210,29 +328,6 @@ ASDISPLAYNODE_INLINE BOOL useOptimizedFlexing(const std::vector<id<ASLayoutable>
|
|||||||
stackDimension(style.direction, sizeRange.max)));
|
stackDimension(style.direction, sizeRange.max)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
The flexible children may have been left not laid out in the initial layout pass, so we may have to go through and size
|
|
||||||
these children at zero size so that the children layouts are at least present.
|
|
||||||
*/
|
|
||||||
static void layoutFlexibleChildrenAtZeroSize(std::vector<ASStackUnpositionedItem> &items,
|
|
||||||
const ASStackLayoutSpecStyle &style,
|
|
||||||
const ASSizeRange &sizeRange,
|
|
||||||
const CGSize size)
|
|
||||||
{
|
|
||||||
for (ASStackUnpositionedItem &item : items) {
|
|
||||||
const id<ASLayoutable> child = item.child;
|
|
||||||
if (isFlexibleInBothDirections(child)) {
|
|
||||||
item.layout = crossChildLayout(child,
|
|
||||||
style,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
crossDimension(style.direction, sizeRange.min),
|
|
||||||
crossDimension(style.direction, sizeRange.max),
|
|
||||||
size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Flexes children in the stack axis to resolve a min or max stack size violation. First, determines which children are
|
Flexes children in the stack axis to resolve a min or max stack size violation. First, determines which children are
|
||||||
flexible (see computeViolation and isFlexibleInViolationDirection). Then computes how much to flex each flexible child
|
flexible (see computeViolation and isFlexibleInViolationDirection). Then computes how much to flex each flexible child
|
||||||
@ -243,7 +338,8 @@ static void layoutFlexibleChildrenAtZeroSize(std::vector<ASStackUnpositionedItem
|
|||||||
|
|
||||||
@param items Reference to unpositioned items from the original, unconstrained layout pass; modified in-place
|
@param items Reference to unpositioned items from the original, unconstrained layout pass; modified in-place
|
||||||
@param style layout style to be applied to all children
|
@param style layout style to be applied to all children
|
||||||
@param sizeRange the range of allowable sizes for the stack layout spec
|
@param sizeRange the range of allowable sizes for the stack layout component
|
||||||
|
@param size Size of the stack layout component. May be undefined in either or both directions.
|
||||||
*/
|
*/
|
||||||
static void flexChildrenAlongStackDimension(std::vector<ASStackUnpositionedItem> &items,
|
static void flexChildrenAlongStackDimension(std::vector<ASStackUnpositionedItem> &items,
|
||||||
const ASStackLayoutSpecStyle &style,
|
const ASStackLayoutSpecStyle &style,
|
||||||
@ -251,32 +347,32 @@ static void flexChildrenAlongStackDimension(std::vector<ASStackUnpositionedItem>
|
|||||||
const CGSize size,
|
const CGSize size,
|
||||||
const BOOL useOptimizedFlexing)
|
const BOOL useOptimizedFlexing)
|
||||||
{
|
{
|
||||||
const CGFloat stackDimensionSum = computeStackDimensionSum(items, style);
|
const CGFloat violation = computeViolation(computeStackDimensionSum(items, style), style, sizeRange);
|
||||||
const CGFloat violation = computeViolation(stackDimensionSum, style, sizeRange);
|
std::function<CGFloat(const ASStackUnpositionedItem &)> flexFactor = flexFactorInViolationDirection(violation);
|
||||||
|
// The flex factor sum is needed to determine if flexing is necessary.
|
||||||
// We count the number of children which are flexible in the direction of the violation
|
// This value is also needed if the violation is positive and flexible children need to grow, so keep it around.
|
||||||
std::function<BOOL(const ASStackUnpositionedItem &)> isFlex = isFlexibleInViolationDirection(violation);
|
const CGFloat flexFactorSum = std::accumulate(items.begin(), items.end(), 0, [&](CGFloat x, const ASStackUnpositionedItem &item) {
|
||||||
const NSUInteger flexibleChildren = std::count_if(items.begin(), items.end(), isFlex);
|
return x + flexFactor(item);
|
||||||
if (flexibleChildren == 0) {
|
});
|
||||||
// If optimized flexing was used then we have to clean up the unsized children, and lay them out at zero size
|
// If no children are able to flex then there is nothing left to do. Bail.
|
||||||
|
if (flexFactorSum == 0) {
|
||||||
|
// If optimized flexing was used then we have to clean up the unsized children and lay them out at zero size.
|
||||||
if (useOptimizedFlexing) {
|
if (useOptimizedFlexing) {
|
||||||
layoutFlexibleChildrenAtZeroSize(items, style, sizeRange, size);
|
layoutFlexibleChildrenAtZeroSize(items, style, sizeRange, size);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
std::function<CGFloat(const ASStackUnpositionedItem &, BOOL)> flexAdjustment = flexAdjustmentInViolationDirection(items,
|
||||||
// Each flexible child along the direction of the violation is expanded or contracted equally
|
style,
|
||||||
const CGFloat violationPerFlexChild = std::floor(violation / flexibleChildren);
|
violation,
|
||||||
// If the floor operation above left a remainder we may have a remainder after deducting the adjustments from all the
|
flexFactorSum);
|
||||||
// contributions of the flexible children.
|
|
||||||
const CGFloat violationRemainder = violation - (violationPerFlexChild * flexibleChildren);
|
|
||||||
|
|
||||||
BOOL isFirstFlex = YES;
|
BOOL isFirstFlex = YES;
|
||||||
for (ASStackUnpositionedItem &item : items) {
|
for (ASStackUnpositionedItem &item : items) {
|
||||||
if (isFlex(item)) {
|
const CGFloat currentFlexAdjustment = flexAdjustment(item, isFirstFlex);
|
||||||
|
// 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 originalStackSize = stackDimension(style.direction, item.layout.size);
|
||||||
// The first flexible child is given the additional violation remainder
|
const CGFloat flexedStackSize = originalStackSize + currentFlexAdjustment;
|
||||||
const CGFloat flexedStackSize = originalStackSize + violationPerFlexChild + (isFirstFlex ? violationRemainder : 0);
|
|
||||||
item.layout = crossChildLayout(item.child,
|
item.layout = crossChildLayout(item.child,
|
||||||
style,
|
style,
|
||||||
MAX(flexedStackSize, 0),
|
MAX(flexedStackSize, 0),
|
||||||
|
@ -90,7 +90,7 @@ static NSString *suffixForCenteringOptions(ASCenterLayoutSpecCenteringOptions ce
|
|||||||
{
|
{
|
||||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
|
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
|
||||||
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor], CGSizeMake(10, 10));
|
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor], CGSizeMake(10, 10));
|
||||||
foregroundNode.style.flexGrow = YES;
|
foregroundNode.style.flexGrow = 1;
|
||||||
|
|
||||||
ASCenterLayoutSpec *layoutSpec =
|
ASCenterLayoutSpec *layoutSpec =
|
||||||
[ASCenterLayoutSpec
|
[ASCenterLayoutSpec
|
||||||
|
@ -107,7 +107,7 @@ static NSString *suffixForPositionOptions(ASRelativeLayoutSpecPosition horizonta
|
|||||||
{
|
{
|
||||||
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
|
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
|
||||||
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor], CGSizeMake(10, 10));
|
ASDisplayNode *foregroundNode = ASDisplayNodeWithBackgroundColor([UIColor redColor], CGSizeMake(10, 10));
|
||||||
foregroundNode.style.flexGrow = YES;
|
foregroundNode.style.flexGrow = 1;
|
||||||
|
|
||||||
ASLayoutSpec *childSpec =
|
ASLayoutSpec *childSpec =
|
||||||
[ASBackgroundLayoutSpec
|
[ASBackgroundLayoutSpec
|
||||||
|
@ -25,10 +25,10 @@
|
|||||||
|
|
||||||
static NSArray<ASDisplayNode *> *defaultSubnodes()
|
static NSArray<ASDisplayNode *> *defaultSubnodes()
|
||||||
{
|
{
|
||||||
return defaultSubnodesWithSameSize(CGSizeZero, NO);
|
return defaultSubnodesWithSameSize(CGSizeZero, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static NSArray<ASDisplayNode *> *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex)
|
static NSArray<ASDisplayNode *> *defaultSubnodesWithSameSize(CGSize subnodeSize, CGFloat flex)
|
||||||
{
|
{
|
||||||
NSArray<ASDisplayNode *> *subnodes = @[
|
NSArray<ASDisplayNode *> *subnodes = @[
|
||||||
ASDisplayNodeWithBackgroundColor([UIColor redColor], subnodeSize),
|
ASDisplayNodeWithBackgroundColor([UIColor redColor], subnodeSize),
|
||||||
@ -62,7 +62,7 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)testStackLayoutSpecWithJustify:(ASStackLayoutJustifyContent)justify
|
- (void)testStackLayoutSpecWithJustify:(ASStackLayoutJustifyContent)justify
|
||||||
flex:(BOOL)flex
|
flexFactor:(CGFloat)flex
|
||||||
sizeRange:(ASSizeRange)sizeRange
|
sizeRange:(ASSizeRange)sizeRange
|
||||||
identifier:(NSString *)identifier
|
identifier:(NSString *)identifier
|
||||||
{
|
{
|
||||||
@ -81,7 +81,7 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
|
|||||||
itemsVerticalAlignment:(ASVerticalAlignment)verticalAlignment
|
itemsVerticalAlignment:(ASVerticalAlignment)verticalAlignment
|
||||||
identifier:(NSString *)identifier
|
identifier:(NSString *)identifier
|
||||||
{
|
{
|
||||||
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize({50, 50}, NO);
|
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize({50, 50}, 0);
|
||||||
|
|
||||||
ASStackLayoutSpec *stackLayoutSpec = [[ASStackLayoutSpec alloc] init];
|
ASStackLayoutSpec *stackLayoutSpec = [[ASStackLayoutSpec alloc] init];
|
||||||
stackLayoutSpec.direction = direction;
|
stackLayoutSpec.direction = direction;
|
||||||
@ -139,34 +139,34 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
|
|||||||
{
|
{
|
||||||
// width 300px; height 0-300px
|
// width 300px; height 0-300px
|
||||||
static ASSizeRange kSize = {{300, 0}, {300, 300}};
|
static ASSizeRange kSize = {{300, 0}, {300, 300}};
|
||||||
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentStart flex:NO sizeRange:kSize identifier:@"justifyStart"];
|
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentStart flexFactor:0 sizeRange:kSize identifier:@"justifyStart"];
|
||||||
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentCenter flex:NO sizeRange:kSize identifier:@"justifyCenter"];
|
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentCenter flexFactor:0 sizeRange:kSize identifier:@"justifyCenter"];
|
||||||
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentEnd flex:NO sizeRange:kSize identifier:@"justifyEnd"];
|
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentEnd flexFactor:0 sizeRange:kSize identifier:@"justifyEnd"];
|
||||||
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentStart flex:YES sizeRange:kSize identifier:@"flex"];
|
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentStart flexFactor:1 sizeRange:kSize identifier:@"flex"];
|
||||||
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceBetween flex:NO sizeRange:kSize identifier:@"justifySpaceBetween"];
|
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceBetween flexFactor:0 sizeRange:kSize identifier:@"justifySpaceBetween"];
|
||||||
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceAround flex:NO sizeRange:kSize identifier:@"justifySpaceAround"];
|
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceAround flexFactor:0 sizeRange:kSize identifier:@"justifySpaceAround"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testOverflowBehaviors
|
- (void)testOverflowBehaviors
|
||||||
{
|
{
|
||||||
// width 110px; height 0-300px
|
// width 110px; height 0-300px
|
||||||
static ASSizeRange kSize = {{110, 0}, {110, 300}};
|
static ASSizeRange kSize = {{110, 0}, {110, 300}};
|
||||||
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentStart flex:NO sizeRange:kSize identifier:@"justifyStart"];
|
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentStart flexFactor:0 sizeRange:kSize identifier:@"justifyStart"];
|
||||||
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentCenter flex:NO sizeRange:kSize identifier:@"justifyCenter"];
|
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentCenter flexFactor:0 sizeRange:kSize identifier:@"justifyCenter"];
|
||||||
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentEnd flex:NO sizeRange:kSize identifier:@"justifyEnd"];
|
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentEnd flexFactor:0 sizeRange:kSize identifier:@"justifyEnd"];
|
||||||
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentStart flex:YES sizeRange:kSize identifier:@"flex"];
|
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentStart flexFactor:1 sizeRange:kSize identifier:@"flex"];
|
||||||
// On overflow, "space between" is identical to "content start"
|
// On overflow, "space between" is identical to "content start"
|
||||||
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceBetween flex:NO sizeRange:kSize identifier:@"justifyStart"];
|
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceBetween flexFactor:0 sizeRange:kSize identifier:@"justifyStart"];
|
||||||
// On overflow, "space around" is identical to "content center"
|
// On overflow, "space around" is identical to "content center"
|
||||||
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceAround flex:NO sizeRange:kSize identifier:@"justifyCenter"];
|
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceAround flexFactor:0 sizeRange:kSize identifier:@"justifyCenter"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testOverflowBehaviorsWhenAllFlexShrinkChildrenHaveBeenClampedToZeroButViolationStillExists
|
- (void)testOverflowBehaviorsWhenAllFlexShrinkChildrenHaveBeenClampedToZeroButViolationStillExists
|
||||||
{
|
{
|
||||||
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
||||||
|
|
||||||
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize({50, 50}, NO);
|
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize({50, 50}, 0);
|
||||||
subnodes[1].style.flexShrink = YES;
|
subnodes[1].style.flexShrink = 1;
|
||||||
|
|
||||||
// Width is 75px--that's less than the sum of the widths of the children, which is 100px.
|
// Width is 75px--that's less than the sum of the widths of the children, which is 100px.
|
||||||
static ASSizeRange kSize = {{75, 0}, {75, 150}};
|
static ASSizeRange kSize = {{75, 0}, {75, 150}};
|
||||||
@ -177,7 +177,7 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
|
|||||||
{
|
{
|
||||||
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
||||||
|
|
||||||
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize({50, 50}, YES);
|
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize({50, 50}, 1);
|
||||||
setCGSizeToNode({150, 150}, subnodes[1]);
|
setCGSizeToNode({150, 150}, subnodes[1]);
|
||||||
|
|
||||||
// width 300px; height 0-150px.
|
// width 300px; height 0-150px.
|
||||||
@ -333,27 +333,27 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
|
|||||||
{
|
{
|
||||||
// width 301px; height 0-300px; 1px remaining
|
// width 301px; height 0-300px; 1px remaining
|
||||||
static ASSizeRange kSize = {{301, 0}, {301, 300}};
|
static ASSizeRange kSize = {{301, 0}, {301, 300}};
|
||||||
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceBetween flex:NO sizeRange:kSize identifier:nil];
|
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceBetween flexFactor:0 sizeRange:kSize identifier:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testJustifiedSpaceAroundWithRemainingSpace
|
- (void)testJustifiedSpaceAroundWithRemainingSpace
|
||||||
{
|
{
|
||||||
// width 305px; height 0-300px; 5px remaining
|
// width 305px; height 0-300px; 5px remaining
|
||||||
static ASSizeRange kSize = {{305, 0}, {305, 300}};
|
static ASSizeRange kSize = {{305, 0}, {305, 300}};
|
||||||
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceAround flex:NO sizeRange:kSize identifier:nil];
|
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceAround flexFactor:0 sizeRange:kSize identifier:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testChildThatChangesCrossSizeWhenMainSizeIsFlexed
|
- (void)testChildThatChangesCrossSizeWhenMainSizeIsFlexed
|
||||||
{
|
{
|
||||||
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
||||||
|
|
||||||
ASDisplayNode * subnode1 = ASDisplayNodeWithBackgroundColor([UIColor blueColor]);
|
ASDisplayNode *subnode1 = ASDisplayNodeWithBackgroundColor([UIColor blueColor]);
|
||||||
ASDisplayNode * subnode2 = ASDisplayNodeWithBackgroundColor([UIColor redColor], {50, 50});
|
ASDisplayNode *subnode2 = ASDisplayNodeWithBackgroundColor([UIColor redColor], {50, 50});
|
||||||
|
|
||||||
ASRatioLayoutSpec *child1 = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:1.5 child:subnode1];
|
ASRatioLayoutSpec *child1 = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:1.5 child:subnode1];
|
||||||
child1.style.flexBasis = ASDimensionMakeWithFraction(1);
|
child1.style.flexBasis = ASDimensionMakeWithFraction(1);
|
||||||
child1.style.flexGrow = YES;
|
child1.style.flexGrow = 1;
|
||||||
child1.style.flexShrink = YES;
|
child1.style.flexShrink = 1;
|
||||||
|
|
||||||
static ASSizeRange kFixedWidth = {{150, 0}, {150, INFINITY}};
|
static ASSizeRange kFixedWidth = {{150, 0}, {150, INFINITY}};
|
||||||
[self testStackLayoutSpecWithStyle:style children:@[child1, subnode2] sizeRange:kFixedWidth subnodes:@[subnode1, subnode2] identifier:nil];
|
[self testStackLayoutSpecWithStyle:style children:@[child1, subnode2] sizeRange:kFixedWidth subnodes:@[subnode1, subnode2] identifier:nil];
|
||||||
@ -366,13 +366,13 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
|
|||||||
.alignItems = ASStackLayoutAlignItemsCenter
|
.alignItems = ASStackLayoutAlignItemsCenter
|
||||||
};
|
};
|
||||||
|
|
||||||
ASDisplayNode *subnode1 = ASDisplayNodeWithBackgroundColor([UIColor redColor], {100, 100});
|
NSArray<ASDisplayNode *> *subnodes = @[
|
||||||
subnode1.style.flexShrink = YES;
|
ASDisplayNodeWithBackgroundColor([UIColor redColor], {100, 100}),
|
||||||
|
ASDisplayNodeWithBackgroundColor([UIColor blueColor], {50, 50})
|
||||||
|
];
|
||||||
|
subnodes[0].style.flexShrink = 1;
|
||||||
|
subnodes[1].style.flexShrink = 1;
|
||||||
|
|
||||||
ASDisplayNode *subnode2 = ASDisplayNodeWithBackgroundColor([UIColor blueColor], {50, 50});
|
|
||||||
subnode2.style.flexShrink = YES;
|
|
||||||
|
|
||||||
NSArray<ASDisplayNode *> *subnodes = @[subnode1, subnode2];
|
|
||||||
static ASSizeRange kFixedWidth = {{150, 0}, {150, 100}};
|
static ASSizeRange kFixedWidth = {{150, 0}, {150, 100}};
|
||||||
[self testStackLayoutSpecWithStyle:style sizeRange:kFixedWidth subnodes:subnodes identifier:nil];
|
[self testStackLayoutSpecWithStyle:style sizeRange:kFixedWidth subnodes:subnodes identifier:nil];
|
||||||
}
|
}
|
||||||
@ -508,11 +508,11 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
|
|||||||
{
|
{
|
||||||
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
||||||
|
|
||||||
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize({50, 50}, NO);
|
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize({50, 50}, 0);
|
||||||
setCGSizeToNode({150, 150}, subnodes[1]);
|
setCGSizeToNode({150, 150}, subnodes[1]);
|
||||||
|
|
||||||
for (ASDisplayNode *subnode in subnodes) {
|
for (ASDisplayNode *subnode in subnodes) {
|
||||||
subnode.style.flexGrow = YES;
|
subnode.style.flexGrow = 1;
|
||||||
subnode.style.flexBasis = ASDimensionMakeWithPoints(10);
|
subnode.style.flexBasis = ASDimensionMakeWithPoints(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -529,9 +529,9 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
|
|||||||
{
|
{
|
||||||
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
||||||
|
|
||||||
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize({50, 50}, NO);
|
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize({50, 50}, 0);
|
||||||
for (ASDisplayNode *subnode in subnodes) {
|
for (ASDisplayNode *subnode in subnodes) {
|
||||||
subnode.style.flexGrow = YES;
|
subnode.style.flexGrow = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This should override the intrinsic size of 50pts and instead compute to 50% = 100pts.
|
// This should override the intrinsic size of 50pts and instead compute to 50% = 100pts.
|
||||||
@ -561,15 +561,20 @@ static void setCGSizeToNode(CGSize size, ASDisplayNode *node)
|
|||||||
|
|
||||||
- (void)testCrossAxisStretchingOccursAfterStackAxisFlexing
|
- (void)testCrossAxisStretchingOccursAfterStackAxisFlexing
|
||||||
{
|
{
|
||||||
|
// If cross axis stretching occurred *before* flexing, then the blue child would be stretched to 3000 points tall.
|
||||||
|
// Instead it should be stretched to 300 points tall, matching the red child and not overlapping the green inset.
|
||||||
|
|
||||||
NSArray<ASDisplayNode *> *subnodes = @[
|
NSArray<ASDisplayNode *> *subnodes = @[
|
||||||
ASDisplayNodeWithBackgroundColor([UIColor greenColor]),
|
ASDisplayNodeWithBackgroundColor([UIColor greenColor]), // Inset background node
|
||||||
ASDisplayNodeWithBackgroundColor([UIColor blueColor], {10, 0}),
|
ASDisplayNodeWithBackgroundColor([UIColor blueColor]), // child1 of stack
|
||||||
ASDisplayNodeWithBackgroundColor([UIColor redColor], {3000, 3000})
|
ASDisplayNodeWithBackgroundColor([UIColor redColor], {500, 500}) // child2 of stack
|
||||||
];
|
];
|
||||||
|
|
||||||
ASRatioLayoutSpec *child2 = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:1.0 child:subnodes[2]];
|
subnodes[1].style.width = ASDimensionMake(10);
|
||||||
child2.style.flexGrow = YES;
|
|
||||||
child2.style.flexShrink = YES;
|
ASDisplayNode *child2 = subnodes[2];
|
||||||
|
child2.style.flexGrow = 1;
|
||||||
|
child2.style.flexShrink = 1;
|
||||||
|
|
||||||
// If cross axis stretching occurred *before* flexing, then the blue child would be stretched to 3000 points tall.
|
// If cross axis stretching occurred *before* flexing, then the blue child would be stretched to 3000 points tall.
|
||||||
// Instead it should be stretched to 300 points tall, matching the red child and not overlapping the green inset.
|
// Instead it should be stretched to 300 points tall, matching the red child and not overlapping the green inset.
|
||||||
@ -592,29 +597,217 @@ 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)testViolationIsDistributedEquallyAmongFlexibleChildren
|
- (void)testPositiveViolationIsDistributedEquallyAmongFlexibleChildren
|
||||||
{
|
{
|
||||||
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
|
||||||
|
|
||||||
NSArray<ASDisplayNode *> *subnodes = defaultSubnodes();
|
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize({50, 50}, 0);
|
||||||
|
subnodes[0].style.flexGrow = 0;
|
||||||
|
subnodes[2].style.flexGrow = 0;
|
||||||
|
|
||||||
setCGSizeToNode({300, 50}, subnodes[0]);
|
// In this scenario a width of 350 results in a positive violation of 200.
|
||||||
setCGSizeToNode({100, 50}, subnodes[1]);
|
// Due to each flexible subnode specifying a flex grow factor of 1 the violation will be distributed evenly.
|
||||||
setCGSizeToNode({200, 50}, subnodes[2]);
|
static ASSizeRange kSize = {{350, 350}, {350, 350}};
|
||||||
|
|
||||||
subnodes[0].style.flexShrink = YES;
|
|
||||||
subnodes[1].style.flexShrink = NO;
|
|
||||||
subnodes[2].style.flexShrink = YES;
|
|
||||||
|
|
||||||
// A width of 400px results in a violation of 200px. This is distributed equally among each flexible child,
|
|
||||||
// causing both of them to be shrunk by 100px, resulting in widths of 300px, 100px, and 50px.
|
|
||||||
// In the W3 flexbox standard, flexible children are shrunk proportionate to their original sizes,
|
|
||||||
// resulting in widths of 180px, 100px, and 120px.
|
|
||||||
// This test verifies the current behavior--the snapshot contains widths 300px, 100px, and 50px.
|
|
||||||
static ASSizeRange kSize = {{400, 0}, {400, 150}};
|
|
||||||
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)testPositiveViolationIsDistributedProportionallyAmongFlexibleChildren
|
||||||
|
{
|
||||||
|
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionVertical};
|
||||||
|
|
||||||
|
NSArray<ASDisplayNode *> *subnodes = defaultSubnodesWithSameSize({50, 50}, 0);
|
||||||
|
subnodes[0].style.flexGrow = 1;
|
||||||
|
subnodes[1].style.flexGrow = 2;
|
||||||
|
subnodes[2].style.flexGrow = 1;
|
||||||
|
|
||||||
|
// In this scenario a width of 350 results in a positive violation of 200.
|
||||||
|
// The first and third subnodes specify a flex grow factor of 1 and will flex by 50.
|
||||||
|
// The second subnode specifies a flex grow factor of 2 and will flex by 100.
|
||||||
|
static ASSizeRange kSize = {{350, 350}, {350, 350}};
|
||||||
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testPositiveViolationIsDistributedEquallyAmongGrowingAndShrinkingFlexibleChildren
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
subnodes[1].style.flexGrow = 1;
|
||||||
|
subnodes[2].style.flexShrink = 0;
|
||||||
|
subnodes[3].style.flexGrow = 1;
|
||||||
|
|
||||||
|
// In this scenario a width of 400 results in a positive violation of 200.
|
||||||
|
// The first and third subnode specify a flex shrink factor of 1 and 0, respectively. They won't flex.
|
||||||
|
// The second and fourth subnode specify a flex grow factor of 1 and will flex by 100.
|
||||||
|
static ASSizeRange kSize = {{400, 400}, {400, 400}};
|
||||||
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testPositiveViolationIsDistributedProportionallyAmongGrowingAndShrinkingFlexibleChildren
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
subnodes[1].style.flexGrow = 3;
|
||||||
|
subnodes[2].style.flexShrink = 0;
|
||||||
|
subnodes[3].style.flexGrow = 1;
|
||||||
|
|
||||||
|
// In this scenario a width of 400 results in a positive violation of 200.
|
||||||
|
// The first and third subnodes specify a flex shrink factor of 1 and 0, respectively. They won't flex.
|
||||||
|
// The second child subnode specifies a flex grow factor of 3 and will flex by 150.
|
||||||
|
// The fourth child subnode specifies a flex grow factor of 1 and will flex by 50.
|
||||||
|
static ASSizeRange kSize = {{400, 400}, {400, 400}};
|
||||||
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testRemainingViolationIsAppliedProperlyToFirstFlexibleChild
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
subnodes[1].style.flexGrow = 1;
|
||||||
|
subnodes[2].style.flexGrow = 1;
|
||||||
|
|
||||||
|
// In this scenario a width of 300 results in a positive violation of 175.
|
||||||
|
// The second and third subnodes specify a flex grow factor of 1 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)testNegativeViolationIsDistributedProportionallyBasedOnSizeAmongFlexibleChildren
|
||||||
|
{
|
||||||
|
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 = 1;
|
||||||
|
subnodes[1].style.flexShrink = 0;
|
||||||
|
subnodes[2].style.flexShrink = 1;
|
||||||
|
|
||||||
|
// In this scenario a width of 400 results in a negative violation of 200.
|
||||||
|
// The first and third subnodes specify a flex shrink factor of 1 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)testNegativeViolationIsDistributedProportionallyBasedOnSizeAndFlexFactorAmongFlexibleChildren
|
||||||
|
{
|
||||||
|
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 = 2;
|
||||||
|
subnodes[1].style.flexShrink = 1;
|
||||||
|
subnodes[2].style.flexShrink = 2;
|
||||||
|
|
||||||
|
// In this scenario a width of 400 results in a negative violation of 200.
|
||||||
|
// The first and third subnodes specify a flex shrink factor of 2 and will flex by -109 and -72, respectively.
|
||||||
|
// The second subnode specifies a flex shrink factor of 1 and will flex by -18.
|
||||||
|
static ASSizeRange kSize = {{400, 400}, {400, 400}};
|
||||||
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testNegativeViolationIsDistributedProportionallyBasedOnSizeAmongGrowingAndShrinkingFlexibleChildren
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
subnodes[1].style.flexShrink = 1;
|
||||||
|
subnodes[2].style.flexGrow = 0;
|
||||||
|
subnodes[3].style.flexShrink = 1;
|
||||||
|
|
||||||
|
// In this scenario a width of 400 results in a negative violation of 200.
|
||||||
|
// The first and third subnodes specify a flex grow factor of 1 and 0, respectively. They won't flex.
|
||||||
|
// The second and fourth subnodes specify a flex grow factor of 1 and will flex by -100.
|
||||||
|
static ASSizeRange kSize = {{400, 400}, {400, 400}};
|
||||||
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testNegativeViolationIsDistributedProportionallyBasedOnSizeAndFlexFactorAmongGrowingAndShrinkingFlexibleChildren
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
subnodes[1].style.flexShrink = 1;
|
||||||
|
subnodes[2].style.flexGrow = 0;
|
||||||
|
subnodes[3].style.flexShrink = 3;
|
||||||
|
|
||||||
|
// In this scenario a width of 400 results in a negative violation of 200.
|
||||||
|
// The first and third subnodes specify a flex grow factor of 1 and 0, respectively. They won't flex.
|
||||||
|
// The second subnode specifies a flex grow factor of 1 and will flex by -28.
|
||||||
|
// The fourth subnode specifies a flex grow factor of 3 and will flex by -171.
|
||||||
|
static ASSizeRange kSize = {{400, 400}, {400, 400}};
|
||||||
|
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testNegativeViolationIsDistributedProportionallyBasedOnSizeAndFlexFactorDoesNotShrinkToZeroWidth
|
||||||
|
{
|
||||||
|
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 = 1;
|
||||||
|
subnodes[1].style.flexShrink = 2;
|
||||||
|
subnodes[2].style.flexShrink = 1;
|
||||||
|
|
||||||
|
// In this scenario a width of 400 results in a negative violation of 200.
|
||||||
|
// The first and third subnodes specify a flex shrink factor of 1 and will flex by 50.
|
||||||
|
// The second subnode specifies a flex shrink factor of 2 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
|
||||||
|
{
|
||||||
|
ASStackLayoutSpec *stackLayoutSpec = [[ASStackLayoutSpec alloc] init]; // Default direction is horizontal
|
||||||
|
stackLayoutSpec.direction = ASStackLayoutDirectionHorizontal;
|
||||||
|
stackLayoutSpec.alignItems = ASStackLayoutAlignItemsStretch;
|
||||||
|
[stackLayoutSpec.style setSizeWithCGSize:{100, 100}];
|
||||||
|
|
||||||
|
ASDisplayNode *child = ASDisplayNodeWithBackgroundColor([UIColor redColor], {50, 50});
|
||||||
|
stackLayoutSpec.children = @[child];
|
||||||
|
|
||||||
|
static ASSizeRange kSize = {{0, 0}, {300, INFINITY}};
|
||||||
|
[self testStackLayoutSpec:stackLayoutSpec sizeRange:kSize subnodes:@[child] identifier:nil];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)testHorizontalAndVerticalAlignments
|
- (void)testHorizontalAndVerticalAlignments
|
||||||
{
|
{
|
||||||
[self testStackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal itemsHorizontalAlignment:ASHorizontalAlignmentLeft itemsVerticalAlignment:ASVerticalAlignmentTop identifier:@"horizontalTopLeft"];
|
[self testStackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal itemsHorizontalAlignment:ASHorizontalAlignmentLeft itemsVerticalAlignment:ASVerticalAlignmentTop identifier:@"horizontalTopLeft"];
|
||||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 8.8 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: 30 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: 1.2 KiB |
After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 8.4 KiB |
After Width: | Height: | Size: 18 KiB |