-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ASCornerLayoutSpec] New layout spec class for declarative corner element layout. #657
Changes from 4 commits
d4f358f
043a2c5
6fe4524
66df920
69d941d
1c441ae
4db2e07
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// | ||
// ASCornerLayoutSpec.h | ||
// Texture | ||
// | ||
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
|
||
#import <AsyncDisplayKit/ASLayoutSpec.h> | ||
|
||
/** | ||
The corner location for positioning corner element. | ||
*/ | ||
typedef NS_ENUM(NSInteger, ASCornerLayoutLocation) { | ||
ASCornerLayoutLocationTopLeft, | ||
ASCornerLayoutLocationTopRight, | ||
ASCornerLayoutLocationBottomLeft, | ||
ASCornerLayoutLocationBottomRight, | ||
}; | ||
|
||
NS_ASSUME_NONNULL_BEGIN | ||
|
||
/** | ||
A layout spec that positions a corner element which relatives to the child element. | ||
|
||
@warning Both child element and corner element must have valid preferredSize for layout calculation. | ||
*/ | ||
@interface ASCornerLayoutSpec : ASLayoutSpec | ||
|
||
/** | ||
A layout spec that positions a corner element which relatives to the child element. | ||
|
||
@param child A child that is laid out to determine the size of this spec. | ||
@param corner A layoutElement object that is laid out to a corner on the child. | ||
@param location The corner position option. | ||
@return An ASCornerLayoutSpec object with a given child and an layoutElement that act as corner. | ||
*/ | ||
- (instancetype)initWithChild:(id <ASLayoutElement>)child corner:(id <ASLayoutElement>)corner location:(ASCornerLayoutLocation)location AS_WARN_UNUSED_RESULT; | ||
|
||
/** | ||
A layout spec that positions a corner element which relatives to the child element. | ||
|
||
@param child A child that is laid out to determine the size of this spec. | ||
@param corner A layoutElement object that is laid out to a corner on the child. | ||
@param location The corner position option. | ||
@return An ASCornerLayoutSpec object with a given child and an layoutElement that act as corner. | ||
*/ | ||
+ (instancetype)cornerLayoutSpecWithChild:(id <ASLayoutElement>)child corner:(id <ASLayoutElement>)corner location:(ASCornerLayoutLocation)location AS_WARN_UNUSED_RESULT; | ||
|
||
/** | ||
A layoutElement object that is laid out to a corner on the child. | ||
*/ | ||
@property (nonatomic, strong) id <ASLayoutElement> corner; | ||
|
||
/** | ||
The corner position option. | ||
*/ | ||
@property (nonatomic, assign) ASCornerLayoutLocation cornerLocation; | ||
|
||
/** | ||
The point which offsets from the corner location. Use this property to make delta | ||
distance from the default corner location. Default is CGPointZero. | ||
*/ | ||
@property (nonatomic, assign) CGPoint offset; | ||
|
||
/** | ||
Whether should include corner element into layout size calculation. If included, | ||
the layout size will be the union size of both child and corner; If not included, | ||
the layout size will be only child's size. Default is NO. | ||
*/ | ||
@property (nonatomic, assign) BOOL wrapsCorner; | ||
|
||
@end | ||
|
||
NS_ASSUME_NONNULL_END |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
// | ||
// ASCornerLayoutSpec.mm | ||
// Texture | ||
// | ||
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
|
||
#import <AsyncDisplayKit/ASCornerLayoutSpec.h> | ||
#import <AsyncDisplayKit/ASLayout.h> | ||
#import <AsyncDisplayKit/ASLayoutSpec+Subclasses.h> | ||
#import <AsyncDisplayKit/ASDisplayNode.h> | ||
|
||
CGPoint as_calculatedCornerOriginIn(CGRect baseFrame, CGSize cornerSize, ASCornerLayoutLocation cornerLocation, CGPoint offset) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: open curly bracket in a new line. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for refining so much details on the new layout spec. I kept the curly bracket at the end of function as the code guide line declared. 🤔🤔🤔 It says:
|
||
|
||
CGPoint cornerOrigin = CGPointZero; | ||
CGPoint baseOrigin = baseFrame.origin; | ||
CGSize baseSize = baseFrame.size; | ||
|
||
switch (cornerLocation) { | ||
case ASCornerLayoutLocationTopLeft: | ||
cornerOrigin.x = baseOrigin.x - cornerSize.width / 2; | ||
cornerOrigin.y = baseOrigin.y - cornerSize.height / 2; | ||
break; | ||
case ASCornerLayoutLocationTopRight: | ||
cornerOrigin.x = baseOrigin.x + baseSize.width - cornerSize.width / 2; | ||
cornerOrigin.y = baseOrigin.y - cornerSize.height / 2; | ||
break; | ||
case ASCornerLayoutLocationBottomLeft: | ||
cornerOrigin.x = baseOrigin.x - cornerSize.width / 2; | ||
cornerOrigin.y = baseOrigin.y + baseSize.height - cornerSize.height / 2; | ||
break; | ||
case ASCornerLayoutLocationBottomRight: | ||
cornerOrigin.x = baseOrigin.x + baseSize.width - cornerSize.width / 2; | ||
cornerOrigin.y = baseOrigin.y + baseSize.height - cornerSize.height / 2; | ||
break; | ||
} | ||
|
||
cornerOrigin.x += offset.x; | ||
cornerOrigin.y += offset.y; | ||
|
||
return cornerOrigin; | ||
} | ||
|
||
static NSUInteger const kBaseChildIndex = 0; | ||
static NSUInteger const kCornerChildIndex = 1; | ||
|
||
@interface ASCornerLayoutSpec() | ||
@end | ||
|
||
@implementation ASCornerLayoutSpec | ||
|
||
- (instancetype)initWithChild:(id <ASLayoutElement>)child corner:(id <ASLayoutElement>)corner location:(ASCornerLayoutLocation)location | ||
{ | ||
self = [super init]; | ||
if (self) { | ||
self.child = child; | ||
self.corner = corner; | ||
self.cornerLocation = location; | ||
} | ||
return self; | ||
} | ||
|
||
+ (instancetype)cornerLayoutSpecWithChild:(id <ASLayoutElement>)child corner:(id <ASLayoutElement>)corner location:(ASCornerLayoutLocation)location | ||
{ | ||
return [[self alloc] initWithChild:child corner:corner location:location]; | ||
} | ||
|
||
#pragma mark - Children | ||
|
||
- (void)setChild:(id<ASLayoutElement>)child | ||
{ | ||
ASDisplayNodeAssertNotNil(child, @"Child shouldn't be nil."); | ||
[super setChild:child atIndex:kBaseChildIndex]; | ||
} | ||
|
||
- (id<ASLayoutElement>)child | ||
{ | ||
return [super childAtIndex:kBaseChildIndex]; | ||
} | ||
|
||
- (void)setCorner:(id<ASLayoutElement>)corner | ||
{ | ||
ASDisplayNodeAssertNotNil(corner, @"Corner element cannot be nil."); | ||
[super setChild:corner atIndex:kCornerChildIndex]; | ||
} | ||
|
||
- (id<ASLayoutElement>)corner | ||
{ | ||
return [super childAtIndex:kCornerChildIndex]; | ||
} | ||
|
||
#pragma mark - Calculation | ||
|
||
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This whole layout algorithm is a beauty! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great to hear that 😁 |
||
{ | ||
CGSize size = { | ||
ASPointsValidForSize(constrainedSize.max.width) == NO ? ASLayoutElementParentDimensionUndefined : constrainedSize.max.width, | ||
ASPointsValidForSize(constrainedSize.max.height) == NO ? ASLayoutElementParentDimensionUndefined : constrainedSize.max.height | ||
}; | ||
|
||
id <ASLayoutElement> child = self.child; | ||
id <ASLayoutElement> corner = self.corner; | ||
|
||
// Element validation | ||
[self _validateElement:child]; | ||
[self _validateElement:corner]; | ||
|
||
CGRect childFrame = CGRectZero; | ||
CGRect cornerFrame = CGRectZero; | ||
|
||
// Layout child | ||
ASLayout *childLayout = [child layoutThatFits:constrainedSize parentSize:size]; | ||
childFrame.size = childLayout.size; | ||
|
||
// Layout corner | ||
ASLayout *cornerLayout = [corner layoutThatFits:constrainedSize parentSize:size]; | ||
cornerFrame.size = cornerLayout.size; | ||
|
||
// Calculate corner's position | ||
CGPoint relativePosition = as_calculatedCornerOriginIn(childFrame, cornerFrame.size, _cornerLocation, _offset); | ||
|
||
// Update corner's position | ||
cornerFrame.origin = relativePosition; | ||
|
||
// Calculate size | ||
CGRect frame = childFrame; | ||
if (_wrapsCorner) { | ||
frame = CGRectUnion(childFrame, cornerFrame); | ||
frame.size = ASSizeRangeClamp(constrainedSize, frame.size); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, always clamp against the constrained size range 👍 |
||
} | ||
|
||
// Shift sublayouts' positions if they are off the bounds. | ||
if (frame.origin.x != 0) { | ||
CGFloat deltaX = frame.origin.x; | ||
childFrame.origin.x -= deltaX; | ||
cornerFrame.origin.x -= deltaX; | ||
} | ||
|
||
if (frame.origin.y != 0) { | ||
CGFloat deltaY = frame.origin.y; | ||
childFrame.origin.y -= deltaY; | ||
cornerFrame.origin.y -= deltaY; | ||
} | ||
|
||
childLayout.position = childFrame.origin; | ||
cornerLayout.position = cornerFrame.origin; | ||
|
||
return [ASLayout layoutWithLayoutElement:self size:frame.size sublayouts:@[childLayout, cornerLayout]]; | ||
} | ||
|
||
- (void)_validateElement:(id <ASLayoutElement>)element | ||
{ | ||
// Validate non-nil element | ||
if (element == nil) { | ||
ASDisplayNodeAssertNotNil(element, @"[%@]: Must have a non-nil child/corner for layout calculation.", self.class); | ||
} | ||
// Validate preferredSize if needed | ||
CGSize size = element.style.preferredSize; | ||
if (!CGSizeEqualToSize(size, CGSizeZero) && !ASIsCGSizeValidForSize(size) && (size.width < 0 || (size.height < 0))) { | ||
ASDisplayNodeFailAssert(@"[%@]: Should give a valid preferredSize value for %@ before corner's position calculation.", self.class, element); | ||
} | ||
} | ||
|
||
@end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Coding convention: Method,
@interface
, and@implementation
brackets on the following line.Please also update the rest of this diff to follow the convention.