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 |
namespace Avalonia.Controls |
||||
{ |
{ |
||||
|
/// <summary>
|
||||
|
/// A panel that can be used to virtualize items.
|
||||
|
/// </summary>
|
||||
public interface IVirtualizingPanel : IPanel |
public interface IVirtualizingPanel : IPanel |
||||
{ |
{ |
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether the panel is full.
|
||||
|
/// </summary>
|
||||
bool IsFull { get; } |
bool IsFull { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the number of items that can be removed while keeping the panel full.
|
||||
|
/// </summary>
|
||||
int OverflowCount { get; } |
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; } |
double AverageItemSize { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the current pixel offset of the items in the direction of scroll.
|
||||
|
/// </summary>
|
||||
double PixelOffset { get; set; } |
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