TrackActions.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. using System.Collections.Generic;
  2. using System.ComponentModel;
  3. using System.Linq;
  4. using JetBrains.Annotations;
  5. using UnityEngine;
  6. using UnityEngine.Timeline;
  7. namespace UnityEditor.Timeline
  8. {
  9. [ActiveInMode(TimelineModes.Default)]
  10. abstract class TrackAction : MenuItemActionBase
  11. {
  12. public abstract bool Execute(WindowState state, TrackAsset[] tracks);
  13. protected virtual MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
  14. {
  15. return tracks.Length > 0 ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled;
  16. }
  17. protected virtual bool IsChecked(WindowState state, TrackAsset[] tracks)
  18. {
  19. return false;
  20. }
  21. protected virtual string GetDisplayName(TrackAsset[] tracks)
  22. {
  23. return menuName;
  24. }
  25. public static void Invoke<T>(WindowState state, TrackAsset[] tracks) where T : TrackAction
  26. {
  27. actions.First(x => x.GetType() == typeof(T)).Execute(state, tracks);
  28. }
  29. static List<TrackAction> s_ActionClasses;
  30. static List<TrackAction> actions
  31. {
  32. get
  33. {
  34. if (s_ActionClasses == null)
  35. s_ActionClasses =
  36. GetActionsOfType(typeof(TrackAction))
  37. .Select(x => (TrackAction)x.GetConstructors()[0].Invoke(null))
  38. .OrderBy(x => x.priority).ThenBy(x => x.category)
  39. .ToList();
  40. return s_ActionClasses;
  41. }
  42. }
  43. public static void GetMenuEntries(WindowState state, Vector2? mousePos, TrackAsset[] tracks, List<MenuActionItem> items)
  44. {
  45. var mode = TimelineWindow.instance.currentMode.mode;
  46. foreach (var action in actions)
  47. {
  48. if (!action.showInMenu)
  49. continue;
  50. var actionItem = action;
  51. items.Add(
  52. new MenuActionItem()
  53. {
  54. category = action.category,
  55. entryName = action.GetDisplayName(tracks),
  56. shortCut = action.shortCut,
  57. isChecked = action.IsChecked(state, tracks),
  58. isActiveInMode = IsActionActiveInMode(action, mode),
  59. priority = action.priority,
  60. state = action.GetDisplayState(state, tracks),
  61. callback = () =>
  62. {
  63. actionItem.mousePosition = mousePos;
  64. actionItem.Execute(state, tracks);
  65. actionItem.mousePosition = null;
  66. }
  67. }
  68. );
  69. }
  70. }
  71. public static bool HandleShortcut(WindowState state, Event evt, TrackAsset[] tracks)
  72. {
  73. foreach (var action in actions)
  74. {
  75. var attr = action.GetType().GetCustomAttributes(typeof(ShortcutAttribute), true);
  76. foreach (ShortcutAttribute shortcut in attr)
  77. {
  78. if (shortcut.MatchesEvent(evt))
  79. {
  80. if (s_ShowActionTriggeredByShortcut)
  81. Debug.Log(action.GetType().Name);
  82. if (!IsActionActiveInMode(action, TimelineWindow.instance.currentMode.mode))
  83. return false;
  84. return action.Execute(state, tracks);
  85. }
  86. }
  87. }
  88. return false;
  89. }
  90. // For testing
  91. internal MenuActionDisplayState InternalGetDisplayState(WindowState state, TrackAsset[] tracks)
  92. {
  93. return GetDisplayState(state, tracks);
  94. }
  95. }
  96. [MenuEntry("Edit in Animation Window", MenuOrder.TrackAction.EditInAnimationWindow)]
  97. class EditTrackInAnimationWindow : TrackAction
  98. {
  99. public static bool Do(WindowState state, TrackAsset track)
  100. {
  101. AnimationClip clipToEdit = null;
  102. AnimationTrack animationTrack = track as AnimationTrack;
  103. if (animationTrack != null)
  104. {
  105. if (!animationTrack.CanConvertToClipMode())
  106. return false;
  107. clipToEdit = animationTrack.infiniteClip;
  108. }
  109. else if (track.hasCurves)
  110. {
  111. clipToEdit = track.curves;
  112. }
  113. if (clipToEdit == null)
  114. return false;
  115. var gameObject = state.GetSceneReference(track);
  116. var timeController = TimelineAnimationUtilities.CreateTimeController(state, CreateTimeControlClipData(track));
  117. TimelineAnimationUtilities.EditAnimationClipWithTimeController(clipToEdit, timeController, gameObject);
  118. return true;
  119. }
  120. protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
  121. {
  122. if (tracks.Length == 0)
  123. return MenuActionDisplayState.Hidden;
  124. if (tracks[0] is AnimationTrack)
  125. {
  126. var animTrack = tracks[0] as AnimationTrack;
  127. if (animTrack.CanConvertToClipMode())
  128. return MenuActionDisplayState.Visible;
  129. }
  130. else if (tracks[0].hasCurves)
  131. {
  132. return MenuActionDisplayState.Visible;
  133. }
  134. return MenuActionDisplayState.Hidden;
  135. }
  136. public override bool Execute(WindowState state, TrackAsset[] tracks)
  137. {
  138. return Do(state, tracks[0]);
  139. }
  140. static TimelineWindowTimeControl.ClipData CreateTimeControlClipData(TrackAsset track)
  141. {
  142. var data = new TimelineWindowTimeControl.ClipData();
  143. data.track = track;
  144. data.start = track.start;
  145. data.duration = track.duration;
  146. return data;
  147. }
  148. }
  149. [MenuEntry("Lock selected track only", MenuOrder.TrackAction.LockSelected)]
  150. class LockSelectedTrack : TrackAction
  151. {
  152. public static readonly string LockSelectedTrackOnlyText = L10n.Tr("Lock selected track only");
  153. public static readonly string UnlockSelectedTrackOnlyText = L10n.Tr("Unlock selected track only");
  154. protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
  155. {
  156. if (tracks.Any(track => TimelineUtility.IsLockedFromGroup(track) || track is GroupTrack ||
  157. !track.subTracksObjects.Any()))
  158. return MenuActionDisplayState.Hidden;
  159. return MenuActionDisplayState.Visible;
  160. }
  161. public override bool Execute(WindowState state, TrackAsset[] tracks)
  162. {
  163. if (!tracks.Any()) return false;
  164. var hasUnlockedTracks = tracks.Any(x => !x.locked);
  165. Lock(state, tracks.Where(p => !(p is GroupTrack)).ToArray(), hasUnlockedTracks);
  166. return true;
  167. }
  168. protected override string GetDisplayName(TrackAsset[] tracks)
  169. {
  170. return tracks.All(t => t.locked) ? UnlockSelectedTrackOnlyText : LockSelectedTrackOnlyText;
  171. }
  172. public static void Lock(WindowState state, TrackAsset[] tracks, bool shouldlock)
  173. {
  174. if (tracks.Length == 0)
  175. return;
  176. foreach (var track in tracks.Where(t => !TimelineUtility.IsLockedFromGroup(t)))
  177. {
  178. TimelineUndo.PushUndo(track, "Lock Tracks");
  179. track.locked = shouldlock;
  180. }
  181. TimelineEditor.Refresh(RefreshReason.WindowNeedsRedraw);
  182. }
  183. }
  184. [MenuEntry("Lock", MenuOrder.TrackAction.LockTrack)]
  185. [Shortcut(Shortcuts.Timeline.toggleLock)]
  186. class LockTrack : TrackAction
  187. {
  188. public static readonly string UnlockText = L10n.Tr("Unlock");
  189. protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
  190. {
  191. bool hasUnlockableTracks = tracks.Any(x => TimelineUtility.IsLockedFromGroup(x));
  192. if (hasUnlockableTracks)
  193. return MenuActionDisplayState.Disabled;
  194. return MenuActionDisplayState.Visible;
  195. }
  196. protected override string GetDisplayName(TrackAsset[] tracks)
  197. {
  198. return tracks.Any(x => !x.locked) ? base.GetDisplayName(tracks) : UnlockText;
  199. }
  200. public override bool Execute(WindowState state, TrackAsset[] tracks)
  201. {
  202. if (!tracks.Any()) return false;
  203. var hasUnlockedTracks = tracks.Any(x => !x.locked);
  204. SetLockState(tracks, hasUnlockedTracks, state);
  205. return true;
  206. }
  207. public static void SetLockState(TrackAsset[] tracks, bool shouldLock, WindowState state = null)
  208. {
  209. if (tracks.Length == 0)
  210. return;
  211. foreach (var track in tracks)
  212. {
  213. if (TimelineUtility.IsLockedFromGroup(track))
  214. continue;
  215. if (track as GroupTrack == null)
  216. SetLockState(track.GetChildTracks().ToArray(), shouldLock, state);
  217. TimelineUndo.PushUndo(track, "Lock Tracks");
  218. track.locked = shouldLock;
  219. }
  220. if (state != null)
  221. {
  222. // find the tracks we've locked. unselect anything locked and remove recording.
  223. foreach (var track in tracks)
  224. {
  225. if (TimelineUtility.IsLockedFromGroup(track) || !track.locked)
  226. continue;
  227. var flattenedChildTracks = track.GetFlattenedChildTracks();
  228. foreach (var i in track.clips)
  229. SelectionManager.Remove(i);
  230. state.UnarmForRecord(track);
  231. foreach (var child in flattenedChildTracks)
  232. {
  233. SelectionManager.Remove(child);
  234. state.UnarmForRecord(child);
  235. foreach (var clip in child.GetClips())
  236. SelectionManager.Remove(clip);
  237. }
  238. }
  239. // no need to rebuild, just repaint (including inspectors)
  240. InspectorWindow.RepaintAllInspectors();
  241. state.editorWindow.Repaint();
  242. }
  243. }
  244. }
  245. [UsedImplicitly]
  246. [MenuEntry("Show Markers", MenuOrder.TrackAction.ShowHideMarkers)]
  247. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  248. class ShowHideMarkers : TrackAction
  249. {
  250. protected override bool IsChecked(WindowState state, TrackAsset[] tracks)
  251. {
  252. return tracks.All(x => x.GetShowMarkers());
  253. }
  254. protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
  255. {
  256. if (tracks.Any(x => x is GroupTrack) || tracks.Any(t => t.GetMarkerCount() == 0))
  257. return MenuActionDisplayState.Hidden;
  258. if (tracks.Any(t => t.lockedInHierarchy))
  259. return MenuActionDisplayState.Disabled;
  260. return MenuActionDisplayState.Visible;
  261. }
  262. public override bool Execute(WindowState state, TrackAsset[] tracks)
  263. {
  264. if (!tracks.Any()) return false;
  265. var hasUnlockedTracks = tracks.Any(x => !x.GetShowMarkers());
  266. ShowHide(state, tracks, hasUnlockedTracks);
  267. return true;
  268. }
  269. static void ShowHide(WindowState state, TrackAsset[] tracks, bool shouldLock)
  270. {
  271. if (tracks.Length == 0)
  272. return;
  273. var window = state.GetWindow();
  274. foreach (var track in tracks)
  275. {
  276. window.SetShowTrackMarkers(track, shouldLock);
  277. }
  278. TimelineEditor.Refresh(RefreshReason.WindowNeedsRedraw);
  279. }
  280. }
  281. [MenuEntry("Mute selected track only", MenuOrder.TrackAction.MuteSelected), UsedImplicitly]
  282. class MuteSelectedTrack : TrackAction
  283. {
  284. public static readonly string UnmuteSelectedText = L10n.Tr("Unmute selected track only");
  285. protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
  286. {
  287. if (tracks.Any(track => TimelineUtility.IsParentMuted(track) || track is GroupTrack ||
  288. !track.subTracksObjects.Any()))
  289. return MenuActionDisplayState.Hidden;
  290. return MenuActionDisplayState.Visible;
  291. }
  292. public override bool Execute(WindowState state, TrackAsset[] tracks)
  293. {
  294. if (!tracks.Any())
  295. return false;
  296. var hasUnmutedTracks = tracks.Any(x => !x.muted);
  297. Mute(state, tracks.Where(p => !(p is GroupTrack)).ToArray(), hasUnmutedTracks);
  298. return true;
  299. }
  300. protected override string GetDisplayName(TrackAsset[] tracks)
  301. {
  302. return tracks.All(t => t.muted) ? UnmuteSelectedText : base.GetDisplayName(tracks);
  303. }
  304. public static void Mute(WindowState state, TrackAsset[] tracks, bool shouldMute)
  305. {
  306. if (tracks.Length == 0)
  307. return;
  308. foreach (var track in tracks.Where(t => !TimelineUtility.IsParentMuted(t)))
  309. {
  310. TimelineUndo.PushUndo(track, "Mute Tracks");
  311. track.muted = shouldMute;
  312. }
  313. state.Refresh();
  314. }
  315. }
  316. [MenuEntry("Mute", MenuOrder.TrackAction.MuteTrack)]
  317. [Shortcut(Shortcuts.Timeline.toggleMute)]
  318. class MuteTrack : TrackAction
  319. {
  320. public static readonly string UnMuteText = L10n.Tr("Unmute");
  321. protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
  322. {
  323. if (tracks.Any(track => TimelineUtility.IsParentMuted(track)))
  324. return MenuActionDisplayState.Disabled;
  325. return MenuActionDisplayState.Visible;
  326. }
  327. protected override string GetDisplayName(TrackAsset[] tracks)
  328. {
  329. return tracks.Any(x => !x.muted) ? base.GetDisplayName(tracks) : UnMuteText;
  330. }
  331. public override bool Execute(WindowState state, TrackAsset[] tracks)
  332. {
  333. if (!tracks.Any() || tracks.Any(track => TimelineUtility.IsParentMuted(track)))
  334. return false;
  335. var hasUnmutedTracks = tracks.Any(x => !x.muted);
  336. Mute(state, tracks, hasUnmutedTracks);
  337. return true;
  338. }
  339. public static void Mute(WindowState state, TrackAsset[] tracks, bool shouldMute)
  340. {
  341. if (tracks.Length == 0)
  342. return;
  343. foreach (var track in tracks)
  344. {
  345. if (track as GroupTrack == null)
  346. Mute(state, track.GetChildTracks().ToArray(), shouldMute);
  347. TimelineUndo.PushUndo(track, "Mute Tracks");
  348. track.muted = shouldMute;
  349. }
  350. state.Refresh();
  351. }
  352. }
  353. class DeleteTracks : TrackAction
  354. {
  355. public static void Do(TimelineAsset timeline, TrackAsset track)
  356. {
  357. SelectionManager.Remove(track);
  358. TrackModifier.DeleteTrack(timeline, track);
  359. }
  360. public override bool Execute(WindowState state, TrackAsset[] tracks)
  361. {
  362. // disable preview mode so deleted tracks revert to default state
  363. // Case 956129: Disable preview mode _before_ deleting the tracks, since clip data is still needed
  364. state.previewMode = false;
  365. TimelineAnimationUtilities.UnlinkAnimationWindowFromTracks(tracks);
  366. foreach (var track in tracks)
  367. Do(state.editSequence.asset, track);
  368. state.Refresh();
  369. return true;
  370. }
  371. }
  372. class CopyTracksToClipboard : TrackAction
  373. {
  374. public static bool Do(WindowState state, TrackAsset[] tracks)
  375. {
  376. var action = new CopyTracksToClipboard();
  377. return action.Execute(state, tracks);
  378. }
  379. public override bool Execute(WindowState state, TrackAsset[] tracks)
  380. {
  381. TimelineEditor.clipboard.CopyTracks(tracks);
  382. return true;
  383. }
  384. }
  385. class DuplicateTracks : TrackAction
  386. {
  387. public override bool Execute(WindowState state, TrackAsset[] tracks)
  388. {
  389. if (tracks.Any())
  390. {
  391. SelectionManager.RemoveTimelineSelection();
  392. }
  393. foreach (var track in TrackExtensions.FilterTracks(tracks))
  394. {
  395. var newTrack = track.Duplicate(TimelineEditor.inspectedDirector, TimelineEditor.inspectedDirector);
  396. SelectionManager.Add(newTrack);
  397. foreach (var childTrack in newTrack.GetFlattenedChildTracks())
  398. {
  399. SelectionManager.Add(childTrack);
  400. }
  401. }
  402. state.Refresh();
  403. return true;
  404. }
  405. }
  406. [MenuEntry("Remove Invalid Markers", MenuOrder.TrackAction.RemoveInvalidMarkers), UsedImplicitly]
  407. class RemoveInvalidMarkersAction : TrackAction
  408. {
  409. protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
  410. {
  411. if (tracks.Any(target => target != null && target.GetMarkerCount() != target.GetMarkersRaw().Count()))
  412. return MenuActionDisplayState.Visible;
  413. return MenuActionDisplayState.Hidden;
  414. }
  415. public override bool Execute(WindowState state, TrackAsset[] tracks)
  416. {
  417. bool anyRemoved = false;
  418. foreach (var target in tracks)
  419. {
  420. var invalids = target.GetMarkersRaw().Where(x => !(x is IMarker)).ToList();
  421. foreach (var m in invalids)
  422. {
  423. anyRemoved = true;
  424. target.DeleteMarkerRaw(m);
  425. }
  426. }
  427. if (anyRemoved)
  428. TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
  429. return anyRemoved;
  430. }
  431. }
  432. }