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());
}
}
}