// 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 IList? _inner;
private NotifyCollectionChangedEventHandler? _collectionChanged;
///
/// 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));
_inner = source switch
{
ItemsSourceView _ => throw new ArgumentException("Cannot wrap an existing ItemsSourceView.", nameof(source)),
IList list => list,
INotifyCollectionChanged _ => throw new ArgumentException(
"Collection implements INotifyCollectionChanged by not IList.",
nameof(source)),
IEnumerable iObj => new List(iObj),
_ => new List(source.Cast())
};
}
///
/// 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;
///
/// Gets the inner collection.
///
public IList Inner
{
get
{
if (_inner is null)
ThrowDisposed();
return _inner!;
}
}
///
/// 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
{
add
{
if (_collectionChanged is null && Inner is INotifyCollectionChanged incc)
{
incc.CollectionChanged += OnCollectionChanged;
}
_collectionChanged += value;
}
remove
{
_collectionChanged -= value;
if (_collectionChanged is null && Inner is INotifyCollectionChanged incc)
{
incc.CollectionChanged -= OnCollectionChanged;
}
}
}
///
public void Dispose()
{
if (_inner is INotifyCollectionChanged incc)
{
incc.CollectionChanged -= OnCollectionChanged;
}
_inner = null;
}
///
/// 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 OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnItemsSourceChanged(e);
}
private void ThrowDisposed() => throw new ObjectDisposedException(nameof(ItemsSourceView));
}
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);
}
}
}
}