浏览代码

Initial implementation of scenegraph hit testing.

Based solely on control bounds as before.
Steven Kirk 9 年之前
父节点
当前提交
d91d1829ac
共有 27 个文件被更改,包括 371 次插入669 次删除
  1. 4 3
      samples/RenderTest/RenderTest.v2.ncrunchproject
  2. 1 0
      src/Avalonia.Visuals/Avalonia.Visuals.csproj
  3. 12 0
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  4. 2 0
      src/Avalonia.Visuals/Rendering/IRenderer.cs
  5. 6 0
      src/Avalonia.Visuals/Rendering/Renderer.cs
  6. 5 0
      src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
  7. 2 1
      src/Avalonia.Visuals/Rendering/SceneGraph/ISceneNode.cs
  8. 3 1
      src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
  9. 5 0
      src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs
  10. 5 0
      src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs
  11. 5 0
      src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
  12. 43 5
      src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
  13. 3 11
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  14. 5 0
      src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs
  15. 7 2
      src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
  16. 13 0
      src/Avalonia.Visuals/Rendering/ZIndexComparer.cs
  17. 6 22
      src/Avalonia.Visuals/VisualTree/VisualExtensions.cs
  18. 0 1
      tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj
  19. 0 484
      tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs
  20. 0 14
      tests/Avalonia.LeakTests/ControlTests.cs
  21. 4 4
      tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.v2.ncrunchproject
  22. 8 2
      tests/Avalonia.UnitTests/TestRoot.cs
  23. 7 4
      tests/Avalonia.UnitTests/TestServices.cs
  24. 16 1
      tests/Avalonia.UnitTests/UnitTestApplication.cs
  25. 4 3
      tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.v2.ncrunchproject
  26. 66 0
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
  27. 139 111
      tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensionsTests_GetVisualsAt.cs

+ 4 - 3
samples/RenderTest/RenderTest.v2.ncrunchproject

@@ -17,10 +17,11 @@
   <DetectStackOverflow>true</DetectStackOverflow>
   <DetectStackOverflow>true</DetectStackOverflow>
   <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
   <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
   <DefaultTestTimeout>60000</DefaultTestTimeout>
   <DefaultTestTimeout>60000</DefaultTestTimeout>
-  <UseBuildConfiguration />
-  <UseBuildPlatform />
-  <ProxyProcessPath />
+  <UseBuildConfiguration></UseBuildConfiguration>
+  <UseBuildPlatform></UseBuildPlatform>
+  <ProxyProcessPath></ProxyProcessPath>
   <UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
   <UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
   <MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
   <MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
   <BuildProcessArchitecture>x86</BuildProcessArchitecture>
   <BuildProcessArchitecture>x86</BuildProcessArchitecture>
+  <HiddenWarnings>MissingOrIgnoredProjectReference</HiddenWarnings>
 </ProjectConfiguration>
 </ProjectConfiguration>

+ 1 - 0
src/Avalonia.Visuals/Avalonia.Visuals.csproj

@@ -124,6 +124,7 @@
     <Compile Include="Rendering\SceneGraph\SceneBuilder.cs" />
     <Compile Include="Rendering\SceneGraph\SceneBuilder.cs" />
     <Compile Include="Rendering\SceneGraph\TextNode.cs" />
     <Compile Include="Rendering\SceneGraph\TextNode.cs" />
     <Compile Include="Rendering\SceneGraph\VisualNode.cs" />
     <Compile Include="Rendering\SceneGraph\VisualNode.cs" />
+    <Compile Include="Rendering\ZIndexComparer.cs" />
     <Compile Include="RenderTargetCorruptedException.cs" />
     <Compile Include="RenderTargetCorruptedException.cs" />
     <Compile Include="Size.cs" />
     <Compile Include="Size.cs" />
     <Compile Include="Thickness.cs" />
     <Compile Include="Thickness.cs" />

+ 12 - 0
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@@ -6,6 +6,7 @@ using Avalonia.Platform;
 using Avalonia.Rendering.SceneGraph;
 using Avalonia.Rendering.SceneGraph;
 using Avalonia.Threading;
 using Avalonia.Threading;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
+using System.Collections.Generic;
 
 
 namespace Avalonia.Rendering
 namespace Avalonia.Rendering
 {
 {
@@ -30,6 +31,7 @@ namespace Avalonia.Rendering
 
 
             _root = root;
             _root = root;
             _scene = new Scene(root);
             _scene = new Scene(root);
+            _needsUpdate = true;
             _renderLoop = renderLoop;
             _renderLoop = renderLoop;
             _renderLoop.Tick += OnRenderLoopTick;
             _renderLoop.Tick += OnRenderLoopTick;
         }
         }
@@ -50,6 +52,16 @@ namespace Avalonia.Rendering
             _renderLoop.Tick -= OnRenderLoopTick;
             _renderLoop.Tick -= OnRenderLoopTick;
         }
         }
 
 
+        public IEnumerable<IVisual> HitTest(Point p, Func<IVisual, bool> filter)
+        {
+            if (_needsUpdate)
+            {
+                UpdateScene();
+            }
+
+            return _scene.HitTest(p, filter);
+        }
+
         public void Render(Rect rect)
         public void Render(Rect rect)
         {
         {
             if (_renderTarget == null)
             if (_renderTarget == null)

+ 2 - 0
src/Avalonia.Visuals/Rendering/IRenderer.cs

@@ -3,6 +3,7 @@
 
 
 using System;
 using System;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
+using System.Collections.Generic;
 
 
 namespace Avalonia.Rendering
 namespace Avalonia.Rendering
 {
 {
@@ -11,6 +12,7 @@ namespace Avalonia.Rendering
         bool DrawFps { get; set; }
         bool DrawFps { get; set; }
 
 
         void AddDirty(IVisual visual);
         void AddDirty(IVisual visual);
+        IEnumerable<IVisual> HitTest(Point p, Func<IVisual, bool> filter);
         void Render(Rect rect);
         void Render(Rect rect);
     }
     }
 }
 }

+ 6 - 0
src/Avalonia.Visuals/Rendering/Renderer.cs

@@ -4,6 +4,7 @@
 using System;
 using System;
 using Avalonia.Platform;
 using Avalonia.Platform;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
+using System.Collections.Generic;
 
 
 namespace Avalonia.Rendering
 namespace Avalonia.Rendering
 {
 {
@@ -35,6 +36,11 @@ namespace Avalonia.Rendering
             _renderLoop.Tick -= OnRenderLoopTick;
             _renderLoop.Tick -= OnRenderLoopTick;
         }
         }
 
 
+        public IEnumerable<IVisual> HitTest(Point p, Func<IVisual, bool> filter)
+        {
+            throw new NotImplementedException();
+        }
+
         public void Render(Rect rect)
         public void Render(Rect rect)
         {
         {
             if (_renderTarget == null)
             if (_renderTarget == null)

+ 5 - 0
src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs

@@ -35,5 +35,10 @@ namespace Avalonia.Rendering.SceneGraph
             context.Transform = Transform;
             context.Transform = Transform;
             context.DrawGeometry(Brush, Pen, Geometry);
             context.DrawGeometry(Brush, Pen, Geometry);
         }
         }
