diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs index 0f365fcb08..a4c674a03b 100644 --- a/src/Avalonia.Controls/Panel.cs +++ b/src/Avalonia.Controls/Panel.cs @@ -112,7 +112,7 @@ namespace Avalonia.Controls case NotifyCollectionChangedAction.Add: controls = e.NewItems.OfType().ToList(); LogicalChildren.InsertRange(e.NewStartingIndex, controls); - VisualChildren.AddRange(e.NewItems.OfType()); + VisualChildren.InsertRange(e.NewStartingIndex, e.NewItems.OfType()); break; case NotifyCollectionChangedAction.Move: diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index b4aa89d5bf..38c29289b6 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/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()); + break; + + case NotifyCollectionChangedAction.Remove: + ClearLogicalParent(e.OldItems.Cast()); + break; + + case NotifyCollectionChangedAction.Replace: + ClearLogicalParent(e.OldItems.Cast()); + SetLogicalParent(e.NewItems.Cast()); + break; + + case NotifyCollectionChangedAction.Reset: + throw new NotSupportedException("Reset should not be signaled on LogicalChildren collection"); + } + } + /// /// Called when the styled element is added to a rooted logical tree. /// @@ -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()); - break; - - case NotifyCollectionChangedAction.Remove: - ClearLogicalParent(e.OldItems.Cast()); - break; - - case NotifyCollectionChangedAction.Replace: - ClearLogicalParent(e.OldItems.Cast()); - SetLogicalParent(e.NewItems.Cast()); - break; - - case NotifyCollectionChangedAction.Reset: - throw new NotSupportedException("Reset should not be signaled on LogicalChildren collection"); - } - } - private void SetLogicalParent(IEnumerable children) { foreach (var i in children) diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index b6546eee08..536e0831ed 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -30,6 +30,7 @@ namespace Avalonia.Rendering private bool _disposed; private volatile IRef _scene; private DirtyVisuals _dirty; + private HashSet _recalculateChildren; private IRef _overlay; private int _lastSceneId = -1; private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects(); @@ -135,6 +136,8 @@ namespace Avalonia.Rendering DisposeRenderTarget(); } + public void RecalculateChildren(IVisual visual) => _recalculateChildren?.Add(visual); + void DisposeRenderTarget() { using (var l = _lock.TryLock()) @@ -518,10 +521,19 @@ namespace Avalonia.Rendering if (_dirty == null) { _dirty = new DirtyVisuals(); + _recalculateChildren = new HashSet(); _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); diff --git a/src/Avalonia.Visuals/Rendering/IRenderer.cs b/src/Avalonia.Visuals/Rendering/IRenderer.cs index 36a1f7d220..9ad7186dca 100644 --- a/src/Avalonia.Visuals/Rendering/IRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/IRenderer.cs @@ -50,6 +50,12 @@ namespace Avalonia.Rendering /// The visuals at the specified point, topmost first. IEnumerable HitTest(Point p, IVisual root, Func filter); + /// + /// Informs the renderer that the z-ordering of a visual's children has changed. + /// + /// The visual. + void RecalculateChildren(IVisual visual); + /// /// Called when a resize notification is received by the control being rendered. /// diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs index 21129e38af..b2d242d4af 100644 --- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs @@ -163,6 +163,9 @@ namespace Avalonia.Rendering return HitTest(root, p, filter); } + /// + public void RecalculateChildren(IVisual visual) => AddDirty(visual); + /// public void Start() { diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs index 4e95d21a48..98915be18d 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs @@ -172,6 +172,37 @@ namespace Avalonia.Rendering.SceneGraph old.Dispose(); } + /// + /// Sorts the collection according to the order of the visual's + /// children and their z-index. + /// + /// The scene that the node is a part of. + public void SortChildren(Scene scene) + { + var keys = new List(); + + for (var i = 0; i < Visual.VisualChildren.Count; ++i) + { + var child = Visual.VisualChildren[i]; + var zIndex = child.ZIndex; + keys.Add(((long)zIndex << 32) + i); + } + + keys.Sort(); + _children.Clear(); + + foreach (var i in keys) + { + var child = Visual.VisualChildren[(int)(i & 0xffffffff)]; + var node = scene.FindNode(child); + + if (node != null) + { + _children.Add(node); + } + } + } + /// /// Removes items in the collection from the specified index /// to the end. diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index 9e088cb136..89d09ae58d 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -111,6 +111,7 @@ namespace Avalonia IsVisibleProperty, OpacityProperty); RenderTransformProperty.Changed.Subscribe(RenderTransformChanged); + ZIndexProperty.Changed.Subscribe(ZIndexChanged); } /// @@ -345,6 +346,12 @@ namespace Avalonia } } + protected override void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + base.LogicalChildrenCollectionChanged(sender, e); + VisualRoot?.Renderer?.RecalculateChildren(this); + } + /// /// Calls the method /// for this control and all of its visual descendants. @@ -501,6 +508,16 @@ namespace Avalonia } } + /// + /// Called when the property changes on any control. + /// + /// The event args. + private static void ZIndexChanged(AvaloniaPropertyChangedEventArgs e) + { + var parent = (e.Sender as Visual)?._visualParent; + parent?.VisualRoot?.Renderer?.RecalculateChildren(parent); + } + /// /// Called when the 's event /// is fired. diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index a841174d2d..1da4746516 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -401,6 +401,10 @@ namespace Avalonia.LeakTests { } + public void RecalculateChildren(IVisual visual) + { + } + public void Resized(Size size) { }