// // ASLayoutElement.mm // Texture // // 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 /ASDK-Licenses directory of this source tree. An additional // grant of patent rights can be found in the PATENTS file in the same directory. // // Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, // Pinterest, Inc. 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 "ASDisplayNode+FrameworkPrivate.h" #import #import #import #import #import #import #if YOGA #import YOGA_HEADER_PATH #endif #pragma mark - ASLayoutElementContext CGFloat const ASLayoutElementParentDimensionUndefined = NAN; CGSize const ASLayoutElementParentSizeUndefined = {ASLayoutElementParentDimensionUndefined, ASLayoutElementParentDimensionUndefined}; int32_t const ASLayoutElementContextInvalidTransitionID = 0; int32_t const ASLayoutElementContextDefaultTransitionID = ASLayoutElementContextInvalidTransitionID + 1; static inline ASLayoutElementContext _ASLayoutElementContextMake(int32_t transitionID) { struct ASLayoutElementContext context; context.transitionID = transitionID; return context; } static inline BOOL _IsValidTransitionID(int32_t transitionID) { return transitionID > ASLayoutElementContextInvalidTransitionID; } struct ASLayoutElementContext const ASLayoutElementContextNull = _ASLayoutElementContextMake(ASLayoutElementContextInvalidTransitionID); BOOL ASLayoutElementContextIsNull(struct ASLayoutElementContext context) { return !_IsValidTransitionID(context.transitionID); } ASLayoutElementContext ASLayoutElementContextMake(int32_t transitionID) { NSCAssert(_IsValidTransitionID(transitionID), @"Invalid transition ID"); return _ASLayoutElementContextMake(transitionID); } pthread_key_t ASLayoutElementContextKey; // pthread_key_create must be called before the key can be used. This function does that. void ASLayoutElementContextEnsureKey() { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ pthread_key_create(&ASLayoutElementContextKey, free); }); } void ASLayoutElementSetCurrentContext(struct ASLayoutElementContext context) { ASLayoutElementContextEnsureKey(); ASDisplayNodeCAssert(pthread_getspecific(ASLayoutElementContextKey) == NULL, @"Nested ASLayoutElementContexts aren't supported."); pthread_setspecific(ASLayoutElementContextKey, new ASLayoutElementContext(context)); } struct ASLayoutElementContext ASLayoutElementGetCurrentContext() { ASLayoutElementContextEnsureKey(); auto heapCtx = (ASLayoutElementContext *)pthread_getspecific(ASLayoutElementContextKey); return (heapCtx ? *heapCtx : ASLayoutElementContextNull); } void ASLayoutElementClearCurrentContext() { ASLayoutElementContextEnsureKey(); auto heapCtx = (ASLayoutElementContext *)pthread_getspecific(ASLayoutElementContextKey); if (heapCtx != NULL) { delete heapCtx; } pthread_setspecific(ASLayoutElementContextKey, NULL); } #pragma mark - ASLayoutElementStyle NSString * const ASLayoutElementStyleWidthProperty = @"ASLayoutElementStyleWidthProperty"; NSString * const ASLayoutElementStyleMinWidthProperty = @"ASLayoutElementStyleMinWidthProperty"; NSString * const ASLayoutElementStyleMaxWidthProperty = @"ASLayoutElementStyleMaxWidthProperty"; NSString * const ASLayoutElementStyleHeightProperty = @"ASLayoutElementStyleHeightProperty"; NSString * const ASLayoutElementStyleMinHeightProperty = @"ASLayoutElementStyleMinHeightProperty"; NSString * const ASLayoutElementStyleMaxHeightProperty = @"ASLayoutElementStyleMaxHeightProperty"; NSString * const ASLayoutElementStyleSpacingBeforeProperty = @"ASLayoutElementStyleSpacingBeforeProperty"; NSString * const ASLayoutElementStyleSpacingAfterProperty = @"ASLayoutElementStyleSpacingAfterProperty"; NSString * const ASLayoutElementStyleFlexGrowProperty = @"ASLayoutElementStyleFlexGrowProperty"; NSString * const ASLayoutElementStyleFlexShrinkProperty = @"ASLayoutElementStyleFlexShrinkProperty"; NSString * const ASLayoutElementStyleFlexBasisProperty = @"ASLayoutElementStyleFlexBasisProperty"; NSString * const ASLayoutElementStyleAlignSelfProperty = @"ASLayoutElementStyleAlignSelfProperty"; NSString * const ASLayoutElementStyleAscenderProperty = @"ASLayoutElementStyleAscenderProperty"; NSString * const ASLayoutElementStyleDescenderProperty = @"ASLayoutElementStyleDescenderProperty"; NSString * const ASLayoutElementStyleLayoutPositionProperty = @"ASLayoutElementStyleLayoutPositionProperty"; #define ASLayoutElementStyleCallDelegate(propertyName)\ do {\ [_delegate style:self propertyDidChange:propertyName];\ } while(0) @implementation ASLayoutElementStyle { ASDN::RecursiveMutex __instanceLock__; ASLayoutElementSize _size; ASLayoutElementStyleExtensions _extensions; std::atomic _spacingBefore; std::atomic _spacingAfter; std::atomic _flexGrow; std::atomic _flexShrink; std::atomic _flexBasis; std::atomic _alignSelf; std::atomic _ascender; std::atomic _descender; std::atomic _layoutPosition; #if YOGA std::atomic _flexDirection; 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; @dynamic preferredSize, minSize, maxSize, preferredLayoutSize, minLayoutSize, maxLayoutSize; #pragma mark - Lifecycle - (instancetype)initWithDelegate:(id)delegate { self = [self init]; if (self) { _delegate = delegate; } return self; } - (instancetype)init { self = [super init]; if (self) { _size = ASLayoutElementSizeMake(); } return self; } #pragma mark - ASLayoutElementStyleSize - (ASLayoutElementSize)size { ASDN::MutexLocker l(__instanceLock__); return _size; } - (void)setSize:(ASLayoutElementSize)size { ASDN::MutexLocker l(__instanceLock__); _size = size; } #pragma mark - ASLayoutElementStyleSizeForwarding - (ASDimension)width { ASDN::MutexLocker l(__instanceLock__); return _size.width; } - (void)setWidth:(ASDimension)width { ASDN::MutexLocker l(__instanceLock__); _size.width = width; ASLayoutElementStyleCallDelegate(ASLayoutElementStyleWidthProperty); } - (ASDimension)height { ASDN::MutexLocker l(__instanceLock__); return _size.height; } - (void)setHeight:(ASDimension)height { ASDN::MutexLocker l(__instanceLock__); _size.height = height; ASLayoutElementStyleCallDelegate(ASLayoutElementStyleHeightProperty); } - (ASDimension)minWidth { ASDN::MutexLocker l(__instanceLock__); return _size.minWidth; } - (void)setMinWidth:(ASDimension)minWidth { ASDN::MutexLocker l(__instanceLock__); _size.minWidth = minWidth; ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinWidthProperty); } - (ASDimension)maxWidth { ASDN::MutexLocker l(__instanceLock__); return _size.maxWidth; } - (void)setMaxWidth:(ASDimension)maxWidth { ASDN::MutexLocker l(__instanceLock__); _size.maxWidth = maxWidth; ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxWidthProperty); } - (ASDimension)minHeight { ASDN::MutexLocker l(__instanceLock__); return _size.minHeight; } - (void)setMinHeight:(ASDimension)minHeight { ASDN::MutexLocker l(__instanceLock__); _size.minHeight = minHeight; ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinHeightProperty); } - (ASDimension)maxHeight { ASDN::MutexLocker l(__instanceLock__); return _size.maxHeight; } - (void)setMaxHeight:(ASDimension)maxHeight { ASDN::MutexLocker l(__instanceLock__); _size.maxHeight = maxHeight; ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxHeightProperty); } #pragma mark - ASLayoutElementStyleSizeHelpers // We explicitly not call the setter for (max/min) width and height to avoid locking overhead - (void)setPreferredSize:(CGSize)preferredSize { ASDN::MutexLocker l(__instanceLock__); _size.width = ASDimensionMakeWithPoints(preferredSize.width); _size.height = ASDimensionMakeWithPoints(preferredSize.height); ASLayoutElementStyleCallDelegate(ASLayoutElementStyleWidthProperty); ASLayoutElementStyleCallDelegate(ASLayoutElementStyleHeightProperty); } - (CGSize)preferredSize { ASDN::MutexLocker l(__instanceLock__); if (_size.width.unit == ASDimensionUnitFraction) { NSCAssert(NO, @"Cannot get preferredSize of element with fractional width. Width: %@.", NSStringFromASDimension(_size.width)); return CGSizeZero; } if (_size.height.unit == ASDimensionUnitFraction) { NSCAssert(NO, @"Cannot get preferredSize of element with fractional height. Height: %@.", NSStringFromASDimension(_size.height)); return CGSizeZero; } return CGSizeMake(_size.width.value, _size.height.value); } - (void)setMinSize:(CGSize)minSize { ASDN::MutexLocker l(__instanceLock__); _size.minWidth = ASDimensionMakeWithPoints(minSize.width); _size.minHeight = ASDimensionMakeWithPoints(minSize.height); ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinWidthProperty); ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinHeightProperty); } - (void)setMaxSize:(CGSize)maxSize { ASDN::MutexLocker l(__instanceLock__); _size.maxWidth = ASDimensionMakeWithPoints(maxSize.width); _size.maxHeight = ASDimensionMakeWithPoints(maxSize.height); ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxWidthProperty); ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxHeightProperty); } - (ASLayoutSize)preferredLayoutSize { ASDN::MutexLocker l(__instanceLock__); return ASLayoutSizeMake(_size.width, _size.height); } - (void)setPreferredLayoutSize:(ASLayoutSize)preferredLayoutSize { ASDN::MutexLocker l(__instanceLock__); _size.width = preferredLayoutSize.width; _size.height = preferredLayoutSize.height; ASLayoutElementStyleCallDelegate(ASLayoutElementStyleWidthProperty); ASLayoutElementStyleCallDelegate(ASLayoutElementStyleHeightProperty); } - (ASLayoutSize)minLayoutSize { ASDN::MutexLocker l(__instanceLock__); return ASLayoutSizeMake(_size.minWidth, _size.minHeight); } - (void)setMinLayoutSize:(ASLayoutSize)minLayoutSize { ASDN::MutexLocker l(__instanceLock__); _size.minWidth = minLayoutSize.width; _size.minHeight = minLayoutSize.height; ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinWidthProperty); ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinHeightProperty); } - (ASLayoutSize)maxLayoutSize { ASDN::MutexLocker l(__instanceLock__); return ASLayoutSizeMake(_size.maxWidth, _size.maxHeight); } - (void)setMaxLayoutSize:(ASLayoutSize)maxLayoutSize { ASDN::MutexLocker l(__instanceLock__); _size.maxWidth = maxLayoutSize.width; _size.maxHeight = maxLayoutSize.height; ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxWidthProperty); ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxHeightProperty); } #pragma mark - ASStackLayoutElement - (void)setSpacingBefore:(CGFloat)spacingBefore { _spacingBefore.store(spacingBefore); ASLayoutElementStyleCallDelegate(ASLayoutElementStyleSpacingBeforeProperty); } - (CGFloat)spacingBefore { return _spacingBefore.load(); } - (void)setSpacingAfter:(CGFloat)spacingAfter { _spacingAfter.store(spacingAfter); ASLayoutElementStyleCallDelegate(ASLayoutElementStyleSpacingAfterProperty); } - (CGFloat)spacingAfter { return _spacingAfter.load(); } - (void)setFlexGrow:(CGFloat)flexGrow { _flexGrow.store(flexGrow); ASLayoutElementStyleCallDelegate(ASLayoutElementStyleFlexGrowProperty); } - (CGFloat)flexGrow { return _flexGrow.load(); } - (void)setFlexShrink:(CGFloat)flexShrink { _flexShrink.store(flexShrink); ASLayoutElementStyleCallDelegate(ASLayoutElementStyleFlexShrinkProperty); } - (CGFloat)flexShrink { return _flexShrink.load(); } - (void)setFlexBasis:(ASDimension)flexBasis { _flexBasis.store(flexBasis); ASLayoutElementStyleCallDelegate(ASLayoutElementStyleFlexBasisProperty); } - (ASDimension)flexBasis { return _flexBasis.load(); } - (void)setAlignSelf:(ASStackLayoutAlignSelf)alignSelf { _alignSelf.store(alignSelf); ASLayoutElementStyleCallDelegate(ASLayoutElementStyleAlignSelfProperty); } - (ASStackLayoutAlignSelf)alignSelf { return _alignSelf.load(); } - (void)setAscender:(CGFloat)ascender { _ascender.store(ascender); ASLayoutElementStyleCallDelegate(ASLayoutElementStyleAscenderProperty); } - (CGFloat)ascender { return _ascender.load(); } - (void)setDescender:(CGFloat)descender { _descender.store(descender); ASLayoutElementStyleCallDelegate(ASLayoutElementStyleDescenderProperty); } - (CGFloat)descender { return _descender.load(); } #pragma mark - ASAbsoluteLayoutElement - (void)setLayoutPosition:(CGPoint)layoutPosition { _layoutPosition.store(layoutPosition); ASLayoutElementStyleCallDelegate(ASLayoutElementStyleLayoutPositionProperty); } - (CGPoint)layoutPosition { return _layoutPosition.load(); } #pragma mark - Extensions - (void)setLayoutOptionExtensionBool:(BOOL)value atIndex:(int)idx { NSCAssert(idx < kMaxLayoutElementBoolExtensions, @"Setting index outside of max bool extensions space"); ASDN::MutexLocker l(__instanceLock__); _extensions.boolExtensions[idx] = value; } - (BOOL)layoutOptionExtensionBoolAtIndex:(int)idx\ { NSCAssert(idx < kMaxLayoutElementBoolExtensions, @"Accessing index outside of max bool extensions space"); ASDN::MutexLocker l(__instanceLock__); return _extensions.boolExtensions[idx]; } - (void)setLayoutOptionExtensionInteger:(NSInteger)value atIndex:(int)idx { NSCAssert(idx < kMaxLayoutElementStateIntegerExtensions, @"Setting index outside of max integer extensions space"); ASDN::MutexLocker l(__instanceLock__); _extensions.integerExtensions[idx] = value; } - (NSInteger)layoutOptionExtensionIntegerAtIndex:(int)idx { NSCAssert(idx < kMaxLayoutElementStateIntegerExtensions, @"Accessing index outside of max integer extensions space"); ASDN::MutexLocker l(__instanceLock__); return _extensions.integerExtensions[idx]; } - (void)setLayoutOptionExtensionEdgeInsets:(UIEdgeInsets)value atIndex:(int)idx { NSCAssert(idx < kMaxLayoutElementStateEdgeInsetExtensions, @"Setting index outside of max edge insets extensions space"); ASDN::MutexLocker l(__instanceLock__); _extensions.edgeInsetsExtensions[idx] = value; } - (UIEdgeInsets)layoutOptionExtensionEdgeInsetsAtIndex:(int)idx { NSCAssert(idx < kMaxLayoutElementStateEdgeInsetExtensions, @"Accessing index outside of max edge insets extensions space"); ASDN::MutexLocker l(__instanceLock__); return _extensions.edgeInsetsExtensions[idx]; } #pragma mark - Debugging - (NSString *)description { return ASObjectDescriptionMake(self, [self propertiesForDescription]); } - (NSMutableArray *)propertiesForDescription { NSMutableArray *result = [NSMutableArray array]; if ((self.minLayoutSize.width.unit != ASDimensionUnitAuto || self.minLayoutSize.height.unit != ASDimensionUnitAuto)) { [result addObject:@{ @"minLayoutSize" : NSStringFromASLayoutSize(self.minLayoutSize) }]; } if ((self.preferredLayoutSize.width.unit != ASDimensionUnitAuto || self.preferredLayoutSize.height.unit != ASDimensionUnitAuto)) { [result addObject:@{ @"preferredSize" : NSStringFromASLayoutSize(self.preferredLayoutSize) }]; } if ((self.maxLayoutSize.width.unit != ASDimensionUnitAuto || self.maxLayoutSize.height.unit != ASDimensionUnitAuto)) { [result addObject:@{ @"maxLayoutSize" : NSStringFromASLayoutSize(self.maxLayoutSize) }]; } if (self.alignSelf != ASStackLayoutAlignSelfAuto) { [result addObject:@{ @"alignSelf" : [@[@"ASStackLayoutAlignSelfAuto", @"ASStackLayoutAlignSelfStart", @"ASStackLayoutAlignSelfEnd", @"ASStackLayoutAlignSelfCenter", @"ASStackLayoutAlignSelfStretch"] objectAtIndex:self.alignSelf] }]; } if (self.ascender != 0) { [result addObject:@{ @"ascender" : @(self.ascender) }]; } if (self.descender != 0) { [result addObject:@{ @"descender" : @(self.descender) }]; } if (ASDimensionEqualToDimension(self.flexBasis, ASDimensionAuto) == NO) { [result addObject:@{ @"flexBasis" : NSStringFromASDimension(self.flexBasis) }]; } if (self.flexGrow != 0) { [result addObject:@{ @"flexGrow" : @(self.flexGrow) }]; } if (self.flexShrink != 0) { [result addObject:@{ @"flexShrink" : @(self.flexShrink) }]; } if (self.spacingAfter != 0) { [result addObject:@{ @"spacingAfter" : @(self.spacingAfter) }]; } if (self.spacingBefore != 0) { [result addObject:@{ @"spacingBefore" : @(self.spacingBefore) }]; } if (CGPointEqualToPoint(self.layoutPosition, CGPointZero) == NO) { [result addObject:@{ @"layoutPosition" : [NSValue valueWithCGPoint:self.layoutPosition] }]; } return result; } #pragma mark - Yoga Flexbox Properties #if YOGA - (ASStackLayoutDirection)flexDirection { return _flexDirection.load(); } - (YGDirection)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)setFlexDirection:(ASStackLayoutDirection)flexDirection { _flexDirection.store(flexDirection); } - (void)setDirection:(YGDirection)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 #pragma clang diagnostic ignored "-Wdeprecated-declarations" - (ASRelativeSizeRange)sizeRange { return ASRelativeSizeRangeMake(self.minLayoutSize, self.maxLayoutSize); } - (void)setSizeRange:(ASRelativeSizeRange)sizeRange { self.minLayoutSize = sizeRange.min; self.maxLayoutSize = sizeRange.max; } #pragma clang diagnostic pop @end