Add Layout nodes.

- The code is forked from LayoutComponents in ComponentKit.
- Public interfaces are modified to be strictly Objective-C. As a result, users are not forced to switch to Objective-C++, the linker can happily compile and Swift fans can continue using the mighty ASDK.
This commit is contained in:
Huy Nguyen
2015-05-28 20:22:35 +03:00
parent 71d9f210f1
commit f8531f467d
33 changed files with 2609 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <AsyncDisplayKit/ASLayoutNode.h>
/**
Lays out a single child node, then lays out a background node behind it stretched to its size.
*/
@interface ASBackgroundLayoutNode : ASLayoutNode
/**
@param node A child that is laid out to determine the size of this node. If this is nil, then this method
returns nil.
@param background A child that is laid out behind it. May be nil, in which case the background is omitted.
*/
+ (instancetype)newWithNode:(ASLayoutNode *)node
background:(ASLayoutNode *)background;
@end

View File

@@ -0,0 +1,69 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASBackgroundLayoutNode.h"
#import "ASAssert.h"
#import "ASBaseDefines.h"
#import "ASLayoutNodeSubclass.h"
@interface ASBackgroundLayoutNode ()
{
ASLayoutNode *_node;
ASLayoutNode *_background;
}
@end
@implementation ASBackgroundLayoutNode
+ (instancetype)newWithNode:(ASLayoutNode *)node
background:(ASLayoutNode *)background
{
if (node == nil) {
return nil;
}
ASBackgroundLayoutNode *n = [super newWithSize:{}];
n->_node = node;
n->_background = background;
return n;
}
+ (instancetype)newWithSize:(ASLayoutNodeSize)size
{
ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER();
}
/**
First layout the contents, then fit the background image.
*/
- (ASLayout *)computeLayoutThatFits:(ASSizeRange)constrainedSize
restrictedToSize:(ASLayoutNodeSize)size
relativeToParentSize:(CGSize)parentSize
{
ASDisplayNodeAssert(ASLayoutNodeSizeEqualToNodeSize(size, ASLayoutNodeSizeZero),
@"ASBackgroundLayoutNode only passes size {} to the super class initializer, but received size %@ "
"(node=%@, background=%@)", NSStringFromASLayoutNodeSize(size), _node, _background);
ASLayout *contentsLayout = [_node layoutThatFits:constrainedSize parentSize:parentSize];
NSMutableArray *children = [NSMutableArray arrayWithCapacity:2];
if (_background) {
// Size background to exactly the same size.
ASLayout *backgroundLayout = [_background layoutThatFits:{contentsLayout.size, contentsLayout.size}
parentSize:contentsLayout.size];
[children addObject:[ASLayoutChild newWithPosition:{0,0} layout:backgroundLayout]];
}
[children addObject:[ASLayoutChild newWithPosition:{0,0} layout:contentsLayout]];
return [ASLayout newWithNode:self size:contentsLayout.size children:children];
}
@end

View File

@@ -0,0 +1,48 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <AsyncDisplayKit/ASLayoutNode.h>
typedef NS_OPTIONS(NSUInteger, ASCenterLayoutNodeCenteringOptions) {
/** The child is positioned in {0,0} relatively to the layout bounds */
ASCenterLayoutNodeCenteringNone = 0,
/** The child is centered along the X axis */
ASCenterLayoutNodeCenteringX = 1 << 0,
/** The child is centered along the Y axis */
ASCenterLayoutNodeCenteringY = 1 << 1,
/** Convenience option to center both along the X and Y axis */
ASCenterLayoutNodeCenteringXY = ASCenterLayoutNodeCenteringX | ASCenterLayoutNodeCenteringY
};
typedef NS_OPTIONS(NSUInteger, ASCenterLayoutNodeSizingOptions) {
/** The node will take up the maximum size possible */
ASCenterLayoutNodeSizingOptionDefault,
/** The node will take up the minimum size possible along the X axis */
ASCenterLayoutNodeSizingOptionMinimumX = 1 << 0,
/** The node will take up the minimum size possible along the Y axis */
ASCenterLayoutNodeSizingOptionMinimumY = 1 << 1,
/** Convenience option to take up the minimum size along both the X and Y axis */
ASCenterLayoutNodeSizingOptionMinimumXY = ASCenterLayoutNodeSizingOptionMinimumX | ASCenterLayoutNodeSizingOptionMinimumY,
};
/** Lays out a single child layout node and position it so that it is centered into the layout bounds. */
@interface ASCenterLayoutNode : ASLayoutNode
/**
@param centeringOptions, see ASCenterLayoutNodeCenteringOptions.
@param child The child to center.
@param size The node size or {} for the default which is for the layout to take the maximum space available.
*/
+ (instancetype)newWithCenteringOptions:(ASCenterLayoutNodeCenteringOptions)centeringOptions
sizingOptions:(ASCenterLayoutNodeSizingOptions)sizingOptions
child:(ASLayoutNode *)child
size:(ASLayoutNodeSize)size;
@end

View File

@@ -0,0 +1,80 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASCenterLayoutNode.h"
#import "ASInternalHelpers.h"
#import "ASLayoutNodeSubclass.h"
@implementation ASCenterLayoutNode
{
ASCenterLayoutNodeCenteringOptions _centeringOptions;
ASCenterLayoutNodeSizingOptions _sizingOptions;
ASLayoutNode *_child;
}
+ (instancetype)newWithCenteringOptions:(ASCenterLayoutNodeCenteringOptions)centeringOptions
sizingOptions:(ASCenterLayoutNodeSizingOptions)sizingOptions
child:(ASLayoutNode *)child
size:(ASLayoutNodeSize)size
{
ASCenterLayoutNode *n = [super newWithSize:size];
if (n) {
n->_centeringOptions = centeringOptions;
n->_sizingOptions = sizingOptions;
n->_child = child;
}
return n;
}
- (ASLayout *)computeLayoutThatFits:(ASSizeRange)constrainedSize
{
// If we have a finite size in any direction, pass this so that the child can
// resolve percentages agains it. Otherwise pass kASLayoutNodeParentDimensionUndefined
// as the size will depend on the content
CGSize size = {
isinf(constrainedSize.max.width) ? kASLayoutNodeParentDimensionUndefined : constrainedSize.max.width,
isinf(constrainedSize.max.height) ? kASLayoutNodeParentDimensionUndefined : constrainedSize.max.height
};
// Layout the child
const CGSize minChildSize = {
(_centeringOptions & ASCenterLayoutNodeCenteringX) != 0 ? 0 : constrainedSize.min.width,
(_centeringOptions & ASCenterLayoutNodeCenteringY) != 0 ? 0 : constrainedSize.min.height,
};
ASLayout *childLayout = [_child layoutThatFits:ASSizeRangeMake(minChildSize, constrainedSize.max) parentSize:size];
// If we have an undetermined height or width, use the child size to define the layout
// size
size = ASSizeRangeClamp(constrainedSize, {
isnan(size.width) ? childLayout.size.width : size.width,
isnan(size.height) ? childLayout.size.height : size.height
});
// If minimum size options are set, attempt to shrink the size to the size of the child
size = ASSizeRangeClamp(constrainedSize, {
MIN(size.width, (_sizingOptions & ASCenterLayoutNodeSizingOptionMinimumX) != 0 ? childLayout.size.width : size.width),
MIN(size.height, (_sizingOptions & ASCenterLayoutNodeSizingOptionMinimumY) != 0 ? childLayout.size.height : size.height)
});
// Compute the centered postion for the child
BOOL shouldCenterAlongX = (_centeringOptions & ASCenterLayoutNodeCenteringX);
BOOL shouldCenterAlongY = (_centeringOptions & ASCenterLayoutNodeCenteringY);
const CGPoint childPosition = {
ASRoundPixelValue(shouldCenterAlongX ? (size.width - childLayout.size.width) * 0.5f : 0),
ASRoundPixelValue(shouldCenterAlongY ? (size.height - childLayout.size.height) * 0.5f : 0)
};
return [ASLayout newWithNode:self
size:size
children:@[[ASLayoutChild newWithPosition:childPosition layout:childLayout]]];
}
@end

View File

@@ -0,0 +1,22 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <AsyncDisplayKit/ASLayoutNode.h>
@class ASDisplayNode;
@interface ASCompositeNode : ASLayoutNode
@property (nonatomic, readonly) ASDisplayNode *displayNode;
+ (instancetype)newWithSize:(ASLayoutNodeSize)size displayNode:(ASDisplayNode *)displayNode;
+ (instancetype)newWithDisplayNode:(ASDisplayNode *)displayNode;
@end

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASCompositeNode.h"
#import "ASBaseDefines.h"
#import "ASDisplayNode.h"
#import "ASLayoutNodeSubclass.h"
@implementation ASCompositeNode
+ (instancetype)newWithDisplayNode:(ASDisplayNode *)displayNode
{
return [self newWithSize:ASLayoutNodeSizeZero displayNode:displayNode];
}
+ (instancetype)newWithSize:(ASLayoutNodeSize)size displayNode:(ASDisplayNode *)displayNode
{
if (displayNode == nil) {
return nil;
}
ASCompositeNode *n = [super newWithSize:size];
if (n) {
n->_displayNode = displayNode;
}
return n;
}
+ (instancetype)newWithSize:(ASLayoutNodeSize)size
{
ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER();
}
- (ASLayout *)computeLayoutThatFits:(ASSizeRange)constrainedSize
{
CGSize measuredSize = ASSizeRangeClamp(constrainedSize, [_displayNode measure:constrainedSize.max]);
return [ASLayout newWithNode:self size:measuredSize];
}
@end

View File

