diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index cf4959662c..acab769ed1 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1426,8 +1426,8 @@ static NSInteger incrementIfFound(NSInteger i) { subnodeSize.width, subnodeSize.height); } - for (ASLayoutChild *child in context.layout.children) { - stack.push({child.layout, context.absolutePosition + child.position, NO}); + for (ASLayout *child in context.layout.children) { + stack.push({child, context.absolutePosition + child.position, NO}); } } } diff --git a/AsyncDisplayKit/Layout/ASBackgroundLayoutNode.mm b/AsyncDisplayKit/Layout/ASBackgroundLayoutNode.mm index 84eb52d150..1939a5aee6 100644 --- a/AsyncDisplayKit/Layout/ASBackgroundLayoutNode.mm +++ b/AsyncDisplayKit/Layout/ASBackgroundLayoutNode.mm @@ -44,14 +44,16 @@ - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize { ASLayout *contentsLayout = [_child calculateLayoutThatFits:constrainedSize]; + contentsLayout.position = CGPointZero; NSMutableArray *children = [NSMutableArray arrayWithCapacity:2]; if (_background) { // Size background to exactly the same size. ASLayout *backgroundLayout = [_background calculateLayoutThatFits:{contentsLayout.size, contentsLayout.size}]; - [children addObject:[ASLayoutChild newWithPosition:{0,0} layout:backgroundLayout]]; + backgroundLayout.position = CGPointZero; + [children addObject:backgroundLayout]; } - [children addObject:[ASLayoutChild newWithPosition:{0,0} layout:contentsLayout]]; + [children addObject:contentsLayout]; return [ASLayout newWithLayoutableObject:self size:contentsLayout.size children:children]; } diff --git a/AsyncDisplayKit/Layout/ASCenterLayoutNode.mm b/AsyncDisplayKit/Layout/ASCenterLayoutNode.mm index 1aab9f26f4..4371d79b55 100644 --- a/AsyncDisplayKit/Layout/ASCenterLayoutNode.mm +++ b/AsyncDisplayKit/Layout/ASCenterLayoutNode.mm @@ -67,14 +67,12 @@ // Compute the centered postion for the child BOOL shouldCenterAlongX = (_centeringOptions & ASCenterLayoutNodeCenteringX); BOOL shouldCenterAlongY = (_centeringOptions & ASCenterLayoutNodeCenteringY); - const CGPoint childPosition = { + childLayout.position = { ASRoundPixelValue(shouldCenterAlongX ? (size.width - childLayout.size.width) * 0.5f : 0), ASRoundPixelValue(shouldCenterAlongY ? (size.height - childLayout.size.height) * 0.5f : 0) }; - return [ASLayout newWithLayoutableObject:self - size:size - children:@[[ASLayoutChild newWithPosition:childPosition layout:childLayout]]]; + return [ASLayout newWithLayoutableObject:self size:size children:@[childLayout]]; } @end diff --git a/AsyncDisplayKit/Layout/ASInsetLayoutNode.mm b/AsyncDisplayKit/Layout/ASInsetLayoutNode.mm index a6d9540c52..023447b96e 100644 --- a/AsyncDisplayKit/Layout/ASInsetLayoutNode.mm +++ b/AsyncDisplayKit/Layout/ASInsetLayoutNode.mm @@ -99,9 +99,10 @@ static CGFloat centerInset(CGFloat outer, CGFloat inner) constrainedSize.max.height - (finite(_insets.bottom, centerInset(constrainedSize.max.height, childLayout.size.height)) + childLayout.size.height)); - return [ASLayout newWithLayoutableObject:self - size:computedSize - children:@[[ASLayoutChild newWithPosition:{x,y} layout:childLayout]]]; + + childLayout.position = CGPointMake(x, y); + + return [ASLayout newWithLayoutableObject:self size:computedSize children:@[childLayout]]; } @end diff --git a/AsyncDisplayKit/Layout/ASLayout.h b/AsyncDisplayKit/Layout/ASLayout.h index 2dea9515d8..9cd482f835 100644 --- a/AsyncDisplayKit/Layout/ASLayout.h +++ b/AsyncDisplayKit/Layout/ASLayout.h @@ -12,35 +12,45 @@ #import #import -/** Represents the computed size of a layout node, as well as the computed sizes and positions of its children. */ +extern CGPoint const CGPointNull; + +extern BOOL CGPointIsNull(CGPoint point); + +/** Represents a computed immutable layout tree. */ @interface ASLayout : NSObject @property (nonatomic, readonly) id layoutableObject; @property (nonatomic, readonly) CGSize size; +/** + * Position parent (if any). Default to CGPointNull. + * + * @discussion Before being used as a child layout, this property must be set and no longer equal CGPointNull. + * + * @discussion Unlike all other properties, this property is read-write because often by initializaion time, it has yet been determined. + * To enforce immutability, this property can be set once and only once. + * + */ +@property (nonatomic, readwrite) CGPoint position; /** - * Each item is of type ASLayoutChild. + * Array of ASLayout children. Each child must have a valid non-null position. */ @property (nonatomic, readonly) NSArray *children; + (instancetype)newWithLayoutableObject:(id)layoutableObject size:(CGSize)size + position:(CGPoint)position children:(NSArray *)children; /** - * Convenience that does not have any children. + * Convenience that has CGPointNull position. + */ ++ (instancetype)newWithLayoutableObject:(id)layoutableObject + size:(CGSize)size + children:(NSArray *)children; + +/** + * Convenience that has CGPointNull position and no children. */ + (instancetype)newWithLayoutableObject:(id)layoutableObject size:(CGSize)size; @end - -@interface ASLayoutChild : NSObject - -@property (nonatomic, readonly) CGPoint position; -@property (nonatomic, readonly) ASLayout *layout; - -/** - * Designated initializer - */ -+ (instancetype)newWithPosition:(CGPoint)position layout:(ASLayout *)layout; - -@end diff --git a/AsyncDisplayKit/Layout/ASLayout.mm b/AsyncDisplayKit/Layout/ASLayout.mm index 8f320a2807..46a343b1f3 100644 --- a/AsyncDisplayKit/Layout/ASLayout.mm +++ b/AsyncDisplayKit/Layout/ASLayout.mm @@ -9,39 +9,53 @@ */ #import "ASLayout.h" +#import "ASAssert.h" + +CGPoint const CGPointNull = {NAN, NAN}; + +extern BOOL CGPointIsNull(CGPoint point) +{ + return isnan(point.x) && isnan(point.y); +} @implementation ASLayout + (instancetype)newWithLayoutableObject:(id)layoutableObject size:(CGSize)size + position:(CGPoint)position children:(NSArray *)children { + for (ASLayout *child in children) { + ASDisplayNodeAssert(!CGPointIsNull(child.position), @"Invalid position is not allowed in children."); + } + ASLayout *l = [super new]; if (l) { l->_layoutableObject = layoutableObject; l->_size = size; + l->_position = position; l->_children = [children copy]; } return l; } ++ (instancetype)newWithLayoutableObject:(id)layoutableObject + size:(CGSize)size + children:(NSArray *)children +{ + return [self newWithLayoutableObject:layoutableObject size:size position:CGPointNull children:children]; +} + + (instancetype)newWithLayoutableObject:(id)layoutableObject size:(CGSize)size { return [self newWithLayoutableObject:layoutableObject size:size children:nil]; } -@end - -@implementation ASLayoutChild - -+ (instancetype)newWithPosition:(CGPoint)position layout:(ASLayout *)layout +- (void)setPosition:(CGPoint)position { - ASLayoutChild *c = [super new]; - if (c) { - c->_position = position; - c->_layout = layout; - } - return c; + ASDisplayNodeAssert(CGPointIsNull(_position), @"Position can be set once and only once."); + ASDisplayNodeAssert(!CGPointIsNull(position), @"Position must not be set to null."); + _position = position; } @end diff --git a/AsyncDisplayKit/Layout/ASOverlayLayoutNode.mm b/AsyncDisplayKit/Layout/ASOverlayLayoutNode.mm index dfa8e1cdcf..87b7216ec0 100644 --- a/AsyncDisplayKit/Layout/ASOverlayLayoutNode.mm +++ b/AsyncDisplayKit/Layout/ASOverlayLayoutNode.mm @@ -41,10 +41,12 @@ - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize { ASLayout *contentsLayout = [_child calculateLayoutThatFits:constrainedSize]; - NSMutableArray *layoutChildren = [NSMutableArray arrayWithObject:[ASLayoutChild newWithPosition:{0, 0} layout:contentsLayout]]; + contentsLayout.position = CGPointZero; + NSMutableArray *layoutChildren = [NSMutableArray arrayWithObject:contentsLayout]; if (_overlay) { ASLayout *overlayLayout = [_overlay calculateLayoutThatFits:{contentsLayout.size, contentsLayout.size}]; - [layoutChildren addObject:[ASLayoutChild newWithPosition:{0, 0} layout:overlayLayout]]; + overlayLayout.position = CGPointZero; + [layoutChildren addObject:overlayLayout]; } return [ASLayout newWithLayoutableObject:self size:contentsLayout.size children:layoutChildren]; diff --git a/AsyncDisplayKit/Layout/ASRatioLayoutNode.mm b/AsyncDisplayKit/Layout/ASRatioLayoutNode.mm index ca6a0d645b..d7579bba75 100644 --- a/AsyncDisplayKit/Layout/ASRatioLayoutNode.mm +++ b/AsyncDisplayKit/Layout/ASRatioLayoutNode.mm @@ -68,9 +68,8 @@ // If there is no max size in *either* dimension, we can't apply the ratio, so just pass our size range through. const ASSizeRange childRange = (bestSize == sizeOptions.end()) ? constrainedSize : ASSizeRangeMake(*bestSize, *bestSize); ASLayout *childLayout = [_child calculateLayoutThatFits:childRange]; - return [ASLayout newWithLayoutableObject:self - size:childLayout.size - children:@[[ASLayoutChild newWithPosition:{0, 0} layout:childLayout]]]; + childLayout.position = CGPointZero; + return [ASLayout newWithLayoutableObject:self size:childLayout.size children:@[childLayout]]; } @end diff --git a/AsyncDisplayKit/Layout/ASStaticLayoutNode.mm b/AsyncDisplayKit/Layout/ASStaticLayoutNode.mm index 58033d7a24..b46f895729 100644 --- a/AsyncDisplayKit/Layout/ASStaticLayoutNode.mm +++ b/AsyncDisplayKit/Layout/ASStaticLayoutNode.mm @@ -68,22 +68,22 @@ ASSizeRange childConstraint = ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRangeUnconstrained, child.size) ? ASSizeRangeMake({0, 0}, autoMaxSize) : ASRelativeSizeRangeResolve(child.size, size); - ASLayoutChild *layoutChild = [ASLayoutChild newWithPosition:child.position - layout:[child.node calculateLayoutThatFits:childConstraint]]; - [layoutChildren addObject:layoutChild]; + ASLayout *childLayout = [child.node calculateLayoutThatFits:childConstraint]; + childLayout.position = child.position; + [layoutChildren addObject:childLayout]; } if (isnan(size.width)) { size.width = constrainedSize.min.width; - for (ASLayoutChild *child in layoutChildren) { - size.width = MAX(size.width, child.position.x + child.layout.size.width); + for (ASLayout *child in layoutChildren) { + size.width = MAX(size.width, child.position.x + child.size.width); } } if (isnan(size.height)) { size.height = constrainedSize.min.height; - for (ASLayoutChild *child in layoutChildren) { - size.height = MAX(size.height, child.position.y + child.layout.size.height); + for (ASLayout *child in layoutChildren) { + size.height = MAX(size.height, child.position.y + child.size.height); } } diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.h b/AsyncDisplayKit/Private/ASStackPositionedLayout.h index d04a175c84..2741e548df 100644 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.h +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.h @@ -15,7 +15,7 @@ /** Represents a set of laid out and positioned stack layout children. */ struct ASStackPositionedLayout { - const std::vector children; + const std::vector children; const CGFloat crossSize; /** Given an unpositioned layout, computes the positions each child should be placed at. */ diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm index 9362b76611..790e7c45d2 100644 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm @@ -47,16 +47,15 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutNodeStyle &style CGPoint p = directionPoint(style.direction, offset, 0); BOOL first = YES; - auto stackedChildren = AS::map(unpositionedLayout.items, [&](const ASStackUnpositionedItem &l) -> ASLayoutChild *{ + auto stackedChildren = AS::map(unpositionedLayout.items, [&](const ASStackUnpositionedItem &l) -> ASLayout *{ p = p + directionPoint(style.direction, l.child.spacingBefore, 0); if (!first) { p = p + directionPoint(style.direction, style.spacing, 0); } first = NO; - ASLayoutChild *c = [ASLayoutChild newWithPosition:p + directionPoint(style.direction, 0, crossOffset(style, l, crossSize)) - layout:l.layout]; + l.layout.position = p + directionPoint(style.direction, 0, crossOffset(style, l, crossSize)); p = p + directionPoint(style.direction, stackDimension(style.direction, l.layout.size) + l.child.spacingAfter, 0); - return c; + return l.layout; }); return {stackedChildren, crossSize}; }