diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs
index b3769091c9..c912cbaa3b 100644
--- a/src/Avalonia.Controls/Primitives/PopupRoot.cs
+++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs
@@ -118,6 +118,33 @@ namespace Avalonia.Controls.Primitives
});
}
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ var measured = base.MeasureOverride(availableSize);
+ var width = measured.Width;
+ var height = measured.Height;
+ var widthCache = Width;
+ var heightCache = Height;
+
+ if (!double.IsNaN(widthCache))
+ {
+ width = widthCache;
+ }
+
+ width = Math.Min(width, MaxWidth);
+ width = Math.Max(width, MinWidth);
+
+ if (!double.IsNaN(heightCache))
+ {
+ height = heightCache;
+ }
+
+ height = Math.Min(height, MaxHeight);
+ height = Math.Max(height, MinHeight);
+
+ return new Size(width, height);
+ }
+
protected override sealed Size ArrangeSetBounds(Size size)
{
using (BeginAutoSizing())
diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs
index ca64ec5e50..18e60c37e8 100644
--- a/src/Avalonia.Controls/Window.cs
+++ b/src/Avalonia.Controls/Window.cs
@@ -525,8 +525,8 @@ namespace Avalonia.Controls
protected override Size MeasureOverride(Size availableSize)
{
var sizeToContent = SizeToContent;
+ var constraint = availableSize;
var clientSize = ClientSize;
- var constraint = clientSize;
if (sizeToContent.HasFlagCustom(SizeToContent.Width))
{
@@ -542,12 +542,26 @@ namespace Avalonia.Controls
if (!sizeToContent.HasFlagCustom(SizeToContent.Width))
{
- result = result.WithWidth(clientSize.Width);
+ if (!double.IsInfinity(availableSize.Width))
+ {
+ result = result.WithWidth(availableSize.Width);
+ }
+ else
+ {
+ result = result.WithWidth(clientSize.Width);
+ }
}
if (!sizeToContent.HasFlagCustom(SizeToContent.Height))
{
- result = result.WithHeight(clientSize.Height);
+ if (!double.IsInfinity(availableSize.Height))
+ {
+ result = result.WithHeight(availableSize.Height);
+ }
+ else
+ {
+ result = result.WithHeight(clientSize.Height);
+ }
}
return result;
@@ -577,6 +591,9 @@ namespace Avalonia.Controls
SizeToContent = SizeToContent.Manual;
}
+ Width = clientSize.Width;
+ Height = clientSize.Height;
+
base.HandleResized(clientSize);
}
diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs
index 281e7ce98b..58c24f0710 100644
--- a/src/Avalonia.Controls/WindowBase.cs
+++ b/src/Avalonia.Controls/WindowBase.cs
@@ -224,8 +224,6 @@ namespace Avalonia.Controls
/// The new client size.
protected override void HandleResized(Size clientSize)
{
- Width = clientSize.Width;
- Height = clientSize.Height;
ClientSize = clientSize;
LayoutManager.ExecuteLayoutPass();
Renderer?.Resized(clientSize);
@@ -245,17 +243,7 @@ namespace Avalonia.Controls
{
ApplyTemplate();
- var constraint = availableSize;
-
- if (!double.IsNaN(Width))
- {
- constraint = constraint.WithWidth(Width);
- }
-
- if (!double.IsNaN(Height))
- {
- constraint = constraint.WithHeight(Height);
- }
+ var constraint = LayoutHelper.ApplyLayoutConstraints(this, availableSize);
return MeasureOverride(constraint);
}
diff --git a/src/Avalonia.Layout/LayoutHelper.cs b/src/Avalonia.Layout/LayoutHelper.cs
index d77cc269f9..65a6d9047e 100644
--- a/src/Avalonia.Layout/LayoutHelper.cs
+++ b/src/Avalonia.Layout/LayoutHelper.cs
@@ -2,6 +2,8 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using Avalonia.Utilities;
+using Avalonia.VisualTree;
namespace Avalonia.Layout
{
@@ -21,13 +23,11 @@ namespace Avalonia.Layout
/// The control's size.
public static Size ApplyLayoutConstraints(ILayoutable control, Size constraints)
{
- double width = (control.Width > 0) ? control.Width : constraints.Width;
- double height = (control.Height > 0) ? control.Height : constraints.Height;
- width = Math.Min(width, control.MaxWidth);
- width = Math.Max(width, control.MinWidth);
- height = Math.Min(height, control.MaxHeight);
- height = Math.Max(height, control.MinHeight);
- return new Size(width, height);
+ var minmax = new MinMax(control);
+
+ return new Size(
+ MathUtilities.Clamp(constraints.Width, minmax.MinWidth, minmax.MaxWidth),
+ MathUtilities.Clamp(constraints.Height, minmax.MinHeight, minmax.MaxHeight));
}
public static Size MeasureChild(ILayoutable control, Size availableSize, Thickness padding,
@@ -58,5 +58,65 @@ namespace Avalonia.Layout
return availableSize;
}
+
+ ///
+ /// Invalidates measure for given control and all visual children recursively.
+ ///
+ public static void InvalidateSelfAndChildrenMeasure(ILayoutable control)
+ {
+ void InnerInvalidateMeasure(IVisual target)
+ {
+ if (target is ILayoutable targetLayoutable)
+ {
+ targetLayoutable.InvalidateMeasure();
+ }
+
+ var visualChildren = target.VisualChildren;
+ var visualChildrenCount = visualChildren.Count;
+
+ for (int i = 0; i < visualChildrenCount; i++)
+ {
+ IVisual child = visualChildren[i];
+
+ InnerInvalidateMeasure(child);
+ }
+ }
+
+ InnerInvalidateMeasure(control);
+ }
+
+ ///
+ /// Calculates the min and max height for a control. Ported from WPF.
+ ///
+ private readonly struct MinMax
+ {
+ public MinMax(ILayoutable e)
+ {
+ MaxHeight = e.MaxHeight;
+ MinHeight = e.MinHeight;
+ double l = e.Height;
+
+ double height = (double.IsNaN(l) ? double.PositiveInfinity : l);
+ MaxHeight = Math.Max(Math.Min(height, MaxHeight), MinHeight);
+
+ height = (double.IsNaN(l) ? 0 : l);
+ MinHeight = Math.Max(Math.Min(MaxHeight, height), MinHeight);
+
+ MaxWidth = e.MaxWidth;
+ MinWidth = e.MinWidth;
+ l = e.Width;
+
+ double width = (double.IsNaN(l) ? double.PositiveInfinity : l);
+ MaxWidth = Math.Max(Math.Min(width, MaxWidth), MinWidth);
+
+ width = (double.IsNaN(l) ? 0 : l);
+ MinWidth = Math.Max(Math.Min(MaxWidth, width), MinWidth);
+ }
+
+ public double MinWidth { get; }
+ public double MaxWidth { get; }
+ public double MinHeight { get; }
+ public double MaxHeight { get; }
+ }
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs
index 9221415a28..d5ea111ee5 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs
@@ -5,12 +5,14 @@ using System;
using System.Linq;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Controls.Templates;
using Avalonia.LogicalTree;
using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
+using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests.Primitives
@@ -210,6 +212,24 @@ namespace Avalonia.Controls.UnitTests.Primitives
}
}
+ [Fact]
+ public void Child_Should_Be_Measured_With_MaxWidth_MaxHeight_When_Set()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var child = new ChildControl();
+ var window = new Window();
+ var target = CreateTarget(window);
+
+ target.MaxWidth = 500;
+ target.MaxHeight = 600;
+ target.Content = child;
+ target.Show();
+
+ Assert.Equal(new Size(500, 600), child.MeasureSize);
+ }
+ }
+
[Fact]
public void Should_Not_Have_Offset_On_Bounds_When_Content_Larger_Than_Max_Window_Size()
{
@@ -219,12 +239,10 @@ namespace Avalonia.Controls.UnitTests.Primitives
var window = new Window();
var popupImpl = MockWindowingPlatform.CreatePopupMock(window.PlatformImpl);
- popupImpl.Setup(x => x.ClientSize).Returns(new Size(400, 480));
-
var child = new Canvas
{
Width = 400,
- Height = 800,
+ Height = 1344,
};
var target = CreateTarget(window, popupImpl.Object);
@@ -232,7 +250,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.Show();
- Assert.Equal(new Size(400, 480), target.Bounds.Size);
+ Assert.Equal(new Size(400, 1024), target.Bounds.Size);
// Issue #3784 causes this to be (0, 160) which makes no sense as Window has no
// parent control to be offset against.
@@ -240,6 +258,61 @@ namespace Avalonia.Controls.UnitTests.Primitives
}
}
+ [Fact]
+ public void MinWidth_MinHeight_Should_Be_Respected()
+ {
+ // Issue #3796
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var window = new Window();
+ var popupImpl = MockWindowingPlatform.CreatePopupMock(window.PlatformImpl);
+
+ var target = CreateTarget(window, popupImpl.Object);
+ target.MinWidth = 400;
+ target.MinHeight = 800;
+ target.Content = new Border
+ {
+ Width = 100,
+ Height = 100,
+ };
+
+ target.Show();
+
+ Assert.Equal(new Rect(0, 0, 400, 800), target.Bounds);
+ Assert.Equal(new Size(400, 800), target.ClientSize);
+ Assert.Equal(new Size(400, 800), target.PlatformImpl.ClientSize);
+ }
+ }
+
+ [Fact]
+ public void Setting_Width_Should_Resize_WindowImpl()
+ {
+ // Issue #3796
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var window = new Window();
+ var popupImpl = MockWindowingPlatform.CreatePopupMock(window.PlatformImpl);
+ var positioner = new Mock();
+ popupImpl.Setup(x => x.PopupPositioner).Returns(positioner.Object);
+
+ var target = CreateTarget(window, popupImpl.Object);
+ target.Width = 400;
+ target.Height = 800;
+
+ target.Show();
+
+ Assert.Equal(400, target.Width);
+ Assert.Equal(800, target.Height);
+
+ target.Width = 410;
+ target.LayoutManager.ExecuteLayoutPass();
+
+ positioner.Verify(x =>
+ x.Update(It.Is(x => x.Size.Width == 410)));
+ Assert.Equal(410, target.Width);
+ }
+ }
+
private PopupRoot CreateTarget(TopLevel popupParent, IPopupImpl impl = null)
{
impl ??= popupParent.PlatformImpl.CreatePopup();
diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
index 5c955dfcd9..6740984f73 100644
--- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
@@ -520,6 +520,32 @@ namespace Avalonia.Controls.UnitTests
}
}
+ [Fact]
+ public void Setting_Width_Should_Resize_WindowImpl()
+ {
+ // Issue #3796
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var target = new Window()
+ {
+ Width = 400,
+ Height = 800,
+ };
+
+ target.Show();
+
+ Assert.Equal(400, target.Width);
+ Assert.Equal(800, target.Height);
+
+ target.Width = 410;
+ target.LayoutManager.ExecuteLayoutPass();
+
+ var windowImpl = Mock.Get(target.PlatformImpl);
+ windowImpl.Verify(x => x.Resize(new Size(410, 800)));
+ Assert.Equal(410, target.Width);
+ }
+ }
+
private IWindowImpl CreateImpl(Mock renderer)
{
return Mock.Of(x =>
diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
index b8b7512c9e..b3e4b4edbc 100644
--- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
+++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs
@@ -66,15 +66,19 @@ namespace Avalonia.UnitTests
public static Mock CreatePopupMock(IWindowBaseImpl parent)
{
var popupImpl = new Mock();
+ var clientSize = new Size();
var positionerHelper = new ManagedPopupPositionerPopupImplHelper(parent, (pos, size, scale) =>
{
+ clientSize = size.Constrain(s_screenSize);
popupImpl.Object.PositionChanged?.Invoke(pos);
- popupImpl.Object.Resized?.Invoke(size);
+ popupImpl.Object.Resized?.Invoke(clientSize);
});
var positioner = new ManagedPopupPositioner(positionerHelper);
+ popupImpl.SetupAllProperties();
+ popupImpl.Setup(x => x.ClientSize).Returns(() => clientSize);
popupImpl.Setup(x => x.Scaling).Returns(1);
popupImpl.Setup(x => x.PopupPositioner).Returns(positioner);