@@ -0,0 +1,152 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
/**
A dimension relative to constraints to be provided in the future.
A RelativeDimension can be one of three types:
"Auto" - This indicated "I have no opinion" and may be resolved in whatever way makes most sense given
the circumstances. This is the default type.
"Points" - Just a number. It will always resolve to exactly this amount.
"Percent" - Multiplied to a provided parent amount to resolve a final amount.
*/
typedef NS_ENUM(NSInteger, ASRelativeDimensionType) {
ASRelativeDimensionTypeAuto,
ASRelativeDimensionTypePoints,
ASRelativeDimensionTypePercent,
};
typedef struct {
ASRelativeDimensionType type;
CGFloat value;
} ASRelativeDimension;
/** Expresses an inclusive range of sizes. Used to provide a simple constraint to layout. */
typedef struct {
CGSize min;
CGSize max;
} ASSizeRange;
/** Expresses a size with relative dimensions. */
typedef struct {
ASRelativeDimension width;
ASRelativeDimension height;
} ASRelativeSize;
/**
Expresses an inclusive range of relative sizes. Used to provide additional constraint to layout.
*/
typedef struct {
ASRelativeSize min;
ASRelativeSize max;
} ASRelativeSizeRange;
/** type = Auto; value = 0 */
extern ASRelativeDimension const ASRelativeDimensionAuto;
/** min = {0,0}; max = {INFINITY, INFINITY} */
extern ASSizeRange const ASSizeRangeUnconstrained;
/** width = Auto; height = Auto */
extern ASRelativeSize const ASRelativeSizeAuto;
/** min = {Auto, Auto}; max = {Auto, Auto} */
extern ASRelativeSizeRange const ASRelativeSizeRangeAuto;
ASDISPLAYNODE_EXTERN_C_BEGIN
#pragma mark ASRelativeDimension
extern ASRelativeDimension ASRelativeDimensionMake(ASRelativeDimensionType type, CGFloat value);
extern ASRelativeDimension ASRelativeDimensionMakeWithPoints(CGFloat points);
extern ASRelativeDimension ASRelativeDimensionMakeWithPercent(CGFloat percent);
extern ASRelativeDimension ASRelativeDimensionCopy(ASRelativeDimension aDimension);
extern BOOL ASRelativeDimensionEqualToDimension(ASRelativeDimension lhs, ASRelativeDimension rhs);
extern NSString *NSStringFromASRelativeDimension(ASRelativeDimension dimension);
extern CGFloat ASRelativeDimensionResolve(ASRelativeDimension dimension, CGFloat autoSize, CGFloat parent);
#pragma mark -
#pragma mark ASSizeRange
extern ASSizeRange ASSizeRangeMake(CGSize min, CGSize max);
/** Clamps the provided CGSize between the [min, max] bounds of this ASSizeRange. */
extern CGSize ASSizeRangeClamp(ASSizeRange sizeRange, CGSize size);
/**
Intersects another size range. If the other size range does not overlap in either dimension, this size range
"wins" by returning a single point within its own range that is closest to the non-overlapping range.
*/
extern ASSizeRange ASSizeRangeIntersect(ASSizeRange sizeRange, ASSizeRange otherSizeRange);
extern BOOL ASSizeRangeEqualToRange(ASSizeRange lhs, ASSizeRange rhs);
extern NSString * NSStringFromASSizeRange(ASSizeRange sizeRange);
#pragma mark -
#pragma mark ASRelativeSize
extern ASRelativeSize ASRelativeSizeMake(ASRelativeDimension width, ASRelativeDimension height);
/** Convenience constructor to provide size in Points. */
extern ASRelativeSize ASRelativeSizeMakeWithCGSize(CGSize size);
/** Resolve this relative size relative to a parent size and an auto size. */
extern CGSize ASRelativeSizeResolveSize(ASRelativeSize relativeSize, CGSize parentSize, CGSize autoSize);
extern BOOL ASRelativeSizeEqualToSize(ASRelativeSize lhs, ASRelativeSize rhs);
extern NSString *NSStringFromASRelativeSize(ASRelativeSize size);
#pragma mark -
#pragma mark ASRelativeSizeRange
extern ASRelativeSizeRange ASRelativeSizeRangeMake(ASRelativeSize min, ASRelativeSize max);
#pragma mark Convenience constructors to provide an exact size (min == max).
extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSize exact);
extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize exact);
extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth,
ASRelativeDimension exactHeight);
/**
Provided a parent size and values to use in place of Auto, compute final dimensions for this RelativeSizeRange
to arrive at a SizeRange.
*/
extern ASSizeRange ASRelativeSizeRangeResolveSizeRange(ASRelativeSizeRange relativeSizeRange,
CGSize parentSize,
ASSizeRange autoSizeRange);
/**
Provided a parent size and a default autoSizeRange, compute final dimensions for this RelativeSizeRange
to arrive at a SizeRange. As an example:
CGSize parent = {200, 120};
RelativeSizeRange rel = {Percent(0.5), Percent(2/3)}
ASRelativeSizeRangeResolve(rel, parent); // {{100, 60}, {100, 60}}
The default autoSizeRange is *everything*, meaning ASSizeRangeUnconstrained.
*/
extern ASSizeRange ASRelativeSizeRangeResolveSizeRangeWithDefaultAutoSizeRange(ASRelativeSizeRange relativeSizeRange,
CGSize parentSize);
ASDISPLAYNODE_EXTERN_C_END

View File

@@ -0,0 +1,218 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASDimension.h"
#import "ASAssert.h"
ASRelativeDimension const ASRelativeDimensionAuto = {ASRelativeDimensionTypeAuto, 0};
ASSizeRange const ASSizeRangeUnconstrained = {{0,0}, {INFINITY, INFINITY}};
ASRelativeSize const ASRelativeSizeAuto = {ASRelativeDimensionAuto, ASRelativeDimensionAuto};
ASRelativeSizeRange const ASRelativeSizeRangeAuto = {ASRelativeSizeAuto, ASRelativeSizeAuto};
#pragma mark ASRelativeDimension
ASRelativeDimension ASRelativeDimensionMake(ASRelativeDimensionType type, CGFloat value)
{
if (type == ASRelativeDimensionTypePoints) { ASDisplayNodeCAssertPositiveReal(@"Points", value); }
if (type == ASRelativeDimensionTypeAuto) { ASDisplayNodeCAssertTrue(value == 0); }
ASRelativeDimension dimension; dimension.type = type; dimension.value = value; return dimension;
}
ASRelativeDimension ASRelativeDimensionMakeWithPoints(CGFloat points)
{
return ASRelativeDimensionMake(ASRelativeDimensionTypePoints, points);
}
ASRelativeDimension ASRelativeDimensionMakeWithPercent(CGFloat percent)
{
return ASRelativeDimensionMake(ASRelativeDimensionTypePercent, percent);
}
ASRelativeDimension ASRelativeDimensionCopy(ASRelativeDimension aDimension)
{
return ASRelativeDimensionMake(aDimension.type, aDimension.value);
}
BOOL ASRelativeDimensionEqualToDimension(ASRelativeDimension lhs, ASRelativeDimension rhs)
{
// Implementation assumes that "auto" assigns '0' to value.
if (lhs.type != rhs.type) {
return false;
}
switch (lhs.type) {
case ASRelativeDimensionTypeAuto:
return true;
case ASRelativeDimensionTypePoints:
case ASRelativeDimensionTypePercent:
return lhs.value == rhs.value;
}
}
NSString *NSStringFromASRelativeDimension(ASRelativeDimension dimension)
{
switch (dimension.type) {
case ASRelativeDimensionTypeAuto:
return @"Auto";
case ASRelativeDimensionTypePoints:
return [NSString stringWithFormat:@"%.0fpt", dimension.value];
case ASRelativeDimensionTypePercent:
return [NSString stringWithFormat:@"%.0f%%", dimension.value * 100.0];
}
}
CGFloat ASRelativeDimensionResolve(ASRelativeDimension dimension, CGFloat autoSize, CGFloat parent)
{
switch (dimension.type) {
case ASRelativeDimensionTypeAuto:
return autoSize;
case ASRelativeDimensionTypePoints:
return dimension.value;
case ASRelativeDimensionTypePercent:
return round(dimension.value * parent);
}
}
#pragma mark -
#pragma mark ASSizeRange
ASSizeRange ASSizeRangeMake(CGSize min, CGSize max)
{
ASDisplayNodeCAssertPositiveReal(@"Range min width", min.width);
ASDisplayNodeCAssertPositiveReal(@"Range min height", min.height);
ASDisplayNodeCAssertInfOrPositiveReal(@"Range max width", max.width);
ASDisplayNodeCAssertInfOrPositiveReal(@"Range max height", max.height);
ASDisplayNodeCAssert(min.width <= max.width,
@"Range min width (%f) must not be larger than max width (%f).", min.width, max.width);
ASDisplayNodeCAssert(min.height <= max.height,
@"Range min height (%f) must not be larger than max height (%f).", min.height, max.height);
ASSizeRange sizeRange; sizeRange.min = min; sizeRange.max = max; return sizeRange;
}
CGSize ASSizeRangeClamp(ASSizeRange sizeRange, CGSize size)
{
return CGSizeMake(MAX(sizeRange.min.width, MIN(sizeRange.max.width, size.width)),
MAX(sizeRange.min.height, MIN(sizeRange.max.height, size.height)));
}
struct _Range {
CGFloat min;
CGFloat max;
/**
Intersects another dimension range. If the other range does not overlap, this size range "wins" by returning a
single point within its own range that is closest to the non-overlapping range.
*/
_Range intersect(const _Range &other) const
{
CGFloat newMin = MAX(min, other.min);
CGFloat newMax = MIN(max, other.max);
if (!(newMin > newMax)) {
return {newMin, newMax};
} else {
// No intersection. If we're before the other range, return our max; otherwise our min.
if (min < other.min) {
return {max, max};
} else {
return {min, min};
}
}
}
};
ASSizeRange ASSizeRangeIntersect(ASSizeRange sizeRange, ASSizeRange otherSizeRange)
{
auto w = _Range({sizeRange.min.width, sizeRange.max.width}).intersect({otherSizeRange.min.width, otherSizeRange.max.width});
auto h = _Range({sizeRange.min.height, sizeRange.max.height}).intersect({otherSizeRange.min.height, otherSizeRange.max.height});
return {{w.min, h.min}, {w.max, h.max}};
}
BOOL ASSizeRangeEqualToRange(ASSizeRange lhs, ASSizeRange rhs)
{
return CGSizeEqualToSize(lhs.min, rhs.min) && CGSizeEqualToSize(lhs.max, rhs.max);
}
NSString * NSStringFromASSizeRange(ASSizeRange sizeRange)
{
return [NSString stringWithFormat:@"<ASSizeRange: min=%@, max=%@>",
NSStringFromCGSize(sizeRange.min),
NSStringFromCGSize(sizeRange.max)];
}
#pragma mark -
#pragma mark ASRelativeSize
ASRelativeSize ASRelativeSizeMake(ASRelativeDimension width, ASRelativeDimension height)
{
ASRelativeSize size; size.width = width; size.height = height; return size;
}
ASRelativeSize ASRelativeSizeMakeWithCGSize(CGSize size)
{
return ASRelativeSizeMake(ASRelativeDimensionMakeWithPoints(size.width),
ASRelativeDimensionMakeWithPoints(size.height));
}
CGSize ASRelativeSizeResolveSize(ASRelativeSize relativeSize, CGSize parentSize, CGSize autoSize)
{
return CGSizeMake(ASRelativeDimensionResolve(relativeSize.width, autoSize.width, parentSize.width),
ASRelativeDimensionResolve(relativeSize.height, autoSize.height, parentSize.height));
}
BOOL ASRelativeSizeEqualToSize(ASRelativeSize lhs, ASRelativeSize rhs)
{
return ASRelativeDimensionEqualToDimension(lhs.width, rhs.width)
&& ASRelativeDimensionEqualToDimension(lhs.height, rhs.height);
}
NSString *NSStringFromASRelativeSize(ASRelativeSize size)
{
return [NSString stringWithFormat:@"{%@, %@}",
NSStringFromASRelativeDimension(size.width),
NSStringFromASRelativeDimension(size.height)];
}
#pragma mark -
#pragma mark ASRelativeSizeRange
ASRelativeSizeRange ASRelativeSizeRangeMake(ASRelativeSize min, ASRelativeSize max)
{
ASRelativeSizeRange sizeRange; sizeRange.min = min; sizeRange.max = max; return sizeRange;
}
ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSize exact)
{
return ASRelativeSizeRangeMake(exact, exact);
}
ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize exact)
{
return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMakeWithCGSize(exact));
}
ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth,
ASRelativeDimension exactHeight)
{
return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMake(exactWidth, exactHeight));
}
ASSizeRange ASRelativeSizeRangeResolveSizeRange(ASRelativeSizeRange relativeSizeRange,
CGSize parentSize,
ASSizeRange autoSizeRange)
{
return ASSizeRangeMake(ASRelativeSizeResolveSize(relativeSizeRange.min, parentSize, autoSizeRange.min),
ASRelativeSizeResolveSize(relativeSizeRange.max, parentSize, autoSizeRange.max));
}
ASSizeRange ASRelativeSizeRangeResolveSizeRangeWithDefaultAutoSizeRange(ASRelativeSizeRange relativeSizeRange,
CGSize parentSize)
{
return ASRelativeSizeRangeResolveSizeRange(relativeSizeRange, parentSize, ASSizeRangeUnconstrained);
}

