// This source file is adapted from the WinUI project.
// (https://github.com/microsoft/microsoft-ui-xaml)
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Controls.Utils;
#nullable enable
namespace Avalonia.Controls
{
///
/// Represents a standardized view of the supported interactions between a given ItemsSource
/// object and an control.
///
///
/// Components written to work with ItemsRepeater should consume the
/// via ItemsSourceView since this provides a normalized
/// view of the Items. That way, each component does not need to know if the source is an
/// IEnumerable, an IList, or something else.
///
public class ItemsSourceView : INotifyCollectionChanged, IDisposable
{
///
/// Gets an empty
///
public static ItemsSourceView Empty { get; } = new ItemsSourceView(Array.Empty());
private protected readonly IList _inner;
private INotifyCollectionChanged? _notifyCollectionChanged;
///
/// Initializes a new instance of the ItemsSourceView class for the specified data source.
///
/// The data source.
public ItemsSourceView(IEnumerable source)
{
source = source ?? throw new ArgumentNullException(nameof(source));
if (source is IList list)
{
_inner = list;
}
else if (source is IEnumerable objectEnumerable)
{
_inner = new List(objectEnumerable);
}
else
{
_inner = new List(source.Cast());
}
ListenToCollectionChanges();
}
///
/// Gets the number of items in the collection.
///
public int Count => _inner.Count;
///
/// Gets a value that indicates whether the items source can provide a unique key for each item.
///
///
/// TODO: Not yet implemented in Avalonia.
///
public bool HasKeyIndexMapping => false;
///
/// Retrieves the item at the specified index.
///
/// The index.
/// The item.
public object? this[int index] => GetAt(index);
///
/// Occurs when the collection has changed to indicate the reason for the change and which items changed.
///
public event NotifyCollectionChangedEventHandler? CollectionChanged;
///
public void Dispose()
{
if (_notifyCollectionChanged != null)
{
_notifyCollectionChanged.CollectionChanged -= OnCollectionChanged;
}
}
///
/// Retrieves the item at the specified index.
///
/// The index.
/// The item.
public object? GetAt(int index) => _inner[index];
public int IndexOf(object? item) => _inner.IndexOf(item);
public static ItemsSourceView GetOrCreate(IEnumerable? items)
{
if (items is ItemsSourceView isv)
{
return isv;
}
else if (items is null)
{
return Empty;
}
else
{
return new ItemsSourceView(items);
}
}
///
/// Retrieves the index of the item that has the specified unique identifier (key).
///
/// The index.
/// The key
///
/// TODO: Not yet implemented in Avalonia.
///
public string KeyFromIndex(int index)
{
throw new NotImplementedException();
}
///
/// Retrieves the unique identifier (key) for the item at the specified index.
///
/// The key.
/// The index.
///
/// TODO: Not yet implemented in Avalonia.
///
public int IndexFromKey(string key)
{
throw new NotImplementedException();
}
internal void AddListener(ICollectionChangedListener listener)
{
if (_inner is INotifyCollectionChanged incc)
{
CollectionChangedEventManager.Instance.AddListener(incc, listener);
}
}
internal void RemoveListener(ICollectionChangedListener listener)
{
if (_inner is INotifyCollectionChanged incc)
{
CollectionChangedEventManager.Instance.RemoveListener(incc, listener);
}
}
protected void OnItemsSourceChanged(NotifyCollectionChangedEventArgs args)
{
CollectionChanged?.Invoke(this, args);
}
private void ListenToCollectionChanges()
{
if (_inner is INotifyCollectionChanged incc)
{
incc.CollectionChanged += OnCollectionChanged;
_notifyCollectionChanged = incc;
}
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnItemsSourceChanged(e);
}
}
public class ItemsSourceView : ItemsSourceView, IReadOnlyList
{
///
/// Gets an empty
///
public new static ItemsSourceView Empty { get; } = new ItemsSourceView(Array.Empty());
///
/// Initializes a new instance of the ItemsSourceView class for the specified data source.
///
/// The data source.
public ItemsSourceView(IEnumerable source)
: base(source)
{
}
private ItemsSourceView(IEnumerable source)
: base(source)
{
}
///
/// Retrieves the item at the specified index.
///
/// The index.
/// The item.
#pragma warning disable CS8603
public new T this[int index] => GetAt(index);
#pragma warning restore CS8603
///
/// Retrieves the item at the specified index.
///
/// The index.
/// The item.
[return: MaybeNull]
public new T GetAt(int index) => (T)_inner[index];
public IEnumerator GetEnumerator() => _inner.Cast().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator();
public static new ItemsSourceView GetOrCreate(IEnumerable? items)
{
if (items is ItemsSourceView isv)
{
return isv;
}
else if (items is null)
{
return Empty;
}
else
{
return new ItemsSourceView(items);
}
}
}
}