Browse Source

Considering scaling when centering windows (#16158)

* removed duplicated code between Window.Show and Window.ShowDialog

* Handling different cases of window initial position and size + unit test

* positioning cursor on resize grip in WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_Clicking_Resize_Grip test

* Fix for flaky test

* displaying decimal digits of slider value to avoid some issues with rounding

---------

Co-authored-by: Herman Kirshin <herman.kirshin@jetbrains.com>
pull/17787/head
Herman K. 2 years ago
committed by GitHub
parent
commit
7a42c7eb8f
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 265
      src/Avalonia.Controls/Window.cs
  2. 4
      src/Avalonia.Controls/WindowBase.cs
  3. 12
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  4. 15
      src/Windows/Avalonia.Win32/WindowImpl.cs
  5. 6
      tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs
  6. 5
      tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs
  7. 29
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  8. 16
      tests/Avalonia.IntegrationTests.Appium/SliderTests.cs
  9. 8
      tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs
  10. 5
      tests/Avalonia.LeakTests/ControlTests.cs

265
src/Avalonia.Controls/Window.cs

@ -94,6 +94,7 @@ namespace Avalonia.Controls
private Thickness _windowDecorationMargin; private Thickness _windowDecorationMargin;
private Thickness _offScreenMargin; private Thickness _offScreenMargin;
private bool _canHandleResized = false; private bool _canHandleResized = false;
private Size _arrangeBounds;
/// <summary> /// <summary>
/// Defines the <see cref="SizeToContent"/> property. /// Defines the <see cref="SizeToContent"/> property.
@ -198,6 +199,7 @@ namespace Avalonia.Controls
private readonly Size _maxPlatformClientSize; private readonly Size _maxPlatformClientSize;
private bool _shown; private bool _shown;
private bool _showingAsDialog; private bool _showingAsDialog;
private bool _positionWasSet;
private bool _wasShownBefore; private bool _wasShownBefore;
/// <summary> /// <summary>
@ -428,7 +430,11 @@ namespace Avalonia.Controls
public PixelPoint Position public PixelPoint Position
{ {
get => PlatformImpl?.Position ?? PixelPoint.Origin; get => PlatformImpl?.Position ?? PixelPoint.Origin;
set => PlatformImpl?.Move(value); set
{
PlatformImpl?.Move(value);
_positionWasSet = true;
}
} }
/// <summary> /// <summary>
@ -622,7 +628,7 @@ namespace Avalonia.Controls
/// </exception> /// </exception>
public override void Show() public override void Show()
{ {
ShowCore(null); ShowCore<object>(null, false);
} }
protected override void IsVisibleChanged(AvaloniaPropertyChangedEventArgs e) protected override void IsVisibleChanged(AvaloniaPropertyChangedEventArgs e)
@ -666,7 +672,7 @@ namespace Avalonia.Controls
throw new ArgumentNullException(nameof(owner), "Showing a child window requires valid parent."); throw new ArgumentNullException(nameof(owner), "Showing a child window requires valid parent.");
} }
ShowCore(owner); ShowCore<object>(owner, false);
} }
private void EnsureStateBeforeShow() private void EnsureStateBeforeShow()
@ -695,12 +701,16 @@ namespace Avalonia.Controls
} }
} }
private void ShowCore(Window? owner) private Task<TResult>? ShowCore<TResult>(Window? owner, bool modal)
{ {
using (FreezeVisibilityChangeHandling()) using (FreezeVisibilityChangeHandling())
{ {
EnsureStateBeforeShow(); EnsureStateBeforeShow();
if (modal && owner == null)
{
throw new ArgumentNullException(nameof(owner));
}
if (owner != null) if (owner != null)
{ {
EnsureParentStateBeforeShow(owner); EnsureParentStateBeforeShow(owner);
@ -708,7 +718,9 @@ namespace Avalonia.Controls
if (_shown) if (_shown)
{ {
return; if (modal)
throw new InvalidOperationException("The window is already being shown.");
return null;
} }
RaiseEvent(new RoutedEventArgs(WindowOpenedEvent)); RaiseEvent(new RoutedEventArgs(WindowOpenedEvent));
@ -716,35 +728,93 @@ namespace Avalonia.Controls
EnsureInitialized(); EnsureInitialized();
ApplyStyling(); ApplyStyling();
_shown = true; _shown = true;
_showingAsDialog = modal;
IsVisible = true; IsVisible = true;
// We need to set position first because it is required for getting correct display scale. If position is not manual then it can be // If window position was not set before then platform may provide incorrect scaling at this time,
// determined only by calling this method. But here it will calculate not precise location because scaling may not yet be applied (see i.e. X11Window), // but we need it for proper calculation of position and in some cases size (size to content)
// thus we ought to call it again later to center window correctly if needed, when scaling will be already applied SetExpectedScaling(owner);
SetWindowStartupLocation(owner);
_canHandleResized = true;
var initialSize = new Size( var initialSize = new Size(
double.IsNaN(Width) ? Math.Max(MinWidth, ClientSize.Width) : Width, double.IsNaN(Width) ? ClientSize.Width : Width,
double.IsNaN(Height) ? Math.Max(MinHeight, ClientSize.Height) : Height); double.IsNaN(Height) ? ClientSize.Height : Height);
initialSize = new Size(
MathUtilities.Clamp(initialSize.Width, MinWidth, MaxWidth),
MathUtilities.Clamp(initialSize.Height, MinHeight, MaxHeight));
if (initialSize != ClientSize) var clientSizeChanged = initialSize != ClientSize;
ClientSize = initialSize; // ClientSize is required for Measure and Arrange
// this will call ArrangeSetBounds
LayoutManager.ExecuteInitialLayoutPass();
if (SizeToContent.HasFlag(SizeToContent.Width))
{ {
PlatformImpl?.Resize(initialSize, WindowResizeReason.Layout); initialSize = initialSize.WithWidth(MathUtilities.Clamp(_arrangeBounds.Width, MinWidth, MaxWidth));
clientSizeChanged |= initialSize != ClientSize;
ClientSize = initialSize;
} }
LayoutManager.ExecuteInitialLayoutPass(); if (SizeToContent.HasFlag(SizeToContent.Height))
{
initialSize = initialSize.WithHeight(MathUtilities.Clamp(_arrangeBounds.Height, MinHeight, MaxHeight));
clientSizeChanged |= initialSize != ClientSize;
ClientSize = initialSize;
}
Owner = owner; Owner = owner;
// Second call will calculate correct position because both current and owner windows have correct scaling.
SetWindowStartupLocation(owner); SetWindowStartupLocation(owner);
DesktopScalingOverride = null;
if (clientSizeChanged || ClientSize != PlatformImpl?.ClientSize)
{
// Previously it was called before ExecuteInitialLayoutPass
PlatformImpl?.Resize(ClientSize, WindowResizeReason.Layout);
// we do not want PlatformImpl?.Resize to trigger HandleResized yet because it will set Width and Height.
// So perform some important actions from HandleResized
Renderer.Resized(ClientSize);
OnResized(new WindowResizedEventArgs(ClientSize, WindowResizeReason.Layout));
if (!double.IsNaN(Width))
Width = ClientSize.Width;
if (!double.IsNaN(Height))
Height = ClientSize.Height;
}
FrameSize = PlatformImpl?.FrameSize;
_canHandleResized = true;
StartRendering(); StartRendering();
PlatformImpl?.Show(ShowActivated, false); PlatformImpl?.Show(ShowActivated, modal);
Task<TResult>? result = null;
if (modal)
{
var tcs = new TaskCompletionSource<TResult>();
Observable.FromEventPattern(
x => Closed += x,
x => Closed -= x)
.Take(1)
.Subscribe(_ =>
{
owner!.Activate();
tcs.SetResult((TResult)(_dialogResult ?? default(TResult)!));
});
result = tcs.Task;
}
OnOpened(EventArgs.Empty); OnOpened(EventArgs.Empty);
_wasShownBefore = true; if (!modal)
_wasShownBefore = true;
return result;
} }
} }
@ -773,74 +843,7 @@ namespace Avalonia.Controls
/// <returns>. /// <returns>.
/// A task that can be used to retrieve the result of the dialog when it closes. /// A task that can be used to retrieve the result of the dialog when it closes.
/// </returns> /// </returns>
public Task<TResult> ShowDialog<TResult>(Window owner) public Task<TResult> ShowDialog<TResult>(Window owner) => ShowCore<TResult>(owner, true)!;
{
using (FreezeVisibilityChangeHandling())
{
EnsureStateBeforeShow();
if (owner == null)
{
throw new ArgumentNullException(nameof(owner));
}
EnsureParentStateBeforeShow(owner);
if (_shown)
{
throw new InvalidOperationException("The window is already being shown.");
}
RaiseEvent(new RoutedEventArgs(WindowOpenedEvent));
EnsureInitialized();
ApplyStyling();
_shown = true;
_showingAsDialog = true;
IsVisible = true;
// We need to set position first because it is required for getting correct display scale. If position is not manual then it can be
// determined only by calling this method. But here it will calculate not precise location because scaling may not yet be applied (see i.e. X11Window),
// thus we ought to call it again later to center window correctly if needed, when scaling will be already applied
SetWindowStartupLocation(owner);
_canHandleResized = true;
var initialSize = new Size(
double.IsNaN(Width) ? ClientSize.Width : Width,
double.IsNaN(Height) ? ClientSize.Height : Height);
if (initialSize != ClientSize)
{
PlatformImpl?.Resize(initialSize, WindowResizeReason.Layout);
}
LayoutManager.ExecuteInitialLayoutPass();
var result = new TaskCompletionSource<TResult>();
Owner = owner;
// Second call will calculate correct position because both current and owner windows have correct scaling.
SetWindowStartupLocation(owner);
StartRendering();
PlatformImpl?.Show(ShowActivated, true);
Observable.FromEventPattern(
x => Closed += x,
x => Closed -= x)
.Take(1)
.Subscribe(_ =>
{
owner.Activate();
result.SetResult((TResult)(_dialogResult ?? default(TResult)!));
});
OnOpened(EventArgs.Empty);
return result.Task;
}
}
/// <summary> /// <summary>
/// Sorts the windows ascending by their Z order - the topmost window will be the last in the list. /// Sorts the windows ascending by their Z order - the topmost window will be the last in the list.
@ -925,31 +928,67 @@ namespace Avalonia.Controls
} }
} }
private void SetWindowStartupLocation(Window? owner = null) private void SetExpectedScaling(WindowBase? owner)
{ {
if (_wasShownBefore == true) if (_wasShownBefore)
{ {
return; return;
} }
var location = GetEffectiveWindowStartupLocation(owner);
switch (location)
{
case WindowStartupLocation.CenterOwner:
DesktopScalingOverride = owner?.DesktopScaling;
break;
case WindowStartupLocation.CenterScreen:
DesktopScalingOverride = owner?.DesktopScaling ?? Screens.ScreenFromPoint(Position)?.Scaling ?? Screens.Primary?.Scaling;
break;
case WindowStartupLocation.Manual:
DesktopScalingOverride = Screens.ScreenFromPoint(Position)?.Scaling;
break;
}
}
private WindowStartupLocation GetEffectiveWindowStartupLocation(WindowBase? owner)
{
var startupLocation = WindowStartupLocation; var startupLocation = WindowStartupLocation;
if (startupLocation == WindowStartupLocation.CenterOwner && if (startupLocation == WindowStartupLocation.CenterOwner &&
(owner is null || (owner is null ||
(Owner is Window ownerWindow && ownerWindow.WindowState == WindowState.Minimized)) (owner is Window ownerWindow && ownerWindow.WindowState == WindowState.Minimized))
) )
{ {
// If startup location is CenterOwner, but owner is null or minimized then fall back // If startup location is CenterOwner, but owner is null or minimized then fall back
// to CenterScreen. This behavior is consistent with WPF. // to CenterScreen. This behavior is consistent with WPF.
startupLocation = WindowStartupLocation.CenterScreen; startupLocation = WindowStartupLocation.CenterScreen;
} }
var scaling = owner?.DesktopScaling ?? PlatformImpl?.DesktopScaling ?? 1; return startupLocation;
}
private void SetWindowStartupLocation(Window? owner = null)
{
if (_wasShownBefore)
{
return;
}
var startupLocation = GetEffectiveWindowStartupLocation(owner);
PixelRect rect;
// Use frame size, falling back to client size if the platform can't give it to us. // Use frame size, falling back to client size if the platform can't give it to us.
var rect = FrameSize.HasValue ? if (PlatformImpl?.FrameSize.HasValue == true)
new PixelRect(PixelSize.FromSize(FrameSize.Value, scaling)) : {
new PixelRect(PixelSize.FromSize(ClientSize, scaling)); // Platform may calculate FrameSize with incorrect scaling, so do not trust the value.
var diff = PlatformImpl.FrameSize.Value - PlatformImpl.ClientSize;
rect = new PixelRect(PixelSize.FromSize(ClientSize + diff, DesktopScaling));
}
else
{
rect = new PixelRect(PixelSize.FromSize(ClientSize, DesktopScaling));
}
if (startupLocation == WindowStartupLocation.CenterScreen) if (startupLocation == WindowStartupLocation.CenterScreen)
{ {
@ -962,10 +1001,16 @@ namespace Avalonia.Controls
} }
screen ??= Screens.ScreenFromPoint(Position); screen ??= Screens.ScreenFromPoint(Position);
screen ??= Screens.Primary;
if (screen is not null) if (screen is not null)
{ {
Position = screen.WorkingArea.CenterRect(rect).Position; var childRect = screen.WorkingArea.CenterRect(rect);
if (Screens.ScreenFromPoint(childRect.Position) == null)
childRect = ApplyScreenConstraint(screen, childRect);
Position = childRect.Position;
} }
} }
else if (startupLocation == WindowStartupLocation.CenterOwner) else if (startupLocation == WindowStartupLocation.CenterOwner)
@ -973,10 +1018,22 @@ namespace Avalonia.Controls
var ownerSize = owner!.FrameSize ?? owner.ClientSize; var ownerSize = owner!.FrameSize ?? owner.ClientSize;
var ownerRect = new PixelRect( var ownerRect = new PixelRect(
owner.Position, owner.Position,
PixelSize.FromSize(ownerSize, scaling)); PixelSize.FromSize(ownerSize, owner.DesktopScaling));
var childRect = ownerRect.CenterRect(rect); var childRect = ownerRect.CenterRect(rect);
if (Screens.ScreenFromWindow(owner)?.WorkingArea is { } constraint) var screen = Screens.ScreenFromWindow(owner);
childRect = ApplyScreenConstraint(screen, childRect);
Position = childRect.Position;
}
if (!_positionWasSet && DesktopScaling != PlatformImpl?.DesktopScaling) // Platform returns incorrect scaling, forcing setting position may fix it
PlatformImpl?.Move(Position);
PixelRect ApplyScreenConstraint(Screen? screen, PixelRect childRect)
{
if (screen?.WorkingArea is { } constraint)
{ {
var maxX = constraint.Right - rect.Width; var maxX = constraint.Right - rect.Width;
var maxY = constraint.Bottom - rect.Height; var maxY = constraint.Bottom - rect.Height;
@ -987,7 +1044,7 @@ namespace Avalonia.Controls
childRect = childRect.WithY(MathUtilities.Clamp(childRect.Y, constraint.Y, maxY)); childRect = childRect.WithY(MathUtilities.Clamp(childRect.Y, constraint.Y, maxY));
} }
Position = childRect.Position; return childRect;
} }
} }
@ -1048,7 +1105,9 @@ namespace Avalonia.Controls
protected sealed override Size ArrangeSetBounds(Size size) protected sealed override Size ArrangeSetBounds(Size size)
{ {
PlatformImpl?.Resize(size, WindowResizeReason.Layout); _arrangeBounds = size;
if (_canHandleResized)
PlatformImpl?.Resize(size, WindowResizeReason.Layout);
return ClientSize; return ClientSize;
} }

