From aabb69fb9d928b94b209869a94da0b9b94e443b4 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 15 Nov 2022 18:09:55 +0000 Subject: [PATCH] remove deferred renderer --- samples/ControlCatalog.NetCore/Program.cs | 1 - samples/RenderDemo/App.xaml.cs | 4 - .../Avalonia.Android/AndroidPlatform.cs | 13 +- .../Platform/SkiaPlatform/TopLevelImpl.cs | 7 +- .../Rendering/DeferredRenderer.cs | 733 ----------- src/Avalonia.Base/Rendering/RenderLayers.cs | 70 -- .../SceneGraph/DeferredDrawingContextImpl.cs | 482 -------- .../Rendering/SceneGraph/ISceneBuilder.cs | 24 - .../Rendering/SceneGraph/IVisualNode.cs | 106 -- .../Rendering/SceneGraph/Scene.cs | 350 ------ .../Rendering/SceneGraph/SceneBuilder.cs | 485 -------- .../Rendering/SceneGraph/SceneLayer.cs | 74 -- .../Rendering/SceneGraph/SceneLayers.cs | 206 ---- .../Rendering/SceneGraph/VisualNode.cs | 448 ------- .../HeadlessVncPlatformExtensions.cs | 1 - .../AvaloniaHeadlessPlatform.cs | 5 +- src/Avalonia.Headless/HeadlessWindowImpl.cs | 5 +- src/Avalonia.Native/AvaloniaNativePlatform.cs | 6 +- .../AvaloniaNativePlatformExtensions.cs | 14 - src/Avalonia.Native/WindowImplBase.cs | 17 +- src/Avalonia.X11/X11Platform.cs | 14 +- src/Avalonia.X11/X11Window.cs | 9 +- src/Windows/Avalonia.Win32/Win32Platform.cs | 19 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 12 +- .../Rendering/DeferredRendererTests.cs | 779 ------------ .../DeferredRendererTests_HitTesting.cs | 577 --------- .../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 -- 32 files changed, 19 insertions(+), 6203 deletions(-) delete mode 100644 src/Avalonia.Base/Rendering/DeferredRenderer.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 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/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 diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index b1bacc6483..e3d196bc26 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -56,7 +56,6 @@ namespace ControlCatalog.NetCore .UseHeadless(new AvaloniaHeadlessPlatformOptions { UseHeadlessDrawing = true, - UseCompositor = 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 2b6d29e7c5..2fd58aff8f 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -53,20 +53,15 @@ namespace Avalonia.Android { EglPlatformOpenGlInterface.TryInitialize(); } - - if (Options.UseCompositor) - { - Compositor = new Compositor( - AvaloniaLocator.Current.GetRequiredService(), - AvaloniaLocator.Current.GetService()); - } + + Compositor = new Compositor( + AvaloniaLocator.Current.GetRequiredService(), + AvaloniaLocator.Current.GetService()); } } public sealed class AndroidPlatformOptions { - 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 5f53eb36c8..8bc51b68fd 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -89,12 +89,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) - : AndroidPlatform.Options.UseDeferredRendering - ? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService()) { RenderOnlyOnRenderThread = true } - : new ImmediateRenderer(root); + public IRenderer CreateRenderer(IRenderRoot root) => new CompositingRenderer(root, AndroidPlatform.Compositor); public virtual void Hide() { diff --git a/src/Avalonia.Base/Rendering/DeferredRenderer.cs b/src/Avalonia.Base/Rendering/DeferredRenderer.cs deleted file mode 100644 index 4236763e3b..0000000000 --- a/src/Avalonia.Base/Rendering/DeferredRenderer.cs +++ /dev/null @@ -1,733 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -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 IVisual _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; - - /// - /// Initializes a new instance of the class. - /// - /// The control to render. - /// The render loop. - /// 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, - ISceneBuilder? sceneBuilder = null, - IDispatcher? dispatcher = null, - IDeferredRendererLock? rendererLock = null) : base(true) - { - _dispatcher = dispatcher ?? Dispatcher.UIThread; - _root = root ?? throw new ArgumentNullException(nameof(root)); - _sceneBuilder = sceneBuilder ?? new SceneBuilder(); - Layers = new RenderLayers(); - _renderLoop = renderLoop; - _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( - IVisual 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(IVisual 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(IVisual 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, IVisual 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 IVisual? HitTestFirst(Point p, IVisual 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; - } - } - } - - 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); - } - } - - Scene? TryGetChildScene(IRef? op) => (op?.Item as BrushDrawOperation)?.Aux as Scene; - - /// - Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush) - { - return TryGetChildScene(_currentDraw)?.Size ?? Size.Empty; - } - - /// - 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 - { - 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, IVisual? layer, Rect clipBounds) - { - if (layer == null || node.LayerRoot == layer) - { - clipBounds = node.ClipBounds.Intersect(clipBounds); - - if (!clipBounds.IsEmpty && 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 as IRenderTargetWithCorruptionInfo)?.IsCorrupted == true) - { - RenderTarget!.Dispose(); - RenderTarget = null; - } - - if (RenderTarget == null) - { - RenderTarget = ((IRenderRoot)_root).CreateRenderTarget(); - } - - 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)); - 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, $"frame-{id}-layer-{index++}.png"); - layer.Bitmap.Item.Save(fileName); - } - } - } -} diff --git a/src/Avalonia.Base/Rendering/RenderLayers.cs b/src/Avalonia.Base/Rendering/RenderLayers.cs deleted file mode 100644 index 7583886835..0000000000 --- a/src/Avalonia.Base/Rendering/RenderLayers.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Avalonia.Platform; -using Avalonia.Rendering.SceneGraph; -using Avalonia.VisualTree; - -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[IVisual 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(IVisual 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 d6766fa9b8..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, GlyphRun 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 dd993fbb8c..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, IVisual visual); - } -} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/SceneGraph/IVisualNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/IVisualNode.cs deleted file mode 100644 index e042236346..0000000000 --- a/src/Avalonia.Base/Rendering/SceneGraph/IVisualNode.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Collections.Generic; -using Avalonia.Platform; -using Avalonia.Utilities; -using Avalonia.VisualTree; - -namespace Avalonia.Rendering.SceneGraph -{ - /// - /// Represents a node in the low-level scene graph representing an . - /// - public interface IVisualNode : IDisposable - { - /// - /// Gets the visual to which the node relates. - /// - IVisual 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 c436299018..0000000000 --- a/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs +++ /dev/null @@ -1,350 +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(IVisual 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(IVisual 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, IVisual 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 IVisual? HitTestFirst(Point p, IVisual 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 IVisual 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 IVisual? _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 IVisual 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); - } -} diff --git a/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs deleted file mode 100644 index 0ceb44ed75..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, IVisual 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, IVisual 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, IVisual 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.TransformedBounds = 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 (IVisual 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 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 = Rect.Empty; - Rect verticalDirtyRect = Rect.Empty; - - 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, IVisual 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.TransformedBounds = 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 = Rect.Empty; - } - - 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(IVisual 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 e9474f6e98..0000000000 --- a/src/Avalonia.Base/Rendering/SceneGraph/SceneLayer.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Avalonia.Media; -using Avalonia.Platform; -using Avalonia.VisualTree; - -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(IVisual 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 IVisual 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 16d704e5f6..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 IVisual _root; - private readonly List _inner; - private readonly Dictionary _index; - - /// - /// Initializes a new instance of the class. - /// - /// The scene's root visual. - public SceneLayers(IVisual root) : this(root, 0) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The scene's root visual. - /// Initial layer capacity. - public SceneLayers(IVisual 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[IVisual visual] => _index[visual]; - - /// - /// Adds a layer to the scene. - /// - /// The root visual of the layer. - /// The created layer. - public SceneLayer Add(IVisual 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(IVisual 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(IVisual 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(IVisual 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(IVisual 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 8a2f1f5073..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 System.Reactive.Disposables; -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(IVisual visual, IVisualNode? parent) - { - Visual = visual ?? throw new ArgumentNullException(nameof(visual)); - Parent = parent; - HasAncestorGeometryClip = parent != null && - (parent.HasAncestorGeometryClip || parent.GeometryClip != null); - } - - /// - public IVisual 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 IVisual? 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.IsEmpty) - 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.Headless.Vnc/HeadlessVncPlatformExtensions.cs b/src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs index cc7d5ef30d..1d1c87981b 100644 --- a/src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs +++ b/src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs @@ -22,7 +22,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 8da10fa59b..220a46b580 100644 --- a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs +++ b/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs @@ -72,8 +72,8 @@ 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 +88,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 742df3324b..680379e6e0 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs @@ -54,10 +54,7 @@ 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) - : new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService()); + public IRenderer CreateRenderer(IRenderRoot root) => new CompositingRenderer(root, AvaloniaHeadlessPlatform.Compositor); public void Invalidate(Rect rect) { diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index db6b6f57cd..07f35bb49a 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -138,11 +138,7 @@ namespace Avalonia.Native } } - - if (_options.UseDeferredRendering && _options.UseCompositor) - { - Compositor = new Compositor(renderLoop, _platformGl); - } + Compositor = new Compositor(renderLoop, _platformGl); } public ITrayIconImpl CreateTrayIcon() diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs index 189f45d7c8..316d281a2b 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs @@ -31,20 +31,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 381741cea9..da3dccf68d 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -53,8 +53,7 @@ namespace Avalonia.Native protected IInputRoot _inputRoot; IAvnWindowBase _native; private object _syncRoot = new object(); - private bool _deferredRendering = false; - private bool _gpu = false; + private bool _gpu; private readonly MouseDevice _mouse; private readonly IKeyboardDevice _keyboard; private readonly ICursorFactory _cursorFactory; @@ -70,7 +69,6 @@ namespace Avalonia.Native { _factory = factory; _gpu = opts.UseGpu && glFeature != null; - _deferredRendering = opts.UseDeferredRendering; _keyboard = AvaloniaLocator.Current.GetService(); _mouse = new MouseDevice(); @@ -372,17 +370,10 @@ namespace Avalonia.Native if (customRendererFactory != null) return customRendererFactory.Create(root, loop); - if (_deferredRendering) + return new CompositingRenderer(root, AvaloniaNativePlatform.Compositor) { - if (AvaloniaNativePlatform.Compositor != null) - return new CompositingRenderer(root, AvaloniaNativePlatform.Compositor) - { - RenderOnlyOnRenderThread = false - }; - return new DeferredRenderer(root, loop); - } - - return new ImmediateRenderer(root); + RenderOnlyOnRenderThread = false + }; } public virtual void Dispose() diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 96dc16e186..5ad72315b3 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -106,8 +106,7 @@ namespace Avalonia.X11 if (gl != null) AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl); - if (options.UseCompositor) - Compositor = new Compositor(AvaloniaLocator.Current.GetService()!, gl); + Compositor = new Compositor(AvaloniaLocator.Current.GetService()!, gl); } @@ -227,17 +226,6 @@ namespace Avalonia /// 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 690ac0ebce..56f8accf68 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -391,14 +391,7 @@ namespace Avalonia.X11 if (customRendererFactory != null) return customRendererFactory.Create(root, loop); - return _platform.Options.UseDeferredRendering - ? _platform.Options.UseCompositor - ? new CompositingRenderer(root, this._platform.Compositor) - : new DeferredRenderer(root, loop) - { - RenderOnlyOnRenderThread = true - } - : (IRenderer)new X11ImmediateRendererProxy(root, loop); + return new CompositingRenderer(root, this._platform.Compositor); } void OnEvent(ref XEvent ev) diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 18977ec4c3..efcfff7a9b 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -41,17 +41,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. /// @@ -123,8 +112,7 @@ namespace Avalonia.Win32 /// Gets the actual WindowsVersion. Same as the info returned from RtlGetVersion. /// 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; } @@ -166,9 +154,8 @@ namespace Avalonia.Win32 if (OleContext.Current != null) AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); - - if (Options.UseCompositor) - Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), gl); + + Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), gl); } public bool HasMessages() diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 9ed1a50ff2..d14d3090d4 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -546,18 +546,8 @@ namespace Avalonia.Win32 if (customRendererFactory != null) return customRendererFactory.Create(root, loop); - - if (Win32Platform.Compositor != null) - return new CompositingRenderer(root, Win32Platform.Compositor); - return Win32Platform.UseDeferredRendering - ? _isUsingComposition - ? new DeferredRenderer(root, loop) - { - RenderOnlyOnRenderThread = true - } - : (IRenderer)new DeferredRenderer(root, loop, rendererLock: _rendererLock) - : new ImmediateRenderer(root); + return new CompositingRenderer(root, Win32Platform.Compositor); } public void Resize(Size value, PlatformResizeReason reason) diff --git a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests.cs deleted file mode 100644 index c345b9a7ea..0000000000 --- a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests.cs +++ /dev/null @@ -1,779 +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, - 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, - 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, - 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, - 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, - 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, - 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, - 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, - 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, - dispatcher: dispatcher); - - var otherSceneBuilder = new SceneBuilder(); - - var otherTarget = new DeferredRenderer( - otherRoot, - loop.Object, - sceneBuilder: otherSceneBuilder, - 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 IVisual[] { 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, - dispatcher: dispatcher); - root.Renderer = target; - target.Start(); - RunFrame(target); - return target; - } - - private Mock GetLayerContext(DeferredRenderer renderer, IControl 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(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 d3fdbb63db..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(root, null); - 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(root, null); - 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(root, null); - 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(root, null); - 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(root, null); - 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(root, null); - 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(root, null); - container.Measure(Size.Infinity); - container.Arrange(new Rect(container.DesiredSize)); - - var result = root.Renderer.HitTest(new Point(120, 120), root, null); - - Assert.Equal(new IVisual[] { 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(root, null); - 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(root, null); - root.Measure(Size.Infinity); - root.Arrange(new Rect(container.DesiredSize)); - - root.Renderer.Paint(Rect.Empty); - 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(Rect.Empty); - 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(root, null); - 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(root, null); - 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 IVisual[] { 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(root, null); - 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(root, null); - 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 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 8f307978f2..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 502575702a..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 IVisual[] { 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 9f5a0363ed..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 IVisual[] { 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 9d25a3aa44..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 IVisual[] { 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 93266c428c..0000000000 --- a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs +++ /dev/null @@ -1,123 +0,0 @@ -using Avalonia.Rendering.SceneGraph; -using Avalonia.Utilities; -using Avalonia.VisualTree; -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(Mock.Of(), null); - var node2 = new VisualNode(Mock.Of(), null); - - Assert.Same(node1.Children, node2.Children); - } - - [Fact] - public void Adding_Child_Should_Create_Collection() - { - var node = new VisualNode(Mock.Of(), null); - var collection = node.Children; - - node.AddChild(Mock.Of(x => x.Parent == node)); - - Assert.NotSame(collection, node.Children); - } - - [Fact] - public void Empty_DrawOperations_Collections_Should_Be_Shared() - { - var node1 = new VisualNode(Mock.Of(), null); - var node2 = new VisualNode(Mock.Of(), null); - - Assert.Same(node1.DrawOperations, node2.DrawOperations); - } - - [Fact] - public void Adding_DrawOperation_Should_Create_Collection() - { - var node = new VisualNode(Mock.Of(), 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(Mock.Of(), 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(Mock.Of(), 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(Mock.Of(), 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(Mock.Of(), null); - var scene = new Scene(Mock.Of()); - - node.SortChildren(scene); - } - - [Fact] - public void TrimChildren_Should_Work_Correctly() - { - var parent = new VisualNode(Mock.Of(), null); - var child1 = new VisualNode(Mock.Of(), parent); - var child2 = new VisualNode(Mock.Of(), parent); - var child3 = new VisualNode(Mock.Of(), 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); - } - } -}