TimelineWindow_EditorCallbacks.cs
11.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
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();
}
}
}
}
}