Skip to content

Commit 214ef88

Browse files
albyrock87PureWeen
authored andcommitted
Compute LayoutConstraints on new *StackLayout and Grid (#28931)
1 parent ebb9749 commit 214ef88

File tree

10 files changed

+373
-0
lines changed

10 files changed

+373
-0
lines changed

src/Controls/src/Core/Layout/Grid.cs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ namespace Microsoft.Maui.Controls
99
[ContentProperty(nameof(Children))]
1010
public class Grid : Layout, IGridLayout
1111
{
12+
static readonly ColumnDefinitionCollection DefaultColumnDefinitions = new(new ColumnDefinition { Width = GridLength.Star });
13+
static readonly RowDefinitionCollection DefaultRowDefinitions = new(new RowDefinition { Height = GridLength.Star });
14+
1215
readonly Dictionary<IView, GridInfo> _viewInfo = new();
1316

1417
/// <summary>Bindable property for <see cref="ColumnDefinitions"/>.</summary>
@@ -354,6 +357,83 @@ protected override void OnBindingContextChanged()
354357
UpdateRowColumnBindingContexts();
355358
}
356359

360+
internal override void ComputeConstraintForView(View view)
361+
{
362+
var result = LayoutConstraint.None;
363+
364+
if (view.VerticalOptions.Alignment == LayoutAlignment.Fill && ViewHasFixedHeightDefinition(view))
365+
{
366+
result |= LayoutConstraint.VerticallyFixed;
367+
}
368+
369+
if (view.HorizontalOptions.Alignment == LayoutAlignment.Fill && ViewHasFixedWidthDefinition(view))
370+
{
371+
result |= LayoutConstraint.HorizontallyFixed;
372+
}
373+
374+
view.ComputedConstraint = result;
375+
}
376+
377+
bool ViewHasFixedHeightDefinition(BindableObject view)
378+
{
379+
var gridHasFixedHeight = (Constraint & LayoutConstraint.VerticallyFixed) != 0;
380+
381+
var row = GetRow(view);
382+
var rowSpan = GetRowSpan(view);
383+
var rowDefinitions = RowDefinitions;
384+
if (rowDefinitions?.Count is not > 0)
385+
{
386+
rowDefinitions = DefaultRowDefinitions;
387+
}
388+
389+
for (int i = row; i < row + rowSpan && i < rowDefinitions.Count; i++)
390+
{
391+
GridLength height = rowDefinitions[i].Height;
392+
393+
if (height.IsAuto)
394+
{
395+
return false;
396+
}
397+
398+
if (!gridHasFixedHeight && height.IsStar)
399+
{
400+
return false;
401+
}
402+
}
403+
404+
return true;
405+
}
406+
407+
bool ViewHasFixedWidthDefinition(BindableObject view)
408+
{
409+
var gridHasFixedWidth = (Constraint & LayoutConstraint.HorizontallyFixed) != 0;
410+
411+
var col = GetColumn(view);
412+
var colSpan = GetColumnSpan(view);
413+
var columnDefinitions = ColumnDefinitions;
414+
if (columnDefinitions?.Count is not > 0)
415+
{
416+
columnDefinitions = DefaultColumnDefinitions;
417+
}
418+
419+
for (int i = col; i < col + colSpan && i < columnDefinitions.Count; i++)
420+
{
421+
GridLength width = columnDefinitions[i].Width;
422+
423+
if (width.IsAuto)
424+
{
425+
return false;
426+
}
427+
428+
if (!gridHasFixedWidth && width.IsStar)
429+
{
430+
return false;
431+
}
432+
}
433+
434+
return true;
435+
}
436+
357437
void UpdateRowColumnBindingContexts()
358438
{
359439
var bindingContext = BindingContext;

src/Controls/src/Core/Layout/HorizontalStackLayout.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,17 @@ namespace Microsoft.Maui.Controls
77
public class HorizontalStackLayout : StackBase
88
{
99
protected override ILayoutManager CreateLayoutManager() => new HorizontalStackLayoutManager(this);
10+
11+
internal override void ComputeConstraintForView(View view)
12+
{
13+
if ((Constraint & LayoutConstraint.VerticallyFixed) != 0 && view.VerticalOptions.Alignment == LayoutAlignment.Fill)
14+
{
15+
view.ComputedConstraint = LayoutConstraint.VerticallyFixed;
16+
}
17+
else
18+
{
19+
view.ComputedConstraint = LayoutConstraint.None;
20+
}
21+
}
1022
}
1123
}

src/Controls/src/Core/Layout/StackLayout.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,31 @@ protected override ILayoutManager CreateLayoutManager()
3636
{
3737
return new StackLayoutManager(this);
3838
}
39+
40+
internal override void ComputeConstraintForView(View view)
41+
{
42+
if (Orientation == StackOrientation.Horizontal)
43+
{
44+
if ((Constraint & LayoutConstraint.VerticallyFixed) != 0 && view.VerticalOptions.Alignment == LayoutAlignment.Fill)
45+
{
46+
view.ComputedConstraint = LayoutConstraint.VerticallyFixed;
47+
}
48+
else
49+
{
50+
view.ComputedConstraint = LayoutConstraint.None;
51+
}
52+
}
53+
else
54+
{
55+
if ((Constraint & LayoutConstraint.HorizontallyFixed) != 0 && view.HorizontalOptions.Alignment == LayoutAlignment.Fill)
56+
{
57+
view.ComputedConstraint = LayoutConstraint.HorizontallyFixed;
58+
}
59+
else
60+
{
61+
view.ComputedConstraint = LayoutConstraint.None;
62+
}
63+
}
64+
}
3965
}
4066
}

