diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index cb0b0d4526..c6b60eb710 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -44,6 +44,7 @@ Pod::Spec.new do |spec| end spec.subspec 'PINRemoteImage' do |pin| + # Note: The core.prefix_header_file includes setup of PIN_REMOTE_IMAGE, so the line below could be removed. pin.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) PIN_REMOTE_IMAGE=1' } pin.dependency 'PINRemoteImage/iOS', '= 3.0.0-beta.7' pin.dependency 'PINRemoteImage/PINCache' @@ -51,11 +52,17 @@ Pod::Spec.new do |spec| end spec.subspec 'IGListKit' do |igl| + # Note: The core.prefix_header_file includes setup of IG_LIST_KIT, so the line below could be removed. igl.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) IG_LIST_KIT=1' } igl.dependency 'IGListKit', '2.1.0' igl.dependency 'AsyncDisplayKit/Core' end + spec.subspec 'Yoga' do |yoga| + yoga.dependency 'Yoga', '1.0.2' + yoga.dependency 'AsyncDisplayKit/Core' + end + # Include optional PINRemoteImage module spec.default_subspec = 'PINRemoteImage' diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 6fdef80577..d4ced2799a 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -177,6 +177,7 @@ 8BBBAB8D1CEBAF1E00107FC6 /* ASDefaultPlaybackButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */; }; 8BDA5FC71CDBDF91007D13B2 /* ASVideoPlayerNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BDA5FC31CDBDDE1007D13B2 /* ASVideoPlayerNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8BDA5FC81CDBDF95007D13B2 /* ASVideoPlayerNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8BDA5FC41CDBDDE1007D13B2 /* ASVideoPlayerNode.mm */; }; + 90FC784F1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm in Sources */ = {isa = PBXBuildFile; fileRef = 90FC784E1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm */; }; 92DD2FE61BF4D05E0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */; }; 92DD2FE81BF4D0A80074C9DD /* ASMapNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -613,6 +614,7 @@ 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDefaultPlaybackButton.m; sourceTree = ""; }; 8BDA5FC31CDBDDE1007D13B2 /* ASVideoPlayerNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASVideoPlayerNode.h; sourceTree = ""; }; 8BDA5FC41CDBDDE1007D13B2 /* ASVideoPlayerNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASVideoPlayerNode.mm; sourceTree = ""; }; + 90FC784E1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASDisplayNode+Yoga.mm"; sourceTree = ""; }; 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMapNode.h; sourceTree = ""; }; 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMapNode.mm; sourceTree = ""; }; 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; @@ -893,6 +895,7 @@ 058D09D8195D050800B7D73C /* ASDisplayNode.h */, 058D09D9195D050800B7D73C /* ASDisplayNode.mm */, 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */, + 90FC784E1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm */, 683489271D70DE3400327501 /* ASDisplayNode+Deprecated.h */, 058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */, 058D09DB195D050800B7D73C /* ASDisplayNodeExtras.h */, @@ -1902,6 +1905,7 @@ 34EFC76F1B701CF700AD841F /* ASRatioLayoutSpec.mm in Sources */, 254C6B8B1BF94F8A003EC431 /* ASTextKitShadower.mm in Sources */, 254C6B851BF94F8A003EC431 /* ASTextKitAttributes.mm in Sources */, + 90FC784F1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm in Sources */, 509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */, B35062091B010EFD0018CF92 /* ASScrollNode.mm in Sources */, 8BDA5FC81CDBDF95007D13B2 /* ASVideoPlayerNode.mm in Sources */, diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/AsyncDisplayKit/ASDisplayNode+Beta.h index 6cc244191b..f465d033a5 100644 --- a/AsyncDisplayKit/ASDisplayNode+Beta.h +++ b/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -8,6 +8,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import #import #import #import @@ -20,15 +21,15 @@ void ASPerformBlockOnBackgroundThread(void (^block)()); // DISPATCH_QUEUE_PRIORI ASDISPLAYNODE_EXTERN_C_END #if ASEVENTLOG_ENABLE -#define ASDisplayNodeLogEvent(node, ...) [node.eventLog logEventWithBacktrace:(AS_SAVE_EVENT_BACKTRACES ? [NSThread callStackSymbols] : nil) format:__VA_ARGS__] + #define ASDisplayNodeLogEvent(node, ...) [node.eventLog logEventWithBacktrace:(AS_SAVE_EVENT_BACKTRACES ? [NSThread callStackSymbols] : nil) format:__VA_ARGS__] #else -#define ASDisplayNodeLogEvent(node, ...) + #define ASDisplayNodeLogEvent(node, ...) #endif #if ASEVENTLOG_ENABLE -#define ASDisplayNodeGetEventLog(node) node.eventLog + #define ASDisplayNodeGetEventLog(node) node.eventLog #else -#define ASDisplayNodeGetEventLog(node) nil + #define ASDisplayNodeGetEventLog(node) nil #endif /** @@ -147,4 +148,38 @@ typedef struct { @end +#pragma mark - Yoga Layout Support + +#if YOGA + +@interface ASDisplayNode (Yoga) + +@property (nonatomic, strong) NSArray *yogaChildren; + +- (void)addYogaChild:(ASDisplayNode *)child; +- (void)removeYogaChild:(ASDisplayNode *)child; + +// This method should not normally be called directly. +- (ASLayout *)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize; + +@end + +@interface ASLayoutElementStyle (Yoga) + +@property (nonatomic, assign, readwrite) ASStackLayoutDirection direction; +@property (nonatomic, assign, readwrite) CGFloat spacing; +@property (nonatomic, assign, readwrite) ASStackLayoutJustifyContent justifyContent; +@property (nonatomic, assign, readwrite) ASStackLayoutAlignItems alignItems; +@property (nonatomic, assign, readwrite) YGPositionType positionType; +@property (nonatomic, assign, readwrite) ASEdgeInsets position; +@property (nonatomic, assign, readwrite) ASEdgeInsets margin; +@property (nonatomic, assign, readwrite) ASEdgeInsets padding; +@property (nonatomic, assign, readwrite) ASEdgeInsets border; +@property (nonatomic, assign, readwrite) CGFloat aspectRatio; +@property (nonatomic, assign, readwrite) YGWrap flexWrap; + +@end + +#endif + NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASDisplayNode+Subclasses.h b/AsyncDisplayKit/ASDisplayNode+Subclasses.h index 2b831a3562..3d5e042494 100644 --- a/AsyncDisplayKit/ASDisplayNode+Subclasses.h +++ b/AsyncDisplayKit/ASDisplayNode+Subclasses.h @@ -232,8 +232,8 @@ NS_ASSUME_NONNULL_BEGIN /** @name Observing node state changes */ /** - * Declare methods as requiring super calls (this can't be required in the protocol). - * For descriptions, see definition. + * Declare methods as requiring super calls (this can't be required in the protocol). + * For descriptions, see definition. */ - (void)didEnterVisibleState ASDISPLAYNODE_REQUIRES_SUPER; diff --git a/AsyncDisplayKit/ASDisplayNode+Yoga.mm b/AsyncDisplayKit/ASDisplayNode+Yoga.mm new file mode 100644 index 0000000000..d798e49277 --- /dev/null +++ b/AsyncDisplayKit/ASDisplayNode+Yoga.mm @@ -0,0 +1,407 @@ +// +// ASDisplayNode+Yoga.mm +// AsyncDisplayKit +// +// Created by Scott Goodson on 2/8/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +#if YOGA /* YOGA */ + +#import +#import +#import +#import + +// If Yoga support becomes a supported feature, move this traversal to ASDisplayNodeExtras. +void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode * _Nullable node, void(^block)(ASDisplayNode *node)); +void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode * _Nullable node, void(^block)(ASDisplayNode *node)) +{ + if (node == nil) { + return; + } + block(node); + for (ASDisplayNode *child in [node yogaChildren]) { + ASDisplayNodePerformBlockOnEveryYogaChild(child, block); + } +} + +#pragma mark - Yoga Type Conversion Helpers + +YGAlign yogaAlignItems(ASStackLayoutAlignItems alignItems); +YGJustify yogaJustifyContent(ASStackLayoutJustifyContent justifyContent); +YGAlign yogaAlignSelf(ASStackLayoutAlignSelf alignSelf); +YGFlexDirection yogaFlexDirection(ASStackLayoutDirection direction); +float yogaFloatForCGFloat(CGFloat value); +float yogaDimensionToPoints(ASDimension dimension); +float yogaDimensionToPercent(ASDimension dimension); +ASDimension dimensionForEdgeWithEdgeInsets(YGEdge edge, ASEdgeInsets insets); +YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode, + float width, YGMeasureMode widthMode, + float height, YGMeasureMode heightMode); + +#define YGNODE_STYLE_SET_DIMENSION(yogaNode, property, dimension) \ + if (dimension.unit == ASDimensionUnitPoints) { \ + YGNodeStyleSet##property(yogaNode, yogaDimensionToPoints(dimension)); \ + } else if (dimension.unit == ASDimensionUnitFraction) { \ + YGNodeStyleSet##property##Percent(yogaNode, yogaDimensionToPercent(dimension)); \ + } else { \ + YGNodeStyleSet##property(yogaNode, YGUndefined); \ + }\ + +#define YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(yogaNode, property, dimension, edge) \ + if (dimension.unit == ASDimensionUnitPoints) { \ + YGNodeStyleSet##property(yogaNode, edge, yogaDimensionToPoints(dimension)); \ + } else if (dimension.unit == ASDimensionUnitFraction) { \ + YGNodeStyleSet##property##Percent(yogaNode, edge, yogaDimensionToPercent(dimension)); \ + } else { \ + YGNodeStyleSet##property(yogaNode, edge, YGUndefined); \ + } \ + +#define YGNODE_STYLE_SET_FLOAT_WITH_EDGE(yogaNode, property, dimension, edge) \ + if (dimension.unit == ASDimensionUnitPoints) { \ + YGNodeStyleSet##property(yogaNode, edge, yogaDimensionToPoints(dimension)); \ + } else if (dimension.unit == ASDimensionUnitFraction) { \ + ASDisplayNodeAssert(NO, @"Unexpected Fraction value in applying ##property## values to YGNode"); \ + } else { \ + YGNodeStyleSet##property(yogaNode, edge, YGUndefined); \ + } \ + +YGAlign yogaAlignItems(ASStackLayoutAlignItems alignItems) +{ + switch (alignItems) { + case ASStackLayoutAlignItemsStart: return YGAlignFlexStart; + case ASStackLayoutAlignItemsEnd: return YGAlignFlexEnd; + case ASStackLayoutAlignItemsCenter: return YGAlignCenter; + case ASStackLayoutAlignItemsStretch: return YGAlignStretch; + case ASStackLayoutAlignItemsBaselineFirst: return YGAlignBaseline; + // FIXME: WARNING, Yoga does not currently support last-baseline item alignment. + case ASStackLayoutAlignItemsBaselineLast: return YGAlignBaseline; + } +} + +YGJustify yogaJustifyContent(ASStackLayoutJustifyContent justifyContent) +{ + switch (justifyContent) { + case ASStackLayoutJustifyContentStart: return YGJustifyFlexStart; + case ASStackLayoutJustifyContentCenter: return YGJustifyCenter; + case ASStackLayoutJustifyContentEnd: return YGJustifyFlexEnd; + case ASStackLayoutJustifyContentSpaceBetween: return YGJustifySpaceBetween; + case ASStackLayoutJustifyContentSpaceAround: return YGJustifySpaceAround; + } +} + +YGAlign yogaAlignSelf(ASStackLayoutAlignSelf alignSelf) +{ + switch (alignSelf) { + case ASStackLayoutAlignSelfStart: return YGAlignFlexStart; + case ASStackLayoutAlignSelfCenter: return YGAlignCenter; + case ASStackLayoutAlignSelfEnd: return YGAlignFlexEnd; + case ASStackLayoutAlignSelfStretch: return YGAlignStretch; + case ASStackLayoutAlignSelfAuto: return YGAlignAuto; + } +} + +YGFlexDirection yogaFlexDirection(ASStackLayoutDirection direction) +{ + return direction == ASStackLayoutDirectionVertical ? YGFlexDirectionColumn : YGFlexDirectionRow; +} + +float yogaFloatForCGFloat(CGFloat value) +{ + if (value < CGFLOAT_MAX / 2) { + return value; + } else { + return YGUndefined; + } +} + +float yogaDimensionToPoints(ASDimension dimension) +{ + ASDisplayNodeCAssert(dimension.unit == ASDimensionUnitPoints, + @"Dimensions should not be type Fraction for this method: %f", dimension.value); + return yogaFloatForCGFloat(dimension.value); +} + +float yogaDimensionToPercent(ASDimension dimension) +{ + ASDisplayNodeCAssert(dimension.unit == ASDimensionUnitFraction, + @"Dimensions should not be type Points for this method: %f", dimension.value); + return 100.0 * yogaFloatForCGFloat(dimension.value); + +} + +ASDimension dimensionForEdgeWithEdgeInsets(YGEdge edge, ASEdgeInsets insets) +{ + switch (edge) { + case YGEdgeLeft: return insets.left; + case YGEdgeTop: return insets.top; + case YGEdgeRight: return insets.right; + case YGEdgeBottom: return insets.bottom; + default: ASDisplayNodeCAssert(NO, @"YGEdge other than ASEdgeInsets is not supported."); + return ASDimensionAuto; + } +} + +YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode, float width, YGMeasureMode widthMode, + float height, YGMeasureMode heightMode) +{ + id layoutElement = (__bridge id )YGNodeGetContext(yogaNode); + ASSizeRange sizeRange; + sizeRange.max = CGSizeMake(width, height); + sizeRange.min = sizeRange.max; + if (widthMode == YGMeasureModeAtMost) { + sizeRange.min.width = 0.0; + } + if (heightMode == YGMeasureModeAtMost) { + sizeRange.min.height = 0.0; + } + CGSize size = [[layoutElement layoutThatFits:sizeRange] size]; + return (YGSize){ .width = (float)size.width, .height = (float)size.height }; +} + +#pragma mark - ASDisplayNode+Yoga + +@interface ASDisplayNode (YogaInternal) +@property (nonatomic, weak) ASDisplayNode *yogaParent; +@property (nonatomic, assign) YGNodeRef yogaNode; +@end + +@implementation ASDisplayNode (Yoga) + +- (void)setYogaNode:(YGNodeRef)yogaNode +{ + _yogaNode = yogaNode; +} + +- (YGNodeRef)yogaNode +{ + if (_yogaNode == NULL) { + _yogaNode = YGNodeNew(); + } + return _yogaNode; +} + +- (void)setYogaParent:(ASDisplayNode *)yogaParent +{ + if (_yogaParent == yogaParent) { + return; + } + + YGNodeRef yogaNode = self.yogaNode; // Use property to assign Ref if needed. + YGNodeRef oldParentRef = YGNodeGetParent(yogaNode); + if (oldParentRef != NULL) { + YGNodeRemoveChild(oldParentRef, yogaNode); + } + + _yogaParent = yogaParent; + if (yogaParent) { + self.hierarchyState |= ASHierarchyStateYogaLayoutEnabled; + YGNodeRef newParentRef = yogaParent.yogaNode; + YGNodeInsertChild(newParentRef, yogaNode, YGNodeGetChildCount(newParentRef)); + } else { + self.hierarchyState &= ~ASHierarchyStateYogaLayoutEnabled; + } +} + +- (ASDisplayNode *)yogaParent +{ + return _yogaParent; +} + +- (void)setYogaChildren:(NSArray *)yogaChildren +{ + for (ASDisplayNode *child in _yogaChildren) { + // Make sure to un-associate the YGNodeRef tree before replacing _yogaChildren + // If this becomes a performance bottleneck, it can be optimized by not doing the NSArray removals here. + [self removeYogaChild:child]; + } + _yogaChildren = nil; + for (ASDisplayNode *child in yogaChildren) { + [self addYogaChild:child]; + } +} + +- (NSArray *)yogaChildren +{ + return _yogaChildren; +} + +- (void)addYogaChild:(ASDisplayNode *)child +{ + if (child == nil) { + return; + } + if (_yogaChildren == nil) { + _yogaChildren = [NSMutableArray array]; + } + + // Clean up state in case this child had another parent. + [self removeYogaChild:child]; + + // YGNodeRef insertion is done in setParent: + child.yogaParent = self; + [_yogaChildren addObject:child]; + + self.hierarchyState |= ASHierarchyStateYogaLayoutEnabled; +} + +- (void)removeYogaChild:(ASDisplayNode *)child +{ + if (child == nil) { + return; + } + // YGNodeRef removal is done in setParent: + child.yogaParent = nil; + [_yogaChildren removeObjectIdenticalTo:child]; + + if (_yogaChildren.count == 0 && self.yogaParent == nil) { + self.hierarchyState &= ~ASHierarchyStateYogaLayoutEnabled; + } +} + +- (ASLayout *)layoutTreeForYogaNode +{ + YGNodeRef yogaNode = self.yogaNode; // Use property to assign Ref if needed. + uint32_t childCount = YGNodeGetChildCount(yogaNode); + ASDisplayNodeAssert(childCount == self.yogaChildren.count, + @"Yoga tree should always be in sync with .yogaNodes array! %@", self.yogaChildren); + + NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:childCount]; + for (ASDisplayNode *subnode in self.yogaChildren) { + [sublayouts addObject:[subnode layoutTreeForYogaNode]]; + } + + CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode)); + CGPoint position = CGPointMake(YGNodeLayoutGetLeft(yogaNode), YGNodeLayoutGetTop(yogaNode)); + + ASDisplayNodeLogEvent(self, @"Yoga calculatedSize: %@", NSStringFromCGSize(size)); + + ASLayout *layout = [ASLayout layoutWithLayoutElement:self + size:size + position:position + sublayouts:sublayouts]; + return layout; +} + +- (void)setYogaMeasureFuncIfNeeded +{ + // Manual size calculation via calculateSizeThatFits: + // This will be used for ASTextNode, as well as any other leaf node that has no layout spec. + if ((self.methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) == NO + && self.layoutSpecBlock == NULL && self.yogaChildren.count == 0) { + YGNodeRef yogaNode = self.yogaNode; // Use property to assign Ref if needed. + YGNodeSetContext(yogaNode, (__bridge void *)self); + YGNodeSetMeasureFunc(yogaNode, &ASLayoutElementYogaMeasureFunc); + } +} + +- (ASLayout *)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize +{ + if (ASHierarchyStateIncludesYogaLayoutMeasuring(self.hierarchyState)) { + ASDisplayNodeAssert(NO, @"A Yoga layout is being performed by a parent; children must not perform their own until it is done! %@", [self displayNodeRecursiveDescription]); + return [ASLayout layoutWithLayoutElement:self size:CGSizeZero]; + } + + ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) { + node.hierarchyState |= ASHierarchyStateYogaLayoutMeasuring; + }); + + YGNodeRef rootYogaNode = self.yogaNode; + + // Apply the constrainedSize as a base, known frame of reference. + // If the root node also has style.*Size set, these will be overridden below. + YGNodeStyleSetMinWidth (rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.min.width)); + YGNodeStyleSetMinHeight(rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.min.height)); + + // TODO(appleguy); Is it sufficient to set only Width OR MaxWidth (+ same for height), or do we need all four? + YGNodeStyleSetMaxWidth (rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.max.width)); + YGNodeStyleSetMaxHeight(rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.max.height)); + YGNodeStyleSetWidth (rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.max.width)); + YGNodeStyleSetHeight (rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.max.height)); + + // TODO(appleguy): We need this on all containers, not just the root. Need to be able to check for "unset" + YGNodeStyleSetAlignItems(rootYogaNode, yogaAlignItems(self.style.alignItems)); + + ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) { + ASLayoutElementStyle *style = node.style; + YGNodeRef yogaNode = node.yogaNode; + + YGNodeStyleSetDirection (yogaNode, YGDirectionInherit); + + YGNodeStyleSetFlexWrap (yogaNode, style.flexWrap); + YGNodeStyleSetFlexGrow (yogaNode, style.flexGrow); + YGNodeStyleSetFlexShrink (yogaNode, style.flexShrink); + YGNODE_STYLE_SET_DIMENSION (yogaNode, FlexBasis, style.flexBasis); + + YGNodeStyleSetFlexDirection (yogaNode, yogaFlexDirection(style.direction)); + YGNodeStyleSetAlignSelf (yogaNode, yogaAlignSelf(style.alignSelf)); + YGNodeStyleSetAlignItems (yogaNode, yogaAlignItems(style.alignItems)); + YGNodeStyleSetJustifyContent(yogaNode, yogaJustifyContent(style.justifyContent)); + + YGNodeStyleSetPositionType (yogaNode, style.positionType); + + ASEdgeInsets position = style.position; + ASEdgeInsets margin = style.margin; + ASEdgeInsets padding = style.padding; + ASEdgeInsets border = style.border; + + YGEdge edge = YGEdgeLeft; + for (int i = 0; i < 4; i++) { + YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(yogaNode, Position, dimensionForEdgeWithEdgeInsets(edge, position), edge); + YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(yogaNode, Margin, dimensionForEdgeWithEdgeInsets(edge, margin), edge); + YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(yogaNode, Padding, dimensionForEdgeWithEdgeInsets(edge, padding), edge); + YGNODE_STYLE_SET_FLOAT_WITH_EDGE(yogaNode, Border, dimensionForEdgeWithEdgeInsets(edge, border), edge); + edge = (edge == YGEdgeLeft ? YGEdgeTop : (edge == YGEdgeTop ? YGEdgeRight : YGEdgeBottom)); + } + + CGFloat aspectRatio = style.aspectRatio; + if (aspectRatio > FLT_EPSILON && aspectRatio < CGFLOAT_MAX / 2.0) { + YGNodeStyleSetAspectRatio(yogaNode, aspectRatio); + } + + // For the root node, we use rootConstrainedSize above. For children, consult the style for their size. + if (node != self) { + YGNODE_STYLE_SET_DIMENSION(yogaNode, Width, style.width); + YGNODE_STYLE_SET_DIMENSION(yogaNode, Height, style.height); + + YGNODE_STYLE_SET_DIMENSION(yogaNode, MinWidth, style.minWidth); + YGNODE_STYLE_SET_DIMENSION(yogaNode, MinHeight, style.minHeight); + + YGNODE_STYLE_SET_DIMENSION(yogaNode, MaxWidth, style.maxWidth); + YGNODE_STYLE_SET_DIMENSION(yogaNode, MaxHeight, style.maxHeight); + } + + [node setYogaMeasureFuncIfNeeded]; + + /* TODO(appleguy): STYLE SETTER METHODS LEFT TO IMPLEMENT + void YGNodeStyleSetFlexDirection(YGNodeRef node, YGFlexDirection flexDirection); + void YGNodeStyleSetOverflow(YGNodeRef node, YGOverflow overflow); + void YGNodeStyleSetFlex(YGNodeRef node, float flex); + */ + }); + + // It is crucial to use yogaFloat... to convert CGFLOAT_MAX into YGUndefined here. + YGNodeCalculateLayout(rootYogaNode, + yogaFloatForCGFloat(rootConstrainedSize.max.width), + yogaFloatForCGFloat(rootConstrainedSize.max.height), + YGDirectionInherit); + +#if ASEVENTLOG_ENABLE + YGNodePrint(rootYogaNode, (YGPrintOptions)(YGPrintOptionsChildren | YGPrintOptionsStyle | YGPrintOptionsLayout)); +#endif + + ASLayout *yogaLayout = [self layoutTreeForYogaNode]; + + ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) { + node.hierarchyState &= ~ASHierarchyStateYogaLayoutMeasuring; + }); + + return yogaLayout; +} + +@end + +#endif /* YOGA */ diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 177288a7a5..b7f4dfbd18 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -427,6 +427,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) _subnodes = nil; +#if YOGA + if (_yogaNode != NULL) { + YGNodeFree(_yogaNode); + } +#endif + // TODO: Remove this? If supernode isn't already nil, this method isn't dealloc-safe anyway. [self __setSupernode:nil]; } @@ -1070,7 +1076,8 @@ ASLayoutElementFinalLayoutElementDefault restrictedToSize:(ASLayoutElementSize)size relativeToParentSize:(CGSize)parentSize { - const ASSizeRange resolvedRange = ASSizeRangeIntersect(constrainedSize, ASLayoutElementSizeResolve(self.style.size, parentSize)); + ASSizeRange styleAndParentSize = ASLayoutElementSizeResolve(self.style.size, parentSize); + const ASSizeRange resolvedRange = ASSizeRangeIntersect(constrainedSize, styleAndParentSize); return [self calculateLayoutThatFits:resolvedRange]; } @@ -1080,6 +1087,14 @@ ASLayoutElementFinalLayoutElementDefault ASDN::MutexLocker l(__instanceLock__); +#if YOGA /* YOGA */ + if (ASHierarchyStateIncludesYogaLayoutEnabled(_hierarchyState) == YES && + ASHierarchyStateIncludesYogaLayoutMeasuring(_hierarchyState) == NO) { + ASDN::MutexUnlocker ul(__instanceLock__); + return [self calculateLayoutFromYogaRoot:constrainedSize]; + } +#endif /* YOGA */ + // Manual size calculation via calculateSizeThatFits: if (((_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) || (_layoutSpecBlock != NULL)) == NO) { @@ -3189,11 +3204,9 @@ ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) { // Entering layout pending state } else { // Leaving layout pending state, reset related properties - { - ASDN::MutexLocker l(__instanceLock__); - _pendingTransitionID = ASLayoutElementContextInvalidTransitionID; - _pendingLayoutTransition = nil; - } + ASDN::MutexLocker l(__instanceLock__); + _pendingTransitionID = ASLayoutElementContextInvalidTransitionID; + _pendingLayoutTransition = nil; } } diff --git a/AsyncDisplayKit/AsyncDisplayKit-Prefix.pch b/AsyncDisplayKit/AsyncDisplayKit-Prefix.pch index 64ec2a6a4e..374bad0162 100644 --- a/AsyncDisplayKit/AsyncDisplayKit-Prefix.pch +++ b/AsyncDisplayKit/AsyncDisplayKit-Prefix.pch @@ -8,16 +8,17 @@ #import #endif +// Some build systems (Cocoapods, Buck, Bazel, etc) will define these flags manually if the functionality +// is needed by the app. Carthage in particular, or if a user forgets to set the build flag, benefit from +// checking if each flag is not defined and then setting it to whether or not the header is accessible. -// CocoaPods has a preproceessor macro for PIN_REMOTE_IMAGE, if already defined, okay #ifndef PIN_REMOTE_IMAGE - // For Carthage or manual builds, this will define PIN_REMOTE_IMAGE if the header is available in the // search path e.g. they've dragged in the framework (technically this will not be able to detect if // a user does not include the framework in the link binary with build step). -#define PIN_REMOTE_IMAGE __has_include() + #define PIN_REMOTE_IMAGE __has_include() #endif #ifndef IG_LIST_KIT -#define IG_LIST_KIT __has_include() + #define IG_LIST_KIT __has_include() #endif diff --git a/AsyncDisplayKit/Layout/ASDimension.h b/AsyncDisplayKit/Layout/ASDimension.h index dd402b7e64..10237f9db6 100644 --- a/AsyncDisplayKit/Layout/ASDimension.h +++ b/AsyncDisplayKit/Layout/ASDimension.h @@ -191,6 +191,15 @@ ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT NSString *NSStringFromASLayoutSize(AS NSStringFromASDimension(size.height)]; } +#pragma mark - ASEdgeInsets + +typedef struct { + ASDimension top; + ASDimension left; + ASDimension bottom; + ASDimension right; +} ASEdgeInsets; + #pragma mark - ASSizeRange /** diff --git a/AsyncDisplayKit/Layout/ASLayoutElement.h b/AsyncDisplayKit/Layout/ASLayoutElement.h index 51b8af3261..0e982a7b38 100644 --- a/AsyncDisplayKit/Layout/ASLayoutElement.h +++ b/AsyncDisplayKit/Layout/ASLayoutElement.h @@ -157,6 +157,8 @@ ASDISPLAYNODE_EXTERN_C_END #pragma mark - Deprecated +#define ASLayoutable ASLayoutElement + /** * @abstract Calculate a layout based on given size range. * @@ -259,7 +261,6 @@ extern NSString * const ASLayoutElementStyleLayoutPositionProperty; */ @property (nonatomic, assign, readwrite) ASDimension maxWidth; - #pragma mark - ASLayoutElementStyleSizeHelpers /** @@ -325,7 +326,6 @@ extern NSString * const ASLayoutElementStyleLayoutPositionProperty; @end - #pragma mark - ASLayoutElementStylability @protocol ASLayoutElementStylability diff --git a/AsyncDisplayKit/Layout/ASLayoutElement.mm b/AsyncDisplayKit/Layout/ASLayoutElement.mm index 3a0b500936..d2907b27b4 100644 --- a/AsyncDisplayKit/Layout/ASLayoutElement.mm +++ b/AsyncDisplayKit/Layout/ASLayoutElement.mm @@ -10,13 +10,14 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import "ASDisplayNode+FrameworkPrivate.h" +#import #import +#import +#import #import #import -#import - -#import extern void ASLayoutElementPerformBlockOnEveryElement(id element, void(^block)(id element)) { @@ -138,6 +139,20 @@ do {\ std::atomic _ascender; std::atomic _descender; std::atomic _layoutPosition; + +#if YOGA + std::atomic _direction; + std::atomic _spacing; + std::atomic _justifyContent; + std::atomic _alignItems; + std::atomic _positionType; + std::atomic _position; + std::atomic _margin; + std::atomic _padding; + std::atomic _border; + std::atomic _aspectRatio; + std::atomic _flexWrap; +#endif } @dynamic width, height, minWidth, maxWidth, minHeight, maxHeight; @@ -350,7 +365,6 @@ do {\ ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxHeightProperty); } - #pragma mark - ASStackLayoutElement - (void)setSpacingBefore:(CGFloat)spacingBefore @@ -573,6 +587,36 @@ do {\ return result; } +#pragma mark - Yoga Flexbox Properties + +#if YOGA + +- (ASStackLayoutDirection)direction { return _direction.load(); } +- (CGFloat)spacing { return _spacing.load(); } +- (ASStackLayoutJustifyContent)justifyContent { return _justifyContent.load(); } +- (ASStackLayoutAlignItems)alignItems { return _alignItems.load(); } +- (YGPositionType)positionType { return _positionType.load(); } +- (ASEdgeInsets)position { return _position.load(); } +- (ASEdgeInsets)margin { return _margin.load(); } +- (ASEdgeInsets)padding { return _padding.load(); } +- (ASEdgeInsets)border { return _border.load(); } +- (CGFloat)aspectRatio { return _aspectRatio.load(); } +- (YGWrap)flexWrap { return _flexWrap.load(); } + +- (void)setDirection:(ASStackLayoutDirection)direction { _direction.store(direction); } +- (void)setSpacing:(CGFloat)spacing { _spacing.store(spacing); } +- (void)setJustifyContent:(ASStackLayoutJustifyContent)justify { _justifyContent.store(justify); } +- (void)setAlignItems:(ASStackLayoutAlignItems)alignItems { _alignItems.store(alignItems); } +- (void)setPositionType:(YGPositionType)positionType { _positionType.store(positionType); } +- (void)setPosition:(ASEdgeInsets)position { _position.store(position); } +- (void)setMargin:(ASEdgeInsets)margin { _margin.store(margin); } +- (void)setPadding:(ASEdgeInsets)padding { _padding.store(padding); } +- (void)setBorder:(ASEdgeInsets)border { _border.store(border); } +- (void)setAspectRatio:(CGFloat)aspectRatio { _aspectRatio.store(aspectRatio); } +- (void)setFlexWrap:(YGWrap)flexWrap { _flexWrap.store(flexWrap); } + +#endif + #pragma mark Deprecated #pragma clang diagnostic push diff --git a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h index 922fa4e28f..7e0a47bbb0 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h +++ b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h @@ -49,7 +49,9 @@ typedef NS_OPTIONS(NSUInteger, ASHierarchyState) /** One of the supernodes of this node is performing a transition. Any layout calculated during this state should not be applied immediately, but pending until later. */ ASHierarchyStateLayoutPending = 1 << 3, - ASHierarchyStateVisualizeLayout = 1 << 4 + ASHierarchyStateYogaLayoutEnabled = 1 << 4, + ASHierarchyStateYogaLayoutMeasuring = 1 << 5, + ASHierarchyStateVisualizeLayout = 1 << 6 }; ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesLayoutPending(ASHierarchyState hierarchyState) @@ -59,7 +61,17 @@ ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesLayoutPending(ASHierarchyState ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesRangeManaged(ASHierarchyState hierarchyState) { - return ((hierarchyState & ASHierarchyStateRangeManaged) == ASHierarchyStateRangeManaged); + return ((hierarchyState & ASHierarchyStateRangeManaged) == ASHierarchyStateRangeManaged); +} + +ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesYogaLayoutMeasuring(ASHierarchyState hierarchyState) +{ + return ((hierarchyState & ASHierarchyStateYogaLayoutMeasuring) == ASHierarchyStateYogaLayoutMeasuring); +} + +ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesYogaLayoutEnabled(ASHierarchyState hierarchyState) +{ + return ((hierarchyState & ASHierarchyStateYogaLayoutEnabled) == ASHierarchyStateYogaLayoutEnabled); } ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesVisualizeLayout(ASHierarchyState hierarchyState) diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 3e8159488c..25ba0c1f90 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -15,14 +15,13 @@ #import #import -#import -#import +#import #import #import +#import +#import #import -#import - NS_ASSUME_NONNULL_BEGIN @protocol _ASDisplayLayerDelegate; @@ -107,8 +106,8 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo @protected ASDisplayNode * __weak _supernode; - NSMutableArray *_subnodes; - + NSMutableArray *_subnodes; + ASLayoutElementStyle *_style; ASPrimitiveTraitCollection _primitiveTraitCollection; @@ -179,6 +178,12 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo NSTimeInterval _layoutComputationTotalTime; NSInteger _layoutComputationNumberOfPasses; +#if YOGA + YGNodeRef _yogaNode; + ASDisplayNode *_yogaParent; + NSMutableArray *_yogaChildren; +#endif + #if TIME_DISPLAYNODE_OPS @public NSTimeInterval _debugTimeToCreateView; diff --git a/Base/ASAvailability.h b/Base/ASAvailability.h index 44d8c9fa18..5f0d0d559a 100644 --- a/Base/ASAvailability.h +++ b/Base/ASAvailability.h @@ -11,11 +11,11 @@ #import #ifndef kCFCoreFoundationVersionNumber_iOS_9_0 -#define kCFCoreFoundationVersionNumber_iOS_9_0 1240.10 + #define kCFCoreFoundationVersionNumber_iOS_9_0 1240.10 #endif #ifndef kCFCoreFoundationVersionNumber_iOS_10_0 -#define kCFCoreFoundationVersionNumber_iOS_10_0 1348.00 + #define kCFCoreFoundationVersionNumber_iOS_10_0 1348.00 #endif #define AS_AT_LEAST_IOS9 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_9_0) @@ -24,6 +24,20 @@ #define AS_TARGET_OS_OSX (!(TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH)) #define AS_TARGET_OS_IOS TARGET_OS_IPHONE +#ifndef YOGA_HEADER_PATH + #define YOGA_HEADER_PATH +#endif + +#ifndef YOGA + #define YOGA __has_include(YOGA_HEADER_PATH) +#endif + +// If Yoga is available, make it available anywhere we use ASAvailability. +// This reduces Yoga-specific code in other files. +#if YOGA + #import YOGA_HEADER_PATH +#endif + #if AS_TARGET_OS_OSX #define UIEdgeInsets NSEdgeInsets @@ -65,5 +79,4 @@ } @end - #endif diff --git a/examples/ASDKLayoutTransition/Podfile b/examples/ASDKLayoutTransition/Podfile index defaf55058..32b4c3336e 100644 --- a/examples/ASDKLayoutTransition/Podfile +++ b/examples/ASDKLayoutTransition/Podfile @@ -1,5 +1,6 @@ source 'https://github.com/CocoaPods/Specs.git' platform :ios, '8.0' target 'Sample' do - pod 'AsyncDisplayKit', :path => '../..' + pod 'AsyncDisplayKit', :path => '../..' + pod 'AsyncDisplayKit/Yoga', :path => '../..' end