csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
182 lines
7.5 KiB
182 lines
7.5 KiB
// 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.
|
|
|
|
using System;
|
|
using System.Collections.Specialized;
|
|
|
|
namespace Avalonia.Layout
|
|
{
|
|
/// <summary>
|
|
/// Represents the state of a <see cref="UniformGridLayout"/>.
|
|
/// </summary>
|
|
public class UniformGridLayoutState
|
|
{
|
|
// We need to measure the element at index 0 to know what size to measure all other items.
|
|
// If FlowlayoutAlgorithm has already realized element 0 then we can use that.
|
|
// If it does not, then we need to do context.GetElement(0) at which point we have requested an element and are on point to clear it.
|
|
// If we are responsible for clearing element 0 we keep m_cachedFirstElement valid.
|
|
// If we are not (because FlowLayoutAlgorithm is holding it for us) then we just null out this field and use the one from FlowLayoutAlgorithm.
|
|
private ILayoutable _cachedFirstElement;
|
|
|
|
internal FlowLayoutAlgorithm FlowAlgorithm { get; } = new FlowLayoutAlgorithm();
|
|
internal double EffectiveItemWidth { get; private set; }
|
|
internal double EffectiveItemHeight { get; private set; }
|
|
|
|
internal void InitializeForContext(VirtualizingLayoutContext context, IFlowLayoutAlgorithmDelegates callbacks)
|
|
{
|
|
FlowAlgorithm.InitializeForContext(context, callbacks);
|
|
context.LayoutState = this;
|
|
}
|
|
|
|
internal void UninitializeForContext(VirtualizingLayoutContext context)
|
|
{
|
|
FlowAlgorithm.UninitializeForContext(context);
|
|
|
|
if (_cachedFirstElement != null)
|
|
{
|
|
context.RecycleElement(_cachedFirstElement);
|
|
}
|
|
}
|
|
|
|
internal void EnsureElementSize(
|
|
Size availableSize,
|
|
VirtualizingLayoutContext context,
|
|
double layoutItemWidth,
|
|
double LayoutItemHeight,
|
|
UniformGridLayoutItemsStretch stretch,
|
|
Orientation orientation,
|
|
double minRowSpacing,
|
|
double minColumnSpacing)
|
|
{
|
|
if (context.ItemCount > 0)
|
|
{
|
|
// If the first element is realized we don't need to cache it or to get it from the context
|
|
var realizedElement = FlowAlgorithm.GetElementIfRealized(0);
|
|
if (realizedElement != null)
|
|
{
|
|
realizedElement.Measure(availableSize);
|
|
SetSize(realizedElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing);
|
|
_cachedFirstElement = null;
|
|
}
|
|
else
|
|
{
|
|
if (_cachedFirstElement == null)
|
|
{
|
|
// we only cache if we aren't realizing it
|
|
_cachedFirstElement = context.GetOrCreateElementAt(
|
|
0,
|
|
ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); // expensive
|
|
}
|
|
|
|
_cachedFirstElement.Measure(availableSize);
|
|
SetSize(_cachedFirstElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing);
|
|
|
|
// 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);
|
|
if (added)
|
|
{
|
|
_cachedFirstElement = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SetSize(
|
|
ILayoutable element,
|
|
double layoutItemWidth,
|
|
double LayoutItemHeight,
|
|
Size availableSize,
|
|
UniformGridLayoutItemsStretch stretch,
|
|
Orientation orientation,
|
|
double minRowSpacing,
|
|
double minColumnSpacing)
|
|
{
|
|
EffectiveItemWidth = (double.IsNaN(layoutItemWidth) ? element.DesiredSize.Width : layoutItemWidth);
|
|
EffectiveItemHeight = (double.IsNaN(LayoutItemHeight) ? element.DesiredSize.Height : LayoutItemHeight);
|
|
|
|
var availableSizeMinor = orientation == Orientation.Horizontal ? availableSize.Width : availableSize.Height;
|
|
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;
|
|
|
|
if (stretch == UniformGridLayoutItemsStretch.Fill)
|
|
{
|
|
if (orientation == Orientation.Horizontal)
|
|
{
|
|
EffectiveItemWidth += extraMinorPixelsForEachItem;
|
|
}
|
|
else
|
|
{
|
|
EffectiveItemHeight += extraMinorPixelsForEachItem;
|
|
}
|
|
}
|
|
else if (stretch == UniformGridLayoutItemsStretch.Uniform)
|
|
{
|
|
var itemSizeMajor = orientation == Orientation.Horizontal ? EffectiveItemHeight : EffectiveItemWidth;
|
|
var extraMajorPixelsForEachItem = itemSizeMajor * (extraMinorPixelsForEachItem / itemSizeMinor);
|
|
if (orientation == Orientation.Horizontal)
|
|
{
|
|
EffectiveItemWidth += extraMinorPixelsForEachItem;
|
|
EffectiveItemHeight += extraMajorPixelsForEachItem;
|
|
}
|
|
else
|
|
{
|
|
EffectiveItemHeight += extraMinorPixelsForEachItem;
|
|
EffectiveItemWidth += extraMajorPixelsForEachItem;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void EnsureFirstElementOwnership()
|
|
{
|
|
if (FlowAlgorithm.GetElementIfRealized(0) != null)
|
|
{
|
|
_cachedFirstElement = null;
|
|
}
|
|
}
|
|
|
|
internal void ClearElementOnDataSourceChange(
|
|
VirtualizingLayoutContext context,
|
|
NotifyCollectionChangedEventArgs args)
|
|
{
|
|
if (_cachedFirstElement != null)
|
|
{
|
|
bool shouldClear = false;
|
|
switch (args.Action)
|
|
{
|
|
case NotifyCollectionChangedAction.Add:
|
|
shouldClear = args.NewStartingIndex == 0;
|
|
break;
|
|
|
|
case NotifyCollectionChangedAction.Replace:
|
|
shouldClear = args.NewStartingIndex == 0 || args.OldStartingIndex == 0;
|
|
break;
|
|
|
|
case NotifyCollectionChangedAction.Remove:
|
|
shouldClear = args.OldStartingIndex == 0;
|
|
break;
|
|
|
|
case NotifyCollectionChangedAction.Reset:
|
|
shouldClear = true;
|
|
break;
|
|
|
|
case NotifyCollectionChangedAction.Move:
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
if (shouldClear)
|
|
{
|
|
context.RecycleElement(_cachedFirstElement);
|
|
_cachedFirstElement = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|