Browse Source

Ported repeater logging from WinUI.

pull/4091/head
Steven Kirk 6 years ago
parent
commit
498cc73bfd
  1. 2
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  2. 7
      src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs
  3. 3
      src/Avalonia.Controls/Repeater/ViewManager.cs
  4. 24
      src/Avalonia.Controls/Repeater/ViewportManager.cs
  5. 2
      src/Avalonia.Layout/AttachedLayout.cs
  6. 4
      src/Avalonia.Layout/ElementManager.cs
  7. 42
      src/Avalonia.Layout/FlowLayoutAlgorithm.cs
  8. 8
      src/Avalonia.Layout/StackLayout.cs
  9. 7
      src/Avalonia.Layout/UniformGridLayout.cs

2
src/Avalonia.Controls/Repeater/ItemsRepeater.cs

@ -10,6 +10,7 @@ using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Logging;
using Avalonia.VisualTree;
namespace Avalonia.Controls
@ -306,6 +307,7 @@ namespace Avalonia.Controls
virtInfo.AutoRecycleCandidate &&
!virtInfo.KeepAlive)
{
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "AutoClear - {Index}", virtInfo.Index);
ClearElementImpl(element);
}
}

7
src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs

@ -7,6 +7,7 @@ using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Layout;
using Avalonia.Logging;
namespace Avalonia.Controls
{
@ -58,7 +59,11 @@ namespace Avalonia.Controls
protected override object GetItemAtCore(int index) => _owner.ItemsSourceView.GetAt(index);
protected override void RecycleElementCore(ILayoutable element) => _owner.ClearElementImpl((IControl)element);
protected override void RecycleElementCore(ILayoutable element)
{
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "RepeaterLayout - RecycleElement: {Index}", _owner.GetElementIndex((IControl)element));
_owner.ClearElementImpl((IControl)element);
}
protected override Rect RealizationRectCore() => _owner.RealizationWindow;
}

3
src/Avalonia.Controls/Repeater/ViewManager.cs

@ -11,6 +11,7 @@ using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Logging;
using Avalonia.VisualTree;
namespace Avalonia.Controls
@ -60,11 +61,13 @@ namespace Avalonia.Controls
if (suppressAutoRecycle)
{
virtInfo.AutoRecycleCandidate = false;
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "GetElement: {Index} Not AutoRecycleCandidate:", virtInfo.Index);
}
else
{
virtInfo.AutoRecycleCandidate = true;
virtInfo.KeepAlive = true;
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "GetElement: {Index} AutoRecycleCandidate:", virtInfo.Index);
}
return element;

24
src/Avalonia.Controls/Repeater/ViewportManager.cs

@ -8,6 +8,7 @@ using System.Collections.Generic;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Layout;
using Avalonia.Logging;
using Avalonia.Threading;
using Avalonia.VisualTree;
@ -184,6 +185,9 @@ namespace Avalonia.Controls
// We tolerate viewport imprecisions up to 1 pixel to avoid invaliding layout too much.
if (Math.Abs(_expectedViewportShift.X) > 1 || Math.Abs(_expectedViewportShift.Y) > 1)
{
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: Expecting viewport shift of ({Shift})",
_owner.Layout.LayoutId, _expectedViewportShift);
// There are cases where we might be expecting a shift but not get it. We will
// be waiting for the effective viewport event but if the scroll viewer is not able
// to perform the shift (perhaps because it cannot scroll in negative offset),
@ -293,6 +297,10 @@ namespace Avalonia.Controls
// that can scroll in the direction where the shift is expected.
if (_pendingViewportShift.X != 0 || _pendingViewportShift.Y != 0)
{
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: Layout Updated with pending shift {Shift}- invalidating measure",
_owner.Layout.LayoutId,
_pendingViewportShift);
// Assume this is never going to come.
_unshiftableShift = new Point(
_unshiftableShift.X + _pendingViewportShift.X,
@ -404,6 +412,7 @@ namespace Avalonia.Controls
var clip = globalClip.TransformToAABB(transform);
var effectiveViewport = clip.Intersect(bounds.Value.Bounds);
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: EffectiveViewportChanged event callback", _owner.Layout.LayoutId);
UpdateViewport(effectiveViewport);
_pendingViewportShift = default;
@ -459,11 +468,17 @@ namespace Avalonia.Controls
private void UpdateViewport(Rect viewport)
{
var currentVisibleWindow = viewport;
var oldVisibleWindow = _visibleWindow;
var previousVisibleWindow = _visibleWindow;
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: Effective Viewport: ({Before})->({After})",
_owner.Layout.LayoutId,
previousVisibleWindow,
viewport);
if (-currentVisibleWindow.X <= ItemsRepeater.ClearedElementsArrangePosition.X &&
-currentVisibleWindow.Y <= ItemsRepeater.ClearedElementsArrangePosition.Y)
{
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: Viewport is invalid. visible window cleared", _owner.Layout.LayoutId);
// We got cleared.
_visibleWindow = default;
}
@ -472,8 +487,12 @@ namespace Avalonia.Controls
_visibleWindow = currentVisibleWindow;
}
if (_visibleWindow != oldVisibleWindow)
if (_visibleWindow != previousVisibleWindow)
{
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: Used Viewport: ({Before})->({After})",
_owner.Layout.LayoutId,
previousVisibleWindow,
currentVisibleWindow);
TryInvalidateMeasure();
}
}
@ -494,6 +513,7 @@ namespace Avalonia.Controls
// We invalidate measure instead of just invalidating arrange because
// we don't invalidate measure in UpdateViewport if the view is changing to
// avoid layout cycles.
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: Invalidating measure due to viewport change", _owner.Layout.LayoutId);
_owner.InvalidateMeasure();
}
}

