From 89a216b90dff4ef08c641176beceef594ec6ba90 Mon Sep 17 00:00:00 2001 From: ricky cancro Date: Tue, 6 Oct 2015 21:41:39 -0700 Subject: [PATCH] Fixes to baseline stack alignment 1) Set the ascender/descender of an ASTextNode when the attributeString is set. Previously ascender/descender were only being computed in `setValuesFromLayoutable` and only when the attribute string was not nil. May make sense to remove the computation from `setValuesFromLayoutable` entirely. 2) Remove ability to allow different children of a stack spec to aling to different baselines. This wasn't working before and I'm not convinced it is possible to do properly/useful enough to invest the time. 3) Have all stack spec run `ASStackBaselinePositionedLayout::compute` to compute the stack's ascender and descender. Even if the stack isn't aligning its children to a baseline, the stack itself may be a child of another stack that IS aligning to a baseline. --- AsyncDisplayKit/ASTextNode.mm | 7 +++ AsyncDisplayKit/Layout/ASStackLayoutDefines.h | 8 +-- AsyncDisplayKit/Layout/ASStackLayoutSpec.mm | 16 +++-- .../ASStackBaselinePositionedLayout.mm | 63 ++++++++++--------- .../Private/ASStackLayoutSpecUtilities.h | 4 -- 5 files changed, 56 insertions(+), 42 deletions(-) diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 3a9509266c..a88920892e 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -17,6 +17,7 @@ #import #import +#import "ASInternalHelpers.h" #import "ASTextNodeRenderer.h" #import "ASTextNodeShadower.h" #import "ASEqualityHelpers.h" @@ -344,6 +345,12 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation self.isAccessibilityElement = YES; } }); + + if (attributedString.length > 0) { + CGFloat screenScale = ASScreenScale(); + self.ascender = round([[attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * screenScale)/screenScale; + self.descender = round([[attributedString attribute:NSFontAttributeName atIndex:attributedString.length - 1 effectiveRange:NULL] descender] * screenScale)/screenScale; + } } #pragma mark - Text Layout diff --git a/AsyncDisplayKit/Layout/ASStackLayoutDefines.h b/AsyncDisplayKit/Layout/ASStackLayoutDefines.h index 82d1aa0daf..0ca7bb3c49 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutDefines.h +++ b/AsyncDisplayKit/Layout/ASStackLayoutDefines.h @@ -45,9 +45,9 @@ typedef NS_ENUM(NSUInteger, ASStackLayoutAlignItems) { ASStackLayoutAlignItemsCenter, /** Expand children to fill cross axis */ ASStackLayoutAlignItemsStretch, - /** Children align to their first baseline. Only available for horizontal stack nodes */ + /** Children align to their first baseline. Only available for horizontal stack spec */ ASStackLayoutAlignItemsBaselineFirst, - /** Children align to their last baseline. Only available for horizontal stack nodes */ + /** Children align to their last baseline. Only available for horizontal stack spec */ ASStackLayoutAlignItemsBaselineLast, }; @@ -66,8 +66,4 @@ typedef NS_ENUM(NSUInteger, ASStackLayoutAlignSelf) { ASStackLayoutAlignSelfCenter, /** Expand to fill cross axis */ ASStackLayoutAlignSelfStretch, - /** Children align to their first baseline. Only available for horizontal stack nodes */ - ASStackLayoutAlignSelfBaselineFirst, - /** Children align to their last baseline. Only available for horizontal stack nodes */ - ASStackLayoutAlignSelfBaselineLast, }; diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm index 3f0abf42fa..a1d27b3c5e 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm @@ -101,7 +101,6 @@ std::vector> stackChildren = std::vector>(); for (id child in self.children) { stackChildren.push_back(child); - needsBaselinePass |= child.alignSelf == ASStackLayoutAlignSelfBaselineFirst || child.alignSelf == ASStackLayoutAlignSelfBaselineLast; } const auto unpositionedLayout = ASStackUnpositionedLayout::compute(stackChildren, style, constrainedSize); @@ -109,12 +108,21 @@ CGSize finalSize = CGSizeZero; NSArray *sublayouts = nil; - if (needsBaselinePass) { - const auto baselinePositionedLayout = ASStackBaselinePositionedLayout::compute(positionedLayout, style, constrainedSize); + + // regardless of whether or not this stack aligns to baseline, we should let ASStackBaselinePositionedLayout::compute find the max ascender + // and min descender in case this spec is a child in another spec that wants to align to a baseline. + const auto baselinePositionedLayout = ASStackBaselinePositionedLayout::compute(positionedLayout, style, constrainedSize); + if (self.direction == ASStackLayoutDirectionVertical) { + ASDN::MutexLocker l(_propertyLock); + self.ascender = [[self.children firstObject] ascender]; + self.descender = [[self.children lastObject] descender]; + } else { ASDN::MutexLocker l(_propertyLock); self.ascender = baselinePositionedLayout.ascender; self.descender = baselinePositionedLayout.descender; - + } + + if (needsBaselinePass) { finalSize = directionSize(style.direction, unpositionedLayout.stackDimensionSum, baselinePositionedLayout.crossSize); sublayouts = [NSArray arrayWithObjects:&baselinePositionedLayout.sublayouts[0] count:baselinePositionedLayout.sublayouts.size()]; } else { diff --git a/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.mm b/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.mm index 4e81dd60b5..57fd1b4c6d 100644 --- a/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackBaselinePositionedLayout.mm @@ -93,7 +93,7 @@ ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const A const auto ascenderIt = std::max_element(positionedLayout.sublayouts.begin(), positionedLayout.sublayouts.end(), [&](const ASLayout *a, const ASLayout *b){ return a.layoutableObject.ascender < b.layoutableObject.ascender; }); - const CGFloat maxAscender = baselineIt == positionedLayout.sublayouts.end() ? 0 : (*ascenderIt).layoutableObject.ascender; + const CGFloat maxAscender = ascenderIt == positionedLayout.sublayouts.end() ? 0 : (*ascenderIt).layoutableObject.ascender; /* Step 3: Take each child and update its layout position based on the baseline offset. @@ -103,33 +103,40 @@ ASStackBaselinePositionedLayout ASStackBaselinePositionedLayout::compute(const A spacing between the two nodes is from the baseline, not the bounding box. */ - CGPoint p = CGPointZero; - BOOL first = YES; - auto stackedChildren = AS::map(positionedLayout.sublayouts, [&](ASLayout *l) -> ASLayout *{ - __weak id child = l.layoutableObject; - p = p + directionPoint(style.direction, child.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); - } - 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)); - - // 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.descender; - } - p = p + directionPoint(style.direction, stackDimension(style.direction, l.size) + child.spacingAfter + spacingAfterBaseline, 0); - - return l; - }); + std::vector 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) { + CGPoint p = CGPointZero; + BOOL first = YES; + stackedChildren = AS::map(positionedLayout.sublayouts, [&](ASLayout *l) -> ASLayout *{ + __weak id child = l.layoutableObject; + p = p + directionPoint(style.direction, child.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); + } + 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)); + + // 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.descender; + } + p = p + directionPoint(style.direction, stackDimension(style.direction, l.size) + child.spacingAfter + spacingAfterBaseline, 0); + + return l; + }); + } else { + stackedChildren = positionedLayout.sublayouts; + } /* Step 4: Since we have been mucking with positions, there is a chance that our cross size has changed. Imagine a node with a font size of 40 diff --git a/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h b/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h index 2b073c106a..a61218cfe4 100644 --- a/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h +++ b/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h @@ -63,10 +63,6 @@ inline ASStackLayoutAlignItems alignment(ASStackLayoutAlignSelf childAlignment, return ASStackLayoutAlignItemsStart; case ASStackLayoutAlignSelfStretch: return ASStackLayoutAlignItemsStretch; - case ASStackLayoutAlignSelfBaselineFirst: - return ASStackLayoutAlignItemsBaselineFirst; - case ASStackLayoutAlignSelfBaselineLast: - return ASStackLayoutAlignItemsBaselineLast; case ASStackLayoutAlignSelfAuto: default: return stackAlignment;