A game about forced loneliness, made by TACStudios
at master 560 lines 19 kB view raw
1using System; 2using System.Collections.Generic; 3using UnityEditor.Callbacks; 4using UnityEngine; 5using UnityEngine.Events; 6using UnityEngine.Playables; 7using UnityEngine.SceneManagement; 8using UnityEngine.Timeline; 9using Object = UnityEngine.Object; 10 11namespace UnityEditor.Timeline 12{ 13 internal interface IWindowStateProvider 14 { 15 IWindowState windowState { get; } 16 } 17 18 [EditorWindowTitle(title = "Timeline", useTypeNameAsIconName = true)] 19 partial class TimelineWindow : TimelineEditorWindow, IHasCustomMenu, IWindowStateProvider 20 { 21 [Serializable] 22 public class TimelineWindowPreferences 23 { 24 public EditMode.EditType editType = EditMode.EditType.Mix; 25 public TimeReferenceMode timeReferenceMode = TimeReferenceMode.Local; 26 } 27 28 [SerializeField] TimelineWindowPreferences m_Preferences = new TimelineWindowPreferences(); 29 public TimelineWindowPreferences preferences { get { return m_Preferences; } } 30 31 [SerializeField] 32 EditorGUIUtility.EditorLockTracker m_LockTracker = new EditorGUIUtility.EditorLockTracker(); 33 34 readonly PreviewResizer m_PreviewResizer = new PreviewResizer(); 35 bool m_LastFrameHadSequence; 36 bool m_ForceRefreshLastSelection; 37 int m_CurrentSceneHashCode = -1; 38 39 [NonSerialized] 40 bool m_HasBeenInitialized; 41 42 [SerializeField] 43 SequenceHierarchy m_SequenceHierarchy; 44 static SequenceHierarchy s_LastHierarchy; 45 46 public static TimelineWindow instance { get; private set; } 47 public Rect clientArea { get; set; } 48 public bool isDragging { get; set; } 49 public static DirectorStyles styles { get { return DirectorStyles.Instance; } } 50 public List<TimelineTrackBaseGUI> allTracks 51 { 52 get 53 { 54 return treeView != null ? treeView.allTrackGuis : new List<TimelineTrackBaseGUI>(); 55 } 56 } 57 58 public WindowState state { get; private set; } 59 60 IWindowState IWindowStateProvider.windowState => state; 61 62 public override bool locked 63 { 64 get 65 { 66 // we can never be in a locked state if there is no timeline asset 67 if (state.editSequence.asset == null) 68 return false; 69 70 return m_LockTracker.isLocked; 71 } 72 set { m_LockTracker.isLocked = value; } 73 } 74 75 public bool hierarchyChangedThisFrame { get; private set; } 76 77 public TimelineWindow() 78 { 79 InitializeManipulators(); 80 m_LockTracker.lockStateChanged.AddPersistentListener(OnLockStateChanged, UnityEventCallState.EditorAndRuntime); 81 } 82 83 void OnLockStateChanged(bool locked) 84 { 85 // Make sure that upon unlocking, any selection change is updated 86 // Case 1123119 -- only force rebuild if not recording 87 if (!locked) 88 RefreshSelection(state != null && !state.recording); 89 } 90 91 void OnEnable() 92 { 93 if (m_SequencePath == null) 94 m_SequencePath = new SequencePath(); 95 96 if (m_SequenceHierarchy == null) 97 { 98 // The sequence hierarchy will become null if maximize on play is used for in/out of playmode 99 // a static var will hang on to the reference 100 if (s_LastHierarchy != null) 101 m_SequenceHierarchy = s_LastHierarchy; 102 else 103 m_SequenceHierarchy = SequenceHierarchy.CreateInstance(); 104 105 state = null; 106 } 107 s_LastHierarchy = m_SequenceHierarchy; 108 109 titleContent = GetLocalizedTitleContent(); 110 111 UpdateTitle(); 112 113 m_PreviewResizer.Init("TimelineWindow"); 114 115 // Unmaximize fix : when unmaximizing, a new window is enabled and disabled. Prevent it from overriding the instance pointer. 116 if (instance == null) 117 instance = this; 118 119 AnimationClipCurveCache.Instance.OnEnable(); 120 TrackAsset.OnClipPlayableCreate += m_PlayableLookup.UpdatePlayableLookup; 121 TrackAsset.OnTrackAnimationPlayableCreate += m_PlayableLookup.UpdatePlayableLookup; 122 123 if (state == null) 124 { 125 state = new WindowState(this, s_LastHierarchy); 126 Initialize(); 127 RefreshSelection(true); 128 m_ForceRefreshLastSelection = true; 129 } 130 } 131 132 void OnDisable() 133 { 134 if (instance == this) 135 instance = null; 136 137 if (state != null) 138 state.Reset(); 139 140 if (instance == null) 141 SelectionManager.RemoveTimelineSelection(); 142 143 AnimationClipCurveCache.Instance.OnDisable(); 144 TrackAsset.OnClipPlayableCreate -= m_PlayableLookup.UpdatePlayableLookup; 145 TrackAsset.OnTrackAnimationPlayableCreate -= m_PlayableLookup.UpdatePlayableLookup; 146 TimelineWindowViewPrefs.SaveAll(); 147 TimelineWindowViewPrefs.UnloadAllViewModels(); 148 } 149 150 void OnDestroy() 151 { 152 if (state != null) 153 { 154 state.OnDestroy(); 155 } 156 m_HasBeenInitialized = false; 157 RemoveEditorCallbacks(); 158 AnimationClipCurveCache.Instance.Clear(); 159 TimelineAnimationUtilities.UnlinkAnimationWindow(); 160 } 161 162 void OnLostFocus() 163 { 164 isDragging = false; 165 166 if (state != null) 167 state.captured.Clear(); 168 169 Repaint(); 170 } 171 172 void OnHierarchyChange() 173 { 174 hierarchyChangedThisFrame = true; 175 Repaint(); 176 } 177 178 void OnStateChange() 179 { 180 state.UpdateRecordingState(); 181 state.editSequence.InvalidateChildAssetCache(); 182 if (treeView != null && state.editSequence.asset != null) 183 treeView.Reload(); 184 if (m_MarkerHeaderGUI != null) 185 m_MarkerHeaderGUI.Rebuild(); 186 } 187 188 void OnGUI() 189 { 190 InitializeGUIIfRequired(); 191 UpdateGUIConstants(); 192 UpdateViewStateHash(); 193 194 EditMode.HandleModeClutch(); // TODO We Want that here? 195 196 DetectStylesChange(); 197 DetectActiveSceneChanges(); 198 DetectStateChanges(); 199 200 state.ProcessStartFramePendingUpdates(); 201 202 var clipRect = new Rect(0.0f, 0.0f, position.width, position.height); 203 204 using (new GUIViewportScope(clipRect)) 205 state.InvokeWindowOnGuiStarted(Event.current); 206 207 if (Event.current.type == EventType.MouseDrag && state != null && state.mouseDragLag > 0.0f) 208 { 209 state.mouseDragLag -= Time.deltaTime; 210 return; 211 } 212 213 if (PerformUndo()) 214 return; 215 216 if (state != null && state.ignorePreview && state.playing) 217 { 218 if (state.recording) 219 state.recording = false; 220 Repaint(); 221 } 222 223 clientArea = position; 224 225 PlaybackScroller.AutoScroll(state); 226 DoLayout(); 227 228 // overlays 229 if (state.captured.Count > 0) 230 { 231 using (new GUIViewportScope(clipRect)) 232 { 233 foreach (var o in state.captured) 234 { 235 o.Overlay(Event.current, state); 236 } 237 Repaint(); 238 } 239 } 240 241 if (state.showQuadTree) 242 { 243 var fillColor = new Color(1.0f, 1.0f, 1.0f, 0.1f); 244 state.spacePartitioner.DebugDraw(fillColor, Color.yellow); 245 state.headerSpacePartitioner.DebugDraw(fillColor, Color.green); 246 } 247 248 // attempt another rebuild -- this will avoid 1 frame flashes 249 if (Event.current.type == EventType.Repaint) 250 { 251 RebuildGraphIfNecessary(); 252 state.ProcessEndFramePendingUpdates(); 253 } 254 255 using (new GUIViewportScope(clipRect)) 256 { 257 if (Event.current.type == EventType.Repaint) 258 EditMode.inputHandler.OnGUI(state, Event.current); 259 } 260 261 if (Event.current.type == EventType.Repaint) 262 { 263 hierarchyChangedThisFrame = false; 264 } 265 266 if (Event.current.type == EventType.Layout) 267 { 268 UpdateTitle(); 269 } 270 } 271 272 void UpdateTitle() 273 { 274#if UNITY_2020_2_OR_NEWER 275 bool dirty = false; 276 List<Object> children = state?.editSequence.cachedChildAssets; 277 if (children != null) 278 { 279 foreach (var child in children) 280 { 281 dirty = EditorUtility.IsDirty(child); 282 if (dirty) 283 { 284 break; 285 } 286 } 287 } 288 289 hasUnsavedChanges = dirty; 290#endif 291 } 292 293 static void DetectStylesChange() 294 { 295 DirectorStyles.ReloadStylesIfNeeded(); 296 } 297 298 void DetectActiveSceneChanges() 299 { 300 if (m_CurrentSceneHashCode == -1) 301 { 302 m_CurrentSceneHashCode = SceneManager.GetActiveScene().GetHashCode(); 303 } 304 305 if (m_CurrentSceneHashCode != SceneManager.GetActiveScene().GetHashCode()) 306 { 307 bool isSceneStillLoaded = false; 308 for (int a = 0; a < SceneManager.sceneCount; a++) 309 { 310 var scene = SceneManager.GetSceneAt(a); 311 if (scene.GetHashCode() == m_CurrentSceneHashCode && scene.isLoaded) 312 { 313 isSceneStillLoaded = true; 314 break; 315 } 316 } 317 318 if (!isSceneStillLoaded) 319 { 320 if (!locked) 321 ClearTimeline(); 322 m_CurrentSceneHashCode = SceneManager.GetActiveScene().GetHashCode(); 323 } 324 } 325 } 326 327 void DetectStateChanges() 328 { 329 if (state != null) 330 { 331 foreach (var sequenceState in state.allSequences) 332 { 333 sequenceState.ResetIsReadOnly(); 334 } 335 // detect if the sequence was removed under our feet 336 if (m_LastFrameHadSequence && state.editSequence.asset == null) 337 { 338 ClearTimeline(); 339 } 340 m_LastFrameHadSequence = state.editSequence.asset != null; 341 342 // the currentDirector can get set to null by a deletion or scene unloading so polling is required 343 if (state.editSequence.director == null) 344 { 345 state.recording = false; 346 state.previewMode = false; 347 348 if (locked) 349 { 350 //revert lock if the original context was not asset mode 351 if (!state.masterSequence.isAssetOnly) 352 locked = false; 353 } 354 355 if (!locked && m_LastFrameHadSequence) 356 { 357 // 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 358 var selectedGameObject = Selection.activeObject != null ? Selection.activeObject as GameObject : null; 359 var selectedDirector = selectedGameObject != null ? selectedGameObject.GetComponent<PlayableDirector>() : null; 360 if (selectedDirector != null) 361 { 362 SetTimeline(selectedDirector); 363 } 364 else 365 { 366 state.masterSequence.isAssetOnly = true; 367 } 368 } 369 } 370 else 371 { 372 // the user may have changed the timeline associated with the current director 373 if (state.editSequence.asset != state.editSequence.director.playableAsset) 374 { 375 if (!locked) 376 { 377 SetTimeline(state.editSequence.director); 378 } 379 else 380 { 381 // Keep locked on the current timeline but set the current director to null since it's not the timeline owner anymore 382 SetTimeline(state.editSequence.asset); 383 } 384 } 385 } 386 } 387 } 388 389 void Initialize() 390 { 391 if (!m_HasBeenInitialized) 392 { 393 InitializeStateChange(); 394 InitializeEditorCallbacks(); 395 m_HasBeenInitialized = true; 396 } 397 } 398 399 void RefreshLastSelectionIfRequired() 400 { 401 // case 1088918 - workaround for the instanceID to object cache being update during Awake. 402 // This corrects any playableDirector ptrs with the correct cached version 403 // This can happen when going from edit to playmode 404 if (m_ForceRefreshLastSelection) 405 { 406 m_ForceRefreshLastSelection = false; 407 RestoreLastSelection(true); 408 } 409 } 410 411 void InitializeGUIIfRequired() 412 { 413 RefreshLastSelectionIfRequired(); 414 InitializeTimeArea(); 415 if (treeView == null && state.editSequence.asset != null) 416 { 417 treeView = new TimelineTreeViewGUI(this, state.editSequence.asset, position); 418 } 419 } 420 421 void UpdateGUIConstants() 422 { 423 m_HorizontalScrollBarSize = 424 GUI.skin.horizontalScrollbar.fixedHeight + GUI.skin.horizontalScrollbar.margin.top; 425 m_VerticalScrollBarSize = (treeView != null && treeView.showingVerticalScrollBar) 426 ? GUI.skin.verticalScrollbar.fixedWidth + GUI.skin.verticalScrollbar.margin.left 427 : 0; 428 } 429 430 void UpdateViewStateHash() 431 { 432 if (Event.current.type == EventType.Layout) 433 state.UpdateViewStateHash(); 434 } 435 436 static bool PerformUndo() 437 { 438 if (!Event.current.isKey) 439 return false; 440 441 if (Event.current.keyCode != KeyCode.Z) 442 return false; 443 444 if (!EditorGUI.actionKey) 445 return false; 446 447 return true; 448 } 449 450 public void RebuildGraphIfNecessary(bool evaluate = true) 451 { 452 if (state == null || currentMode.mode != TimelineModes.Active || state.editSequence.director == null || state.editSequence.asset == null) 453 return; 454 455 if (state.rebuildGraph) 456 { 457 // rebuilding the graph resets the time 458 double time = state.editSequence.time; 459 460 var wasPlaying = false; 461 462 // disable preview mode, 463 if (!state.ignorePreview) 464 { 465 wasPlaying = state.playing; 466 467 state.previewMode = false; 468 state.GatherProperties(state.masterSequence.director); 469 } 470 state.RebuildPlayableGraph(); 471 state.editSequence.time = time; 472 473 if (wasPlaying) 474 state.Play(); 475 476 if (evaluate) 477 { 478 // put the scene back in the correct state 479 state.EvaluateImmediate(); 480 481 // this is necessary to see accurate results when inspector refreshes 482 // case 1154802 - this will property re-force time on the director, so 483 // the play head won't snap back to the timeline duration on rebuilds 484 if (!state.playing) 485 state.Evaluate(); 486 } 487 Repaint(); 488 } 489 490 state.rebuildGraph = false; 491 } 492 493 // for tests 494 public new void RepaintImmediately() 495 { 496 base.RepaintImmediately(); 497 } 498 499 internal static bool IsEditingTimelineAsset(TimelineAsset timelineAsset) 500 { 501 return instance != null && instance.state != null && instance.state.editSequence.asset == timelineAsset; 502 } 503 504 internal static void RepaintIfEditingTimelineAsset(TimelineAsset timelineAsset) 505 { 506 if (IsEditingTimelineAsset(timelineAsset)) 507 instance.Repaint(); 508 } 509 510 internal class DoCreateTimeline : ProjectWindowCallback.EndNameEditAction 511 { 512 public override void Action(int instanceId, string pathName, string resourceFile) 513 { 514 var timeline = TimelineUtility.CreateAndSaveTimelineAsset(pathName); 515 ProjectWindowUtil.ShowCreatedAsset(timeline); 516 } 517 } 518 519 [MenuItem("Assets/Create/Timeline/Timeline", false, -124)] 520 public static void CreateNewTimeline() 521 { 522 var icon = EditorGUIUtility.IconContent("TimelineAsset Icon").image as Texture2D; 523 ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, CreateInstance<DoCreateTimeline>(), "New Timeline.playable", icon, null); 524 } 525 526 [MenuItem("Window/Sequencing/Timeline", false, 1)] 527 public static void ShowWindow() 528 { 529 GetWindow<TimelineWindow>(typeof(SceneView)); 530 instance.Focus(); 531 } 532 533 [OnOpenAsset(1)] 534 public static bool OnDoubleClick(int instanceID, int line) 535 { 536 var assetDoubleClicked = EditorUtility.InstanceIDToObject(instanceID) as TimelineAsset; 537 if (assetDoubleClicked == null) 538 return false; 539 540 ShowWindow(); 541 instance.SetTimeline(assetDoubleClicked); 542 543 return true; 544 } 545 546 public virtual void AddItemsToMenu(GenericMenu menu) 547 { 548 bool disabled = state == null || state.editSequence.asset == null; 549 550 m_LockTracker.AddItemsToMenu(menu, disabled); 551 } 552 553 protected virtual void ShowButton(Rect r) 554 { 555 bool disabled = state == null || state.editSequence.asset == null; 556 557 m_LockTracker.ShowButton(r, DirectorStyles.Instance.timelineLockButton, disabled); 558 } 559 } 560}