diff --git a/Source/Assets/MarkLight/Source/Plugins/AutoSubscription.cs b/Source/Assets/MarkLight/Source/Plugins/AutoSubscription.cs new file mode 100644 index 0000000..ea3c13c --- /dev/null +++ b/Source/Assets/MarkLight/Source/Plugins/AutoSubscription.cs @@ -0,0 +1,59 @@ +using UnityEngine; +using System.Collections.Generic; + +namespace MarkLight +{ + public static class AutoSubscription + { + static Stack AutoSubscriberQueue = new Stack(); + static HashSet AutoSubscribers = new HashSet(); + + public static int ActiveAutoSubscriberCount { get { return AutoSubscriberQueue.Count; } } + + public static void StartSubscription(IAutoSubscriber subscriber) + { + if (subscriber == null) + throw new System.ArgumentNullException("subscriber"); + if (AutoSubscribers.Contains(subscriber)) + throw new System.InvalidOperationException("Recursive calculation detected"); + + AutoSubscriberQueue.Push(subscriber); + AutoSubscribers.Add(subscriber); + } + + public static void EndSubscription(IAutoSubscriber subscriber) + { + if (subscriber == null) + throw new System.ArgumentNullException("subscriber"); + if (AutoSubscriberQueue.Count == 0) + throw new System.InvalidOperationException("There are no active IAutoSubscribers"); + if (!AutoSubscribers.Contains(subscriber)) + throw new System.InvalidOperationException("EndSubscription called on a subscriber without StartSubscription"); + if (AutoSubscriberQueue.Peek() != subscriber) + throw new System.InvalidOperationException("EndSubscription is being called for a subscriber that is not at the top of the stack"); + + AutoSubscribers.Remove(subscriber); + AutoSubscriberQueue.Pop(); + } + + public static void NotifyViewFieldWasAccessed(ViewFieldBase field) + { + if (AutoSubscriberQueue.Count == 0) + return; + + IAutoSubscriber topmostSubscriber = AutoSubscriberQueue.Peek(); + topmostSubscriber.ViewFieldWasAccessed(field); + } + + public static void NotifyObservableListWasAccessed(IObservableList list) + { + if (AutoSubscriberQueue.Count == 0) + return; + + IAutoSubscriber topmostSubscriber = AutoSubscriberQueue.Peek(); + topmostSubscriber.ObservableListWasAccessed(list); + } + + + } +} diff --git a/Source/Assets/MarkLight/Source/Plugins/CalculatedViewField.cs b/Source/Assets/MarkLight/Source/Plugins/CalculatedViewField.cs new file mode 100644 index 0000000..b2d6cc1 --- /dev/null +++ b/Source/Assets/MarkLight/Source/Plugins/CalculatedViewField.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace MarkLight +{ + [Serializable] + public class CalculatedViewField : ViewField, IAutoSubscriber + { + bool IsCalculating; + bool CachedValueIsValid; + HashSet FieldsSubscribedTo = new HashSet(); + HashSet ObservableListsSubscribedTo = new HashSet(); + public System.Func GetCalculatedValue; + public System.Action UpdateSourceValuesFromValue; + + public override T InternalValue + { + get + { + EnsureCachedValueIsUpToDate(); + return base.InternalValue; + } + + set + { + base.InternalValue = value; + } + } + + public override T Value + { + get + { + EnsureCachedValueIsUpToDate(); + return base.Value; + } + + set + { + base.Value = value; + } + } + + void EnsureCachedValueIsUpToDate() + { + if (!CachedValueIsValid) + UpdateCachedValue(); + } + + void UpdateCachedValue() + { + CachedValueIsValid = false; + if (GetCalculatedValue == null) + throw new System.InvalidOperationException("GetCalculatedValue has not been specifed"); + T calculatedValue; + try + { + IsCalculating = true; + ClearChangeSubscriptions(); + AutoSubscription.StartSubscription(this); + calculatedValue = GetCalculatedValue(); + } + finally + { + IsCalculating = false; + AutoSubscription.EndSubscription(this); + } + CachedValueIsValid = true; + Value = calculatedValue; + } + + void SetValue(T value) + { + if (UpdateSourceValuesFromValue == null) + return; + UpdateSourceValuesFromValue(value); + CachedValueIsValid = false; + } + + void IAutoSubscriber.ViewFieldWasAccessed(ViewFieldBase viewField) + { + if (viewField != this && !FieldsSubscribedTo.Contains(viewField)) + { + viewField.ValueSet += ViewField_ValueSet; + FieldsSubscribedTo.Add(viewField); + } + } + + void IAutoSubscriber.ObservableListWasAccessed(IObservableList list) + { + if (!ObservableListsSubscribedTo.Contains(list)) + { + list.ListChanged += List_ListChanged; + ObservableListsSubscribedTo.Add(list); + } + } + + private void List_ListChanged(object sender, ListChangedEventArgs e) + { + CachedValueIsValid = false; + if (!IsCalculating) + UpdateCachedValue(); + } + + void ViewField_ValueSet(object sender, EventArgs e) + { + CachedValueIsValid = false; + if (!IsCalculating) + UpdateCachedValue(); + } + + void ClearChangeSubscriptions() + { + foreach (ViewFieldBase notifier in FieldsSubscribedTo) + notifier.ValueSet -= ViewField_ValueSet; + FieldsSubscribedTo.Clear(); + + foreach (IObservableList notifier in ObservableListsSubscribedTo) + notifier.ListChanged -= List_ListChanged; + ObservableListsSubscribedTo.Clear(); + } + } + + [Serializable] + public class _CalculatedString : CalculatedViewField { } +} \ No newline at end of file diff --git a/Source/Assets/MarkLight/Source/Plugins/IAutoSubscriber.cs b/Source/Assets/MarkLight/Source/Plugins/IAutoSubscriber.cs new file mode 100644 index 0000000..722a460 --- /dev/null +++ b/Source/Assets/MarkLight/Source/Plugins/IAutoSubscriber.cs @@ -0,0 +1,8 @@ +namespace MarkLight +{ + public interface IAutoSubscriber + { + void ViewFieldWasAccessed(ViewFieldBase viewField); + void ObservableListWasAccessed(IObservableList list); + } +} diff --git a/Source/Assets/MarkLight/Source/Plugins/ObservableList.cs b/Source/Assets/MarkLight/Source/Plugins/ObservableList.cs index a702ab7..c83c274 100644 --- a/Source/Assets/MarkLight/Source/Plugins/ObservableList.cs +++ b/Source/Assets/MarkLight/Source/Plugins/ObservableList.cs @@ -1,821 +1,855 @@ -#region Using Statements -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text; -using UnityEngine; -#endregion - -namespace MarkLight -{ - /// - /// Generic list that notify observers when items are added, deleted or moved. - /// - [Serializable] - public class ObservableList : IObservableList, IEnumerable - { - #region Fields - - private List _list; - private object _selectedItem; - public event EventHandler ListChanged; - - #endregion - - #region Constructor - - /// - /// Initializes a new instance of the class. - /// - public ObservableList() - { - _list = new List(); - } - - /// - /// Initializes a new instance of the class. - /// - public ObservableList(int capacity) - { - _list = new List(capacity); - } - - /// - /// Initializes a new instance of the class. - /// - public ObservableList(IEnumerable collection) - { - _list = new List(collection); - } - - #endregion - - #region Methods - - /// - /// Adds item to the end of the list. - /// - public void Add(object item) - { - Add((T)item); - } - - /// - /// Adds a range of items to the list. - /// - public void Add(IEnumerable items) - { - foreach (var item in items) - { - Add((T)item); - } - } - - /// - /// Adds item to the end of the list. - /// - public void Add(T item) - { - int index = _list.Count; - _list.Add(item); - - if (ListChanged != null) - { - ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Add, StartIndex = index, EndIndex = index }); - } - } - - /// - /// Adds a range of items to the end of the list. - /// - public void AddRange(IEnumerable items) - { - int itemCount = items.Count(); - if (itemCount <= 0) - return; - - int startIndex = _list.Count; - int endIndex = startIndex + (itemCount - 1); - - _list.AddRange(items); - - if (ListChanged != null) - { - ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Add, StartIndex = startIndex, EndIndex = endIndex }); - } - } - - /// - /// Replaces the items in the list. - /// - public void Replace(IEnumerable newItems) - { - var newItemsList = new List(newItems); - int newItemsCount = newItemsList.Count; - if (newItemsCount <= 0) - { - Clear(); - return; - } - - int replaceCount = newItemsCount >= Count ? Count : newItemsCount; - for (int i = 0; i < replaceCount; ++i) - { - _list[i] = newItemsList[i]; - } - - if (replaceCount > 0) - { - if (ListChanged != null) - { - ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Replace, StartIndex = 0, EndIndex = replaceCount - 1 }); - } - } - - if (newItemsCount > Count) - { - // old list smaller than new - add items - AddRange(newItemsList.Skip(replaceCount)); - } - else if (newItemsCount < Count) - { - // old list larger than new - remove items - RemoveRange(newItemsCount, Count - newItemsCount); - } - } - - /// - /// Replaces a single item in the list. - /// - public void Replace(int index, T item) - { - if (index < 0 || index >= Count) - return; - - _list[index] = item; - if (ListChanged != null) - { - ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Replace, StartIndex = index, EndIndex = index }); - } - } - - /// - /// Informs observers that item has been modified. - /// - public void ItemModified(T item, string fieldPath = "") - { - int index = IndexOf(item); - if (index < 0) - return; - - ItemsModified(index, index, fieldPath); - } - - /// - /// Informs observers that item has been modified. - /// - public void ItemModified(int index, string fieldPath = "") - { - if (index < 0 || index >= Count) - return; - - ItemsModified(index, index, fieldPath); - } - - /// - /// Informs observers that all items have been modified. - /// - public void ItemsModified(string fieldPath = "") - { - if (Count <= 0) - return; - - if (ListChanged != null) - { - ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Modify, StartIndex = 0, EndIndex = Count - 1, FieldPath = fieldPath }); - } - } - - /// - /// Informs observers that items have been modified. - /// - public void ItemsModified(int startIndex, int endIndex, string fieldPath = "") - { - if (ListChanged != null) - { - ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Modify, StartIndex = startIndex, EndIndex = endIndex, FieldPath = fieldPath }); - } - } - - /// - /// Returns list as read-only collection. - /// - public ReadOnlyCollection AsReadOnly() - { - return _list.AsReadOnly(); - } - - /// - /// Performs a binary search on the sorted list using default comparer and returning a zero-based index of the item. - /// - public int BinarySearch(T item) - { - return _list.BinarySearch(item); - } - - /// - /// Performs a binary search on the sorted list using default comparer and returning a zero-based index of the item. - /// - public int BinarySearch(T item, IComparer comparer) - { - return _list.BinarySearch(item, comparer); - } - - /// - /// Performs a binary search on the sorted list using default comparer and returning a zero-based index of the item. - /// - public int BinarySearch(int index, int count, T item, IComparer comparer) - { - return _list.BinarySearch(index, count, item, comparer); - } - - /// - /// Removes all items from the list. - /// - public void Clear() - { - if (_list.Count > 0) - { - _list.Clear(); - - if (ListChanged != null) - { - ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Clear }); - } - } - } - - /// - /// Returns boolean indicating if list contains the item. - /// - public bool Contains(T item) - { - return _list.Contains(item); - } - - /// - /// Converts the items in the list to another type and returns a new list. - /// - public List ConvertAll(Converter converter) - { - return _list.ConvertAll(converter); - } - - /// - /// Copies the list to an array. - /// - public void CopyTo(T[] array) - { - _list.CopyTo(array); - } - - /// - /// Copies the list to an array. - /// - public void CopyTo(T[] array, int arrayIndex) - { - _list.CopyTo(array, arrayIndex); - } - - /// - /// Copies the list to an array. - /// - public void CopyTo(int index, T[] array, int arrayIndex, int count) - { - _list.CopyTo(index, array, arrayIndex, count); - } - - /// - /// Returns boolean indicating if an item matching the predicate exists in the list. - /// - public bool Exists(Predicate predicate) - { - return _list.Exists(predicate); - } - - /// - /// Returns first item matching the predicate. - /// - public T Find(Predicate predicate) - { - return _list.Find(predicate); - } - - /// - /// Returns all items that matches the predicate. - /// - public List FindAll(Predicate predicate) - { - return _list.FindAll(predicate); - } - - /// - /// Returns the index of the item matching the predicate. - /// - public int FindIndex(Predicate predicate) - { - return _list.FindIndex(predicate); - } - - /// - /// Returns the index of the item matching the predicate. - /// - public int FindIndex(int startIndex, Predicate predicate) - { - return _list.FindIndex(startIndex, predicate); - } - - /// - /// Returns the index of the item matching the predicate. - /// - public int FindIndex(int startIndex, int count, Predicate predicate) - { - return _list.FindIndex(startIndex, count, predicate); - } - - /// - /// Returns the last item matching the predicate. - /// - public T FindLast(Predicate predicate) - { - return _list.FindLast(predicate); - } - - /// - /// Returns the index of the last item matching the predicate. - /// - public int FindLastIndex(Predicate predicate) - { - return _list.FindLastIndex(predicate); - } - - /// - /// Returns the index of the last item matching the predicate. - /// - public int FindLastIndex(int startIndex, Predicate predicate) - { - return _list.FindLastIndex(startIndex, predicate); - } - - /// - /// Returns the index of the last item matching the predicate. - /// - public int FindLastIndex(int startIndex, int count, Predicate predicate) - { - return _list.FindLastIndex(startIndex, count, predicate); - } - - /// - /// Performs an action on each item in the list. - /// - public void ForEach(Action action) - { - _list.ForEach(action); - } - - /// - /// Gets list enumerator. - /// - public IEnumerator GetEnumerator() - { - return _list.GetEnumerator(); - } - - /// - /// Gets list enumerator. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - /// - /// Creates a shallow copies of the specified range of items in the list. - /// - public List GetRange(int index, int count) - { - return _list.GetRange(index, count); - } - - /// - /// Gets the index of the specified item. - /// - public int IndexOf(T item) - { - return _list.IndexOf(item); - } - - /// - /// Gets the index of the specified item. - /// - public int IndexOf(T item, int startIndex) - { - return _list.IndexOf(item, startIndex); - } - - /// - /// Gets the index of the specified item. - /// - public int IndexOf(T item, int startIndex, int count) - { - return _list.IndexOf(item, startIndex, count); - } - - /// - /// Inserts an item into the list at the specified index. - /// - public void Insert(int index, T item) - { - _list.Insert(index, item); - - if (ListChanged != null) - { - ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Add, StartIndex = index, EndIndex = index }); - } - } - - /// - /// Inserts a range of items at the specified index. - /// - public void InsertRange(int startIndex, IEnumerable collection) - { - int itemCount = collection.Count(); - int endIndex = startIndex + (itemCount - 1); - _list.InsertRange(startIndex, collection); - - if (ListChanged != null) - { - ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Add, StartIndex = startIndex, EndIndex = endIndex }); - } - } - - /// - /// Gets the last index of the specified item. - /// - public int LastIndexOf(T item) - { - return _list.LastIndexOf(item); - } - - /// - /// Gets the last index of the specified item. - /// - public int LastIndexOf(T item, int startIndex) - { - return _list.LastIndexOf(item, startIndex); - } - - /// - /// Gets the last index of the specified item. - /// - public int LastIndexOf(T item, int startIndex, int count) - { - return _list.LastIndexOf(item, startIndex, count); - } - - /// - /// Removes the first occurance of an item from the list. - /// - public bool Remove(object item) - { - return Remove((T)item); - } - - /// - /// Removes the first occurance of an item from the list. - /// - public bool Remove(T item) - { - int index = _list.IndexOf(item); - if (index >= 0) - { - RemoveAt(index); - return true; - } - - return false; - } - - /// - /// Removes the items from the list. - /// - public void Remove(IEnumerable items) - { - var toRemove = new List(items.OfType()); - foreach (var item in toRemove) - { - Remove(item); - } - } - - /// - /// Removes all items that matches the predicate. - /// - public int RemoveAll(Predicate predicate) - { - int removedCount = 0; - for (int i = _list.Count - 1; i >= 0; --i) - { - if (predicate(_list[i])) - { - RemoveAt(i); - ++removedCount; - } - } - - return removedCount; - } - - /// - /// Removes item at the specified index. - /// - public void RemoveAt(int index) - { - _list.RemoveAt(index); - - if (ListChanged != null) - { - ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Remove, StartIndex = index, EndIndex = index }); - } - } - - /// - /// Removes a range of items from the list. - /// - public void RemoveRange(int startIndex, int count) - { - int endIndex = startIndex + (count - 1); - _list.RemoveRange(startIndex, count); - - if (ListChanged != null) - { - ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Remove, StartIndex = startIndex, EndIndex = endIndex }); - } - } - - /// - /// Reverses the order of the list. - /// - public void Reverse() - { - _list.Reverse(); - - if (ListChanged != null) - { - ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Move }); - } - } - - /// - /// Reverses the order of the items in the specified range. - /// - public void Reverse(int startIndex, int count) - { - _list.Reverse(startIndex, count); - - if (ListChanged != null) - { - ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Move }); - } - } - - /// - /// Sorts the list using the default comparer. - /// - public void Sort() - { - _list.Sort(); - - if (ListChanged != null) - { - ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Move }); - } - } - - /// - /// Sorts the list. - /// - public void Sort(Comparison comparison) - { - _list.Sort(comparison); - - if (ListChanged != null) - { - ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Move }); - } - } - - /// - /// Sorts the list. - /// - public void Sort(IComparer comparer) - { - _list.Sort(comparer); - - if (ListChanged != null) - { - ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Move }); - } - } - - /// - /// Sorts the list. - /// - public void Sort(int startIndex, int count, IComparer comparer) - { - _list.Sort(startIndex, count, comparer); - - if (ListChanged != null) - { - ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Move }); - } - } - - /// - /// Copies the list to an array. - /// - public T[] ToArray() - { - return _list.ToArray(); - } - - /// - /// Sets capacity to the number of items in the list. - /// - public void TrimExcess() - { - _list.TrimExcess(); - } - - /// - /// Returns boolean indicating if all items matches the predicate. - /// - public bool TrueForAll(Predicate predicate) - { - return _list.TrueForAll(predicate); - } - - /// - /// Sets selected item without notifying observers. - /// - public void SetSelected(object item) - { - _selectedItem = item; - } - - /// - /// Gets index of an item. - /// - public int GetIndex(object item) - { - return item != null ? IndexOf((T)item) : -1; - } - - /// - /// Scrolls to item. - /// - public void ScrollTo(T item, ElementAlignment? alignment = null, ElementMargin offset = null) - { - int index = _list.IndexOf(item); - if (index >= 0) - { - ScrollTo(index, alignment, offset); - } - } - - /// - /// Scrolls to item. - /// - public void ScrollTo(int index, ElementAlignment? alignment = null, ElementMargin offset = null) - { - if (ListChanged != null) - { - ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.ScrollTo, StartIndex = index, EndIndex = index, Alignment = alignment, Offset = offset }); - } - } - - #endregion - - #region Properties - - /// - /// Gets or sets the capacity of the list. - /// - public int Capacity - { - get - { - return _list.Capacity; - } - set - { - _list.Capacity = value; - } - } - - /// - /// Gets the number of items in the list. - /// - public int Count - { - get - { - return _list.Count; - } - } - - /// - /// Gets item at index. - /// - object IObservableList.this[int index] - { - get - { - return this[index]; - } - } - - /// - /// Gets or sets the item at the specified index. - /// - public T this[int index] - { - get - { - return _list[index]; - } - set - { - var currentItem = _list[index]; - bool valueChanged = value != null ? !value.Equals(currentItem) : currentItem != null; - if (valueChanged) - { - _list[index] = value; - - if (ListChanged != null) - { - ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Replace, StartIndex = index, EndIndex = index }); - } - } - } - } - - /// - /// Gets or sets the selected item. - /// - public T SelectedItem - { - get - { - return _selectedItem != null ? (T)_selectedItem : default(T); - } - set - { - SelectedIndex = IndexOf(value); - } - } - - /// - /// Gets or sets the selected index. - /// - public int SelectedIndex - { - get - { - return _selectedItem != null ? IndexOf((T)_selectedItem) : -1; - } - set - { - if (value < 0 || value >= Count) - { - _selectedItem = null; - return; - } - - int currentIndex = SelectedIndex; - if (currentIndex != value) - { - _selectedItem = this[value]; - if (ListChanged != null) - { - ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Select, StartIndex = value, EndIndex = value }); - } - } - } - } - - #endregion - } -} +#region Using Statements +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using UnityEngine; +#endregion + +namespace MarkLight +{ + /// + /// Generic list that notify observers when items are added, deleted or moved. + /// + [Serializable] + public class ObservableList : IObservableList, IEnumerable + { + #region Fields + + private List _list; + private object _selectedItem; + public event EventHandler ListChanged; + + #endregion + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + public ObservableList() + { + _list = new List(); + } + + /// + /// Initializes a new instance of the class. + /// + public ObservableList(int capacity) + { + _list = new List(capacity); + } + + /// + /// Initializes a new instance of the class. + /// + public ObservableList(IEnumerable collection) + { + _list = new List(collection); + } + + #endregion + + #region Methods + + /// + /// Adds item to the end of the list. + /// + public void Add(object item) + { + Add((T)item); + } + + /// + /// Adds a range of items to the list. + /// + public void Add(IEnumerable items) + { + foreach (var item in items) + { + Add((T)item); + } + } + + /// + /// Adds item to the end of the list. + /// + public void Add(T item) + { + int index = _list.Count; + _list.Add(item); + + if (ListChanged != null) + { + ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Add, StartIndex = index, EndIndex = index }); + } + } + + /// + /// Adds a range of items to the end of the list. + /// + public void AddRange(IEnumerable items) + { + int itemCount = items.Count(); + if (itemCount <= 0) + return; + + int startIndex = _list.Count; + int endIndex = startIndex + (itemCount - 1); + + _list.AddRange(items); + + if (ListChanged != null) + { + ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Add, StartIndex = startIndex, EndIndex = endIndex }); + } + } + + /// + /// Replaces the items in the list. + /// + public void Replace(IEnumerable newItems) + { + var newItemsList = new List(newItems); + int newItemsCount = newItemsList.Count; + if (newItemsCount <= 0) + { + Clear(); + return; + } + + int replaceCount = newItemsCount >= Count ? Count : newItemsCount; + for (int i = 0; i < replaceCount; ++i) + { + _list[i] = newItemsList[i]; + } + + if (replaceCount > 0) + { + if (ListChanged != null) + { + ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Replace, StartIndex = 0, EndIndex = replaceCount - 1 }); + } + } + + if (newItemsCount > Count) + { + // old list smaller than new - add items + AddRange(newItemsList.Skip(replaceCount)); + } + else if (newItemsCount < Count) + { + // old list larger than new - remove items + RemoveRange(newItemsCount, Count - newItemsCount); + } + } + + /// + /// Replaces a single item in the list. + /// + public void Replace(int index, T item) + { + if (index < 0 || index >= Count) + return; + + _list[index] = item; + if (ListChanged != null) + { + ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Replace, StartIndex = index, EndIndex = index }); + } + } + + /// + /// Informs observers that item has been modified. + /// + public void ItemModified(T item, string fieldPath = "") + { + int index = IndexOf(item); + if (index < 0) + return; + + ItemsModified(index, index, fieldPath); + } + + /// + /// Informs observers that item has been modified. + /// + public void ItemModified(int index, string fieldPath = "") + { + if (index < 0 || index >= Count) + return; + + ItemsModified(index, index, fieldPath); + } + + /// + /// Informs observers that all items have been modified. + /// + public void ItemsModified(string fieldPath = "") + { + if (Count <= 0) + return; + + if (ListChanged != null) + { + ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Modify, StartIndex = 0, EndIndex = Count - 1, FieldPath = fieldPath }); + } + } + + /// + /// Informs observers that items have been modified. + /// + public void ItemsModified(int startIndex, int endIndex, string fieldPath = "") + { + if (ListChanged != null) + { + ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Modify, StartIndex = startIndex, EndIndex = endIndex, FieldPath = fieldPath }); + } + } + + /// + /// Returns list as read-only collection. + /// + public ReadOnlyCollection AsReadOnly() + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.AsReadOnly(); + } + + /// + /// Performs a binary search on the sorted list using default comparer and returning a zero-based index of the item. + /// + public int BinarySearch(T item) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.BinarySearch(item); + } + + /// + /// Performs a binary search on the sorted list using default comparer and returning a zero-based index of the item. + /// + public int BinarySearch(T item, IComparer comparer) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.BinarySearch(item, comparer); + } + + /// + /// Performs a binary search on the sorted list using default comparer and returning a zero-based index of the item. + /// + public int BinarySearch(int index, int count, T item, IComparer comparer) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.BinarySearch(index, count, item, comparer); + } + + /// + /// Removes all items from the list. + /// + public void Clear() + { + if (_list.Count > 0) + { + _list.Clear(); + + if (ListChanged != null) + { + ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Clear }); + } + } + } + + /// + /// Returns boolean indicating if list contains the item. + /// + public bool Contains(T item) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.Contains(item); + } + + /// + /// Converts the items in the list to another type and returns a new list. + /// + public List ConvertAll(Converter converter) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.ConvertAll(converter); + } + + /// + /// Copies the list to an array. + /// + public void CopyTo(T[] array) + { + _list.CopyTo(array); + } + + /// + /// Copies the list to an array. + /// + public void CopyTo(T[] array, int arrayIndex) + { + _list.CopyTo(array, arrayIndex); + } + + /// + /// Copies the list to an array. + /// + public void CopyTo(int index, T[] array, int arrayIndex, int count) + { + _list.CopyTo(index, array, arrayIndex, count); + } + + /// + /// Returns boolean indicating if an item matching the predicate exists in the list. + /// + public bool Exists(Predicate predicate) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.Exists(predicate); + } + + /// + /// Returns first item matching the predicate. + /// + public T Find(Predicate predicate) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.Find(predicate); + } + + /// + /// Returns all items that matches the predicate. + /// + public List FindAll(Predicate predicate) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.FindAll(predicate); + } + + /// + /// Returns the index of the item matching the predicate. + /// + public int FindIndex(Predicate predicate) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.FindIndex(predicate); + } + + /// + /// Returns the index of the item matching the predicate. + /// + public int FindIndex(int startIndex, Predicate predicate) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.FindIndex(startIndex, predicate); + } + + /// + /// Returns the index of the item matching the predicate. + /// + public int FindIndex(int startIndex, int count, Predicate predicate) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.FindIndex(startIndex, count, predicate); + } + + /// + /// Returns the last item matching the predicate. + /// + public T FindLast(Predicate predicate) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.FindLast(predicate); + } + + /// + /// Returns the index of the last item matching the predicate. + /// + public int FindLastIndex(Predicate predicate) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.FindLastIndex(predicate); + } + + /// + /// Returns the index of the last item matching the predicate. + /// + public int FindLastIndex(int startIndex, Predicate predicate) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.FindLastIndex(startIndex, predicate); + } + + /// + /// Returns the index of the last item matching the predicate. + /// + public int FindLastIndex(int startIndex, int count, Predicate predicate) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.FindLastIndex(startIndex, count, predicate); + } + + /// + /// Performs an action on each item in the list. + /// + public void ForEach(Action action) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + _list.ForEach(action); + } + + /// + /// Gets list enumerator. + /// + public IEnumerator GetEnumerator() + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.GetEnumerator(); + } + + /// + /// Gets list enumerator. + /// + IEnumerator IEnumerable.GetEnumerator() + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return GetEnumerator(); + } + + /// + /// Creates a shallow copies of the specified range of items in the list. + /// + public List GetRange(int index, int count) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.GetRange(index, count); + } + + /// + /// Gets the index of the specified item. + /// + public int IndexOf(T item) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.IndexOf(item); + } + + /// + /// Gets the index of the specified item. + /// + public int IndexOf(T item, int startIndex) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.IndexOf(item, startIndex); + } + + /// + /// Gets the index of the specified item. + /// + public int IndexOf(T item, int startIndex, int count) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.IndexOf(item, startIndex, count); + } + + /// + /// Inserts an item into the list at the specified index. + /// + public void Insert(int index, T item) + { + _list.Insert(index, item); + + if (ListChanged != null) + { + ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Add, StartIndex = index, EndIndex = index }); + } + } + + /// + /// Inserts a range of items at the specified index. + /// + public void InsertRange(int startIndex, IEnumerable collection) + { + int itemCount = collection.Count(); + int endIndex = startIndex + (itemCount - 1); + _list.InsertRange(startIndex, collection); + + if (ListChanged != null) + { + ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Add, StartIndex = startIndex, EndIndex = endIndex }); + } + } + + /// + /// Gets the last index of the specified item. + /// + public int LastIndexOf(T item) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.LastIndexOf(item); + } + + /// + /// Gets the last index of the specified item. + /// + public int LastIndexOf(T item, int startIndex) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.LastIndexOf(item, startIndex); + } + + /// + /// Gets the last index of the specified item. + /// + public int LastIndexOf(T item, int startIndex, int count) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.LastIndexOf(item, startIndex, count); + } + + /// + /// Removes the first occurance of an item from the list. + /// + public bool Remove(object item) + { + return Remove((T)item); + } + + /// + /// Removes the first occurance of an item from the list. + /// + public bool Remove(T item) + { + int index = _list.IndexOf(item); + if (index >= 0) + { + RemoveAt(index); + return true; + } + + return false; + } + + /// + /// Removes the items from the list. + /// + public void Remove(IEnumerable items) + { + var toRemove = new List(items.OfType()); + foreach (var item in toRemove) + { + Remove(item); + } + } + + /// + /// Removes all items that matches the predicate. + /// + public int RemoveAll(Predicate predicate) + { + int removedCount = 0; + for (int i = _list.Count - 1; i >= 0; --i) + { + if (predicate(_list[i])) + { + RemoveAt(i); + ++removedCount; + } + } + + return removedCount; + } + + /// + /// Removes item at the specified index. + /// + public void RemoveAt(int index) + { + _list.RemoveAt(index); + + if (ListChanged != null) + { + ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Remove, StartIndex = index, EndIndex = index }); + } + } + + /// + /// Removes a range of items from the list. + /// + public void RemoveRange(int startIndex, int count) + { + int endIndex = startIndex + (count - 1); + _list.RemoveRange(startIndex, count); + + if (ListChanged != null) + { + ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Remove, StartIndex = startIndex, EndIndex = endIndex }); + } + } + + /// + /// Reverses the order of the list. + /// + public void Reverse() + { + _list.Reverse(); + + if (ListChanged != null) + { + ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Move }); + } + } + + /// + /// Reverses the order of the items in the specified range. + /// + public void Reverse(int startIndex, int count) + { + _list.Reverse(startIndex, count); + + if (ListChanged != null) + { + ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Move }); + } + } + + /// + /// Sorts the list using the default comparer. + /// + public void Sort() + { + _list.Sort(); + + if (ListChanged != null) + { + ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Move }); + } + } + + /// + /// Sorts the list. + /// + public void Sort(Comparison comparison) + { + _list.Sort(comparison); + + if (ListChanged != null) + { + ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Move }); + } + } + + /// + /// Sorts the list. + /// + public void Sort(IComparer comparer) + { + _list.Sort(comparer); + + if (ListChanged != null) + { + ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Move }); + } + } + + /// + /// Sorts the list. + /// + public void Sort(int startIndex, int count, IComparer comparer) + { + _list.Sort(startIndex, count, comparer); + + if (ListChanged != null) + { + ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Move }); + } + } + + /// + /// Copies the list to an array. + /// + public T[] ToArray() + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.ToArray(); + } + + /// + /// Sets capacity to the number of items in the list. + /// + public void TrimExcess() + { + _list.TrimExcess(); + } + + /// + /// Returns boolean indicating if all items matches the predicate. + /// + public bool TrueForAll(Predicate predicate) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.TrueForAll(predicate); + } + + /// + /// Sets selected item without notifying observers. + /// + public void SetSelected(object item) + { + _selectedItem = item; + } + + /// + /// Gets index of an item. + /// + public int GetIndex(object item) + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return item != null ? IndexOf((T)item) : -1; + } + + /// + /// Scrolls to item. + /// + public void ScrollTo(T item, ElementAlignment? alignment = null, ElementMargin offset = null) + { + int index = _list.IndexOf(item); + if (index >= 0) + { + ScrollTo(index, alignment, offset); + } + } + + /// + /// Scrolls to item. + /// + public void ScrollTo(int index, ElementAlignment? alignment = null, ElementMargin offset = null) + { + if (ListChanged != null) + { + ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.ScrollTo, StartIndex = index, EndIndex = index, Alignment = alignment, Offset = offset }); + } + } + + #endregion + + #region Properties + + /// + /// Gets or sets the capacity of the list. + /// + public int Capacity + { + get + { + return _list.Capacity; + } + set + { + _list.Capacity = value; + } + } + + /// + /// Gets the number of items in the list. + /// + public int Count + { + get + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list.Count; + } + } + + /// + /// Gets item at index. + /// + object IObservableList.this[int index] + { + get + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return this[index]; + } + } + + /// + /// Gets or sets the item at the specified index. + /// + public T this[int index] + { + get + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _list[index]; + } + set + { + var currentItem = _list[index]; + bool valueChanged = value != null ? !value.Equals(currentItem) : currentItem != null; + if (valueChanged) + { + _list[index] = value; + + if (ListChanged != null) + { + ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Replace, StartIndex = index, EndIndex = index }); + } + } + } + } + + /// + /// Gets or sets the selected item. + /// + public T SelectedItem + { + get + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _selectedItem != null ? (T)_selectedItem : default(T); + } + set + { + SelectedIndex = IndexOf(value); + } + } + + /// + /// Gets or sets the selected index. + /// + public int SelectedIndex + { + get + { + AutoSubscription.NotifyObservableListWasAccessed(this); + return _selectedItem != null ? IndexOf((T)_selectedItem) : -1; + } + set + { + if (value < 0 || value >= Count) + { + _selectedItem = null; + return; + } + + int currentIndex = SelectedIndex; + if (currentIndex != value) + { + _selectedItem = this[value]; + if (ListChanged != null) + { + ListChanged(this, new ListChangedEventArgs { ListChangeAction = ListChangeAction.Select, StartIndex = value, EndIndex = value }); + } + } + } + } + + #endregion + } +} diff --git a/Source/Assets/MarkLight/Source/Plugins/ViewField.cs b/Source/Assets/MarkLight/Source/Plugins/ViewField.cs index 187c005..6e6043a 100644 --- a/Source/Assets/MarkLight/Source/Plugins/ViewField.cs +++ b/Source/Assets/MarkLight/Source/Plugins/ViewField.cs @@ -1,106 +1,156 @@ -#region Using Statements -using System; -using System.Collections.Generic; -using UnityEngine; -using UnityEngine.EventSystems; -#endregion - -namespace MarkLight -{ - /// - /// Generic base class for dependency view fields. - /// - public class ViewField : ViewFieldBase - { - #region Fields - - public T _internalValue; - - #endregion - - #region Properties - - /// - /// Gets or sets view field notifying observers if the value has changed. - /// - public T Value - { - get - { - if (ParentView != null && IsMapped) - { - return (T)ParentView.GetValue(ViewFieldPath); - } - - return _internalValue; - } - set - { - if (ParentView != null) - { - ParentView.SetValue(ViewFieldPath, value); - } - else - { - InternalValue = value; - _isSet = true; - } - } - } - - /// - /// Sets view field directly without notifying observers that the value has changed. - /// - public T DirectValue - { - set - { - if (ParentView != null && IsMapped) - { - ParentView.SetValue(ViewFieldPath, value, true, null, null, false); - } - else - { - _internalValue = value; - _isSet = true; - } - } - } - - /// - /// Gets boolean indicating if the value has been set. - /// - public bool IsSet - { - get - { - if (ParentView != null) - { - return ParentView.IsSet(ViewFieldPath); - } - else - { - return _isSet; - } - } - } - - /// - /// Gets or sets internal value without considering mappings and without notifying observers. - /// - public T InternalValue - { - get - { - return _internalValue; - } - set - { - _internalValue = value; - TriggerValueSet(); - } - } - - #endregion - } -} +#region Using Statements +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.EventSystems; +#endregion + +namespace MarkLight +{ + /// + /// Generic base class for dependency view fields. + /// + public class ViewField : ViewFieldBase + { + #region Fields + + public T _internalValue; + + #endregion + + #region Properties + + /// + /// Gets or sets view field notifying observers if the value has changed. + /// + public virtual T Value + { + get + { + AutoSubscription.NotifyViewFieldWasAccessed(this); + if (ParentView != null && IsMapped) + { + return (T)ParentView.GetValue(ViewFieldPath); + } + + return _internalValue; + } + set + { + if (ParentView != null) + { + ParentView.SetValue(ViewFieldPath, value); + } + else + { + InternalValue = value; + _isSet = true; + } + } + } + + /// + /// Gets or sets view field notifying observers if the value has changed. + /// + public object ObjectValue + { + get + { + if (ParentView != null && IsMapped) + { + return ParentView.GetValue(ViewFieldPath); + } + + return _internalValue; + } + set + { + if (ParentView != null) + { + ParentView.SetValue(ViewFieldPath, value); + } + else + { + InternalValue = (T)value; + _isSet = true; + } + } + } + + /// + /// Sets view field directly without notifying observers that the value has changed. + /// + public T DirectValue + { + set + { + if (ParentView != null && IsMapped) + { + ParentView.SetValue(ViewFieldPath, value, true, null, null, false); + } + else + { + _internalValue = value; + _isSet = true; + } + } + } + + /// + /// Sets view field directly without notifying observers that the value has changed. + /// + public object DirectObjectValue + { + set + { + if (ParentView != null && IsMapped) + { + ParentView.SetValue(ViewFieldPath, value, true, null, null, false); + } + else + { + _internalValue = (T)value; + _isSet = true; + } + } + } + + /// + /// Gets boolean indicating if the value has been set. + /// + public bool IsSet + { + get + { + AutoSubscription.NotifyViewFieldWasAccessed(this); + if (ParentView != null) + { + return ParentView.IsSet(ViewFieldPath); + } + else + { + return _isSet; + } + } + } + + /// + /// Gets or sets internal value without considering mappings and without notifying observers. + /// + public virtual T InternalValue + { + get + { + AutoSubscription.NotifyViewFieldWasAccessed(this); + return _internalValue; + } + set + { + _internalValue = value; + TriggerValueSet(); + } + } + + #endregion + } +}