diff --git a/src/Controls/src/Core/Layout/FlexLayout.cs b/src/Controls/src/Core/Layout/FlexLayout.cs index bdcdc9ddb515..fade9452f905 100644 --- a/src/Controls/src/Core/Layout/FlexLayout.cs +++ b/src/Controls/src/Core/Layout/FlexLayout.cs @@ -483,6 +483,14 @@ void AddFlexItem(IView child) { if (_root == null) return; + + if (child is not BindableObject) + { + // If this is a pure Core IView, we need to track all the flex properties + // locally because we don't have attached properties for them + _viewInfo.Add(child, new FlexInfo()); + } + var item = (child as FlexLayout)?._root ?? new Flex.Item(); InitItemProperties(child, item); if (child is not FlexLayout) diff --git a/src/Controls/tests/Core.UnitTests/Layouts/FlexLayoutTests.cs b/src/Controls/tests/Core.UnitTests/Layouts/FlexLayoutTests.cs index 6194eff0ea01..5b0d19dbf310 100644 --- a/src/Controls/tests/Core.UnitTests/Layouts/FlexLayoutTests.cs +++ b/src/Controls/tests/Core.UnitTests/Layouts/FlexLayoutTests.cs @@ -1,6 +1,7 @@ using Microsoft.Maui.Graphics; using Microsoft.Maui.Layouts; using Xunit; +using NSubstitute; namespace Microsoft.Maui.Controls.Core.UnitTests.Layouts { @@ -80,5 +81,53 @@ public void FlexLayoutRecognizesVisibilityChange() // now that the first view is not visible Assert.True(whenInvisible != whenVisible); } + + /* + * These next two tests deal with unconstrained measure of FlexLayout. Be default, FL + * wants to stretch children across each axis. But you can't stretch things across infinity + * without it getting weird. So for _measurement_ purposes, we treat infinity as zero and + * just give the children their desired size in the unconstrained direction. Otherwise, FL + * would just set their flex frame sizes to zero, which can either cause blanks or layout cycles, + * depending on the target platform. + */ + + (IFlexLayout, IView) SetUpUnconstrainedTest() + { + var root = new Grid(); // FlexLayout requires a parent, at least for now + var flexLayout = new FlexLayout() as IFlexLayout; + + var view = Substitute.For(); + var size = new Size(100, 100); + view.Measure(Arg.Any(), Arg.Any()).Returns(size); + + root.Add(flexLayout); + flexLayout.Add(view); + + return (flexLayout, view); + } + + [Fact] + public void UnconstrainedHeightChildrenHaveHeight() + { + (var flexLayout, var view) = SetUpUnconstrainedTest(); + + _ = flexLayout.CrossPlatformMeasure(400, double.PositiveInfinity); + + var flexFrame = flexLayout.GetFlexFrame(view); + + Assert.Equal(100, flexFrame.Height); + } + + [Fact] + public void UnconstrainedWidthChildrenHaveWidth() + { + (var flexLayout, var view) = SetUpUnconstrainedTest(); + + _ = flexLayout.CrossPlatformMeasure(double.PositiveInfinity, 400); + + var flexFrame = flexLayout.GetFlexFrame(view); + + Assert.Equal(100, flexFrame.Width); + } } } diff --git a/src/Controls/tests/DeviceTests/Elements/Layout/LayoutTests.cs b/src/Controls/tests/DeviceTests/Elements/Layout/LayoutTests.cs index 5e475c952ee2..50bb680d9897 100644 --- a/src/Controls/tests/DeviceTests/Elements/Layout/LayoutTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/Layout/LayoutTests.cs @@ -3,7 +3,9 @@ using Microsoft.Maui.Controls; using Microsoft.Maui.Graphics; using Microsoft.Maui.Handlers; +using Microsoft.Maui.Platform; using Xunit; +using Xunit.Sdk; namespace Microsoft.Maui.DeviceTests { @@ -151,5 +153,38 @@ static void CreateLayout(Type layoutType, out Layout layout, out Label label) layout.Add(label); } } + + [Fact, Category(TestCategory.FlexLayout)] + public async Task FlexLayoutInVerticalStackLayoutDoesNotCycle() + { + await FlexLayoutInStackLayoutDoesNotCycle(new VerticalStackLayout()); + } + + [Fact, Category(TestCategory.FlexLayout)] + public async Task FlexLayoutInHorizontalStackLayoutDoesNotCycle() + { + await FlexLayoutInStackLayoutDoesNotCycle(new HorizontalStackLayout()); + } + + async Task FlexLayoutInStackLayoutDoesNotCycle(IStackLayout root) + { + var flexLayout = new FlexLayout(); + var label = new Label { Text = "Hello" }; + + flexLayout.Add(label); + root.Add(flexLayout); + + await InvokeOnMainThreadAsync(async () => + { + var labelHandler = CreateHandler(label); + var flexLayoutHandler = CreateHandler(flexLayout); + var layoutHandler = CreateHandler(root); + + // If this can be attached to the hierarchy and make it through a layout + // without crashing, then we're good. + + await root.ToPlatform(MauiContext).AttachAndRun(() => { }); + }); + } } } diff --git a/src/Controls/tests/DeviceTests/TestCategory.cs b/src/Controls/tests/DeviceTests/TestCategory.cs index 980c997a2701..b6465adc46a4 100644 --- a/src/Controls/tests/DeviceTests/TestCategory.cs +++ b/src/Controls/tests/DeviceTests/TestCategory.cs @@ -17,8 +17,9 @@ public static class TestCategory public const string Editor = "Editor"; public const string Element = "Element"; public const string Entry = "Entry"; - public const string Frame = "Frame"; + public const string FlexLayout = "FlexLayout"; public const string FlyoutPage = "FlyoutPage"; + public const string Frame = "Frame"; public const string Gesture = "Gesture"; public const string Image = "Image"; public const string Label = "Label"; diff --git a/src/Core/src/Layouts/Flex.cs b/src/Core/src/Layouts/Flex.cs index 50793eea9979..b9f16253ee16 100644 --- a/src/Core/src/Layouts/Flex.cs +++ b/src/Core/src/Layouts/Flex.cs @@ -530,7 +530,7 @@ static void layout_item(Item item, float width, float height) for (int j = 0; j < 2; j++) { int size_off = j + 2; - if (size_off == layout.frame_size2_i && child_align(child, item) == AlignItems.Stretch) + if (size_off == layout.frame_size2_i && child_align(child, item) == AlignItems.Stretch && layout.align_dim > 0) continue; float val = size[j]; if (!float.IsNaN(val)) @@ -579,7 +579,13 @@ static void layout_item(Item item, float width, float height) layout.flex_grows += child.Grow; layout.flex_shrinks += child.Shrink; - layout.flex_dim -= child_size + child.MarginThickness(layout.vertical); + if(layout.flex_dim > 0) + { + // If flex_dim is zero, it's because we're measuring unconstrained in that direction + // So we don't need to keep a running tally of available space + + layout.flex_dim -= child_size + child.MarginThickness(layout.vertical); + } relative_children_count++;