TimelineContextMenu.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEditor.Timeline.Actions;
  5. using UnityEngine;
  6. using UnityEngine.Playables;
  7. using UnityEngine.Timeline;
  8. using Object = UnityEngine.Object;
  9. namespace UnityEditor.Timeline
  10. {
  11. static class SequencerContextMenu
  12. {
  13. static class Styles
  14. {
  15. public static readonly string addItemFromAssetTemplate = L10n.Tr("Add {0} From {1}");
  16. public static readonly string addSingleItemFromAssetTemplate = L10n.Tr("Add From {1}");
  17. public static readonly string addItemTemplate = L10n.Tr("Add {0}");
  18. public static readonly string typeSelectorTemplate = L10n.Tr("Select {0}");
  19. public static readonly string trackGroup = L10n.Tr("Track Group");
  20. public static readonly string trackSubGroup = L10n.Tr("Track Sub-Group");
  21. public static readonly string addTrackLayer = L10n.Tr("Add Layer");
  22. public static readonly string layerName = L10n.Tr("Layer {0}");
  23. }
  24. public static void ShowMarkerHeaderContextMenu(Vector2? mousePosition, WindowState state)
  25. {
  26. var menu = new GenericMenu();
  27. List<MenuActionItem> items = new List<MenuActionItem>(100);
  28. BuildMarkerHeaderContextMenu(items, mousePosition, state);
  29. ActionManager.BuildMenu(menu, items);
  30. menu.ShowAsContext();
  31. }
  32. public static void ShowNewTracksContextMenu(ICollection<TrackAsset> tracks, WindowState state)
  33. {
  34. var menu = new GenericMenu();
  35. List<MenuActionItem> items = new List<MenuActionItem>(100);
  36. BuildNewTracksContextMenu(items, tracks, state);
  37. ActionManager.BuildMenu(menu, items);
  38. menu.ShowAsContext();
  39. }
  40. public static void ShowNewTracksContextMenu(ICollection<TrackAsset> tracks, WindowState state, Rect rect)
  41. {
  42. var menu = new GenericMenu();
  43. List<MenuActionItem> items = new List<MenuActionItem>(100);
  44. BuildNewTracksContextMenu(items, tracks, state);
  45. ActionManager.BuildMenu(menu, items);
  46. menu.DropDown(rect);
  47. }
  48. public static void ShowTrackContextMenu(Vector2? mousePosition)
  49. {
  50. var items = new List<MenuActionItem>();
  51. var menu = new GenericMenu();
  52. BuildTrackContextMenu(items, mousePosition);
  53. ActionManager.BuildMenu(menu, items);
  54. menu.ShowAsContext();
  55. }
  56. public static void ShowItemContextMenu(Vector2 mousePosition)
  57. {
  58. var menu = new GenericMenu();
  59. var items = new List<MenuActionItem>();
  60. BuildItemContextMenu(items, mousePosition);
  61. ActionManager.BuildMenu(menu, items);
  62. menu.ShowAsContext();
  63. }
  64. public static void BuildItemContextMenu(List<MenuActionItem> items, Vector2 mousePosition)
  65. {
  66. ActionManager.GetMenuEntries(ActionManager.TimelineActions, mousePosition, items);
  67. ActionManager.GetMenuEntries(ActionManager.ClipActions, items);
  68. ActionManager.GetMenuEntries(ActionManager.MarkerActions, items);
  69. var clips = TimelineEditor.selectedClips;
  70. if (clips.Length > 0)
  71. AddMarkerMenuCommands(items, clips.Select(c => c.parentTrack).Distinct().ToList(), TimelineHelpers.GetCandidateTime(mousePosition));
  72. }
  73. public static void BuildNewTracksContextMenu(List<MenuActionItem> menuItems, ICollection<TrackAsset> parentTracks, WindowState state, string format = null)
  74. {
  75. if (parentTracks == null)
  76. parentTracks = new TrackAsset[0];
  77. if (string.IsNullOrEmpty(format))
  78. format = "{0}";
  79. // Add Group or SubGroup
  80. var title = string.Format(format, parentTracks.Any(t => t != null) ? Styles.trackSubGroup : Styles.trackGroup);
  81. var menuState = ActionValidity.Valid;
  82. if (state.editSequence.isReadOnly)
  83. menuState = ActionValidity.Invalid;
  84. if (parentTracks.Any() && parentTracks.Any(t => t != null && t.lockedInHierarchy))
  85. menuState = ActionValidity.Invalid;
  86. GenericMenu.MenuFunction command = () =>
  87. {
  88. SelectionManager.Clear();
  89. if (parentTracks.Count == 0)
  90. Selection.Add(TimelineHelpers.CreateTrack<GroupTrack>(null, title));
  91. foreach (var parentTrack in parentTracks)
  92. Selection.Add(TimelineHelpers.CreateTrack<GroupTrack>(parentTrack, title));
  93. TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
  94. };
  95. menuItems.Add(
  96. new MenuActionItem()
  97. {
  98. category = string.Empty,
  99. entryName = title,
  100. isActiveInMode = true,
  101. priority = MenuPriority.AddItem.addGroup,
  102. state = menuState,
  103. isChecked = false,
  104. callback = command
  105. }
  106. );
  107. var allTypes = TypeUtility.AllTrackTypes().Where(x => x != typeof(GroupTrack) && !TypeUtility.IsHiddenInMenu(x)).ToList();
  108. int builtInPriority = MenuPriority.AddItem.addTrack;
  109. int customPriority = MenuPriority.AddItem.addCustomTrack;
  110. foreach (var trackType in allTypes)
  111. {
  112. var trackItemType = trackType;
  113. command = () =>
  114. {
  115. SelectionManager.Clear();
  116. if (parentTracks.Count == 0)
  117. SelectionManager.Add(TimelineHelpers.CreateTrack((Type)trackItemType, null));
  118. foreach (var parentTrack in parentTracks)
  119. SelectionManager.Add(TimelineHelpers.CreateTrack((Type)trackItemType, parentTrack));
  120. };
  121. menuItems.Add(
  122. new MenuActionItem()
  123. {
  124. category = TimelineHelpers.GetTrackCategoryName(trackType),
  125. entryName = string.Format(format, TimelineHelpers.GetTrackMenuName(trackItemType)),
  126. isActiveInMode = true,
  127. priority = TypeUtility.IsBuiltIn(trackType) ? builtInPriority++ : customPriority++,
  128. state = menuState,
  129. callback = command
  130. }
  131. );
  132. }
  133. }
  134. public static void BuildMarkerHeaderContextMenu(List<MenuActionItem> menu, Vector2? mousePosition, WindowState state)
  135. {
  136. ActionManager.GetMenuEntries(ActionManager.TimelineActions, null, menu, MenuFilter.MarkerHeader);
  137. var timeline = state.editSequence.asset;
  138. var time = TimelineHelpers.GetCandidateTime(mousePosition);
  139. var enabled = timeline.markerTrack == null || !timeline.markerTrack.lockedInHierarchy;
  140. var addMarkerCommand = new Action<Type, Object>
  141. (
  142. (type, obj) => AddSingleMarkerCallback(type, time, timeline, state.editSequence.director, obj)
  143. );
  144. AddMarkerMenuCommands(menu, new TrackAsset[] {timeline.markerTrack}, addMarkerCommand, enabled);
  145. }
  146. public static void BuildTrackContextMenu(List<MenuActionItem> items, Vector2? mousePosition)
  147. {
  148. var tracks = SelectionManager.SelectedTracks().ToArray();
  149. if (tracks.Length == 0)
  150. return;
  151. ActionManager.GetMenuEntries(ActionManager.TimelineActions, mousePosition, items);
  152. ActionManager.GetMenuEntries(ActionManager.TrackActions, items);
  153. AddLayeredTrackCommands(items, tracks);
  154. var first = tracks.First().GetType();
  155. var allTheSame = tracks.All(t => t.GetType() == first);
  156. if (allTheSame)
  157. {
  158. if (first != typeof(GroupTrack))
  159. {
  160. var candidateTime = TimelineHelpers.GetCandidateTime(mousePosition, tracks);
  161. AddClipMenuCommands(items, tracks, candidateTime);
  162. AddMarkerMenuCommands(items, tracks, candidateTime);
  163. }
  164. else
  165. {
  166. BuildNewTracksContextMenu(items, tracks, TimelineWindow.instance.state, Styles.addItemTemplate);
  167. }
  168. }
  169. }
  170. static void AddLayeredTrackCommands(List<MenuActionItem> menuItems, ICollection<TrackAsset> tracks)
  171. {
  172. if (tracks.Count == 0)
  173. return;
  174. var layeredType = tracks.First().GetType();
  175. // animation tracks have a special menu.
  176. if (layeredType == typeof(AnimationTrack))
  177. return;
  178. // must implement ILayerable
  179. if (!typeof(UnityEngine.Timeline.ILayerable).IsAssignableFrom(layeredType))
  180. return;
  181. if (tracks.Any(t => t.GetType() != layeredType))
  182. return;
  183. // only supported on the master track no nesting.
  184. if (tracks.Any(t => t.isSubTrack))
  185. return;
  186. var enabled = tracks.All(t => t != null && !t.lockedInHierarchy) && !TimelineWindow.instance.state.editSequence.isReadOnly;
  187. int priority = MenuPriority.AddTrackMenu.addLayerTrack;
  188. GenericMenu.MenuFunction menuCallback = () =>
  189. {
  190. foreach (var track in tracks)
  191. TimelineHelpers.CreateTrack(layeredType, track, string.Format(Styles.layerName, track.GetChildTracks().Count() + 1));
  192. };
  193. var entryName = Styles.addTrackLayer;
  194. menuItems.Add(
  195. new MenuActionItem()
  196. {
  197. category = string.Empty,
  198. entryName = entryName,
  199. isActiveInMode = true,
  200. priority = priority++,
  201. state = enabled ? ActionValidity.Valid : ActionValidity.Invalid,
  202. callback = menuCallback
  203. }
  204. );
  205. }
  206. static void AddClipMenuCommands(List<MenuActionItem> menuItems, ICollection<TrackAsset> tracks, double candidateTime)
  207. {
  208. if (!tracks.Any())
  209. return;
  210. var trackAsset = tracks.First();
  211. var trackType = trackAsset.GetType();
  212. if (tracks.Any(t => t.GetType() != trackType))
  213. return;
  214. var enabled = tracks.All(t => t != null && !t.lockedInHierarchy) && !TimelineWindow.instance.state.editSequence.isReadOnly;
  215. var assetTypes = TypeUtility.GetPlayableAssetsHandledByTrack(trackType);
  216. var visibleAssetTypes = TypeUtility.GetVisiblePlayableAssetsHandledByTrack(trackType);
  217. // skips the name if there is only a single type
  218. var commandNameTemplate = assetTypes.Count() == 1 ? Styles.addSingleItemFromAssetTemplate : Styles.addItemFromAssetTemplate;
  219. int builtInPriority = MenuPriority.AddItem.addClip;
  220. int customPriority = MenuPriority.AddItem.addCustomClip;
  221. foreach (var assetType in assetTypes)
  222. {
  223. var assetItemType = assetType;
  224. var category = TimelineHelpers.GetItemCategoryName(assetType);
  225. Action<Object> onObjectChanged = obj =>
  226. {
  227. if (obj != null)
  228. {
  229. foreach (var t in tracks)
  230. {
  231. TimelineHelpers.CreateClipOnTrack(assetItemType, obj, t, candidateTime);
  232. }
  233. }
  234. };
  235. foreach (var objectReference in TypeUtility.ObjectReferencesForType(assetType))
  236. {
  237. var isSceneReference = objectReference.isSceneReference;
  238. var dataType = objectReference.type;
  239. GenericMenu.MenuFunction menuCallback = () =>
  240. {
  241. ObjectSelector.get.Show(null, dataType, null, isSceneReference, null, (obj) => onObjectChanged(obj), null);
  242. ObjectSelector.get.titleContent = EditorGUIUtility.TrTextContent(string.Format(Styles.typeSelectorTemplate, TypeUtility.GetDisplayName(dataType)));
  243. };
  244. menuItems.Add(
  245. new MenuActionItem()
  246. {
  247. category = category,
  248. entryName = string.Format(commandNameTemplate, TypeUtility.GetDisplayName(assetType), TypeUtility.GetDisplayName(objectReference.type)),
  249. isActiveInMode = true,
  250. priority = TypeUtility.IsBuiltIn(assetType) ? builtInPriority++ : customPriority++,
  251. state = enabled ? ActionValidity.Valid : ActionValidity.Invalid,
  252. callback = menuCallback
  253. }
  254. );
  255. }
  256. }
  257. foreach (var assetType in visibleAssetTypes)
  258. {
  259. var assetItemType = assetType;
  260. var category = TimelineHelpers.GetItemCategoryName(assetType);
  261. var commandName = string.Format(Styles.addItemTemplate, TypeUtility.GetDisplayName(assetType));
  262. GenericMenu.MenuFunction command = () =>
  263. {
  264. foreach (var t in tracks)
  265. {
  266. TimelineHelpers.CreateClipOnTrack(assetItemType, t, candidateTime);
  267. }
  268. };
  269. menuItems.Add(
  270. new MenuActionItem()
  271. {
  272. category = category,
  273. entryName = commandName,
  274. isActiveInMode = true,
  275. priority = TypeUtility.IsBuiltIn(assetItemType) ? builtInPriority++ : customPriority++,
  276. state = enabled ? ActionValidity.Valid : ActionValidity.Invalid,
  277. callback = command
  278. }
  279. );
  280. }
  281. }
  282. static void AddMarkerMenuCommands(List<MenuActionItem> menu, IEnumerable<Type> markerTypes, Action<Type, Object> addMarkerCommand, bool enabled)
  283. {
  284. int builtInPriority = MenuPriority.AddItem.addMarker;
  285. int customPriority = MenuPriority.AddItem.addCustomMarker;
  286. foreach (var markerType in markerTypes)
  287. {
  288. var markerItemType = markerType;
  289. string category = TimelineHelpers.GetItemCategoryName(markerItemType);
  290. menu.Add(
  291. new MenuActionItem()
  292. {
  293. category = category,
  294. entryName = string.Format(Styles.addItemTemplate, TypeUtility.GetDisplayName(markerType)),
  295. isActiveInMode = true,
  296. priority = TypeUtility.IsBuiltIn(markerType) ? builtInPriority++ : customPriority++,
  297. state = enabled ? ActionValidity.Valid : ActionValidity.Invalid,
  298. callback = () => addMarkerCommand(markerItemType, null)
  299. }
  300. );
  301. foreach (var objectReference in TypeUtility.ObjectReferencesForType(markerType))
  302. {
  303. var isSceneReference = objectReference.isSceneReference;
  304. GenericMenu.MenuFunction menuCallback = () =>
  305. {
  306. Type assetDataType = objectReference.type;
  307. ObjectSelector.get.titleContent = EditorGUIUtility.TrTextContent(string.Format(Styles.typeSelectorTemplate, TypeUtility.GetDisplayName(assetDataType)));
  308. ObjectSelector.get.Show(null, assetDataType, null, isSceneReference, null, obj =>
  309. {
  310. if (obj != null)
  311. addMarkerCommand(markerItemType, obj);
  312. }, null);
  313. };
  314. menu.Add(
  315. new MenuActionItem
  316. {
  317. category = TimelineHelpers.GetItemCategoryName(markerItemType),
  318. entryName = string.Format(Styles.addItemFromAssetTemplate, TypeUtility.GetDisplayName(markerType), TypeUtility.GetDisplayName(objectReference.type)),
  319. isActiveInMode = true,
  320. priority = TypeUtility.IsBuiltIn(markerType) ? builtInPriority++ : customPriority++,
  321. state = enabled ? ActionValidity.Valid : ActionValidity.Invalid,
  322. callback = menuCallback
  323. }
  324. );
  325. }
  326. }
  327. }
  328. static void AddMarkerMenuCommands(List<MenuActionItem> menuItems, ICollection<TrackAsset> tracks, double candidateTime)
  329. {
  330. if (tracks.Count == 0)
  331. return;
  332. var enabled = tracks.All(t => !t.lockedInHierarchy) && !TimelineWindow.instance.state.editSequence.isReadOnly;
  333. var addMarkerCommand = new Action<Type, Object>((type, obj) => AddMarkersCallback(tracks, type, candidateTime, obj));
  334. AddMarkerMenuCommands(menuItems, tracks, addMarkerCommand, enabled);
  335. }
  336. static void AddMarkerMenuCommands(List<MenuActionItem> menuItems, ICollection<TrackAsset> tracks, Action<Type, Object> command, bool enabled)
  337. {
  338. var markerTypes = TypeUtility.GetBuiltInMarkerTypes().Union(TypeUtility.GetUserMarkerTypes());
  339. if (tracks != null)
  340. markerTypes = markerTypes.Where(x => tracks.All(track => (track == null) || TypeUtility.DoesTrackSupportMarkerType(track, x))); // null track indicates marker track to be created
  341. AddMarkerMenuCommands(menuItems, markerTypes, command, enabled);
  342. }
  343. static void AddMarkersCallback(ICollection<TrackAsset> targets, Type markerType, double time, Object obj)
  344. {
  345. SelectionManager.Clear();
  346. foreach (var target in targets)
  347. {
  348. var marker = TimelineHelpers.CreateMarkerOnTrack(markerType, obj, target, time);
  349. SelectionManager.Add(marker);
  350. }
  351. TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
  352. }
  353. static void AddSingleMarkerCallback(Type markerType, double time, TimelineAsset timeline, PlayableDirector director, Object assignableObject)
  354. {
  355. timeline.CreateMarkerTrack();
  356. var markerTrack = timeline.markerTrack;
  357. SelectionManager.Clear();
  358. var marker = TimelineHelpers.CreateMarkerOnTrack(markerType, assignableObject, markerTrack, time);
  359. SelectionManager.Add(marker);
  360. if (typeof(INotification).IsAssignableFrom(markerType) && director != null)
  361. {
  362. if (director != null && director.GetGenericBinding(markerTrack) == null)
  363. director.SetGenericBinding(markerTrack, director.gameObject);
  364. }
  365. TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
  366. }
  367. }
  368. }