View File

@@ -0,0 +1,39 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <AsyncDisplayKit/ASLayoutNode.h>
/**
A layout node that wraps another node, applying insets around it.
If the child node has a size specified as a percentage, the percentage is resolved against this node's parent
size **after** applying insets.
@example ASOuterLayoutNode contains an ASInsetLayoutNode with an ASInnerLayoutNode. Suppose that:
- ASOuterLayoutNode is 200pt wide.
- ASInnerLayoutNode specifies its width as 100%.
- The ASInsetLayoutNode has insets of 10pt on every side.
ASInnerLayoutNode will have size 180pt, not 200pt, because it receives a parent size that has been adjusted for insets.
If you're familiar with CSS: ASInsetLayoutNode's child behaves similarly to "box-sizing: border-box".
An infinite inset is resolved as an inset equal to all remaining space after applying the other insets and child size.
@example An ASInsetLayoutNode with an infinite left inset and 10px for all other edges will position it's child 10px from the right edge.
*/
@interface ASInsetLayoutNode : ASLayoutNode
/**
@param insets The amount of space to inset on each side.
@param node The wrapped child layout node to inset. If nil, this method returns nil.
*/
+ (instancetype)newWithInsets:(UIEdgeInsets)insets
node:(ASLayoutNode *)node;
@end

View File

@@ -0,0 +1,123 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASInsetLayoutNode.h"
#import "ASAssert.h"
#import "ASBaseDefines.h"
#import "ASInternalHelpers.h"
#import "ASLayoutNodeSubclass.h"
@interface ASInsetLayoutNode ()
{
UIEdgeInsets _insets;
ASLayoutNode *_node;
}
@end
/* Returns f if f is finite, substitute otherwise */
static CGFloat finite(CGFloat f, CGFloat substitute)
{
return isinf(f) ? substitute : f;
}
/* Returns f if f is finite, 0 otherwise */
static CGFloat finiteOrZero(CGFloat f)
{
return finite(f, 0);
}
/* Returns the inset required to center 'inner' in 'outer' */
static CGFloat centerInset(CGFloat outer, CGFloat inner)
{
return ASRoundPixelValue((outer - inner) / 2);
}
@implementation ASInsetLayoutNode
+ (instancetype)newWithInsets:(UIEdgeInsets)insets
node:(ASLayoutNode *)node
{
if (node == nil) {
return nil;
}
ASInsetLayoutNode *n = [super newWithSize:{}];
if (n) {
n->_insets = insets;
n->_node = node;
}
return n;
}
+ (instancetype)newWithSize:(ASLayoutNodeSize)size
{
ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER();
}
/**
Inset will compute a new constrained size for it's child after applying insets and re-positioning
the child to respect the inset.
*/
- (ASLayout *)computeLayoutThatFits:(ASSizeRange)constrainedSize
restrictedToSize:(ASLayoutNodeSize)size
relativeToParentSize:(CGSize)parentSize
{
ASDisplayNodeAssert(ASLayoutNodeSizeEqualToNodeSize(size, ASLayoutNodeSizeZero),
@"ASInsetLayoutNode only passes size {} to the super class initializer, but received size %@ "
"(node=%@)", NSStringFromASLayoutNodeSize(size), _node);
const CGFloat insetsX = (finiteOrZero(_insets.left) + finiteOrZero(_insets.right));
const CGFloat insetsY = (finiteOrZero(_insets.top) + finiteOrZero(_insets.bottom));
// if either x-axis inset is infinite, let child be intrinsic width
const CGFloat minWidth = (isinf(_insets.left) || isinf(_insets.right)) ? 0 : constrainedSize.min.width;
// if either y-axis inset is infinite, let child be intrinsic height
const CGFloat minHeight = (isinf(_insets.top) || isinf(_insets.bottom)) ? 0 : constrainedSize.min.height;
const ASSizeRange insetConstrainedSize = {
{
MAX(0, minWidth - insetsX),
MAX(0, minHeight - insetsY),
},
{
MAX(0, constrainedSize.max.width - insetsX),
MAX(0, constrainedSize.max.height - insetsY),
}
};
const CGSize insetParentSize = {
MAX(0, parentSize.width - insetsX),
MAX(0, parentSize.height - insetsY)
};
ASLayout *childLayout = [_node layoutThatFits:insetConstrainedSize parentSize:insetParentSize];
const CGSize computedSize = ASSizeRangeClamp(constrainedSize, {
finite(childLayout.size.width + _insets.left + _insets.right, parentSize.width),
finite(childLayout.size.height + _insets.top + _insets.bottom, parentSize.height),
});
ASDisplayNodeAssert(!isnan(computedSize.width) && !isnan(computedSize.height),
@"Inset node computed size is NaN; you may not specify infinite insets against a NaN parent size\n"
"parentSize = %@, insets = %@", NSStringFromCGSize(parentSize), NSStringFromUIEdgeInsets(_insets));
const CGFloat x = finite(_insets.left, constrainedSize.max.width -
(finite(_insets.right,
centerInset(constrainedSize.max.width, childLayout.size.width)) + childLayout.size.width));
const CGFloat y = finite(_insets.top,
constrainedSize.max.height -
(finite(_insets.bottom,
centerInset(constrainedSize.max.height, childLayout.size.height)) + childLayout.size.height));
return [ASLayout newWithNode:self
size:computedSize
children:@[[ASLayoutChild newWithPosition:{x,y} layout:childLayout]]];
}
@end

View File

@@ -0,0 +1,45 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASAssert.h>
@class ASLayoutNode;
/** Represents the computed size of a layout node, as well as the computed sizes and positions of its children. */
@interface ASLayout : NSObject
@property (nonatomic, readonly) ASLayoutNode *node;
@property (nonatomic, readonly) CGSize size;
/**
* Each item is of type ASLayoutChild.
*/
@property (nonatomic, readonly) NSArray *children;
+ (instancetype)newWithNode:(ASLayoutNode *)node size:(CGSize)size children:(NSArray *)children;
/**
* Convenience that does not have any children.
*/
+ (instancetype)newWithNode:(ASLayoutNode *)node 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

View File

@@ -0,0 +1,45 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASLayout.h"
@implementation ASLayout
+ (instancetype)newWithNode:(ASLayoutNode *)node size:(CGSize)size children:(NSArray *)children
{
ASLayout *l = [super new];
if (l) {
l->_node = node;
l->_size = size;
l->_children = [children copy];
}
return l;
}
+ (instancetype)newWithNode:(ASLayoutNode *)node size:(CGSize)size
{
return [self newWithNode:node size:size children:nil];
}
@end
@implementation ASLayoutChild
+ (instancetype)newWithPosition:(CGPoint)position layout:(ASLayout *)layout
{
ASLayoutChild *c = [super new];
if (c) {
c->_position = position;
c->_layout = layout;
}
return c;
}
@end

View File

@@ -0,0 +1,24 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <AsyncDisplayKit/ASLayoutNodeSize.h>
/** A layout node is an immutable object that describes a layout, loosely inspired by React. */
@interface ASLayoutNode : NSObject
/**
@param size A size constraint that should apply to this layout node. Pass {} to specify no size constraint.
@example A layout node of a square:
[ASLayoutNode newWithSize:{100, 100}]
*/
+ (instancetype)newWithSize:(ASLayoutNodeSize)size;
@end

View File

@@ -0,0 +1,94 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASLayoutNode.h"
#import "ASLayoutNodeSubclass.h"
#import "ASAssert.h"
#import "ASBaseDefines.h"
#import "ASInternalHelpers.h"
#import "ASLayout.h"
CGFloat const kASLayoutNodeParentDimensionUndefined = NAN;
CGSize const kASLayoutNodeParentSizeUndefined = {kASLayoutNodeParentDimensionUndefined, kASLayoutNodeParentDimensionUndefined};
@implementation ASLayoutNode
{
ASLayoutNodeSize _size;
}
#if DEBUG
+ (void)initialize
{
ASDisplayNodeConditionalAssert(self != [ASLayoutNode class],
!ASSubclassOverridesSelector([ASLayoutNode class], self, @selector(layoutThatFits:parentSize:)),
@"%@ overrides -layoutThatFits:parentSize: which is not allowed. Override -computeLayoutThatFits: "
"or -computeLayoutThatFits:restrictedToSize:relativeToParentSize: instead.",
NSStringFromClass(self));
}
#endif
+ (instancetype)newWithSize:(ASLayoutNodeSize)size
{
return [[self alloc] initWithLayoutNodeSize:size];
}
+ (instancetype)new
{
return [self newWithSize:{}];
}
- (instancetype)init
{
ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER();
}
- (instancetype)initWithLayoutNodeSize:(ASLayoutNodeSize)size
{
if (self = [super init]) {
_size = size;
}
return self;
}
#pragma mark - Layout
- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)parentSize
{
ASLayout *layout = [self computeLayoutThatFits:constrainedSize
restrictedToSize:_size
relativeToParentSize:parentSize];
ASDisplayNodeAssert(layout.node == self, @"Layout computed by %@ should return self as node, but returned %@",
[self class], [layout.node class]);
ASSizeRange resolvedRange = ASSizeRangeIntersect(constrainedSize, ASLayoutNodeSizeResolve(_size, parentSize));
ASDisplayNodeAssert(layout.size.width <= resolvedRange.max.width
&& layout.size.width >= resolvedRange.min.width
&& layout.size.height <= resolvedRange.max.height
&& layout.size.height >= resolvedRange.min.height,
@"Computed size %@ for %@ does not fall within constrained size %@",
NSStringFromCGSize(layout.size), [self class], NSStringFromASSizeRange(resolvedRange));
return layout;
}
- (ASLayout *)computeLayoutThatFits:(ASSizeRange)constrainedSize
restrictedToSize:(ASLayoutNodeSize)size
relativeToParentSize:(CGSize)parentSize
{
ASSizeRange resolvedRange = ASSizeRangeIntersect(constrainedSize, ASLayoutNodeSizeResolve(_size, parentSize));
return [self computeLayoutThatFits:resolvedRange];
}
- (ASLayout *)computeLayoutThatFits:(ASSizeRange)constrainedSize
{
return [ASLayout newWithNode:self size:constrainedSize.min];
}
@end

