TimelineClipGUI.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEngine;
  5. using UnityEngine.Playables;
  6. using UnityEngine.Timeline;
  7. namespace UnityEditor.Timeline
  8. {
  9. class TimelineClipGUI : TimelineItemGUI, IClipCurveEditorOwner, ISnappable, IAttractable
  10. {
  11. EditorClip m_EditorItem;
  12. Rect m_ClipCenterSection;
  13. readonly List<Rect> m_LoopRects = new List<Rect>();
  14. ClipDrawData m_ClipDrawData;
  15. Rect m_MixOutRect = new Rect();
  16. Rect m_MixInRect = new Rect();
  17. int m_MinLoopIndex = 1;
  18. // clip dirty detection
  19. int m_LastDirtyIndex = Int32.MinValue;
  20. bool m_ClipViewDirty = true;
  21. bool supportResize { get; }
  22. public ClipCurveEditor clipCurveEditor { get; set; }
  23. public TimelineClipGUI previousClip { get; set; }
  24. public TimelineClipGUI nextClip { get; set; }
  25. static readonly float k_MinMixWidth = 2;
  26. static readonly float k_MaxHandleWidth = 10f;
  27. static readonly float k_MinHandleWidth = 1f;
  28. bool? m_ShowDrillIcon;
  29. ClipEditor m_ClipEditor;
  30. static List<PlayableDirector> s_TempSubDirectors = new List<PlayableDirector>();
  31. static readonly IconData k_DiggableClipIcon = new IconData(DirectorStyles.LoadIcon("TimelineDigIn"));
  32. string name
  33. {
  34. get
  35. {
  36. if (string.IsNullOrEmpty(clip.displayName))
  37. return "(Empty)";
  38. return clip.displayName;
  39. }
  40. }
  41. public bool inlineCurvesSelected => SelectionManager.IsCurveEditorFocused(this);
  42. public Rect mixOutRect
  43. {
  44. get
  45. {
  46. var percent = clip.mixOutPercentage;
  47. var x = Mathf.Round(treeViewRect.width * (1 - percent));
  48. var width = Mathf.Round(treeViewRect.width * percent);
  49. m_MixOutRect.Set(x, 0.0f, width, treeViewRect.height);
  50. return m_MixOutRect;
  51. }
  52. }
  53. public Rect mixInRect
  54. {
  55. get
  56. {
  57. var width = Mathf.Round(treeViewRect.width * clip.mixInPercentage);
  58. m_MixInRect.Set(0.0f, 0.0f, width, treeViewRect.height);
  59. return m_MixInRect;
  60. }
  61. }
  62. public ClipBlends GetClipBlends()
  63. {
  64. var _mixInRect = mixInRect;
  65. var _mixOutRect = mixOutRect;
  66. var blendInKind = BlendKind.None;
  67. if (_mixInRect.width > k_MinMixWidth && clip.hasBlendIn)
  68. blendInKind = BlendKind.Mix;
  69. else if (_mixInRect.width > k_MinMixWidth)
  70. blendInKind = BlendKind.Ease;
  71. var blendOutKind = BlendKind.None;
  72. if (_mixOutRect.width > k_MinMixWidth && clip.hasBlendOut)
  73. blendOutKind = BlendKind.Mix;
  74. else if (_mixOutRect.width > k_MinMixWidth)
  75. blendOutKind = BlendKind.Ease;
  76. return new ClipBlends(blendInKind, _mixInRect, blendOutKind, _mixOutRect);
  77. }
  78. public override double start
  79. {
  80. get { return clip.start; }
  81. }
  82. public override double end
  83. {
  84. get { return clip.end; }
  85. }
  86. public bool supportsLooping
  87. {
  88. get { return clip.SupportsLooping(); }
  89. }
  90. // for the inline curve editor, only show loops if we recorded the asset
  91. bool IClipCurveEditorOwner.showLoops
  92. {
  93. get { return clip.SupportsLooping() && (clip.asset is AnimationPlayableAsset); }
  94. }
  95. TrackAsset IClipCurveEditorOwner.owner
  96. {
  97. get { return clip.parentTrack; }
  98. }
  99. public bool supportsSubTimelines
  100. {
  101. get { return m_ClipEditor.supportsSubTimelines; }
  102. }
  103. public int minLoopIndex
  104. {
  105. get { return m_MinLoopIndex; }
  106. }
  107. public Rect clippedRect { get; private set; }
  108. public override void Select()
  109. {
  110. zOrder = zOrderProvider.Next();
  111. SelectionManager.Add(clip);
  112. if (clipCurveEditor != null && SelectionManager.Count() == 1)
  113. SelectionManager.SelectInlineCurveEditor(this);
  114. }
  115. public override bool IsSelected()
  116. {
  117. return SelectionManager.Contains(clip);
  118. }
  119. public override void Deselect()
  120. {
  121. SelectionManager.Remove(clip);
  122. if (inlineCurvesSelected)
  123. SelectionManager.SelectInlineCurveEditor(null);
  124. }
  125. public override bool CanSelect()
  126. {
  127. ClipBlends clipBlends = GetClipBlends();
  128. //clips that do not overlap are always selectable
  129. if (clipBlends.inKind != BlendKind.Mix && clipBlends.outKind != BlendKind.Mix)
  130. return true;
  131. Vector2 mousePos = Event.current.mousePosition - rect.position;
  132. return m_ClipCenterSection.Contains(mousePos) || IsPointLocatedInClipBlend(mousePos, clipBlends);
  133. }
  134. static bool IsPointLocatedInClipBlend(Vector2 pt, ClipBlends blends)
  135. {
  136. if (blends.inRect.Contains(pt))
  137. return Sign(pt, blends.inRect.min, blends.inRect.max) < 0;
  138. if (blends.outRect.Contains(pt))
  139. return Sign(pt, blends.outRect.min, blends.outRect.max) >= 0;
  140. return false;
  141. }
  142. static float Sign(Vector2 point, Vector2 linePoint1, Vector2 linePoint2)
  143. {
  144. return (point.x - linePoint2.x) * (linePoint1.y - linePoint2.y) - (linePoint1.x - linePoint2.x) * (point.y - linePoint2.y);
  145. }
  146. public override ITimelineItem item
  147. {
  148. get { return ItemsUtils.ToItem(clip); }
  149. }
  150. IZOrderProvider zOrderProvider { get; }
  151. public TimelineClipHandle leftHandle { get; }
  152. public TimelineClipHandle rightHandle { get; }
  153. public TimelineClipGUI(TimelineClip clip, IRowGUI parent, IZOrderProvider provider) : base(parent)
  154. {
  155. zOrderProvider = provider;
  156. zOrder = provider.Next();
  157. m_EditorItem = EditorClipFactory.GetEditorClip(clip);
  158. m_ClipEditor = CustomTimelineEditorCache.GetClipEditor(clip);
  159. supportResize = true;
  160. leftHandle = new TimelineClipHandle(this, TrimEdge.Start);
  161. rightHandle = new TimelineClipHandle(this, TrimEdge.End);
  162. ItemToItemGui.Add(clip, this);
  163. }
  164. void CreateInlineCurveEditor(WindowState state)
  165. {
  166. if (clipCurveEditor != null)
  167. return;
  168. var animationClip = clip.animationClip;
  169. if (animationClip != null && animationClip.empty)
  170. animationClip = null;
  171. // prune out clips coming from FBX
  172. if (animationClip != null && !clip.recordable)
  173. return; // don't show, even if there are curves
  174. if (animationClip == null && !clip.HasAnyAnimatableParameters())
  175. return; // nothing to show
  176. state.AddEndFrameDelegate((istate, currentEvent) =>
  177. {
  178. clipCurveEditor = new ClipCurveEditor(CurveDataSource.Create(this), TimelineWindow.instance, clip.parentTrack);
  179. return true;
  180. });
  181. }
  182. public TimelineClip clip
  183. {
  184. get { return m_EditorItem.clip; }
  185. }
  186. // Draw the actual clip. Defers to the track drawer for customization
  187. void UpdateDrawData(WindowState state, Rect drawRect, string title, bool selected, bool previousClipSelected, float rectXOffset)
  188. {
  189. m_ClipDrawData.clip = clip;
  190. m_ClipDrawData.targetRect = drawRect;
  191. m_ClipDrawData.clipCenterSection = m_ClipCenterSection;
  192. m_ClipDrawData.unclippedRect = treeViewRect;
  193. m_ClipDrawData.title = title;
  194. m_ClipDrawData.selected = selected;
  195. m_ClipDrawData.inlineCurvesSelected = inlineCurvesSelected;
  196. m_ClipDrawData.previousClip = previousClip != null ? previousClip.clip : null;
  197. m_ClipDrawData.previousClipSelected = previousClipSelected;
  198. Vector3 shownAreaTime = state.timeAreaShownRange;
  199. m_ClipDrawData.localVisibleStartTime = clip.ToLocalTimeUnbound(Math.Max(clip.start, shownAreaTime.x));
  200. m_ClipDrawData.localVisibleEndTime = clip.ToLocalTimeUnbound(Math.Min(clip.end, shownAreaTime.y));
  201. m_ClipDrawData.clippedRect = new Rect(clippedRect.x - rectXOffset, 0.0f, clippedRect.width, clippedRect.height);
  202. m_ClipDrawData.minLoopIndex = minLoopIndex;
  203. m_ClipDrawData.loopRects = m_LoopRects;
  204. m_ClipDrawData.supportsLooping = supportsLooping;
  205. m_ClipDrawData.clipBlends = GetClipBlends();
  206. m_ClipDrawData.clipEditor = m_ClipEditor;
  207. m_ClipDrawData.ClipDrawOptions = UpdateClipDrawOptions(m_ClipEditor, clip);
  208. UpdateClipIcons(state);
  209. }
  210. void UpdateClipIcons(WindowState state)
  211. {
  212. // Pass 1 - gather size
  213. int required = 0;
  214. bool requiresDigIn = ShowDrillIcon(state.editSequence.director);
  215. if (requiresDigIn)
  216. required++;
  217. var icons = m_ClipDrawData.ClipDrawOptions.icons;
  218. foreach (var icon in icons)
  219. {
  220. if (icon != null)
  221. required++;
  222. }
  223. // Pass 2 - copy icon data
  224. if (required == 0)
  225. {
  226. m_ClipDrawData.rightIcons = null;
  227. return;
  228. }
  229. if (m_ClipDrawData.rightIcons == null || m_ClipDrawData.rightIcons.Length != required)
  230. m_ClipDrawData.rightIcons = new IconData[required];
  231. int index = 0;
  232. if (requiresDigIn)
  233. m_ClipDrawData.rightIcons[index++] = k_DiggableClipIcon;
  234. foreach (var icon in icons)
  235. {
  236. if (icon != null)
  237. m_ClipDrawData.rightIcons[index++] = new IconData(icon);
  238. }
  239. }
  240. static ClipDrawOptions UpdateClipDrawOptions(ClipEditor clipEditor, TimelineClip clip)
  241. {
  242. try
  243. {
  244. return clipEditor.GetClipOptions(clip);
  245. }
  246. catch (Exception e)
  247. {
  248. Debug.LogException(e);
  249. }
  250. return CustomTimelineEditorCache.GetDefaultClipEditor().GetClipOptions(clip);
  251. }
  252. static void DrawClip(ClipDrawData drawData)
  253. {
  254. ClipDrawer.DrawDefaultClip(drawData);
  255. if (drawData.clip.asset is AnimationPlayableAsset)
  256. {
  257. var state = TimelineWindow.instance.state;
  258. if (state.recording && state.IsArmedForRecord(drawData.clip.parentTrack))
  259. {
  260. ClipDrawer.DrawAnimationRecordBorder(drawData);
  261. ClipDrawer.DrawRecordProhibited(drawData);
  262. }
  263. }
  264. }
  265. public void DrawGhostClip(Rect targetRect)
  266. {
  267. DrawSimpleClip(targetRect, ClipBorder.Selection(), new Color(1.0f, 1.0f, 1.0f, 0.5f));
  268. }
  269. public void DrawInvalidClip(Rect targetRect)
  270. {
  271. DrawSimpleClip(targetRect, ClipBorder.Selection(), DirectorStyles.Instance.customSkin.colorInvalidClipOverlay);
  272. }
  273. void DrawSimpleClip(Rect targetRect, ClipBorder border, Color overlay)
  274. {
  275. var drawOptions = UpdateClipDrawOptions(CustomTimelineEditorCache.GetClipEditor(clip), clip);
  276. ClipDrawer.DrawSimpleClip(clip, targetRect, border, overlay, drawOptions);
  277. }
  278. void DrawInto(Rect drawRect, WindowState state)
  279. {
  280. if (Event.current.type != EventType.Repaint)
  281. return;
  282. // create the inline curve editor if not already created
  283. CreateInlineCurveEditor(state);
  284. // @todo optimization, most of the calculations (rect, offsets, colors, etc.) could be cached
  285. // and rebuilt when the hash of the clip changes.
  286. if (isInvalid)
  287. {
  288. DrawInvalidClip(treeViewRect);
  289. return;
  290. }
  291. GUI.BeginClip(drawRect);
  292. var originRect = new Rect(0.0f, 0.0f, drawRect.width, drawRect.height);
  293. string clipLabel = name;
  294. var selected = SelectionManager.Contains(clip);
  295. var previousClipSelected = previousClip != null && SelectionManager.Contains(previousClip.clip);
  296. if (selected && 1.0 != clip.timeScale)
  297. clipLabel += " " + clip.timeScale.ToString("F2") + "x";
  298. UpdateDrawData(state, originRect, clipLabel, selected, previousClipSelected, drawRect.x);
  299. DrawClip(m_ClipDrawData);
  300. GUI.EndClip();
  301. if (clip.parentTrack != null && !clip.parentTrack.lockedInHierarchy)
  302. {
  303. if (selected && supportResize)
  304. {
  305. var cursorRect = rect;
  306. cursorRect.xMin += leftHandle.boundingRect.width;
  307. cursorRect.xMax -= rightHandle.boundingRect.width;
  308. EditorGUIUtility.AddCursorRect(cursorRect, MouseCursor.MoveArrow);
  309. }
  310. if (supportResize)
  311. {
  312. var handleWidth = Mathf.Clamp(drawRect.width * 0.3f, k_MinHandleWidth, k_MaxHandleWidth);
  313. leftHandle.Draw(drawRect, handleWidth, state);
  314. rightHandle.Draw(drawRect, handleWidth, state);
  315. }
  316. }
  317. }
  318. void CalculateClipRectangle(Rect trackRect, WindowState state)
  319. {
  320. if (m_ClipViewDirty)
  321. {
  322. var clipRect = RectToTimeline(trackRect, state);
  323. treeViewRect = clipRect;
  324. // calculate clipped rect
  325. clipRect.xMin = Mathf.Max(clipRect.xMin, trackRect.xMin);
  326. clipRect.xMax = Mathf.Min(clipRect.xMax, trackRect.xMax);
  327. if (clipRect.width > 0 && clipRect.width < 2)
  328. {
  329. clipRect.width = 5.0f;
  330. }
  331. clippedRect = clipRect;
  332. }
  333. }
  334. void AddToSpacePartitioner(WindowState state)
  335. {
  336. if (Event.current.type == EventType.Repaint && !parent.locked)
  337. state.spacePartitioner.AddBounds(this, rect);
  338. }
  339. void CalculateBlendRect()
  340. {
  341. m_ClipCenterSection = treeViewRect;
  342. m_ClipCenterSection.x = 0;
  343. m_ClipCenterSection.y = 0;
  344. m_ClipCenterSection.xMin = Mathf.Round(treeViewRect.width * clip.mixInPercentage);
  345. m_ClipCenterSection.width = Mathf.Round(treeViewRect.width);
  346. m_ClipCenterSection.xMax -= Mathf.Round(mixOutRect.width + treeViewRect.width * clip.mixInPercentage);
  347. }
  348. // Entry point to the Clip Drawing...
  349. public override void Draw(Rect trackRect, bool trackRectChanged, WindowState state)
  350. {
  351. // if the clip has changed, fire the appropriate callback
  352. DetectClipChanged(trackRectChanged);
  353. // update the clip projected rectangle on the timeline
  354. CalculateClipRectangle(trackRect, state);
  355. AddToSpacePartitioner(state);
  356. // update the blend rects (when clip overlaps with others)
  357. CalculateBlendRect();
  358. // update the loop rects (when clip loops)
  359. CalculateLoopRects(trackRect, state);
  360. DrawExtrapolation(trackRect, treeViewRect);
  361. DrawInto(treeViewRect, state);
  362. ResetClipChanged();
  363. }
  364. void DetectClipChanged(bool trackRectChanged)
  365. {
  366. if (Event.current.type == EventType.Layout)
  367. {
  368. if (clip.DirtyIndex != m_LastDirtyIndex)
  369. {
  370. m_ClipViewDirty = true;
  371. try
  372. {
  373. m_ClipEditor.OnClipChanged(clip);
  374. }
  375. catch (Exception e)
  376. {
  377. Debug.LogException(e);
  378. }
  379. m_LastDirtyIndex = clip.DirtyIndex;
  380. }
  381. m_ClipViewDirty |= trackRectChanged;
  382. }
  383. }
  384. void ResetClipChanged()
  385. {
  386. if (Event.current.type == EventType.Repaint)
  387. m_ClipViewDirty = false;
  388. }
  389. GUIStyle GetExtrapolationIcon(TimelineClip.ClipExtrapolation mode)
  390. {
  391. GUIStyle extrapolationIcon = null;
  392. switch (mode)
  393. {
  394. case TimelineClip.ClipExtrapolation.None: return null;
  395. case TimelineClip.ClipExtrapolation.Hold: extrapolationIcon = m_Styles.extrapolationHold; break;
  396. case TimelineClip.ClipExtrapolation.Loop: extrapolationIcon = m_Styles.extrapolationLoop; break;
  397. case TimelineClip.ClipExtrapolation.PingPong: extrapolationIcon = m_Styles.extrapolationPingPong; break;
  398. case TimelineClip.ClipExtrapolation.Continue: extrapolationIcon = m_Styles.extrapolationContinue; break;
  399. }
  400. return extrapolationIcon;
  401. }
  402. Rect GetPreExtrapolationBounds(Rect trackRect, Rect clipRect, GUIStyle icon)
  403. {
  404. float x = clipRect.xMin - (icon.fixedWidth + 10.0f);
  405. float y = trackRect.yMin + (trackRect.height - icon.fixedHeight) / 2.0f;
  406. if (previousClip != null)
  407. {
  408. float distance = Mathf.Abs(treeViewRect.xMin - previousClip.treeViewRect.xMax);
  409. if (distance < icon.fixedWidth)
  410. return new Rect(0.0f, 0.0f, 0.0f, 0.0f);
  411. if (distance < icon.fixedWidth + 20.0f)
  412. {
  413. float delta = (distance - icon.fixedWidth) / 2.0f;
  414. x = clipRect.xMin - (icon.fixedWidth + delta);
  415. }
  416. }
  417. return new Rect(x, y, icon.fixedWidth, icon.fixedHeight);
  418. }
  419. Rect GetPostExtrapolationBounds(Rect trackRect, Rect clipRect, GUIStyle icon)
  420. {
  421. float x = clipRect.xMax + 10.0f;
  422. float y = trackRect.yMin + (trackRect.height - icon.fixedHeight) / 2.0f;
  423. if (nextClip != null)
  424. {
  425. float distance = Mathf.Abs(nextClip.treeViewRect.xMin - treeViewRect.xMax);
  426. if (distance < icon.fixedWidth)
  427. return new Rect(0.0f, 0.0f, 0.0f, 0.0f);
  428. if (distance < icon.fixedWidth + 20.0f)
  429. {
  430. float delta = (distance - icon.fixedWidth) / 2.0f;
  431. x = clipRect.xMax + delta;
  432. }
  433. }
  434. return new Rect(x, y, icon.fixedWidth, icon.fixedHeight);
  435. }
  436. static void DrawExtrapolationIcon(Rect rect, GUIStyle icon)
  437. {
  438. GUI.Label(rect, GUIContent.none, icon);
  439. }
  440. void DrawExtrapolation(Rect trackRect, Rect clipRect)
  441. {
  442. if (clip.hasPreExtrapolation)
  443. {
  444. GUIStyle icon = GetExtrapolationIcon(clip.preExtrapolationMode);
  445. if (icon != null)
  446. {
  447. Rect iconBounds = GetPreExtrapolationBounds(trackRect, clipRect, icon);
  448. if (iconBounds.width > 1 && iconBounds.height > 1)
  449. DrawExtrapolationIcon(iconBounds, icon);
  450. }
  451. }
  452. if (clip.hasPostExtrapolation)
  453. {
  454. GUIStyle icon = GetExtrapolationIcon(clip.postExtrapolationMode);
  455. if (icon != null)
  456. {
  457. Rect iconBounds = GetPostExtrapolationBounds(trackRect, clipRect, icon);
  458. if (iconBounds.width > 1 && iconBounds.height > 1)
  459. DrawExtrapolationIcon(iconBounds, icon);
  460. }
  461. }
  462. }
  463. static Rect ProjectRectOnTimeline(Rect rect, Rect trackRect, WindowState state)
  464. {
  465. Rect newRect = rect;
  466. // transform clipRect into pixel-space
  467. newRect.x *= state.timeAreaScale.x;
  468. newRect.width *= state.timeAreaScale.x;
  469. newRect.x += state.timeAreaTranslation.x + trackRect.xMin;
  470. // adjust clipRect height and vertical centering
  471. const int clipPadding = 2;
  472. newRect.y = trackRect.y + clipPadding;
  473. newRect.height = trackRect.height - (2 * clipPadding);
  474. return newRect;
  475. }
  476. void CalculateLoopRects(Rect trackRect, WindowState state)
  477. {
  478. if (!m_ClipViewDirty)
  479. return;
  480. m_LoopRects.Clear();
  481. if (clip.duration < WindowState.kTimeEpsilon)
  482. return;
  483. var times = TimelineHelpers.GetLoopTimes(clip);
  484. var loopDuration = TimelineHelpers.GetLoopDuration(clip);
  485. m_MinLoopIndex = -1;
  486. // we have a hold, no need to compute all loops
  487. if (!supportsLooping)
  488. {
  489. if (times.Length > 1)
  490. {
  491. var t = times[1];
  492. float loopTime = (float)(clip.duration - t);
  493. m_LoopRects.Add(ProjectRectOnTimeline(new Rect((float)(t + clip.start), 0, loopTime, 0), trackRect, state));
  494. }
  495. return;
  496. }
  497. var range = state.timeAreaShownRange;
  498. var visibleStartTime = range.x - clip.start;
  499. var visibleEndTime = range.y - clip.start;
  500. for (int i = 1; i < times.Length; i++)
  501. {
  502. var t = times[i];
  503. // don't draw off screen loops
  504. if (t > visibleEndTime)
  505. break;
  506. float loopTime = Mathf.Min((float)(clip.duration - t), (float)loopDuration);
  507. var loopEnd = t + loopTime;
  508. if (loopEnd < visibleStartTime)
  509. continue;
  510. m_LoopRects.Add(ProjectRectOnTimeline(new Rect((float)(t + clip.start), 0, loopTime, 0), trackRect, state));
  511. if (m_MinLoopIndex == -1)
  512. m_MinLoopIndex = i;
  513. }
  514. }
  515. public override Rect RectToTimeline(Rect trackRect, WindowState state)
  516. {
  517. var offsetFromTimeSpaceToPixelSpace = state.timeAreaTranslation.x + trackRect.xMin;
  518. var start = (float)(DiscreteTime)clip.start;
  519. var end = (float)(DiscreteTime)clip.end;
  520. return Rect.MinMaxRect(
  521. Mathf.Round(start * state.timeAreaScale.x + offsetFromTimeSpaceToPixelSpace), Mathf.Round(trackRect.yMin),
  522. Mathf.Round(end * state.timeAreaScale.x + offsetFromTimeSpaceToPixelSpace), Mathf.Round(trackRect.yMax)
  523. );
  524. }
  525. public IEnumerable<Edge> SnappableEdgesFor(IAttractable attractable, ManipulateEdges manipulateEdges)
  526. {
  527. var edges = new List<Edge>();
  528. bool canAddEdges = !parent.muted;
  529. if (canAddEdges)
  530. {
  531. // Hack: Trim Start in Ripple mode should not have any snap point added
  532. if (EditMode.editType == EditMode.EditType.Ripple && manipulateEdges == ManipulateEdges.Left)
  533. return edges;
  534. if (attractable != this)
  535. {
  536. if (EditMode.editType == EditMode.EditType.Ripple)
  537. {
  538. bool skip = false;
  539. // Hack: Since Trim End and Move in Ripple mode causes other snap point to move on the same track (which is not supported), disable snapping for this special cases...
  540. // TODO Find a proper way to have different snap edges for each edit mode.
  541. if (manipulateEdges == ManipulateEdges.Right)
  542. {
  543. var otherClipGUI = attractable as TimelineClipGUI;
  544. skip = otherClipGUI != null && otherClipGUI.parent == parent;
  545. }
  546. else if (manipulateEdges == ManipulateEdges.Both)
  547. {
  548. var moveHandler = attractable as MoveItemHandler;
  549. skip = moveHandler != null && moveHandler.movingItems.Any(clips => clips.targetTrack == clip.parentTrack && clip.start >= clips.start);
  550. }
  551. if (skip)
  552. return edges;
  553. }
  554. AddEdge(edges, clip.start);
  555. AddEdge(edges, clip.end);
  556. }
  557. else
  558. {
  559. if (manipulateEdges == ManipulateEdges.Right)
  560. {
  561. var d = TimelineHelpers.GetClipAssetEndTime(clip);
  562. if (d < double.MaxValue)
  563. {
  564. if (clip.SupportsLooping())
  565. {
  566. var l = TimelineHelpers.GetLoopDuration(clip);
  567. var shownTime = TimelineWindow.instance.state.timeAreaShownRange;
  568. do
  569. {
  570. AddEdge(edges, d, false);
  571. d += l;
  572. }
  573. while (d < shownTime.y);
  574. }
  575. else
  576. {
  577. AddEdge(edges, d, false);
  578. }
  579. }
  580. }
  581. if (manipulateEdges == ManipulateEdges.Left)
  582. {
  583. var clipInfo = AnimationClipCurveCache.Instance.GetCurveInfo(clip.animationClip);
  584. if (clipInfo != null && clipInfo.keyTimes.Any())
  585. AddEdge(edges, clip.FromLocalTimeUnbound(clipInfo.keyTimes.Min()), false);
  586. }
  587. }
  588. }
  589. return edges;
  590. }
  591. public bool ShouldSnapTo(ISnappable snappable)
  592. {
  593. return true;
  594. }
  595. bool ShowDrillIcon(PlayableDirector resolver)
  596. {
  597. if (!m_ShowDrillIcon.HasValue || TimelineWindow.instance.hierarchyChangedThisFrame)
  598. {
  599. var nestable = m_ClipEditor.supportsSubTimelines;
  600. m_ShowDrillIcon = nestable && resolver != null;
  601. if (m_ShowDrillIcon.Value)
  602. {
  603. s_TempSubDirectors.Clear();
  604. try
  605. {
  606. m_ClipEditor.GetSubTimelines(clip, resolver, s_TempSubDirectors);
  607. }
  608. catch (Exception e)
  609. {
  610. Debug.LogException(e);
  611. }
  612. m_ShowDrillIcon &= s_TempSubDirectors.Count > 0;
  613. }
  614. }
  615. return m_ShowDrillIcon.Value;
  616. }
  617. static void AddEdge(List<Edge> edges, double time, bool showEdgeHint = true)
  618. {
  619. var shownTime = TimelineWindow.instance.state.timeAreaShownRange;
  620. if (time >= shownTime.x && time <= shownTime.y)
  621. edges.Add(new Edge(time, showEdgeHint));
  622. }
  623. public void SelectCurves()
  624. {
  625. SelectionManager.SelectOnly(clip);
  626. SelectionManager.SelectInlineCurveEditor(this);
  627. }
  628. public void ValidateCurvesSelection()
  629. {
  630. if (!IsSelected()) //if clip is not selected, deselect the inline curve
  631. SelectionManager.SelectInlineCurveEditor(null);
  632. }
  633. }
  634. }