Browse Source

Merge pull request #11127 from AvaloniaUI/fixes/11076-layout-invalidation

Fix layout invalidation when controls made effectively visible.
pull/11174/head
Max Katz 3 years ago
committed by GitHub
parent
commit
9f64bad365
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/Avalonia.Base/Layout/LayoutManager.cs
  2. 18
      src/Avalonia.Base/Layout/LayoutQueue.cs
  3. 40
      src/Avalonia.Base/Layout/Layoutable.cs
  4. 34
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  5. 23
      tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs

2
src/Avalonia.Base/Layout/LayoutManager.cs

@ -17,7 +17,7 @@ namespace Avalonia.Layout
/// </summary>
public class LayoutManager : ILayoutManager, IDisposable
{
private const int MaxPasses = 3;
private const int MaxPasses = 10;
private readonly Layoutable _owner;
private readonly LayoutQueue<Layoutable> _toMeasure = new LayoutQueue<Layoutable>(v => !v.IsMeasureValid);
private readonly LayoutQueue<Layoutable> _toArrange = new LayoutQueue<Layoutable>(v => !v.IsArrangeValid);

18
src/Avalonia.Base/Layout/LayoutQueue.cs

@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Avalonia.Logging;
namespace Avalonia.Layout
{
@ -48,10 +49,21 @@ namespace Avalonia.Layout
{
_loopQueueInfo.TryGetValue(item, out var info);
if (!info.Active && info.Count < _maxEnqueueCountPerLoop)
if (!info.Active)
{
_inner.Enqueue(item);
_loopQueueInfo[item] = new Info() { Active = true, Count = info.Count + 1 };
if (info.Count < _maxEnqueueCountPerLoop)
{
_inner.Enqueue(item);
_loopQueueInfo[item] = new Info() { Active = true, Count = info.Count + 1 };
}
else
{
Logger.TryGet(LogEventLevel.Warning, LogArea.Layout)?.Log(
this,
"Layout cycle detected. Item {Item} was enqueued {Count} times.",
item,
info.Count);
}
}
}

40
src/Avalonia.Base/Layout/Layoutable.cs

@ -776,10 +776,24 @@ namespace Avalonia.Layout
// All changes to visibility cause the parent element to be notified.
this.GetVisualParent<Layoutable>()?.ChildDesiredSizeChanged(this);
// We only invalidate outselves when visibility is changed to true.
if (change.GetNewValue<bool>())
{
// We only invalidate ourselves when visibility is changed to true.
InvalidateMeasure();
// If any descendant had its measure/arrange invalidated while we were hidden,
// they will need to to be registered with the layout manager now that they
// are again effectively visible. If IsEffectivelyVisible becomes an observable
// property then we can piggy-pack on that; for the moment we do this manually.
if (VisualRoot is ILayoutRoot layoutRoot)
{
var count = VisualChildren.Count;
for (var i = 0; i < count; ++i)
{
(VisualChildren[i] as Layoutable)?.AncestorBecameVisible(layoutRoot.LayoutManager);
}
}
}
}
}
@ -804,6 +818,30 @@ namespace Avalonia.Layout
InvalidateMeasure();
}
private void AncestorBecameVisible(ILayoutManager layoutManager)
{
if (!IsVisible)
return;
if (!IsMeasureValid)
{
layoutManager.InvalidateMeasure(this);
InvalidateVisual();
}
else if (!IsArrangeValid)
{
layoutManager.InvalidateArrange(this);
InvalidateVisual();
}
var count = VisualChildren.Count;
for (var i = 0; i < count; ++i)
{
(VisualChildren[i] as Layoutable)?.AncestorBecameVisible(layoutManager);
}
}
/// <summary>
/// Called when the layout manager raises a LayoutUpdated event.
/// </summary>

34
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -565,7 +565,6 @@ namespace Avalonia.Controls
GetItemIsOwnContainer(items, index) ??
GetRecycledElement(items, index) ??
CreateElement(items, index);
InvalidateHack(e);
return e;
}
@ -713,39 +712,6 @@ namespace Avalonia.Controls
}
}
private static void InvalidateHack(Control c)
{
bool HasInvalidations(Control c)
{
if (!c.IsMeasureValid)
return true;
for (var i = 0; i < c.VisualChildren.Count; ++i)
{
if (c.VisualChildren[i] is Control child)
{
if (!child.IsMeasureValid || HasInvalidations(child))
return true;
}
}
return false;
}
void Invalidate(Control c)
{
c.InvalidateMeasure();
for (var i = 0; i < c.VisualChildren.Count; ++i)
{
if (c.VisualChildren[i] is Control child)
Invalidate(child);
}
}
if (HasInvalidations(c))
Invalidate(c);
}
private void OnUnrealizedFocusedElementLostFocus(object? sender, RoutedEventArgs e)
{
if (_unrealizedFocusedElement is null || sender != _unrealizedFocusedElement)

23
tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs

@ -59,6 +59,29 @@ namespace Avalonia.Base.UnitTests.Layout
Assert.False(control.Arranged);
}
[Fact]
public void Lays_Out_Descendents_That_Were_Invalidated_While_Ancestor_Was_Not_Visible()
{
// Issue #11076
var control = new LayoutTestControl();
var parent = new Decorator { Child = control };
var grandparent = new Decorator { Child = parent };
var root = new LayoutTestRoot { Child = grandparent };
root.LayoutManager.ExecuteInitialLayoutPass();
grandparent.IsVisible = false;
control.InvalidateMeasure();
root.LayoutManager.ExecuteInitialLayoutPass();
grandparent.IsVisible = true;
root.LayoutManager.ExecuteLayoutPass();
Assert.True(control.IsMeasureValid);
Assert.True(control.IsArrangeValid);
}
[Fact]
public void Arranges_InvalidateArranged_Control()
{

Loading…
Cancel
Save