+
+        public bool HitTest(Point p)
+        {
+            throw new NotImplementedException();
+        }
     }
     }
 }
 }

+ 2 - 1
src/Avalonia.Visuals/Rendering/SceneGraph/ISceneNode.cs

@@ -2,13 +2,14 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
 using System;
 using System;
-using System.Collections.Generic;
 using Avalonia.Media;
 using Avalonia.Media;
 
 
 namespace Avalonia.Rendering.SceneGraph
 namespace Avalonia.Rendering.SceneGraph
 {
 {
     public interface ISceneNode
     public interface ISceneNode
     {
     {
+        bool HitTest(Point p);
+
         void Render(IDrawingContextImpl context);
         void Render(IDrawingContextImpl context);
     }
     }
 }
 }

+ 3 - 1
src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs

@@ -9,7 +9,9 @@ namespace Avalonia.Rendering.SceneGraph
 {
 {
     public interface IVisualNode : ISceneNode
     public interface IVisualNode : ISceneNode
     {
     {
-        IReadOnlyList<ISceneNode> Children { get; }
         IVisual Visual { get; }
         IVisual Visual { get; }
+        Rect ClipBounds { get; set; }
+        bool ClipToBounds { get; set; }
+        IReadOnlyList<ISceneNode> Children { get; }
     }
     }
 }
 }

+ 5 - 0
src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs

@@ -38,5 +38,10 @@ namespace Avalonia.Rendering.SceneGraph
             context.Transform = Transform;
             context.Transform = Transform;
             context.DrawImage(Source, Opacity, SourceRect, DestRect);
             context.DrawImage(Source, Opacity, SourceRect, DestRect);
         }
         }
+
+        public bool HitTest(Point p)
+        {
+            throw new NotImplementedException();
+        }
     }
     }
 }
 }

+ 5 - 0
src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs

@@ -31,5 +31,10 @@ namespace Avalonia.Rendering.SceneGraph
             context.Transform = Transform;
             context.Transform = Transform;
             context.DrawLine(Pen, P1, P2);
             context.DrawLine(Pen, P1, P2);
         }
         }
+
+        public bool HitTest(Point p)
+        {
+            throw new NotImplementedException();
+        }
     }
     }
 }
 }

+ 5 - 0
src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs

@@ -46,5 +46,10 @@ namespace Avalonia.Rendering.SceneGraph
                 context.DrawRectangle(Pen, Rect, CornerRadius);
                 context.DrawRectangle(Pen, Rect, CornerRadius);
             }
             }
         }
         }
+
+        public bool HitTest(Point p)
+        {
+            throw new NotImplementedException();
+        }
     }
     }
 }
 }

+ 43 - 5
src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs

@@ -31,6 +31,14 @@ namespace Avalonia.Rendering.SceneGraph
             _index.Add(node.Visual, node);
             _index.Add(node.Visual, node);
         }
         }
 
 
+        public Scene Clone()
+        {
+            var index = new Dictionary<IVisual, IVisualNode>();
+            var root = (VisualNode)Clone((VisualNode)Root, null, index);
+            var result = new Scene(root, index);
+            return result;
+        }
+
         public IVisualNode FindNode(IVisual visual)
         public IVisualNode FindNode(IVisual visual)
         {
         {
             IVisualNode node;
             IVisualNode node;
@@ -38,12 +46,9 @@ namespace Avalonia.Rendering.SceneGraph
             return node;
             return node;
         }
         }
 
 
-        public Scene Clone()
+        public IEnumerable<IVisual> HitTest(Point p, Func<IVisual, bool> filter)
         {
         {
-            var index = new Dictionary<IVisual, IVisualNode>();
-            var root = (VisualNode)Clone((VisualNode)Root, null, index);
-            var result = new Scene(root, index);
-            return result;
+            return HitTest(Root, p, null, filter);
         }
         }
 
 
         private VisualNode Clone(VisualNode source, ISceneNode parent, Dictionary<IVisual, IVisualNode> index)
         private VisualNode Clone(VisualNode source, ISceneNode parent, Dictionary<IVisual, IVisualNode> index)
@@ -68,5 +73,38 @@ namespace Avalonia.Rendering.SceneGraph
 
 
             return result;
             return result;
         }
         }
+
+        private IEnumerable<IVisual> HitTest(IVisualNode node, Point p, Rect? clip, Func<IVisual, bool> filter)
+        {
+            if (filter?.Invoke(node.Visual) != false)
+            {
+                if (node.ClipToBounds)
+                {
+                    // TODO: Handle geometry clip.
+                    clip = clip == null ? node.ClipBounds : clip.Value.Intersect(node.ClipBounds);
+                }
+
+                if (!clip.HasValue || clip.Value.Contains(p))
+                {
+                    for (var i = node.Children.Count - 1; i >= 0; --i)
+                    {
+                        var visualChild = node.Children[i] as IVisualNode;
+
+                        if (visualChild != null)
+                        {
+                            foreach (var h in HitTest(visualChild, p, clip, filter))
+                            {
+                                yield return h;
+                            }
+                        }
+                    }
+
+                    if (node.HitTest(p))
+                    {
+                        yield return node.Visual;
+                    }
+                }
+            }
+        }
     }
     }
 }
 }

+ 3 - 11
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@@ -2,6 +2,7 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
 using System;
 using System;
+using System.Linq;
 using Avalonia.Media;
 using Avalonia.Media;
 using Avalonia.Threading;
 using Avalonia.Threading;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
@@ -55,7 +56,7 @@ namespace Avalonia.Rendering.SceneGraph
                 using (context.PushTransformContainer())
                 using (context.PushTransformContainer())
                 {
                 {
                     node.Transform = contextImpl.Transform;
                     node.Transform = contextImpl.Transform;
-                    node.Bounds = bounds;
+                    node.ClipBounds = bounds * node.Transform;
                     node.ClipToBounds = clipToBounds;
                     node.ClipToBounds = clipToBounds;
                     node.GeometryClip = visual.Clip;
                     node.GeometryClip = visual.Clip;
                     node.Opacity = opacity;
                     node.Opacity = opacity;
@@ -63,16 +64,7 @@ namespace Avalonia.Rendering.SceneGraph
 
 
                     visual.Render(context);
                     visual.Render(context);
 
 
-#pragma warning disable 0618
-                    var transformed = new TransformedBounds(bounds, new Rect(), context.CurrentContainerTransform);
-#pragma warning restore 0618
-
-                    if (visual is Visual)
-                    {
-                        BoundsTracker.SetTransformedBounds((Visual)visual, transformed);
-                    }
-
-                    foreach (var child in visual.VisualChildren)
+                    foreach (var child in visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance))
                     {
                     {
                         Update(context, scene, child, node);
                         Update(context, scene, child, node);
                     }
                     }

+ 5 - 0
src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs

@@ -35,5 +35,10 @@ namespace Avalonia.Rendering.SceneGraph
                 origin == Origin &&
                 origin == Origin &&
                 Equals(text, Text);
                 Equals(text, Text);
         }
         }
