//********************************** Banshee Engine (www.banshee3d.com) **************************************************// //**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************// using System; using BansheeEngine; namespace BansheeEditor { /** @addtogroup AnimationEditor * @{ */ /// /// Renders a timeline that may be used as a header for a graph display. User can set the range of the times to display, /// as well as its physical dimensions. /// public class GUIGraphTime { public const int PADDING = 30; private const float TICK_HEIGHT_PCT = 0.4f; private const int TEXT_PADDING = 2; private int tickHeight; private int drawableWidth; private float rangeLength = 60.0f; private GUICanvas canvas; private GUIGraphTicks tickHandler; private int width; private int height; private int fps = 1; private int markedFrameIdx = -1; /// /// Constructs a new timeline and adds it to the specified layout. /// /// Layout to add the timeline GUI to. /// Width of the timeline in pixels. /// Height of the timeline in pixels. public GUIGraphTime(GUILayout layout, int width, int height) { canvas = new GUICanvas(); layout.AddElement(canvas); tickHandler = new GUIGraphTicks(GUITickStepType.Time); SetSize(width, height); } /// /// Uses the assigned FPS, range and physical size to calculate the frame that is under the provided coordinates. /// /// Coordinate relative to the window the GUI element is on. /// Frame that was clicked on, or -1 if the coordinates are outside of valid bounds. public int GetFrame(Vector2I windowCoords) { Rect2I bounds = canvas.Bounds; if (windowCoords.x < (bounds.x + PADDING) || windowCoords.x >= (bounds.x + bounds.width - PADDING) || windowCoords.y < bounds.y || windowCoords.y >= (bounds.y + bounds.height)) { return -1; } Vector2I relativeCoords = windowCoords - new Vector2I(bounds.x + PADDING, bounds.y); float lengthPerPixel = rangeLength / drawableWidth; float time = relativeCoords.x * lengthPerPixel; return (int)(time * fps); } /// /// Sets the frame at which to display the frame marker. /// /// Index of the frame to display the marker on, or -1 to clear the marker. public void SetMarkedFrame(int frameIdx) { markedFrameIdx = frameIdx; Rebuild(); } /// /// Sets the physical size onto which to draw the timeline. /// /// Width in pixels. /// Height in pixels. public void SetSize(int width, int height) { this.width = width; this.height = height; canvas.SetWidth(width); canvas.SetHeight(height); tickHeight = (int)(height * TICK_HEIGHT_PCT); drawableWidth = Math.Max(0, width - PADDING * 2); tickHandler.SetRange(0.0f, GetRange(true), drawableWidth + PADDING); Rebuild(); } /// /// Sets the range of values to display on the timeline. /// /// Amount of time to display, in seconds. public void SetRange(float length) { rangeLength = Math.Max(0.0f, length); tickHandler.SetRange(0.0f, GetRange(true), drawableWidth + PADDING); Rebuild(); } /// /// Number of frames per second, used for frame selection and marking. /// /// Number of prames per second. public void SetFPS(int fps) { this.fps = Math.Max(1, fps); tickHandler.SetRange(0.0f, GetRange(true), drawableWidth + PADDING); Rebuild(); } /// /// Draws text displaying the time at the provided position. /// /// Position to draw the text at. /// Time to display, in seconds. /// If true the time will be displayed in minutes, otherwise in seconds. private void DrawTime(int xPos, float seconds, bool minutes) { TimeSpan timeSpan = TimeSpan.FromSeconds(seconds); string timeString; if (minutes) timeString = timeSpan.TotalMinutes.ToString("#0") + ":" + timeSpan.Seconds.ToString("D2"); else timeString = timeSpan.TotalSeconds.ToString("#0.00"); Vector2I textBounds = GUIUtility.CalculateTextBounds(timeString, EditorBuiltin.DefaultFont, EditorStyles.DefaultFontSize); Vector2I textPosition = new Vector2I(); textPosition.x = xPos - textBounds.x / 2; textPosition.y = TEXT_PADDING; canvas.DrawText(timeString, textPosition, EditorBuiltin.DefaultFont, Color.LightGray, EditorStyles.DefaultFontSize); } /// /// Draws one tick of the timeline, at the specified time. /// /// Time at which to draw the tick. /// Strength of the tick (determines size and color), in range [0, 1]. /// If true the text displaying the time will be drawn above this tick. /// Should the text drawn be displayed as minutes (if true), or seconds (false). /// Ignored if no text is drawn. private void DrawTick(float t, float strength, bool drawText, bool displayAsMinutes) { int xPos = (int)((t / GetRange()) * drawableWidth) + PADDING; // Draw tick Vector2I start = new Vector2I(xPos, height - (int)(tickHeight * strength)); Vector2I end = new Vector2I(xPos, height); Color color = Color.LightGray; color.a *= strength; canvas.DrawLine(start, end, color); // Draw text if it fits if (drawText) DrawTime(xPos, t, displayAsMinutes); } /// /// Draws a vertical frame marker at the specified time. /// /// Time at which to draw the marker. private void DrawFrameMarker(float t) { int xPos = (int)((t / GetRange()) * drawableWidth) + PADDING; Vector2I start = new Vector2I(xPos, 0); Vector2I end = new Vector2I(xPos, height); canvas.DrawLine(start, end, Color.BansheeOrange); } /// /// Returns the range of times displayed by the timeline rounded to the multiple of FPS. /// /// If true, extra range will be included to cover the right-most padding. /// Time range rounded to a multiple of FPS. private float GetRange(bool padding = false) { float spf = 1.0f/fps; float range = rangeLength; if (padding) { float lengthPerPixel = rangeLength/drawableWidth; range += lengthPerPixel * PADDING; } return ((int)range / spf) * spf; } /// /// Rebuilds the internal GUI elements. Should be called whenever timeline properties change. /// private void Rebuild() { canvas.Clear(); float range = GetRange(); int numTickLevels = tickHandler.NumLevels; for (int i = numTickLevels - 1; i >= 0; i--) { bool drawText = i == 0; float[] ticks = tickHandler.GetTicks(i); float strength = tickHandler.GetLevelStrength(i); if (ticks.Length > 0) { float valuePerTick = range/ticks.Length; bool displayAsMinutes = TimeSpan.FromSeconds(valuePerTick).Minutes > 0; for (int j = 0; j < ticks.Length; j++) DrawTick(ticks[j], strength, drawText, displayAsMinutes); } } if (markedFrameIdx != -1) { int numFrames = (int)range * fps; float timePerFrame = range / numFrames; DrawFrameMarker(markedFrameIdx*timePerFrame); } } } /** @} */ }