diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs
index 4546a1aadb..788fe03162 100644
--- a/src/Avalonia.Controls/Primitives/PopupRoot.cs
+++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs
@@ -117,6 +117,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 dcf4e98528..b21f18a59d 100644
--- a/src/Avalonia.Controls/Window.cs
+++ b/src/Avalonia.Controls/Window.cs
@@ -570,8 +570,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))
{
@@ -587,12 +587,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;
@@ -622,6 +636,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 025dfde610..bb63d1b353 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);
@@ -246,17 +244,7 @@ namespace Avalonia.Controls
ApplyStyling();
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 af7d8ee52e..2b61de00a7 100644
--- a/src/Avalonia.Layout/LayoutHelper.cs
+++ b/src/Avalonia.Layout/LayoutHelper.cs
@@ -1,4 +1,5 @@
using System;
+using Avalonia.Utilities;
using Avalonia.VisualTree;
namespace Avalonia.Layout
@@ -19,16 +20,11 @@ namespace Avalonia.Layout
/// The control's size.
public static Size ApplyLayoutConstraints(ILayoutable control, Size constraints)
{
- var controlWidth = control.Width;
- var controlHeight = control.Height;
-
- double width = (controlWidth > 0) ? controlWidth : constraints.Width;
- double height = (controlHeight > 0) ? controlHeight : 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,
@@ -85,5 +81,39 @@ namespace Avalonia.Layout
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 b03f8b8892..a61d313c8c 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs
@@ -2,12 +2,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
@@ -207,6 +209,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()
{
@@ -216,12 +236,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);
@@ -229,7 +247,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.
@@ -237,6 +255,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 5382e6ea3e..1ac4d7a236 100644
--- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
@@ -514,6 +514,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);