Pārlūkot izejas kodu

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

More window/popup sizing fixes.
danwalmsley 5 gadi atpakaļ
vecāks
revīzija
8e9a5046bf

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

+ 20 - 3
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);
         }
 

+ 1 - 13
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);
         }

+ 40 - 10
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; }
+        }
     }
 }

+ 77 - 4
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 - 0
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 =>

+ 5 - 1
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);