Skip to content

Commit

Permalink
feat: Port RootScale and related classes
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinZikmund committed Apr 22, 2024
1 parent 983501b commit 30ab0b4
Show file tree
Hide file tree
Showing 6 changed files with 429 additions and 27 deletions.
14 changes: 14 additions & 0 deletions src/Uno.UI/UI/Xaml/Internal/Inlined.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace Uno.UI.Xaml.Internal;

internal static class Inlined
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool IsCloseReal(float a, float b) => Math.Abs(a - b) < float.Epsilon; // TODO Uno: The original logic is more complex, but might not be necessary in Uno.
}
27 changes: 0 additions & 27 deletions src/Uno.UI/UI/Xaml/Internal/RootScale.cs

This file was deleted.

54 changes: 54 additions & 0 deletions src/Uno.UI/UI/Xaml/Internal/Scaling/CoreWindowRootScale.mux.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
namespace Uno.UI.Xaml.Core.Scaling;

internal class CoreWindowRootScale : RootScale
{
public CoreWindowRootScale(RootScaleConfig config, CoreServices coreServices, VisualTree visualTree) :
base(config, coreServices, visualTree)
{
}

protected override void ApplyScaleProtected(bool scaleChanged)
{
//var mainRootVisual = _coreServices.MainRootVisual;
//if (mainRootVisual is not null)
//{
// float scale = GetRootVisualScale();
// // The composition subsystem has installed a scale transform on top of our tree, update our tree to be aware of it
// // This ensures that Xaml will know about the physical DPI when needed, e.g. rendering crisp text.
// mainRootVisual.RasterizationScale = scale;

// //const auto connectedAnimationRoot = m_pCoreServices->GetConnectedAnimationRoot();
// //if (connectedAnimationRoot)
// //{
// // // Plateau scale has been applied on the island, and we need to cancel it here,
// // // because the snapshots are created using the pixel size including the plateau scale,
// // // and we don't want double scaling.
// // CValue inverseScaleTransform;
// // IFC_RETURN(CreateReverseTransform(&inverseScaleTransform));
// // IFC_RETURN(connectedAnimationRoot->SetValueByKnownIndex(KnownPropertyIndex::UIElement_RenderTransform, inverseScaleTransform));
// //}
//}

//if (scaleChanged)
//{
// // We need to force re-layout and re-render only if the scale has actually changed.
// m_pCoreServices->MarkRootScaleTransformDirty();

// // Warning: This will not work correctly in the Context of AppWindows
// // Task 18843113: Do not use global MRT resource manager, instead add a new MRT Resource instance on each CRootVisualInstanc
// {
// const unsigned int scalePercentage = XcpRound(GetEffectiveRasterizationScale() * 100.0f);
// // Update the scale factor on the resource manager.
// xref_ptr<IPALResourceManager> resourceManager;
// IFC_RETURN(m_pCoreServices->GetResourceManager(resourceManager.ReleaseAndGetAddressOf()));
// IFC_RETURN(resourceManager->SetScaleFactor(scalePercentage));

// // Flush the XAML parser cache. If the application to reload XAML after a scale change, we want to
// // re-query MRT for the XAML resource, potentially picking up a new resource for the new scale.
// std::shared_ptr<XamlNodeStreamCacheManager> spXamlNodeStreamCacheManager;
// IFC_RETURN(m_pCoreServices->GetXamlNodeStreamCacheManager(spXamlNodeStreamCacheManager));
// spXamlNodeStreamCacheManager->Flush();
// }
//}
}
}
82 changes: 82 additions & 0 deletions src/Uno.UI/UI/Xaml/Internal/Scaling/RootScale.h.mux.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
// MUX Reference dxaml\xcp\components\scaling\inc\RootScale.h, tag winui3/release/1.5.1

#nullable enable

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.UI.Content;
using Microsoft.UI.Xaml;

namespace Uno.UI.Xaml.Core.Scaling;

internal enum RootScaleConfig
{
// Parent scale is identity or it expects the root visual tree to apply system DPI scale itself.
ParentInvert,
// Parent scale already applies the system DPI scale, so need to apply in the internal root visual tree.
ParentApply,
}

