Skip to content

Commit

Permalink
Extract ASLayoutElement and ASLayoutElementStylability into categorie…
Browse files Browse the repository at this point in the history
…s #trivial (#131)

* Initial move of code into layout category

* Cleanup

* Some more
  • Loading branch information
maicki committed May 2, 2017
1 parent c671d2c commit b6734fa
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 163 deletions.
44 changes: 26 additions & 18 deletions Source/ASDisplayNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ extern NSInteger const ASDefaultDrawingPriority;
*
*/

@interface ASDisplayNode : NSObject <ASLayoutElement, ASLayoutElementStylability>
@interface ASDisplayNode : NSObject <ASLayoutElementFinalLayoutElement>

/** @name Initializing a node object */

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -755,6 +738,31 @@ extern NSInteger const ASDefaultDrawingPriority;

@end

@interface ASDisplayNode (ASLayoutElement) <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) <ASLayoutElementStylability>

@end

@interface ASDisplayNode (LayoutTransitioning)

/**
Expand Down
230 changes: 122 additions & 108 deletions Source/ASDisplayNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -859,40 +859,17 @@ - (void)nodeViewDidAddGestureRecognizer
+ (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 <ASLayoutElement>

- (ASLayoutElementStyle *)style
{
ASDN::MutexLocker l(__instanceLock__);
if (_style == nil) {
_style = [[ASLayoutElementStyle alloc] init];
}
return _style;
}

- (ASLayoutElementType)layoutElementType
{
return ASLayoutElementTypeDisplayNode;
}
#pragma mark <ASLayoutElementTransition>

- (BOOL)canLayoutAsynchronous
{
return !self.isNodeLoaded;
}

- (NSArray<id<ASLayoutElement>> *)sublayoutElements
{
return self.subnodes;
}

- (instancetype)styledWithBlock:(AS_NOESCAPE void (^)(__kindof ASLayoutElementStyle *style))styleBlock
{
styleBlock(self.style);
return self;
}

ASLayoutElementFinalLayoutElementDefault

#pragma mark <ASDebugNameProvider>

- (NSString *)debugName
{
ASDN::MutexLocker l(__instanceLock__);
Expand All @@ -907,47 +884,6 @@ - (void)setDebugName:(NSString *)debugName
}
}

#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<ASDisplayNodeLayout>(
[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
Expand Down Expand Up @@ -1297,7 +1233,6 @@ - (void)_locked_setCalculatedDisplayNodeLayout:(std::shared_ptr<ASDisplayNodeLay
_calculatedDisplayNodeLayout = displayNodeLayout;
}


- (CGSize)calculatedSize
{
ASDN::MutexLocker l(__instanceLock__);
Expand Down Expand Up @@ -4063,46 +3998,13 @@ - (CGRect)_frameInWindow
}
}

#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)

Expand Down Expand Up @@ -4136,23 +4038,125 @@ - (UIView *)preferredFocusedView
}
#endif

#pragma mark - Deprecated
@end

#pragma mark - ASDisplayNode (ASLayoutElement)

// This methods cannot be moved into the category ASDisplayNode (Deprecated). So they need to be declared in ASDisplayNode until removed
@implementation ASDisplayNode (ASLayoutElement)

#pragma mark <ASLayoutElement>

- (ASLayoutElementStyle *)style
{
ASDN::MutexLocker l(__instanceLock__);
if (_style == nil) {
_style = [[ASLayoutElementStyle alloc] init];
}
return _style;
}

- (ASLayoutElementType)layoutElementType
{
return ASLayoutElementTypeDisplayNode;
}

- (NSArray<id<ASLayoutElement>> *)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<ASDisplayNodeLayout>(
[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
Expand Down Expand Up @@ -4298,6 +4302,16 @@ - (CGSize)preferredFrameSize
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;
Expand Down
4 changes: 2 additions & 2 deletions Source/Details/ASTraitCollection.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

@class ASTraitCollection;
@protocol ASLayoutElement;
@protocol ASTraitEnvironment;

NS_ASSUME_NONNULL_BEGIN

Expand Down Expand Up @@ -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<ASLayoutElement> root, ASPrimitiveTraitCollection traitCollection);
extern void ASTraitCollectionPropagateDown(id<ASLayoutElement> element, ASPrimitiveTraitCollection traitCollection);

/// For backward compatibility reasons we redefine the old layout element trait collection struct name
#define ASEnvironmentTraitCollection ASPrimitiveTraitCollection
Expand All @@ -88,7 +89,6 @@ ASDISPLAYNODE_EXTERN_C_END
- (void)setPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traitCollection;

/**
* Returns an NSObject-representation of the environment's ASEnvironmentDisplayTraits
*/
- (ASTraitCollection *)asyncTraitCollection;

Expand Down
Loading

0 comments on commit b6734fa

Please sign in to comment.