123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using UnityEditor.SceneManagement;
- 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;
- }
- 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;
- }
- 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 || state.rebuildGraph)
- 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];
- // check if an Avatar Mask has been modified
- if (mod.previousValue != null && mod.previousValue.target is AvatarMask)
- {
- 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.parentTrack;
- if (selectedClip.parentTrack != 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();
- }
- }
- }
- }
- }
|