// 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.Linq; using Avalonia.Controls.Utils; namespace Avalonia.Controls { /// /// Represents a standardized view of the supported interactions between an items collection /// and an items control. /// public class ItemsSourceView : IReadOnlyList, INotifyCollectionChanged, ICollectionChangedListener { /// /// Gets an empty /// public static ItemsSourceView Empty { get; } = new ItemsSourceView(Array.Empty()); private readonly IList _inner; private NotifyCollectionChangedEventHandler? _collectionChanged; private NotifyCollectionChangedEventHandler? _postCollectionChanged; private bool _listening; /// /// Initializes a new instance of the ItemsSourceView class for the specified data source. /// /// The data source. private protected ItemsSourceView(IEnumerable 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 but not IList.", nameof(source)), IEnumerable iObj => new List(iObj), null => throw new ArgumentNullException(nameof(source)), _ => new List(source.Cast()) }; } /// /// Gets the number of items in the collection. /// public int Count => Inner.Count; /// /// Gets the inner collection. /// public IList Inner => _inner; /// /// Retrieves the item at the specified index. /// /// The index. /// The item. public object? this[int index] => GetAt(index); /// /// Gets a value that indicates whether the items source can provide a unique key for each item. /// /// /// TODO: Not yet implemented in Avalonia. /// internal bool HasKeyIndexMapping => false; /// /// Occurs when the collection has changed to indicate the reason for the change and which items changed. /// public event NotifyCollectionChangedEventHandler? CollectionChanged { add { AddListenerIfNecessary(); _collectionChanged += value; } remove { _collectionChanged -= value; RemoveListenerIfNecessary(); } } /// /// Occurs when a collection has finished changing and all /// event handlers have been notified. /// internal event NotifyCollectionChangedEventHandler? PostCollectionChanged { add { AddListenerIfNecessary(); _postCollectionChanged += value; } remove { _postCollectionChanged -= value; RemoveListenerIfNecessary(); } } private void AddListenerIfNecessary() { if (!_listening) { if (_inner is INotifyCollectionChanged incc) CollectionChangedEventManager.Instance.AddListener(incc, this); _listening = true; } } private void RemoveListenerIfNecessary() { if (_listening && _collectionChanged is null && _postCollectionChanged is null) { if (_inner is INotifyCollectionChanged incc) CollectionChangedEventManager.Instance.RemoveListener(incc, this); _listening = false; } } /// /// Retrieves the item at the specified index. /// /// The index. /// The item. public object? GetAt(int index) => Inner[index]; /// /// Determines the index of a specific item in the collection. /// /// The object to locate in the collection. /// The index of value if found in the list; otherwise, -1. public int IndexOf(object? item) => Inner.IndexOf(item); /// /// Gets or creates an for the specified enumerable. /// /// The enumerable. /// /// This method handles the following three cases: /// - If is null, returns /// - If is an returns the existing /// /// - Otherwise creates a new /// public static ItemsSourceView GetOrCreate(IEnumerable? items) { return items switch { ItemsSourceView isv => isv, null => Empty, _ => new ItemsSourceView(items) }; } /// /// Gets or creates an for the specified enumerable. /// /// The enumerable. /// /// This method handles the following three cases: /// - If is null, returns /// - If is an returns the existing /// /// - Otherwise creates a new /// public static ItemsSourceView GetOrCreate(IEnumerable? items) { return items switch { ItemsSourceView isv => isv, null => ItemsSourceView.Empty, _ => new ItemsSourceView(items) }; } /// /// Gets or creates an for the specified enumerable. /// /// The enumerable. /// /// This method handles the following three cases: /// - If is null, returns /// - If is an returns the existing /// /// - Otherwise creates a new /// public static ItemsSourceView GetOrCreate(IEnumerable? items) { return items switch { ItemsSourceView isv => isv, null => ItemsSourceView.Empty, _ => new ItemsSourceView(items) }; } public IEnumerator GetEnumerator() { static IEnumerator EnumerateItems(IList list) { foreach (var o in list) yield return o; } var inner = Inner; return inner switch { IEnumerable e => e.GetEnumerator(), _ => EnumerateItems(inner), }; } IEnumerator IEnumerable.GetEnumerator() => Inner.GetEnumerator(); void ICollectionChangedListener.PreChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e) { } void ICollectionChangedListener.Changed(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e) { _collectionChanged?.Invoke(this, e); } void ICollectionChangedListener.PostChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e) { _postCollectionChanged?.Invoke(this, e); } /// /// Retrieves the index of the item that has the specified unique identifier (key). /// /// The index. /// The key /// /// TODO: Not yet implemented in Avalonia. /// internal string KeyFromIndex(int index) => throw new NotImplementedException(); } public sealed 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. internal ItemsSourceView(IEnumerable source) : base(source) { } internal ItemsSourceView(IEnumerable source) : base(source) { } /// /// Retrieves the item at the specified index. /// /// The index. /// The item. public new T this[int index] => GetAt(index); /// /// Retrieves the item at the specified index. /// /// The index. /// The item. public new T GetAt(int index) => (T)Inner[index]!; public new IEnumerator GetEnumerator() { static IEnumerator EnumerateItems(IList list) { foreach (var o in list) yield return (T)o; } var inner = Inner; return inner switch { IEnumerable e => e.GetEnumerator(), _ => EnumerateItems(inner), }; } IEnumerator IEnumerable.GetEnumerator() => Inner.GetEnumerator(); } }