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 7 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, bool isWrapping,
double minItemSpacing, double minItemSpacing,
double lineSpacing, double lineSpacing,
int maxItemsPerLine,
ScrollOrientation orientation, ScrollOrientation orientation,
string layoutId) string layoutId)
{ {
@ -94,14 +95,14 @@ namespace Avalonia.Layout
_elementManager.OnBeginMeasure(orientation); _elementManager.OnBeginMeasure(orientation);
int anchorIndex = GetAnchorIndex(availableSize, isWrapping, minItemSpacing, layoutId); int anchorIndex = GetAnchorIndex(availableSize, isWrapping, minItemSpacing, layoutId);
Generate(GenerateDirection.Forward, anchorIndex, availableSize, minItemSpacing, lineSpacing, layoutId); Generate(GenerateDirection.Forward, anchorIndex, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, layoutId);
Generate(GenerateDirection.Backward, anchorIndex, availableSize, minItemSpacing, lineSpacing, layoutId); Generate(GenerateDirection.Backward, anchorIndex, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, layoutId);
if (isWrapping && IsReflowRequired()) if (isWrapping && IsReflowRequired())
{ {
var firstElementBounds = _elementManager.GetLayoutBoundsForRealizedIndex(0); var firstElementBounds = _elementManager.GetLayoutBoundsForRealizedIndex(0);
_orientation.SetMinorStart(ref firstElementBounds, 0); _orientation.SetMinorStart(ref firstElementBounds, 0);
_elementManager.SetLayoutBoundsForRealizedIndex(0, firstElementBounds); _elementManager.SetLayoutBoundsForRealizedIndex(0, firstElementBounds);
Generate(GenerateDirection.Forward, 0 /*anchorIndex*/, availableSize, minItemSpacing, lineSpacing, layoutId); Generate(GenerateDirection.Forward, 0 /*anchorIndex*/, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, layoutId);
} }
RaiseLineArranged(); RaiseLineArranged();
@ -270,6 +271,7 @@ namespace Avalonia.Layout
Size availableSize, Size availableSize,
double minItemSpacing, double minItemSpacing,
double lineSpacing, double lineSpacing,
int maxItemsPerLine,
string layoutId) string layoutId)
{ {
if (anchorIndex != -1) if (anchorIndex != -1)
@ -280,7 +282,7 @@ namespace Avalonia.Layout
var anchorBounds = _elementManager.GetLayoutBoundsForDataIndex(anchorIndex); var anchorBounds = _elementManager.GetLayoutBoundsForDataIndex(anchorIndex);
var lineOffset = _orientation.MajorStart(anchorBounds); var lineOffset = _orientation.MajorStart(anchorBounds);
var lineMajorSize = _orientation.MajorSize(anchorBounds); var lineMajorSize = _orientation.MajorSize(anchorBounds);
int countInLine = 1; var countInLine = 1;
int count = 0; int count = 0;
bool lineNeedsReposition = false; bool lineNeedsReposition = false;
@ -301,7 +303,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 (_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);
@ -339,7 +341,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 (_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);

1
src/Avalonia.Layout/StackLayout.cs

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

49
src/Avalonia.Layout/UniformGridLayout.cs

@ -110,6 +110,12 @@ namespace Avalonia.Layout
public static readonly StyledProperty<double> MinRowSpacingProperty = public static readonly StyledProperty<double> MinRowSpacingProperty =
AvaloniaProperty.Register<UniformGridLayout, double>(nameof(MinRowSpacing)); 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> /// <summary>
/// Defines the <see cref="Orientation"/> property. /// Defines the <see cref="Orientation"/> property.
/// </summary> /// </summary>
@ -123,6 +129,7 @@ namespace Avalonia.Layout
private double _minColumnSpacing; private double _minColumnSpacing;
private UniformGridLayoutItemsJustification _itemsJustification; private UniformGridLayoutItemsJustification _itemsJustification;
private UniformGridLayoutItemsStretch _itemsStretch; private UniformGridLayoutItemsStretch _itemsStretch;
private int _maximumRowsOrColumns = int.MaxValue;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="UniformGridLayout"/> class. /// Initializes a new instance of the <see cref="UniformGridLayout"/> class.
@ -219,6 +226,15 @@ namespace Avalonia.Layout
set => SetValue(MinRowSpacingProperty, value); 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> /// <summary>
/// Gets or sets the axis along which items are laid out. /// Gets or sets the axis along which items are laid out.
/// </summary> /// </summary>
@ -269,15 +285,17 @@ namespace Avalonia.Layout
{ {
var gridState = (UniformGridLayoutState)context.LayoutState; var gridState = (UniformGridLayoutState)context.LayoutState;
var lastExtent = gridState.FlowAlgorithm.LastExtent; var lastExtent = gridState.FlowAlgorithm.LastExtent;
int itemsPerLine = Math.Max(1, (int)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context))); var itemsPerLine = Math.Min( // note use of unsigned ints
double majorSize = (itemsCount / itemsPerLine) * GetMajorSizeWithSpacing(context); Math.Max(1u, (uint)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context))),
double realizationWindowStartWithinExtent = _orientation.MajorStart(realizationRect) - _orientation.MajorStart(lastExtent); 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) if ((realizationWindowStartWithinExtent + _orientation.MajorSize(realizationRect)) >= 0 && realizationWindowStartWithinExtent <= majorSize)
{ {
double offset = Math.Max(0.0, _orientation.MajorStart(realizationRect) - _orientation.MajorStart(lastExtent)); double offset = Math.Max(0.0, _orientation.MajorStart(realizationRect) - _orientation.MajorStart(lastExtent));
int anchorRowIndex = (int)(offset / GetMajorSizeWithSpacing(context)); 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); bounds = GetLayoutRectForDataIndex(availableSize, anchorIndex, lastExtent, context);
} }
} }
@ -299,7 +317,9 @@ namespace Avalonia.Layout
int count = context.ItemCount; int count = context.ItemCount;
if (targetIndex >= 0 && targetIndex < count) 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; int indexOfFirstInLine = (targetIndex / itemsPerLine) * itemsPerLine;
index = indexOfFirstInLine; index = indexOfFirstInLine;
var state = context.LayoutState as UniformGridLayoutState; var state = context.LayoutState as UniformGridLayoutState;
@ -329,8 +349,12 @@ namespace Avalonia.Layout
// Constants // Constants
int itemsCount = context.ItemCount; int itemsCount = context.ItemCount;
double availableSizeMinor = _orientation.Minor(availableSize); double availableSizeMinor = _orientation.Minor(availableSize);
int itemsPerLine = Math.Max(1, !double.IsInfinity(availableSizeMinor) ? int itemsPerLine =
(int)(availableSizeMinor / GetMinorSizeWithSpacing(context)) : itemsCount); (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); double lineSize = GetMajorSizeWithSpacing(context);
if (itemsCount > 0) 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. // 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. // 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; 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( var desiredSize = GetFlowAlgorithm(context).Measure(
availableSize, availableSize,
@ -406,6 +430,7 @@ namespace Avalonia.Layout
true, true,
MinItemSpacing, MinItemSpacing,
LineSpacing, LineSpacing,
_maximumRowsOrColumns,
_orientation.ScrollOrientation, _orientation.ScrollOrientation,
LayoutId); LayoutId);
@ -471,6 +496,10 @@ namespace Avalonia.Layout
{ {
_minItemHeight = (double)args.NewValue; _minItemHeight = (double)args.NewValue;
} }
else if (args.Property == MaximumRowsOrColumnsProperty)
{
_maximumRowsOrColumns = (int)args.NewValue;
}
InvalidateLayout(); InvalidateLayout();
} }
@ -499,7 +528,9 @@ namespace Avalonia.Layout
Rect lastExtent, Rect lastExtent,
VirtualizingLayoutContext context) 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 rowIndex = (int)(index / itemsPerLine);
int indexInRow = index - (rowIndex * itemsPerLine); int indexInRow = index - (rowIndex * itemsPerLine);

34
src/Avalonia.Layout/UniformGridLayoutState.cs

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

Loading…
Cancel
Save