10 changed files with 368 additions and 208 deletions
@ -1,15 +1,38 @@ |
|||
using System; |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// A panel that can be used to virtualize items.
|
|||
/// </summary>
|
|||
public interface IVirtualizingPanel : IPanel |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a value indicating whether the panel is full.
|
|||
/// </summary>
|
|||
bool IsFull { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of items that can be removed while keeping the panel full.
|
|||
/// </summary>
|
|||
int OverflowCount { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the direction of scroll.
|
|||
/// </summary>
|
|||
Orientation ScrollDirection { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the average size of the materialized items in the direction of scroll.
|
|||
/// </summary>
|
|||
double AverageItemSize { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the current pixel offset of the items in the direction of scroll.
|
|||
/// </summary>
|
|||
double PixelOffset { get; set; } |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,52 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Specialized; |
|||
using Avalonia.Controls.Primitives; |
|||
|
|||
namespace Avalonia.Controls.Presenters |
|||
{ |
|||
internal abstract class ItemVirtualizer |
|||
{ |
|||
public ItemVirtualizer(ItemsPresenter owner) |
|||
{ |
|||
Owner = owner; |
|||
} |
|||
|
|||
public ItemsPresenter Owner { get; } |
|||
public IVirtualizingPanel VirtualizingPanel => Owner.Panel as IVirtualizingPanel; |
|||
public IEnumerable Items { get; private set; } |
|||
public int FirstIndex { get; set; } |
|||
public int LastIndex { get; set; } = -1; |
|||
|
|||
public abstract bool IsLogicalScrollEnabled { get; } |
|||
public abstract Size Extent { get; } |
|||
public abstract Size Viewport { get; } |
|||
|
|||
public static ItemVirtualizer Create(ItemsPresenter owner) |
|||
{ |
|||
var virtualizingPanel = owner.Panel as IVirtualizingPanel; |
|||
var scrollable = (IScrollable)owner; |
|||
|
|||
if (virtualizingPanel != null && scrollable.InvalidateScroll != null) |
|||
{ |
|||
switch (owner.VirtualizationMode) |
|||
{ |
|||
case ItemVirtualizationMode.Simple: |
|||
return new ItemVirtualizerSimple(owner); |
|||
} |
|||
} |
|||
|
|||
return new ItemVirtualizerNone(owner); |
|||
} |
|||
|
|||
public abstract void Arranging(Size finalSize); |
|||
|
|||
public virtual void ItemsChanged(IEnumerable items, NotifyCollectionChangedEventArgs e) |
|||
{ |
|||
Items = items; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,137 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Specialized; |
|||
using Avalonia.Controls.Generators; |
|||
using Avalonia.Controls.Utils; |
|||
|
|||
namespace Avalonia.Controls.Presenters |
|||
{ |
|||
internal class ItemVirtualizerNone : ItemVirtualizer |
|||
{ |
|||
public ItemVirtualizerNone(ItemsPresenter owner) |
|||
: base(owner) |
|||
{ |
|||
} |
|||
|
|||
public override bool IsLogicalScrollEnabled => false; |
|||
|
|||
public override Size Extent |
|||
{ |
|||
get |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
} |
|||
|
|||
public override Size Viewport |
|||
{ |
|||
get |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
} |
|||
|
|||
public override void Arranging(Size finalSize) |
|||
{ |
|||
// We don't need to do anything here.
|
|||
} |
|||
|
|||
public override void ItemsChanged(IEnumerable items, NotifyCollectionChangedEventArgs e) |
|||
{ |
|||
base.ItemsChanged(items, e); |
|||
|
|||
var generator = Owner.ItemContainerGenerator; |
|||
var panel = Owner.Panel; |
|||
|
|||
// TODO: Handle Move and Replace etc.
|
|||
switch (e.Action) |
|||
{ |
|||
case NotifyCollectionChangedAction.Add: |
|||
if (e.NewStartingIndex + e.NewItems.Count < Items.Count()) |
|||
{ |
|||
generator.InsertSpace(e.NewStartingIndex, e.NewItems.Count); |
|||
} |
|||
|
|||
AddContainers(e.NewStartingIndex, e.NewItems); |
|||
break; |
|||
|
|||
case NotifyCollectionChangedAction.Remove: |
|||
RemoveContainers(generator.RemoveRange(e.OldStartingIndex, e.OldItems.Count)); |
|||
break; |
|||
|
|||
case NotifyCollectionChangedAction.Replace: |
|||
RemoveContainers(generator.Dematerialize(e.OldStartingIndex, e.OldItems.Count)); |
|||
var containers = AddContainers(e.NewStartingIndex, e.NewItems); |
|||
|
|||
var i = e.NewStartingIndex; |
|||
|
|||
foreach (var container in containers) |
|||
{ |
|||
panel.Children[i++] = container.ContainerControl; |
|||
} |
|||
|
|||
break; |
|||
|
|||
case NotifyCollectionChangedAction.Move: |
|||
// TODO: Implement Move in a more efficient manner.
|
|||
case NotifyCollectionChangedAction.Reset: |
|||
RemoveContainers(generator.Clear()); |
|||
|
|||
if (Items != null) |
|||
{ |
|||
AddContainers(0, Items); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
Owner.InvalidateMeasure(); |
|||
} |
|||
|
|||
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, Owner.MemberSelector); |
|||
|
|||
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 void RemoveContainers(IEnumerable<ItemContainerInfo> items) |
|||
{ |
|||
var panel = Owner.Panel; |
|||
|
|||
foreach (var i in items) |
|||
{ |
|||
if (i.ContainerControl != null) |
|||
{ |
|||
panel.Children.Remove(i.ContainerControl); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,93 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using Avalonia.Controls.Utils; |
|||
|
|||
namespace Avalonia.Controls.Presenters |
|||
{ |
|||
internal class ItemVirtualizerSimple : ItemVirtualizer |
|||
{ |
|||
public ItemVirtualizerSimple(ItemsPresenter owner) |
|||
: base(owner) |
|||
{ |
|||
} |
|||
|
|||
public override bool IsLogicalScrollEnabled => true; |
|||
|
|||
public override Size Extent |
|||
{ |
|||
get |
|||
{ |
|||
if (VirtualizingPanel.ScrollDirection == Orientation.Vertical) |
|||
{ |
|||
return new Size(0, Items.Count()); |
|||
} |
|||
else |
|||
{ |
|||
return new Size(Items.Count(), 0); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public override Size Viewport |
|||
{ |
|||
get |
|||
{ |
|||
var panel = VirtualizingPanel; |
|||
|
|||
if (panel.ScrollDirection == Orientation.Vertical) |
|||
{ |
|||
return new Size(0, panel.Children.Count); |
|||
} |
|||
else |
|||
{ |
|||
return new Size(panel.Children.Count, 0); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public override void Arranging(Size finalSize) |
|||
{ |
|||
CreateRemoveContainers(); |
|||
} |
|||
|
|||
private void CreateRemoveContainers() |
|||
{ |
|||
var generator = Owner.ItemContainerGenerator; |
|||
var panel = VirtualizingPanel; |
|||
|
|||
if (!panel.IsFull) |
|||
{ |
|||
var index = LastIndex + 1; |
|||
var items = Items.Cast<object>().Skip(index); |
|||
var memberSelector = Owner.MemberSelector; |
|||
|
|||
foreach (var item in items) |
|||
{ |
|||
var materialized = generator.Materialize(index++, item, memberSelector); |
|||
panel.Children.Add(materialized.ContainerControl); |
|||
|
|||
if (panel.IsFull) |
|||
{ |
|||
break; |
|||
} |
|||
} |
|||
|
|||
LastIndex = index - 1; |
|||
} |
|||
|
|||
if (panel.OverflowCount > 0) |
|||
{ |
|||
var count = panel.OverflowCount; |
|||
var index = panel.Children.Count - count; |
|||
|
|||
panel.Children.RemoveRange(index, count); |
|||
generator.Dematerialize(index, count); |
|||
|
|||
LastIndex -= count; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue