// 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; #nullable enable namespace Avalonia.Controls { /// /// Represents a standardized view of the supported interactions between a given /// or and its items. /// public class ItemsSourceView : INotifyCollectionChanged, IDisposable, IReadOnlyList { /// /// Gets an empty /// public static ItemsSourceView Empty { get; } = new ItemsSourceView(Array.Empty()); private 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) : this((IEnumerable)source) { } private protected ItemsSourceView(IEnumerable source) { source = source ?? throw new ArgumentNullException(nameof(source)); if (source is IList list) { _inner = list; } else if (source is IEnumerable enumerable) { _inner = new List(enumerable); } 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 T 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 T GetAt(int index) => _inner is IList typed ? typed[index] : (T)_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(); } public IEnumerator GetEnumerator() => _inner is IList typed ? typed.GetEnumerator() : _inner.Cast().GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator(); 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 { public ItemsSourceView(IEnumerable source) : base(source) { } } }