| using System; |
| using System.Collections.Generic; |
| using System.Linq; |
| using UnityEngine.Timeline; |
| using UnityEngine.Playables; |
|
|
| namespace UnityEditor.Timeline |
| { |
| static class ClipModifier |
| { |
| public static bool Delete(TimelineAsset timeline, TimelineClip clip) |
| { |
| return timeline.DeleteClip(clip); |
| } |
|
|
| public static bool Tile(IEnumerable<TimelineClip> clips) |
| { |
| if (clips.Count() < 2) |
| return false; |
|
|
| var clipsByTracks = clips.GroupBy(x => x.GetParentTrack()) |
| .Select(track => new { track.Key, Items = track.OrderBy(c => c.start) }); |
|
|
| foreach (var track in clipsByTracks) |
| { |
| UndoExtensions.RegisterTrack(track.Key, L10n.Tr("Tile")); |
| } |
|
|
| foreach (var track in clipsByTracks) |
| { |
| double newStart = track.Items.First().start; |
| foreach (var c in track.Items) |
| { |
| c.start = newStart; |
| newStart += c.duration; |
| } |
| } |
|
|
| return true; |
| } |
|
|
| public static bool TrimStart(IEnumerable<TimelineClip> clips, double trimTime) |
| { |
| var result = false; |
|
|
| foreach (var clip in clips) |
| result |= TrimStart(clip, trimTime); |
|
|
| return result; |
| } |
|
|
| public static bool TrimStart(TimelineClip clip, double trimTime) |
| { |
| if (clip.asset == null) |
| return false; |
|
|
| if (clip.start > trimTime) |
| return false; |
|
|
| if (clip.end < trimTime) |
| return false; |
|
|
| UndoExtensions.RegisterClip(clip, L10n.Tr("Trim Clip Start")); |
|
|
| |
| |
| SetStart(clip, trimTime, false); |
| clip.ConformEaseValues(); |
|
|
| return true; |
| } |
|
|
| public static bool TrimEnd(IEnumerable<TimelineClip> clips, double trimTime) |
| { |
| var result = false; |
|
|
| foreach (var clip in clips) |
| result |= TrimEnd(clip, trimTime); |
|
|
| return result; |
| } |
|
|
| public static bool TrimEnd(TimelineClip clip, double trimTime) |
| { |
| if (clip.asset == null) |
| return false; |
|
|
| if (clip.start > trimTime) |
| return false; |
|
|
| if (clip.end < trimTime) |
| return false; |
|
|
| UndoExtensions.RegisterClip(clip, L10n.Tr("Trim Clip End")); |
| TrimClipWithEditMode(clip, TrimEdge.End, trimTime); |
|
|
| return true; |
| } |
|
|
| public static bool MatchDuration(IEnumerable<TimelineClip> clips) |
| { |
| double referenceDuration = clips.First().duration; |
| UndoExtensions.RegisterClips(clips, L10n.Tr("Match Clip Duration")); |
| foreach (var clip in clips) |
| { |
| var newEnd = clip.start + referenceDuration; |
| TrimClipWithEditMode(clip, TrimEdge.End, newEnd); |
| } |
|
|
| return true; |
| } |
|
|
| public static bool Split(IEnumerable<TimelineClip> clips, double splitTime, PlayableDirector director) |
| { |
| var result = false; |
|
|
| foreach (var clip in clips) |
| { |
| if (clip.start >= splitTime) |
| continue; |
|
|
| if (clip.end <= splitTime) |
| continue; |
|
|
| UndoExtensions.RegisterClip(clip, L10n.Tr("Split Clip")); |
|
|
| TimelineClip newClip = TimelineHelpers.Clone(clip, director, director, clip.start); |
|
|
| clip.easeInDuration = 0; |
| newClip.easeOutDuration = 0; |
|
|
| SetStart(clip, splitTime, false); |
| SetEnd(newClip, splitTime, false); |
|
|
| |
| clip.GetParentTrack().SortClips(); |
|
|
| result = true; |
| } |
|
|
| return result; |
| } |
|
|
| public static void SetStart(TimelineClip clip, double time, bool affectTimeScale) |
| { |
| var supportsClipIn = clip.SupportsClipIn(); |
| var supportsPadding = TimelineUtility.IsRecordableAnimationClip(clip); |
| bool calculateTimeScale = (affectTimeScale && clip.SupportsSpeedMultiplier()); |
|
|
| |
| if (supportsPadding && (clip.animationClip == null || clip.animationClip.empty)) |
| { |
| supportsClipIn = false; |
| } |
|
|
| if (supportsClipIn && !supportsPadding && !calculateTimeScale) |
| { |
| var minStart = clip.FromLocalTimeUnbound(0.0); |
| if (time < minStart) |
| time = minStart; |
| } |
|
|
| var maxStart = clip.end - TimelineClip.kMinDuration; |
| if (time > maxStart) |
| time = maxStart; |
|
|
| var timeOffset = time - clip.start; |
| var duration = clip.duration - timeOffset; |
|
|
| if (calculateTimeScale) |
| { |
| var f = clip.duration / duration; |
| clip.timeScale *= f; |
| } |
|
|
|
|
| if (supportsClipIn && !calculateTimeScale) |
| { |
| if (supportsPadding) |
| { |
| double clipInGlobal = clip.clipIn / clip.timeScale; |
| double keyShift = -timeOffset; |
| if (timeOffset < 0) |
| { |
| double clipInDelta = Math.Max(-clipInGlobal, timeOffset); |
| keyShift = -Math.Min(0, timeOffset - clipInDelta); |
| clip.clipIn += clipInDelta * clip.timeScale; |
| } |
| else if (timeOffset > 0) |
| { |
| var clipInfo = AnimationClipCurveCache.Instance.GetCurveInfo(clip.animationClip); |
| double keyDelta = clip.FromLocalTimeUnbound(clipInfo.keyTimes.Min()) - clip.start; |
| keyShift = -Math.Max(0, Math.Min(timeOffset, keyDelta)); |
| clip.clipIn += Math.Max(timeOffset + keyShift, 0) * clip.timeScale; |
| } |
| if (keyShift != 0) |
| { |
| AnimationTrackRecorder.ShiftAnimationClip(clip.animationClip, (float)(keyShift * clip.timeScale)); |
| } |
| } |
| else |
| { |
| clip.clipIn += timeOffset * clip.timeScale; |
| } |
| } |
|
|
| clip.start = time; |
| clip.duration = duration; |
| } |
|
|
| public static void SetEnd(TimelineClip clip, double time, bool affectTimeScale) |
| { |
| var duration = Math.Max(time - clip.start, TimelineClip.kMinDuration); |
|
|
| if (affectTimeScale && clip.SupportsSpeedMultiplier()) |
| { |
| var f = clip.duration / duration; |
| clip.timeScale *= f; |
| } |
|
|
| clip.duration = duration; |
| } |
|
|
| public static bool ResetEditing(IEnumerable<TimelineClip> clips) |
| { |
| var result = false; |
|
|
| foreach (var clip in clips) |
| result = result || ResetEditing(clip); |
|
|
| return result; |
| } |
|
|
| public static bool ResetEditing(TimelineClip clip) |
| { |
| if (clip.asset == null) |
| return false; |
|
|
| UndoExtensions.RegisterClip(clip, L10n.Tr("Reset Clip Editing")); |
|
|
| clip.clipIn = 0.0; |
|
|
| if (clip.clipAssetDuration < double.MaxValue) |
| { |
| var duration = clip.clipAssetDuration / clip.timeScale; |
| TrimClipWithEditMode(clip, TrimEdge.End, clip.start + duration); |
| } |
|
|
| return true; |
| } |
|
|
| public static bool MatchContent(IEnumerable<TimelineClip> clips) |
| { |
| var result = false; |
|
|
| foreach (var clip in clips) |
| result |= MatchContent(clip); |
|
|
| return result; |
| } |
|
|
| public static bool MatchContent(TimelineClip clip) |
| { |
| if (clip.asset == null) |
| return false; |
|
|
| UndoExtensions.RegisterClip(clip, L10n.Tr("Match Clip Content")); |
|
|
| var newStartCandidate = clip.start - clip.clipIn / clip.timeScale; |
| var newStart = newStartCandidate < 0.0 ? 0.0 : newStartCandidate; |
|
|
| TrimClipWithEditMode(clip, TrimEdge.Start, newStart); |
|
|
| |
| clip.clipIn = (clip.start - newStartCandidate) * clip.timeScale; |
| if (clip.clipAssetDuration > 0 && TimelineHelpers.HasUsableAssetDuration(clip)) |
| { |
| var duration = TimelineHelpers.GetLoopDuration(clip); |
| var offset = (clip.clipIn / clip.timeScale) % duration; |
| TrimClipWithEditMode(clip, TrimEdge.End, clip.start - offset + duration); |
| } |
|
|
| return true; |
| } |
|
|
| public static void TrimClipWithEditMode(TimelineClip clip, TrimEdge edge, double time) |
| { |
| var clipItem = ItemsUtils.ToItem(clip); |
| EditMode.BeginTrim(clipItem, edge); |
| if (edge == TrimEdge.Start) |
| EditMode.TrimStart(clipItem, time, false); |
| else |
| EditMode.TrimEnd(clipItem, time, false); |
| EditMode.FinishTrim(); |
| } |
|
|
| public static bool CompleteLastLoop(IEnumerable<TimelineClip> clips) |
| { |
| foreach (var clip in clips) |
| { |
| CompleteLastLoop(clip); |
| } |
|
|
| return true; |
| } |
|
|
| public static void CompleteLastLoop(TimelineClip clip) |
| { |
| FixLoops(clip, true); |
| } |
|
|
| public static bool TrimLastLoop(IEnumerable<TimelineClip> clips) |
| { |
| foreach (var clip in clips) |
| { |
| TrimLastLoop(clip); |
| } |
|
|
| return true; |
| } |
|
|
| public static void TrimLastLoop(TimelineClip clip) |
| { |
| FixLoops(clip, false); |
| } |
|
|
| static void FixLoops(TimelineClip clip, bool completeLastLoop) |
| { |
| if (!TimelineHelpers.HasUsableAssetDuration(clip)) |
| return; |
|
|
| var loopDuration = TimelineHelpers.GetLoopDuration(clip); |
| var firstLoopDuration = loopDuration - clip.clipIn * (1.0 / clip.timeScale); |
|
|
| |
| if (!completeLastLoop && firstLoopDuration > clip.duration) |
| return; |
|
|
| var numLoops = (clip.duration - firstLoopDuration) / loopDuration; |
| var numCompletedLoops = Math.Floor(numLoops); |
|
|
| if (!(numCompletedLoops < numLoops)) |
| return; |
|
|
| if (completeLastLoop) |
| numCompletedLoops += 1; |
|
|
| var newEnd = clip.start + firstLoopDuration + loopDuration * numCompletedLoops; |
|
|
| UndoExtensions.RegisterClip(clip, L10n.Tr("Trim Clip Last Loop")); |
|
|
| TrimClipWithEditMode(clip, TrimEdge.End, newEnd); |
| } |
|
|
| public static bool DoubleSpeed(IEnumerable<TimelineClip> clips) |
| { |
| foreach (var clip in clips) |
| { |
| if (clip.SupportsSpeedMultiplier()) |
| { |
| UndoExtensions.RegisterClip(clip, L10n.Tr("Double Clip Speed")); |
| clip.timeScale = clip.timeScale * 2.0f; |
| } |
| } |
|
|
| return true; |
| } |
|
|
| public static bool HalfSpeed(IEnumerable<TimelineClip> clips) |
| { |
| foreach (var clip in clips) |
| { |
| if (clip.SupportsSpeedMultiplier()) |
| { |
| UndoExtensions.RegisterClip(clip, L10n.Tr("Half Clip Speed")); |
| clip.timeScale = clip.timeScale * 0.5f; |
| } |
| } |
|
|
| return true; |
| } |
|
|
| public static bool ResetSpeed(IEnumerable<TimelineClip> clips) |
| { |
| foreach (var clip in clips) |
| { |
| if (clip.timeScale != 1.0) |
| { |
| UndoExtensions.RegisterClip(clip, L10n.Tr("Reset Clip Speed")); |
| clip.timeScale = 1.0; |
| } |
| } |
|
|
| return true; |
| } |
| } |
| } |
|
|