using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using UnityEditor.Timeline.Actions; using UnityEngine; using UnityEngine.Timeline; using UnityEngine.Playables; namespace UnityEditor.Timeline { [MenuEntry("Edit in Animation Window", MenuPriority.ClipEditActionSection.editInAnimationWindow), UsedImplicitly] class EditClipInAnimationWindow : ClipAction { public override ActionValidity Validate(IEnumerable clips) { if (!GetEditableClip(clips, out _, out _)) return ActionValidity.NotApplicable; return ActionValidity.Valid; } public override bool Execute(IEnumerable clips) { TimelineClip clip; AnimationClip clipToEdit; if (!GetEditableClip(clips, out clip, out clipToEdit)) return false; GameObject gameObject = null; if (TimelineEditor.inspectedDirector != null) gameObject = TimelineUtility.GetSceneGameObject(TimelineEditor.inspectedDirector, clip.GetParentTrack()); var timeController = TimelineAnimationUtilities.CreateTimeController(clip); TimelineAnimationUtilities.EditAnimationClipWithTimeController( clipToEdit, timeController, clip.animationClip != null ? gameObject : null); return true; } private static bool GetEditableClip(IEnumerable clips, out TimelineClip clip, out AnimationClip animClip) { clip = null; animClip = null; if (clips.Count() != 1) return false; clip = clips.FirstOrDefault(); if (clip == null) return false; if (clip.animationClip != null) animClip = clip.animationClip; else if (clip.curves != null && !clip.curves.empty) animClip = clip.curves; return animClip != null; } } [MenuEntry("Edit Sub-Timeline", MenuPriority.ClipEditActionSection.editSubTimeline), UsedImplicitly] class EditSubTimeline : ClipAction { private static readonly string MultiItemPrefix = "Edit Sub-Timelines/"; private static readonly string SingleItemPrefix = "Edit "; public override ActionValidity Validate(IEnumerable clips) { if (clips == null || clips.Count() != 1 || TimelineEditor.inspectedDirector == null) return ActionValidity.NotApplicable; var clip = clips.First(); var directors = TimelineUtility.GetSubTimelines(clip, TimelineEditor.inspectedDirector); return directors.Any(x => x != null) ? ActionValidity.Valid : ActionValidity.NotApplicable; } public override bool Execute(IEnumerable clips) { if (Validate(clips) != ActionValidity.Valid) return false; var clip = clips.First(); var directors = TimelineUtility.GetSubTimelines(clip, TimelineEditor.inspectedDirector); ExecuteInternal(directors, 0, clip); return true; } static void ExecuteInternal(IList directors, int directorIndex, TimelineClip clip) { SelectionManager.Clear(); TimelineWindow.instance.SetCurrentTimeline(directors[directorIndex], clip); } internal void AddMenuItem(List menuItems) { var clips = TimelineEditor.selectedClips; if (clips == null || clips.Length != 1) return; var mode = TimelineWindow.instance.currentMode.mode; MenuEntryAttribute menuAttribute = GetType().GetCustomAttributes(typeof(MenuEntryAttribute), false).OfType().FirstOrDefault(); var menuItem = new MenuActionItem() { category = menuAttribute.subMenuPath ?? string.Empty, entryName = menuAttribute.name, isActiveInMode = this.IsActionActiveInMode(mode), priority = menuAttribute.priority, state = Validate(clips), callback = null }; var subDirectors = TimelineUtility.GetSubTimelines(clips[0], TimelineEditor.inspectedDirector); if (subDirectors.Count == 1) { menuItem.entryName = SingleItemPrefix + DisplayNameHelper.GetDisplayName(subDirectors[0]); menuItem.callback = () => { Execute(clips); }; menuItems.Add(menuItem); } else { for (int i = 0; i < subDirectors.Count; i++) { var index = i; menuItem.category = MultiItemPrefix; menuItem.entryName = DisplayNameHelper.GetDisplayName(subDirectors[i]); menuItem.callback = () => { ExecuteInternal(subDirectors, index, clips[0]); }; menuItems.Add(menuItem); } } } } [MenuEntry("Editing/Trim Start", MenuPriority.ClipActionSection.trimStart)] [Shortcut(Shortcuts.Clip.trimStart), UsedImplicitly] class TrimStart : ClipAction { public override ActionValidity Validate(IEnumerable clips) { return clips.All(x => TimelineEditor.inspectedSequenceTime <= x.start || TimelineEditor.inspectedSequenceTime >= x.start + x.duration) ? ActionValidity.Invalid : ActionValidity.Valid; } public override bool Execute(IEnumerable clips) { return ClipModifier.TrimStart(clips, TimelineEditor.inspectedSequenceTime); } } [MenuEntry("Editing/Trim End", MenuPriority.ClipActionSection.trimEnd), UsedImplicitly] [Shortcut(Shortcuts.Clip.trimEnd)] class TrimEnd : ClipAction { public override ActionValidity Validate(IEnumerable clips) { return clips.All(x => TimelineEditor.inspectedSequenceTime <= x.start || TimelineEditor.inspectedSequenceTime >= x.start + x.duration) ? ActionValidity.Invalid : ActionValidity.Valid; } public override bool Execute(IEnumerable clips) { return ClipModifier.TrimEnd(clips, TimelineEditor.inspectedSequenceTime); } } [Shortcut(Shortcuts.Clip.split)] [MenuEntry("Editing/Split", MenuPriority.ClipActionSection.split), UsedImplicitly] class Split : ClipAction { public override ActionValidity Validate(IEnumerable clips) { return clips.All(x => TimelineEditor.inspectedSequenceTime <= x.start || TimelineEditor.inspectedSequenceTime >= x.start + x.duration) ? ActionValidity.Invalid : ActionValidity.Valid; } public override bool Execute(IEnumerable clips) { bool success = ClipModifier.Split(clips, TimelineEditor.inspectedSequenceTime, TimelineEditor.inspectedDirector); if (success) TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved); return success; } } [MenuEntry("Editing/Complete Last Loop", MenuPriority.ClipActionSection.completeLastLoop), UsedImplicitly] class CompleteLastLoop : ClipAction { public override ActionValidity Validate(IEnumerable clips) { bool canDisplay = clips.Any(TimelineHelpers.HasUsableAssetDuration); return canDisplay ? ActionValidity.Valid : ActionValidity.Invalid; } public override bool Execute(IEnumerable clips) { return ClipModifier.CompleteLastLoop(clips); } } [MenuEntry("Editing/Trim Last Loop", MenuPriority.ClipActionSection.trimLastLoop), UsedImplicitly] class TrimLastLoop : ClipAction { public override ActionValidity Validate(IEnumerable clips) { bool canDisplay = clips.Any(TimelineHelpers.HasUsableAssetDuration); return canDisplay ? ActionValidity.Valid : ActionValidity.Invalid; } public override bool Execute(IEnumerable clips) { return ClipModifier.TrimLastLoop(clips); } } [MenuEntry("Editing/Match Duration", MenuPriority.ClipActionSection.matchDuration), UsedImplicitly] class MatchDuration : ClipAction { public override ActionValidity Validate(IEnumerable clips) { return clips.Count() > 1 ? ActionValidity.Valid : ActionValidity.Invalid; } public override bool Execute(IEnumerable clips) { return ClipModifier.MatchDuration(clips); } } [MenuEntry("Editing/Double Speed", MenuPriority.ClipActionSection.doubleSpeed), UsedImplicitly] class DoubleSpeed : ClipAction { public override ActionValidity Validate(IEnumerable clips) { bool canDisplay = clips.All(x => x.SupportsSpeedMultiplier()); return canDisplay ? ActionValidity.Valid : ActionValidity.Invalid; } public override bool Execute(IEnumerable clips) { return ClipModifier.DoubleSpeed(clips); } } [MenuEntry("Editing/Half Speed", MenuPriority.ClipActionSection.halfSpeed), UsedImplicitly] class HalfSpeed : ClipAction { public override ActionValidity Validate(IEnumerable clips) { bool canDisplay = clips.All(x => x.SupportsSpeedMultiplier()); return canDisplay ? ActionValidity.Valid : ActionValidity.Invalid; } public override bool Execute(IEnumerable clips) { return ClipModifier.HalfSpeed(clips); } } [MenuEntry("Editing/Reset Duration", MenuPriority.ClipActionSection.resetDuration), UsedImplicitly] class ResetDuration : ClipAction { public override ActionValidity Validate(IEnumerable clips) { bool canDisplay = clips.Any(TimelineHelpers.HasUsableAssetDuration); return canDisplay ? ActionValidity.Valid : ActionValidity.Invalid; } public override bool Execute(IEnumerable clips) { return ClipModifier.ResetEditing(clips); } } [MenuEntry("Editing/Reset Speed", MenuPriority.ClipActionSection.resetSpeed), UsedImplicitly] class ResetSpeed : ClipAction { public override ActionValidity Validate(IEnumerable clips) { bool canDisplay = clips.All(x => x.SupportsSpeedMultiplier()); return canDisplay ? ActionValidity.Valid : ActionValidity.Invalid; } public override bool Execute(IEnumerable clips) { return ClipModifier.ResetSpeed(clips); } } [MenuEntry("Editing/Reset All", MenuPriority.ClipActionSection.resetAll), UsedImplicitly] class ResetAll : ClipAction { public override ActionValidity Validate(IEnumerable clips) { bool canDisplay = clips.Any(TimelineHelpers.HasUsableAssetDuration) || clips.All(x => x.SupportsSpeedMultiplier()); return canDisplay ? ActionValidity.Valid : ActionValidity.Invalid; } public override bool Execute(IEnumerable clips) { var speedResult = ClipModifier.ResetSpeed(clips); var editResult = ClipModifier.ResetEditing(clips); return speedResult || editResult; } } [MenuEntry("Tile", MenuPriority.ClipActionSection.tile), UsedImplicitly] class Tile : ClipAction { public override ActionValidity Validate(IEnumerable clips) { return clips.Count() > 1 ? ActionValidity.Valid : ActionValidity.Invalid; } public override bool Execute(IEnumerable clips) { return ClipModifier.Tile(clips); } } [MenuEntry("Find Source Asset", MenuPriority.ClipActionSection.findSourceAsset), UsedImplicitly] [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)] class FindSourceAsset : ClipAction { public override ActionValidity Validate(IEnumerable clips) { if (clips.Count() > 1) return ActionValidity.Invalid; if (GetUnderlyingAsset(clips.First()) == null) return ActionValidity.Invalid; return ActionValidity.Valid; } public override bool Execute(IEnumerable clips) { EditorGUIUtility.PingObject(GetUnderlyingAsset(clips.First())); return true; } private static UnityEngine.Object GetExternalPlayableAsset(TimelineClip clip) { if (clip.asset == null) return null; if ((clip.asset.hideFlags & HideFlags.HideInHierarchy) != 0) return null; return clip.asset; } private static UnityEngine.Object GetUnderlyingAsset(TimelineClip clip) { var asset = clip.asset as ScriptableObject; if (asset == null) return null; var fields = ObjectReferenceField.FindObjectReferences(asset.GetType()); if (fields.Length == 0) return GetExternalPlayableAsset(clip); // Find the first non-null field foreach (var field in fields) { // skip scene refs in asset mode if (TimelineEditor.inspectedDirector == null && field.isSceneReference) continue; var obj = field.Find(asset, TimelineEditor.inspectedDirector); if (obj != null) return obj; } return GetExternalPlayableAsset(clip); } } class CopyClipsToClipboard : ClipAction { public override ActionValidity Validate(IEnumerable clips) => ActionValidity.Valid; public override bool Execute(IEnumerable clips) { TimelineEditor.clipboard.CopyItems(clips.ToItems()); return true; } } }