Browse Source

Merge pull request #3424 from AvaloniaUI/port/items-repeater

Port ItemsRepeater changes from WinUI
pull/3444/head
Steven Kirk 6 years ago
committed by GitHub
parent
commit
155e9d4bbd
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 34
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  2. 77
      src/Avalonia.Controls/Repeater/ViewManager.cs
  3. 32
      src/Avalonia.Layout/FlowLayoutAlgorithm.cs
  4. 16
      src/Avalonia.Layout/NonVirtualizingLayout.cs
  5. 14
      src/Avalonia.Layout/NonVirtualizingLayoutContext.cs
  6. 2
      src/Avalonia.Layout/StackLayout.cs
  7. 54
      src/Avalonia.Layout/UniformGridLayout.cs
  8. 34
      src/Avalonia.Layout/UniformGridLayoutState.cs

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

@ -562,33 +562,35 @@ namespace Avalonia.Controls
if (Layout != null)
{
if (Layout is VirtualizingLayout virtualLayout)
{
var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
try
{
_processingItemsSourceChange = args;
try
if (Layout is VirtualizingLayout virtualLayout)
{
virtualLayout.OnItemsChanged(GetLayoutContext(), newValue, args);
}
finally
{
_processingItemsSourceChange = null;
}
}
else if (Layout is NonVirtualizingLayout nonVirtualLayout)
{
// Walk through all the elements and make sure they are cleared for
// non-virtualizing layouts.
foreach (var element in Children)
else if (Layout is NonVirtualizingLayout nonVirtualLayout)
{
if (GetVirtualizationInfo(element).IsRealized)
// Walk through all the elements and make sure they are cleared for
// non-virtualizing layouts.
foreach (var element in Children)
{
ClearElementImpl(element);
if (GetVirtualizationInfo(element).IsRealized)
{
ClearElementImpl(element);
}
}
Children.Clear();
}
}
finally
{
_processingItemsSourceChange = null;
}
InvalidateMeasure();
}

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

@ -109,11 +109,22 @@ namespace Avalonia.Controls
public void ClearElementToElementFactory(IControl element)
{
var virtInfo = ItemsRepeater.GetVirtualizationInfo(element);
var clearedIndex = virtInfo.Index;
_owner.OnElementClearing(element);
_owner.ItemTemplateShim.RecycleElement(_owner, element);
if (_owner.ItemTemplateShim != null)
{
_owner.ItemTemplateShim.RecycleElement(_owner, element);
}
else
{
// No ItemTemplate to recycle to, remove the element from the children collection.
if (!_owner.Children.Remove(element))
{
throw new InvalidOperationException("ItemsRepeater's child not found in its Children collection.");
}
}
var virtInfo = ItemsRepeater.GetVirtualizationInfo(element);
virtInfo.MoveOwnershipToElementFactory();
if (_lastFocusedElement == element)
@ -121,9 +132,8 @@ namespace Avalonia.Controls
// Focused element is going away. Remove the tracked last focused element
// and pick a reasonable next focus if we can find one within the layout
// realized elements.
MoveFocusFromClearedIndex(clearedIndex);
MoveFocusFromClearedIndex(virtInfo.Index);
}
}
private void MoveFocusFromClearedIndex(int clearedIndex)
@ -190,7 +200,8 @@ namespace Avalonia.Controls
{
if (virtInfo == null)
{
throw new ArgumentException("Element is not a child of this ItemsRepeater.");
//Element is not a child of this ItemsRepeater.
return -1;
}
return virtInfo.IsRealized || virtInfo.IsInUniqueIdResetPool ? virtInfo.Index : -1;
@ -515,21 +526,52 @@ namespace Avalonia.Controls
return element;
}
// There are several cases handled here with respect to which element gets returned and when DataContext is modified.
//
// 1. If there is no ItemTemplate:
// 1.1 If data is an IControl -> the data is returned
// 1.2 If data is not an IControl -> a default DataTemplate is used to fetch element and DataContext is set to data
//
// 2. If there is an ItemTemplate:
// 2.1 If data is not an IControl -> Element is fetched from ElementFactory and DataContext is set to the data
// 2.2 If data is an IControl:
// 2.2.1 If Element returned by the ElementFactory is the same as the data -> Element (a.k.a. data) is returned as is
// 2.2.2 If Element returned by the ElementFactory is not the same as the data
// -> Element that is fetched from the ElementFactory is returned and
// DataContext is set to the data's DataContext (if it exists), otherwise it is set to the data itself
private IControl GetElementFromElementFactory(int index)
{
// The view generator is the provider of last resort.
var data = _owner.ItemsSourceView.GetAt(index);
var providedElementFactory = _owner.ItemTemplateShim;
ItemTemplateWrapper GetElementFactory()
{
if (providedElementFactory == null)
{
var factory = FuncDataTemplate.Default;
_owner.ItemTemplate = factory;
return _owner.ItemTemplateShim;
}
var itemTemplateFactory = _owner.ItemTemplateShim;
if (itemTemplateFactory == null)
return providedElementFactory;
}
IControl GetElement()
{
// If no ItemTemplate was provided, use a default
var factory = FuncDataTemplate.Default;
_owner.ItemTemplate = factory;
itemTemplateFactory = _owner.ItemTemplateShim;
if (providedElementFactory == null)
{
if (data is IControl dataAsElement)
{
return dataAsElement;
}
}
var elementFactory = GetElementFactory();
return elementFactory.GetElement(_owner, data);
}
var data = _owner.ItemsSourceView.GetAt(index);
var element = itemTemplateFactory.GetElement(_owner, data);
var element = GetElement();
var virtInfo = ItemsRepeater.TryGetVirtualizationInfo(element);
if (virtInfo == null)
@ -537,8 +579,11 @@ namespace Avalonia.Controls
virtInfo = ItemsRepeater.CreateAndInitializeVirtualizationInfo(element);
}
// Prepare the element
element.DataContext = data;
if (data != element)
{
// Prepare the element
element.DataContext = data;
}
virtInfo.MoveOwnershipToLayoutFromElementFactory(
index,

32
src/Avalonia.Layout/FlowLayoutAlgorithm.cs

@ -72,6 +72,7 @@ namespace Avalonia.Layout
bool isWrapping,
double minItemSpacing,
double lineSpacing,
int maxItemsPerLine,
ScrollOrientation orientation,
string layoutId)
{
@ -94,14 +95,14 @@ namespace Avalonia.Layout
_elementManager.OnBeginMeasure(orientation);
int anchorIndex = GetAnchorIndex(availableSize, isWrapping, minItemSpacing, layoutId);
Generate(GenerateDirection.Forward, anchorIndex, availableSize, minItemSpacing, lineSpacing, layoutId);
Generate(GenerateDirection.Backward, anchorIndex, availableSize, minItemSpacing, lineSpacing, layoutId);
Generate(GenerateDirection.Forward, anchorIndex, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, layoutId);
Generate(GenerateDirection.Backward, anchorIndex, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, layoutId);
if (isWrapping && IsReflowRequired())
{
var firstElementBounds = _elementManager.GetLayoutBoundsForRealizedIndex(0);
_orientation.SetMinorStart(ref firstElementBounds, 0);
_elementManager.SetLayoutBoundsForRealizedIndex(0, firstElementBounds);
Generate(GenerateDirection.Forward, 0 /*anchorIndex*/, availableSize, minItemSpacing, lineSpacing, layoutId);
Generate(GenerateDirection.Forward, 0 /*anchorIndex*/, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, layoutId);
}
RaiseLineArranged();
@ -115,10 +116,11 @@ namespace Avalonia.Layout
public Size Arrange(
Size finalSize,
VirtualizingLayoutContext context,
bool isWrapping,
LineAlignment lineAlignment,
string layoutId)
{
ArrangeVirtualizingLayout(finalSize, lineAlignment, layoutId);
ArrangeVirtualizingLayout(finalSize, lineAlignment, isWrapping, layoutId);
return new Size(
Math.Max(finalSize.Width, _lastExtent.Width),
@ -270,6 +272,7 @@ namespace Avalonia.Layout
Size availableSize,
double minItemSpacing,
double lineSpacing,
int maxItemsPerLine,
string layoutId)
{
if (anchorIndex != -1)
@ -280,7 +283,7 @@ namespace Avalonia.Layout
var anchorBounds = _elementManager.GetLayoutBoundsForDataIndex(anchorIndex);
var lineOffset = _orientation.MajorStart(anchorBounds);
var lineMajorSize = _orientation.MajorSize(anchorBounds);
int countInLine = 1;
var countInLine = 1;
int count = 0;
bool lineNeedsReposition = false;
@ -301,7 +304,7 @@ namespace Avalonia.Layout
if (direction == GenerateDirection.Forward)
{
double remainingSpace = _orientation.Minor(availableSize) - (_orientation.MinorStart(previousElementBounds) + _orientation.MinorSize(previousElementBounds) + minItemSpacing + _orientation.Minor(desiredSize));
if (_algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace))
if (countInLine >= maxItemsPerLine || _algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace))
{
// No more space in this row. wrap to next row.
_orientation.SetMinorStart(ref currentBounds, 0);
@ -339,7 +342,7 @@ namespace Avalonia.Layout
{
// Backward
double remainingSpace = _orientation.MinorStart(previousElementBounds) - (_orientation.Minor(desiredSize) + minItemSpacing);
if (_algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace))
if (countInLine >= maxItemsPerLine || _algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace))
{
// Does not fit, wrap to the previous row
var availableSizeMinor = _orientation.Minor(availableSize);
@ -544,6 +547,7 @@ namespace Avalonia.Layout
private void ArrangeVirtualizingLayout(
Size finalSize,
LineAlignment lineAlignment,
bool isWrapping,
string layoutId)
{
// Walk through the realized elements one line at a time and
@ -563,7 +567,7 @@ namespace Avalonia.Layout
if (_orientation.MajorStart(currentBounds) != currentLineOffset)
{
spaceAtLineEnd = _orientation.Minor(finalSize) - _orientation.MinorStart(previousElementBounds) - _orientation.MinorSize(previousElementBounds);
PerformLineAlignment(i - countInLine, countInLine, spaceAtLineStart, spaceAtLineEnd, currentLineSize, lineAlignment, layoutId);
PerformLineAlignment(i - countInLine, countInLine, spaceAtLineStart, spaceAtLineEnd, currentLineSize, lineAlignment, isWrapping, finalSize, layoutId);
spaceAtLineStart = _orientation.MinorStart(currentBounds);
countInLine = 0;
currentLineOffset = _orientation.MajorStart(currentBounds);
@ -580,7 +584,7 @@ namespace Avalonia.Layout
if (countInLine > 0)
{
var spaceAtEnd = _orientation.Minor(finalSize) - _orientation.MinorStart(previousElementBounds) - _orientation.MinorSize(previousElementBounds);
PerformLineAlignment(realizedElementCount - countInLine, countInLine, spaceAtLineStart, spaceAtEnd, currentLineSize, lineAlignment, layoutId);
PerformLineAlignment(realizedElementCount - countInLine, countInLine, spaceAtLineStart, spaceAtEnd, currentLineSize, lineAlignment, isWrapping, finalSize, layoutId);
}
}
}
@ -594,6 +598,8 @@ namespace Avalonia.Layout
double spaceAtLineEnd,
double lineSize,
LineAlignment lineAlignment,
bool isWrapping,
Size finalSize,
string layoutId)
{
for (int rangeIndex = lineStartIndex; rangeIndex < lineStartIndex + countInLine; ++rangeIndex)
@ -659,6 +665,14 @@ namespace Avalonia.Layout
}
bounds = bounds.Translate(-_lastExtent.Position);
if (!isWrapping)
{
_orientation.SetMinorSize(
ref bounds,
Math.Max(_orientation.MinorSize(bounds), _orientation.Minor(finalSize)));
}
var element = _elementManager.GetAt(rangeIndex);
element.Arrange(bounds);
}

16
src/Avalonia.Layout/NonVirtualizingLayout.cs

@ -20,25 +20,25 @@ namespace Avalonia.Layout
/// <inheritdoc/>
public sealed override void InitializeForContext(LayoutContext context)
{
InitializeForContextCore((VirtualizingLayoutContext)context);
InitializeForContextCore((NonVirtualizingLayoutContext)context);
}
/// <inheritdoc/>
public sealed override void UninitializeForContext(LayoutContext context)
{
UninitializeForContextCore((VirtualizingLayoutContext)context);
UninitializeForContextCore((NonVirtualizingLayoutContext)context);
}
/// <inheritdoc/>
public sealed override Size Measure(LayoutContext context, Size availableSize)
{
return MeasureOverride((VirtualizingLayoutContext)context, availableSize);
return MeasureOverride((NonVirtualizingLayoutContext)context, availableSize);
}
/// <inheritdoc/>
public sealed override Size Arrange(LayoutContext context, Size finalSize)
{
return ArrangeOverride((VirtualizingLayoutContext)context, finalSize);
return ArrangeOverride((NonVirtualizingLayoutContext)context, finalSize);
}
/// <summary>
@ -49,7 +49,7 @@ namespace Avalonia.Layout
/// The context object that facilitates communication between the layout and its host
/// container.
/// </param>
protected virtual void InitializeForContextCore(VirtualizingLayoutContext context)
protected virtual void InitializeForContextCore(LayoutContext context)
{
}
@ -61,7 +61,7 @@ namespace Avalonia.Layout
/// The context object that facilitates communication between the layout and its host
/// container.
/// </param>
protected virtual void UninitializeForContextCore(VirtualizingLayoutContext context)
protected virtual void UninitializeForContextCore(LayoutContext context)
{
}
@ -83,7 +83,7 @@ namespace Avalonia.Layout
/// of the allocated sizes for child objects or based on other considerations such as a
/// fixed container size.
/// </returns>
protected abstract Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize);
protected abstract Size MeasureOverride(NonVirtualizingLayoutContext context, Size availableSize);
/// <summary>
/// When implemented in a derived class, provides the behavior for the "Arrange" pass of
@ -98,6 +98,6 @@ namespace Avalonia.Layout
/// its children.
/// </param>
/// <returns>The actual size that is used after the element is arranged in layout.</returns>
protected virtual Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize) => finalSize;
protected virtual Size ArrangeOverride(NonVirtualizingLayoutContext context, Size finalSize) => finalSize;
}
}

