Unity-jump-proj
This commit is contained in:
@ -0,0 +1,341 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[CustomEditor(typeof(AnimationPlayableAsset)), CanEditMultipleObjects]
|
||||
class AnimationPlayableAssetInspector : Editor
|
||||
{
|
||||
static class Styles
|
||||
{
|
||||
public static readonly GUIContent RotationText = L10n.TextContent("Rotation");
|
||||
public static readonly GUIContent AnimClipText = L10n.TextContent("Animation Clip");
|
||||
public static readonly GUIContent TransformOffsetTitle = L10n.TextContent("Clip Transform Offsets", "Use this to offset the root transform position and rotation relative to the track when playing this clip");
|
||||
public static readonly GUIContent AnimationClipName = L10n.TextContent("Animation Clip Name");
|
||||
public static readonly GUIContent MatchTargetFieldsTitle = L10n.TextContent("Offsets Match Fields", "Fields to apply when matching offsets on clips. The defaults can be set on the track.");
|
||||
public static readonly GUIContent UseDefaults = L10n.TextContent("Use defaults");
|
||||
public static readonly GUIContent RemoveStartOffset = L10n.TextContent("Remove Start Offset", "Makes playback of the clip play relative to first key of the root transform");
|
||||
public static readonly GUIContent ApplyFootIK = L10n.TextContent("Foot IK", "Enable to apply foot IK to the AnimationClip when the target is humanoid.");
|
||||
public static readonly GUIContent Loop = L10n.TextContent("Loop", "Whether the source Animation Clip loops during playback.");
|
||||
}
|
||||
|
||||
TimelineWindow m_TimelineWindow;
|
||||
GameObject m_Binding;
|
||||
|
||||
TimelineAnimationUtilities.OffsetEditMode m_OffsetEditMode = TimelineAnimationUtilities.OffsetEditMode.None;
|
||||
EditorClip m_EditorClip;
|
||||
EditorClip[] m_EditorClips;
|
||||
|
||||
SerializedProperty m_PositionProperty;
|
||||
SerializedProperty m_RotationProperty;
|
||||
SerializedProperty m_AnimClipProperty;
|
||||
SerializedProperty m_UseTrackMatchFieldsProperty;
|
||||
SerializedProperty m_MatchTargetFieldsProperty;
|
||||
SerializedObject m_SerializedAnimClip;
|
||||
SerializedProperty m_SerializedAnimClipName;
|
||||
SerializedProperty m_RemoveStartOffsetProperty;
|
||||
SerializedProperty m_ApplyFootIK;
|
||||
SerializedProperty m_Loop;
|
||||
|
||||
Vector3 m_LastPosition;
|
||||
Vector3 m_LastRotation;
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
if (!m_TimelineWindow) m_TimelineWindow = TimelineWindow.instance;
|
||||
|
||||
ShowAnimationClipField();
|
||||
ShowRecordableClipRename();
|
||||
ShowAnimationClipWarnings();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
TransformOffsetsGUI();
|
||||
|
||||
// extra checks are because the context menu may need to cause a re-evaluate
|
||||
bool changed = EditorGUI.EndChangeCheck() ||
|
||||
m_LastPosition != m_PositionProperty.vector3Value ||
|
||||
m_LastRotation != m_RotationProperty.vector3Value;
|
||||
m_LastPosition = m_PositionProperty.vector3Value;
|
||||
m_LastRotation = m_RotationProperty.vector3Value;
|
||||
|
||||
if (changed)
|
||||
{
|
||||
// updates the changed properties and pushes them to the active playable
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
((AnimationPlayableAsset)target).LiveLink();
|
||||
|
||||
// force an evaluate to happen next frame
|
||||
if (TimelineWindow.instance != null && TimelineWindow.instance.state != null)
|
||||
{
|
||||
TimelineWindow.instance.state.Evaluate();
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_ApplyFootIK, Styles.ApplyFootIK);
|
||||
EditorGUILayout.PropertyField(m_Loop, Styles.Loop);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
void ShowAnimationClipField()
|
||||
{
|
||||
bool disabled = m_EditorClips == null || m_EditorClips.Any(c => c.clip == null || c.clip.recordable);
|
||||
using (new EditorGUI.DisabledScope(disabled))
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_AnimClipProperty, Styles.AnimClipText);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
// rename the timeline clips to match the animation name if it did previously
|
||||
if (m_AnimClipProperty.objectReferenceValue != null && m_EditorClips != null)
|
||||
{
|
||||
var newName = m_AnimClipProperty.objectReferenceValue.name;
|
||||
foreach (var c in m_EditorClips)
|
||||
{
|
||||
if (c == null || c.clip == null || c.clip.asset == null)
|
||||
continue;
|
||||
|
||||
var apa = c.clip.asset as AnimationPlayableAsset;
|
||||
if (apa != null && apa.clip != null && c.clip.displayName == apa.clip.name)
|
||||
{
|
||||
if (c.clip.GetParentTrack() != null)
|
||||
Undo.RegisterCompleteObjectUndo(c.clip.GetParentTrack(), L10n.Tr("Inspector"));
|
||||
c.clip.displayName = newName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TransformOffsetsMatchFieldsGUI()
|
||||
{
|
||||
var rect = EditorGUILayout.GetControlRect(true);
|
||||
EditorGUI.BeginProperty(rect, Styles.MatchTargetFieldsTitle, m_UseTrackMatchFieldsProperty);
|
||||
|
||||
rect = EditorGUI.PrefixLabel(rect, Styles.MatchTargetFieldsTitle);
|
||||
int oldIndent = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = 0;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
bool val = m_UseTrackMatchFieldsProperty.boolValue;
|
||||
val = EditorGUI.ToggleLeft(rect, Styles.UseDefaults, val);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
m_UseTrackMatchFieldsProperty.boolValue = val;
|
||||
|
||||
EditorGUI.indentLevel = oldIndent;
|
||||
EditorGUI.EndProperty();
|
||||
|
||||
|
||||
if (!val || m_UseTrackMatchFieldsProperty.hasMultipleDifferentValues)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
AnimationTrackInspector.MatchTargetsFieldGUI(m_MatchTargetFieldsProperty);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
void TransformOffsetsGUI()
|
||||
{
|
||||
if (ShouldShowOffsets())
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField(Styles.TransformOffsetTitle);
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
using (new EditorGUI.DisabledScope(targets.Length > 1))
|
||||
{
|
||||
var previousOffsetMode = m_OffsetEditMode;
|
||||
AnimationTrackInspector.ShowMotionOffsetEditModeToolbar(ref m_OffsetEditMode);
|
||||
if (previousOffsetMode != m_OffsetEditMode)
|
||||
{
|
||||
SetTimeToClip();
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PropertyField(m_PositionProperty);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PropertyField(m_RotationProperty, Styles.RotationText);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
TransformOffsetsMatchFieldsGUI();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_RemoveStartOffsetProperty, Styles.RemoveStartOffset);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Reevaluate()
|
||||
{
|
||||
if (m_TimelineWindow != null && m_TimelineWindow.state != null)
|
||||
{
|
||||
m_TimelineWindow.state.Refresh();
|
||||
m_TimelineWindow.state.EvaluateImmediate();
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the director time is within the bounds of the clip
|
||||
void SetTimeToClip()
|
||||
{
|
||||
if (m_TimelineWindow != null && m_TimelineWindow.state != null)
|
||||
{
|
||||
m_TimelineWindow.state.editSequence.time = Math.Min(m_EditorClip.clip.end, Math.Max(m_EditorClip.clip.start, m_TimelineWindow.state.editSequence.time));
|
||||
}
|
||||
}
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
if (target == null) // case 946080
|
||||
return;
|
||||
|
||||
m_EditorClip = UnityEditor.Selection.activeObject as EditorClip;
|
||||
m_EditorClips = UnityEditor.Selection.objects.OfType<EditorClip>().ToArray();
|
||||
SceneView.duringSceneGui += OnSceneGUI;
|
||||
|
||||
m_PositionProperty = serializedObject.FindProperty("m_Position");
|
||||
m_PositionProperty.isExpanded = true;
|
||||
m_RotationProperty = serializedObject.FindProperty("m_EulerAngles");
|
||||
m_AnimClipProperty = serializedObject.FindProperty("m_Clip");
|
||||
m_UseTrackMatchFieldsProperty = serializedObject.FindProperty("m_UseTrackMatchFields");
|
||||
m_MatchTargetFieldsProperty = serializedObject.FindProperty("m_MatchTargetFields");
|
||||
m_RemoveStartOffsetProperty = serializedObject.FindProperty("m_RemoveStartOffset");
|
||||
m_ApplyFootIK = serializedObject.FindProperty("m_ApplyFootIK");
|
||||
m_Loop = serializedObject.FindProperty("m_Loop");
|
||||
|
||||
m_LastPosition = m_PositionProperty.vector3Value;
|
||||
m_LastRotation = m_RotationProperty.vector3Value;
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
SceneView.duringSceneGui -= OnSceneGUI;
|
||||
}
|
||||
|
||||
void OnSceneGUI(SceneView sceneView)
|
||||
{
|
||||
DoManipulators();
|
||||
}
|
||||
|
||||
Transform GetTransform()
|
||||
{
|
||||
if (m_Binding != null)
|
||||
return m_Binding.transform;
|
||||
|
||||
if (m_TimelineWindow != null && m_TimelineWindow.state != null &&
|
||||
m_TimelineWindow.state.editSequence.director != null &&
|
||||
m_EditorClip != null && m_EditorClip.clip != null)
|
||||
{
|
||||
var obj = TimelineUtility.GetSceneGameObject(m_TimelineWindow.state.editSequence.director,
|
||||
m_EditorClip.clip.GetParentTrack());
|
||||
m_Binding = obj;
|
||||
if (obj != null)
|
||||
return obj.transform;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void DoManipulators()
|
||||
{
|
||||
if (m_EditorClip == null || m_EditorClip.clip == null)
|
||||
return;
|
||||
|
||||
AnimationPlayableAsset animationPlayable = m_EditorClip.clip.asset as AnimationPlayableAsset;
|
||||
AnimationTrack track = m_EditorClip.clip.GetParentTrack() as AnimationTrack;
|
||||
Transform transform = GetTransform();
|
||||
|
||||
if (transform != null && animationPlayable != null && m_OffsetEditMode != TimelineAnimationUtilities.OffsetEditMode.None && track != null)
|
||||
{
|
||||
UndoExtensions.RegisterPlayableAsset(animationPlayable, L10n.Tr("Inspector"));
|
||||
Vector3 position = transform.position;
|
||||
Quaternion rotation = transform.rotation;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
if (m_OffsetEditMode == TimelineAnimationUtilities.OffsetEditMode.Translation)
|
||||
{
|
||||
position = Handles.PositionHandle(position, Tools.pivotRotation == PivotRotation.Global ? Quaternion.identity : rotation);
|
||||
}
|
||||
else if (m_OffsetEditMode == TimelineAnimationUtilities.OffsetEditMode.Rotation)
|
||||
{
|
||||
rotation = Handles.RotationHandle(rotation, position);
|
||||
}
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
var res = TimelineAnimationUtilities.UpdateClipOffsets(animationPlayable, track, transform, position, rotation);
|
||||
animationPlayable.position = res.position;
|
||||
animationPlayable.eulerAngles = AnimationUtility.GetClosestEuler(res.rotation, animationPlayable.eulerAngles, RotationOrder.OrderZXY);
|
||||
Reevaluate();
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShowAnimationClipWarnings()
|
||||
{
|
||||
AnimationClip clip = m_AnimClipProperty.objectReferenceValue as AnimationClip;
|
||||
if (clip == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox(AnimationPlayableAssetEditor.k_NoClipAssignedError, MessageType.Warning);
|
||||
}
|
||||
else if (clip.legacy)
|
||||
{
|
||||
EditorGUILayout.HelpBox(AnimationPlayableAssetEditor.k_LegacyClipError, MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
bool ShouldShowOffsets()
|
||||
{
|
||||
return targets.OfType<AnimationPlayableAsset>().All(x => x.hasRootTransforms);
|
||||
}
|
||||
|
||||
void ShowRecordableClipRename()
|
||||
{
|
||||
if (targets.Length > 1 || m_EditorClip == null || m_EditorClip.clip == null || !m_EditorClip.clip.recordable)
|
||||
return;
|
||||
|
||||
AnimationClip clip = m_AnimClipProperty.objectReferenceValue as AnimationClip;
|
||||
if (clip == null || !AssetDatabase.IsSubAsset(clip))
|
||||
return;
|
||||
|
||||
if (m_SerializedAnimClip == null)
|
||||
{
|
||||
m_SerializedAnimClip = new SerializedObject(clip);
|
||||
m_SerializedAnimClipName = m_SerializedAnimClip.FindProperty("m_Name");
|
||||
}
|
||||
|
||||
if (m_SerializedAnimClipName != null)
|
||||
{
|
||||
m_SerializedAnimClip.Update();
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.DelayedTextField(m_SerializedAnimClipName, Styles.AnimationClipName);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
m_SerializedAnimClip.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f7852b99951997645ae7adaac5f0b083
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,516 @@
|
||||
//#define PERF_PROFILE
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[CustomEditor(typeof(AnimationTrack)), CanEditMultipleObjects]
|
||||
class AnimationTrackInspector : TrackAssetInspector
|
||||
{
|
||||
static class Styles
|
||||
{
|
||||
public static GUIContent MatchTargetFieldsTitle = L10n.TextContent("Default Offset Match Fields", "Fields to apply when matching offsets on clips. These are the defaults, and can be overridden for each clip.");
|
||||
public static readonly GUIContent PositionIcon = EditorGUIUtility.IconContent("MoveTool");
|
||||
public static readonly GUIContent RotationIcon = EditorGUIUtility.IconContent("RotateTool");
|
||||
|
||||
public static GUIContent XTitle = EditorGUIUtility.TextContent("X");
|
||||
public static GUIContent YTitle = EditorGUIUtility.TextContent("Y");
|
||||
public static GUIContent ZTitle = EditorGUIUtility.TextContent("Z");
|
||||
public static GUIContent PositionTitle = L10n.TextContent("Position");
|
||||
public static GUIContent RotationTitle = L10n.TextContent("Rotation");
|
||||
|
||||
public static readonly GUIContent OffsetModeTitle = L10n.TextContent("Track Offsets");
|
||||
public static readonly string TransformOffsetInfo = L10n.Tr("Transform offsets are applied to the entire track. Use this mode to play the animation track at a fixed position and rotation.");
|
||||
public static readonly string SceneOffsetInfo = L10n.Tr("Scene offsets will use the existing transform as initial offsets. Use this to play the track from the gameObjects current position and rotation.");
|
||||
public static readonly string AutoOffsetInfo = L10n.Tr("Auto will apply scene offsets if there is a controller attached to the animator and transform offsets otherwise.");
|
||||
public static readonly string AutoOffsetWarning = L10n.Tr("This mode is deprecated may be removed in a future release.");
|
||||
public static readonly string InheritedFromParent = L10n.Tr("Inherited");
|
||||
public static readonly string InheritedToolTip = L10n.Tr("This value is inherited from it's parent track.");
|
||||
|
||||
public static readonly string AvatarMaskWarning = L10n.Tr("Applying an Avatar Mask to the base track may not properly mask Root Motion or Humanoid bones from an Animator Controller or other Timeline track.");
|
||||
|
||||
public static readonly GUIContent RecordingOffsets = L10n.TextContent("Recorded Offsets", "Offsets applied to recorded position and rotation keys");
|
||||
public static readonly GUIContent RecordingIkApplied = L10n.TextContent("Apply Foot IK", "Applies Foot IK to recorded Animation.");
|
||||
|
||||
public static readonly GUIContent[] OffsetContents;
|
||||
public static readonly GUIContent[] OffsetInheritContents;
|
||||
|
||||
static Styles()
|
||||
{
|
||||
var values = Enum.GetValues(typeof(TrackOffset));
|
||||
OffsetContents = new GUIContent[values.Length];
|
||||
OffsetInheritContents = new GUIContent[values.Length];
|
||||
for (var index = 0; index < values.Length; index++)
|
||||
{
|
||||
var offset = (TrackOffset)index;
|
||||
var name = ObjectNames.NicifyVariableName(L10n.Tr(offset.ToString()));
|
||||
var memInfo = typeof(TrackOffset).GetMember(offset.ToString());
|
||||
var attributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
|
||||
if (attributes.Length > 0)
|
||||
{
|
||||
name = ((DescriptionAttribute)attributes[0]).Description;
|
||||
}
|
||||
|
||||
OffsetContents[index] = new GUIContent(name);
|
||||
OffsetInheritContents[index] = new GUIContent(string.Format("{0} ({1})", InheritedFromParent, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TimelineAnimationUtilities.OffsetEditMode m_OffsetEditMode = TimelineAnimationUtilities.OffsetEditMode.None;
|
||||
|
||||
SerializedProperty m_MatchFieldsProperty;
|
||||
SerializedProperty m_TrackPositionProperty;
|
||||
SerializedProperty m_TrackRotationProperty;
|
||||
SerializedProperty m_AvatarMaskProperty;
|
||||
SerializedProperty m_ApplyAvatarMaskProperty;
|
||||
SerializedProperty m_TrackOffsetProperty;
|
||||
|
||||
SerializedProperty m_RecordedOffsetPositionProperty;
|
||||
SerializedProperty m_RecordedOffsetEulerProperty;
|
||||
SerializedProperty m_RecordedApplyFootIK;
|
||||
|
||||
Vector3 m_lastPosition;
|
||||
Vector3 m_lastRotation;
|
||||
|
||||
GUIContent m_TempContent = new GUIContent();
|
||||
|
||||
|
||||
void Evaluate()
|
||||
{
|
||||
if (timelineWindow.state != null && timelineWindow.state.editSequence.director != null)
|
||||
{
|
||||
// force the update immediately, the deferred doesn't always work with the inspector
|
||||
timelineWindow.state.editSequence.director.Evaluate();
|
||||
}
|
||||
}
|
||||
|
||||
void RebuildGraph()
|
||||
{
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
using (new EditorGUI.DisabledScope(IsTrackLocked()))
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
DrawRootTransformOffset();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
DrawRecordedProperties();
|
||||
DrawAvatarProperties();
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
RebuildGraph();
|
||||
|
||||
DrawMatchFieldsGUI();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
bool AnimatesRootTransform()
|
||||
{
|
||||
return targets.OfType<AnimationTrack>().All(t => t.AnimatesRootTransform());
|
||||
}
|
||||
|
||||
bool ShouldDrawOffsets()
|
||||
{
|
||||
bool hasMultiple;
|
||||
var offsetMode = GetOffsetMode(out hasMultiple);
|
||||
if (hasMultiple)
|
||||
return false;
|
||||
|
||||
if (offsetMode == TrackOffset.ApplySceneOffsets)
|
||||
return false;
|
||||
|
||||
if (offsetMode == TrackOffset.ApplyTransformOffsets)
|
||||
return true;
|
||||
|
||||
// Auto mode.
|
||||
PlayableDirector director = this.m_Context as PlayableDirector;
|
||||
if (director == null)
|
||||
return false;
|
||||
|
||||
// If any bound animators have controllers don't show
|
||||
foreach (var track in targets.OfType<AnimationTrack>())
|
||||
{
|
||||
var animator = track.GetBinding(director);
|
||||
if (animator != null && animator.runtimeAnimatorController != null)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DrawRootTransformOffset()
|
||||
{
|
||||
if (!AnimatesRootTransform())
|
||||
return;
|
||||
|
||||
bool showWarning = SetupOffsetTooltip();
|
||||
DrawRootTransformDropDown();
|
||||
|
||||
if (ShouldDrawOffsets())
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
DrawRootMotionToolBar();
|
||||
DrawRootMotionOffsetFields();
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
if (showWarning)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.HelpBox(Styles.AutoOffsetWarning, MessageType.Warning, true);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
bool SetupOffsetTooltip()
|
||||
{
|
||||
Styles.OffsetModeTitle.tooltip = string.Empty;
|
||||
bool hasMultiple;
|
||||
var offsetMode = GetOffsetMode(out hasMultiple);
|
||||
bool showWarning = false;
|
||||
if (!hasMultiple)
|
||||
{
|
||||
if (offsetMode == TrackOffset.ApplyTransformOffsets)
|
||||
Styles.OffsetModeTitle.tooltip = Styles.TransformOffsetInfo;
|
||||
else if (offsetMode == TrackOffset.ApplySceneOffsets)
|
||||
Styles.OffsetModeTitle.tooltip = Styles.SceneOffsetInfo;
|
||||
else if (offsetMode == TrackOffset.Auto)
|
||||
{
|
||||
Styles.OffsetModeTitle.tooltip = Styles.AutoOffsetInfo;
|
||||
showWarning = true;
|
||||
}
|
||||
}
|
||||
|
||||
return showWarning;
|
||||
}
|
||||
|
||||
void DrawRootTransformDropDown()
|
||||
{
|
||||
bool anySubTracks = targets.OfType<AnimationTrack>().Any(t => t.isSubTrack);
|
||||
bool allSubTracks = targets.OfType<AnimationTrack>().All(t => t.isSubTrack);
|
||||
|
||||
bool mixed;
|
||||
var rootOffsetMode = GetOffsetMode(out mixed);
|
||||
|
||||
// if we are showing subtracks, we need to show the current mode from the parent
|
||||
// BUT keep it disabled
|
||||
if (anySubTracks)
|
||||
{
|
||||
m_TempContent.tooltip = string.Empty;
|
||||
if (mixed)
|
||||
m_TempContent.text = EditorGUI.mixedValueContent.text;
|
||||
else if (!allSubTracks)
|
||||
m_TempContent.text = Styles.OffsetContents[(int)rootOffsetMode].text;
|
||||
else
|
||||
{
|
||||
m_TempContent.text = Styles.OffsetInheritContents[(int)rootOffsetMode].text;
|
||||
m_TempContent.tooltip = Styles.InheritedToolTip;
|
||||
}
|
||||
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
EditorGUILayout.LabelField(Styles.OffsetModeTitle, m_TempContent, EditorStyles.popup);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We use an enum popup explicitly because it will handle the description attribute on the enum
|
||||
using (new GUIMixedValueScope(mixed))
|
||||
{
|
||||
var rect = EditorGUILayout.GetControlRect(true, EditorGUI.kSingleLineHeight);
|
||||
EditorGUI.BeginProperty(rect, Styles.OffsetModeTitle, m_TrackOffsetProperty);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var result = (TrackOffset)EditorGUI.EnumPopup(rect, Styles.OffsetModeTitle, (TrackOffset)m_TrackOffsetProperty.intValue);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
m_TrackOffsetProperty.enumValueIndex = (int)result;
|
||||
|
||||
// this property changes the recordable state of the objects, so auto disable recording
|
||||
if (TimelineWindow.instance != null)
|
||||
{
|
||||
if (TimelineWindow.instance.state != null)
|
||||
TimelineWindow.instance.state.recording = false;
|
||||
RebuildGraph();
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawMatchFieldsGUI()
|
||||
{
|
||||
if (!AnimatesRootTransform())
|
||||
return;
|
||||
|
||||
m_MatchFieldsProperty.isExpanded = EditorGUILayout.Foldout(m_MatchFieldsProperty.isExpanded, Styles.MatchTargetFieldsTitle, true);
|
||||
if (m_MatchFieldsProperty.isExpanded)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
MatchTargetsFieldGUI(m_MatchFieldsProperty);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
void DrawRootMotionOffsetFields()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PropertyField(m_TrackPositionProperty);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PropertyField(m_TrackRotationProperty, Styles.RotationTitle);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
UpdateOffsets();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawRootMotionToolBar()
|
||||
{
|
||||
bool disable = targets.Length > 1;
|
||||
bool changed = false;
|
||||
|
||||
if (!disable)
|
||||
{
|
||||
// detects external changes
|
||||
changed |= m_lastPosition != m_TrackPositionProperty.vector3Value || m_lastRotation != m_TrackRotationProperty.vector3Value;
|
||||
m_lastPosition = m_TrackPositionProperty.vector3Value;
|
||||
m_lastRotation = m_TrackRotationProperty.vector3Value;
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
using (new EditorGUI.DisabledScope(disable))
|
||||
ShowMotionOffsetEditModeToolbar(ref m_OffsetEditMode);
|
||||
changed |= EditorGUI.EndChangeCheck();
|
||||
|
||||
if (changed)
|
||||
{
|
||||
UpdateOffsets();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateOffsets()
|
||||
{
|
||||
foreach (var track in targets.OfType<AnimationTrack>())
|
||||
track.UpdateClipOffsets();
|
||||
Evaluate();
|
||||
}
|
||||
|
||||
void DrawAvatarProperties()
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_ApplyAvatarMaskProperty);
|
||||
if (m_ApplyAvatarMaskProperty.hasMultipleDifferentValues || m_ApplyAvatarMaskProperty.boolValue)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(m_AvatarMaskProperty);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
if (targets.OfType<AnimationTrack>().Any(x => !x.isSubTrack))
|
||||
EditorGUILayout.HelpBox(Styles.AvatarMaskWarning, MessageType.Warning);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
public static void ShowMotionOffsetEditModeToolbar(ref TimelineAnimationUtilities.OffsetEditMode motionOffset)
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
int newMotionOffsetMode = GUILayout.Toolbar((int)motionOffset, new[] { Styles.PositionIcon, Styles.RotationIcon });
|
||||
|
||||
if (GUI.changed)
|
||||
{
|
||||
if ((int)motionOffset == newMotionOffsetMode) //untoggle the button
|
||||
motionOffset = TimelineAnimationUtilities.OffsetEditMode.None;
|
||||
else
|
||||
motionOffset = (TimelineAnimationUtilities.OffsetEditMode)newMotionOffsetMode;
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.Space(3);
|
||||
}
|
||||
|
||||
public override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
SceneView.duringSceneGui += OnSceneGUI;
|
||||
|
||||
m_MatchFieldsProperty = serializedObject.FindProperty("m_MatchTargetFields");
|
||||
m_TrackPositionProperty = serializedObject.FindProperty("m_Position");
|
||||
m_TrackRotationProperty = serializedObject.FindProperty("m_EulerAngles");
|
||||
m_TrackOffsetProperty = serializedObject.FindProperty("m_TrackOffset");
|
||||
m_AvatarMaskProperty = serializedObject.FindProperty("m_AvatarMask");
|
||||
m_ApplyAvatarMaskProperty = serializedObject.FindProperty("m_ApplyAvatarMask");
|
||||
m_RecordedOffsetPositionProperty = serializedObject.FindProperty("m_InfiniteClipOffsetPosition");
|
||||
m_RecordedOffsetEulerProperty = serializedObject.FindProperty("m_InfiniteClipOffsetEulerAngles");
|
||||
m_RecordedApplyFootIK = serializedObject.FindProperty("m_InfiniteClipApplyFootIK");
|
||||
|
||||
m_lastPosition = m_TrackPositionProperty.vector3Value;
|
||||
m_lastRotation = m_TrackRotationProperty.vector3Value;
|
||||
}
|
||||
|
||||
public void OnDestroy()
|
||||
{
|
||||
SceneView.duringSceneGui -= OnSceneGUI;
|
||||
}
|
||||
|
||||
void OnSceneGUI(SceneView sceneView)
|
||||
{
|
||||
DoOffsetManipulator();
|
||||
}
|
||||
|
||||
void DoOffsetManipulator()
|
||||
{
|
||||
if (targets.Length > 1) //do not edit the track offset on a multiple selection
|
||||
return;
|
||||
|
||||
if (timelineWindow == null || timelineWindow.state == null || timelineWindow.state.editSequence.director == null)
|
||||
return;
|
||||
|
||||
AnimationTrack animationTrack = target as AnimationTrack;
|
||||
if (animationTrack != null && (animationTrack.trackOffset == TrackOffset.ApplyTransformOffsets) && m_OffsetEditMode != TimelineAnimationUtilities.OffsetEditMode.None)
|
||||
{
|
||||
var boundObject = TimelineUtility.GetSceneGameObject(timelineWindow.state.editSequence.director, animationTrack);
|
||||
var boundObjectTransform = boundObject != null ? boundObject.transform : null;
|
||||
|
||||
var offsets = TimelineAnimationUtilities.GetTrackOffsets(animationTrack, boundObjectTransform);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
switch (m_OffsetEditMode)
|
||||
{
|
||||
case TimelineAnimationUtilities.OffsetEditMode.Translation:
|
||||
offsets.position = Handles.PositionHandle(offsets.position, (Tools.pivotRotation == PivotRotation.Global)
|
||||
? Quaternion.identity
|
||||
: offsets.rotation);
|
||||
break;
|
||||
case TimelineAnimationUtilities.OffsetEditMode.Rotation:
|
||||
offsets.rotation = Handles.RotationHandle(offsets.rotation, offsets.position);
|
||||
break;
|
||||
}
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
UndoExtensions.RegisterTrack(animationTrack, L10n.Tr("Inspector"));
|
||||
TimelineAnimationUtilities.UpdateTrackOffset(animationTrack, boundObjectTransform, offsets);
|
||||
Evaluate();
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawRecordedProperties()
|
||||
{
|
||||
// only show if this applies to all targets
|
||||
foreach (var track in targets)
|
||||
{
|
||||
var animationTrack = track as AnimationTrack;
|
||||
if (animationTrack == null || animationTrack.inClipMode || animationTrack.infiniteClip == null || animationTrack.infiniteClip.empty)
|
||||
return;
|
||||
}
|
||||
|
||||
GUILayout.Label(Styles.RecordingOffsets);
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PropertyField(m_RecordedOffsetPositionProperty, Styles.PositionTitle);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PropertyField(m_RecordedOffsetEulerProperty, Styles.RotationTitle);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(m_RecordedApplyFootIK, Styles.RecordingIkApplied);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
public static void MatchTargetsFieldGUI(SerializedProperty property)
|
||||
{
|
||||
const float ToggleWidth = 20;
|
||||
int value = 0;
|
||||
|
||||
MatchTargetFields enumValue = (MatchTargetFields)property.intValue;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
Rect rect = EditorGUILayout.GetControlRect(false, kLineHeight * 2);
|
||||
Rect itemRect = new Rect(rect.x, rect.y, rect.width, kLineHeight);
|
||||
EditorGUI.BeginProperty(rect, Styles.MatchTargetFieldsTitle, property);
|
||||
float minWidth = 0, maxWidth = 0;
|
||||
EditorStyles.label.CalcMinMaxWidth(Styles.XTitle, out minWidth, out maxWidth);
|
||||
float width = minWidth + ToggleWidth;
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
Rect r = EditorGUI.PrefixLabel(itemRect, Styles.PositionTitle);
|
||||
int oldIndent = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = 0;
|
||||
r.width = width;
|
||||
value |= EditorGUI.ToggleLeft(r, Styles.XTitle, enumValue.HasAny(MatchTargetFields.PositionX)) ? (int)MatchTargetFields.PositionX : 0;
|
||||
r.x += width;
|
||||
value |= EditorGUI.ToggleLeft(r, Styles.YTitle, enumValue.HasAny(MatchTargetFields.PositionY)) ? (int)MatchTargetFields.PositionY : 0;
|
||||
r.x += width;
|
||||
value |= EditorGUI.ToggleLeft(r, Styles.ZTitle, enumValue.HasAny(MatchTargetFields.PositionZ)) ? (int)MatchTargetFields.PositionZ : 0;
|
||||
EditorGUI.indentLevel = oldIndent;
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
itemRect.y += kLineHeight;
|
||||
r = EditorGUI.PrefixLabel(itemRect, Styles.RotationTitle);
|
||||
EditorGUI.indentLevel = 0;
|
||||
r.width = width;
|
||||
value |= EditorGUI.ToggleLeft(r, Styles.XTitle, enumValue.HasAny(MatchTargetFields.RotationX)) ? (int)MatchTargetFields.RotationX : 0;
|
||||
r.x += width;
|
||||
value |= EditorGUI.ToggleLeft(r, Styles.YTitle, enumValue.HasAny(MatchTargetFields.RotationY)) ? (int)MatchTargetFields.RotationY : 0;
|
||||
r.x += width;
|
||||
value |= EditorGUI.ToggleLeft(r, Styles.ZTitle, enumValue.HasAny(MatchTargetFields.RotationZ)) ? (int)MatchTargetFields.RotationZ : 0;
|
||||
EditorGUI.indentLevel = oldIndent;
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
EditorGUI.EndProperty();
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
property.intValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
static TrackOffset GetOffsetMode(AnimationTrack track)
|
||||
{
|
||||
if (track.isSubTrack)
|
||||
{
|
||||
var parent = track.parent as AnimationTrack;
|
||||
if (parent != null) // fallback to the current track if there is an error
|
||||
track = parent;
|
||||
}
|
||||
|
||||
return track.trackOffset;
|
||||
}
|
||||
|
||||
// gets the current mode,
|
||||
TrackOffset GetOffsetMode(out bool hasMultiple)
|
||||
{
|
||||
var rootOffsetMode = GetOffsetMode(target as AnimationTrack);
|
||||
hasMultiple = targets.OfType<AnimationTrack>().Any(t => GetOffsetMode(t) != rootOffsetMode);
|
||||
return rootOffsetMode;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: edc553b1b3c8a25438b62783410b26ae
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,47 @@
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
// Simple inspector used by built in assets
|
||||
// that only need to hide the script field
|
||||
class BasicAssetInspector : Editor, IInspectorChangeHandler
|
||||
{
|
||||
bool m_ShouldRebuild;
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
serializedObject.Update();
|
||||
|
||||
SerializedProperty property = serializedObject.GetIterator();
|
||||
bool expanded = true;
|
||||
while (property.NextVisible(expanded))
|
||||
{
|
||||
expanded = false;
|
||||
if (SkipField(property.propertyPath))
|
||||
continue;
|
||||
EditorGUILayout.PropertyField(property, true);
|
||||
}
|
||||
|
||||
m_ShouldRebuild = serializedObject.ApplyModifiedProperties();
|
||||
EditorGUI.EndChangeCheck();
|
||||
}
|
||||
|
||||
public virtual void OnPlayableAssetChangedInInspector()
|
||||
{
|
||||
if (m_ShouldRebuild)
|
||||
{
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
}
|
||||
|
||||
m_ShouldRebuild = false;
|
||||
}
|
||||
|
||||
static bool SkipField(string fieldName)
|
||||
{
|
||||
return fieldName == "m_Script";
|
||||
}
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(ActivationPlayableAsset))]
|
||||
class ActivationPlayableAssetInspector : BasicAssetInspector { }
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e086b15460c228c4f9b116f0e3e2f175
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,58 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
// Preset libraries
|
||||
static class BuiltInPresets
|
||||
{
|
||||
static CurvePresetLibrary s_BlendInPresets;
|
||||
static CurvePresetLibrary s_BlendOutPresets;
|
||||
|
||||
internal static CurvePresetLibrary blendInPresets
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_BlendInPresets == null)
|
||||
{
|
||||
s_BlendInPresets = ScriptableObject.CreateInstance<CurvePresetLibrary>();
|
||||
s_BlendInPresets.Add(new AnimationCurve(CurveEditorWindow.GetConstantKeys(1f)), "None");
|
||||
s_BlendInPresets.Add(new AnimationCurve(CurveEditorWindow.GetLinearKeys()), "Linear");
|
||||
s_BlendInPresets.Add(new AnimationCurve(CurveEditorWindow.GetEaseInKeys()), "EaseIn");
|
||||
s_BlendInPresets.Add(new AnimationCurve(CurveEditorWindow.GetEaseOutKeys()), "EaseOut");
|
||||
s_BlendInPresets.Add(new AnimationCurve(CurveEditorWindow.GetEaseInOutKeys()), "EaseInOut");
|
||||
}
|
||||
return s_BlendInPresets;
|
||||
}
|
||||
}
|
||||
|
||||
internal static CurvePresetLibrary blendOutPresets
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_BlendOutPresets == null)
|
||||
{
|
||||
s_BlendOutPresets = ScriptableObject.CreateInstance<CurvePresetLibrary>();
|
||||
s_BlendOutPresets.Add(new AnimationCurve(CurveEditorWindow.GetConstantKeys(1f)), "None");
|
||||
s_BlendOutPresets.Add(ReverseCurve(new AnimationCurve(CurveEditorWindow.GetLinearKeys())), "Linear");
|
||||
s_BlendOutPresets.Add(ReverseCurve(new AnimationCurve(CurveEditorWindow.GetEaseInKeys())), "EaseIn");
|
||||
s_BlendOutPresets.Add(ReverseCurve(new AnimationCurve(CurveEditorWindow.GetEaseOutKeys())), "EaseOut");
|
||||
s_BlendOutPresets.Add(ReverseCurve(new AnimationCurve(CurveEditorWindow.GetEaseInOutKeys())), "EaseInOut");
|
||||
}
|
||||
return s_BlendOutPresets;
|
||||
}
|
||||
}
|
||||
|
||||
static AnimationCurve ReverseCurve(AnimationCurve curve)
|
||||
{
|
||||
Keyframe[] keys = curve.keys;
|
||||
for (int i = 0; i < keys.Length; i++)
|
||||
{
|
||||
keys[i].value = 1 - keys[i].value;
|
||||
keys[i].inTangent *= -1;
|
||||
keys[i].outTangent *= -1;
|
||||
}
|
||||
curve.keys = keys;
|
||||
return curve;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae05f0dd1cf145e4e8e905c7971ee433
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f68c7f7359094f045930a108c444e7a4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,855 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[CustomEditor(typeof(EditorClip)), CanEditMultipleObjects]
|
||||
class ClipInspector : Editor
|
||||
{
|
||||
internal static class Styles
|
||||
{
|
||||
public static readonly GUIContent StartName = L10n.TextContent("Start", "The start time of the clip");
|
||||
public static readonly GUIContent DurationName = L10n.TextContent("Duration", "The length of the clip");
|
||||
public static readonly GUIContent EndName = L10n.TextContent("End", "The end time of the clip");
|
||||
public static readonly GUIContent EaseInDurationName = L10n.TextContent("Ease In Duration", "The length of the ease in");
|
||||
public static readonly GUIContent BlendInDurationName = L10n.TextContent("Blend In Duration", "The length of the blend in");
|
||||
public static readonly GUIContent EaseOutDurationName = L10n.TextContent("Ease Out Duration", "The length of the ease out");
|
||||
public static readonly GUIContent BlendOutDurationName = L10n.TextContent("Blend Out Duration", "The length of the blend out");
|
||||
public static readonly GUIContent ClipInName = L10n.TextContent("Clip In", "Start the clip at this local time");
|
||||
public static readonly GUIContent TimeScaleName = L10n.TextContent("Speed Multiplier", "Time scale of the playback speed");
|
||||
public static readonly GUIContent PreExtrapolateLabel = L10n.TextContent("Pre-Extrapolate", "Extrapolation used prior to the first clip");
|
||||
public static readonly GUIContent PostExtrapolateLabel = L10n.TextContent("Post-Extrapolate", "Extrapolation used after a clip ends");
|
||||
public static readonly GUIContent BlendInCurveName = L10n.TextContent("In", "Blend In Curve");
|
||||
public static readonly GUIContent BlendOutCurveName = L10n.TextContent("Out", "Blend Out Curve");
|
||||
public static readonly GUIContent PreviewTitle = L10n.TextContent("Curve Editor");
|
||||
public static readonly GUIContent ClipTimingTitle = L10n.TextContent("Clip Timing");
|
||||
public static readonly GUIContent AnimationExtrapolationTitle = L10n.TextContent("Animation Extrapolation");
|
||||
public static readonly GUIContent BlendCurvesTitle = L10n.TextContent("Blend Curves");
|
||||
public static readonly GUIContent GroupTimingTitle = L10n.TextContent("Multiple Clip Timing");
|
||||
public static readonly GUIContent MultipleClipsSelectedIncompatibleCapabilitiesWarning = L10n.TextContent("Multiple clips selected. Only common properties are shown.");
|
||||
public static readonly GUIContent MultipleSelectionTitle = L10n.TextContent("Timeline Clips");
|
||||
public static readonly GUIContent MultipleClipStartName = L10n.TextContent("Start", "The start time of the clip group");
|
||||
public static readonly GUIContent MultipleClipEndName = L10n.TextContent("End", "The end time of the clip group");
|
||||
public static readonly GUIContent TimelineClipFG = DirectorStyles.IconContent("TimelineClipFG");
|
||||
public static readonly GUIContent TimelineClipBG = DirectorStyles.IconContent("TimelineClipBG");
|
||||
}
|
||||
|
||||
class EditorClipSelection : ICurvesOwnerInspectorWrapper
|
||||
{
|
||||
public EditorClip editorClip { get; }
|
||||
|
||||
public TimelineClip clip
|
||||
{
|
||||
get { return editorClip == null ? null : editorClip.clip; }
|
||||
}
|
||||
|
||||
public SerializedObject serializedPlayableAsset { get; }
|
||||
|
||||
public ICurvesOwner curvesOwner
|
||||
{
|
||||
get { return clip; }
|
||||
}
|
||||
|
||||
public int lastCurveVersion { get; set; }
|
||||
public double lastEvalTime { get; set; }
|
||||
|
||||
public EditorClipSelection(EditorClip anEditorClip)
|
||||
{
|
||||
editorClip = anEditorClip;
|
||||
lastCurveVersion = -1;
|
||||
lastEvalTime = -1;
|
||||
|
||||
var so = new SerializedObject(editorClip);
|
||||
var playableAssetProperty = so.FindProperty("m_Clip.m_Asset");
|
||||
if (playableAssetProperty != null)
|
||||
{
|
||||
var asset = playableAssetProperty.objectReferenceValue as UnityEngine.Playables.PlayableAsset;
|
||||
if (asset != null)
|
||||
serializedPlayableAsset = new SerializedObject(asset);
|
||||
}
|
||||
}
|
||||
|
||||
public double ToLocalTime(double time)
|
||||
{
|
||||
return clip == null ? time : clip.ToLocalTime(time);
|
||||
}
|
||||
}
|
||||
|
||||
enum PreviewCurveState
|
||||
{
|
||||
None = 0,
|
||||
MixIn = 1,
|
||||
MixOut = 2
|
||||
}
|
||||
|
||||
|
||||
SerializedProperty m_DisplayNameProperty;
|
||||
SerializedProperty m_BlendInDurationProperty;
|
||||
SerializedProperty m_BlendOutDurationProperty;
|
||||
SerializedProperty m_EaseInDurationProperty;
|
||||
SerializedProperty m_EaseOutDurationProperty;
|
||||
SerializedProperty m_ClipInProperty;
|
||||
SerializedProperty m_TimeScaleProperty;
|
||||
SerializedProperty m_PostExtrapolationModeProperty;
|
||||
SerializedProperty m_PreExtrapolationModeProperty;
|
||||
SerializedProperty m_PostExtrapolationTimeProperty;
|
||||
SerializedProperty m_PreExtrapolationTimeProperty;
|
||||
SerializedProperty m_MixInCurveProperty;
|
||||
SerializedProperty m_MixOutCurveProperty;
|
||||
SerializedProperty m_BlendInCurveModeProperty;
|
||||
SerializedProperty m_BlendOutCurveModeProperty;
|
||||
|
||||
void InitializeProperties()
|
||||
{
|
||||
m_DisplayNameProperty = serializedObject.FindProperty("m_Clip.m_DisplayName");
|
||||
m_BlendInDurationProperty = serializedObject.FindProperty("m_Clip.m_BlendInDuration");
|
||||
m_BlendOutDurationProperty = serializedObject.FindProperty("m_Clip.m_BlendOutDuration");
|
||||
m_EaseInDurationProperty = serializedObject.FindProperty("m_Clip.m_EaseInDuration");
|
||||
m_EaseOutDurationProperty = serializedObject.FindProperty("m_Clip.m_EaseOutDuration");
|
||||
m_ClipInProperty = serializedObject.FindProperty("m_Clip.m_ClipIn");
|
||||
m_TimeScaleProperty = serializedObject.FindProperty("m_Clip.m_TimeScale");
|
||||
m_PostExtrapolationModeProperty = serializedObject.FindProperty("m_Clip.m_PostExtrapolationMode");
|
||||
m_PreExtrapolationModeProperty = serializedObject.FindProperty("m_Clip.m_PreExtrapolationMode");
|
||||
m_PostExtrapolationTimeProperty = serializedObject.FindProperty("m_Clip.m_PostExtrapolationTime");
|
||||
m_PreExtrapolationTimeProperty = serializedObject.FindProperty("m_Clip.m_PreExtrapolationTime");
|
||||
m_MixInCurveProperty = serializedObject.FindProperty("m_Clip.m_MixInCurve");
|
||||
m_MixOutCurveProperty = serializedObject.FindProperty("m_Clip.m_MixOutCurve");
|
||||
m_BlendInCurveModeProperty = serializedObject.FindProperty("m_Clip.m_BlendInCurveMode");
|
||||
m_BlendOutCurveModeProperty = serializedObject.FindProperty("m_Clip.m_BlendOutCurveMode");
|
||||
}
|
||||
|
||||
TimelineAsset m_TimelineAsset;
|
||||
|
||||
List<EditorClipSelection> m_SelectionCache;
|
||||
Editor m_SelectedPlayableAssetsInspector;
|
||||
|
||||
ClipInspectorCurveEditor m_ClipCurveEditor;
|
||||
CurvePresetLibrary m_CurvePresets;
|
||||
|
||||
bool m_IsClipAssetInspectorExpanded = true;
|
||||
GUIContent m_ClipAssetTitle = new GUIContent();
|
||||
string m_MultiselectionHeaderTitle;
|
||||
|
||||
ClipInspectorSelectionInfo m_SelectionInfo;
|
||||
|
||||
// the state of the mixin curve preview
|
||||
PreviewCurveState m_PreviewCurveState;
|
||||
|
||||
const double k_TimeScaleSensitivity = 0.003;
|
||||
|
||||
|
||||
bool hasMultipleSelection
|
||||
{
|
||||
get { return targets.Length > 1; }
|
||||
}
|
||||
|
||||
float currentFrameRate
|
||||
{
|
||||
get { return m_TimelineAsset != null ? (float)m_TimelineAsset.editorSettings.frameRate : (float)TimelineAsset.EditorSettings.kDefaultFrameRate; }
|
||||
}
|
||||
|
||||
bool selectionHasIncompatibleCapabilities
|
||||
{
|
||||
get
|
||||
{
|
||||
return !(m_SelectionInfo.supportsBlending
|
||||
&& m_SelectionInfo.supportsClipIn
|
||||
&& m_SelectionInfo.supportsExtrapolation
|
||||
&& m_SelectionInfo.supportsSpeedMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool RequiresConstantRepaint()
|
||||
{
|
||||
return base.RequiresConstantRepaint() || (m_SelectedPlayableAssetsInspector != null && m_SelectedPlayableAssetsInspector.RequiresConstantRepaint());
|
||||
}
|
||||
|
||||
internal override void OnHeaderTitleGUI(Rect titleRect, string header)
|
||||
{
|
||||
if (hasMultipleSelection)
|
||||
{
|
||||
base.OnHeaderTitleGUI(titleRect, m_MultiselectionHeaderTitle);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_DisplayNameProperty != null)
|
||||
{
|
||||
using (new EditorGUI.DisabledScope(!IsEnabled()))
|
||||
{
|
||||
serializedObject.Update();
|
||||
if (IsLocked())
|
||||
{
|
||||
base.OnHeaderTitleGUI(titleRect, m_DisplayNameProperty.stringValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.DelayedTextField(titleRect, m_DisplayNameProperty, GUIContent.none);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
ApplyModifiedProperties();
|
||||
TimelineWindow.RepaintIfEditingTimelineAsset(m_TimelineAsset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal override Rect DrawHeaderHelpAndSettingsGUI(Rect r)
|
||||
{
|
||||
using (new EditorGUI.DisabledScope(IsLocked()))
|
||||
{
|
||||
var helpSize = EditorStyles.iconButton.CalcSize(EditorGUI.GUIContents.helpIcon);
|
||||
const int kTopMargin = 5;
|
||||
// Show Editor Header Items.
|
||||
return EditorGUIUtility.DrawEditorHeaderItems(new Rect(r.xMax - helpSize.x, r.y + kTopMargin, helpSize.x, helpSize.y), targets);
|
||||
}
|
||||
}
|
||||
|
||||
internal override void OnHeaderIconGUI(Rect iconRect)
|
||||
{
|
||||
using (new EditorGUI.DisabledScope(IsLocked()))
|
||||
{
|
||||
var bgColor = Color.white;
|
||||
if (!EditorGUIUtility.isProSkin)
|
||||
bgColor.a = 0.55f;
|
||||
using (new GUIColorOverride(bgColor))
|
||||
{
|
||||
GUI.Label(iconRect, Styles.TimelineClipBG);
|
||||
}
|
||||
|
||||
var fgColor = Color.white;
|
||||
if (m_SelectionInfo != null && m_SelectionInfo.uniqueParentTracks.Count == 1)
|
||||
fgColor = TrackResourceCache.GetTrackColor(m_SelectionInfo.uniqueParentTracks.First());
|
||||
|
||||
using (new GUIColorOverride(fgColor))
|
||||
{
|
||||
GUI.Label(iconRect, Styles.TimelineClipFG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
Undo.undoRedoPerformed += OnUndoRedoPerformed;
|
||||
|
||||
m_ClipCurveEditor = new ClipInspectorCurveEditor();
|
||||
|
||||
m_SelectionCache = new List<EditorClipSelection>();
|
||||
var selectedClips = new List<TimelineClip>();
|
||||
foreach (var editorClipObject in targets)
|
||||
{
|
||||
var editorClip = editorClipObject as EditorClip;
|
||||
if (editorClip != null)
|
||||
{
|
||||
//all selected clips should have the same TimelineAsset
|
||||
if (!IsTimelineAssetValidForEditorClip(editorClip))
|
||||
{
|
||||
m_SelectionCache.Clear();
|
||||
return;
|
||||
}
|
||||
m_SelectionCache.Add(new EditorClipSelection(editorClip));
|
||||
selectedClips.Add(editorClip.clip);
|
||||
}
|
||||
}
|
||||
|
||||
InitializeProperties();
|
||||
m_SelectionInfo = new ClipInspectorSelectionInfo(selectedClips);
|
||||
|
||||
if (m_SelectionInfo.selectedAssetTypesAreHomogeneous)
|
||||
{
|
||||
var selectedAssets = m_SelectionCache.Select(e => e.clip.asset).ToArray();
|
||||
m_SelectedPlayableAssetsInspector = TimelineInspectorUtility.GetInspectorForObjects(selectedAssets, m_SelectedPlayableAssetsInspector);
|
||||
}
|
||||
|
||||
m_MultiselectionHeaderTitle = m_SelectionCache.Count + " " + Styles.MultipleSelectionTitle.text;
|
||||
m_ClipAssetTitle.text = PlayableAssetSectionTitle();
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
Undo.undoRedoPerformed -= OnUndoRedoPerformed;
|
||||
DestroyImmediate(m_SelectedPlayableAssetsInspector);
|
||||
}
|
||||
|
||||
void DrawClipProperties()
|
||||
{
|
||||
var dirtyEditorClipSelection = m_SelectionCache.Where(s => s.editorClip.GetHashCode() != s.editorClip.lastHash);
|
||||
UnselectCurves();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
//Group Selection
|
||||
if (hasMultipleSelection)
|
||||
{
|
||||
GUILayout.Label(Styles.GroupTimingTitle);
|
||||
EditorGUI.indentLevel++;
|
||||
DrawGroupSelectionProperties();
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
//Draw clip timing
|
||||
GUILayout.Label(Styles.ClipTimingTitle);
|
||||
|
||||
if (hasMultipleSelection && selectionHasIncompatibleCapabilities)
|
||||
{
|
||||
GUILayout.Label(Styles.MultipleClipsSelectedIncompatibleCapabilitiesWarning, EditorStyles.helpBox);
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
if (!m_SelectionInfo.containsAtLeastTwoClipsOnTheSameTrack)
|
||||
{
|
||||
DrawStartTimeField();
|
||||
DrawEndTimeField();
|
||||
}
|
||||
|
||||
if (!hasMultipleSelection)
|
||||
{
|
||||
DrawDurationProperty();
|
||||
}
|
||||
|
||||
if (m_SelectionInfo.supportsBlending)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
DrawBlendingProperties();
|
||||
}
|
||||
|
||||
if (m_SelectionInfo.supportsClipIn)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
DrawClipInProperty();
|
||||
}
|
||||
|
||||
if (!hasMultipleSelection && m_SelectionInfo.supportsSpeedMultiplier)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
DrawTimeScale();
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
bool hasDirtyEditorClips = false;
|
||||
foreach (var editorClipSelection in dirtyEditorClipSelection)
|
||||
{
|
||||
EditorUtility.SetDirty(editorClipSelection.editorClip);
|
||||
hasDirtyEditorClips = true;
|
||||
}
|
||||
|
||||
//Re-evaluate the graph in case of a change in properties
|
||||
bool propertiesHaveChanged = false;
|
||||
if (EditorGUI.EndChangeCheck() || hasDirtyEditorClips)
|
||||
{
|
||||
if (TimelineWindow.IsEditingTimelineAsset(m_TimelineAsset) && TimelineWindow.instance.state != null)
|
||||
{
|
||||
TimelineWindow.instance.state.Evaluate();
|
||||
TimelineWindow.instance.Repaint();
|
||||
}
|
||||
propertiesHaveChanged = true;
|
||||
}
|
||||
|
||||
//Draw Animation Extrapolation
|
||||
if (m_SelectionInfo.supportsExtrapolation)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
GUILayout.Label(Styles.AnimationExtrapolationTitle);
|
||||
EditorGUI.indentLevel++;
|
||||
DrawExtrapolationOptions();
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
//Blend curves
|
||||
if (m_SelectionInfo.supportsBlending)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
GUILayout.Label(Styles.BlendCurvesTitle);
|
||||
EditorGUI.indentLevel++;
|
||||
DrawBlendOptions();
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (CanShowPlayableAssetInspector())
|
||||
{
|
||||
DrawClipAssetGui();
|
||||
}
|
||||
|
||||
if (propertiesHaveChanged)
|
||||
{
|
||||
foreach (var item in m_SelectionCache)
|
||||
item.editorClip.lastHash = item.editorClip.GetHashCode();
|
||||
m_SelectionInfo.Update();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
if (TimelineWindow.instance == null || m_TimelineAsset == null)
|
||||
return;
|
||||
|
||||
using (new EditorGUI.DisabledScope(IsLocked()))
|
||||
{
|
||||
EditMode.HandleModeClutch();
|
||||
|
||||
serializedObject.Update();
|
||||
DrawClipProperties();
|
||||
ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
internal override bool IsEnabled()
|
||||
{
|
||||
if (!TimelineUtility.IsCurrentSequenceValid() || IsCurrentSequenceReadOnly())
|
||||
return false;
|
||||
|
||||
if (m_TimelineAsset != TimelineWindow.instance.state.editSequence.asset)
|
||||
return false;
|
||||
return base.IsEnabled();
|
||||
}
|
||||
|
||||
void DrawTimeScale()
|
||||
{
|
||||
var inputEvent = InputEvent.None;
|
||||
var newEndTime = m_SelectionInfo.end;
|
||||
var oldTimeScale = m_TimeScaleProperty.doubleValue;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var newTimeScale = TimelineInspectorUtility.DelayedAndDraggableDoubleField(Styles.TimeScaleName, oldTimeScale, ref inputEvent, k_TimeScaleSensitivity);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
newTimeScale = newTimeScale.Clamp(TimelineClip.kTimeScaleMin, TimelineClip.kTimeScaleMax);
|
||||
newEndTime = m_SelectionInfo.start + (m_SelectionInfo.duration * oldTimeScale / newTimeScale);
|
||||
}
|
||||
EditMode.inputHandler.ProcessTrim(inputEvent, newEndTime, true);
|
||||
}
|
||||
|
||||
void DrawStartTimeField()
|
||||
{
|
||||
var inputEvent = InputEvent.None;
|
||||
var newStart = TimelineInspectorUtility.TimeFieldUsingTimeReference(Styles.StartName, m_SelectionInfo.multipleClipStart, false, m_SelectionInfo.hasMultipleStartValues, currentFrameRate, 0.0, TimelineClip.kMaxTimeValue, ref inputEvent);
|
||||
|
||||
if (inputEvent.InputHasBegun() && m_SelectionInfo.hasMultipleStartValues)
|
||||
{
|
||||
var items = ItemsUtils.ToItems(m_SelectionInfo.clips);
|
||||
EditMode.inputHandler.SetValueForEdge(items, AttractedEdge.Left, newStart); //if the field has multiple values, set the same start on all selected clips
|
||||
m_SelectionInfo.Update(); //clips could have moved relative to each other, recalculate
|
||||
}
|
||||
|
||||
EditMode.inputHandler.ProcessMove(inputEvent, newStart);
|
||||
}
|
||||
|
||||
void DrawEndTimeField()
|
||||
{
|
||||
var inputEvent = InputEvent.None;
|
||||
var newEndTime = TimelineInspectorUtility.TimeFieldUsingTimeReference(Styles.EndName, m_SelectionInfo.multipleClipEnd, false, m_SelectionInfo.hasMultipleEndValues, currentFrameRate, 0, TimelineClip.kMaxTimeValue, ref inputEvent);
|
||||
|
||||
if (inputEvent.InputHasBegun() && m_SelectionInfo.hasMultipleEndValues)
|
||||
{
|
||||
var items = ItemsUtils.ToItems(m_SelectionInfo.clips);
|
||||
EditMode.inputHandler.SetValueForEdge(items, AttractedEdge.Right, newEndTime); //if the field has multiple value, set the same end on all selected clips
|
||||
m_SelectionInfo.Update(); //clips could have moved relative to each other, recalculate
|
||||
}
|
||||
|
||||
var newStartValue = m_SelectionInfo.multipleClipStart + (newEndTime - m_SelectionInfo.multipleClipEnd);
|
||||
EditMode.inputHandler.ProcessMove(inputEvent, newStartValue);
|
||||
}
|
||||
|
||||
void DrawClipAssetGui()
|
||||
{
|
||||
const float labelIndent = 34;
|
||||
if (m_SelectedPlayableAssetsInspector == null)
|
||||
return;
|
||||
|
||||
var rect = GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.inspectorTitlebar);
|
||||
var oldWidth = EditorGUIUtility.labelWidth;
|
||||
EditorGUIUtility.labelWidth = rect.width - labelIndent;
|
||||
m_IsClipAssetInspectorExpanded = EditorGUI.FoldoutTitlebar(rect, m_ClipAssetTitle, m_IsClipAssetInspectorExpanded, false);
|
||||
EditorGUIUtility.labelWidth = oldWidth;
|
||||
if (m_IsClipAssetInspectorExpanded)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
EditorGUI.indentLevel++;
|
||||
ShowPlayableAssetInspector();
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
void DrawExtrapolationOptions()
|
||||
{
|
||||
// PreExtrapolation
|
||||
var preExtrapolationTime = m_PreExtrapolationTimeProperty.doubleValue;
|
||||
bool hasPreExtrap = preExtrapolationTime > 0.0;
|
||||
if (hasPreExtrap)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PropertyField(m_PreExtrapolationModeProperty, Styles.PreExtrapolateLabel);
|
||||
using (new GUIMixedValueScope(m_PreExtrapolationTimeProperty.hasMultipleDifferentValues))
|
||||
EditorGUILayout.DoubleField(preExtrapolationTime, EditorStyles.label);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
// PostExtrapolation
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_PostExtrapolationModeProperty, Styles.PostExtrapolateLabel);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
ApplyModifiedProperties();
|
||||
//recalculate Extrapolation times to update next clip pre-extrapolation
|
||||
foreach (var track in m_SelectionInfo.uniqueParentTracks)
|
||||
Extrapolation.CalculateExtrapolationTimes(track);
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.WindowNeedsRedraw);
|
||||
}
|
||||
|
||||
using (new GUIMixedValueScope(m_PostExtrapolationTimeProperty.hasMultipleDifferentValues))
|
||||
EditorGUILayout.DoubleField(m_PostExtrapolationTimeProperty.doubleValue, EditorStyles.label);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
DestroyImmediate(m_SelectedPlayableAssetsInspector);
|
||||
}
|
||||
|
||||
public override GUIContent GetPreviewTitle()
|
||||
{
|
||||
return Styles.PreviewTitle;
|
||||
}
|
||||
|
||||
public override bool HasPreviewGUI()
|
||||
{
|
||||
return m_PreviewCurveState != PreviewCurveState.None;
|
||||
}
|
||||
|
||||
public override void OnInteractivePreviewGUI(Rect r, GUIStyle background)
|
||||
{
|
||||
if (m_PreviewCurveState != PreviewCurveState.None && m_ClipCurveEditor != null)
|
||||
{
|
||||
SetCurveEditorTrackHead();
|
||||
m_ClipCurveEditor.OnGUI(r, m_CurvePresets);
|
||||
}
|
||||
}
|
||||
|
||||
void SetCurveEditorTrackHead()
|
||||
{
|
||||
if (TimelineWindow.instance == null || TimelineWindow.instance.state == null)
|
||||
return;
|
||||
|
||||
if (hasMultipleSelection)
|
||||
return;
|
||||
|
||||
var editorClip = target as EditorClip;
|
||||
if (editorClip == null)
|
||||
return;
|
||||
|
||||
var director = TimelineWindow.instance.state.editSequence.director;
|
||||
|
||||
if (director == null)
|
||||
return;
|
||||
|
||||
m_ClipCurveEditor.trackTime = ClipInspectorCurveEditor.kDisableTrackTime;
|
||||
}
|
||||
|
||||
void UnselectCurves()
|
||||
{
|
||||
if (Event.current.type == EventType.MouseDown)
|
||||
{
|
||||
if (m_ClipCurveEditor != null)
|
||||
m_ClipCurveEditor.SetUpdateCurveCallback(null);
|
||||
m_PreviewCurveState = PreviewCurveState.None;
|
||||
}
|
||||
}
|
||||
|
||||
// Callback when the mixin/mixout properties are clicked on
|
||||
void OnMixCurveSelected(string title, CurvePresetLibrary library, SerializedProperty curveSelected, bool easeIn)
|
||||
{
|
||||
m_PreviewCurveState = easeIn ? PreviewCurveState.MixIn : PreviewCurveState.MixOut;
|
||||
|
||||
m_CurvePresets = library;
|
||||
var animationCurve = curveSelected.animationCurveValue;
|
||||
m_ClipCurveEditor.headerString = title;
|
||||
m_ClipCurveEditor.SetCurve(animationCurve);
|
||||
m_ClipCurveEditor.SetSelected(animationCurve);
|
||||
if (easeIn)
|
||||
m_ClipCurveEditor.SetUpdateCurveCallback(MixInCurveUpdated);
|
||||
else
|
||||
m_ClipCurveEditor.SetUpdateCurveCallback(MixOutCurveUpdated);
|
||||
Repaint();
|
||||
}
|
||||
|
||||
// callback when the mix property is updated
|
||||
void MixInCurveUpdated(AnimationCurve curve, EditorCurveBinding binding)
|
||||
{
|
||||
curve.keys = CurveEditUtility.SanitizeCurveKeys(curve.keys, true);
|
||||
m_MixInCurveProperty.animationCurveValue = curve;
|
||||
ApplyModifiedProperties();
|
||||
var editorClip = target as EditorClip;
|
||||
if (editorClip != null)
|
||||
editorClip.lastHash = editorClip.GetHashCode();
|
||||
RefreshCurves();
|
||||
}
|
||||
|
||||
void MixOutCurveUpdated(AnimationCurve curve, EditorCurveBinding binding)
|
||||
{
|
||||
curve.keys = CurveEditUtility.SanitizeCurveKeys(curve.keys, false);
|
||||
m_MixOutCurveProperty.animationCurveValue = curve;
|
||||
ApplyModifiedProperties();
|
||||
var editorClip = target as EditorClip;
|
||||
if (editorClip != null)
|
||||
editorClip.lastHash = editorClip.GetHashCode();
|
||||
RefreshCurves();
|
||||
}
|
||||
|
||||
void RefreshCurves()
|
||||
{
|
||||
AnimationCurvePreviewCache.ClearCache();
|
||||
TimelineWindow.RepaintIfEditingTimelineAsset(m_TimelineAsset);
|
||||
Repaint();
|
||||
}
|
||||
|
||||
void DrawBlendCurve(GUIContent title, SerializedProperty modeProperty, SerializedProperty curveProperty, Action<SerializedProperty> onCurveClick)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PropertyField(modeProperty, title);
|
||||
if (hasMultipleSelection)
|
||||
{
|
||||
GUILayout.FlexibleSpace();
|
||||
}
|
||||
else
|
||||
{
|
||||
using (new EditorGUI.DisabledScope(modeProperty.intValue != (int)TimelineClip.BlendCurveMode.Manual))
|
||||
{
|
||||
ClipInspectorCurveEditor.CurveField(GUIContent.none, curveProperty, onCurveClick);
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
void ShowPlayableAssetInspector()
|
||||
{
|
||||
if (!m_SelectionInfo.selectedAssetTypesAreHomogeneous)
|
||||
return;
|
||||
|
||||
if (m_SelectedPlayableAssetsInspector != null)
|
||||
{
|
||||
if (Event.current.type == EventType.Repaint || Event.current.type == EventType.Layout)
|
||||
{
|
||||
foreach (var selectedItem in m_SelectionCache)
|
||||
CurvesOwnerInspectorHelper.PreparePlayableAsset(selectedItem);
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
using (new EditorGUI.DisabledScope(IsLocked()))
|
||||
{
|
||||
m_SelectedPlayableAssetsInspector.OnInspectorGUI();
|
||||
}
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
MarkClipsDirty();
|
||||
if (TimelineWindow.IsEditingTimelineAsset(m_TimelineAsset) && TimelineWindow.instance.state != null)
|
||||
{
|
||||
var inspectorChangeHandler = m_SelectedPlayableAssetsInspector as IInspectorChangeHandler;
|
||||
if (inspectorChangeHandler != null)
|
||||
inspectorChangeHandler.OnPlayableAssetChangedInInspector();
|
||||
else
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyModifiedProperties()
|
||||
{
|
||||
// case 926861 - we need to force the track to be dirty since modifying the clip does not
|
||||
// automatically mark the track asset as dirty
|
||||
if (serializedObject.ApplyModifiedProperties())
|
||||
{
|
||||
foreach (var obj in serializedObject.targetObjects)
|
||||
{
|
||||
var editorClip = obj as EditorClip;
|
||||
if (editorClip != null && editorClip.clip != null && editorClip.clip.GetParentTrack() != null)
|
||||
{
|
||||
editorClip.clip.MarkDirty();
|
||||
EditorUtility.SetDirty(editorClip.clip.GetParentTrack());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MarkClipsDirty()
|
||||
{
|
||||
foreach (var obj in targets)
|
||||
{
|
||||
var editorClip = obj as EditorClip;
|
||||
if (editorClip != null && editorClip.clip != null)
|
||||
{
|
||||
editorClip.clip.MarkDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string PlayableAssetSectionTitle()
|
||||
{
|
||||
var firstSelectedClipAsset = m_SelectionCache.Any() ? m_SelectionCache.First().clip.asset : null;
|
||||
return firstSelectedClipAsset != null
|
||||
? ObjectNames.NicifyVariableName(firstSelectedClipAsset.GetType().Name)
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
bool IsTimelineAssetValidForEditorClip(EditorClip editorClip)
|
||||
{
|
||||
var trackAsset = editorClip.clip.GetParentTrack();
|
||||
if (trackAsset == null)
|
||||
return false;
|
||||
|
||||
var clipTimelineAsset = trackAsset.timelineAsset;
|
||||
if (m_TimelineAsset == null)
|
||||
m_TimelineAsset = clipTimelineAsset;
|
||||
else if (clipTimelineAsset != m_TimelineAsset)
|
||||
{
|
||||
m_TimelineAsset = null;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CanShowPlayableAssetInspector()
|
||||
{
|
||||
if (hasMultipleSelection)
|
||||
return m_SelectedPlayableAssetsInspector != null &&
|
||||
m_SelectedPlayableAssetsInspector.canEditMultipleObjects &&
|
||||
m_SelectionInfo.selectedAssetTypesAreHomogeneous;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
void DrawDurationProperty()
|
||||
{
|
||||
var minDuration = 1.0 / 30.0;
|
||||
if (currentFrameRate > float.Epsilon)
|
||||
{
|
||||
minDuration = 1.0 / currentFrameRate;
|
||||
}
|
||||
|
||||
var inputEvent = InputEvent.None;
|
||||
var newDuration = TimelineInspectorUtility.DurationFieldUsingTimeReference(
|
||||
Styles.DurationName, m_SelectionInfo.start, m_SelectionInfo.end, false, m_SelectionInfo.hasMultipleDurationValues, currentFrameRate, minDuration, TimelineClip.kMaxTimeValue, ref inputEvent);
|
||||
EditMode.inputHandler.ProcessTrim(inputEvent, m_SelectionInfo.start + newDuration, false);
|
||||
}
|
||||
|
||||
void DrawBlendingProperties()
|
||||
{
|
||||
const double mixMinimum = 0.0;
|
||||
var inputEvent = InputEvent.None;
|
||||
double blendMax;
|
||||
GUIContent label;
|
||||
|
||||
var useBlendIn = m_SelectionInfo.hasBlendIn;
|
||||
SerializedProperty currentMixInProperty;
|
||||
if (!useBlendIn)
|
||||
{
|
||||
currentMixInProperty = m_EaseInDurationProperty;
|
||||
var blendOutStart = m_SelectionInfo.duration - m_BlendOutDurationProperty.doubleValue;
|
||||
blendMax = Math.Min(Math.Max(mixMinimum, m_SelectionInfo.maxMixIn), blendOutStart);
|
||||
label = Styles.EaseInDurationName;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentMixInProperty = m_BlendInDurationProperty;
|
||||
blendMax = TimelineClip.kMaxTimeValue;
|
||||
label = Styles.BlendInDurationName;
|
||||
}
|
||||
if (blendMax > TimeUtility.kTimeEpsilon)
|
||||
TimelineInspectorUtility.TimeField(currentMixInProperty, label, useBlendIn, currentFrameRate, mixMinimum,
|
||||
blendMax, ref inputEvent);
|
||||
|
||||
|
||||
var useBlendOut = m_SelectionInfo.hasBlendOut;
|
||||
SerializedProperty currentMixOutProperty;
|
||||
if (!useBlendOut)
|
||||
{
|
||||
currentMixOutProperty = m_EaseOutDurationProperty;
|
||||
var blendInEnd = m_SelectionInfo.duration - m_BlendInDurationProperty.doubleValue;
|
||||
blendMax = Math.Min(Math.Max(mixMinimum, m_SelectionInfo.maxMixOut), blendInEnd);
|
||||
label = Styles.EaseOutDurationName;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentMixOutProperty = m_BlendOutDurationProperty;
|
||||
blendMax = TimelineClip.kMaxTimeValue;
|
||||
label = Styles.BlendOutDurationName;
|
||||
}
|
||||
if (blendMax > TimeUtility.kTimeEpsilon)
|
||||
TimelineInspectorUtility.TimeField(currentMixOutProperty, label, useBlendOut, currentFrameRate,
|
||||
mixMinimum, blendMax, ref inputEvent);
|
||||
}
|
||||
|
||||
void DrawClipInProperty()
|
||||
{
|
||||
var action = InputEvent.None;
|
||||
TimelineInspectorUtility.TimeField(m_ClipInProperty, Styles.ClipInName, false, currentFrameRate, 0, TimelineClip.kMaxTimeValue, ref action);
|
||||
}
|
||||
|
||||
void DrawBlendOptions()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
DrawBlendCurve(Styles.BlendInCurveName, m_BlendInCurveModeProperty, m_MixInCurveProperty, x => OnMixCurveSelected("Blend In", BuiltInPresets.blendInPresets, x, true));
|
||||
DrawBlendCurve(Styles.BlendOutCurveName, m_BlendOutCurveModeProperty, m_MixOutCurveProperty, x => OnMixCurveSelected("Blend Out", BuiltInPresets.blendOutPresets, x, false));
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
TimelineWindow.RepaintIfEditingTimelineAsset(m_TimelineAsset);
|
||||
}
|
||||
|
||||
void DrawGroupSelectionProperties()
|
||||
{
|
||||
var inputEvent = InputEvent.None;
|
||||
var newStartTime = TimelineInspectorUtility.TimeField(Styles.MultipleClipStartName, m_SelectionInfo.multipleClipStart, false, false, currentFrameRate, 0, TimelineClip.kMaxTimeValue, ref inputEvent);
|
||||
EditMode.inputHandler.ProcessMove(inputEvent, newStartTime);
|
||||
|
||||
inputEvent = InputEvent.None;
|
||||
var newEndTime = TimelineInspectorUtility.TimeField(Styles.MultipleClipEndName, m_SelectionInfo.multipleClipEnd, false, false, currentFrameRate, 0, TimelineClip.kMaxTimeValue, ref inputEvent);
|
||||
var newStartValue = newStartTime + (newEndTime - m_SelectionInfo.multipleClipEnd);
|
||||
EditMode.inputHandler.ProcessMove(inputEvent, newStartValue);
|
||||
}
|
||||
|
||||
bool IsLocked()
|
||||
{
|
||||
if (!TimelineUtility.IsCurrentSequenceValid() || IsCurrentSequenceReadOnly())
|
||||
return true;
|
||||
|
||||
return targets.OfType<EditorClip>().Any(t => t.clip.GetParentTrack() != null && t.clip.GetParentTrack().lockedInHierarchy);
|
||||
}
|
||||
|
||||
static bool IsCurrentSequenceReadOnly()
|
||||
{
|
||||
return TimelineWindow.instance.state.editSequence.isReadOnly;
|
||||
}
|
||||
|
||||
void OnUndoRedoPerformed()
|
||||
{
|
||||
if (m_PreviewCurveState == PreviewCurveState.None)
|
||||
return;
|
||||
|
||||
// if an undo is performed the curves need to be updated in the curve editor, as the reference to them is no longer valid
|
||||
// case 978673
|
||||
if (m_ClipCurveEditor != null)
|
||||
{
|
||||
serializedObject.Update();
|
||||
m_ClipCurveEditor.SetCurve(m_PreviewCurveState == PreviewCurveState.MixIn ? m_MixInCurveProperty.animationCurveValue : m_MixOutCurveProperty.animationCurveValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dff73c4907c95264c8fc095a81f9d51e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,353 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
class ClipInspectorCurveEditor
|
||||
{
|
||||
CurveEditor m_CurveEditor;
|
||||
CurveWrapper[] m_CurveWrappers;
|
||||
|
||||
const float k_HeaderHeight = 30;
|
||||
const float k_PresetHeight = 30;
|
||||
|
||||
Action<AnimationCurve, EditorCurveBinding> m_CurveUpdatedCallback;
|
||||
GUIContent m_TextContent = new GUIContent();
|
||||
|
||||
GUIStyle m_LabelStyle;
|
||||
GUIStyle m_LegendStyle;
|
||||
|
||||
// Track time. controls the position of the track head
|
||||
public static readonly double kDisableTrackTime = double.NaN;
|
||||
double m_trackTime = kDisableTrackTime;
|
||||
public double trackTime { get { return m_trackTime; } set { m_trackTime = value; } }
|
||||
|
||||
public string headerString { get; set; }
|
||||
|
||||
public ClipInspectorCurveEditor()
|
||||
{
|
||||
var curveEditorSettings = new CurveEditorSettings
|
||||
{
|
||||
allowDeleteLastKeyInCurve = false,
|
||||
allowDraggingCurvesAndRegions = true,
|
||||
hTickLabelOffset = 0.1f,
|
||||
showAxisLabels = true,
|
||||
useFocusColors = false,
|
||||
wrapColor = new EditorGUIUtility.SkinnedColor(Color.black),
|
||||
hSlider = false,
|
||||
hRangeMin = 0.0f,
|
||||
vRangeMin = 0.0F,
|
||||
vRangeMax = 1.0f,
|
||||
hRangeMax = 1.0F,
|
||||
vSlider = false,
|
||||
hRangeLocked = false,
|
||||
vRangeLocked = false,
|
||||
undoRedoSelection = true,
|
||||
|
||||
|
||||
hTickStyle = new TickStyle
|
||||
{
|
||||
tickColor = new EditorGUIUtility.SkinnedColor(new Color(0.0f, 0.0f, 0.0f, 0.2f)),
|
||||
distLabel = 30,
|
||||
stubs = false,
|
||||
centerLabel = true
|
||||
},
|
||||
|
||||
vTickStyle = new TickStyle
|
||||
{
|
||||
tickColor = new EditorGUIUtility.SkinnedColor(new Color(1.0f, 0.0f, 0.0f, 0.2f)),
|
||||
distLabel = 20,
|
||||
stubs = false,
|
||||
centerLabel = true
|
||||
}
|
||||
};
|
||||
|
||||
m_CurveEditor = new CurveEditor(new Rect(0, 0, 1000, 100), new CurveWrapper[0], true)
|
||||
{
|
||||
settings = curveEditorSettings,
|
||||
ignoreScrollWheelUntilClicked = true
|
||||
};
|
||||
}
|
||||
|
||||
internal bool InitStyles()
|
||||
{
|
||||
if (EditorStyles.s_Current == null)
|
||||
return false;
|
||||
|
||||
if (m_LabelStyle == null)
|
||||
{
|
||||
m_LabelStyle = new GUIStyle(EditorStyles.whiteLargeLabel);
|
||||
m_LegendStyle = new GUIStyle(EditorStyles.miniBoldLabel);
|
||||
|
||||
m_LabelStyle.alignment = TextAnchor.MiddleCenter;
|
||||
m_LegendStyle.alignment = TextAnchor.MiddleCenter;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void OnGUI(Rect clientRect, CurvePresetLibrary presets)
|
||||
{
|
||||
const float presetPad = 30.0f;
|
||||
|
||||
if (!InitStyles())
|
||||
return;
|
||||
|
||||
if (m_CurveWrappers == null || m_CurveWrappers.Length == 0)
|
||||
return;
|
||||
|
||||
// regions
|
||||
var headerRect = new Rect(clientRect.x, clientRect.y, clientRect.width, k_HeaderHeight);
|
||||
var curveRect = new Rect(clientRect.x, clientRect.y + headerRect.height, clientRect.width, clientRect.height - k_HeaderHeight - k_PresetHeight);
|
||||
var presetRect = new Rect(clientRect.x + presetPad, clientRect.y + curveRect.height + k_HeaderHeight, clientRect.width - presetPad, k_PresetHeight);
|
||||
|
||||
GUI.Box(headerRect, headerString, m_LabelStyle);
|
||||
|
||||
//Case 1201474 : Force to update only when Repaint event is called as the new rect provided on other event create a wrong curve editor computation.
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
m_CurveEditor.rect = curveRect;
|
||||
m_CurveEditor.shownAreaInsideMargins = new Rect(0, 0, 1, 1);
|
||||
}
|
||||
m_CurveEditor.animationCurves = m_CurveWrappers;
|
||||
UpdateSelectionColors();
|
||||
|
||||
DrawTrackHead(curveRect);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
m_CurveEditor.OnGUI();
|
||||
DrawPresets(presetRect, presets);
|
||||
|
||||
bool hasChanged = EditorGUI.EndChangeCheck();
|
||||
|
||||
if (presets == null)
|
||||
DrawLegend(presetRect);
|
||||
|
||||
if (hasChanged)
|
||||
ProcessUpdates();
|
||||
|
||||
ConsumeMouseEvents(clientRect);
|
||||
}
|
||||
|
||||
static void ConsumeMouseEvents(Rect rect)
|
||||
{
|
||||
var isMouseEvent = Event.current.type == EventType.MouseUp || Event.current.type == EventType.MouseDown;
|
||||
if (isMouseEvent && rect.Contains(Event.current.mousePosition))
|
||||
Event.current.Use();
|
||||
}
|
||||
|
||||
void DrawPresets(Rect position, PresetLibrary curveLibrary)
|
||||
{
|
||||
if (curveLibrary == null || curveLibrary.Count() == 0)
|
||||
return;
|
||||
|
||||
const int maxNumPresets = 9;
|
||||
int numPresets = curveLibrary.Count();
|
||||
int showNumPresets = Mathf.Min(numPresets, maxNumPresets);
|
||||
|
||||
const float swatchWidth = 30;
|
||||
const float swatchHeight = 15;
|
||||
const float spaceBetweenSwatches = 10;
|
||||
float presetButtonsWidth = showNumPresets * swatchWidth + (showNumPresets - 1) * spaceBetweenSwatches;
|
||||
float flexWidth = (position.width - presetButtonsWidth) * 0.5f;
|
||||
|
||||
// Preset swatch area
|
||||
float curY = (position.height - swatchHeight) * 0.5f;
|
||||
float curX = 3.0f;
|
||||
if (flexWidth > 0)
|
||||
curX = flexWidth;
|
||||
|
||||
GUI.BeginGroup(position);
|
||||
|
||||
for (int i = 0; i < showNumPresets; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
curX += spaceBetweenSwatches;
|
||||
|
||||
var swatchRect = new Rect(curX, curY, swatchWidth, swatchHeight);
|
||||
m_TextContent.tooltip = curveLibrary.GetName(i);
|
||||
if (GUI.Button(swatchRect, m_TextContent, GUIStyle.none))
|
||||
{
|
||||
// if there is only 1, no need to specify
|
||||
IEnumerable<CurveWrapper> wrappers = m_CurveWrappers;
|
||||
if (m_CurveWrappers.Length > 1)
|
||||
wrappers = m_CurveWrappers.Where(x => x.selected == CurveWrapper.SelectionMode.Selected);
|
||||
|
||||
foreach (var wrapper in wrappers)
|
||||
{
|
||||
var presetCurve = (AnimationCurve)curveLibrary.GetPreset(i);
|
||||
wrapper.curve.keys = (Keyframe[])presetCurve.keys.Clone();
|
||||
wrapper.changed = true;
|
||||
}
|
||||
|
||||
// case 1259902 - flushes internal selection caches preventing index out of range exceptions
|
||||
m_CurveEditor.SelectNone();
|
||||
foreach (var wrapper in wrappers)
|
||||
wrapper.selected = CurveWrapper.SelectionMode.Selected;
|
||||
}
|
||||
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
curveLibrary.Draw(swatchRect, i);
|
||||
|
||||
curX += swatchWidth;
|
||||
}
|
||||
|
||||
GUI.EndGroup();
|
||||
}
|
||||
|
||||
// draw a line representing where in the current clip we are
|
||||
void DrawTrackHead(Rect clientRect)
|
||||
{
|
||||
DirectorStyles styles = TimelineWindow.styles;
|
||||
if (styles == null)
|
||||
return;
|
||||
|
||||
if (!double.IsNaN(m_trackTime))
|
||||
{
|
||||
float x = m_CurveEditor.TimeToPixel((float)m_trackTime, clientRect);
|
||||
x = Mathf.Clamp(x, clientRect.xMin, clientRect.xMax);
|
||||
var p1 = new Vector2(x, clientRect.yMin);
|
||||
var p2 = new Vector2(x, clientRect.yMax);
|
||||
Graphics.DrawLine(p1, p2, DirectorStyles.Instance.customSkin.colorPlayhead);
|
||||
}
|
||||
}
|
||||
|
||||
// Draws a legend for the displayed curves
|
||||
void DrawLegend(Rect r)
|
||||
{
|
||||
if (m_CurveWrappers == null || m_CurveWrappers.Length == 0)
|
||||
return;
|
||||
|
||||
Color c = GUI.color;
|
||||
float boxWidth = r.width / m_CurveWrappers.Length;
|
||||
for (int i = 0; i < m_CurveWrappers.Length; i++)
|
||||
{
|
||||
CurveWrapper cw = m_CurveWrappers[i];
|
||||
if (cw != null)
|
||||
{
|
||||
var pos = new Rect(r.x + i * boxWidth, r.y, boxWidth, r.height);
|
||||
var textColor = cw.color;
|
||||
textColor.a = 1;
|
||||
GUI.color = textColor;
|
||||
string name = LabelName(cw.binding.propertyName);
|
||||
EditorGUI.LabelField(pos, name, m_LegendStyle);
|
||||
}
|
||||
}
|
||||
GUI.color = c;
|
||||
}
|
||||
|
||||
// Helper for making label name appropriately small
|
||||
static char[] s_LabelMarkers = { '_' };
|
||||
|
||||
static string LabelName(string propertyName)
|
||||
{
|
||||
propertyName = AnimationWindowUtility.GetPropertyDisplayName(propertyName);
|
||||
int index = propertyName.LastIndexOfAny(s_LabelMarkers);
|
||||
if (index >= 0)
|
||||
propertyName = propertyName.Substring(index);
|
||||
return propertyName;
|
||||
}
|
||||
|
||||
public void SetCurve(AnimationCurve curve)
|
||||
{
|
||||
if (m_CurveWrappers == null || m_CurveWrappers.Length != 1)
|
||||
{
|
||||
m_CurveWrappers = new CurveWrapper[1];
|
||||
var cw = new CurveWrapper
|
||||
{
|
||||
renderer = new NormalCurveRenderer(curve),
|
||||
readOnly = false,
|
||||
color = EditorGUI.kCurveColor,
|
||||
id = 0xFEED,
|
||||
hidden = false,
|
||||
regionId = -1
|
||||
};
|
||||
|
||||
cw.renderer.SetWrap(WrapMode.Clamp, WrapMode.Clamp);
|
||||
cw.renderer.SetCustomRange(0, 1);
|
||||
m_CurveWrappers[0] = cw;
|
||||
|
||||
UpdateSelectionColors();
|
||||
m_CurveEditor.animationCurves = m_CurveWrappers;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_CurveWrappers[0].renderer = new NormalCurveRenderer(curve);
|
||||
}
|
||||
}
|
||||
|
||||
internal void SetUpdateCurveCallback(Action<AnimationCurve, EditorCurveBinding> callback)
|
||||
{
|
||||
m_CurveUpdatedCallback = callback;
|
||||
}
|
||||
|
||||
void ProcessUpdates()
|
||||
{
|
||||
foreach (var cw in m_CurveWrappers)
|
||||
{
|
||||
if (cw.changed)
|
||||
{
|
||||
cw.changed = false;
|
||||
|
||||
if (m_CurveUpdatedCallback != null)
|
||||
m_CurveUpdatedCallback(cw.curve, cw.binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSelected(AnimationCurve curve)
|
||||
{
|
||||
m_CurveEditor.SelectNone();
|
||||
if (m_CurveWrappers != null && m_CurveWrappers.Length > 0)
|
||||
{
|
||||
if (m_CurveWrappers[0].renderer.GetCurve() == curve)
|
||||
{
|
||||
m_CurveWrappers[0].selected = CurveWrapper.SelectionMode.Selected;
|
||||
m_CurveEditor.AddSelection(new CurveSelection(m_CurveWrappers[0].id, 0));
|
||||
}
|
||||
}
|
||||
UpdateSelectionColors();
|
||||
}
|
||||
|
||||
void UpdateSelectionColors()
|
||||
{
|
||||
if (m_CurveWrappers == null)
|
||||
return;
|
||||
|
||||
// manually manage selection colors
|
||||
foreach (var cw in m_CurveWrappers)
|
||||
{
|
||||
Color c = cw.color;
|
||||
if (cw.readOnly)
|
||||
c.a = 0.75f;
|
||||
else if (cw.selected != CurveWrapper.SelectionMode.None)
|
||||
c.a = 1.0f;
|
||||
else
|
||||
c.a = 0.5f;
|
||||
cw.color = c;
|
||||
}
|
||||
}
|
||||
|
||||
public static void CurveField(GUIContent title, SerializedProperty property, Action<SerializedProperty> onClick)
|
||||
{
|
||||
Rect controlRect = EditorGUILayout.GetControlRect(GUILayout.MinWidth(20));
|
||||
EditorGUI.BeginProperty(controlRect, title, property);
|
||||
DrawCurve(controlRect, property, onClick, EditorGUI.kCurveColor, EditorGUI.kCurveBGColor);
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
static Rect DrawCurve(Rect controlRect, SerializedProperty property, Action<SerializedProperty> onClick, Color fgColor, Color bgColor)
|
||||
{
|
||||
if (GUI.Button(controlRect, GUIContent.none))
|
||||
{
|
||||
if (onClick != null)
|
||||
onClick(property);
|
||||
}
|
||||
EditorGUIUtility.DrawCurveSwatch(controlRect, null, property, fgColor, bgColor);
|
||||
return controlRect;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3d14fa8f6934e14d92e37279e40e89b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
class ClipInspectorSelectionInfo
|
||||
{
|
||||
public double start, end, duration;
|
||||
public double multipleClipStart, multipleClipEnd;
|
||||
public double smallestDuration;
|
||||
|
||||
public bool hasMultipleStartValues, hasMultipleEndValues, hasMultipleDurationValues;
|
||||
public bool supportsExtrapolation, supportsClipIn, supportsSpeedMultiplier, supportsBlending;
|
||||
public bool hasBlendIn, hasBlendOut;
|
||||
public double maxMixIn, maxMixOut;
|
||||
public bool selectedAssetTypesAreHomogeneous;
|
||||
public bool containsAtLeastTwoClipsOnTheSameTrack;
|
||||
|
||||
public HashSet<TrackAsset> uniqueParentTracks = new HashSet<TrackAsset>();
|
||||
public ICollection<TimelineClip> clips { get; private set; }
|
||||
|
||||
public ClipInspectorSelectionInfo(ICollection<TimelineClip> selectedClips)
|
||||
{
|
||||
supportsBlending = supportsClipIn = supportsExtrapolation = supportsSpeedMultiplier = true;
|
||||
hasBlendIn = hasBlendOut = true;
|
||||
maxMixIn = maxMixOut = TimelineClip.kMaxTimeValue;
|
||||
selectedAssetTypesAreHomogeneous = true;
|
||||
smallestDuration = TimelineClip.kMaxTimeValue;
|
||||
start = end = duration = 0;
|
||||
multipleClipStart = multipleClipEnd = 0;
|
||||
hasMultipleStartValues = hasMultipleEndValues = hasMultipleDurationValues = false;
|
||||
containsAtLeastTwoClipsOnTheSameTrack = false;
|
||||
|
||||
clips = selectedClips;
|
||||
Build();
|
||||
}
|
||||
|
||||
void Build()
|
||||
{
|
||||
if (!clips.Any()) return;
|
||||
|
||||
var firstSelectedClip = clips.First();
|
||||
if (firstSelectedClip == null) return;
|
||||
|
||||
var firstSelectedClipAssetType = firstSelectedClip.asset != null ? firstSelectedClip.asset.GetType() : null;
|
||||
|
||||
smallestDuration = TimelineClip.kMaxTimeValue;
|
||||
InitSelectionBounds(firstSelectedClip);
|
||||
InitMultipleClipBounds(firstSelectedClip);
|
||||
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
if (clip == null) continue;
|
||||
|
||||
uniqueParentTracks.Add(clip.GetParentTrack());
|
||||
selectedAssetTypesAreHomogeneous &= clip.asset.GetType() == firstSelectedClipAssetType;
|
||||
|
||||
UpdateClipCaps(clip);
|
||||
UpdateBlends(clip);
|
||||
UpdateMixMaximums(clip);
|
||||
UpdateSmallestDuration(clip);
|
||||
UpdateMultipleValues(clip);
|
||||
UpdateMultipleValues(clip);
|
||||
}
|
||||
containsAtLeastTwoClipsOnTheSameTrack = uniqueParentTracks.Count != clips.Count;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
var firstSelectedClip = clips.First();
|
||||
if (firstSelectedClip == null) return;
|
||||
|
||||
hasBlendIn = hasBlendOut = true;
|
||||
maxMixIn = maxMixOut = TimelineClip.kMaxTimeValue;
|
||||
hasMultipleStartValues = hasMultipleDurationValues = hasMultipleEndValues = false;
|
||||
smallestDuration = TimelineClip.kMaxTimeValue;
|
||||
InitSelectionBounds(firstSelectedClip);
|
||||
InitMultipleClipBounds(firstSelectedClip);
|
||||
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
if (clip == null) continue;
|
||||
|
||||
UpdateBlends(clip);
|
||||
UpdateMixMaximums(clip);
|
||||
UpdateSmallestDuration(clip);
|
||||
UpdateMultipleValues(clip);
|
||||
}
|
||||
}
|
||||
|
||||
void InitSelectionBounds(TimelineClip clip)
|
||||
{
|
||||
start = clip.start;
|
||||
duration = clip.duration;
|
||||
end = clip.start + clip.duration;
|
||||
}
|
||||
|
||||
void InitMultipleClipBounds(TimelineClip firstSelectedClip)
|
||||
{
|
||||
multipleClipStart = firstSelectedClip.start;
|
||||
multipleClipEnd = end;
|
||||
}
|
||||
|
||||
void UpdateSmallestDuration(TimelineClip clip)
|
||||
{
|
||||
smallestDuration = Math.Min(smallestDuration, clip.duration);
|
||||
}
|
||||
|
||||
void UpdateClipCaps(TimelineClip clip)
|
||||
{
|
||||
supportsBlending &= clip.SupportsBlending();
|
||||
supportsClipIn &= clip.SupportsClipIn();
|
||||
supportsExtrapolation &= clip.SupportsExtrapolation();
|
||||
supportsSpeedMultiplier &= clip.SupportsSpeedMultiplier();
|
||||
}
|
||||
|
||||
void UpdateMultipleValues(TimelineClip clip)
|
||||
{
|
||||
hasMultipleStartValues |= !Mathf.Approximately((float)clip.start, (float)start);
|
||||
hasMultipleDurationValues |= !Mathf.Approximately((float)clip.duration, (float)duration);
|
||||
var clipEnd = clip.start + clip.duration;
|
||||
hasMultipleEndValues |= !Mathf.Approximately((float)clipEnd, (float)end);
|
||||
|
||||
multipleClipStart = Math.Min(multipleClipStart, clip.start);
|
||||
multipleClipEnd = Math.Max(multipleClipEnd, clip.end);
|
||||
}
|
||||
|
||||
void UpdateBlends(TimelineClip clip)
|
||||
{
|
||||
hasBlendIn &= clip.hasBlendIn;
|
||||
hasBlendOut &= clip.hasBlendOut;
|
||||
}
|
||||
|
||||
void UpdateMixMaximums(TimelineClip clip)
|
||||
{
|
||||
var clipMaxMixIn = Math.Max(0.0, clip.duration - clip.mixOutDuration);
|
||||
var clipMaxMixOut = Math.Max(0.0, clip.duration - clip.mixInDuration);
|
||||
|
||||
maxMixIn = Math.Min(maxMixIn, clipMaxMixIn);
|
||||
maxMixOut = Math.Min(maxMixOut, clipMaxMixOut);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 57a39be2178cca94ab21e15c082e3ab6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86cacab070a0a46e99aedb596a32c4fe
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class CurvesOwnerInspectorHelper
|
||||
{
|
||||
// Because what is animated is not the asset, but the instanced playable,
|
||||
// we apply the animation clip here to preview what is being shown
|
||||
// This could be improved doing something more inline with animation mode,
|
||||
// and reverting values that aren't be recorded later to avoid dirtying the asset
|
||||
public static void PreparePlayableAsset(ICurvesOwnerInspectorWrapper wrapper)
|
||||
{
|
||||
if (wrapper.serializedPlayableAsset == null)
|
||||
return;
|
||||
|
||||
var curvesOwner = wrapper.curvesOwner;
|
||||
if (curvesOwner == null || curvesOwner.curves == null)
|
||||
return;
|
||||
|
||||
var timelineWindow = TimelineWindow.instance;
|
||||
if (timelineWindow == null || timelineWindow.state == null)
|
||||
return;
|
||||
|
||||
// requires preview mode. reset the eval time so previous value is correct value is displayed while toggling
|
||||
if (!timelineWindow.state.previewMode)
|
||||
{
|
||||
wrapper.lastEvalTime = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
var time = wrapper.ToLocalTime(timelineWindow.state.editSequence.time);
|
||||
|
||||
// detect if the time has changed, or if the curves have changed
|
||||
if (Math.Abs(wrapper.lastEvalTime - time) < TimeUtility.kTimeEpsilon)
|
||||
{
|
||||
int curveVersion = AnimationClipCurveCache.Instance.GetCurveInfo(curvesOwner.curves).version;
|
||||
if (curveVersion == wrapper.lastCurveVersion)
|
||||
return;
|
||||
|
||||
wrapper.lastCurveVersion = curveVersion;
|
||||
}
|
||||
|
||||
wrapper.lastEvalTime = time;
|
||||
|
||||
var clipInfo = AnimationClipCurveCache.Instance.GetCurveInfo(curvesOwner.curves);
|
||||
int count = clipInfo.bindings.Length;
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
wrapper.serializedPlayableAsset.Update();
|
||||
|
||||
var prop = wrapper.serializedPlayableAsset.GetIterator();
|
||||
while (prop.NextVisible(true))
|
||||
{
|
||||
if (curvesOwner.IsParameterAnimated(prop.propertyPath))
|
||||
{
|
||||
var curve = curvesOwner.GetAnimatedParameter(prop.propertyPath);
|
||||
switch (prop.propertyType)
|
||||
{
|
||||
case SerializedPropertyType.Boolean:
|
||||
prop.boolValue = curve.Evaluate((float)time) > 0;
|
||||
break;
|
||||
case SerializedPropertyType.Float:
|
||||
prop.floatValue = curve.Evaluate((float)time);
|
||||
break;
|
||||
case SerializedPropertyType.Integer:
|
||||
prop.intValue = Mathf.FloorToInt(curve.Evaluate((float)time));
|
||||
break;
|
||||
case SerializedPropertyType.Color:
|
||||
SetAnimatedValue(curvesOwner, prop, "r", time);
|
||||
SetAnimatedValue(curvesOwner, prop, "g", time);
|
||||
SetAnimatedValue(curvesOwner, prop, "b", time);
|
||||
SetAnimatedValue(curvesOwner, prop, "a", time);
|
||||
break;
|
||||
case SerializedPropertyType.Quaternion:
|
||||
case SerializedPropertyType.Vector4:
|
||||
SetAnimatedValue(curvesOwner, prop, "w", time);
|
||||
goto case SerializedPropertyType.Vector3;
|
||||
case SerializedPropertyType.Vector3:
|
||||
SetAnimatedValue(curvesOwner, prop, "z", time);
|
||||
goto case SerializedPropertyType.Vector2;
|
||||
case SerializedPropertyType.Vector2:
|
||||
SetAnimatedValue(curvesOwner, prop, "x", time);
|
||||
SetAnimatedValue(curvesOwner, prop, "y", time);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrapper.serializedPlayableAsset.ApplyModifiedPropertiesWithoutUndo();
|
||||
}
|
||||
|
||||
static void SetAnimatedValue(ICurvesOwner clip, SerializedProperty property, string path, double localTime)
|
||||
{
|
||||
var prop = property.FindPropertyRelative(path);
|
||||
if (prop != null)
|
||||
{
|
||||
var curve = clip.GetAnimatedParameter(prop.propertyPath);
|
||||
if (curve != null)
|
||||
prop.floatValue = curve.Evaluate((float)localTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a371bcbba2084dd0a8ebc6826aa8794
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,14 @@
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
interface ICurvesOwnerInspectorWrapper
|
||||
{
|
||||
ICurvesOwner curvesOwner { get; }
|
||||
SerializedObject serializedPlayableAsset { get; }
|
||||
int lastCurveVersion { get; set; }
|
||||
double lastEvalTime { get; set; }
|
||||
|
||||
double ToLocalTime(double time);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 926a61ff0dec44a5aab649acb411e9ad
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,27 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Internally used Inspector
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(DirectorNamedColor))]
|
||||
class DirectorNamedColorInspector : Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
if (GUILayout.Button("ToTextAsset"))
|
||||
{
|
||||
DirectorStyles.Instance.ExportSkinToFile();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Reload From File"))
|
||||
{
|
||||
DirectorStyles.Instance.ReloadSkin();
|
||||
UnityEditor.Selection.activeObject = DirectorStyles.Instance.customSkin;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c8ceb9efacb4974bb3b7e2a87137b07
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,25 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[ExcludeFromPreset]
|
||||
[TimelineHelpURL(typeof(TimelineClip))]
|
||||
class EditorClip : ScriptableObject
|
||||
{
|
||||
[SerializeField] TimelineClip m_Clip;
|
||||
|
||||
public TimelineClip clip
|
||||
{
|
||||
get { return m_Clip; }
|
||||
set { m_Clip = value; }
|
||||
}
|
||||
|
||||
public int lastHash { get; set; }
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return clip.Hash();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 197c1114eb793d24c8ef31120a134e88
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityObject = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class EditorClipFactory
|
||||
{
|
||||
static Dictionary<TimelineClip, EditorClip> s_EditorCache = new Dictionary<TimelineClip, EditorClip>();
|
||||
|
||||
public static EditorClip GetEditorClip(TimelineClip clip)
|
||||
{
|
||||
if (clip == null)
|
||||
throw new ArgumentException("parameter cannot be null");
|
||||
|
||||
if (s_EditorCache.ContainsKey(clip))
|
||||
{
|
||||
var editorClip = s_EditorCache[clip];
|
||||
if (editorClip != null)
|
||||
return editorClip;
|
||||
}
|
||||
|
||||
var editor = ScriptableObject.CreateInstance<EditorClip>();
|
||||
editor.hideFlags |= HideFlags.HideInHierarchy | HideFlags.DontSaveInEditor;
|
||||
editor.clip = clip;
|
||||
editor.lastHash = editor.GetHashCode();
|
||||
s_EditorCache[clip] = editor;
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
public static void RemoveEditorClip(TimelineClip clip)
|
||||
{
|
||||
if (clip == null)
|
||||
return;
|
||||
|
||||
if (s_EditorCache.ContainsKey(clip))
|
||||
{
|
||||
var obj = s_EditorCache[clip];
|
||||
if (obj != null)
|
||||
UnityObject.DestroyImmediate(obj);
|
||||
s_EditorCache.Remove(clip);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Contains(TimelineClip clip)
|
||||
{
|
||||
return clip != null && s_EditorCache.ContainsKey(clip);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6ba30c492ac73742bc0cfee6817045a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using TimelineEditorSettings = UnityEngine.Timeline.TimelineAsset.EditorSettings;
|
||||
#if TIMELINE_FRAMEACCURATE
|
||||
using UnityEngine.Playables;
|
||||
#endif
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(FrameRateFieldAttribute), true)]
|
||||
class FrameRateDrawer : PropertyDrawer
|
||||
{
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
var frameRateAttribute = attribute as FrameRateFieldAttribute;
|
||||
if (frameRateAttribute == null)
|
||||
return;
|
||||
EditorGUI.BeginProperty(position, label, property);
|
||||
property.doubleValue = FrameRateField(property.doubleValue, label, position, out bool frameRateIsValid);
|
||||
EditorGUI.EndProperty();
|
||||
#if TIMELINE_FRAMEACCURATE
|
||||
if (!frameRateIsValid && TimelinePreferences.instance.playbackLockedToFrame)
|
||||
EditorGUILayout.HelpBox(
|
||||
L10n.Tr("Locking playback cannot be enabled for this frame rate."),
|
||||
MessageType.Warning);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static double FrameRateField(double frameRate, GUIContent label, Rect position, out bool isValid)
|
||||
{
|
||||
double frameRateDouble = FrameRateDisplayUtility.RoundFrameRate(frameRate);
|
||||
FrameRate frameRateObj = TimeUtility.GetClosestFrameRate(frameRateDouble);
|
||||
isValid = frameRateObj.IsValid();
|
||||
TimeUtility.ToStandardFrameRate(frameRateObj, out StandardFrameRates option);
|
||||
|
||||
position = EditorGUI.PrefixLabel(position, label);
|
||||
Rect posPopup = new Rect(position.x, position.y, position.width / 2, position.height);
|
||||
Rect posFloatField = new Rect(posPopup.xMax, position.y, position.width / 2, position.height);
|
||||
using (var checkOption = new EditorGUI.ChangeCheckScope())
|
||||
{
|
||||
option = (StandardFrameRates)EditorGUI.Popup(posPopup, (int)option,
|
||||
FrameRateDisplayUtility.GetDefaultFrameRatesLabels(option));
|
||||
|
||||
if (checkOption.changed)
|
||||
{
|
||||
isValid = true;
|
||||
return TimeUtility.ToFrameRate(option).rate;
|
||||
}
|
||||
}
|
||||
|
||||
using (var checkFrame = new EditorGUI.ChangeCheckScope())
|
||||
{
|
||||
frameRateDouble = Math.Abs(EditorGUI.DoubleField(posFloatField, frameRateDouble));
|
||||
frameRateObj = TimeUtility.GetClosestFrameRate(frameRateDouble);
|
||||
if (checkFrame.changed)
|
||||
{
|
||||
isValid = frameRateObj.IsValid();
|
||||
return isValid ? frameRateObj.rate : frameRateDouble;
|
||||
}
|
||||
}
|
||||
|
||||
return frameRateDouble;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c503dbc4e0a4e9db2d004409138d5cc
|
||||
timeCreated: 1612886047
|
@ -0,0 +1,107 @@
|
||||
//#define PERF_PROFILE
|
||||
|
||||
using System.Linq;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[CustomEditor(typeof(GroupTrack)), CanEditMultipleObjects]
|
||||
class GroupTrackInspector : TrackAssetInspector
|
||||
{
|
||||
static class Styles
|
||||
{
|
||||
public static readonly GUIContent GroupSubTrackHeaderName = L10n.TextContent("Name");
|
||||
public static readonly GUIContent GroupSubTrackHeaderType = L10n.TextContent("Type");
|
||||
public static readonly GUIContent GroupSubTrackHeaderDuration = L10n.TextContent("Duration");
|
||||
public static readonly GUIContent GroupSubTrackHeaderFrames = L10n.TextContent("Frames");
|
||||
public static readonly GUIContent GroupInvalidTrack = L10n.TextContent("Invalid Track");
|
||||
}
|
||||
|
||||
ReorderableList m_SubTracks;
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
foreach (var group in targets)
|
||||
{
|
||||
var groupTrack = group as GroupTrack;
|
||||
if (groupTrack == null) return;
|
||||
|
||||
var childrenTracks = groupTrack.GetChildTracks();
|
||||
var groupTrackName = groupTrack.name;
|
||||
|
||||
GUILayout.Label(childrenTracks.Count() > 0
|
||||
? groupTrackName + " (" + childrenTracks.Count() + ")"
|
||||
: groupTrackName, EditorStyles.boldLabel);
|
||||
GUILayout.Space(3.0f);
|
||||
|
||||
// the subTrackObjects is used because it's the internal list
|
||||
m_SubTracks.list = groupTrack.subTracksObjects;
|
||||
m_SubTracks.DoLayoutList();
|
||||
m_SubTracks.index = -1;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
m_SubTracks = new ReorderableList(new string[] { }, typeof(string), false, true, false, false)
|
||||
{
|
||||
drawElementCallback = OnDrawSubTrack,
|
||||
drawHeaderCallback = OnDrawHeader,
|
||||
showDefaultBackground = true,
|
||||
index = 0,
|
||||
elementHeight = 20
|
||||
};
|
||||
}
|
||||
|
||||
static void OnDrawHeader(Rect rect)
|
||||
{
|
||||
int sections = 4;
|
||||
float sectionWidth = rect.width / sections;
|
||||
|
||||
rect.width = sectionWidth;
|
||||
GUI.Label(rect, Styles.GroupSubTrackHeaderName, EditorStyles.label);
|
||||
rect.x += sectionWidth;
|
||||
GUI.Label(rect, Styles.GroupSubTrackHeaderType, EditorStyles.label);
|
||||
rect.x += sectionWidth;
|
||||
GUI.Label(rect, Styles.GroupSubTrackHeaderDuration, EditorStyles.label);
|
||||
rect.x += sectionWidth;
|
||||
GUI.Label(rect, Styles.GroupSubTrackHeaderFrames, EditorStyles.label);
|
||||
}
|
||||
|
||||
void OnDrawSubTrack(Rect rect, int index, bool selected, bool focused)
|
||||
{
|
||||
int sections = 4;
|
||||
float sectionWidth = rect.width / sections;
|
||||
|
||||
var childrenTrack = m_SubTracks.list[index] as TrackAsset;
|
||||
if (childrenTrack == null)
|
||||
{
|
||||
object o = m_SubTracks.list[index];
|
||||
rect.width = sectionWidth;
|
||||
if (o != null) // track is loaded, but has broken script
|
||||
{
|
||||
string name = ((UnityEngine.Object)m_SubTracks.list[index]).name;
|
||||
GUI.Label(rect, name, EditorStyles.label);
|
||||
}
|
||||
rect.x += sectionWidth;
|
||||
using (new GUIColorOverride(DirectorStyles.kClipErrorColor))
|
||||
GUI.Label(rect, Styles.GroupInvalidTrack.text, EditorStyles.label);
|
||||
return;
|
||||
}
|
||||
|
||||
rect.width = sectionWidth;
|
||||
GUI.Label(rect, childrenTrack.name, EditorStyles.label);
|
||||
rect.x += sectionWidth;
|
||||
GUI.Label(rect, childrenTrack.GetType().Name, EditorStyles.label);
|
||||
rect.x += sectionWidth;
|
||||
GUI.Label(rect, childrenTrack.duration.ToString(), EditorStyles.label);
|
||||
rect.x += sectionWidth;
|
||||
double exactFrames = TimeUtility.ToExactFrames(childrenTrack.duration, TimelineWindow.instance.state.referenceSequence.frameRate);
|
||||
GUI.Label(rect, exactFrames.ToString(), EditorStyles.label);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 41dcdc094b311464c8d6cb614548d89b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,14 @@
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Implement this interface in your PlayableAsset inspector to change what happens when a UI component in the inspector is modified
|
||||
/// </summary>
|
||||
/// <remarks>The default PlayableAsset inspector will cause any UI change to force a PlayableGraph rebuild</remarks>
|
||||
public interface IInspectorChangeHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// This method will be called when a Playable Asset inspector is modified.
|
||||
/// </summary>
|
||||
void OnPlayableAssetChangedInInspector();
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3b41db653d2547dca9eb5c43b6c5aeb0
|
||||
timeCreated: 1613416310
|
@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[CustomEditor(typeof(Marker), true)]
|
||||
[CanEditMultipleObjects]
|
||||
class MarkerInspector : BasicAssetInspector
|
||||
{
|
||||
static class Styles
|
||||
{
|
||||
public static readonly string MultipleMarkerSelectionTitle = L10n.Tr("{0} Markers");
|
||||
public static readonly string UndoCommand = L10n.Tr("Rename marker");
|
||||
}
|
||||
|
||||
internal override bool IsEnabled()
|
||||
{
|
||||
if (!TimelineUtility.IsCurrentSequenceValid() || IsCurrentSequenceReadOnly())
|
||||
return false;
|
||||
var marker = target as Marker;
|
||||
if (marker != null)
|
||||
{
|
||||
if (!marker.parent.GetShowMarkers())
|
||||
return false;
|
||||
}
|
||||
return base.IsEnabled();
|
||||
}
|
||||
|
||||
internal override void OnHeaderTitleGUI(Rect titleRect, string header)
|
||||
{
|
||||
if (targets.Length > 1)
|
||||
{
|
||||
var multiSelectTitle = string.Format(Styles.MultipleMarkerSelectionTitle, targets.Length);
|
||||
base.OnHeaderTitleGUI(titleRect, multiSelectTitle);
|
||||
return;
|
||||
}
|
||||
|
||||
var marker = target as Marker;
|
||||
if (marker != null)
|
||||
{
|
||||
if (marker.parent.GetShowMarkers() && TimelineUtility.IsCurrentSequenceValid() && !IsCurrentSequenceReadOnly())
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var newName = EditorGUI.DelayedTextField(titleRect, marker.name);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
UndoExtensions.RegisterMarker(marker, Styles.UndoCommand);
|
||||
marker.name = newName;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnHeaderTitleGUI(titleRect, marker.name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var typeName = TypeUtility.GetDisplayName(target.GetType());
|
||||
EditorGUILayout.LabelField(typeName);
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsCurrentSequenceReadOnly()
|
||||
{
|
||||
return TimelineWindow.instance.state.editSequence.isReadOnly;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fb461734117c80c43ab595d699f801eb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(TimeFieldAttribute), true)]
|
||||
class TimeFieldDrawer : PropertyDrawer
|
||||
{
|
||||
static WindowState state
|
||||
{
|
||||
get { return TimelineWindow.instance != null ? TimelineWindow.instance.state : null; }
|
||||
}
|
||||
|
||||
static double currentFrameRate
|
||||
{
|
||||
get { return state != null ? TimelineWindow.instance.state.referenceSequence.frameRate : 0.0; }
|
||||
}
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
if (property.propertyType != SerializedPropertyType.Float)
|
||||
{
|
||||
GUILayout.Label("TimeField only works on floating point types");
|
||||
return;
|
||||
}
|
||||
|
||||
var timeFieldAttribute = attribute as TimeFieldAttribute;
|
||||
if (timeFieldAttribute == null)
|
||||
return;
|
||||
|
||||
var rect = EditorGUILayout.s_LastRect;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
if (timeFieldAttribute.useEditMode == TimeFieldAttribute.UseEditMode.ApplyEditMode)
|
||||
TimeFieldWithEditMode(rect, property, label);
|
||||
else
|
||||
TimeField(rect, property, label);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if (state != null)
|
||||
state.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
static void TimeField(Rect rect, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
var evt1 = InputEvent.None;
|
||||
TimelineInspectorUtility.TimeField(rect, property, label, false, currentFrameRate, 0, float.MaxValue, ref evt1);
|
||||
}
|
||||
|
||||
static void TimeFieldWithEditMode(Rect rect, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
double minStartTime;
|
||||
if (property.hasMultipleDifferentValues)
|
||||
minStartTime = SelectionManager.SelectedItems().Min(i => i.start);
|
||||
else
|
||||
minStartTime = property.doubleValue;
|
||||
|
||||
var evt = InputEvent.None;
|
||||
var newValue = TimelineInspectorUtility.TimeField(
|
||||
rect, label, minStartTime, false, property.hasMultipleDifferentValues, currentFrameRate, 0.0, float.MaxValue, ref evt);
|
||||
|
||||
EditMode.inputHandler.ProcessMove(evt, newValue);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0c73ea1c5ff95e43806e9002c155070
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,139 @@
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[CustomEditor(typeof(TimelineAsset)), CanEditMultipleObjects]
|
||||
class TimelineAssetInspector : Editor
|
||||
{
|
||||
const int k_MinWidth = 140;
|
||||
|
||||
internal static class Styles
|
||||
{
|
||||
public static readonly GUIContent FrameRate = L10n.TextContent("Frame Rate", "The frame rate at which this sequence updates");
|
||||
public static readonly GUIContent DurationMode = L10n.TextContent("Duration Mode", "Specified how the duration of the sequence is calculated");
|
||||
public static readonly GUIContent Duration = L10n.TextContent("Duration", "The length of the sequence");
|
||||
public static readonly GUIContent HeaderTitleMultiselection = L10n.TextContent("Timeline Assets");
|
||||
public static readonly GUIContent IgnorePreviewWarning = L10n.TextContent("When ignoring preview, the Timeline window will modify the scene when this timeline is opened.");
|
||||
public static readonly GUIContent ScenePreviewLabel = L10n.TextContent("Scene Preview");
|
||||
}
|
||||
|
||||
SerializedProperty m_FrameRateProperty;
|
||||
SerializedProperty m_DurationModeProperty;
|
||||
SerializedProperty m_FixedDurationProperty;
|
||||
SerializedProperty m_ScenePreviewProperty;
|
||||
|
||||
void InitializeProperties()
|
||||
{
|
||||
var editorSettings = serializedObject.FindProperty("m_EditorSettings");
|
||||
m_FrameRateProperty = editorSettings.FindPropertyRelative("m_Framerate");
|
||||
m_DurationModeProperty = serializedObject.FindProperty("m_DurationMode");
|
||||
m_FixedDurationProperty = serializedObject.FindProperty("m_FixedDuration");
|
||||
m_ScenePreviewProperty = editorSettings.FindPropertyRelative("m_ScenePreview");
|
||||
}
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
InitializeProperties();
|
||||
}
|
||||
|
||||
internal override bool IsEnabled()
|
||||
{
|
||||
return !FileUtil.HasReadOnly(targets) && base.IsEnabled();
|
||||
}
|
||||
|
||||
protected override void OnHeaderGUI()
|
||||
{
|
||||
string headerTitle;
|
||||
if (targets.Length == 1)
|
||||
headerTitle = target.name;
|
||||
else
|
||||
headerTitle = targets.Length + " " + Styles.HeaderTitleMultiselection.text;
|
||||
|
||||
DrawHeaderGUI(this, headerTitle, 0);
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_FrameRateProperty, Styles.FrameRate);
|
||||
#if TIMELINE_FRAMEACCURATE
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
ResetFrameLockedPlayback(targets);
|
||||
}
|
||||
#else
|
||||
EditorGUI.EndChangeCheck();
|
||||
#endif
|
||||
|
||||
var frameRate = m_FrameRateProperty.doubleValue;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
EditorGUILayout.PropertyField(m_DurationModeProperty, Styles.DurationMode, GUILayout.MinWidth(k_MinWidth));
|
||||
|
||||
var durationMode = (TimelineAsset.DurationMode)m_DurationModeProperty.enumValueIndex;
|
||||
var inputEvent = InputEvent.None;
|
||||
if (durationMode == TimelineAsset.DurationMode.FixedLength)
|
||||
TimelineInspectorUtility.TimeField(m_FixedDurationProperty, Styles.Duration, false, frameRate, double.Epsilon, TimelineClip.kMaxTimeValue * 2, ref inputEvent);
|
||||
else
|
||||
{
|
||||
var isMixed = targets.Length > 1;
|
||||
TimelineInspectorUtility.TimeField(Styles.Duration, ((TimelineAsset)target).duration, true, isMixed, frameRate, double.MinValue, double.MaxValue, ref inputEvent);
|
||||
}
|
||||
|
||||
DrawIgnorePreviewProperty();
|
||||
|
||||
var changed = EditorGUI.EndChangeCheck();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
if (changed)
|
||||
TimelineEditor.Refresh(RefreshReason.WindowNeedsRedraw);
|
||||
}
|
||||
|
||||
void DrawIgnorePreviewProperty()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_ScenePreviewProperty, Styles.ScenePreviewLabel, GUILayout.MinWidth(k_MinWidth));
|
||||
}
|
||||
var changed = EditorGUI.EndChangeCheck();
|
||||
|
||||
if (changed && TimelineWindow.instance && TimelineWindow.instance.state != null && ContainsMasterAsset(targets))
|
||||
ResetWindowState(TimelineWindow.instance.state);
|
||||
|
||||
if (!m_ScenePreviewProperty.boolValue || m_ScenePreviewProperty.hasMultipleDifferentValues)
|
||||
EditorGUILayout.HelpBox(Styles.IgnorePreviewWarning.text, MessageType.Warning);
|
||||
}
|
||||
|
||||
static void ResetWindowState(WindowState state)
|
||||
{
|
||||
state.Reset();
|
||||
state.Stop();
|
||||
state.masterSequence.viewModel.windowTime = 0.0;
|
||||
if (state.masterSequence.director != null) state.masterSequence.director.time = 0.0;
|
||||
}
|
||||
|
||||
static bool ContainsMasterAsset(Object[] asset)
|
||||
{
|
||||
return asset != null && asset.Any(tl => tl == TimelineWindow.instance.state.masterSequence.asset);
|
||||
}
|
||||
|
||||
#if TIMELINE_FRAMEACCURATE
|
||||
static void ResetFrameLockedPlayback(Object[] asset)
|
||||
{
|
||||
if (TimelineWindow.instance != null
|
||||
&& TimelineWindow.instance.state != null
|
||||
&& TimelinePreferences.instance.playbackLockedToFrame
|
||||
&& ContainsMasterAsset(asset))
|
||||
{
|
||||
TimelineEditor.RefreshPreviewPlay();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e00a5dea786950546a21b0e2d817e466
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,347 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[Flags]
|
||||
enum InputEvent
|
||||
{
|
||||
None = 0,
|
||||
DragEnter = 1,
|
||||
DragExit = 2,
|
||||
Drag = 4,
|
||||
KeyboardInput = 8
|
||||
}
|
||||
|
||||
static class InputEventMethods
|
||||
{
|
||||
public static bool InputHasBegun(this InputEvent evt)
|
||||
{
|
||||
return evt == InputEvent.DragEnter || evt == InputEvent.KeyboardInput;
|
||||
}
|
||||
}
|
||||
|
||||
static class TimelineInspectorUtility
|
||||
{
|
||||
internal static class Styles
|
||||
{
|
||||
public static readonly GUIContent SecondsPrefix = L10n.TextContent("s", "Seconds");
|
||||
public static readonly GUIContent FramesPrefix = L10n.TextContent("f", "Frames");
|
||||
}
|
||||
|
||||
public static void TimeField(SerializedProperty property, GUIContent label, bool readOnly, double frameRate, double minValue, double maxValue, ref InputEvent inputEvent)
|
||||
{
|
||||
var rect = EditorGUILayout.GetControlRect();
|
||||
TimeField(rect, property, label, readOnly, frameRate, minValue, maxValue, ref inputEvent);
|
||||
}
|
||||
|
||||
// Display Time related properties in frames and seconds
|
||||
public static void TimeField(Rect rect, SerializedProperty property, GUIContent label, bool readOnly, double frameRate, double minValue, double maxValue, ref InputEvent inputEvent)
|
||||
{
|
||||
using (var propertyScope = new PropertyScope(rect, label, property))
|
||||
{
|
||||
GUIContent title = propertyScope.content;
|
||||
rect = EditorGUI.PrefixLabel(rect, title);
|
||||
|
||||
using (new IndentLevelScope(0))
|
||||
using (new LabelWidthScope((int)EditorGUI.kMiniLabelW))
|
||||
using (new GUIMixedValueScope(property.hasMultipleDifferentValues))
|
||||
{
|
||||
var secondsRect = new Rect(rect.xMin, rect.yMin, rect.width / 2 - EditorGUI.kSpacingSubLabel, rect.height);
|
||||
var framesRect = new Rect(rect.xMin + rect.width / 2, rect.yMin, rect.width / 2, rect.height);
|
||||
|
||||
if (readOnly)
|
||||
{
|
||||
EditorGUI.FloatField(secondsRect, Styles.SecondsPrefix, (float)property.doubleValue, EditorStyles.label);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
DelayedAndDraggableDoubleField(secondsRect, Styles.SecondsPrefix, property, ref inputEvent);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
property.doubleValue = Clamp(property.doubleValue, minValue, maxValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (frameRate > TimeUtility.kTimeEpsilon)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
double time = property.doubleValue;
|
||||
int frames = TimeUtility.ToFrames(time, frameRate);
|
||||
double exactFrames = TimeUtility.ToExactFrames(time, frameRate);
|
||||
bool useIntField = TimeUtility.OnFrameBoundary(time, frameRate);
|
||||
|
||||
if (readOnly)
|
||||
{
|
||||
if (useIntField)
|
||||
EditorGUI.IntField(framesRect, Styles.FramesPrefix, frames, EditorStyles.label);
|
||||
else
|
||||
EditorGUI.DoubleField(framesRect, Styles.FramesPrefix, exactFrames, EditorStyles.label);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (useIntField)
|
||||
{
|
||||
int newFrames = DelayedAndDraggableIntField(framesRect, Styles.FramesPrefix, frames, ref inputEvent);
|
||||
time = Math.Max(0, TimeUtility.FromFrames(newFrames, frameRate));
|
||||
}
|
||||
else
|
||||
{
|
||||
double newExactFrames = DelayedAndDraggableDoubleField(framesRect, Styles.FramesPrefix, exactFrames, ref inputEvent);
|
||||
time = Math.Max(0, TimeUtility.FromFrames((int)Math.Floor(newExactFrames), frameRate));
|
||||
}
|
||||
}
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
property.doubleValue = Clamp(time, minValue, maxValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static double TimeFieldUsingTimeReference(
|
||||
GUIContent label, double time, bool readOnly, bool showMixed, double frameRate, double minValue,
|
||||
double maxValue, ref InputEvent inputEvent)
|
||||
{
|
||||
var state = TimelineWindow.instance.state;
|
||||
var needsTimeConversion = state != null && state.timeReferenceMode == TimeReferenceMode.Global;
|
||||
|
||||
if (needsTimeConversion)
|
||||
time = state.editSequence.ToGlobalTime(time);
|
||||
|
||||
var t = TimeField(label, time, readOnly, showMixed, frameRate, minValue, maxValue, ref inputEvent);
|
||||
|
||||
if (needsTimeConversion)
|
||||
t = state.editSequence.ToLocalTime(t);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
public static double DurationFieldUsingTimeReference(
|
||||
GUIContent label, double start, double end, bool readOnly, bool showMixed, double frameRate,
|
||||
double minValue, double maxValue, ref InputEvent inputEvent)
|
||||
{
|
||||
var state = TimelineWindow.instance.state;
|
||||
var needsTimeConversion = state != null && state.timeReferenceMode == TimeReferenceMode.Global;
|
||||
|
||||
if (needsTimeConversion)
|
||||
{
|
||||
start = state.editSequence.ToGlobalTime(start);
|
||||
end = state.editSequence.ToGlobalTime(end);
|
||||
}
|
||||
|
||||
var duration = end - start;
|
||||
|
||||
var t = TimeField(label, duration, readOnly, showMixed, frameRate, minValue, maxValue, ref inputEvent);
|
||||
|
||||
end = start + t;
|
||||
|
||||
if (needsTimeConversion)
|
||||
{
|
||||
start = state.editSequence.ToLocalTime(start);
|
||||
end = state.editSequence.ToLocalTime(end);
|
||||
}
|
||||
|
||||
return end - start;
|
||||
}
|
||||
|
||||
public static double TimeField(Rect rect, GUIContent label, double time, bool readOnly, bool showMixed, double frameRate, double minValue, double maxValue, ref InputEvent inputEvent)
|
||||
{
|
||||
using (new HorizontalScope(label, GUIStyle.none))
|
||||
{
|
||||
rect = EditorGUI.PrefixLabel(rect, label);
|
||||
|
||||
using (new IndentLevelScope(0))
|
||||
using (new LabelWidthScope((int)EditorGUI.kMiniLabelW))
|
||||
using (new GUIMixedValueScope(showMixed))
|
||||
{
|
||||
var secondsRect = new Rect(rect.xMin, rect.yMin, rect.width / 2 - EditorGUI.kSpacingSubLabel, rect.height);
|
||||
var framesRect = new Rect(rect.xMin + rect.width / 2, rect.yMin, rect.width / 2, rect.height);
|
||||
|
||||
if (readOnly)
|
||||
{
|
||||
EditorGUI.FloatField(secondsRect, Styles.SecondsPrefix, (float)time, EditorStyles.label);
|
||||
}
|
||||
else
|
||||
{
|
||||
time = DelayedAndDraggableDoubleField(secondsRect, Styles.SecondsPrefix, time, ref inputEvent);
|
||||
}
|
||||
|
||||
if (frameRate > TimeUtility.kTimeEpsilon)
|
||||
{
|
||||
int frames = TimeUtility.ToFrames(time, frameRate);
|
||||
double exactFrames = TimeUtility.ToExactFrames(time, frameRate);
|
||||
bool useIntField = TimeUtility.OnFrameBoundary(time, frameRate);
|
||||
if (readOnly)
|
||||
{
|
||||
if (useIntField)
|
||||
EditorGUI.IntField(framesRect, Styles.FramesPrefix, frames, EditorStyles.label);
|
||||
else
|
||||
EditorGUI.FloatField(framesRect, Styles.FramesPrefix, (float)exactFrames, EditorStyles.label);
|
||||
}
|
||||
else
|
||||
{
|
||||
double newTime;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
if (useIntField)
|
||||
{
|
||||
int newFrames = DelayedAndDraggableIntField(framesRect, Styles.FramesPrefix, frames, ref inputEvent);
|
||||
newTime = Math.Max(0, TimeUtility.FromFrames(newFrames, frameRate));
|
||||
}
|
||||
else
|
||||
{
|
||||
double newExactFrames = DelayedAndDraggableDoubleField(framesRect, Styles.FramesPrefix, exactFrames, ref inputEvent);
|
||||
newTime = Math.Max(0, TimeUtility.FromFrames((int)Math.Floor(newExactFrames), frameRate));
|
||||
}
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
time = newTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Clamp(time, minValue, maxValue);
|
||||
}
|
||||
|
||||
public static double TimeField(GUIContent label, double time, bool readOnly, bool showMixed, double frameRate, double minValue, double maxValue, ref InputEvent inputEvent)
|
||||
{
|
||||
var rect = EditorGUILayout.GetControlRect();
|
||||
return TimeField(rect, label, time, readOnly, showMixed, frameRate, minValue, maxValue, ref inputEvent);
|
||||
}
|
||||
|
||||
static InputEvent InputEventType(Rect rect, int id)
|
||||
{
|
||||
var evt = Event.current;
|
||||
switch (evt.GetTypeForControl(id))
|
||||
{
|
||||
case EventType.MouseDown:
|
||||
if (rect.Contains(evt.mousePosition) && evt.button == 0)
|
||||
{
|
||||
return InputEvent.DragEnter;
|
||||
}
|
||||
break;
|
||||
case EventType.MouseUp:
|
||||
if (GUIUtility.hotControl == id)
|
||||
{
|
||||
return InputEvent.DragExit;
|
||||
}
|
||||
break;
|
||||
case EventType.MouseDrag:
|
||||
if (GUIUtility.hotControl == id)
|
||||
{
|
||||
return InputEvent.Drag;
|
||||
}
|
||||
break;
|
||||
case EventType.KeyDown:
|
||||
if (GUIUtility.hotControl == id && evt.keyCode == KeyCode.Escape)
|
||||
{
|
||||
return InputEvent.DragExit;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return InputEvent.None;
|
||||
}
|
||||
|
||||
static double DelayedAndDraggableDoubleField(Rect rect, GUIContent label, double value, ref InputEvent inputEvent, double dragSensitivity)
|
||||
{
|
||||
var id = GUIUtility.GetControlID(FocusType.Keyboard);
|
||||
var fieldRect = EditorGUI.PrefixLabel(rect, id, label);
|
||||
rect.xMax = fieldRect.x;
|
||||
|
||||
double refValue = value;
|
||||
long dummy = 0;
|
||||
|
||||
inputEvent |= InputEventType(rect, id);
|
||||
|
||||
EditorGUI.DragNumberValue(rect, id, true, ref refValue, ref dummy, dragSensitivity);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var result = EditorGUI.DelayedDoubleFieldInternal(fieldRect, GUIContent.none, refValue, EditorStyles.numberField);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
inputEvent |= InputEvent.KeyboardInput;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int DelayedAndDraggableIntField(Rect rect, GUIContent label, int value, ref InputEvent inputEvent, long dragSensitivity)
|
||||
{
|
||||
var id = GUIUtility.GetControlID(FocusType.Keyboard);
|
||||
var fieldRect = EditorGUI.PrefixLabel(rect, id, label);
|
||||
rect.xMax = fieldRect.x;
|
||||
|
||||
double dummy = 0.0;
|
||||
long refValue = value;
|
||||
|
||||
inputEvent |= InputEventType(rect, id);
|
||||
|
||||
EditorGUI.DragNumberValue(rect, id, false, ref dummy, ref refValue, dragSensitivity);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var result = EditorGUI.DelayedIntFieldInternal(fieldRect, GUIContent.none, (int)refValue, EditorStyles.numberField);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
inputEvent |= InputEvent.KeyboardInput;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static double DelayedAndDraggableDoubleField(GUIContent label, double value, ref InputEvent action, double dragSensitivity)
|
||||
{
|
||||
var r = EditorGUILayout.s_LastRect = EditorGUILayout.GetControlRect(false, EditorGUI.kSingleLineHeight);
|
||||
return DelayedAndDraggableDoubleField(r, label, value, ref action, dragSensitivity);
|
||||
}
|
||||
|
||||
static void DelayedAndDraggableDoubleField(Rect rect, GUIContent label, SerializedProperty property, ref InputEvent inputEvent)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var newValue = DelayedAndDraggableDoubleField(rect, label, property.doubleValue, ref inputEvent);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
property.doubleValue = newValue;
|
||||
}
|
||||
|
||||
static double DelayedAndDraggableDoubleField(Rect rect, GUIContent label, double value, ref InputEvent inputEvent)
|
||||
{
|
||||
var dragSensitivity = NumericFieldDraggerUtility.CalculateFloatDragSensitivity(value);
|
||||
return DelayedAndDraggableDoubleField(rect, label, value, ref inputEvent, dragSensitivity);
|
||||
}
|
||||
|
||||
static int DelayedAndDraggableIntField(Rect rect, GUIContent label, int value, ref InputEvent inputEvent)
|
||||
{
|
||||
var dragSensitivity = NumericFieldDraggerUtility.CalculateIntDragSensitivity(value);
|
||||
return DelayedAndDraggableIntField(rect, label, value, ref inputEvent, dragSensitivity);
|
||||
}
|
||||
|
||||
internal static T Clamp<T>(this T val, T min, T max) where T : IComparable<T>
|
||||
{
|
||||
if (val.CompareTo(min) < 0) return min;
|
||||
if (val.CompareTo(max) > 0) return max;
|
||||
return val;
|
||||
}
|
||||
|
||||
public static Editor GetInspectorForObjects(UnityEngine.Object[] objects, Editor previousEditor)
|
||||
{
|
||||
// create cached editor throws on assembly reload...
|
||||
try
|
||||
{
|
||||
if (objects.Any(x => x != null))
|
||||
{
|
||||
var director = TimelineWindow.instance.state.editSequence.director;
|
||||
Editor.CreateCachedEditorWithContext(objects, director, null, ref previousEditor);
|
||||
return previousEditor;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{ }
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3387717991705ce4e8ef033a0e543a06
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,201 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Timeline;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityEngine.UIElements;
|
||||
#if !UNITY_2020_2_OR_NEWER
|
||||
using L10n = UnityEditor.Timeline.L10n;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Store the editor preferences for Timeline.
|
||||
/// </summary>
|
||||
[FilePath("TimelinePreferences.asset", FilePathAttribute.Location.PreferencesFolder)]
|
||||
public class TimelinePreferences : ScriptableSingleton<TimelinePreferences>
|
||||
{
|
||||
/// <summary>
|
||||
/// The time unit used by the Timeline Editor when displaying time values.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public TimeFormat timeFormat;
|
||||
|
||||
/// <summary>
|
||||
/// Define the time unit for the timeline window.
|
||||
/// true : frame unit.
|
||||
/// false : timecode unit.
|
||||
/// </summary>
|
||||
[NonSerialized, Obsolete("timeUnitInFrame is deprecated. Use timeFormat instead", false)]
|
||||
public bool timeUnitInFrame;
|
||||
|
||||
/// <summary>
|
||||
/// Draw the waveforms for all audio clips.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public bool showAudioWaveform = true;
|
||||
|
||||
/// <summary>
|
||||
/// Allow the users to hear audio while scrubbing on audio clip.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
bool m_AudioScrubbing;
|
||||
|
||||
/// <summary>
|
||||
/// Enables audio scrubbing when moving the playhead.
|
||||
/// </summary>
|
||||
public bool audioScrubbing
|
||||
{
|
||||
get { return m_AudioScrubbing; }
|
||||
set
|
||||
{
|
||||
if (m_AudioScrubbing != value)
|
||||
{
|
||||
m_AudioScrubbing = value;
|
||||
TimelinePlayable.muteAudioScrubbing = !value;
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable Snap to Frame to manipulate clips and align them on frames.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public bool snapToFrame = true;
|
||||
|
||||
#if TIMELINE_FRAMEACCURATE
|
||||
[SerializeField] bool m_PlaybackLockedToFrame;
|
||||
#endif
|
||||
/// <summary>
|
||||
/// Enable Timelines to be evaluated on frame during editor preview.
|
||||
/// </summary>
|
||||
public bool playbackLockedToFrame
|
||||
{
|
||||
get
|
||||
{
|
||||
#if TIMELINE_FRAMEACCURATE
|
||||
return m_PlaybackLockedToFrame;
|
||||
#else
|
||||
Debug.LogWarning($"PlaybackLockedToFrame is not available for this Unity version");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
set
|
||||
{
|
||||
#if TIMELINE_FRAMEACCURATE
|
||||
m_PlaybackLockedToFrame = value;
|
||||
TimelineEditor.RefreshPreviewPlay();
|
||||
#else
|
||||
Debug.LogWarning($"PlaybackLockedToFrame is not available for this Unity version");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Enable the ability to snap clips on the edge of another clip.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public bool edgeSnap = true;
|
||||
/// <summary>
|
||||
/// Behavior of the timeline window during playback.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public PlaybackScrollMode playbackScrollMode;
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
Save();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the timeline preferences settings file.
|
||||
/// </summary>
|
||||
public void Save()
|
||||
{
|
||||
Save(true);
|
||||
}
|
||||
|
||||
internal SerializedObject GetSerializedObject()
|
||||
{
|
||||
return new SerializedObject(this);
|
||||
}
|
||||
}
|
||||
|
||||
class TimelinePreferencesProvider : SettingsProvider
|
||||
{
|
||||
SerializedObject m_SerializedObject;
|
||||
SerializedProperty m_ShowAudioWaveform;
|
||||
SerializedProperty m_TimeFormat;
|
||||
SerializedProperty m_SnapToFrame;
|
||||
SerializedProperty m_EdgeSnap;
|
||||
SerializedProperty m_PlaybackScrollMode;
|
||||
SerializedProperty m_PlaybackLockedToFrame;
|
||||
|
||||
internal class Styles
|
||||
{
|
||||
public static readonly GUIContent TimeUnitLabel = L10n.TextContent("Time Unit", "Define the time unit for the timeline window (Frames, Timecode or Seconds).");
|
||||
public static readonly GUIContent ShowAudioWaveformLabel = L10n.TextContent("Show Audio Waveforms", "Draw the waveforms for all audio clips.");
|
||||
public static readonly GUIContent AudioScrubbingLabel = L10n.TextContent("Allow Audio Scrubbing", "Allow the users to hear audio while scrubbing on audio clip.");
|
||||
public static readonly GUIContent SnapToFrameLabel = L10n.TextContent("Snap To Frame", "Enable Snap to Frame to manipulate clips and align them on frames.");
|
||||
public static readonly GUIContent EdgeSnapLabel = L10n.TextContent("Edge Snap", "Enable the ability to snap clips on the edge of another clip.");
|
||||
public static readonly GUIContent PlaybackScrollModeLabel = L10n.TextContent("Playback Scrolling Mode", "Define scrolling behavior during playback.");
|
||||
public static readonly GUIContent EditorSettingLabel = L10n.TextContent("Timeline Editor Settings", "");
|
||||
#if TIMELINE_FRAMEACCURATE
|
||||
public static readonly GUIContent PlaybackLockedToFrame = L10n.TextContent("Playback Locked To Frame", "Enable Frame Accurate Preview.");
|
||||
#endif
|
||||
}
|
||||
|
||||
public TimelinePreferencesProvider(string path, SettingsScope scopes, IEnumerable<string> keywords = null)
|
||||
: base(path, scopes, keywords)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnActivate(string searchContext, VisualElement rootElement)
|
||||
{
|
||||
TimelinePreferences.instance.Save();
|
||||
m_SerializedObject = TimelinePreferences.instance.GetSerializedObject();
|
||||
m_ShowAudioWaveform = m_SerializedObject.FindProperty("showAudioWaveform");
|
||||
m_TimeFormat = m_SerializedObject.FindProperty("timeFormat");
|
||||
m_SnapToFrame = m_SerializedObject.FindProperty("snapToFrame");
|
||||
m_EdgeSnap = m_SerializedObject.FindProperty("edgeSnap");
|
||||
m_PlaybackScrollMode = m_SerializedObject.FindProperty("playbackScrollMode");
|
||||
#if TIMELINE_FRAMEACCURATE
|
||||
m_PlaybackLockedToFrame = m_SerializedObject.FindProperty("m_PlaybackLockedToFrame");
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void OnGUI(string searchContext)
|
||||
{
|
||||
m_SerializedObject.Update();
|
||||
EditorGUI.BeginChangeCheck();
|
||||
using (new SettingsWindow.GUIScope())
|
||||
{
|
||||
EditorGUILayout.LabelField(Styles.EditorSettingLabel, EditorStyles.boldLabel);
|
||||
m_TimeFormat.enumValueIndex = EditorGUILayout.Popup(Styles.TimeUnitLabel, m_TimeFormat.enumValueIndex, m_TimeFormat.enumDisplayNames);
|
||||
m_PlaybackScrollMode.enumValueIndex = EditorGUILayout.Popup(Styles.PlaybackScrollModeLabel, m_PlaybackScrollMode.enumValueIndex, m_PlaybackScrollMode.enumNames);
|
||||
m_ShowAudioWaveform.boolValue = EditorGUILayout.Toggle(Styles.ShowAudioWaveformLabel, m_ShowAudioWaveform.boolValue);
|
||||
TimelinePreferences.instance.audioScrubbing = EditorGUILayout.Toggle(Styles.AudioScrubbingLabel, TimelinePreferences.instance.audioScrubbing);
|
||||
m_SnapToFrame.boolValue = EditorGUILayout.Toggle(Styles.SnapToFrameLabel, m_SnapToFrame.boolValue);
|
||||
m_EdgeSnap.boolValue = EditorGUILayout.Toggle(Styles.EdgeSnapLabel, m_EdgeSnap.boolValue);
|
||||
#if TIMELINE_FRAMEACCURATE
|
||||
m_PlaybackLockedToFrame.boolValue = EditorGUILayout.Toggle(Styles.PlaybackLockedToFrame, m_PlaybackLockedToFrame.boolValue);
|
||||
#endif
|
||||
}
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
m_SerializedObject.ApplyModifiedProperties();
|
||||
TimelinePreferences.instance.Save();
|
||||
TimelineEditor.Refresh(RefreshReason.WindowNeedsRedraw);
|
||||
TimelineEditor.RefreshPreviewPlay();
|
||||
}
|
||||
}
|
||||
|
||||
[SettingsProvider]
|
||||
public static SettingsProvider CreateTimelineProjectSettingProvider()
|
||||
{
|
||||
var provider = new TimelinePreferencesProvider("Preferences/Timeline", SettingsScope.User, GetSearchKeywordsFromGUIContentProperties<Styles>());
|
||||
return provider;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 227613ec5b3943443a284e30a6d6b91e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Timeline;
|
||||
using UnityEngine.Serialization;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityEngine.UIElements;
|
||||
#if !UNITY_2020_2_OR_NEWER
|
||||
using L10n = UnityEditor.Timeline.L10n;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Store the settings for Timeline that will be stored with the Unity Project.
|
||||
/// </summary>
|
||||
[FilePath("ProjectSettings/TimelineSettings.asset", FilePathAttribute.Location.ProjectFolder)]
|
||||
public class TimelineProjectSettings : ScriptableSingleton<TimelineProjectSettings>
|
||||
{
|
||||
/// <summary>
|
||||
/// Define the default framerate when a Timeline asset is created.
|
||||
/// </summary>
|
||||
[HideInInspector, Obsolete("assetDefaultFramerate has been deprecated. Use defaultFrameRate instead.")]
|
||||
public float assetDefaultFramerate = (float)TimelineAsset.EditorSettings.kDefaultFrameRate;
|
||||
|
||||
[SerializeField, FrameRateField, FormerlySerializedAs("assetDefaultFramerate")]
|
||||
private double m_DefaultFrameRate = TimelineAsset.EditorSettings.kDefaultFrameRate;
|
||||
/// <summary>
|
||||
/// Defines the default frame rate when a Timeline asset is created from the project window.
|
||||
/// </summary>
|
||||
|
||||
public double defaultFrameRate
|
||||
{
|
||||
#pragma warning disable 0618
|
||||
get
|
||||
{
|
||||
if (m_DefaultFrameRate != assetDefaultFramerate)
|
||||
{
|
||||
return assetDefaultFramerate;
|
||||
}
|
||||
return m_DefaultFrameRate;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_DefaultFrameRate = value;
|
||||
assetDefaultFramerate = (float)value;
|
||||
}
|
||||
#pragma warning restore 0618
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
Save();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the timeline project settings file in the project directory.
|
||||
/// </summary>
|
||||
public void Save()
|
||||
{
|
||||
Save(true);
|
||||
}
|
||||
|
||||
internal SerializedObject GetSerializedObject()
|
||||
{
|
||||
return new SerializedObject(this);
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
#pragma warning disable 0618
|
||||
assetDefaultFramerate = (float)m_DefaultFrameRate;
|
||||
#pragma warning restore 0618
|
||||
}
|
||||
}
|
||||
|
||||
class TimelineProjectSettingsProvider : SettingsProvider
|
||||
{
|
||||
SerializedObject m_SerializedObject;
|
||||
SerializedProperty m_Framerate;
|
||||
|
||||
private class Styles
|
||||
{
|
||||
public static readonly GUIContent DefaultFramerateLabel = L10n.TextContent("Default frame rate", "The default frame rate for new Timeline assets.");
|
||||
public static readonly GUIContent TimelineAssetLabel = L10n.TextContent("Timeline Asset", "");
|
||||
public static readonly string WarningString = L10n.Tr("Locking playback cannot be enabled for this frame rate.");
|
||||
}
|
||||
|
||||
public TimelineProjectSettingsProvider(string path, SettingsScope scopes, IEnumerable<string> keywords = null)
|
||||
: base(path, scopes, keywords) { }
|
||||
|
||||
public override void OnActivate(string searchContext, VisualElement rootElement)
|
||||
{
|
||||
TimelineProjectSettings.instance.Save();
|
||||
m_SerializedObject = TimelineProjectSettings.instance.GetSerializedObject();
|
||||
m_Framerate = m_SerializedObject.FindProperty("m_DefaultFrameRate");
|
||||
}
|
||||
|
||||
public override void OnGUI(string searchContext)
|
||||
{
|
||||
using (new SettingsWindow.GUIScope())
|
||||
{
|
||||
m_SerializedObject.Update();
|
||||
|
||||
EditorGUILayout.LabelField(Styles.TimelineAssetLabel, EditorStyles.boldLabel);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
m_Framerate.doubleValue = FrameRateDrawer.FrameRateField(m_Framerate.doubleValue, Styles.DefaultFramerateLabel,
|
||||
EditorGUILayout.GetControlRect(), out bool frameRateIsValid);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
m_SerializedObject.ApplyModifiedProperties();
|
||||
TimelineProjectSettings.instance.Save();
|
||||
}
|
||||
#if TIMELINE_FRAMEACCURATE
|
||||
if (!frameRateIsValid && TimelinePreferences.instance.playbackLockedToFrame)
|
||||
EditorGUILayout.HelpBox(Styles.WarningString, MessageType.Warning);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[SettingsProvider]
|
||||
public static SettingsProvider CreateTimelineProjectSettingProvider()
|
||||
{
|
||||
var provider = new TimelineProjectSettingsProvider("Project/Timeline", SettingsScope.Project, GetSearchKeywordsFromGUIContentProperties<Styles>());
|
||||
return provider;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a287be6c49135cd4f9b2b8666c39d999
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,217 @@
|
||||
//#define PERF_PROFILE
|
||||
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[CustomEditor(typeof(TrackAsset), true, isFallback = true)]
|
||||
[CanEditMultipleObjects]
|
||||
class TrackAssetInspector : Editor
|
||||
{
|
||||
class TrackCurvesWrapper : ICurvesOwnerInspectorWrapper
|
||||
{
|
||||
public ICurvesOwner curvesOwner { get; }
|
||||
public SerializedObject serializedPlayableAsset { get; }
|
||||
|
||||
public int lastCurveVersion { get; set; }
|
||||
public double lastEvalTime { get; set; }
|
||||
|
||||
public TrackCurvesWrapper(TrackAsset track)
|
||||
{
|
||||
lastCurveVersion = -1;
|
||||
lastEvalTime = -1;
|
||||
|
||||
if (track != null)
|
||||
{
|
||||
curvesOwner = track;
|
||||
serializedPlayableAsset = new SerializedObject(track);
|
||||
}
|
||||
}
|
||||
|
||||
public double ToLocalTime(double time)
|
||||
{
|
||||
return time;
|
||||
}
|
||||
}
|
||||
|
||||
TrackCurvesWrapper m_TrackCurvesWrapper;
|
||||
|
||||
SerializedProperty m_Name;
|
||||
bool m_IsBuiltInType;
|
||||
|
||||
Texture m_HeaderIcon;
|
||||
|
||||
|
||||
protected TimelineWindow timelineWindow
|
||||
{
|
||||
get
|
||||
{
|
||||
return TimelineWindow.instance;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool IsTrackLocked()
|
||||
{
|
||||
if (!TimelineUtility.IsCurrentSequenceValid() || IsCurrentSequenceReadOnly())
|
||||
return true;
|
||||
|
||||
return targets.Any(track => ((TrackAsset)track).lockedInHierarchy);
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
using (new EditorGUI.DisabledScope(IsTrackLocked()))
|
||||
{
|
||||
DrawInspector();
|
||||
}
|
||||
}
|
||||
|
||||
internal override bool IsEnabled()
|
||||
{
|
||||
return TimelineUtility.IsCurrentSequenceValid() && !IsCurrentSequenceReadOnly() && base.IsEnabled();
|
||||
}
|
||||
|
||||
internal override void OnHeaderTitleGUI(Rect titleRect, string header)
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
var textFieldRect = titleRect;
|
||||
using (new GUIMixedValueScope(m_Name.hasMultipleDifferentValues))
|
||||
{
|
||||
var seqWindow = TimelineWindow.instance;
|
||||
|
||||
if (IsTrackLocked())
|
||||
{
|
||||
base.OnHeaderTitleGUI(titleRect, m_Name.stringValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
string newName = EditorGUI.DelayedTextField(textFieldRect, m_Name.stringValue, EditorStyles.textField);
|
||||
if (EditorGUI.EndChangeCheck() && !string.IsNullOrEmpty(newName))
|
||||
{
|
||||
for (int c = 0; c < targets.Length; c++)
|
||||
{
|
||||
ObjectNames.SetNameSmart(targets[c], newName);
|
||||
}
|
||||
|
||||
if (seqWindow != null)
|
||||
seqWindow.Repaint();
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal override void OnHeaderIconGUI(Rect iconRect)
|
||||
{
|
||||
if (TimelineWindow.instance == null)
|
||||
return;
|
||||
using (new EditorGUI.DisabledScope(IsTrackLocked()))
|
||||
{
|
||||
if (m_HeaderIcon != null)
|
||||
GUI.Label(iconRect, GUIContent.Temp(m_HeaderIcon));
|
||||
}
|
||||
}
|
||||
|
||||
internal override Rect DrawHeaderHelpAndSettingsGUI(Rect r)
|
||||
{
|
||||
using (new EditorGUI.DisabledScope(IsTrackLocked()))
|
||||
{
|
||||
var helpSize = EditorStyles.iconButton.CalcSize(EditorGUI.GUIContents.helpIcon);
|
||||
const int kTopMargin = 5;
|
||||
|
||||
// Show Editor Header Items.
|
||||
return EditorGUIUtility.DrawEditorHeaderItems(new Rect(r.xMax - helpSize.x, r.y + kTopMargin, helpSize.x, helpSize.y), targets);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void OnEnable()
|
||||
{
|
||||
m_IsBuiltInType = target != null && target.GetType().Assembly == typeof(TrackAsset).Assembly;
|
||||
m_Name = serializedObject.FindProperty("m_Name");
|
||||
m_TrackCurvesWrapper = new TrackCurvesWrapper(target as TrackAsset);
|
||||
m_HeaderIcon = TrackResourceCache.s_DefaultIcon.image;
|
||||
|
||||
// only worry about the first track. if types are different, a different inspector is used.
|
||||
var track = target as TrackAsset;
|
||||
if (track != null)
|
||||
{
|
||||
var drawer = CustomTimelineEditorCache.GetTrackEditor(track);
|
||||
UnityEngine.Object binding = null;
|
||||
var director = m_Context as PlayableDirector;
|
||||
if (director != null)
|
||||
binding = director.GetGenericBinding(track);
|
||||
|
||||
var options = drawer.GetTrackOptions(track, binding);
|
||||
if (options.icon != null)
|
||||
m_HeaderIcon = options.icon;
|
||||
else
|
||||
m_HeaderIcon = TrackResourceCache.GetTrackIcon(track).image;
|
||||
}
|
||||
}
|
||||
|
||||
void DrawInspector()
|
||||
{
|
||||
if (serializedObject == null)
|
||||
return;
|
||||
|
||||
if (Event.current.type == EventType.Repaint || Event.current.type == EventType.Layout)
|
||||
{
|
||||
CurvesOwnerInspectorHelper.PreparePlayableAsset(m_TrackCurvesWrapper);
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
using (var changeScope = new EditorGUI.ChangeCheckScope())
|
||||
{
|
||||
DrawTrackProperties();
|
||||
|
||||
if (changeScope.changed)
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
ApplyChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void DrawTrackProperties()
|
||||
{
|
||||
var property = serializedObject.GetIterator();
|
||||
var expanded = true;
|
||||
while (property.NextVisible(expanded))
|
||||
{
|
||||
if ("m_Script" == property.propertyPath)
|
||||
{
|
||||
// Don't draw script field for built-in types
|
||||
if (m_IsBuiltInType)
|
||||
continue;
|
||||
|
||||
// Disable the script field, as it will break your Timeline if you change it.
|
||||
EditorGUI.BeginDisabled(true);
|
||||
EditorGUILayout.PropertyField(property, !expanded);
|
||||
EditorGUI.EndDisabled();
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
EditorGUILayout.PropertyField(property, !expanded);
|
||||
expanded = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ApplyChanges()
|
||||
{
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
}
|
||||
|
||||
static bool IsCurrentSequenceReadOnly()
|
||||
{
|
||||
return TimelineWindow.instance.state.editSequence.isReadOnly;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8795e0dd0041d2f44b1fe1959fc9fb53
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user