Skip to content
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

[Feature] Support an ASEdgeLayoutSpec #1933

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions AsyncDisplayKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,9 @@
CCF1FF5E20C4785000AAD8FC /* ASLocking.h in Headers */ = {isa = PBXBuildFile; fileRef = CCF1FF5D20C4785000AAD8FC /* ASLocking.h */; settings = {ATTRIBUTES = (Public, ); }; };
D933F041224AD17F00FF495E /* ASTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = D933F040224AD17F00FF495E /* ASTransactionTests.mm */; };
D99F9158232990F30083CC8E /* ASImageNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D99F9157232990F30083CC8E /* ASImageNodeTests.m */; };
DA316FC6254D1282008942BE /* ASEdgeLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA316FC5254D1282008942BE /* ASEdgeLayoutSpec.mm */; };
DA316FC8254D13CD008942BE /* ASEdgeLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = DA316FC7254D128C008942BE /* ASEdgeLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; };
DA316FCD254D19C0008942BE /* ASEdgeLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA316FCC254D19C0008942BE /* ASEdgeLayoutSpecSnapshotTests.mm */; };
DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; };
DB78412E1C6BCE1600A9E2B4 /* _ASTransitionContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = DB55C2601C6408D6004EDCF5 /* _ASTransitionContext.mm */; };
DBABFAFC1C6A8D2F0039EA4A /* _ASTransitionContext.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C25F1C6408D6004EDCF5 /* _ASTransitionContext.h */; settings = {ATTRIBUTES = (Private, ); }; };
Expand Down Expand Up @@ -999,6 +1002,9 @@
D785F6611A74327E00291744 /* ASScrollNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASScrollNode.mm; sourceTree = "<group>"; };
D933F040224AD17F00FF495E /* ASTransactionTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTransactionTests.mm; sourceTree = "<group>"; };
D99F9157232990F30083CC8E /* ASImageNodeTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASImageNodeTests.m; sourceTree = "<group>"; };
DA316FC5254D1282008942BE /* ASEdgeLayoutSpec.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASEdgeLayoutSpec.mm; sourceTree = "<group>"; };
DA316FC7254D128C008942BE /* ASEdgeLayoutSpec.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASEdgeLayoutSpec.h; sourceTree = "<group>"; };
DA316FCC254D19C0008942BE /* ASEdgeLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASEdgeLayoutSpecSnapshotTests.mm; sourceTree = "<group>"; };
DB55C25F1C6408D6004EDCF5 /* _ASTransitionContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = _ASTransitionContext.h; path = ../_ASTransitionContext.h; sourceTree = "<group>"; };
DB55C2601C6408D6004EDCF5 /* _ASTransitionContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = _ASTransitionContext.mm; path = ../_ASTransitionContext.mm; sourceTree = "<group>"; };
DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASContextTransitioning.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1326,6 +1332,7 @@
CC54A81D1D7008B300296A24 /* ASDispatchTests.mm */,
058D0A2D195D057000B7D73C /* ASDisplayLayerTests.mm */,
058D0A2E195D057000B7D73C /* ASDisplayNodeAppearanceTests.mm */,
DA316FCC254D19C0008942BE /* ASEdgeLayoutSpecSnapshotTests.mm */,
F711994D1D20C21100568860 /* ASDisplayNodeExtrasTests.mm */,
DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.mm */,
69B225661D72535E00B25B22 /* ASDisplayNodeLayoutTests.mm */,
Expand Down Expand Up @@ -1696,6 +1703,8 @@
ACF6ED081B17843500DA7C62 /* ASDimension.mm */,
690C35631E055C7B00069B91 /* ASDimensionInternal.h */,
690C35601E055C5D00069B91 /* ASDimensionInternal.mm */,
DA316FC7254D128C008942BE /* ASEdgeLayoutSpec.h */,
DA316FC5254D1282008942BE /* ASEdgeLayoutSpec.mm */,
ACF6ED091B17843500DA7C62 /* ASInsetLayoutSpec.h */,
ACF6ED0A1B17843500DA7C62 /* ASInsetLayoutSpec.mm */,
ACF6ED0B1B17843500DA7C62 /* ASLayout.h */,
Expand Down Expand Up @@ -2069,6 +2078,7 @@
CC2F65EE1E5FFB1600DA57C9 /* ASMutableElementMap.h in Headers */,
34EFC76A1B701CE600AD841F /* ASLayoutSpec.h in Headers */,
CCA282D01E9EBF6C0037E8B7 /* ASTipsWindow.h in Headers */,
DA316FC8254D13CD008942BE /* ASEdgeLayoutSpec.h in Headers */,
B350625C1B010F070018CF92 /* ASLog.h in Headers */,
CC3B208A1C3F7A5400798563 /* ASWeakSet.h in Headers */,
B35062041B010EFD0018CF92 /* ASMultiplexImageNode.h in Headers */,
Expand Down Expand Up @@ -2386,6 +2396,7 @@
AEEC47E41C21D3D200EC1693 /* ASVideoNodeTests.mm in Sources */,
254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */,
F325E490217460B100AC93A4 /* ASTextNode2Tests.mm in Sources */,
DA316FCD254D19C0008942BE /* ASEdgeLayoutSpecSnapshotTests.mm in Sources */,
058D0A3D195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.mm in Sources */,
CC3B20901C3F892D00798563 /* ASBridgedPropertiesTests.mm in Sources */,
CCE4F9BE1F0ECE5200062E4E /* ASTLayoutFixture.mm in Sources */,
Expand Down Expand Up @@ -2514,6 +2525,7 @@
254C6B8C1BF94F8A003EC431 /* ASTextKitTailTruncater.mm in Sources */,
6907C25A1DC4ECFE00374C66 /* ASObjectDescriptionHelpers.mm in Sources */,
B35062051B010EFD0018CF92 /* ASMultiplexImageNode.mm in Sources */,
DA316FC6254D1282008942BE /* ASEdgeLayoutSpec.mm in Sources */,
B35062251B010EFD0018CF92 /* ASMutableAttributedStringBuilder.mm in Sources */,
B35062071B010EFD0018CF92 /* ASNetworkImageNode.mm in Sources */,
34EFC76D1B701CF100AD841F /* ASOverlayLayoutSpec.mm in Sources */,
Expand Down
1 change: 1 addition & 0 deletions Source/AsyncDisplayKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
#import <AsyncDisplayKit/ASBackgroundLayoutSpec.h>
#import <AsyncDisplayKit/ASCenterLayoutSpec.h>
#import <AsyncDisplayKit/ASCornerLayoutSpec.h>
#import <AsyncDisplayKit/ASEdgeLayoutSpec.h>
#import <AsyncDisplayKit/ASRelativeLayoutSpec.h>
#import <AsyncDisplayKit/ASInsetLayoutSpec.h>
#import <AsyncDisplayKit/ASOverlayLayoutSpec.h>
Expand Down
80 changes: 80 additions & 0 deletions Source/Layout/ASEdgeLayoutSpec.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// ASEdgeLayoutSpec.h
// AsyncDisplayKit
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//

