mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 14:20:20 +00:00
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:
26
AsyncDisplayKit/Layout/ASBackgroundLayoutNode.h
Normal file
26
AsyncDisplayKit/Layout/ASBackgroundLayoutNode.h
Normal 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
|
||||||
69
AsyncDisplayKit/Layout/ASBackgroundLayoutNode.mm
Normal file
69
AsyncDisplayKit/Layout/ASBackgroundLayoutNode.mm
Normal 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
|
||||||
48
AsyncDisplayKit/Layout/ASCenterLayoutNode.h
Normal file
48
AsyncDisplayKit/Layout/ASCenterLayoutNode.h
Normal 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
|
||||||
80
AsyncDisplayKit/Layout/ASCenterLayoutNode.mm
Normal file
80
AsyncDisplayKit/Layout/ASCenterLayoutNode.mm
Normal 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
|
||||||
22
AsyncDisplayKit/Layout/ASCompositeNode.h
Normal file
22
AsyncDisplayKit/Layout/ASCompositeNode.h
Normal 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
|
||||||
48
AsyncDisplayKit/Layout/ASCompositeNode.mm
Normal file
48
AsyncDisplayKit/Layout/ASCompositeNode.mm
Normal 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
|
||||||
152
AsyncDisplayKit/Layout/ASDimension.h
Normal file
152
AsyncDisplayKit/Layout/ASDimension.h
Normal 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
|
||||||
218
AsyncDisplayKit/Layout/ASDimension.mm
Normal file
218
AsyncDisplayKit/Layout/ASDimension.mm
Normal 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);
|
||||||
|
}
|
||||||
39
AsyncDisplayKit/Layout/ASInsetLayoutNode.h
Normal file
39
AsyncDisplayKit/Layout/ASInsetLayoutNode.h
Normal 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
|
||||||
123
AsyncDisplayKit/Layout/ASInsetLayoutNode.mm
Normal file
123
AsyncDisplayKit/Layout/ASInsetLayoutNode.mm
Normal 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
|
||||||
45
AsyncDisplayKit/Layout/ASLayout.h
Normal file
45
AsyncDisplayKit/Layout/ASLayout.h
Normal 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
|
||||||
45
AsyncDisplayKit/Layout/ASLayout.mm
Normal file
45
AsyncDisplayKit/Layout/ASLayout.mm
Normal 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
|
||||||
24
AsyncDisplayKit/Layout/ASLayoutNode.h
Normal file
24
AsyncDisplayKit/Layout/ASLayoutNode.h
Normal 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
|
||||||
94
AsyncDisplayKit/Layout/ASLayoutNode.mm
Normal file
94
AsyncDisplayKit/Layout/ASLayoutNode.mm
Normal 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
|
||||||
52
AsyncDisplayKit/Layout/ASLayoutNodeSize.h
Normal file
52
AsyncDisplayKit/Layout/ASLayoutNodeSize.h
Normal 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
|
||||||
86
AsyncDisplayKit/Layout/ASLayoutNodeSize.mm
Normal file
86
AsyncDisplayKit/Layout/ASLayoutNodeSize.mm
Normal 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))];
|
||||||
|
}
|
||||||
66
AsyncDisplayKit/Layout/ASLayoutNodeSubclass.h
Normal file
66
AsyncDisplayKit/Layout/ASLayoutNodeSubclass.h
Normal 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
|
||||||
20
AsyncDisplayKit/Layout/ASOverlayLayoutNode.h
Normal file
20
AsyncDisplayKit/Layout/ASOverlayLayoutNode.h
Normal 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
|
||||||
62
AsyncDisplayKit/Layout/ASOverlayLayoutNode.mm
Normal file
62
AsyncDisplayKit/Layout/ASOverlayLayoutNode.mm
Normal 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
|
||||||
37
AsyncDisplayKit/Layout/ASRatioLayoutNode.h
Normal file
37
AsyncDisplayKit/Layout/ASRatioLayoutNode.h
Normal 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
|
||||||
81
AsyncDisplayKit/Layout/ASRatioLayoutNode.mm
Normal file
81
AsyncDisplayKit/Layout/ASRatioLayoutNode.mm
Normal 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
|
||||||
147
AsyncDisplayKit/Layout/ASStackLayoutNode.h
Normal file
147
AsyncDisplayKit/Layout/ASStackLayoutNode.h
Normal 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
|
||||||
133
AsyncDisplayKit/Layout/ASStackLayoutNode.mm
Normal file
133
AsyncDisplayKit/Layout/ASStackLayoutNode.mm
Normal 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
|
||||||
54
AsyncDisplayKit/Layout/ASStaticLayoutNode.h
Normal file
54
AsyncDisplayKit/Layout/ASStaticLayoutNode.h
Normal 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
|
||||||
93
AsyncDisplayKit/Layout/ASStaticLayoutNode.mm
Normal file
93
AsyncDisplayKit/Layout/ASStaticLayoutNode.mm
Normal 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
|
||||||
28
AsyncDisplayKit/Private/ASInternalHelpers.h
Normal file
28
AsyncDisplayKit/Private/ASInternalHelpers.h
Normal 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
|
||||||
63
AsyncDisplayKit/Private/ASInternalHelpers.mm
Normal file
63
AsyncDisplayKit/Private/ASInternalHelpers.mm
Normal 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();
|
||||||
|
}
|
||||||
105
AsyncDisplayKit/Private/ASLayoutNodeUtilities.h
Normal file
105
AsyncDisplayKit/Private/ASLayoutNodeUtilities.h
Normal 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 };
|
||||||
|
}
|
||||||
|
|
||||||
62
AsyncDisplayKit/Private/ASStackLayoutNodeUtilities.h
Normal file
62
AsyncDisplayKit/Private/ASStackLayoutNodeUtilities.h
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
AsyncDisplayKit/Private/ASStackPositionedLayout.h
Normal file
25
AsyncDisplayKit/Private/ASStackPositionedLayout.h
Normal 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);
|
||||||
|
};
|
||||||
75
AsyncDisplayKit/Private/ASStackPositionedLayout.mm
Normal file
75
AsyncDisplayKit/Private/ASStackPositionedLayout.mm
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
36
AsyncDisplayKit/Private/ASStackUnpositionedLayout.h
Normal file
36
AsyncDisplayKit/Private/ASStackUnpositionedLayout.h
Normal 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);
|
||||||
|
};
|
||||||
351
AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm
Normal file
351
AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm
Normal 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)};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user