4
src/Avalonia.Controls/WindowBase.cs

@ -133,7 +133,9 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets the scaling factor for Window positioning and sizing. /// Gets the scaling factor for Window positioning and sizing.
/// </summary> /// </summary>
public double DesktopScaling => PlatformImpl?.DesktopScaling ?? 1; public double DesktopScaling => DesktopScalingOverride ?? PlatformImpl?.DesktopScaling ?? 1;
private protected double? DesktopScalingOverride { get; set; }
/// <summary> /// <summary>
/// Activates the window. /// Activates the window.

12
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@ -149,6 +149,18 @@ namespace Avalonia.Win32
return IntPtr.Zero; return IntPtr.Zero;
} }
else
{
// In case parent is on another screen with different scaling, window will have header scaled with
// parent's scaling factor, so need to update frame
SetWindowPos(hWnd,
IntPtr.Zero, 0, 0, 0, 0,
SetWindowPosFlags.SWP_FRAMECHANGED |
SetWindowPosFlags.SWP_NOSIZE |
SetWindowPosFlags.SWP_NOMOVE |
SetWindowPosFlags.SWP_NOZORDER |
SetWindowPosFlags.SWP_NOACTIVATE);
}
break; break;
case WindowsMessage.WM_GETICON: case WindowsMessage.WM_GETICON:

