From 8d320ceac24e32703fc550860f401af3bdd53efc Mon Sep 17 00:00:00 2001 From: David Hart Date: Sat, 7 Jan 2017 08:22:43 -0800 Subject: [PATCH] Improved the objective-c and swift api Summary: Compared to what was planned, I added the `overflow` value which seemed missing. I had to modify the implementation a bit for all values which are backed by a `YGValue`, but we should probably enable the pixel dimensions in Objective-C and Swift somehow later. Closes https://github.com/facebook/yoga/pull/322 Reviewed By: dshahidehpour Differential Revision: D4386906 Pulled By: emilsjolander fbshipit-source-id: 05ac0e571ef3a8ff0be31469e449a7b23f102218 --- YogaKit/Tests/YogaKitTests.m | 214 +++++----- YogaKit/UIView+Yoga.h | 70 +-- YogaKit/UIView+Yoga.m | 399 +----------------- YogaKit/YGLayout+Private.h | 16 + YogaKit/YGLayout.h | 114 +++++ YogaKit/YGLayout.m | 380 +++++++++++++++++ .../YogaKitSample.xcodeproj/project.pbxproj | 31 +- .../YogaKitSample/SwiftViewController.swift | 40 ++ .../YogaKitSample/ViewController.m | 18 +- .../YogaKitSample-Bridging-Header.h | 10 + enums.py | 8 +- yoga/YGEnums.h | 56 +-- yoga/YGMacros.h | 16 + 13 files changed, 765 insertions(+), 607 deletions(-) create mode 100644 YogaKit/YGLayout+Private.h create mode 100644 YogaKit/YGLayout.h create mode 100644 YogaKit/YGLayout.m create mode 100644 YogaKit/YogaKitSample/YogaKitSample/SwiftViewController.swift create mode 100644 YogaKit/YogaKitSample/YogaKitSample/YogaKitSample-Bridging-Header.h diff --git a/YogaKit/Tests/YogaKitTests.m b/YogaKit/Tests/YogaKitTests.m index bdade66e..79f7043c 100644 --- a/YogaKit/Tests/YogaKitTests.m +++ b/YogaKit/Tests/YogaKitTests.m @@ -23,7 +23,7 @@ XCTAssertEqual(0, YGNodeGetInstanceCount()); UIView *view = [[UIView alloc] initWithFrame:CGRectZero]; - [view yg_setFlexBasis:1]; + view.yoga.flexBasis = 1; XCTAssertEqual(1, YGNodeGetInstanceCount()); view = nil; @@ -35,11 +35,11 @@ XCTAssertEqual(0, YGNodeGetInstanceCount()); UIView *view = [[UIView alloc] initWithFrame:CGRectZero]; - [view yg_setFlexBasis:1]; + view.yoga.flexBasis = 1; for (int i=0; i<10; i++) { UIView *subview = [[UIView alloc] initWithFrame:CGRectZero]; - [subview yg_setFlexBasis:1]; + subview.yoga.flexBasis = 1; [view addSubview:subview]; } XCTAssertEqual(11, YGNodeGetInstanceCount()); @@ -53,69 +53,69 @@ - (void)testUsesYoga { UIView *view = [[UIView alloc] initWithFrame:CGRectZero]; - XCTAssertFalse([view yg_usesYoga]); + XCTAssertFalse(view.yoga.isEnabled); - [view yg_setUsesYoga:YES]; - XCTAssertTrue([view yg_usesYoga]); + view.yoga.isEnabled = YES; + XCTAssertTrue(view.yoga.isEnabled); - [view yg_setUsesYoga:NO]; - XCTAssertFalse([view yg_usesYoga]); + view.yoga.isEnabled = NO; + XCTAssertFalse(view.yoga.isEnabled); } - (void)testSizeThatFitsAsserts { UIView *view = [[UIView alloc] initWithFrame:CGRectZero]; dispatch_sync(dispatch_queue_create("com.facebook.Yoga.testing", DISPATCH_QUEUE_SERIAL), ^(void){ - XCTAssertThrows([view yg_intrinsicSize]); + XCTAssertThrows(view.yoga.intrinsicSize); }); } - (void)testSizeThatFitsSmoke { UIView *container = [[UIView alloc] initWithFrame:CGRectZero]; - [container yg_setUsesYoga:YES]; - [container yg_setFlexDirection:YGFlexDirectionRow]; - [container yg_setAlignItems:YGAlignFlexStart]; + container.yoga.isEnabled = YES; + container.yoga.flexDirection = YGFlexDirectionRow; + container.yoga.alignItems = YGAlignFlexStart; UILabel *longTextLabel = [[UILabel alloc] initWithFrame:CGRectZero]; longTextLabel.text = @"This is a very very very very very very very very long piece of text."; longTextLabel.lineBreakMode = NSLineBreakByTruncatingTail; longTextLabel.numberOfLines = 1; - [longTextLabel yg_setUsesYoga:YES]; - [longTextLabel yg_setFlexShrink:1]; + longTextLabel.yoga.isEnabled = YES; + longTextLabel.yoga.flexShrink = 1; [container addSubview:longTextLabel]; UIView *textBadgeView = [[UIView alloc] initWithFrame:CGRectZero]; - [textBadgeView yg_setUsesYoga:YES]; - [textBadgeView yg_setMargin:3.0 forEdge:YGEdgeLeft]; - [textBadgeView yg_setWidth:10]; - [textBadgeView yg_setHeight:10]; + textBadgeView.yoga.isEnabled = YES; + textBadgeView.yoga.marginLeft = 3.0; + textBadgeView.yoga.width = 10; + textBadgeView.yoga.height = 10; [container addSubview:textBadgeView]; - const CGSize containerSize = [container yg_intrinsicSize]; + const CGSize containerSize = container.yoga.intrinsicSize; XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(514,21), containerSize), @"Size is actually %@", NSStringFromCGSize(containerSize)); } - (void)testThatMarkingLeafsAsDirtyWillTriggerASizeRecalculation { UIView *container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 50)]; - [container yg_setUsesYoga:YES]; - [container yg_setFlexDirection:YGFlexDirectionRow]; - [container yg_setAlignItems:YGAlignFlexStart]; + container.yoga.isEnabled = YES; + container.yoga.flexDirection = YGFlexDirectionRow; + container.yoga.alignItems = YGAlignFlexStart; UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero]; label.text = @"This is a short text."; label.numberOfLines = 1; - [label yg_setUsesYoga:YES]; + label.yoga.isEnabled = YES; [container addSubview:label]; - [container yg_applyLayout]; + [container.yoga applyLayout]; XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(146,21), label.bounds.size), @"Size is actually %@", NSStringFromCGSize(label.bounds.size)); label.text = @"This is a slightly longer text."; - [label yg_markDirty]; + [label.yoga markDirty]; - [container yg_applyLayout]; + [container.yoga applyLayout]; XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(213,21), label.bounds.size), @"Size is actually %@", NSStringFromCGSize(label.bounds.size)); } @@ -124,17 +124,17 @@ const CGSize containerSize = CGSizeMake(320, 50); UIView *container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, containerSize.width, containerSize.height)]; - [container yg_setUsesYoga:YES]; - [container yg_setFlexDirection:YGFlexDirectionRow]; + container.yoga.isEnabled = YES; + container.yoga.flexDirection = YGFlexDirectionRow; for (int i = 0; i < 3; i++) { UIView *subview = [[UIView alloc] initWithFrame:CGRectZero]; - [subview yg_setUsesYoga:YES]; - [subview yg_setFlexGrow:1]; + subview.yoga.isEnabled = YES; + subview.yoga.flexGrow = 1; [container addSubview:subview]; } - [container yg_applyLayout]; + [container.yoga applyLayout]; XCTAssertFalse(CGRectIntersectsRect([container.subviews objectAtIndex:0].frame, [container.subviews objectAtIndex:1].frame)); XCTAssertFalse(CGRectIntersectsRect([container.subviews objectAtIndex:1].frame, [container.subviews objectAtIndex:2].frame)); @@ -153,33 +153,33 @@ const CGSize containerSize = CGSizeMake(300, 50); UIView *container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, containerSize.width, containerSize.height)]; - [container yg_setUsesYoga:YES]; - [container yg_setFlexDirection:YGFlexDirectionRow]; + container.yoga.isEnabled = YES; + container.yoga.flexDirection = YGFlexDirectionRow; UIView *subview1 = [[UIView alloc] initWithFrame:CGRectZero]; - [subview1 yg_setUsesYoga:YES]; - [subview1 yg_setFlexGrow:1]; + subview1.yoga.isEnabled = YES; + subview1.yoga.flexGrow = 1; [container addSubview:subview1]; UIView *subview2 = [[UIView alloc] initWithFrame:CGRectZero]; - [subview2 yg_setUsesYoga:YES]; - [subview2 yg_setFlexGrow:1]; + subview2.yoga.isEnabled = YES; + subview2.yoga.flexGrow = 1; [container addSubview:subview2]; UIView *subview3 = [[UIView alloc] initWithFrame:CGRectZero]; - [subview3 yg_setUsesYoga:YES]; - [subview3 yg_setFlexGrow:1]; + subview3.yoga.isEnabled = YES; + subview3.yoga.flexGrow = 1; [container addSubview:subview3]; - [container yg_applyLayout]; + [container.yoga applyLayout]; XCTAssertTrue(CGRectEqualToRect(subview1.frame, CGRectMake(0, 0, 100, 50))); XCTAssertTrue(CGRectEqualToRect(subview2.frame, CGRectMake(100, 0, 100, 50)), @"It's actually %@", NSStringFromCGRect(subview2.frame)); XCTAssertTrue(CGRectEqualToRect(subview3.frame, CGRectMake(200, 0, 100, 50))); [container exchangeSubviewAtIndex:2 withSubviewAtIndex:0]; - [subview2 yg_setIncludeInLayout:NO]; - [container yg_applyLayout]; + subview2.yoga.isIncludedInLayout = NO; + [container.yoga applyLayout]; XCTAssertTrue(CGRectEqualToRect(subview3.frame, CGRectMake(0, 0, 150, 50))); XCTAssertTrue(CGRectEqualToRect(subview1.frame, CGRectMake(150, 0, 150, 50))); @@ -193,32 +193,32 @@ const CGSize containerSize = CGSizeMake(300, 50); UIView *container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, containerSize.width, containerSize.height)]; - [container yg_setUsesYoga:YES]; - [container yg_setFlexDirection:YGFlexDirectionRow]; + container.yoga.isEnabled = YES; + container.yoga.flexDirection = YGFlexDirectionRow; UIView *subview1 = [[UIView alloc] initWithFrame:CGRectZero]; - [subview1 yg_setUsesYoga:YES]; - [subview1 yg_setFlexGrow:1]; + subview1.yoga.isEnabled = YES; + subview1.yoga.flexGrow = 1; [container addSubview:subview1]; UIView *subview2 = [[UIView alloc] initWithFrame:CGRectZero]; - [subview2 yg_setUsesYoga:YES]; - [subview2 yg_setFlexGrow:1]; + subview2.yoga.isEnabled = YES; + subview2.yoga.flexGrow = 1; [container addSubview:subview2]; UIView *subview3 = [[UIView alloc] initWithFrame:CGRectZero]; - [subview3 yg_setUsesYoga:YES]; - [subview3 yg_setFlexGrow:1]; + subview3.yoga.isEnabled = YES; + subview3.yoga.flexGrow = 1; [container addSubview:subview3]; - [container yg_applyLayout]; + [container.yoga applyLayout]; for (UIView *view in container.subviews) { XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(100, 50), subview1.bounds.size), @"Actual size is %@", NSStringFromCGSize(view.bounds.size)); } - [subview3 yg_setIncludeInLayout:NO]; - [container yg_applyLayout]; + subview3.yoga.isIncludedInLayout = NO; + [container.yoga applyLayout]; XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(150, 50), subview1.bounds.size), @"Actual size is %@", NSStringFromCGSize(subview1.bounds.size)); XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(150, 50), subview2.bounds.size), @"Actual size is %@", NSStringFromCGSize(subview2.bounds.size)); @@ -230,62 +230,62 @@ - (void)testThatNumberOfChildrenIsCorrectWhenWeIgnoreSubviews { UIView *container = [[UIView alloc] initWithFrame:CGRectZero]; - [container yg_setUsesYoga:YES]; - [container yg_setFlexDirection:YGFlexDirectionRow]; + container.yoga.isEnabled = YES; + container.yoga.flexDirection = YGFlexDirectionRow; UIView *subview1 = [[UIView alloc] initWithFrame:CGRectZero]; - [subview1 yg_setUsesYoga:YES]; - [subview1 yg_setIncludeInLayout:NO]; + subview1.yoga.isEnabled = YES; + subview1.yoga.isIncludedInLayout = NO; [container addSubview:subview1]; UIView *subview2 = [[UIView alloc] initWithFrame:CGRectZero]; - [subview2 yg_setUsesYoga:YES]; - [subview2 yg_setIncludeInLayout:NO]; + subview2.yoga.isEnabled = YES; + subview2.yoga.isIncludedInLayout = NO; [container addSubview:subview2]; UIView *subview3 = [[UIView alloc] initWithFrame:CGRectZero]; - [subview3 yg_setUsesYoga:YES]; - [subview3 yg_setIncludeInLayout:YES]; + subview3.yoga.isEnabled = YES; + subview3.yoga.isIncludedInLayout = YES; [container addSubview:subview3]; - [container yg_applyLayout]; - XCTAssertEqual(1, [container yg_numberOfChildren]); + [container.yoga applyLayout]; + XCTAssertEqual(1, container.yoga.numberOfChildren); - [subview2 yg_setIncludeInLayout:YES]; - [container yg_applyLayout]; - XCTAssertEqual(2, [container yg_numberOfChildren]); + subview2.yoga.isIncludedInLayout = YES; + [container.yoga applyLayout]; + XCTAssertEqual(2, container.yoga.numberOfChildren); } - (void)testThatViewNotIncludedInFirstLayoutPassAreIncludedInSecond { UIView *container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 300, 50)]; - [container yg_setUsesYoga:YES]; - [container yg_setFlexDirection:YGFlexDirectionRow]; + container.yoga.isEnabled = YES; + container.yoga.flexDirection = YGFlexDirectionRow; UIView *subview1 = [[UIView alloc] initWithFrame:CGRectZero]; - [subview1 yg_setUsesYoga:YES]; - [subview1 yg_setFlexGrow:1]; + subview1.yoga.isEnabled = YES; + subview1.yoga.flexGrow = 1; [container addSubview:subview1]; UIView *subview2 = [[UIView alloc] initWithFrame:CGRectZero]; - [subview2 yg_setUsesYoga:YES]; - [subview2 yg_setFlexGrow:1]; + subview2.yoga.isEnabled = YES; + subview2.yoga.flexGrow = 1; [container addSubview:subview2]; UIView *subview3 = [[UIView alloc] initWithFrame:CGRectZero]; - [subview3 yg_setUsesYoga:YES]; - [subview3 yg_setFlexGrow:1]; - [subview3 yg_setIncludeInLayout:NO]; + subview3.yoga.isEnabled = YES; + subview3.yoga.flexGrow = 1; + subview3.yoga.isIncludedInLayout = NO; [container addSubview:subview3]; - [container yg_applyLayout]; + [container.yoga applyLayout]; XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(150, 50), subview1.bounds.size), @"Actual size is %@", NSStringFromCGSize(subview1.bounds.size)); XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(150, 50), subview2.bounds.size), @"Actual size is %@", NSStringFromCGSize(subview2.bounds.size)); XCTAssertTrue(CGSizeEqualToSize(CGSizeZero, subview3.bounds.size), @"Actual size %@", NSStringFromCGSize(subview3.bounds.size)); - [subview3 yg_setIncludeInLayout:YES]; - [container yg_applyLayout]; + subview3.yoga.isIncludedInLayout = YES; + [container.yoga applyLayout]; for (UIView *view in container.subviews) { XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(100, 50), subview1.bounds.size), @"Actual size is %@", NSStringFromCGSize(view.bounds.size)); } @@ -294,60 +294,60 @@ - (void)testyg_isLeafFlag { UIView *view = [[UIView alloc] initWithFrame:CGRectZero]; - XCTAssertTrue(view.yg_isLeaf); + XCTAssertTrue(view.yoga.isLeaf); for (int i=0; i<10; i++) { UIView *subview = [[UIView alloc] initWithFrame:CGRectZero]; [view addSubview:subview]; } - XCTAssertTrue(view.yg_isLeaf); + XCTAssertTrue(view.yoga.isLeaf); - [view yg_setUsesYoga:YES]; - [view yg_setWidth:50.0]; - XCTAssertTrue(view.yg_isLeaf); + view.yoga.isEnabled = YES; + view.yoga.width = 50.0; + XCTAssertTrue(view.yoga.isLeaf); UIView *const subview = view.subviews[0]; - [subview yg_setUsesYoga:YES]; - [subview yg_setWidth:50.0]; - XCTAssertFalse(view.yg_isLeaf); + subview.yoga.isEnabled = YES; + subview.yoga.width = 50.0; + XCTAssertFalse(view.yoga.isLeaf); } - (void)testThatWeCorrectlyAttachNestedViews { UIView *container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 300, 50)]; - [container yg_setUsesYoga:YES]; - [container yg_setFlexDirection:YGFlexDirectionColumn]; + container.yoga.isEnabled = YES; + container.yoga.flexDirection = YGFlexDirectionColumn; UIView *subview1 = [[UIView alloc] initWithFrame:CGRectZero]; - [subview1 yg_setUsesYoga:YES]; - [subview1 yg_setWidth:100]; - [subview1 yg_setFlexGrow:1]; - [subview1 yg_setFlexDirection:YGFlexDirectionColumn]; + subview1.yoga.isEnabled = YES; + subview1.yoga.width = 100; + subview1.yoga.flexGrow = 1; + subview1.yoga.flexDirection = YGFlexDirectionColumn; [container addSubview:subview1]; UIView *subview2 = [[UIView alloc] initWithFrame:CGRectZero]; - [subview2 yg_setUsesYoga:YES]; - [subview2 yg_setWidth:150]; - [subview2 yg_setFlexGrow:1]; - [subview2 yg_setFlexDirection:YGFlexDirectionColumn]; + subview2.yoga.isEnabled = YES; + subview2.yoga.width = 150; + subview2.yoga.flexGrow = 1; + subview2.yoga.flexDirection = YGFlexDirectionColumn; [container addSubview:subview2]; for (UIView *view in @[subview1, subview2]) { UIView *someView = [[UIView alloc] initWithFrame:CGRectZero]; - [someView yg_setUsesYoga:YES]; - [someView yg_setFlexGrow:1]; + someView.yoga.isEnabled = YES; + someView.yoga.flexGrow = 1; [view addSubview:someView]; } - [container yg_applyLayout]; + [container.yoga applyLayout]; // Add the same amount of new views, reapply layout. for (UIView *view in @[subview1, subview2]) { UIView *someView = [[UIView alloc] initWithFrame:CGRectZero]; - [someView yg_setUsesYoga:YES]; - [someView yg_setFlexGrow:1]; + someView.yoga.isEnabled = YES; + someView.yoga.flexGrow = 1; [view addSubview:someView]; } - [container yg_applyLayout]; + [container.yoga applyLayout]; XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(100, 25), subview1.bounds.size), @"Actual size is %@", NSStringFromCGSize(subview1.bounds.size)); for (UIView *subview in subview1.subviews) { @@ -369,19 +369,19 @@ - (void)testThatANonLeafNodeCanBecomeALeafNode { UIView *container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 300, 50)]; - [container yg_setUsesYoga:YES]; + container.yoga.isEnabled = YES; UIView *subview1 = [[UIView alloc] initWithFrame:CGRectZero]; - [subview1 yg_setUsesYoga:YES]; + subview1.yoga.isEnabled = YES; [container addSubview:subview1]; UIView *subview2 = [[UIView alloc] initWithFrame:CGRectZero]; - [subview2 yg_setUsesYoga:YES]; + subview2.yoga.isEnabled = YES; [subview1 addSubview:subview2]; - [container yg_applyLayout]; + [container.yoga applyLayout]; [subview2 removeFromSuperview]; - [container yg_applyLayout]; + [container.yoga applyLayout]; } @end diff --git a/YogaKit/UIView+Yoga.h b/YogaKit/UIView+Yoga.h index b565b1ae..70e91917 100644 --- a/YogaKit/UIView+Yoga.h +++ b/YogaKit/UIView+Yoga.h @@ -8,76 +8,10 @@ */ #import -#import +#import "YGLayout.h" @interface UIView (Yoga) -/** - The property that decides if we should include this view when calculating layout. Defaults to YES. - */ -@property (nonatomic, readwrite, assign, setter=yg_setIncludeInLayout:) BOOL yg_includeInLayout; - -/** - The property that decides during layout/sizing whether or not yg_* properties should be applied. Defaults to NO. - */ -@property (nonatomic, readwrite, assign, setter=yg_setUsesYoga:) BOOL yg_usesYoga; - -- (void)yg_setDirection:(YGDirection)direction; -- (void)yg_setFlexDirection:(YGFlexDirection)flexDirection; -- (void)yg_setJustifyContent:(YGJustify)justifyContent; -- (void)yg_setAlignContent:(YGAlign)alignContent; -- (void)yg_setAlignItems:(YGAlign)alignItems; -- (void)yg_setAlignSelf:(YGAlign)alignSelf; -- (void)yg_setPositionType:(YGPositionType)positionType; -- (void)yg_setFlexWrap:(YGWrap)flexWrap; -- (void)yg_setOverflow:(YGOverflow)overflow; - -- (void)yg_setFlexGrow:(CGFloat)flexGrow; -- (void)yg_setFlexShrink:(CGFloat)flexShrink; -- (void)yg_setFlexBasis:(CGFloat)flexBasis; - -- (void)yg_setPosition:(CGFloat)position forEdge:(YGEdge)edge; -- (void)yg_setMargin:(CGFloat)margin forEdge:(YGEdge)edge; -- (void)yg_setPadding:(CGFloat)padding forEdge:(YGEdge)edge; - -- (void)yg_setWidth:(CGFloat)width; -- (void)yg_setHeight:(CGFloat)height; -- (void)yg_setMinWidth:(CGFloat)minWidth; -- (void)yg_setMinHeight:(CGFloat)minHeight; -- (void)yg_setMaxWidth:(CGFloat)maxWidth; -- (void)yg_setMaxHeight:(CGFloat)maxHeight; - -// Yoga specific properties, not compatible with flexbox specification -- (void)yg_setAspectRatio:(CGFloat)aspectRatio; - -/** - Get the resolved direction of this node. This won't be YGDirectionInherit - */ -- (YGDirection)yg_resolvedDirection; - -/** - Perform a layout calculation and update the frames of the views in the hierarchy with the results - */ -- (void)yg_applyLayout; - -/** - Returns the size of the view if no constraints were given. This could equivalent to calling [self sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)]; - */ -- (CGSize)yg_intrinsicSize; - -/** - Returns the number of children that are using Flexbox. - */ -- (NSUInteger)yg_numberOfChildren; - -/** - Return a BOOL indiciating whether or not we this node contains any subviews that are included in Yoga's layout. - */ -- (BOOL)yg_isLeaf; - -/** - Mark that a view's layout needs to be recalculated. Only works for leaf views. - */ -- (void)yg_markDirty; +@property (nonatomic, readonly, strong) YGLayout *yoga; @end diff --git a/YogaKit/UIView+Yoga.m b/YogaKit/UIView+Yoga.m index cfe0afa0..66b7eed0 100644 --- a/YogaKit/UIView+Yoga.m +++ b/YogaKit/UIView+Yoga.m @@ -8,404 +8,23 @@ */ #import "UIView+Yoga.h" - +#import "YGLayout+Private.h" #import -static const void *kYGNodeBridgeAssociatedKey = &kYGNodeBridgeAssociatedKey; +static const void *kYGYogaAssociatedKey = &kYGYogaAssociatedKey; -@interface YGNodeBridge : NSObject -@property (nonatomic, assign, readonly) YGNodeRef cnode; -@end +@implementation UIView (YogaKit) -@implementation YGNodeBridge - -+ (void)initialize +- (YGLayout *)yoga { - YGSetExperimentalFeatureEnabled(YGExperimentalFeatureWebFlexBasis, true); -} - -- (instancetype)init -{ - if ([super init]) { - _cnode = YGNodeNew(); + YGLayout *yoga = objc_getAssociatedObject(self, kYGYogaAssociatedKey); + if (!yoga) { + yoga = [[YGLayout alloc] initWithView:self]; + objc_setAssociatedObject(self, kYGYogaAssociatedKey, yoga, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - return self; + return yoga; } -- (void)dealloc -{ - YGNodeFree(_cnode); -} -@end - -@implementation UIView (Yoga) - -- (void)yg_markDirty -{ - YGNodeBridge *const bridge = objc_getAssociatedObject(self, kYGNodeBridgeAssociatedKey); - if (bridge != nil && [self yg_isLeaf]) { - YGNodeMarkDirty(bridge.cnode); - } -} - -- (BOOL)yg_usesYoga -{ - NSNumber *usesYoga = objc_getAssociatedObject(self, @selector(yg_usesYoga)); - return [usesYoga boolValue]; -} - -- (BOOL)yg_includeInLayout -{ - NSNumber *includeInLayout = objc_getAssociatedObject(self, @selector(yg_includeInLayout)); - return (includeInLayout != nil) ? [includeInLayout boolValue] : YES; -} - -- (NSUInteger)yg_numberOfChildren -{ - return YGNodeGetChildCount([self ygNode]); -} - -- (BOOL)yg_isLeaf -{ - NSAssert([NSThread isMainThread], @"This method must be called on the main thread."); - if ([self yg_usesYoga]) { - for (UIView *subview in self.subviews) { - if ([subview yg_usesYoga] && [subview yg_includeInLayout]) { - return NO; - } - } - } - - return YES; -} - -#pragma mark - Setters - -- (void)yg_setIncludeInLayout:(BOOL)includeInLayout -{ - objc_setAssociatedObject( - self, - @selector(yg_includeInLayout), - @(includeInLayout), - OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -- (void)yg_setUsesYoga:(BOOL)enabled -{ - objc_setAssociatedObject( - self, - @selector(yg_usesYoga), - @(enabled), - OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -- (void)yg_setDirection:(YGDirection)direction -{ - YGNodeStyleSetDirection([self ygNode], direction); -} - -- (void)yg_setFlexDirection:(YGFlexDirection)flexDirection -{ - YGNodeStyleSetFlexDirection([self ygNode], flexDirection); -} - -- (void)yg_setJustifyContent:(YGJustify)justifyContent -{ - YGNodeStyleSetJustifyContent([self ygNode], justifyContent); -} - -- (void)yg_setAlignContent:(YGAlign)alignContent -{ - YGNodeStyleSetAlignContent([self ygNode], alignContent); -} - -- (void)yg_setAlignItems:(YGAlign)alignItems -{ - YGNodeStyleSetAlignItems([self ygNode], alignItems); -} - -- (void)yg_setAlignSelf:(YGAlign)alignSelf -{ - YGNodeStyleSetAlignSelf([self ygNode], alignSelf); -} - -- (void)yg_setPositionType:(YGPositionType)positionType -{ - YGNodeStyleSetPositionType([self ygNode], positionType); -} - -- (void)yg_setFlexWrap:(YGWrap)flexWrap -{ - YGNodeStyleSetFlexWrap([self ygNode], flexWrap); -} - -- (void)yg_setOverflow:(YGOverflow)overflow -{ - YGNodeStyleSetOverflow([self ygNode], overflow); -} - -- (void)yg_setFlexGrow:(CGFloat)flexGrow -{ - YGNodeStyleSetFlexGrow([self ygNode], flexGrow); -} - -- (void)yg_setFlexShrink:(CGFloat)flexShrink -{ - YGNodeStyleSetFlexShrink([self ygNode], flexShrink); -} - -- (void)yg_setFlexBasis:(CGFloat)flexBasis -{ - YGNodeStyleSetFlexBasis([self ygNode], flexBasis); -} - -- (void)yg_setPosition:(CGFloat)position forEdge:(YGEdge)edge -{ - YGNodeStyleSetPosition([self ygNode], edge, position); -} - -- (void)yg_setMargin:(CGFloat)margin forEdge:(YGEdge)edge -{ - YGNodeStyleSetMargin([self ygNode], edge, margin); -} - -- (void)yg_setPadding:(CGFloat)padding forEdge:(YGEdge)edge -{ - YGNodeStyleSetPadding([self ygNode], edge, padding); -} - -- (void)yg_setWidth:(CGFloat)width -{ - YGNodeStyleSetWidth([self ygNode], width); -} - -- (void)yg_setHeight:(CGFloat)height -{ - YGNodeStyleSetHeight([self ygNode], height); -} - -- (void)yg_setMinWidth:(CGFloat)minWidth -{ - YGNodeStyleSetMinWidth([self ygNode], minWidth); -} - -- (void)yg_setMinHeight:(CGFloat)minHeight -{ - YGNodeStyleSetMinHeight([self ygNode], minHeight); -} - -- (void)yg_setMaxWidth:(CGFloat)maxWidth -{ - YGNodeStyleSetMaxWidth([self ygNode], maxWidth); -} - -- (void)yg_setMaxHeight:(CGFloat)maxHeight -{ - YGNodeStyleSetMaxHeight([self ygNode], maxHeight); -} - -- (void)yg_setAspectRatio:(CGFloat)aspectRatio -{ - YGNodeStyleSetAspectRatio([self ygNode], aspectRatio); -} - -#pragma mark - Layout and Sizing - -- (YGDirection)yg_resolvedDirection -{ - return YGNodeLayoutGetDirection([self ygNode]); -} - -- (void)yg_applyLayout -{ - [self calculateLayoutWithSize:self.bounds.size]; - YGApplyLayoutToViewHierarchy(self); -} - -- (CGSize)yg_intrinsicSize -{ - const CGSize constrainedSize = { - .width = YGUndefined, - .height = YGUndefined, - }; - return [self calculateLayoutWithSize:constrainedSize]; -} - -#pragma mark - Private - -- (YGNodeRef)ygNode -{ - YGNodeBridge *node = objc_getAssociatedObject(self, kYGNodeBridgeAssociatedKey); - if (!node) { - node = [YGNodeBridge new]; - YGNodeSetContext(node.cnode, (__bridge void *) self); - objc_setAssociatedObject(self, kYGNodeBridgeAssociatedKey, node, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - } - return node.cnode; -} - -- (CGSize)calculateLayoutWithSize:(CGSize)size -{ - NSAssert([NSThread isMainThread], @"YG Layout calculation must be done on main."); - NSAssert([self yg_usesYoga], @"YG Layout is not enabled for this view."); - - YGAttachNodesFromViewHierachy(self); - - const YGNodeRef node = [self ygNode]; - YGNodeCalculateLayout( - node, - size.width, - size.height, - YGNodeStyleGetDirection(node)); - - return (CGSize) { - .width = YGNodeLayoutGetWidth(node), - .height = YGNodeLayoutGetHeight(node), - }; -} - -static YGSize YGMeasureView( - YGNodeRef node, - float width, - YGMeasureMode widthMode, - float height, - YGMeasureMode heightMode) -{ - const CGFloat constrainedWidth = (widthMode == YGMeasureModeUndefined) ? CGFLOAT_MAX : width; - const CGFloat constrainedHeight = (heightMode == YGMeasureModeUndefined) ? CGFLOAT_MAX: height; - - UIView *view = (__bridge UIView*) YGNodeGetContext(node); - const CGSize sizeThatFits = [view sizeThatFits:(CGSize) { - .width = constrainedWidth, - .height = constrainedHeight, - }]; - - return (YGSize) { - .width = YGSanitizeMeasurement(constrainedWidth, sizeThatFits.width, widthMode), - .height = YGSanitizeMeasurement(constrainedHeight, sizeThatFits.height, heightMode), - }; -} - -static CGFloat YGSanitizeMeasurement( - CGFloat constrainedSize, - CGFloat measuredSize, - YGMeasureMode measureMode) -{ - CGFloat result; - if (measureMode == YGMeasureModeExactly) { - result = constrainedSize; - } else if (measureMode == YGMeasureModeAtMost) { - result = MIN(constrainedSize, measuredSize); - } else { - result = measuredSize; - } - - return result; -} - -static BOOL YGNodeHasExactSameChildren(const YGNodeRef node, NSArray *subviews) -{ - if (YGNodeGetChildCount(node) != subviews.count) { - return NO; - } - - for (int i=0; i *subviewsToInclude = [[NSMutableArray alloc] initWithCapacity:view.subviews.count]; - for (UIView *subview in view.subviews) { - if ([subview yg_includeInLayout]) { - [subviewsToInclude addObject:subview]; - } - } - - if (!YGNodeHasExactSameChildren(node, subviewsToInclude)) { - YGRemoveAllChildren(node); - for (int i=0; i 0) { - YGNodeRemoveChild(node, YGNodeGetChild(node, YGNodeGetChildCount(node) - 1)); - } -} - -static CGFloat YGRoundPixelValue(CGFloat value) -{ - static CGFloat scale; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^(){ - scale = [UIScreen mainScreen].scale; - }); - - return round(value * scale) / scale; -} - -static void YGApplyLayoutToViewHierarchy(UIView *view) -{ - NSCAssert([NSThread isMainThread], @"Framesetting should only be done on the main thread."); - if (![view yg_includeInLayout]) { - return; - } - - YGNodeRef node = [view ygNode]; - const CGPoint topLeft = { - YGNodeLayoutGetLeft(node), - YGNodeLayoutGetTop(node), - }; - - const CGPoint bottomRight = { - topLeft.x + YGNodeLayoutGetWidth(node), - topLeft.y + YGNodeLayoutGetHeight(node), - }; - - view.frame = (CGRect) { - .origin = { - .x = YGRoundPixelValue(topLeft.x), - .y = YGRoundPixelValue(topLeft.y), - }, - .size = { - .width = YGRoundPixelValue(bottomRight.x) - YGRoundPixelValue(topLeft.x), - .height = YGRoundPixelValue(bottomRight.y) - YGRoundPixelValue(topLeft.y), - }, - }; - - if (!view.yg_isLeaf) { - for (NSUInteger i=0; i +#import + +@interface YGLayout : NSObject + +/** + The property that decides if we should include this view when calculating layout. Defaults to YES. + */ +@property (nonatomic, readwrite, assign, setter=setIncludedInLayout:) BOOL isIncludedInLayout; + +/** + The property that decides during layout/sizing whether or not styling properties should be applied. Defaults to NO. + */ +@property (nonatomic, readwrite, assign, setter=setEnabled:) BOOL isEnabled; + +@property (nonatomic, readwrite, assign) YGDirection direction; +@property (nonatomic, readwrite, assign) YGFlexDirection flexDirection; +@property (nonatomic, readwrite, assign) YGJustify justifyContent; +@property (nonatomic, readwrite, assign) YGAlign alignContent; +@property (nonatomic, readwrite, assign) YGAlign alignItems; +@property (nonatomic, readwrite, assign) YGAlign alignSelf; +@property (nonatomic, readwrite, assign) YGPositionType position; +@property (nonatomic, readwrite, assign) YGWrap flexWrap; +@property (nonatomic, readwrite, assign) YGOverflow overflow; + +@property (nonatomic, readwrite, assign) CGFloat flexGrow; +@property (nonatomic, readwrite, assign) CGFloat flexShrink; +@property (nonatomic, readwrite, assign) CGFloat flexBasis; + +@property (nonatomic, readwrite, assign) CGFloat left; +@property (nonatomic, readwrite, assign) CGFloat top; +@property (nonatomic, readwrite, assign) CGFloat right; +@property (nonatomic, readwrite, assign) CGFloat bottom; +@property (nonatomic, readwrite, assign) CGFloat start; +@property (nonatomic, readwrite, assign) CGFloat end; + +@property (nonatomic, readwrite, assign) CGFloat marginLeft; +@property (nonatomic, readwrite, assign) CGFloat marginTop; +@property (nonatomic, readwrite, assign) CGFloat marginRight; +@property (nonatomic, readwrite, assign) CGFloat marginBottom; +@property (nonatomic, readwrite, assign) CGFloat marginStart; +@property (nonatomic, readwrite, assign) CGFloat marginEnd; +@property (nonatomic, readwrite, assign) CGFloat marginHorizontal; +@property (nonatomic, readwrite, assign) CGFloat marginVertical; +@property (nonatomic, readwrite, assign) CGFloat margin; + +@property (nonatomic, readwrite, assign) CGFloat paddingLeft; +@property (nonatomic, readwrite, assign) CGFloat paddingTop; +@property (nonatomic, readwrite, assign) CGFloat paddingRight; +@property (nonatomic, readwrite, assign) CGFloat paddingBottom; +@property (nonatomic, readwrite, assign) CGFloat paddingStart; +@property (nonatomic, readwrite, assign) CGFloat paddingEnd; +@property (nonatomic, readwrite, assign) CGFloat paddingHorizontal; +@property (nonatomic, readwrite, assign) CGFloat paddingVertical; +@property (nonatomic, readwrite, assign) CGFloat padding; + +@property (nonatomic, readwrite, assign) CGFloat borderLeftWidth; +@property (nonatomic, readwrite, assign) CGFloat borderTopWidth; +@property (nonatomic, readwrite, assign) CGFloat borderRightWidth; +@property (nonatomic, readwrite, assign) CGFloat borderBottomWidth; +@property (nonatomic, readwrite, assign) CGFloat borderStartWidth; +@property (nonatomic, readwrite, assign) CGFloat borderEndWidth; +@property (nonatomic, readwrite, assign) CGFloat borderWidth; + +@property (nonatomic, readwrite, assign) CGFloat width; +@property (nonatomic, readwrite, assign) CGFloat height; +@property (nonatomic, readwrite, assign) CGFloat minWidth; +@property (nonatomic, readwrite, assign) CGFloat minHeight; +@property (nonatomic, readwrite, assign) CGFloat maxWidth; +@property (nonatomic, readwrite, assign) CGFloat maxHeight; + +// Yoga specific properties, not compatible with flexbox specification +@property (nonatomic, readwrite, assign) CGFloat aspectRatio; + +/** + Get the resolved direction of this node. This won't be YGDirectionInherit + */ + @property (nonatomic, readonly, assign) YGDirection resolvedDirection; + +/** + Perform a layout calculation and update the frames of the views in the hierarchy with the results + */ +- (void)applyLayout NS_SWIFT_NAME(applyLayout()); + +/** + Returns the size of the view if no constraints were given. This could equivalent to calling [self sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)]; + */ + @property (nonatomic, readonly, assign) CGSize intrinsicSize; + +/** + Returns the number of children that are using Flexbox. + */ + @property (nonatomic, readonly, assign) NSUInteger numberOfChildren; + +/** + Return a BOOL indiciating whether or not we this node contains any subviews that are included in Yoga's layout. + */ + @property (nonatomic, readonly, assign) BOOL isLeaf; + +/** + Mark that a view's layout needs to be recalculated. Only works for leaf views. + */ +- (void)markDirty; + +@end diff --git a/YogaKit/YGLayout.m b/YogaKit/YGLayout.m new file mode 100644 index 00000000..d6a2baf0 --- /dev/null +++ b/YogaKit/YGLayout.m @@ -0,0 +1,380 @@ +/** + * 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 "YGLayout+Private.h" +#import "UIView+Yoga.h" +#import + +#define YG_STYLE_PROPERTY_IMPL(type, lowercased_name, capitalized_name) \ +- (type)lowercased_name \ +{ \ + return YGNodeStyleGet##capitalized_name(self.node); \ +} \ + \ +- (void)set##capitalized_name:(type)lowercased_name \ +{ \ + YGNodeStyleSet##capitalized_name(self.node, lowercased_name); \ +} + +#define YG_STYLE_EDGE_PROPERTY_IMPL(lowercased_name, capitalized_name, property, edge) \ +- (CGFloat)lowercased_name { \ + return YGNodeStyleGet##property(self.node, edge); \ +} \ + \ +- (void)set##capitalized_name:(CGFloat)lowercased_name { \ + YGNodeStyleSet##property(self.node, edge, lowercased_name); \ +} + +#define YG_STYLE_VALUE_PROPERTY_IMPL(lowercased_name, capitalized_name) \ +- (CGFloat)lowercased_name \ +{ \ + YGValue value = YGNodeStyleGet##capitalized_name(self.node); \ + if (value.unit == YGUnitPixel) { \ + return value.value; \ + } else { \ + return YGUndefined; \ + } \ +} \ + \ +- (void)set##capitalized_name:(CGFloat)lowercased_name \ +{ \ + YGNodeStyleSet##capitalized_name(self.node, lowercased_name); \ +} + +#define YG_STYLE_EDGE_PROPERTY_UNIT_IMPL(lowercased_name, capitalized_name, edge, edge_suffix) \ +- (CGFloat)lowercased_name##edge_suffix \ +{ \ + YGValue value = YGNodeStyleGet##capitalized_name(self.node, edge); \ + if (value.unit == YGUnitPixel) { \ + return value.value; \ + } else { \ + return YGUndefined; \ + } \ +} \ + \ +- (void)set##capitalized_name##edge_suffix:(CGFloat)lowercased_name \ +{ \ + YGNodeStyleSet##capitalized_name(self.node, edge, lowercased_name); \ +} + +#define YG_STYLE_ALL_EDGE_PROPERTY_UNIT_IMPL(lowercased_name, capitalized_name) \ +YG_STYLE_EDGE_PROPERTY_UNIT_IMPL(lowercased_name, capitalized_name, YGEdgeLeft, Left) \ +YG_STYLE_EDGE_PROPERTY_UNIT_IMPL(lowercased_name, capitalized_name, YGEdgeTop, Top) \ +YG_STYLE_EDGE_PROPERTY_UNIT_IMPL(lowercased_name, capitalized_name, YGEdgeRight, Right) \ +YG_STYLE_EDGE_PROPERTY_UNIT_IMPL(lowercased_name, capitalized_name, YGEdgeBottom, Bottom) \ +YG_STYLE_EDGE_PROPERTY_UNIT_IMPL(lowercased_name, capitalized_name, YGEdgeStart, Start) \ +YG_STYLE_EDGE_PROPERTY_UNIT_IMPL(lowercased_name, capitalized_name, YGEdgeEnd, End) \ +YG_STYLE_EDGE_PROPERTY_UNIT_IMPL(lowercased_name, capitalized_name, YGEdgeHorizontal, Horizontal) \ +YG_STYLE_EDGE_PROPERTY_UNIT_IMPL(lowercased_name, capitalized_name, YGEdgeVertical, Vertical) \ +YG_STYLE_EDGE_PROPERTY_UNIT_IMPL(lowercased_name, capitalized_name, YGEdgeAll, ) + +@interface YGLayout () + +@property (nonatomic, weak, readonly) UIView *view; +@property (nonatomic, assign, readonly) YGNodeRef node; + +@end + +@implementation YGLayout + +@synthesize isEnabled=_isEnabled; +@synthesize isIncludedInLayout=_isIncludedInLayout; + ++ (void)initialize +{ + YGSetExperimentalFeatureEnabled(YGExperimentalFeatureWebFlexBasis, true); +} + +- (instancetype)initWithView:(UIView*)view +{ + if (self = [super init]) { + _view = view; + _node = YGNodeNew(); + YGNodeSetContext(_node, (__bridge void *) view); + _isEnabled = NO; + _isIncludedInLayout = YES; + } + + return self; +} + +- (void)dealloc +{ + YGNodeFree(self.node); +} + +- (void)markDirty +{ + if (self.isLeaf) { + YGNodeMarkDirty(self.node); + } +} + +- (NSUInteger)numberOfChildren +{ + return YGNodeGetChildCount(self.node); +} + +- (BOOL)isLeaf +{ + NSAssert([NSThread isMainThread], @"This method must be called on the main thread."); + if (self.isEnabled) { + for (UIView *subview in self.view.subviews) { + YGLayout *const yoga = subview.yoga; + if (yoga.isEnabled && yoga.isIncludedInLayout) { + return NO; + } + } + } + + return YES; +} + +#pragma mark - Style + +- (YGPositionType)position +{ + return YGNodeStyleGetPositionType(self.node); +} + +- (void)setPosition:(YGPositionType)position +{ + YGNodeStyleSetPositionType(self.node, position); +} + +YG_STYLE_PROPERTY_IMPL(YGDirection, direction, Direction) +YG_STYLE_PROPERTY_IMPL(YGFlexDirection, flexDirection, FlexDirection) +YG_STYLE_PROPERTY_IMPL(YGJustify, justifyContent, JustifyContent) +YG_STYLE_PROPERTY_IMPL(YGAlign, alignContent, AlignContent) +YG_STYLE_PROPERTY_IMPL(YGAlign, alignItems, AlignItems) +YG_STYLE_PROPERTY_IMPL(YGAlign, alignSelf, AlignSelf) +YG_STYLE_PROPERTY_IMPL(YGWrap, flexWrap, FlexWrap) +YG_STYLE_PROPERTY_IMPL(YGOverflow, overflow, Overflow) + +YG_STYLE_PROPERTY_IMPL(CGFloat, flexGrow, FlexGrow) +YG_STYLE_PROPERTY_IMPL(CGFloat, flexShrink, FlexShrink) +YG_STYLE_VALUE_PROPERTY_IMPL(flexBasis, FlexBasis) + +YG_STYLE_EDGE_PROPERTY_UNIT_IMPL(position, Position, YGEdgeLeft, Left) +YG_STYLE_EDGE_PROPERTY_UNIT_IMPL(position, Position, YGEdgeTop, Top) +YG_STYLE_EDGE_PROPERTY_UNIT_IMPL(position, Position, YGEdgeRight, Right) +YG_STYLE_EDGE_PROPERTY_UNIT_IMPL(position, Position, YGEdgeBottom, Bottom) +YG_STYLE_EDGE_PROPERTY_UNIT_IMPL(position, Position, YGEdgeStart, Start) +YG_STYLE_EDGE_PROPERTY_UNIT_IMPL(position, Position, YGEdgeEnd, End) +YG_STYLE_ALL_EDGE_PROPERTY_UNIT_IMPL(margin, Margin) +YG_STYLE_ALL_EDGE_PROPERTY_UNIT_IMPL(padding, Padding) + +YG_STYLE_EDGE_PROPERTY_IMPL(borderLeftWidth, BorderLeftWidth, Border, YGEdgeLeft) +YG_STYLE_EDGE_PROPERTY_IMPL(borderTopWidth, BorderTopWidth, Border, YGEdgeTop) +YG_STYLE_EDGE_PROPERTY_IMPL(borderRightWidth, BorderRightWidth, Border, YGEdgeRight) +YG_STYLE_EDGE_PROPERTY_IMPL(borderBottomWidth, BorderBottomWidth, Border, YGEdgeBottom) +YG_STYLE_EDGE_PROPERTY_IMPL(borderStartWidth, BorderStartWidth, Border, YGEdgeStart) +YG_STYLE_EDGE_PROPERTY_IMPL(borderEndWidth, BorderEndWidth, Border, YGEdgeEnd) +YG_STYLE_EDGE_PROPERTY_IMPL(borderWidth, BorderWidth, Border, YGEdgeAll) + +YG_STYLE_VALUE_PROPERTY_IMPL(width, Width) +YG_STYLE_VALUE_PROPERTY_IMPL(height, Height) +YG_STYLE_VALUE_PROPERTY_IMPL(minWidth, MinWidth) +YG_STYLE_VALUE_PROPERTY_IMPL(minHeight, MinHeight) +YG_STYLE_VALUE_PROPERTY_IMPL(maxWidth, MaxWidth) +YG_STYLE_VALUE_PROPERTY_IMPL(maxHeight, MaxHeight) +YG_STYLE_PROPERTY_IMPL(CGFloat, aspectRatio, AspectRatio) + +#pragma mark - Layout and Sizing + +- (YGDirection)resolvedDirection +{ + return YGNodeLayoutGetDirection(self.node); +} + +- (void)applyLayout +{ + [self calculateLayoutWithSize:self.view.bounds.size]; + YGApplyLayoutToViewHierarchy(self.view); +} + +- (CGSize)intrinsicSize +{ + const CGSize constrainedSize = { + .width = YGUndefined, + .height = YGUndefined, + }; + return [self calculateLayoutWithSize:constrainedSize]; +} + +#pragma mark - Private + +- (CGSize)calculateLayoutWithSize:(CGSize)size +{ + NSAssert([NSThread isMainThread], @"Yoga calculation must be done on main."); + NSAssert(self.isEnabled, @"Yoga is not enabled for this view."); + + YGAttachNodesFromViewHierachy(self.view); + + const YGNodeRef node = self.node; + YGNodeCalculateLayout( + node, + size.width, + size.height, + YGNodeStyleGetDirection(node)); + + return (CGSize) { + .width = YGNodeLayoutGetWidth(node), + .height = YGNodeLayoutGetHeight(node), + }; +} + +static YGSize YGMeasureView( + YGNodeRef node, + float width, + YGMeasureMode widthMode, + float height, + YGMeasureMode heightMode) +{ + const CGFloat constrainedWidth = (widthMode == YGMeasureModeUndefined) ? CGFLOAT_MAX : width; + const CGFloat constrainedHeight = (heightMode == YGMeasureModeUndefined) ? CGFLOAT_MAX: height; + + UIView *view = (__bridge UIView*) YGNodeGetContext(node); + const CGSize sizeThatFits = [view sizeThatFits:(CGSize) { + .width = constrainedWidth, + .height = constrainedHeight, + }]; + + return (YGSize) { + .width = YGSanitizeMeasurement(constrainedWidth, sizeThatFits.width, widthMode), + .height = YGSanitizeMeasurement(constrainedHeight, sizeThatFits.height, heightMode), + }; +} + +static CGFloat YGSanitizeMeasurement( + CGFloat constrainedSize, + CGFloat measuredSize, + YGMeasureMode measureMode) +{ + CGFloat result; + if (measureMode == YGMeasureModeExactly) { + result = constrainedSize; + } else if (measureMode == YGMeasureModeAtMost) { + result = MIN(constrainedSize, measuredSize); + } else { + result = measuredSize; + } + + return result; +} + +static BOOL YGNodeHasExactSameChildren(const YGNodeRef node, NSArray *subviews) +{ + if (YGNodeGetChildCount(node) != subviews.count) { + return NO; + } + + for (int i=0; i *subviewsToInclude = [[NSMutableArray alloc] initWithCapacity:view.subviews.count]; + for (UIView *subview in view.subviews) { + if (subview.yoga.isIncludedInLayout) { + [subviewsToInclude addObject:subview]; + } + } + + if (!YGNodeHasExactSameChildren(node, subviewsToInclude)) { + YGRemoveAllChildren(node); + for (int i=0; i 0) { + YGNodeRemoveChild(node, YGNodeGetChild(node, YGNodeGetChildCount(node) - 1)); + } +} + +static CGFloat YGRoundPixelValue(CGFloat value) +{ + static CGFloat scale; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^(){ + scale = [UIScreen mainScreen].scale; + }); + + return round(value * scale) / scale; +} + +static void YGApplyLayoutToViewHierarchy(UIView *view) +{ + NSCAssert([NSThread isMainThread], @"Framesetting should only be done on the main thread."); + + const YGLayout *yoga = view.yoga; + + if (!yoga.isIncludedInLayout) { + return; + } + + YGNodeRef node = yoga.node; + const CGPoint topLeft = { + YGNodeLayoutGetLeft(node), + YGNodeLayoutGetTop(node), + }; + + const CGPoint bottomRight = { + topLeft.x + YGNodeLayoutGetWidth(node), + topLeft.y + YGNodeLayoutGetHeight(node), + }; + + view.frame = (CGRect) { + .origin = { + .x = YGRoundPixelValue(topLeft.x), + .y = YGRoundPixelValue(topLeft.y), + }, + .size = { + .width = YGRoundPixelValue(bottomRight.x) - YGRoundPixelValue(topLeft.x), + .height = YGRoundPixelValue(bottomRight.y) - YGRoundPixelValue(topLeft.y), + }, + }; + + if (!yoga.isLeaf) { + for (NSUInteger i=0; i diff --git a/enums.py b/enums.py index 53b2ab53..2f388ccc 100644 --- a/enums.py +++ b/enums.py @@ -119,21 +119,21 @@ def to_java_upper(symbol): root = os.path.dirname(os.path.abspath(__file__)) -# write out C headers +# write out C & Objective-C headers with open(root + '/yoga/YGEnums.h', 'w') as f: f.write(LICENSE) f.write('#pragma once\n\n') f.write('#include "YGMacros.h"\n\n') f.write('YG_EXTERN_C_BEGIN\n\n') for name, values in ENUMS.items(): - f.write('#define YG%sCount %s\n' % (name, len(values))) - f.write('typedef enum YG%s {\n' % name) + f.write('#define YG%sCount %s\n' % (name, len(values))) + f.write('typedef YG_ENUM_BEGIN(YG%s) {\n' % name) for value in values: if isinstance(value, tuple): f.write(' YG%s%s = %d,\n' % (name, value[0], value[1])) else: f.write(' YG%s%s,\n' % (name, value)) - f.write('} YG%s;\n' % name) + f.write('} YG_ENUM_END(YG%s);\n' % name) f.write('\n') f.write('YG_EXTERN_C_END\n') diff --git a/yoga/YGEnums.h b/yoga/YGEnums.h index 24606bcd..0a10bbc2 100644 --- a/yoga/YGEnums.h +++ b/yoga/YGEnums.h @@ -14,29 +14,29 @@ YG_EXTERN_C_BEGIN #define YGFlexDirectionCount 4 -typedef enum YGFlexDirection { +typedef YG_ENUM_BEGIN(YGFlexDirection) { YGFlexDirectionColumn, YGFlexDirectionColumnReverse, YGFlexDirectionRow, YGFlexDirectionRowReverse, -} YGFlexDirection; +} YG_ENUM_END(YGFlexDirection); #define YGMeasureModeCount 3 -typedef enum YGMeasureMode { +typedef YG_ENUM_BEGIN(YGMeasureMode) { YGMeasureModeUndefined, YGMeasureModeExactly, YGMeasureModeAtMost, -} YGMeasureMode; +} YG_ENUM_END(YGMeasureMode); #define YGPrintOptionsCount 3 -typedef enum YGPrintOptions { +typedef YG_ENUM_BEGIN(YGPrintOptions) { YGPrintOptionsLayout = 1, YGPrintOptionsStyle = 2, YGPrintOptionsChildren = 4, -} YGPrintOptions; +} YG_ENUM_END(YGPrintOptions); #define YGEdgeCount 9 -typedef enum YGEdge { +typedef YG_ENUM_BEGIN(YGEdge) { YGEdgeLeft, YGEdgeTop, YGEdgeRight, @@ -46,79 +46,79 @@ typedef enum YGEdge { YGEdgeHorizontal, YGEdgeVertical, YGEdgeAll, -} YGEdge; +} YG_ENUM_END(YGEdge); #define YGPositionTypeCount 2 -typedef enum YGPositionType { +typedef YG_ENUM_BEGIN(YGPositionType) { YGPositionTypeRelative, YGPositionTypeAbsolute, -} YGPositionType; +} YG_ENUM_END(YGPositionType); #define YGDimensionCount 2 -typedef enum YGDimension { +typedef YG_ENUM_BEGIN(YGDimension) { YGDimensionWidth, YGDimensionHeight, -} YGDimension; +} YG_ENUM_END(YGDimension); #define YGJustifyCount 5 -typedef enum YGJustify { +typedef YG_ENUM_BEGIN(YGJustify) { YGJustifyFlexStart, YGJustifyCenter, YGJustifyFlexEnd, YGJustifySpaceBetween, YGJustifySpaceAround, -} YGJustify; +} YG_ENUM_END(YGJustify); #define YGDirectionCount 3 -typedef enum YGDirection { +typedef YG_ENUM_BEGIN(YGDirection) { YGDirectionInherit, YGDirectionLTR, YGDirectionRTL, -} YGDirection; +} YG_ENUM_END(YGDirection); #define YGLogLevelCount 5 -typedef enum YGLogLevel { +typedef YG_ENUM_BEGIN(YGLogLevel) { YGLogLevelError, YGLogLevelWarn, YGLogLevelInfo, YGLogLevelDebug, YGLogLevelVerbose, -} YGLogLevel; +} YG_ENUM_END(YGLogLevel); #define YGWrapCount 2 -typedef enum YGWrap { +typedef YG_ENUM_BEGIN(YGWrap) { YGWrapNoWrap, YGWrapWrap, -} YGWrap; +} YG_ENUM_END(YGWrap); #define YGOverflowCount 3 -typedef enum YGOverflow { +typedef YG_ENUM_BEGIN(YGOverflow) { YGOverflowVisible, YGOverflowHidden, YGOverflowScroll, -} YGOverflow; +} YG_ENUM_END(YGOverflow); #define YGExperimentalFeatureCount 2 -typedef enum YGExperimentalFeature { +typedef YG_ENUM_BEGIN(YGExperimentalFeature) { YGExperimentalFeatureRounding, YGExperimentalFeatureWebFlexBasis, -} YGExperimentalFeature; +} YG_ENUM_END(YGExperimentalFeature); #define YGAlignCount 6 -typedef enum YGAlign { +typedef YG_ENUM_BEGIN(YGAlign) { YGAlignAuto, YGAlignFlexStart, YGAlignCenter, YGAlignFlexEnd, YGAlignStretch, YGAlignBaseline, -} YGAlign; +} YG_ENUM_END(YGAlign); #define YGUnitCount 3 -typedef enum YGUnit { +typedef YG_ENUM_BEGIN(YGUnit) { YGUnitUndefined, YGUnitPixel, YGUnitPercent, -} YGUnit; +} YG_ENUM_END(YGUnit); YG_EXTERN_C_END diff --git a/yoga/YGMacros.h b/yoga/YGMacros.h index def8371a..d6724272 100644 --- a/yoga/YGMacros.h +++ b/yoga/YGMacros.h @@ -40,3 +40,19 @@ YG_ABORT(); \ } #endif + +#ifndef YG_ENUM_BEGIN +#ifndef NS_ENUM +#define YG_ENUM_BEGIN(name) enum name +#else +#define YG_ENUM_BEGIN(name) NS_ENUM(NSInteger, name) +#endif +#endif + +#ifndef YG_ENUM_END +#ifndef NS_ENUM +#define YG_ENUM_END(name) name +#else +#define YG_ENUM_END(name) +#endif +#endif