Przeglądaj źródła

Refactor: Moving GUI curve drawing to native code

BearishSun 7 lat temu
rodzic
commit
e400a8bbe2
40 zmienionych plików z 3116 dodań i 443 usunięć
  1. 2 0
      Source/EditorCore/CMakeSources.cmake
  2. 961 0
      Source/EditorCore/GUI/BsGUICurves.cpp
  3. 612 0
      Source/EditorCore/GUI/BsGUICurves.h
  4. 1 1
      Source/EditorCore/GUI/BsGUIListBoxField.h
  5. 8 8
      Source/Scripting/MBansheeEditor/GUI/GUICurveField.cs
  6. 27 0
      Source/Scripting/MBansheeEditor/Generated/CurveDrawInfo.generated.cs
  7. 174 0
      Source/Scripting/MBansheeEditor/Generated/GUICurves.generated.cs
  8. 94 0
      Source/Scripting/MBansheeEditor/Generated/GUIGraphTicks.generated.cs
  9. 109 0
      Source/Scripting/MBansheeEditor/Generated/GUITimeline.generated.cs
  10. 0 1
      Source/Scripting/MBansheeEditor/MBansheeEditor.csproj
  11. 0 88
      Source/Scripting/MBansheeEditor/Utility/EdAnimationCurve.cs
  12. 7 7
      Source/Scripting/MBansheeEditor/Windows/Animation/EditorAnimInfo.cs
  13. 98 102
      Source/Scripting/MBansheeEditor/Windows/Animation/GUICurveDrawing.cs
  14. 68 15
      Source/Scripting/MBansheeEditor/Windows/Animation/GUICurveEditor.cs
  15. 0 199
      Source/Scripting/MBansheeEditor/Windows/Animation/GUIGraphTicks.cs
  16. 3 3
      Source/Scripting/MBansheeEditor/Windows/Animation/GUIGraphTime.cs
  17. 4 4
      Source/Scripting/MBansheeEditor/Windows/Animation/GUIGraphValues.cs
  18. 11 11
      Source/Scripting/MBansheeEditor/Windows/AnimationWindow.cs
  19. 4 4
      Source/Scripting/MBansheeEditor/Windows/CurveEditorWindow.cs
  20. 13 0
      Source/Scripting/MBansheeEngine/Generated/AnimationUtility.generated.cs
  21. 23 0
      Source/Scripting/MBansheeEngine/Generated/CurveDrawOption.generated.cs
  22. 21 0
      Source/Scripting/MBansheeEngine/Generated/GUITickStepType.generated.cs
  23. 35 0
      Source/Scripting/MBansheeEngine/Generated/KeyframeRef.generated.cs
  24. 44 0
      Source/Scripting/MBansheeEngine/Generated/TangentModeBits.generated.cs
  25. 38 0
      Source/Scripting/MBansheeEngine/Generated/TangentRef.generated.cs
  26. 19 0
      Source/Scripting/MBansheeEngine/Generated/TangentType.generated.cs
  27. 57 0
      Source/Scripting/SBansheeEditor/Generated/BsScriptCurveDrawInfo.generated.cpp
  28. 31 0
      Source/Scripting/SBansheeEditor/Generated/BsScriptCurveDrawInfo.generated.h
  29. 175 0
      Source/Scripting/SBansheeEditor/Generated/BsScriptGUICurves.generated.cpp
  30. 43 0
      Source/Scripting/SBansheeEditor/Generated/BsScriptGUICurves.generated.h
  31. 90 0
      Source/Scripting/SBansheeEditor/Generated/BsScriptGUIGraphTicks.generated.cpp
  32. 31 0
      Source/Scripting/SBansheeEditor/Generated/BsScriptGUIGraphTicks.generated.h
  33. 139 0
      Source/Scripting/SBansheeEditor/Generated/BsScriptGUITimeline.generated.cpp
  34. 31 0
      Source/Scripting/SBansheeEditor/Generated/BsScriptGUITimeline.generated.h
  35. 20 0
      Source/Scripting/SBansheeEngine/Generated/BsScriptAnimationUtility.generated.cpp
  36. 1 0
      Source/Scripting/SBansheeEngine/Generated/BsScriptAnimationUtility.generated.h
  37. 25 0
      Source/Scripting/SBansheeEngine/Generated/BsScriptKeyframeRef.generated.cpp
  38. 21 0
      Source/Scripting/SBansheeEngine/Generated/BsScriptKeyframeRef.generated.h
  39. 45 0
      Source/Scripting/SBansheeEngine/Generated/BsScriptTangentRef.generated.cpp
  40. 31 0
      Source/Scripting/SBansheeEngine/Generated/BsScriptTangentRef.generated.h

+ 2 - 0
Source/EditorCore/CMakeSources.cmake

