//********************************** 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);
}
}
}
/** @} */
}