diff --git a/src/Avalonia.Base/Layout/LayoutHelper.cs b/src/Avalonia.Base/Layout/LayoutHelper.cs index fd81ffaa49..286a79ee8a 100644 --- a/src/Avalonia.Base/Layout/LayoutHelper.cs +++ b/src/Avalonia.Base/Layout/LayoutHelper.cs @@ -265,5 +265,20 @@ namespace Avalonia.Layout // should be. return Math.Round(value, 8, MidpointRounding.ToZero); } + + internal static double ValidateScaling(double scaling) + { + if (MathUtilities.IsNegativeOrNonFinite(scaling) || MathUtilities.IsZero(scaling)) + throw new InvalidOperationException($"Invalid render scaling value {scaling}"); + + if (MathUtilities.IsOne(scaling)) + { + // Ensure we've got exactly 1.0 and not an approximation, + // so we don't have to use MathUtilities.IsOne in various layout hot paths. + return 1.0; + } + + return scaling; + } } } diff --git a/src/Avalonia.Controls/PresentationSource/PresentationSource.RenderRoot.cs b/src/Avalonia.Controls/PresentationSource/PresentationSource.RenderRoot.cs index d017cb3f5c..7c129742b8 100644 --- a/src/Avalonia.Controls/PresentationSource/PresentationSource.RenderRoot.cs +++ b/src/Avalonia.Controls/PresentationSource/PresentationSource.RenderRoot.cs @@ -1,5 +1,5 @@ using System; -using Avalonia.Input; +using Avalonia.Layout; using Avalonia.Rendering; using Avalonia.Rendering.Composition; @@ -15,7 +15,8 @@ internal partial class PresentationSource //TODO: Can we PLEASE get rid of this abomination in tests and use actual hit-testing engine instead? public IHitTester? HitTesterOverride { get; set; } - public double RenderScaling => PlatformImpl?.RenderScaling ?? 1; + public double RenderScaling { get; private set; } = 1.0; + public Size ClientSize => _clientSizeProvider(); public void SceneInvalidated(object? sender, SceneInvalidatedEventArgs sceneInvalidatedEventArgs) @@ -26,4 +27,7 @@ internal partial class PresentationSource public PixelPoint PointToScreen(Point point) => PlatformImpl?.PointToScreen(point) ?? default; public Point PointToClient(PixelPoint point) => PlatformImpl?.PointToClient(point) ?? default; -} \ No newline at end of file + + private void HandleScalingChanged(double scaling) + => RenderScaling = LayoutHelper.ValidateScaling(scaling); +} diff --git a/src/Avalonia.Controls/PresentationSource/PresentationSource.cs b/src/Avalonia.Controls/PresentationSource/PresentationSource.cs index 9917f82c93..aad1bc1003 100644 --- a/src/Avalonia.Controls/PresentationSource/PresentationSource.cs +++ b/src/Avalonia.Controls/PresentationSource/PresentationSource.cs @@ -29,12 +29,15 @@ internal partial class PresentationSource : IPresentationSource, IInputRoot, IDi PlatformImpl = platformImpl; - + _inputManager = TryGetService(dependencyResolver); _handleInputCore = HandleInputCore; PlatformImpl.SetInputRoot(this); PlatformImpl.Input = HandleInput; + + RenderScaling = LayoutHelper.ValidateScaling(platformImpl.RenderScaling); + PlatformImpl.ScalingChanged += HandleScalingChanged; _pointerOverPreProcessor = new PointerOverPreProcessor(this); _pointerOverPreProcessorSubscription = _inputManager?.PreProcess.Subscribe(_pointerOverPreProcessor); @@ -83,6 +86,7 @@ internal partial class PresentationSource : IPresentationSource, IInputRoot, IDi // We need to wait for the renderer to complete any in-flight operations Renderer.Dispose(); + PlatformImpl?.ScalingChanged -= HandleScalingChanged; PlatformImpl = null; _pointerOverPreProcessor?.OnCompleted(); _pointerOverPreProcessorSubscription?.Dispose(); diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index ceb9590564..69d32d2b56 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -215,7 +215,7 @@ namespace Avalonia.Controls impl, dependencyResolver, () => ClientSize); _source.Renderer.SceneInvalidated += SceneInvalidated; - _scaling = ValidateScaling(impl.RenderScaling); + _scaling = LayoutHelper.ValidateScaling(impl.RenderScaling); _actualTransparencyLevel = PlatformImpl.TransparencyLevel; @@ -234,7 +234,7 @@ namespace Avalonia.Controls impl.Closed = HandleClosed; impl.Paint = HandlePaint; impl.Resized = HandleResized; - impl.ScalingChanged = HandleScalingChanged; + impl.ScalingChanged += HandleScalingChanged; impl.TransparencyLevelChanged = HandleTransparencyLevelChanged; CreatePlatformImplBinding(TransparencyLevelHintProperty, hint => PlatformImpl.SetTransparencyLevelHint(hint ?? Array.Empty())); @@ -709,7 +709,7 @@ namespace Avalonia.Controls /// The window scaling. private void HandleScalingChanged(double scaling) { - _scaling = ValidateScaling(scaling); + _scaling = LayoutHelper.ValidateScaling(scaling); LayoutHelper.InvalidateSelfAndChildrenMeasure(this); Dispatcher.UIThread.Send(_ => ScalingChanged?.Invoke(this, EventArgs.Empty)); @@ -833,23 +833,5 @@ namespace Avalonia.Controls { // Do nothing becuase TopLevel should't apply MirrorTransform on himself. } - - private double ValidateScaling(double scaling) - { - if (MathUtilities.IsNegativeOrNonFinite(scaling) || MathUtilities.IsZero(scaling)) - { - throw new InvalidOperationException( - $"Invalid {nameof(ITopLevelImpl.RenderScaling)} value {scaling} returned from {PlatformImpl?.GetType()}"); - } - - if (MathUtilities.IsOne(scaling)) - { - // Ensure we've got exactly 1.0 and not an approximation, - // so we don't have to use MathUtilities.IsOne in various layout hot paths. - return 1.0; - } - - return scaling; - } } } diff --git a/tests/Avalonia.Base.UnitTests/Layout/LayoutHelperTests.cs b/tests/Avalonia.Base.UnitTests/Layout/LayoutHelperTests.cs index 3df31939d1..af71e7206d 100644 --- a/tests/Avalonia.Base.UnitTests/Layout/LayoutHelperTests.cs +++ b/tests/Avalonia.Base.UnitTests/Layout/LayoutHelperTests.cs @@ -24,5 +24,31 @@ namespace Avalonia.Base.UnitTests.Layout var actualValue = LayoutHelper.RoundLayoutValue(value, dpiScale); Assert.Equal(expectedValue, actualValue); } + + [Fact] + public void ValidateScaling_Returns_Exact_One_For_Approximate_One() + { + var result = LayoutHelper.ValidateScaling(1.000000000000001); + Assert.Equal(1.0, result); + } + + [Fact] + public void ValidateScaling_Returns_Valid_Scaling_Value() + { + const double scaling = 1.5; + var result = LayoutHelper.ValidateScaling(scaling); + Assert.Equal(scaling, result); + } + + [Theory] + [InlineData(0.0)] + [InlineData(-1.5)] + [InlineData(double.NaN)] + [InlineData(double.PositiveInfinity)] + [InlineData(double.NegativeInfinity)] + public void ValidateScaling_Throws_For_Invalid_Values(double scaling) + { + Assert.Throws(() => LayoutHelper.ValidateScaling(scaling)); + } } }