// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; using System.Collections.Generic; using UnityEngine; namespace Microsoft.MixedReality.Toolkit.Utilities { public abstract class BaseObjectCollection : MonoBehaviour { /// /// Action called when collection is updated /// public Action OnCollectionUpdated { get; set; } [HideInInspector] [SerializeField] private List nodeList = new List(); /// /// List of objects with generated data on the object. /// protected List NodeList { get { return nodeList; } } /// /// Read only list of objects with generated data on the object. /// public IReadOnlyList NodeListReadOnly { get { return nodeList.AsReadOnly(); } } [Tooltip("Whether to include space for inactive transforms in the layout")] [SerializeField] private bool ignoreInactiveTransforms = true; /// /// Whether to include space for inactive transforms in the layout /// public bool IgnoreInactiveTransforms { get { return ignoreInactiveTransforms; } set { ignoreInactiveTransforms = value; } } [Tooltip("Type of sorting to use")] [SerializeField] private CollationOrder sortType = CollationOrder.None; /// /// Type of sorting to use. /// public CollationOrder SortType { get { return sortType; } set { sortType = value; } } /// /// Rebuilds / updates the collection layout. /// Update collection is called from the editor button on the inspector. /// public virtual void UpdateCollection() { PruneEmptyNodes(); // Check when children change and adjust for (int i = 0; i < transform.childCount; i++) { Transform child = transform.GetChild(i); #if UNITY_EDITOR UnityEditor.Undo.RecordObject(child, "ObjectCollection modify transform"); #endif // UNITY_EDITOR if (!ContainsNode(child) && (child.gameObject.activeSelf || !IgnoreInactiveTransforms)) { NodeList.Add(new ObjectCollectionNode { Name = child.name, Transform = child }); } } SortNodes(); LayoutChildren(); OnCollectionUpdated?.Invoke(this); } /// /// Sorts NodeList based on /// protected void SortNodes() { switch (SortType) { case CollationOrder.ChildOrder: NodeList.Sort((c1, c2) => (c1.Transform.GetSiblingIndex().CompareTo(c2.Transform.GetSiblingIndex()))); break; case CollationOrder.Alphabetical: NodeList.Sort((c1, c2) => (string.CompareOrdinal(c1.Name, c2.Name))); break; case CollationOrder.AlphabeticalReversed: NodeList.Sort((c1, c2) => (string.CompareOrdinal(c1.Name, c2.Name))); NodeList.Reverse(); break; case CollationOrder.ChildOrderReversed: NodeList.Sort((c1, c2) => (c1.Transform.GetSiblingIndex().CompareTo(c2.Transform.GetSiblingIndex()))); NodeList.Reverse(); break; } } /// /// Checks for empty nodes and removes them /// protected void PruneEmptyNodes() { // Check for empty nodes and remove them var emptyNodes = new List(); for (int i = 0; i < NodeList.Count; i++) { if (NodeList[i].Transform == null || (IgnoreInactiveTransforms && !NodeList[i].Transform.gameObject.activeSelf) || NodeList[i].Transform.parent == null || !(NodeList[i].Transform.parent.gameObject == gameObject)) { emptyNodes.Add(NodeList[i]); } } // Now delete the empty nodes for (int i = 0; i < emptyNodes.Count; i++) { NodeList.Remove(emptyNodes[i]); } emptyNodes.Clear(); } /// /// Check if a node exists in the NodeList. /// /// The Transform belonging to the /// true when belongs to an element of the list. protected bool ContainsNode(Transform node) { if (node == null) { return false; } for (int i = 0; i < NodeList.Count; i++) { if (NodeList[i].Transform == node) { return true; } } return false; } /// /// Check if a node exists in the NodeList. /// /// The Transform belonging to /// The index of the element in /// true when belongs to an element of the list. public bool ContainsNode(Transform node, out int nodeIndex) { nodeIndex = 0; if (node == null) { return false; } for (int i = 0; i < NodeList.Count; i++) { if (NodeList[i].Transform == node) { nodeIndex = i; return true; } } return false; } /// /// Implement for laying out all children when UpdateCollection is called. /// protected abstract void LayoutChildren(); } }