diff --git a/src/Avalonia.Animation/Cue.cs b/src/Avalonia.Animation/Cue.cs
index 52d1609cf9..7da7a9382b 100644
--- a/src/Avalonia.Animation/Cue.cs
+++ b/src/Avalonia.Animation/Cue.cs
@@ -30,7 +30,7 @@ namespace Avalonia.Animation
///
/// Parses a string to a object.
///
- public static object Parse(string value, CultureInfo culture)
+ public static Cue Parse(string value, CultureInfo culture)
{
string v = value;
@@ -70,7 +70,7 @@ namespace Avalonia.Animation
}
}
- public class CueTypeConverter : TypeConverter
+ public class CueTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs
index f3540ea631..45efccc1fa 100644
--- a/src/Avalonia.Layout/LayoutManager.cs
+++ b/src/Avalonia.Layout/LayoutManager.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using System.Collections.Generic;
using Avalonia.Logging;
using Avalonia.Threading;
@@ -13,8 +12,8 @@ namespace Avalonia.Layout
///
public class LayoutManager : ILayoutManager
{
- private readonly Queue _toMeasure = new Queue();
- private readonly Queue _toArrange = new Queue();
+ private readonly LayoutQueue _toMeasure = new LayoutQueue(v => !v.IsMeasureValid);
+ private readonly LayoutQueue _toArrange = new LayoutQueue(v => !v.IsArrangeValid);
private bool _queued;
private bool _running;
@@ -80,6 +79,9 @@ namespace Avalonia.Layout
var stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
+ _toMeasure.BeginLoop(MaxPasses);
+ _toArrange.BeginLoop(MaxPasses);
+
try
{
for (var pass = 0; pass < MaxPasses; ++pass)
@@ -98,6 +100,9 @@ namespace Avalonia.Layout
_running = false;
}
+ _toMeasure.EndLoop();
+ _toArrange.EndLoop();
+
stopwatch.Stop();
Logger.Information(LogArea.Layout, this, "Layout pass finished in {Time}", stopwatch.Elapsed);
}
@@ -112,7 +117,7 @@ namespace Avalonia.Layout
Arrange(root);
// Running the initial layout pass may have caused some control to be invalidated
- // so run a full layout pass now (this usually due to scrollbars; its not known
+ // so run a full layout pass now (this usually due to scrollbars; its not known
// whether they will need to be shown until the layout pass has run and if the
// first guess was incorrect the layout will need to be updated).
ExecuteLayoutPass();
@@ -133,7 +138,7 @@ namespace Avalonia.Layout
private void ExecuteArrangePass()
{
- while (_toArrange.Count > 0 && _toMeasure.Count == 0)
+ while (_toArrange.Count > 0)
{
var control = _toArrange.Dequeue();
diff --git a/src/Avalonia.Layout/LayoutQueue.cs b/src/Avalonia.Layout/LayoutQueue.cs
new file mode 100644
index 0000000000..ce40fdde49
--- /dev/null
+++ b/src/Avalonia.Layout/LayoutQueue.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Avalonia.Layout
+{
+ internal class LayoutQueue : IReadOnlyCollection
+ {
+ private struct Info
+ {
+ public bool Active;
+ public int Count;
+ }
+
+ public LayoutQueue(Func shouldEnqueue)
+ {
+ _shouldEnqueue = shouldEnqueue;
+ }
+
+ private Func _shouldEnqueue;
+ private Queue _inner = new Queue();
+ private Dictionary _loopQueueInfo = new Dictionary();
+ private int _maxEnqueueCountPerLoop = 1;
+
+ public int Count => _inner.Count;
+
+ public IEnumerator GetEnumerator() => (_inner as IEnumerable).GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator();
+
+ public T Dequeue()
+ {
+ var result = _inner.Dequeue();
+
+ if (_loopQueueInfo.TryGetValue(result, out var info))
+ {
+ info.Active = false;
+ _loopQueueInfo[result] = info;
+ }
+
+ return result;
+ }
+
+ public void Enqueue(T item)
+ {
+ _loopQueueInfo.TryGetValue(item, out var info);
+
+ if (!info.Active && info.Count < _maxEnqueueCountPerLoop)
+ {
+ _inner.Enqueue(item);
+ _loopQueueInfo[item] = new Info() { Active = true, Count = info.Count + 1 };
+ }
+ }
+
+ public void BeginLoop(int maxEnqueueCountPerLoop)
+ {
+ _maxEnqueueCountPerLoop = maxEnqueueCountPerLoop;
+ }
+
+ public void EndLoop()
+ {
+ var notfinalized = _loopQueueInfo.Where(v => v.Value.Count == _maxEnqueueCountPerLoop).ToArray();
+
+ _loopQueueInfo.Clear();
+
+ //prevent layout cycle but add to next layout the non arranged/measured items that might have caused cycle
+ //one more time as a final attempt
+ foreach (var item in notfinalized)
+ {
+ if (_shouldEnqueue(item.Key))
+ {
+ _loopQueueInfo[item.Key] = new Info() { Active = true, Count = item.Value.Count + 1 };
+ _inner.Enqueue(item.Key);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Layout/Properties/AssemblyInfo.cs b/src/Avalonia.Layout/Properties/AssemblyInfo.cs
index 70fc1e9330..392ad323e5 100644
--- a/src/Avalonia.Layout/Properties/AssemblyInfo.cs
+++ b/src/Avalonia.Layout/Properties/AssemblyInfo.cs
@@ -1,6 +1,10 @@
// 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 System.Runtime.CompilerServices;
using Avalonia.Metadata;
+[assembly: InternalsVisibleTo("Avalonia.Layout.UnitTests")]
+
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Layout")]
+
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
index 2fb8e84a2e..19fb54e125 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
@@ -322,36 +322,51 @@ namespace Avalonia.Rendering.SceneGraph
}
}
+ ///
+ /// Ensures that this node draw operations have been created and are mutable (in case we are using cloned operations).
+ ///
private void EnsureDrawOperationsCreated()
{
if (_drawOperations == null)
{
_drawOperations = new List>();
- _drawOperationsRefCounter = RefCountable.Create(Disposable.Create(DisposeDrawOperations));
+ _drawOperationsRefCounter = RefCountable.Create(CreateDisposeDrawOperations(_drawOperations));
_drawOperationsCloned = false;
}
else if (_drawOperationsCloned)
{
_drawOperations = new List>(_drawOperations.Select(op => op.Clone()));
_drawOperationsRefCounter.Dispose();
- _drawOperationsRefCounter = RefCountable.Create(Disposable.Create(DisposeDrawOperations));
+ _drawOperationsRefCounter = RefCountable.Create(CreateDisposeDrawOperations(_drawOperations));
_drawOperationsCloned = false;
}
}
- public bool Disposed { get; }
-
- public void Dispose()
+ ///
+ /// Creates disposable that will dispose all items in passed draw operations after being disposed.
+ /// It is crucial that we don't capture current instance
+ /// as draw operations can be cloned and may persist across subsequent scenes.
+ ///
+ /// Draw operations that need to be disposed.
+ /// Disposable for given draw operations.
+ private static IDisposable CreateDisposeDrawOperations(List> drawOperations)
{
- _drawOperationsRefCounter?.Dispose();
+ return Disposable.Create(() =>
+ {
+ foreach (var operation in drawOperations)
+ {
+ operation.Dispose();
+ }
+ });
}
- private void DisposeDrawOperations()
+ public bool Disposed { get; private set; }
+
+ public void Dispose()
{
- foreach (var operation in DrawOperations)
- {
- operation.Dispose();
- }
+ _drawOperationsRefCounter?.Dispose();
+
+ Disposed = true;
}
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
index b6f7c9ec96..343d8d41f3 100644
--- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
@@ -267,6 +267,74 @@ namespace Avalonia.Controls.UnitTests
Assert.True(true);
}
+ [Fact]
+ public void LayoutManager_Should_Measure_Arrange_All()
+ {
+ var virtualizationMode = ItemVirtualizationMode.Simple;
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var items = new AvaloniaList(Enumerable.Range(1, 7).Select(v => v.ToString()));
+
+ var wnd = new Window() { SizeToContent = SizeToContent.WidthAndHeight };
+
+ wnd.IsVisible = true;
+
+ var target = new ListBox();
+
+ wnd.Content = target;
+
+ var lm = wnd.LayoutManager;
+
+ target.Height = 110;
+ target.Width = 50;
+ target.DataContext = items;
+ target.VirtualizationMode = virtualizationMode;
+
+ target.ItemTemplate = new FuncDataTemplate