From 232c2714572e344b54264229520f1911bafbe8e8 Mon Sep 17 00:00:00 2001 From: Evgenii Karachevtsev <46753156+kebox7@users.noreply.github.com> Date: Fri, 11 Apr 2025 16:21:30 +0300 Subject: [PATCH] [Grid] Fix size calculation for ranges (#18621) * Fix size calculation for range * Add unit tests * Clean code * More correct fix * Clean code (remove unused params) * Delete accidentally added file --- src/Avalonia.Controls/Grid.cs | 45 ++++--- .../Avalonia.Controls.UnitTests/GridTests.cs | 117 +++++++++++++++++- 2 files changed, 137 insertions(+), 25 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 3a616b6e50..2e49104694 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -445,7 +445,7 @@ namespace Avalonia.Controls // appears in Auto column. // - MeasureCellsGroup(extData.CellGroup1, constraint, false, false); + MeasureCellsGroup(extData.CellGroup1, false, false); double rowSpacing = RowSpacing; double columnSpacing = ColumnSpacing; double combinedRowSpacing = RowSpacing * (RowDefinitions.Count - 1); @@ -458,9 +458,9 @@ namespace Avalonia.Controls if (canResolveStarsV) { if (HasStarCellsV) { ResolveStar(DefinitionsV, innerAvailableSize.Height); } - MeasureCellsGroup(extData.CellGroup2, innerAvailableSize, false, false); + MeasureCellsGroup(extData.CellGroup2, false, false); if (HasStarCellsU) { ResolveStar(DefinitionsU, innerAvailableSize.Width); } - MeasureCellsGroup(extData.CellGroup3, innerAvailableSize, false, false); + MeasureCellsGroup(extData.CellGroup3, false, false); } else { @@ -470,7 +470,7 @@ namespace Avalonia.Controls if (canResolveStarsU) { if (HasStarCellsU) { ResolveStar(DefinitionsU, innerAvailableSize.Width); } - MeasureCellsGroup(extData.CellGroup3, innerAvailableSize, false, false); + MeasureCellsGroup(extData.CellGroup3, false, false); if (HasStarCellsV) { ResolveStar(DefinitionsV, innerAvailableSize.Height); } } else @@ -487,7 +487,7 @@ namespace Avalonia.Controls double[] group2MinSizes = CacheMinSizes(extData.CellGroup2, false); double[] group3MinSizes = CacheMinSizes(extData.CellGroup3, true); - MeasureCellsGroup(extData.CellGroup2, innerAvailableSize, false, true); + MeasureCellsGroup(extData.CellGroup2, false, true); do { @@ -498,20 +498,20 @@ namespace Avalonia.Controls } if (HasStarCellsU) { ResolveStar(DefinitionsU, innerAvailableSize.Width); } - MeasureCellsGroup(extData.CellGroup3, innerAvailableSize, false, false); + MeasureCellsGroup(extData.CellGroup3, false, false); // Reset cached Group2Widths ApplyCachedMinSizes(group2MinSizes, false); if (HasStarCellsV) { ResolveStar(DefinitionsV, innerAvailableSize.Height); } - MeasureCellsGroup(extData.CellGroup2, innerAvailableSize, cnt == c_layoutLoopMaxCount, false, out hasDesiredSizeUChanged); + MeasureCellsGroup(extData.CellGroup2, cnt == c_layoutLoopMaxCount, false, out hasDesiredSizeUChanged); } while (hasDesiredSizeUChanged && ++cnt <= c_layoutLoopMaxCount); } } } - MeasureCellsGroup(extData.CellGroup4, constraint, false, false); + MeasureCellsGroup(extData.CellGroup4, false, false); gridDesiredSize = new Size( CalculateDesiredSize(DefinitionsU) + ColumnSpacing * (DefinitionsU.Count - 1), @@ -979,19 +979,16 @@ namespace Avalonia.Controls private void MeasureCellsGroup( int cellsHead, - Size referenceSize, bool ignoreDesiredSizeU, bool forceInfinityV) { - MeasureCellsGroup(cellsHead, referenceSize, ignoreDesiredSizeU, forceInfinityV, out _); + MeasureCellsGroup(cellsHead, ignoreDesiredSizeU, forceInfinityV, out _); } /// /// Measures one group of cells. /// /// Head index of the cells chain. - /// Reference size for spanned cells - /// calculations. /// When "true" cells' desired /// width is not registered in columns. /// Passed through to MeasureCell. @@ -999,7 +996,6 @@ namespace Avalonia.Controls /// When the method exits, indicates whether the desired size has changed. private void MeasureCellsGroup( int cellsHead, - Size referenceSize, bool ignoreDesiredSizeU, bool forceInfinityV, out bool hasDesiredSizeUChanged) @@ -1066,14 +1062,14 @@ namespace Avalonia.Controls foreach (DictionaryEntry e in spanStore) { SpanKey key = (SpanKey)e.Key; - double requestedSize = (double)e.Value!; + double desiredSize = (double)e.Value!; EnsureMinSizeInDefinitionRange( key.U ? DefinitionsU : DefinitionsV, key.Start, key.Count, - requestedSize, - key.U ? referenceSize.Width : referenceSize.Height); + key.U ? ColumnSpacing : RowSpacing, + desiredSize); } } } @@ -1193,8 +1189,8 @@ namespace Avalonia.Controls measureSize += spacing + (definitions[i].SizeType == LayoutTimeSizeType.Auto ? - definitions[i].MinSize : - definitions[i].MeasureSize); + definitions[i].MinSize : + definitions[i].MeasureSize); } while (--i >= start); return measureSize; @@ -1228,20 +1224,23 @@ namespace Avalonia.Controls /// /// Distributes min size back to definition array's range. /// + /// Array of definitions to process. /// Start of the range. /// Number of items in the range. - /// Minimum size that should "fit" into the definitions range. - /// Definition array receiving distribution. - /// Size used to resolve percentages. + /// or + /// Minimum size that should "fit" into the definitions range. private void EnsureMinSizeInDefinitionRange( IReadOnlyList definitions, int start, int count, - double requestedSize, - double percentReferenceSize) + double spacing, + double desiredSize) { Debug.Assert(1 < count && 0 <= start && (start + count) <= definitions.Count); + // The spacing between definitions that this element spans through must not be distributed + double requestedSize = Math.Max((desiredSize - spacing * (count - 1)), 0.0); + // avoid processing when asked to distribute "0" if (!MathUtilities.IsZero(requestedSize)) { diff --git a/tests/Avalonia.Controls.UnitTests/GridTests.cs b/tests/Avalonia.Controls.UnitTests/GridTests.cs index 2647911721..c1bbc5acc3 100644 --- a/tests/Avalonia.Controls.UnitTests/GridTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Avalonia.UnitTests; @@ -1773,7 +1773,7 @@ namespace Avalonia.Controls.UnitTests ColumnDefinitions = ColumnDefinitions.Parse("20,20"), Children = { - new Border + new Border { Height = 100, [Grid.ColumnSpanProperty] = 2 @@ -1787,6 +1787,119 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Rect(0, 0, 60, 100), target.Children[0].Bounds); } + [Fact] + public void Grid_Controls_With_Spacing_With_Span_And_SharedSize() + { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + + var grid1 = new Grid() + { + [Grid.RowProperty] = 0, + RowDefinitions = RowDefinitions.Parse("Auto,*,Auto,Auto"), + ColumnDefinitions = + [ + new ColumnDefinition(GridLength.Auto), + new ColumnDefinition(GridLength.Star), + new ColumnDefinition(GridLength.Auto), + new ColumnDefinition(GridLength.Auto) + { + SharedSizeGroup = "C3" + } + ], + RowSpacing = 10, + ColumnSpacing = 10, + Children = + { + new ScrollViewer() + { + [Grid.RowProperty] = 0, + [Grid.ColumnProperty] = 0, + [Grid.RowSpanProperty] = 3, + [Grid.ColumnSpanProperty] = 3, + Content = new TextBlock() + { + Text = @"0: 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 +1: 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 +2: 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 +3: 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 +4: 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 +5: 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 +6: 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 +7: 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 +8: 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 +9: 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890" + } + }, + new Button() + { + [Grid.RowProperty] = 3, + [Grid.ColumnProperty] = 0, + Width = 100, + Height = 40 + }, + new Button() + { + [Grid.RowProperty] = 3, + [Grid.ColumnProperty] = 2, + Width = 100, + Height = 40 + }, + new Button() + { + [Grid.RowProperty] = 0, + [Grid.ColumnProperty] = 3, + Width = 100, + Height = 40 + }, + new Button() + { + [Grid.RowProperty] = 2, + [Grid.ColumnProperty] = 3, + Width = 100, + Height = 40 + } + } + }; + + var grid2 = new Grid() + { + [Grid.RowProperty] = 1, + ColumnDefinitions = + [ + new ColumnDefinition(GridLength.Star), + new ColumnDefinition(GridLength.Auto) + { + SharedSizeGroup = "C3" + } + ], + Children = + { + new TextBlock() + { + [Grid.ColumnProperty] = 1, + Height = 20, + Text="1234567890" + } + } + }; + + var root = new Grid() + { + [Grid.IsSharedSizeScopeProperty] = true, + RowDefinitions = RowDefinitions.Parse("*,Auto"), + RowSpacing = 10, + Margin = new Thickness(10) + }; + root.Children.Add(grid1); + root.Children.Add(grid2); + root.Measure(new Size(550, 240)); + root.Arrange(new Rect(new Point(), new Point(550, 240))); + + Assert.Equal(new Rect(0, 0, 420, 140), grid1.Children[0].Bounds); + Assert.Equal(grid1.Children[4].Bounds.Left, grid2.Children[0].Bounds.Left); + Assert.Equal(grid1.Children[4].Bounds.Width, grid2.Children[0].Bounds.Width); + } + private class TestControl : Control { public Size MeasureSize { get; set; }