|
|
using System;using System.Collections.Generic;using UnityEditor.Callbacks;using UnityEngine;using UnityEngine.Events;using UnityEngine.Playables;using UnityEngine.SceneManagement;using UnityEngine.Timeline;
namespace UnityEditor.Timeline{ [EditorWindowTitle(title = "Timeline", useTypeNameAsIconName = true)] partial class TimelineWindow : EditorWindow, IHasCustomMenu { [Serializable] public class TimelineWindowPreferences { public bool frameSnap = true; public bool edgeSnaps = true; public bool muteAudioScrub = true; public bool playRangeLoopMode = true; public PlaybackScrollMode autoScrollMode; 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; }
public bool locked { get { // we can never be in a locked state if there is no timeline asset
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) { // Make sure that upon unlocking, any selection change is updated
// Case 1123119 -- only force rebuild if not recording
if (!locked) RefreshSelection(state != null && !state.recording); }
void OnEnable() { if (m_SequencePath == null) m_SequencePath = new SequencePath();
if (m_SequenceHierarchy == null) { // The sequence hierarchy will become null if maximize on play is used for in/out of playmode
// a static var will hang on to the reference
if (s_LastHierarchy != null) m_SequenceHierarchy = s_LastHierarchy; else m_SequenceHierarchy = SequenceHierarchy.CreateInstance();
state = null; } s_LastHierarchy = m_SequenceHierarchy;
titleContent = GetLocalizedTitleContent();
m_PreviewResizer.Init("TimelineWindow");
// Unmaximize fix : when unmaximizing, a new window is enabled and disabled. Prevent it from overriding the instance pointer.
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(); TimelineAnimationUtilities.UnlinkAnimationWindow(); }
void OnLostFocus() { isDragging = false;
if (state != null) state.captured.Clear();
Repaint(); }
void OnFocus() { if (state == null) return;
// selection may have changed while Timeline Editor was looking away
RefreshSelection(false);
// Inline curves may have become out of sync
RefreshInlineCurves(); }
void OnHierarchyChange() { hierarchyChangedThisFrame = true; Repaint(); }
void OnStateChange() { state.UpdateRecordingState(); if (treeView != null && state.editSequence.asset != null) treeView.Reload(); if (m_MarkerHeaderGUI != null) m_MarkerHeaderGUI.Rebuild(); }
void OnGUI() { InitializeGUIIfRequired(); UpdateGUIConstants(); UpdateViewStateHash();
EditMode.HandleModeClutch(); // TODO We Want that here?
DetectStylesChange(); DetectActiveSceneChanges(); DetectStateChanges();
state.ProcessStartFramePendingUpdates();
var clipRect = new Rect(0.0f, 0.0f, position.width, position.height); clipRect.xMin += state.sequencerHeaderWidth;
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 (EditorApplication.isPlaying) { if (state != null) { if (state.recording) state.recording = false; } Repaint(); }
clientArea = position;
PlaybackScroller.AutoScroll(state); DoLayout();
// overlays
if (state.captured.Count > 0) { using (new GUIViewportScope(clipRect)) { foreach (var o in state.captured) { o.Overlay(Event.current, state); } Repaint(); } }
if (state.showQuadTree) state.spacePartitioner.DebugDraw();
// attempt another rebuild -- this will avoid 1 frame flashes
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; }
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) ClearCurrentTimeline(); m_CurrentSceneHashCode = SceneManager.GetActiveScene().GetHashCode(); } } }
void DetectStateChanges() { if (state != null) { state.editSequence.ResetIsReadOnly(); //Force reset readonly for asset flag for each frame.
// detect if the sequence was removed under our feet
if (m_LastFrameHadSequence && state.editSequence.asset == null) { ClearCurrentTimeline(); } m_LastFrameHadSequence = state.editSequence.asset != null;
// the currentDirector can get set to null by a deletion or scene unloading so polling is required
if (state.editSequence.director == null) { state.recording = false; state.previewMode = false;
//Case 1201405 : Check if the lock state is valid with the lock tracker state
if (locked != m_LockTracker.isLocked) m_LockTracker.isLocked = locked;
if (!locked && m_LastFrameHadSequence) { // the user may be adding a new PlayableDirector to a selected GameObject, make sure the timeline editor is shows the proper director if none is already showing
var selectedGameObject = Selection.activeObject != null ? Selection.activeObject as GameObject : null; var selectedDirector = selectedGameObject != null ? selectedGameObject.GetComponent<PlayableDirector>() : null; if (selectedDirector != null) { SetCurrentTimeline(selectedDirector); } } } else { // the user may have changed the timeline associated with the current director
if (state.editSequence.asset != state.editSequence.director.playableAsset) { if (!locked) { SetCurrentTimeline(state.editSequence.director); } else { // Keep locked on the current timeline but set the current director to null since it's not the timeline owner anymore
SetCurrentTimeline(state.editSequence.asset); } } } } }
void Initialize() { if (!m_HasBeenInitialized) { InitializeStateChange(); InitializeEditorCallbacks(); m_HasBeenInitialized = true; } }
void RefreshLastSelectionIfRequired() { // case 1088918 - workaround for the instanceID to object cache being update during Awake.
// This corrects any playableDirector ptrs with the correct cached version
// This can happen when going from edit to playmode
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 || state.editSequence.director == null || state.editSequence.asset == null) return;
if (state.rebuildGraph) { // rebuilding the graph resets the time
double time = state.editSequence.time;
var wasPlaying = false;
// disable preview mode,
if (!EditorApplication.isPlaying) { wasPlaying = state.playing;
state.previewMode = false; state.GatherProperties(state.masterSequence.director); } state.RebuildPlayableGraph(); state.editSequence.time = time;
if (wasPlaying) state.Play();
if (evaluate) { // put the scene back in the correct state
state.EvaluateImmediate();
// this is necessary to see accurate results when inspector refreshes
// case 1154802 - this will property re-force time on the director, so
// the play head won't snap back to the timeline duration on rebuilds
if (!state.playing) state.Evaluate(); } Repaint(); }
state.rebuildGraph = false; }
// for tests
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 = ScriptableObject.CreateInstance<TimelineAsset>(); AssetDatabase.CreateAsset(timeline, 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.SetCurrentTimeline(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.lockButton, disabled); }
internal void TreeViewKeyboardCallback() { if (Event.current.type != EventType.KeyDown) return; if (TimelineAction.HandleShortcut(state, Event.current)) { Event.current.Use(); } } }}
|