Browse Source

Cache RenderScaling in PresentationSource (#20972)

* Cache RenderScaling in PresentationSource

* Unsubscribe from PlatformImpl.ScalingChanged

* Added tests for ValidateScaling
pull/20993/head
Julien Lebosquain 4 days ago
committed by GitHub
parent
commit
d323efb668
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 15
      src/Avalonia.Base/Layout/LayoutHelper.cs
  2. 10
      src/Avalonia.Controls/PresentationSource/PresentationSource.RenderRoot.cs
  3. 6
      src/Avalonia.Controls/PresentationSource/PresentationSource.cs
  4. 24
      src/Avalonia.Controls/TopLevel.cs
  5. 26
      tests/Avalonia.Base.UnitTests/Layout/LayoutHelperTests.cs

15
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;
}
}
}

10
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;
}
private void HandleScalingChanged(double scaling)
=> RenderScaling = LayoutHelper.ValidateScaling(scaling);
}

6
src/Avalonia.Controls/PresentationSource/PresentationSource.cs

@ -29,12 +29,15 @@ internal partial class PresentationSource : IPresentationSource, IInputRoot, IDi
PlatformImpl = platformImpl;
_inputManager = TryGetService<IInputManager>(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();

24
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<WindowTransparencyLevel>()));
@ -709,7 +709,7 @@ namespace Avalonia.Controls
/// <param name="scaling">The window scaling.</param>
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;
}
}
}

26
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<InvalidOperationException>(() => LayoutHelper.ValidateScaling(scaling));
}
}
}

Loading…
Cancel
Save