From 5708d4f392e832e5e971562b96269374174165b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Arve=20S=C3=A6ther?= Date: Thu, 9 Jul 2026 18:05:20 +0200 Subject: [PATCH] Fix bug with cross-axis alignment when there is space between lines The code adds the space between the lines to the lineHeight by: lineHeight += crossDimLead; *before* the item alignment happens, this will fail for center and end alignment (it will extend too far in the cross-axis) We therefore need to compensate for that for those two align-item cases. This happened with the combination of align-items: center (or flex-end) align-content: space-between (or space-around) flex-wrap: wrap (flexbox width needs to be small enough to trigger wrapping) --- gentest/fixtures/YGAlignContentTest.html | 8 ++ .../com/facebook/yoga/YGAlignContentTest.java | 104 +++++++++++++++++ .../generated/YGAlignContentTest.test.ts | 110 ++++++++++++++++++ tests/generated/YGAlignContentTest.cpp | 105 +++++++++++++++++ yoga/Yoga.cpp | 4 +- 5 files changed, 329 insertions(+), 2 deletions(-) diff --git a/gentest/fixtures/YGAlignContentTest.html b/gentest/fixtures/YGAlignContentTest.html index 3cecdf8d54..985e05327f 100644 --- a/gentest/fixtures/YGAlignContentTest.html +++ b/gentest/fixtures/YGAlignContentTest.html @@ -46,6 +46,14 @@
+
+
+
+
+
+
+
+
diff --git a/java/tests/com/facebook/yoga/YGAlignContentTest.java b/java/tests/com/facebook/yoga/YGAlignContentTest.java index 42f611ce5e..40ea60da63 100644 --- a/java/tests/com/facebook/yoga/YGAlignContentTest.java +++ b/java/tests/com/facebook/yoga/YGAlignContentTest.java @@ -631,6 +631,110 @@ public void test_align_content_spacebetween() { assertEquals(10f, root_child4.getLayoutHeight(), 0.0f); } + @Test + public void test_align_content_spacebetween_and_align_items_flex_end_with_flex_wrap() { + YogaConfig config = YogaConfigFactory.create(); + config.setExperimentalFeatureEnabled(YogaExperimentalFeature.ABSOLUTE_PERCENTAGE_AGAINST_PADDING_EDGE, true); + + final YogaNode root = createNode(config); + root.setFlexDirection(YogaFlexDirection.ROW); + root.setAlignContent(YogaAlign.SPACE_BETWEEN); + root.setAlignItems(YogaAlign.FLEX_END); + root.setWrap(YogaWrap.WRAP); + root.setWidth(150f); + root.setHeight(100f); + + final YogaNode root_child0 = createNode(config); + root_child0.setWidth(50f); + root_child0.setHeight(10f); + root.addChildAt(root_child0, 0); + + final YogaNode root_child1 = createNode(config); + root_child1.setWidth(50f); + root_child1.setHeight(10f); + root.addChildAt(root_child1, 1); + + final YogaNode root_child2 = createNode(config); + root_child2.setWidth(50f); + root_child2.setHeight(10f); + root.addChildAt(root_child2, 2); + + final YogaNode root_child3 = createNode(config); + root_child3.setWidth(50f); + root_child3.setHeight(10f); + root.addChildAt(root_child3, 3); + + final YogaNode root_child4 = createNode(config); + root_child4.setWidth(50f); + root_child4.setHeight(10f); + root.addChildAt(root_child4, 4); + root.setDirection(YogaDirection.LTR); + root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(150f, 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(10f, 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(10f, root_child1.getLayoutHeight(), 0.0f); + + assertEquals(100f, root_child2.getLayoutX(), 0.0f); + assertEquals(0f, root_child2.getLayoutY(), 0.0f); + assertEquals(50f, root_child2.getLayoutWidth(), 0.0f); + assertEquals(10f, root_child2.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child3.getLayoutX(), 0.0f); + assertEquals(90f, root_child3.getLayoutY(), 0.0f); + assertEquals(50f, root_child3.getLayoutWidth(), 0.0f); + assertEquals(10f, root_child3.getLayoutHeight(), 0.0f); + + assertEquals(50f, root_child4.getLayoutX(), 0.0f); + assertEquals(90f, root_child4.getLayoutY(), 0.0f); + assertEquals(50f, root_child4.getLayoutWidth(), 0.0f); + assertEquals(10f, root_child4.getLayoutHeight(), 0.0f); + + root.setDirection(YogaDirection.RTL); + root.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED); + + assertEquals(0f, root.getLayoutX(), 0.0f); + assertEquals(0f, root.getLayoutY(), 0.0f); + assertEquals(150f, 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(50f, root_child0.getLayoutWidth(), 0.0f); + assertEquals(10f, 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(10f, root_child1.getLayoutHeight(), 0.0f); + + assertEquals(0f, root_child2.getLayoutX(), 0.0f); + assertEquals(0f, root_child2.getLayoutY(), 0.0f); + assertEquals(50f, root_child2.getLayoutWidth(), 0.0f); + assertEquals(10f, root_child2.getLayoutHeight(), 0.0f); + + assertEquals(100f, root_child3.getLayoutX(), 0.0f); + assertEquals(90f, root_child3.getLayoutY(), 0.0f); + assertEquals(50f, root_child3.getLayoutWidth(), 0.0f); + assertEquals(10f, root_child3.getLayoutHeight(), 0.0f); + + assertEquals(50f, root_child4.getLayoutX(), 0.0f); + assertEquals(90f, root_child4.getLayoutY(), 0.0f); + assertEquals(50f, root_child4.getLayoutWidth(), 0.0f); + assertEquals(10f, root_child4.getLayoutHeight(), 0.0f); + } + @Test public void test_align_content_spacearound() { YogaConfig config = YogaConfigFactory.create(); diff --git a/javascript/tests/generated/YGAlignContentTest.test.ts b/javascript/tests/generated/YGAlignContentTest.test.ts index 2d56ef4a99..bc4562df59 100644 --- a/javascript/tests/generated/YGAlignContentTest.test.ts +++ b/javascript/tests/generated/YGAlignContentTest.test.ts @@ -667,6 +667,116 @@ test('align_content_spacebetween', () => { config.free(); } }); +test('align_content_spacebetween_and_align_items_flex_end_with_flex_wrap', () => { + const config = Yoga.Config.create(); + let root; + + config.setExperimentalFeatureEnabled(ExperimentalFeature.AbsolutePercentageAgainstPaddingEdge, true); + + try { + root = Yoga.Node.create(config); + root.setFlexDirection(FlexDirection.Row); + root.setAlignContent(Align.SpaceBetween); + root.setAlignItems(Align.FlexEnd); + root.setFlexWrap(Wrap.Wrap); + root.setWidth(150); + root.setHeight(100); + + const root_child0 = Yoga.Node.create(config); + root_child0.setWidth(50); + root_child0.setHeight(10); + root.insertChild(root_child0, 0); + + const root_child1 = Yoga.Node.create(config); + root_child1.setWidth(50); + root_child1.setHeight(10); + root.insertChild(root_child1, 1); + + const root_child2 = Yoga.Node.create(config); + root_child2.setWidth(50); + root_child2.setHeight(10); + root.insertChild(root_child2, 2); + + const root_child3 = Yoga.Node.create(config); + root_child3.setWidth(50); + root_child3.setHeight(10); + root.insertChild(root_child3, 3); + + const root_child4 = Yoga.Node.create(config); + root_child4.setWidth(50); + root_child4.setHeight(10); + root.insertChild(root_child4, 4); + root.calculateLayout(undefined, undefined, Direction.LTR); + + expect(root.getComputedLeft()).toBe(0); + expect(root.getComputedTop()).toBe(0); + expect(root.getComputedWidth()).toBe(150); + expect(root.getComputedHeight()).toBe(100); + + expect(root_child0.getComputedLeft()).toBe(0); + expect(root_child0.getComputedTop()).toBe(0); + expect(root_child0.getComputedWidth()).toBe(50); + expect(root_child0.getComputedHeight()).toBe(10); + + expect(root_child1.getComputedLeft()).toBe(50); + expect(root_child1.getComputedTop()).toBe(0); + expect(root_child1.getComputedWidth()).toBe(50); + expect(root_child1.getComputedHeight()).toBe(10); + + expect(root_child2.getComputedLeft()).toBe(100); + expect(root_child2.getComputedTop()).toBe(0); + expect(root_child2.getComputedWidth()).toBe(50); + expect(root_child2.getComputedHeight()).toBe(10); + + expect(root_child3.getComputedLeft()).toBe(0); + expect(root_child3.getComputedTop()).toBe(90); + expect(root_child3.getComputedWidth()).toBe(50); + expect(root_child3.getComputedHeight()).toBe(10); + + expect(root_child4.getComputedLeft()).toBe(50); + expect(root_child4.getComputedTop()).toBe(90); + expect(root_child4.getComputedWidth()).toBe(50); + expect(root_child4.getComputedHeight()).toBe(10); + + root.calculateLayout(undefined, undefined, Direction.RTL); + + expect(root.getComputedLeft()).toBe(0); + expect(root.getComputedTop()).toBe(0); + expect(root.getComputedWidth()).toBe(150); + expect(root.getComputedHeight()).toBe(100); + + expect(root_child0.getComputedLeft()).toBe(100); + expect(root_child0.getComputedTop()).toBe(0); + expect(root_child0.getComputedWidth()).toBe(50); + expect(root_child0.getComputedHeight()).toBe(10); + + expect(root_child1.getComputedLeft()).toBe(50); + expect(root_child1.getComputedTop()).toBe(0); + expect(root_child1.getComputedWidth()).toBe(50); + expect(root_child1.getComputedHeight()).toBe(10); + + expect(root_child2.getComputedLeft()).toBe(0); + expect(root_child2.getComputedTop()).toBe(0); + expect(root_child2.getComputedWidth()).toBe(50); + expect(root_child2.getComputedHeight()).toBe(10); + + expect(root_child3.getComputedLeft()).toBe(100); + expect(root_child3.getComputedTop()).toBe(90); + expect(root_child3.getComputedWidth()).toBe(50); + expect(root_child3.getComputedHeight()).toBe(10); + + expect(root_child4.getComputedLeft()).toBe(50); + expect(root_child4.getComputedTop()).toBe(90); + expect(root_child4.getComputedWidth()).toBe(50); + expect(root_child4.getComputedHeight()).toBe(10); + } finally { + if (typeof root !== 'undefined') { + root.freeRecursive(); + } + + config.free(); + } +}); test('align_content_spacearound', () => { const config = Yoga.Config.create(); let root; diff --git a/tests/generated/YGAlignContentTest.cpp b/tests/generated/YGAlignContentTest.cpp index ddb2c35d04..81141d33ad 100644 --- a/tests/generated/YGAlignContentTest.cpp +++ b/tests/generated/YGAlignContentTest.cpp @@ -623,6 +623,111 @@ TEST(YogaTest, align_content_spacebetween) { YGConfigFree(config); } +TEST(YogaTest, align_content_spacebetween_and_align_items_flex_end_with_flex_wrap) { + const YGConfigRef config = YGConfigNew(); + YGConfigSetExperimentalFeatureEnabled(config, YGExperimentalFeatureAbsolutePercentageAgainstPaddingEdge, true); + + const YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetFlexDirection(root, YGFlexDirectionRow); + YGNodeStyleSetAlignContent(root, YGAlignSpaceBetween); + YGNodeStyleSetAlignItems(root, YGAlignFlexEnd); + YGNodeStyleSetFlexWrap(root, YGWrapWrap); + YGNodeStyleSetWidth(root, 150); + YGNodeStyleSetHeight(root, 100); + + const YGNodeRef root_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetWidth(root_child0, 50); + YGNodeStyleSetHeight(root_child0, 10); + YGNodeInsertChild(root, root_child0, 0); + + const YGNodeRef root_child1 = YGNodeNewWithConfig(config); + YGNodeStyleSetWidth(root_child1, 50); + YGNodeStyleSetHeight(root_child1, 10); + YGNodeInsertChild(root, root_child1, 1); + + const YGNodeRef root_child2 = YGNodeNewWithConfig(config); + YGNodeStyleSetWidth(root_child2, 50); + YGNodeStyleSetHeight(root_child2, 10); + YGNodeInsertChild(root, root_child2, 2); + + const YGNodeRef root_child3 = YGNodeNewWithConfig(config); + YGNodeStyleSetWidth(root_child3, 50); + YGNodeStyleSetHeight(root_child3, 10); + YGNodeInsertChild(root, root_child3, 3); + + const YGNodeRef root_child4 = YGNodeNewWithConfig(config); + YGNodeStyleSetWidth(root_child4, 50); + YGNodeStyleSetHeight(root_child4, 10); + YGNodeInsertChild(root, root_child4, 4); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(150, 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(10, 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(10, YGNodeLayoutGetHeight(root_child1)); + + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetLeft(root_child2)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child2)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetWidth(root_child2)); + ASSERT_FLOAT_EQ(10, YGNodeLayoutGetHeight(root_child2)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child3)); + ASSERT_FLOAT_EQ(90, YGNodeLayoutGetTop(root_child3)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetWidth(root_child3)); + ASSERT_FLOAT_EQ(10, YGNodeLayoutGetHeight(root_child3)); + + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetLeft(root_child4)); + ASSERT_FLOAT_EQ(90, YGNodeLayoutGetTop(root_child4)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetWidth(root_child4)); + ASSERT_FLOAT_EQ(10, YGNodeLayoutGetHeight(root_child4)); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionRTL); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root)); + ASSERT_FLOAT_EQ(150, 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(50, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(10, 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(10, YGNodeLayoutGetHeight(root_child1)); + + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetLeft(root_child2)); + ASSERT_FLOAT_EQ(0, YGNodeLayoutGetTop(root_child2)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetWidth(root_child2)); + ASSERT_FLOAT_EQ(10, YGNodeLayoutGetHeight(root_child2)); + + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetLeft(root_child3)); + ASSERT_FLOAT_EQ(90, YGNodeLayoutGetTop(root_child3)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetWidth(root_child3)); + ASSERT_FLOAT_EQ(10, YGNodeLayoutGetHeight(root_child3)); + + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetLeft(root_child4)); + ASSERT_FLOAT_EQ(90, YGNodeLayoutGetTop(root_child4)); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetWidth(root_child4)); + ASSERT_FLOAT_EQ(10, YGNodeLayoutGetHeight(root_child4)); + + YGNodeFreeRecursive(root); + + YGConfigFree(config); +} + TEST(YogaTest, align_content_spacearound) { const YGConfigRef config = YGConfigNew(); YGConfigSetExperimentalFeatureEnabled(config, YGExperimentalFeatureAbsolutePercentageAgainstPaddingEdge, true); diff --git a/yoga/Yoga.cpp b/yoga/Yoga.cpp index ab0aa82526..956ce22401 100644 --- a/yoga/Yoga.cpp +++ b/yoga/Yoga.cpp @@ -3351,7 +3351,7 @@ static void YGNodelayoutImpl( } case YGAlignFlexEnd: { child->setLayoutPosition( - currentLead + lineHeight - + currentLead + lineHeight - crossDimLead - child->getTrailingMargin(crossAxis, availableInnerWidth) .unwrap() - child->getLayout().measuredDimensions[dim[crossAxis]], @@ -3363,7 +3363,7 @@ static void YGNodelayoutImpl( child->getLayout().measuredDimensions[dim[crossAxis]]; child->setLayoutPosition( - currentLead + (lineHeight - childHeight) / 2, + currentLead + (lineHeight - crossDimLead - childHeight) / 2, pos[crossAxis]); break; }