diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs
index fa6f5787be..ff6cd482df 100644
--- a/src/Avalonia.Controls/Image.cs
+++ b/src/Avalonia.Controls/Image.cs
@@ -96,7 +96,7 @@ namespace Avalonia.Controls
}
}
- return result.Constrain(availableSize);
+ return result;
}
///
diff --git a/src/Avalonia.Controls/StackPanel.cs b/src/Avalonia.Controls/StackPanel.cs
index 8e3b4905cf..bd3441078d 100644
--- a/src/Avalonia.Controls/StackPanel.cs
+++ b/src/Avalonia.Controls/StackPanel.cs
@@ -1,8 +1,9 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
+// This source file is adapted from the Windows Presentation Foundation project.
+// (https://github.com/dotnet/wpf/)
+//
+// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
-using System.Linq;
using Avalonia.Input;
using Avalonia.Layout;
@@ -155,106 +156,122 @@ namespace Avalonia.Controls
}
///
- /// Measures the control.
+ /// General StackPanel layout behavior is to grow unbounded in the "stacking" direction (Size To Content).
+ /// Children in this dimension are encouraged to be as large as they like. In the other dimension,
+ /// StackPanel will assume the maximum size of its children.
///
- /// The available size.
- /// The desired size of the control.
+ /// Constraint
+ /// Desired size
protected override Size MeasureOverride(Size availableSize)
{
- double childAvailableWidth = double.PositiveInfinity;
- double childAvailableHeight = double.PositiveInfinity;
+ Size stackDesiredSize = new Size();
+ var children = Children;
+ Size layoutSlotSize = availableSize;
+ bool fHorizontal = (Orientation == Orientation.Horizontal);
+ double spacing = Spacing;
+ bool hasVisibleChild = false;
- if (Orientation == Orientation.Vertical)
+ //
+ // Initialize child sizing and iterator data
+ // Allow children as much size as they want along the stack.
+ //
+ if (fHorizontal)
{
- childAvailableWidth = availableSize.Width;
-
- if (!double.IsNaN(Width))
- {
- childAvailableWidth = Width;
- }
-
- childAvailableWidth = Math.Min(childAvailableWidth, MaxWidth);
- childAvailableWidth = Math.Max(childAvailableWidth, MinWidth);
+ layoutSlotSize = layoutSlotSize.WithWidth(Double.PositiveInfinity);
}
else
{
- childAvailableHeight = availableSize.Height;
+ layoutSlotSize = layoutSlotSize.WithHeight(Double.PositiveInfinity);
+ }
- if (!double.IsNaN(Height))
- {
- childAvailableHeight = Height;
- }
+ //
+ // Iterate through children.
+ // While we still supported virtualization, this was hidden in a child iterator (see source history).
+ //
+ for (int i = 0, count = children.Count; i < count; ++i)
+ {
+ // Get next child.
+ var child = children[i];
- childAvailableHeight = Math.Min(childAvailableHeight, MaxHeight);
- childAvailableHeight = Math.Max(childAvailableHeight, MinHeight);
- }
+ if (child == null)
+ { continue; }
- double measuredWidth = 0;
- double measuredHeight = 0;
- double spacing = Spacing;
- bool hasVisibleChild = Children.Any(c => c.IsVisible);
+ bool isVisible = child.IsVisible;
- foreach (Control child in Children)
- {
- child.Measure(new Size(childAvailableWidth, childAvailableHeight));
- Size size = child.DesiredSize;
+ if (isVisible && !hasVisibleChild)
+ {
+ hasVisibleChild = true;
+ }
- if (Orientation == Orientation.Vertical)
+ // Measure the child.
+ child.Measure(layoutSlotSize);
+ Size childDesiredSize = child.DesiredSize;
+
+ // Accumulate child size.
+ if (fHorizontal)
{
- measuredHeight += size.Height + (child.IsVisible ? spacing : 0);
- measuredWidth = Math.Max(measuredWidth, size.Width);
+ stackDesiredSize = stackDesiredSize.WithWidth(stackDesiredSize.Width + (isVisible ? spacing : 0) + childDesiredSize.Width);
+ stackDesiredSize = stackDesiredSize.WithHeight(Math.Max(stackDesiredSize.Height, childDesiredSize.Height));
}
else
{
- measuredWidth += size.Width + (child.IsVisible ? spacing : 0);
- measuredHeight = Math.Max(measuredHeight, size.Height);
+ stackDesiredSize = stackDesiredSize.WithWidth(Math.Max(stackDesiredSize.Width, childDesiredSize.Width));
+ stackDesiredSize = stackDesiredSize.WithHeight(stackDesiredSize.Height + (isVisible ? spacing : 0) + childDesiredSize.Height);
}
}
- if (Orientation == Orientation.Vertical)
+ if (fHorizontal)
{
- measuredHeight -= (hasVisibleChild ? spacing : 0);
+ stackDesiredSize = stackDesiredSize.WithWidth(stackDesiredSize.Width - (hasVisibleChild ? spacing : 0));
}
else
- {
- measuredWidth -= (hasVisibleChild ? spacing : 0);
+ {
+ stackDesiredSize = stackDesiredSize.WithHeight(stackDesiredSize.Height - (hasVisibleChild ? spacing : 0));
}
- return new Size(measuredWidth, measuredHeight).Constrain(availableSize);
+ return stackDesiredSize;
}
- ///
+ ///
+ /// Content arrangement.
+ ///
+ /// Arrange size
protected override Size ArrangeOverride(Size finalSize)
{
- var orientation = Orientation;
+ var children = Children;
+ bool fHorizontal = (Orientation == Orientation.Horizontal);
+ Rect rcChild = new Rect(finalSize);
+ double previousChildSize = 0.0;
var spacing = Spacing;
- var finalRect = new Rect(finalSize);
- var pos = 0.0;
- foreach (Control child in Children)
+ //
+ // Arrange and Position Children.
+ //
+ for (int i = 0, count = children.Count; i < count; ++i)
{
- if (!child.IsVisible)
- {
- continue;
- }
+ var child = children[i];
- double childWidth = child.DesiredSize.Width;
- double childHeight = child.DesiredSize.Height;
+ if (child == null)
+ { continue; }
- if (orientation == Orientation.Vertical)
+ if (fHorizontal)
{
- var rect = new Rect(0, pos, childWidth, childHeight)
- .Align(finalRect, child.HorizontalAlignment, VerticalAlignment.Top);
- ArrangeChild(child, rect, finalSize, orientation);
- pos += childHeight + spacing;
+ rcChild = rcChild.WithX(rcChild.X + previousChildSize);
+ previousChildSize = child.DesiredSize.Width;
+ rcChild = rcChild.WithWidth(previousChildSize);
+ rcChild = rcChild.WithHeight(Math.Max(finalSize.Height, child.DesiredSize.Height));
+ previousChildSize += spacing;
}
else
{
- var rect = new Rect(pos, 0, childWidth, childHeight)
- .Align(finalRect, HorizontalAlignment.Left, child.VerticalAlignment);
- ArrangeChild(child, rect, finalSize, orientation);
- pos += childWidth + spacing;
+ rcChild = rcChild.WithY(rcChild.Y + previousChildSize);
+ previousChildSize = child.DesiredSize.Height;
+ rcChild = rcChild.WithHeight(previousChildSize);
+ rcChild = rcChild.WithWidth(Math.Max(finalSize.Width, child.DesiredSize.Width));
+ previousChildSize += spacing;
}
+
+ ArrangeChild(child, rcChild, finalSize, Orientation);
}
return finalSize;
diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs
index 662f48ec44..bd248d6d44 100644
--- a/src/Avalonia.Layout/Layoutable.cs
+++ b/src/Avalonia.Layout/Layoutable.cs
@@ -534,6 +534,9 @@ namespace Avalonia.Layout
height = Math.Min(height, MaxHeight);
height = Math.Max(height, MinHeight);
+ width = Math.Min(width, availableSize.Width);
+ height = Math.Min(height, availableSize.Height);
+
if (UseLayoutRounding)
{
var scale = GetLayoutScale();
diff --git a/tests/Avalonia.Controls.UnitTests/GridTests.cs b/tests/Avalonia.Controls.UnitTests/GridTests.cs
index df804d5d8c..2b9197e20b 100644
--- a/tests/Avalonia.Controls.UnitTests/GridTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/GridTests.cs
@@ -1357,5 +1357,36 @@ namespace Avalonia.Controls.UnitTests
PrintColumnDefinitions(grid);
Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == null), cd => Assert.Equal(50, cd.ActualWidth));
}
+
+ [Fact]
+ public void Correct_Grid_Bounds_When_Child_Control_Has_DesiredSize_Larger_Than_Available_Space()
+ {
+ // Issue #2746
+ var grid = new Grid
+ {
+ RowDefinitions = RowDefinitions.Parse("Auto"),
+ Children =
+ {
+ new TestControl
+ {
+ MeasureSize = new Size(150, 150),
+ }
+ }
+ };
+
+ var parent = new Decorator { Child = grid };
+
+ parent.Measure(new Size(100, 100));
+ parent.Arrange(new Rect(grid.DesiredSize));
+
+ Assert.Equal(new Size(100, 100), grid.Bounds.Size);
+ }
+
+ private class TestControl : Control
+ {
+ public Size MeasureSize { get; set; }
+
+ protected override Size MeasureOverride(Size availableSize) => MeasureSize;
+ }
}
-}
\ No newline at end of file
+}
diff --git a/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs b/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs
index 722ad1c8ee..db113f0569 100644
--- a/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs
@@ -210,13 +210,13 @@ namespace Avalonia.Controls.UnitTests
new[]
{
new Rect(0, 0, 50, 10),
- new Rect(0, 10, 150, 10),
+ new Rect(0, 10, 100, 10),
new Rect(25, 20, 50, 10),
- new Rect(-25, 30, 150, 10),
+ new Rect(0, 30, 100, 10),
new Rect(50, 40, 50, 10),
- new Rect(-50, 50, 150, 10),
+ new Rect(0, 50, 100, 10),
new Rect(0, 60, 100, 10),
- new Rect(0, 70, 150, 10),
+ new Rect(0, 70, 100, 10),
}, bounds);
}
@@ -283,13 +283,13 @@ namespace Avalonia.Controls.UnitTests
new[]
{
new Rect(0, 0, 10, 50),
- new Rect(10, 0, 10, 150),
+ new Rect(10, 0, 10, 100),
new Rect(20, 25, 10, 50),
- new Rect(30, -25, 10, 150),
+ new Rect(30, 0, 10, 100),
new Rect(40, 50, 10, 50),
- new Rect(50, -50, 10, 150),
+ new Rect(50, 0, 10, 100),
new Rect(60, 0, 10, 100),
- new Rect(70, 0, 10, 150),
+ new Rect(70, 0, 10, 100),
}, bounds);
}