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