2
src/Avalonia.Layout/AttachedLayout.cs

@ -12,7 +12,7 @@ namespace Avalonia.Layout
/// </summary>
public abstract class AttachedLayout : AvaloniaObject
{
internal string LayoutId { get; set; }
public string LayoutId { get; set; }
/// <summary>
/// Occurs when the measurement state (layout) has been invalidated.

4
src/Avalonia.Layout/ElementManager.cs

@ -7,6 +7,7 @@ using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using Avalonia.Layout.Utils;
using Avalonia.Logging;
namespace Avalonia.Layout
{
@ -78,6 +79,7 @@ namespace Avalonia.Layout
{
// Sentinel. Create the element now since we need it.
int dataIndex = GetDataIndexFromRealizedRangeIndex(realizedIndex);
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "Creating element for sentinal with data index {Index}", dataIndex);
element = _context.GetOrCreateElementAt(
dataIndex,
ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle);
@ -232,6 +234,8 @@ namespace Avalonia.Layout
{
Insert(0, dataIndex, element);
}
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: Created element for index {index}", layoutId, dataIndex);
}
}

42
src/Avalonia.Layout/FlowLayoutAlgorithm.cs

@ -5,6 +5,7 @@
using System;
using System.Collections.Specialized;
using Avalonia.Logging;
namespace Avalonia.Layout
{
@ -82,6 +83,9 @@ namespace Avalonia.Layout
// If minor size is infinity, there is only one line and no need to align that line.
_scrollOrientationSameAsFlow = double.IsInfinity(_orientation.Minor(availableSize));
var realizationRect = RealizationRect;
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: MeasureLayout Realization({Rect})",
layoutId,
realizationRect);
var suggestedAnchorIndex = _context.RecommendedAnchorIndex;
if (_elementManager.IsIndexValidInData(suggestedAnchorIndex))
@ -100,6 +104,7 @@ namespace Avalonia.Layout
Generate(GenerateDirection.Backward, anchorIndex, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, disableVirtualization, layoutId);
if (isWrapping && IsReflowRequired())
{
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: Reflow Pass", layoutId);
var firstElementBounds = _elementManager.GetLayoutBoundsForRealizedIndex(0);
_orientation.SetMinorStart(ref firstElementBounds, 0);
_elementManager.SetLayoutBoundsForRealizedIndex(0, firstElementBounds);
@ -121,6 +126,7 @@ namespace Avalonia.Layout
LineAlignment lineAlignment,
string layoutId)
{
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: ArrangeLayout", layoutId);
ArrangeVirtualizingLayout(finalSize, lineAlignment, isWrapping, layoutId);
return new Size(
@ -184,6 +190,7 @@ namespace Avalonia.Layout
if (isAnchorSuggestionValid)
{
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: Using suggested anchor {Anchor}", layoutId, suggestedAnchorIndex);
anchorIndex = _algorithmCallbacks.Algorithm_GetAnchorForTargetElement(
suggestedAnchorIndex,
availableSize,
@ -223,6 +230,9 @@ namespace Avalonia.Layout
}
else if (needAnchorColumnRevaluation || !isRealizationWindowConnected)
{
if (needAnchorColumnRevaluation) { Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: NeedAnchorColumnReevaluation", layoutId); }
if (!isRealizationWindowConnected) { Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: Disconnected Window", layoutId); }
// The anchor is based on the realization window because a connected ItemsRepeater might intersect the realization window
// but not the visible window. In that situation, we still need to produce a valid anchor.
var anchorInfo = _algorithmCallbacks.Algorithm_GetAnchorForRealizationRect(availableSize, context);
@ -231,6 +241,7 @@ namespace Avalonia.Layout
}
else
{
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: Connected Window - picking first realized element as anchor", layoutId);
// No suggestion - just pick first in realized range
anchorIndex = _elementManager.GetDataIndexFromRealizedRangeIndex(0);
var firstElementBounds = _elementManager.GetLayoutBoundsForRealizedIndex(0);
@ -238,12 +249,14 @@ namespace Avalonia.Layout
}
}
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: Picked anchor: {Anchor}", layoutId, anchorIndex);
_firstRealizedDataIndexInsideRealizationWindow = _lastRealizedDataIndexInsideRealizationWindow = anchorIndex;
if (_elementManager.IsIndexValidInData(anchorIndex))
{
if (!_elementManager.IsDataIndexRealized(anchorIndex))
{
// Disconnected, throw everything and create new anchor
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId} Disconnected Window - throwing away all realized elements", layoutId);
_elementManager.ClearRealizedRange();
var anchor = _context.GetOrCreateElementAt(anchorIndex, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle);
@ -254,9 +267,17 @@ namespace Avalonia.Layout
var desiredSize = MeasureElement(anchorElement, anchorIndex, availableSize, _context);
var layoutBounds = new Rect(anchorPosition.X, anchorPosition.Y, desiredSize.Width, desiredSize.Height);
_elementManager.SetLayoutBoundsForDataIndex(anchorIndex, layoutBounds);
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: Layout bounds of anchor {anchor} are ({Bounds})",
layoutId,
anchorIndex,
layoutBounds);
}
else
{
// Throw everything away
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId} Anchor index is not valid - throwing away all realized elements",
layoutId);
_elementManager.ClearRealizedRange();
}
@ -280,6 +301,12 @@ namespace Avalonia.Layout
if (anchorIndex != -1)
{
int step = (direction == GenerateDirection.Forward) ? 1 : -1;
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: Generating {Direction} from anchor {Anchor}",
layoutId,
direction,
anchorIndex);
int previousIndex = anchorIndex;
int currentIndex = anchorIndex + step;
var anchorBounds = _elementManager.GetLayoutBoundsForDataIndex(anchorIndex);
@ -365,6 +392,10 @@ namespace Avalonia.Layout
_orientation.SetMajorStart(ref bounds, previousLineOffset - lineMajorSize - lineSpacing);
_orientation.SetMajorSize(ref bounds, lineMajorSize);
_elementManager.SetLayoutBoundsForDataIndex(dataIndex, bounds);
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: Corrected Layout bounds of element {Index} are ({Bounds})",
layoutId,
dataIndex,
bounds);
}
}
}
@ -387,6 +418,11 @@ namespace Avalonia.Layout
}
_elementManager.SetLayoutBoundsForDataIndex(currentIndex, currentBounds);
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: Layout bounds of element {Index} are ({Bounds}).",
layoutId,
currentIndex,
currentBounds);
previousIndex = currentIndex;
currentIndex += step;
}
@ -505,6 +541,7 @@ namespace Avalonia.Layout
lastDataIndex,
lastBounds);
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId} Extent: ({Bounds})", layoutId, extent);
return extent;
}
@ -673,6 +710,11 @@ namespace Avalonia.Layout
}
var element = _elementManager.GetAt(rangeIndex);
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: Arranging element {Index} at ({Bounds})",
layoutId,
_elementManager.GetDataIndexFromRealizedRangeIndex(rangeIndex),
bounds);
element.Arrange(bounds);
}
}

