Browse Source

Merge pull request #3797 from AvaloniaUI/fixes/3796-set-window-size

More window/popup sizing fixes.
pull/3800/head
danwalmsley 6 years ago
committed by GitHub
parent
commit
8e9a5046bf
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 27
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  2. 23
      src/Avalonia.Controls/Window.cs
  3. 14
      src/Avalonia.Controls/WindowBase.cs
  4. 50
      src/Avalonia.Layout/LayoutHelper.cs
  5. 81
      tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs
  6. 26
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  7. 6
      tests/Avalonia.UnitTests/MockWindowingPlatform.cs

27
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())

23
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);
}

14
src/Avalonia.Controls/WindowBase.cs

@ -224,8 +224,6 @@ namespace Avalonia.Controls
/// <param name="clientSize">The new client size.</param>
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);
}

50
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
/// <returns>The control's size.</returns>
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);
}
/// <summary>
/// Calculates the min and max height for a control. Ported from WPF.
/// </summary>
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; }
}
}
}

81
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<IPopupPositioner>();
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<PopupPositionerParameters>(x => x.Size.Width == 410)));
Assert.Equal(410, target.Width);
}
}
private PopupRoot CreateTarget(TopLevel popupParent, IPopupImpl impl = null)
{
impl ??= popupParent.PlatformImpl.CreatePopup();

26
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<IRenderer> renderer)
{
return Mock.Of<IWindowImpl>(x =>

6
tests/Avalonia.UnitTests/MockWindowingPlatform.cs

@ -66,15 +66,19 @@ namespace Avalonia.UnitTests
public static Mock<IPopupImpl> CreatePopupMock(IWindowBaseImpl parent)
{
var popupImpl = new Mock<IPopupImpl>();
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);

Loading…
Cancel
Save