Browse Source
A lot still broken, in particular virtualization is completely removed.`ItemsPresenter` now no longer has an `Items` or `ItemTemplate` property; it detects when it's hosted in an `ItemsControl`. `IItemsPresenter` interface removed.pull/9677/head
64 changed files with 3040 additions and 5086 deletions
@ -1,18 +0,0 @@ |
|||
using System.Collections; |
|||
using System.Collections.Specialized; |
|||
using Avalonia.Metadata; |
|||
|
|||
namespace Avalonia.Controls.Presenters |
|||
{ |
|||
[NotClientImplementable] |
|||
public interface IItemsPresenter : IPresenter |
|||
{ |
|||
IEnumerable? Items { get; set; } |
|||
|
|||
Panel? Panel { get; } |
|||
|
|||
void ItemsChanged(NotifyCollectionChangedEventArgs e); |
|||
|
|||
void ScrollIntoView(int index); |
|||
} |
|||
} |
|||
@ -1,125 +0,0 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Specialized; |
|||
using Avalonia.Controls.Generators; |
|||
using Avalonia.Controls.Utils; |
|||
|
|||
namespace Avalonia.Controls.Presenters |
|||
{ |
|||
internal static class ItemContainerSync |
|||
{ |
|||
public static void ItemsChanged( |
|||
ItemsPresenterBase owner, |
|||
IEnumerable? items, |
|||
NotifyCollectionChangedEventArgs e) |
|||
{ |
|||
var generator = owner.ItemContainerGenerator; |
|||
var panel = owner.Panel; |
|||
|
|||
if (panel == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
void Add() |
|||
{ |
|||
if (e.NewStartingIndex + e.NewItems!.Count < items!.Count()) |
|||
{ |
|||
generator.InsertSpace(e.NewStartingIndex, e.NewItems.Count); |
|||
} |
|||
|
|||
AddContainers(owner, e.NewStartingIndex, e.NewItems); |
|||
} |
|||
|
|||
void Remove() |
|||
{ |
|||
RemoveContainers(panel, generator.RemoveRange(e.OldStartingIndex, e.OldItems!.Count)); |
|||
} |
|||
|
|||
switch (e.Action) |
|||
{ |
|||
case NotifyCollectionChangedAction.Add: |
|||
Add(); |
|||
break; |
|||
|
|||
case NotifyCollectionChangedAction.Remove: |
|||
Remove(); |
|||
break; |
|||
|
|||
case NotifyCollectionChangedAction.Replace: |
|||
RemoveContainers(panel, generator.Dematerialize(e.OldStartingIndex, e.OldItems!.Count)); |
|||
var containers = AddContainers(owner, e.NewStartingIndex, e.NewItems!); |
|||
|
|||
var i = e.NewStartingIndex; |
|||
|
|||
foreach (var container in containers) |
|||
{ |
|||
panel.Children[i++] = container.ContainerControl; |
|||
} |
|||
|
|||
break; |
|||
|
|||
case NotifyCollectionChangedAction.Move: |
|||
Remove(); |
|||
Add(); |
|||
break; |
|||
|
|||
case NotifyCollectionChangedAction.Reset: |
|||
RemoveContainers(panel, generator.Clear()); |
|||
|
|||
if (items != null) |
|||
{ |
|||
AddContainers(owner, 0, items); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
} |
|||
|
|||
private static IList<ItemContainerInfo> AddContainers( |
|||
ItemsPresenterBase owner, |
|||
int index, |
|||
IEnumerable items) |
|||
{ |
|||
var generator = owner.ItemContainerGenerator; |
|||
var result = new List<ItemContainerInfo>(); |
|||
var panel = owner.Panel; |
|||
|
|||
foreach (var item in items) |
|||
{ |
|||
var i = generator.Materialize(index++, item); |
|||
|
|||
if (i.ContainerControl != null) |
|||
{ |
|||
if (i.Index < panel!.Children.Count) |
|||
{ |
|||
// TODO: This will insert at the wrong place when there are null items.
|
|||
panel.Children.Insert(i.Index, i.ContainerControl); |
|||
} |
|||
else |
|||
{ |
|||
panel.Children.Add(i.ContainerControl); |
|||
} |
|||
} |
|||
|
|||
result.Add(i); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private static void RemoveContainers( |
|||
Panel panel, |
|||
IEnumerable<ItemContainerInfo> items) |
|||
{ |
|||
foreach (var i in items) |
|||
{ |
|||
if (i.ContainerControl != null) |
|||
{ |
|||
panel.Children.Remove(i.ContainerControl); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,303 +0,0 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Specialized; |
|||
using System.Reactive.Linq; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Controls.Utils; |
|||
using Avalonia.Input; |
|||
using Avalonia.Layout; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Controls.Presenters |
|||
{ |
|||
/// <summary>
|
|||
/// Base class for classes which handle virtualization for an <see cref="ItemsPresenter"/>.
|
|||
/// </summary>
|
|||
internal abstract class ItemVirtualizer : IVirtualizingController, IDisposable |
|||
{ |
|||
private double _crossAxisOffset; |
|||
private IDisposable? _subscriptions; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ItemVirtualizer"/> class.
|
|||
/// </summary>
|
|||
/// <param name="owner"></param>
|
|||
public ItemVirtualizer(ItemsPresenter owner) |
|||
{ |
|||
Owner = owner; |
|||
Items = owner.Items; |
|||
ItemCount = owner.Items.Count(); |
|||
|
|||
var panel = VirtualizingPanel; |
|||
|
|||
if (panel != null) |
|||
{ |
|||
_subscriptions = ((AvaloniaObject)panel).GetObservable(Panel.BoundsProperty) |
|||
.Skip(1) |
|||
.Subscribe(_ => InvalidateScroll()); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="ItemsPresenter"/> which owns the virtualizer.
|
|||
/// </summary>
|
|||
public ItemsPresenter Owner { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="IVirtualizingPanel"/> which will host the items.
|
|||
/// </summary>
|
|||
public IVirtualizingPanel? VirtualizingPanel => Owner.Panel as IVirtualizingPanel; |
|||
|
|||
/// <summary>
|
|||
/// Gets the items to display.
|
|||
/// </summary>
|
|||
public IEnumerable? Items { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of items in <see cref="Items"/>.
|
|||
/// </summary>
|
|||
public int ItemCount { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the index of the first item displayed in the panel.
|
|||
/// </summary>
|
|||
public int FirstIndex { get; protected set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the index of the first item beyond those displayed in the panel.
|
|||
/// </summary>
|
|||
public int NextIndex { get; protected set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the items should be scroll horizontally or vertically.
|
|||
/// </summary>
|
|||
public bool Vertical => VirtualizingPanel?.ScrollDirection == Orientation.Vertical; |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether logical scrolling is enabled.
|
|||
/// </summary>
|
|||
public abstract bool IsLogicalScrollEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the value of the scroll extent.
|
|||
/// </summary>
|
|||
public abstract double ExtentValue { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the value of the current scroll offset.
|
|||
/// </summary>
|
|||
public abstract double OffsetValue { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the value of the scrollable viewport.
|
|||
/// </summary>
|
|||
public abstract double ViewportValue { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="ExtentValue"/> as a <see cref="Size"/>.
|
|||
/// </summary>
|
|||
public Size Extent |
|||
{ |
|||
get |
|||
{ |
|||
if (IsLogicalScrollEnabled && Owner.Panel is Panel panel) |
|||
{ |
|||
return Vertical ? |
|||
new Size(panel.DesiredSize.Width, ExtentValue) : |
|||
new Size(ExtentValue, panel.DesiredSize.Height); |
|||
} |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="ViewportValue"/> as a <see cref="Size"/>.
|
|||
/// </summary>
|
|||
public Size Viewport |
|||
{ |
|||
get |
|||
{ |
|||
if (IsLogicalScrollEnabled && Owner.Panel is Panel panel) |
|||
{ |
|||
return Vertical ? |
|||
new Size(panel.Bounds.Width, ViewportValue) : |
|||
new Size(ViewportValue, panel.Bounds.Height); |
|||
} |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the <see cref="OffsetValue"/> as a <see cref="Vector"/>.
|
|||
/// </summary>
|
|||
public Vector Offset |
|||
{ |
|||
get |
|||
{ |
|||
if (IsLogicalScrollEnabled) |
|||
{ |
|||
return Vertical ? new Vector(_crossAxisOffset, OffsetValue) : new Vector(OffsetValue, _crossAxisOffset); |
|||
} |
|||
|
|||
return default; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
if (!IsLogicalScrollEnabled) |
|||
{ |
|||
throw new NotSupportedException("Logical scrolling disabled."); |
|||
} |
|||
|
|||
var oldCrossAxisOffset = _crossAxisOffset; |
|||
|
|||
if (Vertical) |
|||
{ |
|||
OffsetValue = value.Y; |
|||
_crossAxisOffset = value.X; |
|||
} |
|||
else |
|||
{ |
|||
OffsetValue = value.X; |
|||
_crossAxisOffset = value.Y; |
|||
} |
|||
|
|||
if (_crossAxisOffset != oldCrossAxisOffset) |
|||
{ |
|||
Owner.InvalidateArrange(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates an <see cref="ItemVirtualizer"/> based on an item presenter's
|
|||
/// <see cref="ItemVirtualizationMode"/>.
|
|||
/// </summary>
|
|||
/// <param name="owner">The items presenter.</param>
|
|||
/// <returns>An <see cref="ItemVirtualizer"/>.</returns>
|
|||
public static ItemVirtualizer? Create(ItemsPresenter owner) |
|||
{ |
|||
if (owner.Panel == null) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
var virtualizingPanel = owner.Panel as IVirtualizingPanel; |
|||
var scrollContentPresenter = owner.Parent as IScrollable; |
|||
ItemVirtualizer? result = null; |
|||
|
|||
if (virtualizingPanel != null && scrollContentPresenter is object) |
|||
{ |
|||
switch (owner.VirtualizationMode) |
|||
{ |
|||
case ItemVirtualizationMode.Simple: |
|||
result = new ItemVirtualizerSimple(owner); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (result == null) |
|||
{ |
|||
result = new ItemVirtualizerNone(owner); |
|||
} |
|||
|
|||
if (virtualizingPanel != null) |
|||
{ |
|||
virtualizingPanel.Controller = result; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Carries out a measure for the related <see cref="ItemsPresenter"/>.
|
|||
/// </summary>
|
|||
/// <param name="availableSize">The size available to the control.</param>
|
|||
/// <returns>The desired size for the control.</returns>
|
|||
public virtual Size MeasureOverride(Size availableSize) |
|||
{ |
|||
Owner.Panel!.Measure(availableSize); |
|||
return Owner.Panel.DesiredSize; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Carries out an arrange for the related <see cref="ItemsPresenter"/>.
|
|||
/// </summary>
|
|||
/// <param name="finalSize">The size available to the control.</param>
|
|||
/// <returns>The actual size used.</returns>
|
|||
public virtual Size ArrangeOverride(Size finalSize) |
|||
{ |
|||
if (VirtualizingPanel != null) |
|||
{ |
|||
VirtualizingPanel.CrossAxisOffset = _crossAxisOffset; |
|||
Owner.Panel!.Arrange(new Rect(finalSize)); |
|||
} |
|||
else |
|||
{ |
|||
var origin = Vertical ? new Point(-_crossAxisOffset, 0) : new Point(0, _crossAxisOffset); |
|||
Owner.Panel!.Arrange(new Rect(origin, finalSize)); |
|||
} |
|||
|
|||
return finalSize; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public virtual void UpdateControls() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the next control in the specified direction.
|
|||
/// </summary>
|
|||
/// <param name="direction">The movement direction.</param>
|
|||
/// <param name="from">The control from which movement begins.</param>
|
|||
/// <returns>The control.</returns>
|
|||
public virtual Control? GetControlInDirection(NavigationDirection direction, Control? from) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the items for the presenter change, either because
|
|||
/// <see cref="ItemsPresenterBase.Items"/> has been set, the items collection has been
|
|||
/// modified, or the panel has been created.
|
|||
/// </summary>
|
|||
/// <param name="items">The items.</param>
|
|||
/// <param name="e">A description of the change.</param>
|
|||
public virtual void ItemsChanged(IEnumerable? items, NotifyCollectionChangedEventArgs e) |
|||
{ |
|||
Items = items; |
|||
ItemCount = items?.Count() ?? 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Scrolls the specified item into view.
|
|||
/// </summary>
|
|||
/// <param name="index">The index of the item.</param>
|
|||
public virtual void ScrollIntoView(int index) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public virtual void Dispose() |
|||
{ |
|||
_subscriptions?.Dispose(); |
|||
_subscriptions = null; |
|||
|
|||
if (VirtualizingPanel != null) |
|||
{ |
|||
VirtualizingPanel.Controller = null; |
|||
VirtualizingPanel.Children.Clear(); |
|||
} |
|||
|
|||
Owner.ItemContainerGenerator.Clear(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Invalidates the current scroll.
|
|||
/// </summary>
|
|||
protected void InvalidateScroll() => ((ILogicalScrollable)Owner).RaiseScrollInvalidated(EventArgs.Empty); |
|||
} |
|||
} |
|||
@ -1,106 +0,0 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Specialized; |
|||
using Avalonia.Controls.Generators; |
|||
using Avalonia.Controls.Utils; |
|||
|
|||
namespace Avalonia.Controls.Presenters |
|||
{ |
|||
/// <summary>
|
|||
/// Represents an item virtualizer for an <see cref="ItemsPresenter"/> that doesn't actually
|
|||
/// virtualize items - it just creates a container for every item.
|
|||
/// </summary>
|
|||
internal class ItemVirtualizerNone : ItemVirtualizer |
|||
{ |
|||
public ItemVirtualizerNone(ItemsPresenter owner) |
|||
: base(owner) |
|||
{ |
|||
if (Items != null && owner.Panel != null) |
|||
{ |
|||
AddContainers(0, Items); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool IsLogicalScrollEnabled => false; |
|||
|
|||
/// <summary>
|
|||
/// This property should never be accessed because <see cref="IsLogicalScrollEnabled"/> is
|
|||
/// false.
|
|||
/// </summary>
|
|||
public override double ExtentValue |
|||
{ |
|||
get { throw new NotSupportedException(); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// This property should never be accessed because <see cref="IsLogicalScrollEnabled"/> is
|
|||
/// false.
|
|||
/// </summary>
|
|||
public override double OffsetValue |
|||
{ |
|||
get { throw new NotSupportedException(); } |
|||
set { throw new NotSupportedException(); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// This property should never be accessed because <see cref="IsLogicalScrollEnabled"/> is
|
|||
/// false.
|
|||
/// </summary>
|
|||
public override double ViewportValue |
|||
{ |
|||
get { throw new NotSupportedException(); } |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void ItemsChanged(IEnumerable? items, NotifyCollectionChangedEventArgs e) |
|||
{ |
|||
base.ItemsChanged(items, e); |
|||
ItemContainerSync.ItemsChanged(Owner, items, e); |
|||
Owner.InvalidateMeasure(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Scrolls the specified item into view.
|
|||
/// </summary>
|
|||
/// <param name="index">The index of the item.</param>
|
|||
public override void ScrollIntoView(int index) |
|||
{ |
|||
if (index != -1) |
|||
{ |
|||
var container = Owner.ItemContainerGenerator.ContainerFromIndex(index); |
|||
container?.BringIntoView(); |
|||
} |
|||
} |
|||
|
|||
private IList<ItemContainerInfo> AddContainers(int index, IEnumerable items) |
|||
{ |
|||
var generator = Owner.ItemContainerGenerator; |
|||
var result = new List<ItemContainerInfo>(); |
|||
var panel = Owner.Panel; |
|||
|
|||
foreach (var item in items) |
|||
{ |
|||
var i = generator.Materialize(index++, item); |
|||
|
|||
if (i.ContainerControl != null) |
|||
{ |
|||
if (i.Index < panel!.Children.Count) |
|||
{ |
|||
// TODO: This will insert at the wrong place when there are null items.
|
|||
panel.Children.Insert(i.Index, i.ContainerControl); |
|||
} |
|||
else |
|||
{ |
|||
panel.Children.Add(i.ContainerControl); |
|||
} |
|||
} |
|||
|
|||
result.Add(i); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
@ -1,606 +0,0 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Specialized; |
|||
using System.Linq; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Controls.Utils; |
|||
using Avalonia.Input; |
|||
using Avalonia.Layout; |
|||
using Avalonia.Utilities; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Controls.Presenters |
|||
{ |
|||
/// <summary>
|
|||
/// Handles virtualization in an <see cref="ItemsPresenter"/> for
|
|||
/// <see cref="ItemVirtualizationMode.Simple"/>.
|
|||
/// </summary>
|
|||
internal class ItemVirtualizerSimple : ItemVirtualizer |
|||
{ |
|||
private int _anchor; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ItemVirtualizerSimple"/> class.
|
|||
/// </summary>
|
|||
/// <param name="owner"></param>
|
|||
public ItemVirtualizerSimple(ItemsPresenter owner) |
|||
: base(owner) |
|||
{ |
|||
// Don't need to add children here as UpdateControls should be called by the panel
|
|||
// measure/arrange.
|
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool IsLogicalScrollEnabled => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override double ExtentValue => ItemCount; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override double OffsetValue |
|||
{ |
|||
get |
|||
{ |
|||
var offset = VirtualizingPanel.PixelOffset > 0 ? 1 : 0; |
|||
return FirstIndex + offset; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
var panel = VirtualizingPanel; |
|||
var offset = VirtualizingPanel.PixelOffset > 0 ? 1 : 0; |
|||
var delta = (int)(value - (FirstIndex + offset)); |
|||
|
|||
if (delta != 0) |
|||
{ |
|||
var newLastIndex = (NextIndex - 1) + delta; |
|||
|
|||
if (newLastIndex < ItemCount) |
|||
{ |
|||
if (panel.PixelOffset > 0) |
|||
{ |
|||
panel.PixelOffset = 0; |
|||
delta += 1; |
|||
} |
|||
|
|||
if (delta != 0) |
|||
{ |
|||
RecycleContainersForMove(delta); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// We're moving to a partially obscured item at the end of the list so
|
|||
// offset the panel by the height of the first item.
|
|||
var firstIndex = ItemCount - panel.Children.Count; |
|||
RecycleContainersForMove(firstIndex - FirstIndex); |
|||
|
|||
double pixelOffset; |
|||
var child = panel.Children[0]; |
|||
|
|||
if (child.IsArrangeValid) |
|||
{ |
|||
pixelOffset = VirtualizingPanel.ScrollDirection == Orientation.Vertical ? |
|||
child.Bounds.Height : |
|||
child.Bounds.Width; |
|||
} |
|||
else |
|||
{ |
|||
pixelOffset = VirtualizingPanel.ScrollDirection == Orientation.Vertical ? |
|||
child.DesiredSize.Height : |
|||
child.DesiredSize.Width; |
|||
} |
|||
|
|||
panel.PixelOffset = pixelOffset; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override double ViewportValue |
|||
{ |
|||
get |
|||
{ |
|||
// If we can't fit the last item in the panel fully, subtract 1 from the viewport.
|
|||
var overflow = VirtualizingPanel.PixelOverflow > 0 ? 1 : 0; |
|||
return VirtualizingPanel.Children.Count - overflow; |
|||
} |
|||
} |
|||
|
|||
public new IVirtualizingPanel VirtualizingPanel => base.VirtualizingPanel!; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override Size MeasureOverride(Size availableSize) |
|||
{ |
|||
var scrollable = (ILogicalScrollable)Owner; |
|||
var visualRoot = Owner.GetVisualRoot(); |
|||
var maxAvailableSize = (visualRoot as WindowBase)?.PlatformImpl?.MaxAutoSizeHint |
|||
?? (visualRoot as TopLevel)?.ClientSize; |
|||
|
|||
// If infinity is passed as the available size and we're virtualized then we need to
|
|||
// fill the available space, but to do that we *don't* want to materialize all our
|
|||
// items! Take a look at the root of the tree for a MaxClientSize and use that as
|
|||
// the available size.
|
|||
if (VirtualizingPanel.ScrollDirection == Orientation.Vertical) |
|||
{ |
|||
if (availableSize.Height == double.PositiveInfinity) |
|||
{ |
|||
if (maxAvailableSize.HasValue) |
|||
{ |
|||
availableSize = availableSize.WithHeight(maxAvailableSize.Value.Height); |
|||
} |
|||
} |
|||
|
|||
if (scrollable.CanHorizontallyScroll) |
|||
{ |
|||
availableSize = availableSize.WithWidth(double.PositiveInfinity); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
if (availableSize.Width == double.PositiveInfinity) |
|||
{ |
|||
if (maxAvailableSize.HasValue) |
|||
{ |
|||
availableSize = availableSize.WithWidth(maxAvailableSize.Value.Width); |
|||
} |
|||
} |
|||
|
|||
if (scrollable.CanVerticallyScroll) |
|||
{ |
|||
availableSize = availableSize.WithHeight(double.PositiveInfinity); |
|||
} |
|||
} |
|||
|
|||
Owner.Panel!.Measure(availableSize); |
|||
return Owner.Panel.DesiredSize; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void UpdateControls() |
|||
{ |
|||
CreateAndRemoveContainers(); |
|||
InvalidateScroll(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void ItemsChanged(IEnumerable? items, NotifyCollectionChangedEventArgs e) |
|||
{ |
|||
base.ItemsChanged(items, e); |
|||
|
|||
var panel = VirtualizingPanel; |
|||
|
|||
if (items != null) |
|||
{ |
|||
switch (e.Action) |
|||
{ |
|||
case NotifyCollectionChangedAction.Add: |
|||
CreateAndRemoveContainers(); |
|||
|
|||
if (e.NewStartingIndex < NextIndex) |
|||
{ |
|||
RecycleContainers(); |
|||
} |
|||
|
|||
panel.ForceInvalidateMeasure(); |
|||
break; |
|||
|
|||
case NotifyCollectionChangedAction.Remove: |
|||
if (e.OldStartingIndex < NextIndex || |
|||
panel.Children.Count > ItemCount) |
|||
{ |
|||
RecycleContainersOnRemove(); |
|||
} |
|||
|
|||
panel.ForceInvalidateMeasure(); |
|||
break; |
|||
|
|||
case NotifyCollectionChangedAction.Move: |
|||
case NotifyCollectionChangedAction.Replace: |
|||
RecycleContainers(); |
|||
break; |
|||
|
|||
case NotifyCollectionChangedAction.Reset: |
|||
RecycleContainersOnRemove(); |
|||
CreateAndRemoveContainers(); |
|||
panel.ForceInvalidateMeasure(); |
|||
break; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
Owner.ItemContainerGenerator.Clear(); |
|||
VirtualizingPanel.Children.Clear(); |
|||
FirstIndex = NextIndex = 0; |
|||
} |
|||
|
|||
// If we are scrolled to view a partially visible last item but controls were added
|
|||
// then we need to return to a non-offset scroll position.
|
|||
if (panel.PixelOffset != 0 && FirstIndex + panel.Children.Count < ItemCount) |
|||
{ |
|||
panel.PixelOffset = 0; |
|||
RecycleContainersForMove(1); |
|||
} |
|||
|
|||
InvalidateScroll(); |
|||
} |
|||
|
|||
public override Control? GetControlInDirection(NavigationDirection direction, Control? from) |
|||
{ |
|||
var generator = Owner.ItemContainerGenerator; |
|||
var panel = VirtualizingPanel; |
|||
var itemIndex = generator.IndexFromContainer(from); |
|||
var vertical = VirtualizingPanel.ScrollDirection == Orientation.Vertical; |
|||
|
|||
var newItemIndex = -1; |
|||
|
|||
switch (direction) |
|||
{ |
|||
case NavigationDirection.First: |
|||
newItemIndex = 0; |
|||
break; |
|||
|
|||
case NavigationDirection.Last: |
|||
newItemIndex = ItemCount - 1; |
|||
break; |
|||
|
|||
default: |
|||
if (itemIndex == -1) |
|||
{ |
|||
return null; |
|||
} |
|||
break; |
|||
} |
|||
|
|||
switch (direction) |
|||
{ |
|||
case NavigationDirection.Up: |
|||
if (vertical) |
|||
{ |
|||
newItemIndex = itemIndex - 1; |
|||
} |
|||
|
|||
break; |
|||
case NavigationDirection.Down: |
|||
if (vertical) |
|||
{ |
|||
newItemIndex = itemIndex + 1; |
|||
} |
|||
|
|||
break; |
|||
|
|||
case NavigationDirection.Left: |
|||
if (!vertical) |
|||
{ |
|||
newItemIndex = itemIndex - 1; |
|||
} |
|||
break; |
|||
|
|||
case NavigationDirection.Right: |
|||
if (!vertical) |
|||
{ |
|||
newItemIndex = itemIndex + 1; |
|||
} |
|||
break; |
|||
|
|||
case NavigationDirection.PageUp: |
|||
newItemIndex = Math.Max(0, itemIndex - (int)ViewportValue); |
|||
break; |
|||
|
|||
case NavigationDirection.PageDown: |
|||
newItemIndex = Math.Min(ItemCount - 1, itemIndex + (int)ViewportValue); |
|||
break; |
|||
} |
|||
|
|||
return ScrollIntoViewCore(newItemIndex); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void ScrollIntoView(int index) |
|||
{ |
|||
if (index != -1) |
|||
{ |
|||
ScrollIntoViewCore(index); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates and removes containers such that we have at most enough containers to fill
|
|||
/// the panel.
|
|||
/// </summary>
|
|||
private void CreateAndRemoveContainers() |
|||
{ |
|||
var generator = Owner.ItemContainerGenerator; |
|||
var panel = VirtualizingPanel; |
|||
var panelControl = (Control)panel; |
|||
|
|||
if (!panel.IsFull && Items != null && panelControl.IsAttachedToVisualTree) |
|||
{ |
|||
var index = NextIndex; |
|||
var step = 1; |
|||
|
|||
while (!panel.IsFull && index >= 0) |
|||
{ |
|||
if (index >= ItemCount) |
|||
{ |
|||
// We can fit more containers in the panel, but we're at the end of the
|
|||
// items. If we're scrolled to the top (FirstIndex == 0), then there are
|
|||
// no more items to create. Otherwise, go backwards adding containers to
|
|||
// the beginning of the panel.
|
|||
if (FirstIndex == 0) |
|||
{ |
|||
break; |
|||
} |
|||
else |
|||
{ |
|||
index = FirstIndex - 1; |
|||
step = -1; |
|||
} |
|||
} |
|||
|
|||
var materialized = generator.Materialize(index, Items.ElementAt(index)!); |
|||
|
|||
if (step == 1) |
|||
{ |
|||
panel.Children.Add(materialized.ContainerControl); |
|||
} |
|||
else |
|||
{ |
|||
panel.Children.Insert(0, materialized.ContainerControl); |
|||
} |
|||
|
|||
index += step; |
|||
} |
|||
|
|||
if (step == 1) |
|||
{ |
|||
NextIndex = index; |
|||
} |
|||
else |
|||
{ |
|||
NextIndex = ItemCount; |
|||
FirstIndex = index + 1; |
|||
} |
|||
} |
|||
|
|||
if (panel.OverflowCount > 0) |
|||
{ |
|||
if (_anchor <= FirstIndex) |
|||
{ |
|||
RemoveContainers(panel.OverflowCount); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Updates the containers in the panel to make sure they are displaying the correct item
|
|||
/// based on <see cref="ItemVirtualizer.FirstIndex"/>.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method requires that <see cref="ItemVirtualizer.FirstIndex"/> + the number of
|
|||
/// materialized containers is not more than <see cref="ItemVirtualizer.ItemCount"/>.
|
|||
/// </remarks>
|
|||
private void RecycleContainers() |
|||
{ |
|||
var panel = VirtualizingPanel; |
|||
var generator = Owner.ItemContainerGenerator; |
|||
var containers = generator.Containers.ToList(); |
|||
var itemIndex = FirstIndex; |
|||
|
|||
foreach (var container in containers) |
|||
{ |
|||
var item = Items!.ElementAt(itemIndex)!; |
|||
|
|||
if (!object.Equals(container.Item, item)) |
|||
{ |
|||
if (!generator.TryRecycle(itemIndex, itemIndex, item)) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
} |
|||
|
|||
++itemIndex; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Recycles containers when a move occurs.
|
|||
/// </summary>
|
|||
/// <param name="delta">The delta of the move.</param>
|
|||
/// <remarks>
|
|||
/// If the move is less than a page, then this method moves the containers for the items
|
|||
/// that are still visible to the correct place, and recycles and moves the others. For
|
|||
/// example: if there are 20 items and 10 containers visible and the user scrolls 5
|
|||
/// items down, then the bottom 5 containers will be moved to the top and the top 5 will
|
|||
/// be moved to the bottom and recycled to display the newly visible item. Updates
|
|||
/// <see cref="ItemVirtualizer.FirstIndex"/> and <see cref="ItemVirtualizer.NextIndex"/>
|
|||
/// with their new values.
|
|||
/// </remarks>
|
|||
private void RecycleContainersForMove(int delta) |
|||
{ |
|||
var panel = VirtualizingPanel; |
|||
var generator = Owner.ItemContainerGenerator; |
|||
|
|||
//validate delta it should never overflow last index or generate index < 0
|
|||
delta = MathUtilities.Clamp(delta, -FirstIndex, ItemCount - FirstIndex - panel.Children.Count); |
|||
|
|||
var sign = delta < 0 ? -1 : 1; |
|||
var count = Math.Min(Math.Abs(delta), panel.Children.Count); |
|||
var move = count < panel.Children.Count; |
|||
var first = delta < 0 && move ? panel.Children.Count + delta : 0; |
|||
|
|||
for (var i = 0; i < count; ++i) |
|||
{ |
|||
var oldItemIndex = FirstIndex + first + i; |
|||
var newItemIndex = oldItemIndex + delta + ((panel.Children.Count - count) * sign); |
|||
|
|||
var item = Items!.ElementAt(newItemIndex)!; |
|||
|
|||
if (!generator.TryRecycle(oldItemIndex, newItemIndex, item)) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
} |
|||
|
|||
if (move) |
|||
{ |
|||
if (delta > 0) |
|||
{ |
|||
panel.Children.MoveRange(first, count, panel.Children.Count); |
|||
} |
|||
else |
|||
{ |
|||
panel.Children.MoveRange(first, count, 0); |
|||
} |
|||
} |
|||
|
|||
FirstIndex += delta; |
|||
NextIndex += delta; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Recycles containers due to items being removed.
|
|||
/// </summary>
|
|||
private void RecycleContainersOnRemove() |
|||
{ |
|||
var panel = VirtualizingPanel; |
|||
|
|||
if (NextIndex <= ItemCount) |
|||
{ |
|||
// Items have been removed but FirstIndex..NextIndex is still a valid range in the
|
|||
// items, so just recycle the containers to adapt to the new state.
|
|||
RecycleContainers(); |
|||
} |
|||
else |
|||
{ |
|||
// Items have been removed and now the range FirstIndex..NextIndex goes out of
|
|||
// the item bounds. Remove any excess containers, try to scroll up and then recycle
|
|||
// the containers to make sure they point to the correct item.
|
|||
var newFirstIndex = Math.Max(0, FirstIndex - (NextIndex - ItemCount)); |
|||
var delta = newFirstIndex - FirstIndex; |
|||
var newNextIndex = NextIndex + delta; |
|||
|
|||
if (newNextIndex > ItemCount) |
|||
{ |
|||
RemoveContainers(newNextIndex - ItemCount); |
|||
} |
|||
|
|||
if (delta != 0) |
|||
{ |
|||
RecycleContainersForMove(delta); |
|||
} |
|||
|
|||
RecycleContainers(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes the specified number of containers from the end of the panel and updates
|
|||
/// <see cref="ItemVirtualizer.NextIndex"/>.
|
|||
/// </summary>
|
|||
/// <param name="count">The number of containers to remove.</param>
|
|||
private void RemoveContainers(int count) |
|||
{ |
|||
var index = VirtualizingPanel.Children.Count - count; |
|||
|
|||
VirtualizingPanel.Children.RemoveRange(index, count); |
|||
Owner.ItemContainerGenerator.Dematerialize(FirstIndex + index, count); |
|||
NextIndex -= count; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Scrolls the item with the specified index into view.
|
|||
/// </summary>
|
|||
/// <param name="index">The item index.</param>
|
|||
/// <returns>The container that was brought into view.</returns>
|
|||
private Control? ScrollIntoViewCore(int index) |
|||
{ |
|||
var panel = VirtualizingPanel; |
|||
var panelControl = (Control)panel; |
|||
var generator = Owner.ItemContainerGenerator; |
|||
var newOffset = -1.0; |
|||
|
|||
//better not trigger any container generation/recycle while or layout stuff
|
|||
//before panel is attached/visible
|
|||
if (!panelControl.IsAttachedToVisualTree) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
if (!panelControl.IsMeasureValid && panelControl.PreviousMeasure.HasValue) |
|||
{ |
|||
//before any kind of scrolling we need to make sure panel measure is valid
|
|||
//or we risk get panel into not valid state
|
|||
//we make a preemptive quick measure so scrolling is valid
|
|||
panelControl.Measure(panelControl.PreviousMeasure.Value); |
|||
} |
|||
|
|||
if (index >= 0 && index < ItemCount) |
|||
{ |
|||
if (index <= FirstIndex) |
|||
{ |
|||
newOffset = index; |
|||
} |
|||
else if (index >= NextIndex) |
|||
{ |
|||
newOffset = index - Math.Ceiling(ViewportValue - 1); |
|||
} |
|||
|
|||
if (newOffset != -1) |
|||
{ |
|||
OffsetValue = newOffset; |
|||
} |
|||
|
|||
var container = generator.ContainerFromIndex(index); |
|||
var layoutManager = (Owner.GetVisualRoot() as ILayoutRoot)?.LayoutManager; |
|||
|
|||
// We need to do a layout here because it's possible that the container we moved to
|
|||
// is only partially visible due to differing item sizes. If the container is only
|
|||
// partially visible, scroll again. Don't do this if there's no layout manager:
|
|||
// it means we're running a unit test.
|
|||
if (container != null && layoutManager != null) |
|||
{ |
|||
_anchor = index; |
|||
layoutManager.ExecuteLayoutPass(); |
|||
_anchor = -1; |
|||
|
|||
if (newOffset != -1 && newOffset != OffsetValue) |
|||
{ |
|||
OffsetValue = newOffset; |
|||
} |
|||
|
|||
if (panel.ScrollDirection == Orientation.Vertical) |
|||
{ |
|||
if (container.Bounds.Y < panelControl.Bounds.Y || container.Bounds.Bottom > panelControl.Bounds.Bottom) |
|||
{ |
|||
OffsetValue += 1; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
if (container.Bounds.X < panelControl.Bounds.X || container.Bounds.Right > panelControl.Bounds.Right) |
|||
{ |
|||
OffsetValue += 1; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return container; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Ensures an offset value is within the value range.
|
|||
/// </summary>
|
|||
/// <param name="value">The value.</param>
|
|||
/// <returns>The coerced value.</returns>
|
|||
private double CoerceOffset(double value) |
|||
{ |
|||
var max = Math.Max(ExtentValue - ViewportValue, 0); |
|||
return MathUtilities.Clamp(value, 0, max); |
|||
} |
|||
} |
|||
} |
|||
@ -1,179 +1,88 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Specialized; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Input; |
|||
using static Avalonia.Utilities.MathUtilities; |
|||
using System.Diagnostics; |
|||
|
|||
namespace Avalonia.Controls.Presenters |
|||
{ |
|||
/// <summary>
|
|||
/// Displays items inside an <see cref="ItemsControl"/>.
|
|||
/// Presents items inside an <see cref="Avalonia.Controls.ItemsControl"/>.
|
|||
/// </summary>
|
|||
public class ItemsPresenter : ItemsPresenterBase, ILogicalScrollable |
|||
public class ItemsPresenter : Control |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="VirtualizationMode"/> property.
|
|||
/// Defines the <see cref="ItemsPanel"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<ItemVirtualizationMode> VirtualizationModeProperty = |
|||
AvaloniaProperty.Register<ItemsPresenter, ItemVirtualizationMode>( |
|||
nameof(VirtualizationMode), |
|||
defaultValue: ItemVirtualizationMode.None); |
|||
public static readonly StyledProperty<ITemplate<Panel>> ItemsPanelProperty = |
|||
ItemsControl.ItemsPanelProperty.AddOwner<ItemsPresenter>(); |
|||
|
|||
private bool _canHorizontallyScroll; |
|||
private bool _canVerticallyScroll; |
|||
private EventHandler? _scrollInvalidated; |
|||
private ItemsPresenterContainerGenerator? _generator; |
|||
|
|||
/// <summary>
|
|||
/// Initializes static members of the <see cref="ItemsPresenter"/> class.
|
|||
/// Gets or sets a template which creates the <see cref="Panel"/> used to display the items.
|
|||
/// </summary>
|
|||
static ItemsPresenter() |
|||
public ITemplate<Panel> ItemsPanel |
|||
{ |
|||
KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue( |
|||
typeof(ItemsPresenter), |
|||
KeyboardNavigationMode.Once); |
|||
|
|||
VirtualizationModeProperty.Changed |
|||
.AddClassHandler<ItemsPresenter>((x, e) => x.VirtualizationModeChanged(e)); |
|||
get => GetValue(ItemsPanelProperty); |
|||
set => SetValue(ItemsPanelProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the virtualization mode for the items.
|
|||
/// Gets the panel used to display the items.
|
|||
/// </summary>
|
|||
public ItemVirtualizationMode VirtualizationMode |
|||
{ |
|||
get { return GetValue(VirtualizationModeProperty); } |
|||
set { SetValue(VirtualizationModeProperty, value); } |
|||
} |
|||
public Panel? Panel { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the content can be scrolled horizontally.
|
|||
/// Gets the owner <see cref="ItemsControl"/>.
|
|||
/// </summary>
|
|||
bool ILogicalScrollable.CanHorizontallyScroll |
|||
{ |
|||
get { return _canHorizontallyScroll; } |
|||
set |
|||
{ |
|||
_canHorizontallyScroll = value; |
|||
InvalidateMeasure(); |
|||
} |
|||
} |
|||
internal ItemsControl? ItemsControl { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the content can be scrolled horizontally.
|
|||
/// </summary>
|
|||
bool ILogicalScrollable.CanVerticallyScroll |
|||
public override sealed void ApplyTemplate() |
|||
{ |
|||
get { return _canVerticallyScroll; } |
|||
set |
|||
if (Panel is null) |
|||
{ |
|||
_canVerticallyScroll = value; |
|||
InvalidateMeasure(); |
|||
Panel = ItemsPanel.Build(); |
|||
Panel.SetValue(TemplatedParentProperty, TemplatedParent); |
|||
LogicalChildren.Add(Panel); |
|||
VisualChildren.Add(Panel); |
|||
CreateGeneratorIfSimplePanel(); |
|||
} |
|||
} |
|||
/// <inheritdoc/>
|
|||
bool ILogicalScrollable.IsLogicalScrollEnabled |
|||
{ |
|||
get { return Virtualizer?.IsLogicalScrollEnabled ?? false; } |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
Size IScrollable.Extent => Virtualizer?.Extent ?? Size.Empty; |
|||
|
|||
/// <inheritdoc/>
|
|||
Vector IScrollable.Offset |
|||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) |
|||
{ |
|||
get { return Virtualizer?.Offset ?? new Vector(); } |
|||
set |
|||
base.OnPropertyChanged(change); |
|||
|
|||
if (change.Property == TemplatedParentProperty) |
|||
{ |
|||
if (Virtualizer != null) |
|||
_generator?.Dispose(); |
|||
_generator = null; |
|||
|
|||
if (change.NewValue is ItemsControl itemsControl) |
|||
{ |
|||
Virtualizer.Offset = CoerceOffset(value); |
|||
ItemsControl = itemsControl; |
|||
((IItemsPresenterHost)itemsControl)?.RegisterItemsPresenter(this); |
|||
CreateGeneratorIfSimplePanel(); |
|||
} |
|||
} |
|||
else if (change.Property == ItemsPanelProperty) |
|||
{ |
|||
_generator?.Dispose(); |
|||
_generator = null; |
|||
LogicalChildren.Clear(); |
|||
VisualChildren.Clear(); |
|||
Panel = null; |
|||
InvalidateMeasure(); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
Size IScrollable.Viewport => Virtualizer?.Viewport ?? Bounds.Size; |
|||
|
|||
/// <inheritdoc/>
|
|||
event EventHandler? ILogicalScrollable.ScrollInvalidated |
|||
{ |
|||
add => _scrollInvalidated += value; |
|||
remove => _scrollInvalidated -= value; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
Size ILogicalScrollable.ScrollSize => new Size(ScrollViewer.DefaultSmallChange, 1); |
|||
|
|||
/// <inheritdoc/>
|
|||
Size ILogicalScrollable.PageScrollSize => Virtualizer?.Viewport ?? new Size(16, 16); |
|||
|
|||
internal ItemVirtualizer? Virtualizer { get; private set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
bool ILogicalScrollable.BringIntoView(Control target, Rect targetRect) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
Control? ILogicalScrollable.GetControlInDirection(NavigationDirection direction, Control? from) |
|||
{ |
|||
return Virtualizer?.GetControlInDirection(direction, from); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
void ILogicalScrollable.RaiseScrollInvalidated(EventArgs e) |
|||
{ |
|||
_scrollInvalidated?.Invoke(this, e); |
|||
} |
|||
|
|||
public override void ScrollIntoView(int index) |
|||
{ |
|||
Virtualizer?.ScrollIntoView(index); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override Size MeasureOverride(Size availableSize) |
|||
{ |
|||
return Virtualizer?.MeasureOverride(availableSize) ?? Size.Empty; |
|||
} |
|||
|
|||
protected override Size ArrangeOverride(Size finalSize) |
|||
{ |
|||
return Virtualizer?.ArrangeOverride(finalSize) ?? Size.Empty; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void PanelCreated(Panel panel) |
|||
{ |
|||
Virtualizer?.Dispose(); |
|||
Virtualizer = ItemVirtualizer.Create(this); |
|||
_scrollInvalidated?.Invoke(this, EventArgs.Empty); |
|||
|
|||
KeyboardNavigation.SetTabNavigation( |
|||
(InputElement)panel, |
|||
KeyboardNavigation.GetTabNavigation(this)); |
|||
} |
|||
|
|||
protected override void ItemsChanged(NotifyCollectionChangedEventArgs e) |
|||
{ |
|||
Virtualizer?.ItemsChanged(Items, e); |
|||
} |
|||
|
|||
private Vector CoerceOffset(Vector value) |
|||
private void CreateGeneratorIfSimplePanel() |
|||
{ |
|||
var scrollable = (ILogicalScrollable)this; |
|||
var maxX = Math.Max(scrollable.Extent.Width - scrollable.Viewport.Width, 0); |
|||
var maxY = Math.Max(scrollable.Extent.Height - scrollable.Viewport.Height, 0); |
|||
return new Vector(Clamp(value.X, 0, maxX), Clamp(value.Y, 0, maxY)); |
|||
} |
|||
if (ItemsControl is null || Panel is null || Panel is IVirtualizingPanel) |
|||
return; |
|||
|
|||
private void VirtualizationModeChanged(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
Virtualizer?.Dispose(); |
|||
Virtualizer = ItemVirtualizer.Create(this); |
|||
_scrollInvalidated?.Invoke(this, EventArgs.Empty); |
|||
_generator?.Dispose(); |
|||
_generator = new(this); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -1,308 +0,0 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Specialized; |
|||
using Avalonia.Collections; |
|||
using Avalonia.Controls.Generators; |
|||
using Avalonia.Controls.Templates; |
|||
using Avalonia.Controls.Utils; |
|||
using Avalonia.Data; |
|||
using Avalonia.LogicalTree; |
|||
using Avalonia.Styling; |
|||
|
|||
namespace Avalonia.Controls.Presenters |
|||
{ |
|||
/// <summary>
|
|||
/// Base class for controls that present items inside an <see cref="ItemsControl"/>.
|
|||
/// </summary>
|
|||
public abstract class ItemsPresenterBase : Control, IItemsPresenter, IChildIndexProvider |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="Items"/> property.
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<ItemsPresenterBase, IEnumerable?> ItemsProperty = |
|||
ItemsControl.ItemsProperty.AddOwner<ItemsPresenterBase>(o => o.Items, (o, v) => o.Items = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="ItemsPanel"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<ITemplate<Panel>> ItemsPanelProperty = |
|||
ItemsControl.ItemsPanelProperty.AddOwner<ItemsPresenterBase>(); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="ItemTemplate"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty = |
|||
ItemsControl.ItemTemplateProperty.AddOwner<ItemsPresenterBase>(); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="DisplayMemberBinding" /> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<IBinding?> DisplayMemberBindingProperty = |
|||
ItemsControl.DisplayMemberBindingProperty.AddOwner<ItemsPresenterBase>(); |
|||
|
|||
private IEnumerable? _items; |
|||
private IDisposable? _itemsSubscription; |
|||
private bool _createdPanel; |
|||
private IItemContainerGenerator? _generator; |
|||
private EventHandler<ChildIndexChangedEventArgs>? _childIndexChanged; |
|||
|
|||
/// <summary>
|
|||
/// Initializes static members of the <see cref="ItemsPresenter"/> class.
|
|||
/// </summary>
|
|||
static ItemsPresenterBase() |
|||
{ |
|||
TemplatedParentProperty.Changed.AddClassHandler<ItemsPresenterBase>((x,e) => x.TemplatedParentChanged(e)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the items to be displayed.
|
|||
/// </summary>
|
|||
public IEnumerable? Items |
|||
{ |
|||
get |
|||
{ |
|||
return _items; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
_itemsSubscription?.Dispose(); |
|||
_itemsSubscription = null; |
|||
|
|||
if (!IsHosted && _createdPanel && value is INotifyCollectionChanged incc) |
|||
{ |
|||
_itemsSubscription = incc.WeakSubscribe(ItemsCollectionChanged); |
|||
} |
|||
|
|||
SetAndRaise(ItemsProperty, ref _items, value); |
|||
|
|||
if (_createdPanel) |
|||
{ |
|||
ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the item container generator.
|
|||
/// </summary>
|
|||
public IItemContainerGenerator ItemContainerGenerator |
|||
{ |
|||
get |
|||
{ |
|||
if (_generator == null) |
|||
{ |
|||
_generator = CreateItemContainerGenerator(); |
|||
} |
|||
|
|||
return _generator; |
|||
} |
|||
|
|||
internal set |
|||
{ |
|||
if (_generator != null) |
|||
{ |
|||
throw new InvalidOperationException("ItemContainerGenerator already created."); |
|||
} |
|||
|
|||
_generator = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a template which creates the <see cref="Panel"/> used to display the items.
|
|||
/// </summary>
|
|||
public ITemplate<Panel> ItemsPanel |
|||
{ |
|||
get { return GetValue(ItemsPanelProperty); } |
|||
set { SetValue(ItemsPanelProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the data template used to display the items in the control.
|
|||
/// </summary>
|
|||
public IDataTemplate? ItemTemplate |
|||
{ |
|||
get { return GetValue(ItemTemplateProperty); } |
|||
set { SetValue(ItemTemplateProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the <see cref="IBinding"/> to use for binding to the display member of each item.
|
|||
/// </summary>
|
|||
public IBinding? DisplayMemberBinding |
|||
{ |
|||
get { return GetValue(DisplayMemberBindingProperty); } |
|||
set { SetValue(DisplayMemberBindingProperty, value); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the panel used to display the items.
|
|||
/// </summary>
|
|||
public Panel? Panel |
|||
{ |
|||
get; |
|||
private set; |
|||
} |
|||
|
|||
protected bool IsHosted => TemplatedParent is IItemsPresenterHost; |
|||
|
|||
event EventHandler<ChildIndexChangedEventArgs>? IChildIndexProvider.ChildIndexChanged |
|||
{ |
|||
add => _childIndexChanged += value; |
|||
remove => _childIndexChanged -= value; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override sealed void ApplyTemplate() |
|||
{ |
|||
if (!_createdPanel) |
|||
{ |
|||
CreatePanel(); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public virtual void ScrollIntoView(int index) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
void IItemsPresenter.ItemsChanged(NotifyCollectionChangedEventArgs e) |
|||
{ |
|||
if (Panel != null) |
|||
{ |
|||
ItemsChanged(e); |
|||
|
|||
_childIndexChanged?.Invoke(this, ChildIndexChangedEventArgs.Empty); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates the <see cref="ItemContainerGenerator"/> for the control.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// An <see cref="IItemContainerGenerator"/> or null.
|
|||
/// </returns>
|
|||
protected virtual IItemContainerGenerator CreateItemContainerGenerator() |
|||
{ |
|||
var i = TemplatedParent as ItemsControl; |
|||
var result = i?.ItemContainerGenerator; |
|||
|
|||
if (result == null) |
|||
{ |
|||
result = new ItemContainerGenerator(this); |
|||
result.ItemTemplate = ItemTemplate; |
|||
result.DisplayMemberBinding = DisplayMemberBinding; |
|||
} |
|||
|
|||
result.Materialized += ContainerActionHandler; |
|||
result.Dematerialized += ContainerActionHandler; |
|||
result.Recycled += ContainerActionHandler; |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private void ContainerActionHandler(object? sender, ItemContainerEventArgs e) |
|||
{ |
|||
for (var i = 0; i < e.Containers.Count; i++) |
|||
{ |
|||
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(e.Containers[i].ContainerControl)); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override Size MeasureOverride(Size availableSize) |
|||
{ |
|||
Panel!.Measure(availableSize); |
|||
return Panel.DesiredSize; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override Size ArrangeOverride(Size finalSize) |
|||
{ |
|||
Panel!.Arrange(new Rect(finalSize)); |
|||
return finalSize; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="Panel"/> is created.
|
|||
/// </summary>
|
|||
/// <param name="panel">The panel.</param>
|
|||
protected virtual void PanelCreated(Panel panel) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the items for the presenter change, either because <see cref="Items"/>
|
|||
/// has been set, the items collection has been modified, or the panel has been created.
|
|||
/// </summary>
|
|||
/// <param name="e">A description of the change.</param>
|
|||
/// <remarks>
|
|||
/// The panel is guaranteed to be created when this method is called.
|
|||
/// </remarks>
|
|||
protected virtual void ItemsChanged(NotifyCollectionChangedEventArgs e) |
|||
{ |
|||
ItemContainerSync.ItemsChanged(this, Items, e); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates the <see cref="Panel"/> when <see cref="ApplyTemplate"/> is called for the first
|
|||
/// time.
|
|||
/// </summary>
|
|||
private void CreatePanel() |
|||
{ |
|||
Panel = ItemsPanel.Build(); |
|||
Panel.SetValue(TemplatedParentProperty, TemplatedParent); |
|||
|
|||
LogicalChildren.Clear(); |
|||
VisualChildren.Clear(); |
|||
LogicalChildren.Add(Panel); |
|||
VisualChildren.Add(Panel); |
|||
|
|||
_createdPanel = true; |
|||
|
|||
if (!IsHosted && _itemsSubscription == null && Items is INotifyCollectionChanged incc) |
|||
{ |
|||
_itemsSubscription = incc.WeakSubscribe(ItemsCollectionChanged); |
|||
} |
|||
|
|||
PanelCreated(Panel); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Called when the <see cref="Items"/> collection changes.
|
|||
/// </summary>
|
|||
/// <param name="sender">The sender.</param>
|
|||
/// <param name="e">The event args.</param>
|
|||
private void ItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) |
|||
{ |
|||
if (_createdPanel) |
|||
{ |
|||
ItemsChanged(e); |
|||
} |
|||
} |
|||
|
|||
private void TemplatedParentChanged(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
(e.NewValue as IItemsPresenterHost)?.RegisterItemsPresenter(this); |
|||
} |
|||
|
|||
int IChildIndexProvider.GetChildIndex(ILogical child) |
|||
{ |
|||
if (child is Control control && ItemContainerGenerator is { } generator) |
|||
{ |
|||
var index = ItemContainerGenerator.IndexFromContainer(control); |
|||
|
|||
return index; |
|||
} |
|||
|
|||
return -1; |
|||
} |
|||
|
|||
bool IChildIndexProvider.TryGetTotalCount(out int count) |
|||
{ |
|||
return Items.TryGetCountFast(out count); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,108 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Specialized; |
|||
using System.Diagnostics; |
|||
|
|||
namespace Avalonia.Controls.Presenters |
|||
{ |
|||
/// <summary>
|
|||
/// Generates containers for <see cref="ItemsPresenter"/>s that have non-virtualizing panels.
|
|||
/// </summary>
|
|||
internal class ItemsPresenterContainerGenerator : IDisposable |
|||
{ |
|||
private static NotifyCollectionChangedEventArgs s_Reset = new(NotifyCollectionChangedAction.Reset); |
|||
private ItemsPresenter _presenter; |
|||
|
|||
public ItemsPresenterContainerGenerator(ItemsPresenter presenter) |
|||
{ |
|||
Debug.Assert(presenter.ItemsControl is not null); |
|||
Debug.Assert(presenter.Panel is not null or IVirtualizingPanel); |
|||
|
|||
_presenter = presenter; |
|||
_presenter.ItemsControl.PropertyChanged += OnItemsControlPropertyChanged; |
|||
|
|||
if (_presenter.ItemsControl.Items is INotifyCollectionChanged incc) |
|||
incc.CollectionChanged += OnItemsChanged; |
|||
|
|||
OnItemsChanged(null, s_Reset); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_presenter.ItemsControl!.PropertyChanged -= OnItemsControlPropertyChanged; |
|||
|
|||
if (_presenter.ItemsControl.Items is INotifyCollectionChanged incc) |
|||
incc.CollectionChanged -= OnItemsChanged; |
|||
|
|||
_presenter.Panel!.Children.Clear(); |
|||
} |
|||
|
|||
private void OnItemsControlPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (e.Property == ItemsControl.ItemsProperty) |
|||
{ |
|||
if (e.OldValue is INotifyCollectionChanged inccOld) |
|||
inccOld.CollectionChanged -= OnItemsChanged; |
|||
OnItemsChanged(null, s_Reset); |
|||
if (e.NewValue is INotifyCollectionChanged inccNew) |
|||
inccNew.CollectionChanged += OnItemsChanged; |
|||
} |
|||
} |
|||
|
|||
private void OnItemsChanged(object? sender, NotifyCollectionChangedEventArgs e) |
|||
{ |
|||
if (_presenter.ItemsControl?.Items is null || _presenter.Panel is null) |
|||
return; |
|||
|
|||
var generator = _presenter.ItemsControl.ItemContainerGenerator; |
|||
var panel = _presenter.Panel; |
|||
|
|||
void Add(int index, IEnumerable items) |
|||
{ |
|||
var i = index; |
|||
|
|||
foreach (var item in items) |
|||
{ |
|||
var c = generator.Materialize(i, item); |
|||
panel.Children.Insert(i++, c.ContainerControl); |
|||
} |
|||
} |
|||
|
|||
void Remove(int index, int count) |
|||
{ |
|||
for (var i = 0; i < count; ++i) |
|||
panel.Children.RemoveAt(i + index); |
|||
} |
|||
|
|||
switch (e.Action) |
|||
{ |
|||
case NotifyCollectionChangedAction.Add: |
|||
generator.InsertSpace(e.NewStartingIndex, e.NewItems!.Count); |
|||
Add(e.NewStartingIndex, e.NewItems!); |
|||
break; |
|||
case NotifyCollectionChangedAction.Remove: |
|||
generator.RemoveRange(e.OldStartingIndex, e.OldItems!.Count); |
|||
Remove(e.OldStartingIndex, e.OldItems!.Count); |
|||
break; |
|||
case NotifyCollectionChangedAction.Replace: |
|||
generator.RemoveRange(e.OldStartingIndex, e.OldItems!.Count); |
|||
Remove(e.OldStartingIndex, e.OldItems!.Count); |
|||
generator.InsertSpace(e.NewStartingIndex, e.NewItems!.Count); |
|||
Add(e.NewStartingIndex, e.NewItems!); |
|||
break; |
|||
case NotifyCollectionChangedAction.Move: |
|||
generator.RemoveRange(e.OldStartingIndex, e.OldItems!.Count); |
|||
Remove(e.OldStartingIndex, e.OldItems!.Count); |
|||
generator.InsertSpace(e.NewStartingIndex, e.NewItems!.Count); |
|||
Add(e.NewStartingIndex, e.NewItems!); |
|||
break; |
|||
case NotifyCollectionChangedAction.Reset: |
|||
generator.Clear(); |
|||
panel.Children.Clear(); |
|||
if (_presenter.ItemsControl.Items is { } items) |
|||
Add(0, items); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,252 +1,20 @@ |
|||
using System; |
|||
using System.Collections.Specialized; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Input; |
|||
using Avalonia.Layout; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public class VirtualizingStackPanel : StackPanel, IVirtualizingPanel |
|||
public class VirtualizingStackPanel : StackPanel |
|||
{ |
|||
private Size _availableSpace; |
|||
private double _takenSpace; |
|||
private int _canBeRemoved; |
|||
private double _averageItemSize; |
|||
private int _averageCount; |
|||
private double _pixelOffset; |
|||
private double _crossAxisOffset; |
|||
private bool _forceRemeasure; |
|||
/// <summary>
|
|||
/// Defines the <see cref="VirtualizationMode"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<ItemVirtualizationMode> VirtualizationModeProperty = |
|||
AvaloniaProperty.Register<VirtualizingStackPanel, ItemVirtualizationMode>( |
|||
nameof(VirtualizationMode)); |
|||
|
|||
bool IVirtualizingPanel.IsFull |
|||
public ItemVirtualizationMode VirtualizationMode |
|||
{ |
|||
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(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
double IVirtualizingPanel.CrossAxisOffset |
|||
{ |
|||
get { return _crossAxisOffset; } |
|||
|
|||
set |
|||
{ |
|||
if (_crossAxisOffset != value) |
|||
{ |
|||
_crossAxisOffset = value; |
|||
InvalidateArrange(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private IVirtualizingController? Controller => ((IVirtualizingPanel)this).Controller; |
|||
|
|||
void IVirtualizingPanel.ForceInvalidateMeasure() |
|||
{ |
|||
InvalidateMeasure(); |
|||
_forceRemeasure = true; |
|||
} |
|||
|
|||
protected override Size MeasureOverride(Size availableSize) |
|||
{ |
|||
if (_forceRemeasure || availableSize != PreviousMeasure) |
|||
{ |
|||
_forceRemeasure = false; |
|||
_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 (Control control in e.NewItems!) |
|||
{ |
|||
UpdateAdd(control); |
|||
} |
|||
|
|||
break; |
|||
|
|||
case NotifyCollectionChangedAction.Remove: |
|||
foreach (Control control in e.OldItems!) |
|||
{ |
|||
UpdateRemove(control); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
} |
|||
|
|||
protected override IInputElement? GetControlInDirection(NavigationDirection direction, Control? from) |
|||
{ |
|||
var logicalScrollable = Parent as ILogicalScrollable; |
|||
|
|||
if (logicalScrollable?.IsLogicalScrollEnabled == true) |
|||
{ |
|||
return logicalScrollable.GetControlInDirection(direction, from); |
|||
} |
|||
else |
|||
{ |
|||
return base.GetControlInDirection(direction, from); |
|||
} |
|||
} |
|||
|
|||
internal override void ArrangeChild( |
|||
Control child, |
|||
Rect rect, |
|||
Size panelSize, |
|||
Orientation orientation) |
|||
{ |
|||
if (orientation == Orientation.Vertical) |
|||
{ |
|||
rect = new Rect( |
|||
rect.X - _crossAxisOffset, |
|||
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 - _crossAxisOffset, |
|||
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(Control child) |
|||
{ |
|||
var bounds = Bounds; |
|||
var spacing = Spacing; |
|||
|
|||
child.Measure(_availableSpace); |
|||
++_averageCount; |
|||
|
|||
if (Orientation == Orientation.Vertical) |
|||
{ |
|||
var height = child.DesiredSize.Height; |
|||
_takenSpace += height + spacing; |
|||
AddToAverageItemSize(height); |
|||
} |
|||
else |
|||
{ |
|||
var width = child.DesiredSize.Width; |
|||
_takenSpace += width + spacing; |
|||
AddToAverageItemSize(width); |
|||
} |
|||
} |
|||
|
|||
private void UpdateRemove(Control child) |
|||
{ |
|||
var bounds = Bounds; |
|||
var spacing = Spacing; |
|||
|
|||
if (Orientation == Orientation.Vertical) |
|||
{ |
|||
var height = child.DesiredSize.Height; |
|||
_takenSpace -= height + spacing; |
|||
RemoveFromAverageItemSize(height); |
|||
} |
|||
else |
|||
{ |
|||
var width = child.DesiredSize.Width; |
|||
_takenSpace -= width + spacing; |
|||
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; |
|||
get => GetValue(VirtualizationModeProperty); |
|||
set => SetValue(VirtualizationModeProperty, value); |
|||
} |
|||
} |
|||
} |
|||
|
|||
File diff suppressed because it is too large
@ -1,374 +1,374 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Avalonia.Controls.Generators; |
|||
using Avalonia.Controls.Presenters; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Controls.Templates; |
|||
using Avalonia.Layout; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Rendering; |
|||
using Avalonia.UnitTests; |
|||
using Avalonia.VisualTree; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Controls.UnitTests.Presenters |
|||
{ |
|||
public class ItemsPresenterTests_Virtualization |
|||
{ |
|||
[Fact] |
|||
public void Should_Not_Create_Items_Before_Added_To_Visual_Tree() |
|||
{ |
|||
var items = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToList(); |
|||
var target = new TestItemsPresenter(true) |
|||
{ |
|||
Items = items, |
|||
ItemsPanel = VirtualizingPanelTemplate(Orientation.Vertical), |
|||
ItemTemplate = ItemTemplate(), |
|||
VirtualizationMode = ItemVirtualizationMode.Simple, |
|||
}; |
|||
|
|||
var scroller = new ScrollContentPresenter |
|||
{ |
|||
Content = target, |
|||
}; |
|||
|
|||
scroller.UpdateChild(); |
|||
target.ApplyTemplate(); |
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(0, 0, 100, 100)); |
|||
|
|||
Assert.Empty(target.Panel.Children); |
|||
|
|||
var root = new TestRoot |
|||
{ |
|||
Child = scroller, |
|||
}; |
|||
|
|||
target.InvalidateMeasure(); |
|||
target.Panel.InvalidateMeasure(); |
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(0, 0, 100, 100)); |
|||
|
|||
Assert.Equal(10, target.Panel.Children.Count); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Return_IsLogicalScrollEnabled_False_When_Has_No_Virtualizing_Panel() |
|||
{ |
|||
var target = CreateTarget(); |
|||
target.ClearValue(ItemsPresenter.ItemsPanelProperty); |
|||
|
|||
target.ApplyTemplate(); |
|||
|
|||
Assert.False(((ILogicalScrollable)target).IsLogicalScrollEnabled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Return_IsLogicalScrollEnabled_False_When_VirtualizationMode_None() |
|||
{ |
|||
var target = CreateTarget(ItemVirtualizationMode.None); |
|||
|
|||
target.ApplyTemplate(); |
|||
|
|||
Assert.False(((ILogicalScrollable)target).IsLogicalScrollEnabled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Return_IsLogicalScrollEnabled_False_When_Doesnt_Have_ScrollPresenter_Parent() |
|||
{ |
|||
var target = new ItemsPresenter |
|||
{ |
|||
ItemsPanel = VirtualizingPanelTemplate(), |
|||
ItemTemplate = ItemTemplate(), |
|||
VirtualizationMode = ItemVirtualizationMode.Simple, |
|||
}; |
|||
|
|||
target.ApplyTemplate(); |
|||
|
|||
Assert.False(((ILogicalScrollable)target).IsLogicalScrollEnabled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Return_IsLogicalScrollEnabled_True() |
|||
{ |
|||
var target = CreateTarget(); |
|||
|
|||
target.ApplyTemplate(); |
|||
|
|||
Assert.True(((ILogicalScrollable)target).IsLogicalScrollEnabled); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Parent_ScrollContentPresenter_Properties_Should_Be_Set() |
|||
{ |
|||
var target = CreateTarget(); |
|||
|
|||
target.ApplyTemplate(); |
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(0, 0, 100, 100)); |
|||
|
|||
var scroll = (ScrollContentPresenter)target.Parent; |
|||
Assert.Equal(new Size(10, 20), scroll.Extent); |
|||
Assert.Equal(new Size(100, 10), scroll.Viewport); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Fill_Panel_With_Containers() |
|||
{ |
|||
var target = CreateTarget(); |
|||
|
|||
target.ApplyTemplate(); |
|||
|
|||
target.Measure(new Size(100, 100)); |
|||
Assert.Equal(10, target.Panel.Children.Count); |
|||
|
|||
target.Arrange(new Rect(0, 0, 100, 100)); |
|||
Assert.Equal(10, target.Panel.Children.Count); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Only_Create_Enough_Containers_To_Display_All_Items() |
|||
{ |
|||
var target = CreateTarget(itemCount: 2); |
|||
|
|||
target.ApplyTemplate(); |
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(0, 0, 100, 100)); |
|||
|
|||
Assert.Equal(2, target.Panel.Children.Count); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Expand_To_Fit_Containers_When_Flexible_Size() |
|||
{ |
|||
var target = CreateTarget(); |
|||
|
|||
target.ApplyTemplate(); |
|||
target.Measure(Size.Infinity); |
|||
target.Arrange(new Rect(target.DesiredSize)); |
|||
|
|||
Assert.Equal(new Size(10, 200), target.DesiredSize); |
|||
Assert.Equal(new Size(10, 200), target.Bounds.Size); |
|||
Assert.Equal(20, target.Panel.Children.Count); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Initial_Item_DataContexts_Should_Be_Correct() |
|||
{ |
|||
var target = CreateTarget(); |
|||
var items = (IList<string>)target.Items; |
|||
|
|||
target.ApplyTemplate(); |
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(0, 0, 100, 100)); |
|||
|
|||
for (var i = 0; i < target.Panel.Children.Count; ++i) |
|||
{ |
|||
Assert.Equal(items[i], target.Panel.Children[i].DataContext); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Add_New_Items_When_Control_Is_Enlarged() |
|||
{ |
|||
var target = CreateTarget(); |
|||
var items = (IList<string>)target.Items; |
|||
|
|||
target.ApplyTemplate(); |
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(0, 0, 100, 100)); |
|||
|
|||
Assert.Equal(10, target.Panel.Children.Count); |
|||
|
|||
target.Measure(new Size(120, 120)); |
|||
target.Arrange(new Rect(0, 0, 100, 120)); |
|||
|
|||
Assert.Equal(12, target.Panel.Children.Count); |
|||
|
|||
for (var i = 0; i < target.Panel.Children.Count; ++i) |
|||
{ |
|||
Assert.Equal(items[i], target.Panel.Children[i].DataContext); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Not_Create_Virtualizer_Before_Panel() |
|||
{ |
|||
var target = CreateTarget(); |
|||
|
|||
Assert.Null(target.Panel); |
|||
Assert.Null(target.Virtualizer); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Changing_VirtualizationMode_None_To_Simple_Should_Update_Control() |
|||
{ |
|||
var target = CreateTarget(mode: ItemVirtualizationMode.None); |
|||
var scroll = (ScrollContentPresenter)target.Parent; |
|||
|
|||
scroll.Measure(new Size(100, 100)); |
|||
scroll.Arrange(new Rect(0, 0, 100, 100)); |
|||
|
|||
Assert.Equal(20, target.Panel.Children.Count); |
|||
Assert.Equal(new Size(100, 200), scroll.Extent); |
|||
Assert.Equal(new Size(100, 100), scroll.Viewport); |
|||
|
|||
target.VirtualizationMode = ItemVirtualizationMode.Simple; |
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(0, 0, 100, 100)); |
|||
|
|||
Assert.Equal(10, target.Panel.Children.Count); |
|||
Assert.Equal(new Size(10, 20), scroll.Extent); |
|||
Assert.Equal(new Size(100, 10), scroll.Viewport); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Changing_VirtualizationMode_None_To_Simple_Should_Add_Correct_Number_Of_Controls() |
|||
{ |
|||
using (UnitTestApplication.Start(new TestServices())) |
|||
{ |
|||
var target = CreateTarget(mode: ItemVirtualizationMode.None); |
|||
var scroll = (TestScroller)target.Parent; |
|||
|
|||
scroll.Width = scroll.Height = 100; |
|||
scroll.LayoutManager.ExecuteInitialLayoutPass(); |
|||
|
|||
// Ensure than an intermediate measure pass doesn't add more controls than it
|
|||
// should. This can happen if target gets measured with Size.Infinity which
|
|||
// is what the available size should be when VirtualizationMode == None but not
|
|||
// what it should after VirtualizationMode is changed to Simple.
|
|||
target.Panel.Children.CollectionChanged += (s, e) => |
|||
{ |
|||
Assert.InRange(target.Panel.Children.Count, 0, 10); |
|||
}; |
|||
|
|||
target.VirtualizationMode = ItemVirtualizationMode.Simple; |
|||
((ILayoutRoot)scroll.GetVisualRoot()).LayoutManager.ExecuteLayoutPass(); |
|||
|
|||
Assert.Equal(10, target.Panel.Children.Count); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Changing_VirtualizationMode_Simple_To_None_Should_Update_Control() |
|||
{ |
|||
var target = CreateTarget(); |
|||
var scroll = (ScrollContentPresenter)target.Parent; |
|||
|
|||
scroll.Measure(new Size(100, 100)); |
|||
scroll.Arrange(new Rect(0, 0, 100, 100)); |
|||
|
|||
Assert.Equal(10, target.Panel.Children.Count); |
|||
Assert.Equal(new Size(10, 20), scroll.Extent); |
|||
Assert.Equal(new Size(100, 10), scroll.Viewport); |
|||
|
|||
target.VirtualizationMode = ItemVirtualizationMode.None; |
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(0, 0, 100, 100)); |
|||
|
|||
// Here - unlike changing the other way - we need to do a layout pass on the scroll
|
|||
// content presenter as non-logical scroll values are only updated on arrange.
|
|||
scroll.Measure(new Size(100, 100)); |
|||
scroll.Arrange(new Rect(0, 0, 100, 100)); |
|||
|
|||
Assert.Equal(20, target.Panel.Children.Count); |
|||
Assert.Equal(new Size(100, 200), scroll.Extent); |
|||
Assert.Equal(new Size(100, 100), scroll.Viewport); |
|||
} |
|||
|
|||
private static ItemsPresenter CreateTarget( |
|||
ItemVirtualizationMode mode = ItemVirtualizationMode.Simple, |
|||
Orientation orientation = Orientation.Vertical, |
|||
bool useContainers = true, |
|||
int itemCount = 20) |
|||
{ |
|||
ItemsPresenter result; |
|||
var items = Enumerable.Range(0, itemCount).Select(x => $"Item {x}").ToList(); |
|||
|
|||
var scroller = new TestScroller |
|||
{ |
|||
CanHorizontallyScroll = false, |
|||
CanVerticallyScroll = true, |
|||
Content = result = new TestItemsPresenter(useContainers) |
|||
{ |
|||
Items = items, |
|||
ItemsPanel = VirtualizingPanelTemplate(orientation), |
|||
ItemTemplate = ItemTemplate(), |
|||
VirtualizationMode = mode, |
|||
} |
|||
}; |
|||
|
|||
scroller.UpdateChild(); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private static IDataTemplate ItemTemplate() |
|||
{ |
|||
return new FuncDataTemplate<string>((x, _) => new Canvas |
|||
{ |
|||
Width = 10, |
|||
Height = 10, |
|||
}); |
|||
} |
|||
|
|||
private static ITemplate<Panel> VirtualizingPanelTemplate( |
|||
Orientation orientation = Orientation.Vertical) |
|||
{ |
|||
return new FuncTemplate<Panel>(() => new VirtualizingStackPanel |
|||
{ |
|||
Orientation = orientation, |
|||
}); |
|||
} |
|||
|
|||
private class TestScroller : ScrollContentPresenter, IRenderRoot, ILayoutRoot |
|||
{ |
|||
public TestScroller() |
|||
{ |
|||
LayoutManager = new LayoutManager(this); |
|||
} |
|||
|
|||
public IRenderer Renderer { get; } |
|||
public Size ClientSize { get; } |
|||
public double RenderScaling => 1; |
|||
|
|||
public Size MaxClientSize => Size.Infinity; |
|||
|
|||
public double LayoutScaling => 1; |
|||
|
|||
public ILayoutManager LayoutManager { get; } |
|||
|
|||
public IRenderTarget CreateRenderTarget() => throw new NotImplementedException(); |
|||
public void Invalidate(Rect rect) => throw new NotImplementedException(); |
|||
public Point PointToClient(PixelPoint p) => throw new NotImplementedException(); |
|||
public PixelPoint PointToScreen(Point p) => throw new NotImplementedException(); |
|||
} |
|||
|
|||
private class TestItemsPresenter : ItemsPresenter |
|||
{ |
|||
private bool _useContainers; |
|||
|
|||
public TestItemsPresenter(bool useContainers) |
|||
{ |
|||
_useContainers = useContainers; |
|||
} |
|||
|
|||
protected override IItemContainerGenerator CreateItemContainerGenerator() |
|||
{ |
|||
return _useContainers ? |
|||
new ItemContainerGenerator<TestContainer>(this, TestContainer.ContentProperty, null) : |
|||
new ItemContainerGenerator(this); |
|||
} |
|||
} |
|||
|
|||
private class TestContainer : ContentControl |
|||
{ |
|||
public TestContainer() |
|||
{ |
|||
Width = 10; |
|||
Height = 10; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
////using System;
|
|||
////using System.Collections.Generic;
|
|||
////using System.Linq;
|
|||
////using Avalonia.Controls.Generators;
|
|||
////using Avalonia.Controls.Presenters;
|
|||
////using Avalonia.Controls.Primitives;
|
|||
////using Avalonia.Controls.Templates;
|
|||
////using Avalonia.Layout;
|
|||
////using Avalonia.Platform;
|
|||
////using Avalonia.Rendering;
|
|||
////using Avalonia.UnitTests;
|
|||
////using Avalonia.VisualTree;
|
|||
////using Xunit;
|
|||
|
|||
////namespace Avalonia.Controls.UnitTests.Presenters
|
|||
////{
|
|||
//// public class ItemsPresenterTests_Virtualization
|
|||
//// {
|
|||
//// [Fact]
|
|||
//// public void Should_Not_Create_Items_Before_Added_To_Visual_Tree()
|
|||
//// {
|
|||
//// var items = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToList();
|
|||
//// var target = new TestItemsPresenter(true)
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// ItemsPanel = VirtualizingPanelTemplate(Orientation.Vertical),
|
|||
//// ItemTemplate = ItemTemplate(),
|
|||
//// VirtualizationMode = ItemVirtualizationMode.Simple,
|
|||
//// };
|
|||
|
|||
//// var scroller = new ScrollContentPresenter
|
|||
//// {
|
|||
//// Content = target,
|
|||
//// };
|
|||
|
|||
//// scroller.UpdateChild();
|
|||
//// target.ApplyTemplate();
|
|||
//// target.Measure(new Size(100, 100));
|
|||
//// target.Arrange(new Rect(0, 0, 100, 100));
|
|||
|
|||
//// Assert.Empty(target.Panel.Children);
|
|||
|
|||
//// var root = new TestRoot
|
|||
//// {
|
|||
//// Child = scroller,
|
|||
//// };
|
|||
|
|||
//// target.InvalidateMeasure();
|
|||
//// target.Panel.InvalidateMeasure();
|
|||
//// target.Measure(new Size(100, 100));
|
|||
//// target.Arrange(new Rect(0, 0, 100, 100));
|
|||
|
|||
//// Assert.Equal(10, target.Panel.Children.Count);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Return_IsLogicalScrollEnabled_False_When_Has_No_Virtualizing_Panel()
|
|||
//// {
|
|||
//// var target = CreateTarget();
|
|||
//// target.ClearValue(ItemsPresenter.ItemsPanelProperty);
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// Assert.False(((ILogicalScrollable)target).IsLogicalScrollEnabled);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Return_IsLogicalScrollEnabled_False_When_VirtualizationMode_None()
|
|||
//// {
|
|||
//// var target = CreateTarget(ItemVirtualizationMode.None);
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// Assert.False(((ILogicalScrollable)target).IsLogicalScrollEnabled);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Return_IsLogicalScrollEnabled_False_When_Doesnt_Have_ScrollPresenter_Parent()
|
|||
//// {
|
|||
//// var target = new ItemsPresenter
|
|||
//// {
|
|||
//// ItemsPanel = VirtualizingPanelTemplate(),
|
|||
//// ItemTemplate = ItemTemplate(),
|
|||
//// VirtualizationMode = ItemVirtualizationMode.Simple,
|
|||
//// };
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// Assert.False(((ILogicalScrollable)target).IsLogicalScrollEnabled);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Return_IsLogicalScrollEnabled_True()
|
|||
//// {
|
|||
//// var target = CreateTarget();
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// Assert.True(((ILogicalScrollable)target).IsLogicalScrollEnabled);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Parent_ScrollContentPresenter_Properties_Should_Be_Set()
|
|||
//// {
|
|||
//// var target = CreateTarget();
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// target.Measure(new Size(100, 100));
|
|||
//// target.Arrange(new Rect(0, 0, 100, 100));
|
|||
|
|||
//// var scroll = (ScrollContentPresenter)target.Parent;
|
|||
//// Assert.Equal(new Size(10, 20), scroll.Extent);
|
|||
//// Assert.Equal(new Size(100, 10), scroll.Viewport);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Fill_Panel_With_Containers()
|
|||
//// {
|
|||
//// var target = CreateTarget();
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
|
|||
//// target.Measure(new Size(100, 100));
|
|||
//// Assert.Equal(10, target.Panel.Children.Count);
|
|||
|
|||
//// target.Arrange(new Rect(0, 0, 100, 100));
|
|||
//// Assert.Equal(10, target.Panel.Children.Count);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Only_Create_Enough_Containers_To_Display_All_Items()
|
|||
//// {
|
|||
//// var target = CreateTarget(itemCount: 2);
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// target.Measure(new Size(100, 100));
|
|||
//// target.Arrange(new Rect(0, 0, 100, 100));
|
|||
|
|||
//// Assert.Equal(2, target.Panel.Children.Count);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Expand_To_Fit_Containers_When_Flexible_Size()
|
|||
//// {
|
|||
//// var target = CreateTarget();
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// target.Measure(Size.Infinity);
|
|||
//// target.Arrange(new Rect(target.DesiredSize));
|
|||
|
|||
//// Assert.Equal(new Size(10, 200), target.DesiredSize);
|
|||
//// Assert.Equal(new Size(10, 200), target.Bounds.Size);
|
|||
//// Assert.Equal(20, target.Panel.Children.Count);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Initial_Item_DataContexts_Should_Be_Correct()
|
|||
//// {
|
|||
//// var target = CreateTarget();
|
|||
//// var items = (IList<string>)target.Items;
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// target.Measure(new Size(100, 100));
|
|||
//// target.Arrange(new Rect(0, 0, 100, 100));
|
|||
|
|||
//// for (var i = 0; i < target.Panel.Children.Count; ++i)
|
|||
//// {
|
|||
//// Assert.Equal(items[i], target.Panel.Children[i].DataContext);
|
|||
//// }
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Add_New_Items_When_Control_Is_Enlarged()
|
|||
//// {
|
|||
//// var target = CreateTarget();
|
|||
//// var items = (IList<string>)target.Items;
|
|||
|
|||
//// target.ApplyTemplate();
|
|||
//// target.Measure(new Size(100, 100));
|
|||
//// target.Arrange(new Rect(0, 0, 100, 100));
|
|||
|
|||
//// Assert.Equal(10, target.Panel.Children.Count);
|
|||
|
|||
//// target.Measure(new Size(120, 120));
|
|||
//// target.Arrange(new Rect(0, 0, 100, 120));
|
|||
|
|||
//// Assert.Equal(12, target.Panel.Children.Count);
|
|||
|
|||
//// for (var i = 0; i < target.Panel.Children.Count; ++i)
|
|||
//// {
|
|||
//// Assert.Equal(items[i], target.Panel.Children[i].DataContext);
|
|||
//// }
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Should_Not_Create_Virtualizer_Before_Panel()
|
|||
//// {
|
|||
//// var target = CreateTarget();
|
|||
|
|||
//// Assert.Null(target.Panel);
|
|||
//// Assert.Null(target.Virtualizer);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Changing_VirtualizationMode_None_To_Simple_Should_Update_Control()
|
|||
//// {
|
|||
//// var target = CreateTarget(mode: ItemVirtualizationMode.None);
|
|||
//// var scroll = (ScrollContentPresenter)target.Parent;
|
|||
|
|||
//// scroll.Measure(new Size(100, 100));
|
|||
//// scroll.Arrange(new Rect(0, 0, 100, 100));
|
|||
|
|||
//// Assert.Equal(20, target.Panel.Children.Count);
|
|||
//// Assert.Equal(new Size(100, 200), scroll.Extent);
|
|||
//// Assert.Equal(new Size(100, 100), scroll.Viewport);
|
|||
|
|||
//// target.VirtualizationMode = ItemVirtualizationMode.Simple;
|
|||
//// target.Measure(new Size(100, 100));
|
|||
//// target.Arrange(new Rect(0, 0, 100, 100));
|
|||
|
|||
//// Assert.Equal(10, target.Panel.Children.Count);
|
|||
//// Assert.Equal(new Size(10, 20), scroll.Extent);
|
|||
//// Assert.Equal(new Size(100, 10), scroll.Viewport);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Changing_VirtualizationMode_None_To_Simple_Should_Add_Correct_Number_Of_Controls()
|
|||
//// {
|
|||
//// using (UnitTestApplication.Start(new TestServices()))
|
|||
//// {
|
|||
//// var target = CreateTarget(mode: ItemVirtualizationMode.None);
|
|||
//// var scroll = (TestScroller)target.Parent;
|
|||
|
|||
//// scroll.Width = scroll.Height = 100;
|
|||
//// scroll.LayoutManager.ExecuteInitialLayoutPass();
|
|||
|
|||
//// // Ensure than an intermediate measure pass doesn't add more controls than it
|
|||
//// // should. This can happen if target gets measured with Size.Infinity which
|
|||
//// // is what the available size should be when VirtualizationMode == None but not
|
|||
//// // what it should after VirtualizationMode is changed to Simple.
|
|||
//// target.Panel.Children.CollectionChanged += (s, e) =>
|
|||
//// {
|
|||
//// Assert.InRange(target.Panel.Children.Count, 0, 10);
|
|||
//// };
|
|||
|
|||
//// target.VirtualizationMode = ItemVirtualizationMode.Simple;
|
|||
//// ((ILayoutRoot)scroll.GetVisualRoot()).LayoutManager.ExecuteLayoutPass();
|
|||
|
|||
//// Assert.Equal(10, target.Panel.Children.Count);
|
|||
//// }
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Changing_VirtualizationMode_Simple_To_None_Should_Update_Control()
|
|||
//// {
|
|||
//// var target = CreateTarget();
|
|||
//// var scroll = (ScrollContentPresenter)target.Parent;
|
|||
|
|||
//// scroll.Measure(new Size(100, 100));
|
|||
//// scroll.Arrange(new Rect(0, 0, 100, 100));
|
|||
|
|||
//// Assert.Equal(10, target.Panel.Children.Count);
|
|||
//// Assert.Equal(new Size(10, 20), scroll.Extent);
|
|||
//// Assert.Equal(new Size(100, 10), scroll.Viewport);
|
|||
|
|||
//// target.VirtualizationMode = ItemVirtualizationMode.None;
|
|||
//// target.Measure(new Size(100, 100));
|
|||
//// target.Arrange(new Rect(0, 0, 100, 100));
|
|||
|
|||
//// // Here - unlike changing the other way - we need to do a layout pass on the scroll
|
|||
//// // content presenter as non-logical scroll values are only updated on arrange.
|
|||
//// scroll.Measure(new Size(100, 100));
|
|||
//// scroll.Arrange(new Rect(0, 0, 100, 100));
|
|||
|
|||
//// Assert.Equal(20, target.Panel.Children.Count);
|
|||
//// Assert.Equal(new Size(100, 200), scroll.Extent);
|
|||
//// Assert.Equal(new Size(100, 100), scroll.Viewport);
|
|||
//// }
|
|||
|
|||
//// private static ItemsPresenter CreateTarget(
|
|||
//// ItemVirtualizationMode mode = ItemVirtualizationMode.Simple,
|
|||
//// Orientation orientation = Orientation.Vertical,
|
|||
//// bool useContainers = true,
|
|||
//// int itemCount = 20)
|
|||
//// {
|
|||
//// ItemsPresenter result;
|
|||
//// var items = Enumerable.Range(0, itemCount).Select(x => $"Item {x}").ToList();
|
|||
|
|||
//// var scroller = new TestScroller
|
|||
//// {
|
|||
//// CanHorizontallyScroll = false,
|
|||
//// CanVerticallyScroll = true,
|
|||
//// Content = result = new TestItemsPresenter(useContainers)
|
|||
//// {
|
|||
//// Items = items,
|
|||
//// ItemsPanel = VirtualizingPanelTemplate(orientation),
|
|||
//// ItemTemplate = ItemTemplate(),
|
|||
//// VirtualizationMode = mode,
|
|||
//// }
|
|||
//// };
|
|||
|
|||
//// scroller.UpdateChild();
|
|||
|
|||
//// return result;
|
|||
//// }
|
|||
|
|||
//// private static IDataTemplate ItemTemplate()
|
|||
//// {
|
|||
//// return new FuncDataTemplate<string>((x, _) => new Canvas
|
|||
//// {
|
|||
//// Width = 10,
|
|||
//// Height = 10,
|
|||
//// });
|
|||
//// }
|
|||
|
|||
//// private static ITemplate<Panel> VirtualizingPanelTemplate(
|
|||
//// Orientation orientation = Orientation.Vertical)
|
|||
//// {
|
|||
//// return new FuncTemplate<Panel>(() => new VirtualizingStackPanel
|
|||
//// {
|
|||
//// Orientation = orientation,
|
|||
//// });
|
|||
//// }
|
|||
|
|||
//// private class TestScroller : ScrollContentPresenter, IRenderRoot, ILayoutRoot
|
|||
//// {
|
|||
//// public TestScroller()
|
|||
//// {
|
|||
//// LayoutManager = new LayoutManager(this);
|
|||
//// }
|
|||
|
|||
//// public IRenderer Renderer { get; }
|
|||
//// public Size ClientSize { get; }
|
|||
//// public double RenderScaling => 1;
|
|||
|
|||
//// public Size MaxClientSize => Size.Infinity;
|
|||
|
|||
//// public double LayoutScaling => 1;
|
|||
|
|||
//// public ILayoutManager LayoutManager { get; }
|
|||
|
|||
//// public IRenderTarget CreateRenderTarget() => throw new NotImplementedException();
|
|||
//// public void Invalidate(Rect rect) => throw new NotImplementedException();
|
|||
//// public Point PointToClient(PixelPoint p) => throw new NotImplementedException();
|
|||
//// public PixelPoint PointToScreen(Point p) => throw new NotImplementedException();
|
|||
//// }
|
|||
|
|||
//// private class TestItemsPresenter : ItemsPresenter
|
|||
//// {
|
|||
//// private bool _useContainers;
|
|||
|
|||
//// public TestItemsPresenter(bool useContainers)
|
|||
//// {
|
|||
//// _useContainers = useContainers;
|
|||
//// }
|
|||
|
|||
//// protected override IItemContainerGenerator CreateItemContainerGenerator()
|
|||
//// {
|
|||
//// return _useContainers ?
|
|||
//// new ItemContainerGenerator<TestContainer>(this, TestContainer.ContentProperty, null) :
|
|||
//// new ItemContainerGenerator(this);
|
|||
//// }
|
|||
//// }
|
|||
|
|||
//// private class TestContainer : ContentControl
|
|||
//// {
|
|||
//// public TestContainer()
|
|||
//// {
|
|||
//// Width = 10;
|
|||
//// Height = 10;
|
|||
//// }
|
|||
//// }
|
|||
//// }
|
|||
////}
|
|||
|
|||
File diff suppressed because it is too large
@ -1,261 +1,261 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Input; |
|||
using Avalonia.LogicalTree; |
|||
using Moq; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Controls.UnitTests |
|||
{ |
|||
public class VirtualizingStackPanelTests |
|||
{ |
|||
public class Vertical |
|||
{ |
|||
[Fact] |
|||
public void Measure_Invokes_Controller_UpdateControls() |
|||
{ |
|||
var target = new VirtualizingStackPanel(); |
|||
var controller = new Mock<IVirtualizingController>(); |
|||
|
|||
((IVirtualizingPanel)target).Controller = controller.Object; |
|||
target.Measure(new Size(100, 100)); |
|||
|
|||
controller.Verify(x => x.UpdateControls(), Times.Once()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Measure_Invokes_Controller_UpdateControls_If_AvailableSize_Changes() |
|||
{ |
|||
var target = new VirtualizingStackPanel(); |
|||
var controller = new Mock<IVirtualizingController>(); |
|||
|
|||
((IVirtualizingPanel)target).Controller = controller.Object; |
|||
target.Measure(new Size(100, 100)); |
|||
target.InvalidateMeasure(); |
|||
target.Measure(new Size(100, 100)); |
|||
target.InvalidateMeasure(); |
|||
target.Measure(new Size(100, 101)); |
|||
|
|||
controller.Verify(x => x.UpdateControls(), Times.Exactly(2)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Measure_Does_Not_Invoke_Controller_UpdateControls_If_AvailableSize_Is_The_Same() |
|||
{ |
|||
var target = new VirtualizingStackPanel(); |
|||
var controller = new Mock<IVirtualizingController>(); |
|||
|
|||
((IVirtualizingPanel)target).Controller = controller.Object; |
|||
target.Measure(new Size(100, 100)); |
|||
target.InvalidateMeasure(); |
|||
target.Measure(new Size(100, 100)); |
|||
|
|||
controller.Verify(x => x.UpdateControls(), Times.Once()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Measure_Invokes_Controller_UpdateControls_If_AvailableSize_Is_The_Same_After_ForceInvalidateMeasure() |
|||
{ |
|||
var target = new VirtualizingStackPanel(); |
|||
var controller = new Mock<IVirtualizingController>(); |
|||
|
|||
((IVirtualizingPanel)target).Controller = controller.Object; |
|||
target.Measure(new Size(100, 100)); |
|||
((IVirtualizingPanel)target).ForceInvalidateMeasure(); |
|||
target.Measure(new Size(100, 100)); |
|||
|
|||
controller.Verify(x => x.UpdateControls(), Times.Exactly(2)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Arrange_Invokes_Controller_UpdateControls() |
|||
{ |
|||
var target = new VirtualizingStackPanel(); |
|||
var controller = new Mock<IVirtualizingController>(); |
|||
|
|||
((IVirtualizingPanel)target).Controller = controller.Object; |
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(0, 0, 110, 110)); |
|||
|
|||
controller.Verify(x => x.UpdateControls(), Times.Exactly(2)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Reports_IsFull_False_Until_Measure_Height_Is_Reached() |
|||
{ |
|||
var target = new VirtualizingStackPanel(); |
|||
var vp = (IVirtualizingPanel)target; |
|||
|
|||
target.Measure(new Size(100, 100)); |
|||
|
|||
Assert.Equal(new Size(0, 0), target.DesiredSize); |
|||
Assert.Equal(new Size(0, 0), target.Bounds.Size); |
|||
|
|||
Assert.False(vp.IsFull); |
|||
Assert.Equal(0, vp.OverflowCount); |
|||
target.Children.Add(new Canvas { Width = 50, Height = 50 }); |
|||
Assert.False(vp.IsFull); |
|||
Assert.Equal(0, vp.OverflowCount); |
|||
target.Children.Add(new Canvas { Width = 50, Height = 50 }); |
|||
Assert.True(vp.IsFull); |
|||
Assert.Equal(0, vp.OverflowCount); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Reports_Overflow_After_Arrange() |
|||
{ |
|||
var target = new VirtualizingStackPanel(); |
|||
var vp = (IVirtualizingPanel)target; |
|||
|
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(target.DesiredSize)); |
|||
|
|||
Assert.Equal(new Size(0, 0), target.Bounds.Size); |
|||
|
|||
target.Children.Add(new Canvas { Width = 50, Height = 50 }); |
|||
target.Children.Add(new Canvas { Width = 50, Height = 50 }); |
|||
target.Children.Add(new Canvas { Width = 50, Height = 50 }); |
|||
target.Children.Add(new Canvas { Width = 50, Height = 50 }); |
|||
Assert.Equal(0, vp.OverflowCount); |
|||
|
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(target.DesiredSize)); |
|||
|
|||
Assert.Equal(2, vp.OverflowCount); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Reports_Correct_Overflow_During_Arrange() |
|||
{ |
|||
var target = new VirtualizingStackPanel(); |
|||
var vp = (IVirtualizingPanel)target; |
|||
var controller = new Mock<IVirtualizingController>(); |
|||
var called = false; |
|||
|
|||
target.Children.Add(new Canvas { Width = 50, Height = 50 }); |
|||
target.Children.Add(new Canvas { Width = 50, Height = 52 }); |
|||
target.Measure(new Size(100, 100)); |
|||
|
|||
controller.Setup(x => x.UpdateControls()).Callback(() => |
|||
{ |
|||
Assert.Equal(2, vp.PixelOverflow); |
|||
Assert.Equal(0, vp.OverflowCount); |
|||
called = true; |
|||
}); |
|||
|
|||
vp.Controller = controller.Object; |
|||
target.Arrange(new Rect(target.DesiredSize)); |
|||
|
|||
Assert.True(called); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Reports_PixelOverflow_After_Arrange() |
|||
{ |
|||
var target = new VirtualizingStackPanel(); |
|||
|
|||
target.Children.Add(new Canvas { Width = 50, Height = 50 }); |
|||
target.Children.Add(new Canvas { Width = 50, Height = 52 }); |
|||
|
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(target.DesiredSize)); |
|||
|
|||
Assert.Equal(2, ((IVirtualizingPanel)target).PixelOverflow); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Reports_PixelOverflow_After_Arrange_Smaller_Than_Measure() |
|||
{ |
|||
var target = new VirtualizingStackPanel(); |
|||
|
|||
target.Children.Add(new Canvas { Width = 50, Height = 50 }); |
|||
target.Children.Add(new Canvas { Width = 50, Height = 52 }); |
|||
|
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(0, 0, 50, 50)); |
|||
|
|||
Assert.Equal(52, ((IVirtualizingPanel)target).PixelOverflow); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Reports_PixelOverflow_With_PixelOffset() |
|||
{ |
|||
var target = new VirtualizingStackPanel(); |
|||
var vp = (IVirtualizingPanel)target; |
|||
|
|||
target.Children.Add(new Canvas { Width = 50, Height = 50 }); |
|||
target.Children.Add(new Canvas { Width = 50, Height = 52 }); |
|||
vp.PixelOffset = 2; |
|||
|
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(target.DesiredSize)); |
|||
|
|||
Assert.Equal(2, vp.PixelOverflow); |
|||
} |
|||
|
|||
[Fact] |
|||
public void PixelOffset_Can_Be_More_Than_Child_Without_Affecting_IsFull() |
|||
{ |
|||
var target = new VirtualizingStackPanel(); |
|||
var vp = (IVirtualizingPanel)target; |
|||
|
|||
target.Children.Add(new Canvas { Width = 50, Height = 50 }); |
|||
target.Children.Add(new Canvas { Width = 50, Height = 52 }); |
|||
vp.PixelOffset = 55; |
|||
|
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(target.DesiredSize)); |
|||
|
|||
Assert.Equal(55, vp.PixelOffset); |
|||
Assert.Equal(2, vp.PixelOverflow); |
|||
Assert.True(vp.IsFull); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Passes_Navigation_Request_To_ILogicalScrollable_Parent() |
|||
{ |
|||
var target = new VirtualizingStackPanel(); |
|||
var presenter = new TestPresenter { Child = target }; |
|||
var from = new Canvas(); |
|||
|
|||
((INavigableContainer)target).GetControl(NavigationDirection.Next, from, false); |
|||
|
|||
Assert.Equal(1, presenter.NavigationRequests.Count); |
|||
Assert.Equal((NavigationDirection.Next, from), presenter.NavigationRequests[0]); |
|||
} |
|||
|
|||
private class TestPresenter : Decorator, ILogicalScrollable |
|||
{ |
|||
public bool CanHorizontallyScroll { get; set; } |
|||
public bool CanVerticallyScroll { get; set; } |
|||
public bool IsLogicalScrollEnabled => true; |
|||
public Size ScrollSize { get; } |
|||
public Size PageScrollSize { get; } |
|||
public Size Extent { get; } |
|||
public Vector Offset { get; set; } |
|||
public Size Viewport { get; } |
|||
|
|||
public event EventHandler ScrollInvalidated; |
|||
|
|||
public List<(NavigationDirection, Control)> NavigationRequests { get; } = new(); |
|||
|
|||
public bool BringIntoView(Control target, Rect targetRect) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
|
|||
public Control GetControlInDirection(NavigationDirection direction, Control from) |
|||
{ |
|||
NavigationRequests.Add((direction, from)); |
|||
return null; |
|||
} |
|||
|
|||
public void RaiseScrollInvalidated(EventArgs e) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
////using System;
|
|||
////using System.Collections.Generic;
|
|||
////using Avalonia.Controls.Primitives;
|
|||
////using Avalonia.Input;
|
|||
////using Avalonia.LogicalTree;
|
|||
////using Moq;
|
|||
////using Xunit;
|
|||
|
|||
////namespace Avalonia.Controls.UnitTests
|
|||
////{
|
|||
//// public class VirtualizingStackPanelTests
|
|||
//// {
|
|||
//// public class Vertical
|
|||
//// {
|
|||
//// [Fact]
|
|||
//// public void Measure_Invokes_Controller_UpdateControls()
|
|||
//// {
|
|||
//// var target = new VirtualizingStackPanel();
|
|||
//// var controller = new Mock<IVirtualizingController>();
|
|||
|
|||
//// ((IVirtualizingPanel)target).Controller = controller.Object;
|
|||
//// target.Measure(new Size(100, 100));
|
|||
|
|||
//// controller.Verify(x => x.UpdateControls(), Times.Once());
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Measure_Invokes_Controller_UpdateControls_If_AvailableSize_Changes()
|
|||
//// {
|
|||
//// var target = new VirtualizingStackPanel();
|
|||
//// var controller = new Mock<IVirtualizingController>();
|
|||
|
|||
//// ((IVirtualizingPanel)target).Controller = controller.Object;
|
|||
//// target.Measure(new Size(100, 100));
|
|||
//// target.InvalidateMeasure();
|
|||
//// target.Measure(new Size(100, 100));
|
|||
//// target.InvalidateMeasure();
|
|||
//// target.Measure(new Size(100, 101));
|
|||
|
|||
//// controller.Verify(x => x.UpdateControls(), Times.Exactly(2));
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Measure_Does_Not_Invoke_Controller_UpdateControls_If_AvailableSize_Is_The_Same()
|
|||
//// {
|
|||
//// var target = new VirtualizingStackPanel();
|
|||
//// var controller = new Mock<IVirtualizingController>();
|
|||
|
|||
//// ((IVirtualizingPanel)target).Controller = controller.Object;
|
|||
//// target.Measure(new Size(100, 100));
|
|||
//// target.InvalidateMeasure();
|
|||
//// target.Measure(new Size(100, 100));
|
|||
|
|||
//// controller.Verify(x => x.UpdateControls(), Times.Once());
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Measure_Invokes_Controller_UpdateControls_If_AvailableSize_Is_The_Same_After_ForceInvalidateMeasure()
|
|||
//// {
|
|||
//// var target = new VirtualizingStackPanel();
|
|||
//// var controller = new Mock<IVirtualizingController>();
|
|||
|
|||
//// ((IVirtualizingPanel)target).Controller = controller.Object;
|
|||
//// target.Measure(new Size(100, 100));
|
|||
//// ((IVirtualizingPanel)target).ForceInvalidateMeasure();
|
|||
//// target.Measure(new Size(100, 100));
|
|||
|
|||
//// controller.Verify(x => x.UpdateControls(), Times.Exactly(2));
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Arrange_Invokes_Controller_UpdateControls()
|
|||
//// {
|
|||
//// var target = new VirtualizingStackPanel();
|
|||
//// var controller = new Mock<IVirtualizingController>();
|
|||
|
|||
//// ((IVirtualizingPanel)target).Controller = controller.Object;
|
|||
//// target.Measure(new Size(100, 100));
|
|||
//// target.Arrange(new Rect(0, 0, 110, 110));
|
|||
|
|||
//// controller.Verify(x => x.UpdateControls(), Times.Exactly(2));
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Reports_IsFull_False_Until_Measure_Height_Is_Reached()
|
|||
//// {
|
|||
//// var target = new VirtualizingStackPanel();
|
|||
//// var vp = (IVirtualizingPanel)target;
|
|||
|
|||
//// target.Measure(new Size(100, 100));
|
|||
|
|||
//// Assert.Equal(new Size(0, 0), target.DesiredSize);
|
|||
//// Assert.Equal(new Size(0, 0), target.Bounds.Size);
|
|||
|
|||
//// Assert.False(vp.IsFull);
|
|||
//// Assert.Equal(0, vp.OverflowCount);
|
|||
//// target.Children.Add(new Canvas { Width = 50, Height = 50 });
|
|||
//// Assert.False(vp.IsFull);
|
|||
//// Assert.Equal(0, vp.OverflowCount);
|
|||
//// target.Children.Add(new Canvas { Width = 50, Height = 50 });
|
|||
//// Assert.True(vp.IsFull);
|
|||
//// Assert.Equal(0, vp.OverflowCount);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Reports_Overflow_After_Arrange()
|
|||
//// {
|
|||
//// var target = new VirtualizingStackPanel();
|
|||
//// var vp = (IVirtualizingPanel)target;
|
|||
|
|||
//// target.Measure(new Size(100, 100));
|
|||
//// target.Arrange(new Rect(target.DesiredSize));
|
|||
|
|||
//// Assert.Equal(new Size(0, 0), target.Bounds.Size);
|
|||
|
|||
//// target.Children.Add(new Canvas { Width = 50, Height = 50 });
|
|||
//// target.Children.Add(new Canvas { Width = 50, Height = 50 });
|
|||
//// target.Children.Add(new Canvas { Width = 50, Height = 50 });
|
|||
//// target.Children.Add(new Canvas { Width = 50, Height = 50 });
|
|||
//// Assert.Equal(0, vp.OverflowCount);
|
|||
|
|||
//// target.Measure(new Size(100, 100));
|
|||
//// target.Arrange(new Rect(target.DesiredSize));
|
|||
|
|||
//// Assert.Equal(2, vp.OverflowCount);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Reports_Correct_Overflow_During_Arrange()
|
|||
//// {
|
|||
//// var target = new VirtualizingStackPanel();
|
|||
//// var vp = (IVirtualizingPanel)target;
|
|||
//// var controller = new Mock<IVirtualizingController>();
|
|||
//// var called = false;
|
|||
|
|||
//// target.Children.Add(new Canvas { Width = 50, Height = 50 });
|
|||
//// target.Children.Add(new Canvas { Width = 50, Height = 52 });
|
|||
//// target.Measure(new Size(100, 100));
|
|||
|
|||
//// controller.Setup(x => x.UpdateControls()).Callback(() =>
|
|||
//// {
|
|||
//// Assert.Equal(2, vp.PixelOverflow);
|
|||
//// Assert.Equal(0, vp.OverflowCount);
|
|||
//// called = true;
|
|||
//// });
|
|||
|
|||
//// vp.Controller = controller.Object;
|
|||
//// target.Arrange(new Rect(target.DesiredSize));
|
|||
|
|||
//// Assert.True(called);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Reports_PixelOverflow_After_Arrange()
|
|||
//// {
|
|||
//// var target = new VirtualizingStackPanel();
|
|||
|
|||
//// target.Children.Add(new Canvas { Width = 50, Height = 50 });
|
|||
//// target.Children.Add(new Canvas { Width = 50, Height = 52 });
|
|||
|
|||
//// target.Measure(new Size(100, 100));
|
|||
//// target.Arrange(new Rect(target.DesiredSize));
|
|||
|
|||
//// Assert.Equal(2, ((IVirtualizingPanel)target).PixelOverflow);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Reports_PixelOverflow_After_Arrange_Smaller_Than_Measure()
|
|||
//// {
|
|||
//// var target = new VirtualizingStackPanel();
|
|||
|
|||
//// target.Children.Add(new Canvas { Width = 50, Height = 50 });
|
|||
//// target.Children.Add(new Canvas { Width = 50, Height = 52 });
|
|||
|
|||
//// target.Measure(new Size(100, 100));
|
|||
//// target.Arrange(new Rect(0, 0, 50, 50));
|
|||
|
|||
//// Assert.Equal(52, ((IVirtualizingPanel)target).PixelOverflow);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Reports_PixelOverflow_With_PixelOffset()
|
|||
//// {
|
|||
//// var target = new VirtualizingStackPanel();
|
|||
//// var vp = (IVirtualizingPanel)target;
|
|||
|
|||
//// target.Children.Add(new Canvas { Width = 50, Height = 50 });
|
|||
//// target.Children.Add(new Canvas { Width = 50, Height = 52 });
|
|||
//// vp.PixelOffset = 2;
|
|||
|
|||
//// target.Measure(new Size(100, 100));
|
|||
//// target.Arrange(new Rect(target.DesiredSize));
|
|||
|
|||
//// Assert.Equal(2, vp.PixelOverflow);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void PixelOffset_Can_Be_More_Than_Child_Without_Affecting_IsFull()
|
|||
//// {
|
|||
//// var target = new VirtualizingStackPanel();
|
|||
//// var vp = (IVirtualizingPanel)target;
|
|||
|
|||
//// target.Children.Add(new Canvas { Width = 50, Height = 50 });
|
|||
//// target.Children.Add(new Canvas { Width = 50, Height = 52 });
|
|||
//// vp.PixelOffset = 55;
|
|||
|
|||
//// target.Measure(new Size(100, 100));
|
|||
//// target.Arrange(new Rect(target.DesiredSize));
|
|||
|
|||
//// Assert.Equal(55, vp.PixelOffset);
|
|||
//// Assert.Equal(2, vp.PixelOverflow);
|
|||
//// Assert.True(vp.IsFull);
|
|||
//// }
|
|||
|
|||
//// [Fact]
|
|||
//// public void Passes_Navigation_Request_To_ILogicalScrollable_Parent()
|
|||
//// {
|
|||
//// var target = new VirtualizingStackPanel();
|
|||
//// var presenter = new TestPresenter { Child = target };
|
|||
//// var from = new Canvas();
|
|||
|
|||
//// ((INavigableContainer)target).GetControl(NavigationDirection.Next, from, false);
|
|||
|
|||
//// Assert.Equal(1, presenter.NavigationRequests.Count);
|
|||
//// Assert.Equal((NavigationDirection.Next, from), presenter.NavigationRequests[0]);
|
|||
//// }
|
|||
|
|||
//// private class TestPresenter : Decorator, ILogicalScrollable
|
|||
//// {
|
|||
//// public bool CanHorizontallyScroll { get; set; }
|
|||
//// public bool CanVerticallyScroll { get; set; }
|
|||
//// public bool IsLogicalScrollEnabled => true;
|
|||
//// public Size ScrollSize { get; }
|
|||
//// public Size PageScrollSize { get; }
|
|||
//// public Size Extent { get; }
|
|||
//// public Vector Offset { get; set; }
|
|||
//// public Size Viewport { get; }
|
|||
|
|||
//// public event EventHandler ScrollInvalidated;
|
|||
|
|||
//// public List<(NavigationDirection, Control)> NavigationRequests { get; } = new();
|
|||
|
|||
//// public bool BringIntoView(Control target, Rect targetRect)
|
|||
//// {
|
|||
//// throw new NotImplementedException();
|
|||
//// }
|
|||
|
|||
//// public Control GetControlInDirection(NavigationDirection direction, Control from)
|
|||
//// {
|
|||
//// NavigationRequests.Add((direction, from));
|
|||
//// return null;
|
|||
//// }
|
|||
|
|||
//// public void RaiseScrollInvalidated(EventArgs e)
|
|||
//// {
|
|||
//// throw new NotImplementedException();
|
|||
//// }
|
|||
//// }
|
|||
//// }
|
|||
//// }
|
|||
////}
|
|||
|
|||
Loading…
Reference in new issue