Skip to content
This repository was archived by the owner on May 1, 2024. It is now read-only.

Commit 30f29a3

Browse files
authored
Apply maximum starred column/row size for all starred columns/rows (#13085) Fixes #12961 Fixes #12725 Fixes #12900
Fixes #12961 Fixes #12725 Fixes #12900
1 parent 3fdffc0 commit 30f29a3

File tree

3 files changed

+825
-632
lines changed

3 files changed

+825
-632
lines changed

Xamarin.Forms.Core.UnitTests/GridTests.cs

Lines changed: 177 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
using NUnit.Framework;
2-
using NUnit.Framework.Constraints;
31
using System;
42
using System.Collections.Generic;
53
using System.Linq;
64
using System.Reflection;
5+
using NUnit.Framework;
6+
using NUnit.Framework.Constraints;
77
using Xamarin.Forms.Internals;
88

99
namespace Xamarin.Forms.Core.UnitTests
@@ -108,6 +108,168 @@ public void StarRowsHaveEqualHeights()
108108
Assert.That(column0Height, Is.LessThan(gridHeight));
109109
}
110110

111+
[Test]
112+
public void StarRowsDoNotOverlapWithStackLayoutOnTop()
113+
{
114+
SetupStarRowOverlapTest(rowAIsOnTop: false, out VisualElement rowAControl,
115+
out VisualElement rowBControl, out Label lastLabel);
116+
117+
var bottomOfRowB = rowBControl.Y + rowBControl.Height;
118+
var bottomOfLastLabelInRowB = rowBControl.Y + lastLabel.Y + lastLabel.Height;
119+
var topOfRowA = rowAControl.Y;
120+
121+
Assert.That(bottomOfRowB, Is.EqualTo(bottomOfLastLabelInRowB));
122+
123+
Assert.That(topOfRowA, Is.EqualTo(bottomOfRowB),
124+
"B is on top of A, so the top of A should be the bottom of B");
125+
}
126+
127+
[Test]
128+
public void StarRowsDoNotOverlapWithStackLayoutOnBottom()
129+
{
130+
SetupStarRowOverlapTest(rowAIsOnTop: true, out VisualElement rowAControl,
131+
out VisualElement rowBControl, out Label lastLabel);
132+
133+
var topOfRowB = rowBControl.Y;
134+
var bottomOfRowB = rowBControl.Y + rowBControl.Height;
135+
var bottomOfLastLabelInRowB = rowBControl.Y + lastLabel.Y + lastLabel.Height;
136+
var bottomOfRowA = rowAControl.Y + rowAControl.Height;
137+
138+
Assert.That(bottomOfRowB, Is.EqualTo(bottomOfLastLabelInRowB));
139+
140+
Assert.That(topOfRowB, Is.EqualTo(bottomOfRowA),
141+
"A is on top of B, so the top of B should be the bottom of A");
142+
}
143+
144+
[Test]
145+
public void StarColumnsDoNotOverlapWithStackLayoutAtStart()
146+
{
147+
SetupStarColumnOverlapTest(colAIsAtStart: false, out VisualElement colAControl,
148+
out VisualElement colBControl, out Label lastLabel);
149+
150+
var endOfColB = colBControl.X + colBControl.Width;
151+
var endOfLastLabelInColB = colBControl.X + lastLabel.X + lastLabel.Width;
152+
var startOfColA = colAControl.X;
153+
154+
Assert.That(endOfColB, Is.EqualTo(endOfLastLabelInColB));
155+
156+
Assert.That(startOfColA, Is.EqualTo(endOfColB),
157+
"B is before A, so the start of A should be the end of B");
158+
}
159+
160+
[Test]
161+
public void StarColumnsDoNotOverlapWithStackLayoutAtEnd()
162+
{
163+
SetupStarColumnOverlapTest(colAIsAtStart: true, out VisualElement colAControl,
164+
out VisualElement colBControl, out Label lastLabel);
165+
166+
var startOfColB = colBControl.X;
167+
var endOfColB = colBControl.X + colBControl.Width;
168+
var endOfLastLabelInColB = colBControl.X + lastLabel.X + lastLabel.Width;
169+
var endOfColA = colAControl.X + colAControl.Width;
170+
171+
Assert.That(endOfColB, Is.EqualTo(endOfLastLabelInColB));
172+
173+
Assert.That(endOfColA, Is.EqualTo(startOfColB),
174+
"A is before B, so the end of A should be the start of B");
175+
}
176+
177+
void SetupStarRowOverlapTest(bool rowAIsOnTop, out VisualElement rowAControl,
178+
out VisualElement rowBControl, out Label lastLabel)
179+
{
180+
var grid = new Grid
181+
{
182+
RowSpacing = 0,
183+
RowDefinitions = new RowDefinitionCollection
184+
{
185+
new RowDefinition() { Height = GridLength.Star },
186+
new RowDefinition() { Height = GridLength.Star }
187+
}
188+
};
189+
190+
var labelSize = new Size(100, 20);
191+
192+
Label label;
193+
rowAControl = label = new FixedSizeLabel(labelSize) { Text = "Hello" };
194+
195+
StackLayout stackLayout;
196+
rowBControl = stackLayout = new StackLayout() { Spacing = 0, IsPlatformEnabled = true };
197+
198+
for (int n = 0; n < 14; n++)
199+
{
200+
var labelInStack = new FixedSizeLabel(labelSize) { Text = "Hello" };
201+
stackLayout.Children.Add(labelInStack);
202+
}
203+
204+
lastLabel = new FixedSizeLabel(labelSize) { Text = "Hello" };
205+
stackLayout.Children.Add(lastLabel);
206+
207+
grid.Children.Add(stackLayout);
208+
grid.Children.Add(label);
209+
210+
if (rowAIsOnTop)
211+
{
212+
Grid.SetRow(rowAControl, 0);
213+
Grid.SetRow(rowBControl, 1);
214+
}
215+
else
216+
{
217+
Grid.SetRow(rowBControl, 0);
218+
Grid.SetRow(rowAControl, 1);
219+
}
220+
221+
var sizeRequest = grid.Measure(300, double.PositiveInfinity);
222+
grid.Layout(new Rectangle(0, 0, sizeRequest.Request.Width, sizeRequest.Request.Height));
223+
}
224+
225+
void SetupStarColumnOverlapTest(bool colAIsAtStart, out VisualElement colAControl,
226+
out VisualElement colBControl, out Label lastLabel)
227+
{
228+
var grid = new Grid
229+
{
230+
ColumnSpacing = 0,
231+
ColumnDefinitions = new ColumnDefinitionCollection
232+
{
233+
new ColumnDefinition() { Width = GridLength.Star },
234+
new ColumnDefinition() { Width = GridLength.Star }
235+
}
236+
};
237+
238+
var labelSize = new Size(20, 100);
239+
240+
Label label;
241+
colAControl = label = new FixedSizeLabel(labelSize) { Text = "Hello" };
242+
243+
StackLayout stackLayout;
244+
colBControl = stackLayout = new StackLayout() { Spacing = 0, Orientation = StackOrientation.Horizontal, IsPlatformEnabled = true };
245+
246+
for (int n = 0; n < 14; n++)
247+
{
248+
var labelInStack = new FixedSizeLabel(labelSize) { Text = "Hello" };
249+
stackLayout.Children.Add(labelInStack);
250+
}
251+
252+
lastLabel = new FixedSizeLabel(labelSize) { Text = "Hello" };
253+
stackLayout.Children.Add(lastLabel);
254+
255+
grid.Children.Add(stackLayout);
256+
grid.Children.Add(label);
257+
258+
if (colAIsAtStart)
259+
{
260+
Grid.SetColumn(colAControl, 0);
261+
Grid.SetColumn(colBControl, 1);
262+
}
263+
else
264+
{
265+
Grid.SetColumn(colBControl, 0);
266+
Grid.SetColumn(colAControl, 1);
267+
}
268+
269+
var sizeRequest = grid.Measure(double.PositiveInfinity, 300);
270+
grid.Layout(new Rectangle(0, 0, sizeRequest.Request.Width, sizeRequest.Request.Height));
271+
}
272+
111273
[Test(Description = "Columns with a Star width less than one should not cause the Grid to contract below the target width; see https://github.com/xamarin/Xamarin.Forms/issues/11742")]
112274
public void StarWidthsLessThanOneShouldNotContractGrid()
113275
{
@@ -176,8 +338,8 @@ public void ColumnsLessThanOneStarShouldBeTallerThanOneStarColumns()
176338

177339
var grid2 = new Grid() { ColumnSpacing = 0 };
178340

179-
// Because the column with the label in it is narrower in this grid (0.5* vs 1*), the label will have
180-
// grow taller to fit the text. So we expect this grid to grow vertically to accommodate it.
341+
// Because the column with the label in it is narrower in this grid (0.5* vs 1*), the label will have
342+
// grow taller to fit the text. So we expect this grid to grow vertically to accommodate it.
181343
grid2.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(0.5, GridUnitType.Star) });
182344
grid2.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Star });
183345