@@ -62,6 +62,7 @@ set(BS_BANSHEEEDITOR_SRC_GUI
 	"GUI/BsGUIVector4Field.cpp"
 	"GUI/BsGUIWindowFrame.cpp"
 	"GUI/BsGUIWindowFrameWidget.cpp"
+	"GUI/BsGUICurves.cpp"
 )
 
 set(BS_BANSHEEEDITOR_INC_LIBRARY
@@ -98,6 +99,7 @@ set(BS_BANSHEEEDITOR_INC_GUI
 	"GUI/BsGUIVector4Field.h"
 	"GUI/BsGUIWindowFrame.h"
 	"GUI/BsGUIWindowFrameWidget.h"
+	"GUI/BsGUICurves.h"
 )
 
 set(BS_BANSHEEEDITOR_INC_UNDOREDO

+ 961 - 0
Source/EditorCore/GUI/BsGUICurves.cpp

@@ -0,0 +1,961 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include <utility>
+#include "GUI/BsGUICurves.h"
+#include "GUI/BsGUIDimensions.h"
+#include "GUI/BsGUIMouseEvent.h"
+#include "GUI/BsGUIHelper.h"
+#include "GUI/BsGUICanvas.h"
+#include "Math/BsLine2.h"
+
+namespace bs
+{
+	static constexpr INT32 TIMELINE_PADDING_PX = 30;
+
+	GUITimeline::GUITimeline(const String& styleName, const GUIDimensions& dimensions)
+		:GUIElementContainer(dimensions, styleName)
+	{
+		mCanvas = GUICanvas::create();
+		_registerChildElement(mCanvas);
+	}
+
+	const String& GUITimeline::getGUITypeName()
+	{
+		static String name = "Timeline";
+		return name;
+	}
+
+	void GUITimeline::setRange(float range)
+	{
+		mRange = std::max(0.0f, range);
+		_markContentAsDirty();
+	}
+
+	float GUITimeline::getRange() const
+	{
+		const float spf = 1.0f / (float)mFPS;
+		return std::max(1.0f, mRange / spf) * spf;
+	}
+
+	void GUITimeline::setOffset(float offset)
+	{
+		mOffset = offset;
+		_markContentAsDirty();
+	}
+
+	void GUITimeline::setFPS(UINT32 FPS)
+	{
+		mFPS = std::max(1U, FPS);
+		_markContentAsDirty();
+	}
+
+	void GUITimeline::setMarkedFrame(UINT32 index)
+	{
+		mMarkedFrame = index;
+		_markContentAsDirty();
+	}
+
+	UINT32 GUITimeline::getFrame(const Vector2I& pixelCoords) const
+	{
+		const Rect2I& bounds = mLayoutData.area;
+
+		if (pixelCoords.x < (TIMELINE_PADDING_PX) || 
+			pixelCoords.x >= ((INT32)bounds.width - TIMELINE_PADDING_PX) ||
+			pixelCoords.y < 0 || 
+			pixelCoords.y >= (INT32)bounds.height)
+		{
+			return -1;
+		}
+
+		const Vector2I relativeCoords = pixelCoords - Vector2I(bounds.x + TIMELINE_PADDING_PX, bounds.y);
+
+		const float lengthPerPixel = getRange() / getDrawableWidth();
+		const float time = mOffset + relativeCoords.x * lengthPerPixel;
+
+		return Math::roundToPosInt(time * mFPS);
+	}
+
+	float GUITimeline::getTime(INT32 pixel) const
+	{
+		const Rect2I& bounds = mLayoutData.area;
+		const INT32 relativeCoords = pixel - (bounds.x + TIMELINE_PADDING_PX);
+
+		const float lengthPerPixel = getRange() / getDrawableWidth();
+		return mOffset + relativeCoords * lengthPerPixel;
+	}
+
+	INT32 GUITimeline::getOffset(float time) const
+	{
+		return (INT32)(((time - mOffset) / getRange()) * getDrawableWidth()) + TIMELINE_PADDING_PX;
+	}
+
+	float GUITimeline::getTimeForFrame(INT32 index) const
+	{
+		return index / (float)mFPS;
+	}
+
+	UINT32 GUITimeline::getDrawableWidth() const
+	{
+		return std::max(0, (INT32)mLayoutData.area.width - TIMELINE_PADDING_PX * 2);
+	}
+
+	float GUITimeline::getRangeWithPadding() const
+	{
+		const float spf = 1.0f / (float)mFPS;
+
+		const float lengthPerPixel = mRange / getDrawableWidth();
+		const float range = mRange + lengthPerPixel * TIMELINE_PADDING_PX;
+
+		return std::max(1.0f, range / spf) * spf;
+	}
+
+	void GUITimeline::drawFrameMarker(float t)
+	{
+		const INT32 xPos = (INT32)(((t - mOffset) / getRange()) * getDrawableWidth()) + TIMELINE_PADDING_PX;
+
+		const Vector2I start(xPos, 0);
+		const Vector2I end(xPos, mLayoutData.area.height);
+
+		mCanvas->drawLine(start, end, Color::BansheeOrange);
+	}
+
+	void GUITimeline::drawFrameMarker()
+	{
+		if(mMarkedFrame != (UINT32)-1)
+			drawFrameMarker(mMarkedFrame / (float)mFPS);
+	}
+
+	void GUITimeline::_updateLayoutInternal(const GUILayoutData& data)
+	{
+		mCanvas->_setLayoutData(data);
+		mCanvas->_updateLayoutInternal(data);
+	}
+
+	Vector2I GUITimeline::_getOptimalSize() const
+	{
+		return mCanvas->_getOptimalSize();
+	}
+
+	void GUITimeline::styleUpdated()
+	{
+		mCanvas->setStyle(GUICanvas::getGUITypeName());
+	}
+
+	static constexpr int LINE_SPLIT_WIDTH = 2;
+	static constexpr int TANGENT_LINE_DISTANCE = 30;
+	static constexpr Color COLOR_MID_GRAY = Color(90.0f / 255.0f, 90.0f / 255.0f, 90.0f / 255.0f, 1.0f);
+	static constexpr Color COLOR_DARK_GRAY = Color(40.0f / 255.0f, 40.0f / 255.0f, 40.0f / 255.0f, 1.0f);
+
+	GUICurves::GUICurves(CurveDrawOptions drawOptions, const String& styleName, const GUIDimensions& dimensions)
+		:GUITimeline(styleName, dimensions), mDrawOptions(drawOptions), mTickHandler(GUITickStepType::Time)
+	{ }
+
+	const String& GUICurves::getGUITypeName()
+	{
+		static String name = "Curves";
+		return name;
+	}
+
+	GUICurves* GUICurves::create(const String& styleName)
+	{
+		return new (bs_alloc<GUICurves>()) GUICurves(CurveDrawOption::DrawMarkers | CurveDrawOption::DrawKeyframes, 
+			getStyleName<GUICurves>(styleName), GUIDimensions::create());
+	}
+
+	GUICurves* GUICurves::create(CurveDrawOptions drawOptions, const String& styleName)
+	{
+		return new (bs_alloc<GUICurves>()) GUICurves(drawOptions, getStyleName<GUICurves>(styleName), GUIDimensions::create());
+	}
+
+	GUICurves* GUICurves::create(const GUIOptions& options, const String& styleName)
+	{
+		return new (bs_alloc<GUICurves>()) GUICurves(CurveDrawOption::DrawMarkers | CurveDrawOption::DrawKeyframes,
+			getStyleName<GUICurves>(styleName), GUIDimensions::create(options));
+	}
+
+	GUICurves* GUICurves::create(CurveDrawOptions drawOptions, const GUIOptions& options, const String& styleName)
+	{
+		return new (bs_alloc<GUICurves>()) GUICurves(drawOptions, getStyleName<GUICurves>(styleName), 
+			GUIDimensions::create(options));
+	}
+
+	void GUICurves::setCurves(const Vector<CurveDrawInfo>& curves)
+	{
+		mCurves = curves;
+		_markContentAsDirty();
+	}
+
+	void GUICurves::setRange(float xRange, float yRange)
+	{
+		GUITimeline::setRange(xRange);
+		mYRange = yRange;
+
+		_markContentAsDirty();
+	}
+
+	void GUICurves::setOffset(const Vector2& offset)
+	{
+		GUITimeline::setOffset(offset.x);
+		mYOffset = offset.y;
+	}
+
+	void GUICurves::centerAndZoom()
+	{
+		Vector<TAnimationCurve<float>> curves(mCurves.size());
+		UINT32 idx = 0;
+		for(auto& entry : mCurves)
+			curves[idx++] = entry.curve;
+
+		float xMin, xMax;
+		float yMin, yMax;
+		AnimationUtility::calculateRange(curves, xMin, xMax, yMin, yMax);
+
+		float xRange = xMax - xMin;
+
+		float yRange = (yMax - yMin) * 0.5f;
+		float yOffset = yMin + yRange;
+
+		// Add padding to y range
+		yRange *= 1.05f;
+
+		// Don't allow zero range
+		if (xRange == 0.0f)
+			xRange = 60.0f;
+
+		if (yRange == 0.0f)
+			yRange = 10.0f;
+
+		setOffset(Vector2(xMin, yOffset));
+		setRange(xRange, yRange);
+	}
+
+	UINT32 GUICurves::findCurve(const Vector2I& pixelCoords)
+	{
+		Vector2 curveCoords;
+		if (!pixelToCurveSpace(pixelCoords, curveCoords, true))
+			return (UINT32)-1;
+
+		const float time = curveCoords.x;
+
+		float nearestDistance = std::numeric_limits<float>::max();
+		auto curveIdx = (UINT32)-1;
+		for (UINT32 i = 0; i < (UINT32)mCurves.size(); i++)
+		{
+			const TAnimationCurve<float>& curve = mCurves[i].curve;
+
+			const float value = curve.evaluate(time, false);
+			const Vector2I curPixelPos = curveToPixelSpace(Vector2(time, value));
+
+			const auto distanceToKey = (float)pixelCoords.manhattanDist(curPixelPos);
+			if (distanceToKey < nearestDistance)
+			{
+				nearestDistance = distanceToKey;
+				curveIdx = i;
+			}
+		}
+
+		// We're not near any curve
+		if (nearestDistance > 5.0f)
+			return (UINT32)-1;
+
+		return curveIdx;
+	}
+
+	/**
+	 * Attempts to find a keyframe under the provided coordinates.
+	 *
+	 * @param[in]	pixelCoords		Coordinates relative to this GUI element in pixels.
+	 * @param[out]	keyframe		Output object containing keyframe index and index of the curve it belongs to. Only
+	 *								valid if method returns true.
+	 * @return						True if there is a keyframe under the coordinates, false otherwise.
+	 */
+	bool GUICurves::findKeyFrame(const Vector2I& pixelCoords, KeyframeRef& keyframe)
+	{
+		keyframe = KeyframeRef();
+
+		float nearestDistance = std::numeric_limits<float>::max();
+		for (UINT32 i = 0; i < (UINT32)mCurves.size(); i++)
+		{
+			const TAnimationCurve<float>& curve = mCurves[i].curve;
+			const Vector<TKeyframe<float>>& keyframes = curve.getKeyFrames();
+
+			for (UINT32 j = 0; j < (UINT32)keyframes.size(); j++)
+			{
+				const Vector2 keyframeCurveCoords = Vector2(keyframes[j].time, keyframes[j].value);
+				const Vector2I keyframeCoords = curveToPixelSpace(keyframeCurveCoords);
+
+				const auto distanceToKey = (float)pixelCoords.manhattanDist(keyframeCoords);
+				if (distanceToKey < nearestDistance)
+				{
+					nearestDistance = distanceToKey;
+					keyframe.keyIdx = j;
+					keyframe.curveIdx = i;
+				}
+			}
+		}
+
+		return nearestDistance <= 5.0f;
+	}
+
+	/**
+	 * Attempts to find a a tangent handle under the provided coordinates.
+	 *
+	 * @param[in]	pixelCoords		Coordinates relative to this GUI element in pixels.
+	 * @param[in]	tangent			Output object containing keyframe information and tangent type. Only valid if method
+	 *								returns true.
+	 * @return						True if there is a tangent handle under the coordinates, false otherwise.
+	 */
+	bool GUICurves::findTangent(const Vector2I& pixelCoords, TangentRef& tangent)
+	{
+		tangent = TangentRef();
+
+		float nearestDistance = std::numeric_limits<float>::max();
+		for (auto& entry : mSelectedKeyframes)
+		{
+			KeyframeRef keyframeRef = entry.keyframeRef;
+			if (keyframeRef.curveIdx < 0 || keyframeRef.curveIdx >= (INT32)mCurves.size())
+				continue;
+
+			const TAnimationCurve<float>& curve = mCurves[keyframeRef.curveIdx].curve;
+			if (keyframeRef.keyIdx < 0 || keyframeRef.keyIdx >= (INT32)curve.getNumKeyFrames())
+				continue;
+
+			const Vector<TKeyframe<float>>& keyframes = curve.getKeyFrames();
+			TangentMode tangentMode = entry.tangentMode;
+
+			if (isTangentDisplayed(tangentMode, TangentType::In))
+			{
+				const Vector2I tangentCoords = getTangentPosition(keyframes[keyframeRef.keyIdx], TangentType::In);
+
+				const auto distanceToHandle = (float)pixelCoords.manhattanDist(tangentCoords);
+				if (distanceToHandle < nearestDistance)
+				{
+					nearestDistance = distanceToHandle;
+					tangent.keyframeRef.keyIdx = keyframeRef.keyIdx;
+					tangent.keyframeRef.curveIdx = keyframeRef.curveIdx;
+					tangent.type = TangentType::In;
+				}
+			}
+
+			if (isTangentDisplayed(tangentMode, TangentType::Out))
+			{
+				const Vector2I tangentCoords = getTangentPosition(keyframes[keyframeRef.keyIdx], TangentType::Out);
+
+				const auto distanceToHandle = (float)pixelCoords.manhattanDist(tangentCoords);
+				if (distanceToHandle < nearestDistance)
+				{
+					nearestDistance = distanceToHandle;
+					tangent.keyframeRef.keyIdx = keyframeRef.keyIdx;
+					tangent.keyframeRef.curveIdx = keyframeRef.curveIdx;
+					tangent.type = TangentType::Out;
+				}
+			}
+		}
+
+		return nearestDistance <= 5.0f;
+	}
+
+	bool GUICurves::pixelToCurveSpace(const Vector2I& pixelCoords, Vector2& curveCoords, bool padding) const
+	{
+		const Rect2I& bounds = mLayoutData.area;
+
+		bool outsideHorizontal;
+		if (padding)
+			outsideHorizontal = pixelCoords.x < 0 || pixelCoords.x >= (INT32)bounds.width;
+		else
+			outsideHorizontal = pixelCoords.x < TIMELINE_PADDING_PX || pixelCoords.x >= ((INT32)bounds.width - TIMELINE_PADDING_PX);
+
+		// Check if outside of curve drawing bounds
+		if (outsideHorizontal || pixelCoords.y < 0 || pixelCoords.y >= (INT32)bounds.height)
+		{
+			curveCoords = Vector2::ZERO;
+			return false;
+		}
+
+		// Find time and value of the place under the coordinates
+		const Vector2I relativeCoords = pixelCoords - Vector2I(TIMELINE_PADDING_PX, 0);
+
+		const float lengthPerPixel = getRange() / getDrawableWidth();
+		const float heightPerPixel = mYRange / mLayoutData.area.height;
+
+		const float centerOffset = mYRange / 2.0f;
+
+		const float t = mOffset + relativeCoords.x * lengthPerPixel;
+		const float value = mYRange + centerOffset - relativeCoords.y * heightPerPixel;
+
+		curveCoords = Vector2();
+		curveCoords.x = t;
+		curveCoords.y = value;
+
+		return true;
+	}
+
+	Vector2I GUICurves::curveToPixelSpace(const Vector2& curveCoords) const
+	{
+		const UINT32 heightOffset = mLayoutData.area.height / 2; // So that y = 0 is at center of canvas
+
+		const Vector2 relativeCurveCoords = curveCoords - Vector2(mOffset, mYOffset);
+
+		Vector2I pixelCoords = Vector2I();
+		pixelCoords.x = (int)((relativeCurveCoords.x / getRange()) * getDrawableWidth()) + TIMELINE_PADDING_PX;
+		pixelCoords.y = heightOffset - (int)((relativeCurveCoords.y / mYRange) * mLayoutData.area.height);
+
+		return pixelCoords;
+	}
+
+	Vector2 GUICurves::tangentToNormal(float tangent) const
+	{
+		if (tangent == std::numeric_limits<float>::infinity())
+			return Vector2(0, 1);
+
+		Vector2 normal = Vector2(1, tangent);
+		return Vector2::normalize(normal);
+	}
+
+	Vector2I GUICurves::getTangentPosition(const TKeyframe<float>& keyFrame, TangentType type) const
+	{
+		const Vector2I position = curveToPixelSpace(Vector2(keyFrame.time, keyFrame.value));
+
+		Vector2 normal;
+		if (type == TangentType::In)
+			normal = -tangentToNormal(keyFrame.inTangent);
+		else
+			normal = tangentToNormal(keyFrame.outTangent);
+
+		// X/Y ranges aren't scaled 1:1, adjust normal accordingly
+		normal.x /= getRange();
+		normal.y /= mYRange;
+		normal = Vector2::normalize(normal);
+
+		// Convert normal (in percentage) to pixel values
+		Vector2I offset = Vector2I((int)(normal.x * TANGENT_LINE_DISTANCE),
+			(int)(-normal.y * TANGENT_LINE_DISTANCE));
+
+		return position + offset;
+	}
+
+	bool GUICurves::isTangentDisplayed(TangentMode mode, TangentType type) const
+	{
+		if (mode == TangentModeBits::Auto)
+			return false;
+		else if (mode == TangentModeBits::Free)
+			return true;
+
+		if (type == TangentType::In)
+			return mode.isSet(TangentModeBits::InFree);
+		else
+			return mode.isSet(TangentModeBits::OutFree);
+	}
+
+	void GUICurves::drawFrameMarker(float t, Color color, bool onTop)
+	{
+		const INT32 xPos = (INT32)(((t - mOffset) / getRange()) * getDrawableWidth()) + TIMELINE_PADDING_PX;
+
+		const Vector2I start = Vector2I(xPos, 0);
+		const Vector2I end = Vector2I(xPos, mLayoutData.area.height);
+
+		UINT8 depth;
+		if (onTop)
+			depth = 110;
+		else
+			depth = 130;
+
+		mCanvas->drawLine(start, end, color, depth);
+	}
+
+	void GUICurves::drawCenterLine()
+	{
+		const Vector2I center = curveToPixelSpace(Vector2(0.0f, 0.0f));
+
+		const Vector2I start = Vector2I(0, center.y);
+		const Vector2I end = Vector2I(mLayoutData.area.width, center.y);
+
+		mCanvas->drawLine(start, end, COLOR_DARK_GRAY, 130);
+	}
+
+	void GUICurves::drawDiamond(Vector2I center, int size, Color innerColor, Color outerColor)
+	{
+		const Vector2I a = Vector2I(center.x - size, center.y);
+		const Vector2I b = Vector2I(center.x, center.y - size);
+		const Vector2I c = Vector2I(center.x + size, center.y);
+		const Vector2I d = Vector2I(center.x, center.y + size);
+
+		// Draw diamond shape
+		const Vector<Vector2I> linePoints = { a, b, c, d, a };
+		const Vector<Vector2I> trianglePoints = { b, c, a, d };
+
+		mCanvas->drawTriangleStrip(trianglePoints, innerColor, 101);
+		mCanvas->drawPolyLine(linePoints, outerColor, 100);
+	}
+
+	void GUICurves::drawKeyframe(float t, float y, bool selected)
+	{
+		const Vector2I pixelCoords = curveToPixelSpace(Vector2(t, y));
+
+		if (selected)
+			drawDiamond(pixelCoords, 3, Color::White, Color::BansheeOrange);
+		else
+			drawDiamond(pixelCoords, 3, Color::White, Color::Black);
+	}
+
+	void GUICurves::drawTangents(const TKeyframe<float>& keyFrame, TangentMode tangentMode)
+	{
+		const Vector2I keyframeCoords = curveToPixelSpace(Vector2(keyFrame.time, keyFrame.value));
+
+		if (isTangentDisplayed(tangentMode, TangentType::In))
+		{
+			Vector2I tangentCoords = getTangentPosition(keyFrame, TangentType::In);
+
+			mCanvas->drawLine(keyframeCoords, tangentCoords, Color::LightGray);
+			drawDiamond(tangentCoords, 2, Color::Green, Color::Black);
+		}
+
+		if (isTangentDisplayed(tangentMode, TangentType::Out))
+		{
+			Vector2I tangentCoords = getTangentPosition(keyFrame, TangentType::Out);
+
+			mCanvas->drawLine(keyframeCoords, tangentCoords, Color::LightGray);
+			drawDiamond(tangentCoords, 2, Color::Green, Color::Black);
+		}
+	}
+
+	void GUICurves::drawCurve(const TAnimationCurve<float>& curve, const Color& color)
+	{
+		const float range = getRangeWithPadding();
+		const float lengthPerPixel = range / getDrawableWidth();
+
+		const Vector<TKeyframe<float>>& keyframes = curve.getKeyFrames();
+		if (keyframes.empty())
+			return;
+
+		// Draw start line
+		{
+			const float curveStart = keyframes[0].time;
+			const float curveValue = curve.evaluate(curveStart, false);
+
+			const Vector2I end = curveToPixelSpace(Vector2(curveStart, curveValue));
+			const Vector2I start = Vector2I(-TIMELINE_PADDING_PX, end.y);
+
+			if (start.x < end.x)
+				mCanvas->drawLine(start, end, COLOR_MID_GRAY);
+		}
+
+		Vector<Vector2I> linePoints;
+
+		// Draw in between keyframes
+		const float startVisibleTime = mOffset;
+		const float endVisibleTime = startVisibleTime + range;
+		for (INT32 i = 0; i < (INT32)keyframes.size() - 1; i++)
+		{
+			const float start = keyframes[i].time;
+			const float end = keyframes[i + 1].time;
+
+			if (end < startVisibleTime || start > endVisibleTime)
+				continue;
+
+			const bool isStep = keyframes[i].outTangent == std::numeric_limits<float>::infinity() ||
+				keyframes[i + 1].inTangent == std::numeric_limits<float>::infinity();
+
+			// If step tangent, draw the required lines without sampling, as the sampling will miss the step
+			if (isStep)
+			{
+				const float startValue = curve.evaluate(start, false);
+				const float endValue = curve.evaluate(end, false);
+
+				linePoints.push_back(curveToPixelSpace(Vector2(start, startValue)));
+				linePoints.push_back(curveToPixelSpace(Vector2(end, startValue)));
+				linePoints.push_back(curveToPixelSpace(Vector2(end, endValue)));
+			}
+			else // Draw normally
+			{
+				float splitIncrement = LINE_SPLIT_WIDTH * lengthPerPixel;
+
+				const float startValue = keyframes[i].value;
+				const float endValue = keyframes[i + 1].value;
+
+				Vector2I startPixel;
+				startPixel.x = (int)(start / lengthPerPixel);
+				startPixel.y = (int)(startValue / lengthPerPixel);
+
+				Vector2I endPixel;
+				endPixel.x = (int)(end / lengthPerPixel);
+				endPixel.y = (int)(endValue / lengthPerPixel);
+
+				const UINT32 distance = startPixel.manhattanDist(endPixel);
+
+				INT32 numSplits;
+				if (distance > 0)
+				{
+					const float fNumSplits = distance / splitIncrement;
+
+					numSplits = Math::ceilToInt(fNumSplits);
+					splitIncrement = distance / (float)numSplits;
+				}
+				else
+				{
+					numSplits = 1;
+					splitIncrement = 0.0f;
+				}
+
+				for (int j = 0; j < numSplits; j++)
+				{
+					const float t = std::min(start + j * splitIncrement, end);
+					const float value = curve.evaluate(t, false);
+
+					linePoints.push_back(curveToPixelSpace(Vector2(t, value)));
+				}
+			}
+		}
+
+		mCanvas->drawPolyLine(linePoints, color);
+
+		// Draw end line
+		{
+			const float curveEnd = keyframes[keyframes.size() - 1].time;
+			const float curveValue = curve.evaluate(curveEnd, false);
+
+			const Vector2I start = curveToPixelSpace(Vector2(curveEnd, curveValue));
+			const Vector2I end = Vector2I(mLayoutData.area.width, start.y);
+
+			if (start.x < end.x)
+				mCanvas->drawLine(start, end, COLOR_MID_GRAY);
+		}
+	}
+
+	void GUICurves::drawCurveRange(const Vector<TAnimationCurve<float>>& curves, Color color)
+	{
+		const float range = getRangeWithPadding();
+
+		if (curves.size() != 2)
+			return;
+
+		const Vector<TKeyframe<float>>* keyframes[2] = { &curves[0].getKeyFrames(), &curves[1].getKeyFrames() };
+		if (keyframes[0]->empty() || keyframes[1]->empty())
+			return;
+
+		const UINT32 numSamples = (getDrawableWidth() + LINE_SPLIT_WIDTH - 1) / LINE_SPLIT_WIDTH;
+		const float timePerSample = range / numSamples;
+
+		float time = mOffset;
+		const float lengthPerPixel = mRange / getDrawableWidth();
+		time -= lengthPerPixel * TIMELINE_PADDING_PX;
+
+		INT32 keyframeIndices[] = { 0, 0 };
+
+		// Find first valid keyframe indices
+		for (UINT32 curveIdx = 0; curveIdx < 2; curveIdx++)
+		{
+			keyframeIndices[curveIdx] = (INT32)keyframes[curveIdx]->size();
+
+			for (UINT32 i = 0; i < (UINT32)keyframes[curveIdx]->size(); i++)
+			{
+				if ((*keyframes[curveIdx])[i].time > time)
+					keyframeIndices[curveIdx] = i;
+			}
+		}
+
+		Vector<float> times;
+		Vector<float> points[2];
+
+		// Determine start points
+		for (int curveIdx = 0; curveIdx < 2; curveIdx++)
+		{
+			float value = curves[curveIdx].evaluate(time, false);
+			points[curveIdx].push_back(value);
+		}
+
+		times.push_back(time);
+
+		const float rangeEnd = mOffset + range;
+		while (time < rangeEnd)
+		{
+			float nextTime = time + timePerSample;
+			bool hasStep = false;
+
+			// Determine time to sample at. Use fixed increments unless there's a step keyframe within our increment in
+			// which case we use its time so we can evaluate it directly
+			for (UINT32 curveIdx = 0; curveIdx < 2; curveIdx++)
+			{
+				const INT32 keyframeIdx = keyframeIndices[curveIdx];
+				if (keyframeIdx < (INT32)keyframes[curveIdx]->size())
+				{
+					const TKeyframe<float>& keyframe = (*keyframes[curveIdx])[keyframeIdx];
+
+					const bool isStep = keyframe.inTangent == std::numeric_limits<float>::infinity() ||
+						keyframe.outTangent == std::numeric_limits<float>::infinity();
+
+					if (isStep && keyframe.time <= nextTime)
+					{
+						nextTime = std::min(nextTime, keyframe.time);
+						hasStep = true;
+					}
+				}
+			}
+
+			// Evaluate
+			if (hasStep)
+			{
+				for (UINT32 curveIdx = 0; curveIdx < 2; curveIdx++)
+				{
+					const INT32 keyframeIdx = keyframeIndices[curveIdx];
+					if (keyframeIdx < (INT32)keyframes[curveIdx]->size())
+					{
+						const TKeyframe<float>& keyframe = (*keyframes[curveIdx])[keyframeIdx];
+
+						if (Math::approxEquals(keyframe.time, nextTime))
+						{
+							if (keyframeIdx > 0)
+							{
+								const TKeyframe<float>& prevKeyframe = (*keyframes[curveIdx])[keyframeIdx - 1];
+								points[curveIdx].push_back(prevKeyframe.value);
+							}
+							else
+								points[curveIdx].push_back(keyframe.value);
+
+							points[curveIdx].push_back(keyframe.value);
+						}
+						else
+						{
+							// The other curve has step but this one doesn't, we just insert the same value twice
+							float value = curves[curveIdx].evaluate(nextTime, false);
+							points[curveIdx].push_back(value);
+							points[curveIdx].push_back(value);
+						}
+
+						times.push_back(nextTime);
+						times.push_back(nextTime);
+					}
+				}
+			}
+			else
+			{
+				for (UINT32 curveIdx = 0; curveIdx < 2; curveIdx++)
+					points[curveIdx].push_back(curves[curveIdx].evaluate(nextTime, false));
+
+				times.push_back(nextTime);
+			}
+
+			// Advance keyframe indices
+			for (UINT32 curveIdx = 0; curveIdx < 2; curveIdx++)
+			{
+				INT32 keyframeIdx = keyframeIndices[curveIdx];
+				while (keyframeIdx < (INT32)keyframes[curveIdx]->size())
+				{
+					const TKeyframe<float>& keyframe = (*keyframes[curveIdx])[keyframeIdx];
+					if (keyframe.time > nextTime)
+						break;
+
+					keyframeIdx = ++keyframeIndices[curveIdx];
+				}
+			}
+
+			time = nextTime;
+		}
+
+		// End points
+		for (UINT32 curveIdx = 0; curveIdx < 2; curveIdx++)
+		{
+			float value = curves[curveIdx].evaluate(rangeEnd, false);
+			points[curveIdx].push_back(value);
+		}
+
+		times.push_back(rangeEnd);
+
+		const INT32 numQuads = (INT32)times.size() - 1;
+		Vector<Vector2I> vertices;
+
+		for (int i = 0; i < numQuads; i++)
+		{
+			const INT32 idxLeft = points[0][i] < points[1][i] ? 0 : 1;
+			const INT32 idxRight = points[0][i + 1] < points[1][i + 1] ? 0 : 1;
+
+			Vector2 left[] =
+			{
+				Vector2(times[i], points[0][i]),
+				Vector2(times[i], points[1][i])
+			};
+
+			Vector2 right[] =
+			{
+				Vector2(times[i + 1], points[0][i + 1]),
+				Vector2(times[i + 1], points[1][i + 1])
+			};
+
+			if (idxLeft == idxRight)
+			{
+				const INT32 idxA = idxLeft;
+				const INT32 idxB = (idxLeft + 1) % 2;
+
+				vertices.push_back(curveToPixelSpace(left[idxB]));
+				vertices.push_back(curveToPixelSpace(right[idxB]));
+				vertices.push_back(curveToPixelSpace(left[idxA]));
+
+				vertices.push_back(curveToPixelSpace(right[idxB]));
+				vertices.push_back(curveToPixelSpace(right[idxA]));
+				vertices.push_back(curveToPixelSpace(left[idxA]));
+			}
+			// Lines intersects, can't represent them with a single quad
+			else if (idxLeft != idxRight)
+			{
+				const INT32 idxA = idxLeft;
+				const INT32 idxB = (idxLeft + 1) % 2;
+
+				Line2 lineA(left[idxB], right[idxA] - left[idxB]);
+				Line2 lineB(left[idxA], right[idxB] - left[idxA]);
+
+				const auto result = lineA.intersects(lineB);
+
+				if (result.first)
+				{
+					const float t = result.second;
+					const Vector2 intersection = left[idxB] + t * (right[idxA] - left[idxB]);
+
+					vertices.push_back(curveToPixelSpace(left[idxB]));
+					vertices.push_back(curveToPixelSpace(intersection));
+					vertices.push_back(curveToPixelSpace(left[idxA]));
+
+					vertices.push_back(curveToPixelSpace(intersection));
+					vertices.push_back(curveToPixelSpace(right[idxB]));
+					vertices.push_back(curveToPixelSpace(right[idxA]));
+				}
+			}
+		}
+
+		mCanvas->drawTriangleList(vertices, color, 129);
+	}
+
+	void GUICurves::selectKeyframe(const KeyframeRef& keyframeRef, const TangentMode& tangentMode, bool selected)
+	{
+		auto foundIdx = (UINT32)-1;
+		for (UINT32 i = 0; i < (UINT32)mSelectedKeyframes.size(); i++)
+		{
+			const SelectedKeyframe entry = mSelectedKeyframes[i];
+
+			if (entry.keyframeRef.keyIdx == keyframeRef.keyIdx && entry.keyframeRef.curveIdx == keyframeRef.curveIdx &&
+				entry.tangentMode == tangentMode)
+			{
+				foundIdx = i;
+				break;
+			}
+		}
+
+		if (selected)
+		{
+			if (foundIdx == (UINT32)-1)
+				mSelectedKeyframes.push_back(SelectedKeyframe(keyframeRef, tangentMode));
+		}
+		else
+			mSelectedKeyframes.erase(mSelectedKeyframes.begin() + foundIdx);
+	}
+
+	void GUICurves::clearSelectedKeyframes()
+	{
+		mSelectedKeyframes.clear();
+	}
+
+	bool GUICurves::isSelected(int curveIdx, int keyIdx) const
+	{
+		for(auto& entry : mSelectedKeyframes)
+		{
+			if (entry.keyframeRef.curveIdx == curveIdx && entry.keyframeRef.keyIdx == keyIdx)
+				return true;
+		}
+
+		return false;
+	}
+
+	void GUICurves::updateRenderElementsInternal()
+	{
+		mCanvas->clear();
+
+		bool drawMarkers = mDrawOptions.isSet(CurveDrawOption::DrawMarkers);
+		if (drawMarkers)
+		{
+			mTickHandler.setRange(mOffset, mOffset + getRangeWithPadding(), getDrawableWidth() + TIMELINE_PADDING_PX);
+
+			// Draw vertical frame markers
+			const INT32 numTickLevels = (INT32)mTickHandler.getNumLevels();
+			for (INT32 i = numTickLevels - 1; i >= 0; i--)
+			{
+				Vector<float> ticks = mTickHandler.getTicks(i);
+				const float strength = mTickHandler.getLevelStrength(i);
+
+				for (UINT32 j = 0; j < (UINT32)ticks.size(); j++)
+				{
+					Color color = COLOR_DARK_GRAY;
+					color.a *= strength;
+
+					drawFrameMarker(ticks[j], color, false);
+				}
+			}
+
+			// Draw center line
+			drawCenterLine();
+		}
+
+		// Draw range
+		const bool drawRange = mDrawOptions.isSet(CurveDrawOption::DrawRange);
+		auto curvesToDraw = (UINT32)mCurves.size();
+		if (drawRange && mCurves.size() >= 2)
+		{
+			Vector<TAnimationCurve<float>> curves = { mCurves[0].curve, mCurves[1].curve };
+
+			drawCurveRange(curves, Color(1.0f, 0.0f, 0.0f, 0.3f));
+			curvesToDraw = 2;
+		}
+
+		// Draw curves
+		const bool drawKeyframes = mDrawOptions.isSet(CurveDrawOption::DrawKeyframes);
+		for (UINT32 i = 0; i < curvesToDraw; i++)
+		{
+			drawCurve(mCurves[i].curve, mCurves[i].color);
+
+			// Draw keyframes
+			if (drawKeyframes)
+			{
+				const Vector<TKeyframe<float>> keyframes = mCurves[i].curve.getKeyFrames();
+				for (UINT32 j = 0; j < (UINT32)keyframes.size(); j++)
+				{
+					const bool selected = isSelected(i, j);
+					drawKeyframe(keyframes[j].time, keyframes[j].value, selected);
+				}
+			}
+		}
+
+		// Draw tangents
+		for (auto& entry : mSelectedKeyframes)
+		{
+			KeyframeRef keyframeRef = entry.keyframeRef;
+			if (keyframeRef.curveIdx < 0 || keyframeRef.curveIdx >= (INT32)mCurves.size())
+				continue;
+
+			TAnimationCurve<float> curve = mCurves[keyframeRef.curveIdx].curve;
+			if (keyframeRef.keyIdx < 0 || keyframeRef.keyIdx >= (INT32)curve.getNumKeyFrames())
+				continue;
+
+			const Vector<TKeyframe<float>> keyframes = mCurves[keyframeRef.curveIdx].curve.getKeyFrames();
+			const TangentMode tangentMode = entry.tangentMode;
+
+			drawTangents(keyframes[keyframeRef.keyIdx], tangentMode);
+		}
+
+		// Draw selected frame marker
+		if (drawMarkers && mMarkedFrame != (UINT32)-1)
+			drawFrameMarker(getTimeForFrame(mMarkedFrame), Color::BansheeOrange, true);
+
+		GUIElement::updateRenderElementsInternal();
+	}
+
+	bool GUICurves::_mouseEvent(const GUIMouseEvent& ev)
+	{
+		if(ev.getType() == GUIMouseEventType::MouseUp)
+		{
+			if (!_isDisabled())
+				onClicked();
+
+			return true;
+		}
+
+		return false;
+	}
+}

+ 612 - 0
Source/EditorCore/GUI/BsGUICurves.h

@@ -0,0 +1,612 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#pragma once
+
+#include "BsEditorPrerequisites.h"
+#include "GUI/BsGUIElement.h"
+#include "2D/BsImageSprite.h"
+#include "GUI/BsGUIContent.h"
+#include "GUI/BsGUIElementContainer.h"
+#include "Animation/BsAnimationUtility.h"
+
+namespace bs
+{
+	/** @addtogroup GUI-Editor
+	 *  @{
+	 */
+
+	/** Controls which elements should a GUICurves object draw. */
+	enum class BS_SCRIPT_EXPORT(ed:true,m:GUIEditor,n:CurveDrawOptions) CurveDrawOption
+	{
+		/** Draws markers at specific time intervals. */
+		DrawMarkers = 1 << 0,
+
+		/** Draws elements representing individual keyframes. */
+		DrawKeyframes = 1 << 1,
+
+		/** Draws curves and the area between them. Only relevant if only two curves are provided for drawing. */
+		DrawRange = 1 << 2
+	};
+
+	typedef Flags<CurveDrawOption> CurveDrawOptions;
+	BS_FLAGS_OPERATORS(CurveDrawOption)
+
+	/** Curve and a color to draw it in. */
+	struct BS_SCRIPT_EXPORT(ed:true,pl:true,m:GUIEditor) CurveDrawInfo
+	{
+		CurveDrawInfo() = default;
+		CurveDrawInfo(const TAnimationCurve<float>& curve, Color color)
+			:curve(curve), color(color)
+		{ }
+
+		TAnimationCurve<float> curve;
+		Color color;
+	};
+
+	/** Base class that can be implemented by GUI elements needing to elements along draw a horizontal timeline. */
+	class BS_ED_EXPORT BS_SCRIPT_EXPORT(ed:true,m:GUIEditor) GUITimeline : public GUIElementContainer
+	{
+	public:
+		/** Returns type name of the GUI element used for finding GUI element styles. */
+		static const String& getGUITypeName();
+
+		/**	Determines the range of values to display on the timeline, in seconds. */
+		BS_SCRIPT_EXPORT(pr:setter,n:Range)
+		void setRange(float range);
+
+		/** @copydoc setRange */
+		BS_SCRIPT_EXPORT(pr:getter,n:Range)
+		float getRange() const;
+
+		/**	Determines the offset at which the displayed timeline values start at, in seconds. */
+		BS_SCRIPT_EXPORT(pr:setter,n:Offset)
+		void setOffset(float offset);
+
+		/** @copydoc setOffset */
+		BS_SCRIPT_EXPORT(pr:getter,n:Offset)
+		float getOffset() const { return mOffset; }
+
+		/**	Number of frames per second, used for frame selection and marking. */
+		BS_SCRIPT_EXPORT(pr:setter,n:FPS)
+		void setFPS(UINT32 FPS);
+
+		/** @copydoc setFPS */
+		BS_SCRIPT_EXPORT(pr:getter,n:FPS)
+		UINT32 getFPS() const { return mFPS; }
+
+		/**	Frame to display the frame marker on. Set to -1 to clear the frame marker. */
+		BS_SCRIPT_EXPORT(pr:setter,n:MarkedFrame)
+		void setMarkedFrame(UINT32 index);
+
+		/** @copydoc setFPS */
+		BS_SCRIPT_EXPORT(pr:getter,n:MarkedFrame)
+		UINT32 setMarkedFrame() const { return mFPS; }
+
+		/**
+		 * Uses the assigned FPS, range and physical size to calculate the frame that is under the provided coordinates.
+		 *
+		 * @param[in]	pixelCoords		Coordinates relative to this GUI element.
+		 * @return						Frame that was clicked on, or -1 if the coordinates are outside of valid bounds.
+		 */
+		BS_SCRIPT_EXPORT()
+		UINT32 getFrame(const Vector2I& pixelCoords) const;
+
+		/**
+		 * Returns the time at the specified pixel value along the timeline.
+		 * 
+		 * @param[in]	pixel	X coordinate to sample at, relative to this GUI element in pixels.
+		 * @return				Time along the curve at the specified coordinate.
+		 */
+		BS_SCRIPT_EXPORT()
+		float getTime(INT32 pixel) const;
+
+		/**
+		 * Finds the pixel offset relative to the GUI element's origin for the specified time.
+		 *
+		 * @param[in]	time	Time value to return the offset for.
+		 * @return				Offset in pixels relative to GUI element's origin
+		 */
+		BS_SCRIPT_EXPORT()
+		INT32 getOffset(float time) const;
+
+		/**
+		 * Returns time for a frame with the specified index. Depends on set range and FPS.
+		 * 
+		 * @param[in]	index	Index of the frame (not a key-frame) to get the time for.
+		 * @return				Time of the frame with the provided index.	
+		 */
+		BS_SCRIPT_EXPORT()
+		float getTimeForFrame(INT32 index) const;
+
+	protected:
+		GUITimeline(const String& styleName, const GUIDimensions& dimensions);
+		~GUITimeline() override = default;
+
+		/** Returns the width of the GUI element that can be drawn to (width minus padding). */
+		UINT32 getDrawableWidth() const;
+
+		/** Similar to getRange() but expands the range so it's expanded to encompas the right-most padding area. */
+		float getRangeWithPadding() const;
+
+		/** Draws a vertical frame marker at the specified time. */
+		void drawFrameMarker(float t);
+
+		/** Draws a frame marker at the currently selected frame. */
+		void drawFrameMarker();
+
+		/** @copydoc GUIElement::_updateLayoutInternal */
+		void _updateLayoutInternal(const GUILayoutData& data) override;
+
+		/** @copydoc GUIElement::_getOptimalSize */
+		Vector2I _getOptimalSize() const override;
+
+		/** @copydoc GUIElement::styleUpdated */
+		void styleUpdated() override;
+
+		GUICanvas* mCanvas = nullptr;
+
+		float mRange = 20.0f;
+		float mOffset = 0.0f;
+		UINT32 mFPS = 1;
+		UINT32 mMarkedFrame = (UINT32)-1;
+	};
+
+	/** Determines how should ticks reported by <see cref="GUIGraphTicks"/> be distributed. */
+	enum class BS_SCRIPT_EXPORT(ed:true,m:GUIEditor) GUITickStepType
+	{
+		/** Ticks represent time values (Multiples of 60). */
+		Time,
+		/** Ticks represent generic values (Multiples of 10). */
+		Generic
+	};
+
+	/**
+	 * Generates a set of locations that can be used for rendering ticks on a graph. As input the class takes valid range,
+	 * size of the area the ticks will be displayed on, type of ticks and minimum/maximum spacing and outputs a set of
+	 * coordinates along which ticks should be positioned. Ticks are reported as multiple separate levels with different
+	 * strengths, depending on how close their distribution is to the upper valid range.
+	 */
+	class BS_SCRIPT_EXPORT(ed:true,m:GUIEditor) GUIGraphTicks
+	{
+	public:
+		/**
+		 * Contructs a new tick generating object.
+		 *
+		 * @param[in]	stepType	Determines how will ticks be distributed.
+		 */
+		BS_SCRIPT_EXPORT()
+		GUIGraphTicks(GUITickStepType stepType = GUITickStepType::Generic)
+		{
+			if(stepType == GUITickStepType::Generic)
+				setGenericSteps();
+			else
+				setTimeSteps();
+
+			rebuild();
+		}
+
+		/** Number of tick levels that will be generated. */
+		BS_SCRIPT_EXPORT(pr:getter,n:NumLevels)
+		UINT32 getNumLevels() const
+		{
+			return mNumLevels;
+		}
+
+		/**
+		 * Sets the range which ticks are to be displayed for, and the range along which the ticks will be displayed.
+		 *
+		 * @param[in]	valueRangeStart		Start of the range the ticks are to display.
+		 * @param[in]	valueRangeEnd		End of the range the ticks are to display.
+		 * @param[in]	pixelRange			Width or height on which the ticks will be rendered. In pixels.
+		 */
+		BS_SCRIPT_EXPORT()
+		void setRange(float valueRangeStart, float valueRangeEnd, UINT32 pixelRange)
+		{
+			this->mValueRangeStart = valueRangeStart;
+			this->mValueRangeEnd = valueRangeEnd;
+			this->mPixelRange = pixelRange;
+
+			rebuild();
+		}
+
+		/**
+		 * Sets valid spacing between two ticks. Tick strength will be determined by how far away are they from either
+		 * end of this range.
+		 *
+		 * @param[in]	minPx		Minimum spacing between two ticks, in pixels.
+		 * @param[in]	maxPx		Maximum spacing between two ticks, in pixels.
+		 */
+		BS_SCRIPT_EXPORT()
+		void setTickSpacing(int minPx, int maxPx)
+		{
+			mMinTickSpacingPx = minPx;
+			mMaxTickSpacingPx = maxPx;
+
+			rebuild();
+		}
+
+		/**
+		 * Returns the strength of a particular tick level. Levels are ordered in descending order of strength (level 0 is
+		 * the strongest).
+		 *
+		 * @param[in]	level	Level for which to retrieve the strength. Must not be larger than getNumLevels() - 1.
+		 * @return				Strength of the ticks at this level, in range [0, 1].
+		 */
+		BS_SCRIPT_EXPORT()
+		float getLevelStrength(UINT32 level)
+		{
+			if (level >= mNumLevels)
+				return 0.0f;
+
+			return Math::clamp01(mLevelStrengths[mMaxLevel + level]);
+		}
+
+		/**
+		 * Returns positions of all ticks of the provided level. The ticks will be within the range provided to setRange().
+		 * 
+		 * @param[in]	level	Level for which to retrieve the positions. Must not be larger than getNumLevels() - 1.
+		 * @return				Positions of all ticks of the provided level.
+		 */
+		BS_SCRIPT_EXPORT()
+		Vector<float> getTicks(UINT32 level)
+		{
+			if (level < 0 || level >= mNumLevels)
+				return { };
+
+			const float step = mValidSteps[mMaxLevel + level];
+
+			// Round up and down so we get one extra tick on either side (outside of value range)
+			// (Useful when rendering text above the ticks, so the text doesn't just pop-in when the tick appears, instead
+			// it is slowly clipped-in.)
+			const INT32 startTick = Math::ceilToInt(mValueRangeStart / step);
+			const INT32 endTick = Math::floorToInt(mValueRangeEnd / step);
+
+			const INT32 numTicks = endTick - startTick + 1;
+
+			Vector<float> ticks(numTicks);
+			for (INT32 i = startTick; i <= endTick; i++)
+				ticks[i - startTick] = i*step;
+
+			return ticks;
+		}
+
+	protected:
+		/** Rebuilds the tick positions and strengths after some relevant parameter changes. */
+		void rebuild()
+		{
+			mLevelStrengths = Vector<float>(mValidSteps.size());
+			mMaxLevel = 0;
+
+			const float valueRange = mValueRangeEnd - mValueRangeStart;
+			const INT32 tickSpacing = (INT32)mMaxTickSpacingPx - (INT32)mMinTickSpacingPx;
+			UINT32 i = 0;
+			for (; i < (UINT32)mValidSteps.size(); i++)
+			{
+				const float tickSpacingPx = (mValidSteps[i]/valueRange) * mPixelRange;
+				mLevelStrengths[i] = (tickSpacingPx - mMinTickSpacingPx)/tickSpacing;
+
+				if (mLevelStrengths[i] > 1.0f)
+					mMaxLevel = i;
+				else if (mLevelStrengths[i] < 0.0f)
+					break;
+			}
+
+			if (i > 0)
+				mNumLevels = i - mMaxLevel;
+			else
+				mNumLevels = 0;
+		}
+
+		/** 
+		 * Sets tick steps corresponding to time values. This will split the ticks into intervals relevant for displaying
+		 * times.
+		 */
+		void setTimeSteps()
+		{
+			mValidSteps = 
+			{
+				3600.0f, 1800.0f, 600.0f, 300.0f,
+				60.0f, 30.0f, 10.0f, 5.0f,
+				1.0f, 0.5f, 0.25f, 0.1f, 0.05f, 0.01f
+			};
+		}
+
+		/** Sets tick steps corresponding to generic values (as opposed to displaying time values). */
+		void setGenericSteps()
+		{
+			constexpr UINT32 numSteps = 15;
+			float minStep = 0.0000001f;
+
+			mValidSteps = Vector<float>(numSteps * 2);
+			for (int i = numSteps - 1; i >= 0; i--)
+			{
+				mValidSteps[i * 2 + 1] = minStep;
+				mValidSteps[i * 2 + 0] = minStep * 5;
+
+				minStep *= 10.0f;
+			}
+		}
+
+		UINT32 mPixelRange = 100;
+		float mValueRangeStart = 0.0f;
+		float mValueRangeEnd = 1.0f;
+
+		UINT32 mMinTickSpacingPx = 5;
+		UINT32 mMaxTickSpacingPx = 30;
+
+		Vector<float> mValidSteps = { 1.0f };
+		Vector<float> mLevelStrengths = { 1.0f };
+		UINT32 mNumLevels = 1;
+		UINT32 mMaxLevel = 0;
+	};
+
+	/** GUI element that displays one or multiple curves. */
+	class BS_ED_EXPORT BS_SCRIPT_EXPORT(ed:true,m:GUIEditor) GUICurves : public GUITimeline
+	{
+	public:
+		/** Returns type name of the GUI element used for finding GUI element styles. */
+		static const String& getGUITypeName();
+
+		/**
+		 * Creates a new GUI element.
+		 *
+		 * @param[in]	styleName		Optional style to use for the element. Style will be retrieved from GUISkin of the
+		 *								GUIWidget the element is used on. If not specified default style is used.
+		 */
+		BS_SCRIPT_EXPORT(ec:GUICurves)
+		static GUICurves* create(const String& styleName = StringUtil::BLANK);
+
+		/**
+		 * Creates a new GUI element.
+		 *
+		 * @param[in]	drawOptions		Options that control which additional elements to draw.
+		 * @param[in]	styleName		Optional style to use for the element. Style will be retrieved from GUISkin of the
+		 *								GUIWidget the element is used on. If not specified default style is used.
+		 */
+		BS_SCRIPT_EXPORT(ec:GUICurves)
+		static GUICurves* create(CurveDrawOptions drawOptions, const String& styleName = StringUtil::BLANK);
+
+		/**
+		 * Creates a new GUI element.
+		 *
+		 * @param[in]	options			Options that allow you to control how is the element positioned and sized. This will
+		 *								override any similar options set by style.
+		 * @param[in]	styleName		Optional style to use for the element. Style will be retrieved from GUISkin of the
+		 *								GUIWidget the element is used on. If not specified default style is used.
+		 */
+		static GUICurves* create(const GUIOptions& options, const String& styleName = StringUtil::BLANK);
+
+		/**
+		 * Creates a new GUI element.
+		 * 
+		 * @param[in]	drawOptions		Options that control which additional elements to draw.
+		 * @param[in]	options			Options that allow you to control how is the element positioned and sized. This will
+		 *								override any similar options set by style.
+		 * @param[in]	styleName		Optional style to use for the element. Style will be retrieved from GUISkin of the
+		 *								GUIWidget the element is used on. If not specified default style is used.
+		 */
+		static GUICurves* create(CurveDrawOptions drawOptions, const GUIOptions& options, 
+			const String& styleName = StringUtil::BLANK);
+
+		/**	Animation curves to display. */
+		BS_SCRIPT_EXPORT(pr:setter,n:Curves)
+		void setCurves(const Vector<CurveDrawInfo>& curves);
+
+		/** @copydoc setCurves */
+		BS_SCRIPT_EXPORT(pr:getter,n:Curves)
+		const Vector<CurveDrawInfo>& getCurves() const { return mCurves; }
+
+		/**
+		 * Changes the visible range that the GUI element displays.
+		 *
+		 * @param[in]	xRange		Range of the horizontal area. Displayed area will range from [0, xRange].
+		 * @param[in]	yRange		Range of the vertical area. Displayed area will range from [-yRange * 0.5, yRange * 0.5]
+		 */
+		BS_SCRIPT_EXPORT()
+		void setRange(float xRange, float yRange);
+
+		/**
+		 * Returns the offset at which the displayed timeline values start at.
+		 *
+		 * @param[in]	offset		Value to start the timeline values at, where x = time, y = value.
+		 */
+		BS_SCRIPT_EXPORT()
+		void setOffset(const Vector2& offset);
+
+		/** Centers and zooms the view to fully display the provided set of curves. */
+		BS_SCRIPT_EXPORT()
+		void centerAndZoom();
+
+		/**
+		 * Converts pixel coordinates into coordinates in curve space.
+		 *
+		 * @param[in]	pixelCoords		Coordinates relative to this GUI element, in pixels.
+		 * @param[out]	curveCoords		Curve coordinates within the range as specified by setRange(). Only valid when
+		 *								function returns true.
+		 * @param[in]	padding			Determines should coordinates over the area reserved for padding be registered.
+		 * @return						True if the coordinates are within the curve area, false otherwise.
+		 */
+		BS_SCRIPT_EXPORT()
+		bool pixelToCurveSpace(const Vector2I& pixelCoords, Vector2& curveCoords, bool padding = false) const;
+
+		/**
+		 * Converts coordinate in curve space (time, value) into pixel coordinates relative to this element's origin.
+		 *
+		 * @param[in]	curveCoords		Time and value of the location to convert.
+		 * @return						Coordinates relative to this element's origin, in pixels.
+		 */
+		BS_SCRIPT_EXPORT()
+		Vector2I curveToPixelSpace(const Vector2& curveCoords) const;
+
+		/** 
+		 * Attempts to find a curve under the provided coordinates.
+		 *
+		 * @param[in]	pixelCoords		Coordinates relative to this GUI element in pixels.
+		 * @return						Index of the curve, or -1 if none found.
+		 */
+		BS_SCRIPT_EXPORT()
+		UINT32 findCurve(const Vector2I& pixelCoords);
+
+		/**
+		 * Attempts to find a keyframe under the provided coordinates.
+		 *
+		 * @param[in]	pixelCoords		Coordinates relative to this GUI element in pixels.
+		 * @param[out]	keyframe		Output object containing keyframe index and index of the curve it belongs to. Only 
+		 *								valid if method returns true.
+		 * @return						True if there is a keyframe under the coordinates, false otherwise.
+		 */
+		BS_SCRIPT_EXPORT()
+		bool findKeyFrame(const Vector2I& pixelCoords, KeyframeRef& keyframe);
+
+		/**
+		 * Attempts to find a a tangent handle under the provided coordinates.
+		 *
+		 * @param[in]	pixelCoords		Coordinates relative to this GUI element in pixels.
+		 * @param[in]	tangent			Output object containing keyframe information and tangent type. Only valid if method
+		 *								returns true.
+		 * @return						True if there is a tangent handle under the coordinates, false otherwise.
+		 */
+		BS_SCRIPT_EXPORT()
+		bool findTangent(const Vector2I& pixelCoords, TangentRef& tangent);
+
+		/** 
+		 * Marks the specified key-frame as selected, changing the way it is displayed.
+		 *
+		 * @param[in]	keyframeRef		Keyframe reference containing the curve and keyframe index.
+		 * @param[in]	tangentMode		Type of tangent to display on the selected keyframe.
+		 * @param[in]	selected		True to select it, false to deselect it.
+		 */
+		BS_SCRIPT_EXPORT()
+		void selectKeyframe(const KeyframeRef& keyframeRef, const TangentMode& tangentMode, bool selected);
+
+		/** Clears any key-frames that were marked as selected. */
+		BS_SCRIPT_EXPORT()
+		void clearSelectedKeyframes();
+
+		BS_SCRIPT_EXPORT(in:true)
+		Event<void()> onClicked; /**< Triggered when the user clicks on the GUI element. */
+
+	protected:
+		GUICurves(CurveDrawOptions drawOptions, const String& styleName, const GUIDimensions& dimensions);
+		~GUICurves() override = default;
+
+		/**
+		 * Converts a keyframe tangent (slope) value into a 2D normal vector.
+		 *
+		 * @param[in]	tangent		Keyframe tangent (slope).
+		 * @return					Normalized 2D vector pointing in the direction of the tangent.
+		 */
+		Vector2 tangentToNormal(float tangent) const;
+
+		/**
+		 * Returns the position of the tangent, in element's pixel space.
+		 *
+		 * @param[in]	keyFrame	Keyframe that the tangent belongs to.
+		 * @param[in]	type		Which tangent to retrieve the position for.
+		 * @return					Position of the tangent, relative to the this GUI element's origin, in pixels.
+		 */
+		Vector2I getTangentPosition(const TKeyframe<float>& keyFrame, TangentType type) const;
+
+		/** 
+		 * Checks if the tangent should be displayed, depending on the active tangent mode. 
+		 *
+		 * @param[in]	mode	Tangent mode for the keyframe.
+		 * @param[in]	type	Which tangent to check for.
+		 * @return				True if the tangent should be displayed.
+		 */
+		bool isTangentDisplayed(TangentMode mode, TangentType type) const;
+
+		/**
+		 * Checks is the provided key-frame currently marked as selected.
+		 *
+		 * @param[in]	curveIdx		Index of the curve the keyframe is on.
+		 * @param[in]	keyIdx			Index of the keyframe.
+		 * @return						True if selected, false otherwise.
+		 */
+		bool isSelected(int curveIdx, int keyIdx) const;
+
+		/**
+		 * Draws a vertical frame marker on the curve area.
+		 *
+		 * @param[in]	t		Time at which to draw the marker.
+		 * @param[in]	color	Color with which to draw the marker.
+		 * @param[in]	onTop	Determines should the marker be drawn above or below the curve.
+		 */
+		void drawFrameMarker(float t, Color color, bool onTop);
+
+		/** Draws a horizontal line representing the line at y = 0. */
+		void drawCenterLine();
+
+		/**
+		 * Draws a diamond shape of the specified size at the coordinates.
+		 *
+		 * @param[in]	center		Position at which to place the diamond's center, in pixel coordinates.
+		 * @param[in]	size		Determines number of pixels to extend the diamond in each direction.
+		 * @param[in]	innerColor	Color of the diamond's background.
+		 * @param[in]	outerColor	Color of the diamond's outline.
+		 */
+		void drawDiamond(Vector2I center, int size, Color innerColor, Color outerColor);
+
+		/**
+		 * Draws a keyframe a the specified time and value.
+		 *
+		 * @param[in]	t			Time to draw the keyframe at.
+		 * @param[in]	y			Y value to draw the keyframe at.
+		 * @param[in]	selected	Determines should the keyframe be drawing using the selected color scheme, or normally.
+		 */
+		void drawKeyframe(float t, float y, bool selected);
+
+		/**
+		 * Draws zero, one or two tangents for the specified keyframe. Whether tangents are drawn depends on the provided
+		 * mode.
+		 *
+		 * @param[in]	keyFrame	Keyframe to draw the tangents for.
+		 * @param[in]	tangentMode	Type of tangents in the keyframe.
+		 */
+		void drawTangents(const TKeyframe<float>& keyFrame, TangentMode tangentMode);
+
+		/**
+		 * Draws the curve using the provided color.
+		 *
+		 * @param[in]	curve		Curve to draw within the currently set range.
+		 * @param[in]	color		Color to draw the curve with.	
+		 */
+		void drawCurve(const TAnimationCurve<float>& curve, const Color& color);
+
+		/*
+		 * Draws the area between two curves using the provided color.
+		 *
+		 * @param[in]	curves		Curves to draw within the currently set range.
+		 * @param[in]	color		Color to draw the area with.
+		 */
+		void drawCurveRange(const Vector<TAnimationCurve<float>>& curves, Color color);
+
+		/** @copydoc GUIElement::updateRenderElementsInternal */
+		void updateRenderElementsInternal() override;
+
+		/** @copydoc GUIElement::_mouseEvent() */
+		bool _mouseEvent(const GUIMouseEvent& ev) override;
+
+	private:
+		/** Information about a currently selected keyframe. */
+		struct SelectedKeyframe
+		{
+			SelectedKeyframe(KeyframeRef keyframeRef, TangentMode tangentMode)
+				:keyframeRef(keyframeRef), tangentMode(tangentMode)
+			{ }
+
+			KeyframeRef keyframeRef;
+			TangentMode tangentMode;
+		};
+
+		Vector<CurveDrawInfo> mCurves;
+		Vector<SelectedKeyframe> mSelectedKeyframes;
+
+		float mYRange = 20.0f;
+		float mYOffset = 0.0f;
+		CurveDrawOptions mDrawOptions;
+		GUIGraphTicks mTickHandler;
+	};
+
+	/** @} */
+}

+ 1 - 1
Source/EditorCore/GUI/BsGUIListBoxField.h

@@ -186,7 +186,7 @@ namespace bs
 		void setElementStates(const Vector<bool>& states);
 
 		/** @copydoc GUIElement::setTint */
-		virtual void setTint(const Color& color) override;
+		void setTint(const Color& color) override;
 
 		/** Triggers when a new element is selected. Provides index to the element. */
 		Event<void(UINT32, bool)> onSelectionChanged; 

+ 8 - 8
Source/Scripting/MBansheeEditor/GUI/GUICurveField.cs

@@ -25,7 +25,7 @@ namespace BansheeEditor
         /// <param name="height">Height of the GUI element in pixels.</param>
         public GUICurveField(GUILayout layout, AnimationCurve curve, int width = 200, int height = 60)
         { 
-            drawInfos = new [] { new CurveDrawInfo(new EdAnimationCurve(curve, null), Color.BansheeOrange ) };
+            drawInfos = new [] { new CurveDrawInfo(curve, Color.BansheeOrange ) };
             drawRange = false;
 
             Initialize(layout, width, height);
@@ -43,8 +43,8 @@ namespace BansheeEditor
         { 
             drawInfos = new []
             {
-                new CurveDrawInfo(new EdAnimationCurve(curveA, null), Color.BansheeOrange ),
-                new CurveDrawInfo(new EdAnimationCurve(curveB, null), Color.Green )
+                new CurveDrawInfo(curveA, Color.BansheeOrange ),
+                new CurveDrawInfo(curveB, Color.Green )
             };
             drawRange = true;
 
@@ -97,26 +97,26 @@ namespace BansheeEditor
         {
             if (drawRange)
             {
-                CurveEditorWindow.Show(drawInfos[0].curve.Normal, drawInfos[1].curve.Normal,
+                CurveEditorWindow.Show(drawInfos[0].curve, drawInfos[1].curve,
                     (success, curveA, curveB) =>
                 {
                     if (!success)
                         return;
 
-                    drawInfos[0].curve = new EdAnimationCurve(curveA, null);
-                    drawInfos[1].curve = new EdAnimationCurve(curveB, null);
+                    drawInfos[0].curve = curveA;
+                    drawInfos[1].curve = curveB;
                     curveDrawing.SetCurves(drawInfos);
                     Refresh();
                 });
             }
             else
             {
-                CurveEditorWindow.Show(drawInfos[0].curve.Normal, (success, curve) =>
+                CurveEditorWindow.Show(drawInfos[0].curve, (success, curve) =>
                 {
                     if (!success)
                         return;
 
-                    drawInfos[0].curve = new EdAnimationCurve(curve, null);
+                    drawInfos[0].curve = curve;
                     curveDrawing.SetCurves(drawInfos);
                     Refresh();
                 });

+ 27 - 0
Source/Scripting/MBansheeEditor/Generated/CurveDrawInfo.generated.cs

@@ -0,0 +1,27 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using BansheeEngine;
+
+namespace BansheeEditor
+{
+	/** @addtogroup GUIEditor
+	 *  @{
+	 */
+
+	/// <summary>Curve and a color to draw it in.</summary>
+	[StructLayout(LayoutKind.Sequential), SerializeObject]
+	public partial struct CurveDrawInfo
+	{
+		public CurveDrawInfo(AnimationCurve curve, Color color)
+		{
+			this.curve = curve;
+			this.color = color;
+		}
+
+		public AnimationCurve curve;
+		public Color color;
+	}
+
+	/** @} */
+}

+ 174 - 0
Source/Scripting/MBansheeEditor/Generated/GUICurves.generated.cs

@@ -0,0 +1,174 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using BansheeEngine;
+
+namespace BansheeEditor
+{
+	/** @addtogroup GUIEditor
+	 *  @{
+	 */
+
+	/// <summary>GUI element that displays one or multiple curves.</summary>
+	public partial class GUICurves : GUITimeline
+	{
+		private GUICurves(bool __dummy0) { }
+		protected GUICurves() { }
+
+		/// <summary>Creates a new GUI element.</summary>
+		/// <param name="styleName">
+		/// Optional style to use for the element. Style will be retrieved from GUISkin of the GUIWidget the element is used on. 
+		/// If not specified default style is used.
+		/// </param>
+		public GUICurves(string styleName = "")
+		{
+			Internal_create(this, styleName);
+		}
+
+		/// <summary>Creates a new GUI element.</summary>
+		/// <param name="drawOptions">Options that control which additional elements to draw.</param>
+		/// <param name="styleName">
+		/// Optional style to use for the element. Style will be retrieved from GUISkin of the GUIWidget the element is used on. 
+		/// If not specified default style is used.
+		/// </param>
+		public GUICurves(CurveDrawOptions drawOptions, string styleName = "")
+		{
+			Internal_create0(this, drawOptions, styleName);
+		}
+
+		/// <summary>Animation curves to display.</summary>
+		public CurveDrawInfo[] Curves
+		{
+			get { return Internal_getCurves(mCachedPtr); }
+			set { Internal_setCurves(mCachedPtr, value); }
+		}
+
+		/// <summary>Triggered when the user clicks on the GUI element.</summary>
+		partial void OnClicked();
+
+		/// <summary>Changes the visible range that the GUI element displays.</summary>
+		/// <param name="xRange">Range of the horizontal area. Displayed area will range from [0, xRange].</param>
+		/// <param name="yRange">Range of the vertical area. Displayed area will range from [-yRange * 0.5, yRange * 0.5]</param>
+		public void SetRange(float xRange, float yRange)
+		{
+			Internal_setRange(mCachedPtr, xRange, yRange);
+		}
+
+		/// <summary>Returns the offset at which the displayed timeline values start at.</summary>
+		/// <param name="offset">Value to start the timeline values at, where x = time, y = value.</param>
+		public void SetOffset(Vector2 offset)
+		{
+			Internal_setOffset(mCachedPtr, ref offset);
+		}
+
+		/// <summary>Centers and zooms the view to fully display the provided set of curves.</summary>
+		public void CenterAndZoom()
+		{
+			Internal_centerAndZoom(mCachedPtr);
+		}
+
+		/// <summary>Converts pixel coordinates into coordinates in curve space.</summary>
+		/// <param name="pixelCoords">Coordinates relative to this GUI element, in pixels.</param>
+		/// <param name="curveCoords">
+		/// Curve coordinates within the range as specified by setRange(). Only valid when function returns true.
+		/// </param>
+		/// <param name="padding">Determines should coordinates over the area reserved for padding be registered.</param>
+		/// <returns>True if the coordinates are within the curve area, false otherwise.</returns>
+		public bool PixelToCurveSpace(Vector2I pixelCoords, out Vector2 curveCoords, bool padding = false)
+		{
+			return Internal_pixelToCurveSpace(mCachedPtr, ref pixelCoords, out curveCoords, padding);
+		}
+
+		/// <summary>
+		/// Converts coordinate in curve space (time, value) into pixel coordinates relative to this element's origin.
+		/// </summary>
+		/// <param name="curveCoords">Time and value of the location to convert.</param>
+		/// <returns>Coordinates relative to this element's origin, in pixels.</returns>
+		public Vector2I CurveToPixelSpace(Vector2 curveCoords)
+		{
+			Vector2I temp;
+			Internal_curveToPixelSpace(mCachedPtr, ref curveCoords, out temp);
+			return temp;
+		}
+
+		/// <summary>Attempts to find a curve under the provided coordinates.</summary>
+		/// <param name="pixelCoords">Coordinates relative to this GUI element in pixels.</param>
+		/// <returns>Index of the curve, or -1 if none found.</returns>
+		public uint FindCurve(Vector2I pixelCoords)
+		{
+			return Internal_findCurve(mCachedPtr, ref pixelCoords);
+		}
+
+		/// <summary>Attempts to find a keyframe under the provided coordinates.</summary>
+		/// <param name="pixelCoords">Coordinates relative to this GUI element in pixels.</param>
+		/// <param name="keyframe">
+		/// Output object containing keyframe index and index of the curve it belongs to. Only  valid if method returns true.
+		/// </param>
+		/// <returns>True if there is a keyframe under the coordinates, false otherwise.</returns>
+		public bool FindKeyFrame(Vector2I pixelCoords, out KeyframeRef keyframe)
+		{
+			return Internal_findKeyFrame(mCachedPtr, ref pixelCoords, out keyframe);
+		}
+
+		/// <summary>Attempts to find a a tangent handle under the provided coordinates.</summary>
+		/// <param name="pixelCoords">Coordinates relative to this GUI element in pixels.</param>
+		/// <param name="tangent">
+		/// Output object containing keyframe information and tangent type. Only valid if method returns true.
+		/// </param>
+		/// <returns>True if there is a tangent handle under the coordinates, false otherwise.</returns>
+		public bool FindTangent(Vector2I pixelCoords, out TangentRef tangent)
+		{
+			return Internal_findTangent(mCachedPtr, ref pixelCoords, out tangent);
+		}
+
+		/// <summary>Marks the specified key-frame as selected, changing the way it is displayed.</summary>
+		/// <param name="keyframeRef">Keyframe reference containing the curve and keyframe index.</param>
+		/// <param name="tangentMode">Type of tangent to display on the selected keyframe.</param>
+		/// <param name="selected">True to select it, false to deselect it.</param>
+		public void SelectKeyframe(KeyframeRef keyframeRef, TangentMode tangentMode, bool selected)
+		{
+			Internal_selectKeyframe(mCachedPtr, ref keyframeRef, tangentMode, selected);
+		}
+
+		/// <summary>Clears any key-frames that were marked as selected.</summary>
+		public void ClearSelectedKeyframes()
+		{
+			Internal_clearSelectedKeyframes(mCachedPtr);
+		}
+
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern void Internal_setCurves(IntPtr thisPtr, CurveDrawInfo[] curves);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern CurveDrawInfo[] Internal_getCurves(IntPtr thisPtr);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern void Internal_setRange(IntPtr thisPtr, float xRange, float yRange);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern void Internal_setOffset(IntPtr thisPtr, ref Vector2 offset);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern void Internal_centerAndZoom(IntPtr thisPtr);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern bool Internal_pixelToCurveSpace(IntPtr thisPtr, ref Vector2I pixelCoords, out Vector2 curveCoords, bool padding);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern void Internal_curveToPixelSpace(IntPtr thisPtr, ref Vector2 curveCoords, out Vector2I __output);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern uint Internal_findCurve(IntPtr thisPtr, ref Vector2I pixelCoords);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern bool Internal_findKeyFrame(IntPtr thisPtr, ref Vector2I pixelCoords, out KeyframeRef keyframe);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern bool Internal_findTangent(IntPtr thisPtr, ref Vector2I pixelCoords, out TangentRef tangent);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern void Internal_selectKeyframe(IntPtr thisPtr, ref KeyframeRef keyframeRef, TangentMode tangentMode, bool selected);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern void Internal_clearSelectedKeyframes(IntPtr thisPtr);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern void Internal_create(GUICurves managedInstance, string styleName);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern void Internal_create0(GUICurves managedInstance, CurveDrawOptions drawOptions, string styleName);
+		private void Internal_onClicked()
+		{
+			OnClicked();
+		}
+	}
+
+	/** @} */
+}

+ 94 - 0
Source/Scripting/MBansheeEditor/Generated/GUIGraphTicks.generated.cs

@@ -0,0 +1,94 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using BansheeEngine;
+
+namespace BansheeEditor
+{
+	/** @addtogroup GUIEditor
+	 *  @{
+	 */
+
+	/// <summary>
+	/// Generates a set of locations that can be used for rendering ticks on a graph. As input the class takes valid range, 
+	/// size of the area the ticks will be displayed on, type of ticks and minimum/maximum spacing and outputs a set of 
+	/// coordinates along which ticks should be positioned. Ticks are reported as multiple separate levels with different 
+	/// strengths, depending on how close their distribution is to the upper valid range.
+	/// </summary>
+	public partial class GUIGraphTicks : ScriptObject
+	{
+		private GUIGraphTicks(bool __dummy0) { }
+		protected GUIGraphTicks() { }
+
+		/// <summary>Contructs a new tick generating object.</summary>
+		/// <param name="stepType">Determines how will ticks be distributed.</param>
+		public GUIGraphTicks(GUITickStepType stepType = GUITickStepType.Generic)
+		{
+			Internal_GUIGraphTicks(this, stepType);
+		}
+
+		/// <summary>Number of tick levels that will be generated.</summary>
+		public uint NumLevels
+		{
+			get { return Internal_getNumLevels(mCachedPtr); }
+		}
+
+		/// <summary>
+		/// Sets the range which ticks are to be displayed for, and the range along which the ticks will be displayed.
+		/// </summary>
+		/// <param name="valueRangeStart">Start of the range the ticks are to display.</param>
+		/// <param name="valueRangeEnd">End of the range the ticks are to display.</param>
+		/// <param name="pixelRange">Width or height on which the ticks will be rendered. In pixels.</param>
+		public void SetRange(float valueRangeStart, float valueRangeEnd, uint pixelRange)
+		{
+			Internal_setRange(mCachedPtr, valueRangeStart, valueRangeEnd, pixelRange);
+		}
+
+		/// <summary>
+		/// Sets valid spacing between two ticks. Tick strength will be determined by how far away are they from either end of 
+		/// this range.
+		/// </summary>
+		/// <param name="minPx">Minimum spacing between two ticks, in pixels.</param>
+		/// <param name="maxPx">Maximum spacing between two ticks, in pixels.</param>
+		public void SetTickSpacing(int minPx, int maxPx)
+		{
+			Internal_setTickSpacing(mCachedPtr, minPx, maxPx);
+		}
+
+		/// <summary>
+		/// Returns the strength of a particular tick level. Levels are ordered in descending order of strength (level 0 is the 
+		/// strongest).
+		/// </summary>
+		/// <param name="level">Level for which to retrieve the strength. Must not be larger than getNumLevels() - 1.</param>
+		/// <returns>Strength of the ticks at this level, in range [0, 1].</returns>
+		public float GetLevelStrength(uint level)
+		{
+			return Internal_getLevelStrength(mCachedPtr, level);
+		}
+
+		/// <summary>
+		/// Returns positions of all ticks of the provided level. The ticks will be within the range provided to setRange().
+		/// </summary>
+		/// <param name="level">Level for which to retrieve the positions. Must not be larger than getNumLevels() - 1.</param>
+		/// <returns>Positions of all ticks of the provided level.</returns>
+		public float[] GetTicks(int level)
+		{
+			return Internal_getTicks(mCachedPtr, level);
+		}
+
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern void Internal_GUIGraphTicks(GUIGraphTicks managedInstance, GUITickStepType stepType);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern uint Internal_getNumLevels(IntPtr thisPtr);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern void Internal_setRange(IntPtr thisPtr, float valueRangeStart, float valueRangeEnd, uint pixelRange);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern void Internal_setTickSpacing(IntPtr thisPtr, int minPx, int maxPx);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern float Internal_getLevelStrength(IntPtr thisPtr, uint level);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern float[] Internal_getTicks(IntPtr thisPtr, int level);
+	}
+
+	/** @} */
+}

+ 109 - 0
Source/Scripting/MBansheeEditor/Generated/GUITimeline.generated.cs

@@ -0,0 +1,109 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using BansheeEngine;
+
+namespace BansheeEditor
+{
+	/** @addtogroup GUIEditor
+	 *  @{
+	 */
+
+	/// <summary>
+	/// Base class that can be implemented by GUI elements needing to elements along draw a horizontal timeline.
+	/// </summary>
+	public partial class GUITimeline : GUIElement
+	{
+		private GUITimeline(bool __dummy0) { }
+		protected GUITimeline() { }
+
+		/// <summary>Determines the range of values to display on the timeline, in seconds.</summary>
+		public float Range
+		{
+			get { return Internal_getRange(mCachedPtr); }
+			set { Internal_setRange(mCachedPtr, value); }
+		}
+
+		/// <summary>Determines the offset at which the displayed timeline values start at, in seconds.</summary>
+		public float Offset
+		{
+			get { return Internal_getOffset(mCachedPtr); }
+			set { Internal_setOffset(mCachedPtr, value); }
+		}
+
+		/// <summary>Number of frames per second, used for frame selection and marking.</summary>
+		public uint FPS
+		{
+			get { return Internal_getFPS(mCachedPtr); }
+			set { Internal_setFPS(mCachedPtr, value); }
+		}
+
+		/// <summary>Frame to display the frame marker on. Set to -1 to clear the frame marker.</summary>
+		public uint MarkedFrame
+		{
+			get { return Internal_setMarkedFrame0(mCachedPtr); }
+			set { Internal_setMarkedFrame(mCachedPtr, value); }
+		}
+
+		/// <summary>
+		/// Uses the assigned FPS, range and physical size to calculate the frame that is under the provided coordinates.
+		/// </summary>
+		/// <param name="pixelCoords">Coordinates relative to this GUI element.</param>
+		/// <returns>Frame that was clicked on, or -1 if the coordinates are outside of valid bounds.</returns>
+		public uint GetFrame(Vector2I pixelCoords)
+		{
+			return Internal_getFrame(mCachedPtr, ref pixelCoords);
+		}
+
+		/// <summary>Returns the time at the specified pixel value along the timeline.</summary>
+		/// <param name="pixel">X coordinate to sample at, relative to this GUI element in pixels.</param>
+		/// <returns>Time along the curve at the specified coordinate.</returns>
+		public float GetTime(int pixel)
+		{
+			return Internal_getTime(mCachedPtr, pixel);
+		}
+
+		/// <summary>Finds the pixel offset relative to the GUI element's origin for the specified time.</summary>
+		/// <param name="time">Time value to return the offset for.</param>
+		/// <returns>Offset in pixels relative to GUI element's origin</returns>
+		public int GetOffset(float time)
+		{
+			return Internal_getOffset0(mCachedPtr, time);
+		}
+
+		/// <summary>Returns time for a frame with the specified index. Depends on set range and FPS.</summary>
+		/// <param name="index">Index of the frame (not a key-frame) to get the time for.</param>
+		/// <returns>Time of the frame with the provided index.</returns>
+		public float GetTimeForFrame(int index)
+		{
+			return Internal_getTimeForFrame(mCachedPtr, index);
+		}
+
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern void Internal_setRange(IntPtr thisPtr, float range);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern float Internal_getRange(IntPtr thisPtr);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern void Internal_setOffset(IntPtr thisPtr, float offset);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern float Internal_getOffset(IntPtr thisPtr);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern void Internal_setFPS(IntPtr thisPtr, uint FPS);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern uint Internal_getFPS(IntPtr thisPtr);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern void Internal_setMarkedFrame(IntPtr thisPtr, uint index);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern uint Internal_setMarkedFrame0(IntPtr thisPtr);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern uint Internal_getFrame(IntPtr thisPtr, ref Vector2I pixelCoords);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern float Internal_getTime(IntPtr thisPtr, int pixel);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern int Internal_getOffset0(IntPtr thisPtr, float time);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern float Internal_getTimeForFrame(IntPtr thisPtr, int index);
+	}
+
+	/** @} */
+}

+ 0 - 1
Source/Scripting/MBansheeEditor/MBansheeEditor.csproj

@@ -63,7 +63,6 @@
     <Compile Include="Windows\Animation\GUICurveEditor.cs" />
     <Compile Include="Windows\Animation\GUIFieldSelector.cs" />
     <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" />

+ 0 - 88
Source/Scripting/MBansheeEditor/Utility/EdAnimationCurve.cs

@@ -9,64 +9,6 @@ namespace BansheeEditor
      *  @{
      */
 
-    /// <summary>
-    /// Type of tangent on a keyframe in an animation curve.
-    /// </summary>
-    public enum TangentType
-    {
-        In = 1 << 0,
-        Out = 1 << 1
-    }
-
-    /// <summary>
-    /// Flags that are used for describing how are tangents calculated for a specific keyframe in an animation curve. 
-    /// Modes for "in" and "out" tangents can be combined.
-    /// </summary>
-    [Flags]
-    public enum TangentMode
-    {
-        /// <summary>
-        /// Both tangents are calculated automatically based on the two surrounding keyframes.
-        /// </summary>
-        Auto = 0,
-        /// <summary>
-        /// Left tangent is calculated automatically based on the two surrounding keyframes.
-        /// </summary>
-        InAuto = TangentType.In | 1 << 2,
-        /// <summary>
-        /// Left tangent is manually adjusted by the user.
-        /// </summary>
-        InFree = TangentType.In | 1 << 3,
-        /// <summary>
-        /// Tangent is calculated automatically based on the previous keyframe.
-        /// </summary>
-        InLinear = TangentType.In | 1 << 4,
-        /// <summary>
-        /// Tangent is infinite, ensuring there is a instantaneus jump between previous and current keyframe value.
-        /// </summary>
-        InStep = TangentType.In | 1 << 5,
-        /// <summary>
-        /// Right tangents are calculated automatically based on the two surrounding keyframes.
-        /// </summary>
-        OutAuto = TangentType.Out | 1 << 6,
-        /// <summary>
-        /// Right tangent is manually adjusted by the user.
-        /// </summary>
-        OutFree = TangentType.Out | 1 << 7,
-        /// <summary>
-        /// Tangent is calculated automatically based on the next keyframe.
-        /// </summary>
-        OutLinear = TangentType.Out | 1 << 8,
-        /// <summary>
-        /// Tangent is infinite, ensuring there is a instantaneus jump between current and next keyframe value.
-        /// </summary>
-        OutStep = TangentType.Out | 1 << 9,
-        /// <summary>
-        /// Both tangents are manually adjusted by the user.
-        /// </summary>
-        Free = 1 << 10,
-    }
-
     /// <summary>
     /// <see cref="AnimationCurve"/> wrapper for use in editor only. Allows easier manipulation of animation keyframes, and
     /// also stores keyframe tangent modes which are not required for non-editor curves.
@@ -517,35 +459,5 @@ namespace BansheeEditor
         }
     }
 
-    /// <summary>
-    /// Structure containing a reference to a keyframe as a curve index, and a keyframe index within that curve.
-    /// </summary>
-    internal struct KeyframeRef
-    {
-        public KeyframeRef(int curveIdx, int keyIdx)
-        {
-            this.curveIdx = curveIdx;
-            this.keyIdx = keyIdx;
-        }
-
-        public int curveIdx;
-        public int keyIdx;
-    }
-
-    /// <summary>
-    /// Structure containing a reference to a keyframe tangent, as a keyframe reference and type of the tangent.
-    /// </summary>
-    internal struct TangentRef
-    {
-        public TangentRef(KeyframeRef keyframeRef, TangentType type)
-        {
-            this.keyframeRef = keyframeRef;
-            this.type = type;
-        }
-
-        public KeyframeRef keyframeRef;
-        public TangentType type;
-    }
-
     /** @} */
 }

+ 7 - 7
Source/Scripting/MBansheeEditor/Windows/Animation/EditorAnimInfo.cs

@@ -18,7 +18,7 @@ namespace BansheeEditor
     internal struct FieldAnimCurves
     {
         public SerializableProperty.FieldType type;
-        public CurveDrawInfo[] curveInfos;
+        public EdCurveDrawInfo[] curveInfos;
         public bool isPropertyCurve;
     }
 
@@ -145,18 +145,18 @@ namespace BansheeEditor
 
                         FieldAnimCurves fieldCurves = new FieldAnimCurves();
                         fieldCurves.type = SerializableProperty.FieldType.Vector3;
-                        fieldCurves.curveInfos = new CurveDrawInfo[3];
+                        fieldCurves.curveInfos = new EdCurveDrawInfo[3];
                         fieldCurves.isPropertyCurve = !clipInfo.isImported;
 
-                        fieldCurves.curveInfos[0] = new CurveDrawInfo();
+                        fieldCurves.curveInfos[0] = new EdCurveDrawInfo();
                         fieldCurves.curveInfos[0].curve = new EdAnimationCurve(componentCurves[0], tangentsX);
                         fieldCurves.curveInfos[0].color = GUICurveDrawing.GetUniqueColor(globalCurveIdx++);
 
-                        fieldCurves.curveInfos[1] = new CurveDrawInfo();
+                        fieldCurves.curveInfos[1] = new EdCurveDrawInfo();
                         fieldCurves.curveInfos[1].curve = new EdAnimationCurve(componentCurves[1], tangentsY);
                         fieldCurves.curveInfos[1].color = GUICurveDrawing.GetUniqueColor(globalCurveIdx++);
 
-                        fieldCurves.curveInfos[2] = new CurveDrawInfo();
+                        fieldCurves.curveInfos[2] = new EdCurveDrawInfo();
                         fieldCurves.curveInfos[2].curve = new EdAnimationCurve(componentCurves[2], tangentsZ);
                         fieldCurves.curveInfos[2].color = GUICurveDrawing.GetUniqueColor(globalCurveIdx++);
 
@@ -268,7 +268,7 @@ namespace BansheeEditor
                 bool isMorphCurve = false;
                 string curvePath = KVP.Key;
 
-                fieldCurves.curveInfos = new CurveDrawInfo[numCurves];
+                fieldCurves.curveInfos = new EdCurveDrawInfo[numCurves];
                 for (int i = 0; i < numCurves; i++)
                 {
                     int curveIdx = KVP.Value[i].Item1;
@@ -278,7 +278,7 @@ namespace BansheeEditor
                     if (tangentIdx != -1)
                         tangents = editorCurveData.floatCurves[tangentIdx].tangents;
 
-                    fieldCurves.curveInfos[i] = new CurveDrawInfo();
+                    fieldCurves.curveInfos[i] = new EdCurveDrawInfo();
                     fieldCurves.curveInfos[i].curve = new EdAnimationCurve(clipCurves.Generic[curveIdx].curve, tangents);
                     fieldCurves.curveInfos[i].color = GUICurveDrawing.GetUniqueColor(globalCurveIdx++);
 

+ 98 - 102
Source/Scripting/MBansheeEditor/Windows/Animation/GUICurveDrawing.cs

@@ -16,13 +16,28 @@ namespace BansheeEditor
     /// </summary>
     internal class GUICurveDrawing : GUITimelineBase
     {
+        /// <summary>
+        /// Information about a currently selected keyframe.
+        /// </summary>
+        struct SelectedKeyframe
+        {
+            public SelectedKeyframe(KeyframeRef keyframeRef, TangentMode tangentMode)
+            {
+                this.keyframeRef = keyframeRef;
+                this.tangentMode = tangentMode;
+            }
+
+            public KeyframeRef keyframeRef;
+            public TangentMode tangentMode;
+        }
+
         private const int LINE_SPLIT_WIDTH = 2;
         private const int TANGENT_LINE_DISTANCE = 30;
         private static readonly Color COLOR_MID_GRAY = new Color(90.0f / 255.0f, 90.0f / 255.0f, 90.0f / 255.0f, 1.0f);
         private static readonly Color COLOR_DARK_GRAY = new Color(40.0f / 255.0f, 40.0f / 255.0f, 40.0f / 255.0f, 1.0f);
 
         private CurveDrawInfo[] curveInfos;
-        private bool[][] selectedKeyframes;
+        private List<SelectedKeyframe> selectedKeyframes = new List<SelectedKeyframe>();
 
         private float yRange = 20.0f;
         private float yOffset;
@@ -87,19 +102,31 @@ namespace BansheeEditor
         /// Marks the specified key-frame as selected, changing the way it is displayed.
         /// </summary>
         /// <param name="keyframeRef">Keyframe reference containing the curve and keyframe index.</param>
+        /// <param name="tangentMode">Type of tangent to display on the selected keyframe.</param>
         /// <param name="selected">True to select it, false to deselect it.</param>
-        public void SelectKeyframe(KeyframeRef keyframeRef, bool selected)
+        public void SelectKeyframe(KeyframeRef keyframeRef, TangentMode tangentMode, bool selected)
         {
-            if (selectedKeyframes == null)
-                return;
+            int foundIdx = -1;
+            for(int i = 0; i < selectedKeyframes.Count; i++)
+            {
+                SelectedKeyframe entry = selectedKeyframes[i];
 
-            if (keyframeRef.curveIdx < 0 || keyframeRef.curveIdx >= selectedKeyframes.Length)
-                return;
+                if (entry.keyframeRef.keyIdx == keyframeRef.keyIdx && entry.keyframeRef.curveIdx == keyframeRef.curveIdx &&
+                    entry.tangentMode == tangentMode)
+                {
+                    foundIdx = i;
+                    break;
+                }
 
-            if (keyframeRef.keyIdx < 0 || keyframeRef.keyIdx >= selectedKeyframes[keyframeRef.curveIdx].Length)
-                return;
+            }
 
-            selectedKeyframes[keyframeRef.curveIdx][keyframeRef.keyIdx] = selected;
+            if (selected)
+            {
+                if (foundIdx == -1)
+                    selectedKeyframes.Add(new SelectedKeyframe(keyframeRef, tangentMode));
+            }
+            else
+                selectedKeyframes.RemoveAt(foundIdx);
         }
 
         /// <summary>
@@ -107,13 +134,7 @@ namespace BansheeEditor
         /// </summary>
         public void ClearSelectedKeyframes()
         {
-            selectedKeyframes = new bool[curveInfos.Length][];
-
-            for (int i = 0; i < curveInfos.Length; i++)
-            {
-                KeyFrame[] keyframes = curveInfos[i].curve.KeyFrames;
-                selectedKeyframes[i] = new bool[keyframes.Length];
-            }
+            selectedKeyframes.Clear();
         }
 
         /// <summary>
@@ -131,7 +152,7 @@ namespace BansheeEditor
             int curveIdx = -1;
             for (int i = 0; i < curveInfos.Length; i++)
             {
-                EdAnimationCurve curve = curveInfos[i].curve;
+                AnimationCurve curve = curveInfos[i].curve;
 
                 float value = curve.Evaluate(time, false);
                 Vector2I curPixelPos = CurveToPixelSpace(new Vector2(time, value));
@@ -165,7 +186,7 @@ namespace BansheeEditor
             float nearestDistance = float.MaxValue;
             for (int i = 0; i < curveInfos.Length; i++)
             {
-                EdAnimationCurve curve = curveInfos[i].curve;
+                AnimationCurve curve = curveInfos[i].curve;
                 KeyFrame[] keyframes = curve.KeyFrames;
 
                 for (int j = 0; j < keyframes.Length; j++)
@@ -202,44 +223,45 @@ namespace BansheeEditor
             tangent = new TangentRef();
 
             float nearestDistance = float.MaxValue;
-            for (int i = 0; i < curveInfos.Length; i++)
+            foreach (var entry in selectedKeyframes)
             {
-                EdAnimationCurve curve = curveInfos[i].curve;
+                KeyframeRef keyframeRef = entry.keyframeRef;
+                if (keyframeRef.curveIdx < 0 || keyframeRef.curveIdx >= curveInfos.Length)
+                    continue;
+
+                AnimationCurve curve = curveInfos[keyframeRef.curveIdx].curve;
+                if (keyframeRef.keyIdx < 0 || keyframeRef.keyIdx >= curve.KeyFrames.Length)
+                    continue;
+
                 KeyFrame[] keyframes = curve.KeyFrames;
+                TangentMode tangentMode = entry.tangentMode;
 
-                for (int j = 0; j < keyframes.Length; j++)
+                if (IsTangentDisplayed(tangentMode, TangentType.In))
                 {
-                    if (!IsSelected(i, j))
-                        continue;
+                    Vector2I tangentCoords = GetTangentPosition(keyframes[keyframeRef.keyIdx], TangentType.In);
 
-                    TangentMode tangentMode = curve.TangentModes[j];
-
-                    if (IsTangentDisplayed(tangentMode, TangentType.In))
+                    float distanceToHandle = Vector2I.Distance(pixelCoords, tangentCoords);
+                    if (distanceToHandle < nearestDistance)
                     {
-                        Vector2I tangentCoords = GetTangentPosition(keyframes[j], TangentType.In);
+                        nearestDistance = distanceToHandle;
+                        tangent.keyframeRef.keyIdx = keyframeRef.keyIdx;
+                        tangent.keyframeRef.curveIdx = keyframeRef.curveIdx;
+                        tangent.type = TangentType.In;
+                    }
+;
+                }
 
-                        float distanceToHandle = Vector2I.Distance(pixelCoords, tangentCoords);
-                        if (distanceToHandle < nearestDistance)
-                        {
-                            nearestDistance = distanceToHandle;
-                            tangent.keyframeRef.keyIdx = j;
-                            tangent.keyframeRef.curveIdx = i;
-                            tangent.type = TangentType.In;
-                        }
-;                    }
+                if (IsTangentDisplayed(tangentMode, TangentType.Out))
+                {
+                    Vector2I tangentCoords = GetTangentPosition(keyframes[keyframeRef.keyIdx], TangentType.Out);
 
-                    if (IsTangentDisplayed(tangentMode, TangentType.Out))
+                    float distanceToHandle = Vector2I.Distance(pixelCoords, tangentCoords);
+                    if (distanceToHandle < nearestDistance)
                     {
-                        Vector2I tangentCoords = GetTangentPosition(keyframes[j], TangentType.Out);
-
-                        float distanceToHandle = Vector2I.Distance(pixelCoords, tangentCoords);
-                        if (distanceToHandle < nearestDistance)
-                        {
-                            nearestDistance = distanceToHandle;
-                            tangent.keyframeRef.keyIdx = j;
-                            tangent.keyframeRef.curveIdx = i;
-                            tangent.type = TangentType.Out;
-                        }
+                        nearestDistance = distanceToHandle;
+                        tangent.keyframeRef.keyIdx = keyframeRef.keyIdx;
+                        tangent.keyframeRef.curveIdx = keyframeRef.curveIdx;
+                        tangent.type = TangentType.Out;
                     }
                 }
             }
@@ -561,14 +583,14 @@ namespace BansheeEditor
             bool drawMarkers = drawOptions.HasFlag(CurveDrawOptions.DrawMarkers);
             if(drawMarkers)
             {
-                tickHandler.SetRange(rangeOffset, rangeOffset + GetRange(true), drawableWidth + PADDING);
+                tickHandler.SetRange(rangeOffset, rangeOffset + GetRange(true), (uint)(drawableWidth + PADDING));
 
                 // Draw vertical frame markers
-                int numTickLevels = tickHandler.NumLevels;
+                int numTickLevels = (int)tickHandler.NumLevels;
                 for (int i = numTickLevels - 1; i >= 0; i--)
                 {
                     float[] ticks = tickHandler.GetTicks(i);
-                    float strength = tickHandler.GetLevelStrength(i);
+                    float strength = tickHandler.GetLevelStrength((uint)i);
 
                     for (int j = 0; j < ticks.Length; j++)
                     {
@@ -588,7 +610,7 @@ namespace BansheeEditor
             int curvesToDraw = curveInfos.Length;
             if (drawRange && curveInfos.Length >= 2)
             {
-                EdAnimationCurve[] curves = {curveInfos[0].curve, curveInfos[1].curve};
+                AnimationCurve[] curves = {curveInfos[0].curve, curveInfos[1].curve};
 
                 DrawCurveRange(curves, new Color(1.0f, 0.0f, 0.0f, 0.3f));
                 curvesToDraw = 2;
@@ -610,13 +632,27 @@ namespace BansheeEditor
                         bool isSelected = IsSelected(i, j);
 
                         DrawKeyframe(keyframes[j].time, keyframes[j].value, isSelected);
-
-                        if (isSelected)
-                            DrawTangents(keyframes[j], curveInfos[i].curve.TangentModes[j]);
                     }
                 }
             }
 
+            // Draw tangents
+            foreach (var entry in selectedKeyframes)
+            {
+                KeyframeRef keyframeRef = entry.keyframeRef;
+                if (keyframeRef.curveIdx < 0 || keyframeRef.curveIdx >= curveInfos.Length)
+                    continue;
+
+                AnimationCurve curve = curveInfos[keyframeRef.curveIdx].curve;
+                if (keyframeRef.keyIdx < 0 || keyframeRef.keyIdx >= curve.KeyFrames.Length)
+                    continue;
+
+                KeyFrame[] keyframes = curve.KeyFrames;
+                TangentMode tangentMode = entry.tangentMode;
+
+                DrawTangents(keyframes[keyframeRef.keyIdx], tangentMode);
+            }
+
             // Draw selected frame marker
             if (drawMarkers && markedFrameIdx != -1)
                 DrawFrameMarker(GetTimeForFrame(markedFrameIdx), Color.BansheeOrange, true);
@@ -630,16 +666,13 @@ namespace BansheeEditor
         /// <returns>True if selected, false otherwise.</returns>
         private bool IsSelected(int curveIdx, int keyIdx)
         {
-            if (selectedKeyframes == null)
-                return false;
-
-            if (curveIdx < 0 || curveIdx >= selectedKeyframes.Length)
-                return false;
-
-            if (keyIdx < 0 || keyIdx >= selectedKeyframes[curveIdx].Length)
-                return false;
+            foreach (var entry in selectedKeyframes)
+            {
+                if (entry.keyframeRef.curveIdx == curveIdx && entry.keyframeRef.keyIdx == keyIdx)
+                    return true;
+            }
 
-            return selectedKeyframes[curveIdx][keyIdx];
+            return false;
         }
 
         /// <summary>
@@ -647,7 +680,7 @@ namespace BansheeEditor
         /// </summary>
         /// <param name="curve">Curve to draw within the currently set range. </param>
         /// <param name="color">Color to draw the curve with.</param>
-        private void DrawCurve(EdAnimationCurve curve, Color color)
+        private void DrawCurve(AnimationCurve curve, Color color)
         {
             float range = GetRange(true);
             float lengthPerPixel = range / drawableWidth;
@@ -755,7 +788,7 @@ namespace BansheeEditor
         /// </summary>
         /// <param name="curves">Curves to draw within the currently set range.</param>
         /// <param name="color">Color to draw the area with.</param>
-        private void DrawCurveRange(EdAnimationCurve[] curves, Color color)
+        private void DrawCurveRange(AnimationCurve[] curves, Color color)
         {
             float range = GetRange(true);
 
@@ -956,42 +989,5 @@ namespace BansheeEditor
         }
     }
 
-    /// <summary>
-    /// Information necessary to draw a curve.
-    /// </summary>
-    public struct CurveDrawInfo
-    {
-        public CurveDrawInfo(EdAnimationCurve curve, Color color)
-        {
-            this.curve = curve;
-            this.color = color;
-        }
-
-        public EdAnimationCurve curve;
-        public Color color;
-    }
-
-    /// <summary>
-    /// Controls which elements should a <see cref="GUICurveDrawing"/> object draw.
-    /// </summary>
-    [Flags]
-    internal enum CurveDrawOptions
-    {
-        /// <summary>
-        /// Draws markers at specific time intervals.
-        /// </summary>
-        DrawMarkers = 1 << 0,
-
-        /// <summary>
-        /// Draws elements representing individual keyframes.
-        /// </summary>
-        DrawKeyframes = 1 << 1,
-
-        /// <summary>
-        /// Draws curves and the area between them. Only relevant if only two curves are provided for drawing.
-        /// </summary>
-        DrawRange = 1 << 2
-    }
-
     /** }@ */
 }

+ 68 - 15
Source/Scripting/MBansheeEditor/Windows/Animation/GUICurveEditor.cs

@@ -91,7 +91,7 @@ namespace BansheeEditor
         private ContextMenu eventContextMenu;
         private Vector2I contextClickPosition;
 
-        private CurveDrawInfo[] curveInfos = new CurveDrawInfo[0];
+        private EdCurveDrawInfo[] curveInfos = new EdCurveDrawInfo[0];
         private bool disableCurveEdit = false;
 
         private float xRange = 60.0f;
@@ -339,7 +339,7 @@ namespace BansheeEditor
             drawingPanel.SetPosition(0, TIMELINE_HEIGHT + eventsHeaderHeight + VERT_PADDING);
 
             guiCurveDrawing = new GUICurveDrawing(drawingPanel, this.width, this.height - TIMELINE_HEIGHT -
-                eventsHeaderHeight - VERT_PADDING * 2, curveInfos, drawOptions);
+                eventsHeaderHeight - VERT_PADDING * 2, GetPlainCurveDrawInfos(), drawOptions);
             guiCurveDrawing.SetRange(60.0f, 20.0f);
 
             GUIPanel sidebarPanel = mainPanel.AddPanel(-10);
@@ -359,10 +359,10 @@ namespace BansheeEditor
         /// Change the set of curves to display.
         /// </summary>
         /// <param name="curveInfos">New set of curves to draw on the GUI element.</param>
-        public void SetCurves(CurveDrawInfo[] curveInfos)
+        public void SetCurves(EdCurveDrawInfo[] curveInfos)
         {
             this.curveInfos = curveInfos;
-            guiCurveDrawing.SetCurves(curveInfos);
+            guiCurveDrawing.SetCurves(GetPlainCurveDrawInfos());
 
             Redraw();
         }
@@ -471,7 +471,7 @@ namespace BansheeEditor
                 ShowReadOnlyMessage();
 
             OnCurveModified?.Invoke();
-            guiCurveDrawing.Rebuild();
+            RefreshCurveDrawing();
             UpdateEventsGUI();
         }
 
@@ -532,6 +532,15 @@ namespace BansheeEditor
                 guiEvents.Rebuild();
         }
 
+        /// <summary>
+        /// Updates the curve drawing. Should be called after animation curves change.
+        /// </summary>
+        private void RefreshCurveDrawing()
+        {
+            guiCurveDrawing.SetCurves(GetPlainCurveDrawInfos());
+            guiCurveDrawing.Rebuild();
+        }
+
         /// <summary>
         /// Changes the tangent mode for all currently selected keyframes.
         /// </summary>
@@ -552,7 +561,12 @@ namespace BansheeEditor
                 foreach (var keyframeIdx in selectedEntry.keyIndices)
                 {
                     if (mode == TangentMode.Auto || mode == TangentMode.Free)
+                    {
                         curve.SetTangentMode(keyframeIdx, mode);
+
+                        // Refresh tangent display
+                        guiCurveDrawing.SelectKeyframe(new KeyframeRef(selectedEntry.curveIdx, keyframeIdx), mode, true);
+                    }
                     else
                     {
                         TangentMode newMode = curve.TangentModes[keyframeIdx];
@@ -577,6 +591,9 @@ namespace BansheeEditor
                         }
 
                         curve.SetTangentMode(keyframeIdx, newMode);
+
+                        // Refresh tangent display
+                        guiCurveDrawing.SelectKeyframe(new KeyframeRef(selectedEntry.curveIdx, keyframeIdx), newMode, true);
                     }
                 }
 
@@ -584,7 +601,7 @@ namespace BansheeEditor
             }
 
             OnCurveModified?.Invoke();
-            guiCurveDrawing.Rebuild();
+            RefreshCurveDrawing();
         }
 
         /// <summary>
@@ -612,7 +629,7 @@ namespace BansheeEditor
                 ShowReadOnlyMessage();
 
             OnCurveModified?.Invoke();
-            guiCurveDrawing.Rebuild();
+            RefreshCurveDrawing();
             UpdateEventsGUI();
         }
 
@@ -641,7 +658,7 @@ namespace BansheeEditor
                     ShowReadOnlyMessage();
 
                 OnCurveModified?.Invoke();
-                guiCurveDrawing.Rebuild();
+                RefreshCurveDrawing();
                 UpdateEventsGUI();
             }
         }