15
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -524,6 +524,21 @@ namespace Avalonia.Win32
0, 0,
0, 0,
SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOZORDER); SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOZORDER);
if (ShCoreAvailable && Win32Platform.WindowsVersion >= PlatformConstants.Windows8_1)
{
var monitor = MonitorFromPoint(new POINT() { X = value.X, Y = value.Y },
MONITOR.MONITOR_DEFAULTTONEAREST);
if (GetDpiForMonitor(
monitor,
MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI,
out _dpi,
out _) == 0)
{
_scaling = _dpi / StandardDpi;
}
}
} }
} }

6
tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs

@ -41,6 +41,12 @@ public abstract class PointerTestsBase
impl.Setup(r => r.Compositor).Returns(RendererMocks.CreateDummyCompositor()); impl.Setup(r => r.Compositor).Returns(RendererMocks.CreateDummyCompositor());
impl.Setup(r => r.PointToScreen(It.IsAny<Point>())).Returns<Point>(p => new PixelPoint((int)p.X, (int)p.Y)); impl.Setup(r => r.PointToScreen(It.IsAny<Point>())).Returns<Point>(p => new PixelPoint((int)p.X, (int)p.Y));
impl.Setup(r => r.PointToClient(It.IsAny<PixelPoint>())).Returns<PixelPoint>(p => new Point(p.X, p.Y)); impl.Setup(r => r.PointToClient(It.IsAny<PixelPoint>())).Returns<PixelPoint>(p => new Point(p.X, p.Y));
var screen1 = new Mock<Screen>(1.75, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 966)), true);
var screens = new Mock<IScreenImpl>();
screens.Setup(x => x.ScreenFromWindow(It.IsAny<IWindowBaseImpl>())).Returns(screen1.Object);
impl.Setup(x => x.TryGetFeature(It.Is<Type>(t => t == typeof(IScreenImpl)))).Returns(screens.Object);
return impl; return impl;
} }

