Browse Source

Merge pull request #2776 from AvaloniaUI/fixes/2714-zindex-fix

Fix ZIndex rendering.
pull/2794/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
7cb20d7837
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/Avalonia.Controls/Panel.cs
  2. 44
      src/Avalonia.Styling/StyledElement.cs
  3. 16
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  4. 6
      src/Avalonia.Visuals/Rendering/IRenderer.cs
  5. 3
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  6. 31
      src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
  7. 19
      src/Avalonia.Visuals/Visual.cs
  8. 4
      tests/Avalonia.LeakTests/ControlTests.cs
  9. 174
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
  10. 47
      tests/Avalonia.Visuals.UnitTests/VisualTests.cs

2
src/Avalonia.Controls/Panel.cs

@ -112,7 +112,7 @@ namespace Avalonia.Controls
case NotifyCollectionChangedAction.Add:
controls = e.NewItems.OfType<Control>().ToList();
LogicalChildren.InsertRange(e.NewStartingIndex, controls);
VisualChildren.AddRange(e.NewItems.OfType<Visual>());
VisualChildren.InsertRange(e.NewStartingIndex, e.NewItems.OfType<Visual>());
break;
case NotifyCollectionChangedAction.Move:

44
src/Avalonia.Styling/StyledElement.cs

@ -568,6 +568,28 @@ namespace Avalonia
});
}
protected virtual void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
SetLogicalParent(e.NewItems.Cast<ILogical>());
break;
case NotifyCollectionChangedAction.Remove:
ClearLogicalParent(e.OldItems.Cast<ILogical>());
break;
case NotifyCollectionChangedAction.Replace:
ClearLogicalParent(e.OldItems.Cast<ILogical>());
SetLogicalParent(e.NewItems.Cast<ILogical>());
break;
case NotifyCollectionChangedAction.Reset:
throw new NotSupportedException("Reset should not be signaled on LogicalChildren collection");
}
}
/// <summary>
/// Called when the styled element is added to a rooted logical tree.
/// </summary>
@ -736,28 +758,6 @@ namespace Avalonia
OnDataContextChanged(EventArgs.Empty);
}
private void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
SetLogicalParent(e.NewItems.Cast<ILogical>());
break;
case NotifyCollectionChangedAction.Remove:
ClearLogicalParent(e.OldItems.Cast<ILogical>());
break;
case NotifyCollectionChangedAction.Replace:
ClearLogicalParent(e.OldItems.Cast<ILogical>());
SetLogicalParent(e.NewItems.Cast<ILogical>());
break;
case NotifyCollectionChangedAction.Reset:
throw new NotSupportedException("Reset should not be signaled on LogicalChildren collection");
}
}
private void SetLogicalParent(IEnumerable<ILogical> children)
{
foreach (var i in children)

16
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -30,6 +30,7 @@ namespace Avalonia.Rendering
private bool _disposed;
private volatile IRef<Scene> _scene;
private DirtyVisuals _dirty;
private HashSet<IVisual> _recalculateChildren;
private IRef<IRenderTargetBitmapImpl> _overlay;
private int _lastSceneId = -1;
private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects();
@ -135,6 +136,8 @@ namespace Avalonia.Rendering
DisposeRenderTarget();
}
public void RecalculateChildren(IVisual visual) => _recalculateChildren?.Add(visual);
void DisposeRenderTarget()
{
using (var l = _lock.TryLock())
@ -229,6 +232,8 @@ namespace Avalonia.Rendering
internal void UnitTestRender() => Render(false);
internal Scene UnitTestScene() => _scene.Item;
private void Render(bool forceComposite)
{
using (var l = _lock.TryLock())
@ -516,10 +521,19 @@ namespace Avalonia.Rendering
if (_dirty == null)
{
_dirty = new DirtyVisuals();
_recalculateChildren = new HashSet<IVisual>();
_sceneBuilder.UpdateAll(scene);
}
else if (_dirty.Count > 0)
else
{
foreach (var visual in _recalculateChildren)
{
var node = scene.FindNode(visual);
((VisualNode)node)?.SortChildren(scene);
}
_recalculateChildren.Clear();
foreach (var visual in _dirty)
{
_sceneBuilder.Update(scene, visual);

6
src/Avalonia.Visuals/Rendering/IRenderer.cs

@ -50,6 +50,12 @@ namespace Avalonia.Rendering
/// <returns>The visuals at the specified point, topmost first.</returns>
IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool> filter);
/// <summary>
/// Informs the renderer that the z-ordering of a visual's children has changed.
/// </summary>
/// <param name="visual">The visual.</param>
void RecalculateChildren(IVisual visual);
/// <summary>
/// Called when a resize notification is received by the control being rendered.
/// </summary>

3
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@ -163,6 +163,9 @@ namespace Avalonia.Rendering
return HitTest(root, p, filter);
}
/// <inheritdoc/>
public void RecalculateChildren(IVisual visual) => AddDirty(visual);
/// <inheritdoc/>
public void Start()
{

31
src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs

@ -172,6 +172,37 @@ namespace Avalonia.Rendering.SceneGraph
old.Dispose();
}
/// <summary>
/// Sorts the <see cref="Children"/> collection according to the order of the visual's
/// children and their z-index.
/// </summary>
/// <param name="scene">The scene that the node is a part of.</param>
public void SortChildren(Scene scene)
{
var keys = new List<long>();
for (var i = 0; i < Visual.VisualChildren.Count; ++i)
{
var child = Visual.VisualChildren[i];
var zIndex = child.ZIndex;
keys.Add(((long)zIndex << 32) + i);
}
keys.Sort();
_children.Clear();
foreach (var i in keys)
{
var child = Visual.VisualChildren[(int)(i & 0xffffffff)];
var node = scene.FindNode(child);
if (node != null)
{
_children.Add(node);
}
}
}
/// <summary>
/// Removes items in the <see cref="Children"/> collection from the specified index
/// to the end.

19
src/Avalonia.Visuals/Visual.cs

@ -111,6 +111,7 @@ namespace Avalonia
IsVisibleProperty,
OpacityProperty);
RenderTransformProperty.Changed.Subscribe(RenderTransformChanged);
ZIndexProperty.Changed.Subscribe(ZIndexChanged);
}
/// <summary>
@ -345,6 +346,12 @@ namespace Avalonia
}
}
protected override void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
base.LogicalChildrenCollectionChanged(sender, e);
VisualRoot?.Renderer?.RecalculateChildren(this);
}
/// <summary>
/// Calls the <see cref="OnAttachedToVisualTree(VisualTreeAttachmentEventArgs)"/> method
/// for this control and all of its visual descendants.
@ -501,6 +508,18 @@ namespace Avalonia
}
}
/// <summary>
/// Called when the <see cref="ZIndex"/> property changes on any control.
/// </summary>
/// <param name="e">The event args.</param>
private static void ZIndexChanged(AvaloniaPropertyChangedEventArgs e)
{
var sender = e.Sender as IVisual;
var parent = sender?.VisualParent;
sender?.InvalidateVisual();
parent?.VisualRoot?.Renderer?.RecalculateChildren(parent);
}
/// <summary>
/// Called when the <see cref="RenderTransform"/>'s <see cref="Transform.Changed"/> event
/// is fired.