+
+        public bool HitTest(Point p)
+        {
+            throw new NotImplementedException();
+        }
     }
     }
 }
 }

+ 7 - 2
src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs

@@ -18,7 +18,7 @@ namespace Avalonia.Rendering.SceneGraph
 
 
         public IVisual Visual { get; }
         public IVisual Visual { get; }
         public Matrix Transform { get; set; }
         public Matrix Transform { get; set; }
-        public Rect Bounds { get; set; }
+        public Rect ClipBounds { get; set; }
         public bool ClipToBounds { get; set; }
         public bool ClipToBounds { get; set; }
         public Geometry GeometryClip { get; set; }
         public Geometry GeometryClip { get; set; }
         public double Opacity { get; set; }
         public double Opacity { get; set; }
@@ -32,6 +32,11 @@ namespace Avalonia.Rendering.SceneGraph
             return new VisualNode(Visual);
             return new VisualNode(Visual);
         }
         }
 
 
+        public bool HitTest(Point p)
+        {
+            return ClipBounds.Contains(p);
+        }
+
         public void Render(IDrawingContextImpl context)
         public void Render(IDrawingContextImpl context)
         {
         {
             context.Transform = Transform;
             context.Transform = Transform;
@@ -43,7 +48,7 @@ namespace Avalonia.Rendering.SceneGraph
 
 
             if (ClipToBounds)
             if (ClipToBounds)
             {
             {
-                context.PushClip(Bounds);
+                context.PushClip(ClipBounds);
             }
             }
 
 
             foreach (var child in Children)
             foreach (var child in Children)

+ 13 - 0
src/Avalonia.Visuals/Rendering/ZIndexComparer.cs

@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Rendering
+{
+    public class ZIndexComparer : IComparer<IVisual>
+    {
+        public static readonly ZIndexComparer Instance = new ZIndexComparer();
+
+        public int Compare(IVisual x, IVisual y) => x.ZIndex.CompareTo(y.ZIndex);
+    }
+}

+ 6 - 22
src/Avalonia.Visuals/VisualTree/VisualExtensions.cs

@@ -1,6 +1,7 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
+using Avalonia.Rendering;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
@@ -102,26 +103,9 @@ namespace Avalonia.VisualTree
         {
         {
             Contract.Requires<ArgumentNullException>(visual != null);
             Contract.Requires<ArgumentNullException>(visual != null);
 
 
-            if (filter?.Invoke(visual) != false)
-            {
-                bool containsPoint = BoundsTracker.GetTransformedBounds((Visual)visual)?.Contains(p) == true;
-
-                if ((containsPoint || !visual.ClipToBounds) && visual.VisualChildren.Any())
-                {
-                    foreach (var child in visual.VisualChildren.SortByZIndex())
-                    {
-                        foreach (var result in child.GetVisualsAt(p, filter))
-                        {
-                            yield return result;
-                        }
-                    }
-                }
-
-                if (containsPoint)
-                {
-                    yield return visual;
-                }
-            }
+            var root = visual.GetVisualRoot();
+            p = visual.TranslatePoint(p, root);
+            return root.Renderer.HitTest(p, filter);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -197,11 +181,11 @@ namespace Avalonia.VisualTree
         /// <returns>
         /// <returns>
         /// The root visual or null if the visual is not rooted.
         /// The root visual or null if the visual is not rooted.
         /// </returns>
         /// </returns>
-        public static IVisual GetVisualRoot(this IVisual visual)
+        public static IRenderRoot GetVisualRoot(this IVisual visual)
         {
         {
             Contract.Requires<ArgumentNullException>(visual != null);
             Contract.Requires<ArgumentNullException>(visual != null);
 
 
-            return visual.VisualRoot as IVisual;
+            return visual as IRenderRoot ?? visual.VisualRoot;
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 0 - 1
tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj

@@ -86,7 +86,6 @@
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <Compile Include="InputElement_Focus.cs" />
     <Compile Include="InputElement_Focus.cs" />
-    <Compile Include="InputElement_HitTesting.cs" />
     <Compile Include="KeyboardNavigationTests_Arrows.cs" />
     <Compile Include="KeyboardNavigationTests_Arrows.cs" />
     <Compile Include="KeyboardNavigationTests_Tab.cs" />
     <Compile Include="KeyboardNavigationTests_Tab.cs" />
     <Compile Include="KeyGestureParseTests.cs" />
     <Compile Include="KeyGestureParseTests.cs" />

+ 0 - 484
tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs

@@ -1,484 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using Avalonia.Controls;
-using Avalonia.Controls.Presenters;
-using Avalonia.Layout;
-using Avalonia.Media;
-using Avalonia.Platform;
-using Avalonia.Rendering;
-using Avalonia.UnitTests;
-using Moq;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using Xunit;
-
-namespace Avalonia.Input.UnitTests
-{
-    public class InputElement_HitTesting
-    {
-        [Fact]
-        public void InputHitTest_Should_Find_Control_At_Point()
-        {
-            using (var application = UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
-            {
-                var container = new Decorator
-                {
-                    Width = 200,
-                    Height = 200,
-                    Child = new Border
-                    {
-                        Width = 100,
-                        Height = 100,
-                        HorizontalAlignment = HorizontalAlignment.Center,
-                        VerticalAlignment = VerticalAlignment.Center
-                    }
-                };
-
-                container.Measure(Size.Infinity);
-                container.Arrange(new Rect(container.DesiredSize));
-
-                var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
-                context.Render(container);
-
-                var result = container.InputHitTest(new Point(100, 100));
-
-                Assert.Equal(container.Child, result);
-            }
-        }
-
-        [Fact]
-        public void InputHitTest_Should_Not_Find_Control_Outside_Point()
-        {
-            using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
-            {
-                var container = new Decorator
-                {
-                    Width = 200,
-                    Height = 200,
-                    Child = new Border
-                    {
-                        Width = 100,
-                        Height = 100,
-                        HorizontalAlignment = HorizontalAlignment.Center,
-                        VerticalAlignment = VerticalAlignment.Center
-                    }
-                };
-
-                container.Measure(Size.Infinity);
-                container.Arrange(new Rect(container.DesiredSize));
-
-                var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
-                context.Render(container);
-
-                var result = container.InputHitTest(new Point(10, 10));
-
-                Assert.Equal(container, result);
-            }
-        }
-
-        [Fact]
-        public void InputHitTest_Should_Find_Top_Control_At_Point()
-        {
-            using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
-            {
-                var container = new Panel
-                {
-                    Width = 200,
-                    Height = 200,
-                    Children = new Controls.Controls
-                {
-                    new Border
-                    {
-                        Width = 100,
-                        Height = 100,
-                        HorizontalAlignment = HorizontalAlignment.Center,
-                        VerticalAlignment = VerticalAlignment.Center
-                    },
-                    new Border
-                    {
-                        Width = 50,
-                        Height = 50,
-                        HorizontalAlignment = HorizontalAlignment.Center,
-                        VerticalAlignment = VerticalAlignment.Center
-                    }
-                }
-                };
-
-                container.Measure(Size.Infinity);
-                container.Arrange(new Rect(container.DesiredSize));
-
-                var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
-                context.Render(container);
-
-                var result = container.InputHitTest(new Point(100, 100));
-
-                Assert.Equal(container.Children[1], result);
-            }
-        }
-
-        [Fact]
-        public void InputHitTest_Should_Find_Top_Control_At_Point_With_ZOrder()
-        {
-            using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
-            {
-                var container = new Panel
-                {
-                    Width = 200,
-                    Height = 200,
-                    Children = new Controls.Controls
-                {
-                    new Border
-                    {
-                        Width = 100,
-                        Height = 100,
-                        ZIndex = 1,
-                        HorizontalAlignment = HorizontalAlignment.Center,
-                        VerticalAlignment = VerticalAlignment.Center
-                    },
-                    new Border
-                    {
-                        Width = 50,
-                        Height = 50,
-                        HorizontalAlignment = HorizontalAlignment.Center,
-                        VerticalAlignment = VerticalAlignment.Center
-                    }
-                }
-                };
-
-                container.Measure(Size.Infinity);
-                container.Arrange(new Rect(container.DesiredSize));
-
-                var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
-                context.Render(container);
-
-                var result = container.InputHitTest(new Point(100, 100));
-
-                Assert.Equal(container.Children[0], result);
-            }
-        }
-
-        [Fact]
-        public void InputHitTest_Should_Find_Control_Translated_Outside_Parent_Bounds()
-        {
-            using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
-            {
-                Border target;
-                var container = new Panel
-                {
-                    Width = 200,
-                    Height = 200,
-                    ClipToBounds = false,
-                    Children = new Controls.Controls
-                    {
-                        new Border
-                        {
-                            Width = 100,
-                            Height = 100,
-                            ZIndex = 1,
-                            HorizontalAlignment = HorizontalAlignment.Left,
-                            VerticalAlignment = VerticalAlignment.Top,
-                            Child = target = new Border
-                            {
-                                Width = 50,
-                                Height = 50,
-                                HorizontalAlignment = HorizontalAlignment.Left,
-                                VerticalAlignment = VerticalAlignment.Top,
-                                RenderTransform = new TranslateTransform(110, 110),
-                            }
-                        },
-                    }
-                };
-
-                container.Measure(Size.Infinity);
-                container.Arrange(new Rect(container.DesiredSize));
-
-                var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
-                context.Render(container);
-
-                var result = container.InputHitTest(new Point(120, 120));
-
-                Assert.Equal(target, result);
-            }
-        }
-
-        [Fact]
-        public void InputHitTest_Should_Not_Find_Control_Outside_Parent_Bounds_When_Clipped()
-        {
-            using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
-            {
-                Border target;
-
-                var container = new Panel
-                {
-                    Width = 100,
-                    Height = 200,
-                    Children = new Controls.Controls
-                    {
-                        new Panel()
-                        {
-                            Width = 100,
-                            Height = 100,
-                            Margin = new Thickness(0, 100, 0, 0),
-                            ClipToBounds = true,
-                            Children = new Controls.Controls
-                            {
-                                (target = new Border()
-                                {
-                                    Width = 100,
-                                    Height = 100,
-                                    Margin = new Thickness(0, -100, 0, 0)
-                                })
-                            }
-                        }
-                    }
-                };
-
-                container.Measure(Size.Infinity);
-                container.Arrange(new Rect(container.DesiredSize));
-
-                var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
-                context.Render(container);
-
-                var result = container.InputHitTest(new Point(50, 50));
-
-                Assert.NotEqual(target, result);
-                Assert.Equal(container, result);
-            }
-        }
-
-        [Fact]
-        public void InputHitTest_Should_Not_Find_Control_Outside_Scroll_ViewPort()
-        {
-            using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
-            {
-                Border target;
-                Border item1;
-                Border item2;
-                ScrollContentPresenter scroll;
-
-                var container = new Panel
-                {
-                    Width = 100,
-                    Height = 200,
-                    Children = new Controls.Controls
-                    {
-                        (target = new Border()
-                        {
-                            Width = 100,
-                            Height = 100
-                        }),
-                        new Border()
-                        {
-                            Width = 100,
-                            Height = 100,
-                            Margin = new Thickness(0, 100, 0, 0),
-                            Child = scroll = new ScrollContentPresenter()
-                            {
-                                Content = new StackPanel()
-                                {
-                                    Children = new Controls.Controls
-                                    {
-                                        (item1 = new Border()
-                                        {
-                                            Width = 100,
-                                            Height = 100,
-                                        }),
-                                        (item2 = new Border()
-                                        {
-                                            Width = 100,
-                                            Height = 100,
-                                        }),
-                                    }
-                                }
-                            }
-                        }
-                    }
-                };
-
-                scroll.UpdateChild();
-
-                container.Measure(Size.Infinity);
-                container.Arrange(new Rect(container.DesiredSize));
-
-                var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
-                context.Render(container);
-
-                var result = container.InputHitTest(new Point(50, 150));
-
-                Assert.Equal(item1, result);
-
-                result = container.InputHitTest(new Point(50, 50));
-
-                Assert.Equal(target, result);
-
-                scroll.Offset = new Vector(0, 100);
-
-                //we don't have setup LayoutManager so we will make it manually
-                scroll.Parent.InvalidateArrange();
-                container.InvalidateArrange();
-
-                container.Arrange(new Rect(container.DesiredSize));
-                context.Render(container);
-
-                result = container.InputHitTest(new Point(50, 150));
-
-                Assert.Equal(item2, result);
-
-                result = container.InputHitTest(new Point(50, 50));
-
-                Assert.NotEqual(item1, result);
-                Assert.Equal(target, result);
-            }
-        }
-
-        class MockRenderInterface : IPlatformRenderInterface
-        {
-            public IFormattedTextImpl CreateFormattedText(string text, string fontFamilyName, double fontSize, FontStyle fontStyle, TextAlignment textAlignment, FontWeight fontWeight, TextWrapping wrapping)
-            {
-                throw new NotImplementedException();
-            }
-
-            public IRenderTarget CreateRenderTarget(IPlatformHandle handle)
-            {
-                throw new NotImplementedException();
-            }
-
-            public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height)
-            {
-                throw new NotImplementedException();
-            }
-
-            public IStreamGeometryImpl CreateStreamGeometry()
-            {
-                return new MockStreamGeometry();
-            }
-
-            public IBitmapImpl LoadBitmap(Stream stream)
-            {
-                throw new NotImplementedException();
-            }
-
-            public IBitmapImpl LoadBitmap(string fileName)
-            {
-                throw new NotImplementedException();
-            }
-
-            class MockStreamGeometry : Avalonia.Platform.IStreamGeometryImpl
-            {
-                private MockStreamGeometryContext _impl = new MockStreamGeometryContext();
-                public Rect Bounds
-                {
-                    get
-                    {
-                        throw new NotImplementedException();
-                    }
-                }
-
-                public Matrix Transform
-                {
-                    get
-                    {
-                        throw new NotImplementedException();
-                    }
-
-                    set
-                    {
-                        throw new NotImplementedException();
-                    }
-                }
-
-                public IStreamGeometryImpl Clone()
-                {
-                    return this;
-                }
-
-                public bool FillContains(Point point)
-                {
-                    return _impl.FillContains(point);
-                }
-
-                public Rect GetRenderBounds(double strokeThickness)
-                {
-                    throw new NotImplementedException();
-                }
-
-                public IStreamGeometryContextImpl Open()
-                {
-                    return _impl;
-                }
-
-                class MockStreamGeometryContext : IStreamGeometryContextImpl
-                {
-                    private List<Point> points = new List<Point>();
-                    public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
-                    {
-                        throw new NotImplementedException();
-                    }
-
-                    public void BeginFigure(Point startPoint, bool isFilled)
-                    {
-                        points.Add(startPoint);
-                    }
-
-                    public void CubicBezierTo(Point point1, Point point2, Point point3)
-                    {
-                        throw new NotImplementedException();
-                    }
-
-                    public void Dispose()
-                    {
-                    }
-
-                    public void EndFigure(bool isClosed)
-                    {
-                    }
-
-                    public void LineTo(Point point)
-                    {
-                        points.Add(point);
-                    }
-
-                    public void QuadraticBezierTo(Point control, Point endPoint)
-                    {
-                        throw new NotImplementedException();
-                    }
-
-                    public void SetFillRule(FillRule fillRule)
-                    {
-                    }
-
-                    public bool FillContains(Point point)
-                    {
-                        // Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html
-                        // to determine if the point is in the geometry (since it will always be convex in this situation)
-                        for (int i = 0; i < points.Count; i++)
-                        {
-                            var a = points[i];
-                            var b = points[(i + 1) % points.Count];
-                            var c = points[(i + 2) % points.Count];
-
-                            Vector v0 = c - a;
-                            Vector v1 = b - a;
-                            Vector v2 = point - a;
-
-                            var dot00 = v0 * v0;
-                            var dot01 = v0 * v1;
-                            var dot02 = v0 * v2;
-                            var dot11 = v1 * v1;
-                            var dot12 = v1 * v2;
-
-
-                            var invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
-                            var u = (dot11 * dot02 - dot01 * dot12) * invDenom;
-                            var v = (dot00 * dot12 - dot01 * dot02) * invDenom;
-                            if ((u >= 0) && (v >= 0) && (u + v < 1)) return true;
-                        }
-                        return false;
-                    }
-                }
-            }
-        }
-    }
-}

+ 0 - 14
tests/Avalonia.LeakTests/ControlTests.cs

@@ -54,7 +54,6 @@ namespace Avalonia.LeakTests
                 };
                 };
 
 
                 var result = run();
                 var result = run();
-                PurgeMoqReferences();
 
 
                 dotMemory.Check(memory =>
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
@@ -90,7 +89,6 @@ namespace Avalonia.LeakTests
                 };
                 };
 
 
                 var result = run();
                 var result = run();
-                PurgeMoqReferences();
 
 
                 dotMemory.Check(memory =>
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
@@ -127,7 +125,6 @@ namespace Avalonia.LeakTests
                 };
                 };
 
 
                 var result = run();
                 var result = run();
-                PurgeMoqReferences();
 
 
                 dotMemory.Check(memory =>
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
@@ -163,7 +160,6 @@ namespace Avalonia.LeakTests
                 };
                 };
 
 
                 var result = run();
                 var result = run();
-                PurgeMoqReferences();
 
 
                 dotMemory.Check(memory =>
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
@@ -207,7 +203,6 @@ namespace Avalonia.LeakTests
                 };
                 };
 
 
                 var result = run();
                 var result = run();
