[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:
Michael Schneider
2016-10-30 12:07:18 -07:00
committed by GitHub
parent ca203158da
commit 611894329a
7 changed files with 185 additions and 140 deletions

View File

@@ -13,31 +13,31 @@
#import "ASLayoutSpecUtilities.h"
#import "ASLayoutSpec+Subclasses.h"
#import "ASLayoutElement.h"
#import "ASLayoutElementStylePrivate.h"
static CGFloat baselineForItem(const ASStackLayoutSpecStyle &style,
const ASLayout *layout) {
__weak id<ASLayoutElement> child = layout.layoutElement;
const ASStackLayoutSpecItem &l)
{
switch (style.alignItems) {
case ASStackLayoutAlignItemsBaselineFirst:
return child.style.ascender;
return l.child.style.ascender;
case ASStackLayoutAlignItemsBaselineLast:
return layout.size.height + child.style.descender;
return l.layout.size.height + l.child.style.descender;
default:
return 0;
}
}
static CGFloat baselineOffset(const ASStackLayoutSpecStyle &style,
const ASLayout *l,
const ASStackLayoutSpecItem &l,
const CGFloat maxAscender,
const CGFloat maxBaseline)
{
if (style.direction == ASStackLayoutDirectionHorizontal) {
__weak id<ASLayoutElement> child = l.layoutElement;
switch (style.alignItems) {
case ASStackLayoutAlignItemsBaselineFirst:
return maxAscender - child.style.ascender;
return maxAscender - l.child.style.ascender;
case ASStackLayoutAlignItemsBaselineLast:
return maxBaseline - baselineForItem(style, l);
@@ -48,19 +48,28 @@ static CGFloat baselineOffset(const ASStackLayoutSpecStyle &style,
return 0;
}
static CGFloat maxDimensionForLayout(const ASLayout *l,
const ASStackLayoutSpecStyle &style)
static CGFloat maxDimensionForItem(const ASStackLayoutSpecItem &l,
const ASStackLayoutSpecStyle &style)
{
CGFloat maxDimension = crossDimension(style.direction, l.size);
style.direction == ASStackLayoutDirectionVertical ? maxDimension += l.position.x : maxDimension += l.position.y;
CGFloat maxDimension = crossDimension(style.direction, l.layout.size);
style.direction == ASStackLayoutDirectionVertical ? maxDimension += l.layout.position.x : maxDimension += l.layout.position.y;
return maxDimension;
}
BOOL ASStackBaselinePositionedLayout::needsBaselineAlignment(const ASStackLayoutSpecStyle &style)
{
return style.baselineRelativeArrangement ||
style.alignItems == ASStackLayoutAlignItemsBaselineFirst ||
style.alignItems == ASStackLayoutAlignItemsBaselineLast;
}
ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const ASStackPositionedLayout &positionedLayout,
const ASStackLayoutSpecStyle &style,
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:
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 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);
});
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.
@@ -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
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){
return a.layoutElement.style.ascender < b.layoutElement.style.ascender;
const auto ascenderIt = std::max_element(stackedChildren.begin(), stackedChildren.end(), [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b){
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.
@@ -102,39 +111,34 @@ ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const A
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
// 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;
BOOL first = YES;
stackedChildren = AS::map(positionedLayout.sublayouts, [&](ASLayout *l) -> ASLayout *{
__weak id<ASLayoutElement> child = l.layoutElement;
p = p + directionPoint(style.direction, child.style.spacingBefore, 0);
if (first) {
// if this is the first item use the previously computed start point
p = l.position;
} else {
// otherwise add the stack spacing
p = p + directionPoint(style.direction, style.spacing, 0);
}
for (const ASStackLayoutSpecItem &l : stackedChildren) {
ASLayoutElementStyle *layoutElementStyle = l.child.style;
p = p + directionPoint(style.direction, layoutElementStyle.spacingBefore, 0);
// if this is the first item use the previously computed start point otherwise add the stack spacing
p = first ? l.layout.position : p + directionPoint(style.direction, style.spacing, 0);
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.
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
// node from baselines and not bounding boxes.
CGFloat spacingAfterBaseline = 0;
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);
return l;
});
} else {
stackedChildren = positionedLayout.sublayouts;
p = p + directionPoint(style.direction, stackDimension(style.direction, l.layout.size) + layoutElementStyle.spacingAfter + spacingAfterBaseline, 0);
}
}
/*
@@ -147,10 +151,10 @@ ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const A
*/
const auto it = std::max_element(stackedChildren.begin(), stackedChildren.end(),
[&](ASLayout *a, ASLayout *b) {
return maxDimensionForLayout(a, style) < maxDimensionForLayout(b, style);
[&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b) {
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 maxCrossSize = crossDimension(style.direction, constrainedSize.max);
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
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){
return a.position.y + a.size.height < b.position.y + b.size.height;
const auto descenderIt = std::max_element(stackedChildren.begin(), stackedChildren.end(), [&](const ASStackLayoutSpecItem &a, const ASStackLayoutSpecItem &b){
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};
}