@@ -702,7 +719,7 @@ namespace BansheeEditor
             ClearSelection();
 
             OnCurveModified?.Invoke();
-            guiCurveDrawing.Rebuild();
+            RefreshCurveDrawing();
             UpdateEventsGUI();
         }
 
@@ -745,7 +762,15 @@ namespace BansheeEditor
         /// <param name="keyframeRef">Keyframe to select.</param>
         private void SelectKeyframe(KeyframeRef keyframeRef)
         {
-            guiCurveDrawing.SelectKeyframe(keyframeRef, true);
+            TangentMode tangentMode = TangentMode.Auto;
+            if (keyframeRef.curveIdx >= 0 && keyframeRef.curveIdx < curveInfos.Length)
+            {
+                EdAnimationCurve curve = curveInfos[keyframeRef.curveIdx].curve;
+                if (keyframeRef.keyIdx >= 0 && keyframeRef.keyIdx < curve.TangentModes.Length)
+                    tangentMode = curve.TangentModes[keyframeRef.keyIdx];
+            }
+
+            guiCurveDrawing.SelectKeyframe(keyframeRef, tangentMode, true);
 
             if (!IsSelected(keyframeRef))
             {
@@ -822,7 +847,7 @@ namespace BansheeEditor
                 curve.UpdateKeyframe(keyIndex, x.time, x.value);
                 curve.Apply();
 
-                guiCurveDrawing.Rebuild();
+                RefreshCurveDrawing();
             },
             x =>
             {
@@ -1182,8 +1207,8 @@ namespace BansheeEditor
                             }
 
                             isModifiedDuringDrag = true;
-                            guiCurveDrawing.Rebuild();
 
+                            RefreshCurveDrawing();
                             UpdateEventsGUI();
                         }
                         else if (isMousePressedOverTangent && !disableCurveEdit)
