GUITimeline.cs 5.8 KB

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