diff --git a/Source/ASDisplayNode.h b/Source/ASDisplayNode.h index 2d528c91f8..53862ab7c6 100644 --- a/Source/ASDisplayNode.h +++ b/Source/ASDisplayNode.h @@ -121,7 +121,7 @@ extern NSInteger const ASDefaultDrawingPriority; * */ -@interface ASDisplayNode : NSObject +@interface ASDisplayNode : NSObject /** @name Initializing a node object */ @@ -298,23 +298,6 @@ extern NSInteger const ASDefaultDrawingPriority; /** @name Managing dimensions */ -/** - * @abstract Asks the node to return a layout based on given size range. - * - * @param constrainedSize The minimum and maximum sizes the receiver should fit in. - * - * @return An ASLayout instance defining the layout of the receiver (and its children, if the box layout model is used). - * - * @discussion Though this method does not set the bounds of the view, it does have side effects--caching both the - * constraint and the result. - * - * @warning Subclasses must not override this; it caches results from -calculateLayoutThatFits:. Calling this method may - * be expensive if result is not cached. - * - * @see [ASDisplayNode(Subclassing) calculateLayoutThatFits:] - */ -- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize; - /** * @abstract Provides a way to declare a block to provide an ASLayoutSpec without having to subclass ASDisplayNode and * implement layoutSpecThatFits: @@ -755,6 +738,31 @@ extern NSInteger const ASDefaultDrawingPriority; @end +@interface ASDisplayNode (ASLayoutElement) + +/** + * @abstract Asks the node to return a layout based on given size range. + * + * @param constrainedSize The minimum and maximum sizes the receiver should fit in. + * + * @return An ASLayout instance defining the layout of the receiver (and its children, if the box layout model is used). + * + * @discussion Though this method does not set the bounds of the view, it does have side effects--caching both the + * constraint and the result. + * + * @warning Subclasses must not override this; it caches results from -calculateLayoutThatFits:. Calling this method may + * be expensive if result is not cached. + * + * @see [ASDisplayNode(Subclassing) calculateLayoutThatFits:] + */ +- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize; + +@end + +@interface ASDisplayNode (ASLayoutElementStylability) + +@end + @interface ASDisplayNode (LayoutTransitioning) /** diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 13d1b5bc64..d10d646846 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -859,40 +859,17 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) + (ASDisplayNodeSubclassOverridesSelector(self.class, @selector(calculateLayoutThatFits:)) ? 1 : 0)) <= 1, \ @"Subclass %@ must at least provide a layoutSpecBlock or override at most one of the three layout methods: calculateLayoutThatFits:, layoutSpecThatFits:, or calculateSizeThatFits:", NSStringFromClass(self.class)) -#pragma mark - -- (ASLayoutElementStyle *)style -{ - ASDN::MutexLocker l(__instanceLock__); - if (_style == nil) { - _style = [[ASLayoutElementStyle alloc] init]; - } - return _style; -} - -- (ASLayoutElementType)layoutElementType -{ - return ASLayoutElementTypeDisplayNode; -} +#pragma mark - (BOOL)canLayoutAsynchronous { return !self.isNodeLoaded; } -- (NSArray> *)sublayoutElements -{ - return self.subnodes; -} - -- (instancetype)styledWithBlock:(AS_NOESCAPE void (^)(__kindof ASLayoutElementStyle *style))styleBlock -{ - styleBlock(self.style); - return self; -} - ASLayoutElementFinalLayoutElementDefault +#pragma mark + - (NSString *)debugName { ASDN::MutexLocker l(__instanceLock__); @@ -907,47 +884,6 @@ ASLayoutElementFinalLayoutElementDefault } } -#pragma mark Measurement Pass - -- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize -{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - // For now we just call the deprecated measureWithSizeRange: method to not break old API - return [self measureWithSizeRange:constrainedSize]; -#pragma clang diagnostic pop -} - -- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)parentSize -{ - ASDN::MutexLocker l(__instanceLock__); - - // If one or multiple layout transitions are in flight it still can happen that layout information is requested - // on other threads. As the pending and calculated layout to be updated in the layout transition in here just a - // layout calculation wil be performed without side effect - if ([self _isLayoutTransitionInvalid]) { - return [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize]; - } - - if (_calculatedDisplayNodeLayout->isValidForConstrainedSizeParentSize(constrainedSize, parentSize)) { - ASDisplayNodeAssertNotNil(_calculatedDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _calculatedDisplayNodeLayout->layout should not be nil! %@", self); - // Our calculated layout is suitable for this constrainedSize, so keep using it and - // invalidate any pending layout that has been generated in the past. - _pendingDisplayNodeLayout = nullptr; - return _calculatedDisplayNodeLayout->layout ?: [ASLayout layoutWithLayoutElement:self size:{0, 0}]; - } - - // Create a pending display node layout for the layout pass - _pendingDisplayNodeLayout = std::make_shared( - [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize], - constrainedSize, - parentSize - ); - - ASDisplayNodeAssertNotNil(_pendingDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _pendingDisplayNodeLayout->layout should not be nil! %@", self); - return _pendingDisplayNodeLayout->layout ?: [ASLayout layoutWithLayoutElement:self size:{0, 0}]; -} - #pragma mark Layout Pass - (void)__setNeedsLayout @@ -1297,7 +1233,6 @@ ASLayoutElementFinalLayoutElementDefault _calculatedDisplayNodeLayout = displayNodeLayout; } - - (CGSize)calculatedSize { ASDN::MutexLocker l(__instanceLock__); @@ -4063,46 +3998,13 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { } } -#pragma mark - ASPrimitiveTraitCollection - -- (ASPrimitiveTraitCollection)primitiveTraitCollection -{ - ASDN::MutexLocker l(__instanceLock__); - return _primitiveTraitCollection; -} - -- (void)setPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traitCollection -{ - __instanceLock__.lock(); - if (ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(traitCollection, _primitiveTraitCollection) == NO) { - _primitiveTraitCollection = traitCollection; - ASDisplayNodeLogEvent(self, @"asyncTraitCollectionDidChange: %@", NSStringFromASPrimitiveTraitCollection(traitCollection)); - __instanceLock__.unlock(); - - [self asyncTraitCollectionDidChange]; - return; - } - - __instanceLock__.unlock(); -} - -- (ASTraitCollection *)asyncTraitCollection -{ - ASDN::MutexLocker l(__instanceLock__); - return [ASTraitCollection traitCollectionWithASPrimitiveTraitCollection:self.primitiveTraitCollection]; -} +#pragma mark - Trait Collection Hooks - (void)asyncTraitCollectionDidChange { // Subclass override } -ASPrimitiveTraitCollectionDeprecatedImplementation - -#pragma mark - ASLayoutElementStyleExtensibility - -ASLayoutElementStyleExtensibilityForwarding - #if TARGET_OS_TV #pragma mark - UIFocusEnvironment Protocol (tvOS) @@ -4136,23 +4038,125 @@ ASLayoutElementStyleExtensibilityForwarding } #endif -#pragma mark - Deprecated +@end -// This methods cannot be moved into the category ASDisplayNode (Deprecated). So they need to be declared in ASDisplayNode until removed +#pragma mark - ASDisplayNode (ASLayoutElement) + +@implementation ASDisplayNode (ASLayoutElement) + +#pragma mark + +- (ASLayoutElementStyle *)style +{ + ASDN::MutexLocker l(__instanceLock__); + if (_style == nil) { + _style = [[ASLayoutElementStyle alloc] init]; + } + return _style; +} + +- (ASLayoutElementType)layoutElementType +{ + return ASLayoutElementTypeDisplayNode; +} + +- (NSArray> *)sublayoutElements +{ + return self.subnodes; +} + +#pragma mark Measurement Pass + +- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + // For now we just call the deprecated measureWithSizeRange: method to not break old API + return [self measureWithSizeRange:constrainedSize]; +#pragma clang diagnostic pop +} - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize { return [self layoutThatFits:constrainedSize parentSize:constrainedSize.max]; } -- (BOOL)usesImplicitHierarchyManagement +- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)parentSize { - return self.automaticallyManagesSubnodes; + ASDN::MutexLocker l(__instanceLock__); + + // If one or multiple layout transitions are in flight it still can happen that layout information is requested + // on other threads. As the pending and calculated layout to be updated in the layout transition in here just a + // layout calculation wil be performed without side effect + if ([self _isLayoutTransitionInvalid]) { + return [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize]; + } + + if (_calculatedDisplayNodeLayout->isValidForConstrainedSizeParentSize(constrainedSize, parentSize)) { + ASDisplayNodeAssertNotNil(_calculatedDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _calculatedDisplayNodeLayout->layout should not be nil! %@", self); + // Our calculated layout is suitable for this constrainedSize, so keep using it and + // invalidate any pending layout that has been generated in the past. + _pendingDisplayNodeLayout = nullptr; + return _calculatedDisplayNodeLayout->layout ?: [ASLayout layoutWithLayoutElement:self size:{0, 0}]; + } + + // Create a pending display node layout for the layout pass + _pendingDisplayNodeLayout = std::make_shared( + [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize], + constrainedSize, + parentSize + ); + + ASDisplayNodeAssertNotNil(_pendingDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _pendingDisplayNodeLayout->layout should not be nil! %@", self); + return _pendingDisplayNodeLayout->layout ?: [ASLayout layoutWithLayoutElement:self size:{0, 0}]; } -- (void)setUsesImplicitHierarchyManagement:(BOOL)enabled +#pragma mark ASLayoutElementStyleExtensibility + +ASLayoutElementStyleExtensibilityForwarding + +#pragma mark ASPrimitiveTraitCollection + +- (ASPrimitiveTraitCollection)primitiveTraitCollection { - self.automaticallyManagesSubnodes = enabled; + ASDN::MutexLocker l(__instanceLock__); + return _primitiveTraitCollection; +} + +- (void)setPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traitCollection +{ + __instanceLock__.lock(); + if (ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(traitCollection, _primitiveTraitCollection) == NO) { + _primitiveTraitCollection = traitCollection; + ASDisplayNodeLogEvent(self, @"asyncTraitCollectionDidChange: %@", NSStringFromASPrimitiveTraitCollection(traitCollection)); + __instanceLock__.unlock(); + + [self asyncTraitCollectionDidChange]; + return; + } + + __instanceLock__.unlock(); +} + +- (ASTraitCollection *)asyncTraitCollection +{ + ASDN::MutexLocker l(__instanceLock__); + return [ASTraitCollection traitCollectionWithASPrimitiveTraitCollection:self.primitiveTraitCollection]; +} + +ASPrimitiveTraitCollectionDeprecatedImplementation + +@end + + +#pragma mark - ASDisplayNode (ASLayoutElementStylability) + +@implementation ASDisplayNode (ASLayoutElementStylability) + +- (instancetype)styledWithBlock:(AS_NOESCAPE void (^)(__kindof ASLayoutElementStyle *style))styleBlock +{ + styleBlock(self.style); + return self; } @end @@ -4298,6 +4302,16 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; return isPoints ? CGSizeMake(size.width.value, size.height.value) : CGSizeZero; } +- (BOOL)usesImplicitHierarchyManagement +{ + return self.automaticallyManagesSubnodes; +} + +- (void)setUsesImplicitHierarchyManagement:(BOOL)enabled +{ + self.automaticallyManagesSubnodes = enabled; +} + - (CGSize)measure:(CGSize)constrainedSize { return [self layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size; diff --git a/Source/Details/ASTraitCollection.h b/Source/Details/ASTraitCollection.h index 010e44ae5d..a1e9971b10 100644 --- a/Source/Details/ASTraitCollection.h +++ b/Source/Details/ASTraitCollection.h @@ -21,6 +21,7 @@ @class ASTraitCollection; @protocol ASLayoutElement; +@protocol ASTraitEnvironment; NS_ASSUME_NONNULL_BEGIN @@ -63,7 +64,7 @@ extern NSString *NSStringFromASPrimitiveTraitCollection(ASPrimitiveTraitCollecti * This function will walk the layout element hierarchy and updates the layout element trait collection for every * layout element within the hierarchy. */ -extern void ASTraitCollectionPropagateDown(id root, ASPrimitiveTraitCollection traitCollection); +extern void ASTraitCollectionPropagateDown(id element, ASPrimitiveTraitCollection traitCollection); /// For backward compatibility reasons we redefine the old layout element trait collection struct name #define ASEnvironmentTraitCollection ASPrimitiveTraitCollection @@ -88,7 +89,6 @@ ASDISPLAYNODE_EXTERN_C_END - (void)setPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traitCollection; /** - * Returns an NSObject-representation of the environment's ASEnvironmentDisplayTraits */ - (ASTraitCollection *)asyncTraitCollection; diff --git a/Source/Details/ASTraitCollection.m b/Source/Details/ASTraitCollection.m index effb502899..0c845e9f63 100644 --- a/Source/Details/ASTraitCollection.m +++ b/Source/Details/ASTraitCollection.m @@ -22,10 +22,14 @@ #pragma mark - ASPrimitiveTraitCollection -extern void ASTraitCollectionPropagateDown(id root, ASPrimitiveTraitCollection traitCollection) { - ASLayoutElementPerformBlockOnEveryElement(root, ^(id _Nonnull element) { +extern void ASTraitCollectionPropagateDown(id element, ASPrimitiveTraitCollection traitCollection) { + if (element) { element.primitiveTraitCollection = traitCollection; - }); + } + + for (id subelement in element.sublayoutElements) { + ASTraitCollectionPropagateDown(subelement, traitCollection); + } } ASPrimitiveTraitCollection ASPrimitiveTraitCollectionMakeDefault() diff --git a/Source/Layout/ASLayoutElement.h b/Source/Layout/ASLayoutElement.h index 98ab443347..30bb021078 100644 --- a/Source/Layout/ASLayoutElement.h +++ b/Source/Layout/ASLayoutElement.h @@ -42,16 +42,6 @@ typedef NS_ENUM(NSUInteger, ASLayoutElementType) { ASLayoutElementTypeDisplayNode }; -ASDISPLAYNODE_EXTERN_C_BEGIN - -/** - This function will walk the layout element hierarchy. It does run the block on the node provided - directly to the function call. - */ -extern void ASLayoutElementPerformBlockOnEveryElement(id root, void(^block)(id element)); - -ASDISPLAYNODE_EXTERN_C_END - #pragma mark - ASLayoutElement /** @@ -70,7 +60,7 @@ ASDISPLAYNODE_EXTERN_C_END * access to the options via convenience properties. If you are creating custom layout spec, then you can * extend the backing layout options class to accommodate any new layout options. */ -@protocol ASLayoutElement +@protocol ASLayoutElement #pragma mark - Getter diff --git a/Source/Layout/ASLayoutElement.mm b/Source/Layout/ASLayoutElement.mm index 07091da18e..b893c1dbba 100644 --- a/Source/Layout/ASLayoutElement.mm +++ b/Source/Layout/ASLayoutElement.mm @@ -29,17 +29,6 @@ #import YOGA_HEADER_PATH #endif -extern void ASLayoutElementPerformBlockOnEveryElement(id element, void(^block)(id element)) -{ - if (element) { - block(element); - } - - for (id subelement in element.sublayoutElements) { - ASLayoutElementPerformBlockOnEveryElement(subelement, block); - } -} - #pragma mark - ASLayoutElementContext CGFloat const ASLayoutElementParentDimensionUndefined = NAN; diff --git a/Source/Layout/ASLayoutSpec+Subclasses.mm b/Source/Layout/ASLayoutSpec+Subclasses.mm index fcaa86d435..605e7f5423 100644 --- a/Source/Layout/ASLayoutSpec+Subclasses.mm +++ b/Source/Layout/ASLayoutSpec+Subclasses.mm @@ -58,7 +58,7 @@ #pragma mark - Final layoutElement -- (id)layoutElementToAddFromLayoutElement:(id)child +- (id)layoutElementToAddFromLayoutElement:(id)child { if (self.isFinalLayoutElement == NO) { id finalLayoutElement = [child finalLayoutElement]; diff --git a/Source/Layout/ASLayoutSpec.h b/Source/Layout/ASLayoutSpec.h index 0ca4eba87b..5cb6cb3f87 100644 --- a/Source/Layout/ASLayoutSpec.h +++ b/Source/Layout/ASLayoutSpec.h @@ -24,7 +24,7 @@ NS_ASSUME_NONNULL_BEGIN /** * A layout spec is an immutable object that describes a layout, loosely inspired by React. */ -@interface ASLayoutSpec : NSObject +@interface ASLayoutSpec : NSObject /** * Creation of a layout spec should only happen by a user in layoutSpecThatFits:. During that method, a diff --git a/Source/Private/ASDisplayNodeInternal.h b/Source/Private/ASDisplayNodeInternal.h index 964676b17a..434d7d8c51 100644 --- a/Source/Private/ASDisplayNodeInternal.h +++ b/Source/Private/ASDisplayNodeInternal.h @@ -293,17 +293,14 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo */ - (nullable ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass checkViewHierarchy:(BOOL)checkViewHierarchy; -/** - * Convenience method to access this node's trait collection struct. Externally, users should interact - * with the trait collection via ASTraitCollection - */ -- (ASPrimitiveTraitCollection)primitiveTraitCollection; - /** * Whether this node rasterizes its descendants. See -enableSubtreeRasterization. */ @property (atomic, readonly) BOOL rasterizesSubtree; +/** + * Called if a gesture recognizer was attached to an _ASDisplayView + */ - (void)nodeViewDidAddGestureRecognizer; @end diff --git a/Source/Private/ASLayoutTransition.h b/Source/Private/ASLayoutTransition.h index e0a7871745..74a6acb984 100644 --- a/Source/Private/ASLayoutTransition.h +++ b/Source/Private/ASLayoutTransition.h @@ -30,9 +30,9 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - ASLayoutElementTransition /** - * Extend the layout element protocol to check if a the element can layout asynchronously. + * Objects conform to this project returns if it's possible to layout asynchronous */ -@protocol ASLayoutElementTransition +@protocol ASLayoutElementTransition /** * @abstract Returns if the layoutElement can be used to layout in an asynchronous way on a background thread.