@@ -1224,7 +1249,7 @@ namespace BansheeEditor
                                 curve.Apply();
 
                                 isModifiedDuringDrag = true;
-                                guiCurveDrawing.Rebuild();
+                                RefreshCurveDrawing();
                             }
                         }
                     }
@@ -1496,7 +1521,7 @@ namespace BansheeEditor
         internal void CenterAndResize(bool resetTime)
         {
             Vector2 offset, range;
-            GUICurveDrawing.GetOptimalRangeAndOffset(curveInfos, out offset, out range);
+            GUICurveDrawing.GetOptimalRangeAndOffset(GetPlainCurveDrawInfos(), out offset, out range);
 
             if (!resetTime)
             {
@@ -1511,6 +1536,19 @@ namespace BansheeEditor
             UpdateScrollBarSize();
         }
 
+        /// <summary>
+        /// Returns a set of current curve draw infos with non-editor curves.
+        /// </summary>
+        /// <returns>Curve draw infos.</returns>
+        private CurveDrawInfo[] GetPlainCurveDrawInfos()
+        {
+            CurveDrawInfo[] output = new CurveDrawInfo[curveInfos.Length];
+            for(int i = 0; i < curveInfos.Length; i++)
+                output[i] = new CurveDrawInfo(curveInfos[i].curve.Normal, curveInfos[i].color);
+
+            return output;
+        }
+
         /// <summary>
         /// Triggered when the user moves or resizes the horizontal scrollbar.
         /// </summary>
@@ -1543,7 +1581,7 @@ namespace BansheeEditor
         {
             float xMin, xMax;
             float yMin, yMax;
-            GUICurveDrawing.CalculateRange(curveInfos, out xMin, out xMax, out yMin, out yMax);
+            GUICurveDrawing.CalculateRange(GetPlainCurveDrawInfos(), out xMin, out xMax, out yMin, out yMax);
 
             float xRange = xMax;
             float yRange = Math.Max(Math.Abs(yMin), Math.Abs(yMax));
@@ -1588,6 +1626,21 @@ namespace BansheeEditor
         }
 
         #endregion
+    }    
+    
+    /// <summary>
+    /// Information necessary to drawing an editor curve.
+    /// </summary>
+    public struct EdCurveDrawInfo
+    {
+        public EdCurveDrawInfo(EdAnimationCurve curve, Color color)
+        {
+            this.curve = curve;
+            this.color = color;
+        }
+
+        public EdAnimationCurve curve;
+        public Color color;
     }
 
     /// <summary>

