MoveItemHandler.cs 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using UnityEngine;
  4. using UnityEngine.Timeline;
  5. namespace UnityEditor.Timeline
  6. {
  7. class MoveItemHandler : IAttractable, IAttractionHandler
  8. {
  9. bool m_Grabbing;
  10. MovingItems m_LeftMostMovingItems;
  11. MovingItems m_RightMostMovingItems;
  12. HashSet<TimelineItemGUI> m_ItemGUIs;
  13. ItemsGroup m_ItemsGroup;
  14. public TrackAsset targetTrack { get; private set; }
  15. public bool allowTrackSwitch { get; private set; }
  16. int m_GrabbedModalUndoGroup = -1;
  17. readonly WindowState m_State;
  18. public MovingItems[] movingItems { get; private set; }
  19. public MoveItemHandler(WindowState state)
  20. {
  21. m_State = state;
  22. }
  23. public void Grab(IEnumerable<ITimelineItem> items, TrackAsset referenceTrack)
  24. {
  25. Grab(items, referenceTrack, Vector2.zero);
  26. }
  27. public void Grab(IEnumerable<ITimelineItem> items, TrackAsset referenceTrack, Vector2 mousePosition)
  28. {
  29. if (items == null) return;
  30. items = items.ToArray(); // Cache enumeration result
  31. if (!items.Any()) return;
  32. m_GrabbedModalUndoGroup = Undo.GetCurrentGroup();
  33. var trackItems = items.GroupBy(c => c.parentTrack).ToArray();
  34. var trackItemsCount = trackItems.Length;
  35. var tracks = items.Select(c => c.parentTrack).Where(x => x != null).Distinct();
  36. movingItems = new MovingItems[trackItemsCount];
  37. allowTrackSwitch = trackItemsCount == 1 && !trackItems.SelectMany(x => x).Any(x => x is MarkerItem); // For now, track switch is only supported when all items are on the same track and there are no items
  38. // one push per track handles all the clips on the track
  39. UndoExtensions.RegisterTracks(tracks, "Move Items");
  40. foreach (var sourceTrack in tracks)
  41. {
  42. // push all markers on the track because of ripple
  43. UndoExtensions.RegisterMarkers(sourceTrack.GetMarkers(), "Move Items");
  44. }
  45. for (var i = 0; i < trackItemsCount; ++i)
  46. {
  47. var track = trackItems[i].Key;
  48. var grabbedItems = new MovingItems(m_State, track, trackItems[i].ToArray(), referenceTrack, mousePosition, allowTrackSwitch);
  49. movingItems[i] = grabbedItems;
  50. }
  51. m_LeftMostMovingItems = null;
  52. m_RightMostMovingItems = null;
  53. foreach (var grabbedTrackItems in movingItems)
  54. {
  55. if (m_LeftMostMovingItems == null || m_LeftMostMovingItems.start > grabbedTrackItems.start)
  56. m_LeftMostMovingItems = grabbedTrackItems;
  57. if (m_RightMostMovingItems == null || m_RightMostMovingItems.end < grabbedTrackItems.end)
  58. m_RightMostMovingItems = grabbedTrackItems;
  59. }
  60. m_ItemGUIs = new HashSet<TimelineItemGUI>();
  61. m_ItemsGroup = new ItemsGroup(items);
  62. foreach (var item in items)
  63. m_ItemGUIs.Add(item.gui);
  64. targetTrack = referenceTrack;
  65. EditMode.BeginMove(this);
  66. m_Grabbing = true;
  67. }
  68. public void Drop()
  69. {
  70. if (IsValidDrop())
  71. {
  72. foreach (var grabbedItems in movingItems)
  73. {
  74. var track = grabbedItems.targetTrack;
  75. UndoExtensions.RegisterTrack(track, "Move Items");
  76. if (EditModeUtils.IsInfiniteTrack(track) && grabbedItems.clips.Any())
  77. ((AnimationTrack)track).ConvertToClipMode();
  78. }
  79. EditMode.FinishMove();
  80. Done();
  81. }
  82. else
  83. {
  84. Cancel();
  85. }
  86. EditMode.ClearEditMode();
  87. }
  88. bool IsValidDrop()
  89. {
  90. return movingItems.All(g => g.canDrop);
  91. }
  92. void Cancel()
  93. {
  94. if (!m_Grabbing)
  95. return;
  96. // TODO fix undo reselection persistency
  97. // identify the clips by their playable asset, since that reference will survive the undo
  98. // This is a workaround, until a more persistent fix for selection of clips across Undo can be found
  99. var assets = movingItems.SelectMany(x => x.clips).Select(x => x.asset);
  100. Undo.RevertAllDownToGroup(m_GrabbedModalUndoGroup);
  101. // reselect the clips from the original clip
  102. var clipsToSelect = movingItems.Select(x => x.originalTrack).SelectMany(x => x.GetClips()).Where(x => assets.Contains(x.asset)).ToArray();
  103. SelectionManager.RemoveTimelineSelection();
  104. foreach (var c in clipsToSelect)
  105. SelectionManager.Add(c);
  106. Done();
  107. }
  108. void Done()
  109. {
  110. foreach (var movingItem in movingItems)
  111. {
  112. foreach (var item in movingItem.items)
  113. {
  114. if (item.gui != null)
  115. item.gui.isInvalid = false;
  116. }
  117. }
  118. movingItems = null;
  119. m_LeftMostMovingItems = null;
  120. m_RightMostMovingItems = null;
  121. m_Grabbing = false;
  122. m_State.Refresh();
  123. }
  124. public double start { get { return m_ItemsGroup.start; } }
  125. public double end { get { return m_ItemsGroup.end; } }
  126. public bool ShouldSnapTo(ISnappable snappable)
  127. {
  128. var itemGUI = snappable as TimelineItemGUI;
  129. return itemGUI != null && !m_ItemGUIs.Contains(itemGUI);
  130. }
  131. public void UpdateTrackTarget(TrackAsset track)
  132. {
  133. if (!EditMode.AllowTrackSwitch())
  134. return;
  135. targetTrack = track;
  136. var targetTracksChanged = false;
  137. foreach (var grabbedItem in movingItems)
  138. {
  139. var prevTrackGUI = grabbedItem.targetTrack;
  140. grabbedItem.SetReferenceTrack(track);
  141. targetTracksChanged = grabbedItem.targetTrack != prevTrackGUI;
  142. }
  143. if (targetTracksChanged)
  144. EditMode.HandleTrackSwitch(movingItems);
  145. RefreshPreviewItems();
  146. m_State.rebuildGraph |= targetTracksChanged;
  147. }
  148. public void OnGUI(Event evt)
  149. {
  150. if (!m_Grabbing)
  151. return;
  152. if (evt.type != EventType.Repaint)
  153. return;
  154. var isValid = IsValidDrop();
  155. using (new GUIViewportScope(m_State.GetWindow().sequenceContentRect))
  156. {
  157. foreach (var grabbedClip in movingItems)
  158. {
  159. grabbedClip.RefreshBounds(m_State, evt.mousePosition);
  160. if (!grabbedClip.HasAnyDetachedParents())
  161. continue;
  162. grabbedClip.Draw(isValid);
  163. }
  164. if (isValid)
  165. {
  166. EditMode.DrawMoveGUI(m_State, movingItems);
  167. }
  168. else
  169. {
  170. TimelineCursors.ClearCursor();
  171. }
  172. }
  173. }
  174. public void OnAttractedEdge(IAttractable attractable, ManipulateEdges manipulateEdges, AttractedEdge edge, double time)
  175. {
  176. double offset;
  177. if (edge == AttractedEdge.Right)
  178. {
  179. var duration = end - start;
  180. var startTime = time - duration;
  181. startTime = EditMode.AdjustStartTime(m_State, m_RightMostMovingItems, startTime);
  182. offset = startTime + duration - end;
  183. }
  184. else
  185. {
  186. if (edge == AttractedEdge.Left)
  187. time = EditMode.AdjustStartTime(m_State, m_LeftMostMovingItems, time);
  188. offset = time - start;
  189. }
  190. if (start + offset < 0.0)
  191. offset = -start;
  192. if (!offset.Equals(0.0))
  193. {
  194. foreach (var grabbedClips in movingItems)
  195. grabbedClips.start += offset;
  196. EditMode.UpdateMove();
  197. RefreshPreviewItems();
  198. }
  199. }
  200. public void RefreshPreviewItems()
  201. {
  202. foreach (var movingItemsGroup in movingItems)
  203. {
  204. // Check validity
  205. var valid = ValidateItemDrag(movingItemsGroup);
  206. foreach (var item in movingItemsGroup.items)
  207. {
  208. if (item.gui != null)
  209. item.gui.isInvalid = !valid;
  210. }
  211. movingItemsGroup.canDrop = valid;
  212. }
  213. }
  214. static bool ValidateItemDrag(ItemsPerTrack itemsGroup)
  215. {
  216. //TODO-marker: this is to prevent the drag operation from being canceled when moving only markers
  217. if (itemsGroup.clips.Any())
  218. {
  219. if (itemsGroup.targetTrack == null)
  220. return false;
  221. if (itemsGroup.targetTrack.lockedInHierarchy)
  222. return false;
  223. if (itemsGroup.items.Any(i => !i.IsCompatibleWithTrack(itemsGroup.targetTrack)))
  224. return false;
  225. return EditMode.ValidateDrag(itemsGroup);
  226. }
  227. return true;
  228. }
  229. public void OnTrackDetach()
  230. {
  231. EditMode.OnTrackDetach(movingItems);
  232. }
  233. }
  234. }