|
|
@ -16,8 +16,8 @@ namespace Avalonia.Layout |
|
|
private Size _lastAvailableSize; |
|
|
private Size _lastAvailableSize; |
|
|
private double _lastItemSpacing; |
|
|
private double _lastItemSpacing; |
|
|
private bool _collectionChangePending; |
|
|
private bool _collectionChangePending; |
|
|
private VirtualizingLayoutContext _context; |
|
|
private VirtualizingLayoutContext? _context; |
|
|
private IFlowLayoutAlgorithmDelegates _algorithmCallbacks; |
|
|
private IFlowLayoutAlgorithmDelegates? _algorithmCallbacks; |
|
|
private Rect _lastExtent; |
|
|
private Rect _lastExtent; |
|
|
private int _firstRealizedDataIndexInsideRealizationWindow = -1; |
|
|
private int _firstRealizedDataIndexInsideRealizationWindow = -1; |
|
|
private int _lastRealizedDataIndexInsideRealizationWindow = -1; |
|
|
private int _lastRealizedDataIndexInsideRealizationWindow = -1; |
|
|
@ -46,7 +46,7 @@ namespace Avalonia.Layout |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private Rect RealizationRect => IsVirtualizingContext ? _context.RealizationRect : new Rect(Size.Infinity); |
|
|
private Rect RealizationRect => IsVirtualizingContext ? _context!.RealizationRect : new Rect(Size.Infinity); |
|
|
|
|
|
|
|
|
public void InitializeForContext(VirtualizingLayoutContext context, IFlowLayoutAlgorithmDelegates callbacks) |
|
|
public void InitializeForContext(VirtualizingLayoutContext context, IFlowLayoutAlgorithmDelegates callbacks) |
|
|
{ |
|
|
{ |
|
|
@ -76,7 +76,7 @@ namespace Avalonia.Layout |
|
|
int maxItemsPerLine, |
|
|
int maxItemsPerLine, |
|
|
ScrollOrientation orientation, |
|
|
ScrollOrientation orientation, |
|
|
bool disableVirtualization, |
|
|
bool disableVirtualization, |
|
|
string layoutId) |
|
|
string? layoutId) |
|
|
{ |
|
|
{ |
|
|
_orientation.ScrollOrientation = orientation; |
|
|
_orientation.ScrollOrientation = orientation; |
|
|
|
|
|
|
|
|
@ -87,7 +87,7 @@ namespace Avalonia.Layout |
|
|
layoutId, |
|
|
layoutId, |
|
|
realizationRect); |
|
|
realizationRect); |
|
|
|
|
|
|
|
|
var suggestedAnchorIndex = _context.RecommendedAnchorIndex; |
|
|
var suggestedAnchorIndex = _context!.RecommendedAnchorIndex; |
|
|
if (_elementManager.IsIndexValidInData(suggestedAnchorIndex)) |
|
|
if (_elementManager.IsIndexValidInData(suggestedAnchorIndex)) |
|
|
{ |
|
|
{ |
|
|
var anchorRealized = _elementManager.IsDataIndexRealized(suggestedAnchorIndex); |
|
|
var anchorRealized = _elementManager.IsDataIndexRealized(suggestedAnchorIndex); |
|
|
@ -124,7 +124,7 @@ namespace Avalonia.Layout |
|
|
VirtualizingLayoutContext context, |
|
|
VirtualizingLayoutContext context, |
|
|
bool isWrapping, |
|
|
bool isWrapping, |
|
|
LineAlignment lineAlignment, |
|
|
LineAlignment lineAlignment, |
|
|
string layoutId) |
|
|
string? layoutId) |
|
|
{ |
|
|
{ |
|
|
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: ArrangeLayout", layoutId); |
|
|
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: ArrangeLayout", layoutId); |
|
|
ArrangeVirtualizingLayout(finalSize, lineAlignment, isWrapping, layoutId); |
|
|
ArrangeVirtualizingLayout(finalSize, lineAlignment, isWrapping, layoutId); |
|
|
@ -149,7 +149,7 @@ namespace Avalonia.Layout |
|
|
Size availableSize, |
|
|
Size availableSize, |
|
|
VirtualizingLayoutContext context) |
|
|
VirtualizingLayoutContext context) |
|
|
{ |
|
|
{ |
|
|
var measureSize = _algorithmCallbacks.Algorithm_GetMeasureSize(index, availableSize, context); |
|
|
var measureSize = _algorithmCallbacks!.Algorithm_GetMeasureSize(index, availableSize, context); |
|
|
element.Measure(measureSize); |
|
|
element.Measure(measureSize); |
|
|
var provisionalArrangeSize = _algorithmCallbacks.Algorithm_GetProvisionalArrangeSize(index, measureSize, element.DesiredSize, context); |
|
|
var provisionalArrangeSize = _algorithmCallbacks.Algorithm_GetProvisionalArrangeSize(index, measureSize, element.DesiredSize, context); |
|
|
_algorithmCallbacks.Algorithm_OnElementMeasured(element, index, availableSize, measureSize, element.DesiredSize, provisionalArrangeSize, context); |
|
|
_algorithmCallbacks.Algorithm_OnElementMeasured(element, index, availableSize, measureSize, element.DesiredSize, provisionalArrangeSize, context); |
|
|
@ -161,7 +161,7 @@ namespace Avalonia.Layout |
|
|
Size availableSize, |
|
|
Size availableSize, |
|
|
bool isWrapping, |
|
|
bool isWrapping, |
|
|
double minItemSpacing, |
|
|
double minItemSpacing, |
|
|
string layoutId) |
|
|
string? layoutId) |
|
|
{ |
|
|
{ |
|
|
int anchorIndex = -1; |
|
|
int anchorIndex = -1; |
|
|
var anchorPosition= new Point(); |
|
|
var anchorPosition= new Point(); |
|
|
@ -170,7 +170,7 @@ namespace Avalonia.Layout |
|
|
if (!IsVirtualizingContext) |
|
|
if (!IsVirtualizingContext) |
|
|
{ |
|
|
{ |
|
|
// Non virtualizing host, start generating from the element 0
|
|
|
// Non virtualizing host, start generating from the element 0
|
|
|
anchorIndex = context.ItemCount > 0 ? 0 : -1; |
|
|
anchorIndex = context!.ItemCount > 0 ? 0 : -1; |
|
|
} |
|
|
} |
|
|
else |
|
|
else |
|
|
{ |
|
|
{ |
|
|
@ -183,7 +183,7 @@ namespace Avalonia.Layout |
|
|
_lastItemSpacing != minItemSpacing || |
|
|
_lastItemSpacing != minItemSpacing || |
|
|
_collectionChangePending); |
|
|
_collectionChangePending); |
|
|
|
|
|
|
|
|
var suggestedAnchorIndex = _context.RecommendedAnchorIndex; |
|
|
var suggestedAnchorIndex = _context!.RecommendedAnchorIndex; |
|
|
|
|
|
|
|
|
var isAnchorSuggestionValid = suggestedAnchorIndex >= 0 && |
|
|
var isAnchorSuggestionValid = suggestedAnchorIndex >= 0 && |
|
|
_elementManager.IsDataIndexRealized(suggestedAnchorIndex); |
|
|
_elementManager.IsDataIndexRealized(suggestedAnchorIndex); |
|
|
@ -191,10 +191,10 @@ namespace Avalonia.Layout |
|
|
if (isAnchorSuggestionValid) |
|
|
if (isAnchorSuggestionValid) |
|
|
{ |
|
|
{ |
|
|
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Using suggested anchor {Anchor}", layoutId, suggestedAnchorIndex); |
|
|
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Using suggested anchor {Anchor}", layoutId, suggestedAnchorIndex); |
|
|
anchorIndex = _algorithmCallbacks.Algorithm_GetAnchorForTargetElement( |
|
|
anchorIndex = _algorithmCallbacks!.Algorithm_GetAnchorForTargetElement( |
|
|
suggestedAnchorIndex, |
|
|
suggestedAnchorIndex, |
|
|
availableSize, |
|
|
availableSize, |
|
|
context).Index; |
|
|
context!).Index; |
|
|
|
|
|
|
|
|
if (_elementManager.IsDataIndexRealized(anchorIndex)) |
|
|
if (_elementManager.IsDataIndexRealized(anchorIndex)) |
|
|
{ |
|
|
{ |
|
|
@ -235,7 +235,7 @@ namespace Avalonia.Layout |
|
|
|
|
|
|
|
|
// The anchor is based on the realization window because a connected ItemsRepeater might intersect the realization window
|
|
|
// 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.
|
|
|
// but not the visible window. In that situation, we still need to produce a valid anchor.
|
|
|
var anchorInfo = _algorithmCallbacks.Algorithm_GetAnchorForRealizationRect(availableSize, context); |
|
|
var anchorInfo = _algorithmCallbacks!.Algorithm_GetAnchorForRealizationRect(availableSize, context!); |
|
|
anchorIndex = anchorInfo.Index; |
|
|
anchorIndex = anchorInfo.Index; |
|
|
anchorPosition = _orientation.MinorMajorPoint(0, anchorInfo.Offset); |
|
|
anchorPosition = _orientation.MinorMajorPoint(0, anchorInfo.Offset); |
|
|
} |
|
|
} |
|
|
@ -259,12 +259,12 @@ namespace Avalonia.Layout |
|
|
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId} Disconnected Window - throwing away all realized elements", layoutId); |
|
|
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId} Disconnected Window - throwing away all realized elements", layoutId); |
|
|
_elementManager.ClearRealizedRange(); |
|
|
_elementManager.ClearRealizedRange(); |
|
|
|
|
|
|
|
|
var anchor = _context.GetOrCreateElementAt(anchorIndex, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); |
|
|
var anchor = _context!.GetOrCreateElementAt(anchorIndex, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); |
|
|
_elementManager.Add(anchor, anchorIndex); |
|
|
_elementManager.Add(anchor, anchorIndex); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
var anchorElement = _elementManager.GetRealizedElement(anchorIndex); |
|
|
var anchorElement = _elementManager.GetRealizedElement(anchorIndex); |
|
|
var desiredSize = MeasureElement(anchorElement, anchorIndex, availableSize, _context); |
|
|
var desiredSize = MeasureElement(anchorElement!, anchorIndex, availableSize, _context!); |
|
|
var layoutBounds = new Rect(anchorPosition.X, anchorPosition.Y, desiredSize.Width, desiredSize.Height); |
|
|
var layoutBounds = new Rect(anchorPosition.X, anchorPosition.Y, desiredSize.Width, desiredSize.Height); |
|
|
_elementManager.SetLayoutBoundsForDataIndex(anchorIndex, layoutBounds); |
|
|
_elementManager.SetLayoutBoundsForDataIndex(anchorIndex, layoutBounds); |
|
|
|
|
|
|
|
|
@ -296,7 +296,7 @@ namespace Avalonia.Layout |
|
|
double lineSpacing, |
|
|
double lineSpacing, |
|
|
int maxItemsPerLine, |
|
|
int maxItemsPerLine, |
|
|
bool disableVirtualization, |
|
|
bool disableVirtualization, |
|
|
string layoutId) |
|
|
string? layoutId) |
|
|
{ |
|
|
{ |
|
|
if (anchorIndex != -1) |
|
|
if (anchorIndex != -1) |
|
|
{ |
|
|
{ |
|
|
@ -322,7 +322,7 @@ namespace Avalonia.Layout |
|
|
// Ensure layout element.
|
|
|
// Ensure layout element.
|
|
|
_elementManager.EnsureElementRealized(direction == GenerateDirection.Forward, currentIndex, layoutId); |
|
|
_elementManager.EnsureElementRealized(direction == GenerateDirection.Forward, currentIndex, layoutId); |
|
|
var currentElement = _elementManager.GetRealizedElement(currentIndex); |
|
|
var currentElement = _elementManager.GetRealizedElement(currentIndex); |
|
|
var desiredSize = MeasureElement(currentElement, currentIndex, availableSize, _context); |
|
|
var desiredSize = MeasureElement(currentElement!, currentIndex, availableSize, _context!); |
|
|
++count; |
|
|
++count; |
|
|
|
|
|
|
|
|
// Lay it out.
|
|
|
// Lay it out.
|
|
|
@ -333,7 +333,7 @@ namespace Avalonia.Layout |
|
|
if (direction == GenerateDirection.Forward) |
|
|
if (direction == GenerateDirection.Forward) |
|
|
{ |
|
|
{ |
|
|
double remainingSpace = _orientation.Minor(availableSize) - (_orientation.MinorStart(previousElementBounds) + _orientation.MinorSize(previousElementBounds) + minItemSpacing + _orientation.Minor(desiredSize)); |
|
|
double remainingSpace = _orientation.Minor(availableSize) - (_orientation.MinorStart(previousElementBounds) + _orientation.MinorSize(previousElementBounds) + minItemSpacing + _orientation.Minor(desiredSize)); |
|
|
if (countInLine >= maxItemsPerLine || _algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace)) |
|
|
if (countInLine >= maxItemsPerLine || _algorithmCallbacks!.Algorithm_ShouldBreakLine(currentIndex, remainingSpace)) |
|
|
{ |
|
|
{ |
|
|
// No more space in this row. wrap to next row.
|
|
|
// No more space in this row. wrap to next row.
|
|
|
_orientation.SetMinorStart(ref currentBounds, 0); |
|
|
_orientation.SetMinorStart(ref currentBounds, 0); |
|
|
@ -371,7 +371,7 @@ namespace Avalonia.Layout |
|
|
{ |
|
|
{ |
|
|
// Backward
|
|
|
// Backward
|
|
|
double remainingSpace = _orientation.MinorStart(previousElementBounds) - (_orientation.Minor(desiredSize) + minItemSpacing); |
|
|
double remainingSpace = _orientation.MinorStart(previousElementBounds) - (_orientation.Minor(desiredSize) + minItemSpacing); |
|
|
if (countInLine >= maxItemsPerLine || _algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace)) |
|
|
if (countInLine >= maxItemsPerLine || _algorithmCallbacks!.Algorithm_ShouldBreakLine(currentIndex, remainingSpace)) |
|
|
{ |
|
|
{ |
|
|
// Does not fit, wrap to the previous row
|
|
|
// Does not fit, wrap to the previous row
|
|
|
var availableSizeMinor = _orientation.Minor(availableSize); |
|
|
var availableSizeMinor = _orientation.Minor(availableSize); |
|
|
@ -432,13 +432,13 @@ namespace Avalonia.Layout |
|
|
// account for that element in the indices inside the realization window.
|
|
|
// account for that element in the indices inside the realization window.
|
|
|
if (direction == GenerateDirection.Forward) |
|
|
if (direction == GenerateDirection.Forward) |
|
|
{ |
|
|
{ |
|
|
int dataCount = _context.ItemCount; |
|
|
int dataCount = _context!.ItemCount; |
|
|
_lastRealizedDataIndexInsideRealizationWindow = previousIndex == dataCount - 1 ? dataCount - 1 : previousIndex - 1; |
|
|
_lastRealizedDataIndexInsideRealizationWindow = previousIndex == dataCount - 1 ? dataCount - 1 : previousIndex - 1; |
|
|
_lastRealizedDataIndexInsideRealizationWindow = Math.Max(0, _lastRealizedDataIndexInsideRealizationWindow); |
|
|
_lastRealizedDataIndexInsideRealizationWindow = Math.Max(0, _lastRealizedDataIndexInsideRealizationWindow); |
|
|
} |
|
|
} |
|
|
else |
|
|
else |
|
|
{ |
|
|
{ |
|
|
int dataCount = _context.ItemCount; |
|
|
int dataCount = _context!.ItemCount; |
|
|
_firstRealizedDataIndexInsideRealizationWindow = previousIndex == 0 ? 0 : previousIndex + 1; |
|
|
_firstRealizedDataIndexInsideRealizationWindow = previousIndex == 0 ? 0 : previousIndex + 1; |
|
|
_firstRealizedDataIndexInsideRealizationWindow = Math.Min(dataCount - 1, _firstRealizedDataIndexInsideRealizationWindow); |
|
|
_firstRealizedDataIndexInsideRealizationWindow = Math.Min(dataCount - 1, _firstRealizedDataIndexInsideRealizationWindow); |
|
|
} |
|
|
} |
|
|
@ -454,7 +454,7 @@ namespace Avalonia.Layout |
|
|
{ |
|
|
{ |
|
|
_elementManager.ClearRealizedRange(); |
|
|
_elementManager.ClearRealizedRange(); |
|
|
// FlowLayout requires that the anchor is the first element in the row.
|
|
|
// FlowLayout requires that the anchor is the first element in the row.
|
|
|
var internalAnchor = _algorithmCallbacks.Algorithm_GetAnchorForTargetElement(index, availableSize, context); |
|
|
var internalAnchor = _algorithmCallbacks!.Algorithm_GetAnchorForTargetElement(index, availableSize, context); |
|
|
|
|
|
|
|
|
// No need to set the position of the anchor.
|
|
|
// No need to set the position of the anchor.
|
|
|
// (0,0) is fine for now since the extent can
|
|
|
// (0,0) is fine for now since the extent can
|
|
|
@ -487,7 +487,7 @@ namespace Avalonia.Layout |
|
|
} |
|
|
} |
|
|
else |
|
|
else |
|
|
{ |
|
|
{ |
|
|
var realizationRect = _context.RealizationRect; |
|
|
var realizationRect = _context!.RealizationRect; |
|
|
var elementBounds = _elementManager.GetLayoutBoundsForDataIndex(index); |
|
|
var elementBounds = _elementManager.GetLayoutBoundsForDataIndex(index); |
|
|
|
|
|
|
|
|
var elementMajorStart = _orientation.MajorStart(elementBounds); |
|
|
var elementMajorStart = _orientation.MajorStart(elementBounds); |
|
|
@ -510,11 +510,11 @@ namespace Avalonia.Layout |
|
|
return shouldContinue; |
|
|
return shouldContinue; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private Rect EstimateExtent(Size availableSize, string layoutId) |
|
|
private Rect EstimateExtent(Size availableSize, string? layoutId) |
|
|
{ |
|
|
{ |
|
|
ILayoutable firstRealizedElement = null; |
|
|
ILayoutable? firstRealizedElement = null; |
|
|
Rect firstBounds = new Rect(); |
|
|
Rect firstBounds = new Rect(); |
|
|
ILayoutable lastRealizedElement = null; |
|
|
ILayoutable? lastRealizedElement = null; |
|
|
Rect lastBounds = new Rect(); |
|
|
Rect lastBounds = new Rect(); |
|
|
int firstDataIndex = -1; |
|
|
int firstDataIndex = -1; |
|
|
int lastDataIndex = -1; |
|
|
int lastDataIndex = -1; |
|
|
@ -531,9 +531,9 @@ namespace Avalonia.Layout |
|
|
lastBounds = _elementManager.GetLayoutBoundsForRealizedIndex(last); |
|
|
lastBounds = _elementManager.GetLayoutBoundsForRealizedIndex(last); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
Rect extent = _algorithmCallbacks.Algorithm_GetExtent( |
|
|
Rect extent = _algorithmCallbacks!.Algorithm_GetExtent( |
|
|
availableSize, |
|
|
availableSize, |
|
|
_context, |
|
|
_context!, |
|
|
firstRealizedElement, |
|
|
firstRealizedElement, |
|
|
firstDataIndex, |
|
|
firstDataIndex, |
|
|
firstBounds, |
|
|
firstBounds, |
|
|
@ -563,7 +563,7 @@ namespace Avalonia.Layout |
|
|
if (_orientation.MajorStart(currentBounds) != currentLineOffset) |
|
|
if (_orientation.MajorStart(currentBounds) != currentLineOffset) |
|
|
{ |
|
|
{ |
|
|
// Staring a new line
|
|
|
// Staring a new line
|
|
|
_algorithmCallbacks.Algorithm_OnLineArranged(currentDataIndex - countInLine, countInLine, currentLineSize, _context); |
|
|
_algorithmCallbacks!.Algorithm_OnLineArranged(currentDataIndex - countInLine, countInLine, currentLineSize, _context!); |
|
|
countInLine = 0; |
|
|
countInLine = 0; |
|
|
currentLineOffset = _orientation.MajorStart(currentBounds); |
|
|
currentLineOffset = _orientation.MajorStart(currentBounds); |
|
|
currentLineSize = 0; |
|
|
currentLineSize = 0; |
|
|
@ -575,7 +575,7 @@ namespace Avalonia.Layout |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Raise for the last line.
|
|
|
// Raise for the last line.
|
|
|
_algorithmCallbacks.Algorithm_OnLineArranged(_lastRealizedDataIndexInsideRealizationWindow - countInLine + 1, countInLine, currentLineSize, _context); |
|
|
_algorithmCallbacks!.Algorithm_OnLineArranged(_lastRealizedDataIndexInsideRealizationWindow - countInLine + 1, countInLine, currentLineSize, _context!); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
@ -584,7 +584,7 @@ namespace Avalonia.Layout |
|
|
Size finalSize, |
|
|
Size finalSize, |
|
|
LineAlignment lineAlignment, |
|
|
LineAlignment lineAlignment, |
|
|
bool isWrapping, |
|
|
bool isWrapping, |
|
|
string layoutId) |
|
|
string? layoutId) |
|
|
{ |
|
|
{ |
|
|
// Walk through the realized elements one line at a time and
|
|
|
// Walk through the realized elements one line at a time and
|
|
|
// align them, Then call element.Arrange with the arranged bounds.
|
|
|
// align them, Then call element.Arrange with the arranged bounds.
|
|
|
@ -636,7 +636,7 @@ namespace Avalonia.Layout |
|
|
LineAlignment lineAlignment, |
|
|
LineAlignment lineAlignment, |
|
|
bool isWrapping, |
|
|
bool isWrapping, |
|
|
Size finalSize, |
|
|
Size finalSize, |
|
|
string layoutId) |
|
|
string? layoutId) |
|
|
{ |
|
|
{ |
|
|
for (int rangeIndex = lineStartIndex; rangeIndex < lineStartIndex + countInLine; ++rangeIndex) |
|
|
for (int rangeIndex = lineStartIndex; rangeIndex < lineStartIndex + countInLine; ++rangeIndex) |
|
|
{ |
|
|
{ |
|
|
@ -723,11 +723,11 @@ namespace Avalonia.Layout |
|
|
{ |
|
|
{ |
|
|
if (IsVirtualizingContext) |
|
|
if (IsVirtualizingContext) |
|
|
{ |
|
|
{ |
|
|
_context.LayoutOrigin = new Point(_lastExtent.X, _lastExtent.Y); |
|
|
_context!.LayoutOrigin = new Point(_lastExtent.X, _lastExtent.Y); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public ILayoutable GetElementIfRealized(int dataIndex) |
|
|
public ILayoutable? GetElementIfRealized(int dataIndex) |
|
|
{ |
|
|
{ |
|
|
if (_elementManager.IsDataIndexRealized(dataIndex)) |
|
|
if (_elementManager.IsDataIndexRealized(dataIndex)) |
|
|
{ |
|
|
{ |
|
|
|