+ 0 - 199
Source/Scripting/MBansheeEditor/Windows/Animation/GUIGraphTicks.cs

@@ -1,199 +0,0 @@
-//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
-//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
-using System;
-using System.Text;
-using BansheeEngine;
-
-namespace BansheeEditor
-{
-    /** @addtogroup AnimationEditor
-     *  @{
-     */
-
-    /// <summary>
-    /// Determines how should ticks reported by <see cref="GUIGraphTicks"/> be distributed. 
-    /// </summary>
-    internal enum GUITickStepType
-    {
-        /// <summary>
-        /// Ticks represent time values (Multiples of 60).
-        /// </summary>
-        Time,
-        /// <summary>
-        /// Ticks represent generic values (Multiples of 10).
-        /// </summary>
-        Generic
-    }
-
-    /// <summary>
-    /// Generates a set of locations that can be used for rendering ticks on a graph. As input the class takes valid range,
-    /// size of the area the ticks will be displayed on, type of ticks and minimum/maximum spacing and outputs a set of
-    /// coordinates along which ticks should be positioned. Ticks are reported as multiple separate levels with different
-    /// strengths, depending on how close their distribution is to the upper valid range.
-    /// </summary>
-    internal class GUIGraphTicks
-    {
-        private int pixelRange = 100;
-        private float valueRangeStart = 0.0f;
-        private float valueRangeEnd = 1.0f;
-
-        private int minTickSpacingPx = 5;
-        private int maxTickSpacingPx = 30;
-
-        private float[] validSteps = new [] { 1.0f };
-        private float[] levelStrengths = new[] { 1.0f };
-        private int numLevels = 1;
-        private int maxLevel = 0;
-
-        public int NumLevels { get { return numLevels; } }
-
-        /// <summary>
-        /// Contructs a new tick generating object.
-        /// </summary>
-        /// <param name="stepType">Determines how will ticks be distributed.</param>
-        internal GUIGraphTicks(GUITickStepType stepType = GUITickStepType.Generic)
-        {
-            if(stepType == GUITickStepType.Generic)
-                SetGenericSteps();
-            else
-                SetTimeSteps();
-
-            Rebuild();
-        }
-
-        /// <summary>
-        /// Sets the range which ticks are to be displayed for, and the range along which the ticks will be displayed.
-        /// </summary>
-        /// <param name="valueRangeStart">Start of the range the ticks are to display.</param>
-        /// <param name="valueRangeEnd">End of the range the ticks are to display.</param>
-        /// <param name="pixelRange">Width or height on which the ticks will be rendered. In pixels.</param>
-        internal void SetRange(float valueRangeStart, float valueRangeEnd, int pixelRange)
-        {
-            this.valueRangeStart = valueRangeStart;
-            this.valueRangeEnd = valueRangeEnd;
-            this.pixelRange = pixelRange;
-
-            Rebuild();
-        }
-
-        /// <summary>
-        /// Sets valid spacing between two ticks. Tick strength will be determined by how far away are they from either
-        /// end of this range.
-        /// </summary>
-        /// <param name="minPx">Minimum spacing between two ticks, in pixels.</param>
-        /// <param name="maxPx">Maximum spacing between two ticks, in pixels.</param>
-        internal void SetTickSpacing(int minPx, int maxPx)
-        {
-            minTickSpacingPx = minPx;
-            maxTickSpacingPx = maxPx;
-
-            Rebuild();
-        }
-
-        /// <summary>
-        /// Returns the strength of a particular tick level. Levels are ordered in descending order of strength (level 0 is
-        /// the strongest).
-        /// </summary>
-        /// <param name="level">Level for which to retrieve the strength. Must not be larger than 
-        ///                     <see cref="NumLevels"/> - 1.</param>
-        /// <returns>Strength of the ticks at this level, in range [0, 1].</returns>
-        internal float GetLevelStrength(int level)
-        {
-            if (level < 0 || level >= numLevels)
-                return 0.0f;
-
-            return MathEx.Clamp01(levelStrengths[maxLevel + level]);
-        }
-
-        /// <summary>
-        /// Returns positions of all ticks of the provided level. The ticks will be within the range provided to
-        /// <see cref="SetRange"/>.
-        /// </summary>
-        /// <param name="level">Level for which to retrieve the positions. Must not be larger than 
-        ///                     <see cref="NumLevels"/> - 1.</param>
-        /// <returns>Positions of all ticks of the provided level.</returns>
-        internal float[] GetTicks(int level)
-        {
-            if (level < 0 || level >= numLevels)
-                return new float[0];
-
-            float step = validSteps[maxLevel + level];
-
-            // Round up and down so we get one extra tick on either side (outside of value range)
-            // (Useful when rendering text above the ticks, so the text doesn't just pop-in when the tick appears, instead
-            // it is slowly clipped-in.)
-            int startTick = MathEx.CeilToInt(valueRangeStart / step);
-            int endTick = MathEx.FloorToInt(valueRangeEnd / step);
-
-            int numTicks = endTick - startTick + 1;
-
-            float[] ticks = new float[numTicks];
-            for (int i = startTick; i <= endTick; i++)
-                ticks[i - startTick] = i*step;
-
-            return ticks;
-        }
-
-        /// <summary>
-        /// Rebuilds the tick positions and strengths after some relevant parameter changes.
-        /// </summary>
-        private void Rebuild()
-        {
-            levelStrengths = new float[validSteps.Length];
-            maxLevel = 0;
-
-            float valueRange = valueRangeEnd - valueRangeStart;
-            int tickSpacing = maxTickSpacingPx - minTickSpacingPx;
-            int i = 0;
-            for (; i < validSteps.Length; i++)
-            {
-                float tickSpacingPx = (validSteps[i]/valueRange) * pixelRange;
-                levelStrengths[i] = (tickSpacingPx - minTickSpacingPx)/tickSpacing;
-
-                if (levelStrengths[i] > 1.0f)
-                    maxLevel = i;
-                else if (levelStrengths[i] < 0.0f)
-                    break;
-            }
-
-            if (i > 0)
-                numLevels = i - maxLevel;
-            else
-                numLevels = 0;
-        }
-
-        /// <summary>
-        /// Sets tick steps corresponding to time values. This will split the ticks into intervals relevant for displaying
-        /// times.
-        /// </summary>
-        private void SetTimeSteps()
-        {
-            validSteps = new float[]
-            {
-                3600.0f, 1800.0f, 600.0f, 300.0f,
-                60.0f, 30.0f, 10.0f, 5.0f,
-                1.0f, 0.5f, 0.25f, 0.1f, 0.05f, 0.01f
-            };
-        }
-
-        /// <summary>
-        /// Sets tick steps corresponding to generic values (as opposed to displaying time values).
-        /// </summary>
-        private void SetGenericSteps()
-        {
-            float minStep = 0.0000001f;
-            int numSteps = 15;
-
-            validSteps = new float[15 * 2];
-            for (int i = numSteps - 1; i >= 0; i--)
-            {
-                validSteps[i * 2 + 1] = minStep;
-                validSteps[i * 2 + 0] = minStep * 5;
-
-                minStep *= 10.0f;
-            }
-        }
-    }
-
-    /** @} */
-}

