diff --git a/src/Avalonia.Base/Media/DrawingContext.cs b/src/Avalonia.Base/Media/DrawingContext.cs index c55d6de10f..5d258cb040 100644 --- a/src/Avalonia.Base/Media/DrawingContext.cs +++ b/src/Avalonia.Base/Media/DrawingContext.cs @@ -283,7 +283,8 @@ namespace Avalonia.Media Opacity, Clip, GeometryClip, - OpacityMask + OpacityMask, + RenderOptions } public RestoreState(DrawingContext context, PushedStateType type) @@ -308,6 +309,8 @@ namespace Avalonia.Media _context.PopGeometryClipCore(); else if (_type == PushedStateType.OpacityMask) _context.PopOpacityMaskCore(); + else if (_type == PushedStateType.RenderOptions) + _context.PopRenderOptionsCore(); } } @@ -400,6 +403,20 @@ namespace Avalonia.Media return new PushedState(this); } + /// + /// Pushes render options. + /// + /// The render options. + /// A disposable to undo the render options. + public PushedState PushRenderOptions(RenderOptions renderOptions) + { + PushRenderOptionsCore(renderOptions); + _states ??= StateStackPool.Get(); + _states.Push(new RestoreState(this, RestoreState.PushedStateType.RenderOptions)); + return new PushedState(this); + } + protected abstract void PushRenderOptionsCore(RenderOptions renderOptions); + [Obsolete("Use PushTransform"), EditorBrowsable(EditorBrowsableState.Never)] public PushedState PushPreTransform(Matrix matrix) => PushTransform(matrix); [Obsolete("Use PushTransform"), EditorBrowsable(EditorBrowsableState.Never)] @@ -415,6 +432,7 @@ namespace Avalonia.Media protected abstract void PopOpacityCore(); protected abstract void PopOpacityMaskCore(); protected abstract void PopTransformCore(); + protected abstract void PopRenderOptionsCore(); private static bool PenIsVisible(IPen? pen) { diff --git a/src/Avalonia.Base/Media/DrawingGroup.cs b/src/Avalonia.Base/Media/DrawingGroup.cs index 1f3c74c51c..7299bff850 100644 --- a/src/Avalonia.Base/Media/DrawingGroup.cs +++ b/src/Avalonia.Base/Media/DrawingGroup.cs @@ -53,6 +53,8 @@ namespace Avalonia.Media set => SetValue(OpacityMaskProperty, value); } + internal RenderOptions? RenderOptions { get; set; } + /// /// Gets or sets the collection that contains the child geometries. /// @@ -75,6 +77,7 @@ namespace Avalonia.Media using (context.PushOpacity(Opacity)) using (ClipGeometry != null ? context.PushGeometryClip(ClipGeometry) : default) using (OpacityMask != null ? context.PushOpacityMask(OpacityMask, bounds) : default) + using (RenderOptions != null ? context.PushRenderOptions(RenderOptions.Value) : default) { foreach (var drawing in Children) { @@ -313,6 +316,15 @@ namespace Avalonia.Media drawingGroup.Transform = new MatrixTransform(matrix); } + protected override void PushRenderOptionsCore(RenderOptions renderOptions) + { + // Instantiate a new drawing group and set it as the _currentDrawingGroup + var drawingGroup = PushNewDrawingGroup(); + + // Set the render options on the new DrawingGroup + drawingGroup.RenderOptions = renderOptions; + } + protected override void PopClipCore() => Pop(); protected override void PopGeometryClipCore() => Pop(); @@ -323,6 +335,8 @@ namespace Avalonia.Media protected override void PopTransformCore() => Pop(); + protected override void PopRenderOptionsCore() => Pop(); + /// /// Creates a new DrawingGroup for a Push* call by setting the /// _currentDrawingGroup to a newly instantiated DrawingGroup, diff --git a/src/Avalonia.Base/Media/PlatformDrawingContext.cs b/src/Avalonia.Base/Media/PlatformDrawingContext.cs index b81582f845..410d996db2 100644 --- a/src/Avalonia.Base/Media/PlatformDrawingContext.cs +++ b/src/Avalonia.Base/Media/PlatformDrawingContext.cs @@ -90,6 +90,8 @@ internal sealed class PlatformDrawingContext : DrawingContext _impl.Transform = matrix * current; } + protected override void PushRenderOptionsCore(RenderOptions renderOptions) => _impl.PushRenderOptions(renderOptions); + protected override void PopClipCore() => _impl.PopClip(); protected override void PopGeometryClipCore() => _impl.PopGeometryClip(); @@ -102,6 +104,8 @@ internal sealed class PlatformDrawingContext : DrawingContext _impl.Transform = (_transforms ?? throw new ObjectDisposedException(nameof(PlatformDrawingContext))).Pop(); + protected override void PopRenderOptionsCore() => _impl.PopRenderOptions(); + protected override void DisposeCore() { if (_ownsImpl) diff --git a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs index b52378d3b3..fe411c350d 100644 --- a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs @@ -160,6 +160,17 @@ namespace Avalonia.Platform /// void PopGeometryClip(); + /// + /// Pushes render options. + /// + /// The render options. + void PushRenderOptions(RenderOptions renderOptions); + + /// + /// Pops the latest render options. + /// + void PopRenderOptions(); + /// /// Attempts to get an optional feature from the drawing context implementation. /// diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodes.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodes.cs index 2d3b5b0f22..44c8f66bb3 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodes.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodes.cs @@ -211,3 +211,18 @@ abstract class RenderDataBrushAndPenNode : IRenderDataItemWithServerResources public abstract Rect? Bounds { get; } public abstract bool HitTest(Point p); } + +class RenderDataRenderOptionsNode : RenderDataPushNode +{ + public RenderOptions RenderOptions { get; set; } + + public override void Push(ref RenderDataNodeRenderContext context) + { + context.Context.PushRenderOptions(RenderOptions); + } + + public override void Pop(ref RenderDataNodeRenderContext context) + { + context.Context.PopRenderOptions(); + } +} diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs index e7b14f138d..870a084d31 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs @@ -259,6 +259,12 @@ internal class RenderDataDrawingContext : DrawingContext }); } + protected override void PushRenderOptionsCore(RenderOptions renderOptions) => Push(new RenderDataRenderOptionsNode() + { + RenderOptions = renderOptions + }); + + protected override void PopClipCore() => Pop(); protected override void PopGeometryClipCore() => Pop(); @@ -269,6 +275,8 @@ internal class RenderDataDrawingContext : DrawingContext protected override void PopTransformCore() => Pop(); + protected override void PopRenderOptionsCore() => Pop(); + internal override void DrawBitmap(IRef? source, double opacity, Rect sourceRect, Rect destRect) { if (source == null || sourceRect.IsEmpty() || destRect.IsEmpty()) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs index 61f8c8451f..56a572b343 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs @@ -122,6 +122,11 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, _impl.PushOpacityMask(mask, bounds); } + public void PushRenderOptions(RenderOptions renderOptions) + { + _impl.PushRenderOptions(renderOptions); + } + public void PopOpacityMask() { _impl.PopOpacityMask(); @@ -137,6 +142,11 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, _impl.PopGeometryClip(); } + public void PopRenderOptions() + { + _impl.PopRenderOptions(); + } + public object? GetFeature(Type t) => _impl.GetFeature(t); diff --git a/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 7293874671..b03ae8a2ca 100644 --- a/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -542,6 +542,16 @@ namespace Avalonia.Headless { } + + public void PushRenderOptions(RenderOptions renderOptions) + { + + } + + public void PopRenderOptions() + { + + } } private class HeadlessRenderTarget : IRenderTarget diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs index f95d086bf6..4d93408ce8 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs @@ -1,6 +1,7 @@ using System.Linq; using XamlX.Ast; using XamlX.Transform; +using XamlX.Transform.Transformers; using XamlX.TypeSystem; namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers @@ -22,26 +23,19 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers IXamlAstTypeReference targetType; var templatableBaseType = context.Configuration.TypeSystem.GetType("Avalonia.Controls.Control"); - - if ((tt?.Values.FirstOrDefault() is XamlTypeExtensionNode tn)) - { - targetType = tn.Value; - } - else + + targetType = tt?.Values.FirstOrDefault() switch { - var parentScope = context.ParentNodes().OfType() - .FirstOrDefault(); - if (parentScope?.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style) - targetType = parentScope.TargetType; - else if (context.ParentNodes().Skip(1).FirstOrDefault() is XamlAstObjectNode directParentNode - && templatableBaseType.IsAssignableFrom(directParentNode.Type.GetClrType())) - targetType = directParentNode.Type; - else - targetType = new XamlAstClrTypeReference(node, - templatableBaseType, false); - } - - + XamlTypeExtensionNode tn => tn.Value, + XamlAstTextNode textNode => TypeReferenceResolver.ResolveType(context, textNode.Text, false, textNode, true), + _ when context.ParentNodes() + .OfType() + .FirstOrDefault() is { ScopeType: AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style } parentScope => parentScope.TargetType, + _ when context.ParentNodes().Skip(1).FirstOrDefault() is XamlAstObjectNode directParentNode + && templatableBaseType.IsAssignableFrom(directParentNode.Type.GetClrType()) => directParentNode.Type, + _ => new XamlAstClrTypeReference(node, + templatableBaseType, false) + }; return new AvaloniaXamlIlTargetTypeMetadataNode(on, targetType, AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate); @@ -59,7 +53,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers ControlTemplate, Transitions } - + public AvaloniaXamlIlTargetTypeMetadataNode(IXamlAstValueNode value, IXamlAstTypeReference targetType, ScopeTypes type) : base(value, value) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 76d236e18a..5736b7110c 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -23,6 +23,7 @@ namespace Avalonia.Skia private readonly Vector _dpi; private readonly Stack _maskStack = new(); private readonly Stack _opacityStack = new(); + private readonly Stack _renderOptionsStack = new(); private readonly Matrix? _postTransform; private double _currentOpacity = 1.0f; private readonly bool _disableSubpixelTextRendering; @@ -634,6 +635,21 @@ namespace Avalonia.Skia _currentOpacity = _opacityStack.Pop(); } + /// + public void PushRenderOptions(RenderOptions renderOptions) + { + CheckLease(); + + _renderOptionsStack.Push(RenderOptions); + + RenderOptions = RenderOptions.MergeWith(renderOptions); + } + + public void PopRenderOptions() + { + RenderOptions = _renderOptionsStack.Pop(); + } + /// public virtual void Dispose() { diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 5579278f21..8b288598ab 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -24,6 +24,8 @@ namespace Avalonia.Direct2D1.Media private readonly SharpDX.DXGI.SwapChain1 _swapChain; private readonly Action _finishedCallback; + private readonly Stack _renderOptionsStack = new(); + /// /// Initializes a new instance of the class. /// @@ -488,6 +490,18 @@ namespace Avalonia.Direct2D1.Media PopLayer(); } + public void PushRenderOptions(RenderOptions renderOptions) + { + _renderOptionsStack.Push(RenderOptions); + + RenderOptions = RenderOptions.MergeWith(renderOptions); + } + + public void PopRenderOptions() + { + RenderOptions = _renderOptionsStack.Pop(); + } + private void PopLayer() { var layer = _layers.Pop(); diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 9244da5064..9650c44bef 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -49,7 +49,6 @@ namespace Avalonia.Win32 public Win32Platform() { - SetDpiAwareness(); CreateMessageWindow(); _dispatcher = new Win32DispatcherImpl(_hwnd); } @@ -80,6 +79,9 @@ namespace Avalonia.Win32 public static void Initialize(Win32PlatformOptions options) { s_options = options; + + SetDpiAwareness(); + var renderTimer = options.ShouldRenderOnUIThread ? new UiThreadRenderTimer(60) : new DefaultRenderTimer(60); AvaloniaLocator.CurrentMutable @@ -264,12 +266,31 @@ namespace Avalonia.Win32 var user32 = LoadLibrary("user32.dll"); var method = GetProcAddress(user32, nameof(SetProcessDpiAwarenessContext)); + var dpiAwareness = Options.DpiAwareness; + if (method != IntPtr.Zero) { - if (SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) || - SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) + if (dpiAwareness == Win32DpiAwareness.Unaware) { - return; + if (SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE)) + { + return; + } + } + else if (dpiAwareness == Win32DpiAwareness.SystemDpiAware) + { + if (SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE)) + { + return; + } + } + else if (dpiAwareness == Win32DpiAwareness.PerMonitorDpiAware) + { + if (SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) || + SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) + { + return; + } } } @@ -278,11 +299,20 @@ namespace Avalonia.Win32 if (method != IntPtr.Zero) { - SetProcessDpiAwareness(PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE); + var awareness = (dpiAwareness) switch + { + Win32DpiAwareness.Unaware => PROCESS_DPI_AWARENESS.PROCESS_DPI_UNAWARE, + Win32DpiAwareness.SystemDpiAware => PROCESS_DPI_AWARENESS.PROCESS_SYSTEM_DPI_AWARE, + Win32DpiAwareness.PerMonitorDpiAware => PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE, + _ => PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE, + }; + + SetProcessDpiAwareness(awareness); return; } - SetProcessDPIAware(); + if (dpiAwareness != Win32DpiAwareness.Unaware) + SetProcessDPIAware(); } } } diff --git a/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs b/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs index bbb4c37d7e..eac18dc080 100644 --- a/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs +++ b/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs @@ -25,6 +25,27 @@ public enum Win32RenderingMode Wgl = 3 } +/// +/// Represents the DPI Awareness for the application. +/// +public enum Win32DpiAwareness +{ + /// + /// The application is DPI unaware. + /// + Unaware, + + /// + /// The application is system DPI aware. It will query DPI once and will not adjust to new DPI changes + /// + SystemDpiAware, + + /// + /// The application is per-monitor DPI aware. It adjust its scale factor whenever DPI changes. + /// + PerMonitorDpiAware +} + /// /// Represents the Win32 window composition mode. /// @@ -137,4 +158,9 @@ public class Win32PlatformOptions /// and only accepts null or . /// public IPlatformGraphics? CustomPlatformGraphics { get; set; } + + /// + /// Gets or sets the application's DPI awareness. + /// + public Win32DpiAwareness DpiAwareness { get; set; } = Win32DpiAwareness.PerMonitorDpiAware; } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs index ea2407e18d..e21f6cd276 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs @@ -286,6 +286,24 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.IsType(typeof(ContentPresenter), template.Build(new ContentControl()).Result); } + [Fact] + public void ControlTemplate_With_String_TargetType() + { + var xaml = @" + + + +"; + var template = AvaloniaRuntimeXamlLoader.Parse(xaml); + + Assert.Equal(typeof(ContentControl), template.TargetType); + + Assert.IsType(typeof(ContentPresenter), template.Build(new ContentControl()).Result); + } + + [Fact] public void ControlTemplate_With_Panel_Children_Are_Added() {