No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TimelineWindow_EditorCallbacks.cs 14KB

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