Browse Source

Merge pull request #3787 from AvaloniaUI/fixes/3784-large-window-content

Fix Window/PopupRoot arrange.
# Conflicts:
#	src/Avalonia.Controls/Window.cs
#	tests/Avalonia.Controls.UnitTests/WindowTests.cs
#	tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
#	tests/Avalonia.UnitTests/MockWindowingPlatform.cs
release/0.9.8
danwalmsley 7 years ago
committed by Dan Walmsley
parent
commit
114f77e1c0
  1. 10
      Avalonia.sln
  2. 20
      src/Avalonia.Base/EnumExtensions.cs
  3. 12
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  4. 48
      src/Avalonia.Controls/Window.cs
  5. 59
      src/Avalonia.Controls/WindowBase.cs
  6. 5
      src/Avalonia.X11/X11ImmediateRendererProxy.cs
  7. 7
      tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
  8. 82
      tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs
  9. 4
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
  10. 179
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  11. 108
      tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
  12. 111
      tests/Avalonia.UnitTests/MockWindowingPlatform.cs

10
Avalonia.sln

@ -201,18 +201,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Avalonia.Dialogs\Avalonia.Dialogs.csproj", "{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Avalonia.Dialogs\Avalonia.Dialogs.csproj", "{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
EndProject EndProject
Global Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 5
tests\Avalonia.RenderTests\Avalonia.RenderTests.projitems*{dabfd304-d6a4-4752-8123-c2ccf7ac7831}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13 src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13
EndGlobalSection EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution

20
src/Avalonia.Base/EnumExtensions.cs

@ -0,0 +1,20 @@
using System;
using System.Runtime.CompilerServices;
namespace Avalonia
{
/// <summary>
/// Provides extension methods for enums.
/// </summary>
public static class EnumExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool HasFlagCustom<T>(this T value, T flag) where T : unmanaged, Enum
{
var intValue = *(int*)&value;
var intFlag = *(int*)&flag;
return (intValue & intFlag) == intFlag;
}
}
}

12
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -118,20 +118,14 @@ namespace Avalonia.Controls.Primitives
}); });
} }
/// <summary> protected override sealed Size ArrangeSetBounds(Size size)
/// Carries out the arrange pass of the window.
/// </summary>
/// <param name="finalSize">The final window size.</param>
/// <returns>The <paramref name="finalSize"/> parameter unchanged.</returns>
protected override Size ArrangeOverride(Size finalSize)
{ {
using (BeginAutoSizing()) using (BeginAutoSizing())
{ {
_positionerParameters.Size = finalSize; _positionerParameters.Size = size;
UpdatePosition(); UpdatePosition();
return ClientSize;
} }
return base.ArrangeOverride(PlatformImpl?.ClientSize ?? default(Size));
} }
} }
} }

48
src/Avalonia.Controls/Window.cs

