// 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); } } } }