Browse Source

[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
pull/18645/head
Evgenii Karachevtsev 2 years ago
committed by GitHub
parent
commit
f42af483c9
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 45
      src/Avalonia.Controls/Grid.cs
  2. 117
      tests/Avalonia.Controls.UnitTests/GridTests.cs

45
src/Avalonia.Controls/Grid.cs

@ -445,7 +445,7 @@ namespace Avalonia.Controls
// appears in Auto column. // appears in Auto column.
// //
MeasureCellsGroup(extData.CellGroup1, constraint, false, false); MeasureCellsGroup(extData.CellGroup1, false, false);
double rowSpacing = RowSpacing; double rowSpacing = RowSpacing;
double columnSpacing = ColumnSpacing; double columnSpacing = ColumnSpacing;
double combinedRowSpacing = RowSpacing * (RowDefinitions.Count - 1); double combinedRowSpacing = RowSpacing * (RowDefinitions.Count - 1);
@ -458,9 +458,9 @@ namespace Avalonia.Controls
if (canResolveStarsV) if (canResolveStarsV)
{ {
if (HasStarCellsV) { ResolveStar(DefinitionsV, innerAvailableSize.Height); } if (HasStarCellsV) { ResolveStar(DefinitionsV, innerAvailableSize.Height); }
MeasureCellsGroup(extData.CellGroup2, innerAvailableSize, false, false); MeasureCellsGroup(extData.CellGroup2, false, false);
if (HasStarCellsU) { ResolveStar(DefinitionsU, innerAvailableSize.Width); } if (HasStarCellsU) { ResolveStar(DefinitionsU, innerAvailableSize.Width); }
MeasureCellsGroup(extData.CellGroup3, innerAvailableSize, false, false); MeasureCellsGroup(extData.CellGroup3, false, false);
} }
else else
{ {
@ -470,7 +470,7 @@ namespace Avalonia.Controls
if (canResolveStarsU) if (canResolveStarsU)
{ {
if (HasStarCellsU) { ResolveStar(DefinitionsU, innerAvailableSize.Width); } if (HasStarCellsU) { ResolveStar(DefinitionsU, innerAvailableSize.Width); }
MeasureCellsGroup(extData.CellGroup3, innerAvailableSize, false, false); MeasureCellsGroup(extData.CellGroup3, false, false);
if (HasStarCellsV) { ResolveStar(DefinitionsV, innerAvailableSize.Height); } if (HasStarCellsV) { ResolveStar(DefinitionsV, innerAvailableSize.Height); }
} }
else else
@ -487,7 +487,7 @@ namespace Avalonia.Controls
double[] group2MinSizes = CacheMinSizes(extData.CellGroup2, false); double[] group2MinSizes = CacheMinSizes(extData.CellGroup2, false);
double[] group3MinSizes = CacheMinSizes(extData.CellGroup3, true); double[] group3MinSizes = CacheMinSizes(extData.CellGroup3, true);
MeasureCellsGroup(extData.CellGroup2, innerAvailableSize, false, true); MeasureCellsGroup(extData.CellGroup2, false, true);
do do
{ {
@ -498,20 +498,20 @@ namespace Avalonia.Controls
} }
if (HasStarCellsU) { ResolveStar(DefinitionsU, innerAvailableSize.Width); } if (HasStarCellsU) { ResolveStar(DefinitionsU, innerAvailableSize.Width); }
MeasureCellsGroup(extData.CellGroup3, innerAvailableSize, false, false); MeasureCellsGroup(extData.CellGroup3, false, false);
// Reset cached Group2Widths // Reset cached Group2Widths
ApplyCachedMinSizes(group2MinSizes, false); ApplyCachedMinSizes(group2MinSizes, false);
if (HasStarCellsV) { ResolveStar(DefinitionsV, innerAvailableSize.Height); } 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); while (hasDesiredSizeUChanged && ++cnt <= c_layoutLoopMaxCount);
} }
} }
} }
MeasureCellsGroup(extData.CellGroup4, constraint, false, false); MeasureCellsGroup(extData.CellGroup4, false, false);
gridDesiredSize = new Size( gridDesiredSize = new Size(
CalculateDesiredSize(DefinitionsU) + ColumnSpacing * (DefinitionsU.Count - 1), CalculateDesiredSize(DefinitionsU) + ColumnSpacing * (DefinitionsU.Count - 1),
@ -979,19 +979,16 @@ namespace Avalonia.Controls
private void MeasureCellsGroup( private void MeasureCellsGroup(
int cellsHead, int cellsHead,
Size referenceSize,
bool ignoreDesiredSizeU, bool ignoreDesiredSizeU,
bool forceInfinityV) bool forceInfinityV)
{ {
MeasureCellsGroup(cellsHead, referenceSize, ignoreDesiredSizeU, forceInfinityV, out _); MeasureCellsGroup(cellsHead, ignoreDesiredSizeU, forceInfinityV, out _);
} }
/// <summary> /// <summary>
/// Measures one group of cells. /// Measures one group of cells.
/// </summary> /// </summary>
/// <param name="cellsHead">Head index of the cells chain.</param> /// <param name="cellsHead">Head index of the cells chain.</param>
/// <param name="referenceSize">Reference size for spanned cells
/// calculations.</param>
/// <param name="ignoreDesiredSizeU">When "true" cells' desired /// <param name="ignoreDesiredSizeU">When "true" cells' desired
/// width is not registered in columns.</param> /// width is not registered in columns.</param>
/// <param name="forceInfinityV">Passed through to MeasureCell. /// <param name="forceInfinityV">Passed through to MeasureCell.
@ -999,7 +996,6 @@ namespace Avalonia.Controls
/// <param name="hasDesiredSizeUChanged">When the method exits, indicates whether the desired size has changed.</param> /// <param name="hasDesiredSizeUChanged">When the method exits, indicates whether the desired size has changed.</param>
private void MeasureCellsGroup( private void MeasureCellsGroup(
int cellsHead, int cellsHead,
Size referenceSize,
bool ignoreDesiredSizeU, bool ignoreDesiredSizeU,
bool forceInfinityV, bool forceInfinityV,
out bool hasDesiredSizeUChanged) out bool hasDesiredSizeUChanged)
@ -1066,14 +1062,14 @@ namespace Avalonia.Controls
foreach (DictionaryEntry e in spanStore) foreach (DictionaryEntry e in spanStore)
{ {
SpanKey key = (SpanKey)e.Key; SpanKey key = (SpanKey)e.Key;
double requestedSize = (double)e.Value!; double desiredSize = (double)e.Value!;
EnsureMinSizeInDefinitionRange( EnsureMinSizeInDefinitionRange(
key.U ? DefinitionsU : DefinitionsV, key.U ? DefinitionsU : DefinitionsV,
key.Start, key.Start,
key.Count, key.Count,
requestedSize, key.U ? ColumnSpacing : RowSpacing,
key.U ? referenceSize.Width : referenceSize.Height); desiredSize);
} }
} }
} }
@ -1193,8 +1189,8 @@ namespace Avalonia.Controls
measureSize += measureSize +=
spacing + spacing +
(definitions[i].SizeType == LayoutTimeSizeType.Auto ? (definitions[i].SizeType == LayoutTimeSizeType.Auto ?
definitions[i].MinSize : definitions[i].MinSize :
definitions[i].MeasureSize); definitions[i].MeasureSize);
} while (--i >= start); } while (--i >= start);
return measureSize; return measureSize;
@ -1228,20 +1224,23 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Distributes min size back to definition array's range. /// Distributes min size back to definition array's range.
/// </summary> /// </summary>
/// <param name="definitions">Array of definitions to process.</param>
/// <param name="start">Start of the range.</param> /// <param name="start">Start of the range.</param>
/// <param name="count">Number of items in the range.</param> /// <param name="count">Number of items in the range.</param>
/// <param name="requestedSize">Minimum size that should "fit" into the definitions range.</param> /// <param name="spacing"><see cref="ColumnSpacing"/> or <see cref="RowSpacing"/></param>
/// <param name="definitions">Definition array receiving distribution.</param> /// <param name="desiredSize">Minimum size that should "fit" into the definitions range.</param>
/// <param name="percentReferenceSize">Size used to resolve percentages.</param>
private void EnsureMinSizeInDefinitionRange( private void EnsureMinSizeInDefinitionRange(
IReadOnlyList<DefinitionBase> definitions, IReadOnlyList<DefinitionBase> definitions,
int start, int start,
int count, int count,
double requestedSize, double spacing,
double percentReferenceSize) double desiredSize)
{ {
Debug.Assert(1 < count && 0 <= start && (start + count) <= definitions.Count); 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" // avoid processing when asked to distribute "0"
if (!MathUtilities.IsZero(requestedSize)) if (!MathUtilities.IsZero(requestedSize))
{ {

117
tests/Avalonia.Controls.UnitTests/GridTests.cs

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.UnitTests; using Avalonia.UnitTests;
@ -1773,7 +1773,7 @@ namespace Avalonia.Controls.UnitTests
ColumnDefinitions = ColumnDefinitions.Parse("20,20"), ColumnDefinitions = ColumnDefinitions.Parse("20,20"),
Children = Children =
{ {
new Border new Border
{ {
Height = 100, Height = 100,
[Grid.ColumnSpanProperty] = 2 [Grid.ColumnSpanProperty] = 2
@ -1787,6 +1787,119 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Rect(0, 0, 60, 100), target.Children[0].Bounds); 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 private class TestControl : Control
{ {
public Size MeasureSize { get; set; } public Size MeasureSize { get; set; }

Loading…
Cancel
Save