File size: 16,146 Bytes
18a519f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Timeline;

namespace UnityEditor.Timeline
{
    /// <summary>
    /// The user-defined options for drawing a track."
    /// </summary>
    public struct TrackDrawOptions
    {
        /// <summary>
        /// Text that indicates if the track should display an error.
        /// </summary>
        /// <remarks>
        /// If the error text is not empty or null, then the track displays a warning. The error text is used as the tooltip.
        /// </remarks>
        public string errorText { get; set; }

        /// <summary>
        /// The highlight color of the track.
        /// </summary>
        public Color trackColor { get; set; }

        /// <summary>
        /// The minimum height of the track.
        /// </summary>
        public float minimumHeight { get; set; }

        /// <summary>
        /// The icon displayed on the track header.
        /// </summary>
        /// <remarks>
        /// If this value is null, then the default icon for the track is used.
        /// </remarks>
        public Texture2D icon { get; set; }

        /// <summary>
        /// Indicates whether this instance and a specified object are equal.
        /// </summary>
        /// <param name="obj">The object to compare with the current instance.</param>
        /// <returns>Returns <c>true</c> if <paramref name="obj"/> and this instance are the same type and represent the same value.</returns>
        public override bool Equals(object obj)
        {
            if (!(obj is TrackDrawOptions))
                return false;

            return Equals((TrackDrawOptions)obj);
        }

        /// <summary>
        /// Compares this object with another <c>TrackDrawOptions</c>.
        /// </summary>
        /// <param name="other">The object to compare with.</param>
        /// <returns>Returns true if <c>this</c> and <paramref name="other"/> are equal.</returns>
        public bool Equals(TrackDrawOptions other)
        {
            return errorText == other.errorText &&
                trackColor == other.trackColor &&
                minimumHeight == other.minimumHeight &&
                icon == other.icon;
        }

        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>
        /// <returns>A 32-bit signed integer that is the hash code for this instance.</returns>
        public override int GetHashCode()
        {
            return HashUtility.CombineHash(
                errorText != null ? errorText.GetHashCode() : 0,
                trackColor.GetHashCode(),
                minimumHeight.GetHashCode(),
                icon != null ? icon.GetHashCode() : 0
            );
        }

        /// <summary>
        /// Compares two <c>TrackDrawOptions</c> objects.
        /// </summary>
        /// <param name="options1">The first object.</param>
        /// <param name="options2">The second object.</param>
        /// <returns>Returns true if they are equal.</returns>
        public static bool operator ==(TrackDrawOptions options1, TrackDrawOptions options2)
        {
            return options1.Equals(options2);
        }

        /// <summary>
        /// Compares two <c>TrackDrawOptions</c> objects.
        /// </summary>
        /// <param name="options1">The first object.</param>
        /// <param name="options2">The second object.</param>
        /// <returns>Returns true if they are not equal.</returns>
        public static bool operator !=(TrackDrawOptions options1, TrackDrawOptions options2)
        {
            return !options1.Equals(options2);
        }
    }


    /// <summary>
    /// The errors displayed for the track binding.
    /// </summary>
    public enum TrackBindingErrors
    {
        /// <summary>
        /// Select no errors.
        /// </summary>
        None = 0,

        /// <summary>
        /// The bound GameObject is disabled.
        /// </summary>
        BoundGameObjectDisabled = 1 << 0,

        /// <summary>
        /// The bound GameObject does not have a valid component.
        /// </summary>
        NoValidComponent = 1 << 1,

        /// <summary>
        /// The bound Object is a disabled Behaviour.
        /// </summary>
        BehaviourIsDisabled = 1 << 2,

        /// <summary>
        /// The bound Object is not of the correct type.
        /// </summary>
        InvalidBinding = 1 << 3,

        /// <summary>
        /// The bound Object is part of a prefab, and not an instance.
        /// </summary>
        PrefabBound = 1 << 4,

        /// <summary>
        /// Select all errors.
        /// </summary>
        All = Int32.MaxValue
    }

    /// <summary>
    /// Use this class to customize track types in the TimelineEditor.
    /// </summary>
    public class TrackEditor
    {
        static readonly string k_BoundGameObjectDisabled = L10n.Tr("The bound GameObject is disabled.");
        static readonly string k_NoValidComponent = L10n.Tr("Could not find appropriate component on this gameObject");
        static readonly string k_RequiredComponentIsDisabled = L10n.Tr("The component is disabled");
        static readonly string k_InvalidBinding = L10n.Tr("The bound object is not the correct type.");
        static readonly string k_PrefabBound = L10n.Tr("The bound object is a Prefab");