-                PurgeMoqReferences();
 
 
                 dotMemory.Check(memory =>
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
@@ -294,21 +289,12 @@ namespace Avalonia.LeakTests
                 };
                 };
 
 
                 var result = run();
                 var result = run();
-                PurgeMoqReferences();
 
 
                 dotMemory.Check(memory =>
                 dotMemory.Check(memory =>
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TreeView>()).ObjectsCount));
                     Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TreeView>()).ObjectsCount));
             }
             }
         }
         }
 
 
-        private static void PurgeMoqReferences()
-        {
-            // Moq holds onto references in its mock of IRenderer in case we want to check if a method has been called;
-            // clear these.
-            var renderer = Mock.Get(AvaloniaLocator.Current.GetService<IRenderer>());
-            renderer.ResetCalls();
-        }
-
         private class Node
         private class Node
         {
         {
             public string Name { get; set; }
             public string Name { get; set; }

+ 4 - 4
tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.v2.ncrunchproject

@@ -7,7 +7,7 @@
   <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking>
   <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking>
   <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking>
   <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking>
   <AllowCodeAnalysis>false</AllowCodeAnalysis>
   <AllowCodeAnalysis>false</AllowCodeAnalysis>
-  <IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely>
+  <IgnoreThisComponentCompletely>true</IgnoreThisComponentCompletely>
   <RunPreBuildEvents>false</RunPreBuildEvents>
   <RunPreBuildEvents>false</RunPreBuildEvents>
   <RunPostBuildEvents>false</RunPostBuildEvents>
   <RunPostBuildEvents>false</RunPostBuildEvents>
   <PreviouslyBuiltSuccessfully>true</PreviouslyBuiltSuccessfully>
   <PreviouslyBuiltSuccessfully>true</PreviouslyBuiltSuccessfully>
@@ -17,9 +17,9 @@
   <DetectStackOverflow>true</DetectStackOverflow>
   <DetectStackOverflow>true</DetectStackOverflow>
   <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
   <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
   <DefaultTestTimeout>60000</DefaultTestTimeout>
   <DefaultTestTimeout>60000</DefaultTestTimeout>
-  <UseBuildConfiguration />
-  <UseBuildPlatform />
-  <ProxyProcessPath />
+  <UseBuildConfiguration></UseBuildConfiguration>
+  <UseBuildPlatform></UseBuildPlatform>
+  <ProxyProcessPath></ProxyProcessPath>
   <UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
   <UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
   <MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
   <MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
   <BuildProcessArchitecture>x86</BuildProcessArchitecture>
   <BuildProcessArchitecture>x86</BuildProcessArchitecture>

+ 8 - 2
tests/Avalonia.UnitTests/TestRoot.cs

@@ -15,6 +15,13 @@ namespace Avalonia.UnitTests
     {
     {
         private readonly NameScope _nameScope = new NameScope();
         private readonly NameScope _nameScope = new NameScope();
 
 
+        public TestRoot()
+        {
+            var rendererFactory = AvaloniaLocator.Current.GetService<IRendererFactory>();
+            var renderLoop = AvaloniaLocator.Current.GetService<IRenderLoop>();
+            Renderer = rendererFactory?.CreateRenderer(this, renderLoop);
+        }
+
         event EventHandler<NameScopeEventArgs> INameScope.Registered
         event EventHandler<NameScopeEventArgs> INameScope.Registered
         {
         {
             add { _nameScope.Registered += value; ++NameScopeRegisteredSubscribers; }
             add { _nameScope.Registered += value; ++NameScopeRegisteredSubscribers; }
@@ -41,7 +48,7 @@ namespace Avalonia.UnitTests
 
 
         public IRenderTarget RenderTarget => null;
         public IRenderTarget RenderTarget => null;
 
 
-        public IRenderer Renderer => null;
+        public IRenderer Renderer { get; }
 
 
         public IRenderTarget CreateRenderTarget()
         public IRenderTarget CreateRenderTarget()
         {
         {
@@ -50,7 +57,6 @@ namespace Avalonia.UnitTests
 
 
         public void Invalidate(Rect rect)
         public void Invalidate(Rect rect)
         {
         {
-            throw new NotImplementedException();
         }
         }
 
 
         public Point PointToClient(Point p) => p;
         public Point PointToClient(Point p) => p;

+ 7 - 4
tests/Avalonia.UnitTests/TestServices.cs

@@ -21,7 +21,7 @@ namespace Avalonia.UnitTests
             assetLoader: new AssetLoader(),
             assetLoader: new AssetLoader(),
             layoutManager: new LayoutManager(),
             layoutManager: new LayoutManager(),
             platform: new AppBuilder().RuntimePlatform,
             platform: new AppBuilder().RuntimePlatform,
-            renderer: Mock.Of<IRenderer>(),
+            renderer: (_, __) => Mock.Of<IRenderer>(),
             renderInterface: CreateRenderInterfaceMock(),
             renderInterface: CreateRenderInterfaceMock(),
             renderLoop: Mock.Of<IRenderLoop>(),
             renderLoop: Mock.Of<IRenderLoop>(),
             standardCursorFactory: Mock.Of<IStandardCursorFactory>(),
             standardCursorFactory: Mock.Of<IStandardCursorFactory>(),
@@ -42,6 +42,9 @@ namespace Avalonia.UnitTests
         public static readonly TestServices MockThreadingInterface = new TestServices(
         public static readonly TestServices MockThreadingInterface = new TestServices(
             threadingInterface: Mock.Of<IPlatformThreadingInterface>(x => x.CurrentThreadIsLoopThread == true));
             threadingInterface: Mock.Of<IPlatformThreadingInterface>(x => x.CurrentThreadIsLoopThread == true));
 
 
+        public static readonly TestServices RealDeferredRenderer = new TestServices(
+            renderer: (root, loop) => new DeferredRenderer(root, loop));
+
         public static readonly TestServices RealFocus = new TestServices(
         public static readonly TestServices RealFocus = new TestServices(
             focusManager: new FocusManager(),
             focusManager: new FocusManager(),
             keyboardDevice: () => new KeyboardDevice(),
             keyboardDevice: () => new KeyboardDevice(),
@@ -60,7 +63,7 @@ namespace Avalonia.UnitTests
             Func<IKeyboardDevice> keyboardDevice = null,
             Func<IKeyboardDevice> keyboardDevice = null,
             ILayoutManager layoutManager = null,
             ILayoutManager layoutManager = null,
             IRuntimePlatform platform = null,
             IRuntimePlatform platform = null,
-            IRenderer renderer = null,
+            Func<IRenderRoot, IRenderLoop, IRenderer> renderer = null,
             IPlatformRenderInterface renderInterface = null,
             IPlatformRenderInterface renderInterface = null,
             IRenderLoop renderLoop = null,
             IRenderLoop renderLoop = null,
             IStandardCursorFactory standardCursorFactory = null,
             IStandardCursorFactory standardCursorFactory = null,
@@ -93,7 +96,7 @@ namespace Avalonia.UnitTests
         public Func<IKeyboardDevice> KeyboardDevice { get; }
         public Func<IKeyboardDevice> KeyboardDevice { get; }
         public ILayoutManager LayoutManager { get; }
         public ILayoutManager LayoutManager { get; }
         public IRuntimePlatform Platform { get; }
         public IRuntimePlatform Platform { get; }
-        public IRenderer Renderer { get; }
+        public Func<IRenderRoot, IRenderLoop, IRenderer> Renderer { get; }
         public IPlatformRenderInterface RenderInterface { get; }
         public IPlatformRenderInterface RenderInterface { get; }
         public IRenderLoop RenderLoop { get; }
         public IRenderLoop RenderLoop { get; }
         public IStandardCursorFactory StandardCursorFactory { get; }
         public IStandardCursorFactory StandardCursorFactory { get; }
@@ -110,7 +113,7 @@ namespace Avalonia.UnitTests
             Func<IKeyboardDevice> keyboardDevice = null,
             Func<IKeyboardDevice> keyboardDevice = null,
             ILayoutManager layoutManager = null,
             ILayoutManager layoutManager = null,
             IRuntimePlatform platform = null,
             IRuntimePlatform platform = null,
-            IRenderer renderer = null,
+            Func<IRenderRoot, IRenderLoop, IRenderer> renderer = null,
             IPlatformRenderInterface renderInterface = null,
             IPlatformRenderInterface renderInterface = null,
             IRenderLoop renderLoop = null,
             IRenderLoop renderLoop = null,
             IStandardCursorFactory standardCursorFactory = null,
             IStandardCursorFactory standardCursorFactory = null,

+ 16 - 1
tests/Avalonia.UnitTests/UnitTestApplication.cs

@@ -43,7 +43,7 @@ namespace Avalonia.UnitTests
                 .Bind<IKeyboardDevice>().ToConstant(Services.KeyboardDevice?.Invoke())
                 .Bind<IKeyboardDevice>().ToConstant(Services.KeyboardDevice?.Invoke())
                 .Bind<ILayoutManager>().ToConstant(Services.LayoutManager)
                 .Bind<ILayoutManager>().ToConstant(Services.LayoutManager)
                 .Bind<IRuntimePlatform>().ToConstant(Services.Platform)
                 .Bind<IRuntimePlatform>().ToConstant(Services.Platform)
-                .Bind<IRenderer>().ToConstant(Services.Renderer)
+                .Bind<IRendererFactory>().ToConstant(new RendererFactory(Services.Renderer))
                 .Bind<IPlatformRenderInterface>().ToConstant(Services.RenderInterface)
                 .Bind<IPlatformRenderInterface>().ToConstant(Services.RenderInterface)
                 .Bind<IRenderLoop>().ToConstant(Services.RenderLoop)
                 .Bind<IRenderLoop>().ToConstant(Services.RenderLoop)
                 .Bind<IPlatformThreadingInterface>().ToConstant(Services.ThreadingInterface)
                 .Bind<IPlatformThreadingInterface>().ToConstant(Services.ThreadingInterface)
@@ -58,5 +58,20 @@ namespace Avalonia.UnitTests
                 Styles.AddRange(styles);
                 Styles.AddRange(styles);
             }
             }
         }
         }
+
+        private class RendererFactory : IRendererFactory
+        {
+            Func<IRenderRoot, IRenderLoop, IRenderer> _func;
+
+            public RendererFactory(Func<IRenderRoot, IRenderLoop, IRenderer> func)
+            {
+                _func = func;
+            }
+
+            public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop)
+            {
+                return _func?.Invoke(root, renderLoop);
+            }
+        }
     }
     }
 }
 }

+ 4 - 3
tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.v2.ncrunchproject

@@ -17,10 +17,11 @@
   <DetectStackOverflow>true</DetectStackOverflow>
   <DetectStackOverflow>true</DetectStackOverflow>
   <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
   <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
   <DefaultTestTimeout>60000</DefaultTestTimeout>
   <DefaultTestTimeout>60000</DefaultTestTimeout>
-  <UseBuildConfiguration />
-  <UseBuildPlatform />
-  <ProxyProcessPath />
+  <UseBuildConfiguration></UseBuildConfiguration>
+  <UseBuildPlatform></UseBuildPlatform>
+  <ProxyProcessPath></ProxyProcessPath>
   <UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
   <UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
   <MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
   <MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
   <BuildProcessArchitecture>x86</BuildProcessArchitecture>
   <BuildProcessArchitecture>x86</BuildProcessArchitecture>
+  <HiddenWarnings>AbnormalReferenceResolution;LongTestTimesWithoutParallelExecution</HiddenWarnings>
 </ProjectConfiguration>
 </ProjectConfiguration>

+ 66 - 0
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs

@@ -1,8 +1,10 @@
 using System;
 using System;
+using System.Linq;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Media;
 using Avalonia.Media;
 using Avalonia.Rendering.SceneGraph;
 using Avalonia.Rendering.SceneGraph;
 using Avalonia.UnitTests;
 using Avalonia.UnitTests;
+using Avalonia.VisualTree;
 using Xunit;
 using Xunit;
 
 
 namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
 namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
@@ -52,6 +54,70 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
             }
             }
         }
         }
 
 