@@ -224,7 +386,7 @@ public void ContentHeightSumShouldMatchGridHeightWithAutoRows()
224386
}
225387

226388
[Test]
227-
public void UnconstrainedStarRowWithMultipleStarColumnsAllowsTextToGrow()
389+
public void UnconstrainedStarRowWithMultipleStarColumnsAllowsTextToGrow()
228390
{
229391
var outerGrid = new Grid() { ColumnSpacing = 0, Padding = 0, RowSpacing = 0, IsPlatformEnabled = true };
230392
outerGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Star });
@@ -235,10 +397,10 @@ public void UnconstrainedStarRowWithMultipleStarColumnsAllowsTextToGrow()
235397
var sl = new StackLayout { Padding = 0, IsPlatformEnabled = true };
236398

237399
var label1 = new HeightBasedOnTextLengthLabel
238-
{
400+
{
239401
Text = "The actual text here doesn't matter, just the length. The length determines the height."
240402
};
241-
403+
242404
var label2 = new ColumnTestLabel { FontSize = 13, LineBreakMode = LineBreakMode.NoWrap, Text = "Description" };
243405

244406
sl.Children.Add(label1);
@@ -293,8 +455,8 @@ public void AbsoluteColumnShouldNotBloatStarredColumns(double firstColumnWidth)
293455

294456
outerGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(firstColumnWidth, GridUnitType.Star) }); // 0.3, but 0.2 works fine
295457
outerGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Star });
296-
297-
// This last column is the trouble spot; MeasureAndContractStarredColumns needs to account for this width
458+
459+
// This last column is the trouble spot; MeasureAndContractStarredColumns needs to account for this width
298460
// when measuring and distributing the starred column space. Otherwise it reports the wrong width and every
299461
// measure after that is wrong.
300462
outerGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = 36 });
@@ -307,7 +469,7 @@ public void AbsoluteColumnShouldNotBloatStarredColumns(double firstColumnWidth)
307469
innerGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
308470
innerGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
309471

