TimelineWindow_EditorCallbacks.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEditor.SceneManagement;
  5. using UnityEngine;
  6. using UnityEngine.Animations;
  7. using UnityEngine.Playables;
  8. using UnityEngine.SceneManagement;
  9. using UnityEngine.Timeline;
  10. namespace UnityEditor.Timeline
  11. {
  12. partial class TimelineWindow
  13. {
  14. private int m_ComponentAddedFrame;
  15. void OnSelectionChangedInactive()
  16. {
  17. // Case 946942 -- when selection changes and the window is open but hidden, timeline
  18. // needs to update selection immediately so preview mode is correctly released
  19. // Case 1123119 -- except when recording
  20. if (!hasFocus)
  21. {
  22. RefreshSelection(!locked && state != null && !state.recording);
  23. }
  24. }
  25. void InitializeEditorCallbacks()
  26. {
  27. Undo.postprocessModifications += PostprocessAnimationRecordingModifications;
  28. Undo.postprocessModifications += ProcessAssetModifications;
  29. Undo.undoRedoPerformed += OnUndoRedo;
  30. EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
  31. AnimationUtility.onCurveWasModified += OnCurveModified;
  32. EditorApplication.editorApplicationQuit += OnEditorQuit;
  33. Selection.selectionChanged += OnSelectionChangedInactive;
  34. EditorSceneManager.sceneSaved += OnSceneSaved;
  35. ObjectFactory.componentWasAdded += OnComponentWasAdded;
  36. PrefabUtility.prefabInstanceUpdated += OnPrefabApplied;
  37. EditorApplication.pauseStateChanged += OnPlayModePause;
  38. }
  39. void OnEditorQuit()
  40. {
  41. TimelineWindowViewPrefs.SaveAll();
  42. }
  43. void RemoveEditorCallbacks()
  44. {
  45. EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
  46. Undo.undoRedoPerformed -= OnUndoRedo;
  47. Undo.postprocessModifications -= PostprocessAnimationRecordingModifications;
  48. Undo.postprocessModifications -= ProcessAssetModifications;
  49. AnimationUtility.onCurveWasModified -= OnCurveModified;
  50. EditorApplication.editorApplicationQuit -= OnEditorQuit;
  51. Selection.selectionChanged -= OnSelectionChangedInactive;
  52. EditorSceneManager.sceneSaved -= OnSceneSaved;
  53. ObjectFactory.componentWasAdded -= OnComponentWasAdded;
  54. PrefabUtility.prefabInstanceUpdated -= OnPrefabApplied;
  55. EditorApplication.pauseStateChanged -= OnPlayModePause;
  56. }
  57. void OnPlayModePause(PauseState state)
  58. {
  59. // in PlayMode, if the timeline is playing, a constant repaint cycle occurs. Pausing the editor
  60. // breaks the cycle, so this will restart it
  61. Repaint();
  62. }
  63. // Called when a prefab change is applied to the scene.
  64. // Redraw so control tracks that use prefabs can show changes
  65. void OnPrefabApplied(GameObject go)
  66. {
  67. if (!state.previewMode)
  68. return;
  69. // if we added a component this frame, then rebuild, otherwise just let
  70. // the individual playable handle the prefab application
  71. if (Time.frameCount == m_ComponentAddedFrame)
  72. TimelineEditor.Refresh(RefreshReason.ContentsModified);
  73. else
  74. TimelineEditor.Refresh(RefreshReason.SceneNeedsUpdate);
  75. }
  76. // When the scene is save the director time will get reset.
  77. void OnSceneSaved(Scene scene)
  78. {
  79. if (state != null)
  80. state.OnSceneSaved();
  81. }
  82. void OnCurveModified(AnimationClip clip, EditorCurveBinding binding, AnimationUtility.CurveModifiedType type)
  83. {
  84. InspectorWindow.RepaintAllInspectors();
  85. if (state == null || state.rebuildGraph)
  86. return;
  87. //Force refresh of curve when modified by another editor.
  88. Repaint();
  89. if (state.previewMode == false)
  90. return;
  91. bool hasPlayable = m_PlayableLookup.GetPlayableFromAnimClip(clip, out Playable playable);
  92. // mark the timeline clip as dirty
  93. TimelineClip timelineClip = m_PlayableLookup.GetTimelineClipFromCurves(clip);
  94. if (timelineClip != null)
  95. timelineClip.MarkDirty();
  96. if (type == AnimationUtility.CurveModifiedType.CurveModified)
  97. {
  98. if (hasPlayable)
  99. {
  100. playable.SetAnimatedProperties(clip);
  101. }
  102. // updates the duration of the graph without rebuilding
  103. AnimationUtility.SyncEditorCurves(clip); // deleted keys are not synced when this is sent out, so duration could be incorrect
  104. state.UpdateRootPlayableDuration(state.editSequence.duration);
  105. bool isRecording = TimelineRecording.IsRecordingAnimationTrack;
  106. PlayableDirector masterDirector = TimelineEditor.masterDirector;
  107. bool isGraphValid = masterDirector != null && masterDirector.playableGraph.IsValid();
  108. // don't evaluate if this is caused by recording on an animation track, the extra evaluation can cause hiccups
  109. // Prevent graphs to be resurrected by a changed clip.
  110. if (!isRecording && isGraphValid)
  111. state.Evaluate();
  112. }
  113. else if (EditorUtility.IsDirty(clip)) // curve added/removed, or clip added/removed
  114. {
  115. state.rebuildGraph |= timelineClip != null || hasPlayable;
  116. }
  117. }
  118. void OnPlayModeStateChanged(PlayModeStateChange playModeState)
  119. {
  120. // case 923506 - make sure we save view data before switching modes
  121. if (playModeState == PlayModeStateChange.ExitingEditMode ||
  122. playModeState == PlayModeStateChange.ExitingPlayMode)
  123. TimelineWindowViewPrefs.SaveAll();
  124. bool isPlaymodeAboutToChange = playModeState == PlayModeStateChange.ExitingEditMode || playModeState == PlayModeStateChange.ExitingPlayMode;
  125. // Important to stop the graph on any director so temporary objects are properly cleaned up
  126. if (isPlaymodeAboutToChange && state != null)
  127. state.Stop();
  128. }
  129. UndoPropertyModification[] PostprocessAnimationRecordingModifications(UndoPropertyModification[] modifications)
  130. {
  131. DirtyModifiedObjects(modifications);
  132. var remaining = TimelineRecording.ProcessUndoModification(modifications, state);
  133. // if we've changed, we need to repaint the sequence window to show clip length changes
  134. if (remaining != modifications)
  135. {
  136. // only update if us or the sequencer window has focus
  137. // Prevents color pickers and other dialogs from being wrongly dismissed
  138. bool repaint = (focusedWindow == null) ||
  139. (focusedWindow is InspectorWindow) ||
  140. (focusedWindow is TimelineWindow);
  141. if (repaint)
  142. Repaint();
  143. }
  144. return remaining;
  145. }
  146. void DirtyModifiedObjects(UndoPropertyModification[] modifications)
  147. {
  148. foreach (var m in modifications)
  149. {
  150. if (m.currentValue == null || m.currentValue.target == null)
  151. continue;
  152. var track = m.currentValue.target as TrackAsset;
  153. var playableAsset = m.currentValue.target as PlayableAsset;
  154. var editorClip = m.currentValue.target as EditorClip;
  155. if (track != null)
  156. {
  157. track.MarkDirty();
  158. }
  159. else if (playableAsset != null)
  160. {
  161. var clip = TimelineRecording.FindClipWithAsset(state.editSequence.asset, playableAsset);
  162. if (clip != null)
  163. clip.MarkDirty();
  164. }
  165. else if (editorClip != null && editorClip.clip != null)
  166. {
  167. editorClip.clip.MarkDirty();
  168. }
  169. }
  170. }
  171. UndoPropertyModification[] ProcessAssetModifications(UndoPropertyModification[] modifications)
  172. {
  173. bool rebuildGraph = false;
  174. for (int i = 0; i < modifications.Length && !rebuildGraph; i++)
  175. {
  176. var mod = modifications[i];
  177. // check if an Avatar Mask has been modified
  178. if (mod.previousValue != null && mod.previousValue.target is AvatarMask)
  179. {
  180. rebuildGraph = state.editSequence.asset != null &&
  181. state.editSequence.asset.flattenedTracks
  182. .OfType<UnityEngine.Timeline.AnimationTrack>()
  183. .Any(x => mod.previousValue.target == x.avatarMask);
  184. }
  185. }
  186. if (rebuildGraph)
  187. {
  188. state.rebuildGraph = true;
  189. Repaint();
  190. }
  191. return modifications;
  192. }
  193. void OnUndoRedo()
  194. {
  195. var undos = new List<string>();
  196. var redos = new List<string>();
  197. Undo.GetRecords(undos, redos);
  198. var rebuildAll = redos.Any(x => x.StartsWith("Timeline ")) || undos.Any(x => x.StartsWith("Timeline"));
  199. var evalNow = redos.Any(x => x.Contains("Edit Curve")) || undos.Any(x => x.Contains("Edit Curve"));
  200. if (rebuildAll || evalNow)
  201. {
  202. ValidateSelection();
  203. if (state != null)
  204. {
  205. if (evalNow) // when curves change, the new values need to be set in the transform before the inspector handles the undo
  206. state.EvaluateImmediate();
  207. if (rebuildAll)
  208. state.Refresh();
  209. }
  210. Repaint();
  211. }
  212. }
  213. static void ValidateSelection()
  214. {
  215. //get all the clips in the selection
  216. var selectedClips = Selection.GetFiltered<EditorClip>(SelectionMode.Unfiltered).Select(x => x.clip);
  217. foreach (var selectedClip in selectedClips)
  218. {
  219. var parent = selectedClip.parentTrack;
  220. if (selectedClip.parentTrack != null)
  221. {
  222. if (!parent.clips.Contains(selectedClip))
  223. {
  224. SelectionManager.Remove(selectedClip);
  225. }
  226. }
  227. }
  228. }
  229. void OnComponentWasAdded(Component c)
  230. {
  231. m_ComponentAddedFrame = Time.frameCount;
  232. var go = c.gameObject;
  233. foreach (var seq in state.GetAllSequences())
  234. {
  235. if (seq.director == null || seq.asset == null)
  236. {
  237. return;
  238. }
  239. var rebind = seq.asset.GetOutputTracks().Any(track => seq.director.GetGenericBinding(track) == go);
  240. // Either the playable director has a binding for the GameObject or it is a sibling of the director.
  241. // The second case is needed since we have timeline top level markerTracks that do not have a binding, but
  242. // are still "targeting" the playable director
  243. if (rebind || seq.director.gameObject == go)
  244. {
  245. seq.director.RebindPlayableGraphOutputs();
  246. }
  247. }
  248. }
  249. }
  250. }