349 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			349 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.Linq;
 | |
| using UnityEditor.SceneManagement;
 | |
| using UnityEditor.ShortcutManagement;
 | |
| using UnityEditor.Timeline.Actions;
 | |
| using UnityEngine;
 | |
| using UnityEngine.Animations;
 | |
| using UnityEngine.Playables;
 | |
| using UnityEngine.SceneManagement;
 | |
| using UnityEngine.Timeline;
 | |
| 
 | |
| namespace UnityEditor.Timeline
 | |
| {
 | |
|     partial class TimelineWindow
 | |
|     {
 | |
|         private int m_ComponentAddedFrame;
 | |
| 
 | |
|         void OnSelectionChangedInactive()
 | |
|         {
 | |
|             // Case  946942 -- when selection changes and the window is open but hidden, timeline
 | |
|             // needs to update selection immediately so preview mode is correctly released
 | |
|             // Case 1123119 -- except when recording
 | |
|             if (!hasFocus)
 | |
|             {
 | |
|                 RefreshSelection(!locked && state != null && !state.recording);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void InitializeEditorCallbacks()
 | |
|         {
 | |
|             Undo.postprocessModifications += PostprocessAnimationRecordingModifications;
 | |
|             Undo.postprocessModifications += ProcessAssetModifications;
 | |
|             Undo.undoRedoPerformed += OnUndoRedo;
 | |
|             EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
 | |
|             AnimationUtility.onCurveWasModified += OnCurveModified;
 | |
|             EditorApplication.editorApplicationQuit += OnEditorQuit;
 | |
|             Selection.selectionChanged += OnSelectionChangedInactive;
 | |
|             EditorSceneManager.sceneSaved += OnSceneSaved;
 | |
|             ObjectFactory.componentWasAdded += OnComponentWasAdded;
 | |
|             PrefabUtility.prefabInstanceUpdated += OnPrefabApplied;
 | |
|             EditorApplication.pauseStateChanged += OnPlayModePause;
 | |
|             EditorApplication.globalEventHandler += GlobalEventHandler;
 | |
| #if TIMELINE_FRAMEACCURATE
 | |
|             TimelinePlayable.playableLooped += OnPlayableLooped;
 | |
| #endif
 | |
|         }
 | |
| 
 | |
|         // This callback is needed because the Animation window registers "Animation/Key Selected" as a global hotkey
 | |
|         // and we want  to also react to the key.
 | |
|         void GlobalEventHandler()
 | |
|         {
 | |
|             if (instance == null || !state.previewMode)
 | |
|             {
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             var keyBinding = ShortcutManager.instance.GetShortcutBinding("Animation/Key Selected");
 | |
|             if (keyBinding.Equals(ShortcutBinding.empty))
 | |
|             {
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             var evtCombo = KeyCombination.FromKeyboardInput(Event.current);
 | |
|             if (keyBinding.keyCombinationSequence.Contains(evtCombo))
 | |
|             {
 | |
|                 Invoker.InvokeWithSelected<KeyAllAnimated>();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void OnEditorQuit()
 | |
|         {
 | |
|             TimelineWindowViewPrefs.SaveAll();
 | |
|         }
 | |
| 
 | |
|         void RemoveEditorCallbacks()
 | |
|         {
 | |
|             EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
 | |
| 
 | |
|             Undo.undoRedoPerformed -= OnUndoRedo;
 | |
|             Undo.postprocessModifications -= PostprocessAnimationRecordingModifications;
 | |
|             Undo.postprocessModifications -= ProcessAssetModifications;
 | |
|             AnimationUtility.onCurveWasModified -= OnCurveModified;
 | |
|             EditorApplication.editorApplicationQuit -= OnEditorQuit;
 | |
|             Selection.selectionChanged -= OnSelectionChangedInactive;
 | |
|             EditorSceneManager.sceneSaved -= OnSceneSaved;
 | |
|             ObjectFactory.componentWasAdded -= OnComponentWasAdded;
 | |
|             PrefabUtility.prefabInstanceUpdated -= OnPrefabApplied;
 | |
|             EditorApplication.pauseStateChanged -= OnPlayModePause;
 | |
|             EditorApplication.globalEventHandler -= GlobalEventHandler;
 | |
| #if TIMELINE_FRAMEACCURATE
 | |
|             TimelinePlayable.playableLooped -= OnPlayableLooped;
 | |
| #endif
 | |
|         }
 | |
| 
 | |
|         void OnPlayModePause(PauseState state)
 | |
|         {
 | |
|             // in PlayMode, if the timeline is playing, a constant repaint cycle occurs. Pausing the editor
 | |
|             // breaks the cycle, so this will restart it
 | |
|             Repaint();
 | |
|         }
 | |
| 
 | |
|         // Called when a prefab change is applied to the scene.
 | |
|         // Redraw so control tracks that use prefabs can show changes
 | |
|         void OnPrefabApplied(GameObject go)
 | |
|         {
 | |
|             if (!state.previewMode)
 | |
|                 return;
 | |
| 
 | |
|             // if we added a component this frame, then rebuild, otherwise just let
 | |
|             //  the individual playable handle the prefab application
 | |
|             if (Time.frameCount == m_ComponentAddedFrame)
 | |
|                 TimelineEditor.Refresh(RefreshReason.ContentsModified);
 | |
|             else
 | |
|                 TimelineEditor.Refresh(RefreshReason.SceneNeedsUpdate);
 | |
|         }
 | |
| 
 | |
|         // When the scene is save the director time will get reset.
 | |
|         void OnSceneSaved(Scene scene)
 | |
|         {
 | |
|             if (state != null)
 | |
|                 state.OnSceneSaved();
 | |
|         }
 | |
| 
 | |
|         void OnCurveModified(AnimationClip clip, EditorCurveBinding binding, AnimationUtility.CurveModifiedType type)
 | |
|         {
 | |
|             InspectorWindow.RepaintAllInspectors();
 | |
|             if (state == null)
 | |
|                 return;
 | |
| 
 | |
|             //Force refresh of curve when modified by another editor.
 | |
|             Repaint();
 | |
| 
 | |
|             if (state.previewMode == false)
 | |
|                 return;
 | |
| 
 | |
|             bool hasPlayable = m_PlayableLookup.GetPlayableFromAnimClip(clip, out Playable playable);
 | |
| 
 | |
|             // mark the timeline clip as dirty
 | |
|             TimelineClip timelineClip = m_PlayableLookup.GetTimelineClipFromCurves(clip);
 | |
|             if (timelineClip != null)
 | |
|                 timelineClip.MarkDirty();
 | |
| 
 | |
|             if (type == AnimationUtility.CurveModifiedType.CurveModified)
 | |
|             {
 | |
|                 if (hasPlayable)
 | |
|                 {
 | |
|                     playable.SetAnimatedProperties(clip);
 | |
|                 }
 | |
| 
 | |
|                 // updates the duration of the graph without rebuilding
 | |
|                 AnimationUtility.SyncEditorCurves(clip); // deleted keys are not synced when this is sent out, so duration could be incorrect
 | |
|                 state.UpdateRootPlayableDuration(state.editSequence.duration);
 | |
| 
 | |
|                 bool isRecording = TimelineRecording.IsRecordingAnimationTrack;
 | |
|                 PlayableDirector masterDirector = TimelineEditor.masterDirector;
 | |
|                 bool isGraphValid = masterDirector != null && masterDirector.playableGraph.IsValid();
 | |
| 
 | |
|                 // don't evaluate if this is caused by recording on an animation track, the extra evaluation can cause hiccups
 | |
|                 // Prevent graphs to be resurrected  by a changed clip.
 | |
|                 if (!isRecording && isGraphValid)
 | |
|                     state.Evaluate();
 | |
|             }
 | |
|             else if (EditorUtility.IsDirty(clip)) // curve added/removed, or clip added/removed
 | |
|             {
 | |
|                 state.rebuildGraph |= timelineClip != null || hasPlayable;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void OnPlayModeStateChanged(PlayModeStateChange playModeState)
 | |
|         {
 | |
|             // case 923506 - make sure we save view data before switching modes
 | |
|             if (playModeState == PlayModeStateChange.ExitingEditMode ||
 | |
|                 playModeState == PlayModeStateChange.ExitingPlayMode)
 | |
|                 TimelineWindowViewPrefs.SaveAll();
 | |
| 
 | |
|             bool isPlaymodeAboutToChange = playModeState == PlayModeStateChange.ExitingEditMode || playModeState == PlayModeStateChange.ExitingPlayMode;
 | |
| 
 | |
|             // Important to stop the graph on any director so temporary objects are properly cleaned up
 | |
|             if (isPlaymodeAboutToChange && state != null)
 | |
|                 state.Stop();
 | |
|         }
 | |
| 
 | |
|         UndoPropertyModification[] PostprocessAnimationRecordingModifications(UndoPropertyModification[] modifications)
 | |
|         {
 | |
|             DirtyModifiedObjects(modifications);
 | |
| 
 | |
|             var remaining = TimelineRecording.ProcessUndoModification(modifications, state);
 | |
|             // if we've changed, we need to repaint the sequence window to show clip length changes
 | |
|             if (remaining != modifications)
 | |
|             {
 | |
|                 // only update if us or the sequencer window has focus
 | |
|                 // Prevents color pickers and other dialogs from being wrongly dismissed
 | |
|                 bool repaint = (focusedWindow == null) ||
 | |
|                     (focusedWindow is InspectorWindow) ||
 | |
|                     (focusedWindow is TimelineWindow);
 | |
| 
 | |
|                 if (repaint)
 | |
|                     Repaint();
 | |
|             }
 | |
| 
 | |
| 
 | |
|             return remaining;
 | |
|         }
 | |
| 
 | |
|         void DirtyModifiedObjects(UndoPropertyModification[] modifications)
 | |
|         {
 | |
|             foreach (var m in modifications)
 | |
|             {
 | |
|                 if (m.currentValue == null || m.currentValue.target == null)
 | |
|                     continue;
 | |
| 
 | |
|                 var track = m.currentValue.target as TrackAsset;
 | |
|                 var playableAsset = m.currentValue.target as PlayableAsset;
 | |
|                 var editorClip = m.currentValue.target as EditorClip;
 | |
| 
 | |
|                 if (track != null)
 | |
|                 {
 | |
|                     track.MarkDirty();
 | |
|                 }
 | |
|                 else if (playableAsset != null)
 | |
|                 {
 | |
|                     var clip = TimelineRecording.FindClipWithAsset(state.editSequence.asset, playableAsset);
 | |
|                     if (clip != null)
 | |
|                     {
 | |
|                         clip.MarkDirty();
 | |
|                     }
 | |
|                 }
 | |
|                 else if (editorClip != null && editorClip.clip != null)
 | |
|                 {
 | |
|                     editorClip.clip.MarkDirty();
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         UndoPropertyModification[] ProcessAssetModifications(UndoPropertyModification[] modifications)
 | |
|         {
 | |
|             bool rebuildGraph = false;
 | |
| 
 | |
|             for (int i = 0; i < modifications.Length && !rebuildGraph; i++)
 | |
|             {
 | |
|                 var mod = modifications[i];
 | |
| 
 | |
|                 if (mod.currentValue != null && mod.currentValue.target is IMarker currentMarker)
 | |
|                 {
 | |
|                     if (currentMarker.parent != null && currentMarker.parent.timelineAsset == state.editSequence.asset)
 | |
|                     {
 | |
|                         if (mod.currentValue.target is INotification)
 | |
|                             TimelineEditor.Refresh(RefreshReason.ContentsModified);
 | |
|                         else
 | |
|                             TimelineEditor.Refresh(RefreshReason.WindowNeedsRedraw);
 | |
|                     }
 | |
|                 }
 | |
|                 else if (mod.previousValue != null && mod.previousValue.target is AvatarMask) // check if an Avatar Mask has been modified
 | |
|                 {
 | |
|                     rebuildGraph = state.editSequence.asset != null &&
 | |
|                         state.editSequence.asset.flattenedTracks
 | |
|                             .OfType<UnityEngine.Timeline.AnimationTrack>()
 | |
|                             .Any(x => mod.previousValue.target == x.avatarMask);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (rebuildGraph)
 | |
|             {
 | |
|                 state.rebuildGraph = true;
 | |
|                 Repaint();
 | |
|             }
 | |
| 
 | |
|             return modifications;
 | |
|         }
 | |
| 
 | |
|         void OnUndoRedo()
 | |
|         {
 | |
|             var undos = new List<string>();
 | |
|             var redos = new List<string>();
 | |
|             Undo.GetRecords(undos, redos);
 | |
| 
 | |
|             var rebuildAll = redos.Any(x => x.StartsWith("Timeline ")) || undos.Any(x => x.StartsWith("Timeline"));
 | |
|             var evalNow = redos.Any(x => x.Contains("Edit Curve")) || undos.Any(x => x.Contains("Edit Curve"));
 | |
|             if (rebuildAll || evalNow)
 | |
|             {
 | |
|                 ValidateSelection();
 | |
|                 if (state != null)
 | |
|                 {
 | |
|                     if (evalNow) // when curves change, the new values need to be set in the transform before the inspector handles the undo
 | |
|                         state.EvaluateImmediate();
 | |
|                     if (rebuildAll)
 | |
|                         state.Refresh();
 | |
|                 }
 | |
|                 Repaint();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         static void ValidateSelection()
 | |
|         {
 | |
|             //get all the clips in the selection
 | |
|             var selectedClips = Selection.GetFiltered<EditorClip>(SelectionMode.Unfiltered).Select(x => x.clip);
 | |
|             foreach (var selectedClip in selectedClips)
 | |
|             {
 | |
|                 var parent = selectedClip.GetParentTrack();
 | |
|                 if (selectedClip.GetParentTrack() != null)
 | |
|                 {
 | |
|                     if (!parent.clips.Contains(selectedClip))
 | |
|                     {
 | |
|                         SelectionManager.Remove(selectedClip);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void OnComponentWasAdded(Component c)
 | |
|         {
 | |
|             m_ComponentAddedFrame = Time.frameCount;
 | |
|             var go = c.gameObject;
 | |
|             foreach (var seq in state.GetAllSequences())
 | |
|             {
 | |
|                 if (seq.director == null || seq.asset == null)
 | |
|                 {
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 var rebind = seq.asset.GetOutputTracks().Any(track => seq.director.GetGenericBinding(track) == go);
 | |
|                 // Either the playable director has a binding for the GameObject or it is a sibling of the director.
 | |
|                 // The second case is needed since we have timeline top level markerTracks that do not have a binding, but
 | |
|                 // are still "targeting" the playable director
 | |
|                 if (rebind || seq.director.gameObject == go)
 | |
|                 {
 | |
|                     seq.director.RebindPlayableGraphOutputs();
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
| #if TIMELINE_FRAMEACCURATE
 | |
|         void OnPlayableLooped(Playable timelinePlayable)
 | |
|         {
 | |
|             if (state == null || !state.playing || state.masterSequence == null || state.masterSequence.director == null
 | |
|                 || !state.masterSequence.director.playableGraph.IsValid())
 | |
|                 return;
 | |
|             var masterPlayable = state.masterSequence.director.playableGraph.GetRootPlayable(0);
 | |
|             if (!masterPlayable.Equals(Playable.Null)
 | |
|                 && masterPlayable.Equals(timelinePlayable)
 | |
|                 && timelinePlayable.GetGraph().IsMatchFrameRateEnabled())
 | |
|                 timelinePlayable.SetTime(0);
 | |
|         }
 | |
| 
 | |
| #endif
 | |
|     }
 | |
| }
 |