Trackhead.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. using System;
  2. using System.Linq;
  3. using UnityEngine;
  4. using UnityEngine.Timeline;
  5. namespace UnityEditor.Timeline
  6. {
  7. static class Gaps
  8. {
  9. static readonly string kInsertTime = "Insert Time";
  10. public static void Insert(TimelineAsset asset, double at, double amount, double tolerance)
  11. {
  12. // gather all clips
  13. var clips = asset.flattenedTracks.SelectMany(x => x.clips).Where(x => (x.start - at) >= -tolerance).ToList();
  14. var markers = asset.flattenedTracks.SelectMany(x => x.GetMarkers()).Where(x => (x.time - at) >= -tolerance).ToList();
  15. if (!clips.Any() && !markers.Any())
  16. return;
  17. // push undo on the tracks for the clips that are being modified
  18. foreach (var t in clips.Select(x => x.parentTrack).Distinct())
  19. {
  20. TimelineUndo.PushUndo(t, kInsertTime);
  21. }
  22. // push the clips
  23. foreach (var clip in clips)
  24. {
  25. clip.start += amount;
  26. }
  27. // push undos and move the markers
  28. foreach (var marker in markers)
  29. {
  30. var obj = marker as UnityEngine.Object;
  31. if (obj != null)
  32. TimelineUndo.PushUndo(obj, kInsertTime);
  33. marker.time += amount;
  34. }
  35. TimelineEditor.Refresh(RefreshReason.ContentsModified);
  36. }
  37. }
  38. class PlayheadContextMenu : Manipulator
  39. {
  40. readonly TimeAreaItem m_TimeAreaItem;
  41. static readonly int[] kFrameInsertionValues = {5, 10, 25, 100};
  42. public PlayheadContextMenu(TimeAreaItem timeAreaItem)
  43. {
  44. m_TimeAreaItem = timeAreaItem;
  45. }
  46. protected override bool ContextClick(Event evt, WindowState state)
  47. {
  48. if (!m_TimeAreaItem.bounds.Contains(evt.mousePosition))
  49. return false;
  50. var tolerance = TimeUtility.GetEpsilon(state.editSequence.time, state.referenceSequence.frameRate);
  51. var menu = new GenericMenu();
  52. if (!TimelineWindow.instance.state.editSequence.isReadOnly)
  53. {
  54. menu.AddItem(EditorGUIUtility.TrTextContent("Insert/Frame/Single"), false, () =>
  55. Gaps.Insert(state.editSequence.asset, state.editSequence.time, 1.0 / state.referenceSequence.frameRate, tolerance)
  56. );
  57. for (var i = 0; i != kFrameInsertionValues.Length; ++i)
  58. {
  59. double f = kFrameInsertionValues[i];
  60. menu.AddItem(EditorGUIUtility.TrTextContent("Insert/Frame/" + kFrameInsertionValues[i] + " Frames"), false, () =>
  61. Gaps.Insert(state.editSequence.asset, state.editSequence.time, f / state.referenceSequence.frameRate, tolerance)
  62. );
  63. }
  64. var playRangeTime = state.playRange;
  65. if (playRangeTime.y > playRangeTime.x)
  66. {
  67. menu.AddItem(EditorGUIUtility.TrTextContent("Insert/Selected Time"), false, () =>
  68. Gaps.Insert(state.editSequence.asset, playRangeTime.x, playRangeTime.y - playRangeTime.x, TimeUtility.GetEpsilon(playRangeTime.x, state.referenceSequence.frameRate))
  69. );
  70. }
  71. }
  72. menu.AddItem(EditorGUIUtility.TrTextContent("Select/Clips Ending Before"), false, () => SelectMenuCallback(x => x.end < state.editSequence.time + tolerance, state));
  73. menu.AddItem(EditorGUIUtility.TrTextContent("Select/Clips Starting Before"), false, () => SelectMenuCallback(x => x.start < state.editSequence.time + tolerance, state));
  74. menu.AddItem(EditorGUIUtility.TrTextContent("Select/Clips Ending After"), false, () => SelectMenuCallback(x => x.end - state.editSequence.time >= -tolerance, state));
  75. menu.AddItem(EditorGUIUtility.TrTextContent("Select/Clips Starting After"), false, () => SelectMenuCallback(x => x.start - state.editSequence.time >= -tolerance, state));
  76. menu.AddItem(EditorGUIUtility.TrTextContent("Select/Clips Intersecting"), false, () => SelectMenuCallback(x => x.start <= state.editSequence.time && state.editSequence.time <= x.end, state));
  77. menu.AddItem(EditorGUIUtility.TrTextContent("Select/Blends Intersecting"), false, () => SelectMenuCallback(x => SelectBlendingIntersecting(x, state.editSequence.time), state));
  78. menu.ShowAsContext();
  79. return true;
  80. }
  81. static bool SelectBlendingIntersecting(TimelineClip clip, double time)
  82. {
  83. return clip.start <= time && time <= clip.end && (
  84. (time <= clip.start + clip.blendInDuration) ||
  85. (time >= clip.end - clip.blendOutDuration)
  86. );
  87. }
  88. static void SelectMenuCallback(Func<TimelineClip, bool> selector, WindowState state)
  89. {
  90. var allClips = state.GetWindow().treeView.allClipGuis;
  91. if (allClips == null)
  92. return;
  93. SelectionManager.Clear();
  94. for (var i = 0; i != allClips.Count; ++i)
  95. {
  96. var c = allClips[i];
  97. if (c != null && c.clip != null && selector(c.clip))
  98. {
  99. SelectionManager.Add(c.clip);
  100. }
  101. }
  102. }
  103. }
  104. class TimeAreaContextMenu : Manipulator
  105. {
  106. protected override bool ContextClick(Event evt, WindowState state)
  107. {
  108. if (state.timeAreaRect.Contains(Event.current.mousePosition))
  109. {
  110. var menu = new GenericMenu();
  111. AddTimeAreaMenuItems(menu, state);
  112. menu.ShowAsContext();
  113. return true;
  114. }
  115. return false;
  116. }
  117. internal static void AddTimeAreaMenuItems(GenericMenu menu, WindowState state)
  118. {
  119. foreach (var value in Enum.GetValues(typeof(TimelineAsset.DurationMode)))
  120. {
  121. var mode = (TimelineAsset.DurationMode)value;
  122. var item = EditorGUIUtility.TextContent(string.Format(TimelineWindow.Styles.DurationModeText, L10n.Tr(ObjectNames.NicifyVariableName(mode.ToString()))));
  123. if (state.recording || state.IsEditingASubTimeline() || state.editSequence.asset == null
  124. || state.editSequence.isReadOnly)
  125. menu.AddDisabledItem(item);
  126. else
  127. menu.AddItem(item, state.editSequence.asset.durationMode == mode, () => SelectDurationCallback(state, mode));
  128. menu.AddItem(DirectorStyles.showMarkersOnTimeline, state.showMarkerHeader, () => new ToggleShowMarkersOnTimeline().Execute(state));
  129. }
  130. }
  131. static void SelectDurationCallback(WindowState state, TimelineAsset.DurationMode mode)
  132. {
  133. if (mode == state.editSequence.asset.durationMode)
  134. return;
  135. TimelineUndo.PushUndo(state.editSequence.asset, "Duration Mode");
  136. // if we switched from Auto to Fixed, use the auto duration as the new fixed duration so the end marker stay in the same position.
  137. if (state.editSequence.asset.durationMode == TimelineAsset.DurationMode.BasedOnClips && mode == TimelineAsset.DurationMode.FixedLength)
  138. {
  139. state.editSequence.asset.fixedDuration = state.editSequence.duration;
  140. }
  141. state.editSequence.asset.durationMode = mode;
  142. state.UpdateRootPlayableDuration(state.editSequence.duration);
  143. }
  144. }
  145. class Scrub : Manipulator
  146. {
  147. readonly Func<Event, WindowState, bool> m_OnMouseDown;
  148. readonly Action<double> m_OnMouseDrag;
  149. readonly Action m_OnMouseUp;
  150. bool m_IsCaptured;
  151. public Scrub(Func<Event, WindowState, bool> onMouseDown, Action<double> onMouseDrag, Action onMouseUp)
  152. {
  153. m_OnMouseDown = onMouseDown;
  154. m_OnMouseDrag = onMouseDrag;
  155. m_OnMouseUp = onMouseUp;
  156. }
  157. protected override bool MouseDown(Event evt, WindowState state)
  158. {
  159. if (evt.button != 0)
  160. return false;
  161. if (!m_OnMouseDown(evt, state))
  162. return false;
  163. state.AddCaptured(this);
  164. m_IsCaptured = true;
  165. return true;
  166. }
  167. protected override bool MouseUp(Event evt, WindowState state)
  168. {
  169. if (!m_IsCaptured)
  170. return false;
  171. m_IsCaptured = false;
  172. state.RemoveCaptured(this);
  173. m_OnMouseUp();
  174. return true;
  175. }
  176. protected override bool MouseDrag(Event evt, WindowState state)
  177. {
  178. if (!m_IsCaptured)
  179. return false;
  180. m_OnMouseDrag(state.GetSnappedTimeAtMousePosition(evt.mousePosition));
  181. return true;
  182. }
  183. }
  184. class TimeAreaItem : Control
  185. {
  186. public Color headColor { get; set; }
  187. public Color lineColor { get; set; }
  188. public bool drawLine { get; set; }
  189. public bool drawHead { get; set; }
  190. public bool canMoveHead { get; set; }
  191. public string tooltip { get; set; }
  192. public Vector2 boundOffset { get; set; }
  193. readonly GUIContent m_HeaderContent = new GUIContent();
  194. readonly GUIStyle m_Style;
  195. readonly Tooltip m_Tooltip;
  196. Rect m_BoundingRect;
  197. float widgetHeight { get { return m_Style.fixedHeight; } }
  198. float widgetWidth { get { return m_Style.fixedWidth; } }
  199. public Rect bounds
  200. {
  201. get
  202. {
  203. Rect r = m_BoundingRect;
  204. r.y = TimelineWindow.instance.state.timeAreaRect.yMax - widgetHeight;
  205. r.position += boundOffset;
  206. return r;
  207. }
  208. }
  209. public GUIStyle style
  210. {
  211. get { return m_Style; }
  212. }
  213. public bool showTooltip { get; set; }
  214. // is this the first frame the drag callback is being invoked
  215. public bool firstDrag { get; private set; }
  216. public TimeAreaItem(GUIStyle style, Action<double> onDrag)
  217. {
  218. m_Style = style;
  219. headColor = Color.white;
  220. var scrub = new Scrub(
  221. (evt, state) =>
  222. {
  223. firstDrag = true;
  224. return state.timeAreaRect.Contains(evt.mousePosition) && bounds.Contains(evt.mousePosition);
  225. },
  226. (d) =>
  227. {
  228. if (onDrag != null)
  229. onDrag(d);
  230. firstDrag = false;
  231. },
  232. () =>
  233. {
  234. showTooltip = false;
  235. firstDrag = false;
  236. }
  237. );
  238. AddManipulator(scrub);
  239. lineColor = m_Style.normal.textColor;
  240. drawLine = true;
  241. drawHead = true;
  242. canMoveHead = false;
  243. tooltip = string.Empty;
  244. boundOffset = Vector2.zero;
  245. m_Tooltip = new Tooltip(DirectorStyles.Instance.displayBackground, DirectorStyles.Instance.tinyFont);
  246. }
  247. public void Draw(Rect rect, WindowState state, double time)
  248. {
  249. var clipRect = new Rect(0.0f, 0.0f, TimelineWindow.instance.position.width, TimelineWindow.instance.position.height);
  250. clipRect.xMin += state.sequencerHeaderWidth;
  251. using (new GUIViewportScope(clipRect))
  252. {
  253. Vector2 windowCoordinate = rect.min;
  254. windowCoordinate.y += 4.0f;
  255. windowCoordinate.x = state.TimeToPixel(time);
  256. m_BoundingRect = new Rect((windowCoordinate.x - widgetWidth / 2.0f), windowCoordinate.y, widgetWidth, widgetHeight);
  257. // Do not paint if the time cursor goes outside the timeline bounds...
  258. if (Event.current.type == EventType.Repaint)
  259. {
  260. if (m_BoundingRect.xMax < state.timeAreaRect.xMin)
  261. return;
  262. if (m_BoundingRect.xMin > state.timeAreaRect.xMax)
  263. return;
  264. }
  265. var top = new Vector3(windowCoordinate.x, rect.y - DirectorStyles.kDurationGuiThickness);
  266. var bottom = new Vector3(windowCoordinate.x, rect.yMax);
  267. if (drawLine)
  268. {
  269. Rect lineRect = Rect.MinMaxRect(top.x - 0.5f, top.y, bottom.x + 0.5f, bottom.y);
  270. EditorGUI.DrawRect(lineRect, lineColor);
  271. }
  272. if (drawHead)
  273. {
  274. Color c = GUI.color;
  275. GUI.color = headColor;
  276. GUI.Box(bounds, m_HeaderContent, m_Style);
  277. GUI.color = c;
  278. if (canMoveHead)
  279. EditorGUIUtility.AddCursorRect(bounds, MouseCursor.MoveArrow);
  280. }
  281. if (showTooltip)
  282. {
  283. m_Tooltip.text = TimeReferenceUtility.ToTimeString(time);
  284. Vector2 position = bounds.position;
  285. position.y = state.timeAreaRect.y;
  286. position.y -= m_Tooltip.bounds.height;
  287. position.x -= Mathf.Abs(m_Tooltip.bounds.width - bounds.width) / 2.0f;
  288. Rect tooltipBounds = bounds;
  289. tooltipBounds.position = position;
  290. m_Tooltip.bounds = tooltipBounds;
  291. m_Tooltip.Draw();
  292. }
  293. }
  294. }
  295. }
  296. }