// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik // // FlexiMotion // https://kybernetik.com.au/flexi-motion // Copyright 2023 Kybernetik // #if UNITY_EDITOR using System; using System.Collections.Generic; using UnityEditor; using UnityEditor.IMGUI.Controls; using UnityEngine; namespace Animancer.Editor //namespace FlexiMotion.Editor { /// An object that provides data to a . /// https://kybernetik.com.au/animancer/api/Animancer.Editor/ITransformTreeViewSource /// https://kybernetik.com.au/flexi-motion/api/FlexiMotion.Editor/ITransformTreeViewSource public interface ITransformTreeViewSource { /************************************************************************************************************************/ /// The object at the top of the target hierarchy. Transform Root { get; } /// The objects to show in the view. IList Transforms { get; } /// Adds the items to be displayed in the view. void AddItems(ref int id, TreeViewItem root); /// Adds an item for the `transform` to be displayed in the view. TreeViewItem AddItem(ref int id, TreeViewItem parent, Transform transform); /// Called before a row is drawn. void BeforeRowGUI(Rect area, TreeViewItem item); /// Draws a cell in the . void DrawCellGUI(Rect area, int column, int row, TreeViewItem item, ref bool isSelectionClick); /************************************************************************************************************************/ } /// A for displaying s alongside other data. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/TransformTreeView /// https://kybernetik.com.au/flexi-motion/api/FlexiMotion.Editor/TransformTreeView public class TransformTreeView : TreeView, IDisposable { /************************************************************************************************************************/ /// The ID of the root item. public const int RootID = 0; /// The object which defines what to show in this view. public readonly ITransformTreeViewSource Source; /// The field used to filter this view. public readonly SearchField Search = new(); /// The of each row in this view. public readonly List Transforms = new(); /************************************************************************************************************************/ #region Initialization /************************************************************************************************************************/ /// Creates a new . public TransformTreeView( TreeViewState state, MultiColumnHeader header, ITransformTreeViewSource source) : base(state, header) { Source = source; Selection.selectionChanged += OnObjectSelectionChanged; } /************************************************************************************************************************/ /// Cleans up this view. public void Dispose() { Selection.selectionChanged -= OnObjectSelectionChanged; } /************************************************************************************************************************/ /// protected override TreeViewItem BuildRoot() { Transforms.Clear(); var id = RootID; var root = CreateItem(ref id, -1, ""); Transforms.Add(null); Source.AddItems(ref id, root); return root; } /************************************************************************************************************************/ /// Adds a new item for the `transform` as a child of the `parent` and increments the `id`. public virtual TreeViewItem AddItem(ref int id, TreeViewItem parent, Transform transform) { Transforms.Add(transform); var item = CreateItem(ref id, parent.depth + 1, transform.name); parent.AddChild(item); return item; } /// Adds a new item for each child of the `transform` recursively. public void AddItemRecursive(ref int id, TreeViewItem parent, Transform transform) { parent = Source.AddItem(ref id, parent, transform); foreach (Transform child in transform) AddItemRecursive(ref id, parent, child); } /************************************************************************************************************************/ /// Creates a new and increments the `id`. public static TreeViewItem CreateItem(ref int id, int depth, string displayName) => new() { id = id++, depth = depth, displayName = displayName, }; /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region GUI /************************************************************************************************************************/ /// Draws the for filtering this view. public void DrawSearchField(Rect area) { searchString = Search.OnGUI(area, searchString); } /************************************************************************************************************************/ /// protected override void RowGUI(RowGUIArgs args) { Source.BeforeRowGUI(args.rowRect, args.item); var currentEvent = Event.current; var isClick = currentEvent.type == EventType.MouseDown && args.rowRect.Contains(currentEvent.mousePosition); var visibleColumnCount = args.GetNumVisibleColumns(); for (int i = 0; i < visibleColumnCount; ++i) { var area = args.GetCellRect(i); if (i == 0) area.xMin += (args.item.depth + 1) * depthIndentWidth; Source.DrawCellGUI(area, args.GetColumn(i), args.row, args.item, ref isClick); } if (isClick && currentEvent.type == EventType.Used) SelectionClick(args.item, false); } /************************************************************************************************************************/ private static readonly List SelectedObjects = new(); /// Called whenever the selected rows change. public event Action> OnSelectionChanged; /// protected override void SelectionChanged(IList selectedIds) { base.SelectionChanged(selectedIds); SelectedObjects.Clear(); for (int i = 0; i < selectedIds.Count; i++) if (Transforms.TryGetObject(selectedIds[i], out var transform)) SelectedObjects.Add(transform.gameObject); Selection.objects = SelectedObjects.ToArray(); OnSelectionChanged?.Invoke(selectedIds); } /************************************************************************************************************************/ private static readonly List SelectedIDs = new(); /// Called whenever the change. public void OnObjectSelectionChanged() { var selectedObjects = Selection.objects; SelectedIDs.Clear(); SelectedIDs.AddRange(GetSelection()); // Remove IDs that aren't in the Selection. for (int i = SelectedIDs.Count - 1; i >= 0; i--) { if (!Transforms.TryGetObject(SelectedIDs[i], out var transform) || Array.IndexOf(selectedObjects, transform.gameObject) < 0) { SelectedIDs.RemoveAt(i); } } // If no selected rows correspond to a selected object, add all rows of that object. foreach (var selected in selectedObjects) { if (!AnimancerUtilities.TryGetTransform(selected, out var selectedTransform) || IsAlreadySelected(selectedTransform)) continue; var index = Transforms.IndexOf(selectedTransform); if (index >= 0) { SelectedIDs.Add(index); while (Transforms.TryGetObject(++index, out var transform) && transform == selectedTransform) SelectedIDs.Add(index); } } SetSelection(SelectedIDs); } private bool IsAlreadySelected(Transform transform) { for (int i = 0; i < SelectedIDs.Count; i++) { if (!Transforms.TryGetObject(SelectedIDs[i], out var selectedTransform)) continue; if (selectedTransform == transform) return true; } return false; } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ } } #endif