mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-10 16:29:55 +00:00
[Yoga] Implement ASYogaLayoutSpec, a simplified integration strategy for Yoga. (#270)
* [Yoga] Implement ASYogaLayoutSpec, an experimental alternative to full-tree integration. This approach allows us to avoid any ASDisplayNode.mm integration points. However, it is not yet proven to be possible to achieve correctness with this approach. The entry point (to start calculating), and the measurement function inputs, lack the full expressiveness of ASSizeRange; we need to make sure that workarounds like using style.minSize are successful in simulating the behavior of a full Yoga tree. * [Yoga] Fix file comments, move towards <ASLayoutElement> support. * [Yoga] Important fix for simplified, non-contiguous Yoga integration. * [Yoga] Complete implementation of manual memory management (__bridge_transfer, YGNodeFree)
This commit is contained in:
parent
7961aa919a
commit
b285ece35f
@ -180,6 +180,10 @@
|
||||
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 */; };
|
||||
9019FBBD1ED8061D00C45F72 /* ASYogaLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = 9019FBB91ED8061D00C45F72 /* ASYogaLayoutSpec.h */; };
|
||||
9019FBBE1ED8061D00C45F72 /* ASYogaLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9019FBBA1ED8061D00C45F72 /* ASYogaLayoutSpec.mm */; };
|
||||
9019FBBF1ED8061D00C45F72 /* ASYogaUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 9019FBBB1ED8061D00C45F72 /* ASYogaUtilities.h */; };
|
||||
9019FBC01ED8061D00C45F72 /* ASYogaUtilities.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9019FBBC1ED8061D00C45F72 /* ASYogaUtilities.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 */; };
|
||||
@ -676,6 +680,10 @@
|
||||
8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDefaultPlaybackButton.m; sourceTree = "<group>"; };
|
||||
8BDA5FC31CDBDDE1007D13B2 /* ASVideoPlayerNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASVideoPlayerNode.h; sourceTree = "<group>"; };
|
||||
8BDA5FC41CDBDDE1007D13B2 /* ASVideoPlayerNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASVideoPlayerNode.mm; sourceTree = "<group>"; };
|
||||
9019FBB91ED8061D00C45F72 /* ASYogaLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASYogaLayoutSpec.h; sourceTree = "<group>"; };
|
||||
9019FBBA1ED8061D00C45F72 /* ASYogaLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASYogaLayoutSpec.mm; sourceTree = "<group>"; };
|
||||
9019FBBB1ED8061D00C45F72 /* ASYogaUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASYogaUtilities.h; sourceTree = "<group>"; };
|
||||
9019FBBC1ED8061D00C45F72 /* ASYogaUtilities.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASYogaUtilities.mm; sourceTree = "<group>"; };
|
||||
90FC784E1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASDisplayNode+Yoga.mm"; sourceTree = "<group>"; };
|
||||
92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMapNode.h; sourceTree = "<group>"; };
|
||||
92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMapNode.mm; sourceTree = "<group>"; };
|
||||
@ -1481,6 +1489,10 @@
|
||||
9C49C36E1B853957000B0DD5 /* ASStackLayoutElement.h */,
|
||||
ACF6ED161B17843500DA7C62 /* ASStackLayoutSpec.h */,
|
||||
ACF6ED171B17843500DA7C62 /* ASStackLayoutSpec.mm */,
|
||||
9019FBB91ED8061D00C45F72 /* ASYogaLayoutSpec.h */,
|
||||
9019FBBA1ED8061D00C45F72 /* ASYogaLayoutSpec.mm */,
|
||||
9019FBBB1ED8061D00C45F72 /* ASYogaUtilities.h */,
|
||||
9019FBBC1ED8061D00C45F72 /* ASYogaUtilities.mm */,
|
||||
);
|
||||
path = Layout;
|
||||
sourceTree = "<group>";
|
||||
@ -1643,6 +1655,7 @@
|
||||
B35062571B010F070018CF92 /* ASAssert.h in Headers */,
|
||||
CCBBBF5D1EB161760069AA91 /* ASRangeManagingNode.h in Headers */,
|
||||
B35062581B010F070018CF92 /* ASAvailability.h in Headers */,
|
||||
9019FBBF1ED8061D00C45F72 /* ASYogaUtilities.h in Headers */,
|
||||
DE84918D1C8FFF2B003D89E9 /* ASRunLoopQueue.h in Headers */,
|
||||
CC0F88621E4281E200576FED /* ASSectionController.h in Headers */,
|
||||
A2763D7A1CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h in Headers */,
|
||||
@ -1751,6 +1764,7 @@
|
||||
CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */,
|
||||
83A7D95C1D44548100BF333E /* ASWeakMap.h in Headers */,
|
||||
E5711A2C1C840C81009619D4 /* ASCollectionElement.h in Headers */,
|
||||
9019FBBD1ED8061D00C45F72 /* ASYogaLayoutSpec.h in Headers */,
|
||||
6947B0BE1E36B4E30007C478 /* ASStackUnpositionedLayout.h in Headers */,
|
||||
CC4C2A771D88E3BF0039ACAB /* ASTraceEvent.h in Headers */,
|
||||
254C6B7B1BF94DF4003EC431 /* ASTextKitRenderer+Positioning.h in Headers */,
|
||||
@ -2086,6 +2100,7 @@
|
||||
AC026B721BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm in Sources */,
|
||||
B35062421B010EFD0018CF92 /* _ASAsyncTransactionGroup.m in Sources */,
|
||||
CCA282BD1E9EABDD0037E8B7 /* ASTipProvider.m in Sources */,
|
||||
9019FBC01ED8061D00C45F72 /* ASYogaUtilities.mm in Sources */,
|
||||
B350624A1B010EFD0018CF92 /* _ASCoreAnimationExtras.mm in Sources */,
|
||||
68EE0DC01C1B4ED300BA1B99 /* ASMainSerialQueue.mm in Sources */,
|
||||
B35062101B010EFD0018CF92 /* _ASDisplayLayer.mm in Sources */,
|
||||
@ -2167,6 +2182,7 @@
|
||||
6907C25A1DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m in Sources */,
|
||||
B35062051B010EFD0018CF92 /* ASMultiplexImageNode.mm in Sources */,
|
||||
B35062251B010EFD0018CF92 /* ASMutableAttributedStringBuilder.m in Sources */,
|
||||
9019FBBE1ED8061D00C45F72 /* ASYogaLayoutSpec.mm in Sources */,
|
||||
B35062071B010EFD0018CF92 /* ASNetworkImageNode.mm in Sources */,
|
||||
34EFC76D1B701CF100AD841F /* ASOverlayLayoutSpec.mm in Sources */,
|
||||
044285101BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.m in Sources */,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
## master
|
||||
|
||||
* Add your own contributions to the next release on the line below this with your name.
|
||||
- [Yoga] Implement ASYogaLayoutSpec, a simplified integration strategy for Yoga-powered layout calculation. [Scott Goodson](https://github.com/appleguy)
|
||||
- Fixed an issue where calls to setNeedsDisplay and setNeedsLayout would stop working on loaded nodes. [Garrett Moon](https://github.com/garrettmoon)
|
||||
- [ASTextKitFontSizeAdjuster] [Ricky Cancro] Replace use of NSAttributedString's boundingRectWithSize:options:context: with NSLayoutManager's boundingRectForGlyphRange:inTextContainer:
|
||||
- Add support for IGListKit post-removal-of-IGListSectionType, in preparation for IGListKit 3.0.0 release. [Adlai Holler](https://github.com/Adlai-Holler) [#49](https://github.com/TextureGroup/Texture/pull/49)
|
||||
|
||||
@ -171,15 +171,18 @@ extern void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode * _Nullable
|
||||
@interface ASDisplayNode (Yoga)
|
||||
|
||||
@property (nonatomic, strong, nullable) NSArray *yogaChildren;
|
||||
@property (nonatomic, strong, nullable) ASLayout *yogaCalculatedLayout;
|
||||
|
||||
- (void)addYogaChild:(ASDisplayNode *)child;
|
||||
- (void)removeYogaChild:(ASDisplayNode *)child;
|
||||
|
||||
- (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute;
|
||||
|
||||
#if YOGA_TREE_CONTIGUOUS
|
||||
@property (nonatomic, strong, nullable) ASLayout *yogaCalculatedLayout;
|
||||
// These methods should not normally be called directly.
|
||||
- (void)invalidateCalculatedYogaLayout;
|
||||
- (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize;
|
||||
- (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute;
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@ -19,174 +19,108 @@
|
||||
|
||||
#if YOGA /* YOGA */
|
||||
|
||||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import <AsyncDisplayKit/ASYogaLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASYogaUtilities.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
|
||||
#define YOGA_LAYOUT_LOGGING 0
|
||||
|
||||
extern 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 ASStackLayoutAlignItemsNotSet: return YGAlignAuto;
|
||||
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;
|
||||
case YGEdgeStart: return insets.start;
|
||||
case YGEdgeEnd: return insets.end;
|
||||
case YGEdgeHorizontal: return insets.horizontal;
|
||||
case YGEdgeVertical: return insets.vertical;
|
||||
case YGEdgeAll: return insets.all;
|
||||
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 <ASLayoutElement> layoutElement = (__bridge id <ASLayoutElement>)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
|
||||
|
||||
#if YOGA_TREE_CONTIGUOUS
|
||||
|
||||
@interface ASDisplayNode (YogaInternal)
|
||||
@property (nonatomic, weak) ASDisplayNode *yogaParent;
|
||||
@property (nonatomic, assign) YGNodeRef yogaNode;
|
||||
@end
|
||||
|
||||
#endif /* YOGA_TREE_CONTIGUOUS */
|
||||
|
||||
@implementation ASDisplayNode (Yoga)
|
||||
|
||||
- (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];
|
||||
[_yogaChildren addObject:child];
|
||||
|
||||
#if YOGA_TREE_CONTIGUOUS
|
||||
// YGNodeRef insertion is done in setParent:
|
||||
child.yogaParent = self;
|
||||
self.hierarchyState |= ASHierarchyStateYogaLayoutEnabled;
|
||||
#else
|
||||
// When using non-contiguous Yoga layout, each level in the node hierarchy independently uses an ASYogaLayoutSpec
|
||||
__weak ASDisplayNode *weakSelf = self;
|
||||
self.layoutSpecBlock = ^ASLayoutSpec * _Nonnull(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
|
||||
ASYogaLayoutSpec *spec = [[ASYogaLayoutSpec alloc] init];
|
||||
spec.rootNode = weakSelf;
|
||||
spec.children = weakSelf.yogaChildren;
|
||||
return spec;
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)removeYogaChild:(ASDisplayNode *)child
|
||||
{
|
||||
if (child == nil) {
|
||||
return;
|
||||
}
|
||||
[_yogaChildren removeObjectIdenticalTo:child];
|
||||
|
||||
#if YOGA_TREE_CONTIGUOUS
|
||||
// YGNodeRef removal is done in setParent:
|
||||
child.yogaParent = nil;
|
||||
if (_yogaChildren.count == 0 && self.yogaParent == nil) {
|
||||
self.hierarchyState &= ~ASHierarchyStateYogaLayoutEnabled;
|
||||
}
|
||||
#else
|
||||
if (_yogaChildren.count == 0) {
|
||||
self.layoutSpecBlock = nil;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute
|
||||
{
|
||||
if (AS_AT_LEAST_IOS9) {
|
||||
UIUserInterfaceLayoutDirection layoutDirection =
|
||||
[UIView userInterfaceLayoutDirectionForSemanticContentAttribute:attribute];
|
||||
self.style.direction = (layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight
|
||||
? YGDirectionLTR : YGDirectionRTL);
|
||||
}
|
||||
}
|
||||
|
||||
#if YOGA_TREE_CONTIGUOUS /* YOGA_TREE_CONTIGUOUS */
|
||||
|
||||
- (void)setYogaNode:(YGNodeRef)yogaNode
|
||||
{
|
||||
_yogaNode = yogaNode;
|
||||
@ -227,57 +161,6 @@ YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode, float width, YGMeasure
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setYogaCalculatedLayout:(ASLayout *)yogaCalculatedLayout
|
||||
{
|
||||
_yogaCalculatedLayout = yogaCalculatedLayout;
|
||||
@ -338,16 +221,6 @@ YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode, float width, YGMeasure
|
||||
self.yogaCalculatedLayout = nil;
|
||||
}
|
||||
|
||||
- (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute
|
||||
{
|
||||
if (AS_AT_LEAST_IOS9) {
|
||||
UIUserInterfaceLayoutDirection layoutDirection =
|
||||
[UIView userInterfaceLayoutDirectionForSemanticContentAttribute:attribute];
|
||||
self.style.direction = (layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight
|
||||
? YGDirectionLTR : YGDirectionRTL);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize
|
||||
{
|
||||
if (self.yogaParent) {
|
||||
@ -443,7 +316,7 @@ YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode, float width, YGMeasure
|
||||
node.hierarchyState &= ~ASHierarchyStateYogaLayoutMeasuring;
|
||||
});
|
||||
|
||||
#if YOGA_LAYOUT_LOGGING
|
||||
#if YOGA_LAYOUT_LOGGING /* YOGA_LAYOUT_LOGGING */
|
||||
// Concurrent layouts will interleave the NSLog messages unless we serialize.
|
||||
// Use @synchornize rather than trampolining to the main thread so the tree state isn't changed.
|
||||
@synchronized ([ASDisplayNode class]) {
|
||||
@ -458,9 +331,11 @@ YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode, float width, YGMeasure
|
||||
YGNodePrint(node.yogaNode, (YGPrintOptions)(YGPrintOptionsStyle | YGPrintOptionsLayout));
|
||||
});
|
||||
}
|
||||
#endif
|
||||
#endif /* YOGA_LAYOUT_LOGGING */
|
||||
}
|
||||
|
||||
#endif /* YOGA_TREE_CONTIGUOUS */
|
||||
|
||||
@end
|
||||
|
||||
#endif /* YOGA */
|
||||
|
||||
@ -429,7 +429,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
[self _scheduleIvarsForMainDeallocation];
|
||||
}
|
||||
|
||||
#if YOGA
|
||||
#if YOGA_TREE_CONTIGUOUS
|
||||
if (_yogaNode != NULL) {
|
||||
YGNodeFree(_yogaNode);
|
||||
}
|
||||
@ -898,7 +898,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
_pendingDisplayNodeLayout->invalidate();
|
||||
}
|
||||
|
||||
#if YOGA
|
||||
#if YOGA_TREE_CONTIGUOUS
|
||||
[self invalidateCalculatedYogaLayout];
|
||||
#endif
|
||||
}
|
||||
@ -972,7 +972,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
#if YOGA /* YOGA */
|
||||
#if YOGA_TREE_CONTIGUOUS /* YOGA */
|
||||
if (ASHierarchyStateIncludesYogaLayoutEnabled(_hierarchyState) == YES) {
|
||||
if (ASHierarchyStateIncludesYogaLayoutMeasuring(_hierarchyState) == NO && self.yogaCalculatedLayout == nil) {
|
||||
ASDN::MutexUnlocker ul(__instanceLock__);
|
||||
|
||||
@ -32,6 +32,7 @@
|
||||
|
||||
// If Yoga is available, make it available anywhere we use ASAvailability.
|
||||
// This reduces Yoga-specific code in other files.
|
||||
// NOTE: Yoga integration is experimental and not fully tested. Use with caution and test layouts carefully.
|
||||
#ifndef YOGA_HEADER_PATH
|
||||
#define YOGA_HEADER_PATH <Yoga/Yoga.h>
|
||||
#endif
|
||||
@ -40,6 +41,13 @@
|
||||
#define YOGA __has_include(YOGA_HEADER_PATH)
|
||||
#endif
|
||||
|
||||
// Contiguous Yoga layout attempts to build a connected tree of YGNodeRef objects, across multiple levels
|
||||
// in the ASDisplayNode tree (based on .yogaChildren). When disabled, ASYogaLayoutSpec is used, with a
|
||||
// disjoint Yoga tree for each level in the hierarchy. Currently, both modes are experimental.
|
||||
#ifndef YOGA_TREE_CONTIGUOUS
|
||||
#define YOGA_TREE_CONTIGUOUS 0 // To enable, set to YOGA, as the code depends on YOGA also being set.
|
||||
#endif
|
||||
|
||||
#define AS_PIN_REMOTE_IMAGE __has_include(<PINRemoteImage/PINRemoteImage.h>)
|
||||
#define AS_IG_LIST_KIT __has_include(<IGListKit/IGListKit.h>)
|
||||
|
||||
|
||||
26
Source/Layout/ASYogaLayoutSpec.h
Normal file
26
Source/Layout/ASYogaLayoutSpec.h
Normal file
@ -0,0 +1,26 @@
|
||||
//
|
||||
// ASYogaLayoutSpec.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
|
||||
#if YOGA /* YOGA */
|
||||
#if !YOGA_TREE_CONTIGUOUS /* !YOGA_TREE_CONTIGUOUS */
|
||||
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
#import <AsyncDisplayKit/ASLayoutSpec.h>
|
||||
|
||||
@interface ASYogaLayoutSpec : ASLayoutSpec
|
||||
@property (nonatomic, strong, nonnull) ASDisplayNode *rootNode;
|
||||
@end
|
||||
|
||||
#endif /* !YOGA_TREE_CONTIGUOUS */
|
||||
#endif /* YOGA */
|
||||
180
Source/Layout/ASYogaLayoutSpec.mm
Normal file
180
Source/Layout/ASYogaLayoutSpec.mm
Normal file
@ -0,0 +1,180 @@
|
||||
//
|
||||
// ASYogaLayoutSpec.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
|
||||
#if YOGA /* YOGA */
|
||||
#if !YOGA_TREE_CONTIGUOUS /* !YOGA_TREE_CONTIGUOUS */
|
||||
|
||||
#import <AsyncDisplayKit/ASYogaLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASYogaUtilities.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASLayoutSpec+Subclasses.h>
|
||||
|
||||
#define YOGA_LAYOUT_LOGGING 0
|
||||
|
||||
@implementation ASYogaLayoutSpec
|
||||
|
||||
- (ASLayout *)layoutForYogaNode:(YGNodeRef)yogaNode
|
||||
{
|
||||
BOOL isRootNode = (YGNodeGetParent(yogaNode) == NULL);
|
||||
uint32_t childCount = YGNodeGetChildCount(yogaNode);
|
||||
|
||||
NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:childCount];
|
||||
for (uint32_t i = 0; i < childCount; i++) {
|
||||
[sublayouts addObject:[self layoutForYogaNode:YGNodeGetChild(yogaNode, i)]];
|
||||
}
|
||||
|
||||
id <ASLayoutElement> layoutElement = (__bridge id <ASLayoutElement>)YGNodeGetContext(yogaNode);
|
||||
CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode));
|
||||
|
||||
if (isRootNode) {
|
||||
// The layout for root should have position CGPointNull, but include the calculated size.
|
||||
return [ASLayout layoutWithLayoutElement:layoutElement size:size sublayouts:sublayouts];
|
||||
} else {
|
||||
CGPoint position = CGPointMake(YGNodeLayoutGetLeft(yogaNode), YGNodeLayoutGetTop(yogaNode));
|
||||
// TODO: If it were possible to set .flattened = YES, it would be valid to do so here.
|
||||
return [ASLayout layoutWithLayoutElement:layoutElement size:size position:position sublayouts:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)destroyYogaNode:(YGNodeRef)yogaNode
|
||||
{
|
||||
// Release the __bridge_retained Context object.
|
||||
__unused id <ASLayoutElement> element = (__bridge_transfer id)YGNodeGetContext(yogaNode);
|
||||
YGNodeFree(yogaNode);
|
||||
}
|
||||
|
||||
- (void)setupYogaNode:(YGNodeRef)yogaNode forElement:(id <ASLayoutElement>)element withParentYogaNode:(YGNodeRef)parentYogaNode
|
||||
{
|
||||
ASLayoutElementStyle *style = element.style;
|
||||
|
||||
// Retain the Context object. This must be explicitly released with a __bridge_transfer; YGNodeFree() is not sufficient.
|
||||
YGNodeSetContext(yogaNode, (__bridge_retained void *)element);
|
||||
|
||||
YGNodeStyleSetDirection (yogaNode, style.direction);
|
||||
|
||||
YGNodeStyleSetFlexWrap (yogaNode, style.flexWrap);
|
||||
YGNodeStyleSetFlexGrow (yogaNode, style.flexGrow);
|
||||
YGNodeStyleSetFlexShrink (yogaNode, style.flexShrink);
|
||||
YGNODE_STYLE_SET_DIMENSION (yogaNode, FlexBasis, style.flexBasis);
|
||||
|
||||
YGNodeStyleSetFlexDirection (yogaNode, yogaFlexDirection(style.flexDirection));
|
||||
YGNodeStyleSetJustifyContent(yogaNode, yogaJustifyContent(style.justifyContent));
|
||||
YGNodeStyleSetAlignSelf (yogaNode, yogaAlignSelf(style.alignSelf));
|
||||
ASStackLayoutAlignItems alignItems = style.alignItems;
|
||||
if (alignItems != ASStackLayoutAlignItemsNotSet) {
|
||||
YGNodeStyleSetAlignItems(yogaNode, yogaAlignItems(alignItems));
|
||||
}
|
||||
|
||||
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 < YGEdgeAll + 1; ++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 = (YGEdge)(edge + 1);
|
||||
}
|
||||
|
||||
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 (parentYogaNode != NULL) {
|
||||
YGNodeInsertChild(parentYogaNode, yogaNode, YGNodeGetChildCount(parentYogaNode));
|
||||
|
||||
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);
|
||||
|
||||
YGNodeSetMeasureFunc(yogaNode, &ASLayoutElementYogaMeasureFunc);
|
||||
}
|
||||
|
||||
// TODO(appleguy): STYLE SETTER METHODS LEFT TO IMPLEMENT: YGNodeStyleSetOverflow, YGNodeStyleSetFlex
|
||||
}
|
||||
|
||||
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
|
||||
restrictedToSize:(ASLayoutElementSize)layoutElementSize
|
||||
relativeToParentSize:(CGSize)parentSize
|
||||
{
|
||||
ASSizeRange styleAndParentSize = ASLayoutElementSizeResolve(layoutElementSize, parentSize);
|
||||
const ASSizeRange rootConstrainedSize = ASSizeRangeIntersect(constrainedSize, styleAndParentSize);
|
||||
|
||||
YGNodeRef rootYogaNode = YGNodeNew();
|
||||
|
||||
// YGNodeCalculateLayout currently doesn't offer the ability to pass a minimum size (max is passed there).
|
||||
// Apply the constrainedSize.min directly to the root node so that layout accounts for it.
|
||||
YGNodeStyleSetMinWidth (rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.min.width));
|
||||
YGNodeStyleSetMinHeight(rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.min.height));
|
||||
|
||||
// It's crucial to set these values. YGNodeCalculateLayout has unusual behavior for its width and height parameters:
|
||||
// 1. If no maximum size set, infer this means YGMeasureModeExactly. Even if a small minWidth & minHeight are set,
|
||||
// these will never be used because the output size of the root will always exactly match this value.
|
||||
// 2. If a maximum size is set, infer that this means YGMeasureModeAtMost, and allow down to the min* values in output.
|
||||
YGNodeStyleSetMaxWidthPercent(rootYogaNode, 100.0);
|
||||
YGNodeStyleSetMaxHeightPercent(rootYogaNode, 100.0);
|
||||
|
||||
[self setupYogaNode:rootYogaNode forElement:self.rootNode withParentYogaNode:NULL];
|
||||
for (id <ASLayoutElement> child in self.children) {
|
||||
YGNodeRef yogaNode = YGNodeNew();
|
||||
[self setupYogaNode:yogaNode forElement:child withParentYogaNode:rootYogaNode];
|
||||
}
|
||||
|
||||
// It is crucial to use yogaFloat... to convert CGFLOAT_MAX into YGUndefined here.
|
||||
YGNodeCalculateLayout(rootYogaNode,
|
||||
yogaFloatForCGFloat(rootConstrainedSize.max.width),
|
||||
yogaFloatForCGFloat(rootConstrainedSize.max.height),
|
||||
YGDirectionInherit);
|
||||
|
||||
ASLayout *layout = [self layoutForYogaNode:rootYogaNode];
|
||||
|
||||
#if YOGA_LAYOUT_LOGGING
|
||||
// Concurrent layouts will interleave the NSLog messages unless we serialize.
|
||||
// Use @synchornize rather than trampolining to the main thread so the tree state isn't changed.
|
||||
@synchronized ([ASDisplayNode class]) {
|
||||
NSLog(@"****************************************************************************");
|
||||
NSLog(@"******************** STARTING YOGA -> ASLAYOUT CREATION ********************");
|
||||
NSLog(@"****************************************************************************");
|
||||
NSLog(@"node = %@", self.rootNode);
|
||||
NSLog(@"style = %@", self.rootNode.style);
|
||||
YGNodePrint(rootYogaNode, (YGPrintOptions)(YGPrintOptionsStyle | YGPrintOptionsLayout));
|
||||
}
|
||||
NSLog(@"rootConstraint = (%@, %@), layout = %@, sublayouts = %@", NSStringFromCGSize(rootConstrainedSize.min), NSStringFromCGSize(rootConstrainedSize.max), layout, layout.sublayouts);
|
||||
#endif
|
||||
|
||||
while(YGNodeGetChildCount(rootYogaNode) > 0) {
|
||||
YGNodeRef yogaNode = YGNodeGetChild(rootYogaNode, 0);
|
||||
[self destroyYogaNode:yogaNode];
|
||||
}
|
||||
[self destroyYogaNode:rootYogaNode];
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif /* !YOGA_TREE_CONTIGUOUS */
|
||||
#endif /* YOGA */
|
||||
70
Source/Layout/ASYogaUtilities.h
Normal file
70
Source/Layout/ASYogaUtilities.h
Normal file
@ -0,0 +1,70 @@
|
||||
//
|
||||
// ASYogaUtilities.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
|
||||
#if YOGA /* YOGA */
|
||||
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
|
||||
extern void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode *node, void(^block)(ASDisplayNode *node));
|
||||
|
||||
ASDISPLAYNODE_EXTERN_C_BEGIN
|
||||
|
||||
#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);
|
||||
|
||||
#pragma mark - Yoga Style Setter Helpers
|
||||
|
||||
#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); \
|
||||
} \
|
||||
|
||||
ASDISPLAYNODE_EXTERN_C_END
|
||||
|
||||
#endif /* YOGA */
|
||||
140
Source/Layout/ASYogaUtilities.mm
Normal file
140
Source/Layout/ASYogaUtilities.mm
Normal file
@ -0,0 +1,140 @@
|
||||
//
|
||||
// ASYogaUtilities.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASYogaUtilities.h>
|
||||
|
||||
#if YOGA /* YOGA */
|
||||
|
||||
extern void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode *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)
|
||||
{
|
||||
switch (alignItems) {
|
||||
case ASStackLayoutAlignItemsNotSet: return YGAlignAuto;
|
||||
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;
|
||||
case YGEdgeStart: return insets.start;
|
||||
case YGEdgeEnd: return insets.end;
|
||||
case YGEdgeHorizontal: return insets.horizontal;
|
||||
case YGEdgeVertical: return insets.vertical;
|
||||
case YGEdgeAll: return insets.all;
|
||||
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 <ASLayoutElement> layoutElement = (__bridge id <ASLayoutElement>)YGNodeGetContext(yogaNode);
|
||||
ASDisplayNodeCAssert([layoutElement conformsToProtocol:@protocol(ASLayoutElement)], @"Yoga context must be <ASLayoutElement>");
|
||||
|
||||
ASSizeRange sizeRange;
|
||||
sizeRange.min = CGSizeZero;
|
||||
sizeRange.max = CGSizeMake(width, height);
|
||||
if (widthMode == YGMeasureModeExactly) {
|
||||
sizeRange.min.width = sizeRange.max.width;
|
||||
} else {
|
||||
// Mode is (YGMeasureModeAtMost | YGMeasureModeUndefined)
|
||||
ASDimension minWidth = layoutElement.style.minWidth;
|
||||
sizeRange.min.width = (minWidth.unit == ASDimensionUnitPoints ? yogaDimensionToPoints(minWidth) : 0.0);
|
||||
}
|
||||
if (heightMode == YGMeasureModeExactly) {
|
||||
sizeRange.min.height = sizeRange.max.height;
|
||||
} else {
|
||||
// Mode is (YGMeasureModeAtMost | YGMeasureModeUndefined)
|
||||
ASDimension minHeight = layoutElement.style.minHeight;
|
||||
sizeRange.min.height = (minHeight.unit == ASDimensionUnitPoints ? yogaDimensionToPoints(minHeight) : 0.0);
|
||||
}
|
||||
|
||||
CGSize size = [[layoutElement layoutThatFits:sizeRange] size];
|
||||
return (YGSize){ .width = (float)size.width, .height = (float)size.height };
|
||||
}
|
||||
|
||||
#endif /* YOGA */
|
||||
@ -198,9 +198,14 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
|
||||
NSInteger _layoutComputationNumberOfPasses;
|
||||
|
||||
#if YOGA
|
||||
YGNodeRef _yogaNode;
|
||||
ASDisplayNode *_yogaParent;
|
||||
// Only ASDisplayNodes are supported in _yogaChildren currently. This means that it is necessary to
|
||||
// create ASDisplayNodes to make a stack layout when using Yoga.
|
||||
// However, the implementation is mostly ready for id <ASLayoutElement>, with a few areas requiring updates.
|
||||
NSMutableArray<ASDisplayNode *> *_yogaChildren;
|
||||
#endif
|
||||
#if YOGA_TREE_CONTIGUOUS
|
||||
YGNodeRef _yogaNode;
|
||||
__weak ASDisplayNode *_yogaParent;
|
||||
ASLayout *_yogaCalculatedLayout;
|
||||
#endif
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user