Просмотр исходного кода

Refactored animation timeline GUI elements so they use a common base class

BearishSun 9 лет назад
Родитель
Сommit
4dd02370d8

+ 1 - 0
Source/MBansheeEditor/MBansheeEditor.csproj

@@ -60,6 +60,7 @@
     <Compile Include="Windows\Animation\GUIGraphValues.cs" />
     <Compile Include="Windows\Animation\GUIGraphTicks.cs" />
     <Compile Include="Windows\Animation\GUIGraphTime.cs" />
+    <Compile Include="Windows\Animation\GUITimelineBase.cs" />
     <Compile Include="Windows\BrowseDialog.cs" />
     <Compile Include="Windows\Build\BuildManager.cs" />
     <Compile Include="Windows\Build\BuildWindow.cs" />

+ 13 - 88
Source/MBansheeEditor/Windows/Animation/GUIAnimEvents.cs

@@ -13,19 +13,10 @@ namespace BansheeEditor
     /// Renders a list of animation events in a form of a timeline. User can set the range of the times to display,
     /// as well as its physical dimensions.
     /// </summary>
-    public class GUIAnimEvents
+    public class GUIAnimEvents : GUITimelineBase
     {
         private const int EVENT_HALF_WIDTH = 2;
 
-        private float rangeLength = 60.0f;
-        private float rangeOffset = 0.0f;
-        private int fps = 1;
-
-        private GUICanvas canvas;
-        private int width;
-        private int height;
-        private int drawableWidth;
-
         private AnimationEvent[] events = new AnimationEvent[0];
         private bool[] selectedEvents = new bool[0];
 
@@ -36,12 +27,8 @@ namespace BansheeEditor
         /// <param name="width">Width of the GUI element in pixels.</param>
         /// <param name="height">Height of the GUI element in pixels.</param>
         public GUIAnimEvents(GUILayout layout, int width, int height)
-        {
-            canvas = new GUICanvas();
-            layout.AddElement(canvas);
-
-            SetSize(width, height);
-        }
+            :base(layout, width, height)
+        { }
 
         /// <summary>
         /// Attempts to find an event under the provided coordinates.
@@ -54,19 +41,19 @@ namespace BansheeEditor
         {
             Rect2I bounds = canvas.Bounds;
 
-            if (pixelCoords.x < (bounds.x + GUIGraphTime.PADDING) || pixelCoords.x >= (bounds.x + bounds.width - GUIGraphTime.PADDING) ||
+            if (pixelCoords.x < (bounds.x + PADDING) || pixelCoords.x >= (bounds.x + bounds.width - PADDING) ||
                 pixelCoords.y < bounds.y || pixelCoords.y >= (bounds.y + bounds.height))
             {
                 eventIdx = -1;
                 return false;
             }
 
-            Vector2I relativeCoords = pixelCoords - new Vector2I(bounds.x + GUIGraphTime.PADDING, bounds.y);
+            Vector2I relativeCoords = pixelCoords - new Vector2I(bounds.x + PADDING, bounds.y);
             for (int i = 0; i < events.Length; i++)
             {
                 AnimationEvent evnt = events[i];
 
-                int xPos = (int)(((evnt.Time - rangeOffset) / GetRange()) * drawableWidth) + GUIGraphTime.PADDING;
+                int xPos = (int)(((evnt.Time - rangeOffset) / GetRange()) * drawableWidth) + PADDING;
 
                 if (relativeCoords.x >= (xPos - EVENT_HALF_WIDTH) || relativeCoords.y >= (xPos + EVENT_HALF_WIDTH))
                 {
@@ -78,50 +65,7 @@ namespace BansheeEditor
             eventIdx = -1;
             return false;
         }
-
-        /// <summary>
-        /// Sets the physical size of the GUI element.
-        /// </summary>
-        /// <param name="width">Width in pixels.</param>
-        /// <param name="height">Height in pixels.</param>
-        public void SetSize(int width, int height)
-        {
-            this.width = width;
-            this.height = height;
-
-            canvas.SetWidth(width);
-            canvas.SetHeight(height);
-
-            drawableWidth = Math.Max(0, width - GUIGraphTime.PADDING * 2);
-        }
-
-        /// <summary>
-        /// Sets the range of values over which to display the events.
-        /// </summary>
-        /// <param name="length">Amount of time to display, in seconds.</param>
-        public void SetRange(float length)
-        {
-            rangeLength = Math.Max(0.0f, length);
-        }
-
-        /// <summary>
-        /// Returns the offset at which the displayed event values start at.
-        /// </summary>
-        /// <param name="offset">Value to start displaying the events at, in seconds.</param>
-        public void SetOffset(float offset)
-        {
-            rangeOffset = offset;
-        }
-
-        /// <summary>
-        /// Number of frames per second, used for rounding up the displayed range.
-        /// </summary>
-        /// <param name="fps">Number of prames per second.</param>
-        public void SetFPS(int fps)
-        {
-            this.fps = Math.Max(1, fps);
-        }
-
+        
         /// <summary>
         /// Changes the set of displayed animation events.
         /// </summary>
@@ -152,7 +96,7 @@ namespace BansheeEditor
         /// <param name="selected">If true the marker will be drawn as selected.</param>
         private void DrawEventMarker(float t, bool selected)
         {
-            int xPos = (int)(((t - rangeOffset) / GetRange()) * drawableWidth) + GUIGraphTime.PADDING;
+            int xPos = (int)(((t - rangeOffset) / GetRange()) * drawableWidth) + PADDING;
 
             Vector2I a = new Vector2I(xPos - EVENT_HALF_WIDTH, height - 1);
             Vector2I b = new Vector2I(xPos, 0);
@@ -167,30 +111,9 @@ namespace BansheeEditor
             canvas.DrawTriangleStrip(trianglePoints, Color.White, 101);
             canvas.DrawPolyLine(linePoints, outerColor, 100);
         }
-
-        /// <summary>
-        /// Returns the range of times displayed by the timeline rounded to the multiple of FPS.
-        /// </summary>
-        /// <param name="padding">If true, extra range will be included to cover the right-most padding.</param>
-        /// <returns>Time range rounded to a multiple of FPS.</returns>
-        private float GetRange(bool padding = false)
-        {
-            float spf = 1.0f / fps;
-
-            float range = rangeLength;
-            if (padding)
-            {
-                float lengthPerPixel = rangeLength / drawableWidth;
-                range += lengthPerPixel * GUIGraphTime.PADDING;
-            }
-
-            return ((int)range / spf) * spf;
-        }
-
-        /// <summary>
-        /// Rebuilds the internal GUI elements. Should be called whenever timeline properties change.
-        /// </summary>
-        public void Rebuild()
+        
+        /// <inheritdoc/>
+        public override void Rebuild()
         {
             canvas.Clear();
 
@@ -210,6 +133,8 @@ namespace BansheeEditor
 
                 DrawEventMarker(t, selectedEvents[i]);
             }
+
+            DrawFrameMarker();
         }
     }
 

+ 18 - 83
Source/MBansheeEditor/Windows/Animation/GUICurveDrawing.cs

@@ -14,7 +14,7 @@ namespace BansheeEditor
     /// Draws one or multiple curves over the specified physical area. User can specify horizontal and vertical range to
     /// display, as well as physical size of the GUI area.
     /// </summary>
-    internal class GUICurveDrawing
+    internal class GUICurveDrawing : GUITimelineBase
     {
         private const int LINE_SPLIT_WIDTH = 2;
         private const int TANGENT_LINE_DISTANCE = 30;
@@ -24,16 +24,9 @@ namespace BansheeEditor
         private EdAnimationCurve[] curves;
         private bool[][] selectedKeyframes;
 
-        private int width;
-        private int height;
-        private float xRange = 60.0f;
         private float yRange = 20.0f;
-        private Vector2 offset;
-        private int fps = 1;
-        private int markedFrameIdx = 0;
+        private float yOffset;
 
-        private int drawableWidth;
-        private GUICanvas canvas;
         private GUIGraphTicks tickHandler;
 
         /// <summary>
@@ -44,18 +37,12 @@ namespace BansheeEditor
         /// <param name="height">Height of the element in pixels.</param>
         /// <param name="curves">Initial set of curves to display. </param>
         public GUICurveDrawing(GUILayout layout, int width, int height, EdAnimationCurve[] curves)
+            :base(layout, width, height)
         {
-            canvas = new GUICanvas();
-            layout.AddElement(canvas);
-
             tickHandler = new GUIGraphTicks(GUITickStepType.Time);
-
             this.curves = curves;
             
-            SetSize(width, height);
             ClearSelectedKeyframes(); // Makes sure the array is initialized
-
-            Rebuild();
         }
 
         /// <summary>
@@ -66,23 +53,7 @@ namespace BansheeEditor
         {
             this.curves = curves;
         }
-
-        /// <summary>
-        /// Change the physical size of the GUI element.
-        /// </summary>
-        /// <param name="width">Width of the element in pixels.</param>
-        /// <param name="height">Height of the element in pixels.</param>
-        public void SetSize(int width, int height)
-        {
-            this.width = width;
-            this.height = height;
-
-            canvas.SetWidth(width);
-            canvas.SetHeight(height);
-
-            drawableWidth = Math.Max(0, width - GUIGraphTime.PADDING * 2);
-        }
-
+        
         /// <summary>
         /// Changes the visible range that the GUI element displays.
         /// </summary>
@@ -91,7 +62,7 @@ namespace BansheeEditor
         ///                      [-yRange * 0.5, yRange * 0.5]</param>
         public void SetRange(float xRange, float yRange)
         {
-            this.xRange = xRange;
+            SetRange(xRange);
             this.yRange = yRange;
         }
 
@@ -101,27 +72,10 @@ namespace BansheeEditor
         /// <param name="offset">Value to start the timeline values at.</param>
         public void SetOffset(Vector2 offset)
         {
-            this.offset = offset;
+            SetOffset(offset.x);
+            yOffset = offset.y;
         }
-
-        /// <summary>
-        /// Number of frames per second, used for frame selection and marking.
-        /// </summary>
-        /// <param name="fps">Number of prames per second.</param>
-        public void SetFPS(int fps)
-        {
-            this.fps = Math.Max(1, fps);
-        }
-
-        /// <summary>
-        /// Sets the frame at which to display the frame marker.
-        /// </summary>
-        /// <param name="frameIdx">Index of the frame to display the marker on, or -1 to clear the marker.</param>
-        public void SetMarkedFrame(int frameIdx)
-        {
-            markedFrameIdx = frameIdx;
-        }
-
+        
         /// <summary>
         /// Marks the specified key-frame as selected, changing the way it is displayed.
         /// </summary>
@@ -281,7 +235,7 @@ namespace BansheeEditor
             Rect2I bounds = canvas.Bounds;
 
             // Check if outside of curve drawing bounds
-            if (pixelCoords.x < (bounds.x + GUIGraphTime.PADDING) || pixelCoords.x >= (bounds.x + bounds.width - GUIGraphTime.PADDING) ||
+            if (pixelCoords.x < (bounds.x + PADDING) || pixelCoords.x >= (bounds.x + bounds.width - PADDING) ||
                 pixelCoords.y < bounds.y || pixelCoords.y >= (bounds.y + bounds.height))
             {
                 curveCoords = new Vector2();
@@ -289,15 +243,15 @@ namespace BansheeEditor
             }
 
             // Find time and value of the place under the coordinates
-            Vector2I relativeCoords = pixelCoords - new Vector2I(bounds.x + GUIGraphTime.PADDING, bounds.y);
+            Vector2I relativeCoords = pixelCoords - new Vector2I(bounds.x + PADDING, bounds.y);
 
             float lengthPerPixel = GetRange() / drawableWidth;
             float heightPerPixel = yRange / height;
 
-            float yOffset = yRange / 2.0f;
+            float centerOffset = yRange / 2.0f;
 
-            float t = offset.x + relativeCoords.x * lengthPerPixel;
-            float value = offset.y + yOffset - relativeCoords.y * heightPerPixel;
+            float t = rangeOffset + relativeCoords.x * lengthPerPixel;
+            float value = yOffset + centerOffset - relativeCoords.y * heightPerPixel;
 
             curveCoords = new Vector2();
             curveCoords.x = t;
@@ -315,10 +269,10 @@ namespace BansheeEditor
         {
             int heightOffset = height / 2; // So that y = 0 is at center of canvas
 
-            Vector2 relativeCurveCoords = curveCoords - offset;
+            Vector2 relativeCurveCoords = curveCoords - new Vector2(rangeOffset, yOffset);
 
             Vector2I pixelCoords = new Vector2I();
-            pixelCoords.x = (int)((relativeCurveCoords.x / GetRange()) * drawableWidth) + GUIGraphTime.PADDING;
+            pixelCoords.x = (int)((relativeCurveCoords.x / GetRange()) * drawableWidth) + PADDING;
             pixelCoords.y = heightOffset - (int)((relativeCurveCoords.y / yRange) * height);
 
             return pixelCoords;
@@ -332,7 +286,7 @@ namespace BansheeEditor
         /// <param name="onTop">Determines should the marker be drawn above or below the curve.</param>
         private void DrawFrameMarker(float t, Color color, bool onTop)
         {
-            int xPos = (int)(((t - offset.x) / GetRange()) * drawableWidth) + GUIGraphTime.PADDING;
+            int xPos = (int)(((t - rangeOffset) / GetRange()) * drawableWidth) + PADDING;
 
             Vector2I start = new Vector2I(xPos, 0);
             Vector2I end = new Vector2I(xPos, height);
@@ -472,36 +426,17 @@ namespace BansheeEditor
                 return mode.HasFlag(TangentMode.OutFree);
         }
 
-        /// <summary>
-        /// Returns the range of times displayed by the timeline rounded to the multiple of FPS.
-        /// </summary>
-        /// <param name="padding">If true, extra range will be included to cover the right-most padding.</param>
-        /// <returns>Time range rounded to a multiple of FPS.</returns>
-        private float GetRange(bool padding = false)
-        {
-            float spf = 1.0f / fps;
-
-            float range = xRange;
-            if (padding)
-            {
-                float lengthPerPixel = xRange / drawableWidth;
-                range += lengthPerPixel * GUIGraphTime.PADDING;
-            }
-
-            return ((int)range / spf) * spf;
-        }
-
         /// <summary>
         /// Rebuilds the internal GUI elements. Should be called whenever timeline properties change.
         /// </summary>
-        public void Rebuild()
+        public override void Rebuild()
         {
             canvas.Clear();
 
             if (curves == null)
                 return;
 
-            tickHandler.SetRange(offset.x, offset.x + GetRange(true), drawableWidth + GUIGraphTime.PADDING);
+            tickHandler.SetRange(rangeOffset, rangeOffset + GetRange(true), drawableWidth + GUIGraphTime.PADDING);
 
             // Draw vertical frame markers
             int numTickLevels = tickHandler.NumLevels;

+ 1 - 0
Source/MBansheeEditor/Windows/Animation/GUICurveEditor.cs

@@ -571,6 +571,7 @@ namespace BansheeEditor
             markedFrameIdx = frameIdx;
 
             guiTimeline.SetMarkedFrame(frameIdx);
+            guiEvents.SetMarkedFrame(frameIdx);
             guiCurveDrawing.SetMarkedFrame(frameIdx);
 
             Redraw();

+ 7 - 146
Source/MBansheeEditor/Windows/Animation/GUIGraphTime.cs

@@ -13,24 +13,12 @@ namespace BansheeEditor
     /// 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.
     /// </summary>
-    public class GUIGraphTime
+    public class GUIGraphTime : GUITimelineBase
     {
-        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 float rangeOffset = 0.0f;
-
-        private GUICanvas canvas;
         private GUIGraphTicks tickHandler;
-        private int width;
-        private int height;
-        private int fps = 1;
-        private int markedFrameIdx = -1;
 
         /// <summary>
         /// Constructs a new timeline and adds it to the specified layout.
@@ -39,97 +27,9 @@ namespace BansheeEditor
         /// <param name="width">Width of the timeline in pixels.</param>
         /// <param name="height">Height of the timeline in pixels.</param>
         public GUIGraphTime(GUILayout layout, int width, int height)
+            :base(layout, width, height)
         {
-            canvas = new GUICanvas();
-            layout.AddElement(canvas);
-
             tickHandler = new GUIGraphTicks(GUITickStepType.Time);
-
-            SetSize(width, height);
-        }
-
-        /// <summary>
-        /// Uses the assigned FPS, range and physical size to calculate the frame that is under the provided coordinates.
-        /// </summary>
-        /// <param name="pixelCoords">Coordinate relative to the layout the GUI element is on.</param>
-        /// <returns>Frame that was clicked on, or -1 if the coordinates are outside of valid bounds. </returns>
-        public int GetFrame(Vector2I pixelCoords)
-        {
-            Rect2I bounds = canvas.Bounds;
-
-            if (pixelCoords.x < (bounds.x + PADDING) || pixelCoords.x >= (bounds.x + bounds.width - PADDING) ||
-                pixelCoords.y < bounds.y || pixelCoords.y >= (bounds.y + bounds.height))
-            {
-                return -1;
-            }
-
-            Vector2I relativeCoords = pixelCoords - new Vector2I(bounds.x + PADDING, bounds.y);
-
-            float lengthPerPixel = GetRange() / drawableWidth;
-            float time = rangeOffset + relativeCoords.x * lengthPerPixel;
-
-            return MathEx.RoundToInt(time * fps);
-        }
-
-        /// <summary>
-        /// Sets the frame at which to display the frame marker.
-        /// </summary>
-        /// <param name="frameIdx">Index of the frame to display the marker on, or -1 to clear the marker.</param>
-        public void SetMarkedFrame(int frameIdx)
-        {
-            markedFrameIdx = frameIdx;
-        }
-
-        /// <summary>
-        /// Sets the physical size onto which to draw the timeline.
-        /// </summary>
-        /// <param name="width">Width in pixels.</param>
-        /// <param name="height">Height in pixels.</param>
-        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(rangeOffset, rangeOffset + GetRange(true), drawableWidth + PADDING);
-        }
-
-        /// <summary>
-        /// Sets the range of values to display on the timeline.
-        /// </summary>
-        /// <param name="length">Amount of time to display, in seconds.</param>
-        public void SetRange(float length)
-        {
-            rangeLength = Math.Max(0.0f, length);
-
-            tickHandler.SetRange(rangeOffset, rangeOffset + GetRange(true), drawableWidth + PADDING);
-        }
-
-        /// <summary>
-        /// Returns the offset at which the displayed timeline values start at.
-        /// </summary>
-        /// <param name="offset">Value to start the timeline values at, in seconds.</param>
-        public void SetOffset(float offset)
-        {
-            rangeOffset = offset;
-
-            tickHandler.SetRange(rangeOffset, rangeOffset + GetRange(true), drawableWidth + PADDING);
-        }
-
-        /// <summary>
-        /// Number of frames per second, used for frame selection and marking.
-        /// </summary>
-        /// <param name="fps">Number of prames per second.</param>
-        public void SetFPS(int fps)
-        {
-            this.fps = Math.Max(1, fps);
-
-            tickHandler.SetRange(rangeOffset, rangeOffset + GetRange(true), drawableWidth + PADDING);
         }
         
         /// <summary>
@@ -172,6 +72,7 @@ namespace BansheeEditor
             int xPos = (int)(((t - rangeOffset) / GetRange()) * drawableWidth) + PADDING;
 
             // Draw tick
+            float tickHeight = (int)(height * TICK_HEIGHT_PCT);
             Vector2I start = new Vector2I(xPos, height - (int)(tickHeight * strength));
             Vector2I end = new Vector2I(xPos, height);
 
@@ -185,45 +86,11 @@ namespace BansheeEditor
                 DrawTime(xPos, t, displayAsMinutes);
         }
 
-        /// <summary>
-        /// Draws a vertical frame marker at the specified time.
-        /// </summary>
-        /// <param name="t">Time at which to draw the marker.</param>
-        private void DrawFrameMarker(float t)
-        {
-            int xPos = (int)(((t - rangeOffset) / GetRange()) * drawableWidth) + PADDING;
-
-            Vector2I start = new Vector2I(xPos, 0);
-            Vector2I end = new Vector2I(xPos, height);
-
-            canvas.DrawLine(start, end, Color.BansheeOrange);
-        }
-
-        /// <summary>
-        /// Returns the range of times displayed by the timeline rounded to the multiple of FPS.
-        /// </summary>
-        /// <param name="padding">If true, extra range will be included to cover the right-most padding.</param>
-        /// <returns>Time range rounded to a multiple of FPS.</returns>
-        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;
-        }
-
-        /// <summary>
-        /// Rebuilds the internal GUI elements. Should be called whenever timeline properties change.
-        /// </summary>
-        public void Rebuild()
+        /// <inheritdoc/>
+        public override void Rebuild()
         {
             canvas.Clear();
+            tickHandler.SetRange(rangeOffset, rangeOffset + GetRange(true), drawableWidth + PADDING);
 
             float range = GetRange();
 
@@ -244,13 +111,7 @@ namespace BansheeEditor
                 }
             }
 
-            if (markedFrameIdx != -1)
-            {
-                int numFrames = (int)range * fps;
-                float timePerFrame = range / numFrames;
-
-                DrawFrameMarker(markedFrameIdx*timePerFrame);
-            }
+            DrawFrameMarker();
         }
     }
 

+ 176 - 0
Source/MBansheeEditor/Windows/Animation/GUITimelineBase.cs

@@ -0,0 +1,176 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+using System;
+using BansheeEngine;
+
+namespace BansheeEditor
+{
+    /** @addtogroup AnimationEditor
+     *  @{
+     */
+
+    /// <summary>
+    /// Base class that can be implemented by objects needing to elements along draw a horizontal timeline.
+    /// </summary>
+    public class GUITimelineBase
+    {
+        public const int PADDING = 30;
+
+        protected int drawableWidth;
+        protected float rangeLength = 60.0f;
+        protected float rangeOffset = 0.0f;
+
+        protected GUICanvas canvas;
+        protected int width;
+        protected int height;
+        protected int fps = 1;
+        protected int markedFrameIdx = -1;
+
+        /// <summary>
+        /// Constructs a new timeline and adds it to the specified layout.
+        /// </summary>
+        /// <param name="layout">Layout to add the timeline GUI to.</param>
+        /// <param name="width">Width of the timeline in pixels.</param>
+        /// <param name="height">Height of the timeline in pixels.</param>
+        public GUITimelineBase(GUILayout layout, int width, int height)
+        {
+            canvas = new GUICanvas();
+            layout.AddElement(canvas);
+
+            SetSize(width, height);
+        }
+
+        /// <summary>
+        /// Uses the assigned FPS, range and physical size to calculate the frame that is under the provided coordinates.
+        /// </summary>
+        /// <param name="pixelCoords">Coordinate relative to the layout the GUI element is on.</param>
+        /// <returns>Frame that was clicked on, or -1 if the coordinates are outside of valid bounds. </returns>
+        public int GetFrame(Vector2I pixelCoords)
+        {
+            Rect2I bounds = canvas.Bounds;
+
+            if (pixelCoords.x < (bounds.x + PADDING) || pixelCoords.x >= (bounds.x + bounds.width - PADDING) ||
+                pixelCoords.y < bounds.y || pixelCoords.y >= (bounds.y + bounds.height))
+            {
+                return -1;
+            }
+
+            Vector2I relativeCoords = pixelCoords - new Vector2I(bounds.x + PADDING, bounds.y);
+
+            float lengthPerPixel = GetRange() / drawableWidth;
+            float time = rangeOffset + relativeCoords.x * lengthPerPixel;
+
+            return MathEx.RoundToInt(time * fps);
+        }
+
+        /// <summary>
+        /// Sets the frame at which to display the frame marker.
+        /// </summary>
+        /// <param name="frameIdx">Index of the frame to display the marker on, or -1 to clear the marker.</param>
+        public void SetMarkedFrame(int frameIdx)
+        {
+            markedFrameIdx = frameIdx;
+        }
+
+        /// <summary>
+        /// Sets the physical size onto which to draw the timeline.
+        /// </summary>
+        /// <param name="width">Width in pixels.</param>
+        /// <param name="height">Height in pixels.</param>
+        public void SetSize(int width, int height)
+        {
+            this.width = width;
+            this.height = height;
+
+            canvas.SetWidth(width);
+            canvas.SetHeight(height);
+
+            drawableWidth = Math.Max(0, width - PADDING * 2);
+        }
+
+        /// <summary>
+        /// Sets the range of values to display on the timeline.
+        /// </summary>
+        /// <param name="length">Amount of time to display, in seconds.</param>
+        public void SetRange(float length)
+        {
+            rangeLength = Math.Max(0.0f, length);
+        }
+
+        /// <summary>
+        /// Returns the offset at which the displayed timeline values start at.
+        /// </summary>
+        /// <param name="offset">Value to start the timeline values at, in seconds.</param>
+        public void SetOffset(float offset)
+        {
+            rangeOffset = offset;
+        }
+
+        /// <summary>
+        /// Number of frames per second, used for frame selection and marking.
+        /// </summary>
+        /// <param name="fps">Number of prames per second.</param>
+        public void SetFPS(int fps)
+        {
+            this.fps = Math.Max(1, fps);
+        }
+
+        /// <summary>
+        /// Draws a vertical frame marker at the specified time.
+        /// </summary>
+        /// <param name="t">Time at which to draw the marker.</param>
+        private void DrawFrameMarker(float t)
+        {
+            int xPos = (int)(((t - rangeOffset) / GetRange()) * drawableWidth) + PADDING;
+
+            Vector2I start = new Vector2I(xPos, 0);
+            Vector2I end = new Vector2I(xPos, height);
+
+            canvas.DrawLine(start, end, Color.BansheeOrange);
+        }
+
+        /// <summary>
+        /// Returns the range of times displayed by the timeline rounded to the multiple of FPS.
+        /// </summary>
+        /// <param name="padding">If true, extra range will be included to cover the right-most padding.</param>
+        /// <returns>Time range rounded to a multiple of FPS.</returns>
+        protected 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;
+        }
+
+        /// <summary>
+        /// Draws the frame marker at the currently selected frame.
+        /// </summary>
+        protected void DrawFrameMarker()
+        {
+            if (markedFrameIdx != -1)
+            {
+                float range = GetRange();
+                int numFrames = (int)range * fps;
+                float timePerFrame = range / numFrames;
+
+                DrawFrameMarker(markedFrameIdx * timePerFrame);
+            }
+        }
+
+        /// <summary>
+        /// Rebuilds the internal GUI elements. Should be called whenever timeline properties change.
+        /// </summary>
+        public virtual void Rebuild()
+        {
+            canvas.Clear();
+        }
+    }
+
+    /** @} */
+}