mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 22:55:00 +00:00
[ASStackLayoutSpec] Performance improvements (#2470)
* Initial commit for ASStackLayoutSpec improvements * Remove the lock in ASStackLayoutSpec and make the ASStackLayoutSpecStyle const I think we don't need lock here as the style already has a lock while we set the value * Add ASStackLayoutSpecItem that replaces layout specific items * Prevent baseline pass if not needed * Update comments
This commit is contained in:
committed by
GitHub
parent
ca203158da
commit
611894329a
@@ -13,6 +13,8 @@
|
|||||||
|
|
||||||
#import "ASInternalHelpers.h"
|
#import "ASInternalHelpers.h"
|
||||||
|
|
||||||
|
#import "ASLayoutElement.h"
|
||||||
|
#import "ASLayoutElementStylePrivate.h"
|
||||||
#import "ASLayoutSpecUtilities.h"
|
#import "ASLayoutSpecUtilities.h"
|
||||||
#import "ASStackBaselinePositionedLayout.h"
|
#import "ASStackBaselinePositionedLayout.h"
|
||||||
#import "ASThread.h"
|
#import "ASThread.h"
|
||||||
@@ -120,43 +122,65 @@
|
|||||||
|
|
||||||
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
|
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
|
||||||
{
|
{
|
||||||
std::vector<id<ASLayoutElement>> stackChildren;
|
NSArray *children = self.children;
|
||||||
for (id<ASLayoutElement> child in self.children) {
|
if (children.count == 0) {
|
||||||
stackChildren.push_back(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stackChildren.empty()) {
|
|
||||||
return [ASLayout layoutWithLayoutElement:self size:constrainedSize.min];
|
return [ASLayout layoutWithLayoutElement:self size:constrainedSize.min];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Accessing the style and size property is pretty costly we create layout spec children we use to figure
|
||||||
|
// out the layout for each child
|
||||||
|
const auto stackChildren = AS::map(children, [&](const id<ASLayoutElement> child) -> ASStackLayoutSpecChild {
|
||||||
|
ASLayoutElementStyle *style = child.style;
|
||||||
|
return {child, style, style.size};
|
||||||
|
});
|
||||||
|
|
||||||
ASStackLayoutSpecStyle style = {.direction = _direction, .spacing = _spacing, .justifyContent = _justifyContent, .alignItems = _alignItems, .baselineRelativeArrangement = _baselineRelativeArrangement};
|
const ASStackLayoutSpecStyle style = {.direction = _direction, .spacing = _spacing, .justifyContent = _justifyContent, .alignItems = _alignItems, .baselineRelativeArrangement = _baselineRelativeArrangement};
|
||||||
BOOL needsBaselinePass = _baselineRelativeArrangement || _alignItems == ASStackLayoutAlignItemsBaselineFirst || _alignItems == ASStackLayoutAlignItemsBaselineLast;
|
|
||||||
|
|
||||||
|
// First pass is to get the children into a positioned state
|
||||||
const auto unpositionedLayout = ASStackUnpositionedLayout::compute(stackChildren, style, constrainedSize);
|
const auto unpositionedLayout = ASStackUnpositionedLayout::compute(stackChildren, style, constrainedSize);
|
||||||
const auto positionedLayout = ASStackPositionedLayout::compute(unpositionedLayout, style, constrainedSize);
|
const auto positionedLayout = ASStackPositionedLayout::compute(unpositionedLayout, style, constrainedSize);
|
||||||
|
|
||||||
CGSize finalSize = CGSizeZero;
|
// Figure out if a baseline pass is really needed
|
||||||
NSArray *sublayouts = nil;
|
const BOOL directionIsVertical = (style.direction == ASStackLayoutDirectionVertical);
|
||||||
|
const BOOL needsBaselineAlignment = ASStackBaselinePositionedLayout::needsBaselineAlignment(style);
|
||||||
|
const BOOL needsBaselinePositioning = (directionIsVertical == NO || needsBaselineAlignment == YES);
|
||||||
|
|
||||||
// regardless of whether or not this stack aligns to baseline, we should let ASStackBaselinePositionedLayout::compute find the max ascender
|
NSMutableArray *sublayouts = [NSMutableArray array];
|
||||||
// and min descender in case this spec is a child in another spec that wants to align to a baseline.
|
CGSize finalSize = CGSizeZero;
|
||||||
const auto baselinePositionedLayout = ASStackBaselinePositionedLayout::compute(positionedLayout, style, constrainedSize);
|
if (needsBaselinePositioning) {
|
||||||
if (self.direction == ASStackLayoutDirectionVertical) {
|
// All horizontal stacks, regardless of whether or not they are baseline aligned, should go through a baseline
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
// computation. They could be used in another horizontal stack that is baseline aligned and will need to have
|
||||||
self.style.ascender = stackChildren.front().style.ascender;
|
// computed the proper ascender/descender.
|
||||||
self.style.descender = stackChildren.back().style.descender;
|
|
||||||
} else {
|
// Vertical stacks do not need to go through this computation since we can easily compute ascender/descender by
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
// looking at their first/last child's ascender/descender.
|
||||||
self.style.ascender = baselinePositionedLayout.ascender;
|
const auto baselinePositionedLayout = ASStackBaselinePositionedLayout::compute(positionedLayout, style, constrainedSize);
|
||||||
self.style.descender = baselinePositionedLayout.descender;
|
|
||||||
|
if (directionIsVertical == NO) {
|
||||||
|
self.style.ascender = baselinePositionedLayout.ascender;
|
||||||
|
self.style.descender = baselinePositionedLayout.descender;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsBaselineAlignment == YES) {
|
||||||
|
finalSize = directionSize(style.direction, unpositionedLayout.stackDimensionSum, baselinePositionedLayout.crossSize);
|
||||||
|
|
||||||
|
for (const auto &l : baselinePositionedLayout.items) {
|
||||||
|
[sublayouts addObject:l.layout];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needsBaselinePass) {
|
if (directionIsVertical == YES) {
|
||||||
finalSize = directionSize(style.direction, unpositionedLayout.stackDimensionSum, baselinePositionedLayout.crossSize);
|
self.style.ascender = stackChildren.front().style.ascender;
|
||||||
sublayouts = [NSArray arrayWithObjects:&baselinePositionedLayout.sublayouts[0] count:baselinePositionedLayout.sublayouts.size()];
|
self.style.descender = stackChildren.back().style.descender;
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if (needsBaselineAlignment == NO) {
|
||||||
finalSize = directionSize(style.direction, unpositionedLayout.stackDimensionSum, positionedLayout.crossSize);
|
finalSize = directionSize(style.direction, unpositionedLayout.stackDimensionSum, positionedLayout.crossSize);
|
||||||
sublayouts = [NSArray arrayWithObjects:&positionedLayout.sublayouts[0] count:positionedLayout.sublayouts.size()];
|
|
||||||
|
for (const auto &l : positionedLayout.items) {
|
||||||
|
[sublayouts addObject:l.layout];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [ASLayout layoutWithLayoutElement:self size:ASSizeRangeClamp(constrainedSize, finalSize) sublayouts:sublayouts];
|
return [ASLayout layoutWithLayoutElement:self size:ASSizeRangeClamp(constrainedSize, finalSize) sublayouts:sublayouts];
|
||||||
|
|||||||
@@ -13,13 +13,15 @@
|
|||||||
#import "ASStackPositionedLayout.h"
|
#import "ASStackPositionedLayout.h"
|
||||||
|
|
||||||
struct ASStackBaselinePositionedLayout {
|
struct ASStackBaselinePositionedLayout {
|
||||||
const std::vector<ASLayout *> sublayouts;
|
const std::vector<ASStackLayoutSpecItem> items;
|
||||||
const CGFloat crossSize;
|
const CGFloat crossSize;
|
||||||
const CGFloat ascender;
|
const CGFloat ascender;
|
||||||
const CGFloat descender;
|
const CGFloat descender;
|
||||||
|
|
||||||
/** Given a positioned layout, computes each child position using baseline alignment. */
|
/** Given a positioned layout, computes each child position using baseline alignment. */
|
||||||
static ASStackBaselinePositionedLayout compute(const ASStackPositionedLayout &positionedLayout,
|
static ASStackBaselinePositionedLayout compute(const ASStackPositionedLayout &positionedLayout,
|
||||||
const ASStackLayoutSpecStyle &style,
|
const ASStackLayoutSpecStyle &style,
|
||||||
const ASSizeRange &constrainedSize);
|
const ASSizeRange &constrainedSize);
|
||||||
|
|
||||||
|
static BOOL needsBaselineAlignment(const ASStackLayoutSpecStyle &style);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,31 +13,31 @@
|
|||||||
#import "ASLayoutSpecUtilities.h"
|
#import "ASLayoutSpecUtilities.h"
|
||||||
#import "ASLayoutSpec+Subclasses.h"
|
#import "ASLayoutSpec+Subclasses.h"
|
||||||
|
|
||||||
|
#import "ASLayoutElement.h"
|
||||||
|
#import "ASLayoutElementStylePrivate.h"
|
||||||
|
|
||||||
static CGFloat baselineForItem(const ASStackLayoutSpecStyle &style,
|
static CGFloat baselineForItem(const ASStackLayoutSpecStyle &style,
|
||||||
const ASLayout *layout) {
|
const ASStackLayoutSpecItem &l)
|
||||||
|
{
|
||||||
__weak id<ASLayoutElement> child = layout.layoutElement;
|
|
||||||
switch (style.alignItems) {
|
switch (style.alignItems) {
|
||||||
case ASStackLayoutAlignItemsBaselineFirst:
|
case ASStackLayoutAlignItemsBaselineFirst:
|
||||||
return child.style.ascender;
|
return l.child.style.ascender;
|
||||||
case ASStackLayoutAlignItemsBaselineLast:
|
case ASStackLayoutAlignItemsBaselineLast:
|
||||||
return layout.size.height + child.style.descender;
|
return l.layout.size.height + l.child.style.descender;
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static CGFloat baselineOffset(const ASStackLayoutSpecStyle &style,
|
static CGFloat baselineOffset(const ASStackLayoutSpecStyle &style,
|
||||||
const ASLayout *l,
|
const ASStackLayoutSpecItem &l,
|
||||||
const CGFloat maxAscender,
|
const CGFloat maxAscender,
|
||||||
const CGFloat maxBaseline)
|
const CGFloat maxBaseline)
|
||||||
{
|
{
|
||||||
if (style.direction == ASStackLayoutDirectionHorizontal) {
|
if (style.direction == ASStackLayoutDirectionHorizontal) {
|
||||||
__weak id<ASLayoutElement> child = l.layoutElement;
|
|
||||||
switch (style.alignItems) {
|
switch (style.alignItems) {
|
||||||
case ASStackLayoutAlignItemsBaselineFirst:
|
case ASStackLayoutAlignItemsBaselineFirst:
|
||||||
return maxAscender - child.style.ascender;
|
return maxAscender - l.child.style.ascender;
|
||||||
case ASStackLayoutAlignItemsBaselineLast:
|
case ASStackLayoutAlignItemsBaselineLast:
|
||||||
return maxBaseline - baselineForItem(style, l);
|
return maxBaseline - baselineForItem(style, l);
|
||||||
|
|
||||||
@@ -48,19 +48,28 @@ static CGFloat baselineOffset(const ASStackLayoutSpecStyle &style,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static CGFloat maxDimensionForLayout(const ASLayout *l,
|
static CGFloat maxDimensionForItem(const ASStackLayoutSpecItem &l,
|
||||||
const ASStackLayoutSpecStyle &style)
|
const ASStackLayoutSpecStyle &style)
|
||||||
{
|
{
|
||||||
CGFloat maxDimension = crossDimension(style.direction, l.size);
|
CGFloat maxDimension = crossDimension(style.direction, l.layout.size);
|
||||||
style.direction == ASStackLayoutDirectionVertical ? maxDimension += l.position.x : maxDimension += l.position.y;
|
style.direction == ASStackLayoutDirectionVertical ? maxDimension += l.layout.position.x : maxDimension += l.layout.position.y;
|
||||||
return maxDimension;
|
return maxDimension;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOL ASStackBaselinePositionedLayout::needsBaselineAlignment(const ASStackLayoutSpecStyle &style)
|
||||||
|
{
|
||||||
|
return style.baselineRelativeArrangement ||
|
||||||
|
style.alignItems == ASStackLayoutAlignItemsBaselineFirst ||
|
||||||
|
style.alignItems == ASStackLayoutAlignItemsBaselineLast;
|
||||||
|
}
|
||||||
|
|
||||||
ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const ASStackPositionedLayout &positionedLayout,
|
ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const ASStackPositionedLayout &positionedLayout,
|
||||||
const ASStackLayoutSpecStyle &style,
|
const ASStackLayoutSpecStyle &style,
|
||||||
const ASSizeRange &constrainedSize)
|
const ASSizeRange &constrainedSize)
|
||||||
{
|
{
|
||||||
/* Step 1: Look at each child and determine the distance from the top of the child node it's baseline.
|
const auto stackedChildren = positionedLayout.items;
|
||||||
|
|
||||||
|
/* Step 1: Look at each child and determine the distance from the top of the child node to its baseline.
|
||||||
For example, let's say we have the following two text nodes and want to align them to the first baseline:
|
For example, let's say we have the following two text nodes and want to align them to the first baseline:
|
||||||
|
|
||||||
Hello! Why, hello there! How
|
Hello! Why, hello there! How
|
||||||
@@ -74,10 +83,10 @@ ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const A
|
|||||||
the font's descender (it's negative). In the case of the first node, which is only 1 line, this should be the same value as the ascender.
|
the font's descender (it's negative). In the case of the first node, which is only 1 line, this should be the same value as the ascender.
|
||||||
The second node, however, has a larger height and there will have a larger baseline offset.
|
The second node, however, has a larger height and there will have a larger baseline offset.
|
||||||
*/
|
*/
|
||||||
const auto baselineIt = std::max_element(positionedLayout.sublayouts.begin(), positionedLayout.sublayouts.end(), [&](const ASLayout *a, const ASLayout *b){
|
const auto baselineIt = std::max_element(stackedChildren.begin(), stackedChildren.end(), [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b){
|
||||||
return baselineForItem(style, a) < baselineForItem(style, b);
|
return baselineForItem(style, a) < baselineForItem(style, b);
|
||||||
});
|
});
|
||||||
const CGFloat maxBaseline = baselineIt == positionedLayout.sublayouts.end() ? 0 : baselineForItem(style, *baselineIt);
|
const CGFloat maxBaseline = baselineIt == stackedChildren.end() ? 0 : baselineForItem(style, *baselineIt);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Step 2: Find the max ascender for all of the children.
|
Step 2: Find the max ascender for all of the children.
|
||||||
@@ -89,10 +98,10 @@ ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const A
|
|||||||
Note: if we are aligning to the last baseline, then we don't need this value in our computation. However, we do want
|
Note: if we are aligning to the last baseline, then we don't need this value in our computation. However, we do want
|
||||||
our layoutSpec to have it so that it can be baseline aligned with another text node or baseline layout spec.
|
our layoutSpec to have it so that it can be baseline aligned with another text node or baseline layout spec.
|
||||||
*/
|
*/
|
||||||
const auto ascenderIt = std::max_element(positionedLayout.sublayouts.begin(), positionedLayout.sublayouts.end(), [&](const ASLayout *a, const ASLayout *b){
|
const auto ascenderIt = std::max_element(stackedChildren.begin(), stackedChildren.end(), [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b){
|
||||||
return a.layoutElement.style.ascender < b.layoutElement.style.ascender;
|
return a.child.style.ascender < b.child.style.ascender;
|
||||||
});
|
});
|
||||||
const CGFloat maxAscender = ascenderIt == positionedLayout.sublayouts.end() ? 0 : (*ascenderIt).layoutElement.style.ascender;
|
const CGFloat maxAscender = ascenderIt == stackedChildren.end() ? 0 : (*ascenderIt).child.style.ascender;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Step 3: Take each child and update its layout position based on the baseline offset.
|
Step 3: Take each child and update its layout position based on the baseline offset.
|
||||||
@@ -102,39 +111,34 @@ ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const A
|
|||||||
spacing between the two nodes is from the baseline, not the bounding box.
|
spacing between the two nodes is from the baseline, not the bounding box.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
std::vector<ASLayout *> stackedChildren;
|
|
||||||
// Only change positions of layouts this stackSpec is aligning to a baseline. Otherwise we are only here to
|
// Only change positions of layouts this stackSpec is aligning to a baseline. Otherwise we are only here to
|
||||||
// compute the min/max descender/ascender for this stack spec.
|
// compute the min/max descender/ascender for this stack spec.
|
||||||
if (style.baselineRelativeArrangement || style.alignItems == ASStackLayoutAlignItemsBaselineFirst || style.alignItems == ASStackLayoutAlignItemsBaselineLast) {
|
if (ASStackBaselinePositionedLayout::needsBaselineAlignment(style)) {
|
||||||
|
// Adjust the positioned layout items to be positioned based on the baseline
|
||||||
CGPoint p = CGPointZero;
|
CGPoint p = CGPointZero;
|
||||||
BOOL first = YES;
|
BOOL first = YES;
|
||||||
stackedChildren = AS::map(positionedLayout.sublayouts, [&](ASLayout *l) -> ASLayout *{
|
|
||||||
__weak id<ASLayoutElement> child = l.layoutElement;
|
for (const ASStackLayoutSpecItem &l : stackedChildren) {
|
||||||
p = p + directionPoint(style.direction, child.style.spacingBefore, 0);
|
ASLayoutElementStyle *layoutElementStyle = l.child.style;
|
||||||
if (first) {
|
|
||||||
// if this is the first item use the previously computed start point
|
p = p + directionPoint(style.direction, layoutElementStyle.spacingBefore, 0);
|
||||||
p = l.position;
|
|
||||||
} else {
|
// if this is the first item use the previously computed start point otherwise add the stack spacing
|
||||||
// otherwise add the stack spacing
|
p = first ? l.layout.position : p + directionPoint(style.direction, style.spacing, 0);
|
||||||
p = p + directionPoint(style.direction, style.spacing, 0);
|
|
||||||
}
|
|
||||||
first = NO;
|
first = NO;
|
||||||
|
|
||||||
// Find the difference between this node's baseline and the max baseline of all the children. Add this difference to the child's y position.
|
// Find the difference between this node's baseline and the max baseline of all the children. Add this difference to the child's y position.
|
||||||
l.position = p + CGPointMake(0, baselineOffset(style, l, maxAscender, maxBaseline));
|
l.layout.position = p + CGPointMake(0, baselineOffset(style, l, maxAscender, maxBaseline));
|
||||||
|
|
||||||
// If we are a vertical stack, add the item's descender (it is negative) to the offset for the next node. This will ensure we are spacing
|
// If we are a vertical stack, add the item's descender (it is negative) to the offset for the next node. This will ensure we are spacing
|
||||||
// node from baselines and not bounding boxes.
|
// node from baselines and not bounding boxes.
|
||||||
CGFloat spacingAfterBaseline = 0;
|
CGFloat spacingAfterBaseline = 0;
|
||||||
if (style.direction == ASStackLayoutDirectionVertical) {
|
if (style.direction == ASStackLayoutDirectionVertical) {
|
||||||
spacingAfterBaseline = child.style.descender;
|
spacingAfterBaseline = layoutElementStyle.descender;
|
||||||
}
|
}
|
||||||
p = p + directionPoint(style.direction, stackDimension(style.direction, l.size) + child.style.spacingAfter + spacingAfterBaseline, 0);
|
p = p + directionPoint(style.direction, stackDimension(style.direction, l.layout.size) + layoutElementStyle.spacingAfter + spacingAfterBaseline, 0);
|
||||||
|
}
|
||||||
return l;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
stackedChildren = positionedLayout.sublayouts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -147,10 +151,10 @@ ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const A
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
const auto it = std::max_element(stackedChildren.begin(), stackedChildren.end(),
|
const auto it = std::max_element(stackedChildren.begin(), stackedChildren.end(),
|
||||||
[&](ASLayout *a, ASLayout *b) {
|
[&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b) {
|
||||||
return maxDimensionForLayout(a, style) < maxDimensionForLayout(b, style);
|
return maxDimensionForItem(a, style) < maxDimensionForItem(b, style);
|
||||||
});
|
});
|
||||||
const auto largestChildCrossSize = it == stackedChildren.end() ? 0 : maxDimensionForLayout(*it, style);
|
const auto largestChildCrossSize = it == stackedChildren.end() ? 0 : maxDimensionForItem(*it, style);
|
||||||
const auto minCrossSize = crossDimension(style.direction, constrainedSize.min);
|
const auto minCrossSize = crossDimension(style.direction, constrainedSize.min);
|
||||||
const auto maxCrossSize = crossDimension(style.direction, constrainedSize.max);
|
const auto maxCrossSize = crossDimension(style.direction, constrainedSize.max);
|
||||||
const CGFloat crossSize = MIN(MAX(minCrossSize, largestChildCrossSize), maxCrossSize);
|
const CGFloat crossSize = MIN(MAX(minCrossSize, largestChildCrossSize), maxCrossSize);
|
||||||
@@ -159,10 +163,10 @@ ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const A
|
|||||||
Step 5: finally, we must find the smallest descender (descender is negative). This is since ASBaselineLayoutSpec implements
|
Step 5: finally, we must find the smallest descender (descender is negative). This is since ASBaselineLayoutSpec implements
|
||||||
ASLayoutElement and needs an ascender and descender to lay itself out properly.
|
ASLayoutElement and needs an ascender and descender to lay itself out properly.
|
||||||
*/
|
*/
|
||||||
const auto descenderIt = std::max_element(stackedChildren.begin(), stackedChildren.end(), [&](const ASLayout *a, const ASLayout *b){
|
const auto descenderIt = std::max_element(stackedChildren.begin(), stackedChildren.end(), [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b){
|
||||||
return a.position.y + a.size.height < b.position.y + b.size.height;
|
return a.layout.position.y + a.layout.size.height < b.layout.position.y + b.layout.size.height;
|
||||||
});
|
});
|
||||||
const CGFloat minDescender = descenderIt == stackedChildren.end() ? 0 : (*descenderIt).layoutElement.style.descender;
|
const CGFloat minDescender = descenderIt == stackedChildren.end() ? 0 : (*descenderIt).child.style.descender;
|
||||||
|
|
||||||
return {stackedChildren, crossSize, maxAscender, minDescender};
|
return {std::move(stackedChildren), crossSize, maxAscender, minDescender};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
/** Represents a set of laid out and positioned stack layout children. */
|
/** Represents a set of laid out and positioned stack layout children. */
|
||||||
struct ASStackPositionedLayout {
|
struct ASStackPositionedLayout {
|
||||||
const std::vector<ASLayout *> sublayouts;
|
const std::vector<ASStackLayoutSpecItem> items;
|
||||||
const CGFloat crossSize;
|
const CGFloat crossSize;
|
||||||
|
|
||||||
/** Given an unpositioned layout, computes the positions each child should be placed at. */
|
/** Given an unpositioned layout, computes the positions each child should be placed at. */
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
#import "ASLayoutSpec+Subclasses.h"
|
#import "ASLayoutSpec+Subclasses.h"
|
||||||
|
|
||||||
static CGFloat crossOffset(const ASStackLayoutSpecStyle &style,
|
static CGFloat crossOffset(const ASStackLayoutSpecStyle &style,
|
||||||
const ASStackUnpositionedItem &l,
|
const ASStackLayoutSpecItem &l,
|
||||||
const CGFloat crossSize)
|
const CGFloat crossSize)
|
||||||
{
|
{
|
||||||
switch (alignment(l.child.style.alignSelf, style.alignItems)) {
|
switch (alignment(l.child.style.alignSelf, style.alignItems)) {
|
||||||
@@ -50,9 +50,10 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style
|
|||||||
const ASStackUnpositionedLayout &unpositionedLayout,
|
const ASStackUnpositionedLayout &unpositionedLayout,
|
||||||
const ASSizeRange &constrainedSize)
|
const ASSizeRange &constrainedSize)
|
||||||
{
|
{
|
||||||
|
|
||||||
// The cross dimension is the max of the childrens' cross dimensions (clamped to our constraint below).
|
// The cross dimension is the max of the childrens' cross dimensions (clamped to our constraint below).
|
||||||
const auto it = std::max_element(unpositionedLayout.items.begin(), unpositionedLayout.items.end(),
|
const auto it = std::max_element(unpositionedLayout.items.begin(), unpositionedLayout.items.end(),
|
||||||
[&](const ASStackUnpositionedItem &a, const ASStackUnpositionedItem &b){
|
[&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b){
|
||||||
return compareCrossDimension(style.direction, a.layout.size, b.layout.size);
|
return compareCrossDimension(style.direction, a.layout.size, b.layout.size);
|
||||||
});
|
});
|
||||||
const auto largestChildCrossSize = it == unpositionedLayout.items.end() ? 0 : crossDimension(style.direction, it->layout.size);
|
const auto largestChildCrossSize = it == unpositionedLayout.items.end() ? 0 : crossDimension(style.direction, it->layout.size);
|
||||||
@@ -64,9 +65,11 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style
|
|||||||
BOOL first = YES;
|
BOOL first = YES;
|
||||||
const auto lastChild = unpositionedLayout.items.back().child;
|
const auto lastChild = unpositionedLayout.items.back().child;
|
||||||
CGFloat offset = 0;
|
CGFloat offset = 0;
|
||||||
|
|
||||||
auto stackedChildren = AS::map(unpositionedLayout.items, [&](const ASStackUnpositionedItem &l) -> ASLayout *{
|
// Adjust the position of the unpositioned layouts to be positioned
|
||||||
offset = (l.child == lastChild) ? lastChildOffset : 0;
|
const auto stackedChildren = unpositionedLayout.items;
|
||||||
|
for (const auto &l : stackedChildren) {
|
||||||
|
offset = (l.child.element == lastChild.element) ? lastChildOffset : 0;
|
||||||
p = p + directionPoint(style.direction, l.child.style.spacingBefore + offset, 0);
|
p = p + directionPoint(style.direction, l.child.style.spacingBefore + offset, 0);
|
||||||
if (!first) {
|
if (!first) {
|
||||||
p = p + directionPoint(style.direction, style.spacing + extraSpacing, 0);
|
p = p + directionPoint(style.direction, style.spacing + extraSpacing, 0);
|
||||||
@@ -75,9 +78,9 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style
|
|||||||
l.layout.position = p + directionPoint(style.direction, 0, crossOffset(style, l, crossSize));
|
l.layout.position = p + directionPoint(style.direction, 0, crossOffset(style, l, crossSize));
|
||||||
|
|
||||||
p = p + directionPoint(style.direction, stackDimension(style.direction, l.layout.size) + l.child.style.spacingAfter, 0);
|
p = p + directionPoint(style.direction, stackDimension(style.direction, l.layout.size) + l.child.style.spacingAfter, 0);
|
||||||
return l.layout;
|
}
|
||||||
});
|
|
||||||
return {stackedChildren, crossSize};
|
return {std::move(stackedChildren), crossSize};
|
||||||
}
|
}
|
||||||
|
|
||||||
static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style,
|
static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style,
|
||||||
@@ -105,12 +108,15 @@ ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnposition
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (justifyContent) {
|
switch (justifyContent) {
|
||||||
case ASStackLayoutJustifyContentStart:
|
case ASStackLayoutJustifyContentStart: {
|
||||||
return stackedLayout(style, 0, unpositionedLayout, constrainedSize);
|
return stackedLayout(style, 0, unpositionedLayout, constrainedSize);
|
||||||
case ASStackLayoutJustifyContentCenter:
|
}
|
||||||
|
case ASStackLayoutJustifyContentCenter: {
|
||||||
return stackedLayout(style, std::floor(violation / 2), unpositionedLayout, constrainedSize);
|
return stackedLayout(style, std::floor(violation / 2), unpositionedLayout, constrainedSize);
|
||||||
case ASStackLayoutJustifyContentEnd:
|
}
|
||||||
|
case ASStackLayoutJustifyContentEnd: {
|
||||||
return stackedLayout(style, violation, unpositionedLayout, constrainedSize);
|
return stackedLayout(style, violation, unpositionedLayout, constrainedSize);
|
||||||
|
}
|
||||||
case ASStackLayoutJustifyContentSpaceBetween: {
|
case ASStackLayoutJustifyContentSpaceBetween: {
|
||||||
const auto numOfSpacings = numOfItems - 1;
|
const auto numOfSpacings = numOfItems - 1;
|
||||||
return stackedLayout(style, 0, std::floor(violation / numOfSpacings), std::fmod(violation, numOfSpacings), unpositionedLayout, constrainedSize);
|
return stackedLayout(style, 0, std::floor(violation / numOfSpacings), std::fmod(violation, numOfSpacings), unpositionedLayout, constrainedSize);
|
||||||
|
|||||||
@@ -14,24 +14,34 @@
|
|||||||
#import "ASStackLayoutSpecUtilities.h"
|
#import "ASStackLayoutSpecUtilities.h"
|
||||||
#import "ASStackLayoutSpec.h"
|
#import "ASStackLayoutSpec.h"
|
||||||
|
|
||||||
struct ASStackUnpositionedItem {
|
struct ASStackLayoutSpecChild {
|
||||||
/** The original source child. */
|
/** The original source child. */
|
||||||
id<ASLayoutElement> child;
|
id<ASLayoutElement> element;
|
||||||
/** The proposed layout. */
|
/** Style object of element. */
|
||||||
|
ASLayoutElementStyle *style;
|
||||||
|
/** Size object of the element */
|
||||||
|
ASLayoutElementSize size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ASStackLayoutSpecItem {
|
||||||
|
/** The original source child. */
|
||||||
|
ASStackLayoutSpecChild child;
|
||||||
|
/** The proposed layout or nil if no is calculated yet. */
|
||||||
ASLayout *layout;
|
ASLayout *layout;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/** Represents a set of stack layout children that have their final layout computed, but are not yet positioned. */
|
/** Represents a set of stack layout children that have their final layout computed, but are not yet positioned. */
|
||||||
struct ASStackUnpositionedLayout {
|
struct ASStackUnpositionedLayout {
|
||||||
/** A set of proposed child layouts, not yet positioned. */
|
/** A set of proposed child layouts, not yet positioned. */
|
||||||
const std::vector<ASStackUnpositionedItem> items;
|
const std::vector<ASStackLayoutSpecItem> items;
|
||||||
/** The total size of the children in the stack dimension, including all spacing. */
|
/** The total size of the children in the stack dimension, including all spacing. */
|
||||||
const CGFloat stackDimensionSum;
|
const CGFloat stackDimensionSum;
|
||||||
/** The amount by which stackDimensionSum violates constraints. If positive, less than min; negative, greater than max. */
|
/** The amount by which stackDimensionSum violates constraints. If positive, less than min; negative, greater than max. */
|
||||||
const CGFloat violation;
|
const CGFloat violation;
|
||||||
|
|
||||||
/** Given a set of children, computes the unpositioned layouts for those children. */
|
/** Given a set of children, computes the unpositioned layouts for those children. */
|
||||||
static ASStackUnpositionedLayout compute(const std::vector<id<ASLayoutElement>> &children,
|
static ASStackUnpositionedLayout compute(const std::vector<ASStackLayoutSpecChild> &children,
|
||||||
const ASStackLayoutSpecStyle &style,
|
const ASStackLayoutSpecStyle &style,
|
||||||
const ASSizeRange &sizeRange);
|
const ASSizeRange &sizeRange);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,12 +17,11 @@
|
|||||||
#import "ASLayoutElementStylePrivate.h"
|
#import "ASLayoutElementStylePrivate.h"
|
||||||
|
|
||||||
static CGFloat resolveCrossDimensionMaxForStretchChild(const ASStackLayoutSpecStyle &style,
|
static CGFloat resolveCrossDimensionMaxForStretchChild(const ASStackLayoutSpecStyle &style,
|
||||||
const id<ASLayoutElement>child,
|
const ASStackLayoutSpecChild &child,
|
||||||
const CGFloat stackMax,
|
const CGFloat stackMax,
|
||||||
const CGFloat crossMax)
|
const CGFloat crossMax)
|
||||||
{
|
{
|
||||||
// stretched children may have a cross direction max that is smaller than the minimum size constraint of the parent.
|
// 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 ?
|
const CGFloat computedMax = (style.direction == ASStackLayoutDirectionVertical ?
|
||||||
ASLayoutElementSizeResolve(child.style.size, ASLayoutElementParentSizeUndefined).max.width :
|
ASLayoutElementSizeResolve(child.style.size, ASLayoutElementParentSizeUndefined).max.width :
|
||||||
ASLayoutElementSizeResolve(child.style.size, ASLayoutElementParentSizeUndefined).max.height);
|
ASLayoutElementSizeResolve(child.style.size, ASLayoutElementParentSizeUndefined).max.height);
|
||||||
@@ -30,7 +29,7 @@ static CGFloat resolveCrossDimensionMaxForStretchChild(const ASStackLayoutSpecSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
static CGFloat resolveCrossDimensionMinForStretchChild(const ASStackLayoutSpecStyle &style,
|
static CGFloat resolveCrossDimensionMinForStretchChild(const ASStackLayoutSpecStyle &style,
|
||||||
const id<ASLayoutElement>child,
|
const ASStackLayoutSpecChild &child,
|
||||||
const CGFloat stackMax,
|
const CGFloat stackMax,
|
||||||
const CGFloat crossMin)
|
const CGFloat crossMin)
|
||||||
{
|
{
|
||||||
@@ -44,8 +43,8 @@ static CGFloat resolveCrossDimensionMinForStretchChild(const ASStackLayoutSpecSt
|
|||||||
/**
|
/**
|
||||||
Sizes the child given the parameters specified, and returns the computed layout.
|
Sizes the child given the parameters specified, and returns the computed layout.
|
||||||
*/
|
*/
|
||||||
static ASLayout *crossChildLayout(const id<ASLayoutElement> child,
|
static ASLayout *crossChildLayout(const ASStackLayoutSpecChild &child,
|
||||||
const ASStackLayoutSpecStyle style,
|
const ASStackLayoutSpecStyle &style,
|
||||||
const CGFloat stackMin,
|
const CGFloat stackMin,
|
||||||
const CGFloat stackMax,
|
const CGFloat stackMax,
|
||||||
const CGFloat crossMin,
|
const CGFloat crossMin,
|
||||||
@@ -54,14 +53,16 @@ static ASLayout *crossChildLayout(const id<ASLayoutElement> 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 ? resolveCrossDimensionMinForStretchChild(style, child, stackMax, crossMin) : 0);
|
const CGFloat childCrossMin = (alignItems == ASStackLayoutAlignItemsStretch ?
|
||||||
|
resolveCrossDimensionMinForStretchChild(style, child, stackMax, crossMin) :
|
||||||
|
0);
|
||||||
const CGFloat childCrossMax = (alignItems == ASStackLayoutAlignItemsStretch ?
|
const CGFloat childCrossMax = (alignItems == ASStackLayoutAlignItemsStretch ?
|
||||||
resolveCrossDimensionMaxForStretchChild(style, child, stackMax, crossMax) :
|
resolveCrossDimensionMaxForStretchChild(style, child, stackMax, crossMax) :
|
||||||
crossMax);
|
crossMax);
|
||||||
const ASSizeRange childSizeRange = directionSizeRange(style.direction, stackMin, stackMax, childCrossMin, childCrossMax);
|
const ASSizeRange childSizeRange = directionSizeRange(style.direction, stackMin, stackMax, childCrossMin, childCrossMax);
|
||||||
ASLayout *layout = [child layoutThatFits:childSizeRange parentSize:size];
|
ASLayout *layout = [child.element 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.element);
|
||||||
return layout ? : [ASLayout layoutWithLayoutElement:child size:{0, 0}];
|
return layout ? : [ASLayout layoutWithLayoutElement:child.element size:{0, 0}];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,13 +97,13 @@ static ASLayout *crossChildLayout(const id<ASLayoutElement> child,
|
|||||||
@param layouts pre-computed child layouts; modified in-place as needed
|
@param layouts pre-computed child layouts; modified in-place as needed
|
||||||
@param style the layout style of the overall stack layout
|
@param style the layout style of the overall stack layout
|
||||||
*/
|
*/
|
||||||
static void stretchChildrenAlongCrossDimension(std::vector<ASStackUnpositionedItem> &layouts,
|
static void stretchChildrenAlongCrossDimension(std::vector<ASStackLayoutSpecItem> &layouts,
|
||||||
const ASStackLayoutSpecStyle &style,
|
const ASStackLayoutSpecStyle &style,
|
||||||
const CGSize size)
|
const CGSize size)
|
||||||
{
|
{
|
||||||
// Find the maximum cross dimension size among child layouts
|
// Find the maximum cross dimension size among child layouts
|
||||||
const auto it = std::max_element(layouts.begin(), layouts.end(),
|
const auto it = std::max_element(layouts.begin(), layouts.end(),
|
||||||
[&](const ASStackUnpositionedItem &a, const ASStackUnpositionedItem &b) {
|
[&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b) {
|
||||||
return compareCrossDimension(style.direction, a.layout.size, b.layout.size);
|
return compareCrossDimension(style.direction, a.layout.size, b.layout.size);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -129,18 +130,18 @@ static const CGFloat kViolationEpsilon = 0.01;
|
|||||||
Returns a lambda that computes the relevant flex factor based on the given violation.
|
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.
|
@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)
|
static std::function<CGFloat(const ASStackLayoutSpecItem &)> flexFactorInViolationDirection(const CGFloat violation)
|
||||||
{
|
{
|
||||||
if (std::fabs(violation) < kViolationEpsilon) {
|
if (std::fabs(violation) < kViolationEpsilon) {
|
||||||
return [](const ASStackUnpositionedItem &item) { return 0.0; };
|
return [](const ASStackLayoutSpecItem &item) { return 0.0; };
|
||||||
} else if (violation > 0) {
|
} else if (violation > 0) {
|
||||||
return [](const ASStackUnpositionedItem &item) { return item.child.style.flexGrow; };
|
return [](const ASStackLayoutSpecItem &item) { return item.child.style.flexGrow; };
|
||||||
} else {
|
} else {
|
||||||
return [](const ASStackUnpositionedItem &item) { return item.child.style.flexShrink; };
|
return [](const ASStackLayoutSpecItem &item) { return item.child.style.flexShrink; };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline CGFloat scaledFlexShrinkFactor(const ASStackUnpositionedItem &item,
|
static inline CGFloat scaledFlexShrinkFactor(const ASStackLayoutSpecItem &item,
|
||||||
const ASStackLayoutSpecStyle &style,
|
const ASStackLayoutSpecStyle &style,
|
||||||
const CGFloat flexFactorSum)
|
const CGFloat flexFactorSum)
|
||||||
{
|
{
|
||||||
@@ -155,15 +156,15 @@ static inline CGFloat scaledFlexShrinkFactor(const ASStackUnpositionedItem &item
|
|||||||
@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 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 &)> flexShrinkAdjustment(const std::vector<ASStackUnpositionedItem> &items,
|
static std::function<CGFloat(const ASStackLayoutSpecItem &)> flexShrinkAdjustment(const std::vector<ASStackLayoutSpecItem> &items,
|
||||||
const ASStackLayoutSpecStyle &style,
|
const ASStackLayoutSpecStyle &style,
|
||||||
const CGFloat violation,
|
const CGFloat violation,
|
||||||
const CGFloat flexFactorSum)
|
const CGFloat flexFactorSum)
|
||||||
{
|
{
|
||||||
const CGFloat scaledFlexShrinkFactorSum = std::accumulate(items.begin(), items.end(), 0.0, [&](CGFloat x, const ASStackUnpositionedItem &item) {
|
const CGFloat scaledFlexShrinkFactorSum = std::accumulate(items.begin(), items.end(), 0.0, [&](CGFloat x, const ASStackLayoutSpecItem &item) {
|
||||||
return x + scaledFlexShrinkFactor(item, style, flexFactorSum);
|
return x + scaledFlexShrinkFactor(item, style, flexFactorSum);
|
||||||
});
|
});
|
||||||
return [style, scaledFlexShrinkFactorSum, violation, flexFactorSum](const ASStackUnpositionedItem &item) {
|
return [style, scaledFlexShrinkFactorSum, violation, flexFactorSum](const ASStackLayoutSpecItem &item) {
|
||||||
const CGFloat scaledFlexShrinkFactorRatio = scaledFlexShrinkFactor(item, style, flexFactorSum) / 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.
|
||||||
@@ -178,12 +179,12 @@ static std::function<CGFloat(const ASStackUnpositionedItem &)> flexShrinkAdjustm
|
|||||||
@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 &)> flexGrowAdjustment(const std::vector<ASStackUnpositionedItem> &items,
|
static std::function<CGFloat(const ASStackLayoutSpecItem &)> flexGrowAdjustment(const std::vector<ASStackLayoutSpecItem> &items,
|
||||||
const CGFloat violation,
|
const CGFloat violation,
|
||||||
const CGFloat flexFactorSum)
|
const CGFloat 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.
|
||||||
return [violation, flexFactorSum](const ASStackUnpositionedItem &item) {
|
return [violation, flexFactorSum](const ASStackLayoutSpecItem &item) {
|
||||||
return std::floor(violation * (item.child.style.flexGrow / flexFactorSum));
|
return std::floor(violation * (item.child.style.flexGrow / flexFactorSum));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -196,7 +197,7 @@ static std::function<CGFloat(const ASStackUnpositionedItem &)> flexGrowAdjustmen
|
|||||||
@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 &)> flexAdjustmentInViolationDirection(const std::vector<ASStackUnpositionedItem> &items,
|
static std::function<CGFloat(const ASStackLayoutSpecItem &)> flexAdjustmentInViolationDirection(const std::vector<ASStackLayoutSpecItem> &items,
|
||||||
const ASStackLayoutSpecStyle &style,
|
const ASStackLayoutSpecStyle &style,
|
||||||
const CGFloat violation,
|
const CGFloat violation,
|
||||||
const CGFloat flexFactorSum)
|
const CGFloat flexFactorSum)
|
||||||
@@ -208,7 +209,7 @@ static std::function<CGFloat(const ASStackUnpositionedItem &)> flexAdjustmentInV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ASDISPLAYNODE_INLINE BOOL isFlexibleInBothDirections(id<ASLayoutElement> child)
|
ASDISPLAYNODE_INLINE BOOL isFlexibleInBothDirections(const ASStackLayoutSpecChild &child)
|
||||||
{
|
{
|
||||||
return child.style.flexGrow > 0 && child.style.flexShrink > 0;
|
return child.style.flexGrow > 0 && child.style.flexShrink > 0;
|
||||||
}
|
}
|
||||||
@@ -217,15 +218,14 @@ ASDISPLAYNODE_INLINE BOOL isFlexibleInBothDirections(id<ASLayoutElement> child)
|
|||||||
The flexible children may have been left not laid out in the initial layout pass, so we may have to go through and size
|
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.
|
these children at zero size so that the children layouts are at least present.
|
||||||
*/
|
*/
|
||||||
static void layoutFlexibleChildrenAtZeroSize(std::vector<ASStackUnpositionedItem> &items,
|
static void layoutFlexibleChildrenAtZeroSize(std::vector<ASStackLayoutSpecItem> &items,
|
||||||
const ASStackLayoutSpecStyle &style,
|
const ASStackLayoutSpecStyle &style,
|
||||||
const ASSizeRange &sizeRange,
|
const ASSizeRange &sizeRange,
|
||||||
const CGSize size)
|
const CGSize size)
|
||||||
{
|
{
|
||||||
for (ASStackUnpositionedItem &item : items) {
|
for (ASStackLayoutSpecItem &item : items) {
|
||||||
const id<ASLayoutElement> child = item.child;
|
if (isFlexibleInBothDirections(item.child)) {
|
||||||
if (isFlexibleInBothDirections(child)) {
|
item.layout = crossChildLayout(item.child,
|
||||||
item.layout = crossChildLayout(child,
|
|
||||||
style,
|
style,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
@@ -250,21 +250,20 @@ static void layoutFlexibleChildrenAtZeroSize(std::vector<ASStackUnpositionedItem
|
|||||||
@param children unpositioned layouts for the children of the stack spec
|
@param children unpositioned layouts for the children of the stack spec
|
||||||
@param style the layout style of the overall stack layout
|
@param style the layout style of the overall stack layout
|
||||||
*/
|
*/
|
||||||
static CGFloat computeStackDimensionSum(const std::vector<ASStackUnpositionedItem> &children,
|
static CGFloat computeStackDimensionSum(const std::vector<ASStackLayoutSpecItem> &children,
|
||||||
const ASStackLayoutSpecStyle &style)
|
const ASStackLayoutSpecStyle &style)
|
||||||
{
|
{
|
||||||
// Sum up the childrens' spacing
|
// Sum up the childrens' spacing
|
||||||
const CGFloat childSpacingSum = std::accumulate(children.begin(), children.end(),
|
const CGFloat childSpacingSum = std::accumulate(children.begin(), children.end(),
|
||||||
// Start from default spacing between each child:
|
// Start from default spacing between each child:
|
||||||
children.empty() ? 0 : style.spacing * (children.size() - 1),
|
children.empty() ? 0 : style.spacing * (children.size() - 1),
|
||||||
[&](CGFloat x, const ASStackUnpositionedItem &l) {
|
[&](CGFloat x, const ASStackLayoutSpecItem &l) {
|
||||||
const id<ASLayoutElement> child = l.child;
|
return x + l.child.style.spacingBefore + l.child.style.spacingAfter;
|
||||||
return x + child.style.spacingBefore + child.style.spacingAfter;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sum up the childrens' dimensions (including spacing) in the stack direction.
|
// Sum up the childrens' dimensions (including spacing) in the stack direction.
|
||||||
const CGFloat childStackDimensionSum = std::accumulate(children.begin(), children.end(), childSpacingSum,
|
const CGFloat childStackDimensionSum = std::accumulate(children.begin(), children.end(), childSpacingSum,
|
||||||
[&](CGFloat x, const ASStackUnpositionedItem &l) {
|
[&](CGFloat x, const ASStackLayoutSpecItem &l) {
|
||||||
return x + stackDimension(style.direction, l.layout.size);
|
return x + stackDimension(style.direction, l.layout.size);
|
||||||
});
|
});
|
||||||
return childStackDimensionSum;
|
return childStackDimensionSum;
|
||||||
@@ -319,7 +318,7 @@ static CGFloat computeViolation(const CGFloat stackDimensionSum,
|
|||||||
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.
|
||||||
*/
|
*/
|
||||||
ASDISPLAYNODE_INLINE BOOL useOptimizedFlexing(const std::vector<id<ASLayoutElement>> &children,
|
ASDISPLAYNODE_INLINE BOOL useOptimizedFlexing(const std::vector<ASStackLayoutSpecChild> &children,
|
||||||
const ASStackLayoutSpecStyle &style,
|
const ASStackLayoutSpecStyle &style,
|
||||||
const ASSizeRange &sizeRange)
|
const ASSizeRange &sizeRange)
|
||||||
{
|
{
|
||||||
@@ -342,17 +341,17 @@ ASDISPLAYNODE_INLINE BOOL useOptimizedFlexing(const std::vector<id<ASLayoutEleme
|
|||||||
@param sizeRange the range of allowable sizes for the stack layout component
|
@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.
|
@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<ASStackLayoutSpecItem> &items,
|
||||||
const ASStackLayoutSpecStyle &style,
|
const ASStackLayoutSpecStyle &style,
|
||||||
const ASSizeRange &sizeRange,
|
const ASSizeRange &sizeRange,
|
||||||
const CGSize size,
|
const CGSize size,
|
||||||
const BOOL useOptimizedFlexing)
|
const BOOL useOptimizedFlexing)
|
||||||
{
|
{
|
||||||
const CGFloat violation = computeViolation(computeStackDimensionSum(items, style), style, sizeRange);
|
const CGFloat violation = computeViolation(computeStackDimensionSum(items, style), style, sizeRange);
|
||||||
std::function<CGFloat(const ASStackUnpositionedItem &)> flexFactor = flexFactorInViolationDirection(violation);
|
std::function<CGFloat(const ASStackLayoutSpecItem &)> 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.0, [&](CGFloat x, const ASStackUnpositionedItem &item) {
|
const CGFloat flexFactorSum = std::accumulate(items.begin(), items.end(), 0.0, [&](CGFloat x, const ASStackLayoutSpecItem &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 +362,17 @@ static void flexChildrenAlongStackDimension(std::vector<ASStackUnpositionedItem>
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::function<CGFloat(const ASStackUnpositionedItem &)> flexAdjustment = flexAdjustmentInViolationDirection(items,
|
std::function<CGFloat(const ASStackLayoutSpecItem &)> flexAdjustment = flexAdjustmentInViolationDirection(items,
|
||||||
style,
|
style,
|
||||||
violation,
|
violation,
|
||||||
flexFactorSum);
|
flexFactorSum);
|
||||||
|
|
||||||
// Compute any remaining violation to the first flexible child.
|
// Compute any remaining violation to the first flexible child.
|
||||||
const CGFloat remainingViolation = std::accumulate(items.begin(), items.end(), violation, [&](CGFloat x, const ASStackUnpositionedItem &item) {
|
const CGFloat remainingViolation = std::accumulate(items.begin(), items.end(), violation, [&](CGFloat x, const ASStackLayoutSpecItem &item) {
|
||||||
return x - flexAdjustment(item);
|
return x - flexAdjustment(item);
|
||||||
});
|
});
|
||||||
BOOL isFirstFlex = YES;
|
BOOL isFirstFlex = YES;
|
||||||
for (ASStackUnpositionedItem &item : items) {
|
for (ASStackLayoutSpecItem &item : items) {
|
||||||
const CGFloat currentFlexAdjustment = flexAdjustment(item);
|
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) {
|
||||||
@@ -396,7 +395,7 @@ static void flexChildrenAlongStackDimension(std::vector<ASStackUnpositionedItem>
|
|||||||
Performs the first unconstrained layout of the children, generating the unpositioned items that are then flexed and
|
Performs the first unconstrained layout of the children, generating the unpositioned items that are then flexed and
|
||||||
stretched.
|
stretched.
|
||||||
*/
|
*/
|
||||||
static std::vector<ASStackUnpositionedItem> layoutChildrenAlongUnconstrainedStackDimension(const std::vector<id<ASLayoutElement>> &children,
|
static std::vector<ASStackLayoutSpecItem> layoutChildrenAlongUnconstrainedStackDimension(const std::vector<ASStackLayoutSpecChild> &children,
|
||||||
const ASStackLayoutSpecStyle &style,
|
const ASStackLayoutSpecStyle &style,
|
||||||
const ASSizeRange &sizeRange,
|
const ASSizeRange &sizeRange,
|
||||||
const CGSize size,
|
const CGSize size,
|
||||||
@@ -404,9 +403,9 @@ static std::vector<ASStackUnpositionedItem> layoutChildrenAlongUnconstrainedStac
|
|||||||
{
|
{
|
||||||
const CGFloat minCrossDimension = crossDimension(style.direction, sizeRange.min);
|
const CGFloat minCrossDimension = crossDimension(style.direction, sizeRange.min);
|
||||||
const CGFloat maxCrossDimension = crossDimension(style.direction, sizeRange.max);
|
const CGFloat maxCrossDimension = crossDimension(style.direction, sizeRange.max);
|
||||||
return AS::map(children, [&](id<ASLayoutElement> child) -> ASStackUnpositionedItem {
|
return AS::map(children, [&](const ASStackLayoutSpecChild &child) -> ASStackLayoutSpecItem {
|
||||||
if (useOptimizedFlexing && isFlexibleInBothDirections(child)) {
|
if (useOptimizedFlexing && isFlexibleInBothDirections(child)) {
|
||||||
return { child, [ASLayout layoutWithLayoutElement:child size:{0, 0}] };
|
return {child, [ASLayout layoutWithLayoutElement:child.element size:{0, 0}]};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
child,
|
child,
|
||||||
@@ -422,7 +421,7 @@ static std::vector<ASStackUnpositionedItem> layoutChildrenAlongUnconstrainedStac
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ASStackUnpositionedLayout ASStackUnpositionedLayout::compute(const std::vector<id<ASLayoutElement>> &children,
|
ASStackUnpositionedLayout ASStackUnpositionedLayout::compute(const std::vector<ASStackLayoutSpecChild> &children,
|
||||||
const ASStackLayoutSpecStyle &style,
|
const ASStackLayoutSpecStyle &style,
|
||||||
const ASSizeRange &sizeRange)
|
const ASSizeRange &sizeRange)
|
||||||
{
|
{
|
||||||
@@ -439,7 +438,7 @@ ASStackUnpositionedLayout ASStackUnpositionedLayout::compute(const std::vector<i
|
|||||||
// We do a first pass of all the children, generating an unpositioned layout for each with an unbounded range along
|
// We do a first pass of all the children, generating an unpositioned layout for each with an unbounded range along
|
||||||
// the stack dimension. This allows us to compute the "intrinsic" size of each child and find the available violation
|
// the stack dimension. This allows us to compute the "intrinsic" size of each child and find the available violation
|
||||||
// which determines whether we must grow or shrink the flexible children.
|
// which determines whether we must grow or shrink the flexible children.
|
||||||
std::vector<ASStackUnpositionedItem> items = layoutChildrenAlongUnconstrainedStackDimension(children,
|
std::vector<ASStackLayoutSpecItem> items = layoutChildrenAlongUnconstrainedStackDimension(children,
|
||||||
style,
|
style,
|
||||||
sizeRange,
|
sizeRange,
|
||||||
size,
|
size,
|
||||||
@@ -449,5 +448,5 @@ ASStackUnpositionedLayout ASStackUnpositionedLayout::compute(const std::vector<i
|
|||||||
stretchChildrenAlongCrossDimension(items, style, size);
|
stretchChildrenAlongCrossDimension(items, style, size);
|
||||||
|
|
||||||
const CGFloat stackDimensionSum = computeStackDimensionSum(items, style);
|
const CGFloat stackDimensionSum = computeStackDimensionSum(items, style);
|
||||||
return {items, stackDimensionSum, computeViolation(stackDimensionSum, style, sizeRange)};
|
return {std::move(items), stackDimensionSum, computeViolation(stackDimensionSum, style, sizeRange)};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user