No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ActionManager.cs 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using UnityEngine;
  6. using UnityEngine.Timeline;
  7. namespace UnityEditor.Timeline.Actions
  8. {
  9. static class ActionManager
  10. {
  11. static bool s_ShowActionTriggeredByShortcut = false;
  12. public static readonly IReadOnlyList<TimelineAction> TimelineActions = InstantiateClassesOfType<TimelineAction>();
  13. public static readonly IReadOnlyList<ClipAction> ClipActions = InstantiateClassesOfType<ClipAction>();
  14. public static readonly IReadOnlyList<TrackAction> TrackActions = InstantiateClassesOfType<TrackAction>();
  15. public static readonly IReadOnlyList<MarkerAction> MarkerActions = InstantiateClassesOfType<MarkerAction>();
  16. public static readonly IReadOnlyList<TimelineAction> TimelineActionsWithShortcuts = ActionsWithShortCuts(TimelineActions);
  17. public static readonly IReadOnlyList<ClipAction> ClipActionsWithShortcuts = ActionsWithShortCuts(ClipActions);
  18. public static readonly IReadOnlyList<TrackAction> TrackActionsWithShortcuts = ActionsWithShortCuts(TrackActions);
  19. public static readonly IReadOnlyList<MarkerAction> MarkerActionsWithShortcuts = ActionsWithShortCuts(MarkerActions);
  20. public static readonly HashSet<Type> ActionsWithAutoUndo = TypesWithAttribute<ApplyDefaultUndoAttribute>();
  21. public static TU GetCachedAction<T, TU>(this IReadOnlyList<TU> list) where T : TU
  22. {
  23. return list.FirstOrDefault(x => x.GetType() == typeof(T));
  24. }
  25. public static void GetMenuEntries(IReadOnlyList<TimelineAction> actions, Vector2? mousePos, List<MenuActionItem> menuItems)
  26. {
  27. var globalContext = TimelineEditor.CurrentContext(mousePos);
  28. foreach (var action in actions)
  29. {
  30. try
  31. {
  32. BuildMenu(action, globalContext, menuItems);
  33. }
  34. catch (Exception e)
  35. {
  36. Debug.LogException(e);
  37. }
  38. }
  39. }
  40. public static void GetMenuEntries(IReadOnlyList<TrackAction> actions, List<MenuActionItem> menuItems)
  41. {
  42. var tracks = SelectionManager.SelectedTracks();
  43. if (!tracks.Any())
  44. return;
  45. foreach (var action in actions)
  46. {
  47. try
  48. {
  49. BuildMenu(action, tracks, menuItems);
  50. }
  51. catch (Exception e)
  52. {
  53. Debug.LogException(e);
  54. }
  55. }
  56. }
  57. public static void GetMenuEntries(IReadOnlyList<ClipAction> actions, List<MenuActionItem> menuItems)
  58. {
  59. var clips = SelectionManager.SelectedClips();
  60. bool any = clips.Any();
  61. if (!clips.Any())
  62. return;
  63. foreach (var action in actions)
  64. {
  65. try
  66. {
  67. if (action is EditSubTimeline editSubTimelineAction)
  68. editSubTimelineAction.AddMenuItem(menuItems);
  69. else if (any)
  70. BuildMenu(action, clips, menuItems);
  71. }
  72. catch (Exception e)
  73. {
  74. Debug.LogException(e);
  75. }
  76. }
  77. }
  78. public static void GetMenuEntries(IReadOnlyList<MarkerAction> actions, List<MenuActionItem> menuItems)
  79. {
  80. var markers = SelectionManager.SelectedMarkers();
  81. if (!markers.Any())
  82. return;
  83. foreach (var action in actions)
  84. {
  85. try
  86. {
  87. BuildMenu(action, markers, menuItems);
  88. }
  89. catch (Exception e)
  90. {
  91. Debug.LogException(e);
  92. }
  93. }
  94. }
  95. static void BuildMenu(TimelineAction action, ActionContext context, List<MenuActionItem> menuItems)
  96. {
  97. BuildMenu(action, action.Validate(context), () => ExecuteTimelineAction(action, context), menuItems);
  98. }
  99. static void BuildMenu(TrackAction action, IEnumerable<TrackAsset> tracks, List<MenuActionItem> menuItems)
  100. {
  101. BuildMenu(action, action.Validate(tracks), () => ExecuteTrackAction(action, tracks), menuItems);
  102. }
  103. static void BuildMenu(ClipAction action, IEnumerable<TimelineClip> clips, List<MenuActionItem> menuItems)
  104. {
  105. BuildMenu(action, action.Validate(clips), () => ExecuteClipAction(action, clips), menuItems);
  106. }
  107. static void BuildMenu(MarkerAction action, IEnumerable<IMarker> markers, List<MenuActionItem> menuItems)
  108. {
  109. BuildMenu(action, action.Validate(markers), () => ExecuteMarkerAction(action, markers), menuItems);
  110. }
  111. static void BuildMenu(IAction action, ActionValidity validity, GenericMenu.MenuFunction executeFunction, List<MenuActionItem> menuItems)
  112. {
  113. var menuAttribute = action.GetType().GetCustomAttribute<MenuEntryAttribute>(false);
  114. if (menuAttribute == null)
  115. return;
  116. if (validity == ActionValidity.NotApplicable)
  117. return;
  118. var menuActionItem = new MenuActionItem
  119. {
  120. state = validity,
  121. entryName = action.GetMenuEntryName(),
  122. priority = menuAttribute.priority,
  123. category = menuAttribute.subMenuPath,
  124. isActiveInMode = action.IsActionActiveInMode(TimelineWindow.instance.currentMode.mode),
  125. shortCut = action.GetShortcut(),
  126. callback = executeFunction,
  127. isChecked = action.IsChecked()
  128. };
  129. menuItems.Add(menuActionItem);
  130. }
  131. internal static void BuildMenu(GenericMenu menu, List<MenuActionItem> items)
  132. {
  133. // sorted the outer menu by priority, then sort the innermenu by priority
  134. var sortedItems =
  135. 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));
  136. int lastPriority = Int32.MinValue;
  137. string lastCategory = string.Empty;
  138. foreach (var s in sortedItems)
  139. {
  140. if (s.state == ActionValidity.NotApplicable)
  141. continue;
  142. var priority = s.priority;
  143. if (lastPriority != int.MinValue && priority / MenuPriority.separatorAt > lastPriority / MenuPriority.separatorAt)
  144. {
  145. string path = string.Empty;
  146. if (lastCategory == s.category)
  147. path = s.category;
  148. menu.AddSeparator(path);
  149. }
  150. lastPriority = priority;
  151. lastCategory = s.category;
  152. string entry = s.category + s.entryName;
  153. if (!string.IsNullOrEmpty(s.shortCut))
  154. entry += " " + s.shortCut;
  155. if (s.state == ActionValidity.Valid && s.isActiveInMode)
  156. menu.AddItem(new GUIContent(entry), s.isChecked, s.callback);
  157. else
  158. menu.AddDisabledItem(new GUIContent(entry), s.isChecked);
  159. }
  160. }
  161. public static bool HandleShortcut(Event evt)
  162. {
  163. if (EditorGUI.IsEditingTextField())
  164. return false;
  165. return HandleShortcut(evt, TimelineActionsWithShortcuts, (x) => ExecuteTimelineAction(x, TimelineEditor.CurrentContext())) ||
  166. HandleShortcut(evt, ClipActionsWithShortcuts, (x => ExecuteClipAction(x, SelectionManager.SelectedClips()))) ||
  167. HandleShortcut(evt, TrackActionsWithShortcuts, (x => ExecuteTrackAction(x, SelectionManager.SelectedTracks()))) ||
  168. HandleShortcut(evt, MarkerActionsWithShortcuts, (x => ExecuteMarkerAction(x, SelectionManager.SelectedMarkers())));
  169. }
  170. public static bool HandleShortcut<T>(Event evt, IReadOnlyList<T> actions, Func<T, bool> invoke) where T : class, IAction
  171. {
  172. for (int i = 0; i < actions.Count; i++)
  173. {
  174. var action = actions[i];
  175. var attr = action.GetType().GetCustomAttributes(typeof(ShortcutAttribute), true);
  176. foreach (ShortcutAttribute shortcut in attr)
  177. {
  178. if (shortcut.MatchesEvent(evt))
  179. {
  180. if (s_ShowActionTriggeredByShortcut)
  181. Debug.Log(action.GetType().Name);
  182. if (!action.IsActionActiveInMode(TimelineWindow.instance.currentMode.mode))
  183. continue;
  184. if (invoke(action))
  185. return true;
  186. }
  187. }
  188. }
  189. return false;
  190. }
  191. public static bool ExecuteTimelineAction(TimelineAction timelineAction, ActionContext context)
  192. {
  193. if (timelineAction.Validate(context) == ActionValidity.Valid)
  194. {
  195. if (timelineAction.HasAutoUndo())
  196. UndoExtensions.RegisterContext(context, timelineAction.GetUndoName());
  197. return timelineAction.Execute(context);
  198. }
  199. return false;
  200. }
  201. public static bool ExecuteTrackAction(TrackAction trackAction, IEnumerable<TrackAsset> tracks)
  202. {
  203. if (tracks != null && tracks.Any() && trackAction.Validate(tracks) == ActionValidity.Valid)
  204. {
  205. if (trackAction.HasAutoUndo())
  206. UndoExtensions.RegisterTracks(tracks, trackAction.GetUndoName());
  207. return trackAction.Execute(tracks);
  208. }
  209. return false;
  210. }
  211. public static bool ExecuteClipAction(ClipAction clipAction, IEnumerable<TimelineClip> clips)
  212. {
  213. if (clips != null && clips.Any() && clipAction.Validate(clips) == ActionValidity.Valid)
  214. {
  215. if (clipAction.HasAutoUndo())
  216. UndoExtensions.RegisterClips(clips, clipAction.GetUndoName());
  217. return clipAction.Execute(clips);
  218. }
  219. return false;
  220. }
  221. public static bool ExecuteMarkerAction(MarkerAction markerAction, IEnumerable<IMarker> markers)
  222. {
  223. if (markers != null && markers.Any() && markerAction.Validate(markers) == ActionValidity.Valid)
  224. {
  225. if (markerAction.HasAutoUndo())
  226. UndoExtensions.RegisterMarkers(markers, markerAction.GetUndoName());
  227. return markerAction.Execute(markers);
  228. }
  229. return false;
  230. }
  231. static List<T> InstantiateClassesOfType<T>() where T : class
  232. {
  233. var typeCollection = TypeCache.GetTypesDerivedFrom(typeof(T));
  234. var list = new List<T>(typeCollection.Count);
  235. for (int i = 0; i < typeCollection.Count; i++)
  236. {
  237. if (typeCollection[i].IsAbstract || typeCollection[i].IsGenericType)
  238. continue;
  239. if (typeCollection[i].GetConstructor(Type.EmptyTypes) == null)
  240. {
  241. Debug.LogWarning($"{typeCollection[i].FullName} requires a default constructor to be automatically instantiated by Timeline");
  242. continue;
  243. }
  244. list.Add((T)Activator.CreateInstance(typeCollection[i]));
  245. }
  246. return list;
  247. }
  248. static List<T> ActionsWithShortCuts<T>(IReadOnlyList<T> list)
  249. {
  250. return list.Where(x => x.GetType().GetCustomAttributes(typeof(ShortcutAttribute), true).Length > 0).ToList();
  251. }
  252. static HashSet<System.Type> TypesWithAttribute<T>() where T : Attribute
  253. {
  254. var hashSet = new HashSet<System.Type>();
  255. var typeCollection = TypeCache.GetTypesWithAttribute(typeof(T));
  256. for (int i = 0; i < typeCollection.Count; i++)
  257. {
  258. hashSet.Add(typeCollection[i]);
  259. }
  260. return hashSet;
  261. }
  262. }
  263. }