-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve separation of code for layout method types (#1305)
* Improve separation of code for layout method types * Address PR comments - Delegate to layout spec engine if the node is a layout spec node but yoga engine was asked for calculate the layout - Change ASLayoutType to ASLayoutEngineType - Improve layout engine fall through code
- Loading branch information
Showing
16 changed files
with
440 additions
and
281 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
// | ||
// ASDisplayNode+LayoutSpec.h | ||
// Texture | ||
// | ||
// Copyright (c) Pinterest, Inc. All rights reserved. | ||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
|
||
#import <AsyncDisplayKit/ASDisplayNode.h> | ||
#import <AsyncDisplayKit/ASDimension.h> | ||
|
||
@class ASLayout; | ||
|
||
NS_ASSUME_NONNULL_BEGIN | ||
|
||
@interface ASDisplayNode (ASLayoutSpec) | ||
|
||
/** | ||
* @abstract Provides a way to declare a block to provide an ASLayoutSpec without having to subclass ASDisplayNode and | ||
* implement layoutSpecThatFits: | ||
* | ||
* @return A block that takes a constrainedSize ASSizeRange argument, and must return an ASLayoutSpec that includes all | ||
* of the subnodes to position in the layout. This input-output relationship is identical to the subclass override | ||
* method -layoutSpecThatFits: | ||
* | ||
* @warning Subclasses that implement -layoutSpecThatFits: must not also use .layoutSpecBlock. Doing so will trigger | ||
* an exception. A future version of the framework may support using both, calling them serially, with the | ||
* .layoutSpecBlock superseding any values set by the method override. | ||
* | ||
* @code ^ASLayoutSpec *(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {}; | ||
*/ | ||
@property (nullable) ASLayoutSpecBlock layoutSpecBlock; | ||
|
||
@end | ||
|
||
// These methods are intended to be used internally to Texture, and should not be called directly. | ||
@interface ASDisplayNode (ASLayoutSpecPrivate) | ||
|
||
/// For internal usage only | ||
- (ASLayout *)calculateLayoutLayoutSpec:(ASSizeRange)constrainedSize; | ||
|
||
@end | ||
|
||
@interface ASDisplayNode (ASLayoutSpecSubclasses) | ||
|
||
/** | ||
* @abstract Return a layout spec that describes the layout of the receiver and its children. | ||
* | ||
* @param constrainedSize The minimum and maximum sizes the receiver should fit in. | ||
* | ||
* @discussion Subclasses that override should expect this method to be called on a non-main thread. The returned layout spec | ||
* is used to calculate an ASLayout and cached by ASDisplayNode for quick access during -layout. Other expensive work that needs to | ||
* be done before display can be performed here, and using ivars to cache any valuable intermediate results is | ||
* encouraged. | ||
* | ||
* @note This method should not be called directly outside of ASDisplayNode; use -layoutThatFits: instead. | ||
* | ||
* @warning Subclasses that implement -layoutSpecThatFits: must not use .layoutSpecBlock. Doing so will trigger an | ||
* exception. A future version of the framework may support using both, calling them serially, with the .layoutSpecBlock | ||
* superseding any values set by the method override. | ||
*/ | ||
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize; | ||
|
||
@end | ||
|
||
NS_ASSUME_NONNULL_END |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
// | ||
// ASDisplayNode+LayoutSpec.mm | ||
// Texture | ||
// | ||
// Copyright (c) Pinterest, Inc. All rights reserved. | ||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
|
||
#import <UIKit/UIKit.h> | ||
#import <AsyncDisplayKit/ASAvailability.h> | ||
|
||
#import <AsyncDisplayKit/_ASScopeTimer.h> | ||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h> | ||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h> | ||
#import <AsyncDisplayKit/ASLayout.h> | ||
#import <AsyncDisplayKit/ASLayoutSpec+Subclasses.h> | ||
#import <AsyncDisplayKit/ASLayoutSpecPrivate.h> | ||
#import <AsyncDisplayKit/ASLog.h> | ||
#import <AsyncDisplayKit/ASThread.h> | ||
|
||
|
||
@implementation ASDisplayNode (ASLayoutSpec) | ||
|
||
- (void)setLayoutSpecBlock:(ASLayoutSpecBlock)layoutSpecBlock | ||
{ | ||
// For now there should never be an override of layoutSpecThatFits: and a layoutSpecBlock together. | ||
ASDisplayNodeAssert(!(_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits), | ||
@"Nodes with a .layoutSpecBlock must not also implement -layoutSpecThatFits:"); | ||
ASDN::MutexLocker l(__instanceLock__); | ||
_layoutSpecBlock = layoutSpecBlock; | ||
} | ||
|
||
- (ASLayoutSpecBlock)layoutSpecBlock | ||
{ | ||
ASDN::MutexLocker l(__instanceLock__); | ||
return _layoutSpecBlock; | ||
} | ||
|
||
- (ASLayout *)calculateLayoutLayoutSpec:(ASSizeRange)constrainedSize | ||
{ | ||
ASDN::UniqueLock l(__instanceLock__); | ||
|
||
// Manual size calculation via calculateSizeThatFits: | ||
if (_layoutSpecBlock == NULL && (_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) == 0) { | ||
CGSize size = [self calculateSizeThatFits:constrainedSize.max]; | ||
ASDisplayNodeLogEvent(self, @"calculatedSize: %@", NSStringFromCGSize(size)); | ||
return [ASLayout layoutWithLayoutElement:self size:ASSizeRangeClamp(constrainedSize, size) sublayouts:nil]; | ||
} | ||
|
||
// Size calcualtion with layout elements | ||
BOOL measureLayoutSpec = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec; | ||
if (measureLayoutSpec) { | ||
_layoutSpecNumberOfPasses++; | ||
} | ||
|
||
// Get layout element from the node | ||
id<ASLayoutElement> layoutElement = [self _locked_layoutElementThatFits:constrainedSize]; | ||
#if ASEnableVerboseLogging | ||
for (NSString *asciiLine in [[layoutElement asciiArtString] componentsSeparatedByString:@"\n"]) { | ||
as_log_verbose(ASLayoutLog(), "%@", asciiLine); | ||
} | ||
#endif | ||
|
||
|
||
// Certain properties are necessary to set on an element of type ASLayoutSpec | ||
if (layoutElement.layoutElementType == ASLayoutElementTypeLayoutSpec) { | ||
ASLayoutSpec *layoutSpec = (ASLayoutSpec *)layoutElement; | ||
|
||
#if AS_DEDUPE_LAYOUT_SPEC_TREE | ||
NSHashTable *duplicateElements = [layoutSpec findDuplicatedElementsInSubtree]; | ||
if (duplicateElements.count > 0) { | ||
ASDisplayNodeFailAssert(@"Node %@ returned a layout spec that contains the same elements in multiple positions. Elements: %@", self, duplicateElements); | ||
// Use an empty layout spec to avoid crashes | ||
layoutSpec = [[ASLayoutSpec alloc] init]; | ||
} | ||
#endif | ||
|
||
ASDisplayNodeAssert(layoutSpec.isMutable, @"Node %@ returned layout spec %@ that has already been used. Layout specs should always be regenerated.", self, layoutSpec); | ||
|
||
layoutSpec.isMutable = NO; | ||
} | ||
|
||
// Manually propagate the trait collection here so that any layoutSpec children of layoutSpec will get a traitCollection | ||
{ | ||
ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec); | ||
ASTraitCollectionPropagateDown(layoutElement, self.primitiveTraitCollection); | ||
} | ||
|
||
BOOL measureLayoutComputation = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutComputation; | ||
if (measureLayoutComputation) { | ||
_layoutComputationNumberOfPasses++; | ||
} | ||
|
||
// Layout element layout creation | ||
ASLayout *layout = ({ | ||
ASDN::SumScopeTimer t(_layoutComputationTotalTime, measureLayoutComputation); | ||
[layoutElement layoutThatFits:constrainedSize]; | ||
}); | ||
ASDisplayNodeAssertNotNil(layout, @"[ASLayoutElement layoutThatFits:] should never return nil! %@, %@", self, layout); | ||
|
||
// Make sure layoutElementObject of the root layout is `self`, so that the flattened layout will be structurally correct. | ||
BOOL isFinalLayoutElement = (layout.layoutElement != self); | ||
if (isFinalLayoutElement) { | ||
layout.position = CGPointZero; | ||
layout = [ASLayout layoutWithLayoutElement:self size:layout.size sublayouts:@[layout]]; | ||
} | ||
ASDisplayNodeLogEvent(self, @"computedLayout: %@", layout); | ||
|
||
// PR #1157: Reduces accuracy of _unflattenedLayout for debugging/Weaver | ||
if ([ASDisplayNode shouldStoreUnflattenedLayouts]) { | ||
_unflattenedLayout = layout; | ||
} | ||
layout = [layout filteredNodeLayoutTree]; | ||
|
||
return layout; | ||
} | ||
|
||
- (id<ASLayoutElement>)_locked_layoutElementThatFits:(ASSizeRange)constrainedSize | ||
{ | ||
ASAssertLocked(__instanceLock__); | ||
|
||
BOOL measureLayoutSpec = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec; | ||
|
||
if (_layoutSpecBlock != NULL) { | ||
return ({ | ||
ASDN::MutexLocker l(__instanceLock__); | ||
ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec); | ||
_layoutSpecBlock(self, constrainedSize); | ||
}); | ||
} else { | ||
return ({ | ||
ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec); | ||
[self layoutSpecThatFits:constrainedSize]; | ||
}); | ||
} | ||
} | ||
|
||
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize | ||
{ | ||
__ASDisplayNodeCheckForLayoutMethodOverrides; | ||
|
||
ASDisplayNodeAssert(NO, @"-[ASDisplayNode layoutSpecThatFits:] should never return an empty value. One way this is caused is by calling -[super layoutSpecThatFits:] which is not currently supported."); | ||
return [[ASLayoutSpec alloc] init]; | ||
} | ||
|
||
@end |
Oops, something went wrong.