+ 3 - 3
Source/Scripting/MBansheeEditor/Windows/Animation/GUIGraphTime.cs

@@ -90,17 +90,17 @@ namespace BansheeEditor
         public override void Rebuild()
         {
             canvas.Clear();
-            tickHandler.SetRange(rangeOffset, rangeOffset + GetRange(true), drawableWidth + PADDING);
+            tickHandler.SetRange(rangeOffset, rangeOffset + GetRange(true), (uint)(drawableWidth + PADDING));
 
             float range = GetRange();
 
-            int numTickLevels = tickHandler.NumLevels;
+            int numTickLevels = (int)tickHandler.NumLevels;
             for (int i = numTickLevels - 1; i >= 0; i--)
             {
                 bool drawText = i == 0;
 
                 float[] ticks = tickHandler.GetTicks(i);
-                float strength = tickHandler.GetLevelStrength(i);
+                float strength = tickHandler.GetLevelStrength((uint)i);
 
                 if (ticks.Length > 0)
                 {

+ 4 - 4
Source/Scripting/MBansheeEditor/Windows/Animation/GUIGraphValues.cs

@@ -55,7 +55,7 @@ namespace BansheeEditor
             canvas.SetWidth(width);
             canvas.SetHeight(height);
 
-            tickHandler.SetRange(rangeStart, rangeEnd, height);
+            tickHandler.SetRange(rangeStart, rangeEnd, (uint)height);
         }
 
         /// <summary>
@@ -75,7 +75,7 @@ namespace BansheeEditor
             rangeStart = start;
             rangeEnd = end;
 
-            tickHandler.SetRange(rangeStart, rangeEnd, height);
+            tickHandler.SetRange(rangeStart, rangeEnd, (uint)height);
         }
 
         /// <summary>
@@ -125,11 +125,11 @@ namespace BansheeEditor
 
             float yOffset = rangeStart + (rangeEnd - rangeStart)*0.5f;
 
-            int numTickLevels = tickHandler.NumLevels;
+            int numTickLevels = (int)tickHandler.NumLevels;
             for (int i = numTickLevels - 1; i >= 0; i--)
             {
                 float[] ticks = tickHandler.GetTicks(i);
-                float strength = tickHandler.GetLevelStrength(i);
+                float strength = tickHandler.GetLevelStrength((uint)i);
                
                 if (ticks.Length > 0)
                 {

+ 11 - 11
Source/Scripting/MBansheeEditor/Windows/AnimationWindow.cs

@@ -1075,16 +1075,16 @@ namespace BansheeEditor
         /// Returns a list of all animation curves that should be displayed in the curve display.
         /// </summary>
         /// <returns>Array of curves to display.</returns>
-        private CurveDrawInfo[] GetDisplayedCurves()
+        private EdCurveDrawInfo[] GetDisplayedCurves()
         {
-            List<CurveDrawInfo> curvesToDisplay = new List<CurveDrawInfo>();
+            List<EdCurveDrawInfo> curvesToDisplay = new List<EdCurveDrawInfo>();
 
             if (clipInfo == null)
                 return curvesToDisplay.ToArray();
 
             for (int i = 0; i < selectedFields.Count; i++)
             {
-                CurveDrawInfo[] curveInfos;
+                EdCurveDrawInfo[] curveInfos;
                 if (TryGetCurve(selectedFields[i], out curveInfos))
                     curvesToDisplay.AddRange(curveInfos);
             }
@@ -1112,7 +1112,7 @@ namespace BansheeEditor
         ///                         be kept as is.</param>
         private void UpdateDisplayedCurves(bool resetTime = false)
         {
-            CurveDrawInfo[] curvesToDisplay = GetDisplayedCurves();
+            EdCurveDrawInfo[] curvesToDisplay = GetDisplayedCurves();
             guiCurveEditor.SetCurves(curvesToDisplay);
             guiCurveEditor.CenterAndResize(resetTime);
         }
@@ -1138,7 +1138,7 @@ namespace BansheeEditor
                         FieldAnimCurves fieldCurves = new FieldAnimCurves();
                         fieldCurves.type = type;
                         fieldCurves.isPropertyCurve = isPropertyCurve;
-                        fieldCurves.curveInfos = new CurveDrawInfo[4];
+                        fieldCurves.curveInfos = new EdCurveDrawInfo[4];
 
                         string[] subPaths = { ".x", ".y", ".z", ".w" };
                         for (int i = 0; i < subPaths.Length; i++)
@@ -1156,7 +1156,7 @@ namespace BansheeEditor
                         FieldAnimCurves fieldCurves = new FieldAnimCurves();
                         fieldCurves.type = type;
                         fieldCurves.isPropertyCurve = isPropertyCurve;
-                        fieldCurves.curveInfos = new CurveDrawInfo[3];
+                        fieldCurves.curveInfos = new EdCurveDrawInfo[3];
 
                         string[] subPaths = { ".x", ".y", ".z" };
                         for (int i = 0; i < subPaths.Length; i++)
@@ -1174,7 +1174,7 @@ namespace BansheeEditor
                         FieldAnimCurves fieldCurves = new FieldAnimCurves();
                         fieldCurves.type = type;
                         fieldCurves.isPropertyCurve = isPropertyCurve;
-                        fieldCurves.curveInfos = new CurveDrawInfo[2];
+                        fieldCurves.curveInfos = new EdCurveDrawInfo[2];
 
                         string[] subPaths = { ".x", ".y" };
                         for (int i = 0; i < subPaths.Length; i++)
@@ -1192,7 +1192,7 @@ namespace BansheeEditor
                         FieldAnimCurves fieldCurves = new FieldAnimCurves();
                         fieldCurves.type = type;
                         fieldCurves.isPropertyCurve = isPropertyCurve;
-                        fieldCurves.curveInfos = new CurveDrawInfo[4];
+                        fieldCurves.curveInfos = new EdCurveDrawInfo[4];
 
                         string[] subPaths = { ".r", ".g", ".b", ".a" };
                         for (int i = 0; i < subPaths.Length; i++)
@@ -1210,7 +1210,7 @@ namespace BansheeEditor
                         FieldAnimCurves fieldCurves = new FieldAnimCurves();
                         fieldCurves.type = type;
                         fieldCurves.isPropertyCurve = isPropertyCurve;
-                        fieldCurves.curveInfos = new CurveDrawInfo[1];
+                        fieldCurves.curveInfos = new EdCurveDrawInfo[1];
 
                         fieldCurves.curveInfos[0].curve = new EdAnimationCurve();
                         selectedFields.Add(path);
@@ -1298,7 +1298,7 @@ namespace BansheeEditor
         /// <param name="curveInfos">One or multiple curves found for the specific path (one field can have multiple curves
         ///                          if it is a complex type, like a vector).</param>
         /// <returns>True if the curve field was found, false otherwise.</returns>
-        private bool TryGetCurve(string path, out CurveDrawInfo[] curveInfos)
+        private bool TryGetCurve(string path, out EdCurveDrawInfo[] curveInfos)
         {
             int index = path.LastIndexOf(".");
             string parentPath;
@@ -1346,7 +1346,7 @@ namespace BansheeEditor
                 }
             }
 
-            curveInfos = new CurveDrawInfo[0];
+            curveInfos = new EdCurveDrawInfo[0];
             return false;
         }
 

+ 4 - 4
Source/Scripting/MBansheeEditor/Windows/CurveEditorWindow.cs

@@ -94,19 +94,19 @@ namespace BansheeEditor
             curveEditor = new GUICurveEditor(editorPanel, 600, 400, false, drawOptions); 
             curveEditor.Redraw();
 
-            CurveDrawInfo[] drawinfo;
+            EdCurveDrawInfo[] drawinfo;
 
             if (curveB != null)
             {
                 drawinfo = new []
                 {
-                    new CurveDrawInfo(curveA, Color.BansheeOrange),
-                    new CurveDrawInfo(curveB, Color.Green),
+                    new EdCurveDrawInfo(curveA, Color.BansheeOrange),
+                    new EdCurveDrawInfo(curveB, Color.Green),
                 };
             }
             else
             {
-                drawinfo = new [] { new CurveDrawInfo(curveA, Color.BansheeOrange), };
+                drawinfo = new [] { new EdCurveDrawInfo(curveA, Color.BansheeOrange), };
             }
 
             curveEditor.SetCurves(drawinfo);

+ 13 - 0
Source/Scripting/MBansheeEngine/Generated/AnimationUtility.generated.cs

@@ -38,6 +38,17 @@ namespace BansheeEngine
 			return Internal_combineCurve(curveComponents);
 		}
 
+		/// <summary>Calculates the total range covered by a set of curves.</summary>
+		/// <param name="curves">Curves to calculate range for.</param>
+		/// <param name="xMin">Minimum time value present in the curves.</param>
+		/// <param name="xMax">Maximum time value present in the curves.</param>
+		/// <param name="yMin">Minimum curve value present in the curves.</param>
+		/// <param name="yMax">Maximum curve value present in the curves.</param>
+		public static void CalculateRange(AnimationCurve[] curves, out float xMin, out float xMax, out float yMin, out float yMax)
+		{
+			Internal_calculateRange(curves, out xMin, out xMax, out yMin, out yMax);
+		}
+
 		[MethodImpl(MethodImplOptions.InternalCall)]
 		private static extern QuaternionCurve Internal_eulerToQuaternionCurve(Vector3Curve eulerCurve);
 		[MethodImpl(MethodImplOptions.InternalCall)]
@@ -46,6 +57,8 @@ namespace BansheeEngine
 		private static extern AnimationCurve[] Internal_splitCurve(Vector3Curve compoundCurve);
 		[MethodImpl(MethodImplOptions.InternalCall)]
 		private static extern Vector3Curve Internal_combineCurve(AnimationCurve[] curveComponents);
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		private static extern void Internal_calculateRange(AnimationCurve[] curves, out float xMin, out float xMax, out float yMin, out float yMax);
 	}
 
 	/** @} */