src/Controls/src/Core/Layout/VerticalStackLayout.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,17 @@ namespace Microsoft.Maui.Controls
77
public class VerticalStackLayout : StackBase
88
{
99
protected override ILayoutManager CreateLayoutManager() => new VerticalStackLayoutManager(this);
10+
11+
internal override void ComputeConstraintForView(View view)
12+
{
13+
if ((Constraint & LayoutConstraint.HorizontallyFixed) != 0 && view.HorizontalOptions.Alignment == LayoutAlignment.Fill)
14+
{
15+
view.ComputedConstraint = LayoutConstraint.HorizontallyFixed;
16+
}
17+
else
18+
{
19+
view.ComputedConstraint = LayoutConstraint.None;
20+
}
21+
}
1022
}
1123
}

src/Controls/src/Core/Properties/AssemblyInfo.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
[assembly: InternalsVisibleTo("Microsoft.Maui.Controls.Compatibility.Tizen")]
1414
[assembly: InternalsVisibleTo("Microsoft.Maui.Controls.Core.Design")]
1515
[assembly: InternalsVisibleTo("Microsoft.Maui.Controls.Core.UnitTests")]
16+
[assembly: InternalsVisibleTo("Microsoft.Maui.UnitTests")]
1617
[assembly: InternalsVisibleTo("Microsoft.Maui.Controls.Android.UnitTests")]
1718
[assembly: InternalsVisibleTo("Microsoft.Maui.Controls.Compatibility.Android.UnitTests")]
1819
[assembly: InternalsVisibleTo("Microsoft.Maui.Controls.Compatibility.UAP.UnitTests")]
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
using Microsoft.Maui.Controls;
2+
using Xunit;
3+
4+
namespace Microsoft.Maui.UnitTests.Layouts
5+
{
6+
[Category(TestCategory.Core, TestCategory.Layout)]
7+
public class GridTests
8+
{
9+
[Fact]
10+
public void GridConstrainsViewsVerticallyWhenPossible()
11+
{
12+
// While testing vertical constraints, make sure to avoid setting the horizontal one
13+
var grid = new Grid { HorizontalOptions = LayoutOptions.Center };
14+
15+
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(50) });
16+
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Star });
17+
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
18+
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(50) });
19+
20+
var f50 = grid.Add(0, 0);
21+
var f50star = grid.Add(0, 0, rowSpan: 2);
22+
var star = grid.Add(1, 0);
23+
var starAuto = grid.Add(1, 0, rowSpan: 2);
24+
var auto = grid.Add(2, 0);
25+
var autof50 = grid.Add(2, 0, rowSpan: 2);
26+
27+
Assert.Equal(LayoutConstraint.VerticallyFixed, f50.Constraint);
28+
Assert.Equal(LayoutConstraint.None, f50star.Constraint);
29+
Assert.Equal(LayoutConstraint.None, star.Constraint);
30+
Assert.Equal(LayoutConstraint.None, starAuto.Constraint);
31+
Assert.Equal(LayoutConstraint.None, auto.Constraint);
32+
Assert.Equal(LayoutConstraint.None, autof50.Constraint);
33+
34+
// Now set a fixed height on the grid
35+
grid.HeightRequest = 100;
36+
37+
Assert.Equal(LayoutConstraint.VerticallyFixed, f50.Constraint);
38+
Assert.Equal(LayoutConstraint.VerticallyFixed, f50star.Constraint);
39+
Assert.Equal(LayoutConstraint.VerticallyFixed, star.Constraint);
40+
Assert.Equal(LayoutConstraint.None, starAuto.Constraint);
41+
Assert.Equal(LayoutConstraint.None, auto.Constraint);
42+
Assert.Equal(LayoutConstraint.None, autof50.Constraint);
43+
44+
// Now change children vertical options to center so we can see `VerticallyFixed` is not applied anymore
45+
f50.VerticalOptions = LayoutOptions.Center;
46+
f50star.VerticalOptions = LayoutOptions.Center;
47+
star.VerticalOptions = LayoutOptions.Center;
48+
starAuto.VerticalOptions = LayoutOptions.Center;
49+
auto.VerticalOptions = LayoutOptions.Center;
50+
autof50.VerticalOptions = LayoutOptions.Center;
51+
52+
Assert.Equal(LayoutConstraint.None, f50.Constraint);
53+
Assert.Equal(LayoutConstraint.None, f50star.Constraint);
54+
Assert.Equal(LayoutConstraint.None, star.Constraint);
55+
Assert.Equal(LayoutConstraint.None, starAuto.Constraint);
56+
Assert.Equal(LayoutConstraint.None, auto.Constraint);
57+
Assert.Equal(LayoutConstraint.None, autof50.Constraint);
58+
}
59+
60+
[Fact]
61+
public void GridConstrainsViewsHorizontallyWhenPossible()
62+
{
63+
// While testing horizontal constraints, make sure to avoid setting the vertical one
64+
var grid = new Grid { VerticalOptions = LayoutOptions.Center };
65+
66+
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(50) });
67+
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Star });
68+
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
69+
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(50) });
70+
71+
var f50 = grid.Add(0, 0);
72+
var f50star = grid.Add(0, 0, columnSpan: 2);
73+
var star = grid.Add(0, 1);
74+
var starAuto = grid.Add(0, 1, columnSpan: 2);
75+
var auto = grid.Add(0, 2);
76+
var autof50 = grid.Add(0, 2, columnSpan: 2);
77+
78+
Assert.Equal(LayoutConstraint.HorizontallyFixed, f50.Constraint);
79+
Assert.Equal(LayoutConstraint.None, f50star.Constraint);
80+
Assert.Equal(LayoutConstraint.None, star.Constraint);
81+
Assert.Equal(LayoutConstraint.None, starAuto.Constraint);
82+
Assert.Equal(LayoutConstraint.None, auto.Constraint);
83+
Assert.Equal(LayoutConstraint.None, autof50.Constraint);
84+
85+
// Now set a fixed width on the grid
86+
grid.WidthRequest = 100;
87+
88+
Assert.Equal(LayoutConstraint.HorizontallyFixed, f50.Constraint);
89+
Assert.Equal(LayoutConstraint.HorizontallyFixed, f50star.Constraint);
90+
Assert.Equal(LayoutConstraint.HorizontallyFixed, star.Constraint);
91+
Assert.Equal(LayoutConstraint.None, starAuto.Constraint);
92+
Assert.Equal(LayoutConstraint.None, auto.Constraint);
93+
Assert.Equal(LayoutConstraint.None, autof50.Constraint);
94+
95+
// Now change children horizontal options to center so we can see `HorizontallyFixed` is not applied anymore
96+
f50.HorizontalOptions = LayoutOptions.Center;
97+
f50star.HorizontalOptions = LayoutOptions.Center;
98+
star.HorizontalOptions = LayoutOptions.Center;
99+
starAuto.HorizontalOptions = LayoutOptions.Center;
100+
auto.HorizontalOptions = LayoutOptions.Center;
101+
autof50.HorizontalOptions = LayoutOptions.Center;
102+
103+
Assert.Equal(LayoutConstraint.None, f50.Constraint);
104+
Assert.Equal(LayoutConstraint.None, f50star.Constraint);
105+
Assert.Equal(LayoutConstraint.None, star.Constraint);
106+
Assert.Equal(LayoutConstraint.None, starAuto.Constraint);
107+
Assert.Equal(LayoutConstraint.None, auto.Constraint);
108+
}
109+
110+
[Fact]
111+
public void GridCompletelyConstrainsViewsWhenPossible()
112+
{
113+
var grid = new Grid { WidthRequest = 100, HeightRequest = 100 };
114+
115+
var child = grid.Add(0, 0);
116+
117+
Assert.Equal(LayoutConstraint.Fixed, child.Constraint);
118+
}
119+
}
120+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using Microsoft.Maui.Controls;
2+
using Xunit;
3+
4+
namespace Microsoft.Maui.UnitTests.Layouts
5+
{
6+
[Category(TestCategory.Core, TestCategory.Layout)]
7+
public class HorizontalStackLayoutTests
8+
{
9+
[Fact]
10+
public void ConstrainsViewsVerticallyWhenConstrained()
11+
{
12+
var layout = new HorizontalStackLayout();
13+
var child = new ContentView();
14+
layout.Add(child);
15+
16+
Assert.Equal(LayoutConstraint.None, child.Constraint);
17+
18+
layout.HeightRequest = 100;
19+
Assert.Equal(LayoutConstraint.VerticallyFixed, child.Constraint);
20+
21+
child.WidthRequest = 50;
22+
Assert.Equal(LayoutConstraint.Fixed, child.Constraint);
23+
24+
layout.VerticalOptions = LayoutOptions.Center;
25+
layout.HeightRequest = -1;
26+
Assert.Equal(LayoutConstraint.HorizontallyFixed, child.Constraint);
27+
}
28+
}
29+
}

