A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using UnityEngine.Timeline;
5using UnityEngine.Playables;
6
7namespace UnityEditor.Timeline
8{
9 static class ClipModifier
10 {
11 public static bool Delete(TimelineAsset timeline, TimelineClip clip)
12 {
13 return timeline.DeleteClip(clip);
14 }
15
16 public static bool Tile(IEnumerable<TimelineClip> clips)
17 {
18 if (clips.Count() < 2)
19 return false;
20
21 var clipsByTracks = clips.GroupBy(x => x.GetParentTrack())
22 .Select(track => new { track.Key, Items = track.OrderBy(c => c.start) });
23
24 foreach (var track in clipsByTracks)
25 {
26 UndoExtensions.RegisterTrack(track.Key, L10n.Tr("Tile"));
27 }
28
29 foreach (var track in clipsByTracks)
30 {
31 double newStart = track.Items.First().start;
32 foreach (var c in track.Items)
33 {
34 c.start = newStart;
35 newStart += c.duration;
36 }
37 }
38
39 return true;
40 }
41
42 public static bool TrimStart(IEnumerable<TimelineClip> clips, double trimTime)
43 {
44 var result = false;
45
46 foreach (var clip in clips)
47 result |= TrimStart(clip, trimTime);
48
49 return result;
50 }
51
52 public static bool TrimStart(TimelineClip clip, double trimTime)
53 {
54 if (clip.asset == null)
55 return false;
56
57 if (clip.start > trimTime)
58 return false;
59
60 if (clip.end < trimTime)
61 return false;
62
63 UndoExtensions.RegisterClip(clip, L10n.Tr("Trim Clip Start"));
64
65 // Note: We are NOT using edit modes in this case because we want the same result
66 // regardless of the selected EditMode: split at cursor and delete left part
67 SetStart(clip, trimTime, false);
68 clip.ConformEaseValues();
69
70 return true;
71 }
72
73 public static bool TrimEnd(IEnumerable<TimelineClip> clips, double trimTime)
74 {
75 var result = false;
76
77 foreach (var clip in clips)
78 result |= TrimEnd(clip, trimTime);
79
80 return result;
81 }
82
83 public static bool TrimEnd(TimelineClip clip, double trimTime)
84 {
85 if (clip.asset == null)
86 return false;
87
88 if (clip.start > trimTime)
89 return false;
90
91 if (clip.end < trimTime)
92 return false;
93
94 UndoExtensions.RegisterClip(clip, L10n.Tr("Trim Clip End"));
95 TrimClipWithEditMode(clip, TrimEdge.End, trimTime);
96
97 return true;
98 }
99
100 public static bool MatchDuration(IEnumerable<TimelineClip> clips)
101 {
102 double referenceDuration = clips.First().duration;
103 UndoExtensions.RegisterClips(clips, L10n.Tr("Match Clip Duration"));
104 foreach (var clip in clips)
105 {
106 var newEnd = clip.start + referenceDuration;
107 TrimClipWithEditMode(clip, TrimEdge.End, newEnd);
108 }
109
110 return true;
111 }
112
113 public static bool Split(IEnumerable<TimelineClip> clips, double splitTime, PlayableDirector director)
114 {
115 var result = false;
116
117 foreach (var clip in clips)
118 {
119 if (clip.start >= splitTime)
120 continue;
121
122 if (clip.end <= splitTime)
123 continue;
124
125 UndoExtensions.RegisterClip(clip, L10n.Tr("Split Clip"));
126
127 TimelineClip newClip = TimelineHelpers.Clone(clip, director, director, clip.start);
128
129 clip.easeInDuration = 0;
130 newClip.easeOutDuration = 0;
131
132 SetStart(clip, splitTime, false);
133 SetEnd(newClip, splitTime, false);
134
135 // Sort produced by cloning clips on top of each other is unpredictable (it varies between mono runtimes)
136 clip.GetParentTrack().SortClips();
137
138 result = true;
139 }
140
141 return result;
142 }
143
144 public static void SetStart(TimelineClip clip, double time, bool affectTimeScale)
145 {
146 var supportsClipIn = clip.SupportsClipIn();
147 var supportsPadding = TimelineUtility.IsRecordableAnimationClip(clip);
148 bool calculateTimeScale = (affectTimeScale && clip.SupportsSpeedMultiplier());
149
150 // treat empty recordable clips as not supporting clip in (there are no keys to modify)
151 if (supportsPadding && (clip.animationClip == null || clip.animationClip.empty))
152 {
153 supportsClipIn = false;
154 }
155
156 if (supportsClipIn && !supportsPadding && !calculateTimeScale)
157 {
158 var minStart = clip.FromLocalTimeUnbound(0.0);
159 if (time < minStart)
160 time = minStart;
161 }
162
163 var maxStart = clip.end - TimelineClip.kMinDuration;
164 if (time > maxStart)
165 time = maxStart;
166
167 var timeOffset = time - clip.start;
168 var duration = clip.duration - timeOffset;
169
170 if (calculateTimeScale)
171 {
172 var f = clip.duration / duration;
173 clip.timeScale *= f;
174 }
175
176
177 if (supportsClipIn && !calculateTimeScale)
178 {
179 if (supportsPadding)
180 {
181 double clipInGlobal = clip.clipIn / clip.timeScale;
182 double keyShift = -timeOffset;
183 if (timeOffset < 0) // left drag, eliminate clipIn before shifting
184 {
185 double clipInDelta = Math.Max(-clipInGlobal, timeOffset);
186 keyShift = -Math.Min(0, timeOffset - clipInDelta);
187 clip.clipIn += clipInDelta * clip.timeScale;
188 }
189 else if (timeOffset > 0) // right drag, elimate padding in animation clip before adding clip in
190 {
191 var clipInfo = AnimationClipCurveCache.Instance.GetCurveInfo(clip.animationClip);
192 double keyDelta = clip.FromLocalTimeUnbound(clipInfo.keyTimes.Min()) - clip.start;
193 keyShift = -Math.Max(0, Math.Min(timeOffset, keyDelta));
194 clip.clipIn += Math.Max(timeOffset + keyShift, 0) * clip.timeScale;
195 }
196 if (keyShift != 0)
197 {
198 AnimationTrackRecorder.ShiftAnimationClip(clip.animationClip, (float)(keyShift * clip.timeScale));
199 }
200 }
201 else
202 {
203 clip.clipIn += timeOffset * clip.timeScale;
204 }
205 }
206
207 clip.start = time;
208 clip.duration = duration;
209 }
210
211 public static void SetEnd(TimelineClip clip, double time, bool affectTimeScale)
212 {
213 var duration = Math.Max(time - clip.start, TimelineClip.kMinDuration);
214
215 if (affectTimeScale && clip.SupportsSpeedMultiplier())
216 {
217 var f = clip.duration / duration;
218 clip.timeScale *= f;
219 }
220
221 clip.duration = duration;
222 }
223
224 public static bool ResetEditing(IEnumerable<TimelineClip> clips)
225 {
226 var result = false;
227
228 foreach (var clip in clips)
229 result = result || ResetEditing(clip);
230
231 return result;
232 }
233
234 public static bool ResetEditing(TimelineClip clip)
235 {
236 if (clip.asset == null)
237 return false;
238
239 UndoExtensions.RegisterClip(clip, L10n.Tr("Reset Clip Editing"));
240
241 clip.clipIn = 0.0;
242
243 if (clip.clipAssetDuration < double.MaxValue)
244 {
245 var duration = clip.clipAssetDuration / clip.timeScale;
246 TrimClipWithEditMode(clip, TrimEdge.End, clip.start + duration);
247 }
248
249 return true;
250 }
251
252 public static bool MatchContent(IEnumerable<TimelineClip> clips)
253 {
254 var result = false;
255
256 foreach (var clip in clips)
257 result |= MatchContent(clip);
258
259 return result;
260 }
261
262 public static bool MatchContent(TimelineClip clip)
263 {
264 if (clip.asset == null)
265 return false;
266
267 UndoExtensions.RegisterClip(clip, L10n.Tr("Match Clip Content"));
268
269 var newStartCandidate = clip.start - clip.clipIn / clip.timeScale;
270 var newStart = newStartCandidate < 0.0 ? 0.0 : newStartCandidate;
271
272 TrimClipWithEditMode(clip, TrimEdge.Start, newStart);
273
274 // In case resetting the start was blocked by edit mode or timeline start, we do the best we can
275 clip.clipIn = (clip.start - newStartCandidate) * clip.timeScale;
276 if (clip.clipAssetDuration > 0 && TimelineHelpers.HasUsableAssetDuration(clip))
277 {
278 var duration = TimelineHelpers.GetLoopDuration(clip);
279 var offset = (clip.clipIn / clip.timeScale) % duration;
280 TrimClipWithEditMode(clip, TrimEdge.End, clip.start - offset + duration);
281 }
282
283 return true;
284 }
285
286 public static void TrimClipWithEditMode(TimelineClip clip, TrimEdge edge, double time)
287 {
288 var clipItem = ItemsUtils.ToItem(clip);
289 EditMode.BeginTrim(clipItem, edge);
290 if (edge == TrimEdge.Start)
291 EditMode.TrimStart(clipItem, time, false);
292 else
293 EditMode.TrimEnd(clipItem, time, false);
294 EditMode.FinishTrim();
295 }
296
297 public static bool CompleteLastLoop(IEnumerable<TimelineClip> clips)
298 {
299 foreach (var clip in clips)
300 {
301 CompleteLastLoop(clip);
302 }
303
304 return true;
305 }
306
307 public static void CompleteLastLoop(TimelineClip clip)
308 {
309 FixLoops(clip, true);
310 }
311
312 public static bool TrimLastLoop(IEnumerable<TimelineClip> clips)
313 {
314 foreach (var clip in clips)
315 {
316 TrimLastLoop(clip);
317 }
318
319 return true;
320 }
321
322 public static void TrimLastLoop(TimelineClip clip)
323 {
324 FixLoops(clip, false);
325 }
326
327 static void FixLoops(TimelineClip clip, bool completeLastLoop)
328 {
329 if (!TimelineHelpers.HasUsableAssetDuration(clip))
330 return;
331
332 var loopDuration = TimelineHelpers.GetLoopDuration(clip);
333 var firstLoopDuration = loopDuration - clip.clipIn * (1.0 / clip.timeScale);
334
335 // Making sure we don't trim to zero
336 if (!completeLastLoop && firstLoopDuration > clip.duration)
337 return;
338
339 var numLoops = (clip.duration - firstLoopDuration) / loopDuration;
340 var numCompletedLoops = Math.Floor(numLoops);
341
342 if (!(numCompletedLoops < numLoops))
343 return;
344
345 if (completeLastLoop)
346 numCompletedLoops += 1;
347
348 var newEnd = clip.start + firstLoopDuration + loopDuration * numCompletedLoops;
349
350 UndoExtensions.RegisterClip(clip, L10n.Tr("Trim Clip Last Loop"));
351
352 TrimClipWithEditMode(clip, TrimEdge.End, newEnd);
353 }
354
355 public static bool DoubleSpeed(IEnumerable<TimelineClip> clips)
356 {
357 foreach (var clip in clips)
358 {
359 if (clip.SupportsSpeedMultiplier())
360 {
361 UndoExtensions.RegisterClip(clip, L10n.Tr("Double Clip Speed"));
362 clip.timeScale = clip.timeScale * 2.0f;
363 }
364 }
365
366 return true;
367 }
368
369 public static bool HalfSpeed(IEnumerable<TimelineClip> clips)
370 {
371 foreach (var clip in clips)
372 {
373 if (clip.SupportsSpeedMultiplier())
374 {
375 UndoExtensions.RegisterClip(clip, L10n.Tr("Half Clip Speed"));
376 clip.timeScale = clip.timeScale * 0.5f;
377 }
378 }
379
380 return true;
381 }
382
383 public static bool ResetSpeed(IEnumerable<TimelineClip> clips)
384 {
385 foreach (var clip in clips)
386 {
387 if (clip.timeScale != 1.0)
388 {
389 UndoExtensions.RegisterClip(clip, L10n.Tr("Reset Clip Speed"));
390 clip.timeScale = 1.0;
391 }
392 }
393
394 return true;
395 }
396 }
397}