+        [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 = SceneBuilder.Update(new Scene(tree));
+
+                var panelNode = result.FindNode(tree.Child);
+                var expected = new IVisual[] { back, front };
+                var actual = panelNode.Children.OfType<IVisualNode>().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
+                        {
+                            Margin = new Thickness(26, 24),
+                            Width = 100,
+                            Height = 100,
+                        }
+                    }
+                };
+
+                tree.Measure(Size.Infinity);
+                tree.Arrange(new Rect(tree.DesiredSize));
+
+                var result = SceneBuilder.Update(new Scene(tree));
+                var targetNode = result.FindNode(target);
+
+                Assert.Equal(new Rect(50, 50, 100, 100), targetNode.ClipBounds);
+            }
+        }
+
         [Fact]
         [Fact]
         public void Should_Update_Border_Background_Node()
         public void Should_Update_Border_Background_Node()
         {
         {

+ 139 - 111
tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensionsTests_GetVisualsAt.cs

@@ -11,6 +11,7 @@ using Avalonia.UnitTests;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
 using Moq;
 using Moq;
 using Xunit;
 using Xunit;
+using System;
 
 
 namespace Avalonia.Visuals.UnitTests.VisualTree
 namespace Avalonia.Visuals.UnitTests.VisualTree
 {
 {
@@ -19,9 +20,9 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
         [Fact]
         [Fact]
         public void GetVisualsAt_Should_Find_Controls_At_Point()
         public void GetVisualsAt_Should_Find_Controls_At_Point()
         {
         {
-            using (var application = UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
+            using (TestApplication())
             {
             {
-                var container = new Decorator
+                var container = new TestRoot
                 {
                 {
                     Width = 200,
                     Width = 200,
                     Height = 200,
                     Height = 200,
@@ -49,9 +50,9 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
         [Fact]
         [Fact]
         public void GetVisualsAt_Should_Not_Find_Invisible_Controls_At_Point()
         public void GetVisualsAt_Should_Not_Find_Invisible_Controls_At_Point()
         {
         {
-            using (var application = UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
+            using (TestApplication())
             {
             {
-                var container = new Decorator
+                var container = new TestRoot
                 {
                 {
                     Width = 200,
                     Width = 200,
                     Height = 200,
                     Height = 200,
@@ -85,9 +86,9 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
         [Fact]
         [Fact]
         public void GetVisualsAt_Should_Not_Find_Control_Outside_Point()
         public void GetVisualsAt_Should_Not_Find_Control_Outside_Point()
         {
         {
-            using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
+            using (TestApplication())
             {
             {
-                var container = new Decorator
+                var container = new TestRoot
                 {
                 {
                     Width = 200,
                     Width = 200,
                     Height = 200,
                     Height = 200,
@@ -115,27 +116,31 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
         [Fact]
         [Fact]
         public void GetVisualsAt_Should_Return_Top_Controls_First()
         public void GetVisualsAt_Should_Return_Top_Controls_First()
         {
         {
-            using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
+            using (TestApplication())
             {
             {
-                var container = new Panel
+                Panel container;
+                var root = new TestRoot
                 {
                 {
-                    Width = 200,
-                    Height = 200,
-                    Children = new Controls.Controls
+                    Child = container = new Panel
                     {
                     {
-                        new Border
-                        {
-                            Width = 100,
-                            Height = 100,
-                            HorizontalAlignment = HorizontalAlignment.Center,
-                            VerticalAlignment = VerticalAlignment.Center
-                        },
-                        new Border
+                        Width = 200,
+                        Height = 200,
+                        Children = new Controls.Controls
                         {
                         {
-                            Width = 50,
-                            Height = 50,
-                            HorizontalAlignment = HorizontalAlignment.Center,
-                            VerticalAlignment = VerticalAlignment.Center
+                            new Border
+                            {
+                                Width = 100,
+                                Height = 100,
+                                HorizontalAlignment = HorizontalAlignment.Center,
+                                VerticalAlignment = VerticalAlignment.Center
+                            },
+                            new Border
+                            {
+                                Width = 50,
+                                Height = 50,
+                                HorizontalAlignment = HorizontalAlignment.Center,
+                                VerticalAlignment = VerticalAlignment.Center
+                            }
                         }
                         }
                     }
                     }
                 };
                 };
@@ -155,36 +160,40 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
         [Fact]
         [Fact]
         public void GetVisualsAt_Should_Return_Top_Controls_First_With_ZIndex()
         public void GetVisualsAt_Should_Return_Top_Controls_First_With_ZIndex()
         {
         {
-            using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
+            using (TestApplication())
             {
             {
-                var container = new Panel
+                Panel container;
+                var root = new TestRoot
                 {
                 {
-                    Width = 200,
-                    Height = 200,
-                    Children = new Controls.Controls
+                    Child = container = new Panel
                     {
                     {
-                        new Border
+                        Width = 200,
+                        Height = 200,
+                        Children = new Controls.Controls
                         {
                         {
-                            Width = 100,
-                            Height = 100,
-                            ZIndex = 1,
-                            HorizontalAlignment = HorizontalAlignment.Center,
-                            VerticalAlignment = VerticalAlignment.Center
-                        },
-                        new Border
-                        {
-                            Width = 50,
-                            Height = 50,
-                            HorizontalAlignment = HorizontalAlignment.Center,
-                            VerticalAlignment = VerticalAlignment.Center
-                        },
-                        new Border
-                        {
-                            Width = 75,
-                            Height = 75,
-                            ZIndex = 2,
-                            HorizontalAlignment = HorizontalAlignment.Center,
-                            VerticalAlignment = VerticalAlignment.Center
+                            new Border
+                            {
+                                Width = 100,
+                                Height = 100,
+                                ZIndex = 1,
+                                HorizontalAlignment = HorizontalAlignment.Center,
+                                VerticalAlignment = VerticalAlignment.Center
+                            },
+                            new Border
+                            {
+                                Width = 50,
+                                Height = 50,
+                                HorizontalAlignment = HorizontalAlignment.Center,
+                                VerticalAlignment = VerticalAlignment.Center
+                            },
+                            new Border
+                            {
+                                Width = 75,
+                                Height = 75,
+                                ZIndex = 2,
+                                HorizontalAlignment = HorizontalAlignment.Center,
+                                VerticalAlignment = VerticalAlignment.Center
+                            }
                         }
                         }
                     }
                     }
                 };
                 };
@@ -204,32 +213,36 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
         [Fact]
         [Fact]
         public void GetVisualsAt_Should_Find_Control_Translated_Outside_Parent_Bounds()
         public void GetVisualsAt_Should_Find_Control_Translated_Outside_Parent_Bounds()
         {
         {
-            using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
+            using (TestApplication())
             {
             {
                 Border target;
                 Border target;
-                var container = new Panel
+                Panel container;
+                var root = new TestRoot
                 {
                 {
-                    Width = 200,
-                    Height = 200,
-                    ClipToBounds = false,
-                    Children = new Controls.Controls
+                    Child = container = new Panel
                     {
                     {
-                        new Border
+                        Width = 200,
+                        Height = 200,
+                        ClipToBounds = false,
+                        Children = new Controls.Controls
                         {
                         {
-                            Width = 100,
-                            Height = 100,
-                            ZIndex = 1,
-                            HorizontalAlignment = HorizontalAlignment.Left,
-                            VerticalAlignment = VerticalAlignment.Top,
-                            Child = target = new Border
+                            new Border
                             {
                             {
-                                Width = 50,
-                                Height = 50,
+                                Width = 100,
+                                Height = 100,
+                                ZIndex = 1,
                                 HorizontalAlignment = HorizontalAlignment.Left,
                                 HorizontalAlignment = HorizontalAlignment.Left,
                                 VerticalAlignment = VerticalAlignment.Top,
                                 VerticalAlignment = VerticalAlignment.Top,
-                                RenderTransform = new TranslateTransform(110, 110),
-                            }
-                        },
+                                Child = target = new Border
+                                {
+                                    Width = 50,
+                                    Height = 50,
+                                    HorizontalAlignment = HorizontalAlignment.Left,
+                                    VerticalAlignment = VerticalAlignment.Top,
+                                    RenderTransform = new TranslateTransform(110, 110),
+                                }
+                            },
+                        }
                     }
                     }
                 };
                 };
 
 
@@ -248,30 +261,33 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
         [Fact]
         [Fact]
         public void GetVisualsAt_Should_Not_Find_Control_Outside_Parent_Bounds_When_Clipped()
         public void GetVisualsAt_Should_Not_Find_Control_Outside_Parent_Bounds_When_Clipped()
         {
         {
-            using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
+            using (TestApplication())
             {
             {
                 Border target;
                 Border target;
-
-                var container = new Panel
+                Panel container;
+                var root = new TestRoot
                 {
                 {
-                    Width = 100,
-                    Height = 200,
-                    Children = new Controls.Controls
+                    Child = container = new Panel
                     {
                     {
-                        new Panel()
+                        Width = 100,
+                        Height = 200,
+                        Children = new Controls.Controls
                         {
                         {
-                            Width = 100,
-                            Height = 100,
-                            Margin = new Thickness(0, 100, 0, 0),
-                            ClipToBounds = true,
-                            Children = new Controls.Controls
+                            new Panel()
                             {
                             {
-                                (target = new Border()
+                                Width = 100,
+                                Height = 100,
+                                Margin = new Thickness(0, 100, 0, 0),
+                                ClipToBounds = true,
+                                Children = new Controls.Controls
                                 {
                                 {
-                                    Width = 100,
-                                    Height = 100,
-                                    Margin = new Thickness(0, -100, 0, 0)
-                                })
+                                    (target = new Border()
+                                    {
+                                        Width = 100,
+                                        Height = 100,
+                                        Margin = new Thickness(0, -100, 0, 0)
+                                    })
+                                }
                             }
                             }
                         }
                         }
                     }
                     }
@@ -292,45 +308,48 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
         [Fact]
         [Fact]
         public void GetVisualsAt_Should_Not_Find_Control_Outside_Scroll_Viewport()
         public void GetVisualsAt_Should_Not_Find_Control_Outside_Scroll_Viewport()
         {
         {
-            using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
+            using (TestApplication())
             {
             {
                 Border target;
                 Border target;
                 Border item1;
                 Border item1;
                 Border item2;
                 Border item2;
                 ScrollContentPresenter scroll;
                 ScrollContentPresenter scroll;
-
-                var container = new Panel
+                Panel container;
+                var root = new TestRoot
                 {
                 {
-                    Width = 100,
-                    Height = 200,
-                    Children = new Controls.Controls
+                    Child = container = new Panel
                     {
                     {
-                        (target = new Border()
-                        {
-                            Width = 100,
-                            Height = 100
-                        }),
-                        new Border()
+                        Width = 100,
+                        Height = 200,
+                        Children = new Controls.Controls
                         {
                         {
-                            Width = 100,
-                            Height = 100,
-                            Margin = new Thickness(0, 100, 0, 0),
-                            Child = scroll = new ScrollContentPresenter()
+                            (target = new Border()
                             {
                             {
-                                Content = new StackPanel()
+                                Width = 100,
+                                Height = 100
+                            }),
+                            new Border()
+                            {
+                                Width = 100,
+                                Height = 100,
+                                Margin = new Thickness(0, 100, 0, 0),
+                                Child = scroll = new ScrollContentPresenter()
                                 {
                                 {
-                                    Children = new Controls.Controls
+                                    Content = new StackPanel()
                                     {
                                     {
-                                        (item1 = new Border()
-                                        {
-                                            Width = 100,
-                                            Height = 100,
-                                        }),
-                                        (item2 = new Border()
+                                        Children = new Controls.Controls
                                         {
                                         {
-                                            Width = 100,
-                                            Height = 100,
-                                        }),
+                                            (item1 = new Border()
+                                            {
+                                                Width = 100,
+                                                Height = 100,
+                                            }),
+                                            (item2 = new Border()
+                                            {
+                                                Width = 100,
+                                                Height = 100,
+                                            }),
+                                        }
                                     }
                                     }
                                 }
                                 }
                             }
                             }
@@ -373,5 +392,14 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
                 Assert.Equal(target, result);
                 Assert.Equal(target, result);
             }
             }
         }
         }
+
+        private IDisposable TestApplication()
+        {
+            return UnitTestApplication.Start(
+                new TestServices(
+                    renderInterface: new MockRenderInterface(),
+                    renderLoop: Mock.Of<IRenderLoop>(),
+                    renderer: (root, loop) => new DeferredRenderer(root, loop)));
+        }
     }
     }
 }
 }