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; |
||||
|
using System.Collections; |
||||
using System.Collections.Specialized; |
using System.Collections.Specialized; |
||||
using Avalonia.Controls.Primitives; |
using System.Diagnostics; |
||||
using Avalonia.Input; |
|
||||
using static Avalonia.Utilities.MathUtilities; |
|
||||
|
|
||||
namespace Avalonia.Controls.Presenters |
namespace Avalonia.Controls.Presenters |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Displays items inside an <see cref="ItemsControl"/>.
|
/// Presents items inside an <see cref="Avalonia.Controls.ItemsControl"/>.
|
||||
/// </summary>
|
/// </summary>
|
||||
public class ItemsPresenter : ItemsPresenterBase, ILogicalScrollable |
public class ItemsPresenter : Control |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Defines the <see cref="VirtualizationMode"/> property.
|
/// Defines the <see cref="ItemsPanel"/> property.
|
||||
/// </summary>
|
/// </summary>
|
||||
public static readonly StyledProperty<ItemVirtualizationMode> VirtualizationModeProperty = |
public static readonly StyledProperty<ITemplate<Panel>> ItemsPanelProperty = |
||||
AvaloniaProperty.Register<ItemsPresenter, ItemVirtualizationMode>( |
ItemsControl.ItemsPanelProperty.AddOwner<ItemsPresenter>(); |
||||
nameof(VirtualizationMode), |
|
||||
defaultValue: ItemVirtualizationMode.None); |
|
||||
|
|
||||
private bool _canHorizontallyScroll; |
private ItemsPresenterContainerGenerator? _generator; |
||||
private bool _canVerticallyScroll; |
|
||||
private EventHandler? _scrollInvalidated; |
|
||||
|
|
||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||
static ItemsPresenter() |
public ITemplate<Panel> ItemsPanel |
||||
{ |
{ |
||||
KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue( |
get => GetValue(ItemsPanelProperty); |
||||
typeof(ItemsPresenter), |
set => SetValue(ItemsPanelProperty, value); |
||||
KeyboardNavigationMode.Once); |
|
||||
|
|
||||
VirtualizationModeProperty.Changed |
|
||||
.AddClassHandler<ItemsPresenter>((x, e) => x.VirtualizationModeChanged(e)); |
|
||||
} |
} |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Gets or sets the virtualization mode for the items.
|
/// Gets the panel used to display the items.
|
||||
/// </summary>
|
/// </summary>
|
||||
public ItemVirtualizationMode VirtualizationMode |
public Panel? Panel { get; private set; } |
||||
{ |
|
||||
get { return GetValue(VirtualizationModeProperty); } |
|
||||
set { SetValue(VirtualizationModeProperty, value); } |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Gets or sets a value indicating whether the content can be scrolled horizontally.
|
/// Gets the owner <see cref="ItemsControl"/>.
|
||||
/// </summary>
|
/// </summary>
|
||||
bool ILogicalScrollable.CanHorizontallyScroll |
internal ItemsControl? ItemsControl { get; private set; } |
||||
{ |
|
||||
get { return _canHorizontallyScroll; } |
|
||||
set |
|
||||
{ |
|
||||
_canHorizontallyScroll = value; |
|
||||
InvalidateMeasure(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
public override sealed void ApplyTemplate() |
||||
/// Gets or sets a value indicating whether the content can be scrolled horizontally.
|
|
||||
/// </summary>
|
|
||||
bool ILogicalScrollable.CanVerticallyScroll |
|
||||
{ |
{ |
||||
get { return _canVerticallyScroll; } |
if (Panel is null) |
||||
set |
|
||||
{ |
{ |
||||
_canVerticallyScroll = value; |
Panel = ItemsPanel.Build(); |
||||
InvalidateMeasure(); |
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/>
|
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) |
||||
Vector IScrollable.Offset |
|
||||
{ |
{ |
||||
get { return Virtualizer?.Offset ?? new Vector(); } |
base.OnPropertyChanged(change); |
||||
set |
|
||||
|
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/>
|
private void CreateGeneratorIfSimplePanel() |
||||
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) |
|
||||
{ |
{ |
||||
var scrollable = (ILogicalScrollable)this; |
if (ItemsControl is null || Panel is null || Panel is IVirtualizingPanel) |
||||
var maxX = Math.Max(scrollable.Extent.Width - scrollable.Viewport.Width, 0); |
return; |
||||
var maxY = Math.Max(scrollable.Extent.Height - scrollable.Viewport.Height, 0); |
|
||||
return new Vector(Clamp(value.X, 0, maxX), Clamp(value.Y, 0, maxY)); |
|
||||
} |
|
||||
|
|
||||
private void VirtualizationModeChanged(AvaloniaPropertyChangedEventArgs e) |
_generator?.Dispose(); |
||||
{ |
_generator = new(this); |
||||
Virtualizer?.Dispose(); |
|
||||
Virtualizer = ItemVirtualizer.Create(this); |
|
||||
_scrollInvalidated?.Invoke(this, EventArgs.Empty); |
|
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -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; |
||||
using System.Collections.Specialized; |
|
||||
using Avalonia.Controls.Primitives; |
|
||||
using Avalonia.Input; |
|
||||
using Avalonia.Layout; |
|
||||
|
|
||||
namespace Avalonia.Controls |
namespace Avalonia.Controls |
||||
{ |
{ |
||||
public class VirtualizingStackPanel : StackPanel, IVirtualizingPanel |
public class VirtualizingStackPanel : StackPanel |
||||
{ |
{ |
||||
private Size _availableSpace; |
/// <summary>
|
||||
private double _takenSpace; |
/// Defines the <see cref="VirtualizationMode"/> property.
|
||||
private int _canBeRemoved; |
/// </summary>
|
||||
private double _averageItemSize; |
public static readonly StyledProperty<ItemVirtualizationMode> VirtualizationModeProperty = |
||||
private int _averageCount; |
AvaloniaProperty.Register<VirtualizingStackPanel, ItemVirtualizationMode>( |
||||
private double _pixelOffset; |
nameof(VirtualizationMode)); |
||||
private double _crossAxisOffset; |
|
||||
private bool _forceRemeasure; |
|
||||
|
|
||||
bool IVirtualizingPanel.IsFull |
public ItemVirtualizationMode VirtualizationMode |
||||
{ |
{ |
||||
get |
get => GetValue(VirtualizationModeProperty); |
||||
{ |
set => SetValue(VirtualizationModeProperty, value); |
||||
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; |
|
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
File diff suppressed because it is too large
@ -1,374 +1,374 @@ |
|||||
using System; |
////using System;
|
||||
using System.Collections.Generic; |
////using System.Collections.Generic;
|
||||
using System.Linq; |
////using System.Linq;
|
||||
using Avalonia.Controls.Generators; |
////using Avalonia.Controls.Generators;
|
||||
using Avalonia.Controls.Presenters; |
////using Avalonia.Controls.Presenters;
|
||||
using Avalonia.Controls.Primitives; |
////using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Controls.Templates; |
////using Avalonia.Controls.Templates;
|
||||
using Avalonia.Layout; |
////using Avalonia.Layout;
|
||||
using Avalonia.Platform; |
////using Avalonia.Platform;
|
||||
using Avalonia.Rendering; |
////using Avalonia.Rendering;
|
||||
using Avalonia.UnitTests; |
////using Avalonia.UnitTests;
|
||||
using Avalonia.VisualTree; |
////using Avalonia.VisualTree;
|
||||
using Xunit; |
////using Xunit;
|
||||
|
|
||||
namespace Avalonia.Controls.UnitTests.Presenters |
////namespace Avalonia.Controls.UnitTests.Presenters
|
||||
{ |
////{
|
||||
public class ItemsPresenterTests_Virtualization |
//// public class ItemsPresenterTests_Virtualization
|
||||
{ |
//// {
|
||||
[Fact] |
//// [Fact]
|
||||
public void Should_Not_Create_Items_Before_Added_To_Visual_Tree() |
//// public void Should_Not_Create_Items_Before_Added_To_Visual_Tree()
|
||||
{ |
//// {
|
||||
var items = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToList(); |
//// var items = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToList();
|
||||
var target = new TestItemsPresenter(true) |
//// var target = new TestItemsPresenter(true)
|
||||
{ |
//// {
|
||||
Items = items, |
//// Items = items,
|
||||
ItemsPanel = VirtualizingPanelTemplate(Orientation.Vertical), |
//// ItemsPanel = VirtualizingPanelTemplate(Orientation.Vertical),
|
||||
ItemTemplate = ItemTemplate(), |
//// ItemTemplate = ItemTemplate(),
|
||||
VirtualizationMode = ItemVirtualizationMode.Simple, |
//// VirtualizationMode = ItemVirtualizationMode.Simple,
|
||||
}; |
//// };
|
||||
|
|
||||
var scroller = new ScrollContentPresenter |
//// var scroller = new ScrollContentPresenter
|
||||
{ |
//// {
|
||||
Content = target, |
//// Content = target,
|
||||
}; |
//// };
|
||||
|
|
||||
scroller.UpdateChild(); |
//// scroller.UpdateChild();
|
||||
target.ApplyTemplate(); |
//// target.ApplyTemplate();
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
target.Arrange(new Rect(0, 0, 100, 100)); |
//// target.Arrange(new Rect(0, 0, 100, 100));
|
||||
|
|
||||
Assert.Empty(target.Panel.Children); |
//// Assert.Empty(target.Panel.Children);
|
||||
|
|
||||
var root = new TestRoot |
//// var root = new TestRoot
|
||||
{ |
//// {
|
||||
Child = scroller, |
//// Child = scroller,
|
||||
}; |
//// };
|
||||
|
|
||||
target.InvalidateMeasure(); |
//// target.InvalidateMeasure();
|
||||
target.Panel.InvalidateMeasure(); |
//// target.Panel.InvalidateMeasure();
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
target.Arrange(new Rect(0, 0, 100, 100)); |
//// target.Arrange(new Rect(0, 0, 100, 100));
|
||||
|
|
||||
Assert.Equal(10, target.Panel.Children.Count); |
//// Assert.Equal(10, target.Panel.Children.Count);
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Should_Return_IsLogicalScrollEnabled_False_When_Has_No_Virtualizing_Panel() |
//// public void Should_Return_IsLogicalScrollEnabled_False_When_Has_No_Virtualizing_Panel()
|
||||
{ |
//// {
|
||||
var target = CreateTarget(); |
//// var target = CreateTarget();
|
||||
target.ClearValue(ItemsPresenter.ItemsPanelProperty); |
//// target.ClearValue(ItemsPresenter.ItemsPanelProperty);
|
||||
|
|
||||
target.ApplyTemplate(); |
//// target.ApplyTemplate();
|
||||
|
|
||||
Assert.False(((ILogicalScrollable)target).IsLogicalScrollEnabled); |
//// Assert.False(((ILogicalScrollable)target).IsLogicalScrollEnabled);
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Should_Return_IsLogicalScrollEnabled_False_When_VirtualizationMode_None() |
//// public void Should_Return_IsLogicalScrollEnabled_False_When_VirtualizationMode_None()
|
||||
{ |
//// {
|
||||
var target = CreateTarget(ItemVirtualizationMode.None); |
//// var target = CreateTarget(ItemVirtualizationMode.None);
|
||||
|
|
||||
target.ApplyTemplate(); |
//// target.ApplyTemplate();
|
||||
|
|
||||
Assert.False(((ILogicalScrollable)target).IsLogicalScrollEnabled); |
//// Assert.False(((ILogicalScrollable)target).IsLogicalScrollEnabled);
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Should_Return_IsLogicalScrollEnabled_False_When_Doesnt_Have_ScrollPresenter_Parent() |
//// public void Should_Return_IsLogicalScrollEnabled_False_When_Doesnt_Have_ScrollPresenter_Parent()
|
||||
{ |
//// {
|
||||
var target = new ItemsPresenter |
//// var target = new ItemsPresenter
|
||||
{ |
//// {
|
||||
ItemsPanel = VirtualizingPanelTemplate(), |
//// ItemsPanel = VirtualizingPanelTemplate(),
|
||||
ItemTemplate = ItemTemplate(), |
//// ItemTemplate = ItemTemplate(),
|
||||
VirtualizationMode = ItemVirtualizationMode.Simple, |
//// VirtualizationMode = ItemVirtualizationMode.Simple,
|
||||
}; |
//// };
|
||||
|
|
||||
target.ApplyTemplate(); |
//// target.ApplyTemplate();
|
||||
|
|
||||
Assert.False(((ILogicalScrollable)target).IsLogicalScrollEnabled); |
//// Assert.False(((ILogicalScrollable)target).IsLogicalScrollEnabled);
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Should_Return_IsLogicalScrollEnabled_True() |
//// public void Should_Return_IsLogicalScrollEnabled_True()
|
||||
{ |
//// {
|
||||
var target = CreateTarget(); |
//// var target = CreateTarget();
|
||||
|
|
||||
target.ApplyTemplate(); |
//// target.ApplyTemplate();
|
||||
|
|
||||
Assert.True(((ILogicalScrollable)target).IsLogicalScrollEnabled); |
//// Assert.True(((ILogicalScrollable)target).IsLogicalScrollEnabled);
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Parent_ScrollContentPresenter_Properties_Should_Be_Set() |
//// public void Parent_ScrollContentPresenter_Properties_Should_Be_Set()
|
||||
{ |
//// {
|
||||
var target = CreateTarget(); |
//// var target = CreateTarget();
|
||||
|
|
||||
target.ApplyTemplate(); |
//// target.ApplyTemplate();
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
target.Arrange(new Rect(0, 0, 100, 100)); |
//// target.Arrange(new Rect(0, 0, 100, 100));
|
||||
|
|
||||
var scroll = (ScrollContentPresenter)target.Parent; |
//// var scroll = (ScrollContentPresenter)target.Parent;
|
||||
Assert.Equal(new Size(10, 20), scroll.Extent); |
//// Assert.Equal(new Size(10, 20), scroll.Extent);
|
||||
Assert.Equal(new Size(100, 10), scroll.Viewport); |
//// Assert.Equal(new Size(100, 10), scroll.Viewport);
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Should_Fill_Panel_With_Containers() |
//// public void Should_Fill_Panel_With_Containers()
|
||||
{ |
//// {
|
||||
var target = CreateTarget(); |
//// var target = CreateTarget();
|
||||
|
|
||||
target.ApplyTemplate(); |
//// target.ApplyTemplate();
|
||||
|
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
Assert.Equal(10, target.Panel.Children.Count); |
//// Assert.Equal(10, target.Panel.Children.Count);
|
||||
|
|
||||
target.Arrange(new Rect(0, 0, 100, 100)); |
//// target.Arrange(new Rect(0, 0, 100, 100));
|
||||
Assert.Equal(10, target.Panel.Children.Count); |
//// Assert.Equal(10, target.Panel.Children.Count);
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Should_Only_Create_Enough_Containers_To_Display_All_Items() |
//// public void Should_Only_Create_Enough_Containers_To_Display_All_Items()
|
||||
{ |
//// {
|
||||
var target = CreateTarget(itemCount: 2); |
//// var target = CreateTarget(itemCount: 2);
|
||||
|
|
||||
target.ApplyTemplate(); |
//// target.ApplyTemplate();
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
target.Arrange(new Rect(0, 0, 100, 100)); |
//// target.Arrange(new Rect(0, 0, 100, 100));
|
||||
|
|
||||
Assert.Equal(2, target.Panel.Children.Count); |
//// Assert.Equal(2, target.Panel.Children.Count);
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Should_Expand_To_Fit_Containers_When_Flexible_Size() |
//// public void Should_Expand_To_Fit_Containers_When_Flexible_Size()
|
||||
{ |
//// {
|
||||
var target = CreateTarget(); |
//// var target = CreateTarget();
|
||||
|
|
||||
target.ApplyTemplate(); |
//// target.ApplyTemplate();
|
||||
target.Measure(Size.Infinity); |
//// target.Measure(Size.Infinity);
|
||||
target.Arrange(new Rect(target.DesiredSize)); |
//// target.Arrange(new Rect(target.DesiredSize));
|
||||
|
|
||||
Assert.Equal(new Size(10, 200), target.DesiredSize); |
//// Assert.Equal(new Size(10, 200), target.DesiredSize);
|
||||
Assert.Equal(new Size(10, 200), target.Bounds.Size); |
//// Assert.Equal(new Size(10, 200), target.Bounds.Size);
|
||||
Assert.Equal(20, target.Panel.Children.Count); |
//// Assert.Equal(20, target.Panel.Children.Count);
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Initial_Item_DataContexts_Should_Be_Correct() |
//// public void Initial_Item_DataContexts_Should_Be_Correct()
|
||||
{ |
//// {
|
||||
var target = CreateTarget(); |
//// var target = CreateTarget();
|
||||
var items = (IList<string>)target.Items; |
//// var items = (IList<string>)target.Items;
|
||||
|
|
||||
target.ApplyTemplate(); |
//// target.ApplyTemplate();
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
target.Arrange(new Rect(0, 0, 100, 100)); |
//// target.Arrange(new Rect(0, 0, 100, 100));
|
||||
|
|
||||
for (var i = 0; i < target.Panel.Children.Count; ++i) |
//// for (var i = 0; i < target.Panel.Children.Count; ++i)
|
||||
{ |
//// {
|
||||
Assert.Equal(items[i], target.Panel.Children[i].DataContext); |
//// Assert.Equal(items[i], target.Panel.Children[i].DataContext);
|
||||
} |
//// }
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Should_Add_New_Items_When_Control_Is_Enlarged() |
//// public void Should_Add_New_Items_When_Control_Is_Enlarged()
|
||||
{ |
//// {
|
||||
var target = CreateTarget(); |
//// var target = CreateTarget();
|
||||
var items = (IList<string>)target.Items; |
//// var items = (IList<string>)target.Items;
|
||||
|
|
||||
target.ApplyTemplate(); |
//// target.ApplyTemplate();
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
target.Arrange(new Rect(0, 0, 100, 100)); |
//// target.Arrange(new Rect(0, 0, 100, 100));
|
||||
|
|
||||
Assert.Equal(10, target.Panel.Children.Count); |
//// Assert.Equal(10, target.Panel.Children.Count);
|
||||
|
|
||||
target.Measure(new Size(120, 120)); |
//// target.Measure(new Size(120, 120));
|
||||
target.Arrange(new Rect(0, 0, 100, 120)); |
//// target.Arrange(new Rect(0, 0, 100, 120));
|
||||
|
|
||||
Assert.Equal(12, target.Panel.Children.Count); |
//// Assert.Equal(12, target.Panel.Children.Count);
|
||||
|
|
||||
for (var i = 0; i < target.Panel.Children.Count; ++i) |
//// for (var i = 0; i < target.Panel.Children.Count; ++i)
|
||||
{ |
//// {
|
||||
Assert.Equal(items[i], target.Panel.Children[i].DataContext); |
//// Assert.Equal(items[i], target.Panel.Children[i].DataContext);
|
||||
} |
//// }
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Should_Not_Create_Virtualizer_Before_Panel() |
//// public void Should_Not_Create_Virtualizer_Before_Panel()
|
||||
{ |
//// {
|
||||
var target = CreateTarget(); |
//// var target = CreateTarget();
|
||||
|
|
||||
Assert.Null(target.Panel); |
//// Assert.Null(target.Panel);
|
||||
Assert.Null(target.Virtualizer); |
//// Assert.Null(target.Virtualizer);
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Changing_VirtualizationMode_None_To_Simple_Should_Update_Control() |
//// public void Changing_VirtualizationMode_None_To_Simple_Should_Update_Control()
|
||||
{ |
//// {
|
||||
var target = CreateTarget(mode: ItemVirtualizationMode.None); |
//// var target = CreateTarget(mode: ItemVirtualizationMode.None);
|
||||
var scroll = (ScrollContentPresenter)target.Parent; |
//// var scroll = (ScrollContentPresenter)target.Parent;
|
||||
|
|
||||
scroll.Measure(new Size(100, 100)); |
//// scroll.Measure(new Size(100, 100));
|
||||
scroll.Arrange(new Rect(0, 0, 100, 100)); |
//// scroll.Arrange(new Rect(0, 0, 100, 100));
|
||||
|
|
||||
Assert.Equal(20, target.Panel.Children.Count); |
//// Assert.Equal(20, target.Panel.Children.Count);
|
||||
Assert.Equal(new Size(100, 200), scroll.Extent); |
//// Assert.Equal(new Size(100, 200), scroll.Extent);
|
||||
Assert.Equal(new Size(100, 100), scroll.Viewport); |
//// Assert.Equal(new Size(100, 100), scroll.Viewport);
|
||||
|
|
||||
target.VirtualizationMode = ItemVirtualizationMode.Simple; |
//// target.VirtualizationMode = ItemVirtualizationMode.Simple;
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
target.Arrange(new Rect(0, 0, 100, 100)); |
//// target.Arrange(new Rect(0, 0, 100, 100));
|
||||
|
|
||||
Assert.Equal(10, target.Panel.Children.Count); |
//// Assert.Equal(10, target.Panel.Children.Count);
|
||||
Assert.Equal(new Size(10, 20), scroll.Extent); |
//// Assert.Equal(new Size(10, 20), scroll.Extent);
|
||||
Assert.Equal(new Size(100, 10), scroll.Viewport); |
//// Assert.Equal(new Size(100, 10), scroll.Viewport);
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Changing_VirtualizationMode_None_To_Simple_Should_Add_Correct_Number_Of_Controls() |
//// public void Changing_VirtualizationMode_None_To_Simple_Should_Add_Correct_Number_Of_Controls()
|
||||
{ |
//// {
|
||||
using (UnitTestApplication.Start(new TestServices())) |
//// using (UnitTestApplication.Start(new TestServices()))
|
||||
{ |
//// {
|
||||
var target = CreateTarget(mode: ItemVirtualizationMode.None); |
//// var target = CreateTarget(mode: ItemVirtualizationMode.None);
|
||||
var scroll = (TestScroller)target.Parent; |
//// var scroll = (TestScroller)target.Parent;
|
||||
|
|
||||
scroll.Width = scroll.Height = 100; |
//// scroll.Width = scroll.Height = 100;
|
||||
scroll.LayoutManager.ExecuteInitialLayoutPass(); |
//// scroll.LayoutManager.ExecuteInitialLayoutPass();
|
||||
|
|
||||
// Ensure than an intermediate measure pass doesn't add more controls than it
|
//// // 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
|
//// // should. This can happen if target gets measured with Size.Infinity which
|
||||
// is what the available size should be when VirtualizationMode == None but not
|
//// // is what the available size should be when VirtualizationMode == None but not
|
||||
// what it should after VirtualizationMode is changed to Simple.
|
//// // what it should after VirtualizationMode is changed to Simple.
|
||||
target.Panel.Children.CollectionChanged += (s, e) => |
//// target.Panel.Children.CollectionChanged += (s, e) =>
|
||||
{ |
//// {
|
||||
Assert.InRange(target.Panel.Children.Count, 0, 10); |
//// Assert.InRange(target.Panel.Children.Count, 0, 10);
|
||||
}; |
//// };
|
||||
|
|
||||
target.VirtualizationMode = ItemVirtualizationMode.Simple; |
//// target.VirtualizationMode = ItemVirtualizationMode.Simple;
|
||||
((ILayoutRoot)scroll.GetVisualRoot()).LayoutManager.ExecuteLayoutPass(); |
//// ((ILayoutRoot)scroll.GetVisualRoot()).LayoutManager.ExecuteLayoutPass();
|
||||
|
|
||||
Assert.Equal(10, target.Panel.Children.Count); |
//// Assert.Equal(10, target.Panel.Children.Count);
|
||||
} |
//// }
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Changing_VirtualizationMode_Simple_To_None_Should_Update_Control() |
//// public void Changing_VirtualizationMode_Simple_To_None_Should_Update_Control()
|
||||
{ |
//// {
|
||||
var target = CreateTarget(); |
//// var target = CreateTarget();
|
||||
var scroll = (ScrollContentPresenter)target.Parent; |
//// var scroll = (ScrollContentPresenter)target.Parent;
|
||||
|
|
||||
scroll.Measure(new Size(100, 100)); |
//// scroll.Measure(new Size(100, 100));
|
||||
scroll.Arrange(new Rect(0, 0, 100, 100)); |
//// scroll.Arrange(new Rect(0, 0, 100, 100));
|
||||
|
|
||||
Assert.Equal(10, target.Panel.Children.Count); |
//// Assert.Equal(10, target.Panel.Children.Count);
|
||||
Assert.Equal(new Size(10, 20), scroll.Extent); |
//// Assert.Equal(new Size(10, 20), scroll.Extent);
|
||||
Assert.Equal(new Size(100, 10), scroll.Viewport); |
//// Assert.Equal(new Size(100, 10), scroll.Viewport);
|
||||
|
|
||||
target.VirtualizationMode = ItemVirtualizationMode.None; |
//// target.VirtualizationMode = ItemVirtualizationMode.None;
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
target.Arrange(new Rect(0, 0, 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
|
//// // 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.
|
//// // content presenter as non-logical scroll values are only updated on arrange.
|
||||
scroll.Measure(new Size(100, 100)); |
//// scroll.Measure(new Size(100, 100));
|
||||
scroll.Arrange(new Rect(0, 0, 100, 100)); |
//// scroll.Arrange(new Rect(0, 0, 100, 100));
|
||||
|
|
||||
Assert.Equal(20, target.Panel.Children.Count); |
//// Assert.Equal(20, target.Panel.Children.Count);
|
||||
Assert.Equal(new Size(100, 200), scroll.Extent); |
//// Assert.Equal(new Size(100, 200), scroll.Extent);
|
||||
Assert.Equal(new Size(100, 100), scroll.Viewport); |
//// Assert.Equal(new Size(100, 100), scroll.Viewport);
|
||||
} |
//// }
|
||||
|
|
||||
private static ItemsPresenter CreateTarget( |
//// private static ItemsPresenter CreateTarget(
|
||||
ItemVirtualizationMode mode = ItemVirtualizationMode.Simple, |
//// ItemVirtualizationMode mode = ItemVirtualizationMode.Simple,
|
||||
Orientation orientation = Orientation.Vertical, |
//// Orientation orientation = Orientation.Vertical,
|
||||
bool useContainers = true, |
//// bool useContainers = true,
|
||||
int itemCount = 20) |
//// int itemCount = 20)
|
||||
{ |
//// {
|
||||
ItemsPresenter result; |
//// ItemsPresenter result;
|
||||
var items = Enumerable.Range(0, itemCount).Select(x => $"Item {x}").ToList(); |
//// var items = Enumerable.Range(0, itemCount).Select(x => $"Item {x}").ToList();
|
||||
|
|
||||
var scroller = new TestScroller |
//// var scroller = new TestScroller
|
||||
{ |
//// {
|
||||
CanHorizontallyScroll = false, |
//// CanHorizontallyScroll = false,
|
||||
CanVerticallyScroll = true, |
//// CanVerticallyScroll = true,
|
||||
Content = result = new TestItemsPresenter(useContainers) |
//// Content = result = new TestItemsPresenter(useContainers)
|
||||
{ |
//// {
|
||||
Items = items, |
//// Items = items,
|
||||
ItemsPanel = VirtualizingPanelTemplate(orientation), |
//// ItemsPanel = VirtualizingPanelTemplate(orientation),
|
||||
ItemTemplate = ItemTemplate(), |
//// ItemTemplate = ItemTemplate(),
|
||||
VirtualizationMode = mode, |
//// VirtualizationMode = mode,
|
||||
} |
//// }
|
||||
}; |
//// };
|
||||
|
|
||||
scroller.UpdateChild(); |
//// scroller.UpdateChild();
|
||||
|
|
||||
return result; |
//// return result;
|
||||
} |
//// }
|
||||
|
|
||||
private static IDataTemplate ItemTemplate() |
//// private static IDataTemplate ItemTemplate()
|
||||
{ |
//// {
|
||||
return new FuncDataTemplate<string>((x, _) => new Canvas |
//// return new FuncDataTemplate<string>((x, _) => new Canvas
|
||||
{ |
//// {
|
||||
Width = 10, |
//// Width = 10,
|
||||
Height = 10, |
//// Height = 10,
|
||||
}); |
//// });
|
||||
} |
//// }
|
||||
|
|
||||
private static ITemplate<Panel> VirtualizingPanelTemplate( |
//// private static ITemplate<Panel> VirtualizingPanelTemplate(
|
||||
Orientation orientation = Orientation.Vertical) |
//// Orientation orientation = Orientation.Vertical)
|
||||
{ |
//// {
|
||||
return new FuncTemplate<Panel>(() => new VirtualizingStackPanel |
//// return new FuncTemplate<Panel>(() => new VirtualizingStackPanel
|
||||
{ |
//// {
|
||||
Orientation = orientation, |
//// Orientation = orientation,
|
||||
}); |
//// });
|
||||
} |
//// }
|
||||
|
|
||||
private class TestScroller : ScrollContentPresenter, IRenderRoot, ILayoutRoot |
//// private class TestScroller : ScrollContentPresenter, IRenderRoot, ILayoutRoot
|
||||
{ |
//// {
|
||||
public TestScroller() |
//// public TestScroller()
|
||||
{ |
//// {
|
||||
LayoutManager = new LayoutManager(this); |
//// LayoutManager = new LayoutManager(this);
|
||||
} |
//// }
|
||||
|
|
||||
public IRenderer Renderer { get; } |
//// public IRenderer Renderer { get; }
|
||||
public Size ClientSize { get; } |
//// public Size ClientSize { get; }
|
||||
public double RenderScaling => 1; |
//// public double RenderScaling => 1;
|
||||
|
|
||||
public Size MaxClientSize => Size.Infinity; |
//// public Size MaxClientSize => Size.Infinity;
|
||||
|
|
||||
public double LayoutScaling => 1; |
//// public double LayoutScaling => 1;
|
||||
|
|
||||
public ILayoutManager LayoutManager { get; } |
//// public ILayoutManager LayoutManager { get; }
|
||||
|
|
||||
public IRenderTarget CreateRenderTarget() => throw new NotImplementedException(); |
//// public IRenderTarget CreateRenderTarget() => throw new NotImplementedException();
|
||||
public void Invalidate(Rect rect) => throw new NotImplementedException(); |
//// public void Invalidate(Rect rect) => throw new NotImplementedException();
|
||||
public Point PointToClient(PixelPoint p) => throw new NotImplementedException(); |
//// public Point PointToClient(PixelPoint p) => throw new NotImplementedException();
|
||||
public PixelPoint PointToScreen(Point p) => throw new NotImplementedException(); |
//// public PixelPoint PointToScreen(Point p) => throw new NotImplementedException();
|
||||
} |
//// }
|
||||
|
|
||||
private class TestItemsPresenter : ItemsPresenter |
//// private class TestItemsPresenter : ItemsPresenter
|
||||
{ |
//// {
|
||||
private bool _useContainers; |
//// private bool _useContainers;
|
||||
|
|
||||
public TestItemsPresenter(bool useContainers) |
//// public TestItemsPresenter(bool useContainers)
|
||||
{ |
//// {
|
||||
_useContainers = useContainers; |
//// _useContainers = useContainers;
|
||||
} |
//// }
|
||||
|
|
||||
protected override IItemContainerGenerator CreateItemContainerGenerator() |
//// protected override IItemContainerGenerator CreateItemContainerGenerator()
|
||||
{ |
//// {
|
||||
return _useContainers ? |
//// return _useContainers ?
|
||||
new ItemContainerGenerator<TestContainer>(this, TestContainer.ContentProperty, null) : |
//// new ItemContainerGenerator<TestContainer>(this, TestContainer.ContentProperty, null) :
|
||||
new ItemContainerGenerator(this); |
//// new ItemContainerGenerator(this);
|
||||
} |
//// }
|
||||
} |
//// }
|
||||
|
|
||||
private class TestContainer : ContentControl |
//// private class TestContainer : ContentControl
|
||||
{ |
//// {
|
||||
public TestContainer() |
//// public TestContainer()
|
||||
{ |
//// {
|
||||
Width = 10; |
//// Width = 10;
|
||||
Height = 10; |
//// Height = 10;
|
||||
} |
//// }
|
||||
} |
//// }
|
||||
} |
//// }
|
||||
} |
////}
|
||||
|
|||||
File diff suppressed because it is too large
@ -1,261 +1,261 @@ |
|||||
using System; |
////using System;
|
||||
using System.Collections.Generic; |
////using System.Collections.Generic;
|
||||
using Avalonia.Controls.Primitives; |
////using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input; |
////using Avalonia.Input;
|
||||
using Avalonia.LogicalTree; |
////using Avalonia.LogicalTree;
|
||||
using Moq; |
////using Moq;
|
||||
using Xunit; |
////using Xunit;
|
||||
|
|
||||
namespace Avalonia.Controls.UnitTests |
////namespace Avalonia.Controls.UnitTests
|
||||
{ |
////{
|
||||
public class VirtualizingStackPanelTests |
//// public class VirtualizingStackPanelTests
|
||||
{ |
//// {
|
||||
public class Vertical |
//// public class Vertical
|
||||
{ |
//// {
|
||||
[Fact] |
//// [Fact]
|
||||
public void Measure_Invokes_Controller_UpdateControls() |
//// public void Measure_Invokes_Controller_UpdateControls()
|
||||
{ |
//// {
|
||||
var target = new VirtualizingStackPanel(); |
//// var target = new VirtualizingStackPanel();
|
||||
var controller = new Mock<IVirtualizingController>(); |
//// var controller = new Mock<IVirtualizingController>();
|
||||
|
|
||||
((IVirtualizingPanel)target).Controller = controller.Object; |
//// ((IVirtualizingPanel)target).Controller = controller.Object;
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
|
|
||||
controller.Verify(x => x.UpdateControls(), Times.Once()); |
//// controller.Verify(x => x.UpdateControls(), Times.Once());
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Measure_Invokes_Controller_UpdateControls_If_AvailableSize_Changes() |
//// public void Measure_Invokes_Controller_UpdateControls_If_AvailableSize_Changes()
|
||||
{ |
//// {
|
||||
var target = new VirtualizingStackPanel(); |
//// var target = new VirtualizingStackPanel();
|
||||
var controller = new Mock<IVirtualizingController>(); |
//// var controller = new Mock<IVirtualizingController>();
|
||||
|
|
||||
((IVirtualizingPanel)target).Controller = controller.Object; |
//// ((IVirtualizingPanel)target).Controller = controller.Object;
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
target.InvalidateMeasure(); |
//// target.InvalidateMeasure();
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
target.InvalidateMeasure(); |
//// target.InvalidateMeasure();
|
||||
target.Measure(new Size(100, 101)); |
//// target.Measure(new Size(100, 101));
|
||||
|
|
||||
controller.Verify(x => x.UpdateControls(), Times.Exactly(2)); |
//// controller.Verify(x => x.UpdateControls(), Times.Exactly(2));
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Measure_Does_Not_Invoke_Controller_UpdateControls_If_AvailableSize_Is_The_Same() |
//// public void Measure_Does_Not_Invoke_Controller_UpdateControls_If_AvailableSize_Is_The_Same()
|
||||
{ |
//// {
|
||||
var target = new VirtualizingStackPanel(); |
//// var target = new VirtualizingStackPanel();
|
||||
var controller = new Mock<IVirtualizingController>(); |
//// var controller = new Mock<IVirtualizingController>();
|
||||
|
|
||||
((IVirtualizingPanel)target).Controller = controller.Object; |
//// ((IVirtualizingPanel)target).Controller = controller.Object;
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
target.InvalidateMeasure(); |
//// target.InvalidateMeasure();
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
|
|
||||
controller.Verify(x => x.UpdateControls(), Times.Once()); |
//// controller.Verify(x => x.UpdateControls(), Times.Once());
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Measure_Invokes_Controller_UpdateControls_If_AvailableSize_Is_The_Same_After_ForceInvalidateMeasure() |
//// public void Measure_Invokes_Controller_UpdateControls_If_AvailableSize_Is_The_Same_After_ForceInvalidateMeasure()
|
||||
{ |
//// {
|
||||
var target = new VirtualizingStackPanel(); |
//// var target = new VirtualizingStackPanel();
|
||||
var controller = new Mock<IVirtualizingController>(); |
//// var controller = new Mock<IVirtualizingController>();
|
||||
|
|
||||
((IVirtualizingPanel)target).Controller = controller.Object; |
//// ((IVirtualizingPanel)target).Controller = controller.Object;
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
((IVirtualizingPanel)target).ForceInvalidateMeasure(); |
//// ((IVirtualizingPanel)target).ForceInvalidateMeasure();
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
|
|
||||
controller.Verify(x => x.UpdateControls(), Times.Exactly(2)); |
//// controller.Verify(x => x.UpdateControls(), Times.Exactly(2));
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Arrange_Invokes_Controller_UpdateControls() |
//// public void Arrange_Invokes_Controller_UpdateControls()
|
||||
{ |
//// {
|
||||
var target = new VirtualizingStackPanel(); |
//// var target = new VirtualizingStackPanel();
|
||||
var controller = new Mock<IVirtualizingController>(); |
//// var controller = new Mock<IVirtualizingController>();
|
||||
|
|
||||
((IVirtualizingPanel)target).Controller = controller.Object; |
//// ((IVirtualizingPanel)target).Controller = controller.Object;
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
target.Arrange(new Rect(0, 0, 110, 110)); |
//// target.Arrange(new Rect(0, 0, 110, 110));
|
||||
|
|
||||
controller.Verify(x => x.UpdateControls(), Times.Exactly(2)); |
//// controller.Verify(x => x.UpdateControls(), Times.Exactly(2));
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Reports_IsFull_False_Until_Measure_Height_Is_Reached() |
//// public void Reports_IsFull_False_Until_Measure_Height_Is_Reached()
|
||||
{ |
//// {
|
||||
var target = new VirtualizingStackPanel(); |
//// var target = new VirtualizingStackPanel();
|
||||
var vp = (IVirtualizingPanel)target; |
//// var vp = (IVirtualizingPanel)target;
|
||||
|
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
|
|
||||
Assert.Equal(new Size(0, 0), target.DesiredSize); |
//// Assert.Equal(new Size(0, 0), target.DesiredSize);
|
||||
Assert.Equal(new Size(0, 0), target.Bounds.Size); |
//// Assert.Equal(new Size(0, 0), target.Bounds.Size);
|
||||
|
|
||||
Assert.False(vp.IsFull); |
//// Assert.False(vp.IsFull);
|
||||
Assert.Equal(0, vp.OverflowCount); |
//// Assert.Equal(0, vp.OverflowCount);
|
||||
target.Children.Add(new Canvas { Width = 50, Height = 50 }); |
//// target.Children.Add(new Canvas { Width = 50, Height = 50 });
|
||||
Assert.False(vp.IsFull); |
//// Assert.False(vp.IsFull);
|
||||
Assert.Equal(0, vp.OverflowCount); |
//// Assert.Equal(0, vp.OverflowCount);
|
||||
target.Children.Add(new Canvas { Width = 50, Height = 50 }); |
//// target.Children.Add(new Canvas { Width = 50, Height = 50 });
|
||||
Assert.True(vp.IsFull); |
//// Assert.True(vp.IsFull);
|
||||
Assert.Equal(0, vp.OverflowCount); |
//// Assert.Equal(0, vp.OverflowCount);
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Reports_Overflow_After_Arrange() |
//// public void Reports_Overflow_After_Arrange()
|
||||
{ |
//// {
|
||||
var target = new VirtualizingStackPanel(); |
//// var target = new VirtualizingStackPanel();
|
||||
var vp = (IVirtualizingPanel)target; |
//// var vp = (IVirtualizingPanel)target;
|
||||
|
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
target.Arrange(new Rect(target.DesiredSize)); |
//// target.Arrange(new Rect(target.DesiredSize));
|
||||
|
|
||||
Assert.Equal(new Size(0, 0), target.Bounds.Size); |
//// 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 });
|
||||
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); |
//// Assert.Equal(0, vp.OverflowCount);
|
||||
|
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
target.Arrange(new Rect(target.DesiredSize)); |
//// target.Arrange(new Rect(target.DesiredSize));
|
||||
|
|
||||
Assert.Equal(2, vp.OverflowCount); |
//// Assert.Equal(2, vp.OverflowCount);
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Reports_Correct_Overflow_During_Arrange() |
//// public void Reports_Correct_Overflow_During_Arrange()
|
||||
{ |
//// {
|
||||
var target = new VirtualizingStackPanel(); |
//// var target = new VirtualizingStackPanel();
|
||||
var vp = (IVirtualizingPanel)target; |
//// var vp = (IVirtualizingPanel)target;
|
||||
var controller = new Mock<IVirtualizingController>(); |
//// var controller = new Mock<IVirtualizingController>();
|
||||
var called = false; |
//// var called = false;
|
||||
|
|
||||
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 = 52 }); |
//// target.Children.Add(new Canvas { Width = 50, Height = 52 });
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
|
|
||||
controller.Setup(x => x.UpdateControls()).Callback(() => |
//// controller.Setup(x => x.UpdateControls()).Callback(() =>
|
||||
{ |
//// {
|
||||
Assert.Equal(2, vp.PixelOverflow); |
//// Assert.Equal(2, vp.PixelOverflow);
|
||||
Assert.Equal(0, vp.OverflowCount); |
//// Assert.Equal(0, vp.OverflowCount);
|
||||
called = true; |
//// called = true;
|
||||
}); |
//// });
|
||||
|
|
||||
vp.Controller = controller.Object; |
//// vp.Controller = controller.Object;
|
||||
target.Arrange(new Rect(target.DesiredSize)); |
//// target.Arrange(new Rect(target.DesiredSize));
|
||||
|
|
||||
Assert.True(called); |
//// Assert.True(called);
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Reports_PixelOverflow_After_Arrange() |
//// public void Reports_PixelOverflow_After_Arrange()
|
||||
{ |
//// {
|
||||
var target = new VirtualizingStackPanel(); |
//// var target = new VirtualizingStackPanel();
|
||||
|
|
||||
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 = 52 }); |
//// target.Children.Add(new Canvas { Width = 50, Height = 52 });
|
||||
|
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
target.Arrange(new Rect(target.DesiredSize)); |
//// target.Arrange(new Rect(target.DesiredSize));
|
||||
|
|
||||
Assert.Equal(2, ((IVirtualizingPanel)target).PixelOverflow); |
//// Assert.Equal(2, ((IVirtualizingPanel)target).PixelOverflow);
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Reports_PixelOverflow_After_Arrange_Smaller_Than_Measure() |
//// public void Reports_PixelOverflow_After_Arrange_Smaller_Than_Measure()
|
||||
{ |
//// {
|
||||
var target = new VirtualizingStackPanel(); |
//// var target = new VirtualizingStackPanel();
|
||||
|
|
||||
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 = 52 }); |
//// target.Children.Add(new Canvas { Width = 50, Height = 52 });
|
||||
|
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
target.Arrange(new Rect(0, 0, 50, 50)); |
//// target.Arrange(new Rect(0, 0, 50, 50));
|
||||
|
|
||||
Assert.Equal(52, ((IVirtualizingPanel)target).PixelOverflow); |
//// Assert.Equal(52, ((IVirtualizingPanel)target).PixelOverflow);
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Reports_PixelOverflow_With_PixelOffset() |
//// public void Reports_PixelOverflow_With_PixelOffset()
|
||||
{ |
//// {
|
||||
var target = new VirtualizingStackPanel(); |
//// var target = new VirtualizingStackPanel();
|
||||
var vp = (IVirtualizingPanel)target; |
//// var vp = (IVirtualizingPanel)target;
|
||||
|
|
||||
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 = 52 }); |
//// target.Children.Add(new Canvas { Width = 50, Height = 52 });
|
||||
vp.PixelOffset = 2; |
//// vp.PixelOffset = 2;
|
||||
|
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
target.Arrange(new Rect(target.DesiredSize)); |
//// target.Arrange(new Rect(target.DesiredSize));
|
||||
|
|
||||
Assert.Equal(2, vp.PixelOverflow); |
//// Assert.Equal(2, vp.PixelOverflow);
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void PixelOffset_Can_Be_More_Than_Child_Without_Affecting_IsFull() |
//// public void PixelOffset_Can_Be_More_Than_Child_Without_Affecting_IsFull()
|
||||
{ |
//// {
|
||||
var target = new VirtualizingStackPanel(); |
//// var target = new VirtualizingStackPanel();
|
||||
var vp = (IVirtualizingPanel)target; |
//// var vp = (IVirtualizingPanel)target;
|
||||
|
|
||||
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 = 52 }); |
//// target.Children.Add(new Canvas { Width = 50, Height = 52 });
|
||||
vp.PixelOffset = 55; |
//// vp.PixelOffset = 55;
|
||||
|
|
||||
target.Measure(new Size(100, 100)); |
//// target.Measure(new Size(100, 100));
|
||||
target.Arrange(new Rect(target.DesiredSize)); |
//// target.Arrange(new Rect(target.DesiredSize));
|
||||
|
|
||||
Assert.Equal(55, vp.PixelOffset); |
//// Assert.Equal(55, vp.PixelOffset);
|
||||
Assert.Equal(2, vp.PixelOverflow); |
//// Assert.Equal(2, vp.PixelOverflow);
|
||||
Assert.True(vp.IsFull); |
//// Assert.True(vp.IsFull);
|
||||
} |
//// }
|
||||
|
|
||||
[Fact] |
//// [Fact]
|
||||
public void Passes_Navigation_Request_To_ILogicalScrollable_Parent() |
//// public void Passes_Navigation_Request_To_ILogicalScrollable_Parent()
|
||||
{ |
//// {
|
||||
var target = new VirtualizingStackPanel(); |
//// var target = new VirtualizingStackPanel();
|
||||
var presenter = new TestPresenter { Child = target }; |
//// var presenter = new TestPresenter { Child = target };
|
||||
var from = new Canvas(); |
//// var from = new Canvas();
|
||||
|
|
||||
((INavigableContainer)target).GetControl(NavigationDirection.Next, from, false); |
//// ((INavigableContainer)target).GetControl(NavigationDirection.Next, from, false);
|
||||
|
|
||||
Assert.Equal(1, presenter.NavigationRequests.Count); |
//// Assert.Equal(1, presenter.NavigationRequests.Count);
|
||||
Assert.Equal((NavigationDirection.Next, from), presenter.NavigationRequests[0]); |
//// Assert.Equal((NavigationDirection.Next, from), presenter.NavigationRequests[0]);
|
||||
} |
//// }
|
||||
|
|
||||
private class TestPresenter : Decorator, ILogicalScrollable |
//// private class TestPresenter : Decorator, ILogicalScrollable
|
||||
{ |
//// {
|
||||
public bool CanHorizontallyScroll { get; set; } |
//// public bool CanHorizontallyScroll { get; set; }
|
||||
public bool CanVerticallyScroll { get; set; } |
//// public bool CanVerticallyScroll { get; set; }
|
||||
public bool IsLogicalScrollEnabled => true; |
//// public bool IsLogicalScrollEnabled => true;
|
||||
public Size ScrollSize { get; } |
//// public Size ScrollSize { get; }
|
||||
public Size PageScrollSize { get; } |
//// public Size PageScrollSize { get; }
|
||||
public Size Extent { get; } |
//// public Size Extent { get; }
|
||||
public Vector Offset { get; set; } |
//// public Vector Offset { get; set; }
|
||||
public Size Viewport { get; } |
//// public Size Viewport { get; }
|
||||
|
|
||||
public event EventHandler ScrollInvalidated; |
//// public event EventHandler ScrollInvalidated;
|
||||
|
|
||||
public List<(NavigationDirection, Control)> NavigationRequests { get; } = new(); |
//// public List<(NavigationDirection, Control)> NavigationRequests { get; } = new();
|
||||
|
|
||||
public bool BringIntoView(Control target, Rect targetRect) |
//// public bool BringIntoView(Control target, Rect targetRect)
|
||||
{ |
//// {
|
||||
throw new NotImplementedException(); |
//// throw new NotImplementedException();
|
||||
} |
//// }
|
||||
|
|
||||
public Control GetControlInDirection(NavigationDirection direction, Control from) |
//// public Control GetControlInDirection(NavigationDirection direction, Control from)
|
||||
{ |
//// {
|
||||
NavigationRequests.Add((direction, from)); |
//// NavigationRequests.Add((direction, from));
|
||||
return null; |
//// return null;
|
||||
} |
//// }
|
||||
|
|
||||
public void RaiseScrollInvalidated(EventArgs e) |
//// public void RaiseScrollInvalidated(EventArgs e)
|
||||
{ |
//// {
|
||||
throw new NotImplementedException(); |
//// throw new NotImplementedException();
|
||||
} |
//// }
|
||||
} |
//// }
|
||||
} |
//// }
|
||||
} |
//// }
|
||||
} |
////}
|
||||
|
|||||
Loading…
Reference in new issue