8
src/Avalonia.Layout/StackLayout.cs

@ -6,6 +6,7 @@
using System;
using System.Collections.Specialized;
using Avalonia.Data;
using Avalonia.Logging;
namespace Avalonia.Layout
{
@ -107,8 +108,15 @@ namespace Avalonia.Layout
_orientation.MajorStart(extent) +
(remainingItems * averageElementSize));
}
else
{
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: Estimating extent with no realized elements",
LayoutId);
}
}
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: Extent is ({Size}). Based on average {Average}",
LayoutId, extent.Size, averageElementSize);
return extent;
}

7
src/Avalonia.Layout/UniformGridLayout.cs

@ -6,6 +6,7 @@
using System;
using System.Collections.Specialized;
using Avalonia.Data;
using Avalonia.Logging;
namespace Avalonia.Layout
{
@ -379,8 +380,14 @@ namespace Avalonia.Layout
ref extent,
_orientation.MajorEnd(lastRealizedLayoutBounds) - _orientation.MajorStart(extent) + (remainingItems / itemsPerLine) * lineSize);
}
else
{
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: Estimating extent with no realized elements", LayoutId);
}
}
Logger.TryGet(LogEventLevel.Verbose)?.Log("Repeater", this, "{LayoutId}: Extent is ({Size}). Based on lineSize {LineSize} and items per line {ItemsPerLine}",
LayoutId, extent.Size, lineSize, itemsPerLine);
return extent;
}

Loading…
Cancel
Save