A cross-platform UI framework for .NET
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.
 
 
 

227 lines
6.7 KiB

// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Specialized;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
public class VirtualizingStackPanel : StackPanel, IVirtualizingPanel
{
private Size _availableSpace;
private double _takenSpace;
private int _canBeRemoved;
private double _averageItemSize;
private int _averageCount;
private double _pixelOffset;
bool IVirtualizingPanel.IsFull
{
get
{
return Orientation == Orientation.Horizontal ?
_takenSpace >= _availableSpace.Width :
_takenSpace >= _availableSpace.Height;
}
}
IVirtualizingController IVirtualizingPanel.Controller { get; set; }
int IVirtualizingPanel.OverflowCount => _canBeRemoved;
Orientation IVirtualizingPanel.ScrollDirection => Orientation;
double IVirtualizingPanel.AverageItemSize => _averageItemSize;
double IVirtualizingPanel.PixelOverflow
{
get
{
var bounds = Orientation == Orientation.Horizontal ?
_availableSpace.Width : _availableSpace.Height;
return Math.Max(0, _takenSpace - bounds);
}
}
double IVirtualizingPanel.PixelOffset
{
get { return _pixelOffset; }
set
{
if (_pixelOffset != value)
{
_pixelOffset = value;
InvalidateArrange();
}
}
}
private IVirtualizingController Controller => ((IVirtualizingPanel)this).Controller;
protected override Size MeasureOverride(Size availableSize)
{
if (availableSize != ((ILayoutable)this).PreviousMeasure)
{
_availableSpace = availableSize;
Controller?.UpdateControls();
}
return base.MeasureOverride(availableSize);
}
protected override Size ArrangeOverride(Size finalSize)
{
_availableSpace = finalSize;
_canBeRemoved = 0;
_takenSpace = 0;
_averageItemSize = 0;
_averageCount = 0;
var result = base.ArrangeOverride(finalSize);
_takenSpace += _pixelOffset;
Controller?.UpdateControls();
return result;
}
protected override void ChildrenChanged(object sender, NotifyCollectionChangedEventArgs e)
{
base.ChildrenChanged(sender, e);
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (IControl control in e.NewItems)
{
UpdateAdd(control);
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (IControl control in e.OldItems)
{
UpdateRemove(control);
}
break;
}
}
protected override IInputElement GetControlInDirection(NavigationDirection direction, IControl from)
{
var logicalScrollable = Parent as ILogicalScrollable;
var fromControl = from as IControl;
if (logicalScrollable?.IsLogicalScrollEnabled == true && fromControl != null)
{
return logicalScrollable.GetControlInDirection(direction, fromControl);
}
else
{
return base.GetControlInDirection(direction, from);
}
}
internal override void ArrangeChild(
IControl child,
Rect rect,
Size panelSize,
Orientation orientation)
{
if (orientation == Orientation.Vertical)
{
rect = new Rect(rect.X, rect.Y - _pixelOffset, rect.Width, rect.Height);
child.Arrange(rect);
if (rect.Y >= _availableSpace.Height)
{
++_canBeRemoved;
}
if (rect.Bottom >= _takenSpace)
{
_takenSpace = rect.Bottom;
}
AddToAverageItemSize(rect.Height);
}
else
{
rect = new Rect(rect.X - _pixelOffset, rect.Y, rect.Width, rect.Height);
child.Arrange(rect);
if (rect.X >= _availableSpace.Width)
{
++_canBeRemoved;
}
if (rect.Right >= _takenSpace)
{
_takenSpace = rect.Right;
}
AddToAverageItemSize(rect.Width);
}
}
private void UpdateAdd(IControl child)
{
var bounds = Bounds;
var gap = Gap;
child.Measure(_availableSpace);
++_averageCount;
if (Orientation == Orientation.Vertical)
{
var height = child.DesiredSize.Height;
_takenSpace += height + gap;
AddToAverageItemSize(height);
}
else
{
var width = child.DesiredSize.Width;
_takenSpace += width + gap;
AddToAverageItemSize(width);
}
}
private void UpdateRemove(IControl child)
{
var bounds = Bounds;
var gap = Gap;
if (Orientation == Orientation.Vertical)
{
var height = child.DesiredSize.Height;
_takenSpace -= height + gap;
RemoveFromAverageItemSize(height);
}
else
{
var width = child.DesiredSize.Width;
_takenSpace -= width + gap;
RemoveFromAverageItemSize(width);
}
if (_canBeRemoved > 0)
{
--_canBeRemoved;
}
}
private void AddToAverageItemSize(double value)
{
++_averageCount;
_averageItemSize += (value - _averageItemSize) / _averageCount;
}
private void RemoveFromAverageItemSize(double value)
{
_averageItemSize = ((_averageItemSize * _averageCount) - value) / (_averageCount - 1);
--_averageCount;
}
}
}