310-
var hugeLabel = new _12292TestLabel() { };
472+
var hugeLabel = new _12292TestLabel() { };
311473
var tinyLabel = new ColumnTestLabel { Text = "label1" };
312474

313475
innerGrid.Children.Add(hugeLabel);
@@ -448,13 +610,15 @@ public FixedSizeLabel(Size minimumSize, Size requestedSize)
448610
_requestedSize = requestedSize;
449611
}
450612

613+
public FixedSizeLabel(Size size) : this(size, size) { }
614+
451615
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
452616
{
453617
return new SizeRequest(_requestedSize, _minimumSize);
454618
}
455619
}
456620

457-
class _12292TestLabel : Label
621+
class _12292TestLabel : Label
458622
{
459623
// We need a label that simulates a fairly specific layout/measure pattern to reproduce
460624
// the circumstances of issue 12292.
@@ -508,8 +672,8 @@ protected override SizeRequest OnMeasure(double widthConstraint, double heightCo
508672

509673
class HeightBasedOnTextLengthLabel : TestLabel
510674
{
511-
public double DesiredHeight(double widthConstraint)
512-
{
675+
public double DesiredHeight(double widthConstraint)
676+
{
513677
return (Text.Length / widthConstraint) * 200;
514678
}
515679

Xamarin.Forms.Core/Grid.cs

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public partial class Grid : Layout<View>, IGridController, IElementConfiguration
5757
public Grid()
5858
{
5959
_children = new GridElementCollection(InternalChildren, this) { Parent = this };
60-
_platformConfigurationRegistry = new Lazy<PlatformConfigurationRegistry<Grid>>(() =>
60+
_platformConfigurationRegistry = new Lazy<PlatformConfigurationRegistry<Grid>>(() =>
6161
new PlatformConfigurationRegistry<Grid>(this));
6262
}
6363

@@ -162,14 +162,14 @@ internal override void ComputeConstraintForView(View view)
162162

163163
var result = LayoutConstraint.None;
164164

165-
if (_rows == null || _columns == null)
166-
EnsureRowsColumnsInitialized();
165+
// grab a snapshot of this grid's structure for computing the constraints
166+
var structure = new GridStructure(this);
167167

168168
if (vOptions.Alignment == LayoutAlignment.Fill)
169169
{
170170
int row = GetRow(view);
171171
int rowSpan = GetRowSpan(view);
172-
List<RowDefinition> rowDefinitions = _rows;
172+
List<RowDefinition> rowDefinitions = structure.Rows;
173173

174174
var canFix = true;
175175

@@ -196,7 +196,7 @@ internal override void ComputeConstraintForView(View view)
196196
{
197197
int col = GetColumn(view);
198198
int colSpan = GetColumnSpan(view);
199-
List<ColumnDefinition> columnDefinitions = _columns;
199+
List<ColumnDefinition> columnDefinitions = structure.Columns;
200200

201201
var canFix = true;
202202

@@ -227,12 +227,6 @@ public void InvalidateMeasureInernalNonVirtual(InvalidationTrigger trigger)
227227
{
228228
InvalidateMeasureInternal(trigger);
229229
}
230-
internal override void InvalidateMeasureInternal(InvalidationTrigger trigger)
231-
{
232-
base.InvalidateMeasureInternal(trigger);
233-
_columns = null;
234-
_rows = null;
235-
}
236230

237231
void OnDefinitionChanged(object sender, EventArgs args)
238232
{

0 commit comments

Comments
 (0)