GUITimeline.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  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. float timePerFrame = 1.0f/fps;
  59. int bestIntervalIdx = 0;
  60. float bestDistance = float.MaxValue;
  61. for(int i = 0; i < validIntervals.Length; i++)
  62. {
  63. // Cannot choose an interval that would display ticks for below the frame-rate
  64. if (validIntervals[i] < timePerFrame)
  65. continue;
  66. float tickCount = length/validIntervals[i];
  67. float distance = Math.Abs(tickCount - OPTIMAL_TICK_COUNT);
  68. if (distance < bestDistance)
  69. {
  70. bestDistance = distance;
  71. bestIntervalIdx = i;
  72. }
  73. }
  74. // If the draw area is too narrow, limit amount of ticks displayed so they aren't all clumped together
  75. int numTicks = MathEx.FloorToInt(length / validIntervals[bestIntervalIdx]) + 1;
  76. int drawableWidth = Math.Max(0, width - PADDING * 2);
  77. int spacePerTick = drawableWidth/numTicks;
  78. float bestInterval;
  79. if (spacePerTick < MIN_TICK_DISTANCE)
  80. {
  81. int maxTickCount = drawableWidth/MIN_TICK_DISTANCE;
  82. bool foundInterval = false;
  83. for (int i = bestIntervalIdx; i < validIntervals.Length; i++)
  84. {
  85. float tickCount = length/validIntervals[i];
  86. if (tickCount <= maxTickCount)
  87. {
  88. bestIntervalIdx = i;
  89. foundInterval = true;
  90. break;
  91. }
  92. }
  93. // Haven't found a valid round interval, try more intervals
  94. if (!foundInterval)
  95. {
  96. float currentInterval = validIntervals[validIntervals.Length - 1]*2;
  97. while (true)
  98. {
  99. float tickCount = length/ currentInterval;
  100. if (tickCount <= maxTickCount)
  101. {
  102. bestInterval = currentInterval;
  103. break;
  104. }
  105. currentInterval *= 2;
  106. }
  107. }
  108. else
  109. bestInterval = validIntervals[bestIntervalIdx];
  110. }
  111. else
  112. bestInterval = validIntervals[bestIntervalIdx];
  113. return bestInterval;
  114. }
  115. private void DrawTime(int xPos, float seconds, bool minutes)
  116. {
  117. TimeSpan timeSpan = TimeSpan.FromSeconds(seconds);
  118. string timeString;
  119. if (minutes)
  120. {
  121. timeString = timeSpan.TotalMinutes + ":" + timeSpan.Seconds.ToString("D2");
  122. }
  123. else
  124. {
  125. int hundredths = timeSpan.Milliseconds / 10;
  126. timeString = timeSpan.TotalSeconds + "." + hundredths.ToString("D3");
  127. }
  128. Vector2I textBounds = GUIUtility.CalculateTextBounds(timeString, EditorBuiltin.DefaultFont,
  129. EditorStyles.DefaultFontSize);
  130. Vector2I textPosition = new Vector2I();
  131. textPosition.x = xPos - textBounds.x / 2;
  132. textPosition.y = TEXT_PADDING;
  133. canvas.DrawText(timeString, textPosition, EditorBuiltin.DefaultFont, Color.LightGray,
  134. EditorStyles.DefaultFontSize);
  135. }
  136. private void Rebuild()
  137. {
  138. canvas.Clear();
  139. // TODO - Text from invisible ticks should be displayed (otherwise it will just pop in was the tick is shown)
  140. // TODO - Draw small ticks (don't forget to handle offset properly)
  141. // TODO - Transition between interval sizes more lightly (dynamically change tick height?)
  142. // Constants
  143. const int TEXT_SPACING = 10;
  144. int maxTextWidth = GUIUtility.CalculateTextBounds("99:999", EditorBuiltin.DefaultFont,
  145. EditorStyles.DefaultFontSize).x;
  146. int largeTickHeight = (int)(height * LARGE_TICK_HEIGHT_PCT);
  147. int drawableWidth = Math.Max(0, width - PADDING * 2);
  148. float rangeLength = rangeEnd - rangeStart;
  149. // Draw ticks
  150. float tickInterval = CalcTickInterval(rangeLength);
  151. int numTicks = MathEx.FloorToInt(rangeLength / tickInterval) + 1;
  152. bool displayAsMinutes = TimeSpan.FromSeconds(tickInterval).Minutes > 0;
  153. float offset = rangeStart % tickInterval - rangeStart;
  154. float t = offset;
  155. int lastTextPosition = -1000;
  156. for (int i = 0; i < numTicks; i++)
  157. {
  158. int xPos = (int)((t / rangeLength) * drawableWidth) + PADDING;
  159. // Draw tick
  160. Vector2I start = new Vector2I(xPos, height - largeTickHeight);
  161. Vector2I end = new Vector2I(xPos, height);
  162. canvas.DrawLine(start, end, Color.LightGray);
  163. // Draw text if it fits
  164. int diff = xPos - lastTextPosition;
  165. if (diff >= (maxTextWidth + TEXT_SPACING))
  166. {
  167. DrawTime(xPos, rangeStart + t, displayAsMinutes);
  168. lastTextPosition = xPos;
  169. }
  170. // Move to next tick
  171. t += tickInterval;
  172. t = Math.Min(t, rangeLength);
  173. }
  174. }
  175. }
  176. /** @} */
  177. }