+ 23 - 0
Source/Scripting/MBansheeEngine/Generated/CurveDrawOption.generated.cs

@@ -0,0 +1,23 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace BansheeEngine
+{
+	/** @addtogroup GUIEditor
+	 *  @{
+	 */
+
+	/// <summary>Controls which elements should a GUICurves object draw.</summary>
+	public enum CurveDrawOptions
+	{
+		/// <summary>Draws markers at specific time intervals.</summary>
+		DrawMarkers = 1,
+		/// <summary>Draws elements representing individual keyframes.</summary>
+		DrawKeyframes = 2,
+		/// <summary>Draws curves and the area between them. Only relevant if only two curves are provided for drawing.</summary>
+		DrawRange = 4
+	}
+
+	/** @} */
+}

+ 21 - 0
Source/Scripting/MBansheeEngine/Generated/GUITickStepType.generated.cs

@@ -0,0 +1,21 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace BansheeEngine
+{
+	/** @addtogroup GUIEditor
+	 *  @{
+	 */
+
+	/// <summary>Determines how should ticks reported by <see cref="GUIGraphTicks"/> be distributed.</summary>
+	public enum GUITickStepType
+	{
+		/// <summary>Ticks represent time values (Multiples of 60).</summary>
+		Time = 0,
+		/// <summary>Ticks represent generic values (Multiples of 10).</summary>
+		Generic = 1
+	}
+
+	/** @} */
+}

+ 35 - 0
Source/Scripting/MBansheeEngine/Generated/KeyframeRef.generated.cs

@@ -0,0 +1,35 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace BansheeEngine
+{
+	/** @addtogroup Animation
+	 *  @{
+	 */
+
+	[StructLayout(LayoutKind.Sequential), SerializeObject]
+	public partial struct KeyframeRef
+	{
+		/// <summary>Initializes the struct with default values.</summary>
+		public static KeyframeRef Default()
+		{
+			KeyframeRef value = new KeyframeRef();
+			value.curveIdx = 0;
+			value.keyIdx = 0;
+
+			return value;
+		}
+
+		public KeyframeRef(int curveIdx, int keyIdx)
+		{
+			this.curveIdx = curveIdx;
+			this.keyIdx = keyIdx;
+		}
+
+		public int curveIdx;
+		public int keyIdx;
+	}
+
+	/** @} */
+}

+ 44 - 0
Source/Scripting/MBansheeEngine/Generated/TangentModeBits.generated.cs

@@ -0,0 +1,44 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace BansheeEngine
+{
+	/** @addtogroup Animation
+	 *  @{
+	 */
+
+	/// <summary>
+	/// Flags that are used for describing how are tangents calculated for a specific keyframe in an animation curve.  Modes 
+	/// for "in" and "out" tangents can be combined.
+	/// </summary>
+	public enum TangentMode
+	{
+		/// <summary>
+		/// Tangent is infinite, ensuring there is a instantaneus jump between current and next keyframe value.
+		/// </summary>
+		OutStep = 514,
+		/// <summary>Both tangents are calculated automatically based on the two surrounding keyframes.</summary>
+		Auto = 0,
+		/// <summary>Left tangent is calculated automatically based on the two surrounding keyframes.</summary>
+		InAuto = 5,
+		/// <summary>Tangent is calculated automatically based on the next keyframe.</summary>
+		OutLinear = 258,
+		/// <summary>Left tangent is manually adjusted by the user.</summary>
+		InFree = 9,
+		/// <summary>
+		/// Tangent is infinite, ensuring there is a instantaneus jump between previous and current keyframe value.
+		/// </summary>
+		InStep = 33,
+		/// <summary>Tangent is calculated automatically based on the previous keyframe.</summary>
+		InLinear = 17,
+		/// <summary>Right tangent is manually adjusted by the user.</summary>
+		OutFree = 130,
+		/// <summary>Right tangents are calculated automatically based on the two surrounding keyframes.</summary>
+		OutAuto = 66,
+		/// <summary>Both tangents are manually adjusted by the user.</summary>
+		Free = 1024
+	}
+
+	/** @} */
+}

+ 38 - 0
Source/Scripting/MBansheeEngine/Generated/TangentRef.generated.cs

@@ -0,0 +1,38 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace BansheeEngine
+{
+	/** @addtogroup Animation
+	 *  @{
+	 */
+
+	/// <summary>
+	/// Structure containing a reference to a keyframe tangent, as a keyframe reference and type of the tangent.
+	/// </summary>
+	[StructLayout(LayoutKind.Sequential), SerializeObject]
+	public partial struct TangentRef
+	{
+		/// <summary>Initializes the struct with default values.</summary>
+		public static TangentRef Default()
+		{
+			TangentRef value = new TangentRef();
+			value.keyframeRef = new KeyframeRef();
+			value.type = TangentType.In;
+
+			return value;
+		}
+
+		public TangentRef(KeyframeRef keyframeRef, TangentType type)
+		{
+			this.keyframeRef = keyframeRef;
+			this.type = type;
+		}
+
+		public KeyframeRef keyframeRef;
+		public TangentType type;
+	}
+
+	/** @} */
+}

+ 19 - 0
Source/Scripting/MBansheeEngine/Generated/TangentType.generated.cs

@@ -0,0 +1,19 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace BansheeEngine
+{
+	/** @addtogroup Animation
+	 *  @{
+	 */
+
+	/// <summary>Type of tangent on a keyframe in an animation curve.</summary>
+	public enum TangentType
+	{
+		In = 1,
+		Out = 2
+	}
+
+	/** @} */
+}

+ 57 - 0
Source/Scripting/SBansheeEditor/Generated/BsScriptCurveDrawInfo.generated.cpp

@@ -0,0 +1,57 @@
+#include "BsScriptCurveDrawInfo.generated.h"
+#include "BsMonoMethod.h"
+#include "BsMonoClass.h"
+#include "BsMonoUtil.h"
+#include "Wrappers/BsScriptColor.h"
+#include "BsScriptTAnimationCurve.generated.h"
+#include "BsScriptTAnimationCurve.generated.h"
+#include "Wrappers/BsScriptColor.h"
+
+namespace bs
+{
+	ScriptCurveDrawInfo::ScriptCurveDrawInfo(MonoObject* managedInstance)
+		:ScriptObject(managedInstance)
+	{ }
+
+	void ScriptCurveDrawInfo::initRuntimeData()
+	{ }
+
+	MonoObject*ScriptCurveDrawInfo::box(const __CurveDrawInfoInterop& value)
+	{
+		return MonoUtil::box(metaData.scriptClass->_getInternalClass(), (void*)&value);
+	}
+
+	__CurveDrawInfoInterop ScriptCurveDrawInfo::unbox(MonoObject* value)
+	{
+		return *(__CurveDrawInfoInterop*)MonoUtil::unbox(value);
+	}
+
+	CurveDrawInfo ScriptCurveDrawInfo::fromInterop(const __CurveDrawInfoInterop& value)
+	{
+		CurveDrawInfo output;
+		SPtr<TAnimationCurve<float>> tmpcurve;
+		ScriptTAnimationCurvefloat* scriptcurve;
+		scriptcurve = ScriptTAnimationCurvefloat::toNative(value.curve);
+		if(scriptcurve != nullptr)
+			tmpcurve = scriptcurve->getInternal();
+		if(tmpcurve != nullptr)
+		output.curve = *tmpcurve;
+		output.color = value.color;
+
+		return output;
+	}
+
+	__CurveDrawInfoInterop ScriptCurveDrawInfo::toInterop(const CurveDrawInfo& value)
+	{
+		__CurveDrawInfoInterop output;
+		MonoObject* tmpcurve;
+		SPtr<TAnimationCurve<float>> tmpcurvecopy;
+		tmpcurvecopy = bs_shared_ptr_new<TAnimationCurve<float>>(value.curve);
+		tmpcurve = ScriptTAnimationCurvefloat::create(tmpcurvecopy);
+		output.curve = tmpcurve;
+		output.color = value.color;
+
+		return output;
+	}
+
+}

+ 31 - 0
Source/Scripting/SBansheeEditor/Generated/BsScriptCurveDrawInfo.generated.h

@@ -0,0 +1,31 @@
+#pragma once
+
+#include "BsScriptEditorPrerequisites.h"
+#include "BsScriptObject.h"
+#include "../../../EditorCore/GUI/BsGUICurves.h"
+#include "../../../bsf/Source/Foundation/bsfCore/Animation/BsAnimationCurve.h"
+#include "Image/BsColor.h"
+
+namespace bs
+{
+	struct __CurveDrawInfoInterop
+	{
+		MonoObject* curve;
+		Color color;
+	};
+
+	class BS_SCR_BED_EXPORT ScriptCurveDrawInfo : public ScriptObject<ScriptCurveDrawInfo>
+	{
+	public:
+		SCRIPT_OBJ(EDITOR_ASSEMBLY, "BansheeEditor", "CurveDrawInfo")
+
+		static MonoObject* box(const __CurveDrawInfoInterop& value);
+		static __CurveDrawInfoInterop unbox(MonoObject* value);
+		static CurveDrawInfo fromInterop(const __CurveDrawInfoInterop& value);
+		static __CurveDrawInfoInterop toInterop(const CurveDrawInfo& value);
+
+	private:
+		ScriptCurveDrawInfo(MonoObject* managedInstance);
+
+	};
+}

+ 175 - 0
Source/Scripting/SBansheeEditor/Generated/BsScriptGUICurves.generated.cpp

@@ -0,0 +1,175 @@
+#include "BsScriptGUICurves.generated.h"
+#include "BsMonoMethod.h"
+#include "BsMonoClass.h"
+#include "BsMonoUtil.h"
+#include "../../../EditorCore/GUI/BsGUICurves.h"
+#include "BsScriptKeyframeRef.generated.h"
+#include "BsScriptCurveDrawInfo.generated.h"
+#include "../../../EditorCore/GUI/BsGUICurves.h"
+#include "Wrappers/BsScriptVector.h"
+#include "BsScriptTangentRef.generated.h"
+#include "Wrappers/BsScriptVector2I.h"
+
+namespace bs
+{
+	ScriptGUICurves::onClickedThunkDef ScriptGUICurves::onClickedThunk; 
+
+	ScriptGUICurves::ScriptGUICurves(MonoObject* managedInstance, GUICurves* value)
+		:TScriptGUIElement(managedInstance, value)
+	{
+		value->onClicked.connect(std::bind(&ScriptGUICurves::onClicked, this));
+	}
+
+	void ScriptGUICurves::initRuntimeData()
+	{
+		metaData.scriptClass->addInternalCall("Internal_setCurves", (void*)&ScriptGUICurves::Internal_setCurves);
+		metaData.scriptClass->addInternalCall("Internal_getCurves", (void*)&ScriptGUICurves::Internal_getCurves);
+		metaData.scriptClass->addInternalCall("Internal_setRange", (void*)&ScriptGUICurves::Internal_setRange);
+		metaData.scriptClass->addInternalCall("Internal_setOffset", (void*)&ScriptGUICurves::Internal_setOffset);
+		metaData.scriptClass->addInternalCall("Internal_centerAndZoom", (void*)&ScriptGUICurves::Internal_centerAndZoom);
+		metaData.scriptClass->addInternalCall("Internal_pixelToCurveSpace", (void*)&ScriptGUICurves::Internal_pixelToCurveSpace);
+		metaData.scriptClass->addInternalCall("Internal_curveToPixelSpace", (void*)&ScriptGUICurves::Internal_curveToPixelSpace);
+		metaData.scriptClass->addInternalCall("Internal_findCurve", (void*)&ScriptGUICurves::Internal_findCurve);
+		metaData.scriptClass->addInternalCall("Internal_findKeyFrame", (void*)&ScriptGUICurves::Internal_findKeyFrame);
+		metaData.scriptClass->addInternalCall("Internal_findTangent", (void*)&ScriptGUICurves::Internal_findTangent);
+		metaData.scriptClass->addInternalCall("Internal_selectKeyframe", (void*)&ScriptGUICurves::Internal_selectKeyframe);
+		metaData.scriptClass->addInternalCall("Internal_clearSelectedKeyframes", (void*)&ScriptGUICurves::Internal_clearSelectedKeyframes);
+		metaData.scriptClass->addInternalCall("Internal_create", (void*)&ScriptGUICurves::Internal_create);
+		metaData.scriptClass->addInternalCall("Internal_create0", (void*)&ScriptGUICurves::Internal_create0);
+
+		onClickedThunk = (onClickedThunkDef)metaData.scriptClass->getMethodExact("Internal_onClicked", "")->getThunk();
+	}
+
+	void ScriptGUICurves::onClicked()
+	{
+		MonoUtil::invokeThunk(onClickedThunk, getManagedInstance());
+	}
+	void ScriptGUICurves::Internal_setCurves(ScriptGUICurves* thisPtr, MonoArray* curves)
+	{
+		Vector<CurveDrawInfo> veccurves;
+		if(curves != nullptr)
+		{
+			ScriptArray arraycurves(curves);
+			veccurves.resize(arraycurves.size());
+			for(int i = 0; i < (int)arraycurves.size(); i++)
+			{
+				veccurves[i] = ScriptCurveDrawInfo::fromInterop(arraycurves.get<__CurveDrawInfoInterop>(i));
+			}
+		}
+		static_cast<GUICurves*>(thisPtr->getGUIElement())->setCurves(veccurves);
+	}
+
+	MonoArray* ScriptGUICurves::Internal_getCurves(ScriptGUICurves* thisPtr)
+	{
+		Vector<CurveDrawInfo> vec__output;
+		vec__output = static_cast<GUICurves*>(thisPtr->getGUIElement())->getCurves();
+
+		MonoArray* __output;
+		int arraySize__output = (int)vec__output.size();
+		ScriptArray array__output = ScriptArray::create<ScriptCurveDrawInfo>(arraySize__output);
+		for(int i = 0; i < arraySize__output; i++)
+		{
+			array__output.set(i, ScriptCurveDrawInfo::toInterop(vec__output[i]));
+		}
+		__output = array__output.getInternal();
+
+		return __output;
+	}
+
+	void ScriptGUICurves::Internal_setRange(ScriptGUICurves* thisPtr, float xRange, float yRange)
+	{
+		static_cast<GUICurves*>(thisPtr->getGUIElement())->setRange(xRange, yRange);
+	}
+
+	void ScriptGUICurves::Internal_setOffset(ScriptGUICurves* thisPtr, Vector2* offset)
+	{
+		static_cast<GUICurves*>(thisPtr->getGUIElement())->setOffset(*offset);
+	}
+
+	void ScriptGUICurves::Internal_centerAndZoom(ScriptGUICurves* thisPtr)
+	{
+		static_cast<GUICurves*>(thisPtr->getGUIElement())->centerAndZoom();
+	}
+
+	bool ScriptGUICurves::Internal_pixelToCurveSpace(ScriptGUICurves* thisPtr, Vector2I* pixelCoords, Vector2* curveCoords, bool padding)
+	{
+		bool tmp__output;
+		tmp__output = static_cast<GUICurves*>(thisPtr->getGUIElement())->pixelToCurveSpace(*pixelCoords, *curveCoords, padding);
+
+		bool __output;
+		__output = tmp__output;
+
+		return __output;
+	}
+
+	void ScriptGUICurves::Internal_curveToPixelSpace(ScriptGUICurves* thisPtr, Vector2* curveCoords, Vector2I* __output)
+	{
+		Vector2I tmp__output;
+		tmp__output = static_cast<GUICurves*>(thisPtr->getGUIElement())->curveToPixelSpace(*curveCoords);
+
+		*__output = tmp__output;
+	}
+
+	uint32_t ScriptGUICurves::Internal_findCurve(ScriptGUICurves* thisPtr, Vector2I* pixelCoords)
+	{
+		uint32_t tmp__output;
+		tmp__output = static_cast<GUICurves*>(thisPtr->getGUIElement())->findCurve(*pixelCoords);
+
+		uint32_t __output;
+		__output = tmp__output;
+
+		return __output;
+	}
+
+	bool ScriptGUICurves::Internal_findKeyFrame(ScriptGUICurves* thisPtr, Vector2I* pixelCoords, KeyframeRef* keyframe)
+	{
+		bool tmp__output;
+		tmp__output = static_cast<GUICurves*>(thisPtr->getGUIElement())->findKeyFrame(*pixelCoords, *keyframe);
+
+		bool __output;
+		__output = tmp__output;
+
+		return __output;
+	}
+
+	bool ScriptGUICurves::Internal_findTangent(ScriptGUICurves* thisPtr, Vector2I* pixelCoords, __TangentRefInterop* tangent)
+	{
+		bool tmp__output;
+		TangentRef tmptangent;
+		tmp__output = static_cast<GUICurves*>(thisPtr->getGUIElement())->findTangent(*pixelCoords, tmptangent);
+
+		bool __output;
+		__output = tmp__output;
+		__TangentRefInterop interoptangent;
+		interoptangent = ScriptTangentRef::toInterop(tmptangent);
+		MonoUtil::valueCopy(tangent, &interoptangent, ScriptTangentRef::getMetaData()->scriptClass->_getInternalClass());
+
+		return __output;
+	}
+
+	void ScriptGUICurves::Internal_selectKeyframe(ScriptGUICurves* thisPtr, KeyframeRef* keyframeRef, TangentModeBits tangentMode, bool selected)
+	{
+		static_cast<GUICurves*>(thisPtr->getGUIElement())->selectKeyframe(*keyframeRef, tangentMode, selected);
+	}
+
+	void ScriptGUICurves::Internal_clearSelectedKeyframes(ScriptGUICurves* thisPtr)
+	{
+		static_cast<GUICurves*>(thisPtr->getGUIElement())->clearSelectedKeyframes();
+	}
+
+	void ScriptGUICurves::Internal_create(MonoObject* managedInstance, MonoString* styleName)
+	{
+		String tmpstyleName;
+		tmpstyleName = MonoUtil::monoToString(styleName);
+		GUICurves* instance = GUICurves::create(tmpstyleName);
+		new (bs_alloc<ScriptGUICurves>())ScriptGUICurves(managedInstance, instance);
+	}
+
+	void ScriptGUICurves::Internal_create0(MonoObject* managedInstance, CurveDrawOption drawOptions, MonoString* styleName)
+	{
+		String tmpstyleName;
+		tmpstyleName = MonoUtil::monoToString(styleName);
+		GUICurves* instance = GUICurves::create(drawOptions, tmpstyleName);
+		new (bs_alloc<ScriptGUICurves>())ScriptGUICurves(managedInstance, instance);
+	}
+}

+ 43 - 0
Source/Scripting/SBansheeEditor/Generated/BsScriptGUICurves.generated.h

@@ -0,0 +1,43 @@
+#pragma once
+
+#include "BsScriptEditorPrerequisites.h"
+#include "Wrappers/GUI/BsScriptGUIElement.h"
+#include "BsScriptGUITimeline.generated.h"
+#include "../../../EditorCore/GUI/BsGUICurves.h"
+#include "../../../bsf/Source/Foundation/bsfCore/Animation/BsAnimationUtility.h"
+
+namespace bs
+{
+	class GUICurves;
+	struct __CurveDrawInfoInterop;
+	struct __TangentRefInterop;
+
+	class BS_SCR_BED_EXPORT ScriptGUICurves : public TScriptGUIElement<ScriptGUICurves>
+	{
+	public:
+		SCRIPT_OBJ(EDITOR_ASSEMBLY, "BansheeEditor", "GUICurves")
+
+		ScriptGUICurves(MonoObject* managedInstance, GUICurves* value);
+
+	private:
+		void onClicked();
+
+		typedef void(BS_THUNKCALL *onClickedThunkDef) (MonoObject*, MonoException**);
+		static onClickedThunkDef onClickedThunk;
+
+		static void Internal_setCurves(ScriptGUICurves* thisPtr, MonoArray* curves);
+		static MonoArray* Internal_getCurves(ScriptGUICurves* thisPtr);
+		static void Internal_setRange(ScriptGUICurves* thisPtr, float xRange, float yRange);
+		static void Internal_setOffset(ScriptGUICurves* thisPtr, Vector2* offset);
+		static void Internal_centerAndZoom(ScriptGUICurves* thisPtr);
+		static bool Internal_pixelToCurveSpace(ScriptGUICurves* thisPtr, Vector2I* pixelCoords, Vector2* curveCoords, bool padding);
+		static void Internal_curveToPixelSpace(ScriptGUICurves* thisPtr, Vector2* curveCoords, Vector2I* __output);
+		static uint32_t Internal_findCurve(ScriptGUICurves* thisPtr, Vector2I* pixelCoords);
+		static bool Internal_findKeyFrame(ScriptGUICurves* thisPtr, Vector2I* pixelCoords, KeyframeRef* keyframe);
+		static bool Internal_findTangent(ScriptGUICurves* thisPtr, Vector2I* pixelCoords, __TangentRefInterop* tangent);
+		static void Internal_selectKeyframe(ScriptGUICurves* thisPtr, KeyframeRef* keyframeRef, TangentModeBits tangentMode, bool selected);
+		static void Internal_clearSelectedKeyframes(ScriptGUICurves* thisPtr);
+		static void Internal_create(MonoObject* managedInstance, MonoString* styleName);
+		static void Internal_create0(MonoObject* managedInstance, CurveDrawOption drawOptions, MonoString* styleName);
+	};
+}

