UnityGame/Library/PackageCache/com.unity.timeline/Editor/Window/TimelineWindow.cs

561 lines
18 KiB
C#
Raw Permalink Normal View History

2024-10-27 10:53:47 +03:00
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
{
// 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();
UpdateTitle();
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();
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(); // TODO We Want that here?
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();
// 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)
{
var fillColor = new Color(1.0f, 1.0f, 1.0f, 0.1f);
state.spacePartitioner.DebugDraw(fillColor, Color.yellow);
state.headerSpacePartitioner.DebugDraw(fillColor, Color.green);
}
// 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;
}
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();
}
// detect if the sequence was removed under our feet
if (m_LastFrameHadSequence && state.editSequence.asset == null)
{
ClearTimeline();
}
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;
if (locked)
{
//revert lock if the original context was not asset mode
if (!state.masterSequence.isAssetOnly)
locked = false;
}
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)
{
SetTimeline(selectedDirector);
}
else
{
state.masterSequence.isAssetOnly = true;
}
}
}
else
{
// the user may have changed the timeline associated with the current director
if (state.editSequence.asset != state.editSequence.director.playableAsset)
{
if (!locked)
{
SetTimeline(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
SetTimeline(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 || currentMode.mode != TimelineModes.Active || 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 (!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)
{
// 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 = TimelineUtility.CreateAndSaveTimelineAsset(pathName);
ProjectWindowUtil.ShowCreatedAsset(timeline);
}
}
[MenuItem("Assets/Create/Timeline/Timeline", false, -124)]
public static void CreateNewTimeline()
{
var icon = EditorGUIUtility.IconContent("TimelineAsset Icon").image as Texture2D;
ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, 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);
}
}
}