A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using UnityEditor.IMGUI.Controls;
5using UnityEditorInternal;
6using UnityEngine;
7using UnityEngine.Timeline;
8
9#if !UNITY_2020_2_OR_NEWER
10using L10n = UnityEditor.Timeline.L10n;
11#endif
12
13namespace UnityEditor.Timeline
14{
15 class BindingTreeViewDataSource : TreeViewDataSource
16 {
17 struct BindingGroup : IEquatable<BindingGroup>, IComparable<BindingGroup>
18 {
19 public readonly string GroupName;
20 public readonly string Path;
21 public readonly Type Type;
22
23 public BindingGroup(string path, string groupName, Type type)
24 {
25 Path = path;
26 GroupName = groupName;
27 Type = type;
28 }
29
30 public string groupDisplayName => string.IsNullOrEmpty(Path) ? GroupName : string.Format($"{Path} : {GroupName}");
31 public bool Equals(BindingGroup other) => GroupName == other.GroupName && Type == other.Type && Path == other.Path;
32 public int CompareTo(BindingGroup other) => GetHashCode() - other.GetHashCode();
33 public override bool Equals(object obj) => obj is BindingGroup other && Equals(other);
34 public override int GetHashCode()
35 {
36 return HashUtility.CombineHash(GroupName != null ? GroupName.GetHashCode() : 0, Type != null ? Type.GetHashCode() : 0, Path != null ? Path.GetHashCode() : 0);
37 }
38 }
39
40 static readonly string s_DefaultValue = L10n.Tr("{0} (Default Value)");
41
42 public const int RootID = int.MinValue;
43 public const int GroupID = -1;
44
45 private readonly AnimationClip m_Clip;
46 private readonly CurveDataSource m_CurveDataSource;
47
48 public BindingTreeViewDataSource(
49 TreeViewController treeView, AnimationClip clip, CurveDataSource curveDataSource)
50 : base(treeView)
51 {
52 m_Clip = clip;
53 showRootItem = false;
54 m_CurveDataSource = curveDataSource;
55 }
56
57 void SetupRootNodeSettings()
58 {
59 showRootItem = false;
60 SetExpanded(RootID, true);
61 SetExpanded(GroupID, true);
62 }
63
64 public override void FetchData()
65 {
66 if (m_Clip == null)
67 return;
68
69 var bindings = AnimationUtility.GetCurveBindings(m_Clip)
70 .Union(AnimationUtility.GetObjectReferenceCurveBindings(m_Clip))
71 .ToArray();
72
73 // a sorted linear list of nodes
74 var results = bindings.GroupBy(GetBindingGroup, p => p, CreateTuple)
75 .OrderBy(t => t.Item1.Path)
76 .ThenBy(NamePrioritySort)
77 // this makes component ordering match the animation window
78 .ThenBy(t => t.Item1.Type.ToString())
79 .ThenBy(t => t.Item1.GroupName).ToArray();
80
81 m_RootItem = new CurveTreeViewNode(RootID, null, "root", null)
82 {
83 children = new List<TreeViewItem>(1)
84 };
85
86 if (results.Any())
87 {
88 var groupingNode = new CurveTreeViewNode(GroupID, m_RootItem, m_CurveDataSource.groupingName, bindings)
89 {
90 children = new List<TreeViewItem>()
91 };
92
93 m_RootItem.children.Add(groupingNode);
94
95 foreach (var r in results)
96 {
97 var key = r.Item1;
98 var nodeBindings = r.Item2;
99
100 FillMissingTransformCurves(nodeBindings);
101 if (nodeBindings.Count == 1)
102 groupingNode.children.Add(CreateLeafNode(nodeBindings[0], groupingNode, PropertyName(nodeBindings[0], true)));
103 else if (nodeBindings.Count > 1)
104 {
105 var childBindings = nodeBindings.OrderBy(BindingSort).ToArray();
106 var parent = new CurveTreeViewNode(key.GetHashCode(), groupingNode, key.groupDisplayName, childBindings) { children = new List<TreeViewItem>() };
107 groupingNode.children.Add(parent);
108 foreach (var b in childBindings)
109 parent.children.Add(CreateLeafNode(b, parent, PropertyName(b, false)));
110 }
111 }
112 SetupRootNodeSettings();
113 }
114
115 m_NeedRefreshRows = true;
116 }
117
118 public void UpdateData()
119 {
120 m_TreeView.ReloadData();
121 }
122
123 string GroupName(EditorCurveBinding binding)
124 {
125 var propertyName = m_CurveDataSource.ModifyPropertyDisplayName(binding.path, binding.propertyName);
126 return CleanUpArrayBinding(AnimationWindowUtility.NicifyPropertyGroupName(binding.type, propertyName), true);
127 }
128
129 static string CleanUpArrayBinding(string propertyName, bool isGroup)
130 {
131 const string arrayIndicator = ".Array.data[";
132 const string arrayDisplay = ".data[";
133
134 var arrayIndex = propertyName.LastIndexOf(arrayIndicator, StringComparison.Ordinal);
135 if (arrayIndex == -1)
136 return propertyName;
137 if (isGroup)
138 propertyName = propertyName.Substring(0, arrayIndex);
139 return propertyName.Replace(arrayIndicator, arrayDisplay);
140 }
141
142 string PropertyName(EditorCurveBinding binding, bool prependPathName)
143 {
144 var propertyName = m_CurveDataSource.ModifyPropertyDisplayName(binding.path, binding.propertyName);
145 propertyName = CleanUpArrayBinding(AnimationWindowUtility.GetPropertyDisplayName(propertyName), false);
146 if (binding.isPhantom)
147 propertyName = string.Format(s_DefaultValue, propertyName);
148 if (prependPathName && !string.IsNullOrEmpty(binding.path))
149 propertyName = $"{binding.path} : {propertyName}";
150 return propertyName;
151 }
152
153 BindingGroup GetBindingGroup(EditorCurveBinding binding)
154 {
155 return new BindingGroup(binding.path ?? string.Empty, GroupName(binding), binding.type);
156 }
157
158 static CurveTreeViewNode CreateLeafNode(EditorCurveBinding binding, TreeViewItem parent, string displayName)
159 {
160 return new CurveTreeViewNode(binding.GetHashCode(), parent, displayName, new[] { binding }, AnimationWindowUtility.ForceGrouping(binding));
161 }
162
163 static void FillMissingTransformCurves(List<EditorCurveBinding> bindings)
164 {
165 if (!AnimationWindowUtility.IsActualTransformCurve(bindings[0]) || bindings.Count >= 3)
166 return;
167
168 var binding = bindings[0];
169 var prefixPropertyName = binding.propertyName.Split('.').First();
170
171 binding.isPhantom = true;
172 if (!bindings.Any(p => p.propertyName.EndsWith(".x")))
173 {
174 binding.propertyName = prefixPropertyName + ".x";
175 bindings.Insert(0, binding);
176 }
177
178 if (!bindings.Any(p => p.propertyName.EndsWith(".y")))
179 {
180 binding.propertyName = prefixPropertyName + ".y";
181 bindings.Insert(1, binding);
182 }
183
184 if (!bindings.Any(p => p.propertyName.EndsWith(".z")))
185 {
186 binding.propertyName = prefixPropertyName + ".z";
187 bindings.Insert(2, binding);
188 }
189 }
190
191 // make sure vectors and colors are sorted correctly in their subgroups
192 static int BindingSort(EditorCurveBinding b)
193 {
194 return AnimationWindowUtility.GetComponentIndex(b.propertyName);
195 }
196
197 static int NamePrioritySort(ValueTuple<BindingGroup, List<EditorCurveBinding>> group)
198 {
199 if (group.Item1.Type != typeof(Transform))
200 return 0;
201
202 switch (group.Item1.GroupName)
203 {
204 case "Position": return Int32.MinValue;
205 case "Rotation": return Int32.MinValue + 1;
206 case "Scale": return Int32.MinValue + 2;
207 default: return 0;
208 }
209 }
210
211 static ValueTuple<BindingGroup, List<EditorCurveBinding>> CreateTuple(BindingGroup key, IEnumerable<EditorCurveBinding> items)
212 {
213 return new ValueTuple<BindingGroup, List<EditorCurveBinding>>()
214 {
215 Item1 = key,
216 Item2 = items.ToList()
217 };
218 }
219 }
220}