diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 90211d8328..020f6996aa 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -269,6 +269,17 @@ namespace Avalonia.Controls } } + /// + /// Returns min size, never taking into account shared state. + /// + /// + /// This is the definition's own intrinsic contribution to its group. It is the counterpart + /// of : code that saves and restores a min size across a measure + /// must round-trip this value, because feeding the group minimum back into + /// would make the group unable to shrink. + /// + internal double RawMinSize => _minSize; + /// /// Offset. /// @@ -626,7 +637,7 @@ namespace Avalonia.Controls // accumulate min size of all participating definitions for (int i = 0, count = _registry.Count; i < count; ++i) { - sharedMinSize = Math.Max(sharedMinSize, _registry[i].MinSize); + sharedMinSize = Math.Max(sharedMinSize, _registry[i]._minSize); } bool sharedMinSizeChanged = !MathUtilities.AreClose(_minSize, sharedMinSize); diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 3eb4eae98d..992cc38ecc 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -946,11 +946,11 @@ namespace Avalonia.Controls { if (isRows) { - minSizes[PrivateCells[i].RowIndex] = DefinitionsV[PrivateCells[i].RowIndex].MinSize; + minSizes[PrivateCells[i].RowIndex] = DefinitionsV[PrivateCells[i].RowIndex].RawMinSize; } else { - minSizes[PrivateCells[i].ColumnIndex] = DefinitionsU[PrivateCells[i].ColumnIndex].MinSize; + minSizes[PrivateCells[i].ColumnIndex] = DefinitionsU[PrivateCells[i].ColumnIndex].RawMinSize; } i = PrivateCells[i].Next; diff --git a/tests/Avalonia.Controls.UnitTests/GridTests.cs b/tests/Avalonia.Controls.UnitTests/GridTests.cs index a15b8d8c77..435bb79cf3 100644 --- a/tests/Avalonia.Controls.UnitTests/GridTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridTests.cs @@ -1212,6 +1212,103 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(10, grids[1].ColumnDefinitions[0].ActualWidth); } + [Fact] + public void Shared_Size_Group_Shrinks_When_Content_Is_Hidden() + { + var grids = new[] + { + CreateGrid(("A", GridLength.Auto)), + CreateGrid(("A", GridLength.Auto)), + }; + var content = new Border + { + Width = 50, + IsVisible = false, + }; + grids[1].Children.Add(content); + + var scope = new StackPanel + { + [Grid.IsSharedSizeScopeProperty] = true, + Children = + { + grids[0], + grids[1], + }, + }; + var root = new TestRoot(scope); + void ExecuteSharedSizeLayoutPass() + { + // Shared groups validate after layout and apply any resulting invalidation on the next pass. + root.LayoutManager.ExecuteLayoutPass(); + root.LayoutManager.ExecuteLayoutPass(); + } + + root.ExecuteInitialLayoutPass(); + Assert.All(grids, grid => Assert.Equal(0, grid.ColumnDefinitions[0].ActualWidth)); + + content.IsVisible = true; + ExecuteSharedSizeLayoutPass(); + Assert.All(grids, grid => Assert.Equal(50, grid.ColumnDefinitions[0].ActualWidth)); + + content.IsVisible = false; + ExecuteSharedSizeLayoutPass(); + Assert.All(grids, grid => Assert.Equal(0, grid.ColumnDefinitions[0].ActualWidth)); + } + + [Fact] + public void Shared_Size_Group_Shrinks_When_Participant_Uses_Cyclic_Measure_Path() + { + // A grid mixing an auto-column/star-row cell with a star-column/auto-row cell cannot + // resolve stars in either direction up front, so Grid measures it through its cyclic + // dependency path. That path saves definition min sizes before the repeated measure and + // restores them afterwards. Saving the effective min size folds the group minimum into + // the definition's own contribution, which then reclassifies it as a long pole - and + // long poles are deliberately never remeasured, so the group stays pinned open. + var cyclicGrid = CreateGrid(("A", GridLength.Auto), (null, new GridLength(1, GridUnitType.Star))); + cyclicGrid.Height = 100; // star rows collapse to auto under an infinite constraint. + cyclicGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); + cyclicGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }); + + var autoColumnStarRowChild = new Border { Width = 10, Height = 10 }; + Grid.SetColumn(autoColumnStarRowChild, 0); + Grid.SetRow(autoColumnStarRowChild, 1); + + var starColumnAutoRowChild = new Border { Width = 10, Height = 10 }; + Grid.SetColumn(starColumnAutoRowChild, 1); + Grid.SetRow(starColumnAutoRowChild, 0); + + cyclicGrid.Children.Add(autoColumnStarRowChild); + cyclicGrid.Children.Add(starColumnAutoRowChild); + + var plainGrid = CreateGrid(("A", GridLength.Auto), (null, new GridLength(1, GridUnitType.Star))); + var wideContent = new Border { Width = 50, Height = 10 }; + plainGrid.Children.Add(wideContent); + + var scope = new StackPanel + { + [Grid.IsSharedSizeScopeProperty] = true, + Children = { cyclicGrid, plainGrid }, + }; + var root = new TestRoot(scope); + void ExecuteSharedSizeLayoutPass() + { + // Shared groups validate after layout and apply any resulting invalidation on the next pass. + root.LayoutManager.ExecuteLayoutPass(); + root.LayoutManager.ExecuteLayoutPass(); + } + + root.ExecuteInitialLayoutPass(); + ExecuteSharedSizeLayoutPass(); + Assert.Equal(50, cyclicGrid.ColumnDefinitions[0].ActualWidth); + Assert.Equal(50, plainGrid.ColumnDefinitions[0].ActualWidth); + + wideContent.IsVisible = false; + ExecuteSharedSizeLayoutPass(); + Assert.Equal(10, cyclicGrid.ColumnDefinitions[0].ActualWidth); + Assert.Equal(10, plainGrid.ColumnDefinitions[0].ActualWidth); + } + [Fact] public void Collection_Changes_Are_Tracked() {