@ -268,22 +268,7 @@ namespace Avalonia.Controls
/// Should be called from left mouse button press event handler /// Should be called from left mouse button press event handler
/// </summary> /// </summary>
public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e) => PlatformImpl?.BeginResizeDrag(edge, e); public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e) => PlatformImpl?.BeginResizeDrag(edge, e);
/// <summary>
/// Carries out the arrange pass of the window.
/// </summary>
/// <param name="finalSize">The final window size.</param>
/// <returns>The <paramref name="finalSize"/> parameter unchanged.</returns>
protected override Size ArrangeOverride(Size finalSize)
{
using (BeginAutoSizing())
{
PlatformImpl?.Resize(finalSize);
}
return base.ArrangeOverride(PlatformImpl?.ClientSize ?? default(Size));
}
/// <inheritdoc/> /// <inheritdoc/>
Size ILayoutRoot.MaxClientSize => _maxPlatformClientSize; Size ILayoutRoot.MaxClientSize => _maxPlatformClientSize;
@ -405,6 +390,19 @@ namespace Avalonia.Controls
EnsureInitialized(); EnsureInitialized();
IsVisible = true; IsVisible = true;
var initialSize = new Size(
double.IsNaN(Width) ? ClientSize.Width : Width,
double.IsNaN(Height) ? ClientSize.Height : Height);
if (initialSize != ClientSize)
{
using (BeginAutoSizing())
{
PlatformImpl?.Resize(initialSize);
}
}
LayoutManager.ExecuteInitialLayoutPass(this); LayoutManager.ExecuteInitialLayoutPass(this);
using (BeginAutoSizing()) using (BeginAutoSizing())
@ -524,31 +522,30 @@ namespace Avalonia.Controls
} }
} }
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
var sizeToContent = SizeToContent; var sizeToContent = SizeToContent;
var clientSize = ClientSize; var clientSize = ClientSize;
Size constraint = clientSize; var constraint = clientSize;
if ((sizeToContent & SizeToContent.Width) != 0) if (sizeToContent.HasFlagCustom(SizeToContent.Width))
{ {
constraint = constraint.WithWidth(double.PositiveInfinity); constraint = constraint.WithWidth(double.PositiveInfinity);
} }
if ((sizeToContent & SizeToContent.Height) != 0) if (sizeToContent.HasFlagCustom(SizeToContent.Height))
{ {
constraint = constraint.WithHeight(double.PositiveInfinity); constraint = constraint.WithHeight(double.PositiveInfinity);
} }
var result = base.MeasureOverride(constraint); var result = base.MeasureOverride(constraint);
if ((sizeToContent & SizeToContent.Width) == 0) if (!sizeToContent.HasFlagCustom(SizeToContent.Width))
{ {
result = result.WithWidth(clientSize.Width); result = result.WithWidth(clientSize.Width);
} }
if ((sizeToContent & SizeToContent.Height) == 0) if (!sizeToContent.HasFlagCustom(SizeToContent.Height))
{ {
result = result.WithHeight(clientSize.Height); result = result.WithHeight(clientSize.Height);
} }
@ -556,6 +553,15 @@ namespace Avalonia.Controls
return result; return result;
} }
protected sealed override Size ArrangeSetBounds(Size size)
{
using (BeginAutoSizing())
{
PlatformImpl?.Resize(size);
return ClientSize;
}
}
protected sealed override void HandleClosed() protected sealed override void HandleClosed()
{ {
RaiseEvent(new RoutedEventArgs(WindowClosedEvent)); RaiseEvent(new RoutedEventArgs(WindowClosedEvent));

59
src/Avalonia.Controls/WindowBase.cs

@ -224,16 +224,65 @@ namespace Avalonia.Controls
/// <param name="clientSize">The new client size.</param> /// <param name="clientSize">The new client size.</param>
protected override void HandleResized(Size clientSize) protected override void HandleResized(Size clientSize)
{ {
if (!AutoSizing) Width = clientSize.Width;
{ Height = clientSize.Height;
Width = clientSize.Width;
Height = clientSize.Height;
}
ClientSize = clientSize; ClientSize = clientSize;
LayoutManager.ExecuteLayoutPass(); LayoutManager.ExecuteLayoutPass();
Renderer?.Resized(clientSize); Renderer?.Resized(clientSize);
} }
/// <summary>
/// Overrides the core measure logic for windows.
/// </summary>
/// <param name="availableSize">The available size.</param>
/// <returns>The measured size.</returns>
/// <remarks>
/// The layout logic for top-level windows is different than for other controls because
/// they don't have a parent, meaning that many layout properties handled by the default
/// MeasureCore (such as margins and alignment) make no sense.
/// </remarks>
protected override Size MeasureCore(Size availableSize)
{
ApplyTemplate();
var constraint = availableSize;
if (!double.IsNaN(Width))
{
constraint = constraint.WithWidth(Width);
}
if (!double.IsNaN(Height))
{
constraint = constraint.WithHeight(Height);
}
return MeasureOverride(constraint);
}
/// <summary>
/// Overrides the core arrange logic for windows.
/// </summary>
/// <param name="finalRect">The final arrange rect.</param>
/// <remarks>
/// The layout logic for top-level windows is different than for other controls because
/// they don't have a parent, meaning that many layout properties handled by the default
/// ArrangeCore (such as margins and alignment) make no sense.
/// </remarks>
protected override void ArrangeCore(Rect finalRect)
{
var constraint = ArrangeSetBounds(finalRect.Size);
var arrangeSize = ArrangeOverride(constraint);
Bounds = new Rect(arrangeSize);
}
/// <summary>
/// Called durung the arrange pass to set the size of the window.
/// </summary>
/// <param name="size">The requested size of the window.</param>
/// <returns>The actual size of the window.</returns>
protected virtual Size ArrangeSetBounds(Size size) => size;
/// <summary> /// <summary>
/// Handles a window position change notification from /// Handles a window position change notification from
/// <see cref="IWindowBaseImpl.PositionChanged"/>. /// <see cref="IWindowBaseImpl.PositionChanged"/>.

5
src/Avalonia.X11/X11ImmediateRendererProxy.cs

@ -55,11 +55,6 @@ namespace Avalonia.X11
return _renderer.HitTest(p, root, filter); return _renderer.HitTest(p, root, filter);
} }
public IVisual HitTestFirst(Point p, IVisual root, Func<IVisual, bool> filter)
{
return _renderer.HitTestFirst(p, root, filter);
}
public void RecalculateChildren(IVisual visual) public void RecalculateChildren(IVisual visual)
{ {
_renderer.RecalculateChildren(visual); _renderer.RecalculateChildren(visual);

7
tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs

@ -200,16 +200,17 @@ namespace Avalonia.Controls.UnitTests
screenImpl.Setup(x => x.ScreenCount).Returns(1); screenImpl.Setup(x => x.ScreenCount).Returns(1);
screenImpl.Setup(X => X.AllScreens).Returns( new[] { new Screen(1, screen, screen, true) }); screenImpl.Setup(X => X.AllScreens).Returns( new[] { new Screen(1, screen, screen, true) });
popupImpl = MockWindowingPlatform.CreatePopupMock(); var windowImpl = MockWindowingPlatform.CreateWindowMock();
popupImpl = MockWindowingPlatform.CreatePopupMock(windowImpl.Object);
popupImpl.SetupGet(x => x.Scaling).Returns(1); popupImpl.SetupGet(x => x.Scaling).Returns(1);
windowImpl.Setup(x => x.CreatePopup()).Returns(popupImpl.Object);
var windowImpl = MockWindowingPlatform.CreateWindowMock(() => popupImpl.Object);
windowImpl.Setup(x => x.Screen).Returns(screenImpl.Object); windowImpl.Setup(x => x.Screen).Returns(screenImpl.Object);
var services = TestServices.StyledWindow.With( var services = TestServices.StyledWindow.With(
inputManager: new InputManager(), inputManager: new InputManager(),
windowImpl: windowImpl.Object, windowImpl: windowImpl.Object,
windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object, () => popupImpl.Object)); windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object, x => popupImpl.Object));
return UnitTestApplication.Start(services); return UnitTestApplication.Start(services);
} }