src/Core/tests/UnitTests/Layouts/LayoutTestHelpers.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using Microsoft.Maui.Controls;
34
using Microsoft.Maui.Graphics;
45
using NSubstitute;
56
using NSubstitute.Core;
@@ -135,5 +136,19 @@ public static IView CreateHeightDominatedView(double unconstrainedWidth, double
135136

136137
return view;
137138
}
139+
140+
public static View Add(this Grid grid, int row, int column, int rowSpan = 1, int columnSpan = 1)
141+
{
142+
var child = new ContentView();
143+
144+
Grid.SetRow(child, row);
145+
Grid.SetColumn(child, column);
146+
Grid.SetRowSpan(child, rowSpan);
147+
Grid.SetColumnSpan(child, columnSpan);
148+
149+
grid.Add(child);
150+
151+
return child;
152+
}
138153
}
139154
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using Microsoft.Maui.Controls;
2+
using Xunit;
3+
4+
namespace Microsoft.Maui.UnitTests.Layouts
5+
{
6+
[Category(TestCategory.Core, TestCategory.Layout)]
7+
public class StackLayoutTests
8+
{
9+
[Fact]
10+
public void VerticalOrientationConstrainsViewsHorizontallyWhenConstrained()
11+
{
12+
var layout = new StackLayout { Orientation = StackOrientation.Vertical };
13+
var child = new ContentView();
14+
layout.Add(child);
15+
16+
Assert.Equal(LayoutConstraint.None, child.Constraint);
17+
18+
layout.WidthRequest = 100;
19+
Assert.Equal(LayoutConstraint.HorizontallyFixed, child.Constraint);
20+
21+
child.HeightRequest = 50;
22+
Assert.Equal(LayoutConstraint.Fixed, child.Constraint);
23+
24+
layout.HorizontalOptions = LayoutOptions.Center;
25+
layout.WidthRequest = -1;
26+
Assert.Equal(LayoutConstraint.VerticallyFixed, child.Constraint);
27+
}
28+
29+
[Fact]
30+
public void HorizontalOrientationConstrainsViewsVerticallyWhenConstrained()
31+
{
32+
var layout = new StackLayout { Orientation = StackOrientation.Horizontal };
33+
var child = new ContentView();
34+
layout.Add(child);
35+
36+
Assert.Equal(LayoutConstraint.None, child.Constraint);
37+
38+
layout.HeightRequest = 100;
39+
Assert.Equal(LayoutConstraint.VerticallyFixed, child.Constraint);
40+
41+
child.WidthRequest = 50;
42+
Assert.Equal(LayoutConstraint.Fixed, child.Constraint);
43+
44+
layout.VerticalOptions = LayoutOptions.Center;
45+
layout.HeightRequest = -1;
46+
Assert.Equal(LayoutConstraint.HorizontallyFixed, child.Constraint);
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)