No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Trackhead.cs 15KB

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