From c0e09ae7921bb3081ddf16f03827f5227c3b687b Mon Sep 17 00:00:00 2001 From: mrpmorris Date: Mon, 10 Oct 2016 21:29:55 +0100 Subject: [PATCH 1/3] Code-Calculated View Fields --- .../Source/Plugins/AutoSubscription.cs | 51 +++++++ .../Source/Plugins/CalculatedViewField.cs | 134 ++++++++++++++++++ .../Source/Plugins/IAutoSubscriber.cs | 7 + .../MarkLight/Source/Plugins/ViewField.cs | 11 +- 4 files changed, 199 insertions(+), 4 deletions(-) create mode 100644 Source/Assets/MarkLight/Source/Plugins/AutoSubscription.cs create mode 100644 Source/Assets/MarkLight/Source/Plugins/CalculatedViewField.cs create mode 100644 Source/Assets/MarkLight/Source/Plugins/IAutoSubscriber.cs diff --git a/Source/Assets/MarkLight/Source/Plugins/AutoSubscription.cs b/Source/Assets/MarkLight/Source/Plugins/AutoSubscription.cs new file mode 100644 index 0000000..618fa56 --- /dev/null +++ b/Source/Assets/MarkLight/Source/Plugins/AutoSubscription.cs @@ -0,0 +1,51 @@ +using UnityEngine; +using System.Collections.Generic; + +namespace MarkLight +{ + public static class AutoSubscription + { + static Queue AutoSubscriberQueue = new Queue(); + 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.Enqueue(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 queue"); + + AutoSubscribers.Remove(subscriber); + AutoSubscriberQueue.Dequeue(); + } + + public static void NotifyViewFieldWasAccessed(ViewFieldBase field) + { + if (AutoSubscriberQueue.Count == 0) + return; + + Debug.Log("Notifying subscriber a view field was accessed"); + IAutoSubscriber topmostSubscriber = AutoSubscriberQueue.Peek(); + topmostSubscriber.ViewFieldWasAccessed(field); + } + + + } +} diff --git a/Source/Assets/MarkLight/Source/Plugins/CalculatedViewField.cs b/Source/Assets/MarkLight/Source/Plugins/CalculatedViewField.cs new file mode 100644 index 0000000..d51ab2e --- /dev/null +++ b/Source/Assets/MarkLight/Source/Plugins/CalculatedViewField.cs @@ -0,0 +1,134 @@ +using System; +using UnityEngine; + +namespace MarkLight +{ + public class CalculatedViewField : ViewField, IAutoSubscriber + { + bool CachedValueIsValid = false; + System.Collections.Generic.HashSet FieldsSubscribedTo = new System.Collections.Generic.HashSet(); + public System.Func GetCalculatedValue; + public System.Action UpdateSourceValuesFromValue; + + public override T InternalValue + { + get + { + return GetValue(); + } + + set + { + SetValue(value); + base.InternalValue = value; + } + } + + public override T DirectValue + { + set + { + SetValue(value); + base.DirectValue = value; + } + } + + public override object DirectObjectValue + { + set + { + SetValue((T)value); + base.DirectObjectValue = value; + } + } + + public override object ObjectValue + { + get + { + return GetValue(); + } + + set + { + SetValue((T)value); + base.ObjectValue = value; + } + } + + public override bool IsSet + { + get + { + return true; + } + } + + public override T Value + { + get + { + return GetValue(); + } + + set + { + SetValue(value); + base.Value = value; + } + } + + T GetValue() + { + Debug.Log("Calculating value"); + if (GetCalculatedValue == null) + throw new System.InvalidOperationException("GetCalculatedValue has not been specifed"); + + T calculatedValue; + if (CachedValueIsValid) + return InternalValue; + try + { + ClearChangeSubscriptions(); + AutoSubscription.StartSubscription(this); + calculatedValue = GetCalculatedValue(); + } + finally + { + AutoSubscription.EndSubscription(this); + } + CachedValueIsValid = true; + base.Value = calculatedValue; + return calculatedValue; + } + + void SetValue(T value) + { + if (UpdateSourceValuesFromValue == null) + return; + UpdateSourceValuesFromValue(value); + CachedValueIsValid = false; + } + + void IAutoSubscriber.ViewFieldWasAccessed(ViewFieldBase viewField) + { + if (!FieldsSubscribedTo.Contains(viewField)) + viewField.ValueSet += ViewField_ValueSet; + } + + void ViewField_ValueSet(object sender, EventArgs e) + { + Debug.Log("A subscribed-to member's value changed"); + CachedValueIsValid = false; + base.InternalValue = default(T); + } + + void ClearChangeSubscriptions() + { + Debug.Log("Unsubscribing from " + FieldsSubscribedTo.Count + " fields"); + foreach (ViewFieldBase notifier in FieldsSubscribedTo) + notifier.ValueSet -= ViewField_ValueSet; + FieldsSubscribedTo.Clear(); + } + } +} \ 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..2e8c89b --- /dev/null +++ b/Source/Assets/MarkLight/Source/Plugins/IAutoSubscriber.cs @@ -0,0 +1,7 @@ +namespace MarkLight +{ + public interface IAutoSubscriber + { + void ViewFieldWasAccessed(ViewFieldBase viewField); + } +} diff --git a/Source/Assets/MarkLight/Source/Plugins/ViewField.cs b/Source/Assets/MarkLight/Source/Plugins/ViewField.cs index 187c005..832a606 100644 --- a/Source/Assets/MarkLight/Source/Plugins/ViewField.cs +++ b/Source/Assets/MarkLight/Source/Plugins/ViewField.cs @@ -23,10 +23,11 @@ public class ViewField : ViewFieldBase /// /// Gets or sets view field notifying observers if the value has changed. /// - public T Value + public virtual T Value { get { + AutoSubscription.NotifyViewFieldWasAccessed(this); if (ParentView != null && IsMapped) { return (T)ParentView.GetValue(ViewFieldPath); @@ -51,7 +52,7 @@ public T Value /// /// Sets view field directly without notifying observers that the value has changed. /// - public T DirectValue + public virtual T DirectValue { set { @@ -70,10 +71,11 @@ public T DirectValue /// /// Gets boolean indicating if the value has been set. /// - public bool IsSet + public virtual bool IsSet { get { + AutoSubscription.NotifyViewFieldWasAccessed(this); if (ParentView != null) { return ParentView.IsSet(ViewFieldPath); @@ -88,10 +90,11 @@ public bool IsSet /// /// Gets or sets internal value without considering mappings and without notifying observers. /// - public T InternalValue + public virtual T InternalValue { get { + AutoSubscription.NotifyViewFieldWasAccessed(this); return _internalValue; } set From cc11dff59e290cb8f47a31b4756c3844f523d6d4 Mon Sep 17 00:00:00 2001 From: "HOL\\peter.morris" Date: Tue, 11 Oct 2016 16:23:52 +0100 Subject: [PATCH 2/3] Completed --- .../Source/Plugins/AutoSubscription.cs | 101 ++++--- .../Source/Plugins/CalculatedViewField.cs | 240 +++++++--------- .../Source/Plugins/IAutoSubscriber.cs | 12 +- .../MarkLight/Source/Plugins/ViewField.cs | 265 +++++++++++------- 4 files changed, 319 insertions(+), 299 deletions(-) diff --git a/Source/Assets/MarkLight/Source/Plugins/AutoSubscription.cs b/Source/Assets/MarkLight/Source/Plugins/AutoSubscription.cs index 618fa56..1c82b59 100644 --- a/Source/Assets/MarkLight/Source/Plugins/AutoSubscription.cs +++ b/Source/Assets/MarkLight/Source/Plugins/AutoSubscription.cs @@ -1,51 +1,50 @@ -using UnityEngine; -using System.Collections.Generic; - -namespace MarkLight -{ - public static class AutoSubscription - { - static Queue AutoSubscriberQueue = new Queue(); - 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.Enqueue(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 queue"); - - AutoSubscribers.Remove(subscriber); - AutoSubscriberQueue.Dequeue(); - } - - public static void NotifyViewFieldWasAccessed(ViewFieldBase field) - { - if (AutoSubscriberQueue.Count == 0) - return; - - Debug.Log("Notifying subscriber a view field was accessed"); - IAutoSubscriber topmostSubscriber = AutoSubscriberQueue.Peek(); - topmostSubscriber.ViewFieldWasAccessed(field); - } - - - } -} +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); + } + + + } +} diff --git a/Source/Assets/MarkLight/Source/Plugins/CalculatedViewField.cs b/Source/Assets/MarkLight/Source/Plugins/CalculatedViewField.cs index d51ab2e..acdc7eb 100644 --- a/Source/Assets/MarkLight/Source/Plugins/CalculatedViewField.cs +++ b/Source/Assets/MarkLight/Source/Plugins/CalculatedViewField.cs @@ -1,134 +1,108 @@ -using System; -using UnityEngine; - -namespace MarkLight -{ - public class CalculatedViewField : ViewField, IAutoSubscriber - { - bool CachedValueIsValid = false; - System.Collections.Generic.HashSet FieldsSubscribedTo = new System.Collections.Generic.HashSet(); - public System.Func GetCalculatedValue; - public System.Action UpdateSourceValuesFromValue; - - public override T InternalValue - { - get - { - return GetValue(); - } - - set - { - SetValue(value); - base.InternalValue = value; - } - } - - public override T DirectValue - { - set - { - SetValue(value); - base.DirectValue = value; - } - } - - public override object DirectObjectValue - { - set - { - SetValue((T)value); - base.DirectObjectValue = value; - } - } - - public override object ObjectValue - { - get - { - return GetValue(); - } - - set - { - SetValue((T)value); - base.ObjectValue = value; - } - } - - public override bool IsSet - { - get - { - return true; - } - } - - public override T Value - { - get - { - return GetValue(); - } - - set - { - SetValue(value); - base.Value = value; - } - } - - T GetValue() - { - Debug.Log("Calculating value"); - if (GetCalculatedValue == null) - throw new System.InvalidOperationException("GetCalculatedValue has not been specifed"); - - T calculatedValue; - if (CachedValueIsValid) - return InternalValue; - try - { - ClearChangeSubscriptions(); - AutoSubscription.StartSubscription(this); - calculatedValue = GetCalculatedValue(); - } - finally - { - AutoSubscription.EndSubscription(this); - } - CachedValueIsValid = true; - base.Value = calculatedValue; - return calculatedValue; - } - - void SetValue(T value) - { - if (UpdateSourceValuesFromValue == null) - return; - UpdateSourceValuesFromValue(value); - CachedValueIsValid = false; - } - - void IAutoSubscriber.ViewFieldWasAccessed(ViewFieldBase viewField) - { - if (!FieldsSubscribedTo.Contains(viewField)) - viewField.ValueSet += ViewField_ValueSet; - } - - void ViewField_ValueSet(object sender, EventArgs e) - { - Debug.Log("A subscribed-to member's value changed"); - CachedValueIsValid = false; - base.InternalValue = default(T); - } - - void ClearChangeSubscriptions() - { - Debug.Log("Unsubscribing from " + FieldsSubscribedTo.Count + " fields"); - foreach (ViewFieldBase notifier in FieldsSubscribedTo) - notifier.ValueSet -= ViewField_ValueSet; - FieldsSubscribedTo.Clear(); - } - } +using System; +using UnityEngine; + +namespace MarkLight +{ + [Serializable] + public class CalculatedViewField : ViewField, IAutoSubscriber + { + bool IsCalculating; + bool CachedValueIsValid; + [System.NonSerialized] + System.Collections.Generic.HashSet FieldsSubscribedTo = new System.Collections.Generic.HashSet(); + [System.NonSerialized] + public System.Func GetCalculatedValue; + [System.NonSerialized] + 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 ViewField_ValueSet(object sender, EventArgs e) + { + CachedValueIsValid = false; + if (!IsCalculating) + UpdateCachedValue(); + } + + void ClearChangeSubscriptions() + { + foreach (ViewFieldBase notifier in FieldsSubscribedTo) + notifier.ValueSet -= ViewField_ValueSet; + FieldsSubscribedTo.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 index 2e8c89b..e6897fd 100644 --- a/Source/Assets/MarkLight/Source/Plugins/IAutoSubscriber.cs +++ b/Source/Assets/MarkLight/Source/Plugins/IAutoSubscriber.cs @@ -1,7 +1,7 @@ -namespace MarkLight -{ - public interface IAutoSubscriber - { - void ViewFieldWasAccessed(ViewFieldBase viewField); - } +namespace MarkLight +{ + public interface IAutoSubscriber + { + void ViewFieldWasAccessed(ViewFieldBase viewField); + } } diff --git a/Source/Assets/MarkLight/Source/Plugins/ViewField.cs b/Source/Assets/MarkLight/Source/Plugins/ViewField.cs index 832a606..6e6043a 100644 --- a/Source/Assets/MarkLight/Source/Plugins/ViewField.cs +++ b/Source/Assets/MarkLight/Source/Plugins/ViewField.cs @@ -1,109 +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 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; - } - } - } - - /// - /// Sets view field directly without notifying observers that the value has changed. - /// - public virtual 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 virtual 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 - } -} +#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 + } +} From 10cb12bad00e03cf7410b36697c880bb77b72747 Mon Sep 17 00:00:00 2001 From: "HOL\\peter.morris" Date: Tue, 11 Oct 2016 17:46:59 +0100 Subject: [PATCH 3/3] Subscribe to observable lists --- .../Source/Plugins/AutoSubscription.cs | 9 + .../Source/Plugins/CalculatedViewField.cs | 27 +- .../Source/Plugins/IAutoSubscriber.cs | 1 + .../Source/Plugins/ObservableList.cs | 1676 +++++++++-------- 4 files changed, 888 insertions(+), 825 deletions(-) diff --git a/Source/Assets/MarkLight/Source/Plugins/AutoSubscription.cs b/Source/Assets/MarkLight/Source/Plugins/AutoSubscription.cs index 1c82b59..ea3c13c 100644 --- a/Source/Assets/MarkLight/Source/Plugins/AutoSubscription.cs +++ b/Source/Assets/MarkLight/Source/Plugins/AutoSubscription.cs @@ -45,6 +45,15 @@ public static void NotifyViewFieldWasAccessed(ViewFieldBase field) 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 index acdc7eb..b2d6cc1 100644 --- a/Source/Assets/MarkLight/Source/Plugins/CalculatedViewField.cs +++ b/Source/Assets/MarkLight/Source/Plugins/CalculatedViewField.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using UnityEngine; namespace MarkLight @@ -8,11 +9,9 @@ public class CalculatedViewField : ViewField, IAutoSubscriber { bool IsCalculating; bool CachedValueIsValid; - [System.NonSerialized] - System.Collections.Generic.HashSet FieldsSubscribedTo = new System.Collections.Generic.HashSet(); - [System.NonSerialized] + HashSet FieldsSubscribedTo = new HashSet(); + HashSet ObservableListsSubscribedTo = new HashSet(); public System.Func GetCalculatedValue; - [System.NonSerialized] public System.Action UpdateSourceValuesFromValue; public override T InternalValue @@ -88,6 +87,22 @@ void IAutoSubscriber.ViewFieldWasAccessed(ViewFieldBase 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; @@ -100,6 +115,10 @@ void ClearChangeSubscriptions() foreach (ViewFieldBase notifier in FieldsSubscribedTo) notifier.ValueSet -= ViewField_ValueSet; FieldsSubscribedTo.Clear(); + + foreach (IObservableList notifier in ObservableListsSubscribedTo) + notifier.ListChanged -= List_ListChanged; + ObservableListsSubscribedTo.Clear(); } } diff --git a/Source/Assets/MarkLight/Source/Plugins/IAutoSubscriber.cs b/Source/Assets/MarkLight/Source/Plugins/IAutoSubscriber.cs index e6897fd..722a460 100644 --- a/Source/Assets/MarkLight/Source/Plugins/IAutoSubscriber.cs +++ b/Source/Assets/MarkLight/Source/Plugins/IAutoSubscriber.cs @@ -3,5 +3,6 @@ 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 + } +}