Browse Source

Add a MaximumRowsOrColumns to Uniformgridlayout

* Add a MaximumRowsOrColumns property to UniformGridLayout

* Fix nit

* Fix bugs

Ported from 6bd03c98f7?w=1
pull/3424/head
Steven Kirk 6 years ago
parent
commit
36fdb8e534
  1. 14
      src/Avalonia.Layout/FlowLayoutAlgorithm.cs
  2. 1
      src/Avalonia.Layout/StackLayout.cs
  3. 49
      src/Avalonia.Layout/UniformGridLayout.cs
  4. 34
      src/Avalonia.Layout/UniformGridLayoutState.cs

14
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();
@ -270,6 +271,7 @@ namespace Avalonia.Layout
Size availableSize,
double minItemSpacing,
double lineSpacing,
int maxItemsPerLine,
string layoutId)
{
if (anchorIndex != -1)
@ -280,7 +282,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 +303,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 +341,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);

1
src/Avalonia.Layout/StackLayout.cs

@ -267,6 +267,7 @@ namespace Avalonia.Layout
false,
0,
Spacing,
int.MaxValue,
_orientation.ScrollOrientation,
LayoutId);

49
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,8 +349,12 @@ 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)
@ -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);
@ -471,6 +496,10 @@ namespace Avalonia.Layout
{
_minItemHeight = (double)args.NewValue;
}
else if (args.Property == MaximumRowsOrColumnsProperty)
{
_maximumRowsOrColumns = (int)args.NewValue;
}
InvalidateLayout();
}
@ -499,7 +528,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