TransformTreeView.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. // FlexiMotion // https://kybernetik.com.au/flexi-motion // Copyright 2023 Kybernetik //
  3. #if UNITY_EDITOR
  4. using System;
  5. using System.Collections.Generic;
  6. using UnityEditor;
  7. using UnityEditor.IMGUI.Controls;
  8. using UnityEngine;
  9. namespace Animancer.Editor
  10. //namespace FlexiMotion.Editor
  11. {
  12. /// <summary>An object that provides data to a <see cref="TransformTreeView"/>.</summary>
  13. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/ITransformTreeViewSource
  14. /// https://kybernetik.com.au/flexi-motion/api/FlexiMotion.Editor/ITransformTreeViewSource
  15. public interface ITransformTreeViewSource
  16. {
  17. /************************************************************************************************************************/
  18. /// <summary>The object at the top of the target hierarchy.</summary>
  19. Transform Root { get; }
  20. /// <summary>The objects to show in the view.</summary>
  21. IList<Transform> Transforms { get; }
  22. /// <summary>Adds the items to be displayed in the view.</summary>
  23. void AddItems(ref int id, TreeViewItem root);
  24. /// <summary>Adds an item for the `transform` to be displayed in the view.</summary>
  25. TreeViewItem AddItem(ref int id, TreeViewItem parent, Transform transform);
  26. /// <summary>Called before a row is drawn.</summary>
  27. void BeforeRowGUI(Rect area, TreeViewItem item);
  28. /// <summary>Draws a cell in the <see cref="TreeView"/>.</summary>
  29. void DrawCellGUI(Rect area, int column, int row, TreeViewItem item, ref bool isSelectionClick);
  30. /************************************************************************************************************************/
  31. }
  32. /// <summary>A <see cref="TreeView"/> for displaying <see cref="Transform"/>s alongside other data.</summary>
  33. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/TransformTreeView
  34. /// https://kybernetik.com.au/flexi-motion/api/FlexiMotion.Editor/TransformTreeView
  35. public class TransformTreeView : TreeView, IDisposable
  36. {
  37. /************************************************************************************************************************/
  38. /// <summary>The ID of the root item.</summary>
  39. public const int RootID = 0;
  40. /// <summary>The object which defines what to show in this view.</summary>
  41. public readonly ITransformTreeViewSource Source;
  42. /// <summary>The field used to filter this view.</summary>
  43. public readonly SearchField Search = new();
  44. /// <summary>The <see cref="Transform"/> of each row in this view.</summary>
  45. public readonly List<Transform> Transforms = new();
  46. /************************************************************************************************************************/
  47. #region Initialization
  48. /************************************************************************************************************************/
  49. /// <summary>Creates a new <see cref="TransformTreeView"/>.</summary>
  50. public TransformTreeView(
  51. TreeViewState state,
  52. MultiColumnHeader header,
  53. ITransformTreeViewSource source)
  54. : base(state, header)
  55. {
  56. Source = source;
  57. Selection.selectionChanged += OnObjectSelectionChanged;
  58. }
  59. /************************************************************************************************************************/
  60. /// <summary>Cleans up this view.</summary>
  61. public void Dispose()
  62. {
  63. Selection.selectionChanged -= OnObjectSelectionChanged;
  64. }
  65. /************************************************************************************************************************/
  66. /// <inheritdoc/>
  67. protected override TreeViewItem BuildRoot()
  68. {
  69. Transforms.Clear();
  70. var id = RootID;
  71. var root = CreateItem(ref id, -1, "");
  72. Transforms.Add(null);
  73. Source.AddItems(ref id, root);
  74. return root;
  75. }
  76. /************************************************************************************************************************/
  77. /// <summary>Adds a new item for the `transform` as a child of the `parent` and increments the `id`.</summary>
  78. public virtual TreeViewItem AddItem(ref int id, TreeViewItem parent, Transform transform)
  79. {
  80. Transforms.Add(transform);
  81. var item = CreateItem(ref id, parent.depth + 1, transform.name);
  82. parent.AddChild(item);
  83. return item;
  84. }
  85. /// <summary>Adds a new item for each child of the `transform` recursively.</summary>
  86. public void AddItemRecursive(ref int id, TreeViewItem parent, Transform transform)
  87. {
  88. parent = Source.AddItem(ref id, parent, transform);
  89. foreach (Transform child in transform)
  90. AddItemRecursive(ref id, parent, child);
  91. }
  92. /************************************************************************************************************************/
  93. /// <summary>Creates a new <see cref="TreeViewItem"/> and increments the `id`.</summary>
  94. public static TreeViewItem CreateItem(ref int id, int depth, string displayName)
  95. => new()
  96. {
  97. id = id++,
  98. depth = depth,
  99. displayName = displayName,
  100. };
  101. /************************************************************************************************************************/
  102. #endregion
  103. /************************************************************************************************************************/
  104. #region GUI
  105. /************************************************************************************************************************/
  106. /// <summary>Draws the <see cref="SearchField"/> for filtering this view.</summary>
  107. public void DrawSearchField(Rect area)
  108. {
  109. searchString = Search.OnGUI(area, searchString);
  110. }
  111. /************************************************************************************************************************/
  112. /// <inheritdoc/>
  113. protected override void RowGUI(RowGUIArgs args)
  114. {
  115. Source.BeforeRowGUI(args.rowRect, args.item);
  116. var currentEvent = Event.current;
  117. var isClick =
  118. currentEvent.type == EventType.MouseDown &&
  119. args.rowRect.Contains(currentEvent.mousePosition);
  120. var visibleColumnCount = args.GetNumVisibleColumns();
  121. for (int i = 0; i < visibleColumnCount; ++i)
  122. {
  123. var area = args.GetCellRect(i);
  124. if (i == 0)
  125. area.xMin += (args.item.depth + 1) * depthIndentWidth;
  126. Source.DrawCellGUI(area, args.GetColumn(i), args.row, args.item, ref isClick);
  127. }
  128. if (isClick && currentEvent.type == EventType.Used)
  129. SelectionClick(args.item, false);
  130. }
  131. /************************************************************************************************************************/
  132. private static readonly List<GameObject>
  133. SelectedObjects = new();
  134. /// <summary>Called whenever the selected rows change.</summary>
  135. public event Action<IList<int>> OnSelectionChanged;
  136. /// <inheritdoc/>
  137. protected override void SelectionChanged(IList<int> selectedIds)
  138. {
  139. base.SelectionChanged(selectedIds);
  140. SelectedObjects.Clear();
  141. for (int i = 0; i < selectedIds.Count; i++)
  142. if (Transforms.TryGetObject(selectedIds[i], out var transform))
  143. SelectedObjects.Add(transform.gameObject);
  144. Selection.objects = SelectedObjects.ToArray();
  145. OnSelectionChanged?.Invoke(selectedIds);
  146. }
  147. /************************************************************************************************************************/
  148. private static readonly List<int>
  149. SelectedIDs = new();
  150. /// <summary>Called whenever the <see cref="Selection.objects"/> change.</summary>
  151. public void OnObjectSelectionChanged()
  152. {
  153. var selectedObjects = Selection.objects;
  154. SelectedIDs.Clear();
  155. SelectedIDs.AddRange(GetSelection());
  156. // Remove IDs that aren't in the Selection.
  157. for (int i = SelectedIDs.Count - 1; i >= 0; i--)
  158. {
  159. if (!Transforms.TryGetObject(SelectedIDs[i], out var transform) ||
  160. Array.IndexOf(selectedObjects, transform.gameObject) < 0)
  161. {
  162. SelectedIDs.RemoveAt(i);
  163. }
  164. }
  165. // If no selected rows correspond to a selected object, add all rows of that object.
  166. foreach (var selected in selectedObjects)
  167. {
  168. if (!AnimancerUtilities.TryGetTransform(selected, out var selectedTransform) ||
  169. IsAlreadySelected(selectedTransform))
  170. continue;
  171. var index = Transforms.IndexOf(selectedTransform);
  172. if (index >= 0)
  173. {
  174. SelectedIDs.Add(index);
  175. while (Transforms.TryGetObject(++index, out var transform) &&
  176. transform == selectedTransform)
  177. SelectedIDs.Add(index);
  178. }
  179. }
  180. SetSelection(SelectedIDs);
  181. }
  182. private bool IsAlreadySelected(Transform transform)
  183. {
  184. for (int i = 0; i < SelectedIDs.Count; i++)
  185. {
  186. if (!Transforms.TryGetObject(SelectedIDs[i], out var selectedTransform))
  187. continue;
  188. if (selectedTransform == transform)
  189. return true;
  190. }
  191. return false;
  192. }
  193. /************************************************************************************************************************/
  194. #endregion
  195. /************************************************************************************************************************/
  196. }
  197. }
  198. #endif