14
src/Avalonia.Layout/NonVirtualizingLayoutContext.cs

@ -0,0 +1,14 @@
// This source file is adapted from the WinUI project.
// (https://github.com/microsoft/microsoft-ui-xaml)
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
namespace Avalonia.Layout
{
/// <summary>
/// Represents the base class for layout context types that do not support virtualization.
/// </summary>
public abstract class NonVirtualizingLayoutContext : LayoutContext
{
}
}

2
src/Avalonia.Layout/StackLayout.cs

@ -267,6 +267,7 @@ namespace Avalonia.Layout
false,
0,
Spacing,
int.MaxValue,
_orientation.ScrollOrientation,
LayoutId);
@ -278,6 +279,7 @@ namespace Avalonia.Layout
var value = GetFlowAlgorithm(context).Arrange(
finalSize,
context,
false,
FlowLayoutAlgorithm.LineAlignment.Start,
LayoutId);

54
src/Avalonia.Layout/UniformGridLayout.cs

@ -110,6 +110,12 @@ namespace Avalonia.Layout
public static readonly StyledProperty<double> MinRowSpacingProperty =
AvaloniaProperty.Register<UniformGridLayout, double>(nameof(MinRowSpacing));
/// <summary>
/// Defines the <see cref="MaximumRowsOrColumnsProperty"/> property.
/// </summary>
public static readonly StyledProperty<int> MaximumRowsOrColumnsProperty =
AvaloniaProperty.Register<UniformGridLayout, int>(nameof(MinItemWidth));
/// <summary>
/// Defines the <see cref="Orientation"/> property.
/// </summary>
@ -123,6 +129,7 @@ namespace Avalonia.Layout
private double _minColumnSpacing;
private UniformGridLayoutItemsJustification _itemsJustification;
private UniformGridLayoutItemsStretch _itemsStretch;
private int _maximumRowsOrColumns = int.MaxValue;
/// <summary>
/// Initializes a new instance of the <see cref="UniformGridLayout"/> class.
@ -219,6 +226,15 @@ namespace Avalonia.Layout
set => SetValue(MinRowSpacingProperty, value);
}
/// <summary>
/// Gets or sets the maximum row or column count.
/// </summary>
public int MaximumRowsOrColumns
{
get => GetValue(MaximumRowsOrColumnsProperty);
set => SetValue(MaximumRowsOrColumnsProperty, value);
}
/// <summary>
/// Gets or sets the axis along which items are laid out.
/// </summary>
@ -269,15 +285,17 @@ namespace Avalonia.Layout
{
var gridState = (UniformGridLayoutState)context.LayoutState;
var lastExtent = gridState.FlowAlgorithm.LastExtent;
int itemsPerLine = Math.Max(1, (int)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context)));
double majorSize = (itemsCount / itemsPerLine) * GetMajorSizeWithSpacing(context);
double realizationWindowStartWithinExtent = _orientation.MajorStart(realizationRect) - _orientation.MajorStart(lastExtent);
var itemsPerLine = Math.Min( // note use of unsigned ints
Math.Max(1u, (uint)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context))),
Math.Max(1u, (uint)_maximumRowsOrColumns));
var majorSize = (itemsCount / itemsPerLine) * GetMajorSizeWithSpacing(context);
var realizationWindowStartWithinExtent = _orientation.MajorStart(realizationRect) - _orientation.MajorStart(lastExtent);
if ((realizationWindowStartWithinExtent + _orientation.MajorSize(realizationRect)) >= 0 && realizationWindowStartWithinExtent <= majorSize)
{
double offset = Math.Max(0.0, _orientation.MajorStart(realizationRect) - _orientation.MajorStart(lastExtent));
int anchorRowIndex = (int)(offset / GetMajorSizeWithSpacing(context));
anchorIndex = Math.Max(0, Math.Min(itemsCount - 1, anchorRowIndex * itemsPerLine));
anchorIndex = (int)Math.Max(0, Math.Min(itemsCount - 1, anchorRowIndex * itemsPerLine));
bounds = GetLayoutRectForDataIndex(availableSize, anchorIndex, lastExtent, context);
}
}
@ -299,7 +317,9 @@ namespace Avalonia.Layout
int count = context.ItemCount;
if (targetIndex >= 0 && targetIndex < count)
{
int itemsPerLine = Math.Max(1, (int)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context)));
int itemsPerLine = (int)Math.Min( // note use of unsigned ints
Math.Max(1u, (uint)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context))),
Math.Max(1u, _maximumRowsOrColumns));
int indexOfFirstInLine = (targetIndex / itemsPerLine) * itemsPerLine;
index = indexOfFirstInLine;
var state = context.LayoutState as UniformGridLayoutState;
@ -329,17 +349,21 @@ namespace Avalonia.Layout
// Constants
int itemsCount = context.ItemCount;
double availableSizeMinor = _orientation.Minor(availableSize);
int itemsPerLine = Math.Max(1, !double.IsInfinity(availableSizeMinor) ?
(int)(availableSizeMinor / GetMinorSizeWithSpacing(context)) : itemsCount);
int itemsPerLine =
(int)Math.Min( // note use of unsigned ints
Math.Max(1u, !double.IsInfinity(availableSizeMinor)
? (uint)(availableSizeMinor / GetMinorSizeWithSpacing(context))
: (uint)itemsCount),
Math.Max(1u, _maximumRowsOrColumns));
double lineSize = GetMajorSizeWithSpacing(context);
if (itemsCount > 0)
{
_orientation.SetMinorSize(
ref extent,
!double.IsInfinity(availableSizeMinor) ?
!double.IsInfinity(availableSizeMinor) && _itemsStretch == UniformGridLayoutItemsStretch.Fill ?
availableSizeMinor :
Math.Max(0.0, itemsCount * GetMinorSizeWithSpacing(context) - (double)MinItemSpacing));
Math.Max(0.0, itemsPerLine * GetMinorSizeWithSpacing(context) - (double)MinItemSpacing));
_orientation.SetMajorSize(
ref extent,
Math.Max(0.0, (itemsCount / itemsPerLine) * lineSize - (double)LineSpacing));
@ -398,7 +422,7 @@ namespace Avalonia.Layout
// Set the width and height on the grid state. If the user already set them then use the preset.
// If not, we have to measure the first element and get back a size which we're going to be using for the rest of the items.
var gridState = (UniformGridLayoutState)context.LayoutState;
gridState.EnsureElementSize(availableSize, context, _minItemWidth, _minItemHeight, _itemsStretch, Orientation, MinRowSpacing, MinColumnSpacing);
gridState.EnsureElementSize(availableSize, context, _minItemWidth, _minItemHeight, _itemsStretch, Orientation, MinRowSpacing, MinColumnSpacing, _maximumRowsOrColumns);
var desiredSize = GetFlowAlgorithm(context).Measure(
availableSize,
@ -406,6 +430,7 @@ namespace Avalonia.Layout
true,
MinItemSpacing,
LineSpacing,
_maximumRowsOrColumns,
_orientation.ScrollOrientation,
LayoutId);
@ -421,6 +446,7 @@ namespace Avalonia.Layout
var value = GetFlowAlgorithm(context).Arrange(
finalSize,
context,
true,
(FlowLayoutAlgorithm.LineAlignment)_itemsJustification,
LayoutId);
return new Size(value.Width, value.Height);
@ -471,6 +497,10 @@ namespace Avalonia.Layout
{
_minItemHeight = (double)args.NewValue;
}
else if (args.Property == MaximumRowsOrColumnsProperty)
{
_maximumRowsOrColumns = (int)args.NewValue;
}
InvalidateLayout();
}
@ -499,7 +529,9 @@ namespace Avalonia.Layout
Rect lastExtent,
VirtualizingLayoutContext context)
{
int itemsPerLine = Math.Max(1, (int)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context)));
int itemsPerLine = (int)Math.Min( //note use of unsigned ints
Math.Max(1u, (uint)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context))),
Math.Max(1u, _maximumRowsOrColumns));
int rowIndex = (int)(index / itemsPerLine);
int indexInRow = index - (rowIndex * itemsPerLine);

