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; }