#import <AsyncDisplayKit/ASLayoutSpec.h>

/**
The edge location for positioning edge element.
*/
typedef NS_ENUM(NSInteger, ASEdgeLayoutLocation) {
ASEdgeLayoutLocationTop,
ASEdgeLayoutLocationLeft,
ASEdgeLayoutLocationBottom,
ASEdgeLayoutLocationRight,
};

NS_ASSUME_NONNULL_BEGIN

/**
A layout spec that positions a edge element which relatives to the child element.

@warning Both child element and edge element must have valid preferredSize for layout calculation.
*/
@interface ASEdgeLayoutSpec : ASLayoutSpec

/**
A layout spec that positions a edge element which relatives to the child element.

@param child A child that is laid out to determine the size of this spec.
@param edge A layoutElement object that is laid out to a edge on the child.
@param location The edge position option.
@param offset offset from the edge location
@return An ASEdgeLayoutSpec object with a given child and an layoutElement that act as edge.
*/
- (instancetype)initWithChild:(id <ASLayoutElement>)child edge:(id <ASLayoutElement>)edge location:(ASEdgeLayoutLocation)location offset:(CGFloat)offset AS_WARN_UNUSED_RESULT;

/**
A layout spec that positions a edge element which relatives to the child element.

@param child A child that is laid out to determine the size of this spec.
@param edge A layoutElement object that is laid out to a edge on the child.
@param location The edge position option.
@param offset offset from the edge location
@return An ASEdgeLayoutSpec object with a given child and an layoutElement that act as edge.
*/
+ (instancetype)edgeLayoutSpecWithChild:(id <ASLayoutElement>)child edge:(id <ASLayoutElement>)edge location:(ASEdgeLayoutLocation)location offset:(CGFloat)offset NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT;

/**
A layout spec that positions a edge element which relatives to the child element.

@param child A child that is laid out to determine the size of this spec.
@param edge A layoutElement object that is laid out to a edge on the child.
@param location The edge position option.
@return An ASEdgeLayoutSpec object with a given child and an layoutElement that act as edge.
*/
+ (instancetype)edgeLayoutSpecWithChild:(id <ASLayoutElement>)child edge:(id <ASLayoutElement>)edge location:(ASEdgeLayoutLocation)location NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT;

/**
A layoutElement object that is laid out to a edge on the child.
*/
@property (nonatomic) id <ASLayoutElement> edge;