82
tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs

@ -7,6 +7,7 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Platform;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -175,9 +176,75 @@ namespace Avalonia.Controls.UnitTests.Primitives
} }
} }
private PopupRoot CreateTarget(TopLevel popupParent) [Fact]
public void Child_Should_Be_Measured_With_Infinity()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var child = new ChildControl();
var window = new Window();
var target = CreateTarget(window);
target.Content = child;
target.Show();
Assert.Equal(Size.Infinity, child.MeasureSize);
}
}
[Fact]
public void Child_Should_Be_Measured_With_Width_Height_When_Set()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var child = new ChildControl();
var window = new Window();
var target = CreateTarget(window);
target.Width = 500;
target.Height = 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()
{
// Issue #3784.
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
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,
};
var target = CreateTarget(window, popupImpl.Object);
target.Content = child;
target.Show();
Assert.Equal(new Size(400, 480), 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.
Assert.Equal(new Point(0, 0), target.Bounds.Position);
}
}
private PopupRoot CreateTarget(TopLevel popupParent, IPopupImpl impl = null)
{ {
var result = new PopupRoot(popupParent, popupParent.PlatformImpl.CreatePopup()) impl ??= popupParent.PlatformImpl.CreatePopup();
var result = new PopupRoot(popupParent, impl)
{ {
Template = new FuncControlTemplate<PopupRoot>((parent, scope) => Template = new FuncControlTemplate<PopupRoot>((parent, scope) =>
new ContentPresenter new ContentPresenter
@ -220,5 +287,16 @@ namespace Avalonia.Controls.UnitTests.Primitives
Popup = (Popup)this.GetVisualChildren().Single(); Popup = (Popup)this.GetVisualChildren().Single();
} }
} }
private class ChildControl : Control
{
public Size MeasureSize { get; private set; }
protected override Size MeasureOverride(Size availableSize)
{
MeasureSize = availableSize;
return base.MeasureOverride(availableSize);
}
}
} }
} }