5
tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs

@ -220,6 +220,11 @@ namespace Avalonia.Controls.UnitTests
windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1); windowImpl.Setup(x => x.RenderScaling).Returns(1);
var screen1 = new Mock<Screen>(1.75, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 966)), true);
var screens = new Mock<IScreenImpl>();
screens.Setup(x => x.ScreenFromWindow(It.IsAny<IWindowBaseImpl>())).Returns(screen1.Object);
windowImpl.Setup(x => x.TryGetFeature(It.Is<Type>(t => t == typeof(IScreenImpl)))).Returns(screens.Object);
var services = TestServices.StyledWindow.With( var services = TestServices.StyledWindow.With(
windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object));

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

@ -100,11 +100,9 @@ namespace Avalonia.Controls.UnitTests
[Fact] [Fact]
public void IsVisible_Should_Be_False_After_Impl_Signals_Close() public void IsVisible_Should_Be_False_After_Impl_Signals_Close()
{ {
var windowImpl = new Mock<IWindowImpl>(); var windowImpl = CreateImpl();
windowImpl.Setup(r => r.Compositor).Returns(RendererMocks.CreateDummyCompositor());
windowImpl.SetupProperty(x => x.Closed); windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
var services = TestServices.StyledWindow.With( var services = TestServices.StyledWindow.With(
windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object));
@ -273,7 +271,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var target = new Window(CreateImpl()); var target = new Window(CreateImpl().Object);
target.Show(); target.Show();
Assert.True(MediaContext.Instance.IsTopLevelActive(target)); Assert.True(MediaContext.Instance.IsTopLevelActive(target));
@ -286,7 +284,7 @@ namespace Avalonia.Controls.UnitTests
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var parent = new Window(); var parent = new Window();
var target = new Window(CreateImpl()); var target = new Window(CreateImpl().Object);
parent.Show(); parent.Show();
target.ShowDialog<object>(parent); target.ShowDialog<object>(parent);
@ -318,7 +316,7 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var target = new Window(CreateImpl()); var target = new Window(CreateImpl().Object);
target.Show(); target.Show();
target.Hide(); target.Hide();
@ -332,7 +330,7 @@ namespace Avalonia.Controls.UnitTests
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var parent = new Window(); var parent = new Window();
var windowImpl = new Mock<IWindowImpl>(); var windowImpl = CreateImpl();
windowImpl.Setup(x => x.Compositor).Returns(RendererMocks.CreateDummyCompositor()); windowImpl.Setup(x => x.Compositor).Returns(RendererMocks.CreateDummyCompositor());
windowImpl.SetupProperty(x => x.Closed); windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.DesktopScaling).Returns(1);
@ -374,7 +372,7 @@ namespace Avalonia.Controls.UnitTests
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var parent = new Window(); var parent = new Window();
var windowImpl = new Mock<IWindowImpl>(); var windowImpl = CreateImpl();
windowImpl.Setup(x => x.Compositor).Returns(RendererMocks.CreateDummyCompositor()); windowImpl.Setup(x => x.Compositor).Returns(RendererMocks.CreateDummyCompositor());
windowImpl.SetupProperty(x => x.Closed); windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.DesktopScaling).Returns(1);
@ -1099,11 +1097,18 @@ namespace Avalonia.Controls.UnitTests
} }
} }
private static IWindowImpl CreateImpl() private static Mock<IWindowImpl> CreateImpl()
{ {
var compositor = RendererMocks.CreateDummyCompositor(); var screen1 = new Mock<Screen>(1.75, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 966)), true);
return Mock.Of<IWindowImpl>(x => x.RenderScaling == 1 && var screens = new Mock<IScreenImpl>();
x.Compositor == compositor); screens.Setup(x => x.ScreenFromWindow(It.IsAny<IWindowBaseImpl>())).Returns(screen1.Object);
var windowImpl = new Mock<IWindowImpl>();
windowImpl.Setup(r => r.Compositor).Returns(RendererMocks.CreateDummyCompositor());
windowImpl.Setup(x => x.RenderScaling).Returns(1);
windowImpl.Setup(x => x.TryGetFeature(It.Is<Type>(t => t == typeof(IScreenImpl)))).Returns(screens.Object);
return windowImpl;
} }
private class ChildControl : Control private class ChildControl : Control

