From afffd9cfb2209a1af38fc1f8bf8618204c8a5420 Mon Sep 17 00:00:00 2001 From: ricky cancro <@pinterest.com> Date: Wed, 12 Aug 2015 16:55:12 -0700 Subject: [PATCH 1/6] Add baseline support to ASStackLayoutSpec --- AsyncDisplayKit/ASDisplayNode.mm | 4 +++ AsyncDisplayKit/ASTextNode.mm | 3 +++ AsyncDisplayKit/Layout/ASLayoutSpec.mm | 2 ++ AsyncDisplayKit/Layout/ASLayoutable.h | 10 +++++++ AsyncDisplayKit/Layout/ASStackLayoutDefines.h | 4 +++ AsyncDisplayKit/Layout/ASStackLayoutSpec.h | 4 +++ .../Private/ASStackLayoutSpecUtilities.h | 4 +++ .../Private/ASStackPositionedLayout.mm | 27 +++++++++++++++++-- 8 files changed, 56 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 02cf7b3103..e860b1bfc6 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 e5d4ac2348..afcc014ef6 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; }); From d7564e18e57f3bb6102dc1cf48d0609c5bceda40 Mon Sep 17 00:00:00 2001 From: ricky cancro <@pinterest.com> Date: Sat, 15 Aug 2015 08:17:43 -0700 Subject: [PATCH 2/6] Added baseline spacing for vertical stack views. --- AsyncDisplayKit/ASTextNode.mm | 4 ++-- AsyncDisplayKit/Layout/ASStackLayoutSpec.h | 4 ++++ AsyncDisplayKit/Layout/ASStackLayoutSpec.mm | 7 +++++++ AsyncDisplayKit/Private/ASStackPositionedLayout.h | 2 ++ AsyncDisplayKit/Private/ASStackPositionedLayout.mm | 6 ++++-- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index afcc014ef6..b46620d565 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -356,8 +356,8 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) } }); - self.ascender = [[attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender]; - self.descender = [[attributedString attribute:NSFontAttributeName atIndex:attributedString.length - 1 effectiveRange:NULL] descender]; + self.ascender = round([[attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * [UIScreen mainScreen].scale)/[UIScreen mainScreen].scale; + self.descender = round([[attributedString attribute:NSFontAttributeName atIndex:attributedString.length - 1 effectiveRange:NULL] descender] * [UIScreen mainScreen].scale)/[UIScreen mainScreen].scale; } #pragma mark - Text Layout diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.h b/AsyncDisplayKit/Layout/ASStackLayoutSpec.h index 77267eb70d..77ce335891 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.h @@ -62,6 +62,10 @@ typedef struct { ASStackLayoutJustifyContent justifyContent; /** Orientation of children along cross axis */ ASStackLayoutAlignItems alignItems; + /** + If YES the vertical spacing between two views is measured from the last baseline of the + top view to the top of the bottom view*/ + BOOL baselineRelativeArrangement; } ASStackLayoutSpecStyle; /** diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm index 75b699c55b..20ed12590f 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm @@ -20,11 +20,13 @@ #import "ASStackLayoutSpecUtilities.h" #import "ASStackPositionedLayout.h" #import "ASStackUnpositionedLayout.h" +#import "ASThread.h" @implementation ASStackLayoutSpec { ASStackLayoutSpecStyle _style; std::vector> _children; + ASDN::RecursiveMutex _propertyLock; } + (instancetype)newWithStyle:(ASStackLayoutSpecStyle)style children:(NSArray *)children @@ -51,6 +53,11 @@ const auto positionedLayout = ASStackPositionedLayout::compute(unpositionedLayout, _style, constrainedSize); const CGSize finalSize = directionSize(_style.direction, unpositionedLayout.stackDimensionSum, positionedLayout.crossSize); NSArray *sublayouts = [NSArray arrayWithObjects:&positionedLayout.sublayouts[0] count:positionedLayout.sublayouts.size()]; + + ASDN::MutexLocker l(_propertyLock); + self.ascender = positionedLayout.ascender; + self.descender = positionedLayout.desender; + return [ASLayout newWithLayoutableObject:self size:ASSizeRangeClamp(constrainedSize, finalSize) sublayouts:sublayouts]; diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.h b/AsyncDisplayKit/Private/ASStackPositionedLayout.h index 211bda5b11..a4d2718ff3 100644 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.h +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.h @@ -17,6 +17,8 @@ struct ASStackPositionedLayout { const std::vector sublayouts; const CGFloat crossSize; + const CGFloat ascender; + const CGFloat desender; /** Given an unpositioned layout, computes the positions each child should be placed at. */ static ASStackPositionedLayout compute(const ASStackUnpositionedLayout &unpositionedLayout, diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm index 5d32c7b4eb..ccdeb0407f 100644 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm @@ -77,10 +77,12 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style } first = NO; 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); + + CGFloat spacingAfterBaseline = (style.direction == ASStackLayoutDirectionVertical && style.baselineRelativeArrangement) ? l.child.descender : 0;; + p = p + directionPoint(style.direction, stackDimension(style.direction, l.layout.size) + l.child.spacingAfter + spacingAfterBaseline, 0); return l.layout; }); - return {stackedChildren, crossSize}; + return {stackedChildren, crossSize, maxBaseLine, maxBaseLine == 0 ? 0 : crossSize - maxBaseLine}; } ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnpositionedLayout &unpositionedLayout, From 11896904a2803689a1ee623445ad60fbdd64093a Mon Sep 17 00:00:00 2001 From: ricky cancro <@pinterest.com> Date: Sat, 15 Aug 2015 09:45:14 -0700 Subject: [PATCH 3/6] fixed vertical spacing bug and typo --- AsyncDisplayKit/Layout/ASStackLayoutSpec.mm | 2 +- AsyncDisplayKit/Private/ASStackPositionedLayout.h | 2 +- AsyncDisplayKit/Private/ASStackPositionedLayout.mm | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm index 20ed12590f..5e36099d17 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm @@ -56,7 +56,7 @@ ASDN::MutexLocker l(_propertyLock); self.ascender = positionedLayout.ascender; - self.descender = positionedLayout.desender; + self.descender = positionedLayout.descender; return [ASLayout newWithLayoutableObject:self size:ASSizeRangeClamp(constrainedSize, finalSize) diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.h b/AsyncDisplayKit/Private/ASStackPositionedLayout.h index a4d2718ff3..b8664a3855 100644 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.h +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.h @@ -18,7 +18,7 @@ struct ASStackPositionedLayout { const std::vector sublayouts; const CGFloat crossSize; const CGFloat ascender; - const CGFloat desender; + const CGFloat descender; /** Given an unpositioned layout, computes the positions each child should be placed at. */ static ASStackPositionedLayout compute(const ASStackUnpositionedLayout &unpositionedLayout, diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm index ccdeb0407f..4a78594cee 100644 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm @@ -66,7 +66,7 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style 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); + const CGFloat maxBaseline = baselineIt == unpositionedLayout.items.end() ? 0 : baselineForItem(style, *baselineIt); CGPoint p = directionPoint(style.direction, offset, 0); BOOL first = YES; @@ -76,13 +76,13 @@ 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, maxBaseLine)); + l.layout.position = p + directionPoint(style.direction, 0, crossOffset(style, l, crossSize, maxBaseline)); - CGFloat spacingAfterBaseline = (style.direction == ASStackLayoutDirectionVertical && style.baselineRelativeArrangement) ? l.child.descender : 0;; + CGFloat spacingAfterBaseline = (style.direction == ASStackLayoutDirectionVertical && style.baselineRelativeArrangement) ? l.child.descender : 0; p = p + directionPoint(style.direction, stackDimension(style.direction, l.layout.size) + l.child.spacingAfter + spacingAfterBaseline, 0); return l.layout; }); - return {stackedChildren, crossSize, maxBaseLine, maxBaseLine == 0 ? 0 : crossSize - maxBaseLine}; + return {stackedChildren, crossSize, maxBaseline, maxBaseline == 0 ? 0 : maxBaseline - crossSize}; } ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnpositionedLayout &unpositionedLayout, From 774d91653bcd0e16fcbc02b646d0fbb645aea086 Mon Sep 17 00:00:00 2001 From: ricky cancro <@pinterest.com> Date: Wed, 19 Aug 2015 16:59:23 -0700 Subject: [PATCH 4/6] flailing --- AsyncDisplayKit.xcodeproj/project.pbxproj | 6 ++ AsyncDisplayKit/ASDisplayNode.h | 4 +- AsyncDisplayKit/ASDisplayNode.mm | 9 +-- AsyncDisplayKit/ASTextNode.mm | 24 +++++++- AsyncDisplayKit/Layout/ASLayoutSpec.h | 4 +- AsyncDisplayKit/Layout/ASLayoutSpec.mm | 7 ++- AsyncDisplayKit/Layout/ASLayoutable.h | 47 --------------- AsyncDisplayKit/Layout/ASStackLayoutDefines.h | 49 ++++++++++++++-- AsyncDisplayKit/Layout/ASStackLayoutSpec.h | 44 +------------- AsyncDisplayKit/Layout/ASStackLayoutSpec.mm | 17 ++++-- AsyncDisplayKit/Layout/ASStackLayoutable.h | 57 +++++++++++++++++++ .../Private/ASStackLayoutSpecUtilities.h | 4 -- .../Private/ASStackPositionedLayout.h | 3 +- .../Private/ASStackPositionedLayout.mm | 15 ++--- .../Private/ASStackUnpositionedLayout.h | 4 +- .../Private/ASStackUnpositionedLayout.mm | 12 ++-- 16 files changed, 172 insertions(+), 134 deletions(-) create mode 100644 AsyncDisplayKit/Layout/ASStackLayoutable.h diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index aee3b1867a..7cff23c102 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -217,6 +217,8 @@ 509E68651B3AEDC5009B9150 /* CGRect+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */; }; 509E68661B3AEDD7009B9150 /* CGRect+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */; }; 6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9C49C3701B853961000B0DD5 /* ASStackLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9F06E5CD1B4CAF4200F015D8 /* ASCollectionViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */; }; AC21EC101B3D0BF600C8B19A /* ASStackLayoutDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC3C4A511A1139C100143C57 /* ASCollectionView.h in Headers */ = {isa = PBXBuildFile; fileRef = AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -556,6 +558,7 @@ 4640521E1A3F83C40061C0BA /* ASMultidimensionalArrayUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMultidimensionalArrayUtils.h; sourceTree = ""; }; 4640521F1A3F83C40061C0BA /* ASMultidimensionalArrayUtils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMultidimensionalArrayUtils.mm; sourceTree = ""; }; 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = ""; }; + 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutable.h; path = AsyncDisplayKit/Layout/ASStackLayoutable.h; sourceTree = ""; }; 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewTests.m; sourceTree = ""; }; AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutDefines.h; path = AsyncDisplayKit/Layout/ASStackLayoutDefines.h; sourceTree = ""; }; AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionView.h; sourceTree = ""; }; @@ -958,6 +961,7 @@ ACF6ED131B17843500DA7C62 /* ASOverlayLayoutSpec.mm */, ACF6ED141B17843500DA7C62 /* ASRatioLayoutSpec.h */, ACF6ED151B17843500DA7C62 /* ASRatioLayoutSpec.mm */, + 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */, AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */, ACF6ED161B17843500DA7C62 /* ASStackLayoutSpec.h */, ACF6ED171B17843500DA7C62 /* ASStackLayoutSpec.mm */, @@ -1004,6 +1008,7 @@ files = ( AC21EC101B3D0BF600C8B19A /* ASStackLayoutDefines.h in Headers */, AC47D9451B3BB41900AAEE9D /* ASRelativeSize.h in Headers */, + 9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */, ACF6ED511B17847A00DA7C62 /* ASStackUnpositionedLayout.h in Headers */, ACF6ED2D1B17843500DA7C62 /* ASRatioLayoutSpec.h in Headers */, ACF6ED261B17843500DA7C62 /* ASLayoutSpec.h in Headers */, @@ -1170,6 +1175,7 @@ B350620C1B010EFD0018CF92 /* ASTableViewProtocols.h in Headers */, B35062481B010EFD0018CF92 /* _AS-objc-internal.h in Headers */, B350623F1B010EFD0018CF92 /* _ASAsyncTransactionContainer.h in Headers */, + 9C49C3701B853961000B0DD5 /* ASStackLayoutable.h in Headers */, 34EFC7731B701D0700AD841F /* ASStaticLayoutSpec.h in Headers */, B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */, B35061F51B010EFD0018CF92 /* ASCollectionView.h in Headers */, diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 7fcfbd5999..974bbe1272 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -13,7 +13,7 @@ #import #import -#import +#import /** * UIView creation block. Used to create the backing view of a new display node. @@ -40,7 +40,7 @@ typedef CALayer *(^ASDisplayNodeLayerBlock)(); * */ -@interface ASDisplayNode : ASDealloc2MainObject +@interface ASDisplayNode : ASDealloc2MainObject /** @name Initializing a node object */ diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index e860b1bfc6..e14f256b5a 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -48,8 +48,6 @@ @synthesize flexBasis = _flexBasis; @synthesize alignSelf = _alignSelf; @synthesize preferredFrameSize = _preferredFrameSize; -@synthesize ascender = _ascender; -@synthesize descender = _descender; BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector) { @@ -159,8 +157,6 @@ void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block) _flexBasis = ASRelativeDimensionUnconstrained; _preferredFrameSize = CGSizeZero; - _ascender = 0; - _descender = 0; } - (id)init @@ -457,6 +453,11 @@ void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block) #pragma mark - +- (CGFloat)distanceToBaseline:(ASStackLayoutAlignItems)baselineAlignmentType +{ + return 0; +} + - (CGSize)measure:(CGSize)constrainedSize { return [self measureWithSizeRange:ASSizeRangeMake(CGSizeZero, constrainedSize)].size; diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index b46620d565..f15ab5f786 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -17,6 +17,7 @@ #import #import +#import "ASInternalHelpers.h" #import "ASTextNodeRenderer.h" #import "ASTextNodeShadower.h" @@ -105,6 +106,9 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) ASTextNodeShadower *_shadower; UILongPressGestureRecognizer *_longPressGestureRecognizer; + + CGFloat _topBaseline; + CGFloat _bottomBaseline; } #pragma mark - NSObject @@ -356,8 +360,24 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) } }); - self.ascender = round([[attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * [UIScreen mainScreen].scale)/[UIScreen mainScreen].scale; - self.descender = round([[attributedString attribute:NSFontAttributeName atIndex:attributedString.length - 1 effectiveRange:NULL] descender] * [UIScreen mainScreen].scale)/[UIScreen mainScreen].scale; + _topBaseline = round([[attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * ASScreenScale())/ASScreenScale(); + _bottomBaseline = round([[attributedString attribute:NSFontAttributeName atIndex:attributedString.length - 1 effectiveRange:NULL] descender] * ASScreenScale())/ASScreenScale(); +} + +#pragma mark - Baseline computation + +- (CGFloat)distanceToBaseline:(ASStackLayoutAlignItems)baselineAlignmentType +{ + switch (baselineAlignmentType) { + case ASStackLayoutAlignItemsLastBaseline: + return self.calculatedSize.height + _bottomBaseline; + + case ASStackLayoutAlignItemsFirstBaseline: + return _topBaseline; + + default: + return 0; + } } #pragma mark - Text Layout diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec.h b/AsyncDisplayKit/Layout/ASLayoutSpec.h index c2602824e7..e92bbf75cc 100644 --- a/AsyncDisplayKit/Layout/ASLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASLayoutSpec.h @@ -8,9 +8,9 @@ * */ -#import +#import /** A layout spec is an immutable object that describes a layout, loosely inspired by React. */ -@interface ASLayoutSpec : NSObject +@interface ASLayoutSpec : NSObject @end diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec.mm b/AsyncDisplayKit/Layout/ASLayoutSpec.mm index 377fbe144b..34f6206e1b 100644 --- a/AsyncDisplayKit/Layout/ASLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASLayoutSpec.mm @@ -24,8 +24,6 @@ @synthesize flexShrink = _flexShrink; @synthesize flexBasis = _flexBasis; @synthesize alignSelf = _alignSelf; -@synthesize ascender = _ascender; -@synthesize descender = _descender; + (instancetype)new { @@ -36,6 +34,11 @@ return spec; } +- (CGFloat)distanceToBaseline:(ASStackLayoutAlignItems)baselineAlignmentType +{ + return 0; +} + #pragma mark - Layout - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize diff --git a/AsyncDisplayKit/Layout/ASLayoutable.h b/AsyncDisplayKit/Layout/ASLayoutable.h index e6f7df7bdc..4ac01ee05f 100644 --- a/AsyncDisplayKit/Layout/ASLayoutable.h +++ b/AsyncDisplayKit/Layout/ASLayoutable.h @@ -20,53 +20,6 @@ */ @protocol ASLayoutable -/** - * @abstract Additional space to place before this object in the stacking direction. - * Used when attached to a stack layout. - */ -@property (nonatomic, readwrite) CGFloat spacingBefore; - -/** - * @abstract Additional space to place after this object in the stacking direction. - * Used when attached to a stack layout. - */ -@property (nonatomic, readwrite) CGFloat spacingAfter; - -/** - * @abstract If the sum of childrens' stack dimensions is less than the minimum size, should this object grow? - * Used when attached to a stack layout. - */ -@property (nonatomic, readwrite) BOOL flexGrow; - -/** - * @abstract If the sum of childrens' stack dimensions is greater than the maximum size, should this object shrink? - * Used when attached to a stack layout. - */ -@property (nonatomic, readwrite) BOOL flexShrink; - -/** - * @abstract Specifies the initial size in the stack dimension for this object. - * Default to ASRelativeDimensionUnconstrained. - * Used when attached to a stack layout. - */ -@property (nonatomic, readwrite) ASRelativeDimension flexBasis; - -/** - * @abstract Orientation of the object along cross axis, overriding alignItems - * Used when attached to a stack layout. - */ -@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 0e6a600ec3..39e51b65c8 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutDefines.h +++ b/AsyncDisplayKit/Layout/ASStackLayoutDefines.h @@ -8,6 +8,49 @@ * */ +/** The direction children are stacked in */ +typedef NS_ENUM(NSUInteger, ASStackLayoutDirection) { + /** Children are stacked vertically */ + ASStackLayoutDirectionVertical, + /** Children are stacked horizontally */ + ASStackLayoutDirectionHorizontal, +}; + +/** If no children are flexible, how should this spec justify its children in the available space? */ +typedef NS_ENUM(NSUInteger, ASStackLayoutJustifyContent) { + /** + On overflow, children overflow out of this spec's bounds on the right/bottom side. + On underflow, children are left/top-aligned within this spec's bounds. + */ + ASStackLayoutJustifyContentStart, + /** + On overflow, children are centered and overflow on both sides. + On underflow, children are centered within this spec's bounds in the stacking direction. + */ + ASStackLayoutJustifyContentCenter, + /** + On overflow, children overflow out of this spec's bounds on the left/top side. + On underflow, children are right/bottom-aligned within this spec's bounds. + */ + ASStackLayoutJustifyContentEnd, +}; + +/** Orientation of children along cross axis */ +typedef NS_ENUM(NSUInteger, ASStackLayoutAlignItems) { + /** Align children to start of cross axis */ + ASStackLayoutAlignItemsStart, + /** Align children with end of cross axis */ + ASStackLayoutAlignItemsEnd, + /** Center children on cross axis */ + ASStackLayoutAlignItemsCenter, + /** Expand children to fill cross axis */ + ASStackLayoutAlignItemsStretch, + /** Children align along the first baseline of the stack. Only available for horizontal stack nodes */ + ASStackLayoutAlignItemsFirstBaseline, + /** Children align along the last baseline of the stack. Only available for horizontal stack nodes */ + ASStackLayoutAlignItemsLastBaseline, +}; + /** Each child may override their parent stack's cross axis alignment. @see ASStackLayoutAlignItems @@ -23,8 +66,6 @@ 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, + + /** Note: All children in a stack must have the same baseline align type */ }; diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.h b/AsyncDisplayKit/Layout/ASStackLayoutSpec.h index 77ce335891..242d6e3122 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.h @@ -9,49 +9,7 @@ */ #import - -/** The direction children are stacked in */ -typedef NS_ENUM(NSUInteger, ASStackLayoutDirection) { - /** Children are stacked vertically */ - ASStackLayoutDirectionVertical, - /** Children are stacked horizontally */ - ASStackLayoutDirectionHorizontal, -}; - -/** If no children are flexible, how should this spec justify its children in the available space? */ -typedef NS_ENUM(NSUInteger, ASStackLayoutJustifyContent) { - /** - On overflow, children overflow out of this spec's bounds on the right/bottom side. - On underflow, children are left/top-aligned within this spec's bounds. - */ - ASStackLayoutJustifyContentStart, - /** - On overflow, children are centered and overflow on both sides. - On underflow, children are centered within this spec's bounds in the stacking direction. - */ - ASStackLayoutJustifyContentCenter, - /** - On overflow, children overflow out of this spec's bounds on the left/top side. - On underflow, children are right/bottom-aligned within this spec's bounds. - */ - ASStackLayoutJustifyContentEnd, -}; - -/** Orientation of children along cross axis */ -typedef NS_ENUM(NSUInteger, ASStackLayoutAlignItems) { - /** Align children to start of cross axis */ - ASStackLayoutAlignItemsStart, - /** Align children with end of cross axis */ - ASStackLayoutAlignItemsEnd, - /** Center children on cross axis */ - 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, -}; +#import typedef struct { /** Specifies the direction children are stacked in. */ diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm index 5e36099d17..90d2cc7fd8 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm @@ -25,8 +25,9 @@ @implementation ASStackLayoutSpec { ASStackLayoutSpecStyle _style; - std::vector> _children; + std::vector> _children; ASDN::RecursiveMutex _propertyLock; + CGFloat _distanceToBaseline; } + (instancetype)newWithStyle:(ASStackLayoutSpecStyle)style children:(NSArray *)children @@ -34,8 +35,10 @@ ASStackLayoutSpec *spec = [super new]; if (spec) { spec->_style = style; - spec->_children = std::vector>(); - for (id child in children) { + spec->_children = std::vector>(); + for (id child in children) { + ASDisplayNodeAssert([child conformsToProtocol:@protocol(ASStackLayoutable)], @"child must conform to ASStackLayoutable"); + spec->_children.push_back(child); } } @@ -55,12 +58,16 @@ NSArray *sublayouts = [NSArray arrayWithObjects:&positionedLayout.sublayouts[0] count:positionedLayout.sublayouts.size()]; ASDN::MutexLocker l(_propertyLock); - self.ascender = positionedLayout.ascender; - self.descender = positionedLayout.descender; + _distanceToBaseline = positionedLayout.distanceToBaseline; return [ASLayout newWithLayoutableObject:self size:ASSizeRangeClamp(constrainedSize, finalSize) sublayouts:sublayouts]; } +- (CGFloat)distanceToBaseline:(ASStackLayoutAlignItems)baselineAlignmentType +{ + return _distanceToBaseline; +} + @end diff --git a/AsyncDisplayKit/Layout/ASStackLayoutable.h b/AsyncDisplayKit/Layout/ASStackLayoutable.h new file mode 100644 index 0000000000..643bed0382 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASStackLayoutable.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +#import + +@protocol ASStackLayoutable + +/** + * @abstract Additional space to place before this object in the stacking direction. + * Used when attached to a stack layout. + */ +@property (nonatomic, readwrite) CGFloat spacingBefore; + +/** + * @abstract Additional space to place after this object in the stacking direction. + * Used when attached to a stack layout. + */ +@property (nonatomic, readwrite) CGFloat spacingAfter; + +/** + * @abstract If the sum of childrens' stack dimensions is less than the minimum size, should this object grow? + * Used when attached to a stack layout. + */ +@property (nonatomic, readwrite) BOOL flexGrow; + +/** + * @abstract If the sum of childrens' stack dimensions is greater than the maximum size, should this object shrink? + * Used when attached to a stack layout. + */ +@property (nonatomic, readwrite) BOOL flexShrink; + +/** + * @abstract Specifies the initial size in the stack dimension for this object. + * Default to ASRelativeDimensionUnconstrained. + * Used when attached to a stack layout. + */ +@property (nonatomic, readwrite) ASRelativeDimension flexBasis; + +/** + * @abstract Orientation of the object along cross axis, overriding alignItems + * Used when attached to a stack layout. + */ +@property (nonatomic, readwrite) ASStackLayoutAlignSelf alignSelf; + +/** + * @abstract Used for baseline alignment in stack spec. The distance from the top of the object to its baseline. + */ +- (CGFloat)distanceToBaseline:(ASStackLayoutAlignItems)baselineAlignmentType; + +@end diff --git a/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h b/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h index 8a6f7f5126..4f5eff0aeb 100644 --- a/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h +++ b/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h @@ -55,10 +55,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; diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.h b/AsyncDisplayKit/Private/ASStackPositionedLayout.h index b8664a3855..ea9f56fd26 100644 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.h +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.h @@ -17,8 +17,7 @@ struct ASStackPositionedLayout { const std::vector sublayouts; const CGFloat crossSize; - const CGFloat ascender; - const CGFloat descender; + const CGFloat distanceToBaseline; /** Given an unpositioned layout, computes the positions each child should be placed at. */ static ASStackPositionedLayout compute(const ASStackUnpositionedLayout &unpositionedLayout, diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm index 4a78594cee..1e667abd73 100644 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm @@ -18,10 +18,8 @@ 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; + if (alignItems == ASStackLayoutAlignItemsFirstBaseline || alignItems == ASStackLayoutAlignItemsLastBaseline) { + return [item.child distanceToBaseline:alignItems]; } return 0; } @@ -39,9 +37,8 @@ static CGFloat crossOffset(const ASStackLayoutSpecStyle &style, case ASStackLayoutAlignItemsStart: case ASStackLayoutAlignItemsStretch: return 0; - case ASStackLayoutAlignItemsBaselineFirst: - return maxBaseline - l.child.ascender; - case ASStackLayoutAlignItemsBaselineLast: + case ASStackLayoutAlignItemsLastBaseline: + case ASStackLayoutAlignItemsFirstBaseline: return maxBaseline - baselineForItem(style, l); } } @@ -78,11 +75,11 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style first = NO; l.layout.position = p + directionPoint(style.direction, 0, crossOffset(style, l, crossSize, maxBaseline)); - CGFloat spacingAfterBaseline = (style.direction == ASStackLayoutDirectionVertical && style.baselineRelativeArrangement) ? l.child.descender : 0; + CGFloat spacingAfterBaseline = (style.direction == ASStackLayoutDirectionVertical && style.baselineRelativeArrangement) ? l.layout.size.height - [l.child distanceToBaseline:style.alignItems] : 0; p = p + directionPoint(style.direction, stackDimension(style.direction, l.layout.size) + l.child.spacingAfter + spacingAfterBaseline, 0); return l.layout; }); - return {stackedChildren, crossSize, maxBaseline, maxBaseline == 0 ? 0 : maxBaseline - crossSize}; + return {stackedChildren, crossSize, maxBaseline}; } ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnpositionedLayout &unpositionedLayout, diff --git a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.h b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.h index 45f648192e..44b7770ccf 100644 --- a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.h +++ b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.h @@ -15,7 +15,7 @@ struct ASStackUnpositionedItem { /** The original source child. */ - id child; + id child; /** The proposed layout. */ ASLayout *layout; }; @@ -30,7 +30,7 @@ struct ASStackUnpositionedLayout { const CGFloat violation; /** Given a set of children, computes the unpositioned layouts for those children. */ - static ASStackUnpositionedLayout compute(const std::vector> &children, + static ASStackUnpositionedLayout compute(const std::vector> &children, const ASStackLayoutSpecStyle &style, const ASSizeRange &sizeRange); }; diff --git a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm index 9eb83a5b0d..0c094bedfd 100644 --- a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm @@ -18,7 +18,7 @@ /** Sizes the child given the parameters specified, and returns the computed layout. */ -static ASLayout *crossChildLayout(const id child, +static ASLayout *crossChildLayout(const id child, const ASStackLayoutSpecStyle style, const CGFloat stackMin, const CGFloat stackMax, @@ -186,7 +186,7 @@ static std::function isFlexibleInViolatio } } -ASDISPLAYNODE_INLINE BOOL isFlexibleInBothDirections(id child) +ASDISPLAYNODE_INLINE BOOL isFlexibleInBothDirections(id child) { return child.flexGrow && child.flexShrink; } @@ -195,7 +195,7 @@ ASDISPLAYNODE_INLINE BOOL isFlexibleInBothDirections(id child) 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. */ -ASDISPLAYNODE_INLINE BOOL useOptimizedFlexing(const std::vector> &children, +ASDISPLAYNODE_INLINE BOOL useOptimizedFlexing(const std::vector> &children, const ASStackLayoutSpecStyle &style, const ASSizeRange &sizeRange) { @@ -283,7 +283,7 @@ static void flexChildrenAlongStackDimension(std::vector Performs the first unconstrained layout of the children, generating the unpositioned items that are then flexed and stretched. */ -static std::vector layoutChildrenAlongUnconstrainedStackDimension(const std::vector> &children, +static std::vector layoutChildrenAlongUnconstrainedStackDimension(const std::vector> &children, const ASStackLayoutSpecStyle &style, const ASSizeRange &sizeRange, const CGSize size, @@ -292,7 +292,7 @@ static std::vector layoutChildrenAlongUnconstrainedStac const CGFloat minCrossDimension = crossDimension(style.direction, sizeRange.min); const CGFloat maxCrossDimension = crossDimension(style.direction, sizeRange.max); - return AS::map(children, [&](id child) -> ASStackUnpositionedItem { + return AS::map(children, [&](id child) -> ASStackUnpositionedItem { const BOOL isUnconstrainedFlexBasis = ASRelativeDimensionEqualToRelativeDimension(ASRelativeDimensionUnconstrained, child.flexBasis); const CGFloat exactStackDimension = ASRelativeDimensionResolve(child.flexBasis, stackDimension(style.direction, size)); @@ -312,7 +312,7 @@ static std::vector layoutChildrenAlongUnconstrainedStac }); } -ASStackUnpositionedLayout ASStackUnpositionedLayout::compute(const std::vector> &children, +ASStackUnpositionedLayout ASStackUnpositionedLayout::compute(const std::vector> &children, const ASStackLayoutSpecStyle &style, const ASSizeRange &sizeRange) { From e7ffe67e3c5357b1391a40e0bf48465afb02fb2e Mon Sep 17 00:00:00 2001 From: rcancro Date: Wed, 19 Aug 2015 22:03:02 -0700 Subject: [PATCH 5/6] new stack layout spec for text --- AsyncDisplayKit.xcodeproj/project.pbxproj | 14 +- AsyncDisplayKit/ASTextNode.h | 2 +- AsyncDisplayKit/ASTextNode.mm | 26 +--- AsyncDisplayKit/Layout/ASLayoutSpec.mm | 5 - AsyncDisplayKit/Layout/ASStackLayoutSpec.mm | 10 -- AsyncDisplayKit/Layout/ASStackLayoutable.h | 10 +- .../Layout/ASStackTextLayoutSpec.h | 36 +++++ .../Layout/ASStackTextLayoutSpec.mm | 127 ++++++++++++++++++ .../Private/ASStackPositionedLayout.h | 1 - .../Private/ASStackPositionedLayout.mm | 10 +- 10 files changed, 194 insertions(+), 47 deletions(-) create mode 100644 AsyncDisplayKit/Layout/ASStackTextLayoutSpec.h create mode 100644 AsyncDisplayKit/Layout/ASStackTextLayoutSpec.mm diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 7cff23c102..29c10b3ca5 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -217,6 +217,10 @@ 509E68651B3AEDC5009B9150 /* CGRect+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */; }; 509E68661B3AEDD7009B9150 /* CGRect+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */; }; 6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9C3061061B857EC400D0530B /* ASStackTextLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C3061041B857EC400D0530B /* ASStackTextLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9C3061071B857EC400D0530B /* ASStackTextLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C3061041B857EC400D0530B /* ASStackTextLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9C3061081B857EC400D0530B /* ASStackTextLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C3061051B857EC400D0530B /* ASStackTextLayoutSpec.mm */; }; + 9C3061091B857EC400D0530B /* ASStackTextLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C3061051B857EC400D0530B /* ASStackTextLayoutSpec.mm */; }; 9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C49C3701B853961000B0DD5 /* ASStackLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9F06E5CD1B4CAF4200F015D8 /* ASCollectionViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */; }; @@ -447,7 +451,7 @@ 058D09D5195D050800B7D73C /* ASControlNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASControlNode.h; sourceTree = ""; }; 058D09D6195D050800B7D73C /* ASControlNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASControlNode.m; sourceTree = ""; }; 058D09D7195D050800B7D73C /* ASControlNode+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASControlNode+Subclasses.h"; sourceTree = ""; }; - 058D09D8195D050800B7D73C /* ASDisplayNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDisplayNode.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 058D09D8195D050800B7D73C /* ASDisplayNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDisplayNode.h; sourceTree = ""; }; 058D09D9195D050800B7D73C /* ASDisplayNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDisplayNode.mm; sourceTree = ""; }; 058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = "ASDisplayNode+Subclasses.h"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 058D09DB195D050800B7D73C /* ASDisplayNodeExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDisplayNodeExtras.h; sourceTree = ""; }; @@ -558,6 +562,8 @@ 4640521E1A3F83C40061C0BA /* ASMultidimensionalArrayUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMultidimensionalArrayUtils.h; sourceTree = ""; }; 4640521F1A3F83C40061C0BA /* ASMultidimensionalArrayUtils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMultidimensionalArrayUtils.mm; sourceTree = ""; }; 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = ""; }; + 9C3061041B857EC400D0530B /* ASStackTextLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackTextLayoutSpec.h; path = AsyncDisplayKit/Layout/ASStackTextLayoutSpec.h; sourceTree = ""; }; + 9C3061051B857EC400D0530B /* ASStackTextLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASStackTextLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASStackTextLayoutSpec.mm; sourceTree = ""; }; 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutable.h; path = AsyncDisplayKit/Layout/ASStackLayoutable.h; sourceTree = ""; }; 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewTests.m; sourceTree = ""; }; AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutDefines.h; path = AsyncDisplayKit/Layout/ASStackLayoutDefines.h; sourceTree = ""; }; @@ -967,6 +973,8 @@ ACF6ED171B17843500DA7C62 /* ASStackLayoutSpec.mm */, ACF6ED181B17843500DA7C62 /* ASStaticLayoutSpec.h */, ACF6ED191B17843500DA7C62 /* ASStaticLayoutSpec.mm */, + 9C3061041B857EC400D0530B /* ASStackTextLayoutSpec.h */, + 9C3061051B857EC400D0530B /* ASStackTextLayoutSpec.mm */, ); name = Layout; path = ..; @@ -1074,6 +1082,7 @@ 058D0A67195D05DC00B7D73C /* NSMutableAttributedString+TextKitAdditions.m in Headers */, 058D0A68195D05EC00B7D73C /* _ASAsyncTransaction.h in Headers */, 205F0E0F1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h in Headers */, + 9C3061061B857EC400D0530B /* ASStackTextLayoutSpec.h in Headers */, 058D0A69195D05EC00B7D73C /* _ASAsyncTransaction.m in Headers */, 058D0A6A195D05EC00B7D73C /* _ASAsyncTransactionContainer+Private.h in Headers */, 058D0A6B195D05EC00B7D73C /* _ASAsyncTransactionContainer.h in Headers */, @@ -1148,6 +1157,7 @@ B35061F31B010EFD0018CF92 /* ASCellNode.h in Headers */, 34EFC76C1B701CED00AD841F /* ASOverlayLayoutSpec.h in Headers */, B35062201B010EFD0018CF92 /* ASLayoutController.h in Headers */, + 9C3061071B857EC400D0530B /* ASStackTextLayoutSpec.h in Headers */, B35062571B010F070018CF92 /* ASAssert.h in Headers */, B35062411B010EFD0018CF92 /* _ASAsyncTransactionGroup.h in Headers */, B350623C1B010EFD0018CF92 /* _ASAsyncTransaction.h in Headers */, @@ -1451,6 +1461,7 @@ 0549634A1A1EA066000F8E56 /* ASBasicImageDownloader.mm in Sources */, 058D0A14195D050800B7D73C /* ASDisplayNode.mm in Sources */, 058D0A1B195D050800B7D73C /* ASMutableAttributedStringBuilder.m in Sources */, + 9C3061081B857EC400D0530B /* ASStackTextLayoutSpec.mm in Sources */, 058D0A2B195D050800B7D73C /* ASImageNode+CGExtras.m in Sources */, 058D0A24195D050800B7D73C /* _ASAsyncTransactionGroup.m in Sources */, 058D0A1C195D050800B7D73C /* ASTextNodeCoreTextAdditions.m in Sources */, @@ -1512,6 +1523,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9C3061091B857EC400D0530B /* ASStackTextLayoutSpec.mm in Sources */, 34EFC7641B701CC600AD841F /* ASCenterLayoutSpec.mm in Sources */, B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */, B35062401B010EFD0018CF92 /* _ASAsyncTransactionContainer.m in Sources */, diff --git a/AsyncDisplayKit/ASTextNode.h b/AsyncDisplayKit/ASTextNode.h index fc520c1232..681c2cb71e 100644 --- a/AsyncDisplayKit/ASTextNode.h +++ b/AsyncDisplayKit/ASTextNode.h @@ -30,7 +30,7 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) { @abstract Draws interactive rich text. @discussion Backed by TextKit. */ -@interface ASTextNode : ASControlNode +@interface ASTextNode : ASControlNode /** @abstract The attributed string to show. diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index f15ab5f786..8b3158a905 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -106,11 +106,11 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) ASTextNodeShadower *_shadower; UILongPressGestureRecognizer *_longPressGestureRecognizer; - - CGFloat _topBaseline; - CGFloat _bottomBaseline; } +@synthesize ascender = _ascender; +@synthesize descender = _descender; + #pragma mark - NSObject - (instancetype)init @@ -360,24 +360,8 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) } }); - _topBaseline = round([[attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * ASScreenScale())/ASScreenScale(); - _bottomBaseline = round([[attributedString attribute:NSFontAttributeName atIndex:attributedString.length - 1 effectiveRange:NULL] descender] * ASScreenScale())/ASScreenScale(); -} - -#pragma mark - Baseline computation - -- (CGFloat)distanceToBaseline:(ASStackLayoutAlignItems)baselineAlignmentType -{ - switch (baselineAlignmentType) { - case ASStackLayoutAlignItemsLastBaseline: - return self.calculatedSize.height + _bottomBaseline; - - case ASStackLayoutAlignItemsFirstBaseline: - return _topBaseline; - - default: - return 0; - } + _ascender = round([[attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * ASScreenScale())/ASScreenScale(); + _descender = round([[attributedString attribute:NSFontAttributeName atIndex:attributedString.length - 1 effectiveRange:NULL] descender] * ASScreenScale())/ASScreenScale(); } #pragma mark - Text Layout diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec.mm b/AsyncDisplayKit/Layout/ASLayoutSpec.mm index 34f6206e1b..8b3efd6f4d 100644 --- a/AsyncDisplayKit/Layout/ASLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASLayoutSpec.mm @@ -34,11 +34,6 @@ return spec; } -- (CGFloat)distanceToBaseline:(ASStackLayoutAlignItems)baselineAlignmentType -{ - return 0; -} - #pragma mark - Layout - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm index 90d2cc7fd8..c5c3660fc4 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm @@ -26,8 +26,6 @@ { ASStackLayoutSpecStyle _style; std::vector> _children; - ASDN::RecursiveMutex _propertyLock; - CGFloat _distanceToBaseline; } + (instancetype)newWithStyle:(ASStackLayoutSpecStyle)style children:(NSArray *)children @@ -57,17 +55,9 @@ const CGSize finalSize = directionSize(_style.direction, unpositionedLayout.stackDimensionSum, positionedLayout.crossSize); NSArray *sublayouts = [NSArray arrayWithObjects:&positionedLayout.sublayouts[0] count:positionedLayout.sublayouts.size()]; - ASDN::MutexLocker l(_propertyLock); - _distanceToBaseline = positionedLayout.distanceToBaseline; - return [ASLayout newWithLayoutableObject:self size:ASSizeRangeClamp(constrainedSize, finalSize) sublayouts:sublayouts]; } -- (CGFloat)distanceToBaseline:(ASStackLayoutAlignItems)baselineAlignmentType -{ - return _distanceToBaseline; -} - @end diff --git a/AsyncDisplayKit/Layout/ASStackLayoutable.h b/AsyncDisplayKit/Layout/ASStackLayoutable.h index 643bed0382..057ca8b0fd 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutable.h +++ b/AsyncDisplayKit/Layout/ASStackLayoutable.h @@ -49,9 +49,11 @@ */ @property (nonatomic, readwrite) ASStackLayoutAlignSelf alignSelf; -/** - * @abstract Used for baseline alignment in stack spec. The distance from the top of the object to its baseline. - */ -- (CGFloat)distanceToBaseline:(ASStackLayoutAlignItems)baselineAlignmentType; +@end + +@protocol ASStackTextLayoutable + +@property (nonatomic, readwrite) CGFloat ascender; +@property (nonatomic, readwrite) CGFloat descender; @end diff --git a/AsyncDisplayKit/Layout/ASStackTextLayoutSpec.h b/AsyncDisplayKit/Layout/ASStackTextLayoutSpec.h new file mode 100644 index 0000000000..ab5b80db19 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASStackTextLayoutSpec.h @@ -0,0 +1,36 @@ +// +// ASStackTextLayoutSpec.h +// AsyncDisplayKit +// +// Created by ricky cancro on 8/19/15. +// Copyright (c) 2015 Facebook. All rights reserved. +// + +#import + +/** Orientation of children along cross axis */ +typedef NS_ENUM(NSUInteger, ASStackTextLayoutBaselineAlignment) { + ASStackTextLayoutBaselineAlignmentNone, + /** Children align along the first baseline of the stack. Only available for horizontal stack nodes */ + ASStackTextLayoutBaselineAlignmentFirst, + /** Children align along the last baseline of the stack. Only available for horizontal stack nodes */ + ASStackTextLayoutBaselineAlignmentLast, +}; + + +typedef struct { + /** Specifies the direction children are stacked in. */ + ASStackLayoutSpecStyle stackLayoutStyle; + + ASStackTextLayoutBaselineAlignment baselineAlignment; +} ASStackTextLayoutSpecStyle; + +@interface ASStackTextLayoutSpec : ASLayoutSpec + +/** + @param style Specifies how children are laid out. + @param children ASLayoutable children to be positioned. + */ ++ (instancetype)newWithStyle:(ASStackTextLayoutSpecStyle)style children:(NSArray *)children; + +@end diff --git a/AsyncDisplayKit/Layout/ASStackTextLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStackTextLayoutSpec.mm new file mode 100644 index 0000000000..a15f4c6686 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASStackTextLayoutSpec.mm @@ -0,0 +1,127 @@ +// +// ASStackTextLayoutSpec.m +// AsyncDisplayKit +// +// Created by ricky cancro on 8/19/15. +// Copyright (c) 2015 Facebook. All rights reserved. +// + +#import "ASStackTextLayoutSpec.h" +#import "ASStackLayoutable.h" + +#import +#import + +#import "ASBaseDefines.h" +#import "ASInternalHelpers.h" + +#import "ASLayoutSpecUtilities.h" +#import "ASStackLayoutSpecUtilities.h" +#import "ASStackPositionedLayout.h" +#import "ASStackUnpositionedLayout.h" +#import "ASThread.h" + +static CGFloat baselineForItem(const ASStackTextLayoutSpecStyle &style, + const ASLayout *layout) { + + __weak id textChild = (id) layout.layoutableObject; + switch (style.baselineAlignment) { + case ASStackTextLayoutBaselineAlignmentNone: + return 0; + case ASStackTextLayoutBaselineAlignmentFirst: + return textChild.ascender; + case ASStackTextLayoutBaselineAlignmentLast: + return textChild.descender; + } + +} + +static CGFloat baselineOffset(const ASStackTextLayoutSpecStyle &style, + const ASLayout *l, + const CGFloat maxBaseline) +{ + switch (style.baselineAlignment) { + case ASStackTextLayoutBaselineAlignmentFirst: + case ASStackTextLayoutBaselineAlignmentLast: + return maxBaseline - baselineForItem(style, l); + case ASStackTextLayoutBaselineAlignmentNone: + return 0; + } +} + + +@implementation ASStackTextLayoutSpec +{ + ASStackTextLayoutSpecStyle _textStyle; + std::vector> _children; + std::vector> _stackChildren; + ASDN::RecursiveMutex _propertyLock; +} + ++ (instancetype)newWithStyle:(ASStackTextLayoutSpecStyle)style children:(NSArray *)children +{ + ASDisplayNodeAssert(style.stackLayoutStyle.direction == ASStackLayoutDirectionHorizontal && style.baselineAlignment != ASStackTextLayoutBaselineAlignmentNone, @"if you don't need baseline alignment, use ASStackLayoutSpec"); + + ASStackTextLayoutSpec *spec = [super new]; + if (spec) { + spec->_textStyle = style; + spec->_children = std::vector>(); + for (id child in children) { + ASDisplayNodeAssert([child conformsToProtocol:@protocol(ASStackTextLayoutable)], @"child must conform to ASStackLayoutable"); + + spec->_children.push_back(child); + spec->_stackChildren.push_back(child); + } + } + return spec; +} + ++ (instancetype)new +{ + ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER(); +} + +- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize +{ + ASStackLayoutSpecStyle stackStyle = _textStyle.stackLayoutStyle; + + + const auto unpositionedLayout = ASStackUnpositionedLayout::compute(_stackChildren, stackStyle, constrainedSize); + const auto positionedLayout = ASStackPositionedLayout::compute(unpositionedLayout, stackStyle, constrainedSize); + + // Alter the positioned layouts to include baselines + const auto baselineIt = std::max_element(positionedLayout.sublayouts.begin(), positionedLayout.sublayouts.end(), [&](const ASLayout *a, const ASLayout *b){ + return baselineForItem(_textStyle, a) < baselineForItem(_textStyle, b); + }); + const CGFloat maxBaseline = baselineIt == positionedLayout.sublayouts.end() ? 0 : baselineForItem(_textStyle, *baselineIt); + + CGPoint p = CGPointZero; + BOOL first = YES; + auto stackedChildren = AS::map(positionedLayout.sublayouts, [&](ASLayout *l) -> ASLayout *{ + __weak id textChild = (id) l.layoutableObject; + if (first) { + p = l.position; + } + first = NO; + + if (stackStyle.direction == ASStackLayoutDirectionHorizontal) { + l.position = p + CGPointMake(0, baselineOffset(_textStyle, l, maxBaseline)); + } + + CGFloat spacingAfterBaseline = (stackStyle.direction == ASStackLayoutDirectionVertical) ? textChild.descender : 0; + p = p + directionPoint(stackStyle.direction, stackDimension(stackStyle.direction, l.size) + [(id)l.layoutableObject spacingAfter] + spacingAfterBaseline, 0); + + return l; + }); + + const ASStackPositionedLayout alteredPositionedLayouts = {stackedChildren, positionedLayout.crossSize}; + const CGSize finalSize = directionSize(stackStyle.direction, unpositionedLayout.stackDimensionSum, alteredPositionedLayouts.crossSize); + + NSArray *sublayouts = [NSArray arrayWithObjects:&alteredPositionedLayouts.sublayouts[0] count:alteredPositionedLayouts.sublayouts.size()]; + + + return [ASLayout newWithLayoutableObject:self + size:ASSizeRangeClamp(constrainedSize, finalSize) + sublayouts:sublayouts]; +} +@end diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.h b/AsyncDisplayKit/Private/ASStackPositionedLayout.h index ea9f56fd26..211bda5b11 100644 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.h +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.h @@ -17,7 +17,6 @@ struct ASStackPositionedLayout { const std::vector sublayouts; const CGFloat crossSize; - const CGFloat distanceToBaseline; /** Given an unpositioned layout, computes the positions each child should be placed at. */ static ASStackPositionedLayout compute(const ASStackUnpositionedLayout &unpositionedLayout, diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm index 1e667abd73..b567be3ef7 100644 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm @@ -18,8 +18,10 @@ static CGFloat baselineForItem(const ASStackLayoutSpecStyle &style, const ASStackUnpositionedItem &item) { const ASStackLayoutAlignItems alignItems = alignment(item.child.alignSelf, style.alignItems); - if (alignItems == ASStackLayoutAlignItemsFirstBaseline || alignItems == ASStackLayoutAlignItemsLastBaseline) { - return [item.child distanceToBaseline:alignItems]; + if (alignItems == ASStackLayoutAlignItemsFirstBaseline) { + return item.child.layoutInsets.top; + } else if (alignItems == ASStackLayoutAlignItemsLastBaseline) { + return item.child.layoutInsets.bottom; } return 0; } @@ -75,11 +77,11 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style first = NO; l.layout.position = p + directionPoint(style.direction, 0, crossOffset(style, l, crossSize, maxBaseline)); - CGFloat spacingAfterBaseline = (style.direction == ASStackLayoutDirectionVertical && style.baselineRelativeArrangement) ? l.layout.size.height - [l.child distanceToBaseline:style.alignItems] : 0; + CGFloat spacingAfterBaseline = (style.direction == ASStackLayoutDirectionVertical && style.baselineRelativeArrangement) ? l.child.layoutInsets.bottom : 0; p = p + directionPoint(style.direction, stackDimension(style.direction, l.layout.size) + l.child.spacingAfter + spacingAfterBaseline, 0); return l.layout; }); - return {stackedChildren, crossSize, maxBaseline}; + return {stackedChildren, crossSize}; } ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnpositionedLayout &unpositionedLayout, From f404231b35c337109e6d3e9dfcc7615863b59764 Mon Sep 17 00:00:00 2001 From: ricky cancro <@pinterest.com> Date: Thu, 20 Aug 2015 10:38:54 -0700 Subject: [PATCH 6/6] moved baseline alignment to a layout spec. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 14 ++ AsyncDisplayKit/Layout/ASStackLayoutDefines.h | 8 +- .../Layout/ASStackTextLayoutSpec.h | 51 ++++--- .../Layout/ASStackTextLayoutSpec.mm | 140 ++++++------------ .../Private/ASStackPositionedLayout.mm | 30 +--- .../Private/ASStackTextPositionedLayout.h | 26 ++++ .../Private/ASStackTextPositionedLayout.mm | 118 +++++++++++++++ 7 files changed, 243 insertions(+), 144 deletions(-) create mode 100644 AsyncDisplayKit/Private/ASStackTextPositionedLayout.h create mode 100644 AsyncDisplayKit/Private/ASStackTextPositionedLayout.mm diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 29c10b3ca5..83427c715c 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -217,6 +217,10 @@ 509E68651B3AEDC5009B9150 /* CGRect+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */; }; 509E68661B3AEDD7009B9150 /* CGRect+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */; }; 6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9C204A641B86349B00313849 /* ASStackTextPositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C204A621B86349B00313849 /* ASStackTextPositionedLayout.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9C204A651B86349B00313849 /* ASStackTextPositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C204A621B86349B00313849 /* ASStackTextPositionedLayout.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9C204A661B86349B00313849 /* ASStackTextPositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C204A631B86349B00313849 /* ASStackTextPositionedLayout.mm */; }; + 9C204A671B86349B00313849 /* ASStackTextPositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C204A631B86349B00313849 /* ASStackTextPositionedLayout.mm */; }; 9C3061061B857EC400D0530B /* ASStackTextLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C3061041B857EC400D0530B /* ASStackTextLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C3061071B857EC400D0530B /* ASStackTextLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C3061041B857EC400D0530B /* ASStackTextLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C3061081B857EC400D0530B /* ASStackTextLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C3061051B857EC400D0530B /* ASStackTextLayoutSpec.mm */; }; @@ -562,6 +566,8 @@ 4640521E1A3F83C40061C0BA /* ASMultidimensionalArrayUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMultidimensionalArrayUtils.h; sourceTree = ""; }; 4640521F1A3F83C40061C0BA /* ASMultidimensionalArrayUtils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMultidimensionalArrayUtils.mm; sourceTree = ""; }; 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = ""; }; + 9C204A621B86349B00313849 /* ASStackTextPositionedLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackTextPositionedLayout.h; sourceTree = ""; }; + 9C204A631B86349B00313849 /* ASStackTextPositionedLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASStackTextPositionedLayout.mm; sourceTree = ""; }; 9C3061041B857EC400D0530B /* ASStackTextLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackTextLayoutSpec.h; path = AsyncDisplayKit/Layout/ASStackTextLayoutSpec.h; sourceTree = ""; }; 9C3061051B857EC400D0530B /* ASStackTextLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASStackTextLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASStackTextLayoutSpec.mm; sourceTree = ""; }; 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutable.h; path = AsyncDisplayKit/Layout/ASStackLayoutable.h; sourceTree = ""; }; @@ -696,7 +702,9 @@ 058D09AD195D04C000B7D73C /* Products */, FD40E2760492F0CAAEAD552D /* Pods */, ); + indentWidth = 2; sourceTree = ""; + tabWidth = 2; }; 058D09AD195D04C000B7D73C /* Products */ = { isa = PBXGroup; @@ -928,6 +936,8 @@ ACF6ED481B17847A00DA7C62 /* ASStackPositionedLayout.mm */, ACF6ED491B17847A00DA7C62 /* ASStackUnpositionedLayout.h */, ACF6ED4A1B17847A00DA7C62 /* ASStackUnpositionedLayout.mm */, + 9C204A621B86349B00313849 /* ASStackTextPositionedLayout.h */, + 9C204A631B86349B00313849 /* ASStackTextPositionedLayout.mm */, ); path = Private; sourceTree = ""; @@ -1056,6 +1066,7 @@ 0574D5E219C110940097DC25 /* ASTableViewProtocols.h in Headers */, 055F1A3C19ABD43F004DAFF1 /* ASCellNode.h in Headers */, 058D0A53195D05DC00B7D73C /* _ASDisplayLayer.h in Headers */, + 9C204A641B86349B00313849 /* ASStackTextPositionedLayout.h in Headers */, 058D0A54195D05DC00B7D73C /* _ASDisplayLayer.mm in Headers */, 058D0A55195D05DC00B7D73C /* _ASDisplayView.h in Headers */, 058D0A56195D05DC00B7D73C /* _ASDisplayView.mm in Headers */, @@ -1148,6 +1159,7 @@ B35061FA1B010EFD0018CF92 /* ASControlNode+Subclasses.h in Headers */, B35062371B010EFD0018CF92 /* ASTextNodeWordKerner.h in Headers */, B35062261B010EFD0018CF92 /* ASRangeController.h in Headers */, + 9C204A651B86349B00313849 /* ASStackTextPositionedLayout.h in Headers */, B35062111B010EFD0018CF92 /* _ASDisplayView.h in Headers */, B35061F81B010EFD0018CF92 /* ASControlNode.h in Headers */, 430E7C901B4C23F100697A4C /* ASIndexPath.h in Headers */, @@ -1423,6 +1435,7 @@ 058D0A26195D050800B7D73C /* _ASCoreAnimationExtras.mm in Sources */, 058D0A23195D050800B7D73C /* _ASAsyncTransactionContainer.m in Sources */, 058D0A1E195D050800B7D73C /* ASTextNodeShadower.m in Sources */, + 9C204A661B86349B00313849 /* ASStackTextPositionedLayout.mm in Sources */, ACF6ED2E1B17843500DA7C62 /* ASRatioLayoutSpec.mm in Sources */, 058D0A18195D050800B7D73C /* _ASDisplayLayer.mm in Sources */, ACF6ED321B17843500DA7C62 /* ASStaticLayoutSpec.mm in Sources */, @@ -1526,6 +1539,7 @@ 9C3061091B857EC400D0530B /* ASStackTextLayoutSpec.mm in Sources */, 34EFC7641B701CC600AD841F /* ASCenterLayoutSpec.mm in Sources */, B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */, + 9C204A671B86349B00313849 /* ASStackTextPositionedLayout.mm in Sources */, B35062401B010EFD0018CF92 /* _ASAsyncTransactionContainer.m in Sources */, B35062311B010EFD0018CF92 /* ASTextNodeRenderer.mm in Sources */, B35062051B010EFD0018CF92 /* ASMultiplexImageNode.mm in Sources */, diff --git a/AsyncDisplayKit/Layout/ASStackLayoutDefines.h b/AsyncDisplayKit/Layout/ASStackLayoutDefines.h index 39e51b65c8..d7737f7eab 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutDefines.h +++ b/AsyncDisplayKit/Layout/ASStackLayoutDefines.h @@ -44,11 +44,7 @@ typedef NS_ENUM(NSUInteger, ASStackLayoutAlignItems) { /** Center children on cross axis */ ASStackLayoutAlignItemsCenter, /** Expand children to fill cross axis */ - ASStackLayoutAlignItemsStretch, - /** Children align along the first baseline of the stack. Only available for horizontal stack nodes */ - ASStackLayoutAlignItemsFirstBaseline, - /** Children align along the last baseline of the stack. Only available for horizontal stack nodes */ - ASStackLayoutAlignItemsLastBaseline, + ASStackLayoutAlignItemsStretch }; /** @@ -66,6 +62,4 @@ typedef NS_ENUM(NSUInteger, ASStackLayoutAlignSelf) { ASStackLayoutAlignSelfCenter, /** Expand to fill cross axis */ ASStackLayoutAlignSelfStretch, - - /** Note: All children in a stack must have the same baseline align type */ }; diff --git a/AsyncDisplayKit/Layout/ASStackTextLayoutSpec.h b/AsyncDisplayKit/Layout/ASStackTextLayoutSpec.h index ab5b80db19..a7b94da03b 100644 --- a/AsyncDisplayKit/Layout/ASStackTextLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASStackTextLayoutSpec.h @@ -1,35 +1,48 @@ -// -// ASStackTextLayoutSpec.h -// AsyncDisplayKit -// -// Created by ricky cancro on 8/19/15. -// Copyright (c) 2015 Facebook. All rights reserved. -// +/* + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ #import -/** Orientation of children along cross axis */ typedef NS_ENUM(NSUInteger, ASStackTextLayoutBaselineAlignment) { - ASStackTextLayoutBaselineAlignmentNone, - /** Children align along the first baseline of the stack. Only available for horizontal stack nodes */ - ASStackTextLayoutBaselineAlignmentFirst, - /** Children align along the last baseline of the stack. Only available for horizontal stack nodes */ - ASStackTextLayoutBaselineAlignmentLast, + /** No baseline alignment. This is only valid for a vertical stack */ + ASStackTextLayoutBaselineAlignmentNone, + /** Align all children to the first baseline. This is only valid for a horizontal stack */ + ASStackTextLayoutBaselineAlignmentFirst, + /** Align all children to the last baseline. This is useful when a text node wraps and you want to align + to the bottom baseline. This is only valid for a horizontal stack */ + ASStackTextLayoutBaselineAlignmentLast, }; typedef struct { - /** Specifies the direction children are stacked in. */ - ASStackLayoutSpecStyle stackLayoutStyle; - - ASStackTextLayoutBaselineAlignment baselineAlignment; + /** Describes how the stack will be laid out */ + ASStackLayoutSpecStyle stackLayoutStyle; + + /** The type of baseline alignment */ + ASStackTextLayoutBaselineAlignment baselineAlignment; } ASStackTextLayoutSpecStyle; -@interface ASStackTextLayoutSpec : ASLayoutSpec + +/** + A specialized version of a stack layout that aligns its children on a baseline. This spec only works with + ASStackTextLayoutable children. + + If the spec is created with a horizontal direction, the children will be laid on a common baseline. + If the spec is created with a vertical direction, a child's vertical spacing will be measured from its + baseline instead of from the child's bounding box. +*/ +@interface ASStackTextLayoutSpec : ASLayoutSpec /** @param style Specifies how children are laid out. - @param children ASLayoutable children to be positioned. + @param children ASTextLayoutable children to be positioned. */ + (instancetype)newWithStyle:(ASStackTextLayoutSpecStyle)style children:(NSArray *)children; diff --git a/AsyncDisplayKit/Layout/ASStackTextLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStackTextLayoutSpec.mm index a15f4c6686..67c6942696 100644 --- a/AsyncDisplayKit/Layout/ASStackTextLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASStackTextLayoutSpec.mm @@ -1,10 +1,12 @@ -// -// ASStackTextLayoutSpec.m -// AsyncDisplayKit -// -// Created by ricky cancro on 8/19/15. -// Copyright (c) 2015 Facebook. All rights reserved. -// +/* + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ #import "ASStackTextLayoutSpec.h" #import "ASStackLayoutable.h" @@ -19,109 +21,61 @@ #import "ASStackLayoutSpecUtilities.h" #import "ASStackPositionedLayout.h" #import "ASStackUnpositionedLayout.h" +#import "ASStackTextPositionedLayout.h" #import "ASThread.h" -static CGFloat baselineForItem(const ASStackTextLayoutSpecStyle &style, - const ASLayout *layout) { - - __weak id textChild = (id) layout.layoutableObject; - switch (style.baselineAlignment) { - case ASStackTextLayoutBaselineAlignmentNone: - return 0; - case ASStackTextLayoutBaselineAlignmentFirst: - return textChild.ascender; - case ASStackTextLayoutBaselineAlignmentLast: - return textChild.descender; - } - -} - -static CGFloat baselineOffset(const ASStackTextLayoutSpecStyle &style, - const ASLayout *l, - const CGFloat maxBaseline) -{ - switch (style.baselineAlignment) { - case ASStackTextLayoutBaselineAlignmentFirst: - case ASStackTextLayoutBaselineAlignmentLast: - return maxBaseline - baselineForItem(style, l); - case ASStackTextLayoutBaselineAlignmentNone: - return 0; - } -} - @implementation ASStackTextLayoutSpec { - ASStackTextLayoutSpecStyle _textStyle; - std::vector> _children; - std::vector> _stackChildren; - ASDN::RecursiveMutex _propertyLock; + ASStackTextLayoutSpecStyle _textStyle; + std::vector> _stackChildren; + ASDN::RecursiveMutex _propertyLock; } +@synthesize ascender = _ascender; +@synthesize descender = _descender; + + (instancetype)newWithStyle:(ASStackTextLayoutSpecStyle)style children:(NSArray *)children { - ASDisplayNodeAssert(style.stackLayoutStyle.direction == ASStackLayoutDirectionHorizontal && style.baselineAlignment != ASStackTextLayoutBaselineAlignmentNone, @"if you don't need baseline alignment, use ASStackLayoutSpec"); - - ASStackTextLayoutSpec *spec = [super new]; - if (spec) { - spec->_textStyle = style; - spec->_children = std::vector>(); - for (id child in children) { - ASDisplayNodeAssert([child conformsToProtocol:@protocol(ASStackTextLayoutable)], @"child must conform to ASStackLayoutable"); - - spec->_children.push_back(child); - spec->_stackChildren.push_back(child); - } + ASDisplayNodeAssert((style.stackLayoutStyle.direction == ASStackLayoutDirectionHorizontal && style.baselineAlignment != ASStackTextLayoutBaselineAlignmentNone) || style.stackLayoutStyle.direction == ASStackLayoutDirectionVertical, @"baselineAlignment is set to none. If you don't need baseline alignment please use ASStackLayoutSpec"); + + ASStackTextLayoutSpec *spec = [super new]; + if (spec) { + spec->_textStyle = style; + spec->_stackChildren = std::vector>(); + for (id child in children) { + ASDisplayNodeAssert([child conformsToProtocol:@protocol(ASStackTextLayoutable)], @"child must conform to ASStackLayoutable"); + + spec->_stackChildren.push_back(child); } - return spec; + } + return spec; } + (instancetype)new { - ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER(); + ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER(); } - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize { - ASStackLayoutSpecStyle stackStyle = _textStyle.stackLayoutStyle; - - - const auto unpositionedLayout = ASStackUnpositionedLayout::compute(_stackChildren, stackStyle, constrainedSize); - const auto positionedLayout = ASStackPositionedLayout::compute(unpositionedLayout, stackStyle, constrainedSize); - - // Alter the positioned layouts to include baselines - const auto baselineIt = std::max_element(positionedLayout.sublayouts.begin(), positionedLayout.sublayouts.end(), [&](const ASLayout *a, const ASLayout *b){ - return baselineForItem(_textStyle, a) < baselineForItem(_textStyle, b); - }); - const CGFloat maxBaseline = baselineIt == positionedLayout.sublayouts.end() ? 0 : baselineForItem(_textStyle, *baselineIt); - - CGPoint p = CGPointZero; - BOOL first = YES; - auto stackedChildren = AS::map(positionedLayout.sublayouts, [&](ASLayout *l) -> ASLayout *{ - __weak id textChild = (id) l.layoutableObject; - if (first) { - p = l.position; - } - first = NO; - - if (stackStyle.direction == ASStackLayoutDirectionHorizontal) { - l.position = p + CGPointMake(0, baselineOffset(_textStyle, l, maxBaseline)); - } - - CGFloat spacingAfterBaseline = (stackStyle.direction == ASStackLayoutDirectionVertical) ? textChild.descender : 0; - p = p + directionPoint(stackStyle.direction, stackDimension(stackStyle.direction, l.size) + [(id)l.layoutableObject spacingAfter] + spacingAfterBaseline, 0); - - return l; - }); - - const ASStackPositionedLayout alteredPositionedLayouts = {stackedChildren, positionedLayout.crossSize}; - const CGSize finalSize = directionSize(stackStyle.direction, unpositionedLayout.stackDimensionSum, alteredPositionedLayouts.crossSize); - - NSArray *sublayouts = [NSArray arrayWithObjects:&alteredPositionedLayouts.sublayouts[0] count:alteredPositionedLayouts.sublayouts.size()]; - - - return [ASLayout newWithLayoutableObject:self - size:ASSizeRangeClamp(constrainedSize, finalSize) - sublayouts:sublayouts]; + ASStackLayoutSpecStyle stackStyle = _textStyle.stackLayoutStyle; + + const auto unpositionedLayout = ASStackUnpositionedLayout::compute(_stackChildren, stackStyle, constrainedSize); + const auto positionedLayout = ASStackPositionedLayout::compute(unpositionedLayout, stackStyle, constrainedSize); + const auto baselinePositionedLayout = ASStackTextPositionedLayout::compute(positionedLayout, _textStyle, constrainedSize); + + const CGSize finalSize = directionSize(stackStyle.direction, unpositionedLayout.stackDimensionSum, baselinePositionedLayout.crossSize); + + NSArray *sublayouts = [NSArray arrayWithObjects:&baselinePositionedLayout.sublayouts[0] count:baselinePositionedLayout.sublayouts.size()]; + + ASDN::MutexLocker l(_propertyLock); + _ascender = baselinePositionedLayout.ascender; + _descender = baselinePositionedLayout.descender; + + + return [ASLayout newWithLayoutableObject:self + size:ASSizeRangeClamp(constrainedSize, finalSize) + sublayouts:sublayouts]; } @end diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm index b567be3ef7..d106d0618b 100644 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm @@ -15,21 +15,9 @@ #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 == ASStackLayoutAlignItemsFirstBaseline) { - return item.child.layoutInsets.top; - } else if (alignItems == ASStackLayoutAlignItemsLastBaseline) { - return item.child.layoutInsets.bottom; - } - return 0; -} - static CGFloat crossOffset(const ASStackLayoutSpecStyle &style, const ASStackUnpositionedItem &l, - const CGFloat crossSize, - const CGFloat maxBaseline) + const CGFloat crossSize) { switch (alignment(l.child.alignSelf, style.alignItems)) { case ASStackLayoutAlignItemsEnd: @@ -39,9 +27,8 @@ static CGFloat crossOffset(const ASStackLayoutSpecStyle &style, case ASStackLayoutAlignItemsStart: case ASStackLayoutAlignItemsStretch: return 0; - case ASStackLayoutAlignItemsLastBaseline: - case ASStackLayoutAlignItemsFirstBaseline: - return maxBaseline - baselineForItem(style, l); + default: + return 0; } } @@ -61,12 +48,6 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style 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; auto stackedChildren = AS::map(unpositionedLayout.items, [&](const ASStackUnpositionedItem &l) -> ASLayout *{ @@ -75,10 +56,9 @@ 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, maxBaseline)); + l.layout.position = p + directionPoint(style.direction, 0, crossOffset(style, l, crossSize)); - CGFloat spacingAfterBaseline = (style.direction == ASStackLayoutDirectionVertical && style.baselineRelativeArrangement) ? l.child.layoutInsets.bottom : 0; - p = p + directionPoint(style.direction, stackDimension(style.direction, l.layout.size) + l.child.spacingAfter + spacingAfterBaseline, 0); + p = p + directionPoint(style.direction, stackDimension(style.direction, l.layout.size) + l.child.spacingAfter, 0); return l.layout; }); return {stackedChildren, crossSize}; diff --git a/AsyncDisplayKit/Private/ASStackTextPositionedLayout.h b/AsyncDisplayKit/Private/ASStackTextPositionedLayout.h new file mode 100644 index 0000000000..9794e9b807 --- /dev/null +++ b/AsyncDisplayKit/Private/ASStackTextPositionedLayout.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +#import "ASLayout.h" +#import "ASDimension.h" +#import "ASStackTextLayoutSpec.h" +#import "ASStackPositionedLayout.h" + +struct ASStackTextPositionedLayout { + const std::vector sublayouts; + const CGFloat crossSize; + const CGFloat ascender; + const CGFloat descender; + + /** Given a positioned layout, computes each child position using baseline alignment. */ + static ASStackTextPositionedLayout compute(const ASStackPositionedLayout &positionedLayout, + const ASStackTextLayoutSpecStyle &textStyle, + const ASSizeRange &constrainedSize); +}; diff --git a/AsyncDisplayKit/Private/ASStackTextPositionedLayout.mm b/AsyncDisplayKit/Private/ASStackTextPositionedLayout.mm new file mode 100644 index 0000000000..c694a379b0 --- /dev/null +++ b/AsyncDisplayKit/Private/ASStackTextPositionedLayout.mm @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +#import "ASStackTextPositionedLayout.h" + +#import "ASLayoutSpecUtilities.h" +#import "ASStackLayoutSpecUtilities.h" + +static CGFloat baselineForItem(const ASStackTextLayoutSpecStyle &style, + const ASLayout *layout) { + + __weak id textChild = (id) layout.layoutableObject; + switch (style.baselineAlignment) { + case ASStackTextLayoutBaselineAlignmentNone: + return 0; + case ASStackTextLayoutBaselineAlignmentFirst: + return textChild.ascender; + case ASStackTextLayoutBaselineAlignmentLast: + return layout.size.height + textChild.descender; + } + +} + +static CGFloat baselineOffset(const ASStackTextLayoutSpecStyle &style, + const ASLayout *l, + const CGFloat maxAscender, + const CGFloat maxBaseline) +{ + if (style.stackLayoutStyle.direction == ASStackLayoutDirectionHorizontal) { + __weak id textChild = (id)l.layoutableObject; + switch (style.baselineAlignment) { + case ASStackTextLayoutBaselineAlignmentFirst: + return maxAscender - textChild.ascender; + case ASStackTextLayoutBaselineAlignmentLast: + return maxBaseline + textChild.descender - textChild.ascender; + case ASStackTextLayoutBaselineAlignmentNone: + return 0; + } + } + return 0; +} + +static CGFloat maxDimensionForLayout(const ASLayout *l, + const ASStackLayoutSpecStyle &style) +{ + CGFloat maxDimension = crossDimension(style.direction, l.size); + style.direction == ASStackLayoutDirectionVertical ? maxDimension += l.position.x : maxDimension += l.position.y; + return maxDimension; +} + +ASStackTextPositionedLayout ASStackTextPositionedLayout::compute(const ASStackPositionedLayout &positionedLayout, + const ASStackTextLayoutSpecStyle &textStyle, + const ASSizeRange &constrainedSize) +{ + ASStackLayoutSpecStyle stackStyle = textStyle.stackLayoutStyle; + + + // Get the largest distance from the top of the stack to a baseline. This is the baseline we will align to. + const auto baselineIt = std::max_element(positionedLayout.sublayouts.begin(), positionedLayout.sublayouts.end(), [&](const ASLayout *a, const ASLayout *b){ + return baselineForItem(textStyle, a) < baselineForItem(textStyle, b); + }); + const CGFloat maxBaseline = baselineIt == positionedLayout.sublayouts.end() ? 0 : baselineForItem(textStyle, *baselineIt); + + // find the largest ascender for all children. This value will be used in offset computation as well as sent back to the ASStackTextLayoutSpec as its ascender. + const auto ascenderIt = std::max_element(positionedLayout.sublayouts.begin(), positionedLayout.sublayouts.end(), [&](const ASLayout *a, const ASLayout *b){ + return ((id)a.layoutableObject).ascender < ((id)b.layoutableObject).ascender; + }); + const CGFloat maxAscender = baselineIt == positionedLayout.sublayouts.end() ? 0 : ((id)(*ascenderIt).layoutableObject).ascender; + + CGPoint p = CGPointZero; + BOOL first = YES; + auto stackedChildren = AS::map(positionedLayout.sublayouts, [&](ASLayout *l) -> ASLayout *{ + __weak id textChild = (id) l.layoutableObject; + p = p + directionPoint(stackStyle.direction, textChild.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(stackStyle.direction, stackStyle.spacing, 0); + } + first = NO; + + // add the baseline offset. baselineOffset is only valid in the horizontal direction, so we always add to y + l.position = p + CGPointMake(0, baselineOffset(textStyle, l, maxAscender, maxBaseline)); + + // If we are a vertical stack, add the minDescender (it is negative) to the spacing after. This will alter the stack spacing to be on baselines instead of bounding boxes + CGFloat spacingAfterBaseline = (stackStyle.direction == ASStackLayoutDirectionVertical) ? textChild.descender : 0; + p = p + directionPoint(stackStyle.direction, stackDimension(stackStyle.direction, l.size) + textChild.spacingAfter + spacingAfterBaseline, 0); + + return l; + }); + + // The cross dimension is the max of the childrens' cross dimensions (clamped to our constraint below). + const auto it = std::max_element(stackedChildren.begin(), stackedChildren.end(), + [&](ASLayout *a, ASLayout *b) { + return maxDimensionForLayout(a, stackStyle) < maxDimensionForLayout(b, stackStyle); + }); + const auto largestChildCrossSize = it == stackedChildren.end() ? 0 : maxDimensionForLayout(*it, stackStyle); + const auto minCrossSize = crossDimension(stackStyle.direction, constrainedSize.min); + const auto maxCrossSize = crossDimension(stackStyle.direction, constrainedSize.max); + const CGFloat crossSize = MIN(MAX(minCrossSize, largestChildCrossSize), maxCrossSize); + + // find the child with the largest height. Use that child's descender as the descender to pass back to the ASStackTextLayoutSpec. + 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 CGFloat minDescender = descenderIt == stackedChildren.end() ? 0 : ((id)(*descenderIt).layoutableObject).descender; + + return {stackedChildren, crossSize, maxAscender, minDescender}; +}