View File

@@ -0,0 +1,52 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <AsyncDisplayKit/ASDimension.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
/**
A struct specifying a layout node's size. Example:
ASLayoutNodeSize size = {
.width = Percent(0.5),
.maxWidth = 200,
.minHeight = Percent(0.75)
};
// <ASLayoutNodeSize: exact={50%, Auto}, min={Auto, 75%}, max={200pt, Auto}>
size.description();
*/
typedef struct {
ASRelativeDimension width;
ASRelativeDimension height;
ASRelativeDimension minWidth;
ASRelativeDimension minHeight;
ASRelativeDimension maxWidth;
ASRelativeDimension maxHeight;
} ASLayoutNodeSize;
extern ASLayoutNodeSize const ASLayoutNodeSizeZero;
ASDISPLAYNODE_EXTERN_C_BEGIN
extern ASLayoutNodeSize ASLayoutNodeSizeMakeWithCGSize(CGSize size);
extern ASLayoutNodeSize ASLayoutNodeSizeMake(CGFloat width, CGFloat height);
extern ASSizeRange ASLayoutNodeSizeResolve(ASLayoutNodeSize nodeSize, CGSize parentSize);
extern BOOL ASLayoutNodeSizeEqualToNodeSize(ASLayoutNodeSize lhs, ASLayoutNodeSize rhs);
extern NSString *NSStringFromASLayoutNodeSize(ASLayoutNodeSize nodeSize);
ASDISPLAYNODE_EXTERN_C_END

View File

@@ -0,0 +1,86 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASLayoutNodeSize.h"
#import "ASAssert.h"
ASLayoutNodeSize const ASLayoutNodeSizeZero = {};
ASLayoutNodeSize ASLayoutNodeSizeMakeWithCGSize(CGSize size)
{
return ASLayoutNodeSizeMake(size.width, size.height);
}
ASLayoutNodeSize ASLayoutNodeSizeMake(CGFloat width, CGFloat height)
{
return {ASRelativeDimensionMakeWithPoints(width), ASRelativeDimensionMakeWithPoints(height)};
}
ASDISPLAYNODE_INLINE void ASLNSConstrain(CGFloat minVal, CGFloat exactVal, CGFloat maxVal, CGFloat *outMin, CGFloat *outMax)
{
ASDisplayNodeCAssert(!isnan(minVal), @"minVal must not be NaN");
ASDisplayNodeCAssert(!isnan(maxVal), @"maxVal must not be NaN");
// Avoid use of min/max primitives since they're harder to reason
// about in the presence of NaN (in exactVal)
// Follow CSS: min overrides max overrides exact.
// Begin with the min/max range
*outMin = minVal;
*outMax = maxVal;
if (maxVal <= minVal) {
// min overrides max and exactVal is irrelevant
*outMax = minVal;
return;
}
if (isnan(exactVal)) {
// no exact value, so leave as a min/max range
return;
}
if (exactVal > maxVal) {
// clip to max value
*outMin = maxVal;
} else if (exactVal < minVal) {
// clip to min value
*outMax = minVal;
} else {
// use exact value
*outMin = *outMax = exactVal;
}
}
ASSizeRange ASLayoutNodeSizeResolve(ASLayoutNodeSize nodeSize, CGSize parentSize)
{
CGSize resolvedExact = ASRelativeSizeResolveSize(ASRelativeSizeMake(nodeSize.width, nodeSize.height), parentSize, {NAN, NAN});
CGSize resolvedMin = ASRelativeSizeResolveSize(ASRelativeSizeMake(nodeSize.minWidth, nodeSize.minHeight), parentSize, {0, 0});
CGSize resolvedMax = ASRelativeSizeResolveSize(ASRelativeSizeMake(nodeSize.maxWidth, nodeSize.maxHeight), parentSize, {INFINITY, INFINITY});
CGSize rangeMin, rangeMax;
ASLNSConstrain(resolvedMin.width, resolvedExact.width, resolvedMax.width, &rangeMin.width, &rangeMax.width);
ASLNSConstrain(resolvedMin.height, resolvedExact.height, resolvedMax.height, &rangeMin.height, &rangeMax.height);
return {rangeMin, rangeMax};
}
BOOL ASLayoutNodeSizeEqualToNodeSize(ASLayoutNodeSize lhs, ASLayoutNodeSize rhs)
{
return ASRelativeDimensionEqualToDimension(lhs.width, rhs.width)
&& ASRelativeDimensionEqualToDimension(lhs.height, rhs.height)
&& ASRelativeDimensionEqualToDimension(lhs.minWidth, rhs.minWidth)
&& ASRelativeDimensionEqualToDimension(lhs.minHeight, rhs.minHeight)
&& ASRelativeDimensionEqualToDimension(lhs.maxWidth, rhs.maxWidth)
&& ASRelativeDimensionEqualToDimension(lhs.maxHeight, rhs.maxHeight);
}
NSString *NSStringFromASLayoutNodeSize(ASLayoutNodeSize nodeSize)
{
return [NSString stringWithFormat:@"<ASLayoutNodeSize: exact=%@, min=%@, max=%@>",
NSStringFromASRelativeSize(ASRelativeSizeMake(nodeSize.width, nodeSize.height)),
NSStringFromASRelativeSize(ASRelativeSizeMake(nodeSize.minWidth, nodeSize.minHeight)),
NSStringFromASRelativeSize(ASRelativeSizeMake(nodeSize.maxWidth, nodeSize.maxHeight))];
}

View File

@@ -0,0 +1,66 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <AsyncDisplayKit/ASLayoutNode.h>
#import <AsyncDisplayKit/ASLayout.h>
@interface ASLayoutNode ()
/** A constant that indicates that the parent's size is not yet determined in a given dimension. */
extern CGFloat const kASLayoutNodeParentDimensionUndefined;
/** A constant that indicates that the parent's size is not yet determined in either dimension. */
extern CGSize const kASLayoutNodeParentSizeUndefined;
/**
Call this on children layout nodes to compute their layouts within your implementation of -computeLayoutThatFits:.
@warning You may not override this method. Override -computeLayoutThatFits: instead.
@param constrainedSize Specifies a minimum and maximum size. The receiver must choose a size that is in this range.
@param parentSize The parent layout node's size. If the parent layout node does not have a final size in a given dimension,
then it should be passed as kASLayoutNodeParentDimensionUndefined (for example, if the parent's width
depends on the child's size).
@return An ASLayout instance defining the layout of the receiver and its children.
*/
- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize
parentSize:(CGSize)parentSize;
/**
Override this method to compute your node's layout.
@discussion Why do you need to override -computeLayoutThatFits: instead of -layoutThatFits:parentSize:?
The base implementation of -layoutThatFits:parentSize: does the following for you:
1. First, it uses the parentSize parameter to resolve the node's size (the one passed into -initWithSize:).
2. Then, it intersects the resolved size with the constrainedSize parameter. If the two don't intersect,
constrainedSize wins. This allows a node to always override its childrens' sizes when computing its layout.
(The analogy for UIView: you might return a certain size from -sizeThatFits:, but a parent view can always override
that size and set your frame to any size.)
@param constrainedSize A min and max size. This is computed as described in the description. The ASLayout you
return MUST have a size between these two sizes. This is enforced by assertion.
*/
- (ASLayout *)computeLayoutThatFits:(ASSizeRange)constrainedSize;
/**
ASLayoutNode's implementation of -layoutThatFits:parentSize: calls this method to resolve the node's size
against parentSize, intersect it with constrainedSize, and call -computeLayoutThatFits: with the result.
In certain advanced cases, you may want to customize this logic. Overriding this method allows you to receive all
three parameters and do the computation yourself.
@warning Overriding this method should be done VERY rarely.
*/
- (ASLayout *)computeLayoutThatFits:(ASSizeRange)constrainedSize
restrictedToSize:(ASLayoutNodeSize)size
relativeToParentSize:(CGSize)parentSize;
@end

View File

@@ -0,0 +1,20 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <AsyncDisplayKit/ASLayoutNode.h>
/**
This node lays out a single node and then overlays a node on top of it streched to its size
*/
@interface ASOverlayLayoutNode : ASLayoutNode
+ (instancetype)newWithNode:(ASLayoutNode *)node overlay:(ASLayoutNode *)overlay;
@end

View File

@@ -0,0 +1,62 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASOverlayLayoutNode.h"
#import "ASAssert.h"
#import "ASBaseDefines.h"
#import "ASLayoutNodeSubclass.h"
@implementation ASOverlayLayoutNode
{
ASLayoutNode *_overlay;
ASLayoutNode *_node;
}
+ (instancetype)newWithNode:(ASLayoutNode *)node
overlay:(ASLayoutNode *)overlay
{
ASOverlayLayoutNode *n = [super newWithSize:{}];
if (n) {
ASDisplayNodeAssertNotNil(node, @"Node that will be overlayed on shouldn't be nil");
n->_overlay = overlay;
n->_node = node;
}
return n;
}
+ (instancetype)newWithSize:(ASLayoutNodeSize)size
{
ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER();
}
/**
First layout the contents, then fit the overlay on top of it.
*/
- (ASLayout *)computeLayoutThatFits:(ASSizeRange)constrainedSize
restrictedToSize:(ASLayoutNodeSize)size
relativeToParentSize:(CGSize)parentSize
{
ASDisplayNodeAssert(ASLayoutNodeSizeEqualToNodeSize(size, ASLayoutNodeSizeZero),
@"ASOverlayLayoutNode only passes size {} to the super class initializer, but received size %@ "
"(node=%@, overlay=%@)", NSStringFromASLayoutNodeSize(size), _node, _overlay);
ASLayout *contentsLayout = [_node layoutThatFits:constrainedSize parentSize:parentSize];
NSMutableArray *layoutChildren = [NSMutableArray arrayWithObject:[ASLayoutChild newWithPosition:{0, 0} layout:contentsLayout]];
if (_overlay) {
ASLayout *overlayLayout = [_overlay layoutThatFits:{contentsLayout.size, contentsLayout.size} parentSize:contentsLayout.size];
[layoutChildren addObject:[ASLayoutChild newWithPosition:{0, 0} layout:overlayLayout]];
}
return [ASLayout newWithNode:self size:contentsLayout.size children:layoutChildren];
}
@end