16
tests/Avalonia.IntegrationTests.Appium/SliderTests.cs

@ -25,13 +25,13 @@ namespace Avalonia.IntegrationTests.Appium
new Actions(Session).ClickAndHold(thumb).MoveByOffset(100, 0).Release().Perform(); new Actions(Session).ClickAndHold(thumb).MoveByOffset(100, 0).Release().Perform();
var value = Math.Round(double.Parse(slider.Text, CultureInfo.InvariantCulture)); var value = double.Parse(slider.Text, CultureInfo.InvariantCulture);
var boundValue = double.Parse( var boundValue = double.Parse(
Session.FindElementByAccessibilityId("HorizontalSliderValue").Text, Session.FindElementByAccessibilityId("HorizontalSliderValue").Text,
CultureInfo.InvariantCulture); CultureInfo.InvariantCulture);
Assert.True(value > 50); Assert.True(value > 50);
Assert.Equal(value, boundValue); Assert.True(Math.Abs(value - boundValue) < 2.0, $"Expected: {value}, Actual: {boundValue}");
var currentThumbRect = thumb.Rect; var currentThumbRect = thumb.Rect;
Assert.True(currentThumbRect.Left > initialThumbRect.Left); Assert.True(currentThumbRect.Left > initialThumbRect.Left);
@ -46,13 +46,13 @@ namespace Avalonia.IntegrationTests.Appium
new Actions(Session).ClickAndHold(thumb).MoveByOffset(-100, 0).Release().Perform(); new Actions(Session).ClickAndHold(thumb).MoveByOffset(-100, 0).Release().Perform();
var value = Math.Round(double.Parse(slider.Text, CultureInfo.InvariantCulture)); var value = double.Parse(slider.Text, CultureInfo.InvariantCulture);
var boundValue = double.Parse( var boundValue = double.Parse(
Session.FindElementByAccessibilityId("HorizontalSliderValue").Text, Session.FindElementByAccessibilityId("HorizontalSliderValue").Text,
CultureInfo.InvariantCulture); CultureInfo.InvariantCulture);
Assert.True(value < 50); Assert.True(value < 50);
Assert.Equal(value, boundValue); Assert.True(Math.Abs(value - boundValue) < 2.0, $"Expected: {value}, Actual: {boundValue}");
var currentThumbRect = thumb.Rect; var currentThumbRect = thumb.Rect;
Assert.True(currentThumbRect.Left < initialThumbRect.Left); Assert.True(currentThumbRect.Left < initialThumbRect.Left);
@ -67,13 +67,13 @@ namespace Avalonia.IntegrationTests.Appium
new Actions(Session).MoveToElementCenter(slider, 100, 0).Click().Perform(); new Actions(Session).MoveToElementCenter(slider, 100, 0).Click().Perform();
var value = Math.Round(double.Parse(slider.Text, CultureInfo.InvariantCulture)); var value = double.Parse(slider.Text, CultureInfo.InvariantCulture);
var boundValue = double.Parse( var boundValue = double.Parse(
Session.FindElementByAccessibilityId("HorizontalSliderValue").Text, Session.FindElementByAccessibilityId("HorizontalSliderValue").Text,
CultureInfo.InvariantCulture); CultureInfo.InvariantCulture);
Assert.True(value > 50); Assert.True(value > 50);
Assert.Equal(value, boundValue); Assert.True(Math.Abs(value - boundValue) < 2.0, $"Expected: {value}, Actual: {boundValue}");
var currentThumbRect = thumb.Rect; var currentThumbRect = thumb.Rect;
Assert.True(currentThumbRect.Left > initialThumbRect.Left); Assert.True(currentThumbRect.Left > initialThumbRect.Left);
@ -88,13 +88,13 @@ namespace Avalonia.IntegrationTests.Appium
new Actions(Session).MoveToElementCenter(slider, -100, 0).Click().Perform(); new Actions(Session).MoveToElementCenter(slider, -100, 0).Click().Perform();
var value = Math.Round(double.Parse(slider.Text, CultureInfo.InvariantCulture)); var value = double.Parse(slider.Text, CultureInfo.InvariantCulture);
var boundValue = double.Parse( var boundValue = double.Parse(
Session.FindElementByAccessibilityId("HorizontalSliderValue").Text, Session.FindElementByAccessibilityId("HorizontalSliderValue").Text,
CultureInfo.InvariantCulture); CultureInfo.InvariantCulture);
Assert.True(value < 50); Assert.True(value < 50);
Assert.Equal(value, boundValue); Assert.True(Math.Abs(value - boundValue) < 2.0, $"Expected: {value}, Actual: {boundValue}");
var currentThumbRect = thumb.Rect; var currentThumbRect = thumb.Rect;
Assert.True(currentThumbRect.Left < initialThumbRect.Left); Assert.True(currentThumbRect.Left < initialThumbRect.Left);

