From 7a42c7eb8f3c361b8b30cfa4b87c05daaadc60d3 Mon Sep 17 00:00:00 2001 From: "Herman K." Date: Thu, 10 Oct 2024 18:07:24 +0300 Subject: [PATCH] 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 --- src/Avalonia.Controls/Window.cs | 265 +++++++++++------- src/Avalonia.Controls/WindowBase.cs | 4 +- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 12 + src/Windows/Avalonia.Win32/WindowImpl.cs | 15 + .../Input/PointerTestsBase.cs | 6 + .../DesktopStyleApplicationLifetimeTests.cs | 5 + .../WindowTests.cs | 29 +- .../SliderTests.cs | 16 +- .../WindowTests_MacOS.cs | 8 +- tests/Avalonia.LeakTests/ControlTests.cs | 5 + 10 files changed, 239 insertions(+), 126 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index bbaed03dd4..0e20166512 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -94,6 +94,7 @@ namespace Avalonia.Controls private Thickness _windowDecorationMargin; private Thickness _offScreenMargin; private bool _canHandleResized = false; + private Size _arrangeBounds; /// /// Defines the property. @@ -198,6 +199,7 @@ namespace Avalonia.Controls private readonly Size _maxPlatformClientSize; private bool _shown; private bool _showingAsDialog; + private bool _positionWasSet; private bool _wasShownBefore; /// @@ -428,7 +430,11 @@ namespace Avalonia.Controls public PixelPoint Position { get => PlatformImpl?.Position ?? PixelPoint.Origin; - set => PlatformImpl?.Move(value); + set + { + PlatformImpl?.Move(value); + _positionWasSet = true; + } } /// @@ -622,7 +628,7 @@ namespace Avalonia.Controls /// public override void Show() { - ShowCore(null); + ShowCore(null, false); } 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."); } - ShowCore(owner); + ShowCore(owner, false); } private void EnsureStateBeforeShow() @@ -695,12 +701,16 @@ namespace Avalonia.Controls } } - private void ShowCore(Window? owner) + private Task? ShowCore(Window? owner, bool modal) { using (FreezeVisibilityChangeHandling()) { EnsureStateBeforeShow(); - + + if (modal && owner == null) + { + throw new ArgumentNullException(nameof(owner)); + } if (owner != null) { EnsureParentStateBeforeShow(owner); @@ -708,7 +718,9 @@ namespace Avalonia.Controls if (_shown) { - return; + if (modal) + throw new InvalidOperationException("The window is already being shown."); + return null; } RaiseEvent(new RoutedEventArgs(WindowOpenedEvent)); @@ -716,35 +728,93 @@ namespace Avalonia.Controls EnsureInitialized(); ApplyStyling(); _shown = true; + _showingAsDialog = modal; 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); + // If window position was not set before then platform may provide incorrect scaling at this time, + // but we need it for proper calculation of position and in some cases size (size to content) + SetExpectedScaling(owner); - _canHandleResized = true; - var initialSize = new Size( - double.IsNaN(Width) ? Math.Max(MinWidth, ClientSize.Width) : Width, - double.IsNaN(Height) ? Math.Max(MinHeight, ClientSize.Height) : Height); + double.IsNaN(Width) ? ClientSize.Width : Width, + 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; - // Second call will calculate correct position because both current and owner windows have correct scaling. 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(); - PlatformImpl?.Show(ShowActivated, false); + PlatformImpl?.Show(ShowActivated, modal); + + Task? result = null; + if (modal) + { + var tcs = new TaskCompletionSource(); + + 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); - _wasShownBefore = true; + if (!modal) + _wasShownBefore = true; + + return result; } } @@ -773,74 +843,7 @@ namespace Avalonia.Controls /// . /// A task that can be used to retrieve the result of the dialog when it closes. /// - public Task ShowDialog(Window owner) - { - 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(); - - 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; - } - } + public Task ShowDialog(Window owner) => ShowCore(owner, true)!; /// /// 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; } + + 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; if (startupLocation == WindowStartupLocation.CenterOwner && (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 // to CenterScreen. This behavior is consistent with WPF. 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. - var rect = FrameSize.HasValue ? - new PixelRect(PixelSize.FromSize(FrameSize.Value, scaling)) : - new PixelRect(PixelSize.FromSize(ClientSize, scaling)); + if (PlatformImpl?.FrameSize.HasValue == true) + { + // 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) { @@ -962,10 +1001,16 @@ namespace Avalonia.Controls } screen ??= Screens.ScreenFromPoint(Position); - + screen ??= Screens.Primary; + 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) @@ -973,10 +1018,22 @@ namespace Avalonia.Controls var ownerSize = owner!.FrameSize ?? owner.ClientSize; var ownerRect = new PixelRect( owner.Position, - PixelSize.FromSize(ownerSize, scaling)); + PixelSize.FromSize(ownerSize, owner.DesktopScaling)); 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 maxY = constraint.Bottom - rect.Height; @@ -987,7 +1044,7 @@ namespace Avalonia.Controls 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) { - PlatformImpl?.Resize(size, WindowResizeReason.Layout); + _arrangeBounds = size; + if (_canHandleResized) + PlatformImpl?.Resize(size, WindowResizeReason.Layout); return ClientSize; } diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 055979efb4..af84cdafa8 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -133,7 +133,9 @@ namespace Avalonia.Controls /// /// Gets the scaling factor for Window positioning and sizing. /// - public double DesktopScaling => PlatformImpl?.DesktopScaling ?? 1; + public double DesktopScaling => DesktopScalingOverride ?? PlatformImpl?.DesktopScaling ?? 1; + + private protected double? DesktopScalingOverride { get; set; } /// /// Activates the window. diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index caa2d33a25..8e9e2723e7 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -149,6 +149,18 @@ namespace Avalonia.Win32 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; case WindowsMessage.WM_GETICON: diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index f18c358525..e37ba61359 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -524,6 +524,21 @@ namespace Avalonia.Win32 0, 0, 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; + } + } } } diff --git a/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs b/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs index b4d20f2496..69b37fd27b 100644 --- a/tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs +++ b/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.PointToScreen(It.IsAny())).Returns(p => new PixelPoint((int)p.X, (int)p.Y)); impl.Setup(r => r.PointToClient(It.IsAny())).Returns(p => new Point(p.X, p.Y)); + + var screen1 = new Mock(1.75, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 966)), true); + var screens = new Mock(); + screens.Setup(x => x.ScreenFromWindow(It.IsAny())).Returns(screen1.Object); + impl.Setup(x => x.TryGetFeature(It.Is(t => t == typeof(IScreenImpl)))).Returns(screens.Object); + return impl; } diff --git a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs index ec0fc46fc7..936cfbd964 100644 --- a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs +++ b/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.RenderScaling).Returns(1); + var screen1 = new Mock(1.75, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 966)), true); + var screens = new Mock(); + screens.Setup(x => x.ScreenFromWindow(It.IsAny())).Returns(screen1.Object); + windowImpl.Setup(x => x.TryGetFeature(It.Is(t => t == typeof(IScreenImpl)))).Returns(screens.Object); + var services = TestServices.StyledWindow.With( windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index c2519aa95e..421e0b5977 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -100,11 +100,9 @@ namespace Avalonia.Controls.UnitTests [Fact] public void IsVisible_Should_Be_False_After_Impl_Signals_Close() { - var windowImpl = new Mock(); - windowImpl.Setup(r => r.Compositor).Returns(RendererMocks.CreateDummyCompositor()); + var windowImpl = CreateImpl(); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.DesktopScaling).Returns(1); - windowImpl.Setup(x => x.RenderScaling).Returns(1); var services = TestServices.StyledWindow.With( windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); @@ -273,7 +271,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var target = new Window(CreateImpl()); + var target = new Window(CreateImpl().Object); target.Show(); Assert.True(MediaContext.Instance.IsTopLevelActive(target)); @@ -286,7 +284,7 @@ namespace Avalonia.Controls.UnitTests using (UnitTestApplication.Start(TestServices.StyledWindow)) { var parent = new Window(); - var target = new Window(CreateImpl()); + var target = new Window(CreateImpl().Object); parent.Show(); target.ShowDialog(parent); @@ -318,7 +316,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var target = new Window(CreateImpl()); + var target = new Window(CreateImpl().Object); target.Show(); target.Hide(); @@ -332,7 +330,7 @@ namespace Avalonia.Controls.UnitTests using (UnitTestApplication.Start(TestServices.StyledWindow)) { var parent = new Window(); - var windowImpl = new Mock(); + var windowImpl = CreateImpl(); windowImpl.Setup(x => x.Compositor).Returns(RendererMocks.CreateDummyCompositor()); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.DesktopScaling).Returns(1); @@ -374,7 +372,7 @@ namespace Avalonia.Controls.UnitTests using (UnitTestApplication.Start(TestServices.StyledWindow)) { var parent = new Window(); - var windowImpl = new Mock(); + var windowImpl = CreateImpl(); windowImpl.Setup(x => x.Compositor).Returns(RendererMocks.CreateDummyCompositor()); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.DesktopScaling).Returns(1); @@ -1099,11 +1097,18 @@ namespace Avalonia.Controls.UnitTests } } - private static IWindowImpl CreateImpl() + private static Mock CreateImpl() { - var compositor = RendererMocks.CreateDummyCompositor(); - return Mock.Of(x => x.RenderScaling == 1 && - x.Compositor == compositor); + var screen1 = new Mock(1.75, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 966)), true); + var screens = new Mock(); + screens.Setup(x => x.ScreenFromWindow(It.IsAny())).Returns(screen1.Object); + + var windowImpl = new Mock(); + windowImpl.Setup(r => r.Compositor).Returns(RendererMocks.CreateDummyCompositor()); + windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.TryGetFeature(It.Is(t => t == typeof(IScreenImpl)))).Returns(screens.Object); + + return windowImpl; } private class ChildControl : Control diff --git a/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs b/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs index 14b8aaa5cb..5aefff3c7c 100644 --- a/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs @@ -25,13 +25,13 @@ namespace Avalonia.IntegrationTests.Appium 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( Session.FindElementByAccessibilityId("HorizontalSliderValue").Text, CultureInfo.InvariantCulture); Assert.True(value > 50); - Assert.Equal(value, boundValue); + Assert.True(Math.Abs(value - boundValue) < 2.0, $"Expected: {value}, Actual: {boundValue}"); var currentThumbRect = thumb.Rect; Assert.True(currentThumbRect.Left > initialThumbRect.Left); @@ -46,13 +46,13 @@ namespace Avalonia.IntegrationTests.Appium 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( Session.FindElementByAccessibilityId("HorizontalSliderValue").Text, CultureInfo.InvariantCulture); Assert.True(value < 50); - Assert.Equal(value, boundValue); + Assert.True(Math.Abs(value - boundValue) < 2.0, $"Expected: {value}, Actual: {boundValue}"); var currentThumbRect = thumb.Rect; Assert.True(currentThumbRect.Left < initialThumbRect.Left); @@ -67,13 +67,13 @@ namespace Avalonia.IntegrationTests.Appium 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( Session.FindElementByAccessibilityId("HorizontalSliderValue").Text, CultureInfo.InvariantCulture); Assert.True(value > 50); - Assert.Equal(value, boundValue); + Assert.True(Math.Abs(value - boundValue) < 2.0, $"Expected: {value}, Actual: {boundValue}"); var currentThumbRect = thumb.Rect; Assert.True(currentThumbRect.Left > initialThumbRect.Left); @@ -88,13 +88,13 @@ namespace Avalonia.IntegrationTests.Appium 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( Session.FindElementByAccessibilityId("HorizontalSliderValue").Text, CultureInfo.InvariantCulture); Assert.True(value < 50); - Assert.Equal(value, boundValue); + Assert.True(Math.Abs(value - boundValue) < 2.0, $"Expected: {value}, Actual: {boundValue}"); var currentThumbRect = thumb.Rect; Assert.True(currentThumbRect.Left < initialThumbRect.Left); diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index e8e4746523..7507711447 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -27,6 +27,8 @@ namespace Avalonia.IntegrationTests.Appium var secondaryWindowIndex = GetWindowOrder("SecondaryWindow"); + Thread.Sleep(300); // sync with timer + Assert.Equal(1, secondaryWindowIndex); } } @@ -38,15 +40,17 @@ namespace Avalonia.IntegrationTests.Appium using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.Manual)) { + var childWindow = GetWindow("SecondaryWindow"); + new Actions(Session) - .MoveToElement(mainWindow, 100, 1) + .MoveToElement(childWindow, 100, 1) .ClickAndHold() .Perform(); var secondaryWindowIndex = GetWindowOrder("SecondaryWindow"); new Actions(Session) - .MoveToElement(mainWindow, 100, 1) + .MoveToElement(childWindow, 100, 1) .Release() .Perform(); diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index 36f0818638..8e5ab2adc9 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -463,12 +463,17 @@ namespace Avalonia.LeakTests { using (Start()) { + var screen1 = new Mock(1.75, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 966)), true); + var screens = new Mock(); + screens.Setup(x => x.ScreenFromWindow(It.IsAny())).Returns(screen1.Object); + var impl = new Mock(); impl.Setup(r => r.TryGetFeature(It.IsAny())).Returns(null); impl.SetupGet(x => x.RenderScaling).Returns(1); impl.SetupProperty(x => x.Closed); impl.Setup(x => x.Compositor).Returns(RendererMocks.CreateDummyCompositor()); impl.Setup(x => x.Dispose()).Callback(() => impl.Object.Closed()); + impl.Setup(x => x.TryGetFeature(It.Is(t => t == typeof(IScreenImpl)))).Returns(screens.Object); AvaloniaLocator.CurrentMutable.Bind() .ToConstant(new MockWindowingPlatform(() => impl.Object));