SnapEngine.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEngine;
  5. using UnityEngine.Timeline;
  6. namespace UnityEditor.Timeline
  7. {
  8. enum ManipulateEdges
  9. {
  10. Left,
  11. Right,
  12. Both
  13. }
  14. class SnapEngine
  15. {
  16. static readonly float k_MagnetInfluenceInPixels = 10.0f;
  17. class SnapInfo
  18. {
  19. public double time { get; set; }
  20. public bool showSnapHint { get; set; }
  21. public bool IsInInfluenceZone(double currentTime, WindowState state)
  22. {
  23. var pos = state.TimeToPixel(currentTime);
  24. var magnetPos = state.TimeToPixel(time);
  25. return Math.Abs(pos - magnetPos) < k_MagnetInfluenceInPixels;
  26. }
  27. }
  28. struct TimeBoundaries
  29. {
  30. public TimeBoundaries(double l, double r)
  31. {
  32. left = l;
  33. right = r;
  34. }
  35. public readonly double left;
  36. public readonly double right;
  37. public TimeBoundaries Translate(double d)
  38. {
  39. return new TimeBoundaries(left + d, right + d);
  40. }
  41. }
  42. public static bool displayDebugLayout;
  43. readonly IAttractable m_Attractable;
  44. readonly IAttractionHandler m_AttractionHandler;
  45. readonly ManipulateEdges m_ManipulateEdges;
  46. readonly WindowState m_State;
  47. double m_GrabbedTime;
  48. TimeBoundaries m_GrabbedTimes;
  49. TimeBoundaries m_CurrentTimes;
  50. readonly List<SnapInfo> m_Magnets = new List<SnapInfo>();
  51. bool m_SnapEnabled;
  52. public SnapEngine(IAttractable attractable, IAttractionHandler attractionHandler, ManipulateEdges manipulateEdges, WindowState state,
  53. Vector2 mousePosition, IEnumerable<ISnappable> snappables = null)
  54. {
  55. m_Attractable = attractable;
  56. m_ManipulateEdges = manipulateEdges;
  57. m_AttractionHandler = attractionHandler;
  58. m_State = state;
  59. m_CurrentTimes = m_GrabbedTimes = new TimeBoundaries(m_Attractable.start, m_Attractable.end);
  60. m_GrabbedTime = m_State.PixelToTime(mousePosition.x);
  61. // Add Time zero as Magnet
  62. AddMagnet(0.0, true, state);
  63. // Add current Time as Magnet
  64. // case1157280 only add current time as magnet if visible
  65. if (TimelineWindow.instance.currentMode.ShouldShowTimeCursor(m_State))
  66. AddMagnet(state.editSequence.time, true, state);
  67. if (state.IsEditingASubTimeline())
  68. {
  69. // Add start and end of evaluable range as Magnets
  70. // This includes the case where the master timeline has a fixed length
  71. var range = state.editSequence.GetEvaluableRange();
  72. AddMagnet(range.start, true, state);
  73. AddMagnet(range.end, true, state);
  74. }
  75. else if (state.masterSequence.asset.durationMode == TimelineAsset.DurationMode.FixedLength)
  76. {
  77. // Add end sequence Time as Magnet
  78. AddMagnet(state.masterSequence.asset.duration, true, state);
  79. }
  80. if (snappables == null)
  81. snappables = GetVisibleSnappables(m_State);
  82. foreach (var snappable in snappables)
  83. {
  84. if (!attractable.ShouldSnapTo(snappable))
  85. continue;
  86. var edges = snappable.SnappableEdgesFor(attractable, manipulateEdges);
  87. foreach (var edge in edges)
  88. AddMagnet(edge.time, edge.showSnapHint, state);
  89. }
  90. }
  91. public static IEnumerable<ISnappable> GetVisibleSnappables(WindowState state)
  92. {
  93. Rect rect = TimelineWindow.instance.state.timeAreaRect;
  94. rect.height = float.MaxValue;
  95. return state.spacePartitioner.GetItemsInArea<ISnappable>(rect).ToArray();
  96. }
  97. void AddMagnet(double magnetTime, bool showSnapHint, WindowState state)
  98. {
  99. var magnet = m_Magnets.FirstOrDefault(m => m.time.Equals(magnetTime));
  100. if (magnet == null)
  101. {
  102. if (IsMagnetInShownArea(magnetTime, state))
  103. m_Magnets.Add(new SnapInfo { time = magnetTime, showSnapHint = showSnapHint });
  104. }
  105. else
  106. {
  107. magnet.showSnapHint |= showSnapHint;
  108. }
  109. }
  110. static bool IsMagnetInShownArea(double time, WindowState state)
  111. {
  112. var shownArea = state.timeAreaShownRange;
  113. return time >= shownArea.x && time <= shownArea.y;
  114. }
  115. SnapInfo GetMagnetAt(double time)
  116. {
  117. return m_Magnets.FirstOrDefault(m => m.time.Equals(time));
  118. }
  119. SnapInfo ClosestMagnet(double time)
  120. {
  121. SnapInfo candidate = null;
  122. var min = double.MaxValue;
  123. foreach (var magnetInfo in m_Magnets)
  124. {
  125. var m = Math.Abs(magnetInfo.time - time);
  126. if (m < min)
  127. {
  128. candidate = magnetInfo;
  129. min = m;
  130. }
  131. }
  132. if (candidate != null && candidate.IsInInfluenceZone(time, m_State))
  133. return candidate;
  134. return null;
  135. }
  136. public void Snap(Vector2 currentMousePosition, EventModifiers modifiers)
  137. {
  138. var d = m_State.PixelToTime(currentMousePosition.x) - m_GrabbedTime;
  139. m_CurrentTimes = m_GrabbedTimes.Translate(d);
  140. bool isLeft = m_ManipulateEdges == ManipulateEdges.Left || m_ManipulateEdges == ManipulateEdges.Both;
  141. bool isRight = m_ManipulateEdges == ManipulateEdges.Right || m_ManipulateEdges == ManipulateEdges.Both;
  142. bool attracted = false;
  143. m_SnapEnabled = modifiers == ManipulatorsUtils.actionModifier ? !m_State.edgeSnaps : m_State.edgeSnaps;
  144. if (m_SnapEnabled)
  145. {
  146. SnapInfo leftActiveMagnet = null;
  147. SnapInfo rightActiveMagnet = null;
  148. if (isLeft)
  149. leftActiveMagnet = ClosestMagnet(m_CurrentTimes.left);
  150. if (isRight)
  151. rightActiveMagnet = ClosestMagnet(m_CurrentTimes.right);
  152. if (leftActiveMagnet != null || rightActiveMagnet != null)
  153. {
  154. attracted = true;
  155. bool leftAttraction = false;
  156. if (rightActiveMagnet == null)
  157. {
  158. // Attracted by a left magnet only.
  159. leftAttraction = true;
  160. }
  161. else
  162. {
  163. if (leftActiveMagnet != null)
  164. {
  165. // Attracted by both magnets, choose the closest one.
  166. var leftDistance = Math.Abs(leftActiveMagnet.time - m_CurrentTimes.left);
  167. var rightDistance = Math.Abs(rightActiveMagnet.time - m_CurrentTimes.right);
  168. leftAttraction = leftDistance <= rightDistance;
  169. }
  170. // else, Attracted by right magnet only
  171. }
  172. if (leftAttraction)
  173. {
  174. m_AttractionHandler.OnAttractedEdge(m_Attractable, m_ManipulateEdges, AttractedEdge.Left, leftActiveMagnet.time);
  175. }
  176. else
  177. {
  178. m_AttractionHandler.OnAttractedEdge(m_Attractable, m_ManipulateEdges, AttractedEdge.Right, rightActiveMagnet.time);
  179. }
  180. }
  181. }
  182. if (!attracted)
  183. {
  184. var time = isLeft ? m_CurrentTimes.left : m_CurrentTimes.right;
  185. time = m_State.SnapToFrameIfRequired(time);
  186. m_AttractionHandler.OnAttractedEdge(m_Attractable, m_ManipulateEdges, AttractedEdge.None, time);
  187. }
  188. }
  189. public void OnGUI(bool showLeft = true, bool showRight = true)
  190. {
  191. if (displayDebugLayout)
  192. {
  193. // Display Magnet influence zone
  194. foreach (var m in m_Magnets)
  195. {
  196. var window = TimelineWindow.instance;
  197. var rect = new Rect(m_State.TimeToPixel(m.time) - k_MagnetInfluenceInPixels, window.state.timeAreaRect.yMax, 2f * k_MagnetInfluenceInPixels, m_State.windowHeight);
  198. EditorGUI.DrawRect(rect, new Color(1f, 0f, 0f, 0.4f));
  199. }
  200. // Display Cursor position
  201. var mousePos = Event.current.mousePosition;
  202. var time = m_State.PixelToTime(mousePos.x);
  203. var p = new Vector2(m_State.TimeToPixel(time), TimelineWindow.instance.state.timeAreaRect.yMax);
  204. var s = new Vector2(1f, m_State.windowHeight);
  205. EditorGUI.DrawRect(new Rect(p, s), Color.blue);
  206. p = new Vector2(m_State.TimeToPixel(m_GrabbedTime), TimelineWindow.instance.state.timeAreaRect.yMax);
  207. s = new Vector2(1f, m_State.windowHeight);
  208. EditorGUI.DrawRect(new Rect(p, s), Color.red);
  209. p = new Vector2(m_State.TimeToPixel(m_CurrentTimes.left), TimelineWindow.instance.state.timeAreaRect.yMax);
  210. s = new Vector2(1f, m_State.windowHeight);
  211. EditorGUI.DrawRect(new Rect(p, s), Color.yellow);
  212. p = new Vector2(m_State.TimeToPixel(m_CurrentTimes.right), TimelineWindow.instance.state.timeAreaRect.yMax);
  213. EditorGUI.DrawRect(new Rect(p, s), Color.yellow);
  214. }
  215. if (m_SnapEnabled)
  216. {
  217. if (showLeft)
  218. DrawMagnetLineAt(m_Attractable.start);
  219. if (showRight)
  220. DrawMagnetLineAt(m_Attractable.end);
  221. }
  222. }
  223. void DrawMagnetLineAt(double time)
  224. {
  225. var magnet = GetMagnetAt(time);
  226. if (magnet != null && magnet.showSnapHint)
  227. Graphics.DrawLineAtTime(m_State, magnet.time, Color.white);
  228. }
  229. }
  230. }