8
tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs

@ -27,6 +27,8 @@ namespace Avalonia.IntegrationTests.Appium
var secondaryWindowIndex = GetWindowOrder("SecondaryWindow"); var secondaryWindowIndex = GetWindowOrder("SecondaryWindow");
Thread.Sleep(300); // sync with timer
Assert.Equal(1, secondaryWindowIndex); Assert.Equal(1, secondaryWindowIndex);
} }
} }
@ -38,15 +40,17 @@ namespace Avalonia.IntegrationTests.Appium
using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.Manual)) using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.Manual))
{ {
var childWindow = GetWindow("SecondaryWindow");
new Actions(Session) new Actions(Session)
.MoveToElement(mainWindow, 100, 1) .MoveToElement(childWindow, 100, 1)
.ClickAndHold() .ClickAndHold()
.Perform(); .Perform();
var secondaryWindowIndex = GetWindowOrder("SecondaryWindow"); var secondaryWindowIndex = GetWindowOrder("SecondaryWindow");
new Actions(Session) new Actions(Session)
.MoveToElement(mainWindow, 100, 1) .MoveToElement(childWindow, 100, 1)
.Release() .Release()
.Perform(); .Perform();

5
tests/Avalonia.LeakTests/ControlTests.cs

@ -463,12 +463,17 @@ namespace Avalonia.LeakTests
{ {
using (Start()) using (Start())
{ {
var screen1 = new Mock<Screen>(1.75, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 966)), true);
var screens = new Mock<IScreenImpl>();
screens.Setup(x => x.ScreenFromWindow(It.IsAny<IWindowBaseImpl>())).Returns(screen1.Object);
var impl = new Mock<IWindowImpl>(); var impl = new Mock<IWindowImpl>();
impl.Setup(r => r.TryGetFeature(It.IsAny<Type>())).Returns(null); impl.Setup(r => r.TryGetFeature(It.IsAny<Type>())).Returns(null);
impl.SetupGet(x => x.RenderScaling).Returns(1); impl.SetupGet(x => x.RenderScaling).Returns(1);
impl.SetupProperty(x => x.Closed); impl.SetupProperty(x => x.Closed);
impl.Setup(x => x.Compositor).Returns(RendererMocks.CreateDummyCompositor()); impl.Setup(x => x.Compositor).Returns(RendererMocks.CreateDummyCompositor());
impl.Setup(x => x.Dispose()).Callback(() => impl.Object.Closed()); impl.Setup(x => x.Dispose()).Callback(() => impl.Object.Closed());
impl.Setup(x => x.TryGetFeature(It.Is<Type>(t => t == typeof(IScreenImpl)))).Returns(screens.Object);
AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatform>() AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatform>()
.ToConstant(new MockWindowingPlatform(() => impl.Object)); .ToConstant(new MockWindowingPlatform(() => impl.Object));

Loading…
Cancel
Save