/**
The edge position option.
*/
@property (nonatomic) ASEdgeLayoutLocation edgeLocation;

/**
The offset from the edge location. Use this property to make delta
distance from the default edge location. Default is 0.0.
*/
@property (nonatomic) CGFloat offset;

@end

NS_ASSUME_NONNULL_END
149 changes: 149 additions & 0 deletions Source/Layout/ASEdgeLayoutSpec.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
//
// ASEdgeLayoutSpec.m
// AsyncDisplayKit
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//

#import <AsyncDisplayKit/ASEdgeLayoutSpec.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASLayoutSpec+Subclasses.h>

static NSUInteger const kBaseChildIndex = 0;
static NSUInteger const kEdgeChildIndex = 1;

@interface ASEdgeLayoutSpec()
@end

@implementation ASEdgeLayoutSpec

- (instancetype)initWithChild:(id <ASLayoutElement>)child edge:(id <ASLayoutElement>)edge location:(ASEdgeLayoutLocation)location offset:(CGFloat)offset
{
self = [super init];
if (self) {
self.child = child;
self.edge = edge;
self.edgeLocation = location;
self.offset = offset;
}
return self;
}

+ (instancetype)edgeLayoutSpecWithChild:(id <ASLayoutElement>)child edge:(id <ASLayoutElement>)edge location:(ASEdgeLayoutLocation)location NS_RETURNS_RETAINED
{
return [[self alloc] initWithChild:child edge:edge location:location offset:0.0];
}

+ (instancetype)edgeLayoutSpecWithChild:(id <ASLayoutElement>)child edge:(id <ASLayoutElement>)edge location:(ASEdgeLayoutLocation)location offset:(CGFloat)offset NS_RETURNS_RETAINED;
{
return [[self alloc] initWithChild:child edge:edge location:location offset:offset];
}

#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)setEdge:(id<ASLayoutElement>)edge
{
ASDisplayNodeAssertNotNil(edge, @"Edge element cannot be nil.");
[super setChild:edge atIndex:kEdgeChildIndex];
}

- (id<ASLayoutElement>)edge
{
return [super childAtIndex:kEdgeChildIndex];
}

#pragma mark - Calculation

- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
{
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> edge = self.edge;

// Element validation
[self _validateElement:child];
[self _validateElement:edge];

CGRect childFrame = CGRectZero;
CGRect edgeFrame = CGRectZero;

// Layout child
ASLayout *childLayout = [child layoutThatFits:constrainedSize parentSize:size];
childFrame.size = childLayout.size;

// Layout edge
ASLayout *edgeLayout = [edge layoutThatFits:constrainedSize parentSize:size];
edgeFrame.size = edgeLayout.size;

// Update edge's position
switch (_edgeLocation) {
case ASEdgeLayoutLocationTop:
edgeFrame.origin.x = childFrame.origin.x + (childFrame.size.width - edgeFrame.size.width) * 0.5;
edgeFrame.origin.y = childFrame.origin.y - edgeFrame.size.height - _offset;
break;
case ASEdgeLayoutLocationLeft:
edgeFrame.origin.x = childFrame.origin.x - edgeFrame.size.width - _offset;
edgeFrame.origin.y = childFrame.origin.y + (childFrame.size.height - edgeFrame.size.height) * 0.5;
break;
case ASEdgeLayoutLocationBottom:
edgeFrame.origin.x = childFrame.origin.x + (childFrame.size.width - edgeFrame.size.width) * 0.5;
edgeFrame.origin.y = childFrame.origin.y + childFrame.size.height + _offset;
break;
case ASEdgeLayoutLocationRight:
edgeFrame.origin.x = childFrame.origin.x + childFrame.size.width + _offset;
edgeFrame.origin.y = childFrame.origin.y + (childFrame.size.height - edgeFrame.size.height) * 0.5;
break;
}

// Calculate size
CGRect frame = childFrame;

// Shift sublayouts' positions if they are off the bounds.
if (frame.origin.x != 0.0) {
CGFloat deltaX = frame.origin.x;
childFrame.origin.x -= deltaX;
edgeFrame.origin.x -= deltaX;
}

if (frame.origin.y != 0.0) {
CGFloat deltaY = frame.origin.y;
childFrame.origin.y -= deltaY;
edgeFrame.origin.y -= deltaY;
}

childLayout.position = childFrame.origin;
edgeLayout.position = edgeFrame.origin;

return [ASLayout layoutWithLayoutElement:self size:frame.size sublayouts:@[childLayout, edgeLayout]];
}

- (void)_validateElement:(id <ASLayoutElement>)element
{
// Validate non-nil element
if (element == nil) {
ASDisplayNodeAssertNotNil(element, @"[%@]: Must have a non-nil child/edge 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 edge's position calculation.", self.class, element);
}
}

@end
Loading