diff --git a/src/Controls/tests/Core.UnitTests/Layouts/FlexLayoutTests.cs b/src/Controls/tests/Core.UnitTests/Layouts/FlexLayoutTests.cs index 7ed799147fde..72a65af89924 100644 --- a/src/Controls/tests/Core.UnitTests/Layouts/FlexLayoutTests.cs +++ b/src/Controls/tests/Core.UnitTests/Layouts/FlexLayoutTests.cs @@ -33,6 +33,23 @@ protected override Size MeasureOverride(double widthConstraint, double heightCon } } + class FixedSizeLabel : Label + { + readonly double _width; + readonly double _height; + + public FixedSizeLabel(double width, double height) + { + _width = width; + _height = height; + } + + protected override Size MeasureOverride(double widthConstraint, double heightConstraint) + { + return new Size(_width, _height); + } + } + [Fact] public void FlexLayoutMeasuresImagesUnconstrained() { @@ -344,5 +361,43 @@ public void ArrangeOnlyPassFallsBackToDesiredSizeWhenWidthRequestCleared() var clearedFrame = (flexLayout as IFlexLayout).GetFlexFrame(view as IView); Assert.Equal(100, clearedFrame.Width); } + + [Fact] + public void GrowItemsPreserveNaturalSizeAndDistributeFreeSpaceEqually_Issue34464() + { + // Items with different natural widths but equal Grow values should each receive + // an equal share of the available free space added on top of their natural width. + // Before the fix, the natural size was zeroed and the inflated flex_dim was + // distributed proportionally, causing items with larger natural sizes to receive + // less growth than smaller items (violating the flex-grow spec). + var root = new Grid(); + var controlsFlexLayout = new FlexLayout(); + var flexLayout = controlsFlexLayout as IFlexLayout; + + // item1 is narrower (50px), item2 is wider (100px); both have equal Grow + var item1 = new FixedSizeLabel(50, 50); + var item2 = new FixedSizeLabel(100, 50); + + FlexLayout.SetGrow(item1, 1); + FlexLayout.SetShrink(item1, 0); + FlexLayout.SetGrow(item2, 1); + FlexLayout.SetShrink(item2, 0); + + root.Add(controlsFlexLayout); + flexLayout.Add(item1 as IView); + flexLayout.Add(item2 as IView); + + // Container = 300px; total natural width = 150px; free space = 150px. + // With Grow=1 on both items each should receive 75px of extra space: + // item1 expected: 50 + 75 = 125 + // item2 expected: 100 + 75 = 175 + _ = flexLayout.CrossPlatformMeasure(300, 200); + + var frame1 = flexLayout.GetFlexFrame(item1 as IView); + var frame2 = flexLayout.GetFlexFrame(item2 as IView); + + Assert.Equal(125, frame1.Width); + Assert.Equal(175, frame2.Width); + } } } diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/FlexLayoutWithBindableLayoutDisplaysLabels.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/FlexLayoutWithBindableLayoutDisplaysLabels.png new file mode 100644 index 000000000000..aa042e655a97 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/FlexLayoutWithBindableLayoutDisplaysLabels.png differ diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue34464.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue34464.cs new file mode 100644 index 000000000000..685941b45cb8 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue34464.cs @@ -0,0 +1,82 @@ +using System.Collections.Generic; +using Microsoft.Maui; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Graphics; + +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.Github, 34464, "FlexLayout with BindableLayout and Label text display", PlatformAffected.All)] +public class Issue34464 : ContentPage +{ + public Issue34464() + { + var items = new List + { + "Some Medium Text", + "Shorter Text", + "Slightly More Text", + "One - Two", + "Two - Four", + "Two - Three", + "One - Eleven", + }; + + var flexLayout = new FlexLayout + { + Wrap = Microsoft.Maui.Layouts.FlexWrap.Wrap, + AutomationId = "TestFlexLayout" + }; + + BindableLayout.SetItemsSource(flexLayout, items); + BindableLayout.SetItemTemplate(flexLayout, new DataTemplate(() => + { + var border = new Border + { + BackgroundColor = Color.FromArgb("#ffcccccc"), + Stroke = Color.FromArgb("#ffb8b8b8"), + Padding = new Thickness(12) + }; + + FlexLayout.SetGrow(border, 1); + FlexLayout.SetShrink(border, 0); + + var backgroundBorder = new Border + { + BackgroundColor = Color.FromArgb("#44ff0000") + }; + + var label = new Label + { + LineBreakMode = LineBreakMode.NoWrap, + FontSize = 20, + HorizontalTextAlignment = TextAlignment.Center, + VerticalTextAlignment = TextAlignment.Center, + TextColor = Color.FromArgb("#ff000000") + }; + + label.SetBinding(Label.TextProperty, "."); + + var grid = new Grid + { + Children = { backgroundBorder, label } + }; + + border.Content = grid; + + return border; + })); + + var headerLabel = new Label + { + Text = "FlexLayout with BindableLayout Items:", + FontSize = 16, + Margin = new Thickness(10), + AutomationId = "HeaderLabel" + }; + + Content = new VerticalStackLayout + { + Children = { headerLabel, flexLayout } + }; + } +} diff --git a/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/FlexLayoutWithBindableLayoutDisplaysLabels.png b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/FlexLayoutWithBindableLayoutDisplaysLabels.png new file mode 100644 index 000000000000..a71c9b410b12 Binary files /dev/null and b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/FlexLayoutWithBindableLayoutDisplaysLabels.png differ diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34464.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34464.cs new file mode 100644 index 000000000000..177438acfa14 --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34464.cs @@ -0,0 +1,20 @@ +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +public class Issue34464 : _IssuesUITest +{ + public Issue34464(TestDevice device) : base(device) { } + + public override string Issue => "FlexLayout with BindableLayout and Label text display"; + + [Test] + [Category(UITestCategories.Layout)] + public void FlexLayoutWithBindableLayoutDisplaysLabels() + { + App.WaitForElement("HeaderLabel"); + VerifyScreenshot(); + } +} diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/FlexLayoutWithBindableLayoutDisplaysLabels.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/FlexLayoutWithBindableLayoutDisplaysLabels.png new file mode 100644 index 000000000000..409437adb1bd Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/FlexLayoutWithBindableLayoutDisplaysLabels.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/FlexLayoutWithBindableLayoutDisplaysLabels.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/FlexLayoutWithBindableLayoutDisplaysLabels.png new file mode 100644 index 000000000000..78ab9f1a5815 Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/FlexLayoutWithBindableLayoutDisplaysLabels.png differ diff --git a/src/Core/src/Layouts/Flex.cs b/src/Core/src/Layouts/Flex.cs index 070244123344..ff5e546ae891 100644 --- a/src/Core/src/Layouts/Flex.cs +++ b/src/Core/src/Layouts/Flex.cs @@ -783,10 +783,15 @@ static void layout_items(Item item, int child_begin, int child_end, int children float flex_size = 0; if (layout.flex_dim > 0) { + // Only the free space is distributed proportionally, + // not the total container space. layout.flex_dim was inflated by extra_flex_dim + // (the sum of measured sizes of growing items), so we recover the actual free + // space by subtracting it back. The item's measured size is preserved and the + // proportional share of free space is added on top. + float freeSpace = Math.Max(0, layout.flex_dim - layout.extra_flex_dim); if (child.Grow != 0) { - child.Frame[layout.frame_size_i] = 0; // Ignore previous size when growing. - flex_size = (layout.flex_dim / layout.flex_grows) * child.Grow; + flex_size = (freeSpace / layout.flex_grows) * child.Grow; } } else if (layout.flex_dim < 0)