You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
333 lines
10 KiB
333 lines
10 KiB
using System; |
|
using System.Collections.Generic; |
|
using System.Collections.ObjectModel; |
|
|
|
namespace UniRx |
|
{ |
|
public struct CollectionAddEvent<T> : IEquatable<CollectionAddEvent<T>> |
|
{ |
|
public int Index { get; private set; } |
|
public T Value { get; private set; } |
|
|
|
public CollectionAddEvent(int index, T value) |
|
:this() |
|
{ |
|
Index = index; |
|
Value = value; |
|
} |
|
|
|
public override string ToString() |
|
{ |
|
return string.Format("Index:{0} Value:{1}", Index, Value); |
|
} |
|
|
|
public override int GetHashCode() |
|
{ |
|
return Index.GetHashCode() ^ EqualityComparer<T>.Default.GetHashCode(Value) << 2; |
|
} |
|
|
|
public bool Equals(CollectionAddEvent<T> other) |
|
{ |
|
return Index.Equals(other.Index) && EqualityComparer<T>.Default.Equals(Value, other.Value); |
|
} |
|
} |
|
|
|
public struct CollectionRemoveEvent<T> : IEquatable<CollectionRemoveEvent<T>> |
|
{ |
|
public int Index { get; private set; } |
|
public T Value { get; private set; } |
|
|
|
public CollectionRemoveEvent(int index, T value) |
|
: this() |
|
{ |
|
Index = index; |
|
Value = value; |
|
} |
|
|
|
public override string ToString() |
|
{ |
|
return string.Format("Index:{0} Value:{1}", Index, Value); |
|
} |
|
|
|
public override int GetHashCode() |
|
{ |
|
return Index.GetHashCode() ^ EqualityComparer<T>.Default.GetHashCode(Value) << 2; |
|
} |
|
|
|
public bool Equals(CollectionRemoveEvent<T> other) |
|
{ |
|
return Index.Equals(other.Index) && EqualityComparer<T>.Default.Equals(Value, other.Value); |
|
} |
|
} |
|
|
|
public struct CollectionMoveEvent<T> : IEquatable<CollectionMoveEvent<T>> |
|
{ |
|
public int OldIndex { get; private set; } |
|
public int NewIndex { get; private set; } |
|
public T Value { get; private set; } |
|
|
|
public CollectionMoveEvent(int oldIndex, int newIndex, T value) |
|
: this() |
|
{ |
|
OldIndex = oldIndex; |
|
NewIndex = newIndex; |
|
Value = value; |
|
} |
|
|
|
public override string ToString() |
|
{ |
|
return string.Format("OldIndex:{0} NewIndex:{1} Value:{2}", OldIndex, NewIndex, Value); |
|
} |
|
|
|
public override int GetHashCode() |
|
{ |
|
return OldIndex.GetHashCode() ^ NewIndex.GetHashCode() << 2 ^ EqualityComparer<T>.Default.GetHashCode(Value) >> 2; |
|
} |
|
|
|
public bool Equals(CollectionMoveEvent<T> other) |
|
{ |
|
return OldIndex.Equals(other.OldIndex) && NewIndex.Equals(other.NewIndex) && EqualityComparer<T>.Default.Equals(Value, other.Value); |
|
} |
|
} |
|
|
|
public struct CollectionReplaceEvent<T> : IEquatable<CollectionReplaceEvent<T>> |
|
{ |
|
public int Index { get; private set; } |
|
public T OldValue { get; private set; } |
|
public T NewValue { get; private set; } |
|
|
|
public CollectionReplaceEvent(int index, T oldValue, T newValue) |
|
: this() |
|
{ |
|
Index = index; |
|
OldValue = oldValue; |
|
NewValue = newValue; |
|
} |
|
|
|
public override string ToString() |
|
{ |
|
return string.Format("Index:{0} OldValue:{1} NewValue:{2}", Index, OldValue, NewValue); |
|
} |
|
|
|
public override int GetHashCode() |
|
{ |
|
return Index.GetHashCode() ^ EqualityComparer<T>.Default.GetHashCode(OldValue) << 2 ^ EqualityComparer<T>.Default.GetHashCode(NewValue) >> 2; |
|
} |
|
|
|
public bool Equals(CollectionReplaceEvent<T> other) |
|
{ |
|
return Index.Equals(other.Index) |
|
&& EqualityComparer<T>.Default.Equals(OldValue, other.OldValue) |
|
&& EqualityComparer<T>.Default.Equals(NewValue, other.NewValue); |
|
} |
|
} |
|
|
|
// IReadOnlyList<out T> is from .NET 4.5 |
|
public interface IReadOnlyReactiveCollection<T> : IEnumerable<T> |
|
{ |
|
int Count { get; } |
|
T this[int index] { get; } |
|
IObservable<CollectionAddEvent<T>> ObserveAdd(); |
|
IObservable<int> ObserveCountChanged(bool notifyCurrentCount = false); |
|
IObservable<CollectionMoveEvent<T>> ObserveMove(); |
|
IObservable<CollectionRemoveEvent<T>> ObserveRemove(); |
|
IObservable<CollectionReplaceEvent<T>> ObserveReplace(); |
|
IObservable<Unit> ObserveReset(); |
|
} |
|
|
|
public interface IReactiveCollection<T> : IList<T>, IReadOnlyReactiveCollection<T> |
|
{ |
|
new int Count { get; } |
|
new T this[int index] { get; set; } |
|
void Move(int oldIndex, int newIndex); |
|
} |
|
|
|
[Serializable] |
|
public class ReactiveCollection<T> : Collection<T>, IReactiveCollection<T>, IDisposable |
|
{ |
|
[NonSerialized] |
|
bool isDisposed = false; |
|
|
|
public ReactiveCollection() |
|
{ |
|
|
|
} |
|
|
|
public ReactiveCollection(IEnumerable<T> collection) |
|
{ |
|
if (collection == null) throw new ArgumentNullException("collection"); |
|
|
|
foreach (var item in collection) |
|
{ |
|
Add(item); |
|
} |
|
} |
|
|
|
public ReactiveCollection(List<T> list) |
|
: base(list != null ? new List<T>(list) : null) |
|
{ |
|
} |
|
|
|
protected override void ClearItems() |
|
{ |
|
var beforeCount = Count; |
|
base.ClearItems(); |
|
|
|
if (collectionReset != null) collectionReset.OnNext(Unit.Default); |
|
if (beforeCount > 0) |
|
{ |
|
if (countChanged != null) countChanged.OnNext(Count); |
|
} |
|
} |
|
|
|
protected override void InsertItem(int index, T item) |
|
{ |
|
base.InsertItem(index, item); |
|
|
|
if (collectionAdd != null) collectionAdd.OnNext(new CollectionAddEvent<T>(index, item)); |
|
if (countChanged != null) countChanged.OnNext(Count); |
|
} |
|
|
|
public void Move(int oldIndex, int newIndex) |
|
{ |
|
MoveItem(oldIndex, newIndex); |
|
} |
|
|
|
protected virtual void MoveItem(int oldIndex, int newIndex) |
|
{ |
|
T item = this[oldIndex]; |
|
base.RemoveItem(oldIndex); |
|
base.InsertItem(newIndex, item); |
|
|
|
if (collectionMove != null) collectionMove.OnNext(new CollectionMoveEvent<T>(oldIndex, newIndex, item)); |
|
} |
|
|
|
protected override void RemoveItem(int index) |
|
{ |
|
T item = this[index]; |
|
base.RemoveItem(index); |
|
|
|
if (collectionRemove != null) collectionRemove.OnNext(new CollectionRemoveEvent<T>(index, item)); |
|
if (countChanged != null) countChanged.OnNext(Count); |
|
} |
|
|
|
protected override void SetItem(int index, T item) |
|
{ |
|
T oldItem = this[index]; |
|
base.SetItem(index, item); |
|
|
|
if (collectionReplace != null) collectionReplace.OnNext(new CollectionReplaceEvent<T>(index, oldItem, item)); |
|
} |
|
|
|
|
|
[NonSerialized] |
|
Subject<int> countChanged = null; |
|
public IObservable<int> ObserveCountChanged(bool notifyCurrentCount = false) |
|
{ |
|
if (isDisposed) return Observable.Empty<int>(); |
|
|
|
var subject = countChanged ?? (countChanged = new Subject<int>()); |
|
if (notifyCurrentCount) |
|
{ |
|
return subject.StartWith(() => this.Count); |
|
} |
|
else |
|
{ |
|
return subject; |
|
} |
|
} |
|
|
|
[NonSerialized] |
|
Subject<Unit> collectionReset = null; |
|
public IObservable<Unit> ObserveReset() |
|
{ |
|
if (isDisposed) return Observable.Empty<Unit>(); |
|
return collectionReset ?? (collectionReset = new Subject<Unit>()); |
|
} |
|
|
|
[NonSerialized] |
|
Subject<CollectionAddEvent<T>> collectionAdd = null; |
|
public IObservable<CollectionAddEvent<T>> ObserveAdd() |
|
{ |
|
if (isDisposed) return Observable.Empty<CollectionAddEvent<T>>(); |
|
return collectionAdd ?? (collectionAdd = new Subject<CollectionAddEvent<T>>()); |
|
} |
|
|
|
[NonSerialized] |
|
Subject<CollectionMoveEvent<T>> collectionMove = null; |
|
public IObservable<CollectionMoveEvent<T>> ObserveMove() |
|
{ |
|
if (isDisposed) return Observable.Empty<CollectionMoveEvent<T>>(); |
|
return collectionMove ?? (collectionMove = new Subject<CollectionMoveEvent<T>>()); |
|
} |
|
|
|
[NonSerialized] |
|
Subject<CollectionRemoveEvent<T>> collectionRemove = null; |
|
public IObservable<CollectionRemoveEvent<T>> ObserveRemove() |
|
{ |
|
if (isDisposed) return Observable.Empty<CollectionRemoveEvent<T>>(); |
|
return collectionRemove ?? (collectionRemove = new Subject<CollectionRemoveEvent<T>>()); |
|
} |
|
|
|
[NonSerialized] |
|
Subject<CollectionReplaceEvent<T>> collectionReplace = null; |
|
public IObservable<CollectionReplaceEvent<T>> ObserveReplace() |
|
{ |
|
if (isDisposed) return Observable.Empty<CollectionReplaceEvent<T>>(); |
|
return collectionReplace ?? (collectionReplace = new Subject<CollectionReplaceEvent<T>>()); |
|
} |
|
|
|
void DisposeSubject<TSubject>(ref Subject<TSubject> subject) |
|
{ |
|
if (subject != null) |
|
{ |
|
try |
|
{ |
|
subject.OnCompleted(); |
|
} |
|
finally |
|
{ |
|
subject.Dispose(); |
|
subject = null; |
|
} |
|
} |
|
} |
|
|
|
#region IDisposable Support |
|
|
|
private bool disposedValue = false; |
|
|
|
protected virtual void Dispose(bool disposing) |
|
{ |
|
if (!disposedValue) |
|
{ |
|
if (disposing) |
|
{ |
|
DisposeSubject(ref collectionReset); |
|
DisposeSubject(ref collectionAdd); |
|
DisposeSubject(ref collectionMove); |
|
DisposeSubject(ref collectionRemove); |
|
DisposeSubject(ref collectionReplace); |
|
DisposeSubject(ref countChanged); |
|
} |
|
|
|
disposedValue = true; |
|
} |
|
} |
|
|
|
public void Dispose() |
|
{ |
|
Dispose(true); |
|
} |
|
|
|
#endregion |
|
} |
|
|
|
public static partial class ReactiveCollectionExtensions |
|
{ |
|
public static ReactiveCollection<T> ToReactiveCollection<T>(this IEnumerable<T> source) |
|
{ |
|
return new ReactiveCollection<T>(source); |
|
} |
|
} |
|
}
|
|
|