GUITimeline.cs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. using System;
  4. using BansheeEngine;
  5. namespace BansheeEditor
  6. {
  7. /** @addtogroup AnimationEditor
  8. * @{
  9. */
  10. // TODO DOC
  11. public class GUITimeline
  12. {
  13. private const float LARGE_TICK_HEIGHT_PCT = 0.4f;
  14. private const float SMALL_TICK_HEIGHT_PCT = 0.2f;
  15. private const int PADDING = 30;
  16. private const int TEXT_PADDING = 2;
  17. private const int MIN_TICK_DISTANCE = 25;
  18. private GUICanvas canvas;
  19. private int width;
  20. private int height;
  21. private float rangeStart = 0.0f;
  22. private float rangeEnd = 60.0f;
  23. private int fps = 1;
  24. public GUITimeline(GUILayout layout, int width, int height)
  25. {
  26. canvas = new GUICanvas();
  27. layout.AddElement(canvas);
  28. SetSize(width, height);
  29. }
  30. public void SetSize(int width, int height)
  31. {
  32. this.width = width;
  33. this.height = height;
  34. canvas.SetWidth(width);
  35. canvas.SetHeight(height);
  36. Rebuild();
  37. }
  38. public void SetRange(float start, float end)
  39. {
  40. rangeStart = start;
  41. rangeEnd = end;
  42. Rebuild();
  43. }
  44. public void SetFPS(int fps)
  45. {
  46. this.fps = fps;
  47. Rebuild();
  48. }
  49. private float CalcTickInterval(float length)
  50. {
  51. const int OPTIMAL_TICK_COUNT = 10;
  52. float[] validIntervals =
  53. {
  54. 0.001f, 0.005f, 0.010f, 0.025f, 0.050f, 0.100f, 0.250f, 0.500f, // Hundreds of a second
  55. 1.0f, 5.0f, 10.0f, 30.0f, // Seconds
  56. 60.0f, 120.0f, 300.0f, 600.0f, 1800.0f, 3600.0f // Minutes
  57. };
  58. int bestIntervalIdx = 0;
  59. float bestDistance = float.MaxValue;
  60. for(int i = 0; i < validIntervals.Length; i++)
  61. {
  62. float tickCount = length/validIntervals[i];
  63. float distance = Math.Abs(tickCount - OPTIMAL_TICK_COUNT);
  64. if (distance < bestDistance)
  65. {
  66. bestDistance = distance;
  67. bestIntervalIdx = i;
  68. }
  69. }
  70. // If the draw area is too narrow, limit amount of ticks displayed so they aren't all clumped together
  71. int numTicks = MathEx.FloorToInt(length / validIntervals[bestIntervalIdx]) + 1;
  72. int drawableWidth = Math.Max(0, width - PADDING * 2);
  73. int spacePerTick = drawableWidth/numTicks;
  74. float bestInterval;
  75. if (spacePerTick < MIN_TICK_DISTANCE)
  76. {
  77. int maxTickCount = drawableWidth/MIN_TICK_DISTANCE;
  78. bool foundInterval = false;
  79. for (int i = bestIntervalIdx; i < validIntervals.Length; i++)
  80. {
  81. float tickCount = length/validIntervals[i];
  82. if (tickCount <= maxTickCount)
  83. {
  84. bestIntervalIdx = i;
  85. foundInterval = true;
  86. break;
  87. }
  88. }
  89. // Haven't found a valid round interval, try more intervals
  90. if (!foundInterval)
  91. {
  92. float currentInterval = validIntervals[validIntervals.Length - 1]*2;
  93. while (true)
  94. {
  95. float tickCount = length/ currentInterval;
  96. if (tickCount <= maxTickCount)
  97. {
  98. bestInterval = currentInterval;
  99. break;
  100. }
  101. currentInterval *= 2;
  102. }
  103. }
  104. else
  105. bestInterval = validIntervals[bestIntervalIdx];
  106. }
  107. else
  108. bestInterval = validIntervals[bestIntervalIdx];
  109. return bestInterval;
  110. }
  111. private void DrawTime(int xPos, float seconds, bool minutes)
  112. {
  113. TimeSpan timeSpan = TimeSpan.FromSeconds(seconds);
  114. string timeString;
  115. if (minutes)
  116. {
  117. timeString = timeSpan.TotalMinutes + ":" + timeSpan.Seconds.ToString("D2");
  118. }
  119. else
  120. {
  121. int hundredths = timeSpan.Milliseconds / 10;
  122. timeString = timeSpan.TotalSeconds + "." + hundredths.ToString("D3");
  123. }
  124. Vector2I textBounds = GUIUtility.CalculateTextBounds(timeString, EditorBuiltin.DefaultFont,
  125. EditorStyles.DefaultFontSize);
  126. Vector2I textPosition = new Vector2I();
  127. textPosition.x = xPos - textBounds.x / 2;
  128. textPosition.y = TEXT_PADDING;
  129. canvas.DrawText(timeString, textPosition, EditorBuiltin.DefaultFont, Color.LightGray,
  130. EditorStyles.DefaultFontSize);
  131. }
  132. private void Rebuild()
  133. {
  134. canvas.Clear();
  135. // TODO - Enforce a minimum limit between ticks, so when width is too small they don't come too close together
  136. // TODO - Don't draw ticks below the frame rate
  137. // TODO - Text from invisible ticks should be displayed (otherwise it will just pop in was the tick is shown)
  138. // TODO - Draw small ticks (don't forget to handle offset properly)
  139. // TODO - Transition between interval sizes more lightly (dynamically change tick height?)
  140. // Constants
  141. const int TEXT_SPACING = 10;
  142. int maxTextWidth = GUIUtility.CalculateTextBounds("99:999", EditorBuiltin.DefaultFont,
  143. EditorStyles.DefaultFontSize).x;
  144. int largeTickHeight = (int)(height * LARGE_TICK_HEIGHT_PCT);
  145. int drawableWidth = Math.Max(0, width - PADDING * 2);
  146. float rangeLength = rangeEnd - rangeStart;
  147. // Draw ticks
  148. float tickInterval = CalcTickInterval(rangeLength);
  149. int numTicks = MathEx.FloorToInt(rangeLength / tickInterval) + 1;
  150. bool displayAsMinutes = TimeSpan.FromSeconds(tickInterval).Minutes > 0;
  151. float offset = rangeStart % tickInterval - rangeStart;
  152. float t = offset;
  153. int lastTextPosition = -1000;
  154. for (int i = 0; i < numTicks; i++)
  155. {
  156. int xPos = (int)((t / rangeLength) * drawableWidth) + PADDING;
  157. // Draw tick
  158. Vector2I start = new Vector2I(xPos, height - largeTickHeight);
  159. Vector2I end = new Vector2I(xPos, height);
  160. canvas.DrawLine(start, end, Color.LightGray);
  161. // Draw text if it fits
  162. int diff = xPos - lastTextPosition;
  163. if (diff >= (maxTextWidth + TEXT_SPACING))
  164. {
  165. DrawTime(xPos, rangeStart + t, displayAsMinutes);
  166. lastTextPosition = xPos;
  167. }
  168. // Move to next tick
  169. t += tickInterval;
  170. t = Math.Min(t, rangeLength);
  171. }
  172. //float offsetLarge = MathEx.CeilToInt(rangeStart / largeTickInterval) * largeTickInterval - rangeStart;
  173. //float offsetSmall = MathEx.CeilToInt(rangeStart / smallTickInterval) * smallTickInterval - rangeStart;
  174. //int largeTickHeight = (int)(height * LARGE_TICK_HEIGHT_PCT);
  175. //int smallTickHeight = (int)(height * SMALL_TICK_HEIGHT_PCT);
  176. //bool drawSmallTicks = true; // TODO
  177. //float t = offsetSmall;
  178. //for (int i = 0; i < numVisibleTicks; i++)
  179. //{
  180. // float distanceToLargeTick = MathEx.CeilToInt(t / largeTickInterval) * largeTickInterval - t;
  181. // if (MathEx.ApproxEquals(distanceToLargeTick, 0.0f))
  182. // {
  183. // int xPos = (int)((t/rangeLength)* drawableWidth) + PADDING;
  184. // Vector2I start = new Vector2I(xPos, height - largeTickHeight);
  185. // Vector2I end = new Vector2I(xPos, height);
  186. // canvas.DrawLine(start, end, Color.LightGray);
  187. // TimeSpan intervalSpan = TimeSpan.FromSeconds(largeTickInterval);
  188. // TimeSpan timeSpan = TimeSpan.FromSeconds(rangeStart + t);
  189. // string timeString;
  190. // if(intervalSpan.Minutes > 0)
  191. // timeString = timeSpan.ToString(@"m\:ss");
  192. // else
  193. // timeString = timeSpan.ToString(@"ss\:fff");
  194. // Vector2I textBounds = GUIUtility.CalculateTextBounds(timeString, EditorBuiltin.DefaultFont,
  195. // EditorStyles.DefaultFontSize);
  196. // Vector2I textPosition = new Vector2I();
  197. // textPosition.x = xPos - textBounds.x/2;
  198. // textPosition.y = TEXT_PADDING;
  199. // canvas.DrawText(timeString, textPosition, EditorBuiltin.DefaultFont, Color.LightGray,
  200. // EditorStyles.DefaultFontSize);
  201. // }
  202. // else
  203. // {
  204. // if (drawSmallTicks)
  205. // {
  206. // int xPos = (int)((t / rangeLength) * drawableWidth) + PADDING;
  207. // Vector2I start = new Vector2I(xPos, height - smallTickHeight);
  208. // Vector2I end = new Vector2I(xPos, height);
  209. // canvas.DrawLine(start, end, Color.LightGray);
  210. // }
  211. // }
  212. // t += smallTickInterval;
  213. //}
  214. }
  215. }
  216. /** @} */
  217. }