4
tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

@ -332,11 +332,11 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform: return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform:
new MockWindowingPlatform(null, new MockWindowingPlatform(null,
() => x =>
{ {
if(UsePopupHost) if(UsePopupHost)
return null; return null;
return MockWindowingPlatform.CreatePopupMock().Object; return MockWindowingPlatform.CreatePopupMock(x).Object;
}))); })));
} }

179
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@ -7,6 +7,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Layout;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.UnitTests; using Avalonia.UnitTests;
@ -341,6 +342,184 @@ namespace Avalonia.Controls.UnitTests
} }
} }
[Fact]
public void Child_Should_Be_Measured_With_Width_And_Height_If_SizeToContent_Is_Manual()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var child = new ChildControl();
var target = new Window
{
Width = 100,
Height = 50,
SizeToContent = SizeToContent.Manual,
Content = child
};
target.Show();
Assert.Equal(new Size(100, 50), child.MeasureSize);
}
}
[Fact]
public void Child_Should_Be_Measured_With_ClientSize_If_SizeToContent_Is_Manual_And_No_Width_Height_Specified()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var windowImpl = MockWindowingPlatform.CreateWindowMock();
windowImpl.Setup(x => x.ClientSize).Returns(new Size(550, 450));
var child = new ChildControl();
var target = new Window(windowImpl.Object)
{
SizeToContent = SizeToContent.Manual,
Content = child
};
target.Show();
Assert.Equal(new Size(550, 450), child.MeasureSize);
}
}
[Fact]
public void Child_Should_Be_Measured_With_Infinity_If_SizeToContent_Is_WidthAndHeight()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var child = new ChildControl();
var target = new Window
{
Width = 100,
Height = 50,
SizeToContent = SizeToContent.WidthAndHeight,
Content = child
};
target.Show();
Assert.Equal(Size.Infinity, child.MeasureSize);
}
}
[Fact]
public void Should_Not_Have_Offset_On_Bounds_When_Content_Larger_Than_Max_Window_Size()
{
// Issue #3784.
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var windowImpl = MockWindowingPlatform.CreateWindowMock();
var clientSize = new Size(200, 200);
var maxClientSize = new Size(480, 480);
windowImpl.Setup(x => x.Resize(It.IsAny<Size>())).Callback<Size>(size =>
{
clientSize = size.Constrain(maxClientSize);
windowImpl.Object.Resized?.Invoke(clientSize);
});
windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize);
var child = new Canvas
{
Width = 400,
Height = 800,
};
var target = new Window(windowImpl.Object)
{
SizeToContent = SizeToContent.WidthAndHeight,
Content = child
};
target.Show();
Assert.Equal(new Size(400, 480), 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.
Assert.Equal(new Point(0, 0), target.Bounds.Position);
}
}
[Fact]
public void Width_Height_Should_Not_Be_NaN_After_Show_With_SizeToContent_WidthAndHeight()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var child = new Canvas
{
Width = 400,
Height = 800,
};
var target = new Window()
{
SizeToContent = SizeToContent.WidthAndHeight,
Content = child
};
target.Show();
Assert.Equal(400, target.Width);
Assert.Equal(800, target.Height);
}
}
[Fact]
public void SizeToContent_Should_Not_Be_Lost_On_Show()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var child = new Canvas
{
Width = 400,
Height = 800,
};
var target = new Window()
{
SizeToContent = SizeToContent.WidthAndHeight,
Content = child
};
target.Show();
Assert.Equal(SizeToContent.WidthAndHeight, target.SizeToContent);
}
}
[Fact]
public void Width_Height_Should_Be_Updated_When_SizeToContent_Is_WidthAndHeight()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var child = new Canvas
{
Width = 400,
Height = 800,
};
var target = new Window()
{
SizeToContent = SizeToContent.WidthAndHeight,
Content = child
};
target.Show();
Assert.Equal(400, target.Width);
Assert.Equal(800, target.Height);
child.Width = 410;
target.LayoutManager.ExecuteLayoutPass();
Assert.Equal(410, target.Width);
Assert.Equal(800, target.Height);
Assert.Equal(SizeToContent.WidthAndHeight, target.SizeToContent);
}
}
private IWindowImpl CreateImpl(Mock<IRenderer> renderer) private IWindowImpl CreateImpl(Mock<IRenderer> renderer)
{ {
return Mock.Of<IWindowImpl>(x => return Mock.Of<IWindowImpl>(x =>

108
tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs

@ -1,28 +1,12 @@
// 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.
using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using Moq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Diagnostics;
using Avalonia.Input;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Shared.PlatformSupport;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Themes.Default; using Avalonia.UnitTests;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Xunit; using Xunit;
using Avalonia.Media;
using System;
using System.Collections.Generic;
using Avalonia.Controls.UnitTests;
using Avalonia.UnitTests;
namespace Avalonia.Layout.UnitTests namespace Avalonia.Layout.UnitTests
{ {
@ -31,10 +15,8 @@ namespace Avalonia.Layout.UnitTests
[Fact] [Fact]
public void Grandchild_Size_Changed() public void Grandchild_Size_Changed()
{ {
using (var context = AvaloniaLocator.EnterScope()) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
RegisterServices();
Border border; Border border;
TextBlock textBlock; TextBlock textBlock;
@ -58,7 +40,6 @@ namespace Avalonia.Layout.UnitTests
}; };
window.Show(); window.Show();
window.LayoutManager.ExecuteInitialLayoutPass(window);
Assert.Equal(new Size(400, 400), border.Bounds.Size); Assert.Equal(new Size(400, 400), border.Bounds.Size);
textBlock.Width = 200; textBlock.Width = 200;
@ -71,10 +52,8 @@ namespace Avalonia.Layout.UnitTests
[Fact] [Fact]
public void Test_ScrollViewer_With_TextBlock() public void Test_ScrollViewer_With_TextBlock()
{ {
using (var context = AvaloniaLocator.EnterScope()) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
RegisterServices();
ScrollViewer scrollViewer; ScrollViewer scrollViewer;
TextBlock textBlock; TextBlock textBlock;
@ -82,7 +61,6 @@ namespace Avalonia.Layout.UnitTests
{ {
Width = 800, Width = 800,
Height = 600, Height = 600,
SizeToContent = SizeToContent.WidthAndHeight,
Content = scrollViewer = new ScrollViewer Content = scrollViewer = new ScrollViewer
{ {
Width = 200, Width = 200,
@ -102,7 +80,6 @@ namespace Avalonia.Layout.UnitTests
window.Resources["ScrollBarThickness"] = 10.0; window.Resources["ScrollBarThickness"] = 10.0;
window.Show(); window.Show();
window.LayoutManager.ExecuteInitialLayoutPass(window);
Assert.Equal(new Size(800, 600), window.Bounds.Size); Assert.Equal(new Size(800, 600), window.Bounds.Size);
Assert.Equal(new Size(200, 200), scrollViewer.Bounds.Size); Assert.Equal(new Size(200, 200), scrollViewer.Bounds.Size);
@ -134,84 +111,5 @@ namespace Avalonia.Layout.UnitTests
{ {
return v.Bounds.Position; return v.Bounds.Position;
} }
class FormattedTextMock : IFormattedTextImpl
{
public FormattedTextMock(string text)
{
Text = text;
}
public Size Constraint { get; set; }
public string Text { get; }
public Rect Bounds => Rect.Empty;
public void Dispose()
{
}
public IEnumerable<FormattedTextLine> GetLines() => new FormattedTextLine[0];
public TextHitTestResult HitTestPoint(Point point) => new TextHitTestResult();
public Rect HitTestTextPosition(int index) => new Rect();
public IEnumerable<Rect> HitTestTextRange(int index, int length) => new Rect[0];
public Size Measure() => Constraint;
}
private void RegisterServices()
{
var globalStyles = new Mock<IGlobalStyles>();
var globalStylesResources = globalStyles.As<IResourceNode>();
var outObj = (object)10;
globalStylesResources.Setup(x => x.TryGetResource("FontSizeNormal", out outObj)).Returns(true);
var renderInterface = new Mock<IPlatformRenderInterface>();
renderInterface.Setup(x =>
x.CreateFormattedText(
It.IsAny<string>(),
It.IsAny<Typeface>(),
It.IsAny<TextAlignment>(),
It.IsAny<TextWrapping>(),
It.IsAny<Size>(),
It.IsAny<IReadOnlyList<FormattedTextStyleSpan>>()))
.Returns(new FormattedTextMock("TEST"));
var streamGeometry = new Mock<IStreamGeometryImpl>();
streamGeometry.Setup(x =>
x.Open())
.Returns(new Mock<IStreamGeometryContextImpl>().Object);
renderInterface.Setup(x =>
x.CreateStreamGeometry())
.Returns(streamGeometry.Object);
var windowImpl = new Mock<IWindowImpl>();
Size clientSize = default(Size);
windowImpl.SetupGet(x => x.ClientSize).Returns(() => clientSize);
windowImpl.Setup(x => x.Resize(It.IsAny<Size>())).Callback<Size>(s => clientSize = s);
windowImpl.Setup(x => x.MaxClientSize).Returns(new Size(1024, 1024));
windowImpl.SetupGet(x => x.Scaling).Returns(1);
AvaloniaLocator.CurrentMutable
.Bind<IStandardCursorFactory>().ToConstant(new CursorFactoryMock())
.Bind<IAssetLoader>().ToConstant(new AssetLoader())
.Bind<IInputManager>().ToConstant(new Mock<IInputManager>().Object)
.Bind<IGlobalStyles>().ToConstant(globalStyles.Object)
.Bind<IRuntimePlatform>().ToConstant(new AppBuilder().RuntimePlatform)
.Bind<IPlatformRenderInterface>().ToConstant(renderInterface.Object)
.Bind<IStyler>().ToConstant(new Styler())
.Bind<IWindowingPlatform>().ToConstant(new Avalonia.Controls.UnitTests.WindowingPlatformMock(() => windowImpl.Object));
var theme = new DefaultTheme();
globalStyles.Setup(x => x.IsStylesInitialized).Returns(true);
globalStyles.Setup(x => x.Styles).Returns(theme);
}
} }
} }

111
tests/Avalonia.UnitTests/MockWindowingPlatform.cs

@ -8,61 +8,118 @@ namespace Avalonia.UnitTests
{ {
public class MockWindowingPlatform : IWindowingPlatform public class MockWindowingPlatform : IWindowingPlatform
{ {
private static readonly Size s_screenSize = new Size(1280, 1024);
private readonly Func<IWindowImpl> _windowImpl; private readonly Func<IWindowImpl> _windowImpl;
private readonly Func<IPopupImpl> _popupImpl; private readonly Func<IWindowBaseImpl, IPopupImpl> _popupImpl;
public MockWindowingPlatform(Func<IWindowImpl> windowImpl = null, Func<IPopupImpl> popupImpl = null ) public MockWindowingPlatform(
Func<IWindowImpl> windowImpl = null,
Func<IWindowBaseImpl, IPopupImpl> popupImpl = null )
{ {
_windowImpl = windowImpl; _windowImpl = windowImpl;
_popupImpl = popupImpl; _popupImpl = popupImpl;
} }
public static Mock<IWindowImpl> CreateWindowMock(Func<IPopupImpl> popupImpl = null) public static Mock<IWindowImpl> CreateWindowMock()
{ {
var win = Mock.Of<IWindowImpl>(x => x.Scaling == 1); var windowImpl = new Mock<IWindowImpl>();
var mock = Mock.Get(win); var position = new PixelPoint();
mock.Setup(x => x.CreatePopup()).Returns(() => var clientSize = new Size(800, 600);
windowImpl.SetupAllProperties();
windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize);
windowImpl.Setup(x => x.Scaling).Returns(1);
windowImpl.Setup(x => x.Screen).Returns(CreateScreenMock().Object);
windowImpl.Setup(x => x.Position).Returns(() => position);
SetupToplevel(windowImpl);
windowImpl.Setup(x => x.CreatePopup()).Returns(() =>
{
return CreatePopupMock(windowImpl.Object).Object;
});
windowImpl.Setup(x => x.Dispose()).Callback(() =>
{ {
if (popupImpl != null) windowImpl.Object.Closed?.Invoke();
return popupImpl(); });
return CreatePopupMock().Object;
windowImpl.Setup(x => x.Move(It.IsAny<PixelPoint>())).Callback<PixelPoint>(x =>
{
position = x;
windowImpl.Object.PositionChanged?.Invoke(x);
}); });
mock.Setup(x => x.Dispose()).Callback(() =>
windowImpl.Setup(x => x.Resize(It.IsAny<Size>())).Callback<Size>(x =>
{ {
mock.Object.Closed?.Invoke(); clientSize = x.Constrain(s_screenSize);
windowImpl.Object.Resized?.Invoke(clientSize);
}); });
PixelPoint pos = default;
mock.SetupGet(x => x.Position).Returns(() => pos); windowImpl.Setup(x => x.Show()).Callback(() =>
mock.Setup(x => x.Move(It.IsAny<PixelPoint>())).Callback(new Action<PixelPoint>(np => pos = np)); {
SetupToplevel(mock); windowImpl.Object.Activated?.Invoke();
return mock; });
return windowImpl;
} }
static void SetupToplevel<T>(Mock<T> mock) where T : class, ITopLevelImpl public static Mock<IPopupImpl> CreatePopupMock(IWindowBaseImpl parent)
{ {
mock.SetupGet(x => x.MouseDevice).Returns(new MouseDevice()); var popupImpl = new Mock<IPopupImpl>();
var positionerHelper = new ManagedPopupPositionerPopupImplHelper(parent, (pos, size, scale) =>
{
popupImpl.Object.PositionChanged?.Invoke(pos);
popupImpl.Object.Resized?.Invoke(size);
});
var positioner = new ManagedPopupPositioner(positionerHelper);
popupImpl.Setup(x => x.Scaling).Returns(1);
popupImpl.Setup(x => x.PopupPositioner).Returns(positioner);
SetupToplevel(popupImpl);
return popupImpl;
} }
public static Mock<IPopupImpl> CreatePopupMock() public static Mock<IScreenImpl> CreateScreenMock()
{ {
var positioner = Mock.Of<IPopupPositioner>(); var screenImpl = new Mock<IScreenImpl>();
var popup = Mock.Of<IPopupImpl>(x => x.Scaling == 1); var bounds = new PixelRect(0, 0, (int)s_screenSize.Width, (int)s_screenSize.Height);
var mock = Mock.Get(popup); var screen = new Screen(96, bounds, bounds, true);
mock.SetupGet(x => x.PopupPositioner).Returns(positioner); screenImpl.Setup(x => x.AllScreens).Returns(new[] { screen });
SetupToplevel(mock); screenImpl.Setup(x => x.ScreenCount).Returns(1);
return screenImpl;
return mock;
} }
public IWindowImpl CreateWindow() public IWindowImpl CreateWindow()
{ {
return _windowImpl?.Invoke() ?? CreateWindowMock(_popupImpl).Object; if (_windowImpl is object)
{
return _windowImpl();
}
else
{
var mock = CreateWindowMock();
if (_popupImpl is object)
{
mock.Setup(x => x.CreatePopup()).Returns(() => _popupImpl(mock.Object));
}
return mock.Object;
}
} }
public IEmbeddableWindowImpl CreateEmbeddableWindow() public IEmbeddableWindowImpl CreateEmbeddableWindow()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
private static void SetupToplevel<T>(Mock<T> mock) where T : class, ITopLevelImpl
{
mock.SetupGet(x => x.MouseDevice).Returns(new MouseDevice());
}
} }
} }

Loading…
Cancel
Save