From 9d35dce63eb2d7d9ca020d04f255e1083667394e Mon Sep 17 00:00:00 2001 From: yiminghe Date: Tue, 20 Dec 2016 03:20:49 -0800 Subject: [PATCH 01/11] explicit default justifyContent value Summary: make default justifyContent value explicit like alignItems https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content https://developer.mozilla.org/en-US/docs/Web/CSS/align-items https://developer.mozilla.org/en-US/docs/Web/CSS/align-content Closes https://github.com/facebook/yoga/pull/293 Differential Revision: D4351597 Pulled By: emilsjolander fbshipit-source-id: b65ff2284ede4d75f1cc5e22d4106042ab0b0d02 --- yoga/Yoga.c | 1 + 1 file changed, 1 insertion(+) diff --git a/yoga/Yoga.c b/yoga/Yoga.c index dad81116..2e34e4f7 100644 --- a/yoga/Yoga.c +++ b/yoga/Yoga.c @@ -197,6 +197,7 @@ static void YGNodeInit(const YGNodeRef node) { node->style.flexBasis = YGUndefined; node->style.alignItems = YGAlignStretch; + node->style.justifyContent = YGJustifyFlexStart; node->style.alignContent = YGAlignFlexStart; node->style.direction = YGDirectionInherit; From 835bb1cd9c30884f28c5c68b43dd6748b56c5961 Mon Sep 17 00:00:00 2001 From: Dustin Shahidehpour Date: Tue, 20 Dec 2016 11:31:55 -0800 Subject: [PATCH 02/11] Add isLeaf flag to Views. Summary: While building some views with YogaKit, I found an interesting problem. If you are apply `yg_` properties to a view which is constructed of subclassed views (that don't use Yoga.) we still treat view (and it's subviews) like they are using YogaKit. This fixes that behavior. Reviewed By: emilsjolander Differential Revision: D4348344 fbshipit-source-id: 37b8648918529f55afea1d1883a03af398aac849 --- YogaKit/Tests/YogaKitTests.m | 21 +++++++++++++++++++++ YogaKit/UIView+Yoga.h | 5 +++++ YogaKit/UIView+Yoga.m | 25 ++++++++++++++++++++----- 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/YogaKit/Tests/YogaKitTests.m b/YogaKit/Tests/YogaKitTests.m index 99927ade..b54e308b 100644 --- a/YogaKit/Tests/YogaKitTests.m +++ b/YogaKit/Tests/YogaKitTests.m @@ -268,4 +268,25 @@ } } +- (void)testyg_isLeafFlag +{ + UIView *view = [[UIView alloc] initWithFrame:CGRectZero]; + XCTAssertTrue(view.yg_isLeaf); + + for (int i=0; i<10; i++) { + UIView *subview = [[UIView alloc] initWithFrame:CGRectZero]; + [view addSubview:subview]; + } + XCTAssertTrue(view.yg_isLeaf); + + [view yg_setUsesYoga:YES]; + [view yg_setWidth:50.0]; + XCTAssertTrue(view.yg_isLeaf); + + UIView *const subview = view.subviews[0]; + [subview yg_setUsesYoga:YES]; + [subview yg_setWidth:50.0]; + XCTAssertFalse(view.yg_isLeaf); +} + @end diff --git a/YogaKit/UIView+Yoga.h b/YogaKit/UIView+Yoga.h index 386416b2..734c13d4 100644 --- a/YogaKit/UIView+Yoga.h +++ b/YogaKit/UIView+Yoga.h @@ -69,4 +69,9 @@ */ - (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; + @end diff --git a/YogaKit/UIView+Yoga.m b/YogaKit/UIView+Yoga.m index 241ad33e..776304f0 100644 --- a/YogaKit/UIView+Yoga.m +++ b/YogaKit/UIView+Yoga.m @@ -56,6 +56,20 @@ 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 @@ -276,11 +290,12 @@ static CGFloat YGSanitizeMeasurement( return result; } -static void YGAttachNodesFromViewHierachy(UIView *view) { +static void YGAttachNodesFromViewHierachy(UIView *view) +{ YGNodeRef node = [view ygNode]; // Only leaf nodes should have a measure function - if (![view yg_usesYoga] || view.subviews.count == 0) { + if (view.yg_isLeaf) { YGNodeSetMeasureFunc(node, YGMeasureView); YGRemoveAllChildren(node); } else { @@ -340,7 +355,8 @@ static CGFloat YGRoundPixelValue(CGFloat value) return round(value * scale) / scale; } -static void YGApplyLayoutToViewHierarchy(UIView *view) { +static void YGApplyLayoutToViewHierarchy(UIView *view) +{ NSCAssert([NSThread isMainThread], @"Framesetting should only be done on the main thread."); if (![view yg_includeInLayout]) { return; @@ -368,8 +384,7 @@ static void YGApplyLayoutToViewHierarchy(UIView *view) { }, }; - const BOOL isLeaf = ![view yg_usesYoga] || view.subviews.count == 0; - if (!isLeaf) { + if (!view.yg_isLeaf) { for (NSUInteger i = 0; i < view.subviews.count; i++) { YGApplyLayoutToViewHierarchy(view.subviews[i]); } From 7ec3607446ba91a25801e60232fe8b33a12f7ab6 Mon Sep 17 00:00:00 2001 From: Dustin Shahidehpour Date: Wed, 21 Dec 2016 10:54:31 -0800 Subject: [PATCH 03/11] Fix diffing algorithm for reattaching views. Summary: There is a bug currently where we don't traverse the entire tree to detect view hierarchy changes. Currently, if you had a hierachy like this: ``` container = UIView container.subviews = @[subview1, subview2]; subview1.subviews = @[sub11, sub12, sub13]; subview2.subviews = @[sub21, sub22, sub23]; ``` and then modified via: ``` subview2.subviews = @[newView1, newView2, newView3]; ``` our algorithm wouldn't identify that we had new views that needed their layout calculated, and would cause a crash later on. Reviewed By: emilsjolander Differential Revision: D4357662 fbshipit-source-id: 2f61f213c5f1a62948a653f3b1fa3d874c5075f7 --- YogaKit/Tests/YogaKitTests.m | 54 ++++++++++++++++++++++++++++++++++++ YogaKit/UIView+Yoga.m | 43 +++++++++++++++------------- 2 files changed, 77 insertions(+), 20 deletions(-) diff --git a/YogaKit/Tests/YogaKitTests.m b/YogaKit/Tests/YogaKitTests.m index b54e308b..68d97dd5 100644 --- a/YogaKit/Tests/YogaKitTests.m +++ b/YogaKit/Tests/YogaKitTests.m @@ -289,4 +289,58 @@ XCTAssertFalse(view.yg_isLeaf); } +- (void)testThatWeCorrectlyAttachNestedViews +{ + UIView *container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 300, 50)]; + [container yg_setUsesYoga:YES]; + [container yg_setFlexDirection:YGFlexDirectionColumn]; + + UIView *subview1 = [[UIView alloc] initWithFrame:CGRectZero]; + [subview1 yg_setUsesYoga:YES]; + [subview1 yg_setWidth:100]; + [subview1 yg_setFlexGrow:1]; + [subview1 yg_setFlexDirection: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]; + [container addSubview:subview2]; + + for (UIView *view in @[subview1, subview2]) { + UIView *someView = [[UIView alloc] initWithFrame:CGRectZero]; + [someView yg_setUsesYoga:YES]; + [someView yg_setFlexGrow:1]; + [view addSubview:someView]; + } + [container yg_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]; + [view addSubview:someView]; + } + [container yg_applyLayout]; + + XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(100, 25), subview1.bounds.size), @"Actual size is %@", NSStringFromCGSize(subview1.bounds.size)); + for (UIView *subview in subview1.subviews) { + const CGSize subviewSize = subview.bounds.size; + XCTAssertFalse(CGSizeEqualToSize(CGSizeZero, subviewSize)); + XCTAssertFalse(isnan(subviewSize.height)); + XCTAssertFalse(isnan(subviewSize.width)); + } + + XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(150, 25), subview2.bounds.size), @"Actual size is %@", NSStringFromCGSize(subview2.bounds.size)); + for (UIView *subview in subview2.subviews) { + const CGSize subviewSize = subview.bounds.size; + XCTAssertFalse(CGSizeEqualToSize(CGSizeZero, subview.bounds.size)); + XCTAssertFalse(isnan(subviewSize.height)); + XCTAssertFalse(isnan(subviewSize.width)); + } +} + @end diff --git a/YogaKit/UIView+Yoga.m b/YogaKit/UIView+Yoga.m index 776304f0..980a7683 100644 --- a/YogaKit/UIView+Yoga.m +++ b/YogaKit/UIView+Yoga.m @@ -290,9 +290,24 @@ static CGFloat YGSanitizeMeasurement( return result; } -static void YGAttachNodesFromViewHierachy(UIView *view) +static BOOL YGNodeHasExactSameChildren(const YGNodeRef node, NSArray *subviews) { - YGNodeRef node = [view ygNode]; + 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]) { @@ -309,26 +323,15 @@ static void YGAttachNodesFromViewHierachy(UIView *view) } } - BOOL shouldReconstructChildList = NO; - if (YGNodeGetChildCount(node) != subviewsToInclude.count) { - shouldReconstructChildList = YES; - } else { - for (int i = 0; i < subviewsToInclude.count; i++) { - if (YGNodeGetChild(node, i) != [subviewsToInclude[i] ygNode]) { - shouldReconstructChildList = YES; - break; + if (!YGNodeHasExactSameChildren(node, subviewsToInclude)) { + YGRemoveAllChildren(node); + for (int i=0; i Date: Wed, 21 Dec 2016 11:37:32 -0800 Subject: [PATCH 04/11] Test for minHeight with flexing Summary: This test creates a repro case for Yoga to emulate UFI layout failure Reviewed By: emilsjolander Differential Revision: D4313632 fbshipit-source-id: 35be7d86b50a9ae08c81891a889a74e4b61f2d27 --- .../Facebook.Yoga/YGMinMaxDimensionTest.cs | 221 ++++++++++++++++++ gentest/fixtures/YGMinMaxDimensionTest.html | 22 ++ .../facebook/yoga/YGMinMaxDimensionTest.java | 217 +++++++++++++++++ tests/YGMinMaxDimensionTest.cpp | 213 +++++++++++++++++ yoga/Yoga.c | 33 ++- 5 files changed, 703 insertions(+), 3 deletions(-) diff --git a/csharp/tests/Facebook.Yoga/YGMinMaxDimensionTest.cs b/csharp/tests/Facebook.Yoga/YGMinMaxDimensionTest.cs index 04413d41..39eca9ca 100644 --- a/csharp/tests/Facebook.Yoga/YGMinMaxDimensionTest.cs +++ b/csharp/tests/Facebook.Yoga/YGMinMaxDimensionTest.cs @@ -454,5 +454,226 @@ namespace Facebook.Yoga Assert.AreEqual(20f, root_child0_child0.LayoutHeight); } + [Test] + public void Test_flex_grow_within_constrained_min_row() + { + YogaNode root = new YogaNode(); + root.FlexDirection = YogaFlexDirection.Row; + root.MinWidth = 100f; + root.Height = 100f; + + YogaNode root_child0 = new YogaNode(); + root_child0.FlexGrow = 1f; + root.Insert(0, root_child0); + + YogaNode root_child1 = new YogaNode(); + root_child1.Width = 50f; + root.Insert(1, root_child1); + root.StyleDirection = YogaDirection.LTR; + root.CalculateLayout(); + + Assert.AreEqual(0f, root.LayoutX); + Assert.AreEqual(0f, root.LayoutY); + Assert.AreEqual(100f, root.LayoutWidth); + Assert.AreEqual(100f, root.LayoutHeight); + + Assert.AreEqual(0f, root_child0.LayoutX); + Assert.AreEqual(0f, root_child0.LayoutY); + Assert.AreEqual(50f, root_child0.LayoutWidth); + Assert.AreEqual(100f, root_child0.LayoutHeight); + + Assert.AreEqual(50f, root_child1.LayoutX); + Assert.AreEqual(0f, root_child1.LayoutY); + Assert.AreEqual(50f, root_child1.LayoutWidth); + Assert.AreEqual(100f, root_child1.LayoutHeight); + + root.StyleDirection = YogaDirection.RTL; + root.CalculateLayout(); + + Assert.AreEqual(0f, root.LayoutX); + Assert.AreEqual(0f, root.LayoutY); + Assert.AreEqual(100f, root.LayoutWidth); + Assert.AreEqual(100f, root.LayoutHeight); + + Assert.AreEqual(50f, root_child0.LayoutX); + Assert.AreEqual(0f, root_child0.LayoutY); + Assert.AreEqual(50f, root_child0.LayoutWidth); + Assert.AreEqual(100f, root_child0.LayoutHeight); + + Assert.AreEqual(0f, root_child1.LayoutX); + Assert.AreEqual(0f, root_child1.LayoutY); + Assert.AreEqual(50f, root_child1.LayoutWidth); + Assert.AreEqual(100f, root_child1.LayoutHeight); + } + + [Test] + public void Test_flex_grow_within_constrained_min_column() + { + YogaNode root = new YogaNode(); + root.MinHeight = 100f; + + YogaNode root_child0 = new YogaNode(); + root_child0.FlexGrow = 1f; + root.Insert(0, root_child0); + + YogaNode root_child1 = new YogaNode(); + root_child1.Height = 50f; + root.Insert(1, root_child1); + root.StyleDirection = YogaDirection.LTR; + root.CalculateLayout(); + + Assert.AreEqual(0f, root.LayoutX); + Assert.AreEqual(0f, root.LayoutY); + Assert.AreEqual(0f, root.LayoutWidth); + Assert.AreEqual(100f, root.LayoutHeight); + + Assert.AreEqual(0f, root_child0.LayoutX); + Assert.AreEqual(0f, root_child0.LayoutY); + Assert.AreEqual(0f, root_child0.LayoutWidth); + Assert.AreEqual(50f, root_child0.LayoutHeight); + + Assert.AreEqual(0f, root_child1.LayoutX); + Assert.AreEqual(50f, root_child1.LayoutY); + Assert.AreEqual(0f, root_child1.LayoutWidth); + Assert.AreEqual(50f, root_child1.LayoutHeight); + + root.StyleDirection = YogaDirection.RTL; + root.CalculateLayout(); + + Assert.AreEqual(0f, root.LayoutX); + Assert.AreEqual(0f, root.LayoutY); + Assert.AreEqual(0f, root.LayoutWidth); + Assert.AreEqual(100f, root.LayoutHeight); + + Assert.AreEqual(0f, root_child0.LayoutX); + Assert.AreEqual(0f, root_child0.LayoutY); + Assert.AreEqual(0f, root_child0.LayoutWidth); + Assert.AreEqual(50f, root_child0.LayoutHeight); + + Assert.AreEqual(0f, root_child1.LayoutX); + Assert.AreEqual(50f, root_child1.LayoutY); + Assert.AreEqual(0f, root_child1.LayoutWidth); + Assert.AreEqual(50f, root_child1.LayoutHeight); + } + + [Test] + public void Test_flex_grow_within_constrained_max_row() + { + YogaNode root = new YogaNode(); + root.Width = 200f; + + YogaNode root_child0 = new YogaNode(); + root_child0.FlexDirection = YogaFlexDirection.Row; + root_child0.MaxWidth = 100f; + root_child0.Height = 100f; + root.Insert(0, root_child0); + + YogaNode root_child0_child0 = new YogaNode(); + root_child0_child0.FlexShrink = 1f; + root_child0_child0.FlexBasis = 100f; + root_child0.Insert(0, root_child0_child0); + + YogaNode root_child0_child1 = new YogaNode(); + root_child0_child1.Width = 50f; + root_child0.Insert(1, root_child0_child1); + root.StyleDirection = YogaDirection.LTR; + root.CalculateLayout(); + + Assert.AreEqual(0f, root.LayoutX); + Assert.AreEqual(0f, root.LayoutY); + Assert.AreEqual(200f, root.LayoutWidth); + Assert.AreEqual(100f, root.LayoutHeight); + + Assert.AreEqual(0f, root_child0.LayoutX); + Assert.AreEqual(0f, root_child0.LayoutY); + Assert.AreEqual(100f, root_child0.LayoutWidth); + Assert.AreEqual(100f, root_child0.LayoutHeight); + + Assert.AreEqual(0f, root_child0_child0.LayoutX); + Assert.AreEqual(0f, root_child0_child0.LayoutY); + Assert.AreEqual(50f, root_child0_child0.LayoutWidth); + Assert.AreEqual(100f, root_child0_child0.LayoutHeight); + + Assert.AreEqual(50f, root_child0_child1.LayoutX); + Assert.AreEqual(0f, root_child0_child1.LayoutY); + Assert.AreEqual(50f, root_child0_child1.LayoutWidth); + Assert.AreEqual(100f, root_child0_child1.LayoutHeight); + + root.StyleDirection = YogaDirection.RTL; + root.CalculateLayout(); + + Assert.AreEqual(0f, root.LayoutX); + Assert.AreEqual(0f, root.LayoutY); + Assert.AreEqual(200f, root.LayoutWidth); + Assert.AreEqual(100f, root.LayoutHeight); + + Assert.AreEqual(100f, root_child0.LayoutX); + Assert.AreEqual(0f, root_child0.LayoutY); + Assert.AreEqual(100f, root_child0.LayoutWidth); + Assert.AreEqual(100f, root_child0.LayoutHeight); + + Assert.AreEqual(50f, root_child0_child0.LayoutX); + Assert.AreEqual(0f, root_child0_child0.LayoutY); + Assert.AreEqual(50f, root_child0_child0.LayoutWidth); + Assert.AreEqual(100f, root_child0_child0.LayoutHeight); + + Assert.AreEqual(0f, root_child0_child1.LayoutX); + Assert.AreEqual(0f, root_child0_child1.LayoutY); + Assert.AreEqual(50f, root_child0_child1.LayoutWidth); + Assert.AreEqual(100f, root_child0_child1.LayoutHeight); + } + + [Test] + public void Test_flex_grow_within_constrained_max_column() + { + YogaNode root = new YogaNode(); + root.Width = 100f; + root.MaxHeight = 100f; + + YogaNode root_child0 = new YogaNode(); + root_child0.FlexShrink = 1f; + root_child0.FlexBasis = 100f; + root.Insert(0, root_child0); + + YogaNode root_child1 = new YogaNode(); + root_child1.Height = 50f; + root.Insert(1, root_child1); + root.StyleDirection = YogaDirection.LTR; + root.CalculateLayout(); + + Assert.AreEqual(0f, root.LayoutX); + Assert.AreEqual(0f, root.LayoutY); + Assert.AreEqual(100f, root.LayoutWidth); + Assert.AreEqual(100f, root.LayoutHeight); + + Assert.AreEqual(0f, root_child0.LayoutX); + Assert.AreEqual(0f, root_child0.LayoutY); + Assert.AreEqual(100f, root_child0.LayoutWidth); + Assert.AreEqual(50f, root_child0.LayoutHeight); + + Assert.AreEqual(0f, root_child1.LayoutX); + Assert.AreEqual(50f, root_child1.LayoutY); + Assert.AreEqual(100f, root_child1.LayoutWidth); + Assert.AreEqual(50f, root_child1.LayoutHeight); + + root.StyleDirection = YogaDirection.RTL; + root.CalculateLayout(); + + Assert.AreEqual(0f, root.LayoutX); + Assert.AreEqual(0f, root.LayoutY); + Assert.AreEqual(100f, root.LayoutWidth); + Assert.AreEqual(100f, root.LayoutHeight); + + Assert.AreEqual(0f, root_child0.LayoutX); + Assert.AreEqual(0f, root_child0.LayoutY); + Assert.AreEqual(100f, root_child0.LayoutWidth); + Assert.AreEqual(50f, root_child0.LayoutHeight); + + Assert.AreEqual(0f, root_child1.LayoutX); + Assert.AreEqual(50f, root_child1.LayoutY); + Assert.AreEqual(100f, root_child1.LayoutWidth); + Assert.AreEqual(50f, root_child1.LayoutHeight); + } + } } diff --git a/gentest/fixtures/YGMinMaxDimensionTest.html b/gentest/fixtures/YGMinMaxDimensionTest.html index 6e469707..6adf67e5 100644 --- a/gentest/fixtures/YGMinMaxDimensionTest.html +++ b/gentest/fixtures/YGMinMaxDimensionTest.html @@ -41,3 +41,25 @@
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
diff --git a/java/tests/com/facebook/yoga/YGMinMaxDimensionTest.java b/java/tests/com/facebook/yoga/YGMinMaxDimensionTest.java index f37a5c54..f5f96357 100644 --- a/java/tests/com/facebook/yoga/YGMinMaxDimensionTest.java +++ b/java/tests/com/facebook/yoga/YGMinMaxDimensionTest.java @@ -444,4 +444,221 @@ public class YGMinMaxDimensionTest { assertEquals(20f, root_child0_child0.getLayoutHeight(), 0.0f); } + @Test + public void test_flex_grow_within_constrained_min_row() { + final YogaNode root = new YogaNode(); + root.setFlexDirection(YogaFlexDirection.ROW); + root.setMinWidth(100f); + root.setHeight(100f); + + final YogaNode root_child0 = new YogaNode(); + root_child0.setFlexGrow(1f); + root.addChildAt(root_child0, 0); + + final YogaNode root_child1 = new YogaNode(); + root_child1.setWidth(50f); + root.addChildAt(root_child1, 1); + root.setDirection(YogaDirection.LTR); + root.calculateLayout(); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(100f, root.getLayoutWidth(), 0.0f); + assertEquals(100f, root.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(50f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(100f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(50f, root_child1.getLayoutX(), 0.0f); + assertEquals(0f, root_child1.getLayoutY(), 0.0f); + assertEquals(50f, root_child1.getLayoutWidth(), 0.0f); + assertEquals(100f, root_child1.getLayoutHeight(), 0.0f); + + root.setDirection(YogaDirection.RTL); + root.calculateLayout(); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(100f, root.getLayoutWidth(), 0.0f); + assertEquals(100f, root.getLayoutHeight(), 0.0f); + + assertEquals(50f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(50f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(100f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child1.getLayoutX(), 0.0f); + assertEquals(0f, root_child1.getLayoutY(), 0.0f); + assertEquals(50f, root_child1.getLayoutWidth(), 0.0f); + assertEquals(100f, root_child1.getLayoutHeight(), 0.0f); + } + + @Test + public void test_flex_grow_within_constrained_min_column() { + final YogaNode root = new YogaNode(); + root.setMinHeight(100f); + + final YogaNode root_child0 = new YogaNode(); + root_child0.setFlexGrow(1f); + root.addChildAt(root_child0, 0); + + final YogaNode root_child1 = new YogaNode(); + root_child1.setHeight(50f); + root.addChildAt(root_child1, 1); + root.setDirection(YogaDirection.LTR); + root.calculateLayout(); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(0f, root.getLayoutWidth(), 0.0f); + assertEquals(100f, root.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(0f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(50f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child1.getLayoutX(), 0.0f); + assertEquals(50f, root_child1.getLayoutY(), 0.0f); + assertEquals(0f, root_child1.getLayoutWidth(), 0.0f); + assertEquals(50f, root_child1.getLayoutHeight(), 0.0f); + + root.setDirection(YogaDirection.RTL); + root.calculateLayout(); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(0f, root.getLayoutWidth(), 0.0f); + assertEquals(100f, root.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(0f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(50f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child1.getLayoutX(), 0.0f); + assertEquals(50f, root_child1.getLayoutY(), 0.0f); + assertEquals(0f, root_child1.getLayoutWidth(), 0.0f); + assertEquals(50f, root_child1.getLayoutHeight(), 0.0f); + } + + @Test + public void test_flex_grow_within_constrained_max_row() { + final YogaNode root = new YogaNode(); + root.setWidth(200f); + + final YogaNode root_child0 = new YogaNode(); + root_child0.setFlexDirection(YogaFlexDirection.ROW); + root_child0.setMaxWidth(100f); + root_child0.setHeight(100f); + root.addChildAt(root_child0, 0); + + final YogaNode root_child0_child0 = new YogaNode(); + root_child0_child0.setFlexShrink(1f); + root_child0_child0.setFlexBasis(100f); + root_child0.addChildAt(root_child0_child0, 0); + + final YogaNode root_child0_child1 = new YogaNode(); + root_child0_child1.setWidth(50f); + root_child0.addChildAt(root_child0_child1, 1); + root.setDirection(YogaDirection.LTR); + root.calculateLayout(); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(200f, root.getLayoutWidth(), 0.0f); + assertEquals(100f, root.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(100f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(100f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0_child0.getLayoutY(), 0.0f); + assertEquals(50f, root_child0_child0.getLayoutWidth(), 0.0f); + assertEquals(100f, root_child0_child0.getLayoutHeight(), 0.0f); + + assertEquals(50f, root_child0_child1.getLayoutX(), 0.0f); + assertEquals(0f, root_child0_child1.getLayoutY(), 0.0f); + assertEquals(50f, root_child0_child1.getLayoutWidth(), 0.0f); + assertEquals(100f, root_child0_child1.getLayoutHeight(), 0.0f); + + root.setDirection(YogaDirection.RTL); + root.calculateLayout(); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(200f, root.getLayoutWidth(), 0.0f); + assertEquals(100f, root.getLayoutHeight(), 0.0f); + + assertEquals(100f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(100f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(100f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(50f, root_child0_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0_child0.getLayoutY(), 0.0f); + assertEquals(50f, root_child0_child0.getLayoutWidth(), 0.0f); + assertEquals(100f, root_child0_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0_child1.getLayoutX(), 0.0f); + assertEquals(0f, root_child0_child1.getLayoutY(), 0.0f); + assertEquals(50f, root_child0_child1.getLayoutWidth(), 0.0f); + assertEquals(100f, root_child0_child1.getLayoutHeight(), 0.0f); + } + + @Test + public void test_flex_grow_within_constrained_max_column() { + final YogaNode root = new YogaNode(); + root.setWidth(100f); + root.setMaxHeight(100f); + + final YogaNode root_child0 = new YogaNode(); + root_child0.setFlexShrink(1f); + root_child0.setFlexBasis(100f); + root.addChildAt(root_child0, 0); + + final YogaNode root_child1 = new YogaNode(); + root_child1.setHeight(50f); + root.addChildAt(root_child1, 1); + root.setDirection(YogaDirection.LTR); + root.calculateLayout(); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(100f, root.getLayoutWidth(), 0.0f); + assertEquals(100f, root.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(100f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(50f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child1.getLayoutX(), 0.0f); + assertEquals(50f, root_child1.getLayoutY(), 0.0f); + assertEquals(100f, root_child1.getLayoutWidth(), 0.0f); + assertEquals(50f, root_child1.getLayoutHeight(), 0.0f); + + root.setDirection(YogaDirection.RTL); + root.calculateLayout(); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(100f, root.getLayoutWidth(), 0.0f); + assertEquals(100f, root.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child0.getLayoutX(), 0.0f); + assertEquals(0f, root_child0.getLayoutY(), 0.0f); + assertEquals(100f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(50f, root_child0.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child1.getLayoutX(), 0.0f); + assertEquals(50f, root_child1.getLayoutY(), 0.0f); + assertEquals(100f, root_child1.getLayoutWidth(), 0.0f); + assertEquals(50f, root_child1.getLayoutHeight(), 0.0f); + } + } diff --git a/tests/YGMinMaxDimensionTest.cpp b/tests/YGMinMaxDimensionTest.cpp index 2c6d3120..b1463e52 100644 --- a/tests/YGMinMaxDimensionTest.cpp +++ b/tests/YGMinMaxDimensionTest.cpp @@ -430,3 +430,216 @@ TEST(YogaTest, flex_grow_within_constrained_max_width) { YGNodeFreeRecursive(root); } + +TEST(YogaTest, flex_grow_within_constrained_min_row) { + const YGNodeRef root = YGNodeNew(); + YGNodeStyleSetFlexDirection(root, YGFlexDirectionRow); + YGNodeStyleSetMinWidth(root, 100); + YGNodeStyleSetHeight(root, 100); + + const YGNodeRef root_child0 = YGNodeNew(); + YGNodeStyleSetFlexGrow(root_child0, 1); + YGNodeInsertChild(root, root_child0, 0); + + const YGNodeRef root_child1 = YGNodeNew(); + YGNodeStyleSetWidth(root_child1, 50); + YGNodeInsertChild(root, root_child1, 1); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetLeft(root_child1)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child1)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetWidth(root_child1)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root_child1)); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionRTL); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child1)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child1)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetWidth(root_child1)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root_child1)); + + YGNodeFreeRecursive(root); +} + +TEST(YogaTest, flex_grow_within_constrained_min_column) { + const YGNodeRef root = YGNodeNew(); + YGNodeStyleSetMinHeight(root, 100); + + const YGNodeRef root_child0 = YGNodeNew(); + YGNodeStyleSetFlexGrow(root_child0, 1); + YGNodeInsertChild(root, root_child0, 0); + + const YGNodeRef root_child1 = YGNodeNew(); + YGNodeStyleSetHeight(root_child1, 50); + YGNodeInsertChild(root, root_child1, 1); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child1)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetTop(root_child1)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetWidth(root_child1)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetHeight(root_child1)); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionRTL); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child1)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetTop(root_child1)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetWidth(root_child1)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetHeight(root_child1)); + + YGNodeFreeRecursive(root); +} + +TEST(YogaTest, flex_grow_within_constrained_max_row) { + const YGNodeRef root = YGNodeNew(); + YGNodeStyleSetWidth(root, 200); + + const YGNodeRef root_child0 = YGNodeNew(); + YGNodeStyleSetFlexDirection(root_child0, YGFlexDirectionRow); + YGNodeStyleSetMaxWidth(root_child0, 100); + YGNodeStyleSetHeight(root_child0, 100); + YGNodeInsertChild(root, root_child0, 0); + + const YGNodeRef root_child0_child0 = YGNodeNew(); + YGNodeStyleSetFlexShrink(root_child0_child0, 1); + YGNodeStyleSetFlexBasis(root_child0_child0, 100); + YGNodeInsertChild(root_child0, root_child0_child0, 0); + + const YGNodeRef root_child0_child1 = YGNodeNew(); + YGNodeStyleSetWidth(root_child0_child1, 50); + YGNodeInsertChild(root_child0, root_child0_child1, 1); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root_child0_child0)); + + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetLeft(root_child0_child1)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child1)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetWidth(root_child0_child1)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root_child0_child1)); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionRTL); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetLeft(root_child0_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child0)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetWidth(root_child0_child0)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root_child0_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0_child1)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0_child1)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetWidth(root_child0_child1)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root_child0_child1)); + + YGNodeFreeRecursive(root); +} + +TEST(YogaTest, flex_grow_within_constrained_max_column) { + const YGNodeRef root = YGNodeNew(); + YGNodeStyleSetWidth(root, 100); + YGNodeStyleSetMaxHeight(root, 100); + + const YGNodeRef root_child0 = YGNodeNew(); + YGNodeStyleSetFlexShrink(root_child0, 1); + YGNodeStyleSetFlexBasis(root_child0, 100); + YGNodeInsertChild(root, root_child0, 0); + + const YGNodeRef root_child1 = YGNodeNew(); + YGNodeStyleSetHeight(root_child1, 50); + YGNodeInsertChild(root, root_child1, 1); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child1)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetTop(root_child1)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetWidth(root_child1)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetHeight(root_child1)); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionRTL); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetWidth(root)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child1)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetTop(root_child1)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetWidth(root_child1)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetHeight(root_child1)); + + YGNodeFreeRecursive(root); +} diff --git a/yoga/Yoga.c b/yoga/Yoga.c index 2e34e4f7..e207a9b8 100644 --- a/yoga/Yoga.c +++ b/yoga/Yoga.c @@ -1507,10 +1507,25 @@ static void YGNodelayoutImpl(const YGNodeRef node, const float marginAxisColumn = YGNodeMarginForAxis(node, YGFlexDirectionColumn); // STEP 2: DETERMINE AVAILABLE SIZE IN MAIN AND CROSS DIRECTIONS - const float availableInnerWidth = availableWidth - marginAxisRow - paddingAndBorderAxisRow; - const float availableInnerHeight = + float availableInnerWidth = availableWidth - marginAxisRow - paddingAndBorderAxisRow; + const float minInnerWidth = node->style.minDimensions[YGDimensionWidth] - marginAxisRow - paddingAndBorderAxisRow; + const float maxInnerWidth = node->style.maxDimensions[YGDimensionWidth] - marginAxisRow - paddingAndBorderAxisRow; + float availableInnerHeight = availableHeight - marginAxisColumn - paddingAndBorderAxisColumn; - const float availableInnerMainDim = isMainAxisRow ? availableInnerWidth : availableInnerHeight; + const float minInnerHeight = node->style.minDimensions[YGDimensionHeight] - marginAxisColumn - paddingAndBorderAxisColumn; + const float maxInnerHeight = node->style.maxDimensions[YGDimensionHeight] - marginAxisColumn - paddingAndBorderAxisColumn; + const float minInnerMainDim = isMainAxisRow ? minInnerWidth : minInnerHeight; + const float maxInnerMainDim = isMainAxisRow ? maxInnerWidth : maxInnerHeight; + + // Max dimension overrides predefined dimension value; Min dimension in turn overrides both of the above + if (!YGValueIsUndefined(availableInnerWidth)) { + availableInnerWidth = fmaxf(fminf(availableInnerWidth, maxInnerWidth), minInnerWidth); + } + if (!YGValueIsUndefined(availableInnerHeight)) { + availableInnerHeight = fmaxf(fminf(availableInnerHeight, maxInnerHeight), minInnerHeight); + } + + float availableInnerMainDim = isMainAxisRow ? availableInnerWidth : availableInnerHeight; const float availableInnerCrossDim = isMainAxisRow ? availableInnerHeight : availableInnerWidth; // If there is only one child with flexGrow + flexShrink it means we can set the @@ -1664,6 +1679,18 @@ static void YGNodelayoutImpl(const YGNodeRef node, // Calculate the remaining available space that needs to be allocated. // If the main dimension size isn't known, it is computed based on // the line length, so there's no more space left to distribute. + + // We resolve main dimension to fit minimum and maximum values + if (YGValueIsUndefined(availableInnerMainDim)) { + if (!YGValueIsUndefined(minInnerMainDim) && + sizeConsumedOnCurrentLine < minInnerMainDim) { + availableInnerMainDim = minInnerMainDim; + } else if (!YGValueIsUndefined(maxInnerMainDim) && + sizeConsumedOnCurrentLine > maxInnerMainDim) { + availableInnerMainDim = maxInnerMainDim; + } + } + float remainingFreeSpace = 0; if (!YGValueIsUndefined(availableInnerMainDim)) { remainingFreeSpace = availableInnerMainDim - sizeConsumedOnCurrentLine; From 3d10ba5f72f11e421a50653218b663dd538d7123 Mon Sep 17 00:00:00 2001 From: Emil Sjolander Date: Thu, 22 Dec 2016 02:57:12 -0800 Subject: [PATCH 05/11] Correctly check if child is flex by also accounting for undefined Summary: We were incorrectly returning true for children which had no flex as the flex was nan and not 0. Differential Revision: D4346712 fbshipit-source-id: 69ef0bb3fe5b4fcd3b3e2fe5aa348529be40252a --- yoga/Yoga.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yoga/Yoga.c b/yoga/Yoga.c index e207a9b8..4762cee4 100644 --- a/yoga/Yoga.c +++ b/yoga/Yoga.c @@ -825,7 +825,7 @@ static YGFlexDirection YGFlexDirectionCross(const YGFlexDirection flexDirection, static inline bool YGNodeIsFlex(const YGNodeRef node) { return (node->style.positionType == YGPositionTypeRelative && - (node->style.flexGrow != 0 || node->style.flexShrink != 0 || node->style.flex != 0)); + (YGNodeStyleGetFlexGrow(node) != 0 || YGNodeStyleGetFlexShrink(node) != 0)); } static inline float YGNodeDimWithMargin(const YGNodeRef node, const YGFlexDirection axis) { From 071f576db99775e51d384e99373a86610f712a28 Mon Sep 17 00:00:00 2001 From: Emil Sjolander Date: Thu, 22 Dec 2016 02:57:15 -0800 Subject: [PATCH 06/11] BREAKING - Change aspect ratio to always be width/height Summary: @public Aspect ratio being defined as width/height or height/width depending on the situation it was used in turned out to be very confusing. This diff makes aspect ratio always be defined as width/height irregardless of the usage. Differential Revision: D4339132 fbshipit-source-id: e5da32750b55ddaf6acaf1cbd7662d86f2b480c3 --- tests/YGAspectRatioTest.cpp | 4 ++-- yoga/Yoga.c | 6 +++--- yoga/Yoga.h | 3 +++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/YGAspectRatioTest.cpp b/tests/YGAspectRatioTest.cpp index 22846c9b..8b28abdb 100644 --- a/tests/YGAspectRatioTest.cpp +++ b/tests/YGAspectRatioTest.cpp @@ -351,7 +351,7 @@ TEST(YogaTest, aspect_ratio_double_main) { const YGNodeRef root_child0 = YGNodeNew(); YGNodeStyleSetWidth(root_child0, 50); - YGNodeStyleSetAspectRatio(root_child0, 2); + YGNodeStyleSetAspectRatio(root_child0, 0.5); YGNodeInsertChild(root, root_child0, 0); YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); @@ -372,7 +372,7 @@ TEST(YogaTest, aspect_ratio_half_main) { const YGNodeRef root_child0 = YGNodeNew(); YGNodeStyleSetWidth(root_child0, 100); - YGNodeStyleSetAspectRatio(root_child0, 0.5); + YGNodeStyleSetAspectRatio(root_child0, 2); YGNodeInsertChild(root, root_child0, 0); YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); diff --git a/yoga/Yoga.c b/yoga/Yoga.c index 4762cee4..8b1c5fda 100644 --- a/yoga/Yoga.c +++ b/yoga/Yoga.c @@ -1062,7 +1062,7 @@ static void YGNodeComputeFlexBasisForChild(const YGNodeRef node, if (!YGValueIsUndefined(child->style.aspectRatio)) { if (!isMainAxisRow && childWidthMeasureMode == YGMeasureModeExactly) { child->layout.computedFlexBasis = - fmaxf(childWidth * child->style.aspectRatio, + fmaxf(childWidth / child->style.aspectRatio, YGNodePaddingAndBorderForAxis(child, YGFlexDirectionColumn)); return; } else if (isMainAxisRow && childHeightMeasureMode == YGMeasureModeExactly) { @@ -1157,7 +1157,7 @@ static void YGNodeAbsoluteLayoutChild(const YGNodeRef node, childWidth = fmaxf(childHeight * child->style.aspectRatio, YGNodePaddingAndBorderForAxis(child, YGFlexDirectionColumn)); } else if (YGValueIsUndefined(childHeight)) { - childHeight = fmaxf(childWidth * child->style.aspectRatio, + childHeight = fmaxf(childWidth / child->style.aspectRatio, YGNodePaddingAndBorderForAxis(child, YGFlexDirectionRow)); } } @@ -1880,7 +1880,7 @@ static void YGNodelayoutImpl(const YGNodeRef node, if (!YGValueIsUndefined(currentRelativeChild->style.aspectRatio)) { if (isMainAxisRow && childHeightMeasureMode != YGMeasureModeExactly) { childHeight = - fmaxf(childWidth * currentRelativeChild->style.aspectRatio, + fmaxf(childWidth / currentRelativeChild->style.aspectRatio, YGNodePaddingAndBorderForAxis(currentRelativeChild, YGFlexDirectionColumn)); childHeightMeasureMode = YGMeasureModeExactly; } else if (!isMainAxisRow && childWidthMeasureMode != YGMeasureModeExactly) { diff --git a/yoga/Yoga.h b/yoga/Yoga.h index 3bb96727..5e10b503 100644 --- a/yoga/Yoga.h +++ b/yoga/Yoga.h @@ -151,6 +151,9 @@ YG_NODE_STYLE_PROPERTY(float, MaxHeight, maxHeight); // Yoga specific properties, not compatible with flexbox specification // Aspect ratio control the size of the undefined dimension of a node. +// Aspect ratio is encoded as a floating point value width/height. e.g. A value of 2 leads to a node +// with a width twice the size of its height while a value of 0.5 gives the opposite effect. +// // - On a node with a set width/height aspect ratio control the size of the unset dimension // - On a node with a set flex basis aspect ratio controls the size of the node in the cross axis if // unset From 1b5eb7da5e451b549bba75238a132e05c726facd Mon Sep 17 00:00:00 2001 From: Emil Sjolander Date: Thu, 22 Dec 2016 02:57:16 -0800 Subject: [PATCH 07/11] BREAKING - Increase priority of AspectRatio to override flex, align stretch, and fixed sizes if specified. Summary: @public AspectRatio is a new addition and soon after introduction we noticed use cases which is did not support. Specifically we wanted to support a node being as large as possible within a container while maintaining an arbitrary aspect ratio. This was not possible due to the low priority of AspectRatio, by increasing the priority of AspectRatio this is now possible as FlexGrow will grow an item to fit its parent unless the AspectRatio makes it too big in the cross axis, the AspectRatio will now override the FlexGrow in the main axis in that case. Differential Revision: D4346720 fbshipit-source-id: 1f15613604190e3ad5ff4a467ba57db4bcfd2741 --- tests/YGAspectRatioTest.cpp | 273 +++++++++++++++++++++++++++++++++++- yoga/Yoga.c | 24 +++- 2 files changed, 292 insertions(+), 5 deletions(-) diff --git a/tests/YGAspectRatioTest.cpp b/tests/YGAspectRatioTest.cpp index 8b28abdb..b6e32de2 100644 --- a/tests/YGAspectRatioTest.cpp +++ b/tests/YGAspectRatioTest.cpp @@ -63,12 +63,15 @@ TEST(YogaTest, aspect_ratio_main_defined) { YGNodeFreeRecursive(root); } -TEST(YogaTest, aspect_ratio_both_dimensions_defined) { +TEST(YogaTest, aspect_ratio_both_dimensions_defined_row) { const YGNodeRef root = YGNodeNew(); + YGNodeStyleSetFlexDirection(root, YGFlexDirectionRow); + YGNodeStyleSetAlignItems(root, YGAlignFlexStart); YGNodeStyleSetWidth(root, 100); YGNodeStyleSetHeight(root, 100); const YGNodeRef root_child0 = YGNodeNew(); + YGNodeStyleSetWidth(root_child0, 100); YGNodeStyleSetHeight(root_child0, 50); YGNodeStyleSetAspectRatio(root_child0, 1); YGNodeInsertChild(root, root_child0, 0); @@ -78,6 +81,28 @@ TEST(YogaTest, aspect_ratio_both_dimensions_defined) { ASSERT_EQ(0, YGNodeLayoutGetLeft(root_child0)); ASSERT_EQ(0, YGNodeLayoutGetTop(root_child0)); ASSERT_EQ(100, YGNodeLayoutGetWidth(root_child0)); + ASSERT_EQ(100, YGNodeLayoutGetHeight(root_child0)); + + YGNodeFreeRecursive(root); +} + +TEST(YogaTest, aspect_ratio_both_dimensions_defined_column) { + const YGNodeRef root = YGNodeNew(); + YGNodeStyleSetAlignItems(root, YGAlignFlexStart); + YGNodeStyleSetWidth(root, 100); + YGNodeStyleSetHeight(root, 100); + + const YGNodeRef root_child0 = YGNodeNew(); + YGNodeStyleSetWidth(root_child0, 100); + YGNodeStyleSetHeight(root_child0, 50); + YGNodeStyleSetAspectRatio(root_child0, 1); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_EQ(50, YGNodeLayoutGetWidth(root_child0)); ASSERT_EQ(50, YGNodeLayoutGetHeight(root_child0)); YGNodeFreeRecursive(root); @@ -405,3 +430,249 @@ TEST(YogaTest, aspect_ratio_with_measure_func) { YGNodeFreeRecursive(root); } + +TEST(YogaTest, aspect_ratio_width_height_flex_grow_row) { + const YGNodeRef root = YGNodeNew(); + YGNodeStyleSetFlexDirection(root, YGFlexDirectionRow); + YGNodeStyleSetAlignItems(root, YGAlignFlexStart); + YGNodeStyleSetWidth(root, 100); + YGNodeStyleSetHeight(root, 200); + + const YGNodeRef root_child0 = YGNodeNew(); + YGNodeStyleSetWidth(root_child0, 50); + YGNodeStyleSetHeight(root_child0, 50); + YGNodeStyleSetFlexGrow(root_child0, 1); + YGNodeStyleSetAspectRatio(root_child0, 1); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_EQ(100, YGNodeLayoutGetWidth(root_child0)); + ASSERT_EQ(100, YGNodeLayoutGetHeight(root_child0)); + + YGNodeFreeRecursive(root); +} + +TEST(YogaTest, aspect_ratio_width_height_flex_grow_column) { + const YGNodeRef root = YGNodeNew(); + YGNodeStyleSetAlignItems(root, YGAlignFlexStart); + YGNodeStyleSetWidth(root, 200); + YGNodeStyleSetHeight(root, 100); + + const YGNodeRef root_child0 = YGNodeNew(); + YGNodeStyleSetWidth(root_child0, 50); + YGNodeStyleSetHeight(root_child0, 50); + YGNodeStyleSetFlexGrow(root_child0, 1); + YGNodeStyleSetAspectRatio(root_child0, 1); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_EQ(100, YGNodeLayoutGetWidth(root_child0)); + ASSERT_EQ(100, YGNodeLayoutGetHeight(root_child0)); + + YGNodeFreeRecursive(root); +} + +TEST(YogaTest, aspect_ratio_height_as_flex_basis) { + const YGNodeRef root = YGNodeNew(); + YGNodeStyleSetAlignItems(root, YGAlignFlexStart); + YGNodeStyleSetFlexDirection(root, YGFlexDirectionRow); + YGNodeStyleSetWidth(root, 200); + YGNodeStyleSetHeight(root, 200); + + const YGNodeRef root_child0 = YGNodeNew(); + YGNodeStyleSetHeight(root_child0, 50); + YGNodeStyleSetFlexGrow(root_child0, 1); + YGNodeStyleSetAspectRatio(root_child0, 1); + YGNodeInsertChild(root, root_child0, 0); + + const YGNodeRef root_child1 = YGNodeNew(); + YGNodeStyleSetHeight(root_child1, 100); + YGNodeStyleSetFlexGrow(root_child1, 1); + YGNodeStyleSetAspectRatio(root_child1, 1); + YGNodeInsertChild(root, root_child1, 1); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_EQ(75, YGNodeLayoutGetWidth(root_child0)); + ASSERT_EQ(75, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_EQ(75, YGNodeLayoutGetLeft(root_child1)); + ASSERT_EQ(0, YGNodeLayoutGetTop(root_child1)); + ASSERT_EQ(125, YGNodeLayoutGetWidth(root_child1)); + ASSERT_EQ(125, YGNodeLayoutGetHeight(root_child1)); + + YGNodeFreeRecursive(root); +} + +TEST(YogaTest, aspect_ratio_width_as_flex_basis) { + const YGNodeRef root = YGNodeNew(); + YGNodeStyleSetAlignItems(root, YGAlignFlexStart); + YGNodeStyleSetWidth(root, 200); + YGNodeStyleSetHeight(root, 200); + + const YGNodeRef root_child0 = YGNodeNew(); + YGNodeStyleSetWidth(root_child0, 50); + YGNodeStyleSetFlexGrow(root_child0, 1); + YGNodeStyleSetAspectRatio(root_child0, 1); + YGNodeInsertChild(root, root_child0, 0); + + const YGNodeRef root_child1 = YGNodeNew(); + YGNodeStyleSetWidth(root_child1, 100); + YGNodeStyleSetFlexGrow(root_child1, 1); + YGNodeStyleSetAspectRatio(root_child1, 1); + YGNodeInsertChild(root, root_child1, 1); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_EQ(75, YGNodeLayoutGetWidth(root_child0)); + ASSERT_EQ(75, YGNodeLayoutGetHeight(root_child0)); + + ASSERT_EQ(0, YGNodeLayoutGetLeft(root_child1)); + ASSERT_EQ(75, YGNodeLayoutGetTop(root_child1)); + ASSERT_EQ(125, YGNodeLayoutGetWidth(root_child1)); + ASSERT_EQ(125, YGNodeLayoutGetHeight(root_child1)); + + YGNodeFreeRecursive(root); +} + +TEST(YogaTest, aspect_ratio_overrides_flex_grow_row) { + const YGNodeRef root = YGNodeNew(); + YGNodeStyleSetAlignItems(root, YGAlignFlexStart); + YGNodeStyleSetFlexDirection(root, YGFlexDirectionRow); + YGNodeStyleSetWidth(root, 100); + YGNodeStyleSetHeight(root, 100); + + const YGNodeRef root_child0 = YGNodeNew(); + YGNodeStyleSetWidth(root_child0, 50); + YGNodeStyleSetFlexGrow(root_child0, 1); + YGNodeStyleSetAspectRatio(root_child0, 0.5); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_EQ(50, YGNodeLayoutGetWidth(root_child0)); + ASSERT_EQ(100, YGNodeLayoutGetHeight(root_child0)); + + YGNodeFreeRecursive(root); +} + +TEST(YogaTest, aspect_ratio_overrides_flex_grow_column) { + const YGNodeRef root = YGNodeNew(); + YGNodeStyleSetAlignItems(root, YGAlignFlexStart); + YGNodeStyleSetWidth(root, 100); + YGNodeStyleSetHeight(root, 100); + + const YGNodeRef root_child0 = YGNodeNew(); + YGNodeStyleSetHeight(root_child0, 50); + YGNodeStyleSetFlexGrow(root_child0, 1); + YGNodeStyleSetAspectRatio(root_child0, 2); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_EQ(100, YGNodeLayoutGetWidth(root_child0)); + ASSERT_EQ(50, YGNodeLayoutGetHeight(root_child0)); + + YGNodeFreeRecursive(root); +} + +TEST(YogaTest, aspect_ratio_left_right_absolute) { + const YGNodeRef root = YGNodeNew(); + YGNodeStyleSetWidth(root, 100); + YGNodeStyleSetHeight(root, 100); + + const YGNodeRef root_child0 = YGNodeNew(); + YGNodeStyleSetPositionType(root_child0, YGPositionTypeAbsolute); + YGNodeStyleSetPosition(root_child0, YGEdgeLeft, 10); + YGNodeStyleSetPosition(root_child0, YGEdgeTop, 10); + YGNodeStyleSetPosition(root_child0, YGEdgeRight, 10); + YGNodeStyleSetAspectRatio(root_child0, 1); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_EQ(10, YGNodeLayoutGetLeft(root_child0)); + ASSERT_EQ(10, YGNodeLayoutGetTop(root_child0)); + ASSERT_EQ(80, YGNodeLayoutGetWidth(root_child0)); + ASSERT_EQ(80, YGNodeLayoutGetHeight(root_child0)); + + YGNodeFreeRecursive(root); +} + +TEST(YogaTest, aspect_ratio_top_bottom_absolute) { + const YGNodeRef root = YGNodeNew(); + YGNodeStyleSetWidth(root, 100); + YGNodeStyleSetHeight(root, 100); + + const YGNodeRef root_child0 = YGNodeNew(); + YGNodeStyleSetPositionType(root_child0, YGPositionTypeAbsolute); + YGNodeStyleSetPosition(root_child0, YGEdgeLeft, 10); + YGNodeStyleSetPosition(root_child0, YGEdgeTop, 10); + YGNodeStyleSetPosition(root_child0, YGEdgeBottom, 10); + YGNodeStyleSetAspectRatio(root_child0, 1); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_EQ(10, YGNodeLayoutGetLeft(root_child0)); + ASSERT_EQ(10, YGNodeLayoutGetTop(root_child0)); + ASSERT_EQ(80, YGNodeLayoutGetWidth(root_child0)); + ASSERT_EQ(80, YGNodeLayoutGetHeight(root_child0)); + + YGNodeFreeRecursive(root); +} + +TEST(YogaTest, aspect_ratio_width_overrides_align_stretch_row) { + const YGNodeRef root = YGNodeNew(); + YGNodeStyleSetFlexDirection(root, YGFlexDirectionRow); + YGNodeStyleSetWidth(root, 100); + YGNodeStyleSetHeight(root, 100); + + const YGNodeRef root_child0 = YGNodeNew(); + YGNodeStyleSetWidth(root_child0, 50); + YGNodeStyleSetAspectRatio(root_child0, 1); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_EQ(50, YGNodeLayoutGetWidth(root_child0)); + ASSERT_EQ(50, YGNodeLayoutGetHeight(root_child0)); + + YGNodeFreeRecursive(root); +} + +TEST(YogaTest, aspect_ratio_height_overrides_align_stretch_column) { + const YGNodeRef root = YGNodeNew(); + YGNodeStyleSetWidth(root, 100); + YGNodeStyleSetHeight(root, 100); + + const YGNodeRef root_child0 = YGNodeNew(); + YGNodeStyleSetHeight(root_child0, 50); + YGNodeStyleSetAspectRatio(root_child0, 1); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_EQ(0, YGNodeLayoutGetLeft(root_child0)); + ASSERT_EQ(0, YGNodeLayoutGetTop(root_child0)); + ASSERT_EQ(50, YGNodeLayoutGetWidth(root_child0)); + ASSERT_EQ(50, YGNodeLayoutGetHeight(root_child0)); + + YGNodeFreeRecursive(root); +} diff --git a/yoga/Yoga.c b/yoga/Yoga.c index 8b1c5fda..21f7290e 100644 --- a/yoga/Yoga.c +++ b/yoga/Yoga.c @@ -1878,16 +1878,22 @@ static void YGNodelayoutImpl(const YGNodeRef node, } if (!YGValueIsUndefined(currentRelativeChild->style.aspectRatio)) { - if (isMainAxisRow && childHeightMeasureMode != YGMeasureModeExactly) { + if (isMainAxisRow) { childHeight = fmaxf(childWidth / currentRelativeChild->style.aspectRatio, YGNodePaddingAndBorderForAxis(currentRelativeChild, YGFlexDirectionColumn)); childHeightMeasureMode = YGMeasureModeExactly; - } else if (!isMainAxisRow && childWidthMeasureMode != YGMeasureModeExactly) { + + childHeight = fminf(childHeight, availableInnerHeight); + childWidth = childHeight * currentRelativeChild->style.aspectRatio; + } else { childWidth = fmaxf(childHeight * currentRelativeChild->style.aspectRatio, YGNodePaddingAndBorderForAxis(currentRelativeChild, YGFlexDirectionRow)); childWidthMeasureMode = YGMeasureModeExactly; + + childWidth = fminf(childWidth, availableInnerWidth); + childHeight = childWidth / currentRelativeChild->style.aspectRatio; } } @@ -2080,13 +2086,23 @@ static void YGNodelayoutImpl(const YGNodeRef node, YGMeasureMode childHeightMeasureMode = YGMeasureModeExactly; if (isMainAxisRow) { - childHeight = crossDim; childWidth = child->layout.measuredDimensions[YGDimensionWidth] + YGNodeMarginForAxis(child, YGFlexDirectionRow); + + if (!YGValueIsUndefined(child->style.aspectRatio)) { + childHeight = childWidth / child->style.aspectRatio; + } else { + childHeight = crossDim; + } } else { - childWidth = crossDim; childHeight = child->layout.measuredDimensions[YGDimensionHeight] + YGNodeMarginForAxis(child, YGFlexDirectionColumn); + + if (!YGValueIsUndefined(child->style.aspectRatio)) { + childWidth = childHeight * child->style.aspectRatio; + } else { + childWidth = crossDim; + } } YGConstrainMaxSizeForMode(child->style.maxDimensions[YGDimensionWidth], From ed765fe5085cf6e5c9bfb279f0accf1075f65f4d Mon Sep 17 00:00:00 2001 From: Emil Sjolander Date: Thu, 22 Dec 2016 02:57:18 -0800 Subject: [PATCH 08/11] Convert max cache count into a define Summary: This should not be an enum. A define makes a whole lot more sense Differential Revision: D4346529 fbshipit-source-id: 8641c4c5017d915d64e5884cae09ac8f01861337 --- yoga/Yoga.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yoga/Yoga.c b/yoga/Yoga.c index 21f7290e..5dd21af1 100644 --- a/yoga/Yoga.c +++ b/yoga/Yoga.c @@ -42,7 +42,7 @@ typedef struct YGCachedMeasurement { // This value was chosen based on empiracle data. Even the most complicated // layouts should not require more than 16 entries to fit within the cache. -enum { YG_MAX_CACHED_RESULT_COUNT = 16 }; +#define YG_MAX_CACHED_RESULT_COUNT 16 typedef struct YGLayout { float position[4]; From ff1a0e1eb8db37a707d2319ea79394bad133e55f Mon Sep 17 00:00:00 2001 From: David Hart Date: Thu, 22 Dec 2016 02:57:19 -0800 Subject: [PATCH 09/11] Transform the Count enum values into private constants Summary: Hides implementation details for the C, Objective-C and Swift APIs. Closes https://github.com/facebook/yoga/pull/292 Differential Revision: D4351523 Pulled By: emilsjolander fbshipit-source-id: 18a1149d169f0d52bd078714295000b5d07434dd --- enums.py | 6 +++--- yoga/YGEnums.h | 26 +++++++++++++------------- yoga/Yoga.c | 7 ------- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/enums.py b/enums.py index d2fc6dc0..756c7c43 100644 --- a/enums.py +++ b/enums.py @@ -111,22 +111,22 @@ def to_java_upper(symbol): return out -root = os.path.dirname(__file__) +root = os.path.dirname(os.path.abspath(__file__)) -# write out C header +# write out 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) 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%sCount,\n' % name) f.write('} 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 85d9d2f9..a64c1673 100644 --- a/yoga/YGEnums.h +++ b/yoga/YGEnums.h @@ -13,28 +13,29 @@ YG_EXTERN_C_BEGIN +#define YGFlexDirectionCount 4 typedef enum YGFlexDirection { YGFlexDirectionColumn, YGFlexDirectionColumnReverse, YGFlexDirectionRow, YGFlexDirectionRowReverse, - YGFlexDirectionCount, } YGFlexDirection; +#define YGMeasureModeCount 3 typedef enum YGMeasureMode { YGMeasureModeUndefined, YGMeasureModeExactly, YGMeasureModeAtMost, - YGMeasureModeCount, } YGMeasureMode; +#define YGPrintOptionsCount 3 typedef enum YGPrintOptions { YGPrintOptionsLayout = 1, YGPrintOptionsStyle = 2, YGPrintOptionsChildren = 4, - YGPrintOptionsCount, } YGPrintOptions; +#define YGEdgeCount 9 typedef enum YGEdge { YGEdgeLeft, YGEdgeTop, @@ -45,72 +46,71 @@ typedef enum YGEdge { YGEdgeHorizontal, YGEdgeVertical, YGEdgeAll, - YGEdgeCount, } YGEdge; +#define YGPositionTypeCount 2 typedef enum YGPositionType { YGPositionTypeRelative, YGPositionTypeAbsolute, - YGPositionTypeCount, } YGPositionType; +#define YGDimensionCount 2 typedef enum YGDimension { YGDimensionWidth, YGDimensionHeight, - YGDimensionCount, } YGDimension; +#define YGJustifyCount 5 typedef enum YGJustify { YGJustifyFlexStart, YGJustifyCenter, YGJustifyFlexEnd, YGJustifySpaceBetween, YGJustifySpaceAround, - YGJustifyCount, } YGJustify; +#define YGDirectionCount 3 typedef enum YGDirection { YGDirectionInherit, YGDirectionLTR, YGDirectionRTL, - YGDirectionCount, } YGDirection; +#define YGLogLevelCount 5 typedef enum YGLogLevel { YGLogLevelError, YGLogLevelWarn, YGLogLevelInfo, YGLogLevelDebug, YGLogLevelVerbose, - YGLogLevelCount, } YGLogLevel; +#define YGWrapCount 2 typedef enum YGWrap { YGWrapNoWrap, YGWrapWrap, - YGWrapCount, } YGWrap; +#define YGOverflowCount 3 typedef enum YGOverflow { YGOverflowVisible, YGOverflowHidden, YGOverflowScroll, - YGOverflowCount, } YGOverflow; +#define YGExperimentalFeatureCount 2 typedef enum YGExperimentalFeature { YGExperimentalFeatureRounding, YGExperimentalFeatureWebFlexBasis, - YGExperimentalFeatureCount, } YGExperimentalFeature; +#define YGAlignCount 5 typedef enum YGAlign { YGAlignAuto, YGAlignFlexStart, YGAlignCenter, YGAlignFlexEnd, YGAlignStretch, - YGAlignCount, } YGAlign; YG_EXTERN_C_END diff --git a/yoga/Yoga.c b/yoga/Yoga.c index 5dd21af1..a73f8ad5 100644 --- a/yoga/Yoga.c +++ b/yoga/Yoga.c @@ -133,8 +133,6 @@ static int YGAndroidLog(YGLogLevel level, const char *format, va_list args) { case YGLogLevelVerbose: androidLevel = ANDROID_LOG_VERBOSE; break; - case YGLogLevelCount: - break; } const int result = __android_log_vprint(androidLevel, "YG-layout", format, args); return result; @@ -951,8 +949,6 @@ static void YGConstrainMaxSizeForMode(const float maxSize, YGMeasureMode *mode, *size = maxSize; } break; - case YGMeasureModeCount: - break; } } @@ -1968,7 +1964,6 @@ static void YGNodelayoutImpl(const YGNodeRef node, leadingMainDim = betweenMainDim / 2; break; case YGJustifyFlexStart: - case YGJustifyCount: break; } @@ -2171,7 +2166,6 @@ static void YGNodelayoutImpl(const YGNodeRef node, break; case YGAlignAuto: case YGAlignFlexStart: - case YGAlignCount: break; } @@ -2231,7 +2225,6 @@ static void YGNodelayoutImpl(const YGNodeRef node, break; } case YGAlignAuto: - case YGAlignCount: break; } } From 0df58d8aa2ccd458aef52b50b7453c14e486a73e Mon Sep 17 00:00:00 2001 From: Emil Sjolander Date: Thu, 22 Dec 2016 02:57:21 -0800 Subject: [PATCH 10/11] Declaratively initialize default values of nodes Summary: Improve performance of allocations and reseting by writing fewer bits. Also just looks nicer imho. Reviewed By: passy Differential Revision: D4356994 fbshipit-source-id: ebbe52163e0c86230bfa4131b657941afe16fbf1 --- tests/YGMemoryFuncTest.cpp | 2 +- yoga/Yoga.c | 122 +++++++++++++++++++------------------ 2 files changed, 64 insertions(+), 60 deletions(-) diff --git a/tests/YGMemoryFuncTest.cpp b/tests/YGMemoryFuncTest.cpp index bef2985c..b6481373 100644 --- a/tests/YGMemoryFuncTest.cpp +++ b/tests/YGMemoryFuncTest.cpp @@ -56,7 +56,7 @@ TEST(YogaTest, memory_func_test_funcs) { } YGNodeFreeRecursive(root); ASSERT_NE(testMallocCount, 0); - ASSERT_NE(testCallocCount, 0); + ASSERT_EQ(testCallocCount, 0); ASSERT_NE(testReallocCount, 0); ASSERT_NE(testFreeCount, 0); YGSetMemoryFuncs(NULL, NULL, NULL, NULL); diff --git a/yoga/Yoga.c b/yoga/Yoga.c index a73f8ad5..9723d894 100644 --- a/yoga/Yoga.c +++ b/yoga/Yoga.c @@ -106,6 +106,66 @@ typedef struct YGNode { void *context; } YGNode; +#define YG_DEFAULT_EDGE_VALUES { \ + [YGEdgeLeft] = YGUndefined, \ + [YGEdgeTop] = YGUndefined, \ + [YGEdgeRight] = YGUndefined, \ + [YGEdgeBottom] = YGUndefined, \ + [YGEdgeStart] = YGUndefined, \ + [YGEdgeEnd] = YGUndefined, \ + [YGEdgeHorizontal] = YGUndefined, \ + [YGEdgeVertical] = YGUndefined, \ + [YGEdgeAll] = YGUndefined, \ +} + +#define YG_DEFAULT_DIMENSION_VALUES { \ + [YGDimensionWidth] = YGUndefined, \ + [YGDimensionHeight] = YGUndefined, \ +} + +YGNode gYGNodeDefaults = { + .parent = NULL, + .children = NULL, + .hasNewLayout = true, + .isDirty = false, + + .style = { + .flex = YGUndefined, + .flexGrow = YGUndefined, + .flexShrink = YGUndefined, + .flexBasis = YGUndefined, + .justifyContent = YGJustifyFlexStart, + .alignItems = YGAlignStretch, + .alignContent = YGAlignFlexStart, + .direction = YGDirectionInherit, + .flexDirection = YGFlexDirectionColumn, + .overflow = YGOverflowVisible, + .dimensions = YG_DEFAULT_DIMENSION_VALUES, + .minDimensions = YG_DEFAULT_DIMENSION_VALUES, + .maxDimensions = YG_DEFAULT_DIMENSION_VALUES, + .position = YG_DEFAULT_EDGE_VALUES, + .margin = YG_DEFAULT_EDGE_VALUES, + .padding = YG_DEFAULT_EDGE_VALUES, + .border = YG_DEFAULT_EDGE_VALUES, + .aspectRatio = YGUndefined, + }, + + .layout = { + .dimensions = YG_DEFAULT_DIMENSION_VALUES, + .lastParentDirection = (YGDirection) -1, + .nextCachedMeasurementsIndex = 0, + .computedFlexBasis = YGUndefined, + .measuredDimensions = YG_DEFAULT_DIMENSION_VALUES, + + .cachedLayout = { + .widthMeasureMode = (YGMeasureMode) -1, + .heightMeasureMode = (YGMeasureMode) -1, + .computedWidth = -1, + .computedHeight = -1, + }, + }, +}; + static void YGNodeMarkDirtyInternal(const YGNodeRef node); YGMalloc gYGMalloc = &malloc; @@ -183,69 +243,14 @@ static inline float YGComputedEdgeValue(const float edges[YGEdgeCount], return defaultValue; } -static void YGNodeInit(const YGNodeRef node) { - node->parent = NULL; - node->children = NULL; - node->hasNewLayout = true; - node->isDirty = false; - - node->style.flex = YGUndefined; - node->style.flexGrow = YGUndefined; - node->style.flexShrink = YGUndefined; - node->style.flexBasis = YGUndefined; - - node->style.alignItems = YGAlignStretch; - node->style.justifyContent = YGJustifyFlexStart; - node->style.alignContent = YGAlignFlexStart; - - node->style.direction = YGDirectionInherit; - node->style.flexDirection = YGFlexDirectionColumn; - - node->style.overflow = YGOverflowVisible; - - // Some of the fields default to undefined and not 0 - node->style.dimensions[YGDimensionWidth] = YGUndefined; - node->style.dimensions[YGDimensionHeight] = YGUndefined; - - node->style.minDimensions[YGDimensionWidth] = YGUndefined; - node->style.minDimensions[YGDimensionHeight] = YGUndefined; - - node->style.maxDimensions[YGDimensionWidth] = YGUndefined; - node->style.maxDimensions[YGDimensionHeight] = YGUndefined; - - for (YGEdge edge = YGEdgeLeft; edge < YGEdgeCount; edge++) { - node->style.position[edge] = YGUndefined; - node->style.margin[edge] = YGUndefined; - node->style.padding[edge] = YGUndefined; - node->style.border[edge] = YGUndefined; - } - - node->style.aspectRatio = YGUndefined; - - node->layout.dimensions[YGDimensionWidth] = YGUndefined; - node->layout.dimensions[YGDimensionHeight] = YGUndefined; - - // Such that the comparison is always going to be false - node->layout.lastParentDirection = (YGDirection) -1; - node->layout.nextCachedMeasurementsIndex = 0; - node->layout.computedFlexBasis = YGUndefined; - - node->layout.measuredDimensions[YGDimensionWidth] = YGUndefined; - node->layout.measuredDimensions[YGDimensionHeight] = YGUndefined; - node->layout.cachedLayout.widthMeasureMode = (YGMeasureMode) -1; - node->layout.cachedLayout.heightMeasureMode = (YGMeasureMode) -1; - node->layout.cachedLayout.computedWidth = -1; - node->layout.cachedLayout.computedHeight = -1; -} - int32_t gNodeInstanceCount = 0; YGNodeRef YGNodeNew(void) { - const YGNodeRef node = gYGCalloc(1, sizeof(YGNode)); + const YGNodeRef node = gYGMalloc(sizeof(YGNode)); YG_ASSERT(node, "Could not allocate memory for node"); gNodeInstanceCount++; - YGNodeInit(node); + memcpy(node, &gYGNodeDefaults, sizeof(YGNode)); return node; } @@ -280,8 +285,7 @@ void YGNodeReset(const YGNodeRef node) { YG_ASSERT(node->parent == NULL, "Cannot reset a node still attached to a parent"); YGNodeListFree(node->children); - memset(node, 0, sizeof(YGNode)); - YGNodeInit(node); + memcpy(node, &gYGNodeDefaults, sizeof(YGNode)); } int32_t YGNodeGetInstanceCount(void) { From e43c9f66ffda81dcab82f87b6f92bf0f8ea84d8e Mon Sep 17 00:00:00 2001 From: Kazuki Sakamoto Date: Thu, 22 Dec 2016 08:36:31 -0800 Subject: [PATCH 11/11] Add script to build Unity package Summary: Added Bash script and bat file to build a package of Yoga in order to import Yoga for Unity easily. Closes https://github.com/facebook/yoga/pull/288 Reviewed By: emilsjolander Differential Revision: D4344198 Pulled By: splhack fbshipit-source-id: 49ae27dbc513b4a2d161840ca5c5f4c9df2ab227 --- csharp/Unity/.gitignore | 3 +++ csharp/Unity/pack.sh | 53 +++++++++++++++++++++++++++++++++++++++++ csharp/Unity/win.bat | 2 ++ 3 files changed, 58 insertions(+) create mode 100644 csharp/Unity/.gitignore create mode 100755 csharp/Unity/pack.sh create mode 100755 csharp/Unity/win.bat diff --git a/csharp/Unity/.gitignore b/csharp/Unity/.gitignore new file mode 100644 index 00000000..a26b100a --- /dev/null +++ b/csharp/Unity/.gitignore @@ -0,0 +1,3 @@ +Yoga +yoga.dll +yoga.unitypackage diff --git a/csharp/Unity/pack.sh b/csharp/Unity/pack.sh new file mode 100755 index 00000000..402a9632 --- /dev/null +++ b/csharp/Unity/pack.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# 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. + +cd "$( dirname "$0" )" + +if [ \! -f yoga.dll ]; then + echo "Launch win.bat on Windows and copy yoga.dll to here" + exit 1 +fi + +function build { + buck build $1 + echo "$root/`buck targets --show-output $1|tail -1|awk '{print $2}'`" +} + +function copy { + mkdir -p $3 + cp $1 $3/$2 +} + +rm -rf Yoga yoga.unitypackage + +root=`buck root|tail -f` +mac=$(build '//csharp:yoganet#default,shared') +armv7=$(build '//csharp:yoganet#android-armv7,shared') +ios=$(build '//csharp:yoganet-ios') +win=yoga.dll + +Unity -quit -batchMode -createProject Yoga + +copy $win ${win##*/} Yoga/Assets/Facebook.Yoga/Plugins/x86_64 +copy $mac yoga Yoga/Assets/Facebook.Yoga/Plugins/x86_64/yoga.bundle/Contents/MacOS +armv7path=Assets/Plugins/Android/libs/armeabi-v7a +copy $armv7 ${armv7##*/} Yoga/$armv7path +iospath=Assets/Plugins/iOS +copy $ios ${ios##*/} Yoga/$iospath +libs="$armv7path/${armv7##*/} $iospath/${ios##*/}" + +scripts=Yoga/Assets/Facebook.Yoga/Scripts/Facebook.Yoga +mkdir -p $scripts +(cd ../Facebook.Yoga; tar cf - *.cs)|tar -C $scripts -xf - + +tests=Yoga/Assets/Facebook.Yoga/Editor/Facebook.Yoga.Tests +mkdir -p $tests +(cd ../tests/Facebook.Yoga; tar cf - *.cs)|tar -C $tests -xf - + +Unity -quit -batchMode -projectPath `pwd`/Yoga -exportPackage Assets/Facebook.Yoga $libs `pwd`/yoga.unitypackage + diff --git a/csharp/Unity/win.bat b/csharp/Unity/win.bat new file mode 100755 index 00000000..3f838cd4 --- /dev/null +++ b/csharp/Unity/win.bat @@ -0,0 +1,2 @@ +"C:\Program Files (x86)\MSBuild\14.0\Bin\MSBuild.exe" ..\Yoga\Yoga.vcxproj /p:configuration=Release /property:Platform=x64 +xcopy "..\Yoga\x64\Release\yoga.dll" %~dp0 /s /d /y