307 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			307 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| 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;
 | |
|         }
 | |
|     }
 | |
| }
 |