View File

@@ -0,0 +1,37 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <AsyncDisplayKit/ASLayoutNode.h>
/**
Ratio layout node
For when the content should respect a certain inherent ratio but can be scaled (think photos or videos)
The ratio passed is the ratio of height / width you expect
For a ratio 0.5, the node will have a flat rectangle shape
_ _ _ _
| |
|_ _ _ _|
For a ratio 2.0, the node will be twice as tall as it is wide
_ _
| |
| |
| |
|_ _|
**/
@interface ASRatioLayoutNode : ASLayoutNode
+ (instancetype)newWithRatio:(CGFloat)ratio
size:(ASLayoutNodeSize)size
node:(ASLayoutNode *)node;
@end

View File

@@ -0,0 +1,81 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASRatioLayoutNode.h"
#import <algorithm>
#import <vector>
#import "ASAssert.h"
#import "ASBaseDefines.h"
#import "ASLayoutNodeSubclass.h"
#import "ASInternalHelpers.h"
@implementation ASRatioLayoutNode
{
CGFloat _ratio;
ASLayoutNode *_node;
}
+ (instancetype)newWithRatio:(CGFloat)ratio
size:(ASLayoutNodeSize)size
node:(ASLayoutNode *)node
{
ASDisplayNodeAssert(ratio > 0, @"Ratio should be strictly positive, but received %f", ratio);
if (ratio <= 0) {
return nil;
}
ASRatioLayoutNode *n = [super newWithSize:size];
if (n) {
n->_ratio = ratio;
n->_node = node;
}
return n;
}
+ (instancetype)newWithSize:(ASLayoutNodeSize)size
{
ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER();
}
- (ASLayout *)computeLayoutThatFits:(ASSizeRange)constrainedSize
{
std::vector<CGSize> sizeOptions;
if (!isinf(constrainedSize.max.width)) {
sizeOptions.push_back(ASSizeRangeClamp(constrainedSize, {
constrainedSize.max.width,
ASFloorPixelValue(_ratio * constrainedSize.max.width)
}));
}
if (!isinf(constrainedSize.max.height)) {
sizeOptions.push_back(ASSizeRangeClamp(constrainedSize, {
ASFloorPixelValue(constrainedSize.max.height / _ratio),
constrainedSize.max.height
}));
}
// Choose the size closest to the desired ratio.
const auto &bestSize = std::max_element(sizeOptions.begin(), sizeOptions.end(), [&](const CGSize &a, const CGSize &b){
return fabs((a.height / a.width) - _ratio) > fabs((b.height / b.width) - _ratio);
});
// 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);
const CGSize parentSize = (bestSize == sizeOptions.end()) ? kASLayoutNodeParentSizeUndefined : *bestSize;
ASLayout *childLayout = [_node layoutThatFits:childRange parentSize:parentSize];
return [ASLayout newWithNode:self
size:childLayout.size
children:@[[ASLayoutChild newWithPosition:{0, 0} layout:childLayout]]];
}
@end

View File

@@ -0,0 +1,147 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <AsyncDisplayKit/ASLayoutNode.h>
typedef NS_ENUM(NSUInteger, ASStackLayoutDirection) {
ASStackLayoutDirectionVertical,
ASStackLayoutDirectionHorizontal,
};
/** If no children are flexible, how should this node justify its children in the available space? */
typedef NS_ENUM(NSUInteger, ASStackLayoutJustifyContent) {
/**
On overflow, children overflow out of this node's bounds on the right/bottom side.
On underflow, children are left/top-aligned within this node's bounds.
*/
ASStackLayoutJustifyContentStart,
/**
On overflow, children are centered and overflow on both sides.
On underflow, children are centered within this node's bounds in the stacking direction.
*/
ASStackLayoutJustifyContentCenter,
/**
On overflow, children overflow out of this node's bounds on the left/top side.
On underflow, children are right/bottom-aligned within this node's bounds.
*/
ASStackLayoutJustifyContentEnd,
};
typedef NS_ENUM(NSUInteger, ASStackLayoutAlignItems) {
/** Align children to start of cross axis */
ASStackLayoutAlignItemsStart,
/** Align children with end of cross axis */
ASStackLayoutAlignItemsEnd,
/** Center children on cross axis */
ASStackLayoutAlignItemsCenter,
/** Expand children to fill cross axis */
ASStackLayoutAlignItemsStretch,
};
/**
Each child may override their parent stack's cross axis alignment.
@see ASStackLayoutNodeAlignItems
*/
typedef NS_ENUM(NSUInteger, ASStackLayoutAlignSelf) {
/** Inherit alignment value from containing stack. */
ASStackLayoutAlignSelfAuto,
ASStackLayoutAlignSelfStart,
ASStackLayoutAlignSelfEnd,
ASStackLayoutAlignSelfCenter,
ASStackLayoutAlignSelfStretch,
};
typedef struct {
/** Specifies the direction children are stacked in. */
ASStackLayoutDirection direction;
/** The amount of space between each child. */
CGFloat spacing;
/** How children are aligned if there are no flexible children. */
ASStackLayoutJustifyContent justifyContent;
/** Orientation of children along cross axis */
ASStackLayoutAlignItems alignItems;
} ASStackLayoutNodeStyle;
@class ASMutableStackLayoutNodeChild;
@interface ASStackLayoutNodeChild : NSObject <NSCopying, NSMutableCopying>
@property (nonatomic, readonly) ASLayoutNode *node;
/** Additional space to place before the node in the stacking direction. */
@property (nonatomic, readonly) CGFloat spacingBefore;
/** Additional space to place after the node in the stacking direction. */
@property (nonatomic, readonly) CGFloat spacingAfter;
/** If the sum of childrens' stack dimensions is less than the minimum size, should this node grow? */
@property (nonatomic, readonly) BOOL flexGrow;
/** If the sum of childrens' stack dimensions is greater than the maximum size, should this node shrink? */
@property (nonatomic, readonly) BOOL flexShrink;
/** Specifies the initial size in the stack dimension for the child. */
@property (nonatomic, readonly) ASRelativeDimension flexBasis;
/** Orientation of the child along cross axis, overriding alignItems */
@property (nonatomic, readonly) ASStackLayoutAlignSelf alignSelf;
+(instancetype)newWithInitializer:(void(^)(ASMutableStackLayoutNodeChild *mutableChild))initializer;
@end
/** A mutable stack layout node child intended for configuration. */
@interface ASMutableStackLayoutNodeChild : ASStackLayoutNodeChild
/** A read-write version of ASStackLayoutNodeChild node property */
@property (nonatomic, readwrite) ASLayoutNode *node;
/** A read-write version of ASStackLayoutNodeChild spacingBefore property */
@property (nonatomic, readwrite) CGFloat spacingBefore;
/** A read-write version of ASStackLayoutNodeChild spacingAfter property */
@property (nonatomic, readwrite) CGFloat spacingAfter;
/** A read-write version of ASStackLayoutNodeChild flexGrow property */
@property (nonatomic, readwrite) BOOL flexGrow;
/** A read-write version of ASStackLayoutNodeChild flexShrink property */
@property (nonatomic, readwrite) BOOL flexShrink;
/** A read-write version of ASStackLayoutNodeChild flexBasis property */
@property (nonatomic, readwrite) ASRelativeDimension flexBasis;
/** A read-write version of ASStackLayoutNodeChild alignSelf property */
@property (nonatomic, readwrite) ASStackLayoutAlignSelf alignSelf;
@end
/**
A simple layout node that stacks a list of children vertically or horizontally.
- All children are initially laid out with the an infinite available size in the stacking direction.
- In the other direction, this node's constraint is passed.
- The children's sizes are summed in the stacking direction.
- If this sum is less than this node's minimum size in stacking direction, children with flexGrow are flexed.
- If it is greater than this node's maximum size in the stacking direction, children with flexShrink are flexed.
- If, even after flexing, the sum is still greater than this node's maximum size in the stacking direction,
justifyContent determines how children are laid out.
For example:
- Suppose stacking direction is Vertical, min-width=100, max-width=300, min-height=200, max-height=500.
- All children are laid out with min-width=100, max-width=300, min-height=0, max-height=INFINITY.
- If the sum of the childrens' heights is less than 200, nodes with flexGrow are flexed larger.
- If the sum of the childrens' heights is greater than 500, nodes with flexShrink are flexed smaller.
Each node is shrunk by `((sum of heights) - 500)/(number of nodes)`.
- If the sum of the childrens' heights is greater than 500 even after flexShrink-able nodes are flexed,
justifyContent determines how children are laid out.
*/
@interface ASStackLayoutNode : ASLayoutNode
/**
@param size A size, or {} for the default size.
@param style Specifies how children are laid out.
@param children Children to be positioned, each is of type ASStackLayoutNodeChild.
*/
+ (instancetype)newWithSize:(ASLayoutNodeSize)size
style:(ASStackLayoutNodeStyle)style
children:(NSArray *)children;
@end

View File

