Maze / Library /PackageCache /com.unity.timeline@1.6.5 /Editor /Window /TimelineWindow_EditorCallbacks.cs
| 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; | |
| TimelinePlayable.playableLooped += OnPlayableLooped; | |
| } | |
| // 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; | |
| TimelinePlayable.playableLooped -= OnPlayableLooped; | |
| } | |
| 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(); | |
| } | |
| } | |
| } | |
| 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); | |
| } | |
| } | |
| } | |