using UnityEngine; using System.Linq; using System.Collections.Generic; using System; using System.ComponentModel; namespace UIWidgets { /// /// Tree node. /// [Serializable] public class TreeNode : IObservable, IDisposable, INotifyPropertyChanged { /// /// Occurs when on change. /// public event OnChange OnChange; /// /// Occurs when a property value changes. /// public event PropertyChangedEventHandler PropertyChanged; /// /// The pause observation. /// public bool PauseObservation; [SerializeField] bool isVisible = true; /// /// Gets or sets a value indicating whether this instance is visible. /// /// true if this instance is visible; otherwise, false. public bool IsVisible { get { return isVisible; } set { isVisible = value; Changed("IsVisible"); } } [SerializeField] bool isExpanded; /// /// Gets or sets a value indicating whether this instance is expanded. /// /// true if this instance is expanded; otherwise, false. public bool IsExpanded { get { return isExpanded; } set { isExpanded = value; Changed("IsExpanded"); } } [SerializeField] TItem item; /// /// Gets or sets the item. /// /// The item. public TItem Item { get { return item; } set { item = value; Changed("Item"); } } [SerializeField] IObservableList> nodes; /// /// Gets or sets the nodes. /// /// The nodes. public IObservableList> Nodes { get { return nodes; } set { if (nodes!=null) { nodes.OnChange -= Changed; nodes.OnCollectionChange -= CollectionChanged; } nodes = value; if (nodes!=null) { nodes.OnChange += Changed; nodes.OnCollectionChange += CollectionChanged; CollectionChanged(); } Changed("Nodes"); } } /// /// Gets the total nodes count. /// /// The total nodes count. public int TotalNodesCount { get { if (nodes==null) { return 1; } return nodes.Sum(x => x.TotalNodesCount) + 1; } } /// /// The used nodes count. /// public int UsedNodesCount; /// /// Gets all used nodes count. /// /// All used nodes count. public int AllUsedNodesCount { get { if (!isVisible) { return 0; } if (!isExpanded) { return 0 + UsedNodesCount; } if (nodes==null) { return 0 + UsedNodesCount; } return nodes.Sum(x => x.AllUsedNodesCount) + UsedNodesCount; } } WeakReference parent; /// /// Gets or sets the parent. /// /// The parent. public TreeNode Parent { get { if ((parent!=null) && (parent.IsAlive)) { return parent.Target as TreeNode; } return null; } set { SetParentValue(value); } } public List> Path { get { var result = new List>(); var current_parent = Parent; while (current_parent!=null) { result.Add(current_parent); current_parent = current_parent.Parent; } var last = result.Count - 1; if ((last>=0) && (result[last].Item==null)) { result.RemoveAt(last); } return result; } } /// /// Determines whether this instance is parent of node the specified node. /// /// true if this instance is parent of node the specified node; otherwise, false. /// Node. public bool IsParentOfNode(TreeNode node) { var nodeParent = node.Parent; while (nodeParent != null) { if (nodeParent==this) { return true; } nodeParent = nodeParent.Parent; } return false; } /// /// Determines whether this instance can be child of the specified newParent. /// /// true if this instance can be child of the specified newParent; otherwise, false. /// New parent. public bool CanBeParent(TreeNode newParent) { if (this==newParent) { return false; } return !IsParentOfNode(newParent); } void SetParentValue(TreeNode newParent) { var oldParent = ((parent!=null) && (parent.IsAlive)) ? parent.Target as TreeNode : null; if (oldParent==newParent) { return ; } if (newParent!=null) { if (newParent==this) { throw new ArgumentException("Node cannot be own parent."); } if (IsParentOfNode(newParent)) { throw new ArgumentException("Own child node cannot be parent node."); } } if (oldParent!=null) { oldParent.nodes.OnCollectionChange -= oldParent.CollectionChanged; oldParent.nodes.Remove(this); oldParent.nodes.OnCollectionChange += oldParent.CollectionChanged; } parent = new WeakReference(newParent); if (newParent!=null) { if (newParent.nodes==null) { newParent.nodes = new ObservableList>(); newParent.nodes.OnChange += newParent.Changed; newParent.nodes.OnCollectionChange += newParent.CollectionChanged; } newParent.nodes.OnCollectionChange -= newParent.CollectionChanged; newParent.nodes.Add(this); newParent.nodes.OnCollectionChange += newParent.CollectionChanged; } //Changed(); } /// /// Initializes a new instance of the class. /// /// Node item. /// Node nodes. /// If set to true node is expanded. /// If set to true node is visible. public TreeNode(TItem nodeItem, IObservableList> nodeNodes = null, bool nodeIsExpanded = false, bool nodeIsVisible = true) { item = nodeItem; nodes = nodeNodes; isExpanded = nodeIsExpanded; isVisible = nodeIsVisible; if (nodes!=null) { nodes.OnChange += Changed; nodes.OnCollectionChange += CollectionChanged; CollectionChanged(); } } void CollectionChanged() { if (nodes==null) { return ; } nodes.ForEach(SetParent); } void SetParent(TreeNode node) { if ((node.Parent!=null) && (node.Parent!=this)) { node.Parent.nodes.Remove(node); } node.parent = new WeakReference(this); } void Changed() { Changed("Nodes"); } void Changed(string propertyName) { if (PauseObservation) { return ; } if (OnChange!=null) { OnChange(); } if (PropertyChanged!=null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } /// /// Determines whether the specified object is equal to the current object. /// /// The object to compare with the current object. /// true if the specified object is equal to the current object; otherwise, false. public override bool Equals(System.Object obj) { var nodeObj = obj as TreeNode; if (nodeObj==null) { return this==null; } if (this==null) { return false; } return item.Equals(nodeObj.item); } /// /// Serves as a hash function for a particular type. /// /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table. public override int GetHashCode() { return base.GetHashCode(); } /// /// Returns true if the nodes items are equal, false otherwise. /// /// The alpha component. /// The blue component. public static bool operator ==(TreeNode a, TreeNode b) { var a_null = object.ReferenceEquals(null, a); var b_null = object.ReferenceEquals(null, b); if (a_null && b_null) { return true; } if (a_null!=b_null) { return false; } var a_item_null = object.ReferenceEquals(null, a.item); var b_item_null = object.ReferenceEquals(null, b.item); if (a_item_null && b_item_null) { return true; } if (a_item_null!=b_item_null) { return false; } return a.item.Equals(b.item); } /// /// Returns true if the nodes items are not equal, false otherwise. /// /// The alpha component. /// The blue component. public static bool operator !=(TreeNode a, TreeNode b) { var a_null = object.ReferenceEquals(null, a); var b_null = object.ReferenceEquals(null, b); if (a_null && b_null) { return false; } if (a_null!=b_null) { return true; } var a_item_null = object.ReferenceEquals(null, a.item); var b_item_null = object.ReferenceEquals(null, b.item); if (a_item_null && b_item_null) { return false; } if (a_item_null!=b_item_null) { return true; } return !a.item.Equals(b.item); } private bool disposed = false; /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// /// Call when you are finished using the . The method leaves the in an unusable state. After calling , you must release all references to the so the garbage collector can reclaim the memory that the was occupying. public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } void DisposeItem(TreeNode node) { node.OnChange -= Changed; node.Dispose(); } /// /// Dispose. /// /// Free other state (managed objects). protected virtual void Dispose(bool disposing) { if (!disposed) { if (disposing) { // Free other state (managed objects). } if (Nodes!=null) { Nodes.BeginUpdate(); Nodes.ForEach(DisposeItem); Nodes.EndUpdate(); Nodes = null; } // Free your own state (unmanaged objects). // Set large fields to null. disposed = true; } } // Use C# destructor syntax for finalization code. ~TreeNode() { // Simply call Dispose(false). Dispose(false); } } }