        readonly Dictionary<TrackAsset, System.Type> m_BindingCache = new Dictionary<TrackAsset, System.Type>();

        /// <summary>
        /// The default height of a track.
        /// </summary>
        public static readonly float DefaultTrackHeight = 30.0f;

        /// <summary>
        /// The minimum unscaled height of a track.
        /// </summary>
        public static readonly float MinimumTrackHeight = 10.0f;

        /// <summary>
        /// The maximum height of a track.
        /// </summary>
        public static readonly float MaximumTrackHeight = 256.0f;

        /// <summary>
        /// Implement this method to override the default options for drawing a track.
        /// </summary>
        /// <param name="track">The track from which track options are retrieved.</param>
        /// <param name="binding">The binding for the track.</param>
        /// <returns>The options for drawing the track.</returns>
        public virtual TrackDrawOptions GetTrackOptions(TrackAsset track, UnityEngine.Object binding)
        {
            return new TrackDrawOptions()
            {
                errorText = GetErrorText(track, binding, TrackBindingErrors.All),
                minimumHeight = DefaultTrackHeight,
                trackColor = GetTrackColor(track),
                icon = null
            };
        }

        /// <summary>
        /// Gets the error text for the specified track.
        /// </summary>
        /// <param name="track">The track to retrieve options for.</param>
        /// <param name="boundObject">The binding for the track.</param>
        /// <param name="detectErrors">The errors to check for.</param>
        /// <returns>An error to be displayed on the track, or string.Empty if there is no error.</returns>
        public string GetErrorText(TrackAsset track, UnityEngine.Object boundObject, TrackBindingErrors detectErrors)
        {
            if (track == null || boundObject == null)
                return string.Empty;

            var bindingType = GetBindingType(track);
            if (bindingType != null)
            {
                // bound to a prefab asset
                if (HasFlag(detectErrors, TrackBindingErrors.PrefabBound) && PrefabUtility.IsPartOfPrefabAsset(boundObject))
                {
                    return k_PrefabBound;
                }

                // If we are a component, allow for bound game objects (legacy)
                if (typeof(Component).IsAssignableFrom(bindingType))
                {
                    var gameObject = boundObject as GameObject;
                    var component = boundObject as Component;
                    if (component != null)
                        gameObject = component.gameObject;

                    // game object is bound with no component
                    if (HasFlag(detectErrors, TrackBindingErrors.NoValidComponent) && gameObject != null && component == null)
                    {
                        component = gameObject.GetComponent(bindingType);
                        if (component == null)
                        {
                            return k_NoValidComponent;
                        }
                    }

                    // attached gameObject is disables (ignores Activation Track)
                    if (HasFlag(detectErrors, TrackBindingErrors.BoundGameObjectDisabled) && gameObject != null && !gameObject.activeInHierarchy)
                    {
                        return k_BoundGameObjectDisabled;
                    }

                    // component is disabled
                    var behaviour = component as Behaviour;
                    if (HasFlag(detectErrors, TrackBindingErrors.BehaviourIsDisabled) && behaviour != null && !behaviour.enabled)
                    {
                        return k_RequiredComponentIsDisabled;
                    }

                    // mismatched binding
                    if (HasFlag(detectErrors, TrackBindingErrors.InvalidBinding) && component != null && !bindingType.IsAssignableFrom(component.GetType()))
                    {
                        return k_InvalidBinding;
                    }
                }
                // Mismatched binding (non-component)
                else if (HasFlag(detectErrors, TrackBindingErrors.InvalidBinding) && !bindingType.IsAssignableFrom(boundObject.GetType()))
                {
                    return k_InvalidBinding;
                }
            }

            return string.Empty;
        }

        /// <summary>
        /// Gets the color information of a track.
        /// </summary>
        /// <param name="track"></param>
        /// <returns>Returns the color for the specified track.</returns>
        public Color GetTrackColor(TrackAsset track)
        {
            return TrackResourceCache.GetTrackColor(track);
        }

        /// <summary>
        /// Gets the binding type for a track.
        /// </summary>
        /// <param name="track">The track to retrieve the binding type from.</param>
        /// <returns>Returns the binding type for the specified track. Returns null if the track does not have binding.</returns>
        public System.Type GetBindingType(TrackAsset track)
        {
            if (track == null)
                return null;

            if (m_BindingCache.TryGetValue(track, out var result))
                return result;

            result = track.outputs.Select(x => x.outputTargetType).FirstOrDefault();
            m_BindingCache[track] = result;
            return result;
        }

