Michael Schneider 8897614f0e [Layout] Layout API based on content area (#2110)
* Initial commit for adding a size constraint to ASLayoutable's

* Some more commits

* Fix sample projects in extra/

* Remove preferredFrameSize test of ASEditableTextNode

* Remove preferredFrameSize from examples_extra

* Add deprecation warning to -[ASDisplayNode preferredFrameSize]

* Add deprecation warning to -[ASDisplayNode measureWithSizeRange:]

* Commit

* Commit

* Remove ASRelativeSizeRange

* Make ASRelativeSize private

* Adjust examples

* Improve setting of -[ASLayoutable size] with points or fractions

* Add ASWrapperLayoutSpec

* Improve creation of ASRelativeDimension

* Add `preferredFrameSize` back and add deprecated logging

* Add `layoutSpecBlock` setter and getter and add locking for it

* Add better documentation and fix macros to create ASRelativeDimension

* Create new ASSizeRangeMake with just a CGSize as parameter

* Update Kitten and Social App Layout example

* Add layoutThatFits: and deprecate measure:

* Rename ASRelativeDimension to ASDimension

* Fix examples for ASDimension renaming

* Remove fancy height and width setter

* Fix ASDimension helper

* Rename -[ASLayout layoutableObject] to -[ASLayout layoutable]

* Update layout related methods and more clearer documentation around how to use it

* Deprecate old ASLayout class constructors

* Don't unnecessary recalculate layout if constrained or parent size did not change

* Use shared pointer for ASDisplayNodeLayout

* Fix rebase conflicts

* Add documentation and move implementation in mm file of ASDisplayNodeLayout

* Fix test errors

* Rename ASSize to ASLayoutableSize

* Address comments

* Rename setSizeFromCGSize to setSizeWithCGSize

* Improve inline functions in ASDimension

* Fix rebase conflicts
2016-09-07 08:44:48 -07:00

218 lines
6.9 KiB
Plaintext

//
// ASDimension.mm
// AsyncDisplayKit
//
// 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"
#pragma mark - ASDimension
ASDimension const ASDimensionAuto = {ASDimensionUnitAuto, 0};
ASOVERLOADABLE ASDimension ASDimensionMake(NSString *dimension)
{
// Handle empty string
if (dimension.length == 0) {
return ASDimensionMake(ASDimensionUnitPoints, 0.0);
}
// Handle points
NSUInteger pointsStringLocation = [dimension rangeOfString:@"pt"].location;
if (pointsStringLocation != NSNotFound) {
// Check if points is at the end and remove it
if (pointsStringLocation == (dimension.length-2)) {
dimension = [dimension substringToIndex:(dimension.length-2)];
return ASDimensionMake(ASDimensionUnitPoints, dimension.floatValue);
}
}
// Handle fraction
NSUInteger percentStringLocation = [dimension rangeOfString:@"%"].location;
if (percentStringLocation != NSNotFound) {
// Check if percent is at the end and remove it
if (percentStringLocation == (dimension.length-1)) {
dimension = [dimension substringToIndex:(dimension.length-1)];
return ASDimensionMake(ASDimensionUnitFraction, dimension.floatValue);
}
}
// Assert as parsing went wrong
ASDisplayNodeCAssert(NO, @"Parsing dimension failed for: %@", dimension);
return ASDimensionAuto;
}
NSString *NSStringFromASDimension(ASDimension dimension)
{
switch (dimension.unit) {
case ASDimensionUnitPoints:
return [NSString stringWithFormat:@"%.0fpt", dimension.value];
case ASDimensionUnitFraction:
return [NSString stringWithFormat:@"%.0f%%", dimension.value * 100.0];
case ASDimensionUnitAuto:
return @"Auto";
}
}
#pragma mark - NSNumber+ASDimension
@implementation NSNumber (ASDimension)
- (ASDimension)as_pointDimension
{
return ASDimensionMake(ASDimensionUnitPoints, self.floatValue);
}
- (ASDimension)as_fractionDimension
{
return ASDimensionMake(ASDimensionUnitFraction, self.floatValue);
}
@end
#pragma mark - ASRelativeSize
/**
* Expresses a size with relative dimensions. Only used for calculations internally in ASDimension.h
*/
typedef struct {
ASDimension width;
ASDimension height;
} ASRelativeSize;
ASDISPLAYNODE_INLINE ASRelativeSize ASRelativeSizeMake(ASDimension width, ASDimension height)
{
ASRelativeSize size;
size.width = width;
size.height = height;
return size;
}
// ** Resolve this relative size relative to a parent size. */
ASDISPLAYNODE_INLINE CGSize ASRelativeSizeResolveSize(ASRelativeSize relativeSize, CGSize parentSize, CGSize autoSize)
{
return CGSizeMake(ASDimensionResolve(relativeSize.width, parentSize.width, autoSize.width),
ASDimensionResolve(relativeSize.height, parentSize.height, autoSize.height));
}
// ** Returns a string formatted to contain the data from an ASRelativeSize. */
ASDISPLAYNODE_INLINE NSString *NSStringFromASRelativeSize(ASRelativeSize size)
{
return [NSString stringWithFormat:@"{%@, %@}",
NSStringFromASDimension(size.width),
NSStringFromASDimension(size.height)];
}
#pragma mark - ASLayoutableSize
NSString *NSStringFromASLayoutableSize(ASLayoutableSize size)
{
return [NSString stringWithFormat:
@"<ASLayoutableSize: exact=%@, min=%@, max=%@>",
NSStringFromASRelativeSize(ASRelativeSizeMake(size.width, size.height)),
NSStringFromASRelativeSize(ASRelativeSizeMake(size.minWidth, size.minHeight)),
NSStringFromASRelativeSize(ASRelativeSizeMake(size.maxWidth, size.maxHeight))];
}
ASDISPLAYNODE_INLINE void ASLayoutableSizeConstrain(CGFloat minVal, CGFloat exactVal, CGFloat maxVal, CGFloat *outMin, CGFloat *outMax)
{
NSCAssert(!isnan(minVal), @"minVal must not be NaN");
NSCAssert(!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 ASLayoutableSizeResolveAutoSize(ASLayoutableSize size, const CGSize parentSize, ASSizeRange autoASSizeRange)
{
CGSize resolvedExact = ASRelativeSizeResolveSize(ASRelativeSizeMake(size.width, size.height), parentSize, {NAN, NAN});
CGSize resolvedMin = ASRelativeSizeResolveSize(ASRelativeSizeMake(size.minWidth, size.minHeight), parentSize, autoASSizeRange.min);
CGSize resolvedMax = ASRelativeSizeResolveSize(ASRelativeSizeMake(size.maxWidth, size.maxHeight), parentSize, autoASSizeRange.max);
CGSize rangeMin, rangeMax;
ASLayoutableSizeConstrain(resolvedMin.width, resolvedExact.width, resolvedMax.width, &rangeMin.width, &rangeMax.width);
ASLayoutableSizeConstrain(resolvedMin.height, resolvedExact.height, resolvedMax.height, &rangeMin.height, &rangeMax.height);
return {rangeMin, rangeMax};
}
#pragma mark - ASSizeRange
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}};
}
NSString *NSStringFromASSizeRange(ASSizeRange sizeRange)
{
return [NSString stringWithFormat:@"<ASSizeRange: min=%@, max=%@>",
NSStringFromCGSize(sizeRange.min),
NSStringFromCGSize(sizeRange.max)];
}
#pragma mark - Deprecated
ASSizeRange ASSizeRangeMakeExactSize(CGSize size)
{
return ASSizeRangeMake(size);
}