Unity-jump-proj

This commit is contained in:
2024-09-09 11:07:16 +03:00
parent 2c29906bbf
commit fd96a5627d
13707 changed files with 866380 additions and 0 deletions

View File

@ -0,0 +1,63 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline.Actions
{
/// <summary>
/// Action context to be used by actions.
/// </summary>
/// <seealso cref="Invoker"/>
/// <seealso cref="TimelineAction"/>
public struct ActionContext
{
IEnumerable<TrackAsset> m_Tracks;
IEnumerable<TimelineClip> m_Clips;
IEnumerable<IMarker> m_Markers;
/// <summary>
/// The Timeline asset that is currently opened in the Timeline window.
/// </summary>
public TimelineAsset timeline;
/// <summary>
/// The PlayableDirector that is used to play the current Timeline asset.
/// </summary>
public PlayableDirector director;
/// <summary>
/// Time based on the position of the cursor on the timeline (in seconds).
/// null if the time is not available (in case of a shortcut for example).
/// </summary>
public double? invocationTime;
/// <summary>
/// Tracks that will be used by the actions.
/// </summary>
public IEnumerable<TrackAsset> tracks
{
get => m_Tracks ?? Enumerable.Empty<TrackAsset>();
set => m_Tracks = value;
}
/// <summary>
/// Clips that will be used by the actions.
/// </summary>
public IEnumerable<TimelineClip> clips
{
get => m_Clips ?? Enumerable.Empty<TimelineClip>();
set => m_Clips = value;
}
/// <summary>
/// Markers that will be used by the actions.
/// </summary>
public IEnumerable<IMarker> markers
{
get => m_Markers ?? Enumerable.Empty<IMarker>();
set => m_Markers = value;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5e79d50dea5c9c74d9297cce6cd6d053
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,306 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline.Actions
{
static class ActionManager
{
static bool s_ShowActionTriggeredByShortcut = false;
public static readonly IReadOnlyList<TimelineAction> TimelineActions = InstantiateClassesOfType<TimelineAction>();
public static readonly IReadOnlyList<ClipAction> ClipActions = InstantiateClassesOfType<ClipAction>();
public static readonly IReadOnlyList<TrackAction> TrackActions = InstantiateClassesOfType<TrackAction>();
public static readonly IReadOnlyList<MarkerAction> MarkerActions = InstantiateClassesOfType<MarkerAction>();
public static readonly IReadOnlyList<TimelineAction> TimelineActionsWithShortcuts = ActionsWithShortCuts(TimelineActions);
public static readonly IReadOnlyList<ClipAction> ClipActionsWithShortcuts = ActionsWithShortCuts(ClipActions);
public static readonly IReadOnlyList<TrackAction> TrackActionsWithShortcuts = ActionsWithShortCuts(TrackActions);
public static readonly IReadOnlyList<MarkerAction> MarkerActionsWithShortcuts = ActionsWithShortCuts(MarkerActions);
public static readonly HashSet<Type> ActionsWithAutoUndo = TypesWithAttribute<ApplyDefaultUndoAttribute>();
public static TU GetCachedAction<T, TU>(this IReadOnlyList<TU> list) where T : TU
{
return list.FirstOrDefault(x => x.GetType() == typeof(T));
}
public static void GetMenuEntries(IReadOnlyList<TimelineAction> actions, Vector2? mousePos, List<MenuActionItem> menuItems)
{
var globalContext = TimelineEditor.CurrentContext(mousePos);
foreach (var action in actions)
{
try
{
BuildMenu(action, globalContext, menuItems);
}
catch (Exception e)
{
Debug.LogException(e);
}
}
}
public static void GetMenuEntries(IReadOnlyList<TrackAction> actions, List<MenuActionItem> menuItems)
{
var tracks = SelectionManager.SelectedTracks();
if (!tracks.Any())
return;
foreach (var action in actions)
{
try
{
BuildMenu(action, tracks, menuItems);
}
catch (Exception e)
{
Debug.LogException(e);
}
}
}
public static void GetMenuEntries(IReadOnlyList<ClipAction> actions, List<MenuActionItem> menuItems)
{
var clips = SelectionManager.SelectedClips();
bool any = clips.Any();
if (!clips.Any())
return;
foreach (var action in actions)
{
try
{
if (action is EditSubTimeline editSubTimelineAction)
editSubTimelineAction.AddMenuItem(menuItems);
else if (any)
BuildMenu(action, clips, menuItems);
}
catch (Exception e)
{
Debug.LogException(e);
}
}
}
public static void GetMenuEntries(IReadOnlyList<MarkerAction> actions, List<MenuActionItem> menuItems)
{
var markers = SelectionManager.SelectedMarkers();
if (!markers.Any())
return;
foreach (var action in actions)
{
try
{
BuildMenu(action, markers, menuItems);
}
catch (Exception e)
{
Debug.LogException(e);
}
}
}
static void BuildMenu(TimelineAction action, ActionContext context, List<MenuActionItem> menuItems)
{
BuildMenu(action, action.Validate(context), () => ExecuteTimelineAction(action, context), menuItems);
}
static void BuildMenu(TrackAction action, IEnumerable<TrackAsset> tracks, List<MenuActionItem> menuItems)
{
BuildMenu(action, action.Validate(tracks), () => ExecuteTrackAction(action, tracks), menuItems);
}
static void BuildMenu(ClipAction action, IEnumerable<TimelineClip> clips, List<MenuActionItem> menuItems)
{
BuildMenu(action, action.Validate(clips), () => ExecuteClipAction(action, clips), menuItems);
}
static void BuildMenu(MarkerAction action, IEnumerable<IMarker> markers, List<MenuActionItem> menuItems)
{
BuildMenu(action, action.Validate(markers), () => ExecuteMarkerAction(action, markers), menuItems);
}
static void BuildMenu(IAction action, ActionValidity validity, GenericMenu.MenuFunction executeFunction, List<MenuActionItem> menuItems)
{
var menuAttribute = action.GetType().GetCustomAttribute<MenuEntryAttribute>(false);
if (menuAttribute == null)
return;
if (validity == ActionValidity.NotApplicable)
return;
var menuActionItem = new MenuActionItem
{
state = validity,
entryName = action.GetMenuEntryName(),
priority = menuAttribute.priority,
category = menuAttribute.subMenuPath,
isActiveInMode = action.IsActionActiveInMode(TimelineWindow.instance.currentMode.mode),
shortCut = action.GetShortcut(),
callback = executeFunction,
isChecked = action.IsChecked()
};
menuItems.Add(menuActionItem);
}
internal static void BuildMenu(GenericMenu menu, List<MenuActionItem> items)
{
// sorted the outer menu by priority, then sort the innermenu by priority
var sortedItems =
items.GroupBy(x => string.IsNullOrEmpty(x.category) ? x.entryName : x.category).OrderBy(x => x.Min(y => y.priority)).SelectMany(x => x.OrderBy(z => z.priority));
int lastPriority = Int32.MinValue;
string lastCategory = string.Empty;
foreach (var s in sortedItems)
{
if (s.state == ActionValidity.NotApplicable)
continue;
var priority = s.priority;
if (lastPriority != int.MinValue && priority / MenuPriority.separatorAt > lastPriority / MenuPriority.separatorAt)
{
string path = string.Empty;
if (lastCategory == s.category)
path = s.category;
menu.AddSeparator(path);
}
lastPriority = priority;
lastCategory = s.category;
string entry = s.category + s.entryName;
if (!string.IsNullOrEmpty(s.shortCut))
entry += " " + s.shortCut;
if (s.state == ActionValidity.Valid && s.isActiveInMode)
menu.AddItem(new GUIContent(entry), s.isChecked, s.callback);
else
menu.AddDisabledItem(new GUIContent(entry), s.isChecked);
}
}
public static bool HandleShortcut(Event evt)
{
if (EditorGUI.IsEditingTextField())
return false;
return HandleShortcut(evt, TimelineActionsWithShortcuts, (x) => ExecuteTimelineAction(x, TimelineEditor.CurrentContext())) ||
HandleShortcut(evt, ClipActionsWithShortcuts, (x => ExecuteClipAction(x, SelectionManager.SelectedClips()))) ||
HandleShortcut(evt, TrackActionsWithShortcuts, (x => ExecuteTrackAction(x, SelectionManager.SelectedTracks()))) ||
HandleShortcut(evt, MarkerActionsWithShortcuts, (x => ExecuteMarkerAction(x, SelectionManager.SelectedMarkers())));
}
public static bool HandleShortcut<T>(Event evt, IReadOnlyList<T> actions, Func<T, bool> invoke) where T : class, IAction
{
for (int i = 0; i < actions.Count; i++)
{
var action = actions[i];
var attr = action.GetType().GetCustomAttributes(typeof(ShortcutAttribute), true);
foreach (ShortcutAttribute shortcut in attr)
{
if (shortcut.MatchesEvent(evt))
{
if (s_ShowActionTriggeredByShortcut)
Debug.Log(action.GetType().Name);
if (!action.IsActionActiveInMode(TimelineWindow.instance.currentMode.mode))
continue;
if (invoke(action))
return true;
}
}
}
return false;
}
public static bool ExecuteTimelineAction(TimelineAction timelineAction, ActionContext context)
{
if (timelineAction.Validate(context) == ActionValidity.Valid)
{
if (timelineAction.HasAutoUndo())
UndoExtensions.RegisterContext(context, timelineAction.GetUndoName());
return timelineAction.Execute(context);
}
return false;
}
public static bool ExecuteTrackAction(TrackAction trackAction, IEnumerable<TrackAsset> tracks)
{
if (tracks != null && tracks.Any() && trackAction.Validate(tracks) == ActionValidity.Valid)
{
if (trackAction.HasAutoUndo())
UndoExtensions.RegisterTracks(tracks, trackAction.GetUndoName());
return trackAction.Execute(tracks);
}
return false;
}
public static bool ExecuteClipAction(ClipAction clipAction, IEnumerable<TimelineClip> clips)
{
if (clips != null && clips.Any() && clipAction.Validate(clips) == ActionValidity.Valid)
{
if (clipAction.HasAutoUndo())
UndoExtensions.RegisterClips(clips, clipAction.GetUndoName());
return clipAction.Execute(clips);
}
return false;
}
public static bool ExecuteMarkerAction(MarkerAction markerAction, IEnumerable<IMarker> markers)
{
if (markers != null && markers.Any() && markerAction.Validate(markers) == ActionValidity.Valid)
{
if (markerAction.HasAutoUndo())
UndoExtensions.RegisterMarkers(markers, markerAction.GetUndoName());
return markerAction.Execute(markers);
}
return false;
}
static List<T> InstantiateClassesOfType<T>() where T : class
{
var typeCollection = TypeCache.GetTypesDerivedFrom(typeof(T));
var list = new List<T>(typeCollection.Count);
for (int i = 0; i < typeCollection.Count; i++)
{
if (typeCollection[i].IsAbstract || typeCollection[i].IsGenericType)
continue;
if (typeCollection[i].GetConstructor(Type.EmptyTypes) == null)
{
Debug.LogWarning($"{typeCollection[i].FullName} requires a default constructor to be automatically instantiated by Timeline");
continue;
}
list.Add((T)Activator.CreateInstance(typeCollection[i]));
}
return list;
}
static List<T> ActionsWithShortCuts<T>(IReadOnlyList<T> list)
{
return list.Where(x => x.GetType().GetCustomAttributes(typeof(ShortcutAttribute), true).Length > 0).ToList();
}
static HashSet<System.Type> TypesWithAttribute<T>() where T : Attribute
{
var hashSet = new HashSet<System.Type>();
var typeCollection = TypeCache.GetTypesWithAttribute(typeof(T));
for (int i = 0; i < typeCollection.Count; i++)
{
hashSet.Add(typeCollection[i]);
}
return hashSet;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b185b14d428805c46aceb7c663825b2d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,36 @@
using System.Collections.Generic;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline.Actions
{
/// <summary>
/// Base class for a clip action.
/// Inherit from this class to make an action that would react on selected clips after a menu click and/or a key shortcut.
/// </summary>
/// <example>
/// Simple Clip Action example (with context menu and shortcut support).
/// <code source="../../DocCodeExamples/ActionExamples.cs" region="declare-sampleClipAction" title="SampleClipAction"/>
/// </example>
/// <remarks>
/// To add an action as a menu item in the Timeline context menu, add <see cref="MenuEntryAttribute"/> on the action class.
/// To make an action to react to a shortcut, use the Shortcut Manager API with <see cref="TimelineShortcutAttribute"/>.
/// <seealso cref="UnityEditor.ShortcutManagement.ShortcutAttribute"/>
/// </remarks>
[ActiveInMode(TimelineModes.Default)]
public abstract class ClipAction : IAction
{
/// <summary>
/// Execute the action based on clips.
/// </summary>
/// <param name="clips">clips that the action will act on.</param>
/// <returns>Returns true if the action has been correctly executed, false otherwise.</returns>
public abstract bool Execute(IEnumerable<TimelineClip> clips);
/// <summary>
/// Defines the validity of an Action for a given set of clips.
/// </summary>
/// <param name="clips">The clips that the action will act on.</param>
/// <returns>The validity of the set of clips.</returns>
public abstract ActionValidity Validate(IEnumerable<TimelineClip> clips);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fa130a47ff26ad84f867961fc32334af
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,392 @@
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<TimelineClip> clips)
{
if (!GetEditableClip(clips, out _, out _))
return ActionValidity.NotApplicable;
return ActionValidity.Valid;
}
public override bool Execute(IEnumerable<TimelineClip> 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<TimelineClip> 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<TimelineClip> 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<TimelineClip> 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<PlayableDirector> directors, int directorIndex, TimelineClip clip)
{
SelectionManager.Clear();
TimelineWindow.instance.SetCurrentTimeline(directors[directorIndex], clip);
}
internal void AddMenuItem(List<MenuActionItem> 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<MenuEntryAttribute>().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<TimelineClip> clips)
{
return clips.All(x => TimelineEditor.inspectedSequenceTime <= x.start || TimelineEditor.inspectedSequenceTime >= x.start + x.duration) ? ActionValidity.Invalid : ActionValidity.Valid;
}
public override bool Execute(IEnumerable<TimelineClip> 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<TimelineClip> clips)
{
return clips.All(x => TimelineEditor.inspectedSequenceTime <= x.start || TimelineEditor.inspectedSequenceTime >= x.start + x.duration) ? ActionValidity.Invalid : ActionValidity.Valid;
}
public override bool Execute(IEnumerable<TimelineClip> 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<TimelineClip> clips)
{
return clips.All(x => TimelineEditor.inspectedSequenceTime <= x.start || TimelineEditor.inspectedSequenceTime >= x.start + x.duration) ? ActionValidity.Invalid : ActionValidity.Valid;
}
public override bool Execute(IEnumerable<TimelineClip> 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<TimelineClip> clips)
{
bool canDisplay = clips.Any(TimelineHelpers.HasUsableAssetDuration);
return canDisplay ? ActionValidity.Valid : ActionValidity.Invalid;
}
public override bool Execute(IEnumerable<TimelineClip> clips)
{
return ClipModifier.CompleteLastLoop(clips);
}
}
[MenuEntry("Editing/Trim Last Loop", MenuPriority.ClipActionSection.trimLastLoop), UsedImplicitly]
class TrimLastLoop : ClipAction
{
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
{
bool canDisplay = clips.Any(TimelineHelpers.HasUsableAssetDuration);
return canDisplay ? ActionValidity.Valid : ActionValidity.Invalid;
}
public override bool Execute(IEnumerable<TimelineClip> clips)
{
return ClipModifier.TrimLastLoop(clips);
}
}
[MenuEntry("Editing/Match Duration", MenuPriority.ClipActionSection.matchDuration), UsedImplicitly]
class MatchDuration : ClipAction
{
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
{
return clips.Count() > 1 ? ActionValidity.Valid : ActionValidity.Invalid;
}
public override bool Execute(IEnumerable<TimelineClip> clips)
{
return ClipModifier.MatchDuration(clips);
}
}
[MenuEntry("Editing/Double Speed", MenuPriority.ClipActionSection.doubleSpeed), UsedImplicitly]
class DoubleSpeed : ClipAction
{
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
{
bool canDisplay = clips.All(x => x.SupportsSpeedMultiplier());
return canDisplay ? ActionValidity.Valid : ActionValidity.Invalid;
}
public override bool Execute(IEnumerable<TimelineClip> clips)
{
return ClipModifier.DoubleSpeed(clips);
}
}
[MenuEntry("Editing/Half Speed", MenuPriority.ClipActionSection.halfSpeed), UsedImplicitly]
class HalfSpeed : ClipAction
{
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
{
bool canDisplay = clips.All(x => x.SupportsSpeedMultiplier());
return canDisplay ? ActionValidity.Valid : ActionValidity.Invalid;
}
public override bool Execute(IEnumerable<TimelineClip> clips)
{
return ClipModifier.HalfSpeed(clips);
}
}
[MenuEntry("Editing/Reset Duration", MenuPriority.ClipActionSection.resetDuration), UsedImplicitly]
class ResetDuration : ClipAction
{
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
{
bool canDisplay = clips.Any(TimelineHelpers.HasUsableAssetDuration);
return canDisplay ? ActionValidity.Valid : ActionValidity.Invalid;
}
public override bool Execute(IEnumerable<TimelineClip> clips)
{
return ClipModifier.ResetEditing(clips);
}
}
[MenuEntry("Editing/Reset Speed", MenuPriority.ClipActionSection.resetSpeed), UsedImplicitly]
class ResetSpeed : ClipAction
{
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
{
bool canDisplay = clips.All(x => x.SupportsSpeedMultiplier());
return canDisplay ? ActionValidity.Valid : ActionValidity.Invalid;
}
public override bool Execute(IEnumerable<TimelineClip> clips)
{
return ClipModifier.ResetSpeed(clips);
}
}
[MenuEntry("Editing/Reset All", MenuPriority.ClipActionSection.resetAll), UsedImplicitly]
class ResetAll : ClipAction
{
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
{
bool canDisplay = clips.Any(TimelineHelpers.HasUsableAssetDuration) || clips.All(x => x.SupportsSpeedMultiplier());
return canDisplay ? ActionValidity.Valid : ActionValidity.Invalid;
}
public override bool Execute(IEnumerable<TimelineClip> 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<TimelineClip> clips)
{
return clips.Count() > 1 ? ActionValidity.Valid : ActionValidity.Invalid;
}
public override bool Execute(IEnumerable<TimelineClip> 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<TimelineClip> clips)
{
if (clips.Count() > 1)
return ActionValidity.Invalid;
if (GetUnderlyingAsset(clips.First()) == null)
return ActionValidity.Invalid;
return ActionValidity.Valid;
}
public override bool Execute(IEnumerable<TimelineClip> 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<TimelineClip> clips) => ActionValidity.Valid;
public override bool Execute(IEnumerable<TimelineClip> clips)
{
TimelineEditor.clipboard.CopyItems(clips.ToItems());
return true;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4b721099b5d509d4093e516f59ad9ad6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,123 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using UnityEditor.ShortcutManagement;
using UnityEngine;
namespace UnityEditor.Timeline.Actions
{
/// interface indicating an Action class
interface IAction { }
/// extension methods for IActions
static class ActionExtensions
{
const string kActionPostFix = "Action";
public static string GetUndoName(this IAction action)
{
if (action == null)
throw new ArgumentNullException(nameof(action));
var attr = action.GetType().GetCustomAttribute<ApplyDefaultUndoAttribute>(false);
if (attr != null && !string.IsNullOrWhiteSpace(attr.UndoTitle))
return attr.UndoTitle;
return action.GetDisplayName();
}
public static string GetMenuEntryName(this IAction action)
{
var menuAction = action as IMenuName;
if (menuAction != null && !string.IsNullOrWhiteSpace(menuAction.menuName))
return menuAction.menuName;
var attr = action.GetType().GetCustomAttribute<MenuEntryAttribute>(false);
if (attr != null && !string.IsNullOrWhiteSpace(attr.name))
return attr.name;
return action.GetDisplayName();
}
public static string GetDisplayName(this IAction action)
{
if (action == null)
throw new ArgumentNullException(nameof(action));
var attr = action.GetType().GetCustomAttribute<DisplayNameAttribute>(false);
if (attr != null && !string.IsNullOrEmpty(attr.DisplayName))
return attr.DisplayName;
var name = action.GetType().Name;
if (name.EndsWith(kActionPostFix))
return ObjectNames.NicifyVariableName(name.Substring(0, name.Length - kActionPostFix.Length));
return ObjectNames.NicifyVariableName(name);
}
public static bool HasAutoUndo(this IAction action)
{
return action != null && ActionManager.ActionsWithAutoUndo.Contains(action.GetType());
}
public static bool IsChecked(this IAction action)
{
return (action is IMenuChecked menuAction) && menuAction.isChecked;
}
public static bool IsActionActiveInMode(this IAction action, TimelineModes mode)
{
var attr = action.GetType().GetCustomAttribute<ActiveInModeAttribute>(true);
return attr != null && (attr.modes & mode) != 0;
}
public static string GetShortcut(this IAction action)
{
if (action == null)
throw new ArgumentNullException(nameof(action));
var shortcutAttribute = GetShortcutAttributeForAction(action);
var shortCut = shortcutAttribute == null ? string.Empty : shortcutAttribute.GetMenuShortcut();
if (string.IsNullOrWhiteSpace(shortCut))
{
//Check if there is a static method with attribute
var customShortcutMethod = action.GetType().GetMethods().FirstOrDefault(m => m.GetCustomAttribute<TimelineShortcutAttribute>(true) != null);
if (customShortcutMethod != null)
{
var shortcutId = customShortcutMethod.GetCustomAttribute<TimelineShortcutAttribute>(true).identifier;
var shortcut = ShortcutIntegration.instance.directory.FindShortcutEntry(shortcutId);
if (shortcut != null && shortcut.combinations.Any())
shortCut = KeyCombination.SequenceToMenuString(shortcut.combinations);
}
}
return shortCut;
}
static ShortcutAttribute GetShortcutAttributeForAction(this IAction action)
{
if (action == null)
throw new ArgumentNullException(nameof(action));
var shortcutAttributes = action.GetType()
.GetCustomAttributes(typeof(ShortcutAttribute), true)
.Cast<ShortcutAttribute>();
foreach (var shortcutAttribute in shortcutAttributes)
{
if (shortcutAttribute is ShortcutPlatformOverrideAttribute shortcutOverride)
{
if (shortcutOverride.MatchesCurrentPlatform())
return shortcutOverride;
}
else
{
return shortcutAttribute;
}
}
return null;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: de11c48878ba1b44b8b641af54dba8c1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
using System;
namespace UnityEditor.Timeline
{
interface IMenuChecked
{
bool isChecked { get; }
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0939c83f40ef46e584340aa87b3cadfe
timeCreated: 1591130283

View File

@ -0,0 +1,9 @@
using System;
namespace UnityEditor.Timeline
{
interface IMenuName
{
string menuName { get; }
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 512ac45650d443f3be59379c2ecbf068
timeCreated: 1591130274

View File

@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline.Actions
{
/// <summary>
/// Class containing methods to invoke actions.
/// </summary>
public static class Invoker
{
/// <summary>
/// Execute a given action with a context parameter.
/// </summary>
/// <typeparam name="T">Action type to execute.</typeparam>
/// <param name="context">Context for the action.</param>
/// <returns>True if the action has been executed, false otherwise.</returns>
public static bool Invoke<T>(this ActionContext context) where T : TimelineAction
{
var action = ActionManager.TimelineActions.GetCachedAction<T, TimelineAction>();
return ActionManager.ExecuteTimelineAction(action, context);
}
/// <summary>
/// Execute a given action with tracks
/// </summary>
/// <typeparam name="T">Action type to execute.</typeparam>
/// <param name="tracks">Tracks that the action will act on.</param>
/// <returns>True if the action has been executed, false otherwise.</returns>
public static bool Invoke<T>(this IEnumerable<TrackAsset> tracks) where T : TrackAction
{
var action = ActionManager.TrackActions.GetCachedAction<T, TrackAction>();
return ActionManager.ExecuteTrackAction(action, tracks);
}
/// <summary>
/// Execute a given action with clips
/// </summary>
/// <typeparam name="T">Action type to execute.</typeparam>
/// <param name="clips">Clips that the action will act on.</param>
/// <returns>True if the action has been executed, false otherwise.</returns>
public static bool Invoke<T>(this IEnumerable<TimelineClip> clips) where T : ClipAction
{
var action = ActionManager.ClipActions.GetCachedAction<T, ClipAction>();
return ActionManager.ExecuteClipAction(action, clips);
}
/// <summary>
/// Execute a given action with markers
/// </summary>
/// <typeparam name="T">Action type to execute.</typeparam>
/// <param name="markers">Markers that the action will act on.</param>
/// <returns>True if the action has been executed, false otherwise.</returns>
public static bool Invoke<T>(this IEnumerable<IMarker> markers) where T : MarkerAction
{
var action = ActionManager.MarkerActions.GetCachedAction<T, MarkerAction>();
return ActionManager.ExecuteMarkerAction(action, markers);
}
/// <summary>
/// Execute a given timeline action with the selected clips, tracks and markers.
/// </summary>
/// <typeparam name="T">Action type to execute.</typeparam>
/// <returns>True if the action has been executed, false otherwise.</returns>
public static bool InvokeWithSelected<T>() where T : TimelineAction
{
return Invoke<T>(TimelineEditor.CurrentContext());
}
/// <summary>
/// Execute a given clip action with the selected clips.
/// </summary>
/// <typeparam name="T">Action type to execute.</typeparam>
/// <returns>True if the action has been executed, false otherwise.</returns>
public static bool InvokeWithSelectedClips<T>() where T : ClipAction
{
return Invoke<T>(SelectionManager.SelectedClips());
}
/// <summary>
/// Execute a given track action with the selected tracks.
/// </summary>
/// <typeparam name="T">Action type to execute.</typeparam>
/// <returns>True if the action has been executed, false otherwise.</returns>
public static bool InvokeWithSelectedTracks<T>() where T : TrackAction
{
return Invoke<T>(SelectionManager.SelectedTracks());
}
/// <summary>
/// Execute a given marker action with the selected markers.
/// </summary>
/// <typeparam name="T">Action type to execute.</typeparam>
/// <returns>True if the action has been executed, false otherwise.</returns>
public static bool InvokeWithSelectedMarkers<T>() where T : MarkerAction
{
return Invoke<T>(SelectionManager.SelectedMarkers());
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0c379d496c819c14b8b5377d7830a59a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,35 @@
using System.Collections.Generic;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline.Actions
{
/// <summary>
/// Base class for a marker action.
/// Inherit from this class to make an action that would react on selected markers after a menu click and/or a key shortcut.
/// </summary>
/// <example>
/// Simple track Action example (with context menu and shortcut support).
/// <code source="../../DocCodeExamples/ActionExamples.cs" region="declare-sampleMarkerAction" title="SampleMarkerAction"/>
/// </example>
/// <remarks>
/// To add an action as a menu item in the Timeline context menu, add <see cref="MenuEntryAttribute"/> on the action class.
/// To make an action to react to a shortcut, use the Shortcut Manager API with <see cref="TimelineShortcutAttribute"/>.
/// <seealso cref="UnityEditor.ShortcutManagement.ShortcutAttribute"/>
/// </remarks>
[ActiveInMode(TimelineModes.Default)]
public abstract class MarkerAction : IAction
{
/// <summary>
/// Execute the action.
/// </summary>
/// <param name="markers">Markers that will be used for the action. </param>
/// <returns>true if the action has been executed. false otherwise</returns>
public abstract bool Execute(IEnumerable<IMarker> markers);
/// <summary>
/// Defines the validity of an Action for a given set of markers.
/// </summary>
/// <param name="markers">Markers that will be used for the action. </param>
/// <returns>The validity of the set of markers.</returns>
public abstract ActionValidity Validate(IEnumerable<IMarker> markers);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 03d19e545aea1b446b1c56530a4438ae
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,20 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using UnityEditor.Timeline.Actions;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
[UsedImplicitly]
class CopyMarkersToClipboard : MarkerAction
{
public override ActionValidity Validate(IEnumerable<IMarker> markers) => ActionValidity.Valid;
public override bool Execute(IEnumerable<IMarker> markers)
{
TimelineEditor.clipboard.CopyItems(markers.ToItems());
return true;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5da77d4d078922b4c8466e9e35fb3f5e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 985eed4bc2fbee941b761b8816d9055d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,36 @@
namespace UnityEditor.Timeline.Actions
{
/// <summary>
/// Indicates the validity of an action for a given data set.
/// </summary>
public enum ActionValidity
{
/// <summary>
/// Action is valid in the provided context.
/// If the action is linked to a menu item, the menu item will be visible.
/// </summary>
Valid,
/// <summary>
/// Action is not applicable in the current context.
/// If the action is linked to a menu item, the menu item will not be shown.
/// </summary>
NotApplicable,
/// <summary>
/// Action is not valid in the current context.
/// If the action is linked to a menu item, the menu item will be shown but grayed out.
/// </summary>
Invalid
}
struct MenuActionItem
{
public string category;
public string entryName;
public string shortCut;
public int priority;
public bool isActiveInMode;
public ActionValidity state;
public bool isChecked;
public GenericMenu.MenuFunction callback;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5882d0e4313310143acb11d1a66c597f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,381 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.Timeline.Actions;
using UnityEngine;
using UnityEngine.Timeline;
using Object = UnityEngine.Object;
namespace UnityEditor.Timeline
{
static class SequencerContextMenu
{
static class Styles
{
public static readonly string addItemFromAssetTemplate = L10n.Tr("Add {0} From {1}");
public static readonly string addSingleItemFromAssetTemplate = L10n.Tr("Add From {1}");
public static readonly string addItemTemplate = L10n.Tr("Add {0}");
public static readonly string typeSelectorTemplate = L10n.Tr("Select {0}");
public static readonly string trackGroup = L10n.Tr("Track Group");
public static readonly string trackSubGroup = L10n.Tr("Track Sub-Group");
public static readonly string addTrackLayer = L10n.Tr("Add Layer");
public static readonly string layerName = L10n.Tr("Layer {0}");
}
public static void ShowNewTracksContextMenu(ICollection<TrackAsset> tracks, WindowState state)
{
var menu = new GenericMenu();
List<MenuActionItem> items = new List<MenuActionItem>(100);
BuildNewTracksContextMenu(items, tracks, state);
ActionManager.BuildMenu(menu, items);
menu.ShowAsContext();
}
public static void ShowNewTracksContextMenu(ICollection<TrackAsset> tracks, WindowState state, Rect rect)
{
var menu = new GenericMenu();
List<MenuActionItem> items = new List<MenuActionItem>(100);
BuildNewTracksContextMenu(items, tracks, state);
ActionManager.BuildMenu(menu, items);
menu.DropDown(rect);
}
public static void ShowTrackContextMenu(Vector2? mousePosition)
{
var items = new List<MenuActionItem>();
var menu = new GenericMenu();
BuildTrackContextMenu(items, mousePosition);
ActionManager.BuildMenu(menu, items);
menu.ShowAsContext();
}
public static void ShowItemContextMenu(Vector2 mousePosition)
{
var menu = new GenericMenu();
var items = new List<MenuActionItem>();
BuildItemContextMenu(items, mousePosition);
ActionManager.BuildMenu(menu, items);
menu.ShowAsContext();
}
public static void BuildItemContextMenu(List<MenuActionItem> items, Vector2 mousePosition)
{
ActionManager.GetMenuEntries(ActionManager.TimelineActions, mousePosition, items);
ActionManager.GetMenuEntries(ActionManager.ClipActions, items);
ActionManager.GetMenuEntries(ActionManager.MarkerActions, items);
var clips = TimelineEditor.selectedClips;
if (clips.Length > 0)
AddMarkerMenuCommands(items, clips.Select(c => c.GetParentTrack()).Distinct().ToList(), TimelineHelpers.GetCandidateTime(mousePosition));
}
public static void BuildNewTracksContextMenu(List<MenuActionItem> menuItems, ICollection<TrackAsset> parentTracks, WindowState state, string format = null)
{
if (parentTracks == null)
parentTracks = new TrackAsset[0];
if (string.IsNullOrEmpty(format))
format = "{0}";
// Add Group or SubGroup
var title = string.Format(format, parentTracks.Any(t => t != null) ? Styles.trackSubGroup : Styles.trackGroup);
var menuState = ActionValidity.Valid;
if (state.editSequence.isReadOnly)
menuState = ActionValidity.Invalid;
if (parentTracks.Any() && parentTracks.Any(t => t != null && t.lockedInHierarchy))
menuState = ActionValidity.Invalid;
GenericMenu.MenuFunction command = () =>
{
SelectionManager.Clear();
if (parentTracks.Count == 0)
Selection.Add(TimelineHelpers.CreateTrack<GroupTrack>(null, title));
foreach (var parentTrack in parentTracks)
Selection.Add(TimelineHelpers.CreateTrack<GroupTrack>(parentTrack, title));
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
};
menuItems.Add(
new MenuActionItem()
{
category = string.Empty,
entryName = title,
isActiveInMode = true,
priority = MenuPriority.AddItem.addGroup,
state = menuState,
isChecked = false,
callback = command
}
);
var allTypes = TypeUtility.AllTrackTypes().Where(x => x != typeof(GroupTrack) && !TypeUtility.IsHiddenInMenu(x)).ToList();
int builtInPriority = MenuPriority.AddItem.addTrack;
int customPriority = MenuPriority.AddItem.addCustomTrack;
foreach (var trackType in allTypes)
{
var trackItemType = trackType;
command = () =>
{
SelectionManager.Clear();
if (parentTracks.Count == 0)
SelectionManager.Add(TimelineHelpers.CreateTrack((Type)trackItemType, null));
foreach (var parentTrack in parentTracks)
SelectionManager.Add(TimelineHelpers.CreateTrack((Type)trackItemType, parentTrack));
};
menuItems.Add(
new MenuActionItem()
{
category = TimelineHelpers.GetTrackCategoryName(trackType),
entryName = string.Format(format, TimelineHelpers.GetTrackMenuName(trackItemType)),
isActiveInMode = true,
priority = TypeUtility.IsBuiltIn(trackType) ? builtInPriority++ : customPriority++,
state = menuState,
callback = command
}
);
}
}
public static void BuildTrackContextMenu(List<MenuActionItem> items, Vector2? mousePosition)
{
var tracks = SelectionManager.SelectedTracks().ToArray();
if (tracks.Length == 0)
return;
ActionManager.GetMenuEntries(ActionManager.TimelineActions, mousePosition, items);
ActionManager.GetMenuEntries(ActionManager.TrackActions, items);
AddLayeredTrackCommands(items, tracks);
var first = tracks.First().GetType();
var allTheSame = tracks.All(t => t.GetType() == first);
if (allTheSame)
{
if (first != typeof(GroupTrack))
{
var candidateTime = TimelineHelpers.GetCandidateTime(mousePosition, tracks);
AddClipMenuCommands(items, tracks, candidateTime);
AddMarkerMenuCommands(items, tracks, candidateTime);
}
else
{
BuildNewTracksContextMenu(items, tracks, TimelineWindow.instance.state, Styles.addItemTemplate);
}
}
}
static void AddLayeredTrackCommands(List<MenuActionItem> menuItems, ICollection<TrackAsset> tracks)
{
if (tracks.Count == 0)
return;
var layeredType = tracks.First().GetType();
// animation tracks have a special menu.
if (layeredType == typeof(AnimationTrack))
return;
// must implement ILayerable
if (!typeof(UnityEngine.Timeline.ILayerable).IsAssignableFrom(layeredType))
return;
if (tracks.Any(t => t.GetType() != layeredType))
return;
// only supported on the master track no nesting.
if (tracks.Any(t => t.isSubTrack))
return;
var enabled = tracks.All(t => t != null && !t.lockedInHierarchy) && !TimelineWindow.instance.state.editSequence.isReadOnly;
int priority = MenuPriority.AddTrackMenu.addLayerTrack;
GenericMenu.MenuFunction menuCallback = () =>
{
foreach (var track in tracks)
TimelineHelpers.CreateTrack(layeredType, track, string.Format(Styles.layerName, track.GetChildTracks().Count() + 1));
};
var entryName = Styles.addTrackLayer;
menuItems.Add(
new MenuActionItem()
{
category = string.Empty,
entryName = entryName,
isActiveInMode = true,
priority = priority++,
state = enabled ? ActionValidity.Valid : ActionValidity.Invalid,
callback = menuCallback
}
);
}
static void AddClipMenuCommands(List<MenuActionItem> menuItems, ICollection<TrackAsset> tracks, double candidateTime)
{
if (!tracks.Any())
return;
var trackAsset = tracks.First();
var trackType = trackAsset.GetType();
if (tracks.Any(t => t.GetType() != trackType))
return;
var enabled = tracks.All(t => t != null && !t.lockedInHierarchy) && !TimelineWindow.instance.state.editSequence.isReadOnly;
var assetTypes = TypeUtility.GetPlayableAssetsHandledByTrack(trackType);
var visibleAssetTypes = TypeUtility.GetVisiblePlayableAssetsHandledByTrack(trackType);
// skips the name if there is only a single type
var commandNameTemplate = assetTypes.Count() == 1 ? Styles.addSingleItemFromAssetTemplate : Styles.addItemFromAssetTemplate;
int builtInPriority = MenuPriority.AddItem.addClip;
int customPriority = MenuPriority.AddItem.addCustomClip;
foreach (var assetType in assetTypes)
{
var assetItemType = assetType;
var category = TimelineHelpers.GetItemCategoryName(assetType);
Action<Object> onObjectChanged = obj =>
{
if (obj != null)
{
foreach (var t in tracks)
{
TimelineHelpers.CreateClipOnTrack(assetItemType, obj, t, candidateTime);
}
}
};
foreach (var objectReference in TypeUtility.ObjectReferencesForType(assetType))
{
var isSceneReference = objectReference.isSceneReference;
var dataType = objectReference.type;
GenericMenu.MenuFunction menuCallback = () =>
{
ObjectSelector.get.Show(null, dataType, null, isSceneReference, null, (obj) => onObjectChanged(obj), null);
ObjectSelector.get.titleContent = EditorGUIUtility.TrTextContent(string.Format(Styles.typeSelectorTemplate, TypeUtility.GetDisplayName(dataType)));
};
menuItems.Add(
new MenuActionItem()
{
category = category,
entryName = string.Format(commandNameTemplate, TypeUtility.GetDisplayName(assetType), TypeUtility.GetDisplayName(objectReference.type)),
isActiveInMode = true,
priority = TypeUtility.IsBuiltIn(assetType) ? builtInPriority++ : customPriority++,
state = enabled ? ActionValidity.Valid : ActionValidity.Invalid,
callback = menuCallback
}
);
}
}
foreach (var assetType in visibleAssetTypes)
{
var assetItemType = assetType;
var category = TimelineHelpers.GetItemCategoryName(assetType);
var commandName = string.Format(Styles.addItemTemplate, TypeUtility.GetDisplayName(assetType));
GenericMenu.MenuFunction command = () =>
{
foreach (var t in tracks)
{
TimelineHelpers.CreateClipOnTrack(assetItemType, t, candidateTime);
}
};
menuItems.Add(
new MenuActionItem()
{
category = category,
entryName = commandName,
isActiveInMode = true,
priority = TypeUtility.IsBuiltIn(assetItemType) ? builtInPriority++ : customPriority++,
state = enabled ? ActionValidity.Valid : ActionValidity.Invalid,
callback = command
}
);
}
}
static void AddMarkerMenuCommands(List<MenuActionItem> menu, IEnumerable<Type> markerTypes, Action<Type, Object> addMarkerCommand, bool enabled)
{
int builtInPriority = MenuPriority.AddItem.addMarker;
int customPriority = MenuPriority.AddItem.addCustomMarker;
foreach (var markerType in markerTypes)
{
var markerItemType = markerType;
string category = TimelineHelpers.GetItemCategoryName(markerItemType);
menu.Add(
new MenuActionItem()
{
category = category,
entryName = string.Format(Styles.addItemTemplate, TypeUtility.GetDisplayName(markerType)),
isActiveInMode = true,
priority = TypeUtility.IsBuiltIn(markerType) ? builtInPriority++ : customPriority++,
state = enabled ? ActionValidity.Valid : ActionValidity.Invalid,
callback = () => addMarkerCommand(markerItemType, null)
}
);
foreach (var objectReference in TypeUtility.ObjectReferencesForType(markerType))
{
var isSceneReference = objectReference.isSceneReference;
GenericMenu.MenuFunction menuCallback = () =>
{
Type assetDataType = objectReference.type;
ObjectSelector.get.titleContent = EditorGUIUtility.TrTextContent(string.Format(Styles.typeSelectorTemplate, TypeUtility.GetDisplayName(assetDataType)));
ObjectSelector.get.Show(null, assetDataType, null, isSceneReference, null, obj =>
{
if (obj != null)
addMarkerCommand(markerItemType, obj);
}, null);
};
menu.Add(
new MenuActionItem
{
category = TimelineHelpers.GetItemCategoryName(markerItemType),
entryName = string.Format(Styles.addItemFromAssetTemplate, TypeUtility.GetDisplayName(markerType), TypeUtility.GetDisplayName(objectReference.type)),
isActiveInMode = true,
priority = TypeUtility.IsBuiltIn(markerType) ? builtInPriority++ : customPriority++,
state = enabled ? ActionValidity.Valid : ActionValidity.Invalid,
callback = menuCallback
}
);
}
}
}
static void AddMarkerMenuCommands(List<MenuActionItem> menuItems, ICollection<TrackAsset> tracks, double candidateTime)
{
if (tracks.Count == 0)
return;
var enabled = tracks.All(t => !t.lockedInHierarchy) && !TimelineWindow.instance.state.editSequence.isReadOnly;
var addMarkerCommand = new Action<Type, Object>((type, obj) => AddMarkersCallback(tracks, type, candidateTime, obj));
AddMarkerMenuCommands(menuItems, tracks, addMarkerCommand, enabled);
}
static void AddMarkerMenuCommands(List<MenuActionItem> menuItems, ICollection<TrackAsset> tracks, Action<Type, Object> command, bool enabled)
{
var markerTypes = TypeUtility.GetBuiltInMarkerTypes().Union(TypeUtility.GetUserMarkerTypes());
if (tracks != null)
markerTypes = markerTypes.Where(x => tracks.All(track => (track == null) || TypeUtility.DoesTrackSupportMarkerType(track, x))); // null track indicates marker track to be created
AddMarkerMenuCommands(menuItems, markerTypes, command, enabled);
}
static void AddMarkersCallback(ICollection<TrackAsset> targets, Type markerType, double time, Object obj)
{
SelectionManager.Clear();
foreach (var target in targets)
{
var marker = TimelineHelpers.CreateMarkerOnTrack(markerType, obj, target, time);
SelectionManager.Add(marker);
}
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: de86b4ed8106fd84a8bc2f5d69798d53
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,34 @@
namespace UnityEditor.Timeline.Actions
{
/// <summary>
/// Base class for a timeline action.
/// Inherit from this class to make an action on a timeline after a menu click and/or a key shortcut.
/// </summary>
/// <remarks>
/// To add an action as a menu item in the Timeline context menu, add <see cref="MenuEntryAttribute"/> on the action class.
/// To make an action to react to a shortcut, use the Shortcut Manager API with <see cref="TimelineShortcutAttribute"/>.
/// <seealso cref="UnityEditor.ShortcutManagement.ShortcutAttribute"/>
/// <seealso cref="ActiveInModeAttribute"/>
/// </remarks>
/// <example>
/// Simple Timeline Action example (with context menu and shortcut support).
/// <code source="../../DocCodeExamples/ActionExamples.cs" region="declare-sampleTimelineAction" title="SampleTimelineAction"/>
/// </example>
[ActiveInMode(TimelineModes.Default)]
public abstract class TimelineAction : IAction
{
/// <summary>
/// Execute the action.
/// </summary>
/// <param name="context">Context for the action.</param>
/// <returns>true if the action has been executed. false otherwise</returns>
public abstract bool Execute(ActionContext context);
/// <summary>
/// Defines the validity of an Action based on the context.
/// </summary>
/// <param name="context">Context for the action.</param>
/// <returns>Visual state of the menu for the action.</returns>
public abstract ActionValidity Validate(ActionContext context);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 38eaad816666dfb428c3f123a09413c6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,970 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.ShortcutManagement;
using UnityEditor.Timeline.Actions;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
[MenuEntry("Copy", MenuPriority.TimelineActionSection.copy)]
[Shortcut("Main Menu/Edit/Copy", EventCommandNames.Copy)]
class CopyAction : TimelineAction
{
public override ActionValidity Validate(ActionContext context)
{
if (SelectionManager.Count() == 0)
return ActionValidity.NotApplicable;
if (context.tracks.ContainsTimelineMarkerTrack(context.timeline))
return ActionValidity.NotApplicable;
return ActionValidity.Valid;
}
public override bool Execute(ActionContext context)
{
TimelineEditor.clipboard.Clear();
var clips = context.clips;
if (clips.Any())
{
clips.Invoke<CopyClipsToClipboard>();
}
var markers = context.markers;
if (markers.Any())
{
markers.Invoke<CopyMarkersToClipboard>();
}
var tracks = context.tracks;
if (tracks.Any())
{
CopyTracksToClipboard.Do(tracks.ToArray());
}
return true;
}
}
[MenuEntry("Paste", MenuPriority.TimelineActionSection.paste)]
[Shortcut("Main Menu/Edit/Paste", EventCommandNames.Paste)]
class PasteAction : TimelineAction
{
public override ActionValidity Validate(ActionContext context)
{
return CanPaste(context.invocationTime) ? ActionValidity.Valid : ActionValidity.Invalid;
}
public override bool Execute(ActionContext context)
{
if (!CanPaste(context.invocationTime))
return false;
PasteItems(context.invocationTime);
PasteTracks();
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
return true;
}
static bool CanPaste(double? invocationTime)
{
var copiedItems = TimelineEditor.clipboard.GetCopiedItems().ToList();
if (!copiedItems.Any())
return TimelineEditor.clipboard.GetTracks().Any();
return CanPasteItems(copiedItems, invocationTime);
}
static bool CanPasteItems(ICollection<ItemsPerTrack> itemsGroups, double? invocationTime)
{
var hasItemsCopiedFromMultipleTracks = itemsGroups.Count > 1;
var allItemsCopiedFromCurrentAsset = itemsGroups.All(x => x.targetTrack.timelineAsset == TimelineEditor.inspectedAsset);
var hasUsedShortcut = invocationTime == null;
var anySourceLocked = itemsGroups.Any(x => x.targetTrack != null && x.targetTrack.lockedInHierarchy);
var targetTrack = GetPickedTrack();
if (targetTrack == null)
targetTrack = SelectionManager.SelectedTracks().FirstOrDefault();
//do not paste if the user copied items from another timeline
//if the copied items comes from > 1 track (since we do not know where to paste the copied items)
//or if a keyboard shortcut was used (since the user will not see the paste result)
if (!allItemsCopiedFromCurrentAsset)
{
var isSelectedTrackInCurrentAsset = targetTrack != null && targetTrack.timelineAsset == TimelineEditor.inspectedAsset;
if (hasItemsCopiedFromMultipleTracks || (hasUsedShortcut && !isSelectedTrackInCurrentAsset))
return false;
}
// pasting to items to their source track, if items from multiple tracks are selected
// and no track is in the selection items will each be pasted to their respective track.
if (targetTrack == null || itemsGroups.All(x => x.targetTrack == targetTrack))
return !anySourceLocked;
if (hasItemsCopiedFromMultipleTracks)
{
//do not paste if the track which received the paste action does not contain a copied clip
return !anySourceLocked && itemsGroups.Select(x => x.targetTrack).Contains(targetTrack);
}
var copiedItems = itemsGroups.SelectMany(i => i.items);
return IsTrackValidForItems(targetTrack, copiedItems);
}
static void PasteItems(double? invocationTime)
{
var copiedItems = TimelineEditor.clipboard.GetCopiedItems().ToList();
var numberOfUniqueParentsInClipboard = copiedItems.Count();
if (numberOfUniqueParentsInClipboard == 0) return;
List<ITimelineItem> newItems;
//if the copied items were on a single parent, then use the mouse position to get the parent OR the original parent
if (numberOfUniqueParentsInClipboard == 1)
{
var itemsGroup = copiedItems.First();
TrackAsset target = null;
if (invocationTime.HasValue)
target = GetPickedTrack();
if (target == null)
target = FindSuitableParentForSingleTrackPasteWithoutMouse(itemsGroup);
var candidateTime = invocationTime ?? TimelineHelpers.GetCandidateTime(null, target);
newItems = TimelineHelpers.DuplicateItemsUsingCurrentEditMode(TimelineEditor.clipboard.exposedPropertyTable, TimelineEditor.inspectedDirector, itemsGroup, target, candidateTime, "Paste Items").ToList();
}
//if copied items were on multiple parents, then the destination parents are the same as the original parents
else
{
var time = invocationTime ?? TimelineHelpers.GetCandidateTime(null, copiedItems.Select(c => c.targetTrack).ToArray());
newItems = TimelineHelpers.DuplicateItemsUsingCurrentEditMode(TimelineEditor.clipboard.exposedPropertyTable, TimelineEditor.inspectedDirector, copiedItems, time, "Paste Items").ToList();
}
TimelineHelpers.FrameItems(newItems);
SelectionManager.RemoveTimelineSelection();
foreach (var item in newItems)
{
SelectionManager.Add(item);
}
}
static TrackAsset FindSuitableParentForSingleTrackPasteWithoutMouse(ItemsPerTrack itemsGroup)
{
var groupParent = itemsGroup.targetTrack; //set a main parent in the clipboard
var selectedTracks = SelectionManager.SelectedTracks();
if (selectedTracks.Contains(groupParent))
{
return groupParent;
}
//find a selected track suitable for all items
var itemsToPaste = itemsGroup.items;
var compatibleTrack = selectedTracks.FirstOrDefault(t => IsTrackValidForItems(t, itemsToPaste));
return compatibleTrack != null ? compatibleTrack : groupParent;
}
static bool IsTrackValidForItems(TrackAsset track, IEnumerable<ITimelineItem> items)
{
if (track == null || track.lockedInHierarchy) return false;
return items.All(i => i.IsCompatibleWithTrack(track));
}
static TrackAsset GetPickedTrack()
{
if (PickerUtils.pickedElements == null)
return null;
var rowGUI = PickerUtils.pickedElements.OfType<IRowGUI>().FirstOrDefault();
if (rowGUI != null)
return rowGUI.asset;
return null;
}
static void PasteTracks()
{
var trackData = TimelineEditor.clipboard.GetTracks().ToList();
if (trackData.Any())
{
SelectionManager.RemoveTimelineSelection();
}
foreach (var track in trackData)
{
var newTrack = track.item.Duplicate(TimelineEditor.clipboard.exposedPropertyTable, TimelineEditor.inspectedDirector, TimelineEditor.inspectedAsset);
var newTracks = newTrack.GetFlattenedChildTracks().Append(newTrack);
var bindingIt = track.bindings.GetEnumerator();
var newTrackIt = newTracks.GetEnumerator();
while (bindingIt.MoveNext() && newTrackIt.MoveNext())
{
if (bindingIt.Current != null)
BindingUtility.Bind(TimelineEditor.inspectedDirector, newTrackIt.Current, bindingIt.Current);
}
SelectionManager.Add(newTrack);
foreach (var childTrack in newTrack.GetFlattenedChildTracks())
{
SelectionManager.Add(childTrack);
}
if (track.parent != null && track.parent.timelineAsset == TimelineEditor.inspectedAsset)
{
TrackExtensions.ReparentTracks(new List<TrackAsset> { newTrack }, track.parent, track.item);
}
}
}
}
[MenuEntry("Duplicate", MenuPriority.TimelineActionSection.duplicate)]
[Shortcut("Main Menu/Edit/Duplicate", EventCommandNames.Duplicate)]
class DuplicateAction : TimelineAction
{
public override ActionValidity Validate(ActionContext context)
{
IEnumerable<TrackAsset> tracks = context.tracks.RemoveTimelineMarkerTrackFromList(context.timeline);
return context.clips.Any() || tracks.Any() || context.markers.Any() ? ActionValidity.Valid : ActionValidity.NotApplicable;
}
public bool Execute(Func<ITimelineItem, ITimelineItem, double> gapBetweenItems)
{
return Execute(TimelineEditor.CurrentContext(), gapBetweenItems);
}
public override bool Execute(ActionContext context)
{
return Execute(context, (item1, item2) => ItemsUtils.TimeGapBetweenItems(item1, item2));
}
internal bool Execute(ActionContext context, Func<ITimelineItem, ITimelineItem, double> gapBetweenItems)
{
List<ITimelineItem> items = new List<ITimelineItem>();
items.AddRange(context.clips.Select(p => p.ToItem()));
items.AddRange(context.markers.Select(p => p.ToItem()));
List<ItemsPerTrack> selectedItems = items.ToItemsPerTrack().ToList();
if (selectedItems.Any())
{
var requestedTime = CalculateDuplicateTime(selectedItems, gapBetweenItems);
var duplicatedItems = TimelineHelpers.DuplicateItemsUsingCurrentEditMode(TimelineEditor.inspectedDirector, TimelineEditor.inspectedDirector, selectedItems, requestedTime, "Duplicate Items");
TimelineHelpers.FrameItems(duplicatedItems);
SelectionManager.RemoveTimelineSelection();
foreach (var item in duplicatedItems)
SelectionManager.Add(item);
}
var tracks = context.tracks.ToArray();
if (tracks.Length > 0)
tracks.Invoke<DuplicateTracks>();
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
return true;
}
static double CalculateDuplicateTime(IEnumerable<ItemsPerTrack> duplicatedItems, Func<ITimelineItem, ITimelineItem, double> gapBetweenItems)
{
//Find the end time of the rightmost item
var itemsOnTracks = duplicatedItems.SelectMany(i => i.targetTrack.GetItems()).ToList();
var time = itemsOnTracks.Max(i => i.end);
//From all the duplicated items, select the leftmost items
var firstDuplicatedItems = duplicatedItems.Select(i => i.leftMostItem);
var leftMostDuplicatedItems = firstDuplicatedItems.OrderBy(i => i.start).GroupBy(i => i.start).FirstOrDefault();
if (leftMostDuplicatedItems == null) return 0.0;
foreach (var leftMostItem in leftMostDuplicatedItems)
{
var siblings = leftMostItem.parentTrack.GetItems();
var rightMostSiblings = siblings.OrderByDescending(i => i.end).GroupBy(i => i.end).FirstOrDefault();
if (rightMostSiblings == null) continue;
foreach (var sibling in rightMostSiblings)
time = Math.Max(time, sibling.end + gapBetweenItems(leftMostItem, sibling));
}
return time;
}
}
[MenuEntry("Delete", MenuPriority.TimelineActionSection.delete)]
[Shortcut("Main Menu/Edit/Delete", EventCommandNames.Delete)]
[ShortcutPlatformOverride(RuntimePlatform.OSXEditor, KeyCode.Backspace, ShortcutModifiers.Action)]
[ActiveInMode(TimelineModes.Default)]
class DeleteAction : TimelineAction
{
public override ActionValidity Validate(ActionContext context)
{
return CanDelete(context) ? ActionValidity.Valid : ActionValidity.Invalid;
}
static bool CanDelete(ActionContext context)
{
if (TimelineWindow.instance.state.editSequence.isReadOnly)
return false;
if (context.tracks.ContainsTimelineMarkerTrack(context.timeline))
return false;
// All() returns true when empty
return context.tracks.All(x => !x.lockedInHierarchy) &&
context.clips.All(x => x.GetParentTrack() == null || !x.GetParentTrack().lockedInHierarchy) &&
context.markers.All(x => x.parent == null || !x.parent.lockedInHierarchy);
}
public override bool Execute(ActionContext context)
{
if (!CanDelete(context))
return false;
var selectedItems = context.clips.Select(p => p.ToItem()).ToList();
selectedItems.AddRange(context.markers.Select(p => p.ToItem()));
DeleteItems(selectedItems);
if (context.tracks.Any() && SelectionManager.GetCurrentInlineEditorCurve() == null)
context.tracks.Invoke<DeleteTracks>();
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
return selectedItems.Any() || context.tracks.Any();
}
internal static void DeleteItems(IEnumerable<ITimelineItem> items)
{
var tracks = items.GroupBy(c => c.parentTrack);
foreach (var track in tracks)
TimelineUndo.PushUndo(track.Key, L10n.Tr("Delete Items"));
TimelineAnimationUtilities.UnlinkAnimationWindowFromClips(items.OfType<ClipItem>().Select(i => i.clip));
EditMode.PrepareItemsDelete(ItemsUtils.ToItemsPerTrack(items));
EditModeUtils.Delete(items);
SelectionManager.RemoveAllClips();
}
}
[MenuEntry("Match Content", MenuPriority.TimelineActionSection.matchContent)]
[Shortcut(Shortcuts.Timeline.matchContent)]
class MatchContent : TimelineAction
{
public override ActionValidity Validate(ActionContext actionContext)
{
var clips = actionContext.clips;
if (!clips.Any())
return ActionValidity.NotApplicable;
return clips.Any(TimelineHelpers.HasUsableAssetDuration)
? ActionValidity.Valid
: ActionValidity.Invalid;
}
public override bool Execute(ActionContext actionContext)
{
var clips = actionContext.clips;
return clips.Any() && ClipModifier.MatchContent(clips);
}
}
[Shortcut(Shortcuts.Timeline.play)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class PlayTimelineAction : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
var currentState = TimelineEditor.state.playing;
TimelineEditor.state.SetPlaying(!currentState);
return true;
}
}
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class SelectAllAction : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
// otherwise select all tracks.
SelectionManager.Clear();
TimelineWindow.instance.allTracks.ForEach(x => SelectionManager.Add(x.track));
return true;
}
}
[Shortcut(Shortcuts.Timeline.previousFrame)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class PreviousFrameAction : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
if (TimelineEditor.inspectedAsset == null)
return false;
TimelineEditor.inspectedSequenceTime = TimeUtility.PreviousFrameTime(TimelineEditor.inspectedSequenceTime, TimelineEditor.inspectedAsset.editorSettings.frameRate);
return true;
}
}
[Shortcut(Shortcuts.Timeline.nextFrame)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class NextFrameAction : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
if (TimelineEditor.inspectedAsset == null)
return false;
TimelineEditor.inspectedSequenceTime = TimeUtility.NextFrameTime(TimelineEditor.inspectedSequenceTime, TimelineEditor.inspectedAsset.editorSettings.frameRate);
return true;
}
}
[Shortcut(Shortcuts.Timeline.frameAll)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class FrameAllAction : TimelineAction
{
public override ActionValidity Validate(ActionContext context)
{
if (context.timeline != null && !context.timeline.flattenedTracks.Any())
return ActionValidity.NotApplicable;
return ActionValidity.Valid;
}
public override bool Execute(ActionContext actionContext)
{
var inlineCurveEditor = SelectionManager.GetCurrentInlineEditorCurve();
if (FrameSelectedAction.ShouldHandleInlineCurve(inlineCurveEditor))
{
FrameSelectedAction.FrameInlineCurves(inlineCurveEditor, false);
return true;
}
if (TimelineWindow.instance.state.IsCurrentEditingASequencerTextField())
return false;
var visibleTracks = TimelineWindow.instance.treeView.visibleTracks.ToList();
if (TimelineEditor.inspectedAsset != null && TimelineEditor.inspectedAsset.markerTrack != null)
visibleTracks.Add(TimelineEditor.inspectedAsset.markerTrack);
if (visibleTracks.Count == 0)
return false;
var startTime = float.MaxValue;
var endTime = float.MinValue;
foreach (var t in visibleTracks)
{
if (t == null)
continue;
// time range based on track's curves and clips.
double trackStart, trackEnd, trackDuration;
t.GetSequenceTime(out trackStart, out trackDuration);
trackEnd = trackStart + trackDuration;
// take track's markers into account
double itemsStart, itemsEnd;
ItemsUtils.GetItemRange(t, out itemsStart, out itemsEnd);
startTime = Mathf.Min(startTime, (float)trackStart, (float)itemsStart);
endTime = Mathf.Max(endTime, (float)(trackEnd), (float)itemsEnd);
}
FrameSelectedAction.FrameRange(startTime, endTime);
return true;
}
}
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class FrameSelectedAction : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public static void FrameRange(float startTime, float endTime)
{
if (startTime > endTime)
{
return;
}
var halfDuration = endTime - Math.Max(0.0f, startTime);
if (halfDuration > 0.0f)
{
TimelineEditor.visibleTimeRange = new Vector2(Mathf.Max(0.0f, startTime - (halfDuration * 0.1f)), endTime + (halfDuration * 0.1f));
}
else
{
// start == end
// keep the zoom level constant, only pan the time area to center the item
var currentRange = TimelineEditor.visibleTimeRange.y - TimelineEditor.visibleTimeRange.x;
TimelineEditor.visibleTimeRange = new Vector2(startTime - currentRange / 2, startTime + currentRange / 2);
}
TimelineZoomManipulator.InvalidateWheelZoom();
TimelineEditor.Refresh(RefreshReason.SceneNeedsUpdate);
}
public override bool Execute(ActionContext actionContext)
{
var inlineCurveEditor = SelectionManager.GetCurrentInlineEditorCurve();
if (ShouldHandleInlineCurve(inlineCurveEditor))
{
FrameInlineCurves(inlineCurveEditor, true);
return true;
}
if (TimelineWindow.instance.state.IsCurrentEditingASequencerTextField())
return false;
if (SelectionManager.Count() == 0)
{
actionContext.Invoke<FrameAllAction>();
return true;
}
var startTime = float.MaxValue;
var endTime = float.MinValue;
var clips = actionContext.clips.Select(ItemToItemGui.GetGuiForClip);
var markers = actionContext.markers;
if (!clips.Any() && !markers.Any())
return false;
foreach (var c in clips)
{
startTime = Mathf.Min(startTime, (float)c.clip.start);
endTime = Mathf.Max(endTime, (float)c.clip.end);
if (c.clipCurveEditor != null)
{
c.clipCurveEditor.FrameClip();
}
}
foreach (var marker in markers)
{
startTime = Mathf.Min(startTime, (float)marker.time);
endTime = Mathf.Max(endTime, (float)marker.time);
}
FrameRange(startTime, endTime);
return true;
}
public static bool ShouldHandleInlineCurve(IClipCurveEditorOwner curveEditorOwner)
{
return curveEditorOwner?.clipCurveEditor != null &&
curveEditorOwner.inlineCurvesSelected &&
curveEditorOwner.owner != null &&
curveEditorOwner.owner.GetShowInlineCurves();
}
public static void FrameInlineCurves(IClipCurveEditorOwner curveEditorOwner, bool selectionOnly)
{
var curveEditor = curveEditorOwner.clipCurveEditor.curveEditor;
var frameBounds = selectionOnly ? curveEditor.GetSelectionBounds() : curveEditor.GetClipBounds();
var clipGUI = curveEditorOwner as TimelineClipGUI;
var areaOffset = 0.0f;
if (clipGUI != null)
{
areaOffset = (float)Math.Max(0.0, clipGUI.clip.FromLocalTimeUnbound(0.0));
var timeScale = (float)clipGUI.clip.timeScale; // Note: The getter for clip.timeScale is guaranteed to never be zero.
// Apply scaling
var newMin = frameBounds.min.x / timeScale;
var newMax = (frameBounds.max.x - frameBounds.min.x) / timeScale + newMin;
frameBounds.SetMinMax(
new Vector3(newMin, frameBounds.min.y, frameBounds.min.z),
new Vector3(newMax, frameBounds.max.y, frameBounds.max.z));
}
curveEditor.Frame(frameBounds, true, true);
var area = curveEditor.shownAreaInsideMargins;
area.x += areaOffset;
var curveStart = curveEditorOwner.clipCurveEditor.dataSource.start;
FrameRange(curveStart + frameBounds.min.x, curveStart + frameBounds.max.x);
}
}
[Shortcut(Shortcuts.Timeline.previousKey)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class PrevKeyAction : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
if (TimelineEditor.inspectedAsset == null)
return false;
var keyTraverser = new Utilities.KeyTraverser(TimelineEditor.inspectedAsset, 0.01f / (float)TimelineEditor.inspectedAsset.editorSettings.frameRate);
var time = keyTraverser.GetPrevKey((float)TimelineEditor.inspectedSequenceTime, TimelineWindow.instance.state.dirtyStamp);
if (time != TimelineEditor.inspectedSequenceTime)
{
TimelineEditor.inspectedSequenceTime = time;
}
return true;
}
}
[Shortcut(Shortcuts.Timeline.nextKey)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class NextKeyAction : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
if (TimelineEditor.inspectedAsset == null)
return false;
var keyTraverser = new Utilities.KeyTraverser(TimelineEditor.inspectedAsset, 0.01f / (float)TimelineEditor.inspectedAsset.editorSettings.frameRate);
var time = keyTraverser.GetNextKey((float)TimelineEditor.inspectedSequenceTime, TimelineWindow.instance.state.dirtyStamp);
if (time != TimelineEditor.inspectedSequenceTime)
{
TimelineEditor.inspectedSequenceTime = time;
}
return true;
}
}
[Shortcut(Shortcuts.Timeline.goToStart)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class GotoStartAction : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
TimelineEditor.inspectedSequenceTime = 0.0f;
TimelineWindow.instance.state.EnsurePlayHeadIsVisible();
return true;
}
}
[Shortcut(Shortcuts.Timeline.goToEnd)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class GotoEndAction : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
TimelineEditor.inspectedSequenceTime = TimelineWindow.instance.state.editSequence.duration;
TimelineWindow.instance.state.EnsurePlayHeadIsVisible();
return true;
}
}
[Shortcut(Shortcuts.Timeline.zoomIn)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class ZoomIn : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
TimelineZoomManipulator.Instance.DoZoom(1.15f);
return true;
}
}
[Shortcut(Shortcuts.Timeline.zoomOut)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class ZoomOut : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
TimelineZoomManipulator.Instance.DoZoom(0.85f);
return true;
}
}
[Shortcut(Shortcuts.Timeline.navigateLeft)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class NavigateLeft : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
return KeyboardNavigation.NavigateLeft(actionContext.tracks);
}
}
[Shortcut(Shortcuts.Timeline.navigateRight)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class NavigateRight : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
return KeyboardNavigation.NavigateRight(actionContext.tracks);
}
}
[Shortcut(Shortcuts.Timeline.toggleCollapseTrack)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class ToggleCollapseGroup : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
return KeyboardNavigation.ToggleCollapseGroup(actionContext.tracks);
}
}
[Shortcut(Shortcuts.Timeline.selectLeftItem)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class SelectLeftClip : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
// Switches to track header if no left track exists
return KeyboardNavigation.SelectLeftItem();
}
}
[Shortcut(Shortcuts.Timeline.selectRightItem)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class SelectRightClip : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
return KeyboardNavigation.SelectRightItem();
}
}
[Shortcut(Shortcuts.Timeline.selectUpItem)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class SelectUpClip : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
return KeyboardNavigation.SelectUpItem();
}
}
[Shortcut(Shortcuts.Timeline.selectUpTrack)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class SelectUpTrack : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
return KeyboardNavigation.SelectUpTrack();
}
}
[Shortcut(Shortcuts.Timeline.selectDownItem)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class SelectDownClip : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
return KeyboardNavigation.SelectDownItem();
}
}
[Shortcut(Shortcuts.Timeline.selectDownTrack)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class SelectDownTrack : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
if (!KeyboardNavigation.ClipAreaActive() && !KeyboardNavigation.TrackHeadActive())
return KeyboardNavigation.FocusFirstVisibleItem();
else
return KeyboardNavigation.SelectDownTrack();
}
}
[Shortcut(Shortcuts.Timeline.multiSelectLeft)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class MultiselectLeftClip : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
return KeyboardNavigation.SelectLeftItem(true);
}
}
[Shortcut(Shortcuts.Timeline.multiSelectRight)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class MultiselectRightClip : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
return KeyboardNavigation.SelectRightItem(true);
}
}
[Shortcut(Shortcuts.Timeline.multiSelectUp)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class MultiselectUpTrack : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
return KeyboardNavigation.SelectUpTrack(true);
}
}
[Shortcut(Shortcuts.Timeline.multiSelectDown)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class MultiselectDownTrack : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
return KeyboardNavigation.SelectDownTrack(true);
}
}
[Shortcut(Shortcuts.Timeline.toggleClipTrackArea)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class ToggleClipTrackArea : TimelineAction
{
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
public override bool Execute(ActionContext actionContext)
{
if (KeyboardNavigation.TrackHeadActive())
return KeyboardNavigation.FocusFirstVisibleItem(actionContext.tracks);
if (!KeyboardNavigation.ClipAreaActive())
return KeyboardNavigation.FocusFirstVisibleItem();
var item = KeyboardNavigation.GetVisibleSelectedItems().LastOrDefault();
if (item != null)
SelectionManager.SelectOnly(item.parentTrack);
return true;
}
}
[MenuEntry("Key All Animated", MenuPriority.TimelineActionSection.keyAllAnimated)]
[Shortcut(Shortcuts.Timeline.keyAllAnimated)]
class KeyAllAnimated : TimelineAction
{
public override ActionValidity Validate(ActionContext actionContext)
{
return CanExecute(TimelineEditor.state, actionContext)
? ActionValidity.Valid
: ActionValidity.NotApplicable;
}
public override bool Execute(ActionContext actionContext)
{
WindowState state = TimelineEditor.state;
PlayableDirector director = TimelineEditor.inspectedDirector;
if (!CanExecute(state, actionContext) || director == null)
return false;
IEnumerable<TrackAsset> keyableTracks = GetKeyableTracks(state, actionContext);
var curveSelected = SelectionManager.GetCurrentInlineEditorCurve();
if (curveSelected != null)
{
var sel = curveSelected.clipCurveEditor.GetSelectedProperties().ToList();
var go = (director.GetGenericBinding(curveSelected.owner) as Component).gameObject;
if (sel.Count > 0)
{
TimelineRecording.KeyProperties(go, state, sel);
}
else
{
var binding = director.GetGenericBinding(curveSelected.owner) as Component;
TimelineRecording.KeyAllProperties(binding, state);
}
}
else
{
foreach (var track in keyableTracks)
{
var binding = director.GetGenericBinding(track) as Component;
TimelineRecording.KeyAllProperties(binding, state);
}
}
return true;
}
static IEnumerable<TrackAsset> GetKeyableTracks(WindowState state, ActionContext context)
{
if (!context.clips.Any() && !context.tracks.Any()) //no selection -> animate all recorded tracks
return context.timeline.flattenedTracks.Where(state.IsArmedForRecord);
List<TrackAsset> parentTracks = context.tracks.ToList();
parentTracks.AddRange(context.clips.Select(clip => clip.GetParentTrack()).Distinct());
if (!parentTracks.All(state.IsArmedForRecord))
return Enumerable.Empty<TrackAsset>();
return parentTracks;
}
static bool CanExecute(WindowState state, ActionContext context)
{
if (context.timeline == null)
return false;
if (context.markers.Any())
return false;
if (context.tracks.ContainsTimelineMarkerTrack(context.timeline))
return false;
IClipCurveEditorOwner curveSelected = SelectionManager.GetCurrentInlineEditorCurve();
// Can't have an inline curve selected and have multiple tracks also.
if (curveSelected != null)
{
return state.IsArmedForRecord(curveSelected.owner);
}
return GetKeyableTracks(state, context).Any();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b1c789407b55e3a4c9cc86135a714e33
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,36 @@
using System.Collections.Generic;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline.Actions
{
/// <summary>
/// Base class for a track action.
/// Inherit from this class to make an action that would react on selected tracks after a menu click and/or a key shortcut.
/// </summary>
/// <example>
/// Simple track Action example (with context menu and shortcut support).
/// <code source="../../DocCodeExamples/ActionExamples.cs" region="declare-sampleTrackAction" title="SampleTrackAction"/>
/// </example>
/// <remarks>
/// To add an action as a menu item in the Timeline context menu, add <see cref="MenuEntryAttribute"/> on the action class.
/// To make an action to react to a shortcut, use the Shortcut Manager API with <see cref="TimelineShortcutAttribute"/>.
/// <seealso cref="UnityEditor.ShortcutManagement.ShortcutAttribute"/>
/// </remarks>
[ActiveInMode(TimelineModes.Default)]
public abstract class TrackAction : IAction
{
/// <summary>
/// Execute the action.
/// </summary>
/// <param name="tracks">Tracks that will be used for the action. </param>
/// <returns>true if the action has been executed. false otherwise</returns>
public abstract bool Execute(IEnumerable<TrackAsset> tracks);
/// <summary>
/// Defines the validity of an Action for a given set of tracks.
/// </summary>
/// <param name="tracks">tracks that the action would act on.</param>
/// <returns>The validity of the set of tracks.</returns>
public abstract ActionValidity Validate(IEnumerable<TrackAsset> tracks);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bcac94f4ceb0c8049bf47480708ca7e2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,527 @@
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using UnityEditor.Timeline.Actions;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
[MenuEntry("Edit in Animation Window", MenuPriority.TrackActionSection.editInAnimationWindow)]
class EditTrackInAnimationWindow : TrackAction
{
public static bool Do(TrackAsset track)
{
AnimationClip clipToEdit = null;
AnimationTrack animationTrack = track as AnimationTrack;
if (animationTrack != null)
{
if (!animationTrack.CanConvertToClipMode())
return false;
clipToEdit = animationTrack.infiniteClip;
}
else if (track.hasCurves)
{
clipToEdit = track.curves;
}
if (clipToEdit == null)
return false;
GameObject gameObject = null;
if (TimelineEditor.inspectedDirector != null)
gameObject = TimelineUtility.GetSceneGameObject(TimelineEditor.inspectedDirector, track);
var timeController = TimelineAnimationUtilities.CreateTimeController(CreateTimeControlClipData(track));
TimelineAnimationUtilities.EditAnimationClipWithTimeController(clipToEdit, timeController, gameObject);
return true;
}
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
{
if (!tracks.Any())
return ActionValidity.Invalid;
var firstTrack = tracks.First();
if (firstTrack is AnimationTrack)
{
var animTrack = firstTrack as AnimationTrack;
if (animTrack.CanConvertToClipMode())
return ActionValidity.Valid;
}
else if (firstTrack.hasCurves)
{
return ActionValidity.Valid;
}
return ActionValidity.NotApplicable;
}
public override bool Execute(IEnumerable<TrackAsset> tracks)
{
return Do(tracks.First());
}
static TimelineWindowTimeControl.ClipData CreateTimeControlClipData(TrackAsset track)
{
var data = new TimelineWindowTimeControl.ClipData();
data.track = track;
data.start = track.start;
data.duration = track.duration;
return data;
}
}
[MenuEntry("Lock selected track only", MenuPriority.TrackActionSection.lockSelected)]
class LockSelectedTrack : TrackAction, IMenuName
{
public static readonly string LockSelectedTrackOnlyText = L10n.Tr("Lock selected track only");
public static readonly string UnlockSelectedTrackOnlyText = L10n.Tr("Unlock selected track only");
public string menuName { get; private set; }
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
{
UpdateMenuName(tracks);
if (tracks.Any(track => TimelineUtility.IsLockedFromGroup(track) || track is GroupTrack || !track.subTracksObjects.Any()))
return ActionValidity.NotApplicable;
return ActionValidity.Valid;
}
public override bool Execute(IEnumerable<TrackAsset> tracks)
{
if (!tracks.Any()) return false;
var hasUnlockedTracks = tracks.Any(x => !x.locked);
Lock(tracks.Where(p => !(p is GroupTrack)).ToArray(), hasUnlockedTracks);
return true;
}
void UpdateMenuName(IEnumerable<TrackAsset> tracks)
{
menuName = tracks.All(t => t.locked) ? UnlockSelectedTrackOnlyText : LockSelectedTrackOnlyText;
}
public static void Lock(TrackAsset[] tracks, bool shouldlock)
{
if (tracks.Length == 0)
return;
foreach (var track in tracks.Where(t => !TimelineUtility.IsLockedFromGroup(t)))
{
TimelineUndo.PushUndo(track, L10n.Tr("Lock Tracks"));
track.locked = shouldlock;
}
TimelineEditor.Refresh(RefreshReason.WindowNeedsRedraw);
}
}
[MenuEntry("Lock", MenuPriority.TrackActionSection.lockTrack)]
[Shortcut(Shortcuts.Timeline.toggleLock)]
class LockTrack : TrackAction, IMenuName
{
static readonly string k_LockText = L10n.Tr("Lock");
static readonly string k_UnlockText = L10n.Tr("Unlock");
public string menuName { get; private set; }
void UpdateMenuName(IEnumerable<TrackAsset> tracks)
{
menuName = tracks.Any(x => !x.locked) ? k_LockText : k_UnlockText;
}
public override bool Execute(IEnumerable<TrackAsset> tracks)
{
if (!tracks.Any()) return false;
var hasUnlockedTracks = tracks.Any(x => !x.locked);
SetLockState(tracks, hasUnlockedTracks);
return true;
}
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
{
UpdateMenuName(tracks);
tracks = tracks.RemoveTimelineMarkerTrackFromList(TimelineEditor.inspectedAsset);
if (!tracks.Any())
return ActionValidity.NotApplicable;
if (tracks.Any(TimelineUtility.IsLockedFromGroup))
return ActionValidity.Invalid;
return ActionValidity.Valid;
}
public static void SetLockState(IEnumerable<TrackAsset> tracks, bool shouldLock)
{
if (!tracks.Any())
return;
foreach (var track in tracks)
{
if (TimelineUtility.IsLockedFromGroup(track))
continue;
if (track as GroupTrack == null)
SetLockState(track.GetChildTracks().ToArray(), shouldLock);
TimelineUndo.PushUndo(track, L10n.Tr("Lock Tracks"));
track.locked = shouldLock;
}
// find the tracks we've locked. unselect anything locked and remove recording.
foreach (var track in tracks)
{
if (TimelineUtility.IsLockedFromGroup(track) || !track.locked)
continue;
var flattenedChildTracks = track.GetFlattenedChildTracks();
foreach (var i in track.clips)
SelectionManager.Remove(i);
track.UnarmForRecord();
foreach (var child in flattenedChildTracks)
{
SelectionManager.Remove(child);
child.UnarmForRecord();
foreach (var clip in child.GetClips())
SelectionManager.Remove(clip);
}
}
// no need to rebuild, just repaint (including inspectors)
InspectorWindow.RepaintAllInspectors();
TimelineEditor.Refresh(RefreshReason.WindowNeedsRedraw);
}
}
[UsedImplicitly]
[MenuEntry("Show Markers", MenuPriority.TrackActionSection.showHideMarkers)]
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
class ShowHideMarkers : TrackAction, IMenuChecked
{
public bool isChecked { get; private set; }
void UpdateCheckedStatus(IEnumerable<TrackAsset> tracks)
{
isChecked = tracks.All(x => x.GetShowMarkers());
}
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
{
UpdateCheckedStatus(tracks);
if (tracks.Any(x => x is GroupTrack) || tracks.Any(t => t.GetMarkerCount() == 0))
return ActionValidity.NotApplicable;
if (tracks.Any(t => t.lockedInHierarchy))
{
return ActionValidity.Invalid;
}
return ActionValidity.Valid;
}
public override bool Execute(IEnumerable<TrackAsset> tracks)
{
if (!tracks.Any()) return false;
var hasUnlockedTracks = tracks.Any(x => !x.GetShowMarkers());
ShowHide(tracks, hasUnlockedTracks);
return true;
}
static void ShowHide(IEnumerable<TrackAsset> tracks, bool shouldLock)
{
if (!tracks.Any())
return;
foreach (var track in tracks)
track.SetShowTrackMarkers(shouldLock);
TimelineEditor.Refresh(RefreshReason.WindowNeedsRedraw);
}
}
[MenuEntry("Mute selected track only", MenuPriority.TrackActionSection.muteSelected), UsedImplicitly]
class MuteSelectedTrack : TrackAction, IMenuName
{
public static readonly string MuteSelectedText = L10n.Tr("Mute selected track only");
public static readonly string UnmuteSelectedText = L10n.Tr("Unmute selected track only");
public string menuName { get; private set; }
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
{
UpdateMenuName(tracks);
if (tracks.Any(track => TimelineUtility.IsParentMuted(track) || track is GroupTrack || !track.subTracksObjects.Any()))
return ActionValidity.NotApplicable;
return ActionValidity.Valid;
}
public override bool Execute(IEnumerable<TrackAsset> tracks)
{
if (!tracks.Any())
return false;
var hasUnmutedTracks = tracks.Any(x => !x.muted);
Mute(tracks.Where(p => !(p is GroupTrack)).ToArray(), hasUnmutedTracks);
return true;
}
void UpdateMenuName(IEnumerable<TrackAsset> tracks)
{
menuName = tracks.All(t => t.muted) ? UnmuteSelectedText : MuteSelectedText;
}
public static void Mute(TrackAsset[] tracks, bool shouldMute)
{
if (tracks.Length == 0)
return;
foreach (var track in tracks.Where(t => !TimelineUtility.IsParentMuted(t)))
{
TimelineUndo.PushUndo(track, L10n.Tr("Mute Tracks"));
track.muted = shouldMute;
}
TimelineEditor.Refresh(RefreshReason.ContentsModified);
}
}
[MenuEntry("Mute", MenuPriority.TrackActionSection.mute)]
[Shortcut(Shortcuts.Timeline.toggleMute)]
class MuteTrack : TrackAction, IMenuName
{
static readonly string k_MuteText = L10n.Tr("Mute");
static readonly string k_UnMuteText = L10n.Tr("Unmute");
public string menuName { get; private set; }
void UpdateMenuName(IEnumerable<TrackAsset> tracks)
{
menuName = tracks.Any(x => !x.muted) ? k_MuteText : k_UnMuteText;
}
public override bool Execute(IEnumerable<TrackAsset> tracks)
{
if (!tracks.Any() || tracks.Any(TimelineUtility.IsParentMuted))
return false;
var hasUnmutedTracks = tracks.Any(x => !x.muted);
Mute(tracks, hasUnmutedTracks);
return true;
}
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
{
UpdateMenuName(tracks);
if (tracks.Any(TimelineUtility.IsLockedFromGroup))
return ActionValidity.Invalid;
return ActionValidity.Valid;
}
public static void Mute(IEnumerable<TrackAsset> tracks, bool shouldMute)
{
if (!tracks.Any())
return;
foreach (var track in tracks)
{
if (track as GroupTrack == null)
Mute(track.GetChildTracks().ToArray(), shouldMute);
TimelineUndo.PushUndo(track, L10n.Tr("Mute Tracks"));
track.muted = shouldMute;
}
TimelineEditor.Refresh(RefreshReason.ContentsModified);
}
}
class DeleteTracks : TrackAction
{
public static void Do(TimelineAsset timeline, TrackAsset track)
{
SelectionManager.Remove(track);
TrackModifier.DeleteTrack(timeline, track);
}
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks) => ActionValidity.Valid;
public override bool Execute(IEnumerable<TrackAsset> tracks)
{
tracks = tracks.RemoveTimelineMarkerTrackFromList(TimelineEditor.inspectedAsset);
// disable preview mode so deleted tracks revert to default state
// Case 956129: Disable preview mode _before_ deleting the tracks, since clip data is still needed
TimelineEditor.state.previewMode = false;
TimelineAnimationUtilities.UnlinkAnimationWindowFromTracks(tracks);
foreach (var track in tracks)
Do(TimelineEditor.inspectedAsset, track);
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
return true;
}
}
class CopyTracksToClipboard : TrackAction
{
public static bool Do(TrackAsset[] tracks)
{
var action = new CopyTracksToClipboard();
return action.Execute(tracks);
}
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks) => ActionValidity.Valid;
public override bool Execute(IEnumerable<TrackAsset> tracks)
{
tracks = tracks.RemoveTimelineMarkerTrackFromList(TimelineEditor.inspectedAsset);
TimelineEditor.clipboard.CopyTracks(tracks);
return true;
}
}
class DuplicateTracks : TrackAction
{
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks) => ActionValidity.Valid;
public override bool Execute(IEnumerable<TrackAsset> tracks)
{
tracks = tracks.RemoveTimelineMarkerTrackFromList(TimelineEditor.inspectedAsset);
if (tracks.Any())
{
SelectionManager.RemoveTimelineSelection();
}
foreach (var track in TrackExtensions.FilterTracks(tracks))
{
var newTrack = track.Duplicate(TimelineEditor.inspectedDirector, TimelineEditor.inspectedDirector);
//Add all duplicated tracks to selection
SelectionManager.Add(newTrack);
foreach (var childTrack in newTrack.GetFlattenedChildTracks())
{
SelectionManager.Add(childTrack);
}
//Duplicate bindings for tracks and subtracks
if (TimelineEditor.inspectedDirector != null)
{
DuplicateBindings(track, newTrack, TimelineEditor.inspectedDirector);
}
}
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
return true;
}
internal static void DuplicateBindings(TrackAsset track, TrackAsset newTrack, PlayableDirector director)
{
var originalTracks = track.GetFlattenedChildTracks().Append(track);
var newTracks = newTrack.GetFlattenedChildTracks().Append(newTrack);
var toBind = new List<Tuple<TrackAsset, Object>>();
// Collect all track bindings to duplicate
var originalIt = originalTracks.GetEnumerator();
var newIt = newTracks.GetEnumerator();
while (originalIt.MoveNext() && newIt.MoveNext())
{
var binding = director.GetGenericBinding(originalIt.Current);
if (binding != null)
toBind.Add(new Tuple<TrackAsset, Object>(newIt.Current, binding));
}
//Only create Director undo if there are bindings to duplicate
if (toBind.Count > 0)
TimelineUndo.PushUndo(TimelineEditor.inspectedDirector, L10n.Tr("Duplicate"));
//Assign bindings for all tracks after undo.
foreach (var binding in toBind)
{
TimelineEditor.inspectedDirector.SetGenericBinding(binding.Item1, binding.Item2);
}
}
}
[MenuEntry("Remove Invalid Markers", MenuPriority.TrackActionSection.removeInvalidMarkers), UsedImplicitly]
class RemoveInvalidMarkersAction : TrackAction
{
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
{
if (tracks.Any(target => target != null && target.GetMarkerCount() != target.GetMarkersRaw().Count()))
return ActionValidity.Valid;
return ActionValidity.NotApplicable;
}
public override bool Execute(IEnumerable<TrackAsset> tracks)
{
bool anyRemoved = false;
foreach (var target in tracks)
{
var invalids = target.GetMarkersRaw().Where(x => !(x is IMarker)).ToList();
foreach (var m in invalids)
{
anyRemoved = true;
target.DeleteMarkerRaw(m);
}
}
if (anyRemoved)
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
return anyRemoved;
}
}
[Shortcut(Shortcuts.Timeline.collapseTrack)]
[UsedImplicitly]
class CollapseTrackAction : TrackAction
{
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
{
var collapsibleTracks = tracks.Where(track => track.subTracksObjects.Any());
if (!collapsibleTracks.Any())
return ActionValidity.NotApplicable;
if (collapsibleTracks.All(track => track.IsCollapsed()))
return ActionValidity.NotApplicable;
return ActionValidity.Valid;
}
public override bool Execute(IEnumerable<TrackAsset> tracks)
{
return KeyboardNavigation.TryCollapse(tracks.Where(track => track.subTracksObjects.Any() && !track.IsCollapsed()));
}
}
[Shortcut(Shortcuts.Timeline.expandTrack)]
[UsedImplicitly]
class ExpandTrackAction : TrackAction
{
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
{
var collapsibleTracks = tracks.Where(track => track.subTracksObjects.Any());
if (!collapsibleTracks.Any())
return ActionValidity.NotApplicable;
if (collapsibleTracks.All(track => !track.IsCollapsed()))
return ActionValidity.NotApplicable;
return ActionValidity.Valid;
}
public override bool Execute(IEnumerable<TrackAsset> tracks)
{
return KeyboardNavigation.TryExpand(tracks.Where(track => track.subTracksObjects.Any() && track.IsCollapsed()));
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fda82b5ca7a4c5f40b497c4f5f4bd950
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: