GUITimeline.cs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  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 OPTIMAL_TICK_WIDTH = 15;
  18. private int maxTextWidth;
  19. private int largeTickHeight;
  20. private int smallTickHeight;
  21. private int drawableWidth;
  22. private float rangeLength = 60.0f;
  23. private int minWidth = 0;
  24. private GUICanvas canvas;
  25. private int width;
  26. private int height;
  27. private int fps = 1;
  28. private int frameMarkerIdx = -1;
  29. public int MinWidth
  30. {
  31. get { return minWidth; }
  32. }
  33. public GUITimeline(GUILayout layout, int width, int height)
  34. {
  35. canvas = new GUICanvas();
  36. layout.AddElement(canvas);
  37. maxTextWidth = GUIUtility.CalculateTextBounds("99:999", EditorBuiltin.DefaultFont,
  38. EditorStyles.DefaultFontSize).x;
  39. SetSize(width, height);
  40. }
  41. public int GetFrame(Vector2I windowCoords)
  42. {
  43. Rect2I bounds = canvas.Bounds;
  44. if (windowCoords.x < (bounds.x + PADDING) || windowCoords.x >= (bounds.x + bounds.width - PADDING) ||
  45. windowCoords.y < bounds.y || windowCoords.y >= (bounds.y + bounds.height))
  46. {
  47. return -1;
  48. }
  49. Vector2I relativeCoords = windowCoords - new Vector2I(bounds.x + PADDING, bounds.y);
  50. float lengthPerPixel = rangeLength / drawableWidth;
  51. float time = relativeCoords.x * lengthPerPixel;
  52. return (int)(time * fps);
  53. }
  54. // Set to -1 to clear it
  55. public void SetFrameMarker(int frameIdx)
  56. {
  57. frameMarkerIdx = frameIdx;
  58. Rebuild();
  59. }
  60. public void SetSize(int width, int height)
  61. {
  62. this.width = width;
  63. this.height = height;
  64. canvas.SetWidth(width);
  65. canvas.SetHeight(height);
  66. largeTickHeight = (int)(height * LARGE_TICK_HEIGHT_PCT);
  67. smallTickHeight = (int)(height * SMALL_TICK_HEIGHT_PCT);
  68. drawableWidth = Math.Max(0, width - PADDING * 2);
  69. Rebuild();
  70. }
  71. public void SetRange(float length)
  72. {
  73. rangeLength = Math.Max(0.0f, length);
  74. Rebuild();
  75. }
  76. public void SetFPS(int fps)
  77. {
  78. this.fps = Math.Max(1, fps);
  79. Rebuild();
  80. }
  81. private void DrawTime(int xPos, float seconds, bool minutes)
  82. {
  83. TimeSpan timeSpan = TimeSpan.FromSeconds(seconds);
  84. string timeString;
  85. if (minutes)
  86. timeString = timeSpan.TotalMinutes.ToString("#0") + ":" + timeSpan.Seconds.ToString("D2");
  87. else
  88. timeString = timeSpan.TotalSeconds.ToString("#0.00");
  89. Vector2I textBounds = GUIUtility.CalculateTextBounds(timeString, EditorBuiltin.DefaultFont,
  90. EditorStyles.DefaultFontSize);
  91. Vector2I textPosition = new Vector2I();
  92. textPosition.x = xPos - textBounds.x / 2;
  93. textPosition.y = TEXT_PADDING;
  94. canvas.DrawText(timeString, textPosition, EditorBuiltin.DefaultFont, Color.LightGray,
  95. EditorStyles.DefaultFontSize);
  96. }
  97. private void DrawLargeTick(float t, bool drawText, bool displayAsMinutes)
  98. {
  99. int xPos = (int)((t / GetRange()) * drawableWidth) + PADDING;
  100. // Draw tick
  101. Vector2I start = new Vector2I(xPos, height - largeTickHeight);
  102. Vector2I end = new Vector2I(xPos, height);
  103. canvas.DrawLine(start, end, Color.LightGray);
  104. // Draw text if it fits
  105. if (drawText)
  106. DrawTime(xPos, t, displayAsMinutes);
  107. }
  108. private void DrawSmallTick(float t)
  109. {
  110. int xPos = (int)((t / GetRange()) * drawableWidth) + PADDING;
  111. // Draw tick
  112. Vector2I start = new Vector2I(xPos, height - smallTickHeight);
  113. Vector2I end = new Vector2I(xPos, height);
  114. canvas.DrawLine(start, end, Color.LightGray);
  115. }
  116. private void DrawFrameMarker(float t)
  117. {
  118. int xPos = (int)((t / GetRange()) * drawableWidth) + PADDING;
  119. Vector2I start = new Vector2I(xPos, 0);
  120. Vector2I end = new Vector2I(xPos, height);
  121. canvas.DrawLine(start, end, Color.Red);
  122. }
  123. // Returns range rounded to the nearest multiple of FPS
  124. private float GetRange()
  125. {
  126. float spf = 1.0f/fps;
  127. return ((int)rangeLength/spf) * spf;
  128. }
  129. private void Rebuild()
  130. {
  131. const int TEXT_SPACING = 10;
  132. canvas.Clear();
  133. // TODO - Transition between interval sizes more lightly (dynamically change tick height?)
  134. // TODO - Calculate min width
  135. // TODO - Time values change as width changes, keep them constant?
  136. float range = GetRange();
  137. int numFrames = (int)range * fps;
  138. float frameWidth = drawableWidth / (float)numFrames;
  139. int tickInterval = (int)Math.Max(1.0f, OPTIMAL_TICK_WIDTH / frameWidth);
  140. int largeTickInterval = tickInterval * 5;
  141. float largeTickWidth = frameWidth * largeTickInterval;
  142. float timePerFrame = range / numFrames;
  143. float timePerTick = timePerFrame*tickInterval;
  144. bool displayAsMinutes = TimeSpan.FromSeconds(timePerTick).Minutes > 0;
  145. int textInterval = MathEx.CeilToInt((maxTextWidth + TEXT_SPACING) / largeTickWidth);
  146. // Draw extra frames to prevent the out-of-frame ticks from popping in and out as range changes
  147. float extraWidth = PADDING + maxTextWidth / 2;
  148. numFrames += (int)(extraWidth / frameWidth);
  149. float t = 0.0f;
  150. for (int i = 0; i < numFrames; i++)
  151. {
  152. if (i%largeTickInterval == 0)
  153. {
  154. int textIdx = i/largeTickInterval;
  155. bool drawText = textIdx % textInterval == 0;
  156. DrawLargeTick(t, drawText, displayAsMinutes);
  157. }
  158. else if (i%tickInterval == 0)
  159. DrawSmallTick(t);
  160. // Move to next tick
  161. t += timePerFrame;
  162. }
  163. if (frameMarkerIdx != -1)
  164. DrawFrameMarker(frameMarkerIdx * timePerFrame);
  165. }
  166. }
  167. /** @} */
  168. }