| using System; |
| using System.Collections.Generic; |
| using UnityEditor.Callbacks; |
| using UnityEngine; |
| using UnityEngine.Events; |
| using UnityEngine.Playables; |
| using UnityEngine.SceneManagement; |
| using UnityEngine.Timeline; |
| using Object = UnityEngine.Object; |
|
|
| namespace UnityEditor.Timeline |
| { |
| internal interface IWindowStateProvider |
| { |
| IWindowState windowState { get; } |
| } |
|
|
| [EditorWindowTitle(title = "Timeline", useTypeNameAsIconName = true)] |
| partial class TimelineWindow : TimelineEditorWindow, IHasCustomMenu, IWindowStateProvider |
| { |
| [Serializable] |
| public class TimelineWindowPreferences |
| { |
| public EditMode.EditType editType = EditMode.EditType.Mix; |
| public TimeReferenceMode timeReferenceMode = TimeReferenceMode.Local; |
| } |
|
|
| [SerializeField] TimelineWindowPreferences m_Preferences = new TimelineWindowPreferences(); |
| public TimelineWindowPreferences preferences { get { return m_Preferences; } } |
|
|
| [SerializeField] |
| EditorGUIUtility.EditorLockTracker m_LockTracker = new EditorGUIUtility.EditorLockTracker(); |
|
|
| readonly PreviewResizer m_PreviewResizer = new PreviewResizer(); |
| bool m_LastFrameHadSequence; |
| bool m_ForceRefreshLastSelection; |
| int m_CurrentSceneHashCode = -1; |
|
|
| [NonSerialized] |
| bool m_HasBeenInitialized; |
|
|
| [SerializeField] |
| SequenceHierarchy m_SequenceHierarchy; |
| static SequenceHierarchy s_LastHierarchy; |
|
|
| public static TimelineWindow instance { get; private set; } |
| public Rect clientArea { get; set; } |
| public bool isDragging { get; set; } |
| public static DirectorStyles styles { get { return DirectorStyles.Instance; } } |
| public List<TimelineTrackBaseGUI> allTracks |
| { |
| get |
| { |
| return treeView != null ? treeView.allTrackGuis : new List<TimelineTrackBaseGUI>(); |
| } |
| } |
|
|
| public WindowState state { get; private set; } |
|
|
| IWindowState IWindowStateProvider.windowState => state; |
|
|
| public override bool locked |
| { |
| get |
| { |
| |
| if (state.editSequence.asset == null) |
| return false; |
|
|
| return m_LockTracker.isLocked; |
| } |
| set { m_LockTracker.isLocked = value; } |
| } |
|
|
| public bool hierarchyChangedThisFrame { get; private set; } |
|
|
| public TimelineWindow() |
| { |
| InitializeManipulators(); |
| m_LockTracker.lockStateChanged.AddPersistentListener(OnLockStateChanged, UnityEventCallState.EditorAndRuntime); |
| } |
|
|
| void OnLockStateChanged(bool locked) |
| { |
| |
| |
| if (!locked) |
| RefreshSelection(state != null && !state.recording); |
| } |
|
|
| void OnEnable() |
| { |
| if (m_SequencePath == null) |
| m_SequencePath = new SequencePath(); |
|
|
| if (m_SequenceHierarchy == null) |
| { |
| |
| |
| if (s_LastHierarchy != null) |
| m_SequenceHierarchy = s_LastHierarchy; |
| else |
| m_SequenceHierarchy = SequenceHierarchy.CreateInstance(); |
|
|
| state = null; |
| } |
| s_LastHierarchy = m_SequenceHierarchy; |
|
|
| titleContent = GetLocalizedTitleContent(); |
|
|
| UpdateTitle(); |
|
|
| m_PreviewResizer.Init("TimelineWindow"); |
|
|
| |
| if (instance == null) |
| instance = this; |
|
|
| AnimationClipCurveCache.Instance.OnEnable(); |
| TrackAsset.OnClipPlayableCreate += m_PlayableLookup.UpdatePlayableLookup; |
| TrackAsset.OnTrackAnimationPlayableCreate += m_PlayableLookup.UpdatePlayableLookup; |
|
|
| if (state == null) |
| { |
| state = new WindowState(this, s_LastHierarchy); |
| Initialize(); |
| RefreshSelection(true); |
| m_ForceRefreshLastSelection = true; |
| } |
| } |
|
|
| void OnDisable() |
| { |
| if (instance == this) |
| instance = null; |
|
|
| if (state != null) |
| state.Reset(); |
|
|
| if (instance == null) |
| SelectionManager.RemoveTimelineSelection(); |
|
|
| AnimationClipCurveCache.Instance.OnDisable(); |
| TrackAsset.OnClipPlayableCreate -= m_PlayableLookup.UpdatePlayableLookup; |
| TrackAsset.OnTrackAnimationPlayableCreate -= m_PlayableLookup.UpdatePlayableLookup; |
| TimelineWindowViewPrefs.SaveAll(); |
| TimelineWindowViewPrefs.UnloadAllViewModels(); |
| } |
|
|
| void OnDestroy() |
| { |
| if (state != null) |
| { |
| state.OnDestroy(); |
| } |
| m_HasBeenInitialized = false; |
| RemoveEditorCallbacks(); |
| AnimationClipCurveCache.Instance.Clear(); |
| TimelineAnimationUtilities.UnlinkAnimationWindow(); |
| } |
|
|
| void OnLostFocus() |
| { |
| isDragging = false; |
|
|
| if (state != null) |
| state.captured.Clear(); |
|
|
| Repaint(); |
| } |
|
|
| void OnHierarchyChange() |
| { |
| hierarchyChangedThisFrame = true; |
| Repaint(); |
| } |
|
|
| void OnStateChange() |
| { |
| state.UpdateRecordingState(); |
| state.editSequence.InvalidateChildAssetCache(); |
| if (treeView != null && state.editSequence.asset != null) |
| treeView.Reload(); |
| if (m_MarkerHeaderGUI != null) |
| m_MarkerHeaderGUI.Rebuild(); |
| } |
|
|
| void OnGUI() |
| { |
| InitializeGUIIfRequired(); |
| UpdateGUIConstants(); |
| UpdateViewStateHash(); |
|
|
| EditMode.HandleModeClutch(); |
|
|
| DetectStylesChange(); |
| DetectActiveSceneChanges(); |
| DetectStateChanges(); |
|
|
| state.ProcessStartFramePendingUpdates(); |
|
|
| var clipRect = new Rect(0.0f, 0.0f, position.width, position.height); |
|
|
| using (new GUIViewportScope(clipRect)) |
| state.InvokeWindowOnGuiStarted(Event.current); |
|
|
| if (Event.current.type == EventType.MouseDrag && state != null && state.mouseDragLag > 0.0f) |
| { |
| state.mouseDragLag -= Time.deltaTime; |
| return; |
| } |
|
|
| if (PerformUndo()) |
| return; |
|
|
| if (state != null && state.ignorePreview && state.playing) |
| { |
| if (state.recording) |
| state.recording = false; |
| Repaint(); |
| } |
|
|
| clientArea = position; |
|
|
| PlaybackScroller.AutoScroll(state); |
| DoLayout(); |
|
|
| |
| if (state.captured.Count > 0) |
| { |
| using (new GUIViewportScope(clipRect)) |
| { |
| foreach (var o in state.captured) |
| { |
| o.Overlay(Event.current, state); |
| } |
| Repaint(); |
| } |
| } |
|
|
| if (state.showQuadTree) |
| { |
| var fillColor = new Color(1.0f, 1.0f, 1.0f, 0.1f); |
| state.spacePartitioner.DebugDraw(fillColor, Color.yellow); |
| state.headerSpacePartitioner.DebugDraw(fillColor, Color.green); |
| } |
|
|
| |
| if (Event.current.type == EventType.Repaint) |
| { |
| RebuildGraphIfNecessary(); |
| state.ProcessEndFramePendingUpdates(); |
| } |
|
|
| using (new GUIViewportScope(clipRect)) |
| { |
| if (Event.current.type == EventType.Repaint) |
| EditMode.inputHandler.OnGUI(state, Event.current); |
| } |
|
|
| if (Event.current.type == EventType.Repaint) |
| { |
| hierarchyChangedThisFrame = false; |
| } |
|
|
| if (Event.current.type == EventType.Layout) |
| { |
| UpdateTitle(); |
| } |
| } |
|
|
| void UpdateTitle() |
| { |
| #if UNITY_2020_2_OR_NEWER |
| bool dirty = false; |
| List<Object> children = state?.editSequence.cachedChildAssets; |
| if (children != null) |
| { |
| foreach (var child in children) |
| { |
| dirty = EditorUtility.IsDirty(child); |
| if (dirty) |
| { |
| break; |
| } |
| } |
| } |
|
|
| hasUnsavedChanges = dirty; |
| #endif |
| } |
|
|
| static void DetectStylesChange() |
| { |
| DirectorStyles.ReloadStylesIfNeeded(); |
| } |
|
|
| void DetectActiveSceneChanges() |
| { |
| if (m_CurrentSceneHashCode == -1) |
| { |
| m_CurrentSceneHashCode = SceneManager.GetActiveScene().GetHashCode(); |
| } |
|
|
| if (m_CurrentSceneHashCode != SceneManager.GetActiveScene().GetHashCode()) |
| { |
| bool isSceneStillLoaded = false; |
| for (int a = 0; a < SceneManager.sceneCount; a++) |
| { |
| var scene = SceneManager.GetSceneAt(a); |
| if (scene.GetHashCode() == m_CurrentSceneHashCode && scene.isLoaded) |
| { |
| isSceneStillLoaded = true; |
| break; |
| } |
| } |
|
|
| if (!isSceneStillLoaded) |
| { |
| if (!locked) |
| ClearTimeline(); |
| m_CurrentSceneHashCode = SceneManager.GetActiveScene().GetHashCode(); |
| } |
| } |
| } |
|
|
| void DetectStateChanges() |
| { |
| if (state != null) |
| { |
| foreach (var sequenceState in state.allSequences) |
| { |
| sequenceState.ResetIsReadOnly(); |
| } |
| |
| if (m_LastFrameHadSequence && state.editSequence.asset == null) |
| { |
| ClearTimeline(); |
| } |
| m_LastFrameHadSequence = state.editSequence.asset != null; |
|
|
| |
| if (state.editSequence.director == null) |
| { |
| state.recording = false; |
| state.previewMode = false; |
|
|
| if (locked) |
| { |
| |
| if (!state.masterSequence.isAssetOnly) |
| locked = false; |
| } |
|
|
| if (!locked && m_LastFrameHadSequence) |
| { |
| |
| var selectedGameObject = Selection.activeObject != null ? Selection.activeObject as GameObject : null; |
| var selectedDirector = selectedGameObject != null ? selectedGameObject.GetComponent<PlayableDirector>() : null; |
| if (selectedDirector != null) |
| { |
| SetTimeline(selectedDirector); |
| } |
| else |
| { |
| state.masterSequence.isAssetOnly = true; |
| } |
| } |
| } |
| else |
| { |
| |
| if (state.editSequence.asset != state.editSequence.director.playableAsset) |
| { |
| if (!locked) |
| { |
| SetTimeline(state.editSequence.director); |
| } |
| else |
| { |
| |
| SetTimeline(state.editSequence.asset); |
| } |
| } |
| } |
| } |
| } |
|
|
| void Initialize() |
| { |
| if (!m_HasBeenInitialized) |
| { |
| InitializeStateChange(); |
| InitializeEditorCallbacks(); |
| m_HasBeenInitialized = true; |
| } |
| } |
|
|
| void RefreshLastSelectionIfRequired() |
| { |
| |
| |
| |
| if (m_ForceRefreshLastSelection) |
| { |
| m_ForceRefreshLastSelection = false; |
| RestoreLastSelection(true); |
| } |
| } |
|
|
| void InitializeGUIIfRequired() |
| { |
| RefreshLastSelectionIfRequired(); |
| InitializeTimeArea(); |
| if (treeView == null && state.editSequence.asset != null) |
| { |
| treeView = new TimelineTreeViewGUI(this, state.editSequence.asset, position); |
| } |
| } |
|
|
| void UpdateGUIConstants() |
| { |
| m_HorizontalScrollBarSize = |
| GUI.skin.horizontalScrollbar.fixedHeight + GUI.skin.horizontalScrollbar.margin.top; |
| m_VerticalScrollBarSize = (treeView != null && treeView.showingVerticalScrollBar) |
| ? GUI.skin.verticalScrollbar.fixedWidth + GUI.skin.verticalScrollbar.margin.left |
| : 0; |
| } |
|
|
| void UpdateViewStateHash() |
| { |
| if (Event.current.type == EventType.Layout) |
| state.UpdateViewStateHash(); |
| } |
|
|
| static bool PerformUndo() |
| { |
| if (!Event.current.isKey) |
| return false; |
|
|
| if (Event.current.keyCode != KeyCode.Z) |
| return false; |
|
|
| if (!EditorGUI.actionKey) |
| return false; |
|
|
| return true; |
| } |
|
|
| public void RebuildGraphIfNecessary(bool evaluate = true) |
| { |
| if (state == null || currentMode.mode != TimelineModes.Active || state.editSequence.director == null || state.editSequence.asset == null) |
| return; |
|
|
| if (state.rebuildGraph) |
| { |
| |
| double time = state.editSequence.time; |
|
|
| var wasPlaying = false; |
|
|
| |
| if (!state.ignorePreview) |
| { |
| wasPlaying = state.playing; |
|
|
| state.previewMode = false; |
| state.GatherProperties(state.masterSequence.director); |
| } |
| state.RebuildPlayableGraph(); |
| state.editSequence.time = time; |
|
|
| if (wasPlaying) |
| state.Play(); |
|
|
| if (evaluate) |
| { |
| |
| state.EvaluateImmediate(); |
|
|
| |
| |
| |
| if (!state.playing) |
| state.Evaluate(); |
| } |
| Repaint(); |
| } |
|
|
| state.rebuildGraph = false; |
| } |
|
|
| |
| public new void RepaintImmediately() |
| { |
| base.RepaintImmediately(); |
| } |
|
|
| internal static bool IsEditingTimelineAsset(TimelineAsset timelineAsset) |
| { |
| return instance != null && instance.state != null && instance.state.editSequence.asset == timelineAsset; |
| } |
|
|
| internal static void RepaintIfEditingTimelineAsset(TimelineAsset timelineAsset) |
| { |
| if (IsEditingTimelineAsset(timelineAsset)) |
| instance.Repaint(); |
| } |
|
|
| internal class DoCreateTimeline : ProjectWindowCallback.EndNameEditAction |
| { |
| public override void Action(int instanceId, string pathName, string resourceFile) |
| { |
| var timeline = TimelineUtility.CreateAndSaveTimelineAsset(pathName); |
| ProjectWindowUtil.ShowCreatedAsset(timeline); |
| } |
| } |
|
|
| [MenuItem("Assets/Create/Timeline", false, 450)] |
| public static void CreateNewTimeline() |
| { |
| var icon = EditorGUIUtility.IconContent("TimelineAsset Icon").image as Texture2D; |
| ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, ScriptableObject.CreateInstance<DoCreateTimeline>(), "New Timeline.playable", icon, null); |
| } |
|
|
| [MenuItem("Window/Sequencing/Timeline", false, 1)] |
| public static void ShowWindow() |
| { |
| GetWindow<TimelineWindow>(typeof(SceneView)); |
| instance.Focus(); |
| } |
|
|
| [OnOpenAsset(1)] |
| public static bool OnDoubleClick(int instanceID, int line) |
| { |
| var assetDoubleClicked = EditorUtility.InstanceIDToObject(instanceID) as TimelineAsset; |
| if (assetDoubleClicked == null) |
| return false; |
|
|
| ShowWindow(); |
| instance.SetTimeline(assetDoubleClicked); |
|
|
| return true; |
| } |
|
|
| public virtual void AddItemsToMenu(GenericMenu menu) |
| { |
| bool disabled = state == null || state.editSequence.asset == null; |
|
|
| m_LockTracker.AddItemsToMenu(menu, disabled); |
| } |
|
|
| protected virtual void ShowButton(Rect r) |
| { |
| bool disabled = state == null || state.editSequence.asset == null; |
|
|
| m_LockTracker.ShowButton(r, DirectorStyles.Instance.timelineLockButton, disabled); |
| } |
| } |
| } |
|
|