partial class RootScale
{
internal bool IsInitialized => _initialized;

internal static float GetRasterizationScaleForContentRoot(ContentRoot? coreContextRoot)
{
if (GetRootScaleForElement(coreContextRoot) is { } rootScale)
{
return rootScale.GetEffectiveRasterizationScale();
}
return 1.0f;
}

internal static float GetRasterizationScaleForElement(DependencyObject pDO)
{
if (GetRootScaleForElement(pDO) is { } rootScale)
{
return rootScale.GetEffectiveRasterizationScale();
}
return 1.0f;
}

internal static float GetRasterizationScaleForElementWithFallback(DependencyObject pDO)
{
var rootScale = GetRootScaleForElementWithFallback(pDO);
if (rootScale is not null)
{
return rootScale.GetEffectiveRasterizationScale();
}
return 1.0f;
}

protected abstract void ApplyScaleProtected(bool scaleChanged);

private protected VisualTree? VisualTree => _visualTree;

internal enum ScaleKind
{
System,
Test,
}

private readonly RootScaleConfig _config;
// The system DPI, this is accumulated scale,
// tipically this is control by the Display Settings app.
private float _systemScale = 1.0f;
// Used only for testing, it replaces the system DPI scale with a value
// This can only be used when config is RootScaleConfig::ParentInvert
private float _testOverrideScale;
private readonly List<DisplayListener> _displayListeners = new();
private readonly VisualTree _visualTree;
private bool _initialized;
private bool _updating;
private ImageReloadManager? _imageReloadManager;
private ContentIsland? _content; // IExpCompositionContent

protected readonly CoreServices _coreServices;
}
213 changes: 213 additions & 0 deletions src/Uno.UI/UI/Xaml/Internal/Scaling/RootScale.mux.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

#nullable enable

using Microsoft.UI.Content;
using Microsoft.UI.Xaml;
using Uno.Disposables;
using static Uno.UI.Xaml.Internal.Inlined;

namespace Uno.UI.Xaml.Core.Scaling;

