diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs
index aee6a02e73..671032385c 100644
--- a/src/Avalonia.Base/Threading/Dispatcher.cs
+++ b/src/Avalonia.Base/Threading/Dispatcher.cs
@@ -79,6 +79,8 @@ namespace Avalonia.Threading
///
/// The method.
/// The priority with which to invoke the method.
+ // TODO: The naming of this method is confusing: the Async suffix usually means return a task.
+ // Remove this and rename InvokeTaskAsync as InvokeAsync.
public void InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
_jobRunner?.Post(action, priority);
diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
index fd133809c5..0ad0599b18 100644
--- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
+++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
@@ -1,6 +1,5 @@
using System;
using System.Diagnostics;
-using System.Linq;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
@@ -16,6 +15,7 @@ namespace Avalonia.Rendering
private readonly IRenderRoot _root;
private Scene _scene;
private IRenderTarget _renderTarget;
+ private List _dirty = new List();
private bool _needsUpdate;
private bool _needsRender;
@@ -39,11 +39,14 @@ namespace Avalonia.Rendering
public void AddDirty(IVisual visual)
{
- if (!_needsUpdate)
+ // If the root of the scene has no children, then the scene is being set up; don't
+ // bother filling the dirty list with every control in the window.
+ if (_scene.Root.Children.Count > 0)
{
- _needsUpdate = true;
- Dispatcher.UIThread.InvokeAsync(UpdateScene, DispatcherPriority.Render);
+ _dirty.Add(visual);
}
+
+ _needsUpdate = true;
}
public void Dispose()
@@ -63,31 +66,6 @@ namespace Avalonia.Rendering
public void Render(Rect rect)
{
- if (_renderTarget == null)
- {
- _renderTarget = _root.CreateRenderTarget();
- }
-
- try
- {
- _totalFrames++;
-
- using (var context = _renderTarget.CreateDrawingContext())
- {
- _scene.Root.Render(context);
-
- if (DrawFps)
- {
- RenderFps(context);
- }
- }
- }
- catch (RenderTargetCorruptedException ex)
- {
- Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
- _renderTarget.Dispose();
- _renderTarget = null;
- }
}
private void RenderFps(IDrawingContextImpl context)
@@ -122,7 +100,22 @@ namespace Avalonia.Rendering
{
Dispatcher.UIThread.VerifyAccess();
- _scene = SceneBuilder.Update(_scene);
+ var scene = _scene.Clone();
+
+ if (_dirty.Count > 0)
+ {
+ foreach (var visual in _dirty)
+ {
+ SceneBuilder.Update(scene, visual);
+ }
+ }
+ else
+ {
+ SceneBuilder.UpdateAll(scene);
+ }
+
+ _scene = scene;
+
_needsUpdate = false;
_needsRender = true;
_root.Invalidate(new Rect(_root.ClientSize));
@@ -130,10 +123,39 @@ namespace Avalonia.Rendering
private void OnRenderLoopTick(object sender, EventArgs e)
{
- //if (_needsRender)
- //{
- // _needsRender = false;
- //}
+ if (_needsUpdate)
+ {
+ Dispatcher.UIThread.InvokeAsync(UpdateScene, DispatcherPriority.Render);
+ }
+
+ if (_needsRender)
+ {
+ if (_renderTarget == null)
+ {
+ _renderTarget = _root.CreateRenderTarget();
+ }
+
+ try
+ {
+ _totalFrames++;
+
+ using (var context = _renderTarget.CreateDrawingContext())
+ {
+ _scene.Root.Render(context);
+
+ if (DrawFps)
+ {
+ RenderFps(context);
+ }
+ }
+ }
+ catch (RenderTargetCorruptedException ex)
+ {
+ Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
+ _renderTarget.Dispose();
+ _renderTarget = null;
+ }
+ }
}
}
}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
index 5717ccdf0e..7e4dee81a4 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Reactive.Disposables;
using Avalonia.Media;
using Avalonia.Platform;
@@ -25,30 +26,39 @@ namespace Avalonia.Rendering.SceneGraph
}
public IDisposable Begin(VisualNode node)
- {
- _stack.Push(new Frame(node));
- return Disposable.Create(Pop);
- }
-
- public void Dispose()
- {
- }
-
- public void AddChild(IVisualNode visualNode)
{
if (_stack.Count > 0)
{
var next = NextNodeAs();
- if (next == null || next != visualNode)
+ if (next == null || next != node)
{
- Add(visualNode);
+ Add(node);
}
else
{
++Index;
}
}
+
+ _stack.Push(new Frame(node));
+ return Disposable.Create(Pop);
+ }
+
+ public void Dispose()
+ {
+ }
+
+ public void TrimNodes()
+ {
+ var frame = _stack.Peek();
+ var children = frame.Node.Children;
+ var index = frame.Index;
+
+ if (children.Count > index)
+ {
+ children.RemoveRange(index, children.Count - index);
+ }
}
public void DrawGeometry(IBrush brush, Pen pen, IGeometryImpl geometry)
@@ -196,17 +206,7 @@ namespace Avalonia.Rendering.SceneGraph
return Index < Node.Children.Count ? Node.Children[Index] as T : null;
}
- private void Pop()
- {
- var frame = _stack.Pop();
- var children = frame.Node.Children;
- var index = frame.Index;
-
- if (children.Count > index)
- {
- children.RemoveRange(index, children.Count - index);
- }
- }
+ private void Pop() => _stack.Pop();
class Frame
{
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
index 7850100804..a402af100d 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
@@ -10,8 +10,10 @@ namespace Avalonia.Rendering.SceneGraph
public interface IVisualNode : ISceneNode
{
IVisual Visual { get; }
- Rect ClipBounds { get; set; }
- bool ClipToBounds { get; set; }
+ IVisualNode Parent { get; }
+ Matrix Transform { get; }
+ Rect ClipBounds { get; }
+ bool ClipToBounds { get; }
IReadOnlyList Children { get; }
bool HitTest(Point p);
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
index 12a9a45f43..47804d95e6 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
@@ -12,8 +12,9 @@ namespace Avalonia.Rendering.SceneGraph
private Dictionary _index;
public Scene(IVisual rootVisual)
- : this(new VisualNode(rootVisual), new Dictionary())
+ : this(new VisualNode(rootVisual, null), new Dictionary())
{
+ _index.Add(rootVisual, Root);
}
internal Scene(VisualNode root, Dictionary index)
@@ -28,6 +29,8 @@ namespace Avalonia.Rendering.SceneGraph
public void Add(IVisualNode node)
{
+ Contract.Requires(node != null);
+
_index.Add(node.Visual, node);
}
@@ -51,9 +54,16 @@ namespace Avalonia.Rendering.SceneGraph
return HitTest(Root, p, null, filter);
}
- private VisualNode Clone(VisualNode source, ISceneNode parent, Dictionary index)
+ public void Remove(IVisualNode node)
+ {
+ Contract.Requires(node != null);
+
+ _index.Remove(node.Visual);
+ }
+
+ private VisualNode Clone(VisualNode source, IVisualNode parent, Dictionary index)
{
- var result = source.Clone();
+ var result = source.Clone(parent);
index.Add(result.Visual, result);
@@ -99,8 +109,6 @@ namespace Avalonia.Rendering.SceneGraph
}
}
- dynamic d = node.Visual;
-
if (node.HitTest(p))
{
yield return node.Visual;
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
index 4749c99925..46ea25626f 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
@@ -11,32 +11,113 @@ namespace Avalonia.Rendering.SceneGraph
{
public static class SceneBuilder
{
- public static Scene Update(Scene scene)
+ public static void UpdateAll(Scene scene)
{
Dispatcher.UIThread.VerifyAccess();
- scene = scene.Clone();
-
using (var impl = new DeferredDrawingContextImpl())
using (var context = new DrawingContext(impl))
{
- Update(context, scene, scene.Root.Visual, null);
+ Update(context, scene, (VisualNode)scene.Root, true);
}
+ }
+
+ public static bool Update(Scene scene, IVisual visual)
+ {
+ Dispatcher.UIThread.VerifyAccess();
+
+ var node = (VisualNode)scene.FindNode(visual);
+
+ if (visual.VisualRoot != null)
+ {
+ 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())
+ using (var context = new DrawingContext(impl))
+ {
+ if (node.Parent != null)
+ {
+ context.PushPostTransform(node.Parent.Transform);
+ }
+
+ Update(context, scene, (VisualNode)node, recurse);
+ }
- return scene;
+ return true;
+ }
+ }
+ else
+ {
+ if (node != null)
+ {
+ // The control has been removed so remove it from its parent and deindex the
+ // node and its descendents.
+ ((VisualNode)node.Parent)?.Children.Remove(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).Children.Remove(trim);
+ Deindex(scene, trim);
+ return true;
+ }
+
+ return false;
}
- private static void Update(DrawingContext context, Scene scene, IVisual visual, VisualNode parent)
+ private static VisualNode FindExistingAncestor(Scene scene, IVisual visual)
{
+ var node = scene.FindNode(visual);
+
+ while (node == null && visual.IsVisible)
+ {
+ visual = visual.VisualParent;
+ 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 void Update(DrawingContext context, Scene scene, VisualNode node, bool forceRecurse)
+ {
+ var visual = node.Visual;
var opacity = visual.Opacity;
var clipToBounds = visual.ClipToBounds;
var bounds = new Rect(visual.Bounds.Size);
- var node = (VisualNode)scene.FindNode(visual) ?? CreateNode(visual, scene, parent);
var contextImpl = (DeferredDrawingContextImpl)context.PlatformImpl;
- contextImpl.AddChild(node);
-
- if (visual.IsVisible && opacity > 0)
+ if (visual.IsVisible)
{
var m = Matrix.CreateTranslation(visual.Bounds.Position);
@@ -55,6 +136,9 @@ namespace Avalonia.Rendering.SceneGraph
using (context.PushPostTransform(m))
using (context.PushTransformContainer())
{
+ forceRecurse = forceRecurse ||
+ node.Transform != contextImpl.Transform;
+
node.Transform = contextImpl.Transform;
node.ClipBounds = bounds * node.Transform;
node.ClipToBounds = clipToBounds;
@@ -64,19 +148,42 @@ namespace Avalonia.Rendering.SceneGraph
visual.Render(context);
- foreach (var child in visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance))
+ if (forceRecurse)
{
- Update(context, scene, child, node);
+ foreach (var child in visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance))
+ {
+ var childNode = scene.FindNode(child) ?? CreateNode(scene, child, node);
+ Update(context, scene, (VisualNode)childNode, forceRecurse);
+ }
+
+ node.SubTreeUpdated = true;
+ contextImpl.TrimNodes();
}
}
}
}
- private static VisualNode CreateNode(IVisual visual, Scene scene, VisualNode parent)
+ private static VisualNode CreateNode(Scene scene, IVisual visual, IVisualNode parent)
{
- var node = new VisualNode(visual);
+ var node = new VisualNode(visual, parent);
scene.Add(node);
return node;
}
+
+ private static void Deindex(Scene scene, VisualNode node)
+ {
+ scene.Remove(node);
+ node.SubTreeUpdated = true;
+
+ foreach (var child in node.Children)
+ {
+ var visualChild = child as VisualNode;
+
+ if (visualChild != null)
+ {
+ Deindex(scene, visualChild);
+ }
+ }
+ }
}
}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
index 98d7ceab57..1dbf30a7b9 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
@@ -10,13 +10,21 @@ namespace Avalonia.Rendering.SceneGraph
{
public class VisualNode : IVisualNode
{
- public VisualNode(IVisual visual)
+ public VisualNode(IVisual visual, IVisualNode parent)
{
- Children = new List();
+ if (parent == null && visual.VisualParent != null)
+ {
+ throw new AvaloniaInternalException(
+ "Attempted to create root VisualNode for parented visual.");
+ }
+
Visual = visual;
+ Parent = parent;
+ Children = new List();
}
public IVisual Visual { get; }
+ public IVisualNode Parent { get; }
public Matrix Transform { get; set; }
public Rect ClipBounds { get; set; }
public bool ClipToBounds { get; set; }
@@ -24,12 +32,21 @@ namespace Avalonia.Rendering.SceneGraph
public double Opacity { get; set; }
public IBrush OpacityMask { get; set; }
public List Children { get; }
+ public bool SubTreeUpdated { get; set; }
IReadOnlyList IVisualNode.Children => Children;
- public VisualNode Clone()
+ public VisualNode Clone(IVisualNode parent)
{
- return new VisualNode(Visual);
+ return new VisualNode(Visual, parent)
+ {
+ Transform = Transform,
+ ClipBounds = ClipBounds,
+ ClipToBounds = ClipToBounds,
+ GeometryClip = GeometryClip,
+ Opacity = Opacity,
+ OpacityMask = OpacityMask,
+ };
}
public bool HitTest(Point p)
diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs
index aca910b6e8..675ad042e1 100644
--- a/src/Avalonia.Visuals/Visual.cs
+++ b/src/Avalonia.Visuals/Visual.cs
@@ -88,6 +88,7 @@ namespace Avalonia
AvaloniaProperty.Register(nameof(ZIndex));
private Rect _bounds;
+ private IRenderRoot _visualRoot;
private IVisual _visualParent;
///
@@ -233,11 +234,7 @@ namespace Avalonia
///
/// Gets the root of the visual tree, if the control is attached to a visual tree.
///
- protected IRenderRoot VisualRoot
- {
- get;
- private set;
- }
+ protected IRenderRoot VisualRoot => _visualRoot ?? (this as IRenderRoot);
///
/// Gets a value indicating whether this scene graph node is attached to a visual root.
@@ -326,7 +323,7 @@ namespace Avalonia
{
Logger.Verbose(LogArea.Visual, this, "Attached to visual tree");
- VisualRoot = e.Root;
+ _visualRoot = e.Root;
if (RenderTransform != null)
{
@@ -334,6 +331,7 @@ namespace Avalonia
}
OnAttachedToVisualTree(e);
+ InvalidateVisual();
if (VisualChildren != null)
{
@@ -353,7 +351,7 @@ namespace Avalonia
{
Logger.Verbose(LogArea.Visual, this, "Detached from visual tree");
- VisualRoot = null;
+ _visualRoot = null;
if (RenderTransform != null)
{
@@ -361,6 +359,7 @@ namespace Avalonia
}
OnDetachedFromVisualTree(e);
+ e.Root?.Renderer?.AddDirty(this);
if (VisualChildren != null)
{
@@ -501,16 +500,16 @@ namespace Avalonia
var old = _visualParent;
_visualParent = value;
- if (VisualRoot != null)
+ if (_visualRoot != null)
{
- var e = new VisualTreeAttachmentEventArgs(VisualRoot);
+ var e = new VisualTreeAttachmentEventArgs(old, VisualRoot);
OnDetachedFromVisualTreeCore(e);
}
if (_visualParent is IRenderRoot || _visualParent?.IsAttachedToVisualTree == true)
{
var root = this.GetVisualAncestors().OfType().FirstOrDefault();
- var e = new VisualTreeAttachmentEventArgs(root);
+ var e = new VisualTreeAttachmentEventArgs(_visualParent, root);
OnAttachedToVisualTreeCore(e);
}
diff --git a/src/Avalonia.Visuals/VisualTreeAttachmentEventArgs.cs b/src/Avalonia.Visuals/VisualTreeAttachmentEventArgs.cs
index 7a3b97f55b..8c6e660d10 100644
--- a/src/Avalonia.Visuals/VisualTreeAttachmentEventArgs.cs
+++ b/src/Avalonia.Visuals/VisualTreeAttachmentEventArgs.cs
@@ -3,6 +3,7 @@
using System;
using Avalonia.Rendering;
+using Avalonia.VisualTree;
namespace Avalonia
{
@@ -15,14 +16,22 @@ namespace Avalonia
///
/// Initializes a new instance of the class.
///
+ /// The parent that the visual is being attached to or detached from.
/// The root visual.
- public VisualTreeAttachmentEventArgs(IRenderRoot root)
+ public VisualTreeAttachmentEventArgs(IVisual parent, IRenderRoot root)
{
+ Contract.Requires(parent != null);
Contract.Requires(root != null);
+ Parent = parent;
Root = root;
}
+ ///
+ /// Gets the parent that the visual is being attached to or detached from.
+ ///
+ public IVisual Parent { get; }
+
///
/// Gets the root of the visual tree that the visual is being attached to or detached from.
///
diff --git a/tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.v2.ncrunchproject b/tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.v2.ncrunchproject
index 7c25da7d15..0c8a7ff2ac 100644
--- a/tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.v2.ncrunchproject
+++ b/tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.v2.ncrunchproject
@@ -7,7 +7,7 @@
true
false
false
- true
+ false
false
false
true
@@ -23,5 +23,8 @@
AutoDetect
STA
x86
+
+
+
AbnormalReferenceResolution
\ No newline at end of file
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
index 97630d5632..f52efc10bb 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
@@ -6,6 +6,7 @@ using Avalonia.Rendering.SceneGraph;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Xunit;
+using Avalonia.Layout;
namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
{
@@ -35,10 +36,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
tree.Measure(Size.Infinity);
tree.Arrange(new Rect(tree.DesiredSize));
- var initial = new Scene(tree);
- var result = SceneBuilder.Update(initial);
+ var result = new Scene(tree);
+ SceneBuilder.UpdateAll(result);
- Assert.NotSame(initial, result);
Assert.Equal(1, result.Root.Children.Count);
var borderNode = (VisualNode)result.Root.Children[0];
@@ -59,6 +59,41 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
}
}
+ [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(),
+ }
+ };
+
+ tree.Measure(Size.Infinity);
+ tree.Arrange(new Rect(tree.DesiredSize));
+
+ var result = new Scene(tree);
+ 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.Clone();
+ Assert.True(SceneBuilder.Update(result, canvas));
+
+ canvasNode = result.FindNode(canvas);
+ Assert.Equal(new Rect(10, 20, 160, 240), canvasNode.ClipBounds);
+ }
+ }
+
[Fact]
public void Should_Respect_ZIndex()
{
@@ -84,7 +119,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
}
};
- var result = SceneBuilder.Update(new Scene(tree));
+ var result = new Scene(tree);
+ SceneBuilder.UpdateAll(result);
var panelNode = result.FindNode(tree.Child);
var expected = new IVisual[] { back, front };
@@ -116,7 +152,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
tree.Measure(Size.Infinity);
tree.Arrange(new Rect(tree.DesiredSize));
- var result = SceneBuilder.Update(new Scene(tree));
+ var result = new Scene(tree);
+ SceneBuilder.UpdateAll(result);
+
var targetNode = result.FindNode(target);
Assert.Equal(new Rect(50, 50, 100, 100), targetNode.ClipBounds);
@@ -148,7 +186,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
tree.Measure(Size.Infinity);
tree.Arrange(new Rect(tree.DesiredSize));
- var initial = SceneBuilder.Update(new Scene(tree));
+ var initial = new Scene(tree);
+ SceneBuilder.UpdateAll(initial);
+
var initialBackgroundNode = initial.FindNode(border).Children[0];
var initialTextNode = initial.FindNode(textBlock).Children[0];
@@ -156,9 +196,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
Assert.NotNull(initialTextNode);
border.Background = Brushes.Green;
- var result = SceneBuilder.Update(initial);
- Assert.NotSame(initial, result);
+ var result = initial.Clone();
+ SceneBuilder.Update(result, border);
+
var borderNode = (VisualNode)result.Root.Children[0];
Assert.Same(border, borderNode.Visual);
@@ -174,13 +215,64 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
}
}
+ [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);
+ SceneBuilder.UpdateAll(initial);
+
+ border.Child = decorator;
+ var result = initial.Clone();
+
+ 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(2, borderNode.Children.Count);
+
+ var decoratorNode = (VisualNode)borderNode.Children[1];
+ 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;
- TextBlock textBlock;
+ Decorator decorator;
+ Canvas canvas;
var tree = new TestRoot
{
Width = 100,
@@ -188,9 +280,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
Child = border = new Border
{
Background = Brushes.Red,
- Child = textBlock = new TextBlock
+ Child = decorator = new Decorator
{
- Text = "Hello World",
+ Child = canvas = new Canvas()
}
}
};
@@ -198,15 +290,115 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
tree.Measure(Size.Infinity);
tree.Arrange(new Rect(tree.DesiredSize));
- var initial = SceneBuilder.Update(new Scene(tree));
+ var initial = new Scene(tree);
+ SceneBuilder.UpdateAll(initial);
border.Child = null;
- var result = SceneBuilder.Update(initial);
+ var result = initial.Clone();
+
+ Assert.True(SceneBuilder.Update(result, decorator));
+ Assert.False(SceneBuilder.Update(result, canvas));
- Assert.NotSame(initial, result);
var borderNode = (VisualNode)result.Root.Children[0];
Assert.Equal(1, borderNode.Children.Count);
+
+ Assert.Null(result.FindNode(decorator));
+ }
+ }
+
+ [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);
+ SceneBuilder.UpdateAll(initial);
+
+ border.IsVisible = false;
+ var result = initial.Clone();
+
+ 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));
+ }
+ }
+
+ [Fact]
+ public void Should_Update_Descendent_Tranform_When_Margin_Changed()
+ {
+ using (TestApplication())
+ {
+ 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 = AvaloniaLocator.Current.GetService();
+ layout.ExecuteInitialLayoutPass(tree);
+
+ var scene = new Scene(tree);
+ 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.Clone();
+ 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);
}
}
+
+ private IDisposable TestApplication()
+ {
+ return UnitTestApplication.Start(
+ TestServices.MockPlatformRenderInterface.With(
+ layoutManager: new LayoutManager()));
+ }
}
}
diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTests.cs b/tests/Avalonia.Visuals.UnitTests/VisualTests.cs
index 9cec9e434b..28f0a7ad13 100644
--- a/tests/Avalonia.Visuals.UnitTests/VisualTests.cs
+++ b/tests/Avalonia.Visuals.UnitTests/VisualTests.cs
@@ -5,8 +5,10 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
+using Avalonia.Rendering;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
+using Moq;
using Xunit;
namespace Avalonia.Visuals.UnitTests
@@ -73,8 +75,19 @@ namespace Avalonia.Visuals.UnitTests
var called1 = false;
var called2 = false;
- child1.AttachedToVisualTree += (s, e) => called1 = true;
- child2.AttachedToVisualTree += (s, e) => called2 = true;
+ child1.AttachedToVisualTree += (s, e) =>
+ {
+ Assert.Equal(e.Parent, root);
+ Assert.Equal(e.Root, root);
+ called1 = true;
+ };
+
+ child2.AttachedToVisualTree += (s, e) =>
+ {
+ Assert.Equal(e.Parent, root);
+ Assert.Equal(e.Root, root);
+ called2 = true;
+ };
root.Child = child1;
@@ -92,14 +105,83 @@ namespace Avalonia.Visuals.UnitTests
var called2 = false;
root.Child = child1;
- child1.DetachedFromVisualTree += (s, e) => called1 = true;
- child2.DetachedFromVisualTree += (s, e) => called2 = true;
+
+ child1.DetachedFromVisualTree += (s, e) =>
+ {
+ Assert.Equal(e.Parent, root);
+ Assert.Equal(e.Root, root);
+ called1 = true;
+ };
+
+ child2.DetachedFromVisualTree += (s, e) =>
+ {
+ Assert.Equal(e.Parent, root);
+ Assert.Equal(e.Root, root);
+ called2 = true;
+ };
+
root.Child = null;
Assert.True(called1);
Assert.True(called2);
}
+ [Fact]
+ public void Root_Should_Retun_Self_As_VisualRoot()
+ {
+ var root = new TestRoot();
+
+ Assert.Same(root, ((IVisual)root).VisualRoot);
+ }
+
+ [Fact]
+ public void Descendents_Should_RetunVisualRoot()
+ {
+ var root = new TestRoot();
+ var child1 = new Decorator();
+ var child2 = new Decorator();
+
+ root.Child = child1;
+ child1.Child = child2;
+
+ Assert.Same(root, ((IVisual)child1).VisualRoot);
+ Assert.Same(root, ((IVisual)child2).VisualRoot);
+ }
+
+ [Fact]
+ public void Attaching_To_Visual_Tree_Should_Invalidate_Visual()
+ {
+ var renderer = new Mock();
+
+ using (UnitTestApplication.Start(new TestServices(renderer: (root, loop) => renderer.Object)))
+ {
+ var child = new Decorator();
+ var root = new TestRoot();
+
+ root.Child = child;
+
+ renderer.Verify(x => x.AddDirty(child));
+ }
+ }
+
+ [Fact]
+ public void Detaching_From_Visual_Tree_Should_Invalidate_Visual()
+ {
+ var renderer = new Mock();
+
+ using (UnitTestApplication.Start(new TestServices(renderer: (root, loop) => renderer.Object)))
+ {
+ var child = new Decorator();
+ var root = new TestRoot();
+
+ root.Child = child;
+ renderer.ResetCalls();
+ root.Child = null;
+
+ renderer.Verify(x => x.AddDirty(child));
+ }
+ }
+
[Fact]
public void Adding_Already_Parented_Control_Should_Throw()
{
diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensionsTests_GetVisualsAt.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensionsTests_GetVisualsAt.cs
index 4dbec609f8..44cc291be9 100644
--- a/tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensionsTests_GetVisualsAt.cs
+++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensionsTests_GetVisualsAt.cs
@@ -349,12 +349,14 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
{
(target = new Border()
{
+ Name = "b1",
Width = 100,
Height = 100,
Background = Brushes.Red,
}),
new Border()
{
+ Name = "b2",
Width = 100,
Height = 100,
Background = Brushes.Red,
@@ -367,12 +369,14 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
{
(item1 = new Border()
{
+ Name = "b3",
Width = 100,
Height = 100,
Background = Brushes.Red,
}),
(item2 = new Border()
{
+ Name = "b4",
Width = 100,
Height = 100,
Background = Brushes.Red,
@@ -400,19 +404,15 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
scroll.Offset = new Vector(0, 100);
- //we don't have setup LayoutManager so we will make it manually
+ // We don't have LayoutManager set up so do the layout pass manually.
scroll.Parent.InvalidateArrange();
container.InvalidateArrange();
-
container.Arrange(new Rect(container.DesiredSize));
result = container.GetVisualsAt(new Point(50, 150)).First();
-
Assert.Equal(item2, result);
result = container.GetVisualsAt(new Point(50, 50)).First();
-
- Assert.NotEqual(item1, result);
Assert.Equal(target, result);
}
}