TimelineActions.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Linq;
  5. using UnityEditor.ShortcutManagement;
  6. using UnityEngine;
  7. using UnityEngine.Timeline;
  8. using MenuEntryPair = System.Collections.Generic.KeyValuePair<UnityEngine.GUIContent, UnityEditor.Timeline.TimelineAction>;
  9. namespace UnityEditor.Timeline
  10. {
  11. [ActiveInMode(TimelineModes.Default)]
  12. abstract class TimelineAction : MenuItemActionBase
  13. {
  14. public abstract bool Execute(WindowState state);
  15. public virtual MenuActionDisplayState GetDisplayState(WindowState state)
  16. {
  17. return MenuActionDisplayState.Visible;
  18. }
  19. public virtual bool IsChecked(WindowState state)
  20. {
  21. return false;
  22. }
  23. protected string GetDisplayName(WindowState state)
  24. {
  25. return menuName;
  26. }
  27. bool CanExecute(WindowState state)
  28. {
  29. return GetDisplayState(state) == MenuActionDisplayState.Visible;
  30. }
  31. public static void Invoke<T>(WindowState state) where T : TimelineAction
  32. {
  33. var action = AllActions.FirstOrDefault(x => x.GetType() == typeof(T));
  34. if (action != null && action.CanExecute(state))
  35. action.Execute(state);
  36. }
  37. // an instance of all TimelineActions
  38. public static readonly TimelineAction[] AllActions = GetActionsOfType(typeof(TimelineAction)).Select(x => (TimelineAction)x.GetConstructors()[0].Invoke(null)).ToArray();
  39. // an instance of all TimelineActions that should appear in a regular contextMenu
  40. public static readonly TimelineAction[] MenuActions = AllActions.Where(a => a.showInMenu && !(a is MarkerHeaderAction)).ToArray();
  41. public static void GetMenuEntries(IEnumerable<TimelineAction> actions, Vector2? mousePos, List<MenuActionItem> items)
  42. {
  43. var state = TimelineWindow.instance.state;
  44. var mode = TimelineWindow.instance.currentMode.mode;
  45. foreach (var action in actions)
  46. {
  47. var actionItem = action;
  48. action.mousePosition = mousePos;
  49. items.Add(
  50. new MenuActionItem()
  51. {
  52. category = action.category,
  53. entryName = action.GetDisplayName(state),
  54. shortCut = action.shortCut,
  55. isChecked = action.IsChecked(state),
  56. isActiveInMode = IsActionActiveInMode(action, mode),
  57. priority = action.priority,
  58. state = action.GetDisplayState(state),
  59. callback = () =>
  60. {
  61. actionItem.mousePosition = mousePos;
  62. actionItem.Execute(state);
  63. actionItem.mousePosition = null;
  64. }
  65. }
  66. );
  67. action.mousePosition = null;
  68. }
  69. }
  70. public static bool HandleShortcut(WindowState state, Event evt)
  71. {
  72. if (EditorGUI.IsEditingTextField())
  73. return false;
  74. foreach (var action in AllActions)
  75. {
  76. var attr = action.GetType().GetCustomAttributes(typeof(ShortcutAttribute), true);
  77. foreach (ShortcutAttribute shortcut in attr)
  78. {
  79. if (shortcut.MatchesEvent(evt))
  80. {
  81. if (s_ShowActionTriggeredByShortcut)
  82. Debug.Log(action.GetType().Name);
  83. if (!IsActionActiveInMode(action, TimelineWindow.instance.currentMode.mode))
  84. return false;
  85. var handled = action.Execute(state);
  86. if (handled)
  87. return true;
  88. }
  89. }
  90. }
  91. return false;
  92. }
  93. protected static bool DoInternal(Type t, WindowState state)
  94. {
  95. var action = (TimelineAction)t.GetConstructors()[0].Invoke(null);
  96. if (action.CanExecute(state))
  97. return action.Execute(state);
  98. return false;
  99. }
  100. }
  101. // indicates the action only applies to the marker header menu
  102. abstract class MarkerHeaderAction : TimelineAction
  103. {
  104. }
  105. [MenuEntry("Copy", MenuOrder.TimelineAction.Copy)]
  106. [Shortcut("Main Menu/Edit/Copy", EventCommandNames.Copy)]
  107. class CopyAction : TimelineAction
  108. {
  109. public static bool Do(WindowState state)
  110. {
  111. return DoInternal(typeof(CopyAction), state);
  112. }
  113. public override MenuActionDisplayState GetDisplayState(WindowState state)
  114. {
  115. return SelectionManager.Count() > 0 ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled;
  116. }
  117. public override bool Execute(WindowState state)
  118. {
  119. TimelineEditor.clipboard.Clear();
  120. var clips = SelectionManager.SelectedClips().ToArray();
  121. if (clips.Length > 0)
  122. {
  123. ItemAction<TimelineClip>.Invoke<CopyClipsToClipboard>(state, clips);
  124. }
  125. var markers = SelectionManager.SelectedMarkers().ToArray();
  126. if (markers.Length > 0)
  127. {
  128. ItemAction<IMarker>.Invoke<CopyMarkersToClipboard>(state, markers);
  129. }
  130. var tracks = SelectionManager.SelectedTracks().ToArray();
  131. if (tracks.Length > 0)
  132. {
  133. CopyTracksToClipboard.Do(state, tracks);
  134. }
  135. return true;
  136. }
  137. }
  138. [MenuEntry("Paste", MenuOrder.TimelineAction.Paste)]
  139. [Shortcut("Main Menu/Edit/Paste", EventCommandNames.Paste)]
  140. class PasteAction : TimelineAction
  141. {
  142. public static bool Do(WindowState state)
  143. {
  144. return DoInternal(typeof(PasteAction), state);
  145. }
  146. public override MenuActionDisplayState GetDisplayState(WindowState state)
  147. {
  148. return CanPaste(state) ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled;
  149. }
  150. public override bool Execute(WindowState state)
  151. {
  152. if (!CanPaste(state))
  153. return false;
  154. PasteItems(state, mousePosition);
  155. PasteTracks(state);
  156. state.Refresh();
  157. mousePosition = null;
  158. return true;
  159. }
  160. bool CanPaste(WindowState state)
  161. {
  162. var copiedItems = TimelineEditor.clipboard.GetCopiedItems().ToList();
  163. if (!copiedItems.Any())
  164. return TimelineEditor.clipboard.GetTracks().Any();
  165. return CanPasteItems(copiedItems, state, mousePosition);
  166. }
  167. static bool CanPasteItems(ICollection<ItemsPerTrack> itemsGroups, WindowState state, Vector2? mousePosition)
  168. {
  169. var hasItemsCopiedFromMultipleTracks = itemsGroups.Count > 1;
  170. var allItemsCopiedFromCurrentAsset = itemsGroups.All(x => x.targetTrack.timelineAsset == state.editSequence.asset);
  171. var hasUsedShortcut = mousePosition == null;
  172. var anySourceLocked = itemsGroups.Any(x => x.targetTrack != null && x.targetTrack.lockedInHierarchy);
  173. var targetTrack = GetPickedTrack();
  174. if (targetTrack == null)
  175. targetTrack = SelectionManager.SelectedTracks().FirstOrDefault();
  176. //do not paste if the user copied items from another timeline
  177. //if the copied items comes from > 1 track (since we do not know where to paste the copied items)
  178. //or if a keyboard shortcut was used (since the user will not see the paste result)
  179. if (!allItemsCopiedFromCurrentAsset)
  180. {
  181. var isSelectedTrackInCurrentAsset = targetTrack != null && targetTrack.timelineAsset == state.editSequence.asset;
  182. if (hasItemsCopiedFromMultipleTracks || (hasUsedShortcut && !isSelectedTrackInCurrentAsset))
  183. return false;
  184. }
  185. if (hasUsedShortcut)
  186. return !anySourceLocked; // copy/paste to same track
  187. if (hasItemsCopiedFromMultipleTracks)
  188. {
  189. //do not paste if the track which received the paste action does not contain a copied clip
  190. return !anySourceLocked && itemsGroups.Select(x => x.targetTrack).Contains(targetTrack);
  191. }
  192. var copiedItems = itemsGroups.SelectMany(i => i.items);
  193. return IsTrackValidForItems(targetTrack, copiedItems);
  194. }
  195. static void PasteItems(WindowState state, Vector2? mousePosition)
  196. {
  197. var copiedItems = TimelineEditor.clipboard.GetCopiedItems().ToList();
  198. var numberOfUniqueParentsInClipboard = copiedItems.Count();
  199. if (numberOfUniqueParentsInClipboard == 0) return;
  200. List<ITimelineItem> newItems;
  201. //if the copied items were on a single parent, then use the mouse position to get the parent OR the original parent
  202. if (numberOfUniqueParentsInClipboard == 1)
  203. {
  204. var itemsGroup = copiedItems.First();
  205. TrackAsset target = null;
  206. if (mousePosition.HasValue)
  207. target = GetPickedTrack();
  208. if (target == null)
  209. target = FindSuitableParentForSingleTrackPasteWithoutMouse(itemsGroup);
  210. var candidateTime = TimelineHelpers.GetCandidateTime(state, mousePosition, target);
  211. newItems = TimelineHelpers.DuplicateItemsUsingCurrentEditMode(state, TimelineEditor.clipboard.exposedPropertyTable, TimelineEditor.inspectedDirector, itemsGroup, target, candidateTime, "Paste Items").ToList();
  212. }
  213. //if copied items were on multiple parents, then the destination parents are the same as the original parents
  214. else
  215. {
  216. var time = TimelineHelpers.GetCandidateTime(state, mousePosition, copiedItems.Select(c => c.targetTrack).ToArray());
  217. newItems = TimelineHelpers.DuplicateItemsUsingCurrentEditMode(state, TimelineEditor.clipboard.exposedPropertyTable, TimelineEditor.inspectedDirector, copiedItems, time, "Paste Items").ToList();
  218. }
  219. TimelineHelpers.FrameItems(state, newItems);
  220. SelectionManager.RemoveTimelineSelection();
  221. foreach (var item in newItems)
  222. {
  223. SelectionManager.Add(item);
  224. }
  225. }
  226. static TrackAsset FindSuitableParentForSingleTrackPasteWithoutMouse(ItemsPerTrack itemsGroup)
  227. {
  228. var groupParent = itemsGroup.targetTrack; //set a main parent in the clipboard
  229. var selectedTracks = SelectionManager.SelectedTracks();
  230. if (selectedTracks.Contains(groupParent))
  231. {
  232. return groupParent;
  233. }
  234. //find a selected track suitable for all items
  235. var itemsToPaste = itemsGroup.items;
  236. var compatibleTrack = selectedTracks.FirstOrDefault(t => IsTrackValidForItems(t, itemsToPaste));
  237. return compatibleTrack != null ? compatibleTrack : groupParent;
  238. }
  239. static bool IsTrackValidForItems(TrackAsset track, IEnumerable<ITimelineItem> items)
  240. {
  241. if (track == null || track.lockedInHierarchy) return false;
  242. return items.All(i => i.IsCompatibleWithTrack(track));
  243. }
  244. static TrackAsset GetPickedTrack()
  245. {
  246. var rowGUI = PickerUtils.pickedElements.OfType<IRowGUI>().FirstOrDefault();
  247. if (rowGUI != null)
  248. return rowGUI.asset;
  249. return null;
  250. }
  251. static void PasteTracks(WindowState state)
  252. {
  253. var trackData = TimelineEditor.clipboard.GetTracks().ToList();
  254. if (trackData.Any())
  255. {
  256. SelectionManager.RemoveTimelineSelection();
  257. }
  258. foreach (var track in trackData)
  259. {
  260. var newTrack = track.item.Duplicate(TimelineEditor.clipboard.exposedPropertyTable, TimelineEditor.inspectedDirector, TimelineEditor.inspectedAsset);
  261. SelectionManager.Add(newTrack);
  262. foreach (var childTrack in newTrack.GetFlattenedChildTracks())
  263. {
  264. SelectionManager.Add(childTrack);
  265. }
  266. if (track.parent != null && track.parent.timelineAsset == state.editSequence.asset)
  267. {
  268. TrackExtensions.ReparentTracks(new List<TrackAsset> { newTrack }, track.parent, track.item);
  269. }
  270. }
  271. }
  272. }
  273. [MenuEntry("Duplicate", MenuOrder.TimelineAction.Duplicate)]
  274. [Shortcut("Main Menu/Edit/Duplicate", EventCommandNames.Duplicate)]
  275. class DuplicateAction : TimelineAction
  276. {
  277. public override bool Execute(WindowState state)
  278. {
  279. return Execute(state, (item1, item2) => ItemsUtils.TimeGapBetweenItems(item1, item2, state));
  280. }
  281. internal bool Execute(WindowState state, Func<ITimelineItem, ITimelineItem, double> gapBetweenItems)
  282. {
  283. var selectedItems = SelectionManager.SelectedItems().ToItemsPerTrack().ToList();
  284. if (selectedItems.Any())
  285. {
  286. var requestedTime = CalculateDuplicateTime(selectedItems, gapBetweenItems);
  287. var duplicatedItems = TimelineHelpers.DuplicateItemsUsingCurrentEditMode(state, TimelineEditor.inspectedDirector, TimelineEditor.inspectedDirector, selectedItems, requestedTime, "Duplicate Items");
  288. TimelineHelpers.FrameItems(state, duplicatedItems);
  289. SelectionManager.RemoveTimelineSelection();
  290. foreach (var item in duplicatedItems)
  291. SelectionManager.Add(item);
  292. }
  293. var tracks = SelectionManager.SelectedTracks().ToArray();
  294. if (tracks.Length > 0)
  295. TrackAction.Invoke<DuplicateTracks>(state, tracks);
  296. state.Refresh();
  297. return true;
  298. }
  299. static double CalculateDuplicateTime(IEnumerable<ItemsPerTrack> duplicatedItems, Func<ITimelineItem, ITimelineItem, double> gapBetweenItems)
  300. {
  301. //Find the end time of the rightmost item
  302. var itemsOnTracks = duplicatedItems.SelectMany(i => i.targetTrack.GetItems()).ToList();
  303. var time = itemsOnTracks.Max(i => i.end);
  304. //From all the duplicated items, select the leftmost items
  305. var firstDuplicatedItems = duplicatedItems.Select(i => i.leftMostItem);
  306. var leftMostDuplicatedItems = firstDuplicatedItems.OrderBy(i => i.start).GroupBy(i => i.start).FirstOrDefault();
  307. if (leftMostDuplicatedItems == null) return 0.0;
  308. foreach (var leftMostItem in leftMostDuplicatedItems)
  309. {
  310. var siblings = leftMostItem.parentTrack.GetItems();
  311. var rightMostSiblings = siblings.OrderByDescending(i => i.end).GroupBy(i => i.end).FirstOrDefault();
  312. if (rightMostSiblings == null) continue;
  313. foreach (var sibling in rightMostSiblings)
  314. time = Math.Max(time, sibling.end + gapBetweenItems(leftMostItem, sibling));
  315. }
  316. return time;
  317. }
  318. }
  319. [MenuEntry("Delete", MenuOrder.TimelineAction.Delete)]
  320. [Shortcut("Main Menu/Edit/Delete", EventCommandNames.Delete)]
  321. [ShortcutPlatformOverride(RuntimePlatform.OSXEditor, KeyCode.Backspace, ShortcutModifiers.Action)]
  322. [ActiveInMode(TimelineModes.Default)]
  323. class DeleteAction : TimelineAction
  324. {
  325. public override MenuActionDisplayState GetDisplayState(WindowState state)
  326. {
  327. return CanDelete(state) ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled;
  328. }
  329. static bool CanDelete(WindowState state)
  330. {
  331. if (state.editSequence.isReadOnly)
  332. return false;
  333. // All() returns true when empty
  334. return SelectionManager.SelectedTracks().All(x => !x.lockedInHierarchy) &&
  335. SelectionManager.SelectedItems().All(x => x.parentTrack == null || !x.parentTrack.lockedInHierarchy);
  336. }
  337. public override bool Execute(WindowState state)
  338. {
  339. if (SelectionManager.GetCurrentInlineEditorCurve() != null)
  340. return false;
  341. if (!CanDelete(state))
  342. return false;
  343. var selectedItems = SelectionManager.SelectedItems();
  344. DeleteItems(selectedItems);
  345. var tracks = SelectionManager.SelectedTracks().ToArray();
  346. if (tracks.Any())
  347. TrackAction.Invoke<DeleteTracks>(state, tracks);
  348. state.Refresh();
  349. return selectedItems.Any() || tracks.Length > 0;
  350. }
  351. internal static void DeleteItems(IEnumerable<ITimelineItem> items)
  352. {
  353. var tracks = items.GroupBy(c => c.parentTrack);
  354. foreach (var track in tracks)
  355. TimelineUndo.PushUndo(track.Key, "Delete Items");
  356. TimelineAnimationUtilities.UnlinkAnimationWindowFromClips(items.OfType<ClipItem>().Select(i => i.clip));
  357. EditMode.PrepareItemsDelete(ItemsUtils.ToItemsPerTrack(items));
  358. EditModeUtils.Delete(items);
  359. SelectionManager.RemoveAllClips();
  360. }
  361. }
  362. [MenuEntry("Match Content", MenuOrder.TimelineAction.MatchContent)]
  363. [Shortcut(Shortcuts.Timeline.matchContent)]
  364. class MatchContent : TimelineAction
  365. {
  366. public override MenuActionDisplayState GetDisplayState(WindowState state)
  367. {
  368. var clips = SelectionManager.SelectedClips().ToArray();
  369. if (!clips.Any() || SelectionManager.GetCurrentInlineEditorCurve() != null)
  370. return MenuActionDisplayState.Hidden;
  371. return clips.Any(TimelineHelpers.HasUsableAssetDuration)
  372. ? MenuActionDisplayState.Visible
  373. : MenuActionDisplayState.Disabled;
  374. }
  375. public override bool Execute(WindowState state)
  376. {
  377. if (SelectionManager.GetCurrentInlineEditorCurve() != null)
  378. return false;
  379. var clips = SelectionManager.SelectedClips().ToArray();
  380. return clips.Length > 0 && ClipModifier.MatchContent(clips);
  381. }
  382. }
  383. [Shortcut(Shortcuts.Timeline.play)]
  384. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  385. class PlayTimelineAction : TimelineAction
  386. {
  387. public override bool Execute(WindowState state)
  388. {
  389. var currentState = state.playing;
  390. state.SetPlaying(!currentState);
  391. return true;
  392. }
  393. }
  394. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  395. class SelectAllAction : TimelineAction
  396. {
  397. public override bool Execute(WindowState state)
  398. {
  399. // otherwise select all tracks.
  400. SelectionManager.Clear();
  401. state.GetWindow().allTracks.ForEach(x => SelectionManager.Add(x.track));
  402. return true;
  403. }
  404. }
  405. [Shortcut(Shortcuts.Timeline.previousFrame)]
  406. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  407. class PreviousFrameAction : TimelineAction
  408. {
  409. public override bool Execute(WindowState state)
  410. {
  411. state.editSequence.frame--;
  412. return true;
  413. }
  414. }
  415. [Shortcut(Shortcuts.Timeline.nextFrame)]
  416. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  417. class NextFrameAction : TimelineAction
  418. {
  419. public override bool Execute(WindowState state)
  420. {
  421. state.editSequence.frame++;
  422. return true;
  423. }
  424. }
  425. [Shortcut(Shortcuts.Timeline.frameAll)]
  426. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  427. class FrameAllAction : TimelineAction
  428. {
  429. public override bool Execute(WindowState state)
  430. {
  431. var inlineCurveEditor = SelectionManager.GetCurrentInlineEditorCurve();
  432. if (inlineCurveEditor != null && inlineCurveEditor.inlineCurvesSelected)
  433. {
  434. FrameSelectedAction.FrameInlineCurves(inlineCurveEditor, state, false);
  435. return true;
  436. }
  437. if (state.IsEditingASubItem())
  438. return false;
  439. var w = state.GetWindow();
  440. if (w == null || w.treeView == null)
  441. return false;
  442. var visibleTracks = w.treeView.visibleTracks.ToList();
  443. if (state.editSequence.asset != null && state.editSequence.asset.markerTrack != null)
  444. visibleTracks.Add(state.editSequence.asset.markerTrack);
  445. if (visibleTracks.Count == 0)
  446. return false;
  447. var startTime = float.MaxValue;
  448. var endTime = float.MinValue;
  449. foreach (var t in visibleTracks)
  450. {
  451. if (t == null)
  452. continue;
  453. double trackStart, trackEnd;
  454. t.GetItemRange(out trackStart, out trackEnd);
  455. startTime = Mathf.Min(startTime, (float)trackStart);
  456. endTime = Mathf.Max(endTime, (float)(trackEnd));
  457. }
  458. if (startTime != float.MinValue)
  459. {
  460. FrameSelectedAction.FrameRange(startTime, endTime, state);
  461. return true;
  462. }
  463. return false;
  464. }
  465. }
  466. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  467. class FrameSelectedAction : TimelineAction
  468. {
  469. public static void FrameRange(float startTime, float endTime, WindowState state)
  470. {
  471. if (startTime > endTime)
  472. {
  473. return;
  474. }
  475. var halfDuration = endTime - Math.Max(0.0f, startTime);
  476. if (halfDuration > 0.0f)
  477. {
  478. state.SetTimeAreaShownRange(Mathf.Max(-10.0f, startTime - (halfDuration * 0.1f)),
  479. endTime + (halfDuration * 0.1f));
  480. }
  481. else
  482. {
  483. // start == end
  484. // keep the zoom level constant, only pan the time area to center the item
  485. var currentRange = state.timeAreaShownRange.y - state.timeAreaShownRange.x;
  486. state.SetTimeAreaShownRange(startTime - currentRange / 2, startTime + currentRange / 2);
  487. }
  488. TimelineZoomManipulator.InvalidateWheelZoom();
  489. state.Evaluate();
  490. }
  491. public override bool Execute(WindowState state)
  492. {
  493. var inlineCurveEditor = SelectionManager.GetCurrentInlineEditorCurve();
  494. if (inlineCurveEditor != null && inlineCurveEditor.inlineCurvesSelected)
  495. {
  496. FrameInlineCurves(inlineCurveEditor, state, true);
  497. return true;
  498. }
  499. if (state.IsEditingASubItem())
  500. return false;
  501. if (SelectionManager.Count() == 0)
  502. return false;
  503. var startTime = float.MaxValue;
  504. var endTime = float.MinValue;
  505. var clips = SelectionManager.SelectedClipGUI();
  506. var markers = SelectionManager.SelectedMarkers();
  507. if (!clips.Any() && !markers.Any())
  508. return false;
  509. foreach (var c in clips)
  510. {
  511. startTime = Mathf.Min(startTime, (float)c.clip.start);
  512. endTime = Mathf.Max(endTime, (float)c.clip.end);
  513. if (c.clipCurveEditor != null)
  514. {
  515. c.clipCurveEditor.FrameClip();
  516. }
  517. }
  518. foreach (var marker in markers)
  519. {
  520. startTime = Mathf.Min(startTime, (float)marker.time);
  521. endTime = Mathf.Max(endTime, (float)marker.time);
  522. }
  523. FrameRange(startTime, endTime, state);
  524. return true;
  525. }
  526. public static void FrameInlineCurves(IClipCurveEditorOwner curveEditorOwner, WindowState state, bool selectionOnly)
  527. {
  528. var curveEditor = curveEditorOwner.clipCurveEditor.curveEditor;
  529. var frameBounds = selectionOnly ? curveEditor.GetSelectionBounds() : curveEditor.GetClipBounds();
  530. var clipGUI = curveEditorOwner as TimelineClipGUI;
  531. var areaOffset = 0.0f;
  532. if (clipGUI != null)
  533. {
  534. areaOffset = (float)Math.Max(0.0, clipGUI.clip.FromLocalTimeUnbound(0.0));
  535. var timeScale = (float)clipGUI.clip.timeScale; // Note: The getter for clip.timeScale is guaranteed to never be zero.
  536. // Apply scaling
  537. var newMin = frameBounds.min.x / timeScale;
  538. var newMax = (frameBounds.max.x - frameBounds.min.x) / timeScale + newMin;
  539. frameBounds.SetMinMax(
  540. new Vector3(newMin, frameBounds.min.y, frameBounds.min.z),
  541. new Vector3(newMax, frameBounds.max.y, frameBounds.max.z));
  542. }
  543. curveEditor.Frame(frameBounds, true, true);
  544. var area = curveEditor.shownAreaInsideMargins;
  545. area.x += areaOffset;
  546. var curveStart = curveEditorOwner.clipCurveEditor.dataSource.start;
  547. FrameRange(curveStart + frameBounds.min.x, curveStart + frameBounds.max.x, state);
  548. }
  549. }
  550. [Shortcut(Shortcuts.Timeline.previousKey)]
  551. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  552. class PrevKeyAction : TimelineAction
  553. {
  554. public override bool Execute(WindowState state)
  555. {
  556. var keyTraverser = new Utilities.KeyTraverser(state.editSequence.asset, 0.01f / state.referenceSequence.frameRate);
  557. var time = keyTraverser.GetPrevKey((float)state.editSequence.time, state.dirtyStamp);
  558. if (time != state.editSequence.time)
  559. {
  560. state.editSequence.time = time;
  561. }
  562. return true;
  563. }
  564. }
  565. [Shortcut(Shortcuts.Timeline.nextKey)]
  566. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  567. class NextKeyAction : TimelineAction
  568. {
  569. public override bool Execute(WindowState state)
  570. {
  571. var keyTraverser = new Utilities.KeyTraverser(state.editSequence.asset, 0.01f / state.referenceSequence.frameRate);
  572. var time = keyTraverser.GetNextKey((float)state.editSequence.time, state.dirtyStamp);
  573. if (time != state.editSequence.time)
  574. {
  575. state.editSequence.time = time;
  576. }
  577. return true;
  578. }
  579. }
  580. [Shortcut(Shortcuts.Timeline.goToStart)]
  581. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  582. class GotoStartAction : TimelineAction
  583. {
  584. public override bool Execute(WindowState state)
  585. {
  586. state.editSequence.time = 0.0f;
  587. state.EnsurePlayHeadIsVisible();
  588. return true;
  589. }
  590. }
  591. [Shortcut(Shortcuts.Timeline.goToEnd)]
  592. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  593. class GotoEndAction : TimelineAction
  594. {
  595. public override bool Execute(WindowState state)
  596. {
  597. state.editSequence.time = state.editSequence.duration;
  598. state.EnsurePlayHeadIsVisible();
  599. return true;
  600. }
  601. }
  602. [Shortcut(Shortcuts.Timeline.zoomIn)]
  603. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  604. class ZoomIn : TimelineAction
  605. {
  606. public override bool Execute(WindowState state)
  607. {
  608. TimelineZoomManipulator.Instance.DoZoom(1.15f, state);
  609. return true;
  610. }
  611. }
  612. [Shortcut(Shortcuts.Timeline.zoomOut)]
  613. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  614. class ZoomOut : TimelineAction
  615. {
  616. public override bool Execute(WindowState state)
  617. {
  618. TimelineZoomManipulator.Instance.DoZoom(0.85f, state);
  619. return true;
  620. }
  621. }
  622. [Shortcut(Shortcuts.Timeline.collapseGroup)]
  623. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  624. class CollapseGroup : TimelineAction
  625. {
  626. public override bool Execute(WindowState state)
  627. {
  628. return KeyboardNavigation.CollapseGroup(state);
  629. }
  630. }
  631. [Shortcut(Shortcuts.Timeline.unCollapseGroup)]
  632. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  633. class UnCollapseGroup : TimelineAction
  634. {
  635. public override bool Execute(WindowState state)
  636. {
  637. return KeyboardNavigation.UnCollapseGroup(state);
  638. }
  639. }
  640. [Shortcut(Shortcuts.Timeline.selectLeftItem)]
  641. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  642. class SelectLeftClip : TimelineAction
  643. {
  644. public override bool Execute(WindowState state)
  645. {
  646. // Switches to track header if no left track exists
  647. return KeyboardNavigation.SelectLeftItem(state);
  648. }
  649. }
  650. [Shortcut(Shortcuts.Timeline.selectRightItem)]
  651. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  652. class SelectRightClip : TimelineAction
  653. {
  654. public override bool Execute(WindowState state)
  655. {
  656. return KeyboardNavigation.SelectRightItem(state);
  657. }
  658. }
  659. [Shortcut(Shortcuts.Timeline.selectUpItem)]
  660. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  661. class SelectUpClip : TimelineAction
  662. {
  663. public override bool Execute(WindowState state)
  664. {
  665. return KeyboardNavigation.SelectUpItem(state);
  666. }
  667. }
  668. [Shortcut(Shortcuts.Timeline.selectUpTrack)]
  669. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  670. class SelectUpTrack : TimelineAction
  671. {
  672. public override bool Execute(WindowState state)
  673. {
  674. return KeyboardNavigation.SelectUpTrack();
  675. }
  676. }
  677. [Shortcut(Shortcuts.Timeline.selectDownItem)]
  678. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  679. class SelectDownClip : TimelineAction
  680. {
  681. public override bool Execute(WindowState state)
  682. {
  683. return KeyboardNavigation.SelectDownItem(state);
  684. }
  685. }
  686. [Shortcut(Shortcuts.Timeline.selectDownTrack)]
  687. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  688. class SelectDownTrack : TimelineAction
  689. {
  690. public override bool Execute(WindowState state)
  691. {
  692. if (!KeyboardNavigation.ClipAreaActive() && !KeyboardNavigation.TrackHeadActive())
  693. return KeyboardNavigation.FocusFirstVisibleItem(state);
  694. else
  695. return KeyboardNavigation.SelectDownTrack();
  696. }
  697. }
  698. [Shortcut(Shortcuts.Timeline.multiSelectLeft)]
  699. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  700. class MultiselectLeftClip : TimelineAction
  701. {
  702. public override bool Execute(WindowState state)
  703. {
  704. return KeyboardNavigation.SelectLeftItem(state, true);
  705. }
  706. }
  707. [Shortcut(Shortcuts.Timeline.multiSelectRight)]
  708. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  709. class MultiselectRightClip : TimelineAction
  710. {
  711. public override bool Execute(WindowState state)
  712. {
  713. return KeyboardNavigation.SelectRightItem(state, true);
  714. }
  715. }
  716. [Shortcut(Shortcuts.Timeline.multiSelectUp)]
  717. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  718. class MultiselectUpTrack : TimelineAction
  719. {
  720. public override bool Execute(WindowState state)
  721. {
  722. return KeyboardNavigation.SelectUpTrack(true);
  723. }
  724. }
  725. [Shortcut(Shortcuts.Timeline.multiSelectDown)]
  726. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  727. class MultiselectDownTrack : TimelineAction
  728. {
  729. public override bool Execute(WindowState state)
  730. {
  731. return KeyboardNavigation.SelectDownTrack(true);
  732. }
  733. }
  734. [Shortcut(Shortcuts.Timeline.toggleClipTrackArea)]
  735. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  736. class ToggleClipTrackArea : TimelineAction
  737. {
  738. public override bool Execute(WindowState state)
  739. {
  740. if (KeyboardNavigation.TrackHeadActive())
  741. return KeyboardNavigation.FocusFirstVisibleItem(state, SelectionManager.SelectedTracks());
  742. if (!KeyboardNavigation.ClipAreaActive())
  743. return KeyboardNavigation.FocusFirstVisibleItem(state);
  744. var item = KeyboardNavigation.GetVisibleSelectedItems().LastOrDefault();
  745. if (item != null)
  746. SelectionManager.SelectOnly(item.parentTrack);
  747. return true;
  748. }
  749. }
  750. [MenuEntry("Mute", MenuOrder.TrackAction.MuteTrack)]
  751. class ToggleMuteMarkersOnTimeline : MarkerHeaderAction
  752. {
  753. public override bool IsChecked(WindowState state)
  754. {
  755. return IsMarkerTrackValid(state) && state.editSequence.asset.markerTrack.muted;
  756. }
  757. public override bool Execute(WindowState state)
  758. {
  759. if (state.showMarkerHeader)
  760. ToggleMute(state);
  761. return true;
  762. }
  763. static void ToggleMute(WindowState state)
  764. {
  765. var timeline = state.editSequence.asset;
  766. timeline.CreateMarkerTrack();
  767. TimelineUndo.PushUndo(timeline.markerTrack, "Toggle Mute");
  768. timeline.markerTrack.muted = !timeline.markerTrack.muted;
  769. }
  770. static bool IsMarkerTrackValid(WindowState state)
  771. {
  772. var timeline = state.editSequence.asset;
  773. return timeline != null && timeline.markerTrack != null;
  774. }
  775. }
  776. [MenuEntry("Show Markers", MenuOrder.TrackAction.ShowHideMarkers)]
  777. [ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
  778. class ToggleShowMarkersOnTimeline : MarkerHeaderAction
  779. {
  780. public override bool IsChecked(WindowState state)
  781. {
  782. return state.showMarkerHeader;
  783. }
  784. public override bool Execute(WindowState state)
  785. {
  786. ToggleShow(state);
  787. return true;
  788. }
  789. static void ToggleShow(WindowState state)
  790. {
  791. state.GetWindow().SetShowMarkerHeader(!state.showMarkerHeader);
  792. }
  793. }
  794. }