Skip to content

Commit

Permalink
[Yoga Beta] Improvements to the experimental support for Yoga layout. (
Browse files Browse the repository at this point in the history
…#59)

* [Yoga Beta] Improvements to the experimental support for Yoga layout.

Yoga remains an unsupported / speculative feature, but these improvements are important for
the functionality of clients that are experimenting with it.

For example, without these changes, ASButtonNode is not able to lay out correctly. These
changes allow certain subtrees that use layout specs to coexist properly in a Yoga heirarchy.

The most significant change here is moving ASEdgeInsets into the #if YOGA gating. Although
this is technically an API change, this type was added with no known use cases and is
really only useful for flexbox layout specification. So, before usages of it are created,
it makes sense to constrain the Texture API surface until that time.

* [RTL] Bridge the UISemanticContentAttribute property for more convenient RTL support.

Although apps could handle this before by setting the view's property in didLoad, it's
useful to bridge this property for setting during off-main initialization.

This change also makes RTL fully functional / automatic for Yoga layout users.

* Remove RTL property addition and depend on PR #60 landing first.

* Fix warnings

* Add line to changelog
  • Loading branch information
appleguy authored and maicki committed Apr 27, 2017
1 parent 95c3a68 commit 6f82d0f
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 32 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
- Add support for IGListKit post-removal-of-IGListSectionType, in preparation for IGListKit 3.0.0 release. (Adlai-Holler)[https://github.com/Adlai-Holler] (#49)[https://github.com/TextureGroup/Texture/pull/49]
- Fix `__has_include` check in ASLog.h [Philipp Smorygo]([email protected])
- Fix potential deadlock in ASControlNode [Garrett Moon](https://github.com/garrettmoon)
- [Yoga Beta] Improvements to the experimental support for Yoga layout [Scott Goodson](appleguy)
4 changes: 3 additions & 1 deletion Source/ASDisplayNode+Beta.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,14 @@ extern void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode * _Nullable
// These methods should not normally be called directly.
- (void)invalidateCalculatedYogaLayout;
- (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize;
- (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute;

@end

@interface ASLayoutElementStyle (Yoga)

@property (nonatomic, assign, readwrite) ASStackLayoutDirection direction;
@property (nonatomic, assign, readwrite) ASStackLayoutDirection flexDirection;
@property (nonatomic, assign, readwrite) YGDirection direction;
@property (nonatomic, assign, readwrite) CGFloat spacing;
@property (nonatomic, assign, readwrite) ASStackLayoutJustifyContent justifyContent;
@property (nonatomic, assign, readwrite) ASStackLayoutAlignItems alignItems;
Expand Down
45 changes: 32 additions & 13 deletions Source/ASDisplayNode+Yoga.mm
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,15 @@ float yogaDimensionToPercent(ASDimension dimension)
ASDimension dimensionForEdgeWithEdgeInsets(YGEdge edge, ASEdgeInsets insets)
{
switch (edge) {
case YGEdgeLeft: return insets.left;
case YGEdgeTop: return insets.top;
case YGEdgeRight: return insets.right;
case YGEdgeBottom: return insets.bottom;
case YGEdgeLeft: return insets.left;
case YGEdgeTop: return insets.top;
case YGEdgeRight: return insets.right;
case YGEdgeBottom: return insets.bottom;
case YGEdgeStart: return insets.start;
case YGEdgeEnd: return insets.end;
case YGEdgeHorizontal: return insets.horizontal;
case YGEdgeVertical: return insets.vertical;
case YGEdgeAll: return insets.all;
default: ASDisplayNodeCAssert(NO, @"YGEdge other than ASEdgeInsets is not supported.");
return ASDimensionAuto;
}
Expand Down Expand Up @@ -314,10 +319,9 @@ - (void)setupYogaCalculatedLayout

- (void)setYogaMeasureFuncIfNeeded
{
// Manual size calculation via calculateSizeThatFits:
// This will be used for ASTextNode, as well as any other leaf node that has no layout spec.
if ((self.methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) == NO
&& self.layoutSpecBlock == NULL && self.yogaChildren.count == 0) {
// Size calculation via calculateSizeThatFits: or layoutSpecThatFits:
// This will be used for ASTextNode, as well as any other node that has no Yoga children
if (self.yogaChildren.count == 0) {
YGNodeRef yogaNode = self.yogaNode; // Use property to assign Ref if needed.
YGNodeSetContext(yogaNode, (__bridge void *)self);
YGNodeSetMeasureFunc(yogaNode, &ASLayoutElementYogaMeasureFunc);
Expand All @@ -333,8 +337,24 @@ - (void)invalidateCalculatedYogaLayout
}
}

- (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute
{
if (AS_AT_LEAST_IOS9) {
UIUserInterfaceLayoutDirection layoutDirection =
[UIView userInterfaceLayoutDirectionForSemanticContentAttribute:attribute];
self.style.direction = (layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight
? YGDirectionLTR : YGDirectionRTL);
}
}

- (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize
{
if (self.yogaParent) {
if (ASHierarchyStateIncludesYogaLayoutMeasuring(self.hierarchyState) == NO) {
[self _setNeedsLayoutFromAbove];
}
return;
}
if (ASHierarchyStateIncludesYogaLayoutMeasuring(self.hierarchyState)) {
ASDisplayNodeAssert(NO, @"A Yoga layout is being performed by a parent; children must not perform their own until it is done! %@", [self displayNodeRecursiveDescription]);
return;
Expand All @@ -356,14 +376,14 @@ - (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize
ASLayoutElementStyle *style = node.style;
YGNodeRef yogaNode = node.yogaNode;

YGNodeStyleSetDirection (yogaNode, YGDirectionInherit);
YGNodeStyleSetDirection (yogaNode, style.direction);

YGNodeStyleSetFlexWrap (yogaNode, style.flexWrap);
YGNodeStyleSetFlexGrow (yogaNode, style.flexGrow);
YGNodeStyleSetFlexShrink (yogaNode, style.flexShrink);
YGNODE_STYLE_SET_DIMENSION (yogaNode, FlexBasis, style.flexBasis);

YGNodeStyleSetFlexDirection (yogaNode, yogaFlexDirection(style.direction));
YGNodeStyleSetFlexDirection (yogaNode, yogaFlexDirection(style.flexDirection));
YGNodeStyleSetJustifyContent(yogaNode, yogaJustifyContent(style.justifyContent));
YGNodeStyleSetAlignSelf (yogaNode, yogaAlignSelf(style.alignSelf));
ASStackLayoutAlignItems alignItems = style.alignItems;
Expand All @@ -378,12 +398,12 @@ - (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize
ASEdgeInsets border = style.border;

YGEdge edge = YGEdgeLeft;
for (int i = 0; i < 4; i++) {
for (int i = 0; i < YGEdgeAll + 1; ++i) {
YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(yogaNode, Position, dimensionForEdgeWithEdgeInsets(edge, position), edge);
YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(yogaNode, Margin, dimensionForEdgeWithEdgeInsets(edge, margin), edge);
YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(yogaNode, Padding, dimensionForEdgeWithEdgeInsets(edge, padding), edge);
YGNODE_STYLE_SET_FLOAT_WITH_EDGE(yogaNode, Border, dimensionForEdgeWithEdgeInsets(edge, border), edge);
edge = (edge == YGEdgeLeft ? YGEdgeTop : (edge == YGEdgeTop ? YGEdgeRight : YGEdgeBottom));
edge = (YGEdge)(edge + 1);
}

CGFloat aspectRatio = style.aspectRatio;
Expand All @@ -406,7 +426,6 @@ - (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize
[node setYogaMeasureFuncIfNeeded];

/* TODO(appleguy): STYLE SETTER METHODS LEFT TO IMPLEMENT
void YGNodeStyleSetFlexDirection(YGNodeRef node, YGFlexDirection flexDirection);
void YGNodeStyleSetOverflow(YGNodeRef node, YGOverflow overflow);
void YGNodeStyleSetFlex(YGNodeRef node, float flex);
*/
Expand Down
32 changes: 21 additions & 11 deletions Source/Layout/ASDimension.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#pragma once
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import <AsyncDisplayKit/ASAvailability.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <AsyncDisplayKit/ASAssert.h>

Expand Down Expand Up @@ -213,17 +214,6 @@ ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT NSString *NSStringFromASLayoutSize(AS
NSStringFromASDimension(size.height)];
}

#pragma mark - ASEdgeInsets

typedef struct {
ASDimension top;
ASDimension left;
ASDimension bottom;
ASDimension right;
} ASEdgeInsets;

extern ASEdgeInsets const ASEdgeInsetsZero;

#pragma mark - ASSizeRange

/**
Expand Down Expand Up @@ -308,5 +298,25 @@ ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT BOOL ASSizeRangeEqualToSizeRange(ASSi
*/
extern AS_WARN_UNUSED_RESULT NSString *NSStringFromASSizeRange(ASSizeRange sizeRange);

#if YOGA

#pragma mark - ASEdgeInsets

typedef struct {
ASDimension top;
ASDimension left;
ASDimension bottom;
ASDimension right;
ASDimension start;
ASDimension end;
ASDimension horizontal;
ASDimension vertical;
ASDimension all;
} ASEdgeInsets;

extern ASEdgeInsets const ASEdgeInsetsZero;

#endif

NS_ASSUME_NONNULL_END
ASDISPLAYNODE_EXTERN_C_END
9 changes: 5 additions & 4 deletions Source/Layout/ASDimension.mm
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,6 @@ ASOVERLOADABLE ASDimension ASDimensionMake(NSString *dimension)

ASLayoutSize const ASLayoutSizeAuto = {ASDimensionAuto, ASDimensionAuto};

#pragma mark - ASEdgeInsets

ASEdgeInsets const ASEdgeInsetsZero = {};

#pragma mark - ASSizeRange

ASSizeRange const ASSizeRangeZero = {};
Expand Down Expand Up @@ -115,3 +111,8 @@ ASSizeRange ASSizeRangeIntersect(ASSizeRange sizeRange, ASSizeRange otherSizeRan
NSStringFromCGSize(sizeRange.min),
NSStringFromCGSize(sizeRange.max)];
}

#if YOGA
#pragma mark - Yoga - ASEdgeInsets
ASEdgeInsets const ASEdgeInsetsZero = {};
#endif
9 changes: 6 additions & 3 deletions Source/Layout/ASLayoutElement.mm
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ @implementation ASLayoutElementStyle {
std::atomic<CGPoint> _layoutPosition;

#if YOGA
std::atomic<ASStackLayoutDirection> _direction;
std::atomic<ASStackLayoutDirection> _flexDirection;
std::atomic<YGDirection> _direction;
std::atomic<CGFloat> _spacing;
std::atomic<ASStackLayoutJustifyContent> _justifyContent;
std::atomic<ASStackLayoutAlignItems> _alignItems;
Expand Down Expand Up @@ -600,7 +601,8 @@ - (NSString *)description

#if YOGA

- (ASStackLayoutDirection)direction { return _direction.load(); }
- (ASStackLayoutDirection)flexDirection { return _flexDirection.load(); }
- (YGDirection)direction { return _direction.load(); }
- (CGFloat)spacing { return _spacing.load(); }
- (ASStackLayoutJustifyContent)justifyContent { return _justifyContent.load(); }
- (ASStackLayoutAlignItems)alignItems { return _alignItems.load(); }
Expand All @@ -612,7 +614,8 @@ - (ASEdgeInsets)border { return _border.load(); }
- (CGFloat)aspectRatio { return _aspectRatio.load(); }
- (YGWrap)flexWrap { return _flexWrap.load(); }

- (void)setDirection:(ASStackLayoutDirection)direction { _direction.store(direction); }
- (void)setFlexDirection:(ASStackLayoutDirection)flexDirection { _flexDirection.store(flexDirection); }
- (void)setDirection:(YGDirection)direction { _direction.store(direction); }
- (void)setSpacing:(CGFloat)spacing { _spacing.store(spacing); }
- (void)setJustifyContent:(ASStackLayoutJustifyContent)justify { _justifyContent.store(justify); }
- (void)setAlignItems:(ASStackLayoutAlignItems)alignItems { _alignItems.store(alignItems); }
Expand Down
8 changes: 8 additions & 0 deletions Source/Private/ASDisplayNode+FrameworkPrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,14 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyState(ASHierarchyStat
*/
- (BOOL)shouldScheduleDisplayWithNewInterfaceState:(ASInterfaceState)newInterfaceState;

/**
* @abstract Informs the root node that the intrinsic size of the receiver is no longer valid.
*
* @discussion The size of a root node is determined by each subnode. Calling invalidateSize will let the root node know
* that the intrinsic size of the receiver node is no longer valid and a resizing of the root node needs to happen.
*/
- (void)_setNeedsLayoutFromAbove;

/**
* @abstract Subclass hook for nodes that are acting as root nodes. This method is called if one of the subnodes
* size is invalidated and may need to result in a different size as the current calculated size.
Expand Down
3 changes: 3 additions & 0 deletions Source/Private/ASDisplayNode+UIViewBridge.mm
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,9 @@ - (void)setSemanticContentAttribute:(UISemanticContentAttribute)semanticContentA
_bridge_prologue_write;
if (AS_AT_LEAST_IOS9) {
_setToViewOnly(semanticContentAttribute, semanticContentAttribute);
#if YOGA
[self semanticContentAttributeDidChange:semanticContentAttribute];
#endif
}
}

Expand Down

0 comments on commit 6f82d0f

Please sign in to comment.