namespace UIWidgets
{
using System;
using System.Collections.Generic;
using UIWidgets.Attributes;
using UIWidgets.Extensions;
using UnityEngine;
using UnityEngine.Events;
///
/// TreeViewCustom.
///
/// Type of DefaultItem.
/// Type of item.
public class TreeViewCustom : ListViewCustom>
where TComponent : TreeViewComponentBase
{
///
/// NodeEvent.
///
[Serializable]
public class NodeEvent : UnityEvent>
{
}
ObservableList> nodes;
///
/// Gets or sets the nodes.
///
/// The nodes.
[DataBindField]
public virtual ObservableList> Nodes
{
get
{
return nodes;
}
set
{
if (!isTreeViewCustomInited)
{
Init();
}
RemoveNodes(nodes);
nodes = value;
RootNode.Nodes = value;
ListRenderer.SetPosition(0f);
Refresh();
if (nodes != null)
{
nodes.OnChange += NodesChanged;
}
}
}
///
/// Gets the selected node.
///
/// The selected node.
[DataBindField]
public TreeNode SelectedNode
{
get
{
var n = selectedNodes.Count;
if (n == 0)
{
return null;
}
return selectedNodes[n - 1];
}
}
///
/// NodeToggle event.
///
public NodeEvent NodeToggle = new NodeEvent();
///
/// NodeSelected event.
///
[DataBindEvent("SelectedNode", "SelectedNodes")]
public NodeEvent NodeSelected = new NodeEvent();
///
/// NodeDeselected event.
///
[DataBindEvent("SelectedNode", "SelectedNodes")]
public NodeEvent NodeDeselected = new NodeEvent();
///
/// The selected nodes.
///
protected List> selectedNodes = new List>();
///
/// Gets or sets the selected nodes.
///
/// The selected nodes.
[DataBindField]
public List> SelectedNodes
{
get
{
return new List>(selectedNodes);
}
set
{
selectedNodes.Clear();
Nodes2Indices(value, tempIndices);
SelectedIndices = tempIndices;
tempIndices.Clear();
}
}
[SerializeField]
bool deselectCollapsedNodes = true;
///
/// The deselect collapsed nodes.
///
public bool DeselectCollapsedNodes
{
get
{
return deselectCollapsedNodes;
}
set
{
deselectCollapsedNodes = value;
if (value)
{
selectedNodes.Clear();
var selected = SelectedIndicesList;
foreach (var index in selected)
{
selectedNodes.Add(DataSource[index].Node);
}
}
}
}
///
/// Opened nodes converted to list.
///
protected ObservableList> NodesList = new ObservableList>();
///
/// Scroll with node indent.
///
[SerializeField]
public bool ScrollWithIndent = false;
[NonSerialized]
bool isTreeViewCustomInited = false;
TreeNode rootNode;
///
/// Gets the root node.
///
/// The root node.
protected TreeNode RootNode
{
get
{
return rootNode;
}
}
///
/// List can be looped and items is enough to make looped list.
///
/// true if looped list available; otherwise, false.
public override bool LoopedListAvailable
{
get
{
return false;
}
}
///
/// Require EasyLayout.
///
protected override bool RequireEasyLayout
{
get
{
return true;
}
}
///
/// Cache.
///
protected List> Cache = new List>();
///
/// Delegate of the ToggleNode.
///
protected UnityAction ToggleNodeDelegate;
readonly Queue> tempQueue = new Queue>();
readonly List tempIndices = new List();
///
/// Init this instance.
///
public override void Init()
{
if (isTreeViewCustomInited)
{
return;
}
if (DefaultItem != null)
{
var default_item_layout = DefaultItem.GetComponent();
if ((default_item_layout != null) && !IsHorizontal())
{
default_item_layout.CompactConstraint = EasyLayoutNS.CompactConstraints.MaxRowCount;
default_item_layout.CompactConstraintCount = 1;
}
}
isTreeViewCustomInited = true;
var valid_list_type = (listType == ListViewType.ListViewWithFixedSize)
|| (listType == ListViewType.ListViewWithVariableSize);
if (!valid_list_type)
{
Debug.LogWarning("TreeView does not support TileView mode", this);
listType = ListViewType.ListViewWithFixedSize;
}
rootNode = new TreeNode(default(TItem));
setContentSizeFitter = false;
ToggleNodeDelegate = OnToggleNode;
ItemsEvents.NodeToggleClick.AddListener(ToggleNodeDelegate);
base.Init();
Refresh();
KeepSelection = true;
DataSource = NodesList;
}
///
/// Get selected nodes.
///
/// Output.
public void GetSelectedNodes(List> output)
{
output.AddRange(selectedNodes);
}
///
/// Get ListNode.
///
/// Node.
/// Depth.
/// New ListNode.
protected ListNode GetListNode(TreeNode node, int depth)
{
if (Cache.Count > 0)
{
var ln = Cache.Pop();
ln.Replace(node, depth);
return ln;
}
return new ListNode(node, depth);
}
///
protected override ListViewTypeBase GetRenderer(ListViewType type)
{
switch (type)
{
case ListViewType.ListViewWithFixedSize:
return new ListViewTypeFixed(this);
case ListViewType.ListViewWithVariableSize:
return new ListViewTypeSize(this);
case ListViewType.ListViewEllipse:
throw new NotSupportedException("ListViewType.ListViewEllipse not supported for the TreeView");
case ListViewType.TileViewWithFixedSize:
throw new NotSupportedException("ListViewType.TileViewWithFixedSize not supported for the TreeView");
case ListViewType.TileViewWithVariableSize:
throw new NotSupportedException("ListViewType.TileViewWithVariableSize not supported for the TreeView");
case ListViewType.TileViewStaggered:
throw new NotSupportedException("ListViewType.TileViewStaggered not supported for the TreeView");
default:
throw new NotSupportedException(string.Format("Unknown ListView type: {0}", EnumHelper.ToString(type)));
}
}
///
protected override void SetDirection(ListViewDirection newDirection, bool updateView = true)
{
direction = newDirection;
ContainerAnchoredPosition = Vector2.zero;
if (ListRenderer.IsVirtualizationSupported())
{
LayoutBridge.IsHorizontal = IsHorizontal();
CalculateMaxVisibleItems();
}
if (updateView)
{
UpdateView();
}
}
///
/// Convert nodes tree to list.
///
/// The list.
/// Source nodes.
/// Depth.
/// List.
protected virtual int Nodes2List(IObservableList> sourceNodes, int depth, ObservableList> list)
{
var added_nodes = 0;
for (var i = 0; i < sourceNodes.Count; i++)
{
var node = sourceNodes[i];
if (!node.IsVisible)
{
node.Index = -1;
ResetNodesIndex(node.Nodes);
continue;
}
list.Add(GetListNode(node, depth));
node.Index = list.Count - 1;
if (node.IsExpanded && (node.Nodes != null) && (node.Nodes.Count > 0))
{
var used = Nodes2List(node.Nodes, depth + 1, list);
node.UsedNodesCount = used;
}
else
{
ResetNodesIndex(node.Nodes);
node.UsedNodesCount = 0;
}
added_nodes += 1;
}
return added_nodes;
}
///
/// Reset nodes indexes.
///
/// Source nodes.
protected virtual void ResetNodesIndex(IObservableList> sourceNodes)
{
if (sourceNodes == null)
{
return;
}
for (var i = 0; i < sourceNodes.Count; i++)
{
var node = sourceNodes[i];
node.Index = -1;
ResetNodesIndex(node.Nodes);
}
}
///
/// Process the toggle node event.
///
/// Index.
/// Component.
protected void OnToggleNode(int index, ListViewItem component)
{
ToggleNode(index);
NodeToggle.Invoke(NodesList[index].Node);
}
///
/// Expand the parent nodes.
///
/// Node.
public virtual void ExpandParentNodes(TreeNode node)
{
var current = node.Parent;
while (current != null)
{
current.IsExpanded = true;
current = current.Parent;
}
}
///
/// Collapse the parent nodes.
///
/// Node.
public virtual void CollapseParentNodes(TreeNode node)
{
var current = node.Parent;
while (current != null)
{
current.IsExpanded = false;
current = current.Parent;
}
}
///
/// Select the node.
///
/// Node.
public void Select(TreeNode node)
{
if (!IsNodeInTree(node))
{
return;
}
var index = Node2Index(node);
if (IsValid(index))
{
Select(index);
}
else if (index == -1 && !DeselectCollapsedNodes)
{
if (!MultipleSelect)
{
foreach (var n in SelectedNodes)
{
Deselect(n);
}
}
selectedNodes.Add(node);
}
}
///
/// Select the node.
///
/// Node.
public void SelectNode(TreeNode node)
{
Select(node);
}
///
/// Select the node with sub-nodes.
///
/// Node.
public void SelectNodeWithSubnodes(TreeNode node)
{
if (!IsNodeInTree(node))
{
return;
}
tempQueue.Enqueue(node);
while (tempQueue.Count > 0)
{
var current_node = tempQueue.Dequeue();
var index = Node2Index(current_node);
if (index != -1)
{
Select(index);
if (current_node.Nodes != null)
{
foreach (var n in current_node.Nodes)
{
tempQueue.Enqueue(n);
}
}
}
else if (!DeselectCollapsedNodes)
{
selectedNodes.Add(current_node);
if (current_node.Nodes != null)
{
foreach (var n in current_node.Nodes)
{
tempQueue.Enqueue(n);
}
}
}
}
}
///
/// Deselect the node.
///
/// Node.
public void Deselect(TreeNode node)
{
if (!IsNodeInTree(node))
{
return;
}
var index = Node2Index(node);
if (IsValid(index))
{
Deselect(index);
}
else if (index == -1 && !DeselectCollapsedNodes)
{
selectedNodes.Remove(node);
}
}
///
/// Deselect the node.
///
/// Node.
public void DeselectNode(TreeNode node)
{
Deselect(node);
}
///
/// Deselect the node with sub-nodes.
///
/// Node.
public void DeselectNodeWithSubnodes(TreeNode node)
{
if (!IsNodeInTree(node))
{
return;
}
tempQueue.Enqueue(node);
while (tempQueue.Count > 0)
{
var current_node = tempQueue.Dequeue();
var index = Node2Index(current_node);
if (index != -1)
{
Deselect(index);
if (current_node.Nodes != null)
{
foreach (var n in current_node.Nodes)
{
tempQueue.Enqueue(n);
}
}
}
else if (!DeselectCollapsedNodes)
{
selectedNodes.Remove(current_node);
if (current_node.Nodes != null)
{
foreach (var n in current_node.Nodes)
{
tempQueue.Enqueue(n);
}
}
}
}
}
///
protected override void InvokeSelect(int index, bool raiseEvents)
{
if (!IsValid(index))
{
Debug.LogWarning(string.Format("Incorrect index: {0}", index.ToString()), this);
}
var node = NodesList[index].Node;
selectedNodes.Add(node);
base.InvokeSelect(index, raiseEvents);
if (raiseEvents)
{
NodeSelected.Invoke(node);
}
}
///
protected override void InvokeDeselect(int index, bool raiseEvents)
{
if (!IsValid(index))
{
Debug.LogWarning(string.Format("Incorrect index: {0}", index.ToString()), this);
}
var node = NodesList[index].Node;
if (DeselectCollapsedNodes || !IsNodeInTree(node) || Node2Index(node) != -1)
{
selectedNodes.Remove(node);
}
base.InvokeDeselect(index, raiseEvents);
if (raiseEvents)
{
NodeDeselected.Invoke(node);
}
}
///
protected override void RecalculateSelectedIndices(ObservableList> newItems)
{
NewSelectedIndices.Clear();
foreach (var node in selectedNodes)
{
var index = Node2Index(newItems, node);
if (index != -1)
{
NewSelectedIndices.Add(index);
}
}
}
///
/// Get node index.
///
/// Nodes list.
/// Node.
/// Index of the node.
protected int Node2Index(ObservableList> nodes, TreeNode node)
{
for (int i = 0; i < nodes.Count; i++)
{
if (nodes[i].Node == node)
{
return i;
}
}
return -1;
}
///
/// Determines whether specified node in tree.
///
/// true if tree contains specified node; otherwise, false.
/// Node.
public bool IsNodeInTree(TreeNode node)
{
return ReferenceEquals(RootNode, node.RootNode);
}
///
/// Toggles the node.
///
/// Index.
protected virtual void ToggleNode(int index)
{
var node = NodesList[index];
node.Node.IsExpanded = !node.Node.IsExpanded;
}
///
/// Get indices of specified nodes.
///
/// The indices.
/// Target nodes.
protected List Nodes2Indices(List> targetNodes)
{
tempIndices.Clear();
Nodes2Indices(targetNodes, tempIndices);
return tempIndices;
}
///
/// Get indices of specified nodes.
///
/// Target nodes.
/// Indices.
protected void Nodes2Indices(List> targetNodes, List output)
{
for (int i = 0; i < targetNodes.Count; i++)
{
var node = targetNodes[i];
if (!IsNodeInTree(node))
{
continue;
}
var index = Node2Index(node);
if (index != -1)
{
output.Add(index);
}
}
}
///
/// Get node index.
///
/// The node index.
/// Node.
public int Node2Index(TreeNode node)
{
return node.Index;
}
///
/// Update view when node data changed.
///
protected virtual void NodesChanged()
{
Refresh();
}
///
/// Clear NodesList.
///
protected void NodesListClear()
{
foreach (var node in NodesList)
{
node.Node = null;
node.Depth = -1;
}
Cache.AddRange(NodesList);
NodesList.Clear();
}
///
/// Refresh this instance.
///
public virtual void Refresh()
{
if (nodes == null)
{
NodesListClear();
return;
}
NodesList.BeginUpdate();
var selected_nodes = SelectedNodes;
NodesListClear();
Nodes2List(nodes, 0, NodesList);
SilentDeselect(SelectedIndicesList);
Nodes2Indices(selected_nodes, tempIndices);
SilentSelect(tempIndices);
tempIndices.Clear();
NodesList.EndUpdate();
if (DeselectCollapsedNodes)
{
selectedNodes.Clear();
var selected = SelectedIndicesList;
foreach (var index in selected)
{
selectedNodes.Add(DataSource[index].Node);
}
}
}
///
/// Clear items of this instance.
///
public override void Clear()
{
nodes.Clear();
ListRenderer.SetPosition(0f);
}
///
/// [Not supported for TreeView] Add the specified item.
///
/// Item.
/// Index of added item.
[Obsolete("Not supported for TreeView", true)]
public virtual int Add(TItem item)
{
return -1;
}
///
/// [Not supported for TreeView] Remove the specified item.
///
/// Item.
/// Index of removed TItem.
[Obsolete("Not supported for TreeView", true)]
public virtual int Remove(TItem item)
{
return -1;
}
///
/// [Not supported for TreeView] Remove item by specified index.
///
/// Index.
public override void Remove(int index)
{
throw new NotSupportedException("Not supported for TreeView.");
}
///
/// [Not supported for TreeView] Set the specified item.
///
/// Item.
/// If set to true allow duplicate.
/// Index of item.
[Obsolete("Not supported for TreeView", true)]
public virtual int Set(TItem item, bool allowDuplicate = true)
{
return -1;
}
///
/// Removes first elements that match the conditions defined by the specified predicate.
///
/// Match.
/// true if item is successfully removed; otherwise, false.
public bool Remove(Predicate> match)
{
var index = nodes.FindIndex(match);
if (index != -1)
{
nodes.RemoveAt(index);
return true;
}
for (var i = 0; i < nodes.Count; i++)
{
var node = nodes[i];
if (node.Nodes == null)
{
continue;
}
if (Remove(node.Nodes, match))
{
return true;
}
}
return false;
}
static bool Remove(ObservableList> nodes, Predicate> match)
{
var index = nodes.FindIndex(match);
if (index != -1)
{
nodes.RemoveAt(index);
return true;
}
for (var i = 0; i < nodes.Count; i++)
{
var node = nodes[i];
if (node.Nodes == null)
{
continue;
}
if (Remove(node.Nodes, match))
{
return true;
}
}
return false;
}
///
/// Scroll to the specified node immediately.
///
/// Node.
public virtual void ScrollTo(TreeNode node)
{
ExpandParentNodes(node);
var index = Node2Index(node);
if (index < 0)
{
Debug.LogWarning("Specified node not found: node not in TreeView.Nodes or function called between .BeginUpdate() and .EndUpdate() calls");
return;
}
ScrollTo(index);
}
///
/// Scroll to the specified node with animation.
///
/// Node.
public virtual void ScrollToAnimated(TreeNode node)
{
ExpandParentNodes(node);
var index = Node2Index(node);
if (index < 0)
{
Debug.LogWarning("Specified node not found: node not in TreeView.Nodes or function called between .BeginUpdate() and .EndUpdate() calls");
return;
}
ScrollToAnimated(index);
}
///
/// Get node indentation.
///
/// Node index.
/// Indentation.
public virtual float GetNodeIndentation(int index)
{
if (index < 0)
{
return 0f;
}
return DataSource[index].Depth * DefaultItem.PaddingPerLevel;
}
///
/// Get secondary scroll position (for the cross direction).
///
/// Index.
/// Secondary scroll position.
protected override float GetScrollPositionSecondary(int index)
{
if (!ScrollWithIndent)
{
return base.GetScrollPositionSecondary(index);
}
if (ListRenderer.IsVirtualizationPossible())
{
var scroll_value = IsHorizontal() ? ScrollRectSize.y : ScrollRectSize.x;
var item_size = ListRenderer.GetItemFullSize(index);
var item_value = IsHorizontal()
? (item_size.y + LayoutBridge.GetFullMarginY())
: (item_size.x + LayoutBridge.GetFullMarginX());
if (scroll_value >= item_value)
{
return base.GetScrollPositionSecondary(index);
}
}
var value = GetNodeIndentation(index);
return IsHorizontal() ? value : -value;
}
///
/// Searches for an element that matches the conditions defined by the specified predicate, and returns the first occurrence within the entire Nodes.
///
/// The Predicate{TItem} delegate that defines the conditions of the element to search for.
/// The first element that matches the conditions defined by the specified predicate, if found; otherwise, null.
public TreeNode FindNode(Predicate> match)
{
return FindNode(Nodes, match);
}
///
/// Searches for the elements that matches the conditions defined by the specified predicate, and returns the all occurrences within the entire Nodes.
///
/// The Predicate{TItem} delegate that defines the conditions of the elements to search for.
/// List with founded nodes.
public void FindNodes(Predicate> match, List> output)
{
FindNodes(Nodes, match, output);
}
///
/// Searches for the elements that matches the conditions defined by the specified predicate, and returns the all occurrences within the entire Nodes.
///
/// The Predicate{TItem} delegate that defines the conditions of the elements to search for.
/// The all elements that matches the conditions defined by the specified predicate, if found; otherwise, null.
public List> FindNodes(Predicate> match)
{
var result = new List>();
FindNodes(Nodes, match, result);
return result;
}
///
/// Searches for an element that matches the conditions defined by the specified predicate, and returns the first occurrence within the specified nodes.
///
/// Nodes to search.
/// The Predicate{TItem} delegate that defines the conditions of the element to search for.
/// The first element that matches the conditions defined by the specified predicate, if found; otherwise, null.
protected static TreeNode FindNode(ObservableList> searchNodes, Predicate> match)
{
if (searchNodes == null)
{
return null;
}
var result = searchNodes.Find(match);
if (result != null)
{
return result;
}
foreach (var node in searchNodes)
{
var subnode = FindNode(node.Nodes, match);
if (subnode != null)
{
return subnode;
}
}
return null;
}
///
/// Searches for an elements that matches the conditions defined by the specified predicate, and returns the all occurrences within the specified nodes.
///
/// Nodes to search.
/// The Predicate{TItem} delegate that defines the conditions of the elements to search for.
/// List with founded nodes.
protected static void FindNodes(ObservableList> searchNodes, Predicate> match, List> output)
{
if (searchNodes == null)
{
return;
}
foreach (var node in searchNodes)
{
if (match(node))
{
output.Add(node);
}
FindNodes(node.Nodes, match, output);
}
}
///
/// Remove nodes parent.
///
/// Nodes.
protected void RemoveNodes(ObservableList> nodes)
{
if (nodes == null)
{
return;
}
nodes.OnChange -= NodesChanged;
for (int i = nodes.Count - 1; i >= 0; i--)
{
nodes[i].Parent = null;
}
}
///
/// Process the destroy event.
///
protected override void OnDestroy()
{
if (ItemsEvents != null)
{
ItemsEvents.NodeToggleClick.RemoveListener(ToggleNodeDelegate);
}
RemoveNodes(nodes);
nodes = null;
base.OnDestroy();
}
}
}