diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index c02c053e25..9c9a9a2625 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -48,6 +48,8 @@ @synthesize flexBasis = _flexBasis; @synthesize alignSelf = _alignSelf; @synthesize preferredFrameSize = _preferredFrameSize; +@synthesize ascender = _ascender; +@synthesize descender = _descender; BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector) { @@ -157,6 +159,8 @@ void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block) _flexBasis = ASRelativeDimensionUnconstrained; _preferredFrameSize = CGSizeZero; + _ascender = 0; + _descender = 0; } - (id)init diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 68f4f1fdab..1dcd003260 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -355,6 +355,9 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) self.isAccessibilityElement = YES; } }); + + self.ascender = [[attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender]; + self.descender = [[attributedString attribute:NSFontAttributeName atIndex:attributedString.length - 1 effectiveRange:NULL] descender]; } #pragma mark - Text Layout diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec.mm b/AsyncDisplayKit/Layout/ASLayoutSpec.mm index 8b3efd6f4d..377fbe144b 100644 --- a/AsyncDisplayKit/Layout/ASLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASLayoutSpec.mm @@ -24,6 +24,8 @@ @synthesize flexShrink = _flexShrink; @synthesize flexBasis = _flexBasis; @synthesize alignSelf = _alignSelf; +@synthesize ascender = _ascender; +@synthesize descender = _descender; + (instancetype)new { diff --git a/AsyncDisplayKit/Layout/ASLayoutable.h b/AsyncDisplayKit/Layout/ASLayoutable.h index 30bf7af5e8..e6f7df7bdc 100644 --- a/AsyncDisplayKit/Layout/ASLayoutable.h +++ b/AsyncDisplayKit/Layout/ASLayoutable.h @@ -57,6 +57,16 @@ */ @property (nonatomic, readwrite) ASStackLayoutAlignSelf alignSelf; +/** + * @abstract Used for baseline alignment. The distance from the top of the object to its baseline. + */ +@property (nonatomic, readwrite) CGFloat ascender; + +/** + * @abstract Used for baseline alignment. The distance from the baseline of the object to its bottom. + */ +@property (nonatomic, readwrite) CGFloat descender; + /** * @abstract Calculate a layout based on given size range. * diff --git a/AsyncDisplayKit/Layout/ASStackLayoutDefines.h b/AsyncDisplayKit/Layout/ASStackLayoutDefines.h index 6663ad2ae3..0e6a600ec3 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutDefines.h +++ b/AsyncDisplayKit/Layout/ASStackLayoutDefines.h @@ -23,4 +23,8 @@ 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.h b/AsyncDisplayKit/Layout/ASStackLayoutSpec.h index 29f20603c5..77267eb70d 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.h @@ -47,6 +47,10 @@ 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 */ + ASStackLayoutAlignItemsBaselineFirst, + /** Children align to their last baseline. Only available for horizontal stack nodes */ + ASStackLayoutAlignItemsBaselineLast, }; typedef struct { diff --git a/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h b/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h index 4f5eff0aeb..8a6f7f5126 100644 --- a/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h +++ b/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h @@ -55,6 +55,10 @@ inline ASStackLayoutAlignItems alignment(ASStackLayoutAlignSelf childAlignment, return ASStackLayoutAlignItemsStart; case ASStackLayoutAlignSelfStretch: return ASStackLayoutAlignItemsStretch; + case ASStackLayoutAlignSelfBaselineFirst: + return ASStackLayoutAlignItemsBaselineFirst; + case ASStackLayoutAlignSelfBaselineLast: + return ASStackLayoutAlignItemsBaselineLast; case ASStackLayoutAlignSelfAuto: default: return stackAlignment; diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm index 420258460f..5d32c7b4eb 100644 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm @@ -15,9 +15,21 @@ #import "ASStackLayoutSpecUtilities.h" #import "ASLayoutable.h" +static CGFloat baselineForItem(const ASStackLayoutSpecStyle &style, + const ASStackUnpositionedItem &item) { + const ASStackLayoutAlignItems alignItems = alignment(item.child.alignSelf, style.alignItems); + if (alignItems == ASStackLayoutAlignItemsBaselineFirst) { + return item.child.ascender; + } else if (alignItems == ASStackLayoutAlignItemsBaselineLast) { + return item.layout.size.height + item.child.descender; + } + return 0; +} + static CGFloat crossOffset(const ASStackLayoutSpecStyle &style, const ASStackUnpositionedItem &l, - const CGFloat crossSize) + const CGFloat crossSize, + const CGFloat maxBaseline) { switch (alignment(l.child.alignSelf, style.alignItems)) { case ASStackLayoutAlignItemsEnd: @@ -27,9 +39,14 @@ static CGFloat crossOffset(const ASStackLayoutSpecStyle &style, case ASStackLayoutAlignItemsStart: case ASStackLayoutAlignItemsStretch: return 0; + case ASStackLayoutAlignItemsBaselineFirst: + return maxBaseline - l.child.ascender; + case ASStackLayoutAlignItemsBaselineLast: + return maxBaseline - baselineForItem(style, l); } } + static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style, const CGFloat offset, const ASStackUnpositionedLayout &unpositionedLayout, @@ -44,6 +61,12 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &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); + + // Find the maximum height for the baseline + const auto baselineIt = std::max_element(unpositionedLayout.items.begin(), unpositionedLayout.items.end(), [&](const ASStackUnpositionedItem &a, const ASStackUnpositionedItem &b){ + return baselineForItem(style, a) < baselineForItem(style, b); + }); + const CGFloat maxBaseLine = baselineIt == unpositionedLayout.items.end() ? 0 : baselineForItem(style, *baselineIt); CGPoint p = directionPoint(style.direction, offset, 0); BOOL first = YES; @@ -53,7 +76,7 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style p = p + directionPoint(style.direction, style.spacing, 0); } first = NO; - l.layout.position = p + directionPoint(style.direction, 0, crossOffset(style, l, crossSize)); + l.layout.position = p + directionPoint(style.direction, 0, crossOffset(style, l, crossSize, maxBaseLine)); p = p + directionPoint(style.direction, stackDimension(style.direction, l.layout.size) + l.child.spacingAfter, 0); return l.layout; });