TimelineClipGUI.cs 26 KB

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