@@ -0,0 +1,133 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASStackLayoutNode.h"
#import <numeric>
#import <vector>
#import "ASBaseDefines.h"
#import "ASInternalHelpers.h"
#import "ASLayoutNodeUtilities.h"
#import "ASLayoutNodeSubclass.h"
#import "ASStackLayoutNodeUtilities.h"
#import "ASStackPositionedLayout.h"
#import "ASStackUnpositionedLayout.h"
@implementation ASMutableStackLayoutNodeChild
@synthesize node, spacingBefore, spacingAfter, flexGrow, flexShrink, flexBasis, alignSelf;
@end
@implementation ASStackLayoutNodeChild
- (instancetype)initWithNode:(ASLayoutNode *)node
spacingBefore:(CGFloat)spacingBefore
spacingAfter:(CGFloat)spacingAfter
flexGrow:(BOOL)flexGrow
flexShrink:(BOOL)flexShrink
flexBasis:(ASRelativeDimension)flexBasis
alignSelf:(ASStackLayoutAlignSelf)alignSelf
{
if (node == nil)
return nil;
if (self = [super init]) {
_node = node;
_spacingBefore = spacingBefore;
_spacingAfter = spacingAfter;
_flexGrow = flexGrow;
_flexShrink = flexShrink;
_flexBasis = flexBasis;
_alignSelf = alignSelf;
}
return self;
}
- (id)copyWithZone:(NSZone *)zone
{
if ([self isKindOfClass:[ASMutableStackLayoutNodeChild class]]) {
return [[ASStackLayoutNodeChild alloc] initWithNode:self.node
spacingBefore:self.spacingBefore
spacingAfter:self.spacingAfter
flexGrow:self.flexGrow
flexShrink:self.flexShrink
flexBasis:self.flexBasis
alignSelf:self.alignSelf];
} else {
return self;
}
}
- (id)mutableCopyWithZone:(NSZone *)zone
{
ASMutableStackLayoutNodeChild *mutableChild = [[ASMutableStackLayoutNodeChild alloc] init];
mutableChild.node = self.node;
mutableChild.spacingBefore = self.spacingBefore;
mutableChild.spacingAfter = self.spacingAfter;
mutableChild.flexGrow = self.flexGrow;
mutableChild.flexShrink = self.flexShrink;
mutableChild.flexBasis = self.flexBasis;
mutableChild.alignSelf = self.alignSelf;
return mutableChild;
}
+ (instancetype)newWithInitializer:(void (^)(ASMutableStackLayoutNodeChild *))initializer
{
ASStackLayoutNodeChild *c = [super new];
if (c && initializer) {
ASMutableStackLayoutNodeChild *mutableChild = [[ASMutableStackLayoutNodeChild alloc] init];
initializer(mutableChild);
c = [mutableChild copy];
}
return c;
}
@end
@implementation ASStackLayoutNode
{
ASStackLayoutNodeStyle _style;
std::vector<ASStackLayoutNodeChild *> _children;
}
+ (instancetype)newWithSize:(ASLayoutNodeSize)size style:(ASStackLayoutNodeStyle)style children:(NSArray *)children
{
ASStackLayoutNode *n = [super newWithSize:size];
if (n) {
n->_style = style;
n->_children = std::vector<ASStackLayoutNodeChild *>();
for (ASStackLayoutNodeChild *child in children) {
if (child.node != nil) {
n->_children.push_back(child);
}
}
}
return n;
}
+ (instancetype)newWithSize:(ASLayoutNodeSize)size
{
ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER();
}
- (ASLayout *)computeLayoutThatFits:(ASSizeRange)constrainedSize
{
const auto unpositionedLayout = ASStackUnpositionedLayout::compute(_children, _style, constrainedSize);
const auto positionedLayout = ASStackPositionedLayout::compute(unpositionedLayout, _style, constrainedSize);
const CGSize finalSize = directionSize(_style.direction, unpositionedLayout.stackDimensionSum, positionedLayout.crossSize);
NSArray *children = [NSArray arrayWithObjects:&positionedLayout.children[0] count:positionedLayout.children.size()];
return [ASLayout newWithNode:self
size:ASSizeRangeClamp(constrainedSize, finalSize)
children:children];
}
@end

View File

@@ -0,0 +1,54 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <AsyncDisplayKit/ASLayoutNode.h>
@interface ASStaticLayoutNodeChild : NSObject
@property (nonatomic, readonly) CGPoint position;
@property (nonatomic, readonly) ASLayoutNode *node;
/**
If specified, the node's size is restricted according to this size. Percentages are resolved relative to the
static layout node.
*/
@property (nonatomic, readonly) ASRelativeSizeRange size;
+ (instancetype)newWithPosition:(CGPoint)position node:(ASLayoutNode *)node size:(ASRelativeSizeRange)size;
/**
Convenience with default size is Auto in both dimensions, which sets the child's min size to zero
and max size to the maximum available space it can consume without overflowing the node's bounds.
*/
+ (instancetype)newWithPosition:(CGPoint)position node:(ASLayoutNode *)node;
@end
/*
A layout node that positions children at fixed positions.
Computes a size that is the union of all childrens' frames.
*/
@interface ASStaticLayoutNode : ASLayoutNode
/**
@param children Children to be positioned at fixed positions, each is of type ASStaticLayoutNodeChild.
*/
+ (instancetype)newWithSize:(ASLayoutNodeSize)size
children:(NSArray *)children;
/**
Convenience that does not have a size.
@param children Children to be positioned at fixed positions, each is of type ASStaticLayoutNodeChild.
*/
+ (instancetype)newWithChildren:(NSArray *)children;
@end

View File

@@ -0,0 +1,93 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASStaticLayoutNode.h"
#import "ASLayoutNodeUtilities.h"
#import "ASLayoutNodeSubclass.h"
#import "ASInternalHelpers.h"
@implementation ASStaticLayoutNodeChild
+ (instancetype)newWithPosition:(CGPoint)position node:(ASLayoutNode *)node size:(ASRelativeSizeRange)size
{
ASStaticLayoutNodeChild *c = [super new];
if (c) {
c->_position = position;
c->_node = node;
c->_size = size;
}
return c;
}
+ (instancetype)newWithPosition:(CGPoint)position node:(ASLayoutNode *)node
{
return [self newWithPosition:position node:node size:{}];
}
@end
@implementation ASStaticLayoutNode
{
NSArray *_children;
}
+ (instancetype)newWithSize:(ASLayoutNodeSize)size
children:(NSArray *)children
{
ASStaticLayoutNode *n = [super newWithSize:size];
if (n) {
n->_children = children;
}
return n;
}
+ (instancetype)newWithChildren:(NSArray *)children
{
return [self newWithSize:{} children:children];
}
- (ASLayout *)computeLayoutThatFits:(ASSizeRange)constrainedSize
{
CGSize size = {
isinf(constrainedSize.max.width) ? kASLayoutNodeParentDimensionUndefined : constrainedSize.max.width,
isinf(constrainedSize.max.height) ? kASLayoutNodeParentDimensionUndefined : constrainedSize.max.height
};
NSMutableArray *layoutChildren = [NSMutableArray arrayWithCapacity:_children.count];
for (ASStaticLayoutNodeChild *child in _children) {
CGSize autoMaxSize = {
constrainedSize.max.width - child.position.x,
constrainedSize.max.height - child.position.y
};
ASSizeRange childConstraint = ASRelativeSizeRangeResolveSizeRange(child.size, size, {{0,0}, autoMaxSize});
ASLayoutChild *layoutChild = [ASLayoutChild newWithPosition:child.position
layout:[child.node layoutThatFits:childConstraint parentSize: size]];
[layoutChildren addObject:layoutChild];
}
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);
}
}
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);
}
}
return [ASLayout newWithNode:self size:ASSizeRangeClamp(constrainedSize, size) children:layoutChildren];
}
@end

View File

@@ -0,0 +1,28 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <UIKit/UIKit.h>
#import "ASBaseDefines.h"
@class ASLayoutChild;
ASDISPLAYNODE_EXTERN_C_BEGIN
BOOL ASSubclassOverridesSelector(Class superclass, Class subclass, SEL selector);
CGFloat ASScreenScale();
CGFloat ASFloorPixelValue(CGFloat f);
CGFloat ASCeilPixelValue(CGFloat f);
CGFloat ASRoundPixelValue(CGFloat f);
ASDISPLAYNODE_EXTERN_C_END

View File

@@ -0,0 +1,63 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASInternalHelpers.h"
#import <functional>
#import <objc/runtime.h>
#import "ASLayout.h"
BOOL ASSubclassOverridesSelector(Class superclass, Class subclass, SEL selector)
{
Method superclassMethod = class_getInstanceMethod(superclass, selector);
Method subclassMethod = class_getInstanceMethod(subclass, selector);
IMP superclassIMP = superclassMethod ? method_getImplementation(superclassMethod) : NULL;
IMP subclassIMP = subclassMethod ? method_getImplementation(subclassMethod) : NULL;
return (superclassIMP != subclassIMP);
}
static void ASDispatchOnceOnMainThread(dispatch_once_t *predicate, dispatch_block_t block)
{
if ([NSThread isMainThread]) {
dispatch_once(predicate, block);
} else {
if (DISPATCH_EXPECT(*predicate == 0L, NO)) {
dispatch_sync(dispatch_get_main_queue(), ^{
dispatch_once(predicate, block);
});
}
}
}
CGFloat ASScreenScale()
{
static CGFloat _scale;
static dispatch_once_t onceToken;
ASDispatchOnceOnMainThread(&onceToken, ^{
_scale = [UIScreen mainScreen].scale;
});
return _scale;
}
CGFloat ASFloorPixelValue(CGFloat f)
{
return floorf(f * ASScreenScale()) / ASScreenScale();
}
CGFloat ASCeilPixelValue(CGFloat f)
{
return ceilf(f * ASScreenScale()) / ASScreenScale();
}
CGFloat ASRoundPixelValue(CGFloat f)
{
return roundf(f * ASScreenScale()) / ASScreenScale();
}

View File

@@ -0,0 +1,105 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <algorithm>
#import <functional>
#import <type_traits>
#import <vector>
#import <UIKit/UIKit.h>
namespace AS {
// adopted from http://stackoverflow.com/questions/14945223/map-function-with-c11-constructs
// Takes an iterable, applies a function to every element,
// and returns a vector of the results
//
template <typename T, typename Func>
auto map(const T &iterable, Func &&func) -> std::vector<decltype(func(std::declval<typename T::value_type>()))>
{
// Some convenience type definitions
typedef decltype(func(std::declval<typename T::value_type>())) value_type;
typedef std::vector<value_type> result_type;
// Prepares an output vector of the appropriate size
result_type res(iterable.size());
// Let std::transform apply `func` to all elements
// (use perfect forwarding for the function object)
std::transform(
begin(iterable), end(iterable), res.begin(),
std::forward<Func>(func)
);
return res;
}
template<typename Func>
auto map(id<NSFastEnumeration> collection, Func &&func) -> std::vector<decltype(func(std::declval<id>()))>
{
std::vector<decltype(func(std::declval<id>()))> to;
for (id obj in collection) {
to.push_back(func(obj));
}
return to;
}
template <typename T, typename Func>
auto filter(const T &iterable, Func &&func) -> std::vector<typename T::value_type>
{
std::vector<typename T::value_type> to;
for (auto obj : iterable) {
if (func(obj)) {
to.push_back(obj);
}
}
return to;
}
};
inline CGPoint operator+(const CGPoint &p1, const CGPoint &p2)
{
return { p1.x + p2.x, p1.y + p2.y };
}
inline CGPoint operator-(const CGPoint &p1, const CGPoint &p2)
{
return { p1.x - p2.x, p1.y - p2.y };
}
inline CGSize operator+(const CGSize &s1, const CGSize &s2)
{
return { s1.width + s2.width, s1.height + s2.height };
}
inline CGSize operator-(const CGSize &s1, const CGSize &s2)
{
return { s1.width - s2.width, s1.height - s2.height };
}
inline UIEdgeInsets operator+(const UIEdgeInsets &e1, const UIEdgeInsets &e2)
{
return { e1.top + e2.top, e1.left + e2.left, e1.bottom + e2.bottom, e1.right + e2.right };
}
inline UIEdgeInsets operator-(const UIEdgeInsets &e1, const UIEdgeInsets &e2)
{
return { e1.top - e2.top, e1.left - e2.left, e1.bottom - e2.bottom, e1.right - e2.right };
}
inline UIEdgeInsets operator*(const UIEdgeInsets &e1, const UIEdgeInsets &e2)
{
return { e1.top * e2.top, e1.left * e2.left, e1.bottom * e2.bottom, e1.right * e2.right };
}
inline UIEdgeInsets operator-(const UIEdgeInsets &e)
{
return { -e.top, -e.left, -e.bottom, -e.right };
}

