namespace UIWidgets { using System; using System.Collections.Generic; using System.ComponentModel; using UnityEngine; /// /// Tree node. /// /// Type of node item. public class TreeNode : IObservable, INotifyPropertyChanged, IEquatable> { /// /// 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; /// /// Is node visible? /// /// true if this node is visible; otherwise, false. public bool IsVisible { get { return isVisible; } set { if (isVisible != value) { isVisible = value; NotifyPropertyChanged("IsVisible"); } } } [SerializeField] bool isExpanded; /// /// Is node expanded? /// /// true if this node is expanded; otherwise, false. public bool IsExpanded { get { return isExpanded; } set { if (isExpanded != value) { isExpanded = value; NotifyPropertyChanged("IsExpanded"); } } } [SerializeField] TItem item; /// /// Gets or sets the item. /// /// The item. public TItem Item { get { return item; } set { item = value; NotifyPropertyChanged("Item"); } } [SerializeField] ObservableList> nodes; /// /// Gets or sets the nodes. /// /// The nodes. public ObservableList> Nodes { get { return nodes; } set { if (nodes != null) { nodes.OnChange -= NotifyPropertyChanged; nodes.OnCollectionChange -= CollectionChanged; for (int i = nodes.Count - 1; i >= 0; i--) { nodes[i].Parent = null; } } nodes = value; if (nodes != null) { CollectionChanged(); nodes.OnChange += NotifyPropertyChanged; nodes.OnCollectionChange += CollectionChanged; } NotifyPropertyChanged("Nodes"); } } /// /// Gets the total nodes count. /// /// The total nodes count. public int TotalNodesCount { get { if (nodes == null) { return 1; } var total = 1; for (int i = 0; i < nodes.Count; i++) { total += nodes[i].TotalNodesCount; } return total; } } /// /// 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; } var total = UsedNodesCount; for (int i = 0; i < nodes.Count; i++) { total += nodes[i].AllUsedNodesCount; } return total; } } WeakReference parent; /// /// Gets or sets the parent (excluding TreeView.RootNode). /// /// The parent. public TreeNode Parent { get { var result = RealParent; if ((result != null) && !IsNull(result.Item)) { return result; } return null; } set { SetParentValue(value); } } /// /// Gets the real parent (including TreeView.RootNode). /// /// The real parent. public TreeNode RealParent { get { if ((parent != null) && parent.IsAlive) { return parent.Target as TreeNode; } return null; } } /// /// Gets the all parent node, current parent node in first. /// /// The path. 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) && IsNull(result[last].Item)) { result.RemoveAt(last); } return result; } } /// /// Gets the root node. Use object.ReferenceEquals to check if nodes belong to same tree. /// /// The root node. public TreeNode RootNode { get { var current_parent = RealParent; while (current_parent != null) { if (current_parent.RealParent == null) { break; } current_parent = current_parent.RealParent; } return current_parent; } } /// /// The index in TreeView, used for optimization purpose. /// public int Index = -1; /// /// 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) { if (node == null) { return false; } 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 (newParent == null) { return false; } if (this == newParent) { return false; } return !IsParentOfNode(newParent); } void SetParentValue(TreeNode newParent) { var old_parent = RealParent; if (old_parent == 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 (old_parent != null) { old_parent.nodes.OnCollectionChange -= old_parent.CollectionChanged; old_parent.nodes.Remove(this); old_parent.nodes.OnCollectionChange += old_parent.CollectionChanged; } if (newParent != null) { parent = new WeakReference(newParent); if (newParent.nodes == null) { newParent.nodes = new ObservableList>(); newParent.nodes.OnChange += newParent.NotifyPropertyChanged; newParent.nodes.OnCollectionChange += newParent.CollectionChanged; } newParent.nodes.OnCollectionChange -= newParent.CollectionChanged; newParent.nodes.Add(this); newParent.nodes.OnCollectionChange += newParent.CollectionChanged; } else { parent = null; } } /// /// 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, ObservableList> nodeNodes = null, bool nodeIsExpanded = false, bool nodeIsVisible = true) { item = nodeItem; nodes = nodeNodes; isExpanded = nodeIsExpanded; isVisible = nodeIsVisible; if (nodes != null) { nodes.OnChange += NotifyPropertyChanged; nodes.OnCollectionChange += CollectionChanged; CollectionChanged(); } } /// /// TItem is value type. /// private static readonly bool IsValueType; static TreeNode() { IsValueType = typeof(TItem).IsValueType; } /// /// Check if item is null. /// /// Item. /// true if item is null; otherwise false. protected static bool IsNull(TItem item) { if (IsValueType) { return false; } return item == null; } /// /// Initializes a new instance of the class. /// /// Node. /// List of serialized nodes. public TreeNode(TreeNodeSerialized node, List> serializedNodes) { PauseObservation = node.PauseObservation; isVisible = node.IsVisible; isExpanded = node.IsExpanded; item = node.Item; nodes = new ObservableList>(); for (int i = 0; i < node.SubNodesCount; i++) { nodes.Add(new TreeNode(serializedNodes[node.FirstSubNodeIndex + i], serializedNodes)); } nodes.OnChange += NotifyPropertyChanged; nodes.OnCollectionChange += CollectionChanged; } /// /// Finalizes an instance of the class. /// ~TreeNode() { if (nodes != null) { nodes.OnChange -= NotifyPropertyChanged; nodes.OnCollectionChange -= CollectionChanged; } } void CollectionChanged() { if ((nodes == null) || (nodes.Count == 0)) { return; } nodes.BeginUpdate(); foreach (var node in nodes) { var node_parent = node.Parent; if (node_parent != this) { if ((node_parent != null) && (node_parent.Nodes != null)) { if (ReferenceEquals(nodes, node_parent.Nodes)) { node_parent.Nodes = new ObservableList>(); } else { node_parent.Nodes.Remove(node); } } node.parent = new WeakReference(this); } } nodes.EndUpdate(); } void NotifyPropertyChanged() { NotifyPropertyChanged("Nodes"); } void NotifyPropertyChanged(string propertyName) { if (PauseObservation) { return; } var c_handlers = OnChange; if (c_handlers != null) { c_handlers(); } var handlers = PropertyChanged; if (handlers != null) { handlers(this, new PropertyChangedEventArgs(propertyName)); } } /// /// Remove this node from tree. /// public void RemoveFromTree() { Parent = null; } /// /// 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(object other) { if (other is TreeNode) { return Equals((TreeNode)other); } return false; } /// /// 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 virtual bool Equals(TreeNode other) { return ReferenceEquals(this, other); } /// /// Returns true if the nodes items are equal, false otherwise. /// /// The first object. /// The second object. /// true if the objects equal; otherwise, false. public static bool operator ==(TreeNode a, TreeNode b) { if (ReferenceEquals(a, null)) { return ReferenceEquals(b, null); } return a.Equals(b); } /// /// Returns true if the nodes items are not equal, false otherwise. /// /// The first object. /// The second object. /// true if the objects not equal; otherwise, false. public static bool operator !=(TreeNode a, TreeNode b) { return !(a == b); } /// /// 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 nodes != null ? nodes.GetHashCode() : 0; } /// /// Serialize the specified nodes. /// /// Nodes. /// Flat list of nodes. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "Reviewed.")] public static List> Serialize(ObservableList> nodes) { if (nodes == null) { return new List>(); } var serializedNodes = new List>(); Serialize(nodes, serializedNodes, 0); return serializedNodes; } /// /// Serialize the specified nodes. /// /// Nodes. /// List of serialized nodes. /// Depth. /// Flat list of nodes. protected static List> Serialize(ObservableList> nodes, List> serializedNodes, int depth) { foreach (var node in nodes) { serializedNodes.Add(new TreeNodeSerialized(node, serializedNodes.Count + 1, depth)); if (node.Nodes != null) { Serialize(node.Nodes, serializedNodes, depth + 1); } } return serializedNodes; } /// /// De-serialize the specified nodes. /// /// Serialized nodes. /// De-serialized nodes. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "Reviewed.")] public static ObservableList> Deserialize(List> serializedNodes) { var result = new ObservableList>(); if (serializedNodes == null) { return result; } foreach (var serialized_node in serializedNodes) { if (serialized_node.Depth == 0) { result.Add(new TreeNode(serialized_node, serializedNodes)); } } return result; } /// /// Convert this instance to string. /// /// String. public override string ToString() { return string.Format("TreeNode<{0}>(Item = {1})", UtilitiesEditor.GetFriendlyTypeName(typeof(TItem)), IsNull(item) ? "null" : item.ToString()); } } }