From bb6102aa180acd8d7000ccf52fceb6f93a37db43 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 8 Mar 2019 08:32:57 -0800 Subject: [PATCH] Add Yoga support to ASButtonNode (#1381) * Add Yoga support to ASButtonNode * Drop unowned ASLayoutElementStyle parameter * Fix access of Yoga properties * Move ASButtonNode Yoga logic to Category * Update header --- AsyncDisplayKit.xcodeproj/project.pbxproj | 12 ++ Source/ASButtonNode+Private.h | 44 +++++++ Source/ASButtonNode+Yoga.h | 20 ++++ Source/ASButtonNode+Yoga.mm | 106 +++++++++++++++++ Source/ASButtonNode.mm | 134 ++++++++++------------ 5 files changed, 245 insertions(+), 71 deletions(-) create mode 100644 Source/ASButtonNode+Private.h create mode 100644 Source/ASButtonNode+Yoga.h create mode 100644 Source/ASButtonNode+Yoga.mm diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 84d4290de..c3617ea67 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -207,6 +207,9 @@ 9C8898BD1C738BB800D6B02E /* ASTextKitFontSizeAdjuster.h in Headers */ = {isa = PBXBuildFile; fileRef = A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */; settings = {ATTRIBUTES = (Private, ); }; }; 9CC606651D24DF9E006581A0 /* NSIndexSet+ASHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC4981BB1D1C7F65004E13CC /* NSIndexSet+ASHelpers.mm */; }; 9CDC18CD1B910E12004965E2 /* ASLayoutElementPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9CDC18CB1B910E12004965E2 /* ASLayoutElementPrivate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D302F9B2231B07E005739C3 /* ASButtonNode+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D302F9A2231B07E005739C3 /* ASButtonNode+Private.h */; }; + 9D302F9E2231B373005739C3 /* ASButtonNode+Yoga.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D302F9C2231B373005739C3 /* ASButtonNode+Yoga.h */; }; + 9D302F9F2231B373005739C3 /* ASButtonNode+Yoga.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9D302F9D2231B373005739C3 /* ASButtonNode+Yoga.mm */; }; 9D9AA56921E23EE200172C09 /* ASDisplayNode+LayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9D9AA56721E23EE200172C09 /* ASDisplayNode+LayoutSpec.mm */; }; 9D9AA56B21E254B800172C09 /* ASDisplayNode+Yoga.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D9AA56A21E254B800172C09 /* ASDisplayNode+Yoga.h */; }; 9D9AA56D21E2568500172C09 /* ASDisplayNode+LayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D9AA56C21E2568500172C09 /* ASDisplayNode+LayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -777,6 +780,9 @@ 9CDC18CB1B910E12004965E2 /* ASLayoutElementPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutElementPrivate.h; sourceTree = ""; }; 9CFFC6BF1CCAC73C006A6476 /* ASViewController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASViewController.mm; sourceTree = ""; }; 9CFFC6C11CCAC768006A6476 /* ASTableNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTableNode.mm; sourceTree = ""; }; + 9D302F9A2231B07E005739C3 /* ASButtonNode+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASButtonNode+Private.h"; sourceTree = ""; }; + 9D302F9C2231B373005739C3 /* ASButtonNode+Yoga.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASButtonNode+Yoga.h"; sourceTree = ""; }; + 9D302F9D2231B373005739C3 /* ASButtonNode+Yoga.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASButtonNode+Yoga.mm"; sourceTree = ""; }; 9D9AA56721E23EE200172C09 /* ASDisplayNode+LayoutSpec.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASDisplayNode+LayoutSpec.mm"; sourceTree = ""; }; 9D9AA56A21E254B800172C09 /* ASDisplayNode+Yoga.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Yoga.h"; sourceTree = ""; }; 9D9AA56C21E2568500172C09 /* ASDisplayNode+LayoutSpec.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+LayoutSpec.h"; sourceTree = ""; }; @@ -1282,6 +1288,9 @@ 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.mm */, CC55A70B1E529FA200594372 /* UIResponder+AsyncDisplayKit.h */, CC55A70C1E529FA200594372 /* UIResponder+AsyncDisplayKit.mm */, + 9D302F9A2231B07E005739C3 /* ASButtonNode+Private.h */, + 9D302F9C2231B373005739C3 /* ASButtonNode+Yoga.h */, + 9D302F9D2231B373005739C3 /* ASButtonNode+Yoga.mm */, ); path = Source; sourceTree = ""; @@ -1926,6 +1935,7 @@ CCA282B81E9EA8E40037E8B7 /* AsyncDisplayKit+Tips.h in Headers */, B35062571B010F070018CF92 /* ASAssert.h in Headers */, CCBBBF5D1EB161760069AA91 /* ASRangeManagingNode.h in Headers */, + 9D302F9B2231B07E005739C3 /* ASButtonNode+Private.h in Headers */, B35062581B010F070018CF92 /* ASAvailability.h in Headers */, 9019FBBF1ED8061D00C45F72 /* ASYogaUtilities.h in Headers */, DE84918D1C8FFF2B003D89E9 /* ASRunLoopQueue.h in Headers */, @@ -1979,6 +1989,7 @@ 34EFC7691B701CE100AD841F /* ASLayoutElement.h in Headers */, 698DFF471E36B7E9002891F1 /* ASLayoutSpecUtilities.h in Headers */, 9C70F20D1CDBE9CB007D6C76 /* ASDefaultPlayButton.h in Headers */, + 9D302F9E2231B373005739C3 /* ASButtonNode+Yoga.h in Headers */, CCCCCCD51EC3EF060087FE10 /* ASTextDebugOption.h in Headers */, CC034A091E60BEB400626263 /* ASDisplayNode+Convenience.h in Headers */, 254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */, @@ -2433,6 +2444,7 @@ CCB1F95A1EFB60A5009C7475 /* ASLog.mm in Sources */, 767E7F8E1C90191D0066C000 /* AsyncDisplayKit+Debug.mm in Sources */, CCEDDDCB200C2AC300FFCD0A /* ASConfigurationInternal.mm in Sources */, + 9D302F9F2231B373005739C3 /* ASButtonNode+Yoga.mm in Sources */, CCCCCCD61EC3EF060087FE10 /* ASTextDebugOption.mm in Sources */, 34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */, B350624E1B010EFD0018CF92 /* ASDisplayNode+AsyncDisplay.mm in Sources */, diff --git a/Source/ASButtonNode+Private.h b/Source/ASButtonNode+Private.h new file mode 100644 index 000000000..2ecc89446 --- /dev/null +++ b/Source/ASButtonNode+Private.h @@ -0,0 +1,44 @@ +// +// ASButtonNode+Private.h +// Texture +// +// Copyright (c) Pinterest, Inc. All rights reserved. +// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import +#import +#import + +@interface ASButtonNode() { + NSAttributedString *_normalAttributedTitle; + NSAttributedString *_highlightedAttributedTitle; + NSAttributedString *_selectedAttributedTitle; + NSAttributedString *_selectedHighlightedAttributedTitle; + NSAttributedString *_disabledAttributedTitle; + + UIImage *_normalImage; + UIImage *_highlightedImage; + UIImage *_selectedImage; + UIImage *_selectedHighlightedImage; + UIImage *_disabledImage; + + UIImage *_normalBackgroundImage; + UIImage *_highlightedBackgroundImage; + UIImage *_selectedBackgroundImage; + UIImage *_selectedHighlightedBackgroundImage; + UIImage *_disabledBackgroundImage; + + CGFloat _contentSpacing; + BOOL _laysOutHorizontally; + ASVerticalAlignment _contentVerticalAlignment; + ASHorizontalAlignment _contentHorizontalAlignment; + UIEdgeInsets _contentEdgeInsets; + ASButtonNodeImageAlignment _imageAlignment; + ASTextNode *_titleNode; + ASImageNode *_imageNode; + ASImageNode *_backgroundImageNode; +} + +@end diff --git a/Source/ASButtonNode+Yoga.h b/Source/ASButtonNode+Yoga.h new file mode 100644 index 000000000..4fddc9df3 --- /dev/null +++ b/Source/ASButtonNode+Yoga.h @@ -0,0 +1,20 @@ +// +// ASButtonNode+Yoga.h +// Texture +// +// Copyright (c) Pinterest, Inc. All rights reserved. +// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ASButtonNode (Yoga) + +- (void)updateYogaLayoutIfNeeded; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ASButtonNode+Yoga.mm b/Source/ASButtonNode+Yoga.mm new file mode 100644 index 000000000..466647556 --- /dev/null +++ b/Source/ASButtonNode+Yoga.mm @@ -0,0 +1,106 @@ +// +// ASButtonNode+Yoga.mm +// Texture +// +// Copyright (c) Pinterest, Inc. All rights reserved. +// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import "ASButtonNode+Yoga.h" +#import +#import +#import +#import + +#if YOGA +static void ASButtonNodeResolveHorizontalAlignmentForStyle(ASLayoutElementStyle *style, ASStackLayoutDirection _direction, ASHorizontalAlignment _horizontalAlignment, ASStackLayoutJustifyContent _justifyContent, ASStackLayoutAlignItems _alignItems) { + if (_direction == ASStackLayoutDirectionHorizontal) { + style.justifyContent = justifyContent(_horizontalAlignment, _justifyContent); + } else { + style.alignItems = alignment(_horizontalAlignment, _alignItems); + } +} + +static void ASButtonNodeResolveVerticalAlignmentForStyle(ASLayoutElementStyle *style, ASStackLayoutDirection _direction, ASVerticalAlignment _verticalAlignment, ASStackLayoutJustifyContent _justifyContent, ASStackLayoutAlignItems _alignItems) { + if (_direction == ASStackLayoutDirectionHorizontal) { + style.alignItems = alignment(_verticalAlignment, _alignItems); + } else { + style.justifyContent = justifyContent(_verticalAlignment, _justifyContent); + } +} + +@implementation ASButtonNode (Yoga) + +- (void)updateYogaLayoutIfNeeded +{ + NSMutableArray *children = [[NSMutableArray alloc] initWithCapacity:2]; + { + ASLockScopeSelf(); + + // Build up yoga children for button node again + unowned ASLayoutElementStyle *style = [self _locked_style]; + [style yogaNodeCreateIfNeeded]; + + // Setup stack layout values + style.flexDirection = _laysOutHorizontally ? ASStackLayoutDirectionHorizontal : ASStackLayoutDirectionVertical; + + // Resolve horizontal and vertical alignment + ASButtonNodeResolveHorizontalAlignmentForStyle(style, style.flexDirection, _contentHorizontalAlignment, style.justifyContent, style.alignItems); + ASButtonNodeResolveVerticalAlignmentForStyle(style, style.flexDirection, _contentVerticalAlignment, style.justifyContent, style.alignItems); + + // Setup new yoga children + if (_imageNode.image != nil) { + [_imageNode.style yogaNodeCreateIfNeeded]; + [children addObject:_imageNode]; + } + + if (_titleNode.attributedText.length > 0) { + [_titleNode.style yogaNodeCreateIfNeeded]; + if (_imageAlignment == ASButtonNodeImageAlignmentBeginning) { + [children addObject:_titleNode]; + } else { + [children insertObject:_titleNode atIndex:0]; + } + } + + // Add spacing between title and button + if (children.count == 2) { + unowned ASLayoutElementStyle *firstChildStyle = children.firstObject.style; + if (_laysOutHorizontally) { + firstChildStyle.margin = ASEdgeInsetsMake(UIEdgeInsetsMake(0, 0, 0, _contentSpacing)); + } else { + firstChildStyle.margin = ASEdgeInsetsMake(UIEdgeInsetsMake(0, 0, _contentSpacing, 0)); + } + } + + // Add padding to button + if (UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, _contentEdgeInsets) == NO) { + style.padding = ASEdgeInsetsMake(_contentEdgeInsets); + } + + // Add background node + if (_backgroundImageNode.image) { + [_backgroundImageNode.style yogaNodeCreateIfNeeded]; + [children insertObject:_backgroundImageNode atIndex:0]; + + _backgroundImageNode.style.positionType = YGPositionTypeAbsolute; + _backgroundImageNode.style.position = ASEdgeInsetsMake(UIEdgeInsetsZero); + } + } + + // Update new children + [self setYogaChildren:children]; +} + +@end + +#else + +@implementation ASButtonNode (Yoga) + +- (void)updateYogaLayoutIfNeeded {} + +@end + +#endif diff --git a/Source/ASButtonNode.mm b/Source/ASButtonNode.mm index 5ec1f2371..f91b16c2b 100644 --- a/Source/ASButtonNode.mm +++ b/Source/ASButtonNode.mm @@ -7,40 +7,16 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import +#import +#import #import #import #import #import #import #import -#import -#import #import -@interface ASButtonNode () -{ - NSAttributedString *_normalAttributedTitle; - NSAttributedString *_highlightedAttributedTitle; - NSAttributedString *_selectedAttributedTitle; - NSAttributedString *_selectedHighlightedAttributedTitle; - NSAttributedString *_disabledAttributedTitle; - - UIImage *_normalImage; - UIImage *_highlightedImage; - UIImage *_selectedImage; - UIImage *_selectedHighlightedImage; - UIImage *_disabledImage; - - UIImage *_normalBackgroundImage; - UIImage *_highlightedBackgroundImage; - UIImage *_selectedBackgroundImage; - UIImage *_selectedHighlightedBackgroundImage; - UIImage *_disabledBackgroundImage; -} - -@end - @implementation ASButtonNode @synthesize contentSpacing = _contentSpacing; @@ -53,6 +29,8 @@ @implementation ASButtonNode @synthesize imageNode = _imageNode; @synthesize backgroundImageNode = _backgroundImageNode; +#pragma mark - Lifecycle + - (instancetype)init { if (self = [super init]) { @@ -65,6 +43,8 @@ - (instancetype)init _contentEdgeInsets = UIEdgeInsetsZero; _imageAlignment = ASButtonNodeImageAlignmentBeginning; self.accessibilityTraits = self.defaultAccessibilityTraits; + + [self updateYogaLayoutIfNeeded]; } return self; } @@ -84,6 +64,8 @@ - (ASTextNode *)titleNode return _titleNode; } +#pragma mark - Public Getter + - (ASImageNode *)imageNode { ASLockScopeSelf(); @@ -172,6 +154,7 @@ - (void)updateImage _imageNode.image = newImage; [self unlock]; + [self updateYogaLayoutIfNeeded]; [self setNeedsLayout]; return; } @@ -202,6 +185,7 @@ - (void)updateTitle [self unlock]; self.accessibilityLabel = self.defaultAccessibilityLabel; + [self updateYogaLayoutIfNeeded]; [self setNeedsLayout]; return; } @@ -229,7 +213,8 @@ - (void)updateBackgroundImage if ((_backgroundImageNode != nil || newImage != nil) && newImage != self.backgroundImageNode.image) { _backgroundImageNode.image = newImage; [self unlock]; - + + [self updateYogaLayoutIfNeeded]; [self setNeedsLayout]; return; } @@ -246,6 +231,7 @@ - (CGFloat)contentSpacing - (void)setContentSpacing:(CGFloat)contentSpacing { if (ASLockedSelfCompareAssign(_contentSpacing, contentSpacing)) { + [self updateYogaLayoutIfNeeded]; [self setNeedsLayout]; } } @@ -259,6 +245,7 @@ - (BOOL)laysOutHorizontally - (void)setLaysOutHorizontally:(BOOL)laysOutHorizontally { if (ASLockedSelfCompareAssign(_laysOutHorizontally, laysOutHorizontally)) { + [self updateYogaLayoutIfNeeded]; [self setNeedsLayout]; } } @@ -496,50 +483,6 @@ - (void)setBackgroundImage:(UIImage *)image forState:(UIControlState)state [self updateBackgroundImage]; } -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - UIEdgeInsets contentEdgeInsets; - ASButtonNodeImageAlignment imageAlignment; - ASLayoutSpec *spec; - ASStackLayoutSpec *stack = [[ASStackLayoutSpec alloc] init]; - { - ASLockScopeSelf(); - stack.direction = _laysOutHorizontally ? ASStackLayoutDirectionHorizontal : ASStackLayoutDirectionVertical; - stack.spacing = _contentSpacing; - stack.horizontalAlignment = _contentHorizontalAlignment; - stack.verticalAlignment = _contentVerticalAlignment; - - contentEdgeInsets = _contentEdgeInsets; - imageAlignment = _imageAlignment; - } - - NSMutableArray *children = [[NSMutableArray alloc] initWithCapacity:2]; - if (_imageNode.image) { - [children addObject:_imageNode]; - } - - if (_titleNode.attributedText.length > 0) { - if (imageAlignment == ASButtonNodeImageAlignmentBeginning) { - [children addObject:_titleNode]; - } else { - [children insertObject:_titleNode atIndex:0]; - } - } - - stack.children = children; - - spec = stack; - - if (UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, contentEdgeInsets) == NO) { - spec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:contentEdgeInsets child:spec]; - } - - if (_backgroundImageNode.image) { - spec = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:spec background:_backgroundImageNode]; - } - - return spec; -} - (NSString *)defaultAccessibilityLabel { @@ -553,6 +496,55 @@ - (UIAccessibilityTraits)defaultAccessibilityTraits : (UIAccessibilityTraitButton | UIAccessibilityTraitNotEnabled); } +#pragma mark - Layout + +#if !YOGA +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + UIEdgeInsets contentEdgeInsets; + ASButtonNodeImageAlignment imageAlignment; + ASLayoutSpec *spec; + ASStackLayoutSpec *stack = [[ASStackLayoutSpec alloc] init]; + { + ASLockScopeSelf(); + stack.direction = _laysOutHorizontally ? ASStackLayoutDirectionHorizontal : ASStackLayoutDirectionVertical; + stack.spacing = _contentSpacing; + stack.horizontalAlignment = _contentHorizontalAlignment; + stack.verticalAlignment = _contentVerticalAlignment; + + contentEdgeInsets = _contentEdgeInsets; + imageAlignment = _imageAlignment; + } + + NSMutableArray *children = [[NSMutableArray alloc] initWithCapacity:2]; + if (_imageNode.image) { + [children addObject:_imageNode]; + } + + if (_titleNode.attributedText.length > 0) { + if (imageAlignment == ASButtonNodeImageAlignmentBeginning) { + [children addObject:_titleNode]; + } else { + [children insertObject:_titleNode atIndex:0]; + } + } + + stack.children = children; + + spec = stack; + + if (UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, contentEdgeInsets) == NO) { + spec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:contentEdgeInsets child:spec]; + } + + if (_backgroundImageNode.image) { + spec = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:spec background:_backgroundImageNode]; + } + + return spec; +} +#endif + - (void)layout { [super layout];