MoveItemHandler.cs 9.5 KB

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