From c97f03e095720981e3552b2f1b7bbbdc85df36a1 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 16 Jan 2023 12:04:47 +0100 Subject: [PATCH 01/12] fix: CS0436 conflicts with the imported type --- src/Avalonia.Native/Avalonia.Native.csproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj index 93fba4a57c..e69c39a41e 100644 --- a/src/Avalonia.Native/Avalonia.Native.csproj +++ b/src/Avalonia.Native/Avalonia.Native.csproj @@ -16,10 +16,6 @@ PreserveNewest - - - - From a82de4e3fe74a869da808e5cbc3e947bc433d9c0 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 16 Jan 2023 12:42:13 +0100 Subject: [PATCH 02/12] fix: Warning CS0436 ModuleInitializerAttribute conflicts with the imported type --- src/Avalonia.Native/Avalonia.Native.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj index e69c39a41e..095662a538 100644 --- a/src/Avalonia.Native/Avalonia.Native.csproj +++ b/src/Avalonia.Native/Avalonia.Native.csproj @@ -26,4 +26,8 @@ + + + + From 6bdf0eacc7bdf6d7e2b73042ed1d3a0a380e22d9 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 25 Jan 2023 15:52:45 +0600 Subject: [PATCH 03/12] Removed legacy renderers --- samples/ControlCatalog.NetCore/Program.cs | 3 +- samples/RenderDemo/App.xaml.cs | 4 - .../Avalonia.Android/AndroidPlatform.cs | 17 +- .../Platform/SkiaPlatform/TopLevelImpl.cs | 11 +- src/Avalonia.Base/Avalonia.Base.csproj | 4 - .../TextInput/TransformTrackingHelper.cs | 8 + src/Avalonia.Base/Point.cs | 6 + src/Avalonia.Base/Rect.cs | 27 + .../Composition/CompositingRenderer.cs | 15 +- .../Composition/CompositionDrawListVisual.cs | 3 - .../Composition/CompositionTarget.cs | 9 +- .../Rendering/Composition/Compositor.cs | 3 + .../Rendering/DeferredRenderer.cs | 780 ------------ src/Avalonia.Base/Rendering/ICustomHitTest.cs | 16 + .../Rendering/ICustomSimpleHitTest.cs | 33 - src/Avalonia.Base/Rendering/IRenderRoot.cs | 6 - .../Rendering/IRendererFactory.cs | 16 - .../Rendering/ImmediateRenderer.cs | 248 +--- .../PlatformRenderInterfaceContextManager.cs | 4 +- src/Avalonia.Base/Rendering/RenderLayer.cs | 48 - src/Avalonia.Base/Rendering/RenderLayers.cs | 69 -- .../SceneGraph/DeferredDrawingContextImpl.cs | 482 -------- .../Rendering/SceneGraph/ISceneBuilder.cs | 24 - .../Rendering/SceneGraph/IVisualNode.cs | 105 -- .../Rendering/SceneGraph/Scene.cs | 352 ------ .../Rendering/SceneGraph/SceneBuilder.cs | 485 -------- .../Rendering/SceneGraph/SceneLayer.cs | 73 -- .../Rendering/SceneGraph/SceneLayers.cs | 206 ---- .../Rendering/SceneGraph/VisualNode.cs | 448 ------- src/Avalonia.Base/Visual.cs | 18 +- .../Offscreen/OffscreenTopLevelImpl.cs | 18 +- .../WindowNotificationManager.cs | 4 +- .../Platform/DefaultMenuInteractionHandler.cs | 14 +- .../Platform/ITopLevelImpl.cs | 5 - .../Primitives/AdornerLayer.cs | 10 +- .../Primitives/ChromeOverlayLayer.cs | 4 +- .../Primitives/OverlayLayer.cs | 4 +- src/Avalonia.Controls/Primitives/Popup.cs | 16 +- .../Remote/Server/RemoteServerTopLevelImpl.cs | 32 +- src/Avalonia.Controls/TopLevel.cs | 6 - src/Avalonia.DesignerSupport/Remote/Stubs.cs | 14 +- .../Diagnostics/Conventions.cs | 14 +- .../Views/LayoutExplorerView.axaml.cs | 32 +- .../Diagnostics/VisualExtensions.cs | 8 +- .../HeadlessVncPlatformExtensions.cs | 1 - .../AvaloniaHeadlessPlatform.cs | 4 +- src/Avalonia.Headless/HeadlessWindowImpl.cs | 7 +- src/Avalonia.Native/AvaloniaNativePlatform.cs | 13 +- .../AvaloniaNativePlatformExtensions.cs | 14 - src/Avalonia.Native/WindowImplBase.cs | 23 +- src/Avalonia.X11/X11ImmediateRendererProxy.cs | 120 -- src/Avalonia.X11/X11Platform.cs | 20 +- src/Avalonia.X11/X11Window.cs | 21 +- .../FramebufferToplevelImpl.cs | 11 +- .../LinuxFramebufferPlatform.cs | 4 +- src/Windows/Avalonia.Win32/Win32Platform.cs | 20 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 28 +- .../Composition/CompositionAnimationTests.cs | 3 +- .../Rendering/CompositorHitTestingTests.cs | 30 +- .../Rendering/CompositorTestsBase.cs | 173 +-- .../Rendering/CustomHitTestBorder.cs | 3 +- .../Rendering/DeferredRendererTests.cs | 790 ------------ .../DeferredRendererTests_HitTesting.cs | 577 --------- .../Rendering/ImmediateRendererTests.cs | 273 ----- .../ImmediateRendererTests_HitTesting.cs | 458 ------- .../DeferredDrawingContextImplTests.cs | 225 ---- .../Rendering/SceneGraph/SceneBuilderTests.cs | 1087 ----------------- .../SceneGraph/SceneBuilderTests_Layers.cs | 258 ---- .../Rendering/SceneGraph/SceneLayersTests.cs | 35 - .../Rendering/SceneGraph/SceneTests.cs | 33 - .../Rendering/SceneGraph/VisualNodeTests.cs | 123 -- .../VisualTree/TransformedBoundsTests.cs | 52 - .../VisualExtensions_GetVisualsAt.cs | 53 +- .../Controls/AdornerTests.cs | 2 +- tests/Avalonia.RenderTests/TestBase.cs | 27 +- tests/Avalonia.Skia.UnitTests/HitTesting.cs | 71 +- .../Avalonia.UnitTests.csproj | 7 +- .../CompositorTestServices.cs | 207 ++++ ...cs_In_All_Directions.deferred.expected.png | Bin 6332 -> 0 bytes ...cs_In_All_Directions.deferred.expected.png | Bin 5828 -> 0 bytes 80 files changed, 499 insertions(+), 7978 deletions(-) delete mode 100644 src/Avalonia.Base/Rendering/DeferredRenderer.cs create mode 100644 src/Avalonia.Base/Rendering/ICustomHitTest.cs delete mode 100644 src/Avalonia.Base/Rendering/ICustomSimpleHitTest.cs delete mode 100644 src/Avalonia.Base/Rendering/IRendererFactory.cs delete mode 100644 src/Avalonia.Base/Rendering/RenderLayer.cs delete mode 100644 src/Avalonia.Base/Rendering/RenderLayers.cs delete mode 100644 src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs delete mode 100644 src/Avalonia.Base/Rendering/SceneGraph/ISceneBuilder.cs delete mode 100644 src/Avalonia.Base/Rendering/SceneGraph/IVisualNode.cs delete mode 100644 src/Avalonia.Base/Rendering/SceneGraph/Scene.cs delete mode 100644 src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs delete mode 100644 src/Avalonia.Base/Rendering/SceneGraph/SceneLayer.cs delete mode 100644 src/Avalonia.Base/Rendering/SceneGraph/SceneLayers.cs delete mode 100644 src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs delete mode 100644 src/Avalonia.X11/X11ImmediateRendererProxy.cs delete mode 100644 tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests.cs delete mode 100644 tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs delete mode 100644 tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests.cs delete mode 100644 tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs delete mode 100644 tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs delete mode 100644 tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs delete mode 100644 tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs delete mode 100644 tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneLayersTests.cs delete mode 100644 tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneTests.cs delete mode 100644 tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs delete mode 100644 tests/Avalonia.Base.UnitTests/VisualTree/TransformedBoundsTests.cs create mode 100644 tests/Avalonia.UnitTests/CompositorTestServices.cs delete mode 100644 tests/TestFiles/Direct2D1/Media/StreamGeometry/PreciseEllipticArc_Produces_Valid_Arcs_In_All_Directions.deferred.expected.png delete mode 100644 tests/TestFiles/Skia/Media/StreamGeometry/PreciseEllipticArc_Produces_Valid_Arcs_In_All_Directions.deferred.expected.png diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index d5e5cb14dc..e55f003133 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -55,8 +55,7 @@ namespace ControlCatalog.NetCore return builder .UseHeadless(new AvaloniaHeadlessPlatformOptions { - UseHeadlessDrawing = true, - UseCompositor = true + UseHeadlessDrawing = true }) .AfterSetup(_ => { diff --git a/samples/RenderDemo/App.xaml.cs b/samples/RenderDemo/App.xaml.cs index 8f4e02df01..8054b06964 100644 --- a/samples/RenderDemo/App.xaml.cs +++ b/samples/RenderDemo/App.xaml.cs @@ -29,10 +29,6 @@ namespace RenderDemo .With(new Win32PlatformOptions { OverlayPopups = true, - }) - .With(new X11PlatformOptions - { - UseCompositor = true }) .UsePlatformDetect() .LogToTrace(); diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index c73fd92423..daecb58a60 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -32,7 +32,6 @@ namespace Avalonia.Android public static AndroidPlatformOptions Options { get; private set; } internal static Compositor Compositor { get; private set; } - internal static PlatformRenderInterfaceContextManager RenderInterface { get; private set; } public static void Initialize() { @@ -55,16 +54,11 @@ namespace Avalonia.Android EglPlatformGraphics.TryInitialize(); } - if (Options.UseCompositor) - { - Compositor = new Compositor( - AvaloniaLocator.Current.GetRequiredService(), - AvaloniaLocator.Current.GetService()); - } - else - RenderInterface = - new PlatformRenderInterfaceContextManager(AvaloniaLocator.Current - .GetService()); + Compositor = new Compositor( + AvaloniaLocator.Current.GetRequiredService(), + AvaloniaLocator.Current.GetService()); + + } } @@ -72,6 +66,5 @@ namespace Avalonia.Android { public bool UseDeferredRendering { get; set; } = false; public bool UseGpu { get; set; } = true; - public bool UseCompositor { get; set; } = true; } } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 0e452b0bdd..f451fddeb7 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -109,16 +109,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform public IEnumerable Surfaces => new object[] { _gl, _framebuffer, Handle }; public IRenderer CreateRenderer(IRenderRoot root) => - AndroidPlatform.Options.UseCompositor - ? new CompositingRenderer(root, AndroidPlatform.Compositor, () => Surfaces) - : AndroidPlatform.Options.UseDeferredRendering - ? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService(), - () => AndroidPlatform.RenderInterface.CreateRenderTarget(Surfaces), - AndroidPlatform.RenderInterface) - { RenderOnlyOnRenderThread = true } - : new ImmediateRenderer((Visual)root, - () => AndroidPlatform.RenderInterface.CreateRenderTarget(Surfaces), - AndroidPlatform.RenderInterface); + new CompositingRenderer(root, AndroidPlatform.Compositor, () => Surfaces); public virtual void Hide() { diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 35a453ce59..639c27bf03 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -70,8 +70,4 @@ - - - - diff --git a/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs b/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs index 09716b4246..36ee1fff27 100644 --- a/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs +++ b/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs @@ -105,5 +105,13 @@ namespace Avalonia.Input.TextInput UnsubscribeFromParents(); UpdateMatrix(); } + + public static IDisposable Track(Visual visual, Action cb) + { + var rv = new TransformTrackingHelper(); + rv.MatrixChanged += () => cb(visual, rv.Matrix); + rv.SetVisual(visual); + return rv; + } } } diff --git a/src/Avalonia.Base/Point.cs b/src/Avalonia.Base/Point.cs index 0c789ff20f..86f7adf0d1 100644 --- a/src/Avalonia.Base/Point.cs +++ b/src/Avalonia.Base/Point.cs @@ -251,6 +251,12 @@ namespace Avalonia /// The transform. /// The transformed point. public Point Transform(Matrix transform) => transform.Transform(this); + + internal Point Transform(Matrix4x4 matrix) + { + var vec = Vector2.Transform(new Vector2((float)X, (float)Y), matrix); + return new Point(vec.X, vec.Y); + } /// /// Returns a new point with the specified X coordinate. diff --git a/src/Avalonia.Base/Rect.cs b/src/Avalonia.Base/Rect.cs index 831ab28adc..49dc087933 100644 --- a/src/Avalonia.Base/Rect.cs +++ b/src/Avalonia.Base/Rect.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.Numerics; using Avalonia.Animation.Animators; using Avalonia.Utilities; @@ -441,6 +442,32 @@ namespace Avalonia return new Rect(new Point(left, top), new Point(right, bottom)); } + + internal Rect TransformToAABB(Matrix4x4 matrix) + { + ReadOnlySpan points = stackalloc Point[4] + { + TopLeft.Transform(matrix), + TopRight.Transform(matrix), + BottomRight.Transform(matrix), + BottomLeft.Transform(matrix) + }; + + var left = double.MaxValue; + var right = double.MinValue; + var top = double.MaxValue; + var bottom = double.MinValue; + + foreach (var p in points) + { + if (p.X < left) left = p.X; + if (p.X > right) right = p.X; + if (p.Y < top) top = p.Y; + if (p.Y > bottom) bottom = p.Y; + } + + return new Rect(new Point(left, top), new Point(right, bottom)); + } /// /// Translates the rectangle by an offset. diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs index a2209db09f..c5d7ec61e0 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs @@ -83,8 +83,16 @@ public class CompositingRenderer : IRendererWithCompositor } /// - public IEnumerable HitTest(Point p, Visual root, Func? filter) + public IEnumerable HitTest(Point p, Visual? root, Func? filter) { + CompositionVisual? rootVisual = null; + if (root != null) + { + if (root.CompositionVisual == null) + yield break; + rootVisual = root.CompositionVisual; + } + Func? f = null; if (filter != null) f = v => @@ -93,8 +101,8 @@ public class CompositingRenderer : IRendererWithCompositor return filter(dlv.Visual); return true; }; - - var res = CompositionTarget.TryHitTest(p, f); + + var res = CompositionTarget.TryHitTest(p, rootVisual, f); if(res == null) yield break; foreach(var v in res) @@ -257,6 +265,7 @@ public class CompositingRenderer : IRendererWithCompositor _recalculateChildren.Clear(); CompositionTarget.Size = _root.ClientSize; CompositionTarget.Scaling = _root.RenderScaling; + TriggerSceneInvalidatedOnBatchCompletion(_compositor.RequestCommitAsync()); } private async void TriggerSceneInvalidatedOnBatchCompletion(Task batchCompletion) diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs index b019d1792b..f1905fec08 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs @@ -61,9 +61,6 @@ internal class CompositionDrawListVisual : CompositionContainerVisual return false; if (custom != null) { - // Simulate the old behavior - // TODO: Change behavior once legacy renderers are removed - pt += new Point(Offset.X, Offset.Y); return custom.HitTest(pt); } diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs index 0c24d6cd44..f2fa846205 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs @@ -28,16 +28,15 @@ namespace Avalonia.Rendering.Composition /// /// Attempts to perform a hit-tst /// - /// - /// /// - public PooledList? TryHitTest(Point point, Func? filter) + public PooledList? TryHitTest(Point point, CompositionVisual? root, Func? filter) { Server.Readback.NextRead(); - if (Root == null) + root ??= Root; + if (root == null) return null; var res = new PooledList(); - HitTestCore(Root, point, res, filter); + HitTestCore(root, point, res, filter); return res; } diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.cs index bdcbe65403..aea4df525d 100644 --- a/src/Avalonia.Base/Rendering/Composition/Compositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Compositor.cs @@ -37,6 +37,8 @@ namespace Avalonia.Rendering.Composition private List _pendingServerCompositorJobs = new(); internal IEasing DefaultEasing { get; } + + internal event Action? AfterCommit; /// @@ -88,6 +90,7 @@ namespace Avalonia.Rendering.Composition { if (_invokeBeforeCommitWrite.Count > 0) RequestCommitAsync(); + AfterCommit?.Invoke(); } } diff --git a/src/Avalonia.Base/Rendering/DeferredRenderer.cs b/src/Avalonia.Base/Rendering/DeferredRenderer.cs deleted file mode 100644 index f0b993a2b0..0000000000 --- a/src/Avalonia.Base/Rendering/DeferredRenderer.cs +++ /dev/null @@ -1,780 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Avalonia.Logging; -using Avalonia.Media; -using Avalonia.Media.Immutable; -using Avalonia.Platform; -using Avalonia.Rendering.SceneGraph; -using Avalonia.Threading; -using Avalonia.Utilities; -using Avalonia.VisualTree; - -namespace Avalonia.Rendering -{ - /// - /// A renderer which renders the state of the visual tree to an intermediate scene graph - /// representation which is then rendered on a rendering thread. - /// - public class DeferredRenderer : RendererBase, IRenderer, IRenderLoopTask, IVisualBrushRenderer - { - private readonly IDispatcher? _dispatcher; - private readonly IRenderLoop? _renderLoop; - private readonly Func? _renderTargetFactory; - private readonly PlatformRenderInterfaceContextManager? _renderInterface; - private readonly Visual _root; - private readonly ISceneBuilder _sceneBuilder; - - private bool _running; - private bool _disposed; - private volatile IRef? _scene; - private DirtyVisuals? _dirty; - private HashSet? _recalculateChildren; - private IRef? _overlay; - private int _lastSceneId = -1; - private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects(); - private IRef? _currentDraw; - private readonly IDeferredRendererLock _lock; - private readonly object _sceneLock = new object(); - private readonly object _startStopLock = new object(); - private readonly object _renderLoopIsRenderingLock = new object(); - private readonly Action _updateSceneIfNeededDelegate; - private List? _pendingRenderThreadJobs; - - /// - /// Initializes a new instance of the class. - /// - /// The control to render. - /// The render loop. - /// The target render factory. - /// The Platform Render Context. - /// The scene builder to use. Optional. - /// The dispatcher to use. Optional. - /// Lock object used before trying to access render target - public DeferredRenderer( - IRenderRoot root, - IRenderLoop renderLoop, - Func renderTargetFactory, - PlatformRenderInterfaceContextManager? renderInterface = null, - ISceneBuilder? sceneBuilder = null, - IDispatcher? dispatcher = null, - IDeferredRendererLock? rendererLock = null) : base(true) - { - _dispatcher = dispatcher ?? Dispatcher.UIThread; - _root = root as Visual ?? throw new ArgumentNullException(nameof(root)); - _sceneBuilder = sceneBuilder ?? new SceneBuilder(); - Layers = new RenderLayers(); - _renderLoop = renderLoop; - _renderTargetFactory = renderTargetFactory; - _renderInterface = renderInterface; - _lock = rendererLock ?? new ManagedDeferredRendererLock(); - _updateSceneIfNeededDelegate = UpdateSceneIfNeeded; - } - - /// - /// Initializes a new instance of the class. - /// - /// The control to render. - /// The render target. - /// The scene builder to use. Optional. - /// - /// This constructor is intended to be used for unit testing. - /// - public DeferredRenderer( - Visual root, - IRenderTarget renderTarget, - ISceneBuilder? sceneBuilder = null) : base(true) - { - _root = root ?? throw new ArgumentNullException(nameof(root)); - RenderTarget = renderTarget ?? throw new ArgumentNullException(nameof(renderTarget)); - _sceneBuilder = sceneBuilder ?? new SceneBuilder(); - Layers = new RenderLayers(); - _lock = new ManagedDeferredRendererLock(); - _updateSceneIfNeededDelegate = UpdateSceneIfNeeded; - } - - /// - public bool DrawFps { get; set; } - - /// - public bool DrawDirtyRects { get; set; } - - /// - /// Gets or sets a path to which rendered frame should be rendered for debugging. - /// - public string? DebugFramesPath { get; set; } - - /// - /// Forces the renderer to only draw frames on the render thread. Makes Paint to wait until frame is rendered - /// - public bool RenderOnlyOnRenderThread { get; set; } - - /// - public event EventHandler? SceneInvalidated; - - /// - /// Gets the render layers. - /// - internal RenderLayers Layers { get; } - - /// - /// Gets the current render target. - /// - internal IRenderTarget? RenderTarget { get; private set; } - - /// - public void AddDirty(Visual visual) - { - _dirty?.Add(visual); - } - - /// - /// Disposes of the renderer and detaches from the render loop. - /// - public void Dispose() - { - lock (_sceneLock) - { - if (_disposed) - return; - _disposed = true; - var scene = _scene; - _scene = null; - scene?.Dispose(); - } - - Stop(); - // Wait for any in-progress rendering to complete - lock(_renderLoopIsRenderingLock){} - DisposeRenderTarget(); - } - - public void RecalculateChildren(Visual visual) => _recalculateChildren?.Add(visual); - - void DisposeRenderTarget() - { - using (var l = _lock.TryLock()) - { - if(l == null) - { - // We are still trying to render on the render thread, try again a bit later - DispatcherTimer.RunOnce(DisposeRenderTarget, TimeSpan.FromMilliseconds(50), - DispatcherPriority.Background); - return; - } - - Layers.Clear(); - RenderTarget?.Dispose(); - RenderTarget = null; - } - } - - /// - public IEnumerable HitTest(Point p, Visual root, Func? filter) - { - EnsureCanHitTest(); - - //It's safe to access _scene here without a lock since - //it's only changed from UI thread which we are currently on - return _scene?.Item.HitTest(p, root, filter) ?? Enumerable.Empty(); - } - - /// - public Visual? HitTestFirst(Point p, Visual root, Func? filter) - { - EnsureCanHitTest(); - - //It's safe to access _scene here without a lock since - //it's only changed from UI thread which we are currently on - return _scene?.Item.HitTestFirst(p, root, filter); - } - - /// - public void Paint(Rect rect) - { - if (RenderOnlyOnRenderThread) - { - // Renderer is stopped and doesn't tick on the render thread - // This indicates a bug somewhere in our code - // (currently happens when a window gets minimized with Show desktop on Windows 10) - if(!_running) - return; - - while (true) - { - Scene? scene; - bool? updated; - lock (_sceneLock) - { - updated = UpdateScene(); - scene = _scene?.Item; - } - - // Renderer is in invalid state, skip drawing - if(updated == null) - return; - - // Wait for the scene to be rendered or disposed - scene?.Rendered.Wait(); - - // That was an up-to-date scene, we can return immediately - if (updated == true) - return; - } - } - else - { - var t = (IRenderLoopTask)this; - if (t.NeedsUpdate) - UpdateScene(); - if (_scene?.Item != null) - Render(true); - } - } - - /// - public void Resized(Size size) - { - } - - /// - public void Start() - { - lock (_startStopLock) - { - if (!_running && _renderLoop != null) - { - _renderLoop.Add(this); - _running = true; - } - } - } - - /// - public void Stop() - { - lock (_startStopLock) - { - if (_running && _renderLoop != null) - { - _renderLoop.Remove(this); - _running = false; - } - } - } - - public ValueTask TryGetRenderInterfaceFeature(Type featureType) - { - if (_renderInterface == null) - return new((object?)null); - - var tcs = new TaskCompletionSource(); - _pendingRenderThreadJobs ??= new(); - _pendingRenderThreadJobs.Add(() => - { - try - { - using (_renderInterface.EnsureCurrent()) - { - tcs.TrySetResult(_renderInterface.Value.TryGetFeature(featureType)); - } - } - catch (Exception e) - { - tcs.TrySetException(e); - } - }); - return new ValueTask(tcs.Task); - } - - bool NeedsUpdate => _dirty == null || _dirty.Count > 0; - bool IRenderLoopTask.NeedsUpdate => NeedsUpdate; - - void IRenderLoopTask.Update(TimeSpan time) => UpdateScene(); - - void IRenderLoopTask.Render() - { - lock (_renderLoopIsRenderingLock) - { - lock(_startStopLock) - if(!_running) - return; - Render(false); - } - } - - static Scene? TryGetChildScene(IRef? op) => (op?.Item as BrushDrawOperation)?.Aux as Scene; - - /// - Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush) - { - return TryGetChildScene(_currentDraw)?.Size ?? default; - } - - /// - void IVisualBrushRenderer.RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush) - { - var childScene = TryGetChildScene(_currentDraw); - - if (childScene != null) - { - Render(context, (VisualNode)childScene.Root, null, new Rect(childScene.Size)); - } - } - - internal void UnitTestUpdateScene() => UpdateScene(); - - internal void UnitTestRender() => Render(false); - - internal Scene? UnitTestScene() => _scene?.Item; - - private void EnsureCanHitTest() - { - if (_renderLoop == null && (_dirty == null || _dirty.Count > 0)) - { - // When unit testing the renderLoop may be null, so update the scene manually. - UpdateScene(); - } - } - - internal void Render(bool forceComposite) - { - using (var l = _lock.TryLock()) - { - if (l == null) - return; - - IDrawingContextImpl? context = null; - try - { - try - { - var (scene, updated) = UpdateRenderLayersAndConsumeSceneIfNeeded(ref context); - if (updated) - FpsTick(); - using (scene) - { - if (scene?.Item != null) - { - try - { - var overlay = DrawDirtyRects || DrawFps; - if (DrawDirtyRects) - _dirtyRectsDisplay.Tick(); - if (overlay) - RenderOverlay(scene.Item, ref context); - if (updated || forceComposite || overlay) - RenderComposite(scene.Item, ref context); - } - finally - { - try - { - if(scene.Item.RenderThreadJobs!=null) - foreach (var job in scene.Item.RenderThreadJobs) - job(); - } - finally - { - scene.Item.MarkAsRendered(); - } - } - } - } - } - finally - { - context?.Dispose(); - } - } - catch (RenderTargetCorruptedException ex) - { - Logger.TryGet(LogEventLevel.Information, LogArea.Animations)?.Log(this, "Render target was corrupted. Exception: {0}", ex); - RenderTarget?.Dispose(); - RenderTarget = null; - } - } - } - - private (IRef? scene, bool updated) UpdateRenderLayersAndConsumeSceneIfNeeded(ref IDrawingContextImpl? context, - bool recursiveCall = false) - { - IRef? sceneRef; - lock (_sceneLock) - sceneRef = _scene?.Clone(); - if (sceneRef == null) - return (null, false); - using (sceneRef) - { - var scene = sceneRef.Item; - if (scene.Generation != _lastSceneId) - { - EnsureDrawingContext(ref context); - - Layers.Update(scene, context); - - RenderToLayers(scene); - - if (DebugFramesPath != null) - { - SaveDebugFrames(scene.Generation); - } - - lock (_sceneLock) - _lastSceneId = scene.Generation; - - - var isUiThread = Dispatcher.UIThread.CheckAccess(); - // We have consumed the previously available scene, but there might be some dirty - // rects since the last update. *If* we are on UI thread, we can force immediate scene - // rebuild before rendering anything on-screen - // We are calling the same method recursively here - if (!recursiveCall && isUiThread && NeedsUpdate) - { - UpdateScene(); - var (rs, _) = UpdateRenderLayersAndConsumeSceneIfNeeded(ref context, true); - return (rs, true); - } - - // We are rendering a new scene version, so it's highly likely - // that there is already a pending update for animations - // So we are scheduling an update call so UI thread could prepare a scene before - // the next render timer tick - if (!recursiveCall && !isUiThread) - Dispatcher.UIThread.Post(_updateSceneIfNeededDelegate, DispatcherPriority.Render); - - // Indicate that we have updated the layers - return (sceneRef.Clone(), true); - } - - // Just return scene, layers weren't updated - return (sceneRef.Clone(), false); - } - - } - - - private void Render(IDrawingContextImpl context, VisualNode node, Visual? layer, Rect clipBounds) - { - if (layer == null || node.LayerRoot == layer) - { - clipBounds = node.ClipBounds.Intersect(clipBounds); - - if (!clipBounds.IsDefault && node.Opacity > 0) - { - var isLayerRoot = node.Visual == layer; - - node.BeginRender(context, isLayerRoot); - - var drawOperations = node.DrawOperations; - var drawOperationsCount = drawOperations.Count; - for (int i = 0; i < drawOperationsCount; i++) - { - var operation = drawOperations[i]; - _currentDraw = operation; - operation.Item.Render(context); - _currentDraw = null; - } - - var children = node.Children; - var childrenCount = children.Count; - for (int i = 0; i < childrenCount; i++) - { - var child = children[i]; - Render(context, (VisualNode)child, layer, clipBounds); - } - - node.EndRender(context, isLayerRoot); - } - } - } - - private void RenderToLayers(Scene scene) - { - foreach (var layer in scene.Layers) - { - var renderLayer = Layers[layer.LayerRoot]; - if (layer.Dirty.IsEmpty && !renderLayer.IsEmpty) - continue; - var renderTarget = renderLayer.Bitmap; - var node = (VisualNode?)scene.FindNode(layer.LayerRoot); - - if (node != null) - { - using (var context = renderTarget.Item.CreateDrawingContext(this)) - { - if (renderLayer.IsEmpty) - { - // Render entire layer root node - context.Clear(Colors.Transparent); - context.Transform = Matrix.Identity; - context.PushClip(node.ClipBounds); - Render(context, node, layer.LayerRoot, node.ClipBounds); - context.PopClip(); - if (DrawDirtyRects) - { - _dirtyRectsDisplay.Add(node.ClipBounds); - } - - renderLayer.IsEmpty = false; - } - else - { - var scale = scene.Scaling; - - foreach (var rect in layer.Dirty) - { - var snappedRect = SnapToDevicePixels(rect, scale); - context.Transform = Matrix.Identity; - context.PushClip(snappedRect); - context.Clear(Colors.Transparent); - Render(context, node, layer.LayerRoot, snappedRect); - context.PopClip(); - - if (DrawDirtyRects) - { - _dirtyRectsDisplay.Add(snappedRect); - } - } - } - } - } - } - } - - private static Rect SnapToDevicePixels(Rect rect, double scale) - { - return new Rect( - new Point( - Math.Floor(rect.X * scale) / scale, - Math.Floor(rect.Y * scale) / scale), - new Point( - Math.Ceiling(rect.Right * scale) / scale, - Math.Ceiling(rect.Bottom * scale) / scale)); - } - - private void RenderOverlay(Scene scene, ref IDrawingContextImpl? parentContent) - { - EnsureDrawingContext(ref parentContent); - - if (DrawDirtyRects) - { - var overlay = GetOverlay(parentContent, scene.Size, scene.Scaling); - - using (var context = overlay.Item.CreateDrawingContext(this)) - { - context.Clear(Colors.Transparent); - RenderDirtyRects(context); - } - } - else - { - _overlay?.Dispose(); - _overlay = null; - } - } - - private void RenderDirtyRects(IDrawingContextImpl context) - { - foreach (var r in _dirtyRectsDisplay) - { - var brush = new ImmutableSolidColorBrush(Colors.Magenta, r.Opacity); - context.DrawRectangle(brush,null, r.Rect); - } - } - - private void RenderComposite(Scene scene, ref IDrawingContextImpl? context) - { - EnsureDrawingContext(ref context); - - context.Clear(Colors.Transparent); - - var clientRect = new Rect(scene.Size); - - var firstLayer = true; - foreach (var layer in scene.Layers) - { - var bitmap = Layers[layer.LayerRoot].Bitmap; - var sourceRect = new Rect(0, 0, bitmap.Item.PixelSize.Width, bitmap.Item.PixelSize.Height); - - if (layer.GeometryClip != null) - { - context.PushGeometryClip(layer.GeometryClip); - } - - if (layer.OpacityMask == null) - { - if (firstLayer && bitmap.Item.CanBlit) - bitmap.Item.Blit(context); - else - context.DrawBitmap(bitmap, layer.Opacity, sourceRect, clientRect); - } - else - { - context.DrawBitmap(bitmap, layer.OpacityMask, layer.OpacityMaskRect, sourceRect); - } - - if (layer.GeometryClip != null) - { - context.PopGeometryClip(); - } - - firstLayer = false; - } - - if (_overlay != null) - { - var sourceRect = new Rect(0, 0, _overlay.Item.PixelSize.Width, _overlay.Item.PixelSize.Height); - context.DrawBitmap(_overlay, 0.5, sourceRect, clientRect); - } - - if (DrawFps) - { - using (var c = new DrawingContext(context, false)) - { - RenderFps(c, clientRect, scene.Layers.Count); - } - } - } - - private void EnsureDrawingContext([NotNull] ref IDrawingContextImpl? context) - { - if (context != null) - { - return; - } - - if (RenderTarget?.IsCorrupted == true) - { - RenderTarget!.Dispose(); - RenderTarget = null; - } - - if (RenderTarget == null) - { - RenderTarget = _renderTargetFactory!(); - } - - context = RenderTarget.CreateDrawingContext(this); - } - - private void UpdateSceneIfNeeded() - { - if(NeedsUpdate) - UpdateScene(); - } - - private bool? UpdateScene() - { - Dispatcher.UIThread.VerifyAccess(); - using var noPump = NonPumpingLockHelper.Use(); - lock (_sceneLock) - { - if (_disposed) - return null; - if (_scene?.Item.Generation > _lastSceneId) - return false; - } - if (_root.IsVisible) - { - var sceneRef = RefCountable.Create(_scene?.Item.CloneScene() ?? new Scene(_root) - { - RenderThreadJobs = _pendingRenderThreadJobs - }); - _pendingRenderThreadJobs = null; - var scene = sceneRef.Item; - - if (_dirty == null) - { - _dirty = new DirtyVisuals(); - _recalculateChildren = new HashSet(); - _sceneBuilder.UpdateAll(scene); - } - else - { - foreach (var visual in _recalculateChildren!) - { - var node = scene.FindNode(visual); - ((VisualNode?)node)?.SortChildren(scene); - } - - _recalculateChildren.Clear(); - - foreach (var visual in _dirty) - { - _sceneBuilder.Update(scene, visual); - } - } - - lock (_sceneLock) - { - var oldScene = _scene; - _scene = sceneRef; - oldScene?.Dispose(); - } - - _dirty.Clear(); - - if (SceneInvalidated != null) - { - var rect = new Rect(); - - foreach (var layer in scene.Layers) - { - foreach (var dirty in layer.Dirty) - { - rect = rect.Union(dirty); - } - } - - SceneInvalidated(this, new SceneInvalidatedEventArgs((IRenderRoot)_root, rect)); - } - - return true; - } - else - { - lock (_sceneLock) - { - var oldScene = _scene; - _scene = null; - oldScene?.Dispose(); - } - - return null; - } - } - - private IRef GetOverlay( - IDrawingContextImpl parentContext, - Size size, - double scaling) - { - var pixelSize = size * scaling; - - if (_overlay == null || - _overlay.Item.PixelSize.Width != pixelSize.Width || - _overlay.Item.PixelSize.Height != pixelSize.Height) - { - _overlay?.Dispose(); - _overlay = RefCountable.Create(parentContext.CreateLayer(size)); - } - - return _overlay; - } - - private void SaveDebugFrames(int id) - { - var index = 0; - - foreach (var layer in Layers) - { - var fileName = Path.Combine(DebugFramesPath ?? string.Empty, FormattableString.Invariant($"frame-{id}-layer-{index++}.png")); - layer.Bitmap.Item.Save(fileName); - } - } - } -} diff --git a/src/Avalonia.Base/Rendering/ICustomHitTest.cs b/src/Avalonia.Base/Rendering/ICustomHitTest.cs new file mode 100644 index 0000000000..65bb24b4b3 --- /dev/null +++ b/src/Avalonia.Base/Rendering/ICustomHitTest.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Linq; +using Avalonia.VisualTree; + +namespace Avalonia.Rendering +{ + + /// + /// Allows customization of hit-testing + /// + public interface ICustomHitTest + { + /// The point to hit test in global coordinate space. + bool HitTest(Point point); + } +} diff --git a/src/Avalonia.Base/Rendering/ICustomSimpleHitTest.cs b/src/Avalonia.Base/Rendering/ICustomSimpleHitTest.cs deleted file mode 100644 index fae595413f..0000000000 --- a/src/Avalonia.Base/Rendering/ICustomSimpleHitTest.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Avalonia.VisualTree; - -namespace Avalonia.Rendering -{ - /// - /// An interface to allow non-templated controls to customize their hit-testing - /// when using a renderer with a simple hit-testing algorithm without a scene graph, - /// such as - /// - public interface ICustomSimpleHitTest - { - /// The point to hit test in global coordinate space. - bool HitTest(Point point); - } - - /// - /// Allows customization of hit-testing for all renderers. - /// - public interface ICustomHitTest : ICustomSimpleHitTest - { - } - - public static class CustomSimpleHitTestExtensions - { - public static bool HitTestCustom(this Visual visual, Point point) - => (visual as ICustomSimpleHitTest)?.HitTest(point) ?? visual.TransformedBounds?.Contains(point) == true; - - public static bool HitTestCustom(this IEnumerable children, Point point) - => children.Any(ctrl => ctrl.HitTestCustom(point)); - } -} diff --git a/src/Avalonia.Base/Rendering/IRenderRoot.cs b/src/Avalonia.Base/Rendering/IRenderRoot.cs index cabb1302cd..fa3260ffb4 100644 --- a/src/Avalonia.Base/Rendering/IRenderRoot.cs +++ b/src/Avalonia.Base/Rendering/IRenderRoot.cs @@ -25,12 +25,6 @@ namespace Avalonia.Rendering /// double RenderScaling { get; } - /// - /// Adds a rectangle to the window's dirty region. - /// - /// The rectangle. - void Invalidate(Rect rect); - /// /// Converts a point from screen to client coordinates. /// diff --git a/src/Avalonia.Base/Rendering/IRendererFactory.cs b/src/Avalonia.Base/Rendering/IRendererFactory.cs deleted file mode 100644 index 11d89ef94c..0000000000 --- a/src/Avalonia.Base/Rendering/IRendererFactory.cs +++ /dev/null @@ -1,16 +0,0 @@ - -namespace Avalonia.Rendering -{ - /// - /// Defines the interface for a renderer factory. - /// - public interface IRendererFactory - { - /// - /// Creates a renderer. - /// - /// The root visual. - /// The render loop. - IRenderer Create(IRenderRoot root, IRenderLoop renderLoop); - } -} diff --git a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs index 60b144e806..c67ac7057d 100644 --- a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs @@ -10,102 +10,12 @@ using Avalonia.VisualTree; namespace Avalonia.Rendering { /// - /// A renderer which renders the state of the visual tree without an intermediate scene graph - /// representation. + /// This class is used to render the visual tree into a DrawingContext by doing + /// a simple tree traversal. + /// It's currently used mostly for RenderTargetBitmap.Render and VisualBrush /// - /// - /// The immediate renderer supports only clip-bound-based hit testing; a control's geometry is - /// not taken into account. - /// - public class ImmediateRenderer : RendererBase, IRenderer, IVisualBrushRenderer + internal class ImmediateRenderer : IVisualBrushRenderer//, IRenderer { - private readonly Visual _root; - private readonly Func _renderTargetFactory; - private readonly PlatformRenderInterfaceContextManager? _renderContext; - private readonly IRenderRoot? _renderRoot; - private bool _updateTransformedBounds = true; - private IRenderTarget? _renderTarget; - - /// - /// Initializes a new instance of the class. - /// - /// The control to render. - /// The target render factory. - /// The render contex. - public ImmediateRenderer(Visual root, Func renderTargetFactory, - PlatformRenderInterfaceContextManager? renderContext = null) - { - _root = root ?? throw new ArgumentNullException(nameof(root)); - _renderTargetFactory = renderTargetFactory; - _renderContext = renderContext; - _renderRoot = root as IRenderRoot; - } - - private ImmediateRenderer(Visual root, Func renderTargetFactory, bool updateTransformedBounds) - { - _root = root ?? throw new ArgumentNullException(nameof(root)); - _renderTargetFactory = renderTargetFactory; - _renderRoot = root as IRenderRoot; - _updateTransformedBounds = updateTransformedBounds; - } - - /// - public bool DrawFps { get; set; } - - /// - public bool DrawDirtyRects { get; set; } - - /// - public event EventHandler? SceneInvalidated; - - /// - public void Paint(Rect rect) - { - if (_renderTarget == null) - { - _renderTarget = _renderTargetFactory(); - } - - try - { - using (var context = new DrawingContext(_renderTarget.CreateDrawingContext(this))) - { - context.PlatformImpl.Clear(Colors.Transparent); - - using (context.PushTransformContainer()) - { - Render(context, _root, _root.Bounds); - } - - if (DrawDirtyRects) - { - var color = (uint)new Random().Next(0xffffff) | 0x44000000; - context.FillRectangle( - new SolidColorBrush(color), - rect); - } - - if (DrawFps) - { - RenderFps(context, _root.Bounds, null); - } - } - } - catch (RenderTargetCorruptedException ex) - { - Logger.TryGet(LogEventLevel.Information, LogArea.Animations)?.Log(this, "Render target was corrupted. Exception: {0}", ex); - _renderTarget.Dispose(); - _renderTarget = null; - } - - SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs((IRenderRoot)_root, rect)); - } - - /// - public void Resized(Size size) - { - } - /// /// Renders a visual to a render target. /// @@ -113,11 +23,8 @@ namespace Avalonia.Rendering /// The render target. public static void Render(Visual visual, IRenderTarget target) { - using (var renderer = new ImmediateRenderer(visual, () => target, updateTransformedBounds: false)) - using (var context = new DrawingContext(target.CreateDrawingContext(renderer))) - { - renderer.Render(context, visual, visual.Bounds); - } + using var context = new DrawingContext(target.CreateDrawingContext(new ImmediateRenderer())); + Render(context, visual, visual.Bounds); } /// @@ -127,77 +34,9 @@ namespace Avalonia.Rendering /// The drawing context. public static void Render(Visual visual, DrawingContext context) { - using (var renderer = new ImmediateRenderer(visual, - () => throw new InvalidOperationException("This is not supposed to be called"), - updateTransformedBounds: false)) - { - renderer.Render(context, visual, visual.Bounds); - } + Render(context, visual, visual.Bounds); } - - /// - public void AddDirty(Visual visual) - { - if (!visual.Bounds.IsDefault) - { - var m = visual.TransformToVisual(_root); - - if (m.HasValue) - { - var bounds = new Rect(visual.Bounds.Size).TransformToAABB(m.Value); - - //use transformedbounds as previous render state of the visual bounds - //so we can invalidate old and new bounds of a control in case it moved/shrinked - if (visual.TransformedBounds.HasValue) - { - var trb = visual.TransformedBounds.Value; - var trBounds = trb.Bounds.TransformToAABB(trb.Transform); - - if (trBounds != bounds) - { - _renderRoot?.Invalidate(trBounds); - } - } - - _renderRoot?.Invalidate(bounds); - } - } - } - - /// - /// Ends the operation of the renderer. - /// - public void Dispose() - { - _renderTarget?.Dispose(); - } - - /// - public IEnumerable HitTest(Point p, Visual root, Func filter) - { - return HitTest(root, p, filter); - } - - public Visual? HitTestFirst(Point p, Visual root, Func filter) - { - return HitTest(root, p, filter).FirstOrDefault(); - } - - /// - public void RecalculateChildren(Visual visual) => AddDirty(visual); - - /// - public void Start() - { - } - - /// - public void Stop() - { - } - - public ValueTask TryGetRenderInterfaceFeature(Type featureType) => - new(_renderContext?.Value?.TryGetFeature(featureType)); + /// Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush) @@ -215,18 +54,7 @@ namespace Avalonia.Rendering internal static void Render(Visual visual, DrawingContext context, bool updateTransformedBounds) { - using var renderer = new ImmediateRenderer(visual, - () => throw new InvalidOperationException("This is not supposed to be called"), - updateTransformedBounds); - renderer.Render(context, visual, visual.Bounds); - } - - private static void ClearTransformedBounds(Visual visual) - { - foreach (var e in visual.GetSelfAndVisualDescendants()) - { - visual.SetTransformedBounds(null); - } + Render(context, visual, visual.Bounds); } private static Rect GetTransformedBounds(Visual visual) @@ -244,45 +72,8 @@ namespace Avalonia.Rendering } } - private static IEnumerable HitTest( - Visual visual, - Point p, - Func? filter) - { - _ = visual ?? throw new ArgumentNullException(nameof(visual)); - - if (filter?.Invoke(visual) != false) - { - bool containsPoint; - - if (visual is ICustomSimpleHitTest custom) - { - containsPoint = custom.HitTest(p); - } - else - { - containsPoint = visual.TransformedBounds?.Contains(p) == true; - } - - if ((containsPoint || !visual.ClipToBounds) && visual.VisualChildren.Count > 0) - { - foreach (var child in visual.VisualChildren.SortByZIndex()) - { - foreach (var result in HitTest(child, p, filter)) - { - yield return result; - } - } - } - if (containsPoint) - { - yield return visual; - } - } - } - - private void Render(DrawingContext context, Visual visual, Rect clipRect) + private static void Render(DrawingContext context, Visual visual, Rect clipRect) { var opacity = visual.Opacity; var clipToBounds = visual.ClipToBounds; @@ -338,15 +129,7 @@ namespace Avalonia.Rendering using (context.PushTransformContainer()) { visual.Render(context); - -#pragma warning disable 0618 - var transformed = - new TransformedBounds(bounds, new Rect(), context.CurrentContainerTransform); -#pragma warning restore 0618 - - if (_updateTransformedBounds) - visual.SetTransformedBounds(transformed); - + var childrenEnumerable = visual.HasNonUniformZIndexChildren ? visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance) : (IEnumerable)visual.VisualChildren; @@ -362,18 +145,9 @@ namespace Avalonia.Rendering : clipRect; Render(context, child, childClipRect); } - else if (_updateTransformedBounds) - { - ClearTransformedBounds(child); - } } } } - - if (!visual.IsVisible && _updateTransformedBounds) - { - ClearTransformedBounds(visual); - } } } } diff --git a/src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs b/src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs index c13bcfda96..82dcd8f184 100644 --- a/src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs +++ b/src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs @@ -6,9 +6,7 @@ using Avalonia.Reactive; namespace Avalonia.Rendering; -[Unstable] -// TODO: Make it internal once legacy renderers are removed -public class PlatformRenderInterfaceContextManager +internal class PlatformRenderInterfaceContextManager { private readonly IPlatformGraphics? _graphics; private IPlatformRenderInterfaceContext? _backend; diff --git a/src/Avalonia.Base/Rendering/RenderLayer.cs b/src/Avalonia.Base/Rendering/RenderLayer.cs deleted file mode 100644 index d1e3fcafb1..0000000000 --- a/src/Avalonia.Base/Rendering/RenderLayer.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Avalonia.Media; -using Avalonia.Platform; -using Avalonia.Utilities; -using Avalonia.VisualTree; - -namespace Avalonia.Rendering -{ - public class RenderLayer - { - public RenderLayer( - IDrawingContextImpl drawingContext, - Size size, - double scaling, - Visual layerRoot) - { - Bitmap = RefCountable.Create(drawingContext.CreateLayer(size)); - Size = size; - Scaling = scaling; - LayerRoot = layerRoot; - IsEmpty = true; - } - - public IRef Bitmap { get; private set; } - public bool IsEmpty { get; set; } - public double Scaling { get; private set; } - public Size Size { get; private set; } - public Visual LayerRoot { get; } - - public void RecreateBitmap(IDrawingContextImpl drawingContext, Size size, double scaling) - { - if (Size != size || Scaling != scaling) - { - var resized = RefCountable.Create(drawingContext.CreateLayer(size)); - - using (var context = resized.Item.CreateDrawingContext(null)) - { - Bitmap.Dispose(); - context.Clear(default); - - Bitmap = resized; - Scaling = scaling; - Size = size; - IsEmpty = true; - } - } - } - } -} diff --git a/src/Avalonia.Base/Rendering/RenderLayers.cs b/src/Avalonia.Base/Rendering/RenderLayers.cs deleted file mode 100644 index eff81e6bbf..0000000000 --- a/src/Avalonia.Base/Rendering/RenderLayers.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Avalonia.Platform; -using Avalonia.Rendering.SceneGraph; - -namespace Avalonia.Rendering -{ - public class RenderLayers : IEnumerable - { - private readonly List _inner = new List(); - private readonly Dictionary _index = new Dictionary(); - - public int Count => _inner.Count; - public RenderLayer this[Visual layerRoot] => _index[layerRoot]; - - public void Update(Scene scene, IDrawingContextImpl context) - { - for (var i = scene.Layers.Count - 1; i >= 0; --i) - { - var src = scene.Layers[i]; - - if (!_index.TryGetValue(src.LayerRoot, out var layer)) - { - layer = new RenderLayer(context, scene.Size, scene.Scaling, src.LayerRoot); - _inner.Add(layer); - _index.Add(src.LayerRoot, layer); - } - else - { - layer.RecreateBitmap(context, scene.Size, scene.Scaling); - } - } - - for (var i = 0; i < _inner.Count;) - { - var layer = _inner[i]; - - if (!scene.Layers.Exists(layer.LayerRoot)) - { - layer.Bitmap.Dispose(); - _inner.RemoveAt(i); - _index.Remove(layer.LayerRoot); - } - else - i++; - } - } - - public void Clear() - { - foreach (var layer in _index.Values) - { - layer.Bitmap.Dispose(); - } - - _index.Clear(); - _inner.Clear(); - } - - public bool TryGetValue(Visual layerRoot, [NotNullWhen(true)] out RenderLayer? value) - { - return _index.TryGetValue(layerRoot, out value); - } - - public IEnumerator GetEnumerator() => _inner.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } -} diff --git a/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs deleted file mode 100644 index b1d8301557..0000000000 --- a/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ /dev/null @@ -1,482 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Numerics; -using Avalonia.Media; -using Avalonia.Platform; -using Avalonia.Utilities; -using Avalonia.Media.Imaging; -using Avalonia.VisualTree; - -namespace Avalonia.Rendering.SceneGraph -{ - /// - /// A drawing context which builds a scene graph. - /// - internal class DeferredDrawingContextImpl : IDrawingContextImpl, IDrawingContextWithAcrylicLikeSupport - { - private readonly ISceneBuilder _sceneBuilder; - private VisualNode? _node; - private int _childIndex; - private int _drawOperationindex; - - /// - /// Initializes a new instance of the class. - /// - /// - /// A scene builder used for constructing child scenes for visual brushes. - /// - /// The scene layers. - public DeferredDrawingContextImpl(ISceneBuilder sceneBuilder, SceneLayers layers) - { - _sceneBuilder = sceneBuilder; - Layers = layers; - } - - /// - public Matrix Transform { get; set; } = Matrix.Identity; - - /// - /// Gets the layers in the scene being built. - /// - public SceneLayers Layers { get; } - - /// - /// Informs the drawing context of the visual node that is about to be rendered. - /// - /// The visual node. - /// - /// An object which when disposed will commit the changes to visual node. - /// - public UpdateState BeginUpdate(VisualNode node) - { - _ = node ?? throw new ArgumentNullException(nameof(node)); - - if (_node != null) - { - if (_childIndex < _node.Children.Count) - { - _node.ReplaceChild(_childIndex, node); - } - else - { - _node.AddChild(node); - } - - ++_childIndex; - } - - var state = new UpdateState(this, _node, _childIndex, _drawOperationindex); - _node = node; - _childIndex = _drawOperationindex = 0; - return state; - } - - /// - public void Clear(Color color) - { - // Cannot clear a deferred scene. - } - - /// - public void Dispose() - { - // Nothing to do here since we allocate no unmanaged resources. - } - - /// - /// Removes any remaining drawing operations from the visual node. - /// - /// - /// Drawing operations are updated in place, overwriting existing drawing operations if - /// they are different. Once drawing has completed for the current visual node, it is - /// possible that there are stale drawing operations at the end of the list. This method - /// trims these stale drawing operations. - /// - public void TrimChildren() - { - _node!.TrimChildren(_childIndex); - } - - /// - public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry) - { - var next = NextDrawAs(); - - if (next == null || !next.Item.Equals(Transform, brush, pen, geometry)) - { - Add(new GeometryNode(Transform, brush, pen, geometry, CreateChildScene(brush))); - } - else - { - ++_drawOperationindex; - } - } - - /// - public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) - { - var next = NextDrawAs(); - - if (next == null || !next.Item.Equals(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode)) - { - Add(new ImageNode(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode)); - } - else - { - ++_drawOperationindex; - } - } - - /// - public void DrawBitmap(IRef source, IBrush opacityMask, Rect opacityMaskRect, Rect sourceRect) - { - // This method is currently only used to composite layers so shouldn't be called here. - throw new NotSupportedException(); - } - - /// - public void DrawLine(IPen pen, Point p1, Point p2) - { - var next = NextDrawAs(); - - if (next == null || !next.Item.Equals(Transform, pen, p1, p2)) - { - Add(new LineNode(Transform, pen, p1, p2, CreateChildScene(pen.Brush))); - } - else - { - ++_drawOperationindex; - } - } - - /// - public void DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rect, - BoxShadows boxShadows = default) - { - var next = NextDrawAs(); - - if (next == null || !next.Item.Equals(Transform, brush, pen, rect, boxShadows)) - { - Add(new RectangleNode(Transform, brush, pen, rect, boxShadows, CreateChildScene(brush))); - } - else - { - ++_drawOperationindex; - } - } - - /// - public void DrawRectangle(IExperimentalAcrylicMaterial material, RoundedRect rect) - { - var next = NextDrawAs(); - - if (next == null || !next.Item.Equals(Transform, material, rect)) - { - Add(new ExperimentalAcrylicNode(Transform, material, rect)); - } - else - { - ++_drawOperationindex; - } - } - - public void DrawEllipse(IBrush? brush, IPen? pen, Rect rect) - { - var next = NextDrawAs(); - - if (next == null || !next.Item.Equals(Transform, brush, pen, rect)) - { - Add(new EllipseNode(Transform, brush, pen, rect, CreateChildScene(brush))); - } - else - { - ++_drawOperationindex; - } - } - - public void Custom(ICustomDrawOperation custom) - { - var next = NextDrawAs(); - if (next == null || !next.Item.Equals(Transform, custom)) - Add(new CustomDrawOperation(custom, Transform)); - else - ++_drawOperationindex; - } - - public object? GetFeature(Type t) => null; - - /// - public void DrawGlyphRun(IBrush foreground, IRef glyphRun) - { - var next = NextDrawAs(); - - if (next == null || !next.Item.Equals(Transform, foreground, glyphRun)) - { - Add(new GlyphRunNode(Transform, foreground, glyphRun, CreateChildScene(foreground))); - } - - else - { - ++_drawOperationindex; - } - } - public IDrawingContextLayerImpl CreateLayer(Size size) - { - throw new NotSupportedException("Creating layers on a deferred drawing context not supported"); - } - - /// - public void PopClip() - { - var next = NextDrawAs(); - - if (next == null || !next.Item.Equals(null)) - { - Add(new ClipNode()); - } - else - { - ++_drawOperationindex; - } - } - - /// - public void PopGeometryClip() - { - var next = NextDrawAs(); - - if (next == null || !next.Item.Equals(null)) - { - Add(new GeometryClipNode()); - } - else - { - ++_drawOperationindex; - } - } - - /// - public void PopBitmapBlendMode() - { - var next = NextDrawAs(); - - if (next == null || !next.Item.Equals(null)) - { - Add(new BitmapBlendModeNode()); - } - else - { - ++_drawOperationindex; - } - } - - /// - public void PopOpacity() - { - var next = NextDrawAs(); - - if (next == null || !next.Item.Equals(null)) - { - Add(new OpacityNode()); - } - else - { - ++_drawOperationindex; - } - } - - /// - public void PopOpacityMask() - { - var next = NextDrawAs(); - - if (next == null || !next.Item.Equals(null, null)) - { - Add(new OpacityMaskNode()); - } - else - { - ++_drawOperationindex; - } - } - - /// - public void PushClip(Rect clip) - { - var next = NextDrawAs(); - - if (next == null || !next.Item.Equals(Transform, clip)) - { - Add(new ClipNode(Transform, clip)); - } - else - { - ++_drawOperationindex; - } - } - - /// - public void PushClip(RoundedRect clip) - { - var next = NextDrawAs(); - - if (next == null || !next.Item.Equals(Transform, clip)) - { - Add(new ClipNode(Transform, clip)); - } - else - { - ++_drawOperationindex; - } - } - - /// - public void PushGeometryClip(IGeometryImpl? clip) - { - if (clip is null) - return; - - var next = NextDrawAs(); - - if (next == null || !next.Item.Equals(Transform, clip)) - { - Add(new GeometryClipNode(Transform, clip)); - } - else - { - ++_drawOperationindex; - } - } - - /// - public void PushOpacity(double opacity) - { - var next = NextDrawAs(); - - if (next == null || !next.Item.Equals(opacity)) - { - Add(new OpacityNode(opacity)); - } - else - { - ++_drawOperationindex; - } - } - - /// - public void PushOpacityMask(IBrush mask, Rect bounds) - { - var next = NextDrawAs(); - - if (next == null || !next.Item.Equals(mask, bounds)) - { - Add(new OpacityMaskNode(mask, bounds, CreateChildScene(mask))); - } - else - { - ++_drawOperationindex; - } - } - - /// - public void PushBitmapBlendMode(BitmapBlendingMode blendingMode) - { - var next = NextDrawAs(); - - if (next == null || !next.Item.Equals(blendingMode)) - { - Add(new BitmapBlendModeNode(blendingMode)); - } - else - { - ++_drawOperationindex; - } - } - - public readonly struct UpdateState : IDisposable - { - public UpdateState( - DeferredDrawingContextImpl owner, - VisualNode? node, - int childIndex, - int drawOperationIndex) - { - Owner = owner; - Node = node; - ChildIndex = childIndex; - DrawOperationIndex = drawOperationIndex; - } - - public void Dispose() - { - Owner._node!.TrimDrawOperations(Owner._drawOperationindex); - - var dirty = Owner.Layers.GetOrAdd(Owner._node.LayerRoot!).Dirty; - - var drawOperations = Owner._node.DrawOperations; - var drawOperationsCount = drawOperations.Count; - - for (var i = 0; i < drawOperationsCount; i++) - { - dirty.Add(drawOperations[i].Item.Bounds); - } - - Owner._node = Node; - Owner._childIndex = ChildIndex; - Owner._drawOperationindex = DrawOperationIndex; - } - - public DeferredDrawingContextImpl Owner { get; } - public VisualNode? Node { get; } - public int ChildIndex { get; } - public int DrawOperationIndex { get; } - } - - private void Add(T node) where T : class, IDrawOperation - { - using (var refCounted = RefCountable.Create(node)) - { - Add(refCounted); - } - } - - private void Add(IRef node) - { - if (_drawOperationindex < _node!.DrawOperations.Count) - { - _node.ReplaceDrawOperation(_drawOperationindex, node); - } - else - { - _node.AddDrawOperation(node); - } - - ++_drawOperationindex; - } - - private IRef? NextDrawAs() where T : class, IDrawOperation - { - return _drawOperationindex < _node!.DrawOperations.Count ? _node.DrawOperations[_drawOperationindex] as IRef : null; - } - - private IDisposable? CreateChildScene(IBrush? brush) - { - var visualBrush = brush as VisualBrush; - - if (visualBrush != null) - { - var visual = visualBrush.Visual; - - if (visual != null) - { - (visual as IVisualBrushInitialize)?.EnsureInitialized(); - var scene = new Scene(visual); - _sceneBuilder.UpdateAll(scene); - return scene; - } - } - - return null; - } - } -} diff --git a/src/Avalonia.Base/Rendering/SceneGraph/ISceneBuilder.cs b/src/Avalonia.Base/Rendering/SceneGraph/ISceneBuilder.cs deleted file mode 100644 index f469fdbfe8..0000000000 --- a/src/Avalonia.Base/Rendering/SceneGraph/ISceneBuilder.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Avalonia.VisualTree; - -namespace Avalonia.Rendering.SceneGraph -{ - /// - /// Builds a scene graph from a visual tree. - /// - public interface ISceneBuilder - { - /// - /// Builds the initial scene graph for a visual tree. - /// - /// The scene to build. - void UpdateAll(Scene scene); - - /// - /// Updates the visual (and potentially its children) in a scene. - /// - /// The scene. - /// The visual to update. - /// True if changes were made, otherwise false. - bool Update(Scene scene, Visual visual); - } -} diff --git a/src/Avalonia.Base/Rendering/SceneGraph/IVisualNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/IVisualNode.cs deleted file mode 100644 index 59a032748d..0000000000 --- a/src/Avalonia.Base/Rendering/SceneGraph/IVisualNode.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections.Generic; -using Avalonia.Platform; -using Avalonia.Utilities; - -namespace Avalonia.Rendering.SceneGraph -{ - /// - /// Represents a node in the low-level scene graph representing a . - /// - public interface IVisualNode : IDisposable - { - /// - /// Gets the visual to which the node relates. - /// - Visual Visual { get; } - - /// - /// Gets the parent scene graph node. - /// - IVisualNode? Parent { get; } - - /// - /// Gets the transform for the node from global to control coordinates. - /// - Matrix Transform { get; } - - /// - /// Gets the corner radius of visual. Contents are clipped to this radius. - /// - CornerRadius ClipToBoundsRadius { get; } - - /// - /// Gets the bounds of the node's geometry in global coordinates. - /// - Rect Bounds { get; } - - /// - /// Gets the clip bounds for the node in global coordinates. - /// - Rect ClipBounds { get; } - - /// - /// Gets the layout bounds for the node in global coordinates. - /// - Rect LayoutBounds { get; } - - /// - /// Whether the node is clipped to . - /// - bool ClipToBounds { get; } - - /// - /// Gets the node's clip geometry, if any. - /// - IGeometryImpl? GeometryClip { get; set; } - - /// - /// Gets a value indicating whether one of the node's ancestors has a geometry clip. - /// - bool HasAncestorGeometryClip { get; } - - /// - /// Gets the child scene graph nodes. - /// - IReadOnlyList Children { get; } - - /// - /// Gets the drawing operations for the visual. - /// - IReadOnlyList> DrawOperations { get; } - - /// - /// Gets the opacity of the scene graph node. - /// - double Opacity { get; } - - /// - /// Sets up the drawing context for rendering the node's geometry. - /// - /// The drawing context. - /// Whether to skip pushing the control's opacity. - void BeginRender(IDrawingContextImpl context, bool skipOpacity); - - /// - /// Resets the drawing context after rendering the node's geometry. - /// - /// The drawing context. - /// Whether to skip popping the control's opacity. - void EndRender(IDrawingContextImpl context, bool skipOpacity); - - /// - /// Hit test the geometry in this node. - /// - /// The point in global coordinates. - /// True if the point hits the node's geometry; otherwise false. - /// - /// This method does not recurse to child s, if you want - /// to hit test children they must be hit tested manually. - /// - bool HitTest(Point p); - - bool Disposed { get; } - } -} diff --git a/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs deleted file mode 100644 index 735eb3bb3f..0000000000 --- a/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs +++ /dev/null @@ -1,352 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Avalonia.Collections.Pooled; -using Avalonia.VisualTree; - -namespace Avalonia.Rendering.SceneGraph -{ - /// - /// Represents a scene graph used by the . - /// - public class Scene : IDisposable - { - private readonly Dictionary _index; - private readonly TaskCompletionSource _rendered = new TaskCompletionSource(); - - /// - /// Initializes a new instance of the class. - /// - /// The root visual to draw. - public Scene(Visual rootVisual) - : this( - new VisualNode(rootVisual, null), - new Dictionary(), - new SceneLayers(rootVisual), - 0) - { - _index.Add(rootVisual, Root); - } - - private Scene(VisualNode root, Dictionary index, SceneLayers layers, int generation) - { - _ = root ?? throw new ArgumentNullException(nameof(root)); - - var renderRoot = root.Visual as IRenderRoot; - - _index = index; - Root = root; - Layers = layers; - Generation = generation; - root.LayerRoot = root.Visual; - } - - public Task Rendered => _rendered.Task; - - /// - /// Gets a value identifying the scene's generation. This is incremented each time the scene is cloned. - /// - public int Generation { get; } - - /// - /// Gets the layers for the scene. - /// - public SceneLayers Layers { get; } - - /// - /// Gets the root node of the scene graph. - /// - public IVisualNode Root { get; } - - /// - /// Gets or sets the size of the scene in device independent pixels. - /// - public Size Size { get; set; } - - /// - /// Gets or sets the scene scaling. - /// - public double Scaling { get; set; } = 1; - - /// - /// Adds a node to the scene index. - /// - /// The node. - public void Add(IVisualNode node) - { - _ = node ?? throw new ArgumentNullException(nameof(node)); - - _index.Add(node.Visual, node); - } - - /// - /// Clones the scene. - /// - /// The cloned scene. - public Scene CloneScene() - { - var index = new Dictionary(_index.Count); - var root = Clone((VisualNode)Root, null, index); - - var result = new Scene(root, index, Layers.Clone(), Generation + 1) - { - Size = Size, - Scaling = Scaling, - }; - - return result; - } - - public void Dispose() - { - _rendered.TrySetResult(false); - foreach (var node in _index.Values) - { - node.Dispose(); - } - } - - /// - /// Tries to find a node in the scene graph representing the specified visual. - /// - /// The visual. - /// - /// The node representing the visual or null if it could not be found. - /// - public IVisualNode? FindNode(Visual visual) - { - _index.TryGetValue(visual, out var node); - return node; - } - - /// - /// Gets the visuals at a point in the scene. - /// - /// The point. - /// The root of the subtree to search. - /// A filter. May be null. - /// The visuals at the specified point. - public IEnumerable HitTest(Point p, Visual root, Func? filter) - { - var node = FindNode(root); - return (node != null) ? new HitTestEnumerable(node, filter, p, Root) : Enumerable.Empty(); - } - - /// - /// Gets the visual at a point in the scene. - /// - /// The point. - /// The root of the subtree to search. - /// A filter. May be null. - /// The visual at the specified point. - public Visual? HitTestFirst(Point p, Visual root, Func? filter) - { - var node = FindNode(root); - return (node != null) ? HitTestFirst(node, p, filter) : null; - } - - /// - /// Removes a node from the scene index. - /// - /// The node. - public void Remove(IVisualNode node) - { - _ = node ?? throw new ArgumentNullException(nameof(node)); - - _index.Remove(node.Visual); - - node.Dispose(); - } - - private VisualNode Clone(VisualNode source, IVisualNode? parent, Dictionary index) - { - var result = source.Clone(parent); - - index.Add(result.Visual, result); - - var children = source.Children; - var childrenCount = children.Count; - - if (childrenCount > 0) - { - result.TryPreallocateChildren(childrenCount); - - for (var i = 0; i < childrenCount; i++) - { - var child = children[i]; - - result.AddChild(Clone((VisualNode)child, result, index)); - } - } - - return result; - } - - private Visual HitTestFirst(IVisualNode root, Point p, Func? filter) - { - using var enumerator = new HitTestEnumerator(root, filter, p, Root); - - enumerator.MoveNext(); - - return enumerator.Current; - } - - private class HitTestEnumerable : IEnumerable - { - private readonly IVisualNode _root; - private readonly Func? _filter; - private readonly IVisualNode _sceneRoot; - private readonly Point _point; - - public HitTestEnumerable(IVisualNode root, Func? filter, Point point, IVisualNode sceneRoot) - { - _root = root; - _filter = filter; - _point = point; - _sceneRoot = sceneRoot; - } - - public IEnumerator GetEnumerator() - { - return new HitTestEnumerator(_root, _filter, _point, _sceneRoot); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } - - private struct HitTestEnumerator : IEnumerator - { - private readonly PooledStack _nodeStack; - private readonly Func? _filter; - private readonly IVisualNode _sceneRoot; - private Visual? _current; - private readonly Point _point; - - public HitTestEnumerator(IVisualNode root, Func? filter, Point point, IVisualNode sceneRoot) - { - _nodeStack = new PooledStack(); - _nodeStack.Push(new Entry(root, false, null, true)); - - _filter = filter; - _point = point; - _sceneRoot = sceneRoot; - - _current = null; - } - - public bool MoveNext() - { - while (_nodeStack.Count > 0) - { - (var wasVisited, var isRoot, IVisualNode node, Rect? clip) = _nodeStack.Pop(); - - if (wasVisited && isRoot) - { - break; - } - - var children = node.Children; - int childCount = children.Count; - - if (childCount == 0 || wasVisited) - { - if ((wasVisited || FilterAndClip(node, ref clip)) && - (node.Visual is ICustomHitTest custom ? custom.HitTest(_point) : node.HitTest(_point))) - { - _current = node.Visual; - - return true; - } - } - else if (FilterAndClip(node, ref clip)) - { - _nodeStack.Push(new Entry(node, true, null)); - - for (var i = 0; i < childCount; i++) - { - _nodeStack.Push(new Entry(children[i], false, clip)); - } - } - } - - return false; - } - - public void Reset() - { - throw new NotSupportedException(); - } - - public Visual Current => _current!; - - object IEnumerator.Current => Current; - - public void Dispose() - { - _nodeStack.Dispose(); - } - - private bool FilterAndClip(IVisualNode node, ref Rect? clip) - { - if (_filter?.Invoke(node.Visual) != false && node.Visual.IsAttachedToVisualTree) - { - var clipped = false; - - if (node.ClipToBounds) - { - clip = clip == null ? node.ClipBounds : clip.Value.Intersect(node.ClipBounds); - clipped = !clip.Value.ContainsExclusive(_point); - } - - if (node.GeometryClip != null) - { - var controlPoint = _sceneRoot.Visual.TranslatePoint(_point, node.Visual); - clipped = !node.GeometryClip.FillContains(controlPoint!.Value); - } - - if (!clipped && node.Visual is ICustomHitTest custom) - { - clipped = !custom.HitTest(_point); - } - - return !clipped; - } - - return false; - } - - private readonly struct Entry - { - public readonly bool WasVisited; - public readonly bool IsRoot; - public readonly IVisualNode Node; - public readonly Rect? Clip; - - public Entry(IVisualNode node, bool wasVisited, Rect? clip, bool isRoot = false) - { - Node = node; - WasVisited = wasVisited; - IsRoot = isRoot; - Clip = clip; - } - - public void Deconstruct(out bool wasVisited, out bool isRoot, out IVisualNode node, out Rect? clip) - { - wasVisited = WasVisited; - isRoot = IsRoot; - node = Node; - clip = Clip; - } - } - } - - public void MarkAsRendered() => _rendered.TrySetResult(true); - - public List? RenderThreadJobs { get; set; } - } -} diff --git a/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs deleted file mode 100644 index 55ff772772..0000000000 --- a/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs +++ /dev/null @@ -1,485 +0,0 @@ -using System; -using System.Collections.Generic; -using Avalonia.Media; -using Avalonia.Platform; -using Avalonia.Threading; -using Avalonia.VisualTree; - -namespace Avalonia.Rendering.SceneGraph -{ - /// - /// Builds a scene graph from a visual tree. - /// - public class SceneBuilder : ISceneBuilder - { - /// - public void UpdateAll(Scene scene) - { - _ = scene ?? throw new ArgumentNullException(nameof(scene)); - Dispatcher.UIThread.VerifyAccess(); - - UpdateSize(scene); - scene.Layers.GetOrAdd(scene.Root.Visual); - - using (var impl = new DeferredDrawingContextImpl(this, scene.Layers)) - using (var context = new DrawingContext(impl)) - { - var clip = new Rect(scene.Root.Visual.Bounds.Size); - Update(context, scene, (VisualNode)scene.Root, clip, true); - } - } - - /// - public bool Update(Scene scene, Visual visual) - { - _ = scene ?? throw new ArgumentNullException(nameof(scene)); - _ = visual ?? throw new ArgumentNullException(nameof(visual)); - - Dispatcher.UIThread.VerifyAccess(); - - if (!scene.Root.Visual.IsVisible) - { - throw new AvaloniaInternalException("Cannot update the scene for an invisible root visual."); - } - - var node = (VisualNode?)scene.FindNode(visual); - - if (visual == scene.Root.Visual) - { - UpdateSize(scene); - } - - if (visual.VisualRoot == scene.Root.Visual) - { - if (node?.Parent != null && - visual.VisualParent != null && - node.Parent.Visual != visual.VisualParent) - { - // The control has changed parents. Remove the node and recurse into the new parent node. - ((VisualNode)node.Parent).RemoveChild(node); - Deindex(scene, node); - node = (VisualNode?)scene.FindNode(visual.VisualParent); - } - - if (visual.IsVisible) - { - // If the node isn't yet part of the scene, find the nearest ancestor that is. - node = node ?? FindExistingAncestor(scene, visual); - - // We don't need to do anything if this part of the tree has already been fully - // updated. - if (node != null && !node.SubTreeUpdated) - { - // If the control we've been asked to update isn't part of the scene then - // we're carrying out an add operation, so recurse and add all the - // descendents too. - var recurse = node.Visual != visual; - - using (var impl = new DeferredDrawingContextImpl(this, scene.Layers)) - using (var context = new DrawingContext(impl)) - { - var clip = new Rect(scene.Root.Visual.Bounds.Size); - - if (node.Parent != null) - { - context.PushPostTransform(node.Parent.Transform); - clip = node.Parent.ClipBounds; - } - - using (context.PushTransformContainer()) - { - Update(context, scene, node, clip, recurse); - } - } - - return true; - } - } - else - { - if (node != null) - { - // The control has been hidden so remove it from its parent and deindex the - // node and its descendents. - ((VisualNode?)node.Parent)?.RemoveChild(node); - Deindex(scene, node); - return true; - } - } - } - else if (node != null) - { - // The control has been removed so remove it from its parent and deindex the - // node and its descendents. - var trim = FindFirstDeadAncestor(scene, node); - ((VisualNode)trim.Parent!).RemoveChild(trim); - Deindex(scene, trim); - return true; - } - - return false; - } - - private static VisualNode? FindExistingAncestor(Scene scene, Visual visual) - { - var node = scene.FindNode(visual); - - while (node == null && visual.IsVisible) - { - var parent = visual.VisualParent; - - if (parent is null) - return null; - - visual = parent; - node = scene.FindNode(visual); - } - - return visual.IsVisible ? (VisualNode?)node : null; - } - - private static VisualNode FindFirstDeadAncestor(Scene scene, IVisualNode node) - { - var parent = node.Parent; - - while (parent!.Visual.VisualRoot == null) - { - node = parent; - parent = node.Parent; - } - - return (VisualNode)node; - } - - private static object GetOrCreateChildNode(Scene scene, Visual child, VisualNode parent) - { - var result = (VisualNode?)scene.FindNode(child); - - if (result != null && result.Parent != parent) - { - Deindex(scene, result); - ((VisualNode?)result.Parent)?.RemoveChild(result); - result = null; - } - - return result ?? CreateNode(scene, child, parent); - } - - private static void Update(DrawingContext context, Scene scene, VisualNode node, Rect clip, bool forceRecurse) - { - var visual = node.Visual; - var opacity = visual.Opacity; - var clipToBounds = visual.ClipToBounds; -#pragma warning disable CS0618 // Type or member is obsolete - var clipToBoundsRadius = visual is IVisualWithRoundRectClip roundRectClip ? - roundRectClip.ClipToBoundsRadius : - default; -#pragma warning restore CS0618 // Type or member is obsolete - - var bounds = new Rect(visual.Bounds.Size); - var contextImpl = (DeferredDrawingContextImpl)context.PlatformImpl; - - contextImpl.Layers.Find(node.LayerRoot!)?.Dirty.Add(node.Bounds); - - if (visual.IsVisible) - { - var m = node != scene.Root ? - Matrix.CreateTranslation(visual.Bounds.Position) : - Matrix.Identity; - - var renderTransform = Matrix.Identity; - - // this should be calculated BEFORE renderTransform - if (visual.HasMirrorTransform) - { - var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0); - renderTransform *= mirrorMatrix; - } - - if (visual.RenderTransform != null) - { - var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height)); - var offset = Matrix.CreateTranslation(origin); - var finalTransform = (-offset) * visual.RenderTransform.Value * (offset); - renderTransform *= finalTransform; - } - - m = renderTransform * m; - - using (contextImpl.BeginUpdate(node)) - using (context.PushPostTransform(m)) - using (context.PushTransformContainer()) - { - var globalBounds = bounds.TransformToAABB(contextImpl.Transform); - var clipBounds = clipToBounds ? - globalBounds.Intersect(clip) : - clip; - - forceRecurse = forceRecurse || - node.ClipBounds != clipBounds || - node.Opacity != opacity || - node.Transform != contextImpl.Transform; - - node.Transform = contextImpl.Transform; - node.ClipBounds = clipBounds; - node.ClipToBounds = clipToBounds; - node.LayoutBounds = globalBounds; - node.ClipToBoundsRadius = clipToBoundsRadius; - node.GeometryClip = visual.Clip?.PlatformImpl; - node.Opacity = opacity; - - // TODO: Check equality between node.OpacityMask and visual.OpacityMask before assigning. - node.OpacityMask = visual.OpacityMask?.ToImmutable(); - - if (ShouldStartLayer(visual)) - { - if (node.LayerRoot != visual) - { - MakeLayer(scene, node); - } - else - { - UpdateLayer(node, scene.Layers[node.LayerRoot]); - } - } - else if (node.LayerRoot == node.Visual && node.Parent != null) - { - ClearLayer(scene, node); - } - - if (node.ClipToBounds) - { - clip = clip.Intersect(node.ClipBounds); - } - - try - { - visual.Render(context); - } - catch { } - - var transformed = new TransformedBounds(new Rect(visual.Bounds.Size), clip, node.Transform); - visual.SetTransformedBounds(transformed); - - if (forceRecurse) - { - var visualChildren = (IList) visual.VisualChildren; - - node.TryPreallocateChildren(visualChildren.Count); - - if (visualChildren.Count == 1) - { - var childNode = GetOrCreateChildNode(scene, visualChildren[0], node); - Update(context, scene, (VisualNode)childNode, clip, forceRecurse); - } - else if (visualChildren.Count > 1) - { - var count = visualChildren.Count; - - if (visual.HasNonUniformZIndexChildren) - { - var sortedChildren = new (Visual visual, int index)[count]; - - for (var i = 0; i < count; i++) - { - sortedChildren[i] = (visualChildren[i], i); - } - - // Regular Array.Sort is unstable, we need to provide indices as well to avoid reshuffling elements. - Array.Sort(sortedChildren, (lhs, rhs) => - { - var result = ZIndexComparer.Instance.Compare(lhs.visual, rhs.visual); - - return result == 0 ? lhs.index.CompareTo(rhs.index) : result; - }); - - foreach (var child in sortedChildren) - { - var childNode = GetOrCreateChildNode(scene, child.Item1, node); - Update(context, scene, (VisualNode)childNode, clip, forceRecurse); - } - } - else - foreach (var child in visualChildren) - { - var childNode = GetOrCreateChildNode(scene, child, node); - Update(context, scene, (VisualNode)childNode, clip, forceRecurse); - } - } - - node.SubTreeUpdated = true; - contextImpl.TrimChildren(); - } - } - } - else - { - contextImpl.BeginUpdate(node).Dispose(); - } - } - - private static void UpdateSize(Scene scene) - { - var renderRoot = scene.Root.Visual as IRenderRoot; - var newSize = renderRoot?.ClientSize ?? scene.Root.Visual.Bounds.Size; - - scene.Scaling = renderRoot?.RenderScaling ?? 1; - - if (scene.Size != newSize) - { - var oldSize = scene.Size; - - scene.Size = newSize; - - Rect horizontalDirtyRect = default; - Rect verticalDirtyRect = default; - - if (newSize.Width > oldSize.Width) - { - horizontalDirtyRect = new Rect(oldSize.Width, 0, newSize.Width - oldSize.Width, oldSize.Height); - } - - if (newSize.Height > oldSize.Height) - { - verticalDirtyRect = new Rect(0, oldSize.Height, newSize.Width, newSize.Height - oldSize.Height); - } - - foreach (var layer in scene.Layers) - { - layer.Dirty.Add(horizontalDirtyRect); - layer.Dirty.Add(verticalDirtyRect); - } - } - } - - private static VisualNode CreateNode(Scene scene, Visual visual, VisualNode parent) - { - var node = new VisualNode(visual, parent); - node.LayerRoot = parent.LayerRoot; - scene.Add(node); - return node; - } - - private static void Deindex(Scene scene, VisualNode node) - { - var nodeChildren = node.Children; - var nodeChildrenCount = nodeChildren.Count; - - for (var i = 0; i < nodeChildrenCount; i++) - { - if (nodeChildren[i] is VisualNode visual) - { - Deindex(scene, visual); - } - } - - scene.Remove(node); - - node.SubTreeUpdated = true; - - scene.Layers[node.LayerRoot!].Dirty.Add(node.Bounds); - - node.Visual.SetTransformedBounds(null); - - if (node.LayerRoot == node.Visual && node.Visual != scene.Root.Visual) - { - scene.Layers.Remove(node.LayerRoot); - } - } - - private static void ClearLayer(Scene scene, VisualNode node) - { - var parent = (VisualNode)node.Parent!; - var oldLayerRoot = node.LayerRoot; - var newLayerRoot = parent.LayerRoot!; - var existingDirtyRects = scene.Layers[node.LayerRoot!].Dirty; - var newDirtyRects = scene.Layers[newLayerRoot].Dirty; - - existingDirtyRects.Coalesce(); - - foreach (var r in existingDirtyRects) - { - newDirtyRects.Add(r); - } - - var oldLayer = scene.Layers[oldLayerRoot!]; - PropagateLayer(node, scene.Layers[newLayerRoot], oldLayer); - scene.Layers.Remove(oldLayer); - } - - private static void MakeLayer(Scene scene, VisualNode node) - { - var oldLayerRoot = node.LayerRoot!; - var layer = scene.Layers.Add(node.Visual); - var oldLayer = scene.Layers[oldLayerRoot!]; - - UpdateLayer(node, layer); - PropagateLayer(node, layer, scene.Layers[oldLayerRoot]); - } - - private static void UpdateLayer(VisualNode node, SceneLayer layer) - { - layer.Opacity = node.Visual.Opacity; - - if (node.Visual.OpacityMask != null) - { - layer.OpacityMask = node.Visual.OpacityMask?.ToImmutable(); - layer.OpacityMaskRect = node.ClipBounds; - } - else - { - layer.OpacityMask = null; - layer.OpacityMaskRect = default; - } - - layer.GeometryClip = node.HasAncestorGeometryClip ? - CreateLayerGeometryClip(node) : - null; - } - - private static void PropagateLayer(VisualNode node, SceneLayer layer, SceneLayer oldLayer) - { - node.LayerRoot = layer.LayerRoot; - - layer.Dirty.Add(node.Bounds); - oldLayer.Dirty.Add(node.Bounds); - - foreach (VisualNode child in node.Children) - { - // If the child is not the start of a new layer, recurse. - if (child.LayerRoot != child.Visual) - { - PropagateLayer(child, layer, oldLayer); - } - } - } - - // HACK: Disabled layers because they're broken in current renderer. See #2244. - private static bool ShouldStartLayer(Visual visual) => false; - - private static IGeometryImpl? CreateLayerGeometryClip(VisualNode node) - { - IGeometryImpl? result = null; - VisualNode? n = node; - - for (;;) - { - n = (VisualNode?)n!.Parent; - - if (n == null || (n.GeometryClip == null && !n.HasAncestorGeometryClip)) - { - break; - } - - if (n?.GeometryClip != null) - { - var transformed = n.GeometryClip.WithTransform(n.Transform); - - result = result == null ? transformed : result.Intersect(transformed); - } - } - - return result; - } - } -} diff --git a/src/Avalonia.Base/Rendering/SceneGraph/SceneLayer.cs b/src/Avalonia.Base/Rendering/SceneGraph/SceneLayer.cs deleted file mode 100644 index a5e3b88188..0000000000 --- a/src/Avalonia.Base/Rendering/SceneGraph/SceneLayer.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Avalonia.Media; -using Avalonia.Platform; - -namespace Avalonia.Rendering.SceneGraph -{ - /// - /// Represents a layer in a . - /// - public class SceneLayer - { - /// - /// Initializes a new instance of the class. - /// - /// The visual at the root of the layer. - /// The distance from the scene root. - public SceneLayer(Visual layerRoot, int distanceFromRoot) - { - LayerRoot = layerRoot; - Dirty = new DirtyRects(); - DistanceFromRoot = distanceFromRoot; - } - - /// - /// Clones the layer. - /// - /// The cloned layer. - public SceneLayer Clone() - { - return new SceneLayer(LayerRoot, DistanceFromRoot) - { - Opacity = Opacity, - OpacityMask = OpacityMask, - OpacityMaskRect = OpacityMaskRect, - GeometryClip = GeometryClip, - }; - } - - /// - /// Gets the visual at the root of the layer. - /// - public Visual LayerRoot { get; } - - /// - /// Gets the distance of the layer root from the root of the scene. - /// - public int DistanceFromRoot { get; } - - /// - /// Gets or sets the opacity of the layer. - /// - public double Opacity { get; set; } = 1; - - /// - /// Gets or sets the opacity mask for the layer. - /// - public IBrush? OpacityMask { get; set; } - - /// - /// Gets or sets the target rectangle for the layer opacity mask. - /// - public Rect OpacityMaskRect { get; set; } - - /// - /// Gets the layer's geometry clip. - /// - public IGeometryImpl? GeometryClip { get; set; } - - /// - /// Gets the dirty rectangles for the layer. - /// - internal DirtyRects Dirty { get; } - } -} diff --git a/src/Avalonia.Base/Rendering/SceneGraph/SceneLayers.cs b/src/Avalonia.Base/Rendering/SceneGraph/SceneLayers.cs deleted file mode 100644 index 8a997c5ace..0000000000 --- a/src/Avalonia.Base/Rendering/SceneGraph/SceneLayers.cs +++ /dev/null @@ -1,206 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using Avalonia.VisualTree; - -namespace Avalonia.Rendering.SceneGraph -{ - /// - /// Holds a collection of layers for a . - /// - public class SceneLayers : IEnumerable - { - private readonly Visual _root; - private readonly List _inner; - private readonly Dictionary _index; - - /// - /// Initializes a new instance of the class. - /// - /// The scene's root visual. - public SceneLayers(Visual root) : this(root, 0) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The scene's root visual. - /// Initial layer capacity. - public SceneLayers(Visual root, int capacity) - { - _root = root; - - _inner = new List(capacity); - _index = new Dictionary(capacity); - } - - /// - /// Gets the number of layers in the scene. - /// - public int Count => _inner.Count; - - /// - /// Gets a value indicating whether any of the layers have a dirty region. - /// - public bool HasDirty - { - get - { - foreach (var layer in _inner) - { - if (!layer.Dirty.IsEmpty) - { - return true; - } - } - - return false; - } - } - - /// - /// Gets a layer by index. - /// - /// The index of the layer. - /// The layer. - public SceneLayer this[int index] => _inner[index]; - - /// - /// Gets a layer by its root visual. - /// - /// The layer's root visual. - /// The layer. - public SceneLayer this[Visual visual] => _index[visual]; - - /// - /// Adds a layer to the scene. - /// - /// The root visual of the layer. - /// The created layer. - public SceneLayer Add(Visual layerRoot) - { - _ = layerRoot ?? throw new ArgumentNullException(nameof(layerRoot)); - - var distance = layerRoot.CalculateDistanceFromAncestor(_root); - var layer = new SceneLayer(layerRoot, distance); - var insert = FindInsertIndex(layer); - _index.Add(layerRoot, layer); - _inner.Insert(insert, layer); - return layer; - } - - /// - /// Makes a deep clone of the layers. - /// - /// The cloned layers. - public SceneLayers Clone() - { - var result = new SceneLayers(_root, Count); - - foreach (var src in _inner) - { - var dest = src.Clone(); - result._index.Add(dest.LayerRoot, dest); - result._inner.Add(dest); - } - - return result; - } - - /// - /// Tests whether a layer exists with the specified root visual. - /// - /// The root visual. - /// - /// True if a layer exists with the specified root visual, otherwise false. - /// - public bool Exists(Visual layerRoot) - { - _ = layerRoot ?? throw new ArgumentNullException(nameof(layerRoot)); - - return _index.ContainsKey(layerRoot); - } - - /// - /// Tries to find a layer with the specified root visual. - /// - /// The root visual. - /// The layer if found, otherwise null. - public SceneLayer? Find(Visual layerRoot) - { - _index.TryGetValue(layerRoot, out var result); - return result; - } - - /// - /// Gets an existing layer or creates a new one if no existing layer is found. - /// - /// The root visual. - /// The layer. - public SceneLayer GetOrAdd(Visual layerRoot) - { - _ = layerRoot ?? throw new ArgumentNullException(nameof(layerRoot)); - - if (!_index.TryGetValue(layerRoot, out var result)) - { - result = Add(layerRoot); - } - - return result; - } - - /// - /// Removes a layer from the scene. - /// - /// The root visual. - /// True if a matching layer was removed, otherwise false. - public bool Remove(Visual layerRoot) - { - _ = layerRoot ?? throw new ArgumentNullException(nameof(layerRoot)); - - if (_index.TryGetValue(layerRoot, out var layer)) - { - Remove(layer); - } - - return layer != null; - } - - /// - /// Removes a layer from the scene. - /// - /// The layer. - /// True if the layer was part of the scene, otherwise false. - public bool Remove(SceneLayer layer) - { - _ = layer ?? throw new ArgumentNullException(nameof(layer)); - - _index.Remove(layer.LayerRoot); - return _inner.Remove(layer); - } - - /// - public IEnumerator GetEnumerator() => _inner.GetEnumerator(); - - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - private int FindInsertIndex(SceneLayer insert) - { - var index = 0; - - foreach (var layer in _inner) - { - if (layer.DistanceFromRoot > insert.DistanceFromRoot) - { - break; - } - - ++index; - } - - return index; - } - } -} diff --git a/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs deleted file mode 100644 index b9491e6cbd..0000000000 --- a/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs +++ /dev/null @@ -1,448 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Avalonia.Reactive; -using Avalonia.Media; -using Avalonia.Platform; -using Avalonia.Utilities; -using Avalonia.VisualTree; - -namespace Avalonia.Rendering.SceneGraph -{ - /// - /// A node in the low-level scene graph representing an . - /// - internal class VisualNode : IVisualNode - { - private static readonly IReadOnlyList EmptyChildren = Array.Empty(); - private static readonly IReadOnlyList> EmptyDrawOperations = Array.Empty>(); - - private Rect? _bounds; - private double _opacity; - private List? _children; - private List>? _drawOperations; - private IRef? _drawOperationsRefCounter; - private bool _drawOperationsCloned; - private Matrix transformRestore; - - /// - /// Initializes a new instance of the class. - /// - /// The visual that this node represents. - /// The parent scene graph node, if any. - public VisualNode(Visual visual, IVisualNode? parent) - { - Visual = visual ?? throw new ArgumentNullException(nameof(visual)); - Parent = parent; - HasAncestorGeometryClip = parent != null && - (parent.HasAncestorGeometryClip || parent.GeometryClip != null); - } - - /// - public Visual Visual { get; } - - /// - public IVisualNode? Parent { get; } - - /// - public CornerRadius ClipToBoundsRadius { get; set; } - - /// - public Matrix Transform { get; set; } - - /// - public Rect Bounds => _bounds ?? CalculateBounds(); - - /// - public Rect ClipBounds { get; set; } - - /// - public Rect LayoutBounds { get; set; } - - /// - public bool ClipToBounds { get; set; } - - /// - public IGeometryImpl? GeometryClip { get; set; } - - /// - public bool HasAncestorGeometryClip { get; } - - /// - public double Opacity - { - get { return _opacity; } - set - { - if (_opacity != value) - { - _opacity = value; - OpacityChanged = true; - } - } - } - - /// - /// Gets or sets the opacity mask for the scene graph node. - /// - public IBrush? OpacityMask { get; set; } - - /// - /// Gets a value indicating whether this node in the scene graph has already - /// been updated in the current update pass. - /// - public bool SubTreeUpdated { get; set; } - - /// - /// Gets a value indicating whether the property has changed. - /// - public bool OpacityChanged { get; private set; } - - public Visual? LayerRoot { get; set; } - - /// - public IReadOnlyList Children => _children ?? EmptyChildren; - - /// - public IReadOnlyList> DrawOperations => _drawOperations ?? EmptyDrawOperations; - - /// - /// Adds a child to the collection. - /// - /// The child to add. - public void AddChild(IVisualNode child) - { - if (child.Disposed) - { - throw new ObjectDisposedException("Visual node for {node.Visual}"); - } - - if (child.Parent != this) - { - throw new AvaloniaInternalException("VisualNode added to wrong parent."); - } - - EnsureChildrenCreated(); - _children.Add(child); - } - - /// - /// Adds an operation to the collection. - /// - /// The operation to add. - public void AddDrawOperation(IRef operation) - { - EnsureDrawOperationsCreated(); - _drawOperations.Add(operation.Clone()); - } - - /// - /// Removes a child from the collection. - /// - /// The child to remove. - public void RemoveChild(IVisualNode child) - { - EnsureChildrenCreated(); - _children.Remove(child); - } - - /// - /// Replaces a child in the collection. - /// - /// The child to be replaced. - /// The child to add. - public void ReplaceChild(int index, IVisualNode node) - { - if (node.Disposed) - { - throw new ObjectDisposedException("Visual node for {node.Visual}"); - } - - if (node.Parent != this) - { - throw new AvaloniaInternalException("VisualNode added to wrong parent."); - } - - EnsureChildrenCreated(); - _children[index] = node; - } - - /// - /// Replaces an item in the collection. - /// - /// The operation to be replaced. - /// The operation to add. - public void ReplaceDrawOperation(int index, IRef operation) - { - EnsureDrawOperationsCreated(); - var old = _drawOperations[index]; - _drawOperations[index] = operation.Clone(); - old.Dispose(); - } - - /// - /// Sorts the collection according to the order of the visual's - /// children and their z-index. - /// - /// The scene that the node is a part of. - public void SortChildren(Scene scene) - { - if (_children == null || _children.Count <= 1) - { - return; - } - - var keys = new List(Visual.VisualChildren.Count); - - for (var i = 0; i < Visual.VisualChildren.Count; ++i) - { - var child = Visual.VisualChildren[i]; - var zIndex = child.ZIndex; - keys.Add(((long)zIndex << 32) + i); - } - - keys.Sort(); - _children.Clear(); - - foreach (var i in keys) - { - var child = Visual.VisualChildren[(int)(i & 0xffffffff)]; - var node = scene.FindNode(child); - - if (node != null) - { - _children.Add(node); - } - } - } - - /// - /// Removes items in the collection from the specified index - /// to the end. - /// - /// The index of the first child to be removed. - public void TrimChildren(int first) - { - if (first < _children?.Count) - { - EnsureChildrenCreated(); - for (int i = first; i < _children.Count; i++) - { - _children[i].Dispose(); - } - _children.RemoveRange(first, _children.Count - first); - } - } - - /// - /// Removes items in the collection from the specified index - /// to the end. - /// - /// The index of the first operation to be removed. - public void TrimDrawOperations(int first) - { - if (first < _drawOperations?.Count) - { - EnsureDrawOperationsCreated(); - for (int i = first; i < _drawOperations.Count; i++) - { - _drawOperations[i].Dispose(); - } - _drawOperations.RemoveRange(first, _drawOperations.Count - first); - } - } - - /// - /// Makes a copy of the node - /// - /// The new parent node. - /// A cloned node. - public VisualNode Clone(IVisualNode? parent) - { - return new VisualNode(Visual, parent) - { - Transform = Transform, - ClipBounds = ClipBounds, - ClipToBoundsRadius = ClipToBoundsRadius, - ClipToBounds = ClipToBounds, - LayoutBounds = LayoutBounds, - GeometryClip = GeometryClip, - _opacity = Opacity, - OpacityMask = OpacityMask, - _drawOperations = _drawOperations, - _drawOperationsRefCounter = _drawOperationsRefCounter?.Clone(), - _drawOperationsCloned = true, - LayerRoot= LayerRoot, - }; - } - - /// - public bool HitTest(Point p) - { - var drawOperations = DrawOperations; - var drawOperationsCount = drawOperations.Count; - - for (var i = 0; i < drawOperationsCount; i++) - { - var operation = drawOperations[i]; - - if (operation?.Item?.HitTest(p) == true) - { - return true; - } - } - - return false; - } - - /// - public void BeginRender(IDrawingContextImpl context, bool skipOpacity) - { - transformRestore = context.Transform; - - if (ClipToBounds) - { - context.Transform = Matrix.Identity; - if (ClipToBoundsRadius.IsDefault) - context.PushClip(ClipBounds); - else - context.PushClip(new RoundedRect(ClipBounds, ClipToBoundsRadius)); - } - - context.Transform = Transform; - - if (Opacity != 1 && !skipOpacity) - { - context.PushOpacity(Opacity); - } - - if (GeometryClip != null) - { - context.PushGeometryClip(GeometryClip); - } - - if (OpacityMask != null) - { - context.PushOpacityMask(OpacityMask, LayoutBounds); - } - } - - /// - public void EndRender(IDrawingContextImpl context, bool skipOpacity) - { - if (OpacityMask != null) - { - context.PopOpacityMask(); - } - - if (GeometryClip != null) - { - context.PopGeometryClip(); - } - - if (Opacity != 1 && !skipOpacity) - { - context.PopOpacity(); - } - - if (ClipToBounds) - { - context.Transform = Matrix.Identity; - context.PopClip(); - } - - context.Transform = transformRestore; - } - - internal void TryPreallocateChildren(int count) - { - if (count == 0) - { - return; - } - - EnsureChildrenCreated(count); - } - - private Rect CalculateBounds() - { - var result = new Rect(); - - if (_drawOperations != null) - { - foreach (var operation in _drawOperations) - { - result = result.Union(operation.Item.Bounds); - } - } - - _bounds = result; - return result; - } - - [MemberNotNull(nameof(_children))] - private void EnsureChildrenCreated(int capacity = 0) - { - if (_children == null) - { - _children = new List(capacity); - } - } - - /// - /// Ensures that this node draw operations have been created and are mutable (in case we are using cloned operations). - /// - [MemberNotNull(nameof(_drawOperations))] - private void EnsureDrawOperationsCreated() - { - if (_drawOperations == null) - { - _drawOperations = new List>(); - _drawOperationsRefCounter = RefCountable.Create(CreateDisposeDrawOperations(_drawOperations)); - _drawOperationsCloned = false; - } - else if (_drawOperationsCloned) - { - var oldDrawOperations = _drawOperations; - - _drawOperations = new List>(oldDrawOperations.Count); - - foreach (var drawOperation in oldDrawOperations) - { - _drawOperations.Add(drawOperation.Clone()); - } - - _drawOperationsRefCounter?.Dispose(); - _drawOperationsRefCounter = RefCountable.Create(CreateDisposeDrawOperations(_drawOperations)); - _drawOperationsCloned = false; - } - } - - /// - /// Creates disposable that will dispose all items in passed draw operations after being disposed. - /// It is crucial that we don't capture current instance - /// as draw operations can be cloned and may persist across subsequent scenes. - /// - /// Draw operations that need to be disposed. - /// Disposable for given draw operations. - private static IDisposable CreateDisposeDrawOperations(List> drawOperations) - { - return Disposable.Create(drawOperations, operations => - { - foreach (var operation in operations) - { - operation.Dispose(); - } - }); - } - - public bool Disposed { get; private set; } - - public void Dispose() - { - _drawOperationsRefCounter?.Dispose(); - - Disposed = true; - } - } -} diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index 7fcb53bcea..e6d7492c51 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -36,12 +36,7 @@ namespace Avalonia /// public static readonly DirectProperty BoundsProperty = AvaloniaProperty.RegisterDirect(nameof(Bounds), o => o.Bounds); - - public static readonly DirectProperty TransformedBoundsProperty = - AvaloniaProperty.RegisterDirect( - nameof(TransformedBounds), - o => o.TransformedBounds); - + /// /// Defines the property. /// @@ -116,7 +111,6 @@ namespace Avalonia (s, h) => s.Invalidated -= h); private Rect _bounds; - private TransformedBounds? _transformedBounds; private IRenderRoot? _visualRoot; private Visual? _visualParent; private bool _hasMirrorTransform; @@ -172,11 +166,6 @@ namespace Avalonia protected set { SetAndRaise(BoundsProperty, ref _bounds, value); } } - /// - /// Gets the bounds of the control relative to the window, accounting for rendering transforms. - /// - public TransformedBounds? TransformedBounds => _transformedBounds; - /// /// Gets or sets a value indicating whether the control should be clipped to its bounds. /// @@ -523,11 +512,6 @@ namespace Avalonia return CompositionVisual; } - internal void SetTransformedBounds(TransformedBounds? value) - { - SetAndRaise(TransformedBoundsProperty, ref _transformedBounds, value); - } - /// /// Calls the method /// for this control and all of its visual descendants. diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index 64f96b6987..c976ad6255 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -1,10 +1,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Metadata; using Avalonia.Platform; using Avalonia.Rendering; +using Avalonia.Rendering.Composition; +using Avalonia.Threading; namespace Avalonia.Controls.Embedding.Offscreen { @@ -13,7 +16,7 @@ namespace Avalonia.Controls.Embedding.Offscreen { private double _scaling = 1; private Size _clientSize; - private PlatformRenderInterfaceContextManager _renderInterface = new(null); + private ManualRenderTimer _manualRenderTimer = new(); public IInputRoot? InputRoot { get; private set; } public bool IsDisposed { get; private set; } @@ -23,10 +26,19 @@ namespace Avalonia.Controls.Embedding.Offscreen IsDisposed = true; } + class ManualRenderTimer : IRenderTimer + { + static Stopwatch St = Stopwatch.StartNew(); + public event Action? Tick; + public bool RunsInBackground => false; + public void TriggerTick() => Tick?.Invoke(St.Elapsed); + } + + public IRenderer CreateRenderer(IRenderRoot root) => - new ImmediateRenderer((Visual)root, () => _renderInterface.CreateRenderTarget(Surfaces), _renderInterface); + new CompositingRenderer(root, new Compositor(new RenderLoop(_manualRenderTimer, Dispatcher.UIThread), null), + () => Surfaces); - public abstract void Invalidate(Rect rect); public abstract IEnumerable Surfaces { get; } public Size ClientSize diff --git a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs index 3ccddf4155..b03099f750 100644 --- a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs +++ b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs @@ -15,7 +15,7 @@ namespace Avalonia.Controls.Notifications /// [TemplatePart("PART_Items", typeof(Panel))] [PseudoClasses(":topleft", ":topright", ":bottomleft", ":bottomright")] - public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager, ICustomSimpleHitTest + public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager { private IList? _items; @@ -160,7 +160,5 @@ namespace Avalonia.Controls.Notifications PseudoClasses.Set(":bottomleft", position == NotificationPosition.BottomLeft); PseudoClasses.Set(":bottomright", position == NotificationPosition.BottomRight); } - - public bool HitTest(Point point) => VisualChildren.HitTestCustom(point); } } diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index e09da02f17..de3aca76d9 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -350,13 +350,19 @@ namespace Avalonia.Controls.Platform { // HACK: #8179 needs to be addressed to correctly implement it in the PointerPressed method. var item = GetMenuItem(e.Source as Control) as MenuItem; - if (item?.TransformedBounds == null) - { + + if (item == null) return; - } + + var serverTransform = item?.CompositionVisual?.TryGetServerGlobalTransform(); + if (serverTransform == null) + return; + var point = e.GetCurrentPoint(null); + var transformedPoint = point.Position.Transform(serverTransform.Value); - if (point.Properties.IsLeftButtonPressed && item.TransformedBounds.Value.Contains(point.Position) == false) + if (point.Properties.IsLeftButtonPressed && + new Rect(item!.Bounds.Size).Contains(transformedPoint) == false) { e.Pointer.Capture(null); } diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index bf74e0f8f4..5a27b70ba2 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -111,11 +111,6 @@ namespace Avalonia.Platform /// The toplevel. IRenderer CreateRenderer(IRenderRoot root); - /// - /// Invalidates a rect on the toplevel. - /// - void Invalidate(Rect rect); - /// /// Sets the for the toplevel. /// diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs index c9585d50ae..3464857131 100644 --- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs +++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls.Primitives /// /// TODO: Need to track position of adorned elements and move the adorner if they move. /// - public class AdornerLayer : Canvas, ICustomSimpleHitTest + public class AdornerLayer : Canvas { /// /// Allows for getting and setting of the adorned element. @@ -305,17 +305,9 @@ namespace Avalonia.Controls.Primitives info.Bounds = new TransformedBounds(new Rect(adorned.Bounds.Size), new Rect(adorned.Bounds.Size), Matrix.Identity); InvalidateMeasure(); }); - else - info.Subscription = adorned.GetObservable(TransformedBoundsProperty).Subscribe(x => - { - info.Bounds = x; - InvalidateMeasure(); - }); } } - public bool HitTest(Point point) => Children.HitTestCustom(point); - private class AdornedElementInfo { public IDisposable? Subscription { get; set; } diff --git a/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs b/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs index 20b3b849a6..74b5beecad 100644 --- a/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs +++ b/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs @@ -4,7 +4,7 @@ using Avalonia.VisualTree; namespace Avalonia.Controls.Primitives { - public class ChromeOverlayLayer : Panel, ICustomSimpleHitTest + public class ChromeOverlayLayer : Panel { public static Panel? GetOverlayLayer(Visual visual) { @@ -26,7 +26,5 @@ namespace Avalonia.Controls.Primitives { base.Children.Add(c); } - - public bool HitTest(Point point) => Children.HitTestCustom(point); } } diff --git a/src/Avalonia.Controls/Primitives/OverlayLayer.cs b/src/Avalonia.Controls/Primitives/OverlayLayer.cs index 91136cb295..76b56f3a11 100644 --- a/src/Avalonia.Controls/Primitives/OverlayLayer.cs +++ b/src/Avalonia.Controls/Primitives/OverlayLayer.cs @@ -4,7 +4,7 @@ using Avalonia.VisualTree; namespace Avalonia.Controls.Primitives { - public class OverlayLayer : Canvas, ICustomSimpleHitTest + public class OverlayLayer : Canvas { public Size AvailableSize { get; private set; } public static OverlayLayer? GetOverlayLayer(Visual visual) @@ -22,8 +22,6 @@ namespace Avalonia.Controls.Primitives return null; } - public bool HitTest(Point point) => Children.HitTestCustom(point); - protected override Size MeasureOverride(Size availableSize) { foreach (Control child in Children) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 7cac12eabe..c85199a665 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -8,6 +8,7 @@ using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Input; using Avalonia.Input.Raw; +using Avalonia.Input.TextInput; using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Metadata; @@ -378,11 +379,8 @@ namespace Avalonia.Controls.Primitives if (InheritsTransform && placementTarget is Control c) { - SubscribeToEventHandler>( - c, - PlacementTargetPropertyChanged, - (x, handler) => x.PropertyChanged += handler, - (x, handler) => x.PropertyChanged -= handler).DisposeWith(handlerCleanup); + TransformTrackingHelper.Track(c, PlacementTargetTransformChanged) + .DisposeWith(handlerCleanup); } else { @@ -872,13 +870,11 @@ namespace Avalonia.Controls.Primitives Close(); } } - - private void PlacementTargetPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) + + private void PlacementTargetTransformChanged(Visual v, Matrix? matrix) { - if (_openState is not null && e.Property == Visual.TransformedBoundsProperty) - { + if (_openState is not null) UpdateHostSizing(_openState.PopupHost, _openState.TopLevel, _openState.PlacementTarget); - } } private void WindowLostFocus() diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index bc11c35fde..b70d1c9369 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -11,6 +11,7 @@ using Avalonia.Platform; using Avalonia.Remote.Protocol; using Avalonia.Remote.Protocol.Input; using Avalonia.Remote.Protocol.Viewport; +using Avalonia.Rendering; using Avalonia.Threading; using Key = Avalonia.Input.Key; using PixelFormat = Avalonia.Platform.PixelFormat; @@ -19,7 +20,7 @@ using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat; namespace Avalonia.Controls.Remote.Server { [Unstable] - public class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface + internal class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface, ITopLevelImpl { private readonly IAvaloniaRemoteTransportConnection _transport; private LockedFramebuffer? _framebuffer; @@ -28,7 +29,8 @@ namespace Avalonia.Controls.Remote.Server private long _lastReceivedFrame = -1; private long _nextFrameNumber = 1; private ClientViewportAllocatedMessage? _pendingAllocation; - private bool _invalidated; + private bool _queuedNextRender; + private bool _inRender; private Vector _dpi = new Vector(96, 96); private ProtocolPixelFormat[]? _supportedFormats; @@ -38,6 +40,14 @@ namespace Avalonia.Controls.Remote.Server _transport.OnMessage += OnMessage; KeyboardDevice = AvaloniaLocator.Current.GetRequiredService(); + QueueNextRender(); + } + + IRenderer ITopLevelImpl.CreateRenderer(IRenderRoot root) + { + var r = (IRendererWithCompositor)base.CreateRenderer(root); + r.Compositor.AfterCommit += QueueNextRender; + return r; } private static RawPointerEventType GetAvaloniaEventType (Avalonia.Remote.Protocol.Input.MouseButton button, bool pressed) @@ -125,7 +135,7 @@ namespace Avalonia.Controls.Remote.Server lock(_lock) { _dpi = new Vector(renderInfo.DpiX, renderInfo.DpiY); - _invalidated = true; + _queuedNextRender = true; } Dispatcher.UIThread.Post(RenderIfNeeded); @@ -315,7 +325,7 @@ namespace Avalonia.Controls.Remote.Server { lock (_lock) { - if (_lastReceivedFrame != _lastSentFrame || !_invalidated || _supportedFormats == null) + if (_lastReceivedFrame != _lastSentFrame || !_queuedNextRender || _supportedFormats == null) return; } @@ -327,23 +337,25 @@ namespace Avalonia.Controls.Remote.Server format = fmt; break; } - + + _inRender = true; var frame = RenderFrame((int) ClientSize.Width, (int) ClientSize.Height, format); lock (_lock) { _lastSentFrame = _nextFrameNumber++; frame.SequenceId = _lastSentFrame; - _invalidated = false; + _queuedNextRender = false; } + _inRender = false; _transport.Send(frame); } - public override void Invalidate(Rect rect) + private void QueueNextRender() { - if (!IsDisposed) + if (!_inRender && !IsDisposed) { - _invalidated = true; - Dispatcher.UIThread.Post(RenderIfNeeded); + _queuedNextRender = true; + DispatcherTimer.RunOnce(RenderIfNeeded, TimeSpan.FromMilliseconds(2), DispatcherPriority.Background); } } diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index ff241dce7a..f2be053bc9 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -385,12 +385,6 @@ namespace Avalonia.Controls ?? (PlatformImpl as ITopLevelImplWithStorageProvider)?.StorageProvider ?? throw new InvalidOperationException("StorageProvider platform implementation is not available."); - /// - void IRenderRoot.Invalidate(Rect rect) - { - PlatformImpl?.Invalidate(rect); - } - /// Point IRenderRoot.PointToClient(PixelPoint p) { diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 72d03c4b7f..effec9cc58 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -14,6 +14,8 @@ using Avalonia.Platform; using Avalonia.Platform.Storage; using Avalonia.Platform.Storage.FileIO; using Avalonia.Rendering; +using Avalonia.Rendering.Composition; +using Avalonia.Threading; namespace Avalonia.DesignerSupport.Remote { @@ -61,9 +63,15 @@ namespace Avalonia.DesignerSupport.Remote })); } - public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer((Visual)root, () => - new PlatformRenderInterfaceContextManager(null) - .CreateRenderTarget(Surfaces)); + class DummyRenderTimer : IRenderTimer + { + public event Action Tick; + public bool RunsInBackground => false; + } + + public IRenderer CreateRenderer(IRenderRoot root) => + new CompositingRenderer(root, + new Compositor(new RenderLoop(new DummyRenderTimer(), Dispatcher.UIThread), null), () => Surfaces); public void Dispose() { diff --git a/src/Avalonia.Diagnostics/Diagnostics/Conventions.cs b/src/Avalonia.Diagnostics/Diagnostics/Conventions.cs index 9091b83ba2..540f2b549d 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Conventions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Conventions.cs @@ -1,12 +1,20 @@ using System; +using System.IO; namespace Avalonia.Diagnostics { internal static class Conventions { - public static string DefaultScreenshotsRoot => - System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures, Environment.SpecialFolderOption.Create), - "Screenshots"); + public static string DefaultScreenshotsRoot + { + get + { + var dir = System.IO.Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Screenshots"); + Directory.CreateDirectory(dir); + return dir; + } + } public static IScreenshotHandler DefaultScreenshotHandler { get; } = new Screenshots.FilePickerHandler(); diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs index 56d8737d79..42db30020f 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs @@ -40,15 +40,14 @@ namespace Avalonia.Diagnostics.Views _layoutRoot = this.GetControl("LayoutRoot"); - void SubscribeToBounds(Visual visual) + Visual? visual = _contentArea; + while (visual != null && !ReferenceEquals(visual, this)) { - visual.GetPropertyChangedObservable(TransformedBoundsProperty) + visual.GetPropertyChangedObservable(BoundsProperty) .Subscribe(UpdateSizeGuidelines); + visual = visual.VisualParent; } - - SubscribeToBounds(_borderArea); - SubscribeToBounds(_paddingArea); - SubscribeToBounds(_contentArea); + } private void InitializeComponent() @@ -56,22 +55,27 @@ namespace Avalonia.Diagnostics.Views AvaloniaXamlLoader.Load(this); } - private void UpdateSizeGuidelines(AvaloniaPropertyChangedEventArgs e) + private void UpdateSizeGuidelines(AvaloniaPropertyChangedEventArgs _) { void UpdateGuidelines(Visual area) { - if (area.TransformedBounds is TransformedBounds bounds) + // That's what TransformedBounds.Bounds actually was. + // The code below doesn't really make sense to me, so I've just changed v.TransformedBounds.Bounds + // to GetPseudoTransformedBounds + Rect GetPseudoTransformedBounds(Visual v) => new(v.Bounds.Size); + var bounds = GetPseudoTransformedBounds(area); + { // Horizontal guideline { - var sizeArea = TranslateToRoot((_horizontalSize.TransformedBounds ?? default).Bounds.BottomLeft, + var sizeArea = TranslateToRoot(GetPseudoTransformedBounds(_horizontalSize).BottomLeft, _horizontalSize); - var start = TranslateToRoot(bounds.Bounds.BottomLeft, area); + var start = TranslateToRoot(bounds.BottomLeft, area); SetPosition(_horizontalSizeBegin, start); - var end = TranslateToRoot(bounds.Bounds.BottomRight, area); + var end = TranslateToRoot(bounds.BottomRight, area); SetPosition(_horizontalSizeEnd, end.WithX(end.X - 1)); @@ -83,13 +87,13 @@ namespace Avalonia.Diagnostics.Views // Vertical guideline { - var sizeArea = TranslateToRoot((_verticalSize.TransformedBounds ?? default).Bounds.TopRight, _verticalSize); + var sizeArea = TranslateToRoot(GetPseudoTransformedBounds(_verticalSize).TopRight, _verticalSize); - var start = TranslateToRoot(bounds.Bounds.TopRight, area); + var start = TranslateToRoot(bounds.TopRight, area); SetPosition(_verticalSizeBegin, start); - var end = TranslateToRoot(bounds.Bounds.BottomRight, area); + var end = TranslateToRoot(bounds.BottomRight, area); SetPosition(_verticalSizeEnd, end.WithY(end.Y - 1)); diff --git a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs index 092e26c96d..ff11fc357e 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs @@ -17,11 +17,11 @@ namespace Avalonia.Diagnostics /// Dpi quality. public static void RenderTo(this Control source, Stream destination, double dpi = 96) { - if (source.TransformedBounds == null) - { + var transform = source.CompositionVisual?.TryGetServerGlobalTransform(); + if (transform == null) return; - } - var rect = source.TransformedBounds.Value.Clip; + + var rect = new Rect(source.Bounds.Size).TransformToAABB(transform.Value); var top = rect.TopLeft; var pixelSize = new PixelSize((int)rect.Width, (int)rect.Height); var dpiVector = new Vector(dpi, dpi); diff --git a/src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs b/src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs index b6bb69b05d..efc8c66fde 100644 --- a/src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs +++ b/src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs @@ -21,7 +21,6 @@ namespace Avalonia return builder .UseHeadless(new AvaloniaHeadlessPlatformOptions { - UseCompositor = true, UseHeadlessDrawing = false }) .AfterSetup(_ => diff --git a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs b/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs index 301a23b608..9834969898 100644 --- a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs +++ b/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs @@ -72,8 +72,7 @@ namespace Avalonia.Headless .Bind().ToSingleton() .Bind().ToConstant(new HeadlessWindowingPlatform()) .Bind().ToSingleton(); - if (opts.UseCompositor) - Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), null); + Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), null); } @@ -88,7 +87,6 @@ namespace Avalonia.Headless public class AvaloniaHeadlessPlatformOptions { - public bool UseCompositor { get; set; } = true; public bool UseHeadlessDrawing { get; set; } = true; } diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index 195328cd65..3cf3c91eae 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs @@ -54,11 +54,8 @@ namespace Avalonia.Headless public Action Resized { get; set; } public Action ScalingChanged { get; set; } - public IRenderer CreateRenderer(IRenderRoot root) - => AvaloniaHeadlessPlatform.Compositor != null - ? new CompositingRenderer(root, AvaloniaHeadlessPlatform.Compositor, () => Surfaces) - : new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService(), - () => new PlatformRenderInterfaceContextManager(null).CreateRenderTarget(Surfaces), null); + public IRenderer CreateRenderer(IRenderRoot root) => + new CompositingRenderer(root, AvaloniaHeadlessPlatform.Compositor, () => Surfaces); public void Invalidate(Rect rect) { diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index fc23fbb226..09feb0c768 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -24,8 +24,7 @@ namespace Avalonia.Native static extern IntPtr CreateAvaloniaNative(); internal static readonly KeyboardDevice KeyboardDevice = new KeyboardDevice(); - internal static Compositor? Compositor { get; private set; } - internal static PlatformRenderInterfaceContextManager? RenderInterface { get; private set; } + internal static Compositor Compositor { get; private set; } = null!; public static AvaloniaNativePlatform Initialize(IntPtr factory, AvaloniaNativePlatformOptions options) { @@ -140,14 +139,8 @@ namespace Avalonia.Native // ignored } } - - - if (_options.UseDeferredRendering && _options.UseCompositor) - { - Compositor = new Compositor(renderLoop, _platformGl); - } - else - RenderInterface = new PlatformRenderInterfaceContextManager(_platformGl); + + Compositor = new Compositor(renderLoop, _platformGl); } public ITrayIconImpl CreateTrayIcon() diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs index 6613fc09be..2b989ce733 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs @@ -30,20 +30,6 @@ namespace Avalonia /// public class AvaloniaNativePlatformOptions { - /// - /// Deferred renderer would be used when set to true. Immediate renderer when set to false. The default value is true. - /// - /// - /// Avalonia has two rendering modes: Immediate and Deferred rendering. - /// Immediate re-renders the whole scene when some element is changed on the scene. Deferred re-renders only changed elements. - /// - public bool UseDeferredRendering { get; set; } = true; - - /// - /// Enables new compositing rendering with UWP-like API - /// - public bool UseCompositor { get; set; } = true; - /// /// Determines whether to use GPU for rendering in your project. The default value is true. /// diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 7686b42275..41de2356e6 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -53,7 +53,6 @@ namespace Avalonia.Native protected IInputRoot _inputRoot; IAvnWindowBase _native; private object _syncRoot = new object(); - private bool _deferredRendering = false; private bool _gpu = false; private readonly MouseDevice _mouse; private readonly IKeyboardDevice _keyboard; @@ -69,7 +68,6 @@ namespace Avalonia.Native { _factory = factory; _gpu = opts.UseGpu && glFeature != null; - _deferredRendering = opts.UseDeferredRendering; _keyboard = AvaloniaLocator.Current.GetService(); _mouse = new MouseDevice(); @@ -365,25 +363,10 @@ namespace Avalonia.Native public IRenderer CreateRenderer(IRenderRoot root) { - var customRendererFactory = AvaloniaLocator.Current.GetService(); - var loop = AvaloniaLocator.Current.GetService(); - if (customRendererFactory != null) - return customRendererFactory.Create(root, loop); - - if (_deferredRendering) + return new CompositingRenderer(root, AvaloniaNativePlatform.Compositor, () => Surfaces) { - if (AvaloniaNativePlatform.Compositor != null) - return new CompositingRenderer(root, AvaloniaNativePlatform.Compositor, () => Surfaces) - { - RenderOnlyOnRenderThread = false - }; - return new DeferredRenderer(root, loop, - () => AvaloniaNativePlatform.RenderInterface!.CreateRenderTarget(Surfaces), - AvaloniaNativePlatform.RenderInterface); - } - - return new ImmediateRenderer((Visual)root, - () => AvaloniaNativePlatform.RenderInterface!.CreateRenderTarget(Surfaces), AvaloniaNativePlatform.RenderInterface); + RenderOnlyOnRenderThread = false + }; } public virtual void Dispose() diff --git a/src/Avalonia.X11/X11ImmediateRendererProxy.cs b/src/Avalonia.X11/X11ImmediateRendererProxy.cs deleted file mode 100644 index ef061cbe5c..0000000000 --- a/src/Avalonia.X11/X11ImmediateRendererProxy.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Avalonia.Platform; -using Avalonia.Rendering; -using Avalonia.Threading; -using Avalonia.VisualTree; - -namespace Avalonia.X11 -{ - public class X11ImmediateRendererProxy : IRenderer, IRenderLoopTask - { - private readonly IRenderLoop _loop; - private ImmediateRenderer _renderer; - private bool _invalidated; - private bool _running; - private object _lock = new object(); - - public X11ImmediateRendererProxy(Visual root, IRenderLoop loop, Func renderTargetFactory, - PlatformRenderInterfaceContextManager renderContext) - { - _loop = loop; - _renderer = new ImmediateRenderer(root, renderTargetFactory, renderContext); - } - - public void Dispose() - { - _running = false; - _renderer.Dispose(); - } - - public bool DrawFps - { - get => _renderer.DrawFps; - set => _renderer.DrawFps = value; - } - - public bool DrawDirtyRects - { - get => _renderer.DrawDirtyRects; - set => _renderer.DrawDirtyRects = value; - } - - public event EventHandler SceneInvalidated - { - add => _renderer.SceneInvalidated += value; - remove => _renderer.SceneInvalidated -= value; - } - - public void AddDirty(Visual visual) - { - lock (_lock) - _invalidated = true; - _renderer.AddDirty(visual); - } - - public IEnumerable HitTest(Point p, Visual root, Func filter) - { - return _renderer.HitTest(p, root, filter); - } - - public Visual HitTestFirst(Point p, Visual root, Func filter) - { - return _renderer.HitTestFirst(p, root, filter); - } - - public void RecalculateChildren(Visual visual) - { - _renderer.RecalculateChildren(visual); - } - - public void Resized(Size size) - { - _renderer.Resized(size); - } - - public void Paint(Rect rect) - { - _invalidated = false; - _renderer.Paint(rect); - } - - public void Start() - { - _running = true; - _loop.Add(this); - _renderer.Start(); - } - - public void Stop() - { - _running = false; - _loop.Remove(this); - _renderer.Stop(); - } - - public ValueTask TryGetRenderInterfaceFeature(Type featureType) => - _renderer.TryGetRenderInterfaceFeature(featureType); - - public bool NeedsUpdate => false; - public void Update(TimeSpan time) - { - - } - - public void Render() - { - if (_invalidated) - { - lock (_lock) - _invalidated = false; - Dispatcher.UIThread.Post(() => - { - if (_running) - Paint(new Rect(0, 0, 100000, 100000)); - }); - } - } - } -} diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 4cc2cca39d..c5ae327c2f 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -30,7 +30,6 @@ namespace Avalonia.X11 public X11Info Info { get; private set; } public IX11Screens X11Screens { get; private set; } public Compositor Compositor { get; private set; } - public PlatformRenderInterfaceContextManager RenderInterface { get; private set; } public IScreenImpl Screens { get; private set; } public X11PlatformOptions Options { get; private set; } public IntPtr OrphanedWindow { get; private set; } @@ -103,12 +102,8 @@ namespace Avalonia.X11 } var gl = AvaloniaLocator.Current.GetService(); - - if (options.UseCompositor) - Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), gl); - else - RenderInterface = new(gl); + Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), gl); } public IntPtr DeferredDisplay { get; set; } @@ -226,18 +221,7 @@ namespace Avalonia /// The default value is true. /// public bool UseDBusFilePicker { get; set; } = true; - - /// - /// Deferred renderer would be used when set to true. Immediate renderer when set to false. The default value is true. - /// - /// - /// Avalonia has two rendering modes: Immediate and Deferred rendering. - /// Immediate re-renders the whole scene when some element is changed on the scene. Deferred re-renders only changed elements. - /// - public bool UseDeferredRendering { get; set; } = true; - - public bool UseCompositor { get; set; } = true; - + /// /// Determines whether to use IME. /// IME would be enabled by default if the current user input language is one of the following: Mandarin, Japanese, Vietnamese or Korean. diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 84331aa43a..88486d36bd 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -384,25 +384,8 @@ namespace Avalonia.X11 public Action PositionChanged { get; set; } public Action LostFocus { get; set; } - public IRenderer CreateRenderer(IRenderRoot root) - { - var loop = AvaloniaLocator.Current.GetService(); - var customRendererFactory = AvaloniaLocator.Current.GetService(); - - if (customRendererFactory != null) - return customRendererFactory.Create(root, loop); - - return _platform.Options.UseDeferredRendering - ? _platform.Options.UseCompositor - ? new CompositingRenderer(root, this._platform.Compositor, () => Surfaces) - : new DeferredRenderer(root, loop, () => _platform.RenderInterface.CreateRenderTarget(Surfaces), _platform.RenderInterface) - { - RenderOnlyOnRenderThread = true - } - : new X11ImmediateRendererProxy((Visual)root, loop, - () => _platform.RenderInterface.CreateRenderTarget(Surfaces), - _platform.RenderInterface); - } + public IRenderer CreateRenderer(IRenderRoot root) => + new CompositingRenderer(root, _platform.Compositor, () => Surfaces); void OnEvent(ref XEvent ev) { diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index cb158097eb..5705d14edb 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -24,16 +24,12 @@ namespace Avalonia.LinuxFramebuffer _inputBackend = inputBackend; Surfaces = new object[] { _outputBackend }; - - Invalidate(default); _inputBackend.Initialize(this, e => Input?.Invoke(e)); } public IRenderer CreateRenderer(IRenderRoot root) { - var factory = AvaloniaLocator.Current.GetService(); - var renderLoop = AvaloniaLocator.Current.GetService(); - return factory?.Create(root, renderLoop) ?? new CompositingRenderer(root, LinuxFramebufferPlatform.Compositor, () => Surfaces); + return new CompositingRenderer(root, LinuxFramebufferPlatform.Compositor, () => Surfaces); } public void Dispose() @@ -41,11 +37,6 @@ namespace Avalonia.LinuxFramebuffer throw new NotSupportedException(); } - - public void Invalidate(Rect rect) - { - } - public void SetInputRoot(IInputRoot inputRoot) { InputRoot = inputRoot; diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index 4202ba821f..2dcce12df9 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -26,8 +26,8 @@ namespace Avalonia.LinuxFramebuffer private static readonly Stopwatch St = Stopwatch.StartNew(); internal static uint Timestamp => (uint)St.ElapsedTicks; public static InternalPlatformThreadingInterface? Threading; - - internal static Compositor? Compositor { get; private set; } + + internal static Compositor Compositor { get; private set; } = null!; LinuxFramebufferPlatform(IOutputBackend backend) diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 7e1e00bded..f16a1ca8cf 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -42,17 +42,6 @@ namespace Avalonia /// public class Win32PlatformOptions { - /// - /// Deferred renderer would be used when set to true. Immediate renderer when set to false. The default value is true. - /// - /// - /// Avalonia has two rendering modes: Immediate and Deferred rendering. - /// Immediate re-renders the whole scene when some element is changed on the scene. Deferred re-renders only changed elements. - /// - public bool UseDeferredRendering { get; set; } = true; - - public bool UseCompositor { get; set; } = true; - /// /// Enables ANGLE for Windows. For every Windows version that is above Windows 7, the default is true otherwise it's false. /// @@ -137,12 +126,10 @@ namespace Avalonia.Win32 /// public static Version WindowsVersion { get; } = RtlGetVersion(); - public static bool UseDeferredRendering => Options.UseDeferredRendering; internal static bool UseOverlayPopups => Options.OverlayPopups; public static Win32PlatformOptions Options { get; private set; } internal static Compositor Compositor { get; private set; } - internal static PlatformRenderInterfaceContextManager RenderInterface { get; private set; } public static void Initialize() { @@ -181,11 +168,8 @@ namespace Avalonia.Win32 if (OleContext.Current != null) AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); - - if (Options.UseCompositor) - Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), platformGraphics); - else - RenderInterface = new PlatformRenderInterfaceContextManager(platformGraphics); + + Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), platformGraphics); } public bool HasMessages() diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index a7741477e5..de1b463a07 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -558,32 +558,8 @@ namespace Avalonia.Win32 _maxSize = maxSize; } - public IRenderer CreateRenderer(IRenderRoot root) - { - var loop = AvaloniaLocator.Current.GetService(); - var customRendererFactory = AvaloniaLocator.Current.GetService(); - - if (customRendererFactory != null) - return customRendererFactory.Create(root, loop); - - if (Win32Platform.Compositor != null) - return new CompositingRenderer(root, Win32Platform.Compositor, () => Surfaces); - - return Win32Platform.UseDeferredRendering - ? _isUsingComposition - ? new DeferredRenderer(root, loop, - () => Win32Platform.RenderInterface.CreateRenderTarget(Surfaces), - Win32Platform.RenderInterface) - { - RenderOnlyOnRenderThread = true - } - : (IRenderer)new DeferredRenderer(root, loop, rendererLock: _rendererLock, - renderTargetFactory: () => Win32Platform.RenderInterface.CreateRenderTarget(Surfaces), - renderInterface: Win32Platform.RenderInterface) - : new ImmediateRenderer((Visual)root, - () => Win32Platform.RenderInterface.CreateRenderTarget(Surfaces), - Win32Platform.RenderInterface); - } + public IRenderer CreateRenderer(IRenderRoot root) => + new CompositingRenderer(root, Win32Platform.Compositor, () => Surfaces); public void Resize(Size value, PlatformResizeReason reason) { diff --git a/tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationTests.cs b/tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationTests.cs index 9b38422dde..41c0eb3958 100644 --- a/tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationTests.cs +++ b/tests/Avalonia.Base.UnitTests/Composition/CompositionAnimationTests.cs @@ -9,6 +9,7 @@ using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Server; using Avalonia.Threading; +using Avalonia.UnitTests; using Xunit; using Xunit.Sdk; @@ -67,7 +68,7 @@ public class CompositionAnimationTests public void GenericCheck(AnimationData data) { var compositor = - new Compositor(new RenderLoop(new CompositorTestsBase.ManualRenderTimer(), new Dispatcher(null)), null); + new Compositor(new RenderLoop(new CompositorTestServices.ManualRenderTimer(), new Dispatcher(null)), null); var target = compositor.CreateSolidColorVisual(); var ani = new ScalarKeyFrameAnimation(null); foreach (var frame in data.Frames) diff --git a/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs index 3d8369faeb..27bb0355e6 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs @@ -20,7 +20,7 @@ public class CompositorHitTestingTests : CompositorTestsBase [Fact] public void HitTest_Should_Find_Controls_At_Point() { - using (var s = new CompositorServices(new Size(200, 200))) + using (var s = new CompositorTestServices(new Size(200, 200))) { var border = new Border { @@ -40,7 +40,7 @@ public class CompositorHitTestingTests : CompositorTestsBase [Fact] public void HitTest_Should_Not_Find_Empty_Controls_At_Point() { - using (var s = new CompositorServices(new Size(200, 200))) + using (var s = new CompositorTestServices(new Size(200, 200))) { var border = new Border { @@ -59,7 +59,7 @@ public class CompositorHitTestingTests : CompositorTestsBase [Fact] public void HitTest_Should_Not_Find_Invisible_Controls_At_Point() { - using (var s = new CompositorServices(new Size(200, 200))) + using (var s = new CompositorTestServices(new Size(200, 200))) { Border visible, border; s.TopLevel.Content = border = new Border @@ -91,7 +91,7 @@ public class CompositorHitTestingTests : CompositorTestsBase public void HitTest_Should_Find_Zero_Opacity_Controls_At_Point(bool parent, bool child) { - using (var s = new CompositorServices(new Size(200, 200))) + using (var s = new CompositorTestServices(new Size(200, 200))) { Border visible, border; s.TopLevel.Content = border = new Border @@ -118,7 +118,7 @@ public class CompositorHitTestingTests : CompositorTestsBase [Fact] public void HitTest_Should_Not_Find_Control_Outside_Point() { - using (var s = new CompositorServices(new Size(200, 200))) + using (var s = new CompositorTestServices(new Size(200, 200))) { var border = new Border { @@ -138,7 +138,7 @@ public class CompositorHitTestingTests : CompositorTestsBase [Fact] public void HitTest_Should_Return_Top_Controls_First() { - using (var s = new CompositorServices(new Size(200, 200))) + using (var s = new CompositorTestServices(new Size(200, 200))) { Panel container = new Panel { @@ -173,7 +173,7 @@ public class CompositorHitTestingTests : CompositorTestsBase [Fact] public void HitTest_Should_Return_Top_Controls_First_With_ZIndex() { - using (var s = new CompositorServices(new Size(200, 200))) + using (var s = new CompositorTestServices(new Size(200, 200))) { Panel container = new Panel { @@ -219,7 +219,7 @@ public class CompositorHitTestingTests : CompositorTestsBase [Fact] public void HitTest_Should_Find_Control_Translated_Outside_Parent_Bounds() { - using (var s = new CompositorServices(new Size(200, 200))) + using (var s = new CompositorTestServices(new Size(200, 200))) { Border target; Panel container = new Panel @@ -259,7 +259,7 @@ public class CompositorHitTestingTests : CompositorTestsBase [Fact] public void HitTest_Should_Not_Find_Control_Outside_Parent_Bounds_When_Clipped() { - using (var s = new CompositorServices(new Size(200, 200))) + using (var s = new CompositorTestServices(new Size(200, 200))) { Border target; Panel container = new Panel @@ -299,7 +299,7 @@ public class CompositorHitTestingTests : CompositorTestsBase [Fact] public void HitTest_Should_Not_Find_Control_Outside_Scroll_Viewport() { - using (var s = new CompositorServices(new Size(100, 200))) + using (var s = new CompositorTestServices(new Size(100, 200))) { Border target; Border item1; @@ -373,7 +373,7 @@ public class CompositorHitTestingTests : CompositorTestsBase [Fact] public void HitTest_Should_Not_Find_Path_When_Outside_Fill() { - using (var s = new CompositorServices(new Size(200, 200))) + using (var s = new CompositorTestServices(new Size(200, 200))) { Path path = new Path { @@ -392,7 +392,7 @@ public class CompositorHitTestingTests : CompositorTestsBase [Fact] public void HitTest_Should_Respect_Geometry_Clip() { - using (var s = new CompositorServices(new Size(400, 400))) + using (var s = new CompositorTestServices(new Size(400, 400))) { Canvas canvas; Border border = new Border @@ -422,7 +422,7 @@ public class CompositorHitTestingTests : CompositorTestsBase [Fact] public void HitTest_Should_Accommodate_ICustomHitTest() { - using (var s = new CompositorServices(new Size(300, 200))) + using (var s = new CompositorTestServices(new Size(300, 200))) { Border border = new CustomHitTestBorder { @@ -445,7 +445,7 @@ public class CompositorHitTestingTests : CompositorTestsBase [Fact] public void HitTest_Should_Not_Hit_Controls_Next_Pixel() { - using (var s = new CompositorServices(new Size(200, 200))) + using (var s = new CompositorTestServices(new Size(200, 200))) { Border targetRectangle; @@ -470,7 +470,7 @@ public class CompositorHitTestingTests : CompositorTestsBase [Fact] public void HitTest_Filter_Should_Filter_Out_Children() { - using (var s = new CompositorServices(new Size(200, 200))) + using (var s = new CompositorTestServices(new Size(200, 200))) { Border child, parent; s.TopLevel.Content = parent = new Border diff --git a/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs b/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs index 12db249f04..fed193e973 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs @@ -24,178 +24,7 @@ namespace Avalonia.Base.UnitTests.Rendering; public class CompositorTestsBase { - public class DebugEvents : ICompositionTargetDebugEvents - { - public List Rects = new(); - - public void RectInvalidated(Rect rc) - { - Rects.Add(rc); - } - - public void Reset() - { - Rects.Clear(); - } - } - - public class ManualRenderTimer : IRenderTimer - { - public event Action Tick; - public bool RunsInBackground => false; - public void TriggerTick() => Tick?.Invoke(TimeSpan.Zero); - public Task TriggerBackgroundTick() => Task.Run(TriggerTick); - } - - class TopLevelImpl : ITopLevelImpl - { - private readonly Compositor _compositor; - public CompositingRenderer Renderer { get; private set; } - - public TopLevelImpl(Compositor compositor, Size clientSize) - { - ClientSize = clientSize; - _compositor = compositor; - } - - public void Dispose() - { - - } - - public Size ClientSize { get; } - public Size? FrameSize { get; } - public double RenderScaling => 1; - public IEnumerable Surfaces { get; } = Array.Empty(); - public Action Input { get; set; } - public Action Paint { get; set; } - public Action Resized { get; set; } - public Action ScalingChanged { get; set; } - public Action TransparencyLevelChanged { get; set; } - - public IRenderer CreateRenderer(IRenderRoot root) - { - return Renderer = new CompositingRenderer(root, _compositor, () => Surfaces); - } - - public void Invalidate(Rect rect) - { - } - - public void SetInputRoot(IInputRoot inputRoot) - { - } - - public Point PointToClient(PixelPoint point) => default; - - public PixelPoint PointToScreen(Point point) => new(); - - public void SetCursor(ICursorImpl cursor) - { - } - - public Action Closed { get; set; } - public Action LostFocus { get; set; } - public IMouseDevice MouseDevice { get; } = new MouseDevice(); - public IPopupImpl CreatePopup() => throw new NotImplementedException(); - - public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) - { - } - - public WindowTransparencyLevel TransparencyLevel { get; } - public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } - - public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } - } - - protected class CompositorServices : IDisposable - { - private readonly IDisposable _app; - public Compositor Compositor { get; } - public ManualRenderTimer Timer { get; } = new(); - public EmbeddableControlRoot TopLevel { get; } - public CompositingRenderer Renderer { get; } = null!; - public DebugEvents Events { get; } = new(); - - public void Dispose() - { - TopLevel.Renderer.Stop(); - TopLevel.Dispose(); - _app.Dispose(); - } - - public CompositorServices(Size? size = null) - { - _app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); - try - { - AvaloniaLocator.CurrentMutable.Bind().ToConstant(Timer); - AvaloniaLocator.CurrentMutable.Bind() - .ToConstant(new RenderLoop(Timer, Dispatcher.UIThread)); - - Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), null); - var impl = new TopLevelImpl(Compositor, size ?? new Size(1000, 1000)); - TopLevel = new EmbeddableControlRoot(impl) - { - Template = new FuncControlTemplate((parent, scope) => - { - var presenter = new ContentPresenter - { - [~ContentPresenter.ContentProperty] = new TemplateBinding(ContentControl.ContentProperty) - }; - scope.Register("PART_ContentPresenter", presenter); - return presenter; - }) - }; - Renderer = impl.Renderer; - TopLevel.Prepare(); - TopLevel.Renderer.Start(); - RunJobs(); - Renderer.CompositionTarget.Server.DebugEvents = Events; - } - catch - { - _app.Dispose(); - throw; - } - } - - public void RunJobs() - { - Dispatcher.UIThread.RunJobs(); - Timer.TriggerTick(); - Dispatcher.UIThread.RunJobs(); - } - - public void AssertRects(params Rect[] rects) - { - RunJobs(); - var toAssert = rects.Select(x => x.ToString()).Distinct().OrderBy(x => x); - var invalidated = Events.Rects.Select(x => x.ToString()).Distinct().OrderBy(x => x); - Assert.Equal(toAssert, invalidated); - Events.Rects.Clear(); - } - - public void AssertHitTest(double x, double y, Func filter, params object[] expected) - => AssertHitTest(new Point(x, y), filter, expected); - public void AssertHitTest(Point pt, Func filter, params object[] expected) - { - RunJobs(); - var tested = Renderer.HitTest(pt, TopLevel, filter); - Assert.Equal(expected, tested); - } - - public void AssertHitTestFirst(Point pt, Func filter, object expected) - { - RunJobs(); - var tested = Renderer.HitTest(pt, TopLevel, filter).First(); - Assert.Equal(expected, tested); - } - } - - - protected class CompositorCanvas : CompositorServices + protected class CompositorCanvas : CompositorTestServices { public Canvas Canvas { get; } = new(); diff --git a/tests/Avalonia.Base.UnitTests/Rendering/CustomHitTestBorder.cs b/tests/Avalonia.Base.UnitTests/Rendering/CustomHitTestBorder.cs index fece152713..d65df992be 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/CustomHitTestBorder.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/CustomHitTestBorder.cs @@ -8,8 +8,7 @@ namespace Avalonia.Base.UnitTests.Rendering public bool HitTest(Point point) { // Move hit testing window halfway to the left - return Bounds - .WithX(Bounds.X - Bounds.Width / 2) + return new Rect( -Bounds.Width / 2,0, Bounds.Width, Bounds.Height) .Contains(point); } } diff --git a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests.cs deleted file mode 100644 index f0c5a24cc4..0000000000 --- a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests.cs +++ /dev/null @@ -1,790 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Subjects; -using Avalonia.Controls; -using Avalonia.Data; -using Avalonia.Media; -using Avalonia.Platform; -using Avalonia.Rendering; -using Avalonia.Rendering.SceneGraph; -using Avalonia.Threading; -using Avalonia.UnitTests; -using Avalonia.Media.Imaging; -using Avalonia.VisualTree; -using Moq; -using Xunit; - -namespace Avalonia.Base.UnitTests.Rendering -{ - public class DeferredRendererTests - { - [Fact] - public void First_Frame_Calls_SceneBuilder_UpdateAll() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var root = new TestRoot(); - var sceneBuilder = MockSceneBuilder(root); - - CreateTargetAndRunFrame(root, sceneBuilder: sceneBuilder.Object); - - sceneBuilder.Verify(x => x.UpdateAll(It.IsAny())); - } - } - - [Fact] - public void Frame_Does_Not_Call_SceneBuilder_If_No_Dirty_Controls() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var dispatcher = new ImmediateDispatcher(); - var loop = new Mock(); - var root = new TestRoot(); - var sceneBuilder = MockSceneBuilder(root); - - var target = new DeferredRenderer( - root, - loop.Object, - renderTargetFactory: root.CreateRenderTarget, - sceneBuilder: sceneBuilder.Object); - - target.Start(); - IgnoreFirstFrame(target, sceneBuilder); - RunFrame(target); - - sceneBuilder.Verify(x => x.UpdateAll(It.IsAny()), Times.Never); - sceneBuilder.Verify(x => x.Update(It.IsAny(), It.IsAny()), Times.Never); - } - } - - [Fact] - public void Should_Update_Dirty_Controls_In_Order() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var dispatcher = new ImmediateDispatcher(); - var loop = new Mock(); - - Border border; - Decorator decorator; - Canvas canvas; - - var root = new TestRoot - { - Child = decorator = new Decorator - { - Child = border = new Border { Child = canvas = new Canvas() } - } - }; - - var sceneBuilder = MockSceneBuilder(root); - - var target = new DeferredRenderer( - root, - loop.Object, - sceneBuilder: sceneBuilder.Object, - renderTargetFactory: root.CreateRenderTarget, - dispatcher: dispatcher); - - target.Start(); - IgnoreFirstFrame(target, sceneBuilder); - target.AddDirty(border); - target.AddDirty(canvas); - target.AddDirty(root); - target.AddDirty(decorator); - - var result = new List(); - - sceneBuilder.Setup(x => x.Update(It.IsAny(), It.IsAny())) - .Callback((_, v) => result.Add(v)); - - RunFrame(target); - - Assert.Equal(new List { root, decorator, border, canvas }, result); - } - } - - [Fact] - public void Should_Add_Dirty_Rect_On_Child_Remove() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var dispatcher = new ImmediateDispatcher(); - var loop = new Mock(); - - Decorator decorator; - Border border; - - var root = new TestRoot - { - Width = 100, - Height = 100, - Child = decorator = new Decorator - { - Child = border = new Border { Width = 50, Height = 50, Background = Brushes.Red, }, - } - }; - - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - - var sceneBuilder = new SceneBuilder(); - - var target = new DeferredRenderer( - root, - loop.Object, - sceneBuilder: sceneBuilder, - renderTargetFactory: root.CreateRenderTarget, - dispatcher: dispatcher); - - root.Renderer = target; - target.Start(); - RunFrame(target); - - decorator.Child = null; - - RunFrame(target); - - var scene = target.UnitTestScene(); - var stackNode = scene.FindNode(decorator); - var dirty = scene.Layers[0].Dirty.ToList(); - - Assert.Equal(1, dirty.Count); - Assert.Equal(new Rect(25, 25, 50, 50), dirty[0]); - } - } - - [Fact] - public void Should_Update_VisualNode_Order_On_Child_Remove_Insert() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var dispatcher = new ImmediateDispatcher(); - var loop = new Mock(); - - StackPanel stack; - Canvas canvas1; - Canvas canvas2; - - var root = new TestRoot - { - Child = stack = new StackPanel - { - Children = { (canvas1 = new Canvas()), (canvas2 = new Canvas()), } - } - }; - - var sceneBuilder = new SceneBuilder(); - - var target = new DeferredRenderer( - root, - loop.Object, - sceneBuilder: sceneBuilder, - renderTargetFactory: root.CreateRenderTarget, - dispatcher: dispatcher); - - root.Renderer = target; - target.Start(); - RunFrame(target); - - stack.Children.Remove(canvas2); - stack.Children.Insert(0, canvas2); - - RunFrame(target); - - var scene = target.UnitTestScene(); - var stackNode = scene.FindNode(stack); - - Assert.Same(stackNode.Children[0].Visual, canvas2); - Assert.Same(stackNode.Children[1].Visual, canvas1); - } - } - - [Fact] - public void Should_Update_VisualNode_Order_On_Child_Move() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var dispatcher = new ImmediateDispatcher(); - var loop = new Mock(); - - StackPanel stack; - Canvas canvas1; - Canvas canvas2; - - var root = new TestRoot - { - Child = stack = new StackPanel - { - Children = { (canvas1 = new Canvas()), (canvas2 = new Canvas()), } - } - }; - - var sceneBuilder = new SceneBuilder(); - - var target = new DeferredRenderer( - root, - loop.Object, - sceneBuilder: sceneBuilder, - renderTargetFactory: root.CreateRenderTarget, - dispatcher: dispatcher); - - root.Renderer = target; - target.Start(); - RunFrame(target); - - stack.Children.Move(1, 0); - - RunFrame(target); - - var scene = target.UnitTestScene(); - var stackNode = scene.FindNode(stack); - - Assert.Same(stackNode.Children[0].Visual, canvas2); - Assert.Same(stackNode.Children[1].Visual, canvas1); - } - } - - [Fact] - public void Should_Update_VisualNode_Order_On_ZIndex_Change() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var dispatcher = new ImmediateDispatcher(); - var loop = new Mock(); - - StackPanel stack; - Canvas canvas1; - Canvas canvas2; - - var root = new TestRoot - { - Child = stack = new StackPanel - { - Children = - { - (canvas1 = new Canvas { ZIndex = 1 }), (canvas2 = new Canvas { ZIndex = 2 }), - } - } - }; - - var sceneBuilder = new SceneBuilder(); - - var target = new DeferredRenderer( - root, - loop.Object, - sceneBuilder: sceneBuilder, - renderTargetFactory: root.CreateRenderTarget, - dispatcher: dispatcher); - - root.Renderer = target; - target.Start(); - RunFrame(target); - - canvas1.ZIndex = 3; - - RunFrame(target); - - var scene = target.UnitTestScene(); - var stackNode = scene.FindNode(stack); - - Assert.Same(stackNode.Children[0].Visual, canvas2); - Assert.Same(stackNode.Children[1].Visual, canvas1); - } - } - - [Fact] - public void Should_Update_VisualNode_Order_On_ZIndex_Change_With_Dirty_Ancestor() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var dispatcher = new ImmediateDispatcher(); - var loop = new Mock(); - - StackPanel stack; - Canvas canvas1; - Canvas canvas2; - - var root = new TestRoot - { - Child = stack = new StackPanel - { - Children = - { - (canvas1 = new Canvas { ZIndex = 1 }), (canvas2 = new Canvas { ZIndex = 2 }), - } - } - }; - - var sceneBuilder = new SceneBuilder(); - - var target = new DeferredRenderer( - root, - loop.Object, - sceneBuilder: sceneBuilder, - renderTargetFactory: root.CreateRenderTarget, - dispatcher: dispatcher); - - root.Renderer = target; - target.Start(); - RunFrame(target); - - root.InvalidateVisual(); - canvas1.ZIndex = 3; - - RunFrame(target); - - var scene = target.UnitTestScene(); - var stackNode = scene.FindNode(stack); - - Assert.Same(stackNode.Children[0].Visual, canvas2); - Assert.Same(stackNode.Children[1].Visual, canvas1); - } - } - - [Fact] - public void Should_Update_VisualNodes_When_Child_Moved_To_New_Parent() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var dispatcher = new ImmediateDispatcher(); - var loop = new Mock(); - - Decorator moveFrom; - Decorator moveTo; - Canvas moveMe; - - var root = new TestRoot - { - Child = new StackPanel - { - Children = - { - (moveFrom = new Decorator { Child = moveMe = new Canvas(), }), - (moveTo = new Decorator()), - } - } - }; - - var sceneBuilder = new SceneBuilder(); - - var target = new DeferredRenderer( - root, - loop.Object, - sceneBuilder: sceneBuilder, - renderTargetFactory: root.CreateRenderTarget, - dispatcher: dispatcher); - - root.Renderer = target; - target.Start(); - RunFrame(target); - - moveFrom.Child = null; - moveTo.Child = moveMe; - - RunFrame(target); - - var scene = target.UnitTestScene(); - var moveFromNode = (VisualNode)scene.FindNode(moveFrom); - var moveToNode = (VisualNode)scene.FindNode(moveTo); - - Assert.Empty(moveFromNode.Children); - Assert.Equal(1, moveToNode.Children.Count); - Assert.Same(moveMe, moveToNode.Children[0].Visual); - } - } - - [Fact] - public void Should_Update_VisualNodes_When_Child_Moved_To_New_Parent_And_New_Root() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var dispatcher = new ImmediateDispatcher(); - var loop = new Mock(); - - Decorator moveFrom; - Decorator moveTo; - Canvas moveMe; - - var root = new TestRoot - { - Child = new StackPanel - { - Children = { (moveFrom = new Decorator { Child = moveMe = new Canvas(), }) } - } - }; - - var otherRoot = new TestRoot { Child = new StackPanel { Children = { (moveTo = new Decorator()) } } }; - - var sceneBuilder = new SceneBuilder(); - - var target = new DeferredRenderer( - root, - loop.Object, - sceneBuilder: sceneBuilder, - renderTargetFactory: root.CreateRenderTarget, - dispatcher: dispatcher); - - var otherSceneBuilder = new SceneBuilder(); - - var otherTarget = new DeferredRenderer( - otherRoot, - loop.Object, - sceneBuilder: otherSceneBuilder, - renderTargetFactory: root.CreateRenderTarget, - dispatcher: dispatcher); - - root.Renderer = target; - otherRoot.Renderer = otherTarget; - - target.Start(); - otherTarget.Start(); - - RunFrame(target); - RunFrame(otherTarget); - - moveFrom.Child = null; - moveTo.Child = moveMe; - - RunFrame(target); - RunFrame(otherTarget); - - var scene = target.UnitTestScene(); - var otherScene = otherTarget.UnitTestScene(); - - var moveFromNode = (VisualNode)scene.FindNode(moveFrom); - var moveToNode = (VisualNode)otherScene.FindNode(moveTo); - - Assert.Empty(moveFromNode.Children); - Assert.Equal(1, moveToNode.Children.Count); - Assert.Same(moveMe, moveToNode.Children[0].Visual); - } - } - - [Fact] - public void Should_Push_Opacity_For_Controls_With_Less_Than_1_Opacity() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var root = new TestRoot - { - Width = 100, Height = 100, Child = new Border { Background = Brushes.Red, Opacity = 0.5, } - }; - - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - - var target = CreateTargetAndRunFrame(root); - var context = GetLayerContext(target, root); - var animation = new BehaviorSubject(0.5); - - context.Verify(x => x.PushOpacity(0.5), Times.Once); - context.Verify(x => x.DrawRectangle(Brushes.Red, null, new Rect(0, 0, 100, 100), default), Times.Once); - context.Verify(x => x.PopOpacity(), Times.Once); - } - } - - [Fact] - public void Should_Not_Draw_Controls_With_0_Opacity() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var root = new TestRoot - { - Width = 100, - Height = 100, - Child = new Border - { - Background = Brushes.Red, - Opacity = 0, - Child = new Border { Background = Brushes.Green, } - } - }; - - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - - var target = CreateTargetAndRunFrame(root); - var context = GetLayerContext(target, root); - var animation = new BehaviorSubject(0.5); - - context.Verify(x => x.PushOpacity(0.5), Times.Never); - context.Verify(x => x.DrawRectangle(Brushes.Red, null, new Rect(0, 0, 100, 100), default), Times.Never); - context.Verify(x => x.PopOpacity(), Times.Never); - } - } - - [Fact] - public void Should_Push_Opacity_Mask() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var root = new TestRoot - { - Width = 100, - Height = 100, - Child = new Border { Background = Brushes.Red, OpacityMask = Brushes.Green, } - }; - - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - - var target = CreateTargetAndRunFrame(root); - var context = GetLayerContext(target, root); - var animation = new BehaviorSubject(0.5); - - context.Verify(x => x.PushOpacityMask(Brushes.Green, new Rect(0, 0, 100, 100)), Times.Once); - context.Verify(x => x.DrawRectangle(Brushes.Red, null, new Rect(0, 0, 100, 100), default), Times.Once); - context.Verify(x => x.PopOpacityMask(), Times.Once); - } - } - - [Fact] - public void Should_Create_Layer_For_Root() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var root = new TestRoot(); - var rootLayer = new Mock(); - - var sceneBuilder = new Mock(); - - sceneBuilder.Setup(x => x.UpdateAll(It.IsAny())) - .Callback(scene => - { - scene.Size = root.ClientSize; - scene.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize)); - }); - - var renderInterface = new Mock(); - var target = CreateTargetAndRunFrame(root, sceneBuilder: sceneBuilder.Object); - - Assert.Single(target.Layers); - } - } - - [Fact(Skip = "Layers are disabled. See #2244")] - public void Should_Create_And_Delete_Layers_For_Controls_With_Animated_Opacity() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Border border; - - var root = new TestRoot - { - Width = 100, - Height = 100, - Child = new Border - { - Background = Brushes.Red, - Child = border = new Border - { - Background = Brushes.Green, Child = new Canvas(), Opacity = 0.9, - } - } - }; - - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - - var timer = new Mock(); - var target = CreateTargetAndRunFrame(root, timer); - - Assert.Equal(new[] { root }, target.Layers.Select(x => x.LayerRoot)); - - var animation = new BehaviorSubject(0.5); - border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); - RunFrame(target); - - Assert.Equal(new Visual[] { root, border }, target.Layers.Select(x => x.LayerRoot)); - - animation.OnCompleted(); - RunFrame(target); - - Assert.Equal(new[] { root }, target.Layers.Select(x => x.LayerRoot)); - } - } - - [Fact(Skip = "Layers are disabled. See #2244")] - public void Should_Not_Create_Layer_For_Childless_Control_With_Animated_Opacity() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Border border; - - var root = new TestRoot - { - Width = 100, - Height = 100, - Child = new Border - { - Background = Brushes.Red, Child = border = new Border { Background = Brushes.Green, } - } - }; - - var animation = new BehaviorSubject(0.5); - border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); - - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - - var timer = new Mock(); - var target = CreateTargetAndRunFrame(root, timer); - - Assert.Single(target.Layers); - } - } - - [Fact(Skip = "Layers are disabled. See #2244")] - public void Should_Not_Push_Opacity_For_Transparent_Layer_Root_Control() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Border border; - - var root = new TestRoot - { - Width = 100, - Height = 100, - Child = border = new Border { Background = Brushes.Red, Child = new Canvas(), } - }; - - var animation = new BehaviorSubject(0.5); - border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); - - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - - var target = CreateTargetAndRunFrame(root); - var context = GetLayerContext(target, border); - - context.Verify(x => x.PushOpacity(0.5), Times.Never); - context.Verify(x => x.DrawRectangle(Brushes.Red, null, new Rect(0, 0, 100, 100), default), Times.Once); - context.Verify(x => x.PopOpacity(), Times.Never); - } - } - - [Fact(Skip = "Layers are disabled. See #2244")] - public void Should_Draw_Transparent_Layer_With_Correct_Opacity() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Border border; - - var root = new TestRoot - { - Width = 100, - Height = 100, - Child = border = new Border { Background = Brushes.Red, Child = new Canvas(), } - }; - - var animation = new BehaviorSubject(0.5); - border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); - - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - - var target = CreateTargetAndRunFrame(root); - var context = Mock.Get(target.RenderTarget.CreateDrawingContext(null)); - var borderLayer = target.Layers[border].Bitmap; - - context.Verify(x => x.DrawBitmap(borderLayer, 0.5, It.IsAny(), It.IsAny(), - BitmapInterpolationMode.Default)); - } - } - - [Fact] - public void Can_Dirty_Control_In_SceneInvalidated() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Border border1; - Border border2; - - var root = new TestRoot - { - Width = 100, - Height = 100, - Child = new StackPanel - { - Children = - { - (border1 = new Border { Background = Brushes.Red, Child = new Canvas(), }), - (border2 = new Border { Background = Brushes.Red, Child = new Canvas(), }), - } - } - }; - - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - - var target = CreateTargetAndRunFrame(root); - var invalidated = false; - - target.SceneInvalidated += (s, e) => - { - invalidated = true; - target.AddDirty(border2); - }; - - target.AddDirty(border1); - target.Paint(new Rect(root.DesiredSize)); - - Assert.True(invalidated); - Assert.True(((IRenderLoopTask)target).NeedsUpdate); - } - } - - private DeferredRenderer CreateTargetAndRunFrame( - TestRoot root, - Mock timer = null, - ISceneBuilder sceneBuilder = null, - IDispatcher dispatcher = null) - { - timer = timer ?? new Mock(); - dispatcher = dispatcher ?? new ImmediateDispatcher(); - var target = new DeferredRenderer( - root, - new RenderLoop(timer.Object, dispatcher), - sceneBuilder: sceneBuilder, - renderTargetFactory: root.CreateRenderTarget, - dispatcher: dispatcher); - root.Renderer = target; - target.Start(); - RunFrame(target); - return target; - } - - private Mock GetLayerContext(DeferredRenderer renderer, Control layerRoot) - { - return Mock.Get(renderer.Layers[layerRoot].Bitmap.Item.CreateDrawingContext(null)); - } - - private void IgnoreFirstFrame(IRenderLoopTask task, Mock sceneBuilder) - { - RunFrame(task); - sceneBuilder.Invocations.Clear(); - } - - private void RunFrame(IRenderLoopTask task) - { - task.Update(TimeSpan.Zero); - task.Render(); - } - - private IRenderTargetBitmapImpl CreateLayer() - { - return Mock.Of(x => - x.CreateDrawingContext(It.IsAny()) == Mock.Of()); - } - - private Mock MockSceneBuilder(IRenderRoot root) - { - var result = new Mock(); - result.Setup(x => x.UpdateAll(It.IsAny())) - .Callback(x => x.Layers.Add((Visual)root).Dirty.Add(new Rect(root.ClientSize))); - return result; - } - } -} diff --git a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs deleted file mode 100644 index 4f11af7327..0000000000 --- a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs +++ /dev/null @@ -1,577 +0,0 @@ -using System; -using System.Linq; -using Avalonia.Controls; -using Avalonia.Controls.Presenters; -using Avalonia.Controls.Shapes; -using Avalonia.Layout; -using Avalonia.Media; -using Avalonia.Platform; -using Avalonia.Rendering; -using Avalonia.UnitTests; -using Avalonia.VisualTree; -using Moq; -using Xunit; - -namespace Avalonia.Base.UnitTests.Rendering -{ - public class DeferredRendererTests_HitTesting - { - [Fact] - public void HitTest_Should_Find_Controls_At_Point() - { - using (TestApplication()) - { - var root = new TestRoot - { - Width = 200, - Height = 200, - Child = new Border - { - Width = 100, - Height = 100, - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - } - }; - - root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - - var result = root.Renderer.HitTest(new Point(100, 100), root, null); - - Assert.Equal(new[] { root.Child }, result); - } - } - - [Fact] - public void HitTest_Should_Not_Find_Empty_Controls_At_Point() - { - using (TestApplication()) - { - var root = new TestRoot - { - Width = 200, - Height = 200, - Child = new Border - { - Width = 100, - Height = 100, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - } - }; - - root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - - var result = root.Renderer.HitTest(new Point(100, 100), root, null); - - Assert.Empty(result); - } - } - - [Fact] - public void HitTest_Should_Not_Find_Invisible_Controls_At_Point() - { - using (TestApplication()) - { - Border visible; - var root = new TestRoot - { - Width = 200, - Height = 200, - Child = new Border - { - Width = 100, - Height = 100, - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center, - IsVisible = false, - Child = visible = new Border - { - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - } - } - }; - - root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - - var result = root.Renderer.HitTest(new Point(100, 100), root, null); - - Assert.Empty(result); - } - } - - [Fact] - public void HitTest_Should_Not_Find_Control_Outside_Point() - { - using (TestApplication()) - { - var root = new TestRoot - { - Width = 200, - Height = 200, - Child = new Border - { - Width = 100, - Height = 100, - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - } - }; - - root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - - var result = root.Renderer.HitTest(new Point(10, 10), root, null); - - Assert.Empty(result); - } - } - - [Fact] - public void HitTest_Should_Return_Top_Controls_First() - { - using (TestApplication()) - { - Panel container; - var root = new TestRoot - { - Child = container = new Panel - { - Width = 200, - Height = 200, - Children = - { - new Border - { - Width = 100, - Height = 100, - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - }, - new Border - { - Width = 50, - Height = 50, - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - } - } - } - }; - - root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(container.DesiredSize)); - - var result = root.Renderer.HitTest(new Point(100, 100), root, null); - - Assert.Equal(new[] { container.Children[1], container.Children[0] }, result); - } - } - - [Fact] - public void HitTest_Should_Return_Top_Controls_First_With_ZIndex() - { - using (TestApplication()) - { - Panel container; - var root = new TestRoot - { - Child = container = new Panel - { - Width = 200, - Height = 200, - Children = - { - new Border - { - Width = 100, - Height = 100, - ZIndex = 1, - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - }, - new Border - { - Width = 50, - Height = 50, - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - }, - new Border - { - Width = 75, - Height = 75, - ZIndex = 2, - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - } - } - } - }; - - root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(container.DesiredSize)); - - var result = root.Renderer.HitTest(new Point(100, 100), root, null); - - Assert.Equal(new[] { container.Children[2], container.Children[0], container.Children[1] }, result); - } - } - - [Fact] - public void HitTest_Should_Find_Control_Translated_Outside_Parent_Bounds() - { - using (TestApplication()) - { - Border target; - Panel container; - var root = new TestRoot - { - Child = container = new Panel - { - Width = 200, - Height = 200, - Background = Brushes.Red, - ClipToBounds = false, - Children = - { - new Border - { - Width = 100, - Height = 100, - ZIndex = 1, - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top, - Child = target = new Border - { - Width = 50, - Height = 50, - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top, - RenderTransform = new TranslateTransform(110, 110), - } - }, - } - } - }; - - root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); - container.Measure(Size.Infinity); - container.Arrange(new Rect(container.DesiredSize)); - - var result = root.Renderer.HitTest(new Point(120, 120), root, null); - - Assert.Equal(new Visual[] { target, container }, result); - } - } - - [Fact] - public void HitTest_Should_Not_Find_Control_Outside_Parent_Bounds_When_Clipped() - { - using (TestApplication()) - { - Border target; - Panel container; - var root = new TestRoot - { - Child = container = new Panel - { - Width = 100, - Height = 200, - Background = Brushes.Red, - Children = - { - new Panel() - { - Width = 100, - Height = 100, - Background = Brushes.Red, - Margin = new Thickness(0, 100, 0, 0), - ClipToBounds = true, - Children = - { - (target = new Border() - { - Width = 100, - Height = 100, - Background = Brushes.Red, - Margin = new Thickness(0, -100, 0, 0) - }) - } - } - } - } - }; - - root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(container.DesiredSize)); - - var result = root.Renderer.HitTest(new Point(50, 50), root, null); - - Assert.Equal(new[] { container }, result); - } - } - - [Fact] - public void HitTest_Should_Not_Find_Control_Outside_Scroll_Viewport() - { - using (TestApplication()) - { - Border target; - Border item1; - Border item2; - ScrollContentPresenter scroll; - Panel container; - var root = new TestRoot - { - Child = container = new Panel - { - Width = 100, - Height = 200, - Background = Brushes.Red, - Children = - { - (target = new Border() - { - Name = "b1", - Width = 100, - Height = 100, - Background = Brushes.Red, - }), - new Border() - { - Name = "b2", - Width = 100, - Height = 100, - Background = Brushes.Red, - Margin = new Thickness(0, 100, 0, 0), - Child = scroll = new ScrollContentPresenter() - { - CanHorizontallyScroll = true, - CanVerticallyScroll = true, - Content = new StackPanel() - { - Children = - { - (item1 = new Border() - { - Name = "b3", - Width = 100, - Height = 100, - Background = Brushes.Red, - }), - (item2 = new Border() - { - Name = "b4", - Width = 100, - Height = 100, - Background = Brushes.Red, - }), - } - } - } - } - } - } - }; - - scroll.UpdateChild(); - - root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(container.DesiredSize)); - - root.Renderer.Paint(default); - var result = root.Renderer.HitTest(new Point(50, 150), root, null).First(); - - Assert.Equal(item1, result); - - result = root.Renderer.HitTest(new Point(50, 50), root, null).First(); - - Assert.Equal(target, result); - - scroll.Offset = new Vector(0, 100); - - // We don't have LayoutManager set up so do the layout pass manually. - scroll.Parent.InvalidateArrange(); - container.InvalidateArrange(); - container.Arrange(new Rect(container.DesiredSize)); - - root.Renderer.Paint(default); - result = root.Renderer.HitTest(new Point(50, 150), root, null).First(); - Assert.Equal(item2, result); - - result = root.Renderer.HitTest(new Point(50, 50), root, null).First(); - Assert.Equal(target, result); - } - } - - [Fact] - public void HitTest_Should_Not_Find_Path_When_Outside_Fill() - { - using (TestApplication()) - { - Path path; - var root = new TestRoot - { - Width = 200, - Height = 200, - Child = path = new Path - { - Width = 200, - Height = 200, - Fill = Brushes.Red, - Data = StreamGeometry.Parse("M100,0 L0,100 100,100") - } - }; - - root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - - var context = new DrawingContext(Mock.Of()); - - var result = root.Renderer.HitTest(new Point(100, 100), root, null); - Assert.Equal(new[] { path }, result); - - result = root.Renderer.HitTest(new Point(10, 10), root, null); - Assert.Empty(result); - } - } - - [Fact] - public void HitTest_Should_Respect_Geometry_Clip() - { - using (TestApplication()) - { - Border border; - Canvas canvas; - var root = new TestRoot - { - Width = 400, - Height = 400, - Child = border = new Border - { - Background = Brushes.Red, - Clip = StreamGeometry.Parse("M100,0 L0,100 100,100"), - Width = 200, - Height = 200, - Child = canvas = new Canvas - { - Background = Brushes.Yellow, - Margin = new Thickness(10), - } - } - }; - - root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - Assert.Equal(new Rect(100, 100, 200, 200), border.Bounds); - - var context = new DrawingContext(Mock.Of()); - - var result = root.Renderer.HitTest(new Point(200, 200), root, null); - Assert.Equal(new Visual[] { canvas, border }, result); - - result = root.Renderer.HitTest(new Point(110, 110), root, null); - Assert.Empty(result); - } - } - - [Fact] - public void HitTest_Should_Accommodate_ICustomHitTest() - { - using (TestApplication()) - { - Border border; - - var root = new TestRoot - { - Width = 300, - Height = 200, - Child = border = new CustomHitTestBorder - { - Width = 100, - Height = 100, - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - } - }; - - root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - - var result = root.Renderer.HitTest(new Point(75, 100), root, null); - Assert.Equal(new[] { border }, result); - - result = root.Renderer.HitTest(new Point(125, 100), root, null); - Assert.Equal(new[] { border }, result); - - result = root.Renderer.HitTest(new Point(175, 100), root, null); - Assert.Empty(result); - } - } - - [Fact] - public void HitTest_Should_Not_Hit_Controls_Next_Pixel() - { - using (TestApplication()) - { - Border targetRectangle; - - var root = new TestRoot - { - Width = 50, - Height = 200, - Child = new StackPanel - { - Orientation = Orientation.Vertical, - HorizontalAlignment = HorizontalAlignment.Left, - Children = - { - new Border { Width = 50, Height = 50, Background = Brushes.Red}, - { targetRectangle = new Border { Width = 50, Height = 50, Background = Brushes.Green} } - } - } - }; - - root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - - var result = root.Renderer.HitTest(new Point(25, 50), root, null); - Assert.Equal(new[] { targetRectangle }, result); - } - } - - private static IDisposable TestApplication() - { - return UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); - } - } -} diff --git a/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests.cs deleted file mode 100644 index a07191f464..0000000000 --- a/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests.cs +++ /dev/null @@ -1,273 +0,0 @@ -using System.Collections.Generic; -using Avalonia.Controls; -using Avalonia.Layout; -using Avalonia.Media; -using Avalonia.Platform; -using Avalonia.Rendering; -using Avalonia.UnitTests; -using Avalonia.VisualTree; -using Moq; -using Xunit; - -namespace Avalonia.Base.UnitTests.Rendering -{ - public class ImmediateRendererTests - { - [Fact] - public void AddDirty_Call_RenderRoot_Invalidate() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var child = new Border - { - Width = 100, - Height = 100, - Margin = new(10), - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top, - }; - - var root = new RenderRoot - { - Child = child, - Width = 400, - Height = 400, - }; - - root.LayoutManager.ExecuteInitialLayoutPass(); - - var target = new ImmediateRenderer(root, root.CreateRenderTarget); - - target.AddDirty(child); - - Assert.Equal(new[] { new Rect(10, 10, 100, 100) }, root.Invalidations); - } - } - - [Fact] - public void AddDirty_With_RenderTransform_Call_RenderRoot_Invalidate() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var child = new Border - { - Width = 100, - Height = 100, - Margin = new(100), - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top, - }; - - var root = new RenderRoot - { - Child = child, - Width = 400, - Height = 400, - }; - - root.LayoutManager.ExecuteInitialLayoutPass(); - - child.RenderTransform = new ScaleTransform() { ScaleX = 2, ScaleY = 2 }; - - var target = new ImmediateRenderer(root, root.CreateRenderTarget); - - target.AddDirty(child); - - Assert.Equal(new[] { new Rect(50, 50, 200, 200) }, root.Invalidations); - } - } - - [Fact] - public void AddDirty_For_Child_Moved_Should_Invalidate_Previous_Bounds() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var child = new Border - { - Width = 100, - Height = 100, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top, - }; - - var root = new RenderRoot - { - Child = child, - Width = 400, - Height = 400, - }; - - var target = new ImmediateRenderer(root, root.CreateRenderTarget); - - root.LayoutManager.ExecuteInitialLayoutPass(); - target.AddDirty(child); - - Assert.Equal(new Rect(0, 0, 100, 100), root.Invalidations[0]); - - target.Paint(new Rect(0, 0, 100, 100)); - - //move child 100 pixels bottom/right - child.Margin = new(100, 100); - root.LayoutManager.ExecuteLayoutPass(); - - //renderer should invalidate old child bounds with new one - //as on old area there can be artifacts - target.AddDirty(child); - - //invalidate first old position - Assert.Equal(new Rect(0, 0, 100, 100), root.Invalidations[1]); - - //then new position - Assert.Equal(new Rect(100, 100, 100, 100), root.Invalidations[2]); - } - } - - [Fact] - public void Should_Not_Clip_Children_With_RenderTransform_When_In_Bounds() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - const int RootWidth = 300; - const int RootHeight = 300; - - var rootGrid = new Grid { Width = RootWidth, Height = RootHeight, ClipToBounds = true }; - - var stackPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - VerticalAlignment = VerticalAlignment.Top, - HorizontalAlignment = HorizontalAlignment.Right, - Margin = new Thickness(0, 10, 0, 0), - RenderTransformOrigin = new RelativePoint(new Point(0, 0), RelativeUnit.Relative), - RenderTransform = new TransformGroup - { - Children = { new RotateTransform { Angle = 90 }, new TranslateTransform { X = 240 } } - } - }; - - rootGrid.Children.Add(stackPanel); - - TestControl CreateControl() - => new TestControl - { - Width = 80, - Height = 40, - Margin = new Thickness(0, 0, 5, 0), - ClipToBounds = true - }; - - var control1 = CreateControl(); - var control2 = CreateControl(); - var control3 = CreateControl(); - - stackPanel.Children.Add(control1); - stackPanel.Children.Add(control2); - stackPanel.Children.Add(control3); - - var root = new TestRoot(rootGrid); - root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget); - root.LayoutManager.ExecuteInitialLayoutPass(); - - var rootSize = new Size(RootWidth, RootHeight); - root.Measure(rootSize); - root.Arrange(new Rect(rootSize)); - - root.Renderer.Paint(root.Bounds); - - Assert.True(control1.Rendered); - Assert.True(control2.Rendered); - Assert.True(control3.Rendered); - } - } - - [Fact] - public void Should_Not_Render_Clipped_Child_With_RenderTransform_When_Not_In_Bounds() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - const int RootWidth = 300; - const int RootHeight = 300; - - var rootGrid = new Grid { Width = RootWidth, Height = RootHeight, ClipToBounds = true }; - - var stackPanel = new StackPanel - { - Orientation = Orientation.Horizontal, - VerticalAlignment = VerticalAlignment.Top, - HorizontalAlignment = HorizontalAlignment.Right, - Margin = new Thickness(0, 10, 0, 0), - RenderTransformOrigin = new RelativePoint(new Point(0, 0), RelativeUnit.Relative), - RenderTransform = new TransformGroup - { - Children = { new RotateTransform { Angle = 90 }, new TranslateTransform { X = 280 } } - } - }; - - rootGrid.Children.Add(stackPanel); - - TestControl CreateControl() - => new TestControl - { - Width = 160, - Height = 40, - Margin = new Thickness(0, 0, 5, 0), - ClipToBounds = true - }; - - var control1 = CreateControl(); - var control2 = CreateControl(); - var control3 = CreateControl(); - - stackPanel.Children.Add(control1); - stackPanel.Children.Add(control2); - stackPanel.Children.Add(control3); - - var root = new TestRoot(rootGrid); - root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget); - root.LayoutManager.ExecuteInitialLayoutPass(); - - var rootSize = new Size(RootWidth, RootHeight); - root.Measure(rootSize); - root.Arrange(new Rect(rootSize)); - - root.Renderer.Paint(root.Bounds); - - Assert.True(control1.Rendered); - Assert.True(control2.Rendered); - Assert.False(control3.Rendered); - } - } - - [Fact] - public void Static_Render_Method_Does_Not_Update_TransformedBounds() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var target = new Border(); - var expected = new TransformedBounds(new Rect(1, 2, 3, 4), new Rect(4, 5, 6, 7), Matrix.CreateRotation(0.8)); - - target.SetTransformedBounds(expected); - - var renderTarget = Mock.Of(x => - x.CreateDrawingContext(It.IsAny()) == Mock.Of()); - ImmediateRenderer.Render(target, renderTarget); - - Assert.Equal(expected, target.TransformedBounds); - } - } - - private class RenderRoot : TestRoot, IRenderRoot - { - public List Invalidations { get; } = new(); - void IRenderRoot.Invalidate(Rect rect) => Invalidations.Add(rect); - } - - private class TestControl : Control - { - public bool Rendered { get; private set; } - - public override void Render(DrawingContext context) - => Rendered = true; - } - } -} diff --git a/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs b/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs deleted file mode 100644 index 9ce8c42e33..0000000000 --- a/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs +++ /dev/null @@ -1,458 +0,0 @@ -using System; -using System.Linq; -using Avalonia.Controls; -using Avalonia.Controls.Presenters; -using Avalonia.Layout; -using Avalonia.Media; -using Avalonia.Rendering; -using Avalonia.UnitTests; -using Avalonia.VisualTree; -using Xunit; - -namespace Avalonia.Base.UnitTests.Rendering -{ - public class ImmediateRendererTests_HitTesting - { - [Fact] - public void HitTest_Should_Find_Controls_At_Point() - { - using (TestApplication()) - { - var root = new TestRoot - { - Width = 200, - Height = 200, - Child = new Border - { - Width = 100, - Height = 100, - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - } - }; - - root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - root.Renderer.Paint(new Rect(root.ClientSize)); - - var result = root.Renderer.HitTest(new Point(100, 100), root, null); - - Assert.Equal(new[] { root.Child, root }, result); - } - } - - [Fact] - public void HitTest_Should_Not_Find_Invisible_Controls_At_Point() - { - using (TestApplication()) - { - Border visible; - var root = new TestRoot - { - Width = 200, - Height = 200, - Child = new Border - { - Width = 100, - Height = 100, - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center, - IsVisible = false, - Child = visible = new Border - { - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Stretch, - } - } - }; - - root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - root.Renderer.Paint(new Rect(root.ClientSize)); - - var result = root.Renderer.HitTest(new Point(100, 100), root, null); - - Assert.Equal(new[] { root }, result); - } - } - - [Fact] - public void HitTest_Should_Not_Find_Control_Outside_Point() - { - using (TestApplication()) - { - var root = new TestRoot - { - Width = 200, - Height = 200, - Child = new Border - { - Width = 100, - Height = 100, - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - } - }; - - root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - root.Renderer.Paint(new Rect(root.ClientSize)); - - var result = root.Renderer.HitTest(new Point(10, 10), root, null); - - Assert.Equal(new[] { root }, result); - } - } - - [Fact] - public void HitTest_Should_Return_Top_Controls_First() - { - using (TestApplication()) - { - Panel container; - var root = new TestRoot - { - Child = container = new Panel - { - Width = 200, - Height = 200, - Children = - { - new Border - { - Width = 100, - Height = 100, - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - }, - new Border - { - Width = 50, - Height = 50, - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - } - } - } - }; - - root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(container.DesiredSize)); - root.Renderer.Paint(new Rect(root.ClientSize)); - - var result = root.Renderer.HitTest(new Point(100, 100), root, null); - - Assert.Equal(new[] { container.Children[1], container.Children[0], container, root }, result); - } - } - - [Fact] - public void HitTest_Should_Return_Top_Controls_First_With_ZIndex() - { - using (TestApplication()) - { - Panel container; - var root = new TestRoot - { - Child = container = new Panel - { - Width = 200, - Height = 200, - Children = - { - new Border - { - Width = 100, - Height = 100, - ZIndex = 1, - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - }, - new Border - { - Width = 50, - Height = 50, - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - }, - new Border - { - Width = 75, - Height = 75, - ZIndex = 2, - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - } - } - } - }; - - root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(container.DesiredSize)); - root.Renderer.Paint(new Rect(root.ClientSize)); - - var result = root.Renderer.HitTest(new Point(100, 100), root, null); - - Assert.Equal( - new[] - { - container.Children[2], - container.Children[0], - container.Children[1], - container, - root - }, - result); - } - } - - [Fact] - public void HitTest_Should_Find_Control_Translated_Outside_Parent_Bounds() - { - using (TestApplication()) - { - Border target; - Panel container; - var root = new TestRoot - { - Child = container = new Panel - { - Width = 200, - Height = 200, - Background = Brushes.Red, - ClipToBounds = false, - Children = - { - new Border - { - Width = 100, - Height = 100, - ZIndex = 1, - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top, - Child = target = new Border - { - Width = 50, - Height = 50, - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top, - RenderTransform = new TranslateTransform(110, 110), - } - }, - } - } - }; - - root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget); - container.Measure(Size.Infinity); - container.Arrange(new Rect(container.DesiredSize)); - root.Renderer.Paint(new Rect(root.ClientSize)); - - var result = root.Renderer.HitTest(new Point(120, 120), root, null); - - Assert.Equal(new Visual[] { target, container }, result); - } - } - - [Fact] - public void HitTest_Should_Not_Find_Control_Outside_Parent_Bounds_When_Clipped() - { - using (TestApplication()) - { - Border target; - Panel container; - var root = new TestRoot - { - Child = container = new Panel - { - Width = 100, - Height = 200, - Background = Brushes.Red, - Children = - { - new Panel() - { - Width = 100, - Height = 100, - Background = Brushes.Red, - Margin = new Thickness(0, 100, 0, 0), - ClipToBounds = true, - Children = - { - (target = new Border() - { - Width = 100, - Height = 100, - Background = Brushes.Red, - Margin = new Thickness(0, -100, 0, 0) - }) - } - } - } - } - }; - - root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(container.DesiredSize)); - root.Renderer.Paint(new Rect(root.ClientSize)); - - var result = root.Renderer.HitTest(new Point(50, 50), root, null); - - Assert.Equal(new Visual[] { container, root }, result); - } - } - - [Fact] - public void HitTest_Should_Not_Find_Control_Outside_Scroll_Viewport() - { - using (TestApplication()) - { - Border target; - Border item1; - Border item2; - ScrollContentPresenter scroll; - Panel container; - var root = new TestRoot - { - Child = container = new Panel - { - Width = 100, - Height = 200, - Background = Brushes.Red, - Children = - { - (target = new Border() - { - Name = "b1", - Width = 100, - Height = 100, - Background = Brushes.Red, - }), - new Border() - { - Name = "b2", - Width = 100, - Height = 100, - Background = Brushes.Red, - Margin = new Thickness(0, 100, 0, 0), - Child = scroll = new ScrollContentPresenter() - { - CanHorizontallyScroll = true, - CanVerticallyScroll = true, - Content = new StackPanel() - { - Children = - { - (item1 = new Border() - { - Name = "b3", - Width = 100, - Height = 100, - Background = Brushes.Red, - }), - (item2 = new Border() - { - Name = "b4", - Width = 100, - Height = 100, - Background = Brushes.Red, - }), - } - } - } - } - } - } - }; - - scroll.UpdateChild(); - - root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(container.DesiredSize)); - root.Renderer.Paint(new Rect(root.ClientSize)); - - var result = root.Renderer.HitTest(new Point(50, 150), root, null).First(); - - Assert.Equal(item1, result); - - result = root.Renderer.HitTest(new Point(50, 50), root, null).First(); - - Assert.Equal(target, result); - - scroll.Offset = new Vector(0, 100); - - // We don't have LayoutManager set up so do the layout pass manually. - scroll.Parent.InvalidateArrange(); - container.InvalidateArrange(); - container.Arrange(new Rect(container.DesiredSize)); - root.Renderer.Paint(new Rect(root.ClientSize)); - - result = root.Renderer.HitTest(new Point(50, 150), root, null).First(); - Assert.Equal(item2, result); - - result = root.Renderer.HitTest(new Point(50, 50), root, null).First(); - Assert.Equal(target, result); - } - } - - [Fact] - public void HitTest_Should_Accommodate_ICustomHitTest() - { - using (TestApplication()) - { - Border border; - - var root = new TestRoot - { - Width = 300, - Height = 200, - Child = border = new CustomHitTestBorder - { - Width = 100, - Height = 100, - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - } - }; - - root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - root.Renderer.Paint(new Rect(root.ClientSize)); - - var result = root.Renderer.HitTest(new Point(75, 100), root, null).First(); - Assert.Equal(border, result); - - result = root.Renderer.HitTest(new Point(125, 100), root, null).First(); - Assert.Equal(border, result); - - result = root.Renderer.HitTest(new Point(175, 100), root, null).First(); - Assert.Equal(root, result); - } - } - - private static IDisposable TestApplication() - { - return UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); - } - } -} diff --git a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs deleted file mode 100644 index 4cdb11b468..0000000000 --- a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs +++ /dev/null @@ -1,225 +0,0 @@ -using System.Linq; -using Avalonia.Media; -using Avalonia.Rendering.SceneGraph; -using Avalonia.UnitTests; -using Avalonia.Utilities; -using Avalonia.VisualTree; -using Moq; -using Xunit; - -namespace Avalonia.Base.UnitTests.Rendering.SceneGraph -{ - public class DeferredDrawingContextImplTests - { - [Fact] - public void Should_Add_VisualNode() - { - var parent = new VisualNode(new TestRoot(), null); - var child = new VisualNode(Mock.Of(), parent); - var layers = new SceneLayers(parent.Visual); - var target = new DeferredDrawingContextImpl(null, layers); - - target.BeginUpdate(parent); - target.BeginUpdate(child); - - Assert.Equal(1, parent.Children.Count); - Assert.Same(child, parent.Children[0]); - } - - [Fact] - public void Should_Not_Replace_Identical_VisualNode() - { - var parent = new VisualNode(new TestRoot(), null); - var child = new VisualNode(Mock.Of(), parent); - var layers = new SceneLayers(parent.Visual); - - parent.AddChild(child); - - var target = new DeferredDrawingContextImpl(null, layers); - - target.BeginUpdate(parent); - target.BeginUpdate(child); - - Assert.Equal(1, parent.Children.Count); - Assert.Same(child, parent.Children[0]); - } - - [Fact] - public void Should_Replace_Different_VisualNode() - { - var parent = new VisualNode(new TestRoot(), null); - var child1 = new VisualNode(Mock.Of(), parent); - var child2 = new VisualNode(Mock.Of(), parent); - var layers = new SceneLayers(parent.Visual); - - parent.AddChild(child1); - - var target = new DeferredDrawingContextImpl(null, layers); - - target.BeginUpdate(parent); - target.BeginUpdate(child2); - - Assert.Equal(1, parent.Children.Count); - Assert.Same(child2, parent.Children[0]); - } - - [Fact] - public void TrimChildren_Should_Trim_Children() - { - var root = new TestRoot(); - var node = new VisualNode(root, null) { LayerRoot = root }; - - node.AddChild(new VisualNode(Mock.Of(), node) { LayerRoot = root }); - node.AddChild(new VisualNode(Mock.Of(), node) { LayerRoot = root }); - node.AddChild(new VisualNode(Mock.Of(), node) { LayerRoot = root }); - node.AddChild(new VisualNode(Mock.Of(), node) { LayerRoot = root }); - - var layers = new SceneLayers(root); - var target = new DeferredDrawingContextImpl(null, layers); - var child1 = new VisualNode(Mock.Of(), node) { LayerRoot = root }; - var child2 = new VisualNode(Mock.Of(), node) { LayerRoot = root }; - - target.BeginUpdate(node); - using (target.BeginUpdate(child1)) { } - using (target.BeginUpdate(child2)) { } - target.TrimChildren(); - - Assert.Equal(2, node.Children.Count); - } - - [Fact] - public void Should_Add_DrawOperations() - { - var node = new VisualNode(new TestRoot(), null); - var layers = new SceneLayers(node.Visual); - var target = new DeferredDrawingContextImpl(null, layers); - - node.LayerRoot = node.Visual; - - using (target.BeginUpdate(node)) - { - target.DrawRectangle(Brushes.Red, new Pen(Brushes.Green, 1), new Rect(0, 0, 100, 100)); - } - - Assert.Equal(1, node.DrawOperations.Count); - Assert.IsType(node.DrawOperations[0].Item); - } - - [Fact] - public void Should_Not_Replace_Identical_DrawOperation() - { - var node = new VisualNode(new TestRoot(), null); - var operation = RefCountable.Create(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), default)); - var layers = new SceneLayers(node.Visual); - var target = new DeferredDrawingContextImpl(null, layers); - - node.LayerRoot = node.Visual; - node.AddDrawOperation(operation); - - using (target.BeginUpdate(node)) - { - target.DrawRectangle(Brushes.Red, null, new Rect(0, 0, 100, 100)); - } - - Assert.Equal(1, node.DrawOperations.Count); - Assert.Same(operation.Item, node.DrawOperations.Single().Item); - - Assert.IsType(node.DrawOperations[0].Item); - } - - [Fact] - public void Should_Replace_Different_DrawOperation() - { - var node = new VisualNode(new TestRoot(), null); - var operation = RefCountable.Create(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), default)); - var layers = new SceneLayers(node.Visual); - var target = new DeferredDrawingContextImpl(null, layers); - - node.LayerRoot = node.Visual; - node.AddDrawOperation(operation); - - using (target.BeginUpdate(node)) - { - target.DrawRectangle(Brushes.Green, null, new Rect(0, 0, 100, 100)); - } - - Assert.Equal(1, node.DrawOperations.Count); - Assert.NotSame(operation, node.DrawOperations.Single()); - - Assert.IsType(node.DrawOperations[0].Item); - } - - [Fact] - public void Should_Update_DirtyRects() - { - var node = new VisualNode(new TestRoot(), null); - var operation = new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), default); - var layers = new SceneLayers(node.Visual); - var target = new DeferredDrawingContextImpl(null, layers); - - node.LayerRoot = node.Visual; - - using (target.BeginUpdate(node)) - { - target.DrawRectangle(Brushes.Green, null, new Rect(0, 0, 100, 100)); - } - - Assert.Equal(new Rect(0, 0, 100, 100), layers.Single().Dirty.Single()); - } - - [Fact] - public void Should_Trim_DrawOperations() - { - var node = new VisualNode(new TestRoot(), null); - node.LayerRoot = node.Visual; - - for (var i = 0; i < 4; ++i) - { - var drawOperation = new Mock(); - using (var r = RefCountable.Create(drawOperation.Object)) - { - node.AddDrawOperation(r); - } - } - - var drawOperations = node.DrawOperations.Select(op => op.Item).ToList(); - var layers = new SceneLayers(node.Visual); - var target = new DeferredDrawingContextImpl(null, layers); - - using (target.BeginUpdate(node)) - { - target.DrawRectangle(Brushes.Green, null, new Rect(0, 0, 10, 100)); - target.DrawRectangle(Brushes.Blue, null, new Rect(0, 0, 20, 100)); - } - - Assert.Equal(2, node.DrawOperations.Count); - - foreach (var i in drawOperations) - { - Mock.Get(i).Verify(x => x.Dispose()); - } - } - - [Fact] - public void Trimmed_DrawOperations_Releases_Reference() - { - var node = new VisualNode(new TestRoot(), null); - var operation = RefCountable.Create(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), default)); - var layers = new SceneLayers(node.Visual); - var target = new DeferredDrawingContextImpl(null, layers); - - node.LayerRoot = node.Visual; - node.AddDrawOperation(operation); - Assert.Equal(2, operation.RefCount); - - using (target.BeginUpdate(node)) - { - target.DrawRectangle(Brushes.Green, null, new Rect(0, 0, 100, 100)); - } - - Assert.Equal(1, node.DrawOperations.Count); - Assert.NotSame(operation, node.DrawOperations.Single()); - Assert.Equal(1, operation.RefCount); - } - } -} diff --git a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs deleted file mode 100644 index a3d11a76aa..0000000000 --- a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs +++ /dev/null @@ -1,1087 +0,0 @@ -using System.Linq; -using System.Reactive.Subjects; -using Avalonia.Controls; -using Avalonia.Data; -using Avalonia.Layout; -using Avalonia.Media; -using Avalonia.Media.Imaging; -using Avalonia.Platform; -using Avalonia.Rendering.SceneGraph; -using Avalonia.UnitTests; -using Avalonia.Utilities; -using Avalonia.VisualTree; -using Moq; -using Xunit; - -namespace Avalonia.Base.UnitTests.Rendering.SceneGraph -{ - public partial class SceneBuilderTests - { - [Fact] - public void Should_Build_Initial_Scene() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Border border; - TextBlock textBlock; - var tree = new TestRoot - { - Child = border = new Border - { - Width = 100, - Height = 100, - Background = Brushes.Red, - Child = textBlock = new TextBlock - { - TextWrapping = TextWrapping.NoWrap, - Text = "Hello World", - } - } - }; - - tree.Measure(Size.Infinity); - tree.Arrange(new Rect(tree.DesiredSize)); - - var result = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(result); - - Assert.Same(tree, ((VisualNode)result.Root).LayerRoot); - Assert.Equal(1, result.Root.Children.Count); - - var borderNode = (VisualNode)result.Root.Children[0]; - Assert.Same(borderNode, result.FindNode(border)); - Assert.Same(border, borderNode.Visual); - Assert.Equal(1, borderNode.Children.Count); - Assert.Equal(1, borderNode.DrawOperations.Count); - - var backgroundNode = (RectangleNode)borderNode.DrawOperations[0].Item; - Assert.Equal(Brushes.Red, backgroundNode.Brush); - - var textBlockNode = borderNode.Children[0]; - Assert.Same(textBlockNode, result.FindNode(textBlock)); - Assert.Same(textBlock, textBlockNode.Visual); - Assert.Equal(1, textBlockNode.DrawOperations.Count); - - var textNode = (GlyphRunNode)textBlockNode.DrawOperations[0].Item; - Assert.NotNull(textNode.GlyphRun); - } - } - - [Fact] - public void Should_Respect_Margin_For_ClipBounds() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Canvas canvas; - var tree = new TestRoot - { - Width = 200, - Height = 300, - Child = new Border - { - Margin = new Thickness(10, 20, 30, 40), - Child = canvas = new Canvas - { - ClipToBounds = true, - Background = Brushes.AliceBlue, - } - } - }; - - tree.Measure(Size.Infinity); - tree.Arrange(new Rect(tree.DesiredSize)); - - var result = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(result); - - var canvasNode = result.FindNode(canvas); - Assert.Equal(new Rect(10, 20, 160, 240), canvasNode.ClipBounds); - - // Initial ClipBounds are correct, make sure they're still correct after updating canvas. - result = result.CloneScene(); - Assert.True(sceneBuilder.Update(result, canvas)); - - canvasNode = result.FindNode(canvas); - Assert.Equal(new Rect(10, 20, 160, 240), canvasNode.ClipBounds); - } - } - - [Fact] - public void ClipBounds_Should_Be_Intersection_With_Parent_ClipBounds() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Border border; - var tree = new TestRoot - { - Width = 200, - Height = 300, - Child = new Canvas - { - ClipToBounds = true, - Width = 100, - Height = 100, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top, - Children = - { - (border = new Border - { - Background = Brushes.AliceBlue, - ClipToBounds = true, - Width = 100, - Height = 100, - [Canvas.LeftProperty] = 50, - [Canvas.TopProperty] = 50, - }) - } - } - }; - - tree.Measure(Size.Infinity); - tree.Arrange(new Rect(tree.DesiredSize)); - - var scene = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene); - - var borderNode = scene.FindNode(border); - Assert.Equal(new Rect(50, 50, 50, 50), borderNode.ClipBounds); - } - } - - [Fact] - public void Should_Update_Descendent_ClipBounds_When_Margin_Changed() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Border border; - Canvas canvas; - var tree = new TestRoot - { - Width = 200, - Height = 300, - Child = canvas = new Canvas - { - ClipToBounds = true, - Width = 100, - Height = 100, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top, - Children = - { - (border = new Border - { - Background = Brushes.AliceBlue, - ClipToBounds = true, - Width = 100, - Height = 100, - [Canvas.LeftProperty] = 50, - [Canvas.TopProperty] = 50, - }) - } - } - }; - - tree.Measure(Size.Infinity); - tree.Arrange(new Rect(tree.DesiredSize)); - - var scene = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene); - - var borderNode = scene.FindNode(border); - Assert.Equal(new Rect(50, 50, 50, 50), borderNode.ClipBounds); - - canvas.Width = canvas.Height = 125; - canvas.Measure(Size.Infinity); - canvas.Arrange(new Rect(tree.DesiredSize)); - - // Initial ClipBounds are correct, make sure they're still correct after updating canvas. - scene = scene.CloneScene(); - Assert.True(sceneBuilder.Update(scene, canvas)); - - borderNode = scene.FindNode(border); - Assert.Equal(new Rect(50, 50, 75, 75), borderNode.ClipBounds); - } - } - - [Fact] - public void Should_Respect_ZIndex() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Border front; - Border back; - var tree = new TestRoot - { - Child = new Panel - { - Children = - { - (front = new Border - { - ZIndex = 1, - }), - (back = new Border - { - ZIndex = 0, - }), - } - } - }; - - var result = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(result); - - var panelNode = result.FindNode(tree.Child); - var expected = new Visual[] { back, front }; - var actual = panelNode.Children.OfType().Select(x => x.Visual).ToArray(); - Assert.Equal(expected, actual); - } - } - - [Fact] - public void Should_Respect_Uniform_ZIndex() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Panel panel; - - var tree = new TestRoot - { - Child = panel = new Panel() - }; - - for (var i = 0; i < 128; i++) - { - panel.Children.Add(new Border()); - } - - var result = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(result); - - var panelNode = result.FindNode(tree.Child); - var expected = panel.Children.ToArray(); - var actual = panelNode.Children.OfType().Select(x => x.Visual).ToArray(); - Assert.Equal(expected, actual); - } - } - - [Fact] - public void ClipBounds_Should_Be_In_Global_Coordinates() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Border target; - var tree = new TestRoot - { - Child = new Decorator - { - Margin = new Thickness(24, 26), - Child = target = new Border - { - ClipToBounds = true, - Margin = new Thickness(26, 24), - Width = 100, - Height = 100, - } - } - }; - - tree.Measure(Size.Infinity); - tree.Arrange(new Rect(tree.DesiredSize)); - - var result = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(result); - - var targetNode = result.FindNode(target); - - Assert.Equal(new Rect(50, 50, 100, 100), targetNode.ClipBounds); - } - } - - [Fact] - public void Transform_For_Control_With_RenderTransform_Should_Be_Correct_After_Update() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Border border; - var tree = new TestRoot - { - Width = 400, - Height = 200, - Child = new Decorator - { - Width = 200, - Height = 100, - Child = border = new Border - { - Background = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Right, - Width = 100, - RenderTransform = new ScaleTransform(0.5, 1), - } - } - }; - - tree.Measure(Size.Infinity); - tree.Arrange(new Rect(tree.DesiredSize)); - - var scene = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene); - - var expectedTransform = Matrix.CreateScale(0.5, 1) * Matrix.CreateTranslation(225, 50); - var borderNode = scene.FindNode(border); - Assert.Equal(expectedTransform, borderNode.Transform); - - scene = scene.CloneScene(); - Assert.True(sceneBuilder.Update(scene, border)); - - borderNode = scene.FindNode(border); - Assert.Equal(expectedTransform, borderNode.Transform); - } - } - - [Fact] - public void MirrorTransform_For_Control_With_RenderTransform_Should_Be_Correct() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Border border; - var tree = new TestRoot - { - Width = 400, - Height = 200, - Child = border = new Border - { - HorizontalAlignment = HorizontalAlignment.Left, - Background = Brushes.Red, - Width = 100, - RenderTransform = new ScaleTransform(0.5, 1), - FlowDirection = FlowDirection.RightToLeft - } - }; - - tree.Measure(Size.Infinity); - tree.Arrange(new Rect(tree.DesiredSize)); - - var scene = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene); - - var expectedTransform = new Matrix(-1, 0, 0, 1, 100, 0) * Matrix.CreateScale(0.5, 1) * Matrix.CreateTranslation(25, 0); - var borderNode = scene.FindNode(border); - Assert.Equal(expectedTransform, borderNode.Transform); - } - } - - [Fact] - public void Should_Update_Border_Background_Node() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Border border; - TextBlock textBlock; - var tree = new TestRoot - { - Child = border = new Border - { - Width = 100, - Height = 100, - Background = Brushes.Red, - Child = textBlock = new TextBlock - { - Foreground = Brushes.Green, - Text = "Hello World", - } - } - }; - - tree.Measure(Size.Infinity); - tree.Arrange(new Rect(tree.DesiredSize)); - - var initial = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(initial); - - var initialBackgroundNode = initial.FindNode(border).Children[0]; - var initialTextNode = initial.FindNode(textBlock).DrawOperations[0]; - - Assert.NotNull(initialBackgroundNode); - Assert.NotNull(initialTextNode); - - border.Background = Brushes.Green; - - var result = initial.CloneScene(); - sceneBuilder.Update(result, border); - - var borderNode = (VisualNode)result.Root.Children[0]; - Assert.Same(border, borderNode.Visual); - - var backgroundNode = (RectangleNode)borderNode.DrawOperations[0].Item; - Assert.NotSame(initialBackgroundNode, backgroundNode); - Assert.Equal(Brushes.Green, backgroundNode.Brush); - - var textBlockNode = (VisualNode)borderNode.Children[0]; - Assert.Same(textBlock, textBlockNode.Visual); - - var textNode = (GlyphRunNode)textBlockNode.DrawOperations[0].Item; - Assert.Same(initialTextNode.Item, textNode); - } - } - - [Fact] - public void Should_Update_When_Control_Added() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Border border; - var tree = new TestRoot - { - Width = 100, - Height = 100, - Child = border = new Border - { - Background = Brushes.Red, - } - }; - - Canvas canvas; - var decorator = new Decorator - { - Child = canvas = new Canvas(), - }; - - tree.Measure(Size.Infinity); - tree.Arrange(new Rect(tree.DesiredSize)); - - var initial = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(initial); - - border.Child = decorator; - var result = initial.CloneScene(); - - Assert.True(sceneBuilder.Update(result, decorator)); - - // Updating canvas should result in no-op as it should have been updated along - // with decorator as part of the add opeation. - Assert.False(sceneBuilder.Update(result, canvas)); - - var borderNode = (VisualNode)result.Root.Children[0]; - Assert.Equal(1, borderNode.Children.Count); - Assert.Equal(1, borderNode.DrawOperations.Count); - - var decoratorNode = (VisualNode)borderNode.Children[0]; - Assert.Same(decorator, decoratorNode.Visual); - Assert.Same(decoratorNode, result.FindNode(decorator)); - - var canvasNode = (VisualNode)decoratorNode.Children[0]; - Assert.Same(canvas, canvasNode.Visual); - Assert.Same(canvasNode, result.FindNode(canvas)); - } - } - - [Fact] - public void Should_Update_When_Control_Removed() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Border border; - Decorator decorator; - Canvas canvas; - var tree = new TestRoot - { - Width = 100, - Height = 100, - Child = border = new Border - { - Background = Brushes.Red, - Child = decorator = new Decorator - { - Child = canvas = new Canvas - { - Background = Brushes.AliceBlue, - } - } - } - }; - - tree.Measure(Size.Infinity); - tree.Arrange(new Rect(tree.DesiredSize)); - - var initial = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(initial); - - border.Child = null; - var result = initial.CloneScene(); - - Assert.True(sceneBuilder.Update(result, decorator)); - Assert.False(sceneBuilder.Update(result, canvas)); - - var borderNode = (VisualNode)result.Root.Children[0]; - Assert.Equal(0, borderNode.Children.Count); - Assert.Equal(1, borderNode.DrawOperations.Count); - - Assert.Null(result.FindNode(decorator)); - Assert.Equal(new Rect(0, 0, 100, 100), result.Layers.Single().Dirty.Single()); - } - } - - [Fact] - public void Should_Update_When_Control_Moved() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Decorator moveFrom; - Decorator moveTo; - Canvas moveMe; - var tree = new TestRoot - { - Width = 100, - Height = 100, - Child = new StackPanel - { - Children = - { - (moveFrom = new Decorator - { - Child = moveMe = new Canvas(), - }), - (moveTo = new Decorator()), - } - } - }; - - tree.Measure(Size.Infinity); - tree.Arrange(new Rect(tree.DesiredSize)); - - var scene = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene); - - var moveFromNode = (VisualNode)scene.FindNode(moveFrom); - var moveToNode = (VisualNode)scene.FindNode(moveTo); - - Assert.Equal(1, moveFromNode.Children.Count); - Assert.Same(moveMe, moveFromNode.Children[0].Visual); - Assert.Empty(moveToNode.Children); - - moveFrom.Child = null; - moveTo.Child = moveMe; - - scene = scene.CloneScene(); - moveFromNode = (VisualNode)scene.FindNode(moveFrom); - moveToNode = (VisualNode)scene.FindNode(moveTo); - - moveFromNode.SortChildren(scene); - moveToNode.SortChildren(scene); - sceneBuilder.Update(scene, moveFrom); - sceneBuilder.Update(scene, moveTo); - sceneBuilder.Update(scene, moveMe); - - Assert.Empty(moveFromNode.Children); - Assert.Equal(1, moveToNode.Children.Count); - Assert.Same(moveMe, moveToNode.Children[0].Visual); - } - } - - [Fact] - public void Should_Update_When_Control_Moved_Causing_Layout_Change() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Decorator moveFrom; - Decorator moveTo; - Canvas moveMe; - var tree = new TestRoot - { - Width = 100, - Height = 100, - Child = new DockPanel - { - Children = - { - (moveFrom = new Decorator - { - Child = moveMe = new Canvas - { - Width = 100, - Height = 100, - }, - }), - (moveTo = new Decorator()), - } - } - }; - - tree.Measure(Size.Infinity); - tree.Arrange(new Rect(tree.DesiredSize)); - - var scene = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene); - - var moveFromNode = (VisualNode)scene.FindNode(moveFrom); - var moveToNode = (VisualNode)scene.FindNode(moveTo); - - Assert.Equal(1, moveFromNode.Children.Count); - Assert.Same(moveMe, moveFromNode.Children[0].Visual); - Assert.Empty(moveToNode.Children); - - moveFrom.Child = null; - moveTo.Child = moveMe; - tree.LayoutManager.ExecuteLayoutPass(); - - scene = scene.CloneScene(); - moveFromNode = (VisualNode)scene.FindNode(moveFrom); - moveToNode = (VisualNode)scene.FindNode(moveTo); - - moveFromNode.SortChildren(scene); - moveToNode.SortChildren(scene); - sceneBuilder.Update(scene, moveFrom); - sceneBuilder.Update(scene, moveTo); - sceneBuilder.Update(scene, moveMe); - - Assert.Empty(moveFromNode.Children); - Assert.Equal(1, moveToNode.Children.Count); - Assert.Same(moveMe, moveToNode.Children[0].Visual); - } - } - - [Fact] - public void Should_Update_When_Control_Made_Invisible() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Decorator decorator; - Border border; - Canvas canvas; - var tree = new TestRoot - { - Width = 100, - Height = 100, - Child = decorator = new Decorator - { - Child = border = new Border - { - Background = Brushes.Red, - Child = canvas = new Canvas(), - } - } - }; - - tree.Measure(Size.Infinity); - tree.Arrange(new Rect(tree.DesiredSize)); - - var initial = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(initial); - - border.IsVisible = false; - var result = initial.CloneScene(); - - Assert.True(sceneBuilder.Update(result, border)); - Assert.False(sceneBuilder.Update(result, canvas)); - - var decoratorNode = (VisualNode)result.Root.Children[0]; - Assert.Equal(0, decoratorNode.Children.Count); - - Assert.Null(result.FindNode(border)); - Assert.Null(result.FindNode(canvas)); - Assert.Equal(new Rect(0, 0, 100, 100), result.Layers.Single().Dirty.Single()); - } - } - - [Fact] - public void Should_Not_Dispose_Active_VisualNode_When_Control_Reparented_And_Child_Made_Invisible() - { - // Issue #3115 - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - StackPanel panel; - Border border1; - Border border2; - var tree = new TestRoot - { - Width = 100, - Height = 100, - Child = panel = new StackPanel - { - Children = - { - (border1 = new Border - { - Background = Brushes.Red, - }), - (border2 = new Border - { - Background = Brushes.Green, - }), - } - } - }; - - tree.Measure(Size.Infinity); - tree.Arrange(new Rect(tree.DesiredSize)); - - var scene = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene); - - var decorator = new Decorator(); - tree.Child = null; - decorator.Child = panel; - tree.Child = decorator; - border1.IsVisible = false; - - scene = scene.CloneScene(); - sceneBuilder.Update(scene, decorator); - - var panelNode = (VisualNode)scene.FindNode(panel); - Assert.Equal(2, panelNode.Children.Count); - Assert.False(panelNode.Children[0].Disposed); - Assert.False(panelNode.Children[1].Disposed); - } - } - - [Fact] - public void Should_Update_ClipBounds_For_Negative_Margin() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Decorator decorator; - Border border; - var tree = new TestRoot - { - Width = 100, - Height = 100, - Child = decorator = new Decorator - { - Margin = new Thickness(0, 10, 0, 0), - Child = border = new Border - { - Background = Brushes.Red, - ClipToBounds = true, - Margin = new Thickness(0, -5, 0, 0), - } - } - }; - - var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(); - - var scene = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene); - - var borderNode = scene.FindNode(border); - Assert.Equal(new Rect(0, 5, 100, 95), borderNode.ClipBounds); - - border.Margin = new Thickness(0, -8, 0, 0); - layout.ExecuteLayoutPass(); - - scene = scene.CloneScene(); - sceneBuilder.Update(scene, border); - - borderNode = scene.FindNode(border); - Assert.Equal(new Rect(0, 2, 100, 98), borderNode.ClipBounds); - } - } - - [Fact] - public void Should_Update_Descendent_Tranform_When_Margin_Changed() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Decorator decorator; - Border border; - Canvas canvas; - var tree = new TestRoot - { - Width = 100, - Height = 100, - Child = decorator = new Decorator - { - Margin = new Thickness(0, 10, 0, 0), - Child = border = new Border - { - Child = canvas = new Canvas(), - } - } - }; - - var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(); - - var scene = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene); - - var borderNode = scene.FindNode(border); - var canvasNode = scene.FindNode(canvas); - Assert.Equal(Matrix.CreateTranslation(0, 10), borderNode.Transform); - Assert.Equal(Matrix.CreateTranslation(0, 10), canvasNode.Transform); - - decorator.Margin = new Thickness(0, 20, 0, 0); - layout.ExecuteLayoutPass(); - - scene = scene.CloneScene(); - sceneBuilder.Update(scene, decorator); - - borderNode = scene.FindNode(border); - canvasNode = scene.FindNode(canvas); - Assert.Equal(Matrix.CreateTranslation(0, 20), borderNode.Transform); - Assert.Equal(Matrix.CreateTranslation(0, 20), canvasNode.Transform); - } - } - - [Fact] - public void DirtyRects_Should_Contain_Old_And_New_Bounds_When_Margin_Changed() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Decorator decorator; - Border border; - Canvas canvas; - var tree = new TestRoot - { - Width = 100, - Height = 100, - Child = decorator = new Decorator - { - Margin = new Thickness(0, 10, 0, 0), - Child = border = new Border - { - Background = Brushes.Red, - Child = canvas = new Canvas(), - } - } - }; - - var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(); - - var scene = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene); - - var borderNode = scene.FindNode(border); - var canvasNode = scene.FindNode(canvas); - Assert.Equal(Matrix.CreateTranslation(0, 10), borderNode.Transform); - Assert.Equal(Matrix.CreateTranslation(0, 10), canvasNode.Transform); - - decorator.Margin = new Thickness(0, 20, 0, 0); - layout.ExecuteLayoutPass(); - - scene = scene.CloneScene(); - - sceneBuilder.Update(scene, decorator); - - var rects = scene.Layers.Single().Dirty.ToArray(); - Assert.Equal(new[] { new Rect(0, 10, 100, 90) }, rects); - } - } - - [Fact] - public void Resizing_Scene_Should_Add_DirtyRects() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Decorator decorator; - Border border; - Canvas canvas; - var tree = new TestRoot - { - ClientSize = new Size(100, 100), - Child = decorator = new Decorator - { - Margin = new Thickness(0, 10, 0, 0), - Child = border = new Border - { - Background = Brushes.Red, - Child = canvas = new Canvas(), - } - } - }; - - var animation = new BehaviorSubject(0.5); - border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); - - var scene = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene); - - Assert.Equal(new Size(100, 100), scene.Size); - - tree.ClientSize = new Size(110, 120); - scene = scene.CloneScene(); - sceneBuilder.Update(scene, tree); - - Assert.Equal(new Size(110, 120), scene.Size); - - var expected = new[] - { - new Rect(100, 0, 10, 100), - new Rect(0, 100, 110, 20), - }; - - Assert.Equal(expected, scene.Layers[tree].Dirty.ToArray()); - - // Layers are disabled. See #2244 - // Assert.Equal(expected, scene.Layers[border].Dirty.ToArray()); - } - } - - [Fact] - public void Setting_Opacity_Should_Add_Descendent_Bounds_To_DirtyRects() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Decorator decorator; - Border border; - var tree = new TestRoot - { - Child = decorator = new Decorator - { - Child = border = new Border - { - Background = Brushes.Red, - Width = 100, - Height = 100, - } - } - }; - - tree.Measure(Size.Infinity); - tree.Arrange(new Rect(tree.DesiredSize)); - - var scene = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene); - - decorator.Opacity = 0.5; - scene = scene.CloneScene(); - sceneBuilder.Update(scene, decorator); - - Assert.NotEmpty(scene.Layers.Single().Dirty); - var dirty = scene.Layers.Single().Dirty.Single(); - Assert.Equal(new Rect(0, 0, 100, 100), dirty); - } - } - - [Fact] - public void Should_Set_GeometryClip() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var clip = StreamGeometry.Parse("M100,0 L0,100 100,100"); - Decorator decorator; - var tree = new TestRoot - { - Child = decorator = new Decorator - { - Clip = clip, - } - }; - - var scene = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene); - - var decoratorNode = scene.FindNode(decorator); - Assert.Same(clip.PlatformImpl, decoratorNode.GeometryClip); - } - } - - [Fact] - public void Disposing_Scene_Releases_DrawOperation_References() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var bitmap = RefCountable.Create(Mock.Of( - x => x.PixelSize == new PixelSize(100, 100) && - x.Dpi == new Vector(96, 96))); - - Image img; - var tree = new TestRoot - { - Child = img = new Image - { - Source = new Bitmap(bitmap), - Height = 100, - Width = 100 - } - }; - - tree.Measure(Size.Infinity); - tree.Arrange(new Rect(new Size(100, 100))); - - Assert.Equal(2, bitmap.RefCount); - IRef operation; - - using (var scene = new Scene(tree)) - { - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene); - operation = scene.FindNode(img).DrawOperations[0]; - Assert.Equal(1, operation.RefCount); - - Assert.Equal(3, bitmap.RefCount); - } - Assert.Equal(0, operation.RefCount); - Assert.Equal(2, bitmap.RefCount); - } - } - - [Fact] - public void Replacing_Control_Releases_DrawOperation_Reference() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var bitmap = RefCountable.Create(Mock.Of( - x => x.PixelSize == new PixelSize(100, 100) && - x.Dpi == new Vector(96, 96))); - - Image img; - var tree = new TestRoot - { - Child = img = new Image - { - Source = new Bitmap(bitmap), - Width = 100, - Height = 100 - } - }; - - tree.Measure(Size.Infinity); - tree.Arrange(new Rect(new Size(100, 100))); - - var scene = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene); - - var operation = scene.FindNode(img).DrawOperations[0]; - - tree.Child = new Decorator(); - - using (var result = scene.CloneScene()) - { - sceneBuilder.Update(result, img); - scene.Dispose(); - - Assert.Equal(0, operation.RefCount); - Assert.Equal(2, bitmap.RefCount); - } - } - } - } -} diff --git a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs deleted file mode 100644 index 48ddef8bf4..0000000000 --- a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs +++ /dev/null @@ -1,258 +0,0 @@ -using System.Linq; -using System.Reactive.Subjects; -using Avalonia.Controls; -using Avalonia.Data; -using Avalonia.Media; -using Avalonia.Rendering.SceneGraph; -using Avalonia.UnitTests; -using Avalonia.VisualTree; -using Xunit; - -namespace Avalonia.Base.UnitTests.Rendering.SceneGraph -{ - public partial class SceneBuilderTests - { - [Fact(Skip = "Layers are disabled. See #2244")] - public void Control_With_Animated_Opacity_And_Children_Should_Start_New_Layer() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Decorator decorator; - Border border; - Canvas canvas; - var tree = new TestRoot - { - Padding = new Thickness(10), - Width = 100, - Height = 120, - Child = decorator = new Decorator - { - Padding = new Thickness(11), - Child = border = new Border - { - Background = Brushes.Red, - Padding = new Thickness(12), - Child = canvas = new Canvas() - } - } - }; - - var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(); - - var animation = new BehaviorSubject(0.5); - border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); - - var scene = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene); - - var rootNode = (VisualNode)scene.Root; - var borderNode = (VisualNode)scene.FindNode(border); - var canvasNode = (VisualNode)scene.FindNode(canvas); - - Assert.Same(tree, rootNode.LayerRoot); - Assert.Same(border, borderNode.LayerRoot); - Assert.Same(border, canvasNode.LayerRoot); - Assert.Equal(0.5, scene.Layers[border].Opacity); - - Assert.Equal(2, scene.Layers.Count()); - Assert.Empty(scene.Layers.Select(x => x.LayerRoot).Except(new Visual[] { tree, border })); - - animation.OnCompleted(); - scene = scene.CloneScene(); - - sceneBuilder.Update(scene, border); - - rootNode = (VisualNode)scene.Root; - borderNode = (VisualNode)scene.FindNode(border); - canvasNode = (VisualNode)scene.FindNode(canvas); - - Assert.Same(tree, rootNode.LayerRoot); - Assert.Same(tree, borderNode.LayerRoot); - Assert.Same(tree, canvasNode.LayerRoot); - Assert.Single(scene.Layers); - - var rootDirty = scene.Layers[tree].Dirty; - - Assert.Single(rootDirty); - Assert.Equal(new Rect(21, 21, 58, 78), rootDirty.Single()); - } - } - - [Fact] - public void Control_With_Animated_Opacity_And_No_Children_Should_Not_Start_New_Layer() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Decorator decorator; - Border border; - var tree = new TestRoot - { - Padding = new Thickness(10), - Width = 100, - Height = 120, - Child = decorator = new Decorator - { - Padding = new Thickness(11), - Child = border = new Border - { - Background = Brushes.Red, - } - } - }; - - var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(); - - var animation = new BehaviorSubject(0.5); - border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); - - var scene = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene); - - Assert.Single(scene.Layers); - } - } - - [Fact(Skip = "Layers are disabled. See #2244")] - public void Removing_Control_With_Animated_Opacity_Should_Remove_Layers() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Decorator decorator; - Border border; - Canvas canvas; - var tree = new TestRoot - { - Padding = new Thickness(10), - Width = 100, - Height = 120, - Child = decorator = new Decorator - { - Padding = new Thickness(11), - Child = border = new Border - { - Background = Brushes.Red, - Padding = new Thickness(12), - Child = canvas = new Canvas - { - Children = { new TextBlock() }, - } - } - } - }; - - var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(); - - var animation = new BehaviorSubject(0.5); - border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); - canvas.Bind(Canvas.OpacityProperty, animation, BindingPriority.Animation); - - var scene = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene); - - Assert.Equal(3, scene.Layers.Count); - - decorator.Child = null; - scene = scene.CloneScene(); - - sceneBuilder.Update(scene, border); - - Assert.Equal(1, scene.Layers.Count); - } - } - - [Fact(Skip = "Layers are disabled. See #2244")] - public void Hiding_Transparent_Control_Should_Remove_Layers() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - Decorator decorator; - Border border; - Canvas canvas; - var tree = new TestRoot - { - Padding = new Thickness(10), - Width = 100, - Height = 120, - Child = decorator = new Decorator - { - Padding = new Thickness(11), - Child = border = new Border - { - Background = Brushes.Red, - Padding = new Thickness(12), - Child = canvas = new Canvas - { - Children = { new TextBlock() }, - } - } - } - }; - - var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(); - - var animation = new BehaviorSubject(0.5); - border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); - canvas.Bind(Canvas.OpacityProperty, animation, BindingPriority.Animation); - - var scene = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene); - - Assert.Equal(3, scene.Layers.Count); - - border.IsVisible = false; - scene = scene.CloneScene(); - - sceneBuilder.Update(scene, border); - - Assert.Equal(1, scene.Layers.Count); - } - } - - [Fact(Skip = "Layers are disabled. See #2244")] - public void GeometryClip_Should_Affect_Child_Layers() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var clip = StreamGeometry.Parse("M100,0 L0,100 100,100"); - Decorator decorator; - Border border; - var tree = new TestRoot - { - Child = decorator = new Decorator - { - Clip = clip, - Margin = new Thickness(12, 16), - Child = border = new Border - { - Opacity = 0.5, - Child = new Canvas(), - } - } - }; - - var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(); - - var animation = new BehaviorSubject(0.5); - border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); - - var scene = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene); - - var borderLayer = scene.Layers[border]; - Assert.Equal( - Matrix.CreateTranslation(12, 16), - ((MockStreamGeometryImpl)borderLayer.GeometryClip).Transform); - } - } - } -} diff --git a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneLayersTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneLayersTests.cs deleted file mode 100644 index 7e515e7ef9..0000000000 --- a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneLayersTests.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Linq; -using Avalonia.Controls; -using Avalonia.Rendering.SceneGraph; -using Avalonia.UnitTests; -using Avalonia.VisualTree; -using Xunit; - -namespace Avalonia.Base.UnitTests.Rendering.SceneGraph -{ - public class SceneLayersTests - { - [Fact] - public void Layers_Should_Be_Ordered() - { - Border border; - Decorator decorator; - var root = new TestRoot - { - Child = border = new Border - { - Child = decorator = new Decorator(), - } - }; - - var target = new SceneLayers(root); - target.Add(root); - target.Add(decorator); - target.Add(border); - - var result = target.Select(x => x.LayerRoot).ToArray(); - - Assert.Equal(new Visual[] { root, border, decorator }, result); - } - } -} diff --git a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneTests.cs deleted file mode 100644 index 18ff31f676..0000000000 --- a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/SceneTests.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Linq; -using Avalonia.Controls; -using Avalonia.Rendering.SceneGraph; -using Avalonia.UnitTests; -using Xunit; - -namespace Avalonia.Base.UnitTests.Rendering.SceneGraph -{ - public class SceneTests - { - [Fact] - public void Cloning_Scene_Should_Retain_Layers_But_Not_DirtyRects() - { - Decorator decorator; - var tree = new TestRoot - { - Child = decorator = new Decorator(), - }; - - var scene = new Scene(tree); - scene.Layers.Add(tree); - scene.Layers.Add(decorator); - - scene.Layers[tree].Dirty.Add(new Rect(0, 0, 100, 100)); - scene.Layers[decorator].Dirty.Add(new Rect(0, 0, 50, 100)); - - scene = scene.CloneScene(); - Assert.Equal(2, scene.Layers.Count()); - Assert.Empty(scene.Layers[0].Dirty); - Assert.Empty(scene.Layers[1].Dirty); - } - } -} diff --git a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs deleted file mode 100644 index b6920dc381..0000000000 --- a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs +++ /dev/null @@ -1,123 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Rendering.SceneGraph; -using Avalonia.Utilities; -using Moq; -using Xunit; - -namespace Avalonia.Base.UnitTests.Rendering.SceneGraph -{ - public class VisualNodeTests - { - [Fact] - public void Empty_Children_Collections_Should_Be_Shared() - { - var node1 = new VisualNode(new Control(), null); - var node2 = new VisualNode(new Control(), null); - - Assert.Same(node1.Children, node2.Children); - } - - [Fact] - public void Adding_Child_Should_Create_Collection() - { - var node = new VisualNode(new Control(), null); - var collection = node.Children; - - node.AddChild(new VisualNode(new Border(), node)); - - Assert.NotSame(collection, node.Children); - } - - [Fact] - public void Empty_DrawOperations_Collections_Should_Be_Shared() - { - var node1 = new VisualNode(new Control(), null); - var node2 = new VisualNode(new Control(), null); - - Assert.Same(node1.DrawOperations, node2.DrawOperations); - } - - [Fact] - public void Adding_DrawOperation_Should_Create_Collection() - { - var node = new VisualNode(new Control(), null); - var collection = node.DrawOperations; - - node.AddDrawOperation(RefCountable.Create(Mock.Of())); - - Assert.NotSame(collection, node.DrawOperations); - } - - [Fact] - public void Cloned_Nodes_Should_Share_DrawOperations_Collection() - { - var node1 = new VisualNode(new Control(), null); - node1.AddDrawOperation(RefCountable.Create(Mock.Of())); - - var node2 = node1.Clone(null); - - Assert.Same(node1.DrawOperations, node2.DrawOperations); - } - - [Fact] - public void Adding_DrawOperation_To_Cloned_Node_Should_Create_New_Collection() - { - var node1 = new VisualNode(new Control(), null); - var operation1 = RefCountable.Create(Mock.Of()); - node1.AddDrawOperation(operation1); - - var node2 = node1.Clone(null); - var operation2 = RefCountable.Create(Mock.Of()); - node2.ReplaceDrawOperation(0, operation2); - - Assert.NotSame(node1.DrawOperations, node2.DrawOperations); - Assert.Equal(1, node1.DrawOperations.Count); - Assert.Equal(1, node2.DrawOperations.Count); - Assert.Same(operation1.Item, node1.DrawOperations[0].Item); - Assert.Same(operation2.Item, node2.DrawOperations[0].Item); - } - - [Fact] - public void DrawOperations_In_Cloned_Node_Are_Cloned() - { - var node1 = new VisualNode(new Control(), null); - var operation1 = RefCountable.Create(Mock.Of()); - node1.AddDrawOperation(operation1); - - var node2 = node1.Clone(null); - var operation2 = RefCountable.Create(Mock.Of()); - node2.AddDrawOperation(operation2); - - Assert.Same(node1.DrawOperations[0].Item, node2.DrawOperations[0].Item); - Assert.NotSame(node1.DrawOperations[0], node2.DrawOperations[0]); - } - - [Fact] - public void SortChildren_Does_Not_Throw_On_Null_Children() - { - var node = new VisualNode(new Control(), null); - var scene = new Scene(new Control()); - - node.SortChildren(scene); - } - - [Fact] - public void TrimChildren_Should_Work_Correctly() - { - var parent = new VisualNode(new Control(), null); - var child1 = new VisualNode(new Control(), parent); - var child2 = new VisualNode(new Control(), parent); - var child3 = new VisualNode(new Control(), parent); - - parent.AddChild(child1); - parent.AddChild(child2); - parent.AddChild(child3); - parent.TrimChildren(2); - - Assert.Equal(2, parent.Children.Count); - Assert.False(child1.Disposed); - Assert.False(child2.Disposed); - Assert.True(child3.Disposed); - } - } -} diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/TransformedBoundsTests.cs b/tests/Avalonia.Base.UnitTests/VisualTree/TransformedBoundsTests.cs deleted file mode 100644 index d8158fd673..0000000000 --- a/tests/Avalonia.Base.UnitTests/VisualTree/TransformedBoundsTests.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Collections.Generic; -using Avalonia.Controls; -using Avalonia.Controls.Shapes; -using Avalonia.Media; -using Avalonia.Platform; -using Avalonia.Rendering; -using Avalonia.UnitTests; -using Avalonia.VisualTree; -using Moq; -using Xunit; - -namespace Avalonia.Base.UnitTests.VisualTree -{ - public class TransformedBoundsTests - { - [Fact] - public void Should_Track_Bounds() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - var control = default(Rectangle); - var tree = new Decorator - { - Padding = new Thickness(10), - Child = new Decorator - { - Padding = new Thickness(5), - Child = control = new Rectangle - { - Width = 15, - Height = 15, - }, - } - }; - - var context = new DrawingContext(Mock.Of()); - - tree.Measure(Size.Infinity); - tree.Arrange(new Rect(0, 0, 100, 100)); - ImmediateRenderer.Render(tree, context, true); - - var track = control.GetObservable(Visual.TransformedBoundsProperty); - var results = new List(); - track.Subscribe(results.Add); - - Assert.Equal(new Rect(0, 0, 15, 15), results[0].Value.Bounds); - Assert.Equal(Matrix.CreateTranslation(42, 42), results[0].Value.Transform); - } - } - } -} diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs b/tests/Avalonia.Base.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs index 0c516a0481..dc2286bcc2 100644 --- a/tests/Avalonia.Base.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs +++ b/tests/Avalonia.Base.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs @@ -15,16 +15,14 @@ namespace Avalonia.Base.UnitTests.VisualTree [Fact] public void Should_Find_Control() { - using (TestApplication()) + Border target; + var services = new CompositorTestServices(new Size(200, 200)) { - Border target; - var root = new TestRoot + TopLevel = { - Width = 200, - Height = 200, - Child = new StackPanel + Content = new StackPanel { - Background = Brushes.White, + Background = null, Children = { (target = new Border @@ -42,29 +40,25 @@ namespace Avalonia.Base.UnitTests.VisualTree }, Orientation = Orientation.Horizontal, } - }; + } + }; - root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); + services.RunJobs(); + var result = target.GetVisualsAt(new Point(50, 50)); - var result = target.GetVisualsAt(new Point(50, 50)); + Assert.Same(target, result.Single()); - Assert.Same(target, result.Single()); - } } [Fact] public void Should_Not_Find_Sibling_Control() { - using (TestApplication()) + Border target; + var services = new CompositorTestServices(new Size(200, 200)) { - Border target; - var root = new TestRoot + TopLevel = { - Width = 200, - Height = 200, - Child = new StackPanel + Content = new StackPanel { Background = Brushes.White, Children = @@ -84,21 +78,12 @@ namespace Avalonia.Base.UnitTests.VisualTree }, Orientation = Orientation.Horizontal, } - }; - - root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - - var result = target.GetVisualsAt(new Point(150, 50)); + } + }; + services.RunJobs(); + var result = target.GetVisualsAt(new Point(150, 50)); - Assert.Empty(result); - } - } - - private static IDisposable TestApplication() - { - return UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + Assert.Empty(result); } } } diff --git a/tests/Avalonia.RenderTests/Controls/AdornerTests.cs b/tests/Avalonia.RenderTests/Controls/AdornerTests.cs index c833017212..c0159aecff 100644 --- a/tests/Avalonia.RenderTests/Controls/AdornerTests.cs +++ b/tests/Avalonia.RenderTests/Controls/AdornerTests.cs @@ -68,6 +68,6 @@ public class AdornerTests : TestBase tree.Arrange(new Rect(size)); await RenderToFile(tree); - CompareImages(skipImmediate: true, skipDeferred: true); + CompareImages(skipImmediate: true); } } \ No newline at end of file diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index edde62f041..258b2243a1 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -89,7 +89,6 @@ namespace Avalonia.Direct2D1.RenderTests } var immediatePath = Path.Combine(OutputPath, testName + ".immediate.out.png"); - var deferredPath = Path.Combine(OutputPath, testName + ".deferred.out.png"); var compositedPath = Path.Combine(OutputPath, testName + ".composited.out.png"); var factory = AvaloniaLocator.Current.GetRequiredService(); var pixelSize = new PixelSize((int)target.Width, (int)target.Height); @@ -104,22 +103,6 @@ namespace Avalonia.Direct2D1.RenderTests bitmap.Save(immediatePath); } - - using (var rtb = factory.CreateRenderTargetBitmap(pixelSize, dpiVector)) - using (var renderer = new DeferredRenderer(target, rtb)) - { - target.Measure(size); - target.Arrange(new Rect(size)); - renderer.UnitTestUpdateScene(); - - // Do the deferred render on a background thread to expose any threading errors in - // the deferred rendering path. - await Task.Run((Action)renderer.UnitTestRender); - threadingInterface.MainThread = Thread.CurrentThread; - - rtb.Save(deferredPath); - } - var timer = new ManualRenderTimer(); var compositor = new Compositor(new RenderLoop(timer, Dispatcher.UIThread), null); @@ -157,20 +140,17 @@ namespace Avalonia.Direct2D1.RenderTests } protected void CompareImages([CallerMemberName] string testName = "", - bool skipImmediate = false, bool skipDeferred = false, bool skipCompositor = false) + bool skipImmediate = false, bool skipCompositor = false) { var expectedPath = Path.Combine(OutputPath, testName + ".expected.png"); var immediatePath = Path.Combine(OutputPath, testName + ".immediate.out.png"); - var deferredPath = Path.Combine(OutputPath, testName + ".deferred.out.png"); var compositedPath = Path.Combine(OutputPath, testName + ".composited.out.png"); using (var expected = Image.Load(expectedPath)) using (var immediate = Image.Load(immediatePath)) - using (var deferred = Image.Load(deferredPath)) using (var composited = Image.Load(compositedPath)) { var immediateError = CompareImages(immediate, expected); - var deferredError = CompareImages(deferred, expected); var compositedError = CompareImages(composited, expected); if (immediateError > 0.022 && !skipImmediate) @@ -178,11 +158,6 @@ namespace Avalonia.Direct2D1.RenderTests Assert.True(false, immediatePath + ": Error = " + immediateError); } - if (deferredError > 0.022 && !skipDeferred) - { - Assert.True(false, deferredPath + ": Error = " + deferredError); - } - if (compositedError > 0.022 && !skipCompositor) { Assert.True(false, compositedPath + ": Error = " + compositedError); diff --git a/tests/Avalonia.Skia.UnitTests/HitTesting.cs b/tests/Avalonia.Skia.UnitTests/HitTesting.cs index df267ee136..2c9e4f587f 100644 --- a/tests/Avalonia.Skia.UnitTests/HitTesting.cs +++ b/tests/Avalonia.Skia.UnitTests/HitTesting.cs @@ -1,6 +1,8 @@ -using Avalonia.Controls.Shapes; +using System; +using Avalonia.Controls.Shapes; using Avalonia.Layout; using Avalonia.Media; +using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.UnitTests; using Xunit; @@ -16,29 +18,24 @@ namespace Avalonia.Skia.UnitTests { SkiaPlatform.Initialize(); - var root = new TestRoot + using var services = new CompositorTestServices(new Size(100, 100), + AvaloniaLocator.Current.GetRequiredService()) { - Width = 100, - Height = 100, - Child = new Ellipse + TopLevel = { - Width = 100, - Height = 100, - Fill = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center + Content = new Ellipse + { + Width = 100, + Height = 100, + Fill = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + } } }; - root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - - var outsideResult = root.Renderer.HitTest(new Point(10, 10), root, null); - var insideResult = root.Renderer.HitTest(new Point(50, 50), root, null); - - Assert.Empty(outsideResult); - Assert.Equal(new[] { root.Child }, insideResult); + services.AssertHitTest(10, 10, null, Array.Empty()); + services.AssertHitTest(50, 50, null, services.TopLevel.Content); } } @@ -49,30 +46,26 @@ namespace Avalonia.Skia.UnitTests { SkiaPlatform.Initialize(); - var root = new TestRoot + using var services = new CompositorTestServices(new Size(100, 100), + AvaloniaLocator.Current.GetRequiredService()) { - Width = 100, - Height = 100, - Child = new Ellipse + TopLevel = { - Width = 100, - Height = 100, - Stroke = Brushes.Red, - StrokeThickness = 5, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center + Content = new Ellipse + { + Width = 100, + Height = 100, + Stroke = Brushes.Red, + StrokeThickness = 5, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + } } }; - - root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); - root.Measure(Size.Infinity); - root.Arrange(new Rect(root.DesiredSize)); - - var outsideResult = root.Renderer.HitTest(new Point(50, 50), root, null); - var insideResult = root.Renderer.HitTest(new Point(1, 50), root, null); - - Assert.Empty(outsideResult); - Assert.Equal(new[] { root.Child }, insideResult); + + + services.AssertHitTest(50, 50, null, Array.Empty()); + services.AssertHitTest(1, 50, null, services.TopLevel.Content); } } } diff --git a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj index 17448ade76..aec6647226 100644 --- a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj +++ b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj @@ -1,11 +1,9 @@  - netstandard2.0 + netstandard2.0 false Library false - ..\..\build\avalonia.snk - false true @@ -20,6 +18,7 @@ - + + diff --git a/tests/Avalonia.UnitTests/CompositorTestServices.cs b/tests/Avalonia.UnitTests/CompositorTestServices.cs new file mode 100644 index 0000000000..486177c61a --- /dev/null +++ b/tests/Avalonia.UnitTests/CompositorTestServices.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Embedding; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Templates; +using Avalonia.Data; +using Avalonia.Input; +using Avalonia.Input.Raw; +using Avalonia.Platform; +using Avalonia.Rendering; +using Avalonia.Rendering.Composition; +using Avalonia.Threading; +using Xunit; + +namespace Avalonia.UnitTests; +public class CompositorTestServices : IDisposable +{ + private readonly IDisposable _app; + public Compositor Compositor { get; } + public ManualRenderTimer Timer { get; } = new(); + public EmbeddableControlRoot TopLevel { get; } + public CompositingRenderer Renderer { get; } = null!; + public DebugEvents Events { get; } = new(); + + public void Dispose() + { + TopLevel.Renderer.Stop(); + TopLevel.Dispose(); + _app.Dispose(); + } + + public CompositorTestServices(Size? size = null, IPlatformRenderInterface renderInterface = null) + { + var services = TestServices.MockPlatformRenderInterface; + if (renderInterface != null) + services = services.With(renderInterface: renderInterface); + + _app = UnitTestApplication.Start(services); + try + { + AvaloniaLocator.CurrentMutable.Bind().ToConstant(Timer); + AvaloniaLocator.CurrentMutable.Bind() + .ToConstant(new RenderLoop(Timer, Dispatcher.UIThread)); + + Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), null); + var impl = new TopLevelImpl(Compositor, size ?? new Size(1000, 1000)); + TopLevel = new EmbeddableControlRoot(impl) + { + Template = new FuncControlTemplate((parent, scope) => + { + var presenter = new ContentPresenter + { + [~ContentPresenter.ContentProperty] = new TemplateBinding(ContentControl.ContentProperty) + }; + scope.Register("PART_ContentPresenter", presenter); + return presenter; + }) + }; + Renderer = impl.Renderer; + TopLevel.Prepare(); + TopLevel.Renderer.Start(); + RunJobs(); + Renderer.CompositionTarget.Server.DebugEvents = Events; + } + catch + { + _app.Dispose(); + throw; + } + } + + public void RunJobs() + { + Dispatcher.UIThread.RunJobs(); + Timer.TriggerTick(); + Dispatcher.UIThread.RunJobs(); + } + + public void AssertRects(params Rect[] rects) + { + RunJobs(); + var toAssert = rects.Select(x => x.ToString()).Distinct().OrderBy(x => x); + var invalidated = Events.Rects.Select(x => x.ToString()).Distinct().OrderBy(x => x); + Assert.Equal(toAssert, invalidated); + Events.Rects.Clear(); + } + + public void AssertHitTest(double x, double y, Func filter, params object[] expected) + => AssertHitTest(new Point(x, y), filter, expected); + + public void AssertHitTest(Point pt, Func filter, params object[] expected) + { + RunJobs(); + var tested = Renderer.HitTest(pt, TopLevel, filter); + Assert.Equal(expected, tested); + } + + public void AssertHitTestFirst(Point pt, Func filter, object expected) + { + RunJobs(); + var tested = Renderer.HitTest(pt, TopLevel, filter).First(); + Assert.Equal(expected, tested); + } + + public class DebugEvents : ICompositionTargetDebugEvents + { + public List Rects = new(); + + public void RectInvalidated(Rect rc) + { + Rects.Add(rc); + } + + public void Reset() + { + Rects.Clear(); + } + } + + public class ManualRenderTimer : IRenderTimer + { + public event Action Tick; + public bool RunsInBackground => false; + public void TriggerTick() => Tick?.Invoke(TimeSpan.Zero); + public Task TriggerBackgroundTick() => Task.Run(TriggerTick); + } + + class TopLevelImpl : ITopLevelImpl + { + private readonly Compositor _compositor; + public CompositingRenderer Renderer { get; private set; } + + public TopLevelImpl(Compositor compositor, Size clientSize) + { + ClientSize = clientSize; + _compositor = compositor; + } + + public void Dispose() + { + + } + + public Size ClientSize { get; } + public Size? FrameSize { get; } + public double RenderScaling => 1; + public IEnumerable Surfaces { get; } = new[] { new DummyFramebufferSurface() }; + public Action Input { get; set; } + public Action Paint { get; set; } + public Action Resized { get; set; } + public Action ScalingChanged { get; set; } + public Action TransparencyLevelChanged { get; set; } + + class DummyFramebufferSurface : IFramebufferPlatformSurface + { + public ILockedFramebuffer Lock() + { + var ptr = Marshal.AllocHGlobal(128); + return new LockedFramebuffer(ptr, new PixelSize(1, 1), 4, new Vector(96, 96), + PixelFormat.Rgba8888, () => Marshal.FreeHGlobal(ptr)); + } + } + + public IRenderer CreateRenderer(IRenderRoot root) + { + return Renderer = new CompositingRenderer(root, _compositor, () => Surfaces); + } + + public void Invalidate(Rect rect) + { + } + + public void SetInputRoot(IInputRoot inputRoot) + { + } + + public Point PointToClient(PixelPoint point) => default; + + public PixelPoint PointToScreen(Point point) => new(); + + public void SetCursor(ICursorImpl cursor) + { + } + + public Action Closed { get; set; } + public Action LostFocus { get; set; } + public IMouseDevice MouseDevice { get; } = new MouseDevice(); + public IPopupImpl CreatePopup() => throw new NotImplementedException(); + + public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) + { + } + + public WindowTransparencyLevel TransparencyLevel { get; } + + public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + { + } + + public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } + } +} \ No newline at end of file diff --git a/tests/TestFiles/Direct2D1/Media/StreamGeometry/PreciseEllipticArc_Produces_Valid_Arcs_In_All_Directions.deferred.expected.png b/tests/TestFiles/Direct2D1/Media/StreamGeometry/PreciseEllipticArc_Produces_Valid_Arcs_In_All_Directions.deferred.expected.png deleted file mode 100644 index 825b1d7ea7d2d1356f2798c162fed7572f8eb27b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6332 zcmc(jcQ9Q4`p4G_kw~JqsL@Ff1VPq{=)ET_Hi_ONdXyC+(V|53mgrHVE*2{hErRG} zu_9P3mQ|O7yLja&AftvV?0svrj&{BP98ep?I zciqQyIj3jmT$+u_^Kz7yX4nF-OSC^Wpd%Cia?IQx3xPoPpU^PNtA3`{MvYr1wcIg_ zt@3-^_|OQL`aHIhi^dlfQ)yhGS<@9R!&o6=c!gF)g^Kc)-HOu5gP~cciDUZSUjsQA zI863Z;Fh;WhFvHDeKH$_i6_0(f92re6h>aRnjWFt^e2BTFNEw5^>{;0h+x`*0D2WZ zqv`cdaNTF}%{I~aLe+XgOfUcNv38N^Yin*J$&7n1-UmNB@-KoODjcGN)3YY!no`B? zD!BM{eNz(S?aJp943ZbnZl!KWm1rD-I`1UoiwuI_5sN<8hure`a{qyR)m~aks>Mpl z>af@dZ{Eo}ftSm_am|LIw7@94CrySLG>o#TnZyCwVUv{7(gC!n13N zSQK3krhFn_{mG##o4A&X$*ERz@w;au91DLSbQ1r5kDIJ;Fb1E${u56u=d;w4jBW6| zr_T#>=C{=kugHcp2^8C6vbIuN{&rH`0`5Bz*w_V%2h^W<))7v&QZe@k)W|pm>ejN> z(ndtQwcEj^JeTOhR2=fa*4bm=a1!{%-jT0aK}oH?0>p-^bUVw`zp*{Z8z;3TmHzH$ zY^JxbTgyak^Pe9XwH51cZ3ksDJdFQwzlHEY-7`A9gD>X_9P)wwvFt0VL>@ z&{r`hgNBk9<__d#+<>~bY^5)Z!rG*3fS;3-D1O(DJ}csT>^xk9ISkdiX>bic3Hahz z(n(iY)o{8mBNq!2kJ|2=0y>$cV%$x5!A|=x?Kig+iAFrETR);E5>Uj`=R?S&0;=RV z@i6o9c(k+1RBiKwd)#Yf=V*!AI$Au7J{uIUY5L^0FZgo)Fw=%_IB!`vG18=)MOo)I zAsS5qECJy(LRoGlGf}))-XMxr?sLKDs6Mu7qHU5EBzRS8^8uFHU-C|@Kiz(?45vz} z5DBv!yB*3xm5m>xCe8aGLt)r@--OmDhogEPcT2Ul1R$CKj1o^ScsVm4>+P++oyMR_23R>lfd;qZ5l2I985euZ^aWw*v z*@<1{39C>eyOh`<9QI$)(tmi!r`?Zehir)G4TE?QYnRXtqZQZsiIxs%%BMng=jj50 zT5FBHp~r)R)uhjpV{SftUtcG*DfVhFK_Pjgx%QPl^d|by{fyHS>CP=dSVU3Fnn~^7Z8c;6;B;j7 z?^@Z#z+LXsSAX1m{}^(nWVTnY%W?E3 zd+yx6rk~R+XyZ;`yF;;?!i!D>VN`n#V(i~N7t5UU{hX?V-<|sD^oL{m=(6$RAS1{h z#PiidJLRFd=*!u~#mVGLj)<+qv;%ZH>+hB_v&FP3QHfd;#t!E^FyevNLT2F-F6Rk2 z4Y@C4?=k&Lv$4yED1}eE>1+WgG2^%sU$*M`D;eCQ@9r3vE#Qc0+o;xyU*{{rPsPVH ze$lF2?X~A9eLJoM^D9#fS+bIz3mJrFaUkOL6PZwHNk~Q=1n@p$@o64&^qHlINWJiJiF+qL~p$mzQ()=bbhTVDC(E6@B~^N@@4o9tEYz-9>a82)Ld|tns+v3JAw9E$I0^j# zXA?lb=sL1e)jBWBTX*F>qH6SVmOKf=ZL_NjSEsM#^tar- zRl(u8XT9i<>NAx^1)*Y}K>`QOMr-{Pd0gO5muxVG1vF>>r|#|H9B-b;mwN_kYHgZ! zHQ=~wEM`V&$la@<)5`>ePp?!O*l5KQn8SPwQidO50uh_{7OF;!YcA&@KD6m$C*CX0 zc9mK73@*;`W(*+bJ*(R$##SVHA4zckSiv3WLH2i^fR{zsI)kPZh4zBNyd;!H8~t$A z<*|9=w`cfGD5tT|%|Gd@4<;XWj~FdiX*N1ON3o6LXj|2O2EU$)L~z0Ngsr5O=q6o^CXTtd_L))g~sS)`Wib= z+SgM%my%EAn$4Z%Cw&tGKQ;@l(gYkn*d?OSrwrAey^B*5hgXE4A*)sVXlVaI&vm zyfI_Ps2%u6w{a$bH{c|bf#UZgs!3FW`F7&{V%XESl%;SFam5l`56_8Q8IX%zpM14SGR zUQG=FtCSYEf(_0-!~u`!Zt7=?9G*(jDV^l@YtAClX&z}9ZlJ`tr;c?OGfnVm^o$)e z&mO@^3KoB3x!0H;Pp5RLdu50jj8C&<>^Q7RYpNjhhrB%G&h;Oqv3*n(Z(w0&V~XVh z6efa}@JH-4iO4WRK!~5tiRnIX2GY44F~(En{oE33AAucg+WU;pcQ}8htUblx=GTc> z@Q1x%QAkEUwOhV!gv$xYy`H`i5)@}DXU&7i6zr`kV3@THW>?i{?<=CzEIuH(zQ?pU zZ&eliweR^ujYc363nl}#=Ne!7DZyK`Ybx%9asQ}rGSAPui%eolsiSV{r2Mc|fESu^ zrDw9(44f}Z@ur3!6=+^$I=IyIT#qV~MZO*=K5omtUGrX7CQ%zVlxJLP68lOZf3G_k zgr4tR7W)qf@V^U>q~TG9qL7(Sryj;;gMBpnL+@Ykz%nEj;h*hBCO7o5X1U0N*9UM%1-*=)CH^0I zWxIrAIgesUY8>RI2&WsZA;`iYSP9G%pSN1Ez$Oywo8N98I^Zs|6w?#B+~mbWB@1(q zvU+yXU&i&l(r+oo|X$$S*FM;HOw=-FKr_mQ2d7|DgBMsGm+NcJ)%TV?T8* zZb}m-mS`ypvr$c%Ax>vR4R8yok361XQtp3yw*+#ytLGmv2ZUh`*_Ylaa@%>*zWz#x z)gq1-W8<&PAND|dW5D2FzTwWoV$`4POZYD2I;N|^wcWZJO@b+5II>j_(D8!oe!OSvPaR8JEp5?JH89JcMNI4lwF+REiP~@X zL(K3m54CI(PHPwZA+vv*ZwY*GR`2>oC+J5#gBepu71%`8#&mmG29D|;-rS8y;a5NBB2yJ@B5DN}3;PNXbZFR4R6c@Cp=Av%D$Z<` zDdKlWKhoFvipzq%Xn;|sbzO%1p+)BFRt$~6%6&rT3N-l_|?{suT8P#FQlWE|T285>OY+ka}d74A;sZH>9)H zJ(Gf_wbTAXWQU8=1EWoScbJ9LO%ed#MIpw}{DQJyK91?iANffkVx&e|D{bR;SL@O9 zy2!S9?hho4?3)gh=-+Oy{<$!($Y0$Px`sOaz*ale9wVXEti$n7f|kzac(Tqy5Vc%ZO#k+}Z6rv6O=MI^wX`VfO_ z4Tt`eaXdv_TWv;F(f|q6c_cm_+lYw>Ik@h01Z8iZ&9;@o&0?2QjpQy{5)<11m=Ba?2O*`L{Z8m>_ZF`g-gY?5y2)KCPE~K4k>xG*qi* zwkM~p?C1N@d6jtiM~6acJMNs#Qvgw$7Gj^iofodk6MkDKq62_ zAHczh9~nE?m@aDw565oWQ%b83(zC1Z2+P9qTM=%s+uML{)7Yg_r0Q1IP0kv12MhDpjlAN0` zZQO<2irJnSwB*d?*$y2AXo-bM7U{Svwr{hM>U!VY&=4zR`*uwa;B`Xs7|`k9Onths z>2U+8Vz1!k3Jp>q044oe>IEm%%n5<)p^3KMS20hxs)j%VUGw|fbAA{a#O)z#`nAHj0(X!ZOl03YyypniC8NM5 zI!18SUosGxUQ?ioEM!&|OWJG<`P-`s=MWs%W8qiO4v5u$d)52QkCPK;DYniu`B0c2 z4p844a)*p>TadC4nX>}VWnZ^yh(012csB&e7kkL-`f;@XZlEiYYq_Cv>&rzyBsl0w zXeH>+S4?RL#NqxJmtw!$YoYWRp@C+4zA(13+6SIl&Mw$^8t>}BCmV{gBO%cTnfeF8 zS;R=qmj>ai#l20a7ev2v1a}L1S(Yn(-BRE<`l69E2b3B&IY8~oNYAOT68-Yd;h%WR zAM0`Z@z#Xh)qF)3r_QTWzdCi{wbzyauzGAO16D|46De8q$xbalLbu4*E!tghEIKSmqnr+>2CGi?O zzqNEm_mN66vH`JjLKwxG4~i{G%Rdraqxd~sLvS#eeSX+lme8bV=X)h+?Hi@^>DQpb zd_MHvyQDw7xpL_C6|C#e*&1}!^IgYt$_?+Gk1Ye->QRM-fSJm#=Ib^nn43mq7UiXZ z+$f9M&5~$1kMoy3nnXV82gm#0a~zmoCTvzuwO8oi9Sy$M59PtaX>TQGf|U6tu&Zqj z5?_DjCP9vmX}a{a!k?3$FSvr;6)BrPFBzU$!GRV9gdaTAV2wx98SliJ zmLx!htpH2$H$Q_Wd}22>mfMvkPHaL-LZf>=GMDN zcMZ3QF0Q#=;hw1@By$8#Y+Q+N{Kq&qyG+5fJq9Ju=}LOIFpxud;PavTLiz!TNFVwq zWc9_MMuf&*PCK=(wg;TsW{%XlHKD9b9m>3$yZ-J8N^Fzlh?cuTM$v5xfl#capIzR1-E(BDh`3LuLFL4^Aco;grl4LykK)aEvCEvX)OZjZXJdLqKwu)AL z>;c0M?10G{j>bP~qGg`w@2ZBmBM@+(vx-*KGk49#_KfzJUtRGn(f+NtSeQGr_}DOc zOA6eVho1kW$`KJWWJ@4MbV?jO&3vQ}2I&hM;q_HUoP_xH2UJ3|9)W=1YX5D3H! z(Ya>~0#T^}-`KNsz{qS_!&~5o*6S|BA7|t|s{Oj$e3X z8HNyW-*57!v41Q+qRq{9?}+jd2^=CkF+!?!0&qNns zerQjUlI=0Q!ua*7=$*_YEUU`td1A5T2|vqs`e<W zAnA&IuW0ReYRKtANOf~l<{_^aUV$S@7YJ*Fy(XTo$*bh!MDiIMQ+YK=kG>*di09+i?pu_|tM`3alpB&eybLyKpz8Zv?`K%%p{g|K>enSmM5z!QljiOix}DqZ4R zC&God0_=!2m?BzAX6N8j2FDpXBU6Px7q$jR?L+-K2*!rNo>v`1!Rh4eX}s>1eo+gR zk4IFL&i6&w6}0|z>N9qX5&xTlHSSEX{gr}A0+(SE&*BRZ3;M(Jptm_&g4lI6H_?(a zC@iSx9i1>l`t}-(0qqosU=J!U&Z2uxB8yYW^;Pf$(V!i;GP~buk;|xRBFJYRZpf-> z)1GU&iu`0E!~6p2STajlx{L4M5r=M(6s@$B^td zbmsR|1PWjj!;iA@;d#hNp_q&JT8~vAalB)7!*U`=@p^S4 z|ANob4*uke4unu6$TVWbsZ@p|1fVsuq{bw8On_wCg%8;Cj^A^yFgRZ3++!^rtNFRG z&RtzQm^@^r^)}D^7xRn7micMv@7EyS^Tqpx9pY8~6L5v!H++86mtXy|%zPw_={9g6 z^qwLszI#3KI>s%SM>$K$AW_w8C%npIyF07F8ym@8TCV`p_Mn!Wq^cVwdPgPjFcM z#kRqH$O`yI%W^ea0B@NOj%6fPZFY7DH%i{g$Mb$Klk1%kTT_XK&SVO(`>a{{RFsT` z*{1QuVi$_*>5^5qSB}6JEfIs3ZgAJ>X6?YLlrF8^D%F5Y)zev+;Dk|*;hS@~+7zPo zdF8SJf-(pSp#DP>C-|4~|N*qARsS*G9%&Kb<^X_dml9WR|R3`f-3=6A1(oajSqi#S=P&^u zC04yBU)Gr8p6kG*{2u32%ml635UVBGL<0d~ZgW^9C+)_4;BWyk9y~(P2x+0KkO%T7 zZFuKxVmz2LUdPA&L*UJPM%orCt3@HfRUld)4tOU`++d7Z1O5xxlrxX@))4nE`O@4)tH`24xnm*4ui$dqPGjsP))s0>39|k%jt#Bku`krSPa4CH2CU!lU zO=4uTk`jm_}CDYks$olIcCXvwznNX;ml)C%sm3##QHic z#WdYrxjMu=)N_Y_na_bscHBk!Jd{bj+rou6F*l^KdN!}Gr}#Wf=qHS-Zb8{>s)>|s zv43iF+*~KM_{Jii-?WEh_131qKcaE^+0vlQcsZgqidvbPHprN9_w1IMW1tbibS9ar zw*9*qP<2ptrF7vn`m}nnJM6i=o}!!rtx8LQg(#`_jx^anfx43Ph03?(c@XURwYtN} z(+h^z@LD(>RAdGFacOSlFo-bJAi*a=wmTx1(A2Cxa?qc@{#l@6jQzIST#o(Tk@g)#2mwAg`}phytOih$uTa zYx9NmohII|?*;HkQAnj3VLM%RN1G-sI7NH5zv0~u0oaP zFRaejdbNEK%-_EgvnJq_X^sh6lCjt|{Gkm9 zbSOy4y=SH3oH_I-Zr;UQYjk)FCVG~IJxlZ}ql?^=NZ#XLv*543vnhTaG2}B3GPQ#` zzD1y914_H`x`q*v6#^*PxTr%LMqTg_^SLd{EGqH+CYA)9OzzCY)udd&_s8OAYPvqC zKyG_4b7nB97t%IT-S%u0EIjQQRWW~KWfut)YrM7@=@Emj;Lx8?1s(aZWfvbJWJ!O| zZ-WrIXI-oZ#H}vW?N<{|f*Pe{o zd>S~qiF1D9TM218f**_1%Y~3l3&Nd%Wl}va;MRR20l|G2$AZGH85uGE z0e5lzB4v;ocSY%?U+~OfZp`Z0o_xw+@{s?wxw2>xmF`UYaMtzaxH$m$DTuOi3t z7PvAKSf2vK4tJ5JAiYM$#^UqH%H*5KN7pUuaV~& z?fLfdW}}Ml5SjicI^vh7z~#&M z4z&JA$<9;iFk^}WYLqp#TaZd3Q@mB~pS;O&Rt<|b5prLyg~(rp;69+G{7tR}31cW@ zrKzzM3=pGj`u|X7{OxTr8#%E|Td#7ZUy8vnCHjD7yweR&dIqIr(+-eLubSii+N>0w z+Ans9I@>RF2wQ~*#$o5qk{WiR;4%#B^%7l0{XPAm{jtmm+7xcb1%A?nxByHfElLjM zn$x4XPjQsHd)-nkCq#M#knS3B<$PRTCm0g3$z{5TaSU&qrDUZy9DSbxWw(-)?3RU^ z9qPy*tDG%vp*u(eMJ?`z%-&9bG;2k)EKlH26ey5GP3IZ6(p4@%fx4at$Zs(3(?e2gM z9Xda<Q%icC;>Mu(mMVAQe+VZ1h$j4Uw>w)$FiBXb7#1SM4hTGfN`lyZ|s{OEYo@7)+xFx=0~G z74u+zF@Kt=dWCpNX*a-=wSn#X&pdeR^X$y?Cpa+tkPWfNJ54;akA}H3<=l-?ADw?L zTysoFS2(iWe?4gwF0$nhvFiUyb7CR!u28~V&xo7iG6k(+pbMIsjHraKrf10$&YMjw zgiIQJrF#yf#|K0m%k^>%(qI>fd6s<<+ze>cD%f$~Jxd zY;VouIAyY#-lz?1p|k<2c2a zNq1O0SO-CseBJO6thWTtuY%~brM;Tub}s+wKankOy(Y_l`R)JOfF z0t$-Ue;;;^N7EySc(r9Cy-!?T84qKF?|9+Qe2Gn1o23Q-aGHKppGd(xtNJ`-Kh^uL&=$-wf6mj+60k37D{bMAf+c-P63w&$2$k(Nk1cs976`Xq9Iw=Equ$|6!3%$ zUPPWusQ&=BNHfk>9eHH>`+PIoxcLJst-`M zY4UCKg+sJ%n=o8*JCjC^zk9oj<|(fJV$$Roa5Mn$Ug$4>mXJrKYy4Lqydk3%qf0FB z57^8aUs2{t1MUX^yJaMNc)Ro!-))vkx!>-r22x!y!7GMM2?@0CR}@c2SF12M=T<2L z)QUN04-TaKU&>p=6f=SK_$gidey@+{A{+XDnbot6rV6q-aPKp$$r^n81%7j8E6Xa0 z_hG;)vcHfMaN2I_E~`h&;;OfSugUvL1vL?*EK5^^GRl&GF@q|gaUr%=4NE|~rClR= zM_yUqAg1mIa6tf2bUMj1%7D2vo#=ZN@)zwEQn~-BI6Z)_Uz~9PoZ);tAVpCZ&G}*O z7Avpkl@O?byz{kGZ0F%4hkj;al^J0L2=#4qzW}G8j}-lKnaY)Nj+Y&O_$mjthORJB zfcefDT*_}(;<3%Bk@kXzW!41ak`KJ1t3o@>8v@*dWR_!k+*1#x+W>v9du&@uSEkxM zu?zH1mbU6r{e822KOw|&|E-Tc;x5r#i@R~Ee?y61FrBh0Va*;-zp<;NOowP1+$+2L H Date: Wed, 25 Jan 2023 20:17:07 +0600 Subject: [PATCH 04/12] Workaround for https://github.com/AvaloniaUI/Avalonia/issues/10081 --- tests/Avalonia.UnitTests/UnitTestApplication.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/Avalonia.UnitTests/UnitTestApplication.cs b/tests/Avalonia.UnitTests/UnitTestApplication.cs index 82626877d0..620f301565 100644 --- a/tests/Avalonia.UnitTests/UnitTestApplication.cs +++ b/tests/Avalonia.UnitTests/UnitTestApplication.cs @@ -10,6 +10,7 @@ using System.Reactive.Disposables; using System.Reactive.Concurrency; using Avalonia.Input.Platform; using Avalonia.Animation; +using Avalonia.Media; namespace Avalonia.UnitTests { @@ -69,6 +70,10 @@ namespace Avalonia.UnitTests .Bind().ToConstant(Services.StandardCursorFactory) .Bind().ToConstant(Services.WindowingPlatform) .Bind().ToSingleton(); + + // This is a hack to make tests work, we need to refactor the way font manager is registered + // See https://github.com/AvaloniaUI/Avalonia/issues/10081 + AvaloniaLocator.CurrentMutable.Bind().ToConstant((FontManager)null!); var theme = Services.Theme?.Invoke(); if (theme is Style styles) From d0a4890f186d57e22644b81e4d201399f505996c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 25 Jan 2023 20:20:56 +0600 Subject: [PATCH 05/12] Dispose CompositorTestServices --- .../VisualTree/VisualExtensions_GetVisualsAt.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs b/tests/Avalonia.Base.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs index dc2286bcc2..3f5cb9dd01 100644 --- a/tests/Avalonia.Base.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs +++ b/tests/Avalonia.Base.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs @@ -16,7 +16,7 @@ namespace Avalonia.Base.UnitTests.VisualTree public void Should_Find_Control() { Border target; - var services = new CompositorTestServices(new Size(200, 200)) + using var services = new CompositorTestServices(new Size(200, 200)) { TopLevel = { @@ -54,7 +54,7 @@ namespace Avalonia.Base.UnitTests.VisualTree public void Should_Not_Find_Sibling_Control() { Border target; - var services = new CompositorTestServices(new Size(200, 200)) + using var services = new CompositorTestServices(new Size(200, 200)) { TopLevel = { From d8d2240ecbd8204502c483054e5d1e4f47ae8164 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 29 Jan 2023 03:04:51 -0500 Subject: [PATCH 06/12] Implement support for DataTypeInheritFromAttribute --- .../Metadata/DataTypeInheritFromAttribute.cs | 34 +++++ .../DataGridBoundColumn.cs | 2 + .../DataGridTemplateColumn.cs | 2 + src/Avalonia.Controls/ItemsControl.cs | 1 + .../Repeater/ItemsRepeater.cs | 2 + ...valoniaXamlIlDataContextTypeTransformer.cs | 60 +++++---- .../AvaloniaXamlIlWellKnownTypes.cs | 2 + .../XamlIlBindingPathHelper.cs | 4 + .../CompiledBindingExtensionTests.cs | 119 ++++++++++++++++++ 9 files changed, 203 insertions(+), 23 deletions(-) create mode 100644 src/Avalonia.Base/Metadata/DataTypeInheritFromAttribute.cs diff --git a/src/Avalonia.Base/Metadata/DataTypeInheritFromAttribute.cs b/src/Avalonia.Base/Metadata/DataTypeInheritFromAttribute.cs new file mode 100644 index 0000000000..6bd967769a --- /dev/null +++ b/src/Avalonia.Base/Metadata/DataTypeInheritFromAttribute.cs @@ -0,0 +1,34 @@ +using System; + +namespace Avalonia.Metadata; + +/// +/// Hints the compiler how to resolve the compiled bindings data type for the collection-like controls' item specific properties. +/// +/// +/// Typical example usage is a ListBox control, where DataTypeInheritFrom is defined on the ItemTemplate property, +/// so template can try to inherit data type from the Items collection binding. +/// +[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] +public sealed class DataTypeInheritFromAttribute : Attribute +{ + /// + /// Initializes a new instance of the class. + /// + /// Defines property name which items' type should used on the target property + public DataTypeInheritFromAttribute(string ancestorProperty) + { + AncestorProperty = ancestorProperty; + } + + /// + /// Defines property name which items' type should used on the target property. + /// + public string AncestorProperty { get; } + + /// + /// Defines ancestor type which should be used in a lookup for . + /// If null, declaring type of the target property is used. + /// + public Type? AncestorType { get; set; } +} diff --git a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs index 8f532b9803..2365e0ab5b 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs @@ -7,6 +7,7 @@ using Avalonia.Data; using System; using Avalonia.Controls.Utils; using Avalonia.Markup.Xaml.MarkupExtensions; +using Avalonia.Metadata; using Avalonia.Reactive; namespace Avalonia.Controls @@ -24,6 +25,7 @@ namespace Avalonia.Controls /// //TODO Binding [AssignBinding] + [DataTypeInheritFrom(nameof(DataGrid.Items), AncestorType = typeof(DataGrid))] public virtual IBinding Binding { get diff --git a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs index 516e9cf6c2..6cf4c881b3 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs @@ -24,6 +24,7 @@ namespace Avalonia.Controls (o, v) => o.CellTemplate = v); [Content] + [DataTypeInheritFrom(nameof(DataGrid.Items), AncestorType = typeof(DataGrid))] public IDataTemplate CellTemplate { get { return _cellTemplate; } @@ -50,6 +51,7 @@ namespace Avalonia.Controls /// /// If this property is the column is read-only. /// + [DataTypeInheritFrom(nameof(DataGrid.Items), AncestorType = typeof(DataGrid))] public IDataTemplate CellEditingTemplate { get => _cellEditingCellTemplate; diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index db49da85e8..5ea3fbb98e 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -168,6 +168,7 @@ namespace Avalonia.Controls /// /// Gets or sets the data template used to display the items in the control. /// + [DataTypeInheritFrom(nameof(Items))] public IDataTemplate? ItemTemplate { get { return GetValue(ItemTemplateProperty); } diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs index 4de6a5188d..bfd667d530 100644 --- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs +++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs @@ -11,6 +11,7 @@ using Avalonia.Input; using Avalonia.Layout; using Avalonia.Logging; using Avalonia.LogicalTree; +using Avalonia.Metadata; using Avalonia.Utilities; using Avalonia.VisualTree; @@ -121,6 +122,7 @@ namespace Avalonia.Controls /// /// Gets or sets the template used to display each item. /// + [DataTypeInheritFrom(nameof(Items))] public IDataTemplate? ItemTemplate { get => GetValue(ItemTemplateProperty); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs index 574d46e737..18af6d5a39 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs @@ -68,26 +68,41 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers // If there is no x:DataType directive, // do more specialized inference - if (directiveDataContextTypeNode is null) + if (directiveDataContextTypeNode is null && inferredDataContextTypeNode is null) { - if (context.GetAvaloniaTypes().IDataTemplate.IsAssignableFrom(on.Type.GetClrType()) - && inferredDataContextTypeNode is null) + // Infer data type from collection binding on a control that displays items. + var property = context.ParentNodes().OfType().FirstOrDefault(); + var attributeType = context.GetAvaloniaTypes().DataTypeInheritFromAttribute; + var attribute = property?.Property?.GetClrProperty().CustomAttributes + .FirstOrDefault(a => a.Type == attributeType); + + if (attribute is not null) { - // Infer data type from collection binding on a control that displays items. - var parentObject = context.ParentNodes().OfType().FirstOrDefault(); + var propertyName = (string)attribute.Parameters.First(); + XamlAstConstructableObjectNode parentObject; + if (attribute.Properties.TryGetValue("AncestorType", out var type) + && type is IXamlType xamlType) + { + parentObject = context.ParentNodes().OfType() + .FirstOrDefault(n => n.Type.GetClrType().FullName == xamlType.FullName); + } + else + { + parentObject = context.ParentNodes().OfType().FirstOrDefault(); + } + if (parentObject != null) { - var parentType = parentObject.Type.GetClrType(); - - if (context.GetAvaloniaTypes().ItemsControl.IsDirectlyAssignableFrom(parentType) - || context.GetAvaloniaTypes().ItemsRepeater.IsDirectlyAssignableFrom(parentType)) - { - inferredDataContextTypeNode = InferDataContextOfPresentedItem(context, on, parentObject); - } + inferredDataContextTypeNode = InferDataContextOfPresentedItem(context, on, parentObject, propertyName); } - if (inferredDataContextTypeNode is null) + if (inferredDataContextTypeNode is null + // Only for IDataTemplate, as we want to notify user as early as possible, + // and IDataTemplate cannot inherit DataType from the parent implicitly. + && context.GetAvaloniaTypes().IDataTemplate.IsAssignableFrom(on.Type.GetClrType())) { + // We can't infer the collection type and the currently calculated type is definitely wrong. + // Notify the user that we were unable to infer the data context type if they use a compiled binding. inferredDataContextTypeNode = new AvaloniaXamlIlUninferrableDataContextMetadataNode(on); } } @@ -98,18 +113,18 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers return node; } - - private static AvaloniaXamlIlDataContextTypeMetadataNode InferDataContextOfPresentedItem(AstTransformationContext context, XamlAstConstructableObjectNode on, XamlAstConstructableObjectNode parentObject) + + private static AvaloniaXamlIlDataContextTypeMetadataNode InferDataContextOfPresentedItem( + AstTransformationContext context, XamlAstConstructableObjectNode on, + XamlAstConstructableObjectNode parentObject, string propertyName) { var parentItemsValue = parentObject .Children.OfType() - .FirstOrDefault(pa => pa.Property.Name == "Items") + .FirstOrDefault(pa => pa.Property.Name == propertyName) ?.Values[0]; if (parentItemsValue is null) { - // We can't infer the collection type and the currently calculated type is definitely wrong. - // Notify the user that we were unable to infer the data context type if they use a compiled binding. - return new AvaloniaXamlIlUninferrableDataContextMetadataNode(on); + return null; } IXamlType itemsCollectionType = null; @@ -140,9 +155,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers } } } - // We can't infer the collection type and the currently calculated type is definitely wrong. - // Notify the user that we were unable to infer the data context type if they use a compiled binding. - return new AvaloniaXamlIlUninferrableDataContextMetadataNode(on); + + return null; } private static AvaloniaXamlIlDataContextTypeMetadataNode ParseDataContext(AstTransformationContext context, XamlAstConstructableObjectNode on, XamlAstConstructableObjectNode obj) @@ -208,6 +222,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { } - public override IXamlType DataContextType => throw new XamlTransformException("Unable to infer DataContext type for compiled bindings nested within this element.", Value); + public override IXamlType DataContextType => throw new XamlTransformException("Unable to infer DataContext type for compiled bindings nested within this element. Please set x:DataType on the Binding or parent.", Value); } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 0b61316603..6b36343852 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -30,6 +30,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType AssignBindingAttribute { get; } public IXamlType DependsOnAttribute { get; } public IXamlType DataTypeAttribute { get; } + public IXamlType DataTypeInheritFromAttribute { get; } public IXamlType MarkupExtensionOptionAttribute { get; } public IXamlType MarkupExtensionDefaultOptionAttribute { get; } public IXamlType OnExtensionType { get; } @@ -135,6 +136,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers AssignBindingAttribute = cfg.TypeSystem.GetType("Avalonia.Data.AssignBindingAttribute"); DependsOnAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.DependsOnAttribute"); DataTypeAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.DataTypeAttribute"); + DataTypeInheritFromAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.DataTypeInheritFromAttribute"); MarkupExtensionOptionAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.MarkupExtensionOptionAttribute"); MarkupExtensionDefaultOptionAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.MarkupExtensionDefaultOptionAttribute"); OnExtensionType = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.On"); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs index ae29dcf9cb..fb825cf636 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs @@ -37,6 +37,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions bindingResultType = transformed.BindingResultType; binding.Arguments[0] = transformed; } + if (binding.Arguments.Count > 0 && binding.Arguments[0] is XamlIlBindingPathNode alreadyTransformed) + { + bindingResultType = alreadyTransformed.BindingResultType; + } else { var bindingPathAssignment = binding.Children.OfType() diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index 16b8bb3f91..ba4b083e0d 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; @@ -7,6 +8,7 @@ using System.Linq; using System.Reactive.Subjects; using System.Runtime.CompilerServices; using System.Threading.Tasks; +using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; @@ -550,6 +552,98 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.Equal(dataContext.ListProperty[0], (string)((ContentPresenter)target.Presenter.Panel.Children[0]).Content); } } + + [Fact] + public void InfersDataTemplateTypeFromParentDataGridItemsType() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var window = (Window)AvaloniaRuntimeXamlLoader.Load(@" + + + + + + + + + + + + +"); + var target = window.FindControl("target"); + var column = target!.Columns.Single(); + + var dataContext = new TestDataContext(); + + dataContext.ListProperty.Add("Test"); + + window.DataContext = dataContext; + + window.ApplyTemplate(); + target.ApplyTemplate(); + + // Assert DataGridLikeColumn.Binding data type. + var compiledPath = ((CompiledBindingExtension)column.Binding).Path; + var node = Assert.IsType(Assert.Single(compiledPath.Elements)); + Assert.Equal(typeof(int), node.Property.PropertyType); + + // Assert DataGridLikeColumn.Template data type by evaluating the template. + var firstItem = dataContext.ListProperty[0]; + var textBlockFromTemplate = (TextBlock)column.Template.Build(firstItem); + textBlockFromTemplate.DataContext = firstItem; + Assert.Equal(firstItem.Length.ToString(), textBlockFromTemplate.Text); + } + } + + [Fact] + public void ExplicitDataTypeStillWorksOnDataGridLikeControls() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var window = (Window)AvaloniaRuntimeXamlLoader.Load(@" + + + + + + + + + + + + +"); + var target = window.FindControl("target"); + var column = target!.Columns.Single(); + + var dataContext = new TestDataContext(); + dataContext.ListProperty.Add("Test"); + target.Items = dataContext.ListProperty; + + window.ApplyTemplate(); + target.ApplyTemplate(); + + // Assert DataGridLikeColumn.Binding data type. + var compiledPath = ((CompiledBindingExtension)column.Binding).Path; + var node = Assert.IsType(Assert.Single(compiledPath.Elements)); + Assert.Equal(typeof(int), node.Property.PropertyType); + + // Assert DataGridLikeColumn.Template data type by evaluating the template. + var firstItem = dataContext.ListProperty[0]; + var textBlockFromTemplate = (TextBlock)column.Template.Build(firstItem); + textBlockFromTemplate.DataContext = firstItem; + Assert.Equal(firstItem.Length.ToString(), textBlockFromTemplate.Text); + } + } [Fact] public void ThrowsOnUninferrableDataTemplateInItemsControlWithoutItemsBinding() @@ -1835,4 +1929,29 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions { [AssignBinding] public IBinding X { get; set; } } + + public class DataGridLikeControl : Control + { + public static readonly DirectProperty ItemsProperty = + ItemsControl.ItemsProperty.AddOwner(o => o.Items, (o, v) => o.Items = v); + + private IEnumerable _items; + public IEnumerable Items + { + get { return _items; } + set { SetAndRaise(ItemsProperty, ref _items, value); } + } + + public AvaloniaList Columns { get; } = new(); + } + + public class DataGridLikeColumn + { + [AssignBinding] + [DataTypeInheritFrom(nameof(DataGridLikeControl.Items), AncestorType = typeof(DataGridLikeControl))] + public IBinding Binding { get; set; } + + [DataTypeInheritFrom(nameof(DataGridLikeControl.Items), AncestorType = typeof(DataGridLikeControl))] + public IDataTemplate Template { get; set; } + } } From 768edfcb6c9bc8b9435b5fb0c035d0a4a3a3de99 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 29 Jan 2023 03:05:17 -0500 Subject: [PATCH 07/12] Update control catalog --- .../ControlCatalog/Pages/DataGridPage.xaml | 22 +++++++++++-------- .../ControlCatalog/Pages/DataGridPage.xaml.cs | 13 ++++++----- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml b/samples/ControlCatalog/Pages/DataGridPage.xaml index 4c3c211ca5..c39e9f0a81 100644 --- a/samples/ControlCatalog/Pages/DataGridPage.xaml +++ b/samples/ControlCatalog/Pages/DataGridPage.xaml @@ -1,7 +1,9 @@ + xmlns:pages="clr-namespace:ControlCatalog.Pages" + x:Class="ControlCatalog.Pages.DataGridPage" + x:DataType="pages:DataGridPage"> @@ -33,7 +35,7 @@ - + - + + - - - - + + + + - + - + diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml.cs b/samples/ControlCatalog/Pages/DataGridPage.xaml.cs index 3565d113bc..b0c3e3a553 100644 --- a/samples/ControlCatalog/Pages/DataGridPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DataGridPage.xaml.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using Avalonia.Controls; @@ -48,20 +49,22 @@ namespace ControlCatalog.Pages var dg3 = this.Get("dataGridEdit"); dg3.IsReadOnly = false; - var items = new List + var list = new ObservableCollection { new Person { FirstName = "John", LastName = "Doe" , Age = 30}, new Person { FirstName = "Elizabeth", LastName = "Thomas", IsBanned = true , Age = 40 }, new Person { FirstName = "Zack", LastName = "Ward" , Age = 50 } }; - var collectionView3 = new DataGridCollectionView(items); - - dg3.Items = collectionView3; + DataGrid3Source = list; var addButton = this.Get