Mesen2/UI/Utilities/MesenList.cs
Sour d208fdc6a7 UI: Support for NativeAOT
Added Windows & Linux builds using AOT compilation
2024-06-05 20:23:31 +09:00

729 lines
18 KiB
C#

using Avalonia.Collections;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Mesen.Utilities
{
//Copy of Avalonia's AvaloniaList with an added Replace function to avoid triggering 2 updates
public partial class MesenList<T>
{
public void Replace(IList<T> items)
{
_inner.Clear();
_inner.Capacity = items.Count;
_inner.AddRange(items);
_collectionChanged?.Invoke(this, EventArgsCache.ResetCollectionChanged);
}
public List<T> GetInnerList()
{
return _inner;
}
}
/// <summary>
/// A notifying list.
/// </summary>
/// <typeparam name="T">The type of the list items.</typeparam>
/// <remarks>
/// <para>
/// AvaloniaList is similar to <see cref="System.Collections.ObjectModel.ObservableCollection{T}"/>
/// with a few added features:
/// </para>
///
/// <list type="bullet">
/// <item>
/// It can be configured to notify the <see cref="CollectionChanged"/> event with a
/// <see cref="NotifyCollectionChangedAction.Remove"/> action instead of a
/// <see cref="NotifyCollectionChangedAction.Reset"/> when the list is cleared by
/// setting <see cref="ResetBehavior"/> to <see cref="ResetBehavior.Remove"/>.
/// removed
/// </item>
/// <item>
/// A <see cref="Validate"/> function can be used to validate each item before insertion.
/// removed
/// </item>
/// </list>
/// </remarks>
public partial class MesenList<T> : IAvaloniaList<T>, IList
{
private readonly List<T> _inner;
private NotifyCollectionChangedEventHandler? _collectionChanged;
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaList{T}"/> class.
/// </summary>
public MesenList()
{
_inner = new List<T>();
}
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaList{T}"/>.
/// </summary>
/// <param name="capacity">Initial list capacity.</param>
public MesenList(int capacity)
{
_inner = new List<T>(capacity);
}
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaList{T}"/> class.
/// </summary>
/// <param name="items">The initial items for the collection.</param>
public MesenList(IEnumerable<T> items)
{
_inner = new List<T>(items);
}
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaList{T}"/> class.
/// </summary>
/// <param name="items">The initial items for the collection.</param>
public MesenList(params T[] items)
{
_inner = new List<T>(items);
}
/// <summary>
/// Raised when a change is made to the collection's items.
/// </summary>
public event NotifyCollectionChangedEventHandler? CollectionChanged
{
add => _collectionChanged += value;
remove => _collectionChanged -= value;
}
/// <summary>
/// Raised when a property on the collection changes.
/// </summary>
public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>
/// Gets the number of items in the collection.
/// </summary>
public int Count => _inner.Count;
/// <summary>
/// Gets or sets the reset behavior of the list.
/// </summary>
public ResetBehavior ResetBehavior { get; set; }
/// <summary>
/// Gets or sets a validation routine that can be used to validate items before they are
/// added.
/// </summary>
public Action<T>? Validate { get; set; }
/// <inheritdoc/>
bool IList.IsFixedSize => false;
/// <inheritdoc/>
bool IList.IsReadOnly => false;
/// <inheritdoc/>
int ICollection.Count => _inner.Count;
/// <inheritdoc/>
bool ICollection.IsSynchronized => false;
/// <inheritdoc/>
object ICollection.SyncRoot => this;
/// <inheritdoc/>
bool ICollection<T>.IsReadOnly => false;
/// <summary>
/// Gets or sets the item at the specified index.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The item.</returns>
public T this[int index]
{
get
{
return _inner[index];
}
set
{
Validate?.Invoke(value);
T old = _inner[index];
if(!EqualityComparer<T>.Default.Equals(old, value)) {
_inner[index] = value;
if(_collectionChanged != null) {
var e = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Replace,
value,
old,
index);
_collectionChanged(this, e);
}
}
}
}
/// <summary>
/// Gets or sets the item at the specified index.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The item.</returns>
object? IList.this[int index]
{
get { return this[index]; }
set { this[index] = (T)value!; }
}
/// <summary>
/// Gets or sets the total number of elements the internal data structure can hold without resizing.
/// </summary>
public int Capacity
{
get => _inner.Capacity;
set => _inner.Capacity = value;
}
/// <summary>
/// Adds an item to the collection.
/// </summary>
/// <param name="item">The item.</param>
public virtual void Add(T item)
{
Validate?.Invoke(item);
int index = _inner.Count;
_inner.Add(item);
NotifyAdd(item, index);
}
/// <summary>
/// Adds multiple items to the collection.
/// </summary>
/// <param name="items">The items.</param>
public virtual void AddRange(IEnumerable<T> items) => InsertRange(_inner.Count, items);
/// <summary>
/// Removes all items from the collection.
/// </summary>
public virtual void Clear()
{
if(Count > 0) {
if(_collectionChanged != null) {
var e = ResetBehavior == ResetBehavior.Reset ?
EventArgsCache.ResetCollectionChanged :
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, _inner.ToArray(), 0);
_inner.Clear();
_collectionChanged(this, e);
} else {
_inner.Clear();
}
NotifyCountChanged();
}
}
/// <summary>
/// Tests if the collection contains the specified item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>True if the collection contains the item; otherwise false.</returns>
public bool Contains(T item)
{
return _inner.Contains(item);
}
/// <summary>
/// Copies the collection's contents to an array.
/// </summary>
/// <param name="array">The array.</param>
/// <param name="arrayIndex">The first index of the array to copy to.</param>
public void CopyTo(T[] array, int arrayIndex)
{
_inner.CopyTo(array, arrayIndex);
}
/// <summary>
/// Returns an enumerator that enumerates the items in the collection.
/// </summary>
/// <returns>An <see cref="IEnumerator{T}"/>.</returns>
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return new Enumerator(_inner);
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return new Enumerator(_inner);
}
public Enumerator GetEnumerator()
{
return new Enumerator(_inner);
}
/// <summary>
/// Gets a range of items from the collection.
/// </summary>
/// <param name="index">The zero-based <see cref="AvaloniaList{T}"/> index at which the range starts.</param>
/// <param name="count">The number of elements in the range.</param>
public IEnumerable<T> GetRange(int index, int count)
{
return _inner.GetRange(index, count);
}
/// <summary>
/// Gets the index of the specified item in the collection.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>
/// The index of the item or -1 if the item is not contained in the collection.
/// </returns>
public int IndexOf(T item)
{
return _inner.IndexOf(item);
}
/// <summary>
/// Inserts an item at the specified index.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="item">The item.</param>
public virtual void Insert(int index, T item)
{
Validate?.Invoke(item);
_inner.Insert(index, item);
NotifyAdd(item, index);
}
/// <summary>
/// Inserts multiple items at the specified index.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="items">The items.</param>
public virtual void InsertRange(int index, IEnumerable<T> items)
{
_ = items ?? throw new ArgumentNullException(nameof(items));
bool willRaiseCollectionChanged = _collectionChanged != null;
bool hasValidation = Validate != null;
if(items is IList list) {
if(list.Count > 0) {
if(list is ICollection<T> collection) {
if(hasValidation) {
foreach(T item in collection) {
Validate!(item);
}
}
_inner.InsertRange(index, collection);
NotifyAdd(list, index);
} else {
EnsureCapacity(_inner.Count + list.Count);
using(IEnumerator<T> en = items.GetEnumerator()) {
int insertIndex = index;
while(en.MoveNext()) {
T item = en.Current;
if(hasValidation) {
Validate!(item);
}
_inner.Insert(insertIndex++, item);
}
}
NotifyAdd(list, index);
}
}
} else {
using(IEnumerator<T> en = items.GetEnumerator()) {
if(en.MoveNext()) {
// Avoid allocating list for collection notification if there is no event subscriptions.
List<T>? notificationItems = willRaiseCollectionChanged ?
new List<T>() :
null;
int insertIndex = index;
do {
T item = en.Current;
if(hasValidation) {
Validate!(item);
}
_inner.Insert(insertIndex++, item);
notificationItems?.Add(item);
} while(en.MoveNext());
if(notificationItems is not null) {
NotifyAdd(notificationItems, index);
} else {
NotifyCountChanged();
}
}
}
}
}
/// <summary>
/// Moves an item to a new index.
/// </summary>
/// <param name="oldIndex">The index of the item to move.</param>
/// <param name="newIndex">The index to move the item to.</param>
public void Move(int oldIndex, int newIndex)
{
var item = this[oldIndex];
_inner.RemoveAt(oldIndex);
_inner.Insert(newIndex, item);
if(_collectionChanged != null) {
var e = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Move,
item,
newIndex,
oldIndex);
_collectionChanged(this, e);
}
}
/// <summary>
/// Moves multiple items to a new index.
/// </summary>
/// <param name="oldIndex">The first index of the items to move.</param>
/// <param name="count">The number of items to move.</param>
/// <param name="newIndex">The index to move the items to.</param>
public void MoveRange(int oldIndex, int count, int newIndex)
{
var items = _inner.GetRange(oldIndex, count);
var modifiedNewIndex = newIndex;
_inner.RemoveRange(oldIndex, count);
if(newIndex > oldIndex) {
modifiedNewIndex -= count;
}
_inner.InsertRange(modifiedNewIndex, items);
if(_collectionChanged != null) {
var e = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Move,
items,
newIndex,
oldIndex);
_collectionChanged(this, e);
}
}
/// <summary>
/// Ensures that the capacity of the list is at least <see cref="Capacity"/>.
/// </summary>
/// <param name="capacity">The capacity.</param>
public void EnsureCapacity(int capacity)
{
// Adapted from List<T> implementation.
var currentCapacity = _inner.Capacity;
if(currentCapacity < capacity) {
var newCapacity = currentCapacity == 0 ? 4 : currentCapacity * 2;
if(newCapacity < capacity) {
newCapacity = capacity;
}
_inner.Capacity = newCapacity;
}
}
/// <summary>
/// Removes an item from the collection.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>True if the item was found and removed, otherwise false.</returns>
public virtual bool Remove(T item)
{
int index = _inner.IndexOf(item);
if(index != -1) {
_inner.RemoveAt(index);
NotifyRemove(item, index);
return true;
}
return false;
}
/// <summary>
/// Removes multiple items from the collection.
/// </summary>
/// <param name="items">The items.</param>
public virtual void RemoveAll(IEnumerable<T> items)
{
_ = items ?? throw new ArgumentNullException(nameof(items));
var hItems = new HashSet<T>(items);
int counter = 0;
for(int i = _inner.Count - 1; i >= 0; --i) {
if(hItems.Contains(_inner[i])) {
counter += 1;
} else if(counter > 0) {
RemoveRange(i + 1, counter);
counter = 0;
}
}
if(counter > 0)
RemoveRange(0, counter);
}
/// <summary>
/// Removes the item at the specified index.
/// </summary>
/// <param name="index">The index.</param>
public virtual void RemoveAt(int index)
{
T item = _inner[index];
_inner.RemoveAt(index);
NotifyRemove(item, index);
}
/// <summary>
/// Removes a range of elements from the collection.
/// </summary>
/// <param name="index">The first index to remove.</param>
/// <param name="count">The number of items to remove.</param>
public virtual void RemoveRange(int index, int count)
{
if(count > 0) {
var list = _inner.GetRange(index, count);
_inner.RemoveRange(index, count);
NotifyRemove(list, index);
}
}
/// <inheritdoc/>
int IList.Add(object? value)
{
int index = Count;
Add((T)value!);
return index;
}
/// <inheritdoc/>
bool IList.Contains(object? value)
{
return Contains((T)value!);
}
/// <inheritdoc/>
void IList.Clear()
{
Clear();
}
/// <inheritdoc/>
int IList.IndexOf(object? value)
{
return IndexOf((T)value!);
}
/// <inheritdoc/>
void IList.Insert(int index, object? value)
{
Insert(index, (T)value!);
}
/// <inheritdoc/>
void IList.Remove(object? value)
{
Remove((T)value!);
}
/// <inheritdoc/>
void IList.RemoveAt(int index)
{
RemoveAt(index);
}
/// <inheritdoc/>
void ICollection.CopyTo(Array array, int index)
{
if(array == null) {
throw new ArgumentNullException(nameof(array));
}
if(array.Rank != 1) {
throw new ArgumentException("Multi-dimensional arrays are not supported.");
}
if(array.GetLowerBound(0) != 0) {
throw new ArgumentException("Non-zero lower bounds are not supported.");
}
if(index < 0) {
throw new ArgumentException("Invalid index.");
}
if(array.Length - index < Count) {
throw new ArgumentException("The target array is too small.");
}
if(array is T[] tArray) {
_inner.CopyTo(tArray, index);
} else {
//
// Catch the obvious case assignment will fail.
// We can't find all possible problems by doing the check though.
// For example, if the element type of the Array is derived from T,
// we can't figure out if we can successfully copy the element beforehand.
//
Type targetType = array.GetType().GetElementType()!;
Type sourceType = typeof(T);
if(!(targetType.IsAssignableFrom(sourceType) || sourceType.IsAssignableFrom(targetType))) {
throw new ArgumentException("Invalid array type");
}
//
// We can't cast array of value type to object[], so we don't support
// widening of primitive types here.
//
if(array is not object?[] objects) {
throw new ArgumentException("Invalid array type");
}
int count = _inner.Count;
try {
for(int i = 0; i < count; i++) {
objects[index++] = _inner[i];
}
} catch(ArrayTypeMismatchException) {
throw new ArgumentException("Invalid array type");
}
}
}
/// <summary>
/// Raises the <see cref="CollectionChanged"/> event with an add action.
/// </summary>
/// <param name="t">The items that were added.</param>
/// <param name="index">The starting index.</param>
private void NotifyAdd(IList t, int index)
{
if(_collectionChanged != null) {
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, t, index);
_collectionChanged(this, e);
}
NotifyCountChanged();
}
/// <summary>
/// Raises the <see cref="CollectionChanged"/> event with a add action.
/// </summary>
/// <param name="item">The item that was added.</param>
/// <param name="index">The starting index.</param>
private void NotifyAdd(T item, int index)
{
if(_collectionChanged != null) {
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new[] { item }, index);
_collectionChanged(this, e);
}
NotifyCountChanged();
}
/// <summary>
/// Raises the <see cref="PropertyChanged"/> event when the <see cref="Count"/> property
/// changes.
/// </summary>
private void NotifyCountChanged()
{
PropertyChanged?.Invoke(this, EventArgsCache.CountPropertyChanged);
}
/// <summary>
/// Raises the <see cref="CollectionChanged"/> event with a remove action.
/// </summary>
/// <param name="t">The items that were removed.</param>
/// <param name="index">The starting index.</param>
private void NotifyRemove(IList t, int index)
{
if(_collectionChanged != null) {
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, t, index);
_collectionChanged(this, e);
}
NotifyCountChanged();
}
/// <summary>
/// Raises the <see cref="CollectionChanged"/> event with a remove action.
/// </summary>
/// <param name="item">The item that was removed.</param>
/// <param name="index">The starting index.</param>
private void NotifyRemove(T item, int index)
{
if(_collectionChanged != null) {
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { item }, index);
_collectionChanged(this, e);
}
NotifyCountChanged();
}
/// <summary>
/// Enumerates the elements of a <see cref="AvaloniaList{T}"/>.
/// </summary>
public struct Enumerator : IEnumerator<T>
{
private List<T>.Enumerator _innerEnumerator;
public Enumerator(List<T> inner)
{
_innerEnumerator = inner.GetEnumerator();
}
public bool MoveNext()
{
return _innerEnumerator.MoveNext();
}
void IEnumerator.Reset()
{
((IEnumerator)_innerEnumerator).Reset();
}
public T Current => _innerEnumerator.Current;
object? IEnumerator.Current => Current;
public void Dispose()
{
_innerEnumerator.Dispose();
}
}
}
internal static class EventArgsCache
{
internal static readonly PropertyChangedEventArgs CountPropertyChanged = new PropertyChangedEventArgs(nameof(AvaloniaList<object>.Count));
internal static readonly NotifyCollectionChangedEventArgs ResetCollectionChanged = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
}
}