TimelineWindow_EditorCallbacks.cs 11 KB

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