Unity-jump-proj
This commit is contained in:
@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class AnimatedParameterCache
|
||||
{
|
||||
static readonly Dictionary<Type, FieldInfo[]> k_ScriptPlayableFieldsCache = new Dictionary<Type, FieldInfo[]>();
|
||||
static readonly Dictionary<PropertyKey, FieldInfo> k_PropertyFieldInfoCache = new Dictionary<PropertyKey, FieldInfo>();
|
||||
static readonly Dictionary<PropertyKey, bool> k_PropertyIsAnimatableCache = new Dictionary<PropertyKey, bool>();
|
||||
static readonly Dictionary<PropertyKey, string> k_BindingNameCache = new Dictionary<PropertyKey, string>();
|
||||
|
||||
public static bool TryGetScriptPlayableFields(Type type, out FieldInfo[] scriptPlayableFields)
|
||||
{
|
||||
return k_ScriptPlayableFieldsCache.TryGetValue(type, out scriptPlayableFields);
|
||||
}
|
||||
|
||||
public static void SetScriptPlayableFields(Type type, FieldInfo[] scriptPlayableFields)
|
||||
{
|
||||
k_ScriptPlayableFieldsCache[type] = scriptPlayableFields;
|
||||
}
|
||||
|
||||
public static bool TryGetFieldInfoForProperty(SerializedProperty property, out FieldInfo fieldInfo)
|
||||
{
|
||||
return k_PropertyFieldInfoCache.TryGetValue(new PropertyKey(property), out fieldInfo);
|
||||
}
|
||||
|
||||
public static void SetFieldInfoForProperty(SerializedProperty property, FieldInfo fieldInfo)
|
||||
{
|
||||
k_PropertyFieldInfoCache[new PropertyKey(property)] = fieldInfo;
|
||||
}
|
||||
|
||||
public static bool TryGetIsPropertyAnimatable(SerializedProperty property, out bool isAnimatable)
|
||||
{
|
||||
return k_PropertyIsAnimatableCache.TryGetValue(new PropertyKey(property), out isAnimatable);
|
||||
}
|
||||
|
||||
public static void SetIsPropertyAnimatable(SerializedProperty property, bool isAnimatable)
|
||||
{
|
||||
k_PropertyIsAnimatableCache[new PropertyKey(property)] = isAnimatable;
|
||||
}
|
||||
|
||||
public static bool TryGetBindingName(Type type, string path, out string bindingName)
|
||||
{
|
||||
return k_BindingNameCache.TryGetValue(new PropertyKey(type, path), out bindingName);
|
||||
}
|
||||
|
||||
public static void SetBindingName(Type type, string path, string bindingName)
|
||||
{
|
||||
k_BindingNameCache[new PropertyKey(type, path)] = bindingName;
|
||||
}
|
||||
}
|
||||
|
||||
struct PropertyKey : IEquatable<PropertyKey>
|
||||
{
|
||||
readonly Type m_Type;
|
||||
readonly string m_Path;
|
||||
|
||||
public PropertyKey(SerializedProperty property)
|
||||
{
|
||||
m_Type = property.serializedObject.targetObject.GetType();
|
||||
m_Path = property.propertyPath;
|
||||
}
|
||||
|
||||
public PropertyKey(Type type, string path)
|
||||
{
|
||||
m_Type = type;
|
||||
m_Path = path;
|
||||
}
|
||||
|
||||
public bool Equals(PropertyKey other)
|
||||
{
|
||||
return m_Type == other.m_Type && string.Equals(m_Path, other.m_Path);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
return obj is PropertyKey && Equals((PropertyKey)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return ((m_Type != null ? m_Type.GetHashCode() : 0) * 397) ^ (m_Path != null ? m_Path.GetHashCode() : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1fe0f539450e54dbc85bfb2fa6b466fb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,358 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityObject = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class AnimatedParameterUtility
|
||||
{
|
||||
static readonly Type k_DefaultAnimationType = typeof(TimelineAsset);
|
||||
static SerializedObject s_CachedObject;
|
||||
|
||||
public static ICurvesOwner ToCurvesOwner(IPlayableAsset playableAsset, TimelineAsset timeline)
|
||||
{
|
||||
if (playableAsset == null)
|
||||
return null;
|
||||
|
||||
var curvesOwner = playableAsset as ICurvesOwner;
|
||||
if (curvesOwner == null)
|
||||
{
|
||||
// If the asset is not directly an ICurvesOwner, it might be the asset for a TimelineClip
|
||||
curvesOwner = TimelineRecording.FindClipWithAsset(timeline, playableAsset);
|
||||
}
|
||||
|
||||
return curvesOwner;
|
||||
}
|
||||
|
||||
public static bool TryGetSerializedPlayableAsset(UnityObject asset, out SerializedObject serializedObject)
|
||||
{
|
||||
serializedObject = null;
|
||||
if (asset == null || Attribute.IsDefined(asset.GetType(), typeof(NotKeyableAttribute)) || !HasScriptPlayable(asset))
|
||||
return false;
|
||||
|
||||
serializedObject = GetSerializedPlayableAsset(asset);
|
||||
return serializedObject != null;
|
||||
}
|
||||
|
||||
public static SerializedObject GetSerializedPlayableAsset(UnityObject asset)
|
||||
{
|
||||
if (!(asset is IPlayableAsset))
|
||||
return null;
|
||||
|
||||
var scriptObject = asset as ScriptableObject;
|
||||
if (scriptObject == null)
|
||||
return null;
|
||||
|
||||
if (s_CachedObject == null || s_CachedObject.targetObject != asset)
|
||||
{
|
||||
s_CachedObject = new SerializedObject(scriptObject);
|
||||
}
|
||||
|
||||
return s_CachedObject;
|
||||
}
|
||||
|
||||
public static void UpdateSerializedPlayableAsset(UnityObject asset)
|
||||
{
|
||||
var so = GetSerializedPlayableAsset(asset);
|
||||
if (so != null)
|
||||
so.UpdateIfRequiredOrScript();
|
||||
}
|
||||
|
||||
public static bool HasScriptPlayable(UnityObject asset)
|
||||
{
|
||||
if (asset == null)
|
||||
return false;
|
||||
|
||||
var scriptPlayable = asset as IPlayableBehaviour;
|
||||
return scriptPlayable != null || GetScriptPlayableFields(asset as IPlayableAsset).Any();
|
||||
}
|
||||
|
||||
public static FieldInfo[] GetScriptPlayableFields(IPlayableAsset asset)
|
||||
{
|
||||
if (asset == null)
|
||||
return new FieldInfo[0];
|
||||
|
||||
FieldInfo[] scriptPlayableFields;
|
||||
if (!AnimatedParameterCache.TryGetScriptPlayableFields(asset.GetType(), out scriptPlayableFields))
|
||||
{
|
||||
scriptPlayableFields = GetScriptPlayableFields_Internal(asset);
|
||||
AnimatedParameterCache.SetScriptPlayableFields(asset.GetType(), scriptPlayableFields);
|
||||
}
|
||||
|
||||
return scriptPlayableFields;
|
||||
}
|
||||
|
||||
static FieldInfo[] GetScriptPlayableFields_Internal(IPlayableAsset asset)
|
||||
{
|
||||
return asset.GetType()
|
||||
.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
.Where(
|
||||
f => typeof(IPlayableBehaviour).IsAssignableFrom(f.FieldType) && // The field is an IPlayableBehaviour
|
||||
(f.IsPublic || f.GetCustomAttributes(typeof(SerializeField), false).Any()) && // The field is either public or marked with [SerializeField]
|
||||
!f.GetCustomAttributes(typeof(NotKeyableAttribute), false).Any() && // The field is not marked with [NotKeyable]
|
||||
!f.GetCustomAttributes(typeof(HideInInspector), false).Any() && // The field is not marked with [HideInInspector]
|
||||
!f.FieldType.GetCustomAttributes(typeof(NotKeyableAttribute), false).Any()) // The field is not of a type marked with [NotKeyable]
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public static bool HasAnyAnimatableParameters(UnityObject asset)
|
||||
{
|
||||
return GetAllAnimatableParameters(asset).Any();
|
||||
}
|
||||
|
||||
public static IEnumerable<SerializedProperty> GetAllAnimatableParameters(UnityObject asset)
|
||||
{
|
||||
SerializedObject serializedObject;
|
||||
if (!TryGetSerializedPlayableAsset(asset, out serializedObject))
|
||||
yield break;
|
||||
|
||||
var prop = serializedObject.GetIterator();
|
||||
|
||||
// We need to keep this variable because prop starts invalid
|
||||
var outOfBounds = false;
|
||||
while (!outOfBounds && prop.NextVisible(true))
|
||||
{
|
||||
foreach (var property in SelectAnimatableProperty(prop))
|
||||
yield return property;
|
||||
|
||||
// We can become out of bounds by calling SelectAnimatableProperty, if the last iterated property is a color.
|
||||
outOfBounds = !prop.isValid;
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerable<SerializedProperty> SelectAnimatableProperty(SerializedProperty prop)
|
||||
{
|
||||
// We're only interested by animatable leaf parameters
|
||||
if (!prop.hasChildren && IsParameterAnimatable(prop))
|
||||
yield return prop.Copy();
|
||||
|
||||
// Color type is not considered "visible" when iterating
|
||||
if (prop.propertyType == SerializedPropertyType.Color)
|
||||
{
|
||||
var end = prop.GetEndProperty();
|
||||
|
||||
// For some reasons, if the last 2+ serialized properties are of type Color, prop becomes invalid and
|
||||
// Next() throws an exception. This is not the case when only the last serialized property is a Color.
|
||||
while (!SerializedProperty.EqualContents(prop, end) && prop.isValid && prop.Next(true))
|
||||
{
|
||||
foreach (var property in SelectAnimatableProperty(prop))
|
||||
yield return property;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsParameterAnimatable(UnityObject asset, string parameterName)
|
||||
{
|
||||
SerializedObject serializedObject;
|
||||
if (!TryGetSerializedPlayableAsset(asset, out serializedObject))
|
||||
return false;
|
||||
|
||||
var prop = serializedObject.FindProperty(parameterName);
|
||||
return IsParameterAnimatable(prop);
|
||||
}
|
||||
|
||||
public static bool IsParameterAnimatable(SerializedProperty property)
|
||||
{
|
||||
if (property == null)
|
||||
return false;
|
||||
|
||||
bool isAnimatable;
|
||||
if (!AnimatedParameterCache.TryGetIsPropertyAnimatable(property, out isAnimatable))
|
||||
{
|
||||
isAnimatable = IsParameterAnimatable_Internal(property);
|
||||
AnimatedParameterCache.SetIsPropertyAnimatable(property, isAnimatable);
|
||||
}
|
||||
|
||||
return isAnimatable;
|
||||
}
|
||||
|
||||
static bool IsParameterAnimatable_Internal(SerializedProperty property)
|
||||
{
|
||||
if (property == null)
|
||||
return false;
|
||||
|
||||
var asset = property.serializedObject.targetObject;
|
||||
|
||||
// Currently not supported
|
||||
if (asset is AnimationTrack)
|
||||
return false;
|
||||
|
||||
if (IsParameterKeyable(property))
|
||||
return asset is IPlayableBehaviour || IsParameterAtPathAnimatable(asset, property.propertyPath);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool IsParameterKeyable(SerializedProperty property)
|
||||
{
|
||||
return IsTypeAnimatable(property.propertyType) && IsKeyableInHierarchy(property);
|
||||
}
|
||||
|
||||
static bool IsKeyableInHierarchy(SerializedProperty property)
|
||||
{
|
||||
const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
var pathSegments = property.propertyPath.Split('.');
|
||||
var type = property.serializedObject.targetObject.GetType();
|
||||
foreach (var segment in pathSegments)
|
||||
{
|
||||
if (type.GetCustomAttributes(typeof(NotKeyableAttribute), false).Any())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type.IsArray)
|
||||
return false;
|
||||
|
||||
var fieldInfo = type.GetField(segment, bindingFlags);
|
||||
|
||||
if (fieldInfo == null ||
|
||||
fieldInfo.GetCustomAttributes(typeof(NotKeyableAttribute), false).Any() ||
|
||||
fieldInfo.GetCustomAttributes(typeof(HideInInspector), false).Any())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
type = fieldInfo.FieldType;
|
||||
|
||||
// only value types are supported
|
||||
if (!type.IsValueType && !typeof(IPlayableBehaviour).IsAssignableFrom(type))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsParameterAtPathAnimatable(UnityObject asset, string path)
|
||||
{
|
||||
if (asset == null)
|
||||
return false;
|
||||
|
||||
return GetScriptPlayableFields(asset as IPlayableAsset)
|
||||
.Any(
|
||||
f => path.StartsWith(f.Name, StringComparison.Ordinal) &&
|
||||
path.Length > f.Name.Length &&
|
||||
path[f.Name.Length] == '.');
|
||||
}
|
||||
|
||||
public static bool IsTypeAnimatable(SerializedPropertyType type)
|
||||
{
|
||||
// Note: Integer is not currently supported by the animated property system
|
||||
switch (type)
|
||||
{
|
||||
case SerializedPropertyType.Boolean:
|
||||
case SerializedPropertyType.Float:
|
||||
case SerializedPropertyType.Vector2:
|
||||
case SerializedPropertyType.Vector3:
|
||||
case SerializedPropertyType.Color:
|
||||
case SerializedPropertyType.Quaternion:
|
||||
case SerializedPropertyType.Vector4:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsParameterAnimated(UnityObject asset, AnimationClip animationData, string parameterName)
|
||||
{
|
||||
if (asset == null || animationData == null)
|
||||
return false;
|
||||
|
||||
var binding = GetCurveBinding(asset, parameterName);
|
||||
var bindings = AnimationClipCurveCache.Instance.GetCurveInfo(animationData).bindings;
|
||||
return bindings.Any(x => BindingMatchesParameterName(x, binding.propertyName));
|
||||
}
|
||||
|
||||
// Retrieve an animated parameter curve. parameter name is required to include the appropriate field for vectors
|
||||
// e.g.: position
|
||||
public static AnimationCurve GetAnimatedParameter(UnityObject asset, AnimationClip animationData, string parameterName)
|
||||
{
|
||||
if (!(asset is ScriptableObject) || animationData == null)
|
||||
return null;
|
||||
|
||||
var binding = GetCurveBinding(asset, parameterName);
|
||||
return AnimationUtility.GetEditorCurve(animationData, binding);
|
||||
}
|
||||
|
||||
// get an animatable curve binding for this parameter
|
||||
public static EditorCurveBinding GetCurveBinding(UnityObject asset, string parameterName)
|
||||
{
|
||||
var animationName = GetAnimatedParameterBindingName(asset, parameterName);
|
||||
return EditorCurveBinding.FloatCurve(string.Empty, GetValidAnimationType(asset), animationName);
|
||||
}
|
||||
|
||||
public static string GetAnimatedParameterBindingName(UnityObject asset, string parameterName)
|
||||
{
|
||||
if (asset == null)
|
||||
return parameterName;
|
||||
|
||||
string bindingName;
|
||||
if (!AnimatedParameterCache.TryGetBindingName(asset.GetType(), parameterName, out bindingName))
|
||||
{
|
||||
bindingName = GetAnimatedParameterBindingName_Internal(asset, parameterName);
|
||||
AnimatedParameterCache.SetBindingName(asset.GetType(), parameterName, bindingName);
|
||||
}
|
||||
|
||||
return bindingName;
|
||||
}
|
||||
|
||||
static string GetAnimatedParameterBindingName_Internal(UnityObject asset, string parameterName)
|
||||
{
|
||||
if (asset is IPlayableBehaviour)
|
||||
return parameterName;
|
||||
|
||||
// strip the IScript playable field name
|
||||
var fields = GetScriptPlayableFields(asset as IPlayableAsset);
|
||||
foreach (var f in fields)
|
||||
{
|
||||
if (parameterName.StartsWith(f.Name, StringComparison.Ordinal))
|
||||
{
|
||||
if (parameterName.Length > f.Name.Length && parameterName[f.Name.Length] == '.')
|
||||
return parameterName.Substring(f.Name.Length + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return parameterName;
|
||||
}
|
||||
|
||||
public static bool BindingMatchesParameterName(EditorCurveBinding binding, string parameterName)
|
||||
{
|
||||
if (binding.propertyName == parameterName)
|
||||
return true;
|
||||
|
||||
var indexOfDot = binding.propertyName.LastIndexOf('.');
|
||||
return indexOfDot > 0 && parameterName.Length == indexOfDot &&
|
||||
binding.propertyName.StartsWith(parameterName, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
// the animated type must be a non-abstract instantiable object.
|
||||
public static Type GetValidAnimationType(UnityObject asset)
|
||||
{
|
||||
return asset != null ? asset.GetType() : k_DefaultAnimationType;
|
||||
}
|
||||
|
||||
public static FieldInfo GetFieldInfoForProperty(SerializedProperty property)
|
||||
{
|
||||
FieldInfo fieldInfo;
|
||||
|
||||
if (!AnimatedParameterCache.TryGetFieldInfoForProperty(property, out fieldInfo))
|
||||
{
|
||||
Type _;
|
||||
fieldInfo = ScriptAttributeUtility.GetFieldInfoFromProperty(property, out _);
|
||||
AnimatedParameterCache.SetFieldInfoForProperty(property, fieldInfo);
|
||||
}
|
||||
|
||||
return fieldInfo;
|
||||
}
|
||||
|
||||
public static T GetAttributeForProperty<T>(SerializedProperty property) where T : Attribute
|
||||
{
|
||||
var fieldInfo = GetFieldInfoForProperty(property);
|
||||
return fieldInfo.GetCustomAttributes(typeof(T), false).FirstOrDefault() as T;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84d86c98104d94063ad70bc591530f65
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
// Helper methods for animated properties
|
||||
internal static class AnimatedPropertyUtility
|
||||
{
|
||||
public static bool IsMaterialProperty(string propertyName)
|
||||
{
|
||||
return propertyName.StartsWith("material.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a propertyName (from an EditorCurveBinding), and the gameObject it refers to,
|
||||
/// remaps the path to include the exposed name of the shader parameter
|
||||
/// </summary>
|
||||
/// <param name="gameObject">The gameObject being referenced.</param>
|
||||
/// <param name="propertyName">The propertyName to remap.</param>
|
||||
/// <returns>The remapped propertyName, or the original propertyName if it cannot be remapped</returns>
|
||||
public static string RemapMaterialName(GameObject gameObject, string propertyName)
|
||||
{
|
||||
if (!IsMaterialProperty(propertyName) || gameObject == null)
|
||||
return propertyName;
|
||||
|
||||
var renderers = gameObject.GetComponents<Renderer>();
|
||||
if (renderers == null || renderers.Length == 0)
|
||||
return propertyName;
|
||||
|
||||
var propertySplits = propertyName.Split('.');
|
||||
if (propertySplits.Length <= 1)
|
||||
return propertyName;
|
||||
|
||||
// handles post fixes for texture properties
|
||||
var exposedParameter = HandleTextureProperties(propertySplits[1], out var postFix);
|
||||
foreach (var renderer in renderers)
|
||||
{
|
||||
foreach (var material in renderer.sharedMaterials)
|
||||
{
|
||||
if (material.shader == null)
|
||||
continue;
|
||||
|
||||
var index = material.shader.FindPropertyIndex(exposedParameter);
|
||||
if (index >= 0)
|
||||
{
|
||||
propertySplits[1] = material.shader.GetPropertyDescription(index) + postFix;
|
||||
return String.Join(".", propertySplits);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return propertyName;
|
||||
}
|
||||
|
||||
private static string HandleTextureProperties(string exposedParameter, out string postFix)
|
||||
{
|
||||
postFix = String.Empty;
|
||||
RemoveEnding(ref exposedParameter, ref postFix, "_ST");
|
||||
RemoveEnding(ref exposedParameter, ref postFix, "_TexelSize");
|
||||
RemoveEnding(ref exposedParameter, ref postFix, "_HDR");
|
||||
return exposedParameter;
|
||||
}
|
||||
|
||||
private static void RemoveEnding(ref string name, ref string postFix, string ending)
|
||||
{
|
||||
if (name.EndsWith(ending))
|
||||
{
|
||||
name = name.Substring(0, name.Length - ending.Length);
|
||||
postFix = ending;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e22173cccd50433098692e06d6811255
|
||||
timeCreated: 1602163952
|
@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class BindingUtility
|
||||
{
|
||||
public enum BindingAction
|
||||
{
|
||||
DoNotBind,
|
||||
BindDirectly,
|
||||
BindToExistingComponent,
|
||||
BindToMissingComponent
|
||||
}
|
||||
|
||||
const string k_BindingOperation = "Bind Track";
|
||||
|
||||
public static void Bind(PlayableDirector director, TrackAsset bindTo, Object objectToBind)
|
||||
{
|
||||
if (director == null || bindTo == null || TimelineWindow.instance == null)
|
||||
return;
|
||||
|
||||
if (director.GetGenericBinding(bindTo) == objectToBind)
|
||||
return;
|
||||
|
||||
TimelineWindow.instance.state.previewMode = false; // returns all objects to previous state
|
||||
TimelineUndo.PushUndo(director, k_BindingOperation);
|
||||
director.SetGenericBinding(bindTo, objectToBind);
|
||||
TimelineWindow.instance.state.rebuildGraph = true;
|
||||
}
|
||||
|
||||
public static void BindWithEditorValidation(PlayableDirector director, TrackAsset bindTo, Object objectToBind)
|
||||
{
|
||||
TrackEditor trackEditor = CustomTimelineEditorCache.GetTrackEditor(bindTo);
|
||||
Object validatedObject = trackEditor.GetBindingFrom_Safe(objectToBind, bindTo);
|
||||
Bind(director, bindTo, validatedObject);
|
||||
}
|
||||
|
||||
public static void BindWithInteractiveEditorValidation(PlayableDirector director, TrackAsset bindTo, Object objectToBind)
|
||||
{
|
||||
TrackEditor trackEditor = CustomTimelineEditorCache.GetTrackEditor(bindTo);
|
||||
if (trackEditor.SupportsBindingAssign())
|
||||
BindWithEditorValidation(director, bindTo, objectToBind);
|
||||
else
|
||||
{
|
||||
Type bindingType = TypeUtility.GetTrackBindingAttribute(bindTo.GetType())?.type;
|
||||
BindingAction action = GetBindingAction(bindingType, objectToBind);
|
||||
if (action == BindingAction.BindToMissingComponent)
|
||||
InteractiveBindToMissingComponent(director, bindTo, objectToBind, bindingType);
|
||||
else
|
||||
{
|
||||
var validatedObject = GetBinding(action, objectToBind, bindingType);
|
||||
Bind(director, bindTo, validatedObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static BindingAction GetBindingAction(Type requiredBindingType, Object objectToBind)
|
||||
{
|
||||
if (requiredBindingType == null || objectToBind == null)
|
||||
return BindingAction.DoNotBind;
|
||||
|
||||
// prevent drag and drop of prefab assets
|
||||
if (PrefabUtility.IsPartOfPrefabAsset(objectToBind))
|
||||
return BindingAction.DoNotBind;
|
||||
|
||||
if (requiredBindingType.IsInstanceOfType(objectToBind))
|
||||
return BindingAction.BindDirectly;
|
||||
|
||||
var draggedGameObject = objectToBind as GameObject;
|
||||
|
||||
if (!typeof(Component).IsAssignableFrom(requiredBindingType) || draggedGameObject == null)
|
||||
return BindingAction.DoNotBind;
|
||||
|
||||
if (draggedGameObject.GetComponent(requiredBindingType) == null)
|
||||
return BindingAction.BindToMissingComponent;
|
||||
|
||||
return BindingAction.BindToExistingComponent;
|
||||
}
|
||||
|
||||
public static Object GetBinding(BindingAction bindingAction, Object objectToBind, Type requiredBindingType)
|
||||
{
|
||||
if (objectToBind == null) return null;
|
||||
|
||||
switch (bindingAction)
|
||||
{
|
||||
case BindingAction.BindDirectly:
|
||||
{
|
||||
return objectToBind;
|
||||
}
|
||||
case BindingAction.BindToExistingComponent:
|
||||
{
|
||||
var gameObjectBeingDragged = objectToBind as GameObject;
|
||||
Debug.Assert(gameObjectBeingDragged != null, "The object being dragged was detected as being a GameObject");
|
||||
return gameObjectBeingDragged.GetComponent(requiredBindingType);
|
||||
}
|
||||
case BindingAction.BindToMissingComponent:
|
||||
{
|
||||
var gameObjectBeingDragged = objectToBind as GameObject;
|
||||
Debug.Assert(gameObjectBeingDragged != null, "The object being dragged was detected as being a GameObject");
|
||||
return Undo.AddComponent(gameObjectBeingDragged, requiredBindingType);
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static void InteractiveBindToMissingComponent(PlayableDirector director, TrackAsset bindTo, Object objectToBind, Type requiredComponentType)
|
||||
{
|
||||
var gameObjectBeingDragged = objectToBind as GameObject;
|
||||
Debug.Assert(gameObjectBeingDragged != null, "The object being dragged was detected as being a GameObject");
|
||||
|
||||
string typeNameOfComponent = requiredComponentType.ToString().Split(".".ToCharArray()).Last();
|
||||
var bindMenu = new GenericMenu();
|
||||
bindMenu.AddItem(
|
||||
EditorGUIUtility.TextContent("Create " + typeNameOfComponent + " on " + gameObjectBeingDragged.name),
|
||||
false,
|
||||
nullParam => Bind(director, bindTo, Undo.AddComponent(gameObjectBeingDragged, requiredComponentType)),
|
||||
null);
|
||||
|
||||
bindMenu.AddSeparator("");
|
||||
bindMenu.AddItem(EditorGUIUtility.TrTextContent("Cancel"), false, userData => { }, null);
|
||||
bindMenu.ShowAsContext();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef5fa6e2005defb4ab5142723827b58e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,230 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
using UnityEditor.SceneManagement;
|
||||
#else
|
||||
using UnityEditor.Experimental.SceneManagement;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
enum TitleMode
|
||||
{
|
||||
None,
|
||||
DisabledComponent,
|
||||
Prefab,
|
||||
PrefabOutOfContext,
|
||||
Asset,
|
||||
GameObject
|
||||
}
|
||||
|
||||
struct BreadCrumbTitle
|
||||
{
|
||||
public string name;
|
||||
public TitleMode mode;
|
||||
}
|
||||
|
||||
class BreadcrumbDrawer
|
||||
{
|
||||
static readonly GUIContent s_TextContent = new GUIContent();
|
||||
static readonly string k_DisabledComponentText = L10n.Tr("The PlayableDirector is disabled");
|
||||
static readonly string k_PrefabOutOfContext = L10n.Tr("Prefab Isolation not enabled. Click to Enable.");
|
||||
|
||||
static readonly GUIStyle k_BreadCrumbLeft;
|
||||
static readonly GUIStyle k_BreadCrumbMid;
|
||||
static readonly GUIStyle k_BreadCrumbLeftBg;
|
||||
static readonly GUIStyle k_BreadCrumbMidBg;
|
||||
static readonly GUIStyle k_BreadCrumbMidSelected;
|
||||
static readonly GUIStyle k_BreadCrumbMidBgSelected;
|
||||
|
||||
static readonly Texture k_TimelineIcon;
|
||||
|
||||
const string k_Elipsis = "…";
|
||||
|
||||
static BreadcrumbDrawer()
|
||||
{
|
||||
k_BreadCrumbLeft = "GUIEditor.BreadcrumbLeft";
|
||||
k_BreadCrumbMid = "GUIEditor.BreadcrumbMid";
|
||||
k_BreadCrumbLeftBg = "GUIEditor.BreadcrumbLeftBackground";
|
||||
k_BreadCrumbMidBg = "GUIEditor.BreadcrumbMidBackground";
|
||||
|
||||
k_BreadCrumbMidSelected = k_BreadCrumbMid;
|
||||
k_BreadCrumbMidSelected.normal = k_BreadCrumbMidSelected.onNormal;
|
||||
|
||||
k_BreadCrumbMidBgSelected = k_BreadCrumbMidBg;
|
||||
k_BreadCrumbMidBgSelected.normal = k_BreadCrumbMidBgSelected.onNormal;
|
||||
k_TimelineIcon = EditorGUIUtility.IconContent("TimelineAsset Icon").image;
|
||||
}
|
||||
|
||||
static string FitTextInArea(float areaWidth, string text, GUIStyle style)
|
||||
{
|
||||
var borderWidth = style.border.left + style.border.right;
|
||||
var textWidth = style.CalcSize(EditorGUIUtility.TextContent(text)).x;
|
||||
|
||||
if (borderWidth + textWidth < areaWidth)
|
||||
return text;
|
||||
|
||||
// Need to truncate the text to fit in the areaWidth
|
||||
var textAreaWidth = areaWidth - borderWidth;
|
||||
var pixByChar = textWidth / text.Length;
|
||||
var charNeeded = (int)Mathf.Floor(textAreaWidth / pixByChar);
|
||||
charNeeded -= k_Elipsis.Length;
|
||||
|
||||
if (charNeeded <= 0)
|
||||
return k_Elipsis;
|
||||
|
||||
if (charNeeded <= text.Length)
|
||||
return k_Elipsis + " " + text.Substring(text.Length - charNeeded);
|
||||
|
||||
return k_Elipsis;
|
||||
}
|
||||
|
||||
public static void Draw(float breadcrumbAreaWidth, List<BreadCrumbTitle> labels, Action<int> navigateToBreadcrumbIndex)
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
var labelWidth = (int)(breadcrumbAreaWidth / labels.Count);
|
||||
|
||||
for (var i = 0; i < labels.Count; i++)
|
||||
{
|
||||
var label = labels[i];
|
||||
|
||||
var style = i == 0 ? k_BreadCrumbLeft : k_BreadCrumbMid;
|
||||
var backgroundStyle = i == 0 ? k_BreadCrumbLeftBg : k_BreadCrumbMidBg;
|
||||
|
||||
if (i == labels.Count - 1)
|
||||
{
|
||||
if (i > 0) // Only tint last breadcrumb if we are dug-in
|
||||
DrawBreadcrumbAsSelectedSubSequence(labelWidth, label, k_BreadCrumbMidSelected, k_BreadCrumbMidBgSelected);
|
||||
else
|
||||
DrawActiveBreadcrumb(labelWidth, label, style, backgroundStyle);
|
||||
}
|
||||
else
|
||||
{
|
||||
var previousContentColor = GUI.contentColor;
|
||||
GUI.contentColor = new Color(previousContentColor.r,
|
||||
previousContentColor.g,
|
||||
previousContentColor.b,
|
||||
previousContentColor.a * 0.6f);
|
||||
var content = GetTextContent(labelWidth, label, style);
|
||||
var rect = GetBreadcrumbLayoutRect(content, style);
|
||||
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
backgroundStyle.Draw(rect, GUIContent.none, 0);
|
||||
|
||||
if (GUI.Button(rect, content, style))
|
||||
navigateToBreadcrumbIndex.Invoke(i);
|
||||
GUI.contentColor = previousContentColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
static GUIContent GetTextContent(int width, BreadCrumbTitle text, GUIStyle style)
|
||||
{
|
||||
s_TextContent.tooltip = string.Empty;
|
||||
s_TextContent.image = null;
|
||||
if (text.mode == TitleMode.DisabledComponent)
|
||||
{
|
||||
s_TextContent.tooltip = k_DisabledComponentText;
|
||||
s_TextContent.image = EditorGUIUtility.GetHelpIcon(MessageType.Warning);
|
||||
}
|
||||
else if (text.mode == TitleMode.Prefab)
|
||||
s_TextContent.image = PrefabUtility.GameObjectStyles.prefabIcon;
|
||||
else if (text.mode == TitleMode.GameObject)
|
||||
s_TextContent.image = PrefabUtility.GameObjectStyles.gameObjectIcon;
|
||||
else if (text.mode == TitleMode.Asset)
|
||||
s_TextContent.image = k_TimelineIcon;
|
||||
else if (text.mode == TitleMode.PrefabOutOfContext)
|
||||
{
|
||||
s_TextContent.image = PrefabUtility.GameObjectStyles.prefabIcon;
|
||||
if (!TimelineWindow.instance.locked)
|
||||
s_TextContent.tooltip = k_PrefabOutOfContext;
|
||||
}
|
||||
|
||||
if (s_TextContent.image != null)
|
||||
width = Math.Max(0, width - s_TextContent.image.width);
|
||||
s_TextContent.text = FitTextInArea(width, text.name, style);
|
||||
|
||||
return s_TextContent;
|
||||
}
|
||||
|
||||
static void DrawBreadcrumbAsSelectedSubSequence(int width, BreadCrumbTitle label, GUIStyle style, GUIStyle backgroundStyle)
|
||||
{
|
||||
var rect = DrawActiveBreadcrumb(width, label, style, backgroundStyle);
|
||||
const float underlineThickness = 2.0f;
|
||||
const float underlineVerticalOffset = 0.0f;
|
||||
var underlineHorizontalOffset = backgroundStyle.border.right * 0.333f;
|
||||
var underlineRect = Rect.MinMaxRect(
|
||||
rect.xMin - underlineHorizontalOffset,
|
||||
rect.yMax - underlineThickness - underlineVerticalOffset,
|
||||
rect.xMax - underlineHorizontalOffset,
|
||||
rect.yMax - underlineVerticalOffset);
|
||||
|
||||
EditorGUI.DrawRect(underlineRect, DirectorStyles.Instance.customSkin.colorSubSequenceDurationLine);
|
||||
}
|
||||
|
||||
static Rect GetBreadcrumbLayoutRect(GUIContent content, GUIStyle style)
|
||||
{
|
||||
// the image makes the button far too big compared to non-image versions
|
||||
var image = content.image;
|
||||
content.image = null;
|
||||
var size = style.CalcSizeWithConstraints(content, Vector2.zero);
|
||||
content.image = image;
|
||||
if (image != null)
|
||||
size.x += size.y; // assumes square image, constrained by height
|
||||
|
||||
return GUILayoutUtility.GetRect(content, style, GUILayout.MaxWidth(size.x));
|
||||
}
|
||||
|
||||
static Rect DrawActiveBreadcrumb(int width, BreadCrumbTitle label, GUIStyle style, GUIStyle backgroundStyle)
|
||||
{
|
||||
var content = GetTextContent(width, label, style);
|
||||
var rect = GetBreadcrumbLayoutRect(content, style);
|
||||
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
backgroundStyle.Draw(rect, GUIContent.none, 0);
|
||||
}
|
||||
|
||||
if (GUI.Button(rect, content, style))
|
||||
{
|
||||
UnityEngine.Object target = TimelineEditor.inspectedDirector;
|
||||
if (target == null)
|
||||
target = TimelineEditor.inspectedAsset;
|
||||
if (target != null)
|
||||
{
|
||||
bool ping = true;
|
||||
if (label.mode == TitleMode.PrefabOutOfContext)
|
||||
{
|
||||
var gameObject = PrefabUtility.GetRootGameObject(target);
|
||||
if (gameObject != null)
|
||||
{
|
||||
target = gameObject; // ping the prefab root if it's locked.
|
||||
if (!TimelineWindow.instance.locked)
|
||||
{
|
||||
var assetPath = AssetDatabase.GetAssetPath(gameObject);
|
||||
if (!string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
var stage = PrefabStageUtility.OpenPrefab(assetPath);
|
||||
if (stage != null)
|
||||
ping = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ping)
|
||||
{
|
||||
EditorGUIUtility.PingObject(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rect;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 526f285e8d4fb8140b4cdfeb9102d8cb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,397 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class ClipModifier
|
||||
{
|
||||
public static bool Delete(TimelineAsset timeline, TimelineClip clip)
|
||||
{
|
||||
return timeline.DeleteClip(clip);
|
||||
}
|
||||
|
||||
public static bool Tile(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
if (clips.Count() < 2)
|
||||
return false;
|
||||
|
||||
var clipsByTracks = clips.GroupBy(x => x.GetParentTrack())
|
||||
.Select(track => new { track.Key, Items = track.OrderBy(c => c.start) });
|
||||
|
||||
foreach (var track in clipsByTracks)
|
||||
{
|
||||
UndoExtensions.RegisterTrack(track.Key, L10n.Tr("Tile"));
|
||||
}
|
||||
|
||||
foreach (var track in clipsByTracks)
|
||||
{
|
||||
double newStart = track.Items.First().start;
|
||||
foreach (var c in track.Items)
|
||||
{
|
||||
c.start = newStart;
|
||||
newStart += c.duration;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TrimStart(IEnumerable<TimelineClip> clips, double trimTime)
|
||||
{
|
||||
var result = false;
|
||||
|
||||
foreach (var clip in clips)
|
||||
result |= TrimStart(clip, trimTime);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool TrimStart(TimelineClip clip, double trimTime)
|
||||
{
|
||||
if (clip.asset == null)
|
||||
return false;
|
||||
|
||||
if (clip.start > trimTime)
|
||||
return false;
|
||||
|
||||
if (clip.end < trimTime)
|
||||
return false;
|
||||
|
||||
UndoExtensions.RegisterClip(clip, L10n.Tr("Trim Clip Start"));
|
||||
|
||||
// Note: We are NOT using edit modes in this case because we want the same result
|
||||
// regardless of the selected EditMode: split at cursor and delete left part
|
||||
SetStart(clip, trimTime, false);
|
||||
clip.ConformEaseValues();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TrimEnd(IEnumerable<TimelineClip> clips, double trimTime)
|
||||
{
|
||||
var result = false;
|
||||
|
||||
foreach (var clip in clips)
|
||||
result |= TrimEnd(clip, trimTime);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool TrimEnd(TimelineClip clip, double trimTime)
|
||||
{
|
||||
if (clip.asset == null)
|
||||
return false;
|
||||
|
||||
if (clip.start > trimTime)
|
||||
return false;
|
||||
|
||||
if (clip.end < trimTime)
|
||||
return false;
|
||||
|
||||
UndoExtensions.RegisterClip(clip, L10n.Tr("Trim Clip End"));
|
||||
TrimClipWithEditMode(clip, TrimEdge.End, trimTime);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool MatchDuration(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
double referenceDuration = clips.First().duration;
|
||||
UndoExtensions.RegisterClips(clips, L10n.Tr("Match Clip Duration"));
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
var newEnd = clip.start + referenceDuration;
|
||||
TrimClipWithEditMode(clip, TrimEdge.End, newEnd);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool Split(IEnumerable<TimelineClip> clips, double splitTime, PlayableDirector director)
|
||||
{
|
||||
var result = false;
|
||||
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
if (clip.start >= splitTime)
|
||||
continue;
|
||||
|
||||
if (clip.end <= splitTime)
|
||||
continue;
|
||||
|
||||
UndoExtensions.RegisterClip(clip, L10n.Tr("Split Clip"));
|
||||
|
||||
TimelineClip newClip = TimelineHelpers.Clone(clip, director, director, clip.start);
|
||||
|
||||
clip.easeInDuration = 0;
|
||||
newClip.easeOutDuration = 0;
|
||||
|
||||
SetStart(clip, splitTime, false);
|
||||
SetEnd(newClip, splitTime, false);
|
||||
|
||||
// Sort produced by cloning clips on top of each other is unpredictable (it varies between mono runtimes)
|
||||
clip.GetParentTrack().SortClips();
|
||||
|
||||
result = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void SetStart(TimelineClip clip, double time, bool affectTimeScale)
|
||||
{
|
||||
var supportsClipIn = clip.SupportsClipIn();
|
||||
var supportsPadding = TimelineUtility.IsRecordableAnimationClip(clip);
|
||||
bool calculateTimeScale = (affectTimeScale && clip.SupportsSpeedMultiplier());
|
||||
|
||||
// treat empty recordable clips as not supporting clip in (there are no keys to modify)
|
||||
if (supportsPadding && (clip.animationClip == null || clip.animationClip.empty))
|
||||
{
|
||||
supportsClipIn = false;
|
||||
}
|
||||
|
||||
if (supportsClipIn && !supportsPadding && !calculateTimeScale)
|
||||
{
|
||||
var minStart = clip.FromLocalTimeUnbound(0.0);
|
||||
if (time < minStart)
|
||||
time = minStart;
|
||||
}
|
||||
|
||||
var maxStart = clip.end - TimelineClip.kMinDuration;
|
||||
if (time > maxStart)
|
||||
time = maxStart;
|
||||
|
||||
var timeOffset = time - clip.start;
|
||||
var duration = clip.duration - timeOffset;
|
||||
|
||||
if (calculateTimeScale)
|
||||
{
|
||||
var f = clip.duration / duration;
|
||||
clip.timeScale *= f;
|
||||
}
|
||||
|
||||
|
||||
if (supportsClipIn && !calculateTimeScale)
|
||||
{
|
||||
if (supportsPadding)
|
||||
{
|
||||
double clipInGlobal = clip.clipIn / clip.timeScale;
|
||||
double keyShift = -timeOffset;
|
||||
if (timeOffset < 0) // left drag, eliminate clipIn before shifting
|
||||
{
|
||||
double clipInDelta = Math.Max(-clipInGlobal, timeOffset);
|
||||
keyShift = -Math.Min(0, timeOffset - clipInDelta);
|
||||
clip.clipIn += clipInDelta * clip.timeScale;
|
||||
}
|
||||
else if (timeOffset > 0) // right drag, elimate padding in animation clip before adding clip in
|
||||
{
|
||||
var clipInfo = AnimationClipCurveCache.Instance.GetCurveInfo(clip.animationClip);
|
||||
double keyDelta = clip.FromLocalTimeUnbound(clipInfo.keyTimes.Min()) - clip.start;
|
||||
keyShift = -Math.Max(0, Math.Min(timeOffset, keyDelta));
|
||||
clip.clipIn += Math.Max(timeOffset + keyShift, 0) * clip.timeScale;
|
||||
}
|
||||
if (keyShift != 0)
|
||||
{
|
||||
AnimationTrackRecorder.ShiftAnimationClip(clip.animationClip, (float)(keyShift * clip.timeScale));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
clip.clipIn += timeOffset * clip.timeScale;
|
||||
}
|
||||
}
|
||||
|
||||
clip.start = time;
|
||||
clip.duration = duration;
|
||||
}
|
||||
|
||||
public static void SetEnd(TimelineClip clip, double time, bool affectTimeScale)
|
||||
{
|
||||
var duration = Math.Max(time - clip.start, TimelineClip.kMinDuration);
|
||||
|
||||
if (affectTimeScale && clip.SupportsSpeedMultiplier())
|
||||
{
|
||||
var f = clip.duration / duration;
|
||||
clip.timeScale *= f;
|
||||
}
|
||||
|
||||
clip.duration = duration;
|
||||
}
|
||||
|
||||
public static bool ResetEditing(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
var result = false;
|
||||
|
||||
foreach (var clip in clips)
|
||||
result = result || ResetEditing(clip);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool ResetEditing(TimelineClip clip)
|
||||
{
|
||||
if (clip.asset == null)
|
||||
return false;
|
||||
|
||||
UndoExtensions.RegisterClip(clip, L10n.Tr("Reset Clip Editing"));
|
||||
|
||||
clip.clipIn = 0.0;
|
||||
|
||||
if (clip.clipAssetDuration < double.MaxValue)
|
||||
{
|
||||
var duration = clip.clipAssetDuration / clip.timeScale;
|
||||
TrimClipWithEditMode(clip, TrimEdge.End, clip.start + duration);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool MatchContent(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
var result = false;
|
||||
|
||||
foreach (var clip in clips)
|
||||
result |= MatchContent(clip);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool MatchContent(TimelineClip clip)
|
||||
{
|
||||
if (clip.asset == null)
|
||||
return false;
|
||||
|
||||
UndoExtensions.RegisterClip(clip, L10n.Tr("Match Clip Content"));
|
||||
|
||||
var newStartCandidate = clip.start - clip.clipIn / clip.timeScale;
|
||||
var newStart = newStartCandidate < 0.0 ? 0.0 : newStartCandidate;
|
||||
|
||||
TrimClipWithEditMode(clip, TrimEdge.Start, newStart);
|
||||
|
||||
// In case resetting the start was blocked by edit mode or timeline start, we do the best we can
|
||||
clip.clipIn = (clip.start - newStartCandidate) * clip.timeScale;
|
||||
if (clip.clipAssetDuration > 0 && TimelineHelpers.HasUsableAssetDuration(clip))
|
||||
{
|
||||
var duration = TimelineHelpers.GetLoopDuration(clip);
|
||||
var offset = (clip.clipIn / clip.timeScale) % duration;
|
||||
TrimClipWithEditMode(clip, TrimEdge.End, clip.start - offset + duration);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void TrimClipWithEditMode(TimelineClip clip, TrimEdge edge, double time)
|
||||
{
|
||||
var clipItem = ItemsUtils.ToItem(clip);
|
||||
EditMode.BeginTrim(clipItem, edge);
|
||||
if (edge == TrimEdge.Start)
|
||||
EditMode.TrimStart(clipItem, time, false);
|
||||
else
|
||||
EditMode.TrimEnd(clipItem, time, false);
|
||||
EditMode.FinishTrim();
|
||||
}
|
||||
|
||||
public static bool CompleteLastLoop(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
CompleteLastLoop(clip);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void CompleteLastLoop(TimelineClip clip)
|
||||
{
|
||||
FixLoops(clip, true);
|
||||
}
|
||||
|
||||
public static bool TrimLastLoop(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
TrimLastLoop(clip);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void TrimLastLoop(TimelineClip clip)
|
||||
{
|
||||
FixLoops(clip, false);
|
||||
}
|
||||
|
||||
static void FixLoops(TimelineClip clip, bool completeLastLoop)
|
||||
{
|
||||
if (!TimelineHelpers.HasUsableAssetDuration(clip))
|
||||
return;
|
||||
|
||||
var loopDuration = TimelineHelpers.GetLoopDuration(clip);
|
||||
var firstLoopDuration = loopDuration - clip.clipIn * (1.0 / clip.timeScale);
|
||||
|
||||
// Making sure we don't trim to zero
|
||||
if (!completeLastLoop && firstLoopDuration > clip.duration)
|
||||
return;
|
||||
|
||||
var numLoops = (clip.duration - firstLoopDuration) / loopDuration;
|
||||
var numCompletedLoops = Math.Floor(numLoops);
|
||||
|
||||
if (!(numCompletedLoops < numLoops))
|
||||
return;
|
||||
|
||||
if (completeLastLoop)
|
||||
numCompletedLoops += 1;
|
||||
|
||||
var newEnd = clip.start + firstLoopDuration + loopDuration * numCompletedLoops;
|
||||
|
||||
UndoExtensions.RegisterClip(clip, L10n.Tr("Trim Clip Last Loop"));
|
||||
|
||||
TrimClipWithEditMode(clip, TrimEdge.End, newEnd);
|
||||
}
|
||||
|
||||
public static bool DoubleSpeed(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
if (clip.SupportsSpeedMultiplier())
|
||||
{
|
||||
UndoExtensions.RegisterClip(clip, L10n.Tr("Double Clip Speed"));
|
||||
clip.timeScale = clip.timeScale * 2.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool HalfSpeed(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
if (clip.SupportsSpeedMultiplier())
|
||||
{
|
||||
UndoExtensions.RegisterClip(clip, L10n.Tr("Half Clip Speed"));
|
||||
clip.timeScale = clip.timeScale * 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ResetSpeed(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
if (clip.timeScale != 1.0)
|
||||
{
|
||||
UndoExtensions.RegisterClip(clip, L10n.Tr("Reset Clip Speed"));
|
||||
clip.timeScale = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0eeee3cdfa56734abca5c1a4e7989ba
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,156 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
class Clipboard
|
||||
{
|
||||
class ExposedReferenceTable : IExposedPropertyTable
|
||||
{
|
||||
Dictionary<PropertyName, Object> m_ReferenceTable = new Dictionary<PropertyName, Object>();
|
||||
public void SetReferenceValue(PropertyName id, Object value)
|
||||
{
|
||||
m_ReferenceTable[id] = value;
|
||||
}
|
||||
|
||||
public Object GetReferenceValue(PropertyName id, out bool idValid)
|
||||
{
|
||||
Object reference;
|
||||
idValid = m_ReferenceTable.TryGetValue(id, out reference);
|
||||
return reference;
|
||||
}
|
||||
|
||||
public void ClearReferenceValue(PropertyName id)
|
||||
{
|
||||
m_ReferenceTable.Remove(id);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
m_ReferenceTable.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public struct ClipboardTrackEntry
|
||||
{
|
||||
public TrackAsset item;
|
||||
public TrackAsset parent;
|
||||
public List<Object> bindings;
|
||||
}
|
||||
|
||||
static readonly int kListInitialSize = 10;
|
||||
|
||||
readonly List<ItemsPerTrack> m_ItemsData = new List<ItemsPerTrack>(kListInitialSize);
|
||||
readonly List<ClipboardTrackEntry> m_trackData = new List<ClipboardTrackEntry>(kListInitialSize);
|
||||
TimelineAsset rootTimeline;
|
||||
|
||||
public readonly IExposedPropertyTable exposedPropertyTable = new ExposedReferenceTable();
|
||||
|
||||
public Clipboard()
|
||||
{
|
||||
rootTimeline = CreateTimeline();
|
||||
|
||||
EditorApplication.playModeStateChanged += OnPlayModeChanged;
|
||||
}
|
||||
|
||||
public void CopyItems(IEnumerable<ITimelineItem> items)
|
||||
{
|
||||
using (new TimelineUndo.DisableUndoGuard(true))
|
||||
{
|
||||
var itemsByParent = items.ToLookup(i => i.parentTrack);
|
||||
foreach (var itemsGroup in itemsByParent)
|
||||
{
|
||||
var parent = itemsGroup.Key;
|
||||
var itemsList = new List<ITimelineItem>();
|
||||
foreach (var item in itemsGroup)
|
||||
{
|
||||
if (item is ClipItem)
|
||||
itemsList.Add(CopyItem((ClipItem)item));
|
||||
else if (item is MarkerItem)
|
||||
itemsList.Add(CopyItem((MarkerItem)item));
|
||||
}
|
||||
m_ItemsData.Add(new ItemsPerTrack(parent, itemsList));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClipItem CopyItem(ClipItem clipItem)
|
||||
{
|
||||
var newClip = TimelineHelpers.Clone(clipItem.clip, TimelineWindow.instance.state.editSequence.director, exposedPropertyTable, rootTimeline);
|
||||
return new ClipItem(newClip);
|
||||
}
|
||||
|
||||
static MarkerItem CopyItem(MarkerItem markerItem)
|
||||
{
|
||||
var markerObject = markerItem.marker as Object;
|
||||
if (markerObject != null)
|
||||
{
|
||||
var newMarker = Object.Instantiate(markerObject);
|
||||
newMarker.name = markerObject.name;
|
||||
return new MarkerItem((IMarker)newMarker);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void CopyTracks(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
using (new TimelineUndo.DisableUndoGuard(true))
|
||||
{
|
||||
foreach (var track in TrackExtensions.FilterTracks(tracks))
|
||||
{
|
||||
var newTrack = track.Duplicate(TimelineEditor.inspectedDirector, TimelineEditor.clipboard.exposedPropertyTable, rootTimeline);
|
||||
|
||||
var originalTracks = track.GetFlattenedChildTracks().Append(track);
|
||||
var newTracks = newTrack.GetFlattenedChildTracks().Append(newTrack);
|
||||
|
||||
var toBind = new List<Object>();
|
||||
|
||||
// Collect all track bindings to duplicate
|
||||
var originalIt = originalTracks.GetEnumerator();
|
||||
var newIt = newTracks.GetEnumerator();
|
||||
while (originalIt.MoveNext() && newIt.MoveNext())
|
||||
{
|
||||
toBind.Add(TimelineEditor.inspectedDirector != null ? TimelineEditor.inspectedDirector.GetGenericBinding(originalIt.Current) : null);
|
||||
}
|
||||
m_trackData.Add(new ClipboardTrackEntry { item = newTrack, parent = track.parent as TrackAsset, bindings = toBind });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<ClipboardTrackEntry> GetTracks()
|
||||
{
|
||||
return m_trackData;
|
||||
}
|
||||
|
||||
public IEnumerable<ItemsPerTrack> GetCopiedItems()
|
||||
{
|
||||
return m_ItemsData;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
m_ItemsData.Clear();
|
||||
m_trackData.Clear();
|
||||
rootTimeline = CreateTimeline();
|
||||
((ExposedReferenceTable)exposedPropertyTable).Clear();
|
||||
}
|
||||
|
||||
private void OnPlayModeChanged(PlayModeStateChange state)
|
||||
{
|
||||
if (state == PlayModeStateChange.EnteredEditMode || state == PlayModeStateChange.EnteredPlayMode)
|
||||
Clear();
|
||||
}
|
||||
|
||||
static TimelineAsset CreateTimeline()
|
||||
{
|
||||
var timeline = ScriptableObject.CreateInstance<TimelineAsset>();
|
||||
timeline.hideFlags |= HideFlags.DontSave;
|
||||
timeline.name = "Clipboard";
|
||||
|
||||
return timeline;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b57629d89799e004182564256307b0cc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,62 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class ControlPlayableUtility
|
||||
{
|
||||
public static bool DetectCycle(
|
||||
ControlPlayableAsset asset, PlayableDirector director, HashSet<PlayableDirector> set = null)
|
||||
{
|
||||
if (director == null || asset == null || !asset.updateDirector)
|
||||
return false;
|
||||
|
||||
if (set == null)
|
||||
set = new HashSet<PlayableDirector>();
|
||||
|
||||
if (set.Contains(director))
|
||||
return true;
|
||||
|
||||
var gameObject = asset.sourceGameObject.Resolve(director);
|
||||
if (gameObject == null)
|
||||
return false;
|
||||
|
||||
set.Add(director);
|
||||
|
||||
foreach (var subDirector in asset.GetComponent<PlayableDirector>(gameObject))
|
||||
{
|
||||
foreach (var childAsset in GetPlayableAssets(subDirector))
|
||||
{
|
||||
if (DetectCycle(childAsset, subDirector, set))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
set.Remove(director);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static IEnumerable<ControlPlayableAsset> GetPlayableAssets(PlayableDirector director)
|
||||
{
|
||||
var timeline = director != null ? (director.playableAsset as TimelineAsset) : null;
|
||||
if (timeline != null)
|
||||
{
|
||||
foreach (var t in timeline.GetOutputTracks())
|
||||
{
|
||||
var controlTrack = t as ControlTrack;
|
||||
if (controlTrack != null)
|
||||
{
|
||||
foreach (var c in t.GetClips())
|
||||
{
|
||||
var asset = c.asset as ControlPlayableAsset;
|
||||
if (asset != null)
|
||||
yield return asset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e801faa3b0dd2478dbe801a2441b679e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,44 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
// Tells a custom [[TrackDrawer]] which [[TrackAsset]] it's a drawer for.
|
||||
sealed class CustomTrackDrawerAttribute : Attribute
|
||||
{
|
||||
public Type assetType;
|
||||
public CustomTrackDrawerAttribute(Type type)
|
||||
{
|
||||
assetType = type;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute that specifies a class as an editor for an extended Timeline type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use this attribute on a class that extends ClipEditor, TrackEditor, or MarkerEditor to specify either the PlayableAsset, Marker, or TrackAsset derived classes for associated customization.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code source="../../DocCodeExamples/TimelineAttributesExamples.cs" region="declare-customTimelineEditorAttr" title="customTimelineEditorAttr"/>
|
||||
/// </example>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class CustomTimelineEditorAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The type that that this editor applies to.
|
||||
/// </summary>
|
||||
public Type classToEdit { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="type"> The type that that this editor applies to.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown if type is null</exception>
|
||||
public CustomTimelineEditorAttribute(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
throw new System.ArgumentNullException(nameof(type));
|
||||
classToEdit = type;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1e957d39ca70834f9212a1289b6a0d5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,33 @@
|
||||
using System.Text;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class DisplayNameHelper
|
||||
{
|
||||
static readonly string k_NoAssetDisplayName = L10n.Tr("<No Asset>");
|
||||
static readonly string k_ReadOnlyDisplayName = L10n.Tr("[Read Only]");
|
||||
static readonly StringBuilder k_StringBuilder = new StringBuilder();
|
||||
|
||||
public static string GetDisplayName(ISequenceState sequence)
|
||||
{
|
||||
string displayName = sequence.director != null ? GetDisplayName(sequence.director) : GetDisplayName(sequence.asset);
|
||||
if (sequence.asset != null && sequence.isReadOnly)
|
||||
displayName += " " + k_ReadOnlyDisplayName;
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public static string GetDisplayName(PlayableAsset asset)
|
||||
{
|
||||
return asset != null ? asset.name : k_NoAssetDisplayName;
|
||||
}
|
||||
|
||||
public static string GetDisplayName(PlayableDirector director)
|
||||
{
|
||||
k_StringBuilder.Length = 0;
|
||||
k_StringBuilder.Append(GetDisplayName(director.playableAsset));
|
||||
k_StringBuilder.Append(" (").Append(director.name).Append(')');
|
||||
return k_StringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7fc2147e42d71644aad0eaf9a3526249
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,54 @@
|
||||
using System.IO;
|
||||
using UnityEditor.VersionControl;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class FileUtility
|
||||
{
|
||||
internal static bool IsReadOnly(UnityEngine.Object asset)
|
||||
{
|
||||
return IsReadOnlyImpl(asset);
|
||||
}
|
||||
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
static bool IsReadOnlyImpl(UnityEngine.Object asset)
|
||||
{
|
||||
string assetPath = AssetDatabase.GetAssetPath(asset);
|
||||
if (string.IsNullOrEmpty(assetPath))
|
||||
return false;
|
||||
|
||||
if (Provider.enabled && VersionControlUtils.IsPathVersioned(assetPath))
|
||||
{
|
||||
return !AssetDatabase.CanOpenForEdit(asset, StatusQueryOptions.UseCachedIfPossible);
|
||||
}
|
||||
|
||||
return (uint)(File.GetAttributes(assetPath) & FileAttributes.ReadOnly) > 0U;
|
||||
}
|
||||
#else
|
||||
static bool IsReadOnlyImpl(UnityEngine.Object asset)
|
||||
{
|
||||
string assetPath = AssetDatabase.GetAssetPath(asset);
|
||||
if (Provider.enabled)
|
||||
{
|
||||
if (!Provider.isActive)
|
||||
return false;
|
||||
|
||||
Asset vcAsset = Provider.GetAssetByPath(assetPath);
|
||||
if (Provider.IsOpenForEdit(vcAsset))
|
||||
return false;
|
||||
|
||||
|
||||
//I can't get any of the Provider checks to work, but here we should check for exclusive checkout issues.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
return (File.GetAttributes(assetPath) & FileAttributes.ReadOnly) != 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0303d12aca9843d3bd68adf722d9c9fd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class FrameRateDisplayUtility
|
||||
{
|
||||
private static string[] s_FrameRateLabels;
|
||||
public static bool GetStandardFromFrameRate(double frameRate, out StandardFrameRates standard)
|
||||
{
|
||||
FrameRate frameRateObj = TimeUtility.GetClosestFrameRate(RoundFrameRate(frameRate));
|
||||
return TimeUtility.ToStandardFrameRate(frameRateObj, out standard);
|
||||
}
|
||||
|
||||
public static double RoundFrameRate(double frameRate)
|
||||
{
|
||||
double trunc = Math.Truncate(frameRate * (1 / TimeUtility.kFrameRateRounding)) * TimeUtility.kFrameRateRounding;
|
||||
return Math.Min(Math.Max(TimelineAsset.EditorSettings.kMinFrameRate, trunc),
|
||||
TimelineAsset.EditorSettings.kMaxFrameRate);
|
||||
}
|
||||
|
||||
public static string[] GetDefaultFrameRatesLabels(StandardFrameRates option)
|
||||
{
|
||||
string[] labels;
|
||||
if (s_FrameRateLabels == null || !s_FrameRateLabels.Any())
|
||||
{
|
||||
var frameRates = (StandardFrameRates[])Enum.GetValues(typeof(StandardFrameRates));
|
||||
labels = Array.ConvertAll(frameRates, GetLabelForStandardFrameRate);
|
||||
s_FrameRateLabels = labels;
|
||||
}
|
||||
else
|
||||
{
|
||||
labels = s_FrameRateLabels;
|
||||
}
|
||||
|
||||
if (!Enum.IsDefined(typeof(StandardFrameRates), option))
|
||||
{
|
||||
Array.Resize(ref labels, (int)option + 1);
|
||||
labels[(int)option] = GetLabelForStandardFrameRate(option);
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
static string GetLabelForStandardFrameRate(StandardFrameRates option)
|
||||
{
|
||||
switch (option)
|
||||
{
|
||||
case StandardFrameRates.Fps23_97:
|
||||
return L10n.Tr("Film NTSC: 23.97 fps");
|
||||
case StandardFrameRates.Fps24:
|
||||
return L10n.Tr("Film: 24 fps");
|
||||
case StandardFrameRates.Fps25:
|
||||
return L10n.Tr("PAL: 25 fps");
|
||||
case StandardFrameRates.Fps29_97:
|
||||
return L10n.Tr("NTSC: 29.97 fps");
|
||||
case StandardFrameRates.Fps30:
|
||||
return L10n.Tr("HD: 30 fps");
|
||||
case StandardFrameRates.Fps50:
|
||||
return L10n.Tr("Interlaced PAL: 50 fps");
|
||||
case StandardFrameRates.Fps59_94:
|
||||
return L10n.Tr("Interlaced NTSC: 59.94 fps");
|
||||
case StandardFrameRates.Fps60:
|
||||
return L10n.Tr("Game: 60 fps");
|
||||
default:
|
||||
return L10n.Tr("Custom");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e6372298ccbe45b9a771c50dd0293b6e
|
||||
timeCreated: 1612896067
|
@ -0,0 +1,121 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class Graphics
|
||||
{
|
||||
public static void ShadowLabel(Rect rect, string text, GUIStyle style, Color textColor, Color shadowColor)
|
||||
{
|
||||
ShadowLabel(rect, GUIContent.Temp(text), style, textColor, shadowColor);
|
||||
}
|
||||
|
||||
public static void ShadowLabel(Rect rect, GUIContent content, GUIStyle style, Color textColor, Color shadowColor)
|
||||
{
|
||||
var shadowRect = rect;
|
||||
shadowRect.xMin += 2.0f;
|
||||
shadowRect.yMin += 2.0f;
|
||||
style.normal.textColor = shadowColor;
|
||||
style.hover.textColor = shadowColor;
|
||||
GUI.Label(shadowRect, content, style);
|
||||
|
||||
style.normal.textColor = textColor;
|
||||
style.hover.textColor = textColor;
|
||||
GUI.Label(rect, content, style);
|
||||
}
|
||||
|
||||
public static void DrawLine(Vector3 p1, Vector3 p2, Color color)
|
||||
{
|
||||
var c = Handles.color;
|
||||
Handles.color = color;
|
||||
Handles.DrawLine(p1, p2);
|
||||
Handles.color = c;
|
||||
}
|
||||
|
||||
public static void DrawPolygonAA(Color color, Vector3[] vertices)
|
||||
{
|
||||
var prevColor = Handles.color;
|
||||
Handles.color = color;
|
||||
Handles.DrawAAConvexPolygon(vertices);
|
||||
Handles.color = prevColor;
|
||||
}
|
||||
|
||||
public static void DrawDottedLine(Vector3 p1, Vector3 p2, float segmentsLength, Color col)
|
||||
{
|
||||
HandleUtility.ApplyWireMaterial();
|
||||
|
||||
GL.Begin(GL.LINES);
|
||||
GL.Color(col);
|
||||
|
||||
var length = Vector3.Distance(p1, p2); // ignore z component
|
||||
var count = Mathf.CeilToInt(length / segmentsLength);
|
||||
for (var i = 0; i < count; i += 2)
|
||||
{
|
||||
GL.Vertex((Vector3.Lerp(p1, p2, i * segmentsLength / length)));
|
||||
GL.Vertex((Vector3.Lerp(p1, p2, (i + 1) * segmentsLength / length)));
|
||||
}
|
||||
|
||||
GL.End();
|
||||
}
|
||||
|
||||
public static void DrawLineAtTime(WindowState state, double time, Color color, bool dotted = false)
|
||||
{
|
||||
var t = state.TimeToPixel(time);
|
||||
|
||||
var p0 = new Vector3(t, state.timeAreaRect.yMax);
|
||||
var p1 = new Vector3(t, state.timeAreaRect.yMax + state.windowHeight - WindowConstants.sliderWidth);
|
||||
|
||||
if (dotted)
|
||||
DrawDottedLine(p0, p1, 4.0f, color);
|
||||
else
|
||||
DrawLine(p0, p1, color);
|
||||
}
|
||||
|
||||
public static void DrawTextureRepeated(Rect area, Texture texture)
|
||||
{
|
||||
if (texture == null || Event.current.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
GUI.BeginClip(area);
|
||||
int w = Mathf.CeilToInt(area.width / texture.width);
|
||||
int h = Mathf.CeilToInt(area.height / texture.height);
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
GUI.DrawTexture(new Rect(x * texture.width, y * texture.height, texture.width, texture.height), texture);
|
||||
}
|
||||
}
|
||||
|
||||
GUI.EndClip();
|
||||
}
|
||||
|
||||
public static void DrawShadow(Rect clientRect)
|
||||
{
|
||||
var rect = clientRect;
|
||||
rect.height = WindowConstants.shadowUnderTimelineHeight;
|
||||
GUI.Box(rect, GUIContent.none, DirectorStyles.Instance.bottomShadow);
|
||||
}
|
||||
|
||||
public static void DrawBackgroundRect(WindowState state, Rect rect, bool subSequenceMode = false)
|
||||
{
|
||||
Color c = subSequenceMode ? DirectorStyles.Instance.customSkin.colorSubSequenceBackground : DirectorStyles.Instance.customSkin.colorSequenceBackground;
|
||||
EditorGUI.DrawRect(rect, c);
|
||||
if (state.IsEditingAPrefabAsset())
|
||||
{
|
||||
c = SceneView.kSceneViewPrefabBackground.Color;
|
||||
c.a = 0.5f;
|
||||
EditorGUI.DrawRect(rect, c);
|
||||
}
|
||||
}
|
||||
|
||||
public static Rect CalculateTextBoxSize(Rect trackRect, GUIStyle font, GUIContent content, float padding)
|
||||
{
|
||||
Rect textRect = trackRect;
|
||||
textRect.width = font.CalcSize(content).x + padding;
|
||||
textRect.x += (trackRect.width - textRect.width) / 2f;
|
||||
textRect.height -= 4f;
|
||||
textRect.y += 2f;
|
||||
return textRect;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4545bb65ccebf8040ac212d5792979b5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,203 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline.Utilities
|
||||
{
|
||||
class KeyTraverser
|
||||
{
|
||||
float[] m_KeyCache;
|
||||
int m_DirtyStamp = -1;
|
||||
int m_LastHash = -1;
|
||||
readonly TimelineAsset m_Asset;
|
||||
readonly float m_Epsilon;
|
||||
int m_LastIndex = -1;
|
||||
|
||||
public int lastIndex
|
||||
{
|
||||
get { return m_LastIndex; }
|
||||
}
|
||||
|
||||
public static IEnumerable<float> GetClipKeyTimes(TimelineClip clip)
|
||||
{
|
||||
if (clip == null || clip.animationClip == null || clip.animationClip.empty)
|
||||
return new float[0];
|
||||
|
||||
return AnimationClipCurveCache.Instance.GetCurveInfo(clip.animationClip).keyTimes.
|
||||
Select(k => (float)clip.FromLocalTimeUnbound(k)). // convert to sequence time
|
||||
Where(k => k >= clip.start && k <= clip.end); // remove non visible keys
|
||||
}
|
||||
|
||||
public static IEnumerable<float> GetTrackKeyTimes(AnimationTrack track)
|
||||
{
|
||||
if (track != null)
|
||||
{
|
||||
if (track.inClipMode)
|
||||
return track.clips.Where(c => c.recordable).
|
||||
SelectMany(x => GetClipKeyTimes(x));
|
||||
if (track.infiniteClip != null && !track.infiniteClip.empty)
|
||||
return AnimationClipCurveCache.Instance.GetCurveInfo(track.infiniteClip).keyTimes;
|
||||
}
|
||||
return new float[0];
|
||||
}
|
||||
|
||||
static int CalcAnimClipHash(TrackAsset asset)
|
||||
{
|
||||
int hash = 0;
|
||||
if (asset != null)
|
||||
{
|
||||
AnimationTrack animTrack = asset as AnimationTrack;
|
||||
if (animTrack != null)
|
||||
{
|
||||
for (var i = 0; i != animTrack.clips.Length; ++i)
|
||||
{
|
||||
hash ^= (animTrack.clips[i]).Hash();
|
||||
}
|
||||
}
|
||||
foreach (var subTrack in asset.GetChildTracks())
|
||||
{
|
||||
if (subTrack != null)
|
||||
hash ^= CalcAnimClipHash(subTrack);
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
internal static int CalcAnimClipHash(TimelineAsset asset)
|
||||
{
|
||||
int hash = 0;
|
||||
foreach (var t in asset.GetRootTracks())
|
||||
{
|
||||
if (t != null)
|
||||
hash ^= CalcAnimClipHash(t);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
void RebuildKeyCache()
|
||||
{
|
||||
m_KeyCache = m_Asset.flattenedTracks.Where(x => (x as AnimationTrack) != null)
|
||||
.Cast<AnimationTrack>()
|
||||
.SelectMany(t => GetTrackKeyTimes(t)).
|
||||
OrderBy(x => x).ToArray();
|
||||
|
||||
if (m_KeyCache.Length > 0)
|
||||
{
|
||||
float[] unique = new float[m_KeyCache.Length];
|
||||
unique[0] = m_KeyCache[0];
|
||||
int index = 0;
|
||||
for (int i = 1; i < m_KeyCache.Length; i++)
|
||||
{
|
||||
if (m_KeyCache[i] - unique[index] > m_Epsilon)
|
||||
{
|
||||
index++;
|
||||
unique[index] = m_KeyCache[i];
|
||||
}
|
||||
}
|
||||
m_KeyCache = unique;
|
||||
Array.Resize(ref m_KeyCache, index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public KeyTraverser(TimelineAsset timeline, float epsilon)
|
||||
{
|
||||
m_Asset = timeline;
|
||||
m_Epsilon = epsilon;
|
||||
}
|
||||
|
||||
void CheckCache(int dirtyStamp)
|
||||
{
|
||||
int hash = CalcAnimClipHash(m_Asset);
|
||||
if (dirtyStamp != m_DirtyStamp || hash != m_LastHash)
|
||||
{
|
||||
RebuildKeyCache();
|
||||
m_DirtyStamp = dirtyStamp;
|
||||
m_LastHash = hash;
|
||||
}
|
||||
}
|
||||
|
||||
public float GetNextKey(float key, int dirtyStamp)
|
||||
{
|
||||
CheckCache(dirtyStamp);
|
||||
if (m_KeyCache.Length > 0)
|
||||
{
|
||||
if (key < m_KeyCache.Last() - m_Epsilon)
|
||||
{
|
||||
if (key > m_KeyCache[0] - m_Epsilon)
|
||||
{
|
||||
float t = key + m_Epsilon;
|
||||
// binary search
|
||||
int max = m_KeyCache.Length - 1;
|
||||
int min = 0;
|
||||
while (max - min > 1)
|
||||
{
|
||||
int imid = (min + max) / 2;
|
||||
if (t > m_KeyCache[imid])
|
||||
min = imid;
|
||||
else
|
||||
max = imid;
|
||||
}
|
||||
m_LastIndex = max;
|
||||
return m_KeyCache[max];
|
||||
}
|
||||
|
||||
m_LastIndex = 0;
|
||||
return m_KeyCache[0];
|
||||
}
|
||||
if (key < m_KeyCache.Last() + m_Epsilon)
|
||||
{
|
||||
m_LastIndex = m_KeyCache.Length - 1;
|
||||
return Mathf.Max(key, m_KeyCache.Last());
|
||||
}
|
||||
}
|
||||
m_LastIndex = -1;
|
||||
return key;
|
||||
}
|
||||
|
||||
public float GetPrevKey(float key, int dirtyStamp)
|
||||
{
|
||||
CheckCache(dirtyStamp);
|
||||
if (m_KeyCache.Length > 0)
|
||||
{
|
||||
if (key > m_KeyCache[0] + m_Epsilon)
|
||||
{
|
||||
if (key < m_KeyCache.Last() + m_Epsilon)
|
||||
{
|
||||
float t = key - m_Epsilon;
|
||||
|
||||
// binary search
|
||||
int max = m_KeyCache.Length - 1;
|
||||
int min = 0;
|
||||
while (max - min > 1)
|
||||
{
|
||||
int imid = (min + max) / 2;
|
||||
if (t < m_KeyCache[imid])
|
||||
max = imid;
|
||||
else
|
||||
min = imid;
|
||||
}
|
||||
m_LastIndex = min;
|
||||
return m_KeyCache[min];
|
||||
}
|
||||
m_LastIndex = m_KeyCache.Length - 1;
|
||||
return m_KeyCache.Last();
|
||||
}
|
||||
if (key >= m_KeyCache[0] - m_Epsilon)
|
||||
{
|
||||
m_LastIndex = 0;
|
||||
return Mathf.Min(key, m_KeyCache[0]);
|
||||
}
|
||||
}
|
||||
m_LastIndex = -1;
|
||||
return key;
|
||||
}
|
||||
|
||||
public int GetKeyCount(int dirtyStamp)
|
||||
{
|
||||
CheckCache(dirtyStamp);
|
||||
return m_KeyCache.Length;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b57f909f22642d469a39e9628535312
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class MarkerModifier
|
||||
{
|
||||
public static void DeleteMarker(IMarker marker)
|
||||
{
|
||||
var trackAsset = marker.parent;
|
||||
if (trackAsset != null)
|
||||
{
|
||||
SelectionManager.Remove(marker);
|
||||
trackAsset.DeleteMarker(marker);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<IMarker> CloneMarkersToParent(IEnumerable<IMarker> markers, TrackAsset parent)
|
||||
{
|
||||
if (!markers.Any()) return Enumerable.Empty<IMarker>();
|
||||
var clonedMarkers = new List<IMarker>();
|
||||
foreach (var marker in markers)
|
||||
clonedMarkers.Add(CloneMarkerToParent(marker, parent));
|
||||
return clonedMarkers;
|
||||
}
|
||||
|
||||
public static IMarker CloneMarkerToParent(IMarker marker, TrackAsset parent)
|
||||
{
|
||||
var markerObject = marker as ScriptableObject;
|
||||
if (markerObject == null) return null;
|
||||
|
||||
var newMarkerObject = Object.Instantiate(markerObject);
|
||||
AddMarkerToParent(newMarkerObject, parent);
|
||||
|
||||
newMarkerObject.name = markerObject.name;
|
||||
try
|
||||
{
|
||||
CustomTimelineEditorCache.GetMarkerEditor((IMarker)newMarkerObject).OnCreate((IMarker)newMarkerObject, marker);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
|
||||
|
||||
return (IMarker)newMarkerObject;
|
||||
}
|
||||
|
||||
static void AddMarkerToParent(ScriptableObject marker, TrackAsset parent)
|
||||
{
|
||||
TimelineCreateUtilities.SaveAssetIntoObject(marker, parent);
|
||||
TimelineUndo.RegisterCreatedObjectUndo(marker, L10n.Tr("Duplicate Marker"));
|
||||
UndoExtensions.RegisterTrack(parent, L10n.Tr("Duplicate Marker"));
|
||||
|
||||
if (parent != null)
|
||||
{
|
||||
parent.AddMarker(marker);
|
||||
((IMarker)marker).Initialize(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7cfaad4e53832d94c9421d2dd1ad82f7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,28 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class ObjectExtension
|
||||
{
|
||||
public static bool IsSceneObject(this Object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return false;
|
||||
|
||||
bool isSceneType = obj is GameObject || obj is Component;
|
||||
if (!isSceneType)
|
||||
return false;
|
||||
|
||||
return !PrefabUtility.IsPartOfPrefabAsset(obj);
|
||||
}
|
||||
|
||||
public static bool IsPrefab(this Object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return false;
|
||||
|
||||
return PrefabUtility.IsPartOfPrefabAsset(obj);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4722a1362908a1843ab03a055c5c3fa0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,195 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine.Playables;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
// Describes the object references on a ScriptableObject, ignoring script fields
|
||||
struct ObjectReferenceField
|
||||
{
|
||||
public string propertyPath;
|
||||
public bool isSceneReference;
|
||||
public System.Type type;
|
||||
|
||||
private readonly static ObjectReferenceField[] None = new ObjectReferenceField[0];
|
||||
private readonly static Dictionary<System.Type, ObjectReferenceField[]> s_Cache = new Dictionary<System.Type, ObjectReferenceField[]>();
|
||||
|
||||
public static ObjectReferenceField[] FindObjectReferences(System.Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return None;
|
||||
|
||||
if (type.IsAbstract || type.IsInterface)
|
||||
return None;
|
||||
|
||||
if (!typeof(ScriptableObject).IsAssignableFrom(type))
|
||||
return None;
|
||||
|
||||
ObjectReferenceField[] result = null;
|
||||
if (s_Cache.TryGetValue(type, out result))
|
||||
return result;
|
||||
|
||||
result = SearchForFields(type);
|
||||
s_Cache[type] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static ObjectReferenceField[] FindObjectReferences<T>() where T : ScriptableObject, new()
|
||||
{
|
||||
return FindObjectReferences(typeof(T));
|
||||
}
|
||||
|
||||
private static ObjectReferenceField[] SearchForFields(System.Type t)
|
||||
{
|
||||
Object instance = ScriptableObject.CreateInstance(t);
|
||||
var list = new List<ObjectReferenceField>();
|
||||
|
||||
var serializableObject = new SerializedObject(instance);
|
||||
var prop = serializableObject.GetIterator();
|
||||
bool enterChildren = true;
|
||||
while (prop.NextVisible(enterChildren))
|
||||
{
|
||||
enterChildren = true;
|
||||
var ppath = prop.propertyPath;
|
||||
if (ppath == "m_Script")
|
||||
{
|
||||
enterChildren = false;
|
||||
}
|
||||
else if (prop.propertyType == SerializedPropertyType.ObjectReference || prop.propertyType == SerializedPropertyType.ExposedReference)
|
||||
{
|
||||
enterChildren = false;
|
||||
var exposedType = GetTypeFromPath(t, prop.propertyPath);
|
||||
if (exposedType != null && typeof(Object).IsAssignableFrom(exposedType))
|
||||
{
|
||||
bool isSceneRef = prop.propertyType == SerializedPropertyType.ExposedReference;
|
||||
list.Add(
|
||||
new ObjectReferenceField() { propertyPath = prop.propertyPath, isSceneReference = isSceneRef, type = exposedType }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Object.DestroyImmediate(instance);
|
||||
if (list.Count == 0)
|
||||
return None;
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
private static System.Type GetTypeFromPath(System.Type baseType, string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return null;
|
||||
|
||||
System.Type parentType = baseType;
|
||||
FieldInfo field = null;
|
||||
var pathTo = path.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var flags = BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.NonPublic |
|
||||
BindingFlags.Instance;
|
||||
foreach (string s in pathTo)
|
||||
{
|
||||
field = parentType.GetField(s, flags);
|
||||
while (field == null)
|
||||
{
|
||||
if (parentType.BaseType == null)
|
||||
return null; // Should not happen really. Means SerializedObject got the property, but the reflection missed it
|
||||
parentType = parentType.BaseType;
|
||||
field = parentType.GetField(s, flags);
|
||||
}
|
||||
|
||||
parentType = field.FieldType;
|
||||
}
|
||||
|
||||
// dig out exposed reference types
|
||||
if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(ExposedReference<Object>).GetGenericTypeDefinition())
|
||||
{
|
||||
return field.FieldType.GetGenericArguments()[0];
|
||||
}
|
||||
|
||||
return field.FieldType;
|
||||
}
|
||||
|
||||
public Object Find(ScriptableObject sourceObject, Object context = null)
|
||||
{
|
||||
if (sourceObject == null)
|
||||
return null;
|
||||
|
||||
SerializedObject obj = new SerializedObject(sourceObject, context);
|
||||
var prop = obj.FindProperty(propertyPath);
|
||||
if (prop == null)
|
||||
throw new InvalidOperationException("sourceObject is not of the proper type. It does not contain a path to " + propertyPath);
|
||||
|
||||
Object result = null;
|
||||
if (isSceneReference)
|
||||
{
|
||||
if (prop.propertyType != SerializedPropertyType.ExposedReference)
|
||||
throw new InvalidOperationException(propertyPath + " is marked as a Scene Reference, but is not an exposed reference type");
|
||||
if (context == null)
|
||||
Debug.LogWarning("ObjectReferenceField.Find " + " is called on a scene reference without a context, will always be null");
|
||||
|
||||
result = prop.exposedReferenceValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (prop.propertyType != SerializedPropertyType.ObjectReference)
|
||||
throw new InvalidOperationException(propertyPath + "is marked as an asset reference, but is not an object reference type");
|
||||
result = prop.objectReferenceValue;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if an Object satisfies this field, including components
|
||||
/// </summary>
|
||||
public bool IsAssignable(Object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return false;
|
||||
|
||||
// types match
|
||||
bool potentialMatch = type.IsAssignableFrom(obj.GetType());
|
||||
|
||||
// field is component, and it exists on the gameObject
|
||||
if (!potentialMatch && typeof(Component).IsAssignableFrom(type) && obj is GameObject)
|
||||
potentialMatch = ((GameObject)obj).GetComponent(type) != null;
|
||||
|
||||
return potentialMatch && isSceneReference == obj.IsSceneObject();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns a value to the field
|
||||
/// </summary>
|
||||
public bool Assign(ScriptableObject scriptableObject, Object value, IExposedPropertyTable exposedTable = null)
|
||||
{
|
||||
var serializedObject = new SerializedObject(scriptableObject, exposedTable as Object);
|
||||
var property = serializedObject.FindProperty(propertyPath);
|
||||
if (property == null)
|
||||
return false;
|
||||
|
||||
// if the value is a game object, but the field is a component
|
||||
if (value is GameObject && typeof(Component).IsAssignableFrom(type))
|
||||
value = ((GameObject)value).GetComponent(type);
|
||||
|
||||
if (isSceneReference)
|
||||
{
|
||||
property.exposedReferenceValue = value;
|
||||
|
||||
// the object gets dirtied, but not the scene which is where the reference is stored
|
||||
var component = exposedTable as Component;
|
||||
if (component != null && !EditorApplication.isPlaying)
|
||||
EditorSceneManager.MarkSceneDirty(component.gameObject.scene);
|
||||
}
|
||||
else
|
||||
property.objectReferenceValue = value;
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 29bf1d4ec1012bc45967ce95b729b8b3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine.Playables;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
readonly struct PreviewedBindings<T> where T : Object
|
||||
{
|
||||
readonly IEnumerable<T> m_UniqueBindings;
|
||||
readonly IReadOnlyDictionary<Object, HashSet<T>> m_BindingsPerObject;
|
||||
|
||||
PreviewedBindings(IEnumerable<T> uniqueBindings, IReadOnlyDictionary<Object, HashSet<T>> bindingsPerObject)
|
||||
{
|
||||
m_UniqueBindings = uniqueBindings;
|
||||
m_BindingsPerObject = bindingsPerObject;
|
||||
}
|
||||
|
||||
public IEnumerable<T> GetUniqueBindings() => m_UniqueBindings ?? Enumerable.Empty<T>();
|
||||
|
||||
public IEnumerable<T> GetBindingsForObject(Object track)
|
||||
{
|
||||
if (m_BindingsPerObject != null && m_BindingsPerObject.TryGetValue(track, out HashSet<T> bindings))
|
||||
return bindings;
|
||||
|
||||
return Enumerable.Empty<T>();
|
||||
}
|
||||
|
||||
public static PreviewedBindings<T> GetPreviewedBindings(IEnumerable<PlayableDirector> directors)
|
||||
{
|
||||
var uniqueBindings = new HashSet<T>();
|
||||
var bindingsPerTrack = new Dictionary<Object, HashSet<T>>();
|
||||
foreach (PlayableDirector director in directors)
|
||||
{
|
||||
if (director.playableAsset == null) continue;
|
||||
|
||||
foreach (PlayableBinding output in director.playableAsset.outputs)
|
||||
{
|
||||
var binding = director.GetGenericBinding(output.sourceObject) as T;
|
||||
Add(output.sourceObject, binding, uniqueBindings, bindingsPerTrack);
|
||||
}
|
||||
}
|
||||
|
||||
return new PreviewedBindings<T>(uniqueBindings, bindingsPerTrack);
|
||||
}
|
||||
|
||||
static void Add(Object obj, T binding, HashSet<T> bindings, Dictionary<Object, HashSet<T>> bindingsPerObject)
|
||||
{
|
||||
if (binding == null)
|
||||
return;
|
||||
|
||||
bindings.Add(binding);
|
||||
if (bindingsPerObject.TryGetValue(obj, out HashSet<T> objectBindings))
|
||||
objectBindings.Add(binding);
|
||||
else
|
||||
bindingsPerObject.Add(obj, new HashSet<T> { binding });
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 907eeaaade2f4a3c9b079743c2bf77ee
|
||||
timeCreated: 1686150636
|
@ -0,0 +1,226 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
class PropertyCollector : IPropertyCollector
|
||||
{
|
||||
readonly Stack<GameObject> m_ObjectStack = new Stack<GameObject>();
|
||||
|
||||
// Call immediately before use
|
||||
public void Reset()
|
||||
{
|
||||
m_ObjectStack.Clear();
|
||||
}
|
||||
|
||||
// call to reset caches. should be called when switching master timelines
|
||||
public void Clear()
|
||||
{
|
||||
m_ObjectStack.Clear();
|
||||
AnimationPreviewUtilities.ClearCaches();
|
||||
}
|
||||
|
||||
public void PushActiveGameObject(GameObject gameObject)
|
||||
{
|
||||
m_ObjectStack.Push(gameObject);
|
||||
}
|
||||
|
||||
public void PopActiveGameObject()
|
||||
{
|
||||
m_ObjectStack.Pop();
|
||||
}
|
||||
|
||||
public void AddFromClip(AnimationClip clip)
|
||||
{
|
||||
var go = m_ObjectStack.Peek(); // allow it to throw if empty
|
||||
if (go != null && clip != null) // null game object is allowed for calls to be ignored
|
||||
AddFromClip(go, clip);
|
||||
}
|
||||
|
||||
public void AddFromClips(IEnumerable<AnimationClip> clips)
|
||||
{
|
||||
var go = m_ObjectStack.Peek();
|
||||
if (go != null)
|
||||
AddFromClips(go, clips);
|
||||
}
|
||||
|
||||
public void AddFromName<T>(string name) where T : Component
|
||||
{
|
||||
var go = m_ObjectStack.Peek(); // allow it to throw if empty
|
||||
if (go != null) // null game object is allowed for calls to be ignored
|
||||
AddFromName<T>(go, name);
|
||||
}
|
||||
|
||||
public void AddFromName(string name)
|
||||
{
|
||||
var go = m_ObjectStack.Peek(); // allow it to throw if empty
|
||||
if (go != null) // null game object is allowed for calls to be ignored
|
||||
AddFromName(go, name);
|
||||
}
|
||||
|
||||
public void AddFromClip(GameObject obj, AnimationClip clip)
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
AddPropertiesFromClip(obj, clip);
|
||||
}
|
||||
|
||||
public void AddFromClips(GameObject animatorRoot, IEnumerable<AnimationClip> clips)
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
return;
|
||||
|
||||
AnimationPreviewUtilities.PreviewFromCurves(animatorRoot, AnimationPreviewUtilities.GetBindings(animatorRoot, clips));
|
||||
}
|
||||
|
||||
public void AddFromName<T>(GameObject obj, string name) where T : Component
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
AddPropertiesFromName(obj, typeof(T), name);
|
||||
}
|
||||
|
||||
public void AddFromName(GameObject obj, string name)
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
AddPropertiesFromName(obj, name);
|
||||
}
|
||||
|
||||
public void AddFromName(Component component, string name)
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
AddPropertyModification(component, name);
|
||||
}
|
||||
|
||||
public void AddFromComponent(GameObject obj, Component component)
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
return;
|
||||
|
||||
if (obj == null || component == null)
|
||||
return;
|
||||
|
||||
var serializedObject = new SerializedObject(component);
|
||||
SerializedProperty property = serializedObject.GetIterator();
|
||||
|
||||
while (property.NextVisible(true))
|
||||
{
|
||||
if (property.hasVisibleChildren || !AnimatedParameterUtility.IsTypeAnimatable(property.propertyType))
|
||||
continue;
|
||||
|
||||
AddPropertyModification(component, property.propertyPath);
|
||||
}
|
||||
}
|
||||
|
||||
void AddPropertiesFromClip(GameObject go, AnimationClip clip)
|
||||
{
|
||||
if (go != null && clip != null)
|
||||
{
|
||||
AnimationMode.InitializePropertyModificationForGameObject(go, clip);
|
||||
}
|
||||
}
|
||||
|
||||
static void AddPropertiesFromName(GameObject go, string property)
|
||||
{
|
||||
if (go == null)
|
||||
return;
|
||||
|
||||
AddPropertyModification(go, property);
|
||||
}
|
||||
|
||||
static void AddPropertiesFromName(GameObject go, Type compType, string property)
|
||||
{
|
||||
if (go == null)
|
||||
return;
|
||||
var comp = go.GetComponent(compType);
|
||||
if (comp == null)
|
||||
return;
|
||||
|
||||
AddPropertyModification(comp, property);
|
||||
}
|
||||
|
||||
public void AddObjectProperties(Object obj, AnimationClip clip)
|
||||
{
|
||||
if (obj == null || clip == null)
|
||||
return;
|
||||
|
||||
IPlayableAsset asset = obj as IPlayableAsset;
|
||||
IPlayableBehaviour playable = obj as IPlayableBehaviour;
|
||||
|
||||
// special case for assets that contain animated script playables.
|
||||
// The paths in the clip start from the field with the templated playable
|
||||
if (asset != null)
|
||||
{
|
||||
if (playable == null)
|
||||
{
|
||||
AddSerializedPlayableModifications(asset, clip);
|
||||
}
|
||||
else
|
||||
{
|
||||
// in this case the asset is the playable. The clip applies directly
|
||||
AnimationMode.InitializePropertyModificationForObject(obj, clip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AddSerializedPlayableModifications(IPlayableAsset asset, AnimationClip clip)
|
||||
{
|
||||
var obj = asset as Object;
|
||||
if (obj == null)
|
||||
return;
|
||||
|
||||
var driver = WindowState.previewDriver;
|
||||
if (driver == null || !AnimationMode.InAnimationMode(driver))
|
||||
return;
|
||||
|
||||
var serializedObj = new SerializedObject(obj);
|
||||
var bindings = AnimationClipCurveCache.Instance.GetCurveInfo(clip).bindings;
|
||||
var fields = AnimatedParameterUtility.GetScriptPlayableFields(asset);
|
||||
|
||||
// go through each binding and offset using the field name
|
||||
// so the modification system can find the particle object using the asset as a root
|
||||
foreach (var b in bindings)
|
||||
{
|
||||
foreach (var f in fields)
|
||||
{
|
||||
var propertyPath = f.Name + "." + b.propertyName;
|
||||
if (serializedObj.FindProperty(propertyPath) != null)
|
||||
{
|
||||
DrivenPropertyManager.RegisterProperty(driver, obj, propertyPath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddPropertyModification(GameObject obj, string propertyName)
|
||||
{
|
||||
var driver = WindowState.previewDriver;
|
||||
if (driver == null || !AnimationMode.InAnimationMode(driver))
|
||||
return;
|
||||
|
||||
DrivenPropertyManager.RegisterProperty(driver, obj, propertyName);
|
||||
}
|
||||
|
||||
private static void AddPropertyModification(Component comp, string name)
|
||||
{
|
||||
if (comp == null)
|
||||
return;
|
||||
|
||||
var driver = WindowState.previewDriver;
|
||||
if (driver == null || !AnimationMode.InAnimationMode(driver))
|
||||
return;
|
||||
|
||||
// Register Property will display an error if a property doesn't exist (wanted behaviour)
|
||||
// However, it also displays an error on Monobehaviour m_Script property, since it can't be driven. (not wanted behaviour)
|
||||
// case 967026
|
||||
if (name == "m_Script" && (comp as MonoBehaviour) != null)
|
||||
return;
|
||||
|
||||
DrivenPropertyManager.RegisterProperty(driver, comp, name);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f3a562675833b4448299e4f627b0cec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
struct Range
|
||||
{
|
||||
public double start;
|
||||
public double end;
|
||||
public double length { get { return end - start; } }
|
||||
|
||||
public static Range Union(Range lhs, Range rhs)
|
||||
{
|
||||
return new Range
|
||||
{
|
||||
start = Math.Min(lhs.start, rhs.start),
|
||||
end = Math.Max(lhs.end, rhs.end)
|
||||
};
|
||||
}
|
||||
|
||||
public static Range Intersection(Range lhs, Range rhs)
|
||||
{
|
||||
var s = Math.Max(lhs.start, rhs.start);
|
||||
var e = Math.Min(lhs.end, rhs.end);
|
||||
|
||||
if (s > e)
|
||||
{
|
||||
// No intersection returns a 0-length range from 0 to 0
|
||||
return new Range();
|
||||
}
|
||||
|
||||
return new Range
|
||||
{
|
||||
start = s,
|
||||
end = e
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ToString("F3");
|
||||
}
|
||||
|
||||
public string ToString(string format)
|
||||
{
|
||||
return UnityString.Format("({0}, {1})", start.ToString(format), end.ToString(format));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d31dfeaa131921f4eae00783cc48146f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d55400f78d024c05b04dc124bb181d9e
|
||||
timeCreated: 1605887929
|
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
struct GUIColorOverride : IDisposable
|
||||
{
|
||||
readonly Color m_OldColor;
|
||||
|
||||
public GUIColorOverride(Color newColor)
|
||||
{
|
||||
m_OldColor = GUI.color;
|
||||
GUI.color = newColor;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GUI.color = m_OldColor;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44507a833d0ca8a42aaec1c3d752eb5f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor
|
||||
{
|
||||
struct GUIGroupScope : IDisposable
|
||||
{
|
||||
public GUIGroupScope(Rect position)
|
||||
{
|
||||
GUI.BeginGroup(position);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GUI.EndGroup();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0bee8aba5e8a40446b7098666c5314d9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor
|
||||
{
|
||||
struct GUIMixedValueScope : IDisposable
|
||||
{
|
||||
readonly bool m_PrevValue;
|
||||
public GUIMixedValueScope(bool newValue)
|
||||
{
|
||||
m_PrevValue = EditorGUI.showMixedValue;
|
||||
EditorGUI.showMixedValue = newValue;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
EditorGUI.showMixedValue = m_PrevValue;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d59cefc45e3c31d4a90563364e7258fa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor
|
||||
{
|
||||
// Special Clip Scope that only effects painting, and keeps the coordinate system identical
|
||||
struct GUIViewportScope : IDisposable
|
||||
{
|
||||
bool m_open;
|
||||
public GUIViewportScope(Rect position)
|
||||
{
|
||||
m_open = false;
|
||||
if (Event.current.type == EventType.Repaint || Event.current.type == EventType.Layout)
|
||||
{
|
||||
GUI.BeginClip(position, -position.min, Vector2.zero, false);
|
||||
m_open = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CloseScope();
|
||||
}
|
||||
|
||||
void CloseScope()
|
||||
{
|
||||
if (m_open)
|
||||
{
|
||||
GUI.EndClip();
|
||||
m_open = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af84cf39b8fa0654badd9278cbd00d77
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
readonly struct HorizontalScope : IDisposable
|
||||
{
|
||||
public readonly Rect rect;
|
||||
|
||||
public HorizontalScope(GUIContent content, GUIStyle style)
|
||||
{
|
||||
rect = EditorGUILayout.BeginHorizontal(content, style);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75e3737506434a75b1f7901d8883ad47
|
||||
timeCreated: 1605888588
|
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
readonly struct IndentLevelScope : IDisposable
|
||||
{
|
||||
readonly int m_PrevValue;
|
||||
|
||||
public IndentLevelScope(int newValue)
|
||||
{
|
||||
m_PrevValue = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = newValue;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
EditorGUI.indentLevel = m_PrevValue;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e42e3f559ace4a5dbae7d6d3d2e5e7b0
|
||||
timeCreated: 1605888248
|
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
readonly struct LabelWidthScope : IDisposable
|
||||
{
|
||||
readonly float m_PrevValue;
|
||||
|
||||
public LabelWidthScope(float newValue)
|
||||
{
|
||||
m_PrevValue = EditorGUIUtility.labelWidth;
|
||||
EditorGUIUtility.labelWidth = newValue;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
EditorGUIUtility.labelWidth = m_PrevValue;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d095bb54df44c01a27e76a2b060a072
|
||||
timeCreated: 1605888410
|
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
readonly struct PropertyScope : IDisposable
|
||||
{
|
||||
public readonly GUIContent content;
|
||||
|
||||
public PropertyScope(Rect totalPosition, GUIContent label, SerializedProperty property)
|
||||
{
|
||||
content = EditorGUI.BeginProperty(totalPosition, label, property);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ccfd55dae664bbcb54c8457acffdbf8
|
||||
timeCreated: 1605887959
|
@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
// Class used for uniquely format names used in the GenericMenu. We can't add duplicate MenuItem in GenericMenu
|
||||
// so that's why we need to keep information about the text we want to uniquely format.
|
||||
class SequenceMenuNameFormater
|
||||
{
|
||||
Dictionary<int, int> m_UniqueItem = new Dictionary<int, int>();
|
||||
|
||||
public string Format(string text)
|
||||
{
|
||||
var key = text.GetHashCode();
|
||||
var index = 0;
|
||||
|
||||
if (m_UniqueItem.ContainsKey(key))
|
||||
{
|
||||
index = m_UniqueItem[key];
|
||||
index++;
|
||||
m_UniqueItem[key] = index;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_UniqueItem.Add(key, index);
|
||||
return text;
|
||||
}
|
||||
|
||||
return $"{text}{index}";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1861286ba69badd439188a65bebf3cda
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
interface IBounds
|
||||
{
|
||||
Rect boundingRect { get; }
|
||||
}
|
||||
|
||||
class SpacePartitioner
|
||||
{
|
||||
internal class CachedList<T>
|
||||
{
|
||||
public static readonly List<T> Instance = new List<T>(1000);
|
||||
}
|
||||
|
||||
struct Entry : IInterval
|
||||
{
|
||||
public object item { get; set; }
|
||||
public long intervalStart { get; set; }
|
||||
public long intervalEnd { get; set; }
|
||||
public Rect bounds { get; set; }
|
||||
|
||||
private const float kPrecision = 100.0f;
|
||||
private const float kMaxFloat = (float)long.MaxValue;
|
||||
private const float kMinFloat = (float)long.MinValue;
|
||||
|
||||
static public Int64 FromFloat(float f)
|
||||
{
|
||||
if (Single.IsPositiveInfinity(f))
|
||||
return long.MaxValue;
|
||||
if (Single.IsNegativeInfinity(f))
|
||||
return long.MinValue;
|
||||
|
||||
f = Mathf.Clamp(f, kMinFloat, kMaxFloat); // prevent overflow of floats
|
||||
f = Mathf.Clamp(f * kPrecision, kMinFloat, kMaxFloat); // clamp to 'long' range
|
||||
return (long)(f);
|
||||
}
|
||||
}
|
||||
|
||||
const EventType k_GuiEventLock = EventType.Repaint;
|
||||
|
||||
IntervalTree<Entry> m_Tree = new IntervalTree<Entry>();
|
||||
List<Entry> m_CacheList = new List<Entry>();
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
m_Tree.Clear();
|
||||
}
|
||||
|
||||
public void AddBounds(IBounds bounds)
|
||||
{
|
||||
AddBounds(bounds, bounds.boundingRect);
|
||||
}
|
||||
|
||||
public void AddBounds(object item, Rect rect)
|
||||
{
|
||||
if (item == null)
|
||||
throw new ArgumentNullException("item");
|
||||
|
||||
m_Tree.Add(new Entry()
|
||||
{
|
||||
intervalStart = Entry.FromFloat(rect.yMin),
|
||||
intervalEnd = Entry.FromFloat(rect.yMax),
|
||||
bounds = rect,
|
||||
item = item
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get items of type T at a given position
|
||||
/// </summary>
|
||||
/// <param name="position"></param>
|
||||
/// <param name="inClipSpace"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <remarks>
|
||||
/// Uses a (1,1) sized box
|
||||
/// Use .ToList() or .ToArray() when not enumerating the result immediately
|
||||
/// </remarks>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<T> GetItemsAtPosition<T>(Vector2 position)
|
||||
{
|
||||
return GetItemsInArea<T>(new Rect(position.x, position.y, 1, 1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="area"></param>
|
||||
/// <param name="inClipSpace"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<T> GetItemsInArea<T>(Rect area)
|
||||
{
|
||||
m_CacheList.Clear();
|
||||
m_Tree.IntersectsWithRange(long.MinValue, long.MaxValue, m_CacheList);
|
||||
|
||||
var list = CachedList<T>.Instance;
|
||||
list.Clear();
|
||||
foreach (var i in m_CacheList)
|
||||
{
|
||||
if (i.item is T && i.bounds.Overlaps(area))
|
||||
list.Add((T)i.item);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public void DebugDraw(Color fillColor, Color outlineColor)
|
||||
{
|
||||
m_CacheList.Clear();
|
||||
m_Tree.IntersectsWithRange(long.MinValue, long.MaxValue, m_CacheList);
|
||||
HandleUtility.ApplyWireMaterial();
|
||||
|
||||
foreach (var item in m_CacheList)
|
||||
{
|
||||
Handles.DrawSolidRectangleWithOutline(item.bounds, fillColor, outlineColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2fa2cf7de51b0d34d9dce3747b72e49d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.Experimental;
|
||||
using UnityEditor.StyleSheets;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class StyleManager
|
||||
{
|
||||
static readonly StyleState[] k_StyleStates = { StyleState.any };
|
||||
static readonly string k_ErrorCannotFindStyle = L10n.Tr("Cannot find style {0} for {1}");
|
||||
|
||||
static Dictionary<Type, GUIStyle> s_CustomStyles = new Dictionary<Type, GUIStyle>();
|
||||
static GUISkin s_CurrentSkin;
|
||||
|
||||
public static GUIStyle UssStyleForType(Type type)
|
||||
{
|
||||
ClearCacheIfInvalid();
|
||||
|
||||
GUIStyle cachedStyle;
|
||||
if (s_CustomStyles.TryGetValue(type, out cachedStyle))
|
||||
return cachedStyle;
|
||||
|
||||
var style = DirectorStyles.GetGUIStyle(DirectorStyles.markerDefaultStyle);
|
||||
|
||||
var customStyleForType = CustomStyleForType(type);
|
||||
if (customStyleForType != null)
|
||||
{
|
||||
if (IsStyleValid(customStyleForType))
|
||||
style = DirectorStyles.GetGUIStyle(customStyleForType);
|
||||
else
|
||||
Debug.LogWarningFormat(k_ErrorCannotFindStyle, customStyleForType, type.Name);
|
||||
}
|
||||
|
||||
s_CustomStyles.Add(type, style);
|
||||
return style;
|
||||
}
|
||||
|
||||
static string CustomStyleForType(Type type)
|
||||
{
|
||||
var attr = (CustomStyleAttribute)type.GetCustomAttributes(typeof(CustomStyleAttribute), true).FirstOrDefault();
|
||||
return attr != null ? attr.ussStyle : null;
|
||||
}
|
||||
|
||||
static bool IsStyleValid(string ussStyle)
|
||||
{
|
||||
return GUISkin.current.FindStyle(ussStyle) != null || EditorResources.styleCatalog.GetStyle(ussStyle, k_StyleStates).IsValid();
|
||||
}
|
||||
|
||||
static void ClearCacheIfInvalid()
|
||||
{
|
||||
if (s_CurrentSkin != GUISkin.current)
|
||||
s_CustomStyles.Clear();
|
||||
s_CurrentSkin = GUISkin.current;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f31f28cc64c91042976555c016ffd5f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
struct StyleNormalColorOverride : IDisposable
|
||||
{
|
||||
readonly GUIStyle m_Style;
|
||||
readonly Color m_OldColor;
|
||||
|
||||
public StyleNormalColorOverride(GUIStyle style, Color newColor)
|
||||
{
|
||||
m_Style = style;
|
||||
m_OldColor = style.normal.textColor;
|
||||
style.normal.textColor = newColor;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
m_Style.normal.textColor = m_OldColor;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2bd3ca1fde4b154448ef972b0f9d292e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// The available display modes for time in the Timeline Editor.
|
||||
/// </summary>
|
||||
public enum TimeFormat
|
||||
{
|
||||
/// <summary>Displays time values as frames.</summary>
|
||||
Frames,
|
||||
|
||||
/// <summary>Displays time values as timecode (SS:FF) format.</summary>
|
||||
Timecode,
|
||||
|
||||
/// <summary>Displays time values as seconds.</summary>
|
||||
Seconds
|
||||
};
|
||||
|
||||
static class TimeDisplayUnitExtensions
|
||||
{
|
||||
public static TimeArea.TimeFormat ToTimeAreaFormat(this TimeFormat timeDisplayUnit)
|
||||
{
|
||||
switch (timeDisplayUnit)
|
||||
{
|
||||
case TimeFormat.Frames: return TimeArea.TimeFormat.Frame;
|
||||
case TimeFormat.Timecode: return TimeArea.TimeFormat.TimeFrame;
|
||||
case TimeFormat.Seconds: return TimeArea.TimeFormat.None;
|
||||
}
|
||||
|
||||
return TimeArea.TimeFormat.Frame;
|
||||
}
|
||||
|
||||
public static string ToTimeString(this TimeFormat timeFormat, double time, double frameRate, string format = "f2")
|
||||
{
|
||||
switch (timeFormat)
|
||||
{
|
||||
case TimeFormat.Frames: return TimeUtility.TimeAsFrames(time, frameRate, format);
|
||||
case TimeFormat.Timecode: return TimeUtility.TimeAsTimeCode(time, frameRate, format);
|
||||
case TimeFormat.Seconds: return time.ToString(format, (IFormatProvider)CultureInfo.InvariantCulture.NumberFormat);
|
||||
}
|
||||
|
||||
return time.ToString(format);
|
||||
}
|
||||
|
||||
public static string ToTimeStringWithDelta(this TimeFormat timeFormat, double time, double frameRate, double delta, string format = "f2")
|
||||
{
|
||||
const double epsilon = 1e-7;
|
||||
var result = ToTimeString(timeFormat, time, frameRate, format);
|
||||
if (delta > epsilon || delta < -epsilon)
|
||||
{
|
||||
var sign = ((delta >= 0) ? "+" : "-");
|
||||
var deltaStr = ToTimeString(timeFormat, Math.Abs(delta), frameRate, format);
|
||||
return $"{result} ({sign}{deltaStr})";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static double FromTimeString(this TimeFormat timeFormat, string timeString, double frameRate, double defaultValue)
|
||||
{
|
||||
double time = defaultValue;
|
||||
switch (timeFormat)
|
||||
{
|
||||
case TimeFormat.Frames:
|
||||
if (!double.TryParse(timeString, NumberStyles.Any, CultureInfo.InvariantCulture, out time))
|
||||
return defaultValue;
|
||||
time = TimeUtility.FromFrames(time, frameRate);
|
||||
break;
|
||||
case TimeFormat.Seconds:
|
||||
time = TimeUtility.ParseTimeSeconds(timeString, frameRate, defaultValue);
|
||||
break;
|
||||
case TimeFormat.Timecode:
|
||||
time = TimeUtility.ParseTimeCode(timeString, frameRate, defaultValue);
|
||||
break;
|
||||
default:
|
||||
time = defaultValue;
|
||||
break;
|
||||
}
|
||||
|
||||
return time;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 25eff19ce9634557bc1b23c644c46692
|
||||
timeCreated: 1602253301
|
@ -0,0 +1,62 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class TimeReferenceUtility
|
||||
{
|
||||
static WindowState state { get { return TimelineWindow.instance.state; } }
|
||||
|
||||
public static float PixelToTime(Vector2 mousePos)
|
||||
{
|
||||
return PixelToTime(mousePos.x);
|
||||
}
|
||||
|
||||
public static float PixelToTime(float pixelX)
|
||||
{
|
||||
return state.PixelToTime(pixelX);
|
||||
}
|
||||
|
||||
public static double GetSnappedTimeAtMousePosition(Vector2 mousePos)
|
||||
{
|
||||
return state.GetSnappedTimeAtMousePosition(mousePos);
|
||||
}
|
||||
|
||||
public static double SnapToFrameIfRequired(double currentTime)
|
||||
{
|
||||
return TimelinePreferences.instance.snapToFrame ? SnapToFrame(currentTime) : currentTime;
|
||||
}
|
||||
|
||||
public static double SnapToFrame(double time)
|
||||
{
|
||||
if (state.timeReferenceMode == TimeReferenceMode.Global)
|
||||
{
|
||||
time = state.editSequence.ToGlobalTime(time);
|
||||
time = TimeUtility.RoundToFrame(time, state.referenceSequence.frameRate);
|
||||
return state.editSequence.ToLocalTime(time);
|
||||
}
|
||||
|
||||
return TimeUtility.RoundToFrame(time, state.referenceSequence.frameRate);
|
||||
}
|
||||
|
||||
public static string ToTimeString(double time, string format = "F2")
|
||||
{
|
||||
if (state.timeReferenceMode == TimeReferenceMode.Global)
|
||||
time = state.editSequence.ToGlobalTime(time);
|
||||
|
||||
return state.timeFormat.ToTimeString(time, state.referenceSequence.frameRate, format);
|
||||
}
|
||||
|
||||
public static double FromTimeString(string timeString)
|
||||
{
|
||||
double newTime = state.timeFormat.FromTimeString(timeString, state.referenceSequence.frameRate, -1);
|
||||
if (newTime >= 0.0)
|
||||
{
|
||||
return state.timeReferenceMode == TimeReferenceMode.Global ?
|
||||
state.editSequence.ToLocalTime(newTime) : newTime;
|
||||
}
|
||||
|
||||
return state.editSequence.time;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6bb32665bcc91b41a7177fd6af08ad6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,461 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class KeyboardNavigation
|
||||
{
|
||||
public static void FrameTrackHeader(TreeViewItem treeItem = null)
|
||||
{
|
||||
if (TrackHeadActive())
|
||||
treeItem = treeItem ?? SelectionManager.SelectedTrackGUI().Last();
|
||||
else
|
||||
{
|
||||
var item = GetVisibleSelectedItems().LastOrDefault();
|
||||
treeItem = TimelineWindow.instance.allTracks.FirstOrDefault(
|
||||
x => item != null && x.track == item.parentTrack);
|
||||
}
|
||||
|
||||
if (treeItem != null)
|
||||
TimelineWindow.instance.treeView.FrameItem(treeItem);
|
||||
}
|
||||
|
||||
public static bool TrackHeadActive()
|
||||
{
|
||||
return SelectionManager.SelectedTracks().Any(x => x.IsVisibleInHierarchy()) && !ClipAreaActive();
|
||||
}
|
||||
|
||||
public static bool ClipAreaActive()
|
||||
{
|
||||
return GetVisibleSelectedItems().Any();
|
||||
}
|
||||
|
||||
public static IEnumerable<ITimelineItem> GetVisibleSelectedItems()
|
||||
{
|
||||
return SelectionManager.SelectedItems().Where(x => x.parentTrack.IsVisibleInHierarchy());
|
||||
}
|
||||
|
||||
public static IEnumerable<TimelineTrackBaseGUI> GetVisibleTracks()
|
||||
{
|
||||
return TimelineWindow.instance.allTracks.Where(x => x.track.IsVisibleInHierarchy());
|
||||
}
|
||||
|
||||
static TrackAsset PreviousTrack(this TrackAsset track)
|
||||
{
|
||||
var uiOrderTracks = GetVisibleTracks().Select(t => t.track).ToList();
|
||||
var selIdx = uiOrderTracks.IndexOf(track);
|
||||
return selIdx > 0 ? uiOrderTracks[selIdx - 1] : null;
|
||||
}
|
||||
|
||||
static TrackAsset NextTrack(this TrackAsset track)
|
||||
{
|
||||
var uiOrderTracks = GetVisibleTracks().Select(t => t.track).ToList();
|
||||
var selIdx = uiOrderTracks.IndexOf(track);
|
||||
return selIdx < uiOrderTracks.Count - 1 && selIdx != -1 ? uiOrderTracks[selIdx + 1] : null;
|
||||
}
|
||||
|
||||
static ITimelineItem PreviousItem(this ITimelineItem item, bool clipOnly)
|
||||
{
|
||||
var items = item.parentTrack.GetItems().ToArray();
|
||||
if (clipOnly)
|
||||
{
|
||||
items = items.Where(x => x is ClipItem).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
items = items.Where(x => x is MarkerItem).ToArray();
|
||||
}
|
||||
|
||||
var idx = Array.IndexOf(items, item);
|
||||
return idx > 0 ? items[idx - 1] : null;
|
||||
}
|
||||
|
||||
static ITimelineItem NextItem(this ITimelineItem item, bool clipOnly)
|
||||
{
|
||||
var items = item.parentTrack.GetItems().ToArray();
|
||||
if (clipOnly)
|
||||
{
|
||||
items = items.Where(x => x is ClipItem).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
items = items.Where(x => x is MarkerItem).ToArray();
|
||||
}
|
||||
|
||||
var idx = Array.IndexOf(items, item);
|
||||
return idx < items.Length - 1 ? items[idx + 1] : null;
|
||||
}
|
||||
|
||||
static bool FilterItems(ref List<ITimelineItem> items)
|
||||
{
|
||||
var clipOnly = false;
|
||||
if (items.Any(x => x is ClipItem))
|
||||
{
|
||||
items = items.Where(x => x is ClipItem).ToList();
|
||||
clipOnly = true;
|
||||
}
|
||||
|
||||
return clipOnly;
|
||||
}
|
||||
|
||||
static ITimelineItem GetClosestItem(TrackAsset track, ITimelineItem refItem)
|
||||
{
|
||||
var start = refItem.start;
|
||||
var end = refItem.end;
|
||||
var items = track.GetItems().ToList();
|
||||
|
||||
if (refItem is ClipItem)
|
||||
{
|
||||
items = items.Where(x => x is ClipItem).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
items = items.Where(x => x is MarkerItem).ToList();
|
||||
}
|
||||
|
||||
if (!items.Any())
|
||||
return null;
|
||||
ITimelineItem ret = null;
|
||||
var scoreToBeat = double.NegativeInfinity;
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
// test for overlap
|
||||
var low = Math.Max(item.start, start);
|
||||
var high = Math.Min(item.end, end);
|
||||
if (low <= high)
|
||||
{
|
||||
var score = high - low;
|
||||
if (score >= scoreToBeat)
|
||||
{
|
||||
scoreToBeat = score;
|
||||
ret = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static bool FocusFirstVisibleItem(IEnumerable<TrackAsset> focusTracks = null)
|
||||
{
|
||||
var timeRange = TimelineEditor.visibleTimeRange;
|
||||
|
||||
var tracks = focusTracks ?? TimelineWindow.instance.treeView.visibleTracks.Where(x => x.IsVisibleInHierarchy() && x.GetItems().Any());
|
||||
var items = tracks.SelectMany(t => t.GetItems().OfType<ClipItem>().Where(x => x.end >= timeRange.x && x.end <= timeRange.y ||
|
||||
x.start >= timeRange.x && x.start <= timeRange.y)).ToList();
|
||||
var itemFullyInView = items.Where(x => x.end >= timeRange.x && x.end <= timeRange.y &&
|
||||
x.start >= timeRange.x && x.start <= timeRange.y);
|
||||
var itemToSelect = itemFullyInView.FirstOrDefault() ?? items.FirstOrDefault();
|
||||
if (itemToSelect != null && !itemToSelect.parentTrack.lockedInHierarchy)
|
||||
{
|
||||
SelectionManager.SelectOnly(itemToSelect);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool NavigateLeft(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
if (!TrackHeadActive())
|
||||
return false;
|
||||
|
||||
if (TryCollapse(tracks))
|
||||
return true;
|
||||
|
||||
return SelectAndShowParentTrack(tracks.LastOrDefault());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to collapse any track from a list of tracks
|
||||
/// </summary>
|
||||
/// <param name="tracks"></param>
|
||||
/// <returns>true if any were collapsed, false otherwise</returns>
|
||||
public static bool TryCollapse(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
var didCollapse = false;
|
||||
|
||||
foreach (TrackAsset track in tracks)
|
||||
{
|
||||
if (!track.GetChildTracks().Any())
|
||||
continue;
|
||||
|
||||
if (!track.IsCollapsed())
|
||||
{
|
||||
didCollapse = true;
|
||||
track.SetCollapsed(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (didCollapse)
|
||||
{
|
||||
TimelineEditor.window.treeView.Reload();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool ToggleCollapseGroup(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
if (!TrackHeadActive())
|
||||
return false;
|
||||
|
||||
var didChange = false;
|
||||
|
||||
foreach (TrackAsset track in tracks)
|
||||
{
|
||||
if (!track.GetChildTracks().Any())
|
||||
continue;
|
||||
|
||||
track.SetCollapsed(!track.IsCollapsed());
|
||||
didChange = true;
|
||||
}
|
||||
|
||||
if (didChange)
|
||||
TimelineEditor.window.treeView.Reload();
|
||||
|
||||
return didChange;
|
||||
}
|
||||
|
||||
static bool SelectAndShowParentTrack(TrackAsset track)
|
||||
{
|
||||
TrackAsset parent = track != null ? track.parent as TrackAsset : null;
|
||||
if (parent)
|
||||
{
|
||||
SelectionManager.SelectOnly(parent);
|
||||
FrameTrackHeader(GetVisibleTracks().FirstOrDefault(x => x.track == parent));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool SelectLeftItem(bool shift = false)
|
||||
{
|
||||
if (ClipAreaActive())
|
||||
{
|
||||
var items = SelectionManager.SelectedItems().ToList();
|
||||
var clipOnly = FilterItems(ref items);
|
||||
|
||||
var item = items.Last();
|
||||
var prev = item.PreviousItem(clipOnly);
|
||||
if (prev != null && !prev.parentTrack.lockedInHierarchy)
|
||||
{
|
||||
if (shift)
|
||||
{
|
||||
if (SelectionManager.Contains(prev))
|
||||
SelectionManager.Remove(item);
|
||||
SelectionManager.Add(prev);
|
||||
}
|
||||
else
|
||||
SelectionManager.SelectOnly(prev);
|
||||
TimelineHelpers.FrameItems(new[] { prev });
|
||||
}
|
||||
else if (item != null && !shift && item.parentTrack != TimelineEditor.inspectedAsset.markerTrack)
|
||||
SelectionManager.SelectOnly(item.parentTrack);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool SelectRightItem(bool shift = false)
|
||||
{
|
||||
if (ClipAreaActive())
|
||||
{
|
||||
var items = SelectionManager.SelectedItems().ToList();
|
||||
var clipOnly = FilterItems(ref items);
|
||||
|
||||
var item = items.Last();
|
||||
var next = item.NextItem(clipOnly);
|
||||
if (next != null && !next.parentTrack.lockedInHierarchy)
|
||||
{
|
||||
if (shift)
|
||||
{
|
||||
if (SelectionManager.Contains(next))
|
||||
SelectionManager.Remove(item);
|
||||
SelectionManager.Add(next);
|
||||
}
|
||||
else
|
||||
SelectionManager.SelectOnly(next);
|
||||
TimelineHelpers.FrameItems(new[] { next });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool NavigateRight(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
if (!TrackHeadActive())
|
||||
return false;
|
||||
|
||||
if (TryExpand(tracks))
|
||||
return true;
|
||||
|
||||
return TrySelectFirstChild(tracks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to expand from a list of tracks
|
||||
/// </summary>
|
||||
/// <param name="tracks"></param>
|
||||
/// <returns>true if any expanded, false otherwise</returns>
|
||||
public static bool TryExpand(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
var didExpand = false;
|
||||
foreach (TrackAsset track in tracks)
|
||||
{
|
||||
if (!track.GetChildTracks().Any())
|
||||
continue;
|
||||
|
||||
if (track.IsCollapsed())
|
||||
{
|
||||
didExpand = true;
|
||||
track.SetCollapsed(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (didExpand)
|
||||
{
|
||||
TimelineEditor.window.treeView.Reload();
|
||||
}
|
||||
|
||||
return didExpand;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to select the first clip from a list of tracks
|
||||
/// </summary>
|
||||
/// <param name="tracks"></param>
|
||||
/// <returns>true if any expanded, false otherwise</returns>
|
||||
public static bool TrySelectFirstChild(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
foreach (var track in tracks)
|
||||
{
|
||||
//Try to navigate in group tracks first
|
||||
if (track is GroupTrack)
|
||||
{
|
||||
if (track.GetChildTracks().Any())
|
||||
{
|
||||
SelectionManager.SelectOnly(track.GetChildTracks().First());
|
||||
return true;
|
||||
}
|
||||
//Group tracks should not halt navigation
|
||||
continue;
|
||||
}
|
||||
//if track is locked or has no clips, do nothing
|
||||
if (track.lockedInHierarchy || !track.clips.Any())
|
||||
continue;
|
||||
|
||||
var firstClip = track.clips.First();
|
||||
SelectionManager.SelectOnly(firstClip);
|
||||
TimelineHelpers.FrameItems(new ITimelineItem[] { firstClip.ToItem() });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool SelectUpTrack(bool shift = false)
|
||||
{
|
||||
if (TrackHeadActive())
|
||||
{
|
||||
var prevTrack = PreviousTrack(SelectionManager.SelectedTracks().Last());
|
||||
if (prevTrack != null)
|
||||
{
|
||||
if (shift)
|
||||
{
|
||||
if (SelectionManager.Contains(prevTrack))
|
||||
SelectionManager.Remove(SelectionManager.SelectedTracks().Last());
|
||||
SelectionManager.Add(prevTrack);
|
||||
}
|
||||
else
|
||||
SelectionManager.SelectOnly(prevTrack);
|
||||
FrameTrackHeader(GetVisibleTracks().First(x => x.track == prevTrack));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool SelectUpItem()
|
||||
{
|
||||
if (ClipAreaActive())
|
||||
{
|
||||
var refItem = SelectionManager.SelectedItems().Last();
|
||||
var prevTrack = refItem.parentTrack.PreviousTrack();
|
||||
while (prevTrack != null)
|
||||
{
|
||||
var selectionItem = GetClosestItem(prevTrack, refItem);
|
||||
if (selectionItem == null || prevTrack.lockedInHierarchy)
|
||||
{
|
||||
prevTrack = prevTrack.PreviousTrack();
|
||||
continue;
|
||||
}
|
||||
|
||||
SelectionManager.SelectOnly(selectionItem);
|
||||
TimelineHelpers.FrameItems(new[] { selectionItem });
|
||||
FrameTrackHeader(GetVisibleTracks().First(x => x.track == selectionItem.parentTrack));
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool SelectDownTrack(bool shift = false)
|
||||
{
|
||||
if (TrackHeadActive())
|
||||
{
|
||||
var nextTrack = SelectionManager.SelectedTracks().Last().NextTrack();
|
||||
if (nextTrack != null)
|
||||
{
|
||||
if (shift)
|
||||
{
|
||||
if (SelectionManager.Contains(nextTrack))
|
||||
SelectionManager.Remove(SelectionManager.SelectedTracks().Last());
|
||||
SelectionManager.Add(nextTrack);
|
||||
}
|
||||
else
|
||||
SelectionManager.SelectOnly(nextTrack);
|
||||
|
||||
FrameTrackHeader(GetVisibleTracks().First(x => x.track == nextTrack));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool SelectDownItem()
|
||||
{
|
||||
if (ClipAreaActive())
|
||||
{
|
||||
var refItem = SelectionManager.SelectedItems().Last();
|
||||
var nextTrack = refItem.parentTrack.NextTrack();
|
||||
while (nextTrack != null)
|
||||
{
|
||||
var selectionItem = GetClosestItem(nextTrack, refItem);
|
||||
if (selectionItem == null || nextTrack.lockedInHierarchy)
|
||||
{
|
||||
nextTrack = nextTrack.NextTrack();
|
||||
continue;
|
||||
}
|
||||
|
||||
SelectionManager.SelectOnly(selectionItem);
|
||||
TimelineHelpers.FrameItems(new[] { selectionItem });
|
||||
FrameTrackHeader(GetVisibleTracks().First(x => x.track == selectionItem.parentTrack));
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9be6112c2b1c3ae44927680ba7b36e10
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,21 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class TrackModifier
|
||||
{
|
||||
public static bool DeleteTrack(TimelineAsset timeline, TrackAsset track)
|
||||
{
|
||||
if (TimelineEditor.inspectedDirector != null)
|
||||
{
|
||||
TimelineUndo.PushUndo(TimelineEditor.inspectedDirector, L10n.Tr("Delete Track"));
|
||||
TimelineEditor.inspectedDirector.ClearGenericBinding(track);
|
||||
}
|
||||
|
||||
return timeline.DeleteTrack(track);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 411b7c7ffc0960249b35a2a247b66ff7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class TrackResourceCache
|
||||
{
|
||||
private static Dictionary<System.Type, GUIContent> s_TrackIconCache = new Dictionary<Type, GUIContent>(10);
|
||||
private static Dictionary<System.Type, Color> s_TrackColorCache = new Dictionary<Type, Color>(10);
|
||||
public static GUIContent s_DefaultIcon = EditorGUIUtility.IconContent("UnityEngine/ScriptableObject Icon");
|
||||
|
||||
public static GUIContent GetTrackIcon(TrackAsset track)
|
||||
{
|
||||
if (track == null)
|
||||
return s_DefaultIcon;
|
||||
|
||||
GUIContent content = null;
|
||||
if (!s_TrackIconCache.TryGetValue(track.GetType(), out content))
|
||||
{
|
||||
content = FindTrackIcon(track);
|
||||
s_TrackIconCache[track.GetType()] = content;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
public static Texture2D GetTrackIconForType(System.Type trackType)
|
||||
{
|
||||
if (trackType == null || !typeof(TrackAsset).IsAssignableFrom(trackType))
|
||||
return null;
|
||||
|
||||
GUIContent content;
|
||||
if (!s_TrackIconCache.TryGetValue(trackType, out content) || content.image == null)
|
||||
return s_DefaultIcon.image as Texture2D;
|
||||
return content.image as Texture2D;
|
||||
}
|
||||
|
||||
public static Color GetTrackColor(TrackAsset track)
|
||||
{
|
||||
if (track == null)
|
||||
return Color.white;
|
||||
|
||||
// Try to ensure DirectorStyles is initialized first
|
||||
// Note: GUISkin.current must exist to be able do so
|
||||
if (!DirectorStyles.IsInitialized && GUISkin.current != null)
|
||||
DirectorStyles.ReloadStylesIfNeeded();
|
||||
|
||||
Color color;
|
||||
if (!s_TrackColorCache.TryGetValue(track.GetType(), out color))
|
||||
{
|
||||
var attr = track.GetType().GetCustomAttributes(typeof(TrackColorAttribute), true);
|
||||
if (attr.Length > 0)
|
||||
{
|
||||
color = ((TrackColorAttribute)attr[0]).color;
|
||||
}
|
||||
else
|
||||
{
|
||||
// case 1141958
|
||||
// There was an error initializing DirectorStyles
|
||||
if (!DirectorStyles.IsInitialized)
|
||||
return Color.white;
|
||||
|
||||
color = DirectorStyles.Instance.customSkin.colorDefaultTrackDrawer;
|
||||
}
|
||||
|
||||
s_TrackColorCache[track.GetType()] = color;
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
public static void ClearTrackIconCache()
|
||||
{
|
||||
s_TrackIconCache.Clear();
|
||||
}
|
||||
|
||||
public static void SetTrackIcon<T>(GUIContent content) where T : TrackAsset
|
||||
{
|
||||
s_TrackIconCache[typeof(T)] = content;
|
||||
}
|
||||
|
||||
public static void ClearTrackColorCache()
|
||||
{
|
||||
s_TrackColorCache.Clear();
|
||||
}
|
||||
|
||||
public static void SetTrackColor<T>(Color c) where T : TrackAsset
|
||||
{
|
||||
s_TrackColorCache[typeof(T)] = c;
|
||||
}
|
||||
|
||||
private static GUIContent FindTrackIcon(TrackAsset track)
|
||||
{
|
||||
// backwards compatible -- try to load from Gizmos folder
|
||||
Texture2D texture = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/Gizmos/" + track.GetType().Name + ".png");
|
||||
if (texture != null)
|
||||
return new GUIContent(texture);
|
||||
|
||||
// try to load based on the binding type
|
||||
var binding = track.outputs.FirstOrDefault();
|
||||
if (binding.outputTargetType != null)
|
||||
{
|
||||
// Type calls don't properly handle monobehaviours, because an instance is required to
|
||||
// get the monoscript icons
|
||||
if (typeof(MonoBehaviour).IsAssignableFrom(binding.outputTargetType))
|
||||
{
|
||||
texture = null;
|
||||
var scripts = UnityEngine.Resources.FindObjectsOfTypeAll<MonoScript>();
|
||||
foreach (var script in scripts)
|
||||
{
|
||||
if (script.GetClass() == binding.outputTargetType)
|
||||
{
|
||||
texture = AssetPreview.GetMiniThumbnail(script);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
texture = EditorGUIUtility.FindTexture(binding.outputTargetType);
|
||||
}
|
||||
|
||||
if (texture != null)
|
||||
return new GUIContent(texture);
|
||||
}
|
||||
|
||||
// default to the scriptable object icon
|
||||
return s_DefaultIcon;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 63f2caa33e79582448112b2e286d576d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,373 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
using Component = UnityEngine.Component;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class TypeUtility
|
||||
{
|
||||
private static List<Type> s_AllTrackTypes;
|
||||
private static List<Type> s_AllClipTypes;
|
||||
private static List<Type> s_MarkerTypes;
|
||||
private static Dictionary<Type, Type[]> s_TrackTypeToVisibleClipType = new Dictionary<Type, Type[]>();
|
||||
private static Dictionary<Type, Type[]> s_TrackTypeToAllClipType = new Dictionary<Type, Type[]>();
|
||||
private static Dictionary<Type, TrackBindingTypeAttribute> s_TrackToBindingCache = new Dictionary<Type, TrackBindingTypeAttribute>();
|
||||
|
||||
public static bool IsConcretePlayableAsset(Type t)
|
||||
{
|
||||
return typeof(IPlayableAsset).IsAssignableFrom(t)
|
||||
&& IsConcreteAsset(t);
|
||||
}
|
||||
|
||||
private static bool IsConcreteAsset(Type t)
|
||||
{
|
||||
return typeof(ScriptableObject).IsAssignableFrom(t)
|
||||
&& !t.IsAbstract
|
||||
&& !t.IsGenericType
|
||||
&& !t.IsInterface
|
||||
&& !typeof(TrackAsset).IsAssignableFrom(t)
|
||||
&& !typeof(TimelineAsset).IsAssignableFrom(t);
|
||||
}
|
||||
|
||||
class TimelineTypeComparer : IComparer<Type>
|
||||
{
|
||||
public static readonly TimelineTypeComparer Instance = new TimelineTypeComparer();
|
||||
|
||||
public int Compare(Type x, Type y)
|
||||
{
|
||||
if (ReferenceEquals(x, y))
|
||||
return 0;
|
||||
|
||||
if (ReferenceEquals(null, y))
|
||||
return 1;
|
||||
|
||||
if (ReferenceEquals(null, x))
|
||||
return -1;
|
||||
|
||||
if (IsBuiltIn(x) && !IsBuiltIn(y))
|
||||
return -1;
|
||||
|
||||
if (!IsBuiltIn(x) && IsBuiltIn(y))
|
||||
return 1;
|
||||
|
||||
return string.Compare(x.AssemblyQualifiedName, y.AssemblyQualifiedName, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
TimelineTypeComparer() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of all PlayableAssets
|
||||
/// </summary>
|
||||
public static IEnumerable<Type> AllClipTypes()
|
||||
{
|
||||
if (s_AllClipTypes == null)
|
||||
{
|
||||
s_AllClipTypes = TypeCache.GetTypesDerivedFrom<IPlayableAsset>().
|
||||
Where(t => IsConcreteAsset(t)).
|
||||
ToList();
|
||||
s_AllClipTypes.Sort(TimelineTypeComparer.Instance);
|
||||
}
|
||||
return s_AllClipTypes;
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> AllTrackTypes()
|
||||
{
|
||||
if (s_AllTrackTypes == null)
|
||||
{
|
||||
s_AllTrackTypes = TypeCache.GetTypesDerivedFrom<TrackAsset>()
|
||||
.Where(x => !x.IsAbstract)
|
||||
.ToList();
|
||||
s_AllTrackTypes.Sort(TimelineTypeComparer.Instance);
|
||||
}
|
||||
|
||||
return s_AllTrackTypes;
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> GetVisiblePlayableAssetsHandledByTrack(Type trackType)
|
||||
{
|
||||
if (trackType == null || !typeof(TrackAsset).IsAssignableFrom(trackType))
|
||||
return Enumerable.Empty<Type>();
|
||||
|
||||
Type[] types;
|
||||
if (s_TrackTypeToVisibleClipType.TryGetValue(trackType, out types))
|
||||
{
|
||||
return types;
|
||||
}
|
||||
|
||||
// special case -- the playable track handles all types not handled by other tracks
|
||||
if (trackType == typeof(PlayableTrack))
|
||||
{
|
||||
types = GetUnhandledClipTypes().ToArray();
|
||||
s_TrackTypeToVisibleClipType[trackType] = types;
|
||||
return types;
|
||||
}
|
||||
|
||||
var attributes = trackType.GetCustomAttributes(typeof(TrackClipTypeAttribute), true);
|
||||
var baseClasses = attributes.
|
||||
OfType<TrackClipTypeAttribute>().
|
||||
Where(t => t.allowAutoCreate).
|
||||
Select(a => a.inspectedType);
|
||||
|
||||
types = AllClipTypes().Where(t => baseClasses.Any(x => x.IsAssignableFrom(t))).ToArray();
|
||||
s_TrackTypeToVisibleClipType[trackType] = types;
|
||||
return types;
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> GetPlayableAssetsHandledByTrack(Type trackType)
|
||||
{
|
||||
if (trackType == null || !typeof(TrackAsset).IsAssignableFrom(trackType))
|
||||
return Enumerable.Empty<Type>();
|
||||
|
||||
Type[] types;
|
||||
if (s_TrackTypeToAllClipType.TryGetValue(trackType, out types))
|
||||
{
|
||||
return types;
|
||||
}
|
||||
|
||||
// special case -- the playable track handles all types not handled by other tracks
|
||||
if (trackType == typeof(PlayableTrack))
|
||||
{
|
||||
types = GetUnhandledClipTypes().ToArray();
|
||||
s_TrackTypeToAllClipType[trackType] = types;
|
||||
return types;
|
||||
}
|
||||
|
||||
var attributes = trackType.GetCustomAttributes(typeof(TrackClipTypeAttribute), true);
|
||||
var baseClasses = attributes.
|
||||
OfType<TrackClipTypeAttribute>().
|
||||
Select(a => a.inspectedType);
|
||||
|
||||
types = AllClipTypes().Where(t => baseClasses.Any(x => x.IsAssignableFrom(t))).ToArray();
|
||||
s_TrackTypeToAllClipType[trackType] = types;
|
||||
return types;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the binding attribute attrached to the track
|
||||
/// </summary>
|
||||
public static TrackBindingTypeAttribute GetTrackBindingAttribute(Type trackType)
|
||||
{
|
||||
if (trackType == null || !typeof(TrackAsset).IsAssignableFrom(trackType))
|
||||
return null;
|
||||
|
||||
TrackBindingTypeAttribute attribute = null;
|
||||
if (!s_TrackToBindingCache.TryGetValue(trackType, out attribute))
|
||||
{
|
||||
attribute = (TrackBindingTypeAttribute)Attribute.GetCustomAttribute(trackType, typeof(TrackBindingTypeAttribute));
|
||||
s_TrackToBindingCache.Add(trackType, attribute);
|
||||
}
|
||||
|
||||
return attribute;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the given track has a clip type that handles the given object
|
||||
/// </summary>
|
||||
public static bool TrackHasClipForObject(Type trackType, Object obj)
|
||||
{
|
||||
return GetPlayableAssetsHandledByTrack(trackType)
|
||||
.Any(c => ObjectReferenceField.FindObjectReferences(c).Any(o => o.IsAssignable(obj)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the list of markers that have fields for the object
|
||||
/// </summary>
|
||||
public static IEnumerable<Type> MarkerTypesWithFieldForObject(Object obj)
|
||||
{
|
||||
return GetAllMarkerTypes().Where(
|
||||
c => ObjectReferenceField.FindObjectReferences(c).Any(o => o.IsAssignable(obj))
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the list of tracks that can handle this object as clips
|
||||
/// </summary>
|
||||
public static IEnumerable<Type> GetTrackTypesForObject(Object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return Enumerable.Empty<Type>();
|
||||
|
||||
return AllTrackTypes().Where(t => TrackHasClipForObject(t, obj));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a trackType and an object, does the binding type match
|
||||
/// Takes into account whether creating a missing component is permitted
|
||||
/// </summary>
|
||||
public static bool IsTrackCreatableFromObject(Object obj, Type trackType)
|
||||
{
|
||||
if (obj == null || obj.IsPrefab())
|
||||
return false;
|
||||
|
||||
var attribute = GetTrackBindingAttribute(trackType);
|
||||
if (attribute == null || attribute.type == null)
|
||||
return false;
|
||||
|
||||
if (attribute.type.IsAssignableFrom(obj.GetType()))
|
||||
return true;
|
||||
|
||||
var gameObject = obj as GameObject;
|
||||
if (gameObject != null && typeof(Component).IsAssignableFrom(attribute.type))
|
||||
{
|
||||
return gameObject.GetComponent(attribute.type) != null ||
|
||||
(attribute.flags & TrackBindingFlags.AllowCreateComponent) != 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given an object, get the list of track that are creatable from it. Takes
|
||||
/// binding flags into account
|
||||
/// </summary>
|
||||
public static IEnumerable<Type> GetTracksCreatableFromObject(Object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return Enumerable.Empty<Type>();
|
||||
|
||||
return AllTrackTypes().Where(t => !IsHiddenInMenu(t) && IsTrackCreatableFromObject(obj, t));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the list of playable assets that can handle an object for a particular track
|
||||
/// </summary>
|
||||
/// <param name="trackType">The type of the track</param>
|
||||
/// <param name="obj">The object to handle</param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Type> GetAssetTypesForObject(Type trackType, Object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return Enumerable.Empty<Type>();
|
||||
|
||||
return GetPlayableAssetsHandledByTrack(trackType).Where(
|
||||
c => ObjectReferenceField.FindObjectReferences(c).Any(o => o.IsAssignable(obj))
|
||||
);
|
||||
}
|
||||
|
||||
// get the track types for a track from it's attributes
|
||||
private static IEnumerable<Type> GetTrackClipTypesFromAttributes(Type trackType)
|
||||
{
|
||||
if (trackType == null || !typeof(TrackAsset).IsAssignableFrom(trackType))
|
||||
return Enumerable.Empty<Type>();
|
||||
|
||||
var attributes = trackType.GetCustomAttributes(typeof(TrackClipTypeAttribute), true);
|
||||
var baseClasses = attributes.
|
||||
OfType<TrackClipTypeAttribute>().
|
||||
Select(a => a.inspectedType);
|
||||
|
||||
return AllClipTypes().Where(t => baseClasses.Any(x => x.IsAssignableFrom(t)));
|
||||
}
|
||||
|
||||
// find the playable asset types that are unhandled
|
||||
private static IEnumerable<Type> GetUnhandledClipTypes()
|
||||
{
|
||||
var typesHandledByTrack = AllTrackTypes().SelectMany(t => GetTrackClipTypesFromAttributes(t));
|
||||
|
||||
// exclude anything in the timeline assembly, handled by tracks, has a hide in menu attribute
|
||||
// or is explicity ignored
|
||||
return AllClipTypes()
|
||||
.Except(typesHandledByTrack)
|
||||
.Where(t => !TypeUtility.IsBuiltIn(t)) // exclude built-in
|
||||
.Where(t => !typeof(TrackAsset).IsAssignableFrom(t)) // exclude track types (they are playable assets)
|
||||
.Where(t => !t.IsDefined(typeof(HideInMenuAttribute), false) && !t.IsDefined(typeof(IgnoreOnPlayableTrackAttribute), true))
|
||||
.Distinct();
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> GetAllMarkerTypes()
|
||||
{
|
||||
if (s_MarkerTypes == null)
|
||||
{
|
||||
s_MarkerTypes = TypeCache.GetTypesDerivedFrom<IMarker>()
|
||||
.Where(x =>
|
||||
typeof(ScriptableObject).IsAssignableFrom(x)
|
||||
&& !x.IsAbstract
|
||||
&& !x.IsGenericType
|
||||
&& !x.IsInterface)
|
||||
.ToList();
|
||||
|
||||
s_MarkerTypes.Sort(TimelineTypeComparer.Instance);
|
||||
}
|
||||
return s_MarkerTypes;
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> GetUserMarkerTypes()
|
||||
{
|
||||
return GetAllMarkerTypes().Where(x => !IsBuiltIn(x) && !IsHiddenInMenu(x));
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> GetBuiltInMarkerTypes()
|
||||
{
|
||||
return GetAllMarkerTypes().Where(IsBuiltIn);
|
||||
}
|
||||
|
||||
public static bool DoesTrackSupportMarkerType(TrackAsset track, Type type)
|
||||
{
|
||||
if (track.supportsNotifications)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return !typeof(INotification).IsAssignableFrom(type);
|
||||
}
|
||||
|
||||
internal static string GetDisplayName(Type t)
|
||||
{
|
||||
var displayName = ObjectNames.NicifyVariableName(t.Name);
|
||||
var attr = Attribute.GetCustomAttribute(t, typeof(DisplayNameAttribute), false) as DisplayNameAttribute;
|
||||
if (attr != null)
|
||||
displayName = attr.DisplayName;
|
||||
return L10n.Tr(displayName);
|
||||
}
|
||||
|
||||
public static bool IsHiddenInMenu(Type type)
|
||||
{
|
||||
var attr = type.GetCustomAttributes(typeof(HideInMenuAttribute), false);
|
||||
return attr.Length > 0;
|
||||
}
|
||||
|
||||
public struct ObjectReference
|
||||
{
|
||||
public Type type;
|
||||
public bool isSceneReference;
|
||||
}
|
||||
|
||||
public static IEnumerable<ObjectReference> ObjectReferencesForType(Type type)
|
||||
{
|
||||
var objectReferences = ObjectReferenceField.FindObjectReferences(type);
|
||||
var uniqueTypes = objectReferences.Select(objRef => objRef.type).Distinct();
|
||||
foreach (var refType in uniqueTypes)
|
||||
{
|
||||
var isSceneReference = objectReferences.Any(objRef => objRef.type == refType && objRef.isSceneReference);
|
||||
yield return new ObjectReference { type = refType, isSceneReference = isSceneReference };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a type has an overridden method with a specific name. This method also checks overridden members in parent classes.
|
||||
/// </summary>
|
||||
public static bool HasOverrideMethod(System.Type t, string name)
|
||||
{
|
||||
const MethodAttributes mask = MethodAttributes.Virtual | MethodAttributes.NewSlot;
|
||||
const MethodAttributes expectedResult = MethodAttributes.Virtual;
|
||||
|
||||
var method = t.GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
return method != null && (method.Attributes & mask) == expectedResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the given type resides in the timeline assembly
|
||||
/// </summary>
|
||||
public static bool IsBuiltIn(System.Type t)
|
||||
{
|
||||
return t != null && t.Assembly.Equals(typeof(TimelineAsset).Assembly);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c1821c1816c6fa44967b8ecb79ea7e4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user