using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers { /// A watcher which detects changes to an observable collection. /// The value type within the collection. internal class ObservableCollectionWatcher : BaseDisposableWatcher, ICollectionWatcher { /********* ** Fields *********/ /// The field being watched. private readonly ObservableCollection Field; /// The pairs added since the last reset. private readonly List AddedImpl = new(); /// The pairs removed since the last reset. private readonly List RemovedImpl = new(); /// The previous values as of the last update. private readonly List PreviousValues = new(); /********* ** Accessors *********/ /// Whether the collection changed since the last reset. public bool IsChanged => this.AddedImpl.Count > 0 || this.RemovedImpl.Count > 0; /// The values added since the last reset. public IEnumerable Added => this.AddedImpl; /// The values removed since the last reset. public IEnumerable Removed => this.RemovedImpl; /********* ** Public methods *********/ /// Construct an instance. /// The field to watch. public ObservableCollectionWatcher(ObservableCollection field) { this.Field = field; field.CollectionChanged += this.OnCollectionChanged; } /// Update the current value if needed. public void Update() { this.AssertNotDisposed(); } /// Set the current value as the baseline. public void Reset() { this.AssertNotDisposed(); this.AddedImpl.Clear(); this.RemovedImpl.Clear(); } /// Stop watching the field and release all references. public override void Dispose() { if (!this.IsDisposed) this.Field.CollectionChanged -= this.OnCollectionChanged; base.Dispose(); } /********* ** Private methods *********/ /// A callback invoked when an entry is added or removed from the collection. /// The event sender. /// The event arguments. private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Reset) { this.RemovedImpl.AddRange(this.PreviousValues); this.PreviousValues.Clear(); } else { TValue[]? added = e.NewItems?.Cast().ToArray(); TValue[]? removed = e.OldItems?.Cast().ToArray(); if (removed != null) { this.RemovedImpl.AddRange(removed); this.PreviousValues.RemoveRange(e.OldStartingIndex, removed.Length); } if (added != null) { this.AddedImpl.AddRange(added); this.PreviousValues.InsertRange(e.NewStartingIndex, added); } } } } }