+ 90 - 0
Source/Scripting/SBansheeEditor/Generated/BsScriptGUIGraphTicks.generated.cpp

@@ -0,0 +1,90 @@
+#include "BsScriptGUIGraphTicks.generated.h"
+#include "BsMonoMethod.h"
+#include "BsMonoClass.h"
+#include "BsMonoUtil.h"
+#include "../../../EditorCore/GUI/BsGUICurves.h"
+
+namespace bs
+{
+	ScriptGUIGraphTicks::ScriptGUIGraphTicks(MonoObject* managedInstance, const SPtr<GUIGraphTicks>& value)
+		:ScriptObject(managedInstance), mInternal(value)
+	{
+	}
+
+	void ScriptGUIGraphTicks::initRuntimeData()
+	{
+		metaData.scriptClass->addInternalCall("Internal_GUIGraphTicks", (void*)&ScriptGUIGraphTicks::Internal_GUIGraphTicks);
+		metaData.scriptClass->addInternalCall("Internal_getNumLevels", (void*)&ScriptGUIGraphTicks::Internal_getNumLevels);
+		metaData.scriptClass->addInternalCall("Internal_setRange", (void*)&ScriptGUIGraphTicks::Internal_setRange);
+		metaData.scriptClass->addInternalCall("Internal_setTickSpacing", (void*)&ScriptGUIGraphTicks::Internal_setTickSpacing);
+		metaData.scriptClass->addInternalCall("Internal_getLevelStrength", (void*)&ScriptGUIGraphTicks::Internal_getLevelStrength);
+		metaData.scriptClass->addInternalCall("Internal_getTicks", (void*)&ScriptGUIGraphTicks::Internal_getTicks);
+
+	}
+
+	MonoObject* ScriptGUIGraphTicks::create(const SPtr<GUIGraphTicks>& value)
+	{
+		if(value == nullptr) return nullptr; 
+
+		bool dummy = false;
+		void* ctorParams[1] = { &dummy };
+
+		MonoObject* managedInstance = metaData.scriptClass->createInstance("bool", ctorParams);
+		new (bs_alloc<ScriptGUIGraphTicks>()) ScriptGUIGraphTicks(managedInstance, value);
+		return managedInstance;
+	}
+	void ScriptGUIGraphTicks::Internal_GUIGraphTicks(MonoObject* managedInstance, GUITickStepType stepType)
+	{
+		SPtr<GUIGraphTicks> instance = bs_shared_ptr_new<GUIGraphTicks>(stepType);
+		new (bs_alloc<ScriptGUIGraphTicks>())ScriptGUIGraphTicks(managedInstance, instance);
+	}
+
+	uint32_t ScriptGUIGraphTicks::Internal_getNumLevels(ScriptGUIGraphTicks* thisPtr)
+	{
+		uint32_t tmp__output;
+		tmp__output = thisPtr->getInternal()->getNumLevels();
+
+		uint32_t __output;
+		__output = tmp__output;
+
+		return __output;
+	}
+
+	void ScriptGUIGraphTicks::Internal_setRange(ScriptGUIGraphTicks* thisPtr, float valueRangeStart, float valueRangeEnd, uint32_t pixelRange)
+	{
+		thisPtr->getInternal()->setRange(valueRangeStart, valueRangeEnd, pixelRange);
+	}
+
+	void ScriptGUIGraphTicks::Internal_setTickSpacing(ScriptGUIGraphTicks* thisPtr, int32_t minPx, int32_t maxPx)
+	{
+		thisPtr->getInternal()->setTickSpacing(minPx, maxPx);
+	}
+
+	float ScriptGUIGraphTicks::Internal_getLevelStrength(ScriptGUIGraphTicks* thisPtr, uint32_t level)
+	{
+		float tmp__output;
+		tmp__output = thisPtr->getInternal()->getLevelStrength(level);
+
+		float __output;
+		__output = tmp__output;
+
+		return __output;
+	}
+
+	MonoArray* ScriptGUIGraphTicks::Internal_getTicks(ScriptGUIGraphTicks* thisPtr, int32_t level)
+	{
+		Vector<float> vec__output;
+		vec__output = thisPtr->getInternal()->getTicks(level);
+
+		MonoArray* __output;
+		int arraySize__output = (int)vec__output.size();
+		ScriptArray array__output = ScriptArray::create<float>(arraySize__output);
+		for(int i = 0; i < arraySize__output; i++)
+		{
+			array__output.set(i, vec__output[i]);
+		}
+		__output = array__output.getInternal();
+
+		return __output;
+	}
+}

+ 31 - 0
Source/Scripting/SBansheeEditor/Generated/BsScriptGUIGraphTicks.generated.h

@@ -0,0 +1,31 @@
+#pragma once
+
+#include "BsScriptEditorPrerequisites.h"
+#include "BsScriptObject.h"
+#include "../../../EditorCore/GUI/BsGUICurves.h"
+
+namespace bs
+{
+	class GUIGraphTicks;
+
+	class BS_SCR_BED_EXPORT ScriptGUIGraphTicks : public ScriptObject<ScriptGUIGraphTicks>
+	{
+	public:
+		SCRIPT_OBJ(EDITOR_ASSEMBLY, "BansheeEditor", "GUIGraphTicks")
+
+		ScriptGUIGraphTicks(MonoObject* managedInstance, const SPtr<GUIGraphTicks>& value);
+
+		SPtr<GUIGraphTicks> getInternal() const { return mInternal; }
+		static MonoObject* create(const SPtr<GUIGraphTicks>& value);
+
+	private:
+		SPtr<GUIGraphTicks> mInternal;
+
+		static void Internal_GUIGraphTicks(MonoObject* managedInstance, GUITickStepType stepType);
+		static uint32_t Internal_getNumLevels(ScriptGUIGraphTicks* thisPtr);
+		static void Internal_setRange(ScriptGUIGraphTicks* thisPtr, float valueRangeStart, float valueRangeEnd, uint32_t pixelRange);
+		static void Internal_setTickSpacing(ScriptGUIGraphTicks* thisPtr, int32_t minPx, int32_t maxPx);
+		static float Internal_getLevelStrength(ScriptGUIGraphTicks* thisPtr, uint32_t level);
+		static MonoArray* Internal_getTicks(ScriptGUIGraphTicks* thisPtr, int32_t level);
+	};
+}

+ 139 - 0
Source/Scripting/SBansheeEditor/Generated/BsScriptGUITimeline.generated.cpp

@@ -0,0 +1,139 @@
+#include "BsScriptGUITimeline.generated.h"
+#include "BsMonoMethod.h"
+#include "BsMonoClass.h"
+#include "BsMonoUtil.h"
+#include "../../../EditorCore/GUI/BsGUICurves.h"
+#include "Wrappers/BsScriptVector2I.h"
+
+namespace bs
+{
+	ScriptGUITimeline::ScriptGUITimeline(MonoObject* managedInstance, GUITimeline* value)
+		:TScriptGUIElement(managedInstance, value)
+	{
+	}
+
+	void ScriptGUITimeline::initRuntimeData()
+	{
+		metaData.scriptClass->addInternalCall("Internal_setRange", (void*)&ScriptGUITimeline::Internal_setRange);
+		metaData.scriptClass->addInternalCall("Internal_getRange", (void*)&ScriptGUITimeline::Internal_getRange);
+		metaData.scriptClass->addInternalCall("Internal_setOffset", (void*)&ScriptGUITimeline::Internal_setOffset);
+		metaData.scriptClass->addInternalCall("Internal_getOffset", (void*)&ScriptGUITimeline::Internal_getOffset);
+		metaData.scriptClass->addInternalCall("Internal_setFPS", (void*)&ScriptGUITimeline::Internal_setFPS);
+		metaData.scriptClass->addInternalCall("Internal_getFPS", (void*)&ScriptGUITimeline::Internal_getFPS);
+		metaData.scriptClass->addInternalCall("Internal_setMarkedFrame", (void*)&ScriptGUITimeline::Internal_setMarkedFrame);
+		metaData.scriptClass->addInternalCall("Internal_setMarkedFrame0", (void*)&ScriptGUITimeline::Internal_setMarkedFrame0);
+		metaData.scriptClass->addInternalCall("Internal_getFrame", (void*)&ScriptGUITimeline::Internal_getFrame);
+		metaData.scriptClass->addInternalCall("Internal_getTime", (void*)&ScriptGUITimeline::Internal_getTime);
+		metaData.scriptClass->addInternalCall("Internal_getOffset0", (void*)&ScriptGUITimeline::Internal_getOffset0);
+		metaData.scriptClass->addInternalCall("Internal_getTimeForFrame", (void*)&ScriptGUITimeline::Internal_getTimeForFrame);
+
+	}
+
+	void ScriptGUITimeline::Internal_setRange(ScriptGUIElementBaseTBase* thisPtr, float range)
+	{
+		static_cast<GUITimeline*>(thisPtr->getGUIElement())->setRange(range);
+	}
+
+	float ScriptGUITimeline::Internal_getRange(ScriptGUIElementBaseTBase* thisPtr)
+	{
+		float tmp__output;
+		tmp__output = static_cast<GUITimeline*>(thisPtr->getGUIElement())->getRange();
+
+		float __output;
+		__output = tmp__output;
+
+		return __output;
+	}
+
+	void ScriptGUITimeline::Internal_setOffset(ScriptGUIElementBaseTBase* thisPtr, float offset)
+	{
+		static_cast<GUITimeline*>(thisPtr->getGUIElement())->setOffset(offset);
+	}
+
+	float ScriptGUITimeline::Internal_getOffset(ScriptGUIElementBaseTBase* thisPtr)
+	{
+		float tmp__output;
+		tmp__output = static_cast<GUITimeline*>(thisPtr->getGUIElement())->getOffset();
+
+		float __output;
+		__output = tmp__output;
+
+		return __output;
+	}
+
+	void ScriptGUITimeline::Internal_setFPS(ScriptGUIElementBaseTBase* thisPtr, uint32_t FPS)
+	{
+		static_cast<GUITimeline*>(thisPtr->getGUIElement())->setFPS(FPS);
+	}
+
+	uint32_t ScriptGUITimeline::Internal_getFPS(ScriptGUIElementBaseTBase* thisPtr)
+	{
+		uint32_t tmp__output;
+		tmp__output = static_cast<GUITimeline*>(thisPtr->getGUIElement())->getFPS();
+
+		uint32_t __output;
+		__output = tmp__output;
+
+		return __output;
+	}
+
+	void ScriptGUITimeline::Internal_setMarkedFrame(ScriptGUIElementBaseTBase* thisPtr, uint32_t index)
+	{
+		static_cast<GUITimeline*>(thisPtr->getGUIElement())->setMarkedFrame(index);
+	}
+
+	uint32_t ScriptGUITimeline::Internal_setMarkedFrame0(ScriptGUIElementBaseTBase* thisPtr)
+	{
+		uint32_t tmp__output;
+		tmp__output = static_cast<GUITimeline*>(thisPtr->getGUIElement())->setMarkedFrame();
+
+		uint32_t __output;
+		__output = tmp__output;
+
+		return __output;
+	}
+
+	uint32_t ScriptGUITimeline::Internal_getFrame(ScriptGUIElementBaseTBase* thisPtr, Vector2I* pixelCoords)
+	{
+		uint32_t tmp__output;
+		tmp__output = static_cast<GUITimeline*>(thisPtr->getGUIElement())->getFrame(*pixelCoords);
+
+		uint32_t __output;
+		__output = tmp__output;
+
+		return __output;
+	}
+
+	float ScriptGUITimeline::Internal_getTime(ScriptGUIElementBaseTBase* thisPtr, int32_t pixel)
+	{
+		float tmp__output;
+		tmp__output = static_cast<GUITimeline*>(thisPtr->getGUIElement())->getTime(pixel);
+
+		float __output;
+		__output = tmp__output;
+
+		return __output;
+	}
+
+	int32_t ScriptGUITimeline::Internal_getOffset0(ScriptGUIElementBaseTBase* thisPtr, float time)
+	{
+		int32_t tmp__output;
+		tmp__output = static_cast<GUITimeline*>(thisPtr->getGUIElement())->getOffset(time);
+
+		int32_t __output;
+		__output = tmp__output;
+
+		return __output;
+	}
+
+	float ScriptGUITimeline::Internal_getTimeForFrame(ScriptGUIElementBaseTBase* thisPtr, int32_t index)
+	{
+		float tmp__output;
+		tmp__output = static_cast<GUITimeline*>(thisPtr->getGUIElement())->getTimeForFrame(index);
+
+		float __output;
+		__output = tmp__output;
+
+		return __output;
+	}
+}

+ 31 - 0
Source/Scripting/SBansheeEditor/Generated/BsScriptGUITimeline.generated.h

@@ -0,0 +1,31 @@
+#pragma once
+
+#include "BsScriptEditorPrerequisites.h"
+#include "Wrappers/GUI/BsScriptGUIElement.h"
+
+namespace bs
+{
+	class GUITimeline;
+
+	class BS_SCR_BED_EXPORT ScriptGUITimeline : public TScriptGUIElement<ScriptGUITimeline>
+	{
+	public:
+		SCRIPT_OBJ(EDITOR_ASSEMBLY, "BansheeEditor", "GUITimeline")
+
+		ScriptGUITimeline(MonoObject* managedInstance, GUITimeline* value);
+
+	private:
+		static void Internal_setRange(ScriptGUIElementBaseTBase* thisPtr, float range);
+		static float Internal_getRange(ScriptGUIElementBaseTBase* thisPtr);
+		static void Internal_setOffset(ScriptGUIElementBaseTBase* thisPtr, float offset);
+		static float Internal_getOffset(ScriptGUIElementBaseTBase* thisPtr);
+		static void Internal_setFPS(ScriptGUIElementBaseTBase* thisPtr, uint32_t FPS);
+		static uint32_t Internal_getFPS(ScriptGUIElementBaseTBase* thisPtr);
+		static void Internal_setMarkedFrame(ScriptGUIElementBaseTBase* thisPtr, uint32_t index);
+		static uint32_t Internal_setMarkedFrame0(ScriptGUIElementBaseTBase* thisPtr);
+		static uint32_t Internal_getFrame(ScriptGUIElementBaseTBase* thisPtr, Vector2I* pixelCoords);
+		static float Internal_getTime(ScriptGUIElementBaseTBase* thisPtr, int32_t pixel);
+		static int32_t Internal_getOffset0(ScriptGUIElementBaseTBase* thisPtr, float time);
+		static float Internal_getTimeForFrame(ScriptGUIElementBaseTBase* thisPtr, int32_t index);
+	};
+}

+ 20 - 0
Source/Scripting/SBansheeEngine/Generated/BsScriptAnimationUtility.generated.cpp

@@ -20,6 +20,7 @@ namespace bs
 		metaData.scriptClass->addInternalCall("Internal_quaternionToEulerCurve", (void*)&ScriptAnimationUtility::Internal_quaternionToEulerCurve);
 		metaData.scriptClass->addInternalCall("Internal_splitCurve", (void*)&ScriptAnimationUtility::Internal_splitCurve);
 		metaData.scriptClass->addInternalCall("Internal_combineCurve", (void*)&ScriptAnimationUtility::Internal_combineCurve);
+		metaData.scriptClass->addInternalCall("Internal_calculateRange", (void*)&ScriptAnimationUtility::Internal_calculateRange);
 
 	}
 
@@ -108,4 +109,23 @@ namespace bs
 
 		return __output;
 	}
+
+	void ScriptAnimationUtility::Internal_calculateRange(MonoArray* curves, float* xMin, float* xMax, float* yMin, float* yMax)
+	{
+		Vector<SPtr<TAnimationCurve<float>>> veccurves;
+		if(curves != nullptr)
+		{
+			ScriptArray arraycurves(curves);
+			veccurves.resize(arraycurves.size());
+			for(int i = 0; i < (int)arraycurves.size(); i++)
+			{
+				ScriptTAnimationCurvefloat* scriptcurves;
+				scriptcurves = ScriptTAnimationCurvefloat::toNative(arraycurves.get<MonoObject*>(i));
+				if(scriptcurves != nullptr)
+					veccurves[i] = scriptcurves->getInternal();
+			}
+
+		}
+		AnimationUtility::calculateRange(veccurves, *xMin, *xMax, *yMin, *yMax);
+	}
 }

+ 1 - 0
Source/Scripting/SBansheeEngine/Generated/BsScriptAnimationUtility.generated.h

@@ -24,5 +24,6 @@ namespace bs
 		static MonoObject* Internal_quaternionToEulerCurve(MonoObject* quatCurve);
 		static MonoArray* Internal_splitCurve(MonoObject* compoundCurve);
 		static MonoObject* Internal_combineCurve(MonoArray* curveComponents);
+		static void Internal_calculateRange(MonoArray* curves, float* xMin, float* xMax, float* yMin, float* yMax);
 	};
 }

+ 25 - 0
Source/Scripting/SBansheeEngine/Generated/BsScriptKeyframeRef.generated.cpp

@@ -0,0 +1,25 @@
+#include "BsScriptKeyframeRef.generated.h"
+#include "BsMonoMethod.h"
+#include "BsMonoClass.h"
+#include "BsMonoUtil.h"
+
+namespace bs
+{
+	ScriptKeyframeRef::ScriptKeyframeRef(MonoObject* managedInstance)
+		:ScriptObject(managedInstance)
+	{ }
+
+	void ScriptKeyframeRef::initRuntimeData()
+	{ }
+
+	MonoObject*ScriptKeyframeRef::box(const KeyframeRef& value)
+	{
+		return MonoUtil::box(metaData.scriptClass->_getInternalClass(), (void*)&value);
+	}
+
+	KeyframeRef ScriptKeyframeRef::unbox(MonoObject* value)
+	{
+		return *(KeyframeRef*)MonoUtil::unbox(value);
+	}
+
+}

+ 21 - 0
Source/Scripting/SBansheeEngine/Generated/BsScriptKeyframeRef.generated.h

@@ -0,0 +1,21 @@
+#pragma once
+
+#include "BsScriptEnginePrerequisites.h"
+#include "BsScriptObject.h"
+#include "../../../bsf/Source/Foundation/bsfCore/Animation/BsAnimationUtility.h"
+
+namespace bs
+{
+	class BS_SCR_BE_EXPORT ScriptKeyframeRef : public ScriptObject<ScriptKeyframeRef>
+	{
+	public:
+		SCRIPT_OBJ(ENGINE_ASSEMBLY, "BansheeEngine", "KeyframeRef")
+
+		static MonoObject* box(const KeyframeRef& value);
+		static KeyframeRef unbox(MonoObject* value);
+
+	private:
+		ScriptKeyframeRef(MonoObject* managedInstance);
+
+	};
+}

+ 45 - 0
Source/Scripting/SBansheeEngine/Generated/BsScriptTangentRef.generated.cpp

@@ -0,0 +1,45 @@
+#include "BsScriptTangentRef.generated.h"
+#include "BsMonoMethod.h"
+#include "BsMonoClass.h"
+#include "BsMonoUtil.h"
+#include "BsScriptKeyframeRef.generated.h"
+#include "BsScriptKeyframeRef.generated.h"
+
+namespace bs
+{
+	ScriptTangentRef::ScriptTangentRef(MonoObject* managedInstance)
+		:ScriptObject(managedInstance)
+	{ }
+
+	void ScriptTangentRef::initRuntimeData()
+	{ }
+
+	MonoObject*ScriptTangentRef::box(const __TangentRefInterop& value)
+	{
+		return MonoUtil::box(metaData.scriptClass->_getInternalClass(), (void*)&value);
+	}
+
+	__TangentRefInterop ScriptTangentRef::unbox(MonoObject* value)
+	{
+		return *(__TangentRefInterop*)MonoUtil::unbox(value);
+	}
+
+	TangentRef ScriptTangentRef::fromInterop(const __TangentRefInterop& value)
+	{
+		TangentRef output;
+		output.keyframeRef = value.keyframeRef;
+		output.type = value.type;
+
+		return output;
+	}
+
+	__TangentRefInterop ScriptTangentRef::toInterop(const TangentRef& value)
+	{
+		__TangentRefInterop output;
+		output.keyframeRef = value.keyframeRef;
+		output.type = value.type;
+
+		return output;
+	}
+
+}

+ 31 - 0
Source/Scripting/SBansheeEngine/Generated/BsScriptTangentRef.generated.h

@@ -0,0 +1,31 @@
+#pragma once
+
+#include "BsScriptEnginePrerequisites.h"
+#include "BsScriptObject.h"
+#include "../../../bsf/Source/Foundation/bsfCore/Animation/BsAnimationUtility.h"
+#include "../../../bsf/Source/Foundation/bsfCore/Animation/BsAnimationUtility.h"
+#include "../../../bsf/Source/Foundation/bsfCore/Animation/BsAnimationUtility.h"
+
+namespace bs
+{
+	struct __TangentRefInterop
+	{
+		KeyframeRef keyframeRef;
+		TangentType type;
+	};
+
+	class BS_SCR_BE_EXPORT ScriptTangentRef : public ScriptObject<ScriptTangentRef>
+	{
+	public:
+		SCRIPT_OBJ(ENGINE_ASSEMBLY, "BansheeEngine", "TangentRef")
+
+		static MonoObject* box(const __TangentRefInterop& value);
+		static __TangentRefInterop unbox(MonoObject* value);
+		static TangentRef fromInterop(const __TangentRefInterop& value);
+		static __TangentRefInterop toInterop(const TangentRef& value);
+
+	private:
+		ScriptTangentRef(MonoObject* managedInstance);
+
+	};
+}