diff --git a/src/Avalonia.Visuals/Avalonia.Visuals.csproj b/src/Avalonia.Visuals/Avalonia.Visuals.csproj
index f95097d1b1..01b1da43e7 100644
--- a/src/Avalonia.Visuals/Avalonia.Visuals.csproj
+++ b/src/Avalonia.Visuals/Avalonia.Visuals.csproj
@@ -108,6 +108,7 @@
+
diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
index e29eab0d41..8a472a51d7 100644
--- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
+++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
@@ -20,7 +20,7 @@ namespace Avalonia.Rendering
private Scene _scene;
private IRenderTarget _renderTarget;
- private HashSet _dirty;
+ private DirtyVisuals _dirty;
private IRenderTargetBitmapImpl _overlay;
private bool _updateQueued;
private bool _rendering;
@@ -268,7 +268,7 @@ namespace Avalonia.Rendering
if (_dirty == null)
{
- _dirty = new HashSet();
+ _dirty = new DirtyVisuals();
_sceneBuilder.UpdateAll(scene);
}
else if (_dirty.Count > 0)
diff --git a/src/Avalonia.Visuals/Rendering/DirtyVisuals.cs b/src/Avalonia.Visuals/Rendering/DirtyVisuals.cs
new file mode 100644
index 0000000000..3eddb7e94d
--- /dev/null
+++ b/src/Avalonia.Visuals/Rendering/DirtyVisuals.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Rendering
+{
+ internal class DirtyVisuals : IEnumerable
+ {
+ private SortedDictionary> _inner = new SortedDictionary>();
+ private Dictionary _index = new Dictionary();
+
+ public int Count => _index.Count;
+
+ public void Add(IVisual visual)
+ {
+ var distance = visual.CalculateDistanceFromVisualRoot();
+ int existingDistance;
+
+ if (_index.TryGetValue(visual, out existingDistance))
+ {
+ if (distance == existingDistance)
+ {
+ return;
+ }
+
+ _inner[existingDistance].Remove(visual);
+ _index.Remove(visual);
+ }
+
+ List list;
+
+ if (!_inner.TryGetValue(distance, out list))
+ {
+ list = new List();
+ _inner.Add(distance, list);
+ }
+
+ list.Add(visual);
+ _index.Add(visual, distance);
+ }
+
+ public void Clear()
+ {
+ _inner.Clear();
+ _index.Clear();
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ foreach (var i in _inner)
+ {
+ foreach (var j in i.Value)
+ {
+ yield return j;
+ }
+ }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ }
+}
diff --git a/src/Avalonia.Visuals/Rendering/RenderLayer.cs b/src/Avalonia.Visuals/Rendering/RenderLayer.cs
index 87624864d4..9c2f9d4736 100644
--- a/src/Avalonia.Visuals/Rendering/RenderLayer.cs
+++ b/src/Avalonia.Visuals/Rendering/RenderLayer.cs
@@ -18,13 +18,11 @@ namespace Avalonia.Rendering
Bitmap = factory.CreateLayer(layerRoot, size);
Size = size;
LayerRoot = layerRoot;
- Order = GetDistanceFromRenderRoot(layerRoot);
}
public IRenderTargetBitmapImpl Bitmap { get; private set; }
public Size Size { get; private set; }
public IVisual LayerRoot { get; }
- public int Order { get; }
public void ResizeBitmap(Size size)
{
@@ -42,26 +40,5 @@ namespace Avalonia.Rendering
}
}
}
-
- private static int GetDistanceFromRenderRoot(IVisual visual)
- {
- var root = visual as IRenderRoot;
- var result = 0;
-
- while (root == null)
- {
- ++result;
- visual = visual.VisualParent;
-
- if (visual == null)
- {
- throw new AvaloniaInternalException("Visual is not rooted.");
- }
-
- root = visual as IRenderRoot;
- }
-
- return result;
- }
}
}
diff --git a/src/Avalonia.Visuals/Rendering/RenderLayers.cs b/src/Avalonia.Visuals/Rendering/RenderLayers.cs
index e406990cfb..3173247180 100644
--- a/src/Avalonia.Visuals/Rendering/RenderLayers.cs
+++ b/src/Avalonia.Visuals/Rendering/RenderLayers.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
-using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
using Avalonia.VisualTree;
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs
index 295fab9791..564879ad9e 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs
@@ -9,7 +9,7 @@ namespace Avalonia.Rendering.SceneGraph
{
LayerRoot = layerRoot;
Dirty = new DirtyRects();
- DistanceFromRoot = CalculateDistanceFromRoot(layerRoot);
+ DistanceFromRoot = layerRoot.CalculateDistanceFromVisualRoot();
}
internal SceneLayer(IVisual layerRoot, int distanceFromRoot)
@@ -31,25 +31,5 @@ namespace Avalonia.Rendering.SceneGraph
public DirtyRects Dirty { get; }
public int DistanceFromRoot { get; }
public double Opacity { get; set; } = 1;
-
- private int CalculateDistanceFromRoot(IVisual visual)
- {
- var result = 0;
-
- while (!(visual is IRenderRoot))
- {
- visual = visual.VisualParent;
-
- if (visual == null)
- {
- throw new AvaloniaInternalException(
- "Attempted to create a SceneLayer for an unrooted visual.");
- }
-
- ++result;
- }
-
- return result;
- }
}
}
diff --git a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs
index 6eaef3363d..bedabf2292 100644
--- a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs
+++ b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs
@@ -13,6 +13,34 @@ namespace Avalonia.VisualTree
///
public static class VisualExtensions
{
+ ///
+ /// Calculates the distance from a visual's .
+ ///
+ /// The visual.
+ /// The number of steps from the visual to the render root.
+ /// The visual is not rooted.
+ public static int CalculateDistanceFromVisualRoot(this IVisual visual)
+ {
+ Contract.Requires(visual != null);
+
+ var root = visual.VisualRoot;
+
+ if (root == null)
+ {
+ throw new ArgumentException("Visual is not rooted.");
+ }
+
+ var result = 0;
+
+ while (visual != root)
+ {
+ ++result;
+ visual = visual.VisualParent;
+ }
+
+ return result;
+ }
+
///
/// Tries to get the first common ancestor of two visuals.
///
@@ -21,6 +49,8 @@ namespace Avalonia.VisualTree
/// The common ancestor, or null if not found.
public static IVisual FindCommonVisualAncestor(this IVisual visual, IVisual target)
{
+ Contract.Requires(visual != null);
+
return visual.GetSelfAndVisualAncestors().Intersect(target.GetSelfAndVisualAncestors())
.FirstOrDefault();
}
@@ -50,6 +80,8 @@ namespace Avalonia.VisualTree
/// The visual and its ancestors.
public static IEnumerable GetSelfAndVisualAncestors(this IVisual visual)
{
+ Contract.Requires(visual != null);
+
yield return visual;
foreach (var ancestor in visual.GetVisualAncestors())
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
index 86ae7f016b..5a4435238d 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
@@ -82,14 +82,26 @@ namespace Avalonia.Visuals.UnitTests.Rendering
}
[Fact]
- public void Frame_Should_Call_SceneBuilder_Update_With_Dirty_Controls()
+ public void Should_Update_Dirty_Controls_In_Order()
{
var loop = new Mock();
- var root = new TestRoot();
- var sceneBuilder = MockSceneBuilder(root);
var dispatcher = new ImmediateDispatcher();
- var control1 = new Border();
- var control2 = new Canvas();
+
+ 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,
@@ -98,12 +110,18 @@ namespace Avalonia.Visuals.UnitTests.Rendering
dispatcher: dispatcher);
IgnoreFirstFrame(loop, sceneBuilder);
- target.AddDirty(control1);
- target.AddDirty(control2);
+ 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(loop);
- sceneBuilder.Verify(x => x.Update(It.IsAny(), control1));
- sceneBuilder.Verify(x => x.Update(It.IsAny(), control2));
+ Assert.Equal(new List { root, decorator, border, canvas }, result);
}
[Fact]