4
tests/Avalonia.LeakTests/ControlTests.cs

@ -401,6 +401,10 @@ namespace Avalonia.LeakTests
{
}
public void RecalculateChildren(IVisual visual)
{
}
public void Resized(Size size)
{
}

174
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

@ -96,6 +96,180 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Assert.Equal(new List<IVisual> { root, decorator, border, canvas }, result);
}
[Fact]
public void Should_Update_VisualNode_Order_On_Child_Remove_Insert()
{
var dispatcher = new ImmediateDispatcher();
var loop = new Mock<IRenderLoop>();
StackPanel stack;
Canvas canvas1;
Canvas canvas2;
var root = new TestRoot
{
Child = stack = new StackPanel
{
Children=
{
(canvas1 = new Canvas()),
(canvas2 = new Canvas()),
}
}
};
var sceneBuilder = new SceneBuilder();
var target = new DeferredRenderer(
root,
loop.Object,
sceneBuilder: sceneBuilder,
dispatcher: dispatcher);
root.Renderer = target;
target.Start();
RunFrame(target);
stack.Children.Remove(canvas2);
stack.Children.Insert(0, canvas2);
RunFrame(target);
var scene = target.UnitTestScene();
var stackNode = scene.FindNode(stack);
Assert.Same(stackNode.Children[0].Visual, canvas2);
Assert.Same(stackNode.Children[1].Visual, canvas1);
}
[Fact]
public void Should_Update_VisualNode_Order_On_Child_Move()
{
var dispatcher = new ImmediateDispatcher();
var loop = new Mock<IRenderLoop>();
StackPanel stack;
Canvas canvas1;
Canvas canvas2;
var root = new TestRoot
{
Child = stack = new StackPanel
{
Children =
{
(canvas1 = new Canvas()),
(canvas2 = new Canvas()),
}
}
};
var sceneBuilder = new SceneBuilder();
var target = new DeferredRenderer(
root,
loop.Object,
sceneBuilder: sceneBuilder,
dispatcher: dispatcher);
root.Renderer = target;
target.Start();
RunFrame(target);
stack.Children.Move(1, 0);
RunFrame(target);
var scene = target.UnitTestScene();
var stackNode = scene.FindNode(stack);
Assert.Same(stackNode.Children[0].Visual, canvas2);
Assert.Same(stackNode.Children[1].Visual, canvas1);
}
[Fact]
public void Should_Update_VisualNode_Order_On_ZIndex_Change()
{
var dispatcher = new ImmediateDispatcher();
var loop = new Mock<IRenderLoop>();
StackPanel stack;
Canvas canvas1;
Canvas canvas2;
var root = new TestRoot
{
Child = stack = new StackPanel
{
Children =
{
(canvas1 = new Canvas { ZIndex = 1 }),
(canvas2 = new Canvas { ZIndex = 2 }),
}
}
};
var sceneBuilder = new SceneBuilder();
var target = new DeferredRenderer(
root,
loop.Object,
sceneBuilder: sceneBuilder,
dispatcher: dispatcher);
root.Renderer = target;
target.Start();
RunFrame(target);
canvas1.ZIndex = 3;
RunFrame(target);
var scene = target.UnitTestScene();
var stackNode = scene.FindNode(stack);
Assert.Same(stackNode.Children[0].Visual, canvas2);
Assert.Same(stackNode.Children[1].Visual, canvas1);
}
[Fact]
public void Should_Update_VisualNode_Order_On_ZIndex_Change_With_Dirty_Ancestor()
{
var dispatcher = new ImmediateDispatcher();
var loop = new Mock<IRenderLoop>();
StackPanel stack;
Canvas canvas1;
Canvas canvas2;
var root = new TestRoot
{
Child = stack = new StackPanel
{
Children =
{
(canvas1 = new Canvas { ZIndex = 1 }),
(canvas2 = new Canvas { ZIndex = 2 }),
}
}
};
var sceneBuilder = new SceneBuilder();
var target = new DeferredRenderer(
root,
loop.Object,
sceneBuilder: sceneBuilder,
dispatcher: dispatcher);
root.Renderer = target;
target.Start();
RunFrame(target);
root.InvalidateVisual();
canvas1.ZIndex = 3;
RunFrame(target);
var scene = target.UnitTestScene();
var stackNode = scene.FindNode(stack);
Assert.Same(stackNode.Children[0].Visual, canvas2);
Assert.Same(stackNode.Children[1].Visual, canvas1);
}
[Fact]
public void Should_Push_Opacity_For_Controls_With_Less_Than_1_Opacity()
{

47
tests/Avalonia.Visuals.UnitTests/VisualTests.cs

@ -282,5 +282,52 @@ namespace Avalonia.Visuals.UnitTests
Assert.True(called);
}
[Fact]
public void Changing_ZIndex_Should_InvalidateVisual()
{
Canvas canvas1;
var renderer = new Mock<IRenderer>();
var root = new TestRoot
{
Child = new StackPanel
{
Children =
{
(canvas1 = new Canvas()),
new Canvas(),
},
},
};
root.Renderer = renderer.Object;
canvas1.ZIndex = 10;
renderer.Verify(x => x.AddDirty(canvas1));
}
[Fact]
public void Changing_ZIndex_Should_Recalculate_Parent_Children()
{
Canvas canvas1;
StackPanel stackPanel;
var renderer = new Mock<IRenderer>();
var root = new TestRoot
{
Child = stackPanel = new StackPanel
{
Children =
{
(canvas1 = new Canvas()),
new Canvas(),
},
},
};
root.Renderer = renderer.Object;
canvas1.ZIndex = 10;
renderer.Verify(x => x.RecalculateChildren(stackPanel));
}
}
}

Loading…
Cancel
Save