internal partial class RootScale
{
public RootScale(RootScaleConfig config, CoreServices coreServices, VisualTree visualTree)
{
_config = config;
_visualTree = visualTree;
_coreServices = coreServices;
}

// TODO Uno: Implement
//~RootScale()
//{
// // It's OK to still have some displayListeners at this point. When a test is shutting down XAML, we'll
// // destroy this object, but we may still have a CLoadedImageSurface object in this list. Since we're shutting
// // down, there won't be new scale changes anyway.
// _displayListeners.Clear();
// _imageReloadManager.ClearImages();
//}

private float GetSystemScale()
{
float rasterizationScale = 1.0f;
if (_content is not null)
{
// For CoreWindow scenarios, the CompositionContent is also listening for the CoreWindow's closed event.
// CompositionContent will get the notification first and close the entire visual tree, then Xaml will
// exit its message loop and tear down the tree. Since CompositionContent already closed everything,
// Xaml will get lots of RO_E_CLOSED errors. These are all safe to ignore. So tolerate RO_E_CLOSED if
// we're also in the middle of tearing down the tree.
rasterizationScale = _content.RasterizationScale;
}

return rasterizationScale;
}

internal void SetContentIsland(ContentIsland? content) => _content = content;

private void UpdateSystemScale()
{
// Remove SuspendFailFastOnStowedException
// Bug 19696972: QueryScaleFactor silently fails at statup
// SuspendFailFastOnStowedException raiiSuspender;
var systemScale = GetSystemScale();
if (systemScale != 0.0f)
{
SetSystemScale(systemScale);
}
}

private float GetEffectiveRasterizationScale()
{
if (!IsInitialized && !_updating)
{
UpdateSystemScale();
}

// A testOverrideScale of 0 means there's no override; just use the systemScale
float effectiveScale = _testOverrideScale == 0.0f ? _systemScale : _testOverrideScale;
return effectiveScale;
}

protected float GetRootVisualScale()
{
// In XamlOneCoreTransforms mode, there is no need to do a RenderTransform on the root, because the scale has already been
// applied for us by the CompositionIsland. However, due to legacy reasons, our DComp tests has a dependency that, even when the scale is 1,
// a RenderTransform is still applied on the root (Identity). To support these tests, we will always apply a scale transform on the root
// in XamlOneCoreTransforms mode. When we've enabled XamlOneCoreTransforms mode by default, we can break this dependency and
// update the tests to not expect an Identity RenderTransform set on the root.
// In OneCoreTransforms mode, there's already a scale applied to XAML visuals matching the systemScale, so we factor that scale
// out on the XAML content.
float newRootVisualScale = 0.0f;
float effectiveScale = GetEffectiveRasterizationScale();
if (_config == RootScaleConfig.ParentApply)
{
// This is the case where we're pushing a non-identity scale into the root visual
newRootVisualScale = effectiveScale / _systemScale;
}
else
{
newRootVisualScale = effectiveScale;
}
return newRootVisualScale;
}

private void SetTestOverride(float scale) => SetScale(scale, ScaleKind.Test);

private void SetSystemScale(float scale) => SetScale(scale, ScaleKind.System);

private void SetScale(float scale, RootScale.ScaleKind kind)
{
_updating = true;
using var cleanup = Disposable.Create(() =>
{
_updating = false;
});

float oldScale = GetEffectiveRasterizationScale();
bool scaleIsValid = scale != 0.0f;
switch (kind)
{
case RootScale.ScaleKind.System:
if (scaleIsValid)
{
_systemScale = scale;
}
break;
case RootScale.ScaleKind.Test:
_testOverrideScale = scale;
break;
}
float newScale = GetEffectiveRasterizationScale();
bool scaleChanged = !IsCloseReal(oldScale, newScale);
ApplyScale(scaleChanged);
_initialized = true;
}

private void ApplyScale() => ApplyScale(false);

private void ApplyScale(bool scaleChanged)
{
ApplyScaleProtected(scaleChanged);

if (scaleChanged)
{
foreach (var displayListener in _displayListeners)
{
displayListener.OnScaleChanged();
}

// TODO Uno: Reload images on scale change!
//if (IsInitialized())
//{
// // Reload images.
// m_imageReloadManager.ReloadImages(ResourceInvalidationReason.ScaleChanged);
//}

VisualTree.ContentRoot.AddPendingXamlRootChangedEvent(ContentRoot.ChangeType.RasterizationScale);
}
}

private void AddDisplayListener(DisplayListener displayListener)
{
MUX_ASSERT(!_displayListeners.Contains(displayListener));
_displayListeners.Add(displayListener);
}

private void RemoveDisplayListener(DisplayListener displayListener)
{
MUX_ASSERT(_displayListeners.Count(d => d == displayListener) == 1);
_displayListeners.Remove(displayListener);
}

//CImageReloadManager& RootScale::GetImageReloadManager()
//{
// return m_imageReloadManager;
//}

private RootScale? GetRootScaleForElement(DependencyObject pDO)
{
if (VisualTree.GetContentRootForElement(pDO) is { } contentRoot)
{
return GetRootScaleForContentRoot(contentRoot);
}

return null;
}

private RootScale? GetRootScaleForContentRoot(ContentRoot contentRoot)
{
if (contentRoot is not null)
{
if (contentRoot.VisualTree is { } visualTree)
{
return visualTree.RootScale;
}
}

return null;
}

private RootScale? GetRootScaleForElementWithFallback(DependencyObject? pDO)
{
RootScale? result = null;
if (pDO is not null)
{
result = GetRootScaleForElement(pDO);
}

if (result is null)
{
var coreServices = CoreServices.Instance; // TODO Uno: This should be DXamlServices::GetHandle()
var contentRootCoordinator = coreServices.ContentRootCoordinator;
if (contentRootCoordinator.CoreWindowContentRoot is { } root)
{
result = GetRootScaleForContentRoot(root);
}
}

return result;
}
}
Loading

0 comments on commit 30ab0b4

Please sign in to comment.