View File

@@ -0,0 +1,62 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASStackLayoutNode.h"
inline CGFloat stackDimension(const ASStackLayoutDirection direction, const CGSize size)
{
return (direction == ASStackLayoutDirectionVertical) ? size.height : size.width;
}
inline CGFloat crossDimension(const ASStackLayoutDirection direction, const CGSize size)
{
return (direction == ASStackLayoutDirectionVertical) ? size.width : size.height;
}
inline BOOL compareCrossDimension(const ASStackLayoutDirection direction, const CGSize a, const CGSize b)
{
return crossDimension(direction, a) < crossDimension(direction, b);
}
inline CGPoint directionPoint(const ASStackLayoutDirection direction, const CGFloat stack, const CGFloat cross)
{
return (direction == ASStackLayoutDirectionVertical) ? CGPointMake(cross, stack) : CGPointMake(stack, cross);
}
inline CGSize directionSize(const ASStackLayoutDirection direction, const CGFloat stack, const CGFloat cross)
{
return (direction == ASStackLayoutDirectionVertical) ? CGSizeMake(cross, stack) : CGSizeMake(stack, cross);
}
inline ASSizeRange directionSizeRange(const ASStackLayoutDirection direction,
const CGFloat stackMin,
const CGFloat stackMax,
const CGFloat crossMin,
const CGFloat crossMax)
{
return {directionSize(direction, stackMin, crossMin), directionSize(direction, stackMax, crossMax)};
}
inline ASStackLayoutAlignItems alignment(ASStackLayoutAlignSelf childAlignment, ASStackLayoutAlignItems stackAlignment)
{
switch (childAlignment) {
case ASStackLayoutAlignSelfCenter:
return ASStackLayoutAlignItemsCenter;
case ASStackLayoutAlignSelfEnd:
return ASStackLayoutAlignItemsEnd;
case ASStackLayoutAlignSelfStart:
return ASStackLayoutAlignItemsStart;
case ASStackLayoutAlignSelfStretch:
return ASStackLayoutAlignItemsStretch;
case ASStackLayoutAlignSelfAuto:
default:
return stackAlignment;
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASLayout.h"
#import "ASDimension.h"
#import "ASStackLayoutNode.h"
#import "ASStackUnpositionedLayout.h"
/** Represents a set of laid out and positioned stack layout children. */
struct ASStackPositionedLayout {
const std::vector<ASLayoutChild *> children;
const CGFloat crossSize;
/** Given an unpositioned layout, computes the positions each child should be placed at. */
static ASStackPositionedLayout compute(const ASStackUnpositionedLayout &unpositionedLayout,
const ASStackLayoutNodeStyle &style,
const ASSizeRange &constrainedSize);
};

View File

@@ -0,0 +1,75 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASStackPositionedLayout.h"
#import "ASInternalHelpers.h"
#import "ASLayoutNodeUtilities.h"
#import "ASStackLayoutNodeUtilities.h"
static CGFloat crossOffset(const ASStackLayoutNodeStyle &style,
const ASStackUnpositionedItem &l,
const CGFloat crossSize)
{
switch (alignment(l.child.alignSelf, style.alignItems)) {
case ASStackLayoutAlignItemsEnd:
return crossSize - crossDimension(style.direction, l.layout.size);
case ASStackLayoutAlignItemsCenter:
return ASFloorPixelValue((crossSize - crossDimension(style.direction, l.layout.size)) / 2);
case ASStackLayoutAlignItemsStart:
case ASStackLayoutAlignItemsStretch:
return 0;
}
}
static ASStackPositionedLayout stackedLayout(const ASStackLayoutNodeStyle &style,
const CGFloat offset,
const ASStackUnpositionedLayout &unpositionedLayout,
const ASSizeRange &constrainedSize)
{
// The cross dimension is the max of the childrens' cross dimensions (clamped to our constraint below).
const auto it = std::max_element(unpositionedLayout.items.begin(), unpositionedLayout.items.end(),
[&](const ASStackUnpositionedItem &a, const ASStackUnpositionedItem &b){
return compareCrossDimension(style.direction, a.layout.size, b.layout.size);
});
const auto largestChildCrossSize = it == unpositionedLayout.items.end() ? 0 : crossDimension(style.direction, it->layout.size);
const auto minCrossSize = crossDimension(style.direction, constrainedSize.min);
const auto maxCrossSize = crossDimension(style.direction, constrainedSize.max);
const CGFloat crossSize = MIN(MAX(minCrossSize, largestChildCrossSize), maxCrossSize);
CGPoint p = directionPoint(style.direction, offset, 0);
BOOL first = YES;
auto stackedChildren = AS::map(unpositionedLayout.items, [&](const ASStackUnpositionedItem &l) -> ASLayoutChild *{
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];
p = p + directionPoint(style.direction, stackDimension(style.direction, l.layout.size) + l.child.spacingAfter, 0);
return c;
});
return {stackedChildren, crossSize};
}
ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnpositionedLayout &unpositionedLayout,
const ASStackLayoutNodeStyle &style,
const ASSizeRange &constrainedSize)
{
switch (style.justifyContent) {
case ASStackLayoutJustifyContentStart:
return stackedLayout(style, 0, unpositionedLayout, constrainedSize);
case ASStackLayoutJustifyContentCenter:
return stackedLayout(style, floorf(unpositionedLayout.violation / 2), unpositionedLayout, constrainedSize);
case ASStackLayoutJustifyContentEnd:
return stackedLayout(style, unpositionedLayout.violation, unpositionedLayout, constrainedSize);
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <vector>
#import "ASLayout.h"
#import "ASStackLayoutNode.h"
struct ASStackUnpositionedItem {
/** The original source child. */
ASStackLayoutNodeChild *child;
/** The proposed layout. */
ASLayout *layout;
};
/** Represents a set of stack layout children that have their final layout computed, but are not yet positioned. */
struct ASStackUnpositionedLayout {
/** A set of proposed child layouts, not yet positioned. */
const std::vector<ASStackUnpositionedItem> items;
/** The total size of the children in the stack dimension, including all spacing. */
const CGFloat stackDimensionSum;
/** The amount by which stackDimensionSum violates constraints. If positive, less than min; negative, greater than max. */
const CGFloat violation;
/** Given a set of children, computes the unpositioned layouts for those children. */
static ASStackUnpositionedLayout compute(const std::vector<ASStackLayoutNodeChild *> &children,
const ASStackLayoutNodeStyle &style,
const ASSizeRange &sizeRange);
};

View File

@@ -0,0 +1,351 @@
/*
* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASStackUnpositionedLayout.h"
#import <numeric>
#import "ASLayoutNodeUtilities.h"
#import "ASLayoutNodeSubclass.h"
#import "ASStackLayoutNodeUtilities.h"
/**
Sizes the child given the parameters specified, and returns the computed layout.
@param size the size of the stack layout node. May be undefined in either or both directions.
*/
static ASLayout *crossChildLayout(const ASStackLayoutNodeChild *child,
const ASStackLayoutNodeStyle style,
const CGFloat stackMin,
const CGFloat stackMax,
const CGFloat crossMin,
const CGFloat crossMax,
const CGSize size)
{
const ASStackLayoutAlignItems alignItems = alignment(child.alignSelf, style.alignItems);
// stretched children will have a cross dimension of at least crossMin
const CGFloat childCrossMin = alignItems == ASStackLayoutAlignItemsStretch ? crossMin : 0;
const ASSizeRange childSizeRange = directionSizeRange(style.direction, stackMin, stackMax, childCrossMin, crossMax);
return [child.node layoutThatFits:childSizeRange parentSize:size];
}
/**
Stretches children to lay out along the cross axis according to the alignment stretch settings of the children
(child.alignSelf), and the stack layout's alignment settings (style.alignItems). This does not do the actual alignment
of the items once stretched though; ASStackPositionedLayout will do centering etc.
Finds the maximum cross dimension among child layouts. If that dimension exceeds the minimum cross layout size then
we must stretch any children whose alignItems specify ASStackLayoutAlignItemsStretch.
The diagram below shows 3 children in a horizontal stack. The second child is larger than the minCrossDimension, so
its height is used as the childCrossMax. Any children that are stretchable (which may be all children if
style.alignItems specifies stretch) like the first child must be stretched to match that maximum. All children must be
at least minCrossDimension in cross dimension size, which is shown by the sizing of the third child.
Stack Dimension
+--------------------->
+ +-+-------------+-+-------------+--+---------------+ + + +
| | child. | | | | | | | |
| | alignSelf | | | | | | | |
Cross | | = stretch | | | +-------+-------+ | | |
Dimension | +-----+-------+ | | | | | | | |
| | | | | | | | | |
| | | | | v | | | |
v +-+- - - - - - -+-+ - - - - - - +--+- - - - - - - -+ | | + minCrossDimension
| | | | |
| v | | | | |
+- - - - - - -+ +-------------+ | + childCrossMax
|
+--------------------------------------------------+ + crossMax
@param layouts pre-computed child layouts; modified in-place as needed
@param style the layout style of the overall stack layout
@param size the size of the stack layout node. May be undefined in either or both directions.
*/
static void stretchChildrenAlongCrossDimension(std::vector<ASStackUnpositionedItem> &layouts,
const ASStackLayoutNodeStyle &style,
const CGSize size)
{
// Find the maximum cross dimension size among child layouts
const auto it = std::max_element(layouts.begin(), layouts.end(),
[&](const ASStackUnpositionedItem &a, const ASStackUnpositionedItem &b) {
return compareCrossDimension(style.direction, a.layout.size, b.layout.size);
});
const CGFloat childCrossMax = it == layouts.end() ? 0 : crossDimension(style.direction, it->layout.size);
for (auto &l : layouts) {
const ASStackLayoutAlignItems alignItems = alignment(l.child.alignSelf, style.alignItems);
const CGFloat cross = crossDimension(style.direction, l.layout.size);
const CGFloat stack = stackDimension(style.direction, l.layout.size);
// restretch all stretchable children along the cross axis using the new min. set their max size to childCrossMax,
// not crossMax, so that if any of them would choose a larger size just because the min size increased (weird!)
// they are forced to choose the same width as all the other nodes.
if (alignItems == ASStackLayoutAlignItemsStretch && fabs(cross - childCrossMax) > 0.01) {
l.layout = crossChildLayout(l.child, style, stack, stack, childCrossMax, childCrossMax, size);
}
}
}
/**
Computes the consumed stack dimension length for the given vector of children and stacking style.
stackDimensionSum
<----------------------->
+-----+ +-------+ +---+
| | | | | |
| | | | | |
+-----+ | | +---+
+-------+
@param children unpositioned layouts for the child nodes of the stack node
@param style the layout style of the overall stack layout
*/
static CGFloat computeStackDimensionSum(const std::vector<ASStackUnpositionedItem> &children,
const ASStackLayoutNodeStyle &style)
{
// Sum up the childrens' spacing
const CGFloat childSpacingSum = std::accumulate(children.begin(), children.end(),
// Start from default spacing between each child:
children.empty() ? 0 : style.spacing * (children.size() - 1),
[&](CGFloat x, const ASStackUnpositionedItem &l) {
return x + l.child.spacingBefore + l.child.spacingAfter;
});
// Sum up the childrens' dimensions (including spacing) in the stack direction.
const CGFloat childStackDimensionSum = std::accumulate(children.begin(), children.end(), childSpacingSum,
[&](CGFloat x, const ASStackUnpositionedItem &l) {
return x + stackDimension(style.direction, l.layout.size);
});
return childStackDimensionSum;
}
/**
Computes the violation by comparing a stack dimension sum with the overall allowable size range for the stack.
Violation is the distance you would have to add to the unbounded stack-direction length of the stack node's
children in order to bring the stack within its allowed sizeRange. The diagram below shows 3 horizontal stacks with
the different types of violation.
sizeRange
|------------|
+------+ +-------+ +-------+ +---------+
| | | | | | | | | |
| | | | | | | | (zero violation)
| | | | | | | | | |
+------+ +-------+ +-------+ +---------+
| |
+------+ +-------+ +-------+
| | | | | | | |
| | | | | |<--> (positive violation)
| | | | | | | |
+------+ +-------+ +-------+
| |<------> (negative violation)
+------+ +-------+ +-------+ +---------+ +-----------+
| | | | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | | | |
+------+ +-------+ +-------+ +---------+ +-----------+
@param stackDimensionSum the consumed length of the children in the stack along the stack dimension
@param style layout style to be applied to all children
@param sizeRange the range of allowable sizes for the stack layout node
*/
static CGFloat computeViolation(const CGFloat stackDimensionSum,
const ASStackLayoutNodeStyle &style,
const ASSizeRange &sizeRange)
{
const CGFloat minStackDimension = stackDimension(style.direction, sizeRange.min);
const CGFloat maxStackDimension = stackDimension(style.direction, sizeRange.max);
if (stackDimensionSum < minStackDimension) {
return minStackDimension - stackDimensionSum;
} else if (stackDimensionSum > maxStackDimension) {
return maxStackDimension - stackDimensionSum;
}
return 0;
}
/** The threshold that determines if a violation has actually occurred. */
static const CGFloat kViolationEpsilon = 0.01;
/**
Returns a lambda that determines if the given unpositioned item's child is flexible in the direction of the violation.
@param violation the amount that the stack layout violates its size range. See header for sign interpretation.
*/
static std::function<BOOL(const ASStackUnpositionedItem &)> isFlexibleInViolationDirection(const CGFloat violation)
{
if (fabs(violation) < kViolationEpsilon) {
return [](const ASStackUnpositionedItem &l) { return NO; };
} else if (violation > 0) {
return [](const ASStackUnpositionedItem &l) { return l.child.flexGrow; };
} else {
return [](const ASStackUnpositionedItem &l) { return l.child.flexShrink; };
}
}
ASDISPLAYNODE_INLINE BOOL isFlexibleInBothDirections(ASStackLayoutNodeChild *child)
{
return child.flexGrow && child.flexShrink;
}
/**
If we have a single flexible (both shrinkable and growable) child, and our allowed size range is set to a specific
number then we may avoid the first "intrinsic" size calculation.
*/
ASDISPLAYNODE_INLINE BOOL useOptimizedFlexing(const std::vector<ASStackLayoutNodeChild *> &children,
const ASStackLayoutNodeStyle &style,
const ASSizeRange &sizeRange)
{
const NSUInteger flexibleChildren = std::count_if(children.begin(), children.end(), isFlexibleInBothDirections);
return ((flexibleChildren == 1)
&& (stackDimension(style.direction, sizeRange.min) ==
stackDimension(style.direction, sizeRange.max)));
}
/**
The flexible children may have been left not laid out in the initial layout pass, so we may have to go through and size
these children at zero size so that the children layouts are at least present.
*/
static void layoutFlexibleChildrenAtZeroSize(std::vector<ASStackUnpositionedItem> &items,
const ASStackLayoutNodeStyle &style,
const ASSizeRange &sizeRange,
const CGSize size)
{
for (ASStackUnpositionedItem &item : items) {
if (isFlexibleInBothDirections(item.child)) {
item.layout = crossChildLayout(item.child,
style,
0,
0,
crossDimension(style.direction, sizeRange.min),
crossDimension(style.direction, sizeRange.max),
size);
}
}
}
/**
Flexes children in the stack axis to resolve a min or max stack size violation. First, determines which children are
flexible (see computeViolation and isFlexibleInViolationDirection). Then computes how much to flex each flexible child
and performs re-layout. Note that there may still be a non-zero violation even after flexing.
The actual CSS flexbox spec describes an iterative looping algorithm here, which may be adopted in t5837937:
http://www.w3.org/TR/css3-flexbox/#resolve-flexible-lengths
@param items Reference to unpositioned items from the original, unconstrained layout pass; modified in-place
@param style layout style to be applied to all children
@param sizeRange the range of allowable sizes for the stack layout node
@param size Size of the stack layout node. May be undefined in either or both directions.
*/
static void flexChildrenAlongStackDimension(std::vector<ASStackUnpositionedItem> &items,
const ASStackLayoutNodeStyle &style,
const ASSizeRange &sizeRange,
const CGSize size,
const BOOL useOptimizedFlexing)
{
const CGFloat stackDimensionSum = computeStackDimensionSum(items, style);
const CGFloat violation = computeViolation(stackDimensionSum, style, sizeRange);
// We count the number of children which are flexible in the direction of the violation
std::function<BOOL(const ASStackUnpositionedItem &)> isFlex = isFlexibleInViolationDirection(violation);
const NSUInteger flexibleChildren = std::count_if(items.begin(), items.end(), isFlex);
if (flexibleChildren == 0) {
// If optimized flexing was used then we have to clean up the unsized children, and lay them out at zero size
if (useOptimizedFlexing) {
layoutFlexibleChildrenAtZeroSize(items, style, sizeRange, size);
}
return;
}
// Each flexible child along the direction of the violation is expanded or contracted equally
const CGFloat violationPerFlexChild = floorf(violation / flexibleChildren);
// If the floor operation above left a remainder we may have a remainder after deducting the adjustments from all the
// contributions of the flexible children.
const CGFloat violationRemainder = violation - (violationPerFlexChild * flexibleChildren);
BOOL isFirstFlex = YES;
for (ASStackUnpositionedItem &item : items) {
if (isFlex(item)) {
const CGFloat originalStackSize = stackDimension(style.direction, item.layout.size);
// The first flexible child is given the additional violation remainder
const CGFloat flexedStackSize = originalStackSize + violationPerFlexChild + (isFirstFlex ? violationRemainder : 0);
item.layout = crossChildLayout(item.child,
style,
MAX(flexedStackSize, 0),
MAX(flexedStackSize, 0),
crossDimension(style.direction, sizeRange.min),
crossDimension(style.direction, sizeRange.max),
size);
isFirstFlex = NO;
}
}
}
/**
Performs the first unconstrained layout of the children, generating the unpositioned items that are then flexed and
stretched.
*/
static std::vector<ASStackUnpositionedItem> layoutChildrenAlongUnconstrainedStackDimension(const std::vector<ASStackLayoutNodeChild *> &children,
const ASStackLayoutNodeStyle &style,
const ASSizeRange &sizeRange,
const CGSize size,
const BOOL useOptimizedFlexing)
{
const CGFloat minCrossDimension = crossDimension(style.direction, sizeRange.min);
const CGFloat maxCrossDimension = crossDimension(style.direction, sizeRange.max);
return AS::map(children, [&](ASStackLayoutNodeChild *child) -> ASStackUnpositionedItem {
if (useOptimizedFlexing && isFlexibleInBothDirections(child)) {
return { child, [ASLayout newWithNode:child.node size:{0, 0}] };
} else {
return {
child,
crossChildLayout(child,
style,
ASRelativeDimensionResolve(child.flexBasis, 0, stackDimension(style.direction, size)),
ASRelativeDimensionResolve(child.flexBasis, INFINITY, stackDimension(style.direction, size)),
minCrossDimension,
maxCrossDimension,
size)
};
}
});
}
ASStackUnpositionedLayout ASStackUnpositionedLayout::compute(const std::vector<ASStackLayoutNodeChild *> &children,
const ASStackLayoutNodeStyle &style,
const ASSizeRange &sizeRange)
{
// If we have a fixed size in either dimension, pass it to children so they can resolve percentages against it.
// Otherwise, we pass kASLayoutNodeParentDimensionUndefined since it will depend on the content.
const CGSize size = {
(sizeRange.min.width == sizeRange.max.width) ? sizeRange.min.width : kASLayoutNodeParentDimensionUndefined,
(sizeRange.min.height == sizeRange.max.height) ? sizeRange.min.height : kASLayoutNodeParentDimensionUndefined,
};
// We may be able to avoid some redundant layout passes
const BOOL optimizedFlexing = useOptimizedFlexing(children, style, sizeRange);
// We do a first pass of all the children, generating an unpositioned layout for each with an unbounded range along
// the stack dimension. This allows us to compute the "intrinsic" size of each child and find the available violation
// which determines whether we must grow or shrink the flexible children.
std::vector<ASStackUnpositionedItem> items = layoutChildrenAlongUnconstrainedStackDimension(children,
style,
sizeRange,
size,
optimizedFlexing);
flexChildrenAlongStackDimension(items, style, sizeRange, size, optimizedFlexing);
stretchChildrenAlongCrossDimension(items, style, size);
const CGFloat stackDimensionSum = computeStackDimensionSum(items, style);
return {items, stackDimensionSum, computeViolation(stackDimensionSum, style, sizeRange)};
}