        /// <summary>
        /// Callback for when a track is created.
        /// </summary>
        /// <param name="track">The track that is created.</param>
        /// <param name="copiedFrom">The source that the track is copied from. This can be set to null if the track is not a copy.</param>
        public virtual void OnCreate(TrackAsset track, TrackAsset copiedFrom)
        {
        }

        /// <summary>
        /// Callback for when a track is changed.
        /// </summary>
        /// <param name="track">The track that is changed.</param>
        public virtual void OnTrackChanged(TrackAsset track)
        {
        }

        static bool HasFlag(TrackBindingErrors errors, TrackBindingErrors flag)
        {
            return (errors & flag) != 0;
        }

        /// <summary>
        /// Override this method to validate if a binding for <paramref name="track"/>
        /// can be determined from <paramref name="candidate"/>.
        ///
        /// The default implementation of this method will return true if
        /// - <paramref name="candidate"/> is not null or,
        /// - <paramref name="candidate"/> is not part of a Prefab Asset or,
        /// - <paramref name="candidate"/> is a Component that can be bound to <paramref name="track"/>
        /// </summary>
        /// <param name="candidate"></param>
        /// <param name="track">TBD</param>
        /// <returns>True if a binding can be determined from <paramref name="candidate"/>.</returns>
        /// <seealso cref="UnityEngine.Timeline.TrackBindingTypeAttribute"/>
        /// <seealso cref="UnityEngine.Timeline.TrackAsset"/>
        public virtual bool IsBindingAssignableFrom(UnityEngine.Object candidate, TrackAsset track)
        {
            var action = BindingUtility.GetBindingAction(GetBindingType(track), candidate);
            return action != BindingUtility.BindingAction.DoNotBind;
        }

        /// <summary>
        /// Override this method to determine which object to bind to <paramref name="track"/>.
        /// A binding object should be determined from <paramref name="candidate"/>.
        ///
        /// By default, the `TrackBindingType` attribute from <paramref name="track"/> will be used to determine the binding.
        /// </summary>
        /// <param name="candidate">The source object from which a track binding should be determined.</param>
        /// <param name="track">The track to bind an object to.</param>
        /// <returns>The object to bind to <paramref name="track"/>.</returns>
        /// <seealso cref="UnityEngine.Timeline.TrackBindingTypeAttribute"/>
        /// <seealso cref="UnityEngine.Timeline.TrackAsset"/>
        public virtual UnityEngine.Object GetBindingFrom(UnityEngine.Object candidate, TrackAsset track)
        {
            Type bindingType = GetBindingType(track);
            BindingUtility.BindingAction action = BindingUtility.GetBindingAction(bindingType, candidate);
            return BindingUtility.GetBinding(action, candidate, bindingType);
        }
    }

    static class TrackEditorExtension
    {
        public static bool SupportsBindingAssign(this TrackEditor editor)
        {
            return TypeUtility.HasOverrideMethod(editor.GetType(), nameof(TrackEditor.GetBindingFrom));
        }

        public static void OnCreate_Safe(this TrackEditor editor, TrackAsset track, TrackAsset copiedFrom)
        {
            try
            {
                editor.OnCreate(track, copiedFrom);
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }
        }

        public static TrackDrawOptions GetTrackOptions_Safe(this TrackEditor editor, TrackAsset track, UnityEngine.Object binding)
        {
            try
            {
                return editor.GetTrackOptions(track, binding);
            }
            catch (Exception e)
            {
                Debug.LogException(e);
                return CustomTimelineEditorCache.GetDefaultTrackEditor().GetTrackOptions(track, binding);
            }
        }

        public static UnityEngine.Object GetBindingFrom_Safe(this TrackEditor editor, UnityEngine.Object candidate, TrackAsset track)
        {
            try
            {
                return editor.GetBindingFrom(candidate, track);
            }
            catch (Exception e)
            {
                Debug.LogException(e);
                return candidate;
            }
        }

        public static bool IsBindingAssignableFrom_Safe(this TrackEditor editor, UnityEngine.Object candidate, TrackAsset track)
        {
            try
            {
                return editor.IsBindingAssignableFrom(candidate, track);
            }
            catch (Exception e)
            {
                Debug.LogException(e);
                return false;
            }
        }

        public static void OnTrackChanged_Safe(this TrackEditor editor, TrackAsset track)
        {
            try
            {
                editor.OnTrackChanged(track);
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }
        }
    }
}