You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
476 lines
11 KiB
476 lines
11 KiB
3 years ago
|
using UnityEngine;
|
||
|
using System.Linq;
|
||
|
using System.Collections.Generic;
|
||
|
using System;
|
||
|
using System.ComponentModel;
|
||
|
|
||
|
namespace UIWidgets {
|
||
|
/// <summary>
|
||
|
/// Tree node.
|
||
|
/// </summary>
|
||
|
[Serializable]
|
||
|
public class TreeNode<TItem> : IObservable, IDisposable, INotifyPropertyChanged
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Occurs when on change.
|
||
|
/// </summary>
|
||
|
public event OnChange OnChange;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Occurs when a property value changes.
|
||
|
/// </summary>
|
||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||
|
|
||
|
/// <summary>
|
||
|
/// The pause observation.
|
||
|
/// </summary>
|
||
|
public bool PauseObservation;
|
||
|
|
||
|
[SerializeField]
|
||
|
bool isVisible = true;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets or sets a value indicating whether this instance is visible.
|
||
|
/// </summary>
|
||
|
/// <value><c>true</c> if this instance is visible; otherwise, <c>false</c>.</value>
|
||
|
public bool IsVisible {
|
||
|
get {
|
||
|
return isVisible;
|
||
|
}
|
||
|
set {
|
||
|
isVisible = value;
|
||
|
Changed("IsVisible");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[SerializeField]
|
||
|
bool isExpanded;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets or sets a value indicating whether this instance is expanded.
|
||
|
/// </summary>
|
||
|
/// <value><c>true</c> if this instance is expanded; otherwise, <c>false</c>.</value>
|
||
|
public bool IsExpanded {
|
||
|
get {
|
||
|
return isExpanded;
|
||
|
}
|
||
|
set {
|
||
|
isExpanded = value;
|
||
|
Changed("IsExpanded");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[SerializeField]
|
||
|
TItem item;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets or sets the item.
|
||
|
/// </summary>
|
||
|
/// <value>The item.</value>
|
||
|
public TItem Item {
|
||
|
get {
|
||
|
return item;
|
||
|
}
|
||
|
set {
|
||
|
item = value;
|
||
|
Changed("Item");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[SerializeField]
|
||
|
IObservableList<TreeNode<TItem>> nodes;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets or sets the nodes.
|
||
|
/// </summary>
|
||
|
/// <value>The nodes.</value>
|
||
|
public IObservableList<TreeNode<TItem>> 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");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the total nodes count.
|
||
|
/// </summary>
|
||
|
/// <value>The total nodes count.</value>
|
||
|
public int TotalNodesCount {
|
||
|
get {
|
||
|
if (nodes==null)
|
||
|
{
|
||
|
return 1;
|
||
|
}
|
||
|
return nodes.Sum(x => x.TotalNodesCount) + 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// The used nodes count.
|
||
|
/// </summary>
|
||
|
public int UsedNodesCount;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets all used nodes count.
|
||
|
/// </summary>
|
||
|
/// <value>All used nodes count.</value>
|
||
|
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;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets or sets the parent.
|
||
|
/// </summary>
|
||
|
/// <value>The parent.</value>
|
||
|
public TreeNode<TItem> Parent {
|
||
|
get {
|
||
|
if ((parent!=null) && (parent.IsAlive))
|
||
|
{
|
||
|
return parent.Target as TreeNode<TItem>;
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
set {
|
||
|
SetParentValue(value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public List<TreeNode<TItem>> Path {
|
||
|
get {
|
||
|
var result = new List<TreeNode<TItem>>();
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Determines whether this instance is parent of node the specified node.
|
||
|
/// </summary>
|
||
|
/// <returns><c>true</c> if this instance is parent of node the specified node; otherwise, <c>false</c>.</returns>
|
||
|
/// <param name="node">Node.</param>
|
||
|
public bool IsParentOfNode(TreeNode<TItem> node)
|
||
|
{
|
||
|
var nodeParent = node.Parent;
|
||
|
while (nodeParent != null)
|
||
|
{
|
||
|
if (nodeParent==this)
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
nodeParent = nodeParent.Parent;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Determines whether this instance can be child of the specified newParent.
|
||
|
/// </summary>
|
||
|
/// <returns><c>true</c> if this instance can be child of the specified newParent; otherwise, <c>false</c>.</returns>
|
||
|
/// <param name="newParent">New parent.</param>
|
||
|
public bool CanBeParent(TreeNode<TItem> newParent)
|
||
|
{
|
||
|
if (this==newParent)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
return !IsParentOfNode(newParent);
|
||
|
}
|
||
|
|
||
|
void SetParentValue(TreeNode<TItem> newParent)
|
||
|
{
|
||
|
var oldParent = ((parent!=null) && (parent.IsAlive)) ? parent.Target as TreeNode<TItem> : 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<TreeNode<TItem>>();
|
||
|
|
||
|
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();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Initializes a new instance of the class.
|
||
|
/// </summary>
|
||
|
/// <param name="nodeItem">Node item.</param>
|
||
|
/// <param name="nodeNodes">Node nodes.</param>
|
||
|
/// <param name="nodeIsExpanded">If set to <c>true</c> node is expanded.</param>
|
||
|
/// <param name="nodeIsVisible">If set to <c>true</c> node is visible.</param>
|
||
|
public TreeNode(TItem nodeItem,
|
||
|
IObservableList<TreeNode<TItem>> 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<TItem> 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));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Determines whether the specified object is equal to the current object.
|
||
|
/// </summary>
|
||
|
/// <param name="obj">The object to compare with the current object.</param>
|
||
|
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, <c>false</c>.</returns>
|
||
|
public override bool Equals(System.Object obj)
|
||
|
{
|
||
|
var nodeObj = obj as TreeNode<TItem>;
|
||
|
if (nodeObj==null)
|
||
|
{
|
||
|
return this==null;
|
||
|
}
|
||
|
if (this==null)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
return item.Equals(nodeObj.item);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Serves as a hash function for a particular type.
|
||
|
/// </summary>
|
||
|
/// <returns>A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table.</returns>
|
||
|
public override int GetHashCode()
|
||
|
{
|
||
|
return base.GetHashCode();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns true if the nodes items are equal, false otherwise.
|
||
|
/// </summary>
|
||
|
/// <param name="a">The alpha component.</param>
|
||
|
/// <param name="b">The blue component.</param>
|
||
|
public static bool operator ==(TreeNode<TItem> a, TreeNode<TItem> 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);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns true if the nodes items are not equal, false otherwise.
|
||
|
/// </summary>
|
||
|
/// <param name="a">The alpha component.</param>
|
||
|
/// <param name="b">The blue component.</param>
|
||
|
public static bool operator !=(TreeNode<TItem> a, TreeNode<TItem> 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;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||
|
/// </summary>
|
||
|
/// <remarks>Call <see cref="Dispose"/> when you are finished using the <see cref="UIWidgets.ObservableList`1"/>. The <see cref="Dispose"/> method leaves the <see cref="UIWidgets.ObservableList`1"/> in an unusable state. After calling <see cref="Dispose"/>, you must release all references to the <see cref="UIWidgets.ObservableList`1"/> so the garbage collector can reclaim the memory that the <see cref="UIWidgets.ObservableList`1"/> was occupying.</remarks>
|
||
|
public void Dispose()
|
||
|
{
|
||
|
Dispose(true);
|
||
|
GC.SuppressFinalize(this);
|
||
|
}
|
||
|
|
||
|
void DisposeItem(TreeNode<TItem> node)
|
||
|
{
|
||
|
node.OnChange -= Changed;
|
||
|
node.Dispose();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Dispose.
|
||
|
/// </summary>
|
||
|
/// <param name="disposing">Free other state (managed objects).</param>
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
}
|