34
src/Avalonia.Layout/UniformGridLayoutState.cs

@ -48,8 +48,14 @@ namespace Avalonia.Layout
UniformGridLayoutItemsStretch stretch,
Orientation orientation,
double minRowSpacing,
double minColumnSpacing)
double minColumnSpacing,
int maxItemsPerLine)
{
if (maxItemsPerLine == 0)
{
maxItemsPerLine = 1;
}
if (context.ItemCount > 0)
{
// If the first element is realized we don't need to cache it or to get it from the context
@ -57,7 +63,7 @@ namespace Avalonia.Layout
if (realizedElement != null)
{
realizedElement.Measure(availableSize);
SetSize(realizedElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing);
SetSize(realizedElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing, maxItemsPerLine);
_cachedFirstElement = null;
}
else
@ -72,7 +78,7 @@ namespace Avalonia.Layout
_cachedFirstElement.Measure(availableSize);
SetSize(_cachedFirstElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing);
SetSize(_cachedFirstElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing, maxItemsPerLine);
// See if we can move ownership to the flow algorithm. If we can, we do not need a local cache.
bool added = FlowAlgorithm.TryAddElement0(_cachedFirstElement);
@ -92,8 +98,14 @@ namespace Avalonia.Layout
UniformGridLayoutItemsStretch stretch,
Orientation orientation,
double minRowSpacing,
double minColumnSpacing)
double minColumnSpacing,
int maxItemsPerLine)
{
if (maxItemsPerLine == 0)
{
maxItemsPerLine = 1;
}
EffectiveItemWidth = (double.IsNaN(layoutItemWidth) ? element.DesiredSize.Width : layoutItemWidth);
EffectiveItemHeight = (double.IsNaN(LayoutItemHeight) ? element.DesiredSize.Height : LayoutItemHeight);
@ -101,11 +113,17 @@ namespace Avalonia.Layout
var minorItemSpacing = orientation == Orientation.Vertical ? minRowSpacing : minColumnSpacing;
var itemSizeMinor = orientation == Orientation.Horizontal ? EffectiveItemWidth : EffectiveItemHeight;
itemSizeMinor += minorItemSpacing;
var numItemsPerColumn = (int)(Math.Max(1.0, availableSizeMinor / itemSizeMinor));
var remainingSpace = ((int)availableSizeMinor) % ((int)itemSizeMinor);
var extraMinorPixelsForEachItem = remainingSpace / numItemsPerColumn;
double extraMinorPixelsForEachItem = 0.0;
if (!double.IsInfinity(availableSizeMinor))
{
var numItemsPerColumn = Math.Min(
maxItemsPerLine,
Math.Max(1.0, availableSizeMinor / (itemSizeMinor + minorItemSpacing)));
var usedSpace = (numItemsPerColumn * (itemSizeMinor + minorItemSpacing)) - minorItemSpacing;
var remainingSpace = ((int)(availableSizeMinor - usedSpace));
extraMinorPixelsForEachItem = remainingSpace / ((int)numItemsPerColumn);
}
if (stretch == UniformGridLayoutItemsStretch.Fill)
{

Loading…
Cancel
Save