//
// Asura_Graph_Gadget.h
//
// Created by KOR - 16/02/2004 17:15:47
//
// Copyright (C) 2004 Rebellion, All Rights Reserved.
//

#ifdef ASURA_USE_PRAGMA_ONCE
	#pragma once
#endif

#ifndef __ASURA_GRAPH_GADGET_H__
#define __ASURA_GRAPH_GADGET_H__


#include "Core/Asura_Graph.h"
#include "Core/Asura_Timers.h"
#include "Core/Asura_UserInput.h"
#include "Core/Asura_TextureManagement.h"
#include "Interface/Asura_Gadget.h"
#include "Interface/Asura_Cursor.h"
#include "Rendering/Asura_Render.h"
#include "Rendering/Asura_Text_Output.h"

#include "Misc/Tools_Timed_Messages.h"

// The header is a bit of a monster as this is a template

const float ASURA_GRAPH_BORDER_SIZE = 20;
const float ASURA_GRAPH_WIDTH = 300;
const float ASURA_GRAPH_HEIGHT = 150;
const float ASURA_GRAPH_CTRL_PT_SIZE = 8;
// Determines how large the steps in the smooth representation are
const u_int ASURA_GRAPH_SMOOTH_INC = 3;

extern unsigned int Asura_OverlayBrightness;
template <class T> class Asura_Graph_Dialog;

template <class T> class Asura_Graph_Gadget : public Asura_Gadget
{
	friend Asura_Graph_Dialog<T>;

public:
	virtual ~Asura_Graph_Gadget<T>()
	{
		delete [] m_pxGraphCustomGridlinesX;
		m_pxGraphCustomGridlinesX = NULL;
		delete [] m_pxGraphCustomGridlinesY;
		m_pxGraphCustomGridlinesY = NULL;
	}
	 
	Asura_Graph_Gadget<T>(const Asura_Vector_2 xOffset, Asura_Graph<T>* pxGraph, Asura_Graph_Dialog<T>* pxGraphDialog, float fRangeMin, float fRangeMax, bool* pbFlag)
	: m_pxGraph(pxGraph)
	, m_pxGraphDialog(pxGraphDialog)
	, m_pxCurrentPoint(NULL)
	, m_uNumCustomGridlinesX(0)
	, m_pxGraphCustomGridlinesX(NULL)
	, m_uNumCustomGridlinesY(0)
	, m_pxGraphCustomGridlinesY(NULL)
	, m_ucCurrentDimension(0xff)
	, m_ucVisibleDimension(0xff)
	, m_bDragging(false)
	, m_fRangeMax(fRangeMax)
	, m_fRangeMin(fRangeMin)
	, m_fGridlineSpacingX(-1.0f)
	, m_fGridlineSpacingY(-1.0f)
	, m_bShowDiscreteSamples(true)
	, m_pbChangedFlag(pbFlag)
	, m_bScaling(false)
	, m_bShifting(false)
	, m_iFreezeMouseX(0)
	, m_iFreezeMouseY(0)
	, m_uVisibleDimensionFlags(0)
	, m_uPrevVisibleDimensionFlags(0)
	, m_bForceNormalisedAlpha(false)
	{
		m_xOffset = xOffset;
		m_fWidth = (m_pxGraphDialog->GetGraphPixelWidth() + ASURA_GRAPH_BORDER_SIZE * 2) / 10.0f;
		m_fHeight = (m_pxGraphDialog->GetGraphPixelHeight() + ASURA_GRAPH_BORDER_SIZE * 2) / 14.0f;
		m_pxOriginalValuePoint = 0;
	}
	struct CustomGridline
	{
		CustomGridline() : m_fValue( -Asura_Maths::LargeFloat ), m_uColour( 0xFFC0C0C0 ) {}
		float m_fValue;
		u_int m_uColour;
	};
	void SetUseCustomXGridLines( u_int uAmount = 0 )
	{
		delete [] m_pxGraphCustomGridlinesX;
		m_pxGraphCustomGridlinesX = NULL;
		if ( uAmount )
		{
			m_uNumCustomGridlinesX    = uAmount;
			m_pxGraphCustomGridlinesX = new CustomGridline[ m_uNumCustomGridlinesX	];
		}
	}
	void SetUseCustomYGridLines( u_int uAmount = 0 )
	{
		delete [] m_pxGraphCustomGridlinesY;
		m_pxGraphCustomGridlinesY = NULL;
		if ( uAmount )
		{
			m_uNumCustomGridlinesY    = uAmount;
			m_pxGraphCustomGridlinesY = new CustomGridline[ m_uNumCustomGridlinesY	];
		}
	}
	struct CustomGridline* GetCustomXGridlines()
	{
		return m_pxGraphCustomGridlinesX;
	}
	struct CustomGridline* GetCustomYGridlines()
	{
		return m_pxGraphCustomGridlinesY;
	}

	bool IsAnyPointSelected() const { return m_pxCurrentPoint != NULL; }

	void SetRange(float fMin, float fMax)
	{
		Asura_Assert(fMin < fMax, "Min and max wrong way around?");
		m_fRangeMin = fMin;
		m_fRangeMax = fMax;
	}

	virtual void Render( const Asura_Vector_2& xOrigin )
	{
		Asura_Gadget::Render( xOrigin );

		m_xOrigin = xOrigin;
		Asura_Vector_2 xBase = GetGraphOrigin();

		// Outside border
		Asura_Render::Overlay.Colour = Asura_OverlayBrightness;
		Asura_Render::Overlay.TextureIndex = ASURA_TEXTURE_NONE;
		Asura_Render::Overlay.TranslucencyMode = ASURA_TRANSLUCENCY_NORMAL;
		Asura_Render::Overlay.SetRectXY(xBase.x - ASURA_GRAPH_BORDER_SIZE,
										xBase.y - ASURA_GRAPH_BORDER_SIZE,
										m_pxGraphDialog->GetGraphPixelWidth() + 2 * ASURA_GRAPH_BORDER_SIZE,
										m_pxGraphDialog->GetGraphPixelHeight() + 2 * ASURA_GRAPH_BORDER_SIZE);
		Asura_Render::Overlay.Render();
		Asura_Render::FlushDisplay();

		// Main graph bg
		RenderBackground();

		if(!IsEnabled()) return;
		const u_int uSamples = m_pxGraph->GetNumDiscreteSamples();
		const float fDomainMin = m_pxGraph->GetDomainMin();
		const float fDomainMax = m_pxGraph->GetDomainMax();


		// Vertical gridlines
		if (m_pxGraphCustomGridlinesX != NULL)
		{
			for ( u_int uLine = 0; uLine < m_uNumCustomGridlinesX; ++uLine )
			{
				const CustomGridline& xGridline = m_pxGraphCustomGridlinesX[ uLine ];

				if ( fDomainMin < xGridline.m_fValue && xGridline.m_fValue < fDomainMax )
				{
					const float fScreenX = xBase.x + (xGridline.m_fValue - fDomainMin) * m_pxGraphDialog->GetGraphPixelWidth() / (fDomainMax - fDomainMin);
					const u_int uColour = (Asura_Maths::Abs( xGridline.m_fValue ) < 0.01f ? 0xFF000000 : xGridline.m_uColour);

					// +1 stops the top pixel poking out of the white background
					Asura_Render::Render2DLine(fScreenX, xBase.y + 1, fScreenX, xBase.y + m_pxGraphDialog->GetGraphPixelHeight(), uColour );

				}
			}
		}
		else
		{
			if( m_bShowDiscreteSamples )
			{
				// Vertical lines showing sample density
				for(u_int u = 0; u < uSamples; ++u)
				{
					float fX = xBase.x + u * m_pxGraphDialog->GetGraphPixelWidth() / (uSamples - 1);
					Asura_Render::Render2DLine(fX, xBase.y, fX, xBase.y + m_pxGraphDialog->GetGraphPixelHeight(), 0x80808080);
				}
			}

			if( m_fGridlineSpacingX > 0.0f )
			{
				const float fOffsetX = Asura_Maths::FloatMod( m_fGridlineSpacingX - fDomainMin, m_fGridlineSpacingX );
				for( float fX = fDomainMin + fOffsetX; fX < fDomainMax; fX += m_fGridlineSpacingX )
				{
					const float fScreenX = xBase.x + (fX - fDomainMin) * m_pxGraphDialog->GetGraphPixelWidth() / (fDomainMax - fDomainMin);
					const u_int uColour = (Asura_Maths::Abs( fX ) < 0.01f ? 0xFF000000 : 0xFFC0C0C0);

					// +1 stops the top pixel poking out of the white background
					Asura_Render::Render2DLine(fScreenX, xBase.y + 1, fScreenX, xBase.y + m_pxGraphDialog->GetGraphPixelHeight(), uColour );
				}
			}
			else
			{
				// Just render one along the y-axis
				const float fScreenX = xBase.x - fDomainMin * m_pxGraphDialog->GetGraphPixelWidth() / (fDomainMax - fDomainMin);
				Asura_Render::Render2DLine(fScreenX, xBase.y + 1, fScreenX, xBase.y + m_pxGraphDialog->GetGraphPixelHeight(), 0xFF000000 );
			}
		}

		// Horizontal gridlines
		if ( m_pxGraphCustomGridlinesY )
		{
			for ( u_int uLine = 0; uLine < m_uNumCustomGridlinesY; ++uLine )
			{
				const CustomGridline& xGridline = m_pxGraphCustomGridlinesY[ uLine ];

				if ( fDomainMin < xGridline.m_fValue && xGridline.m_fValue < fDomainMax )
				{
					const float fScreenY = xBase.y + (m_fRangeMax - xGridline.m_fValue) * m_pxGraphDialog->GetGraphPixelHeight() / (m_fRangeMax - m_fRangeMin);
					const u_int uColour = (Asura_Maths::Abs( xGridline.m_fValue ) < 0.01f ? 0xFF000000 : xGridline.m_uColour);

					Asura_Render::Render2DLine(xBase.x, fScreenY, xBase.x + m_pxGraphDialog->GetGraphPixelWidth(), fScreenY, uColour);
				}
			}
		}
		else
		{
			if( m_fGridlineSpacingY > 0.0f )
			{
				const float fOffsetY = Asura_Maths::FloatMod( m_fGridlineSpacingY - m_fRangeMin, m_fGridlineSpacingY );
				for( float fY = m_fRangeMin + fOffsetY; fY < m_fRangeMax; fY += m_fGridlineSpacingY )
				{
					const float fScreenY = xBase.y + (m_fRangeMax - fY) * m_pxGraphDialog->GetGraphPixelHeight() / (m_fRangeMax - m_fRangeMin);
					const u_int uColour = (Asura_Maths::Abs( fY ) < 0.01f ? 0xFF000000 : 0xFFC0C0C0);

					Asura_Render::Render2DLine(xBase.x, fScreenY, xBase.x + m_pxGraphDialog->GetGraphPixelWidth(), fScreenY, uColour);
				}
			}
			else
			{
				// Just render one along the x-axis
				const float fScreenY = xBase.y + m_fRangeMax * m_pxGraphDialog->GetGraphPixelHeight() / (m_fRangeMax - m_fRangeMin);
				Asura_Render::Render2DLine(xBase.x, fScreenY, xBase.x + m_pxGraphDialog->GetGraphPixelWidth(), fScreenY, 0xFF000000);
			}
		}

		Asura_Render::FlushDisplay();

		// Render sampled graph
		unsigned char ucAlpha = static_cast<unsigned char>(Asura_OverlayBrightness >> 25);
		if( uSamples > 1 && m_bShowDiscreteSamples )
		{
			for(u_int u = 0; u < uSamples - 1; ++u)
			{
				const float fProportion1 = static_cast< float >(u) / ( uSamples - 1 );
				const float fProportion2 = static_cast< float >( u + 1 ) / ( uSamples - 1 );
				const float fX1 = Asura_Maths::Lerp( fDomainMin, fDomainMax, fProportion1 );
				const float fX2 = Asura_Maths::Lerp( fDomainMin, fDomainMax, fProportion2 );

				RenderLineSegment(fX1, fX2, ucAlpha);
			}
		}

		// Render smooth graph
		for(u_int u = 0; u < m_pxGraphDialog->GetGraphPixelWidth(); u += ASURA_GRAPH_SMOOTH_INC)
		{
			const float fProportion1 = static_cast< float >(u) / m_pxGraphDialog->GetGraphPixelWidth();
			const float fProportion2 = static_cast< float >( u + ASURA_GRAPH_SMOOTH_INC ) / m_pxGraphDialog->GetGraphPixelWidth();
			const float fX1 = Asura_Maths::Lerp( fDomainMin, fDomainMax, fProportion1 );
			const float fX2 = Asura_Maths::Lerp( fDomainMin, fDomainMax, Asura_Maths::Min(1.0f, fProportion2) );

			RenderLineSegment(fX1, fX2, ucAlpha);
		}

		// Render control points
		Asura_Render::Overlay.TextureIndex = ASURA_TEXTURE_NONE;
		Asura_Render::Overlay.TranslucencyMode = ASURA_TRANSLUCENCY_NORMAL;
		// Skip first
		const Asura_Graph_Point<T>* pxPoint = m_pxGraph->GetControlPoints()->m_pxNext;
		// Skip last
		while(pxPoint->m_pxNext)
		{
			RenderControlPoint(pxPoint);
			pxPoint = pxPoint->m_pxNext;
		}

		Asura_Render::FlushDisplay();
	}

	virtual void Update( const Asura_Vector_2& xOrigin )
	{
		m_xOrigin = xOrigin;
		Asura_Vector_2 xBase = GetGraphOrigin();
		//Check for changes to the visible dimension buttons
		if(m_uVisibleDimensionFlags != m_uPrevVisibleDimensionFlags)
		{
			//Find the bit that was changed ,and make sure it is the only one set
			u_int uChangedBit = m_uVisibleDimensionFlags^m_uPrevVisibleDimensionFlags;
			for(u_int i=0;i<GetNumberOfDimensions();i++)
			{
				if(uChangedBit & (1<<i))
				{
					m_uVisibleDimensionFlags &= (1<<i);
					m_ucVisibleDimension = m_uVisibleDimensionFlags ? (u_char)i : 0xff; 
					break;
				}
			}
			m_uPrevVisibleDimensionFlags = m_uVisibleDimensionFlags;
		}

		// Do we have a point currently selected?
		if(m_pxCurrentPoint)
		{
			//If a dimension is selected , make sure it is a viaible one
			if(m_ucCurrentDimension != 0xff && m_ucVisibleDimension != 0xff)
			{
				m_ucCurrentDimension = m_ucVisibleDimension;
			}
			
			m_fSelectionTimer += Asura_Timers::GetFrameTime();
			if(IsMouseOver())
			{
				float fSpeed = 0;
				if(Asura_UserInput::KeyPressed(ASURA_KEYID_ALT))
				{
					// Navigate nodes using arrow keys
					if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_LEFT))
					{
						if(m_pxCurrentPoint->m_pxPrev && m_pxCurrentPoint->m_pxPrev->m_pxPrev)
						{
							SelectPoint(m_pxCurrentPoint->m_pxPrev);
						}
					}
					else if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_RIGHT))
					{
						if(m_pxCurrentPoint->m_pxNext && m_pxCurrentPoint->m_pxNext->m_pxNext)
						{
							SelectPoint(m_pxCurrentPoint->m_pxNext);
						}
					}
					else if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_UP))
					{
						if(m_ucCurrentDimension)
						{
							m_ucCurrentDimension--;
						}
						else
						{
							m_ucCurrentDimension = (unsigned char)(GetNumberOfDimensions() - 1);
						}
					}
					else if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_DOWN))
					{
						if(m_ucCurrentDimension < GetNumberOfDimensions() - 1)
						{
							m_ucCurrentDimension++;
						}
						else
						{
							m_ucCurrentDimension = 0;
						}
					}
					//If the current dimension was changed , make sure the visible one gets changed to match
					if(m_ucCurrentDimension != 0xff && m_ucVisibleDimension != 0xff)
					{
						m_ucVisibleDimension = m_ucCurrentDimension;
						m_uVisibleDimensionFlags = m_uPrevVisibleDimensionFlags = (1<<m_ucVisibleDimension);
					}
					
				}
				else if(Asura_UserInput::KeyPressed(ASURA_KEYID_SHIFT))
				{
					fSpeed = Asura_Timers::GetFrameTime() * 0.1f;
				}
				else if(Asura_UserInput::KeyPressed(ASURA_KEYID_CTRL))
				{
					fSpeed = Asura_Timers::GetFrameTime() * 0.4f;
				}

				if(fSpeed > 0.f)
				{
					// Move node using arrow keys
					if(Asura_UserInput::KeyPressed(ASURA_KEYID_LEFT))
					{
						m_pxCurrentPoint->m_fTime -= fSpeed;
						ClampCurrentTime();
						OnChanged();
					}
					else if(Asura_UserInput::KeyPressed(ASURA_KEYID_RIGHT))
					{
						m_pxCurrentPoint->m_fTime += fSpeed;
						ClampCurrentTime();
						OnChanged();
					}

					fSpeed *= (m_fRangeMax - m_fRangeMin) * 2;
					if(Asura_UserInput::KeyPressed(ASURA_KEYID_UP))
					{
						SetCurrentValue(Asura_Maths::Min(m_fRangeMax, GetCurrentValue() + fSpeed));
						OnChanged();
					}
					else if(Asura_UserInput::KeyPressed(ASURA_KEYID_DOWN))
					{
						SetCurrentValue(Asura_Maths::Max(m_fRangeMin, GetCurrentValue() - fSpeed));
						OnChanged();
					}
				}
			}
			if(Asura_UserInput::DebouncedKeyPressed(','))
			{
				// Reset position
				UndoPoint();
				return;
			}
			if(m_bDragging)
			{
				// Point selected. Update with mouse position.
				int iCursorX, iCursorY;
				Asura_Cursor::GetScreenSpaceCoords(iCursorX, iCursorY);
				
				const float fNewTime = ScreenToGraphPosX(iCursorX);
				m_pxCurrentPoint->m_fTime = fNewTime;
				ClampCurrentTime();

				float fCurrentValue = ScreenToGraphPosY(iCursorY);

				if (m_bForceNormalisedAlpha && (m_ucCurrentDimension == 3))
				{
					fCurrentValue = (fCurrentValue - m_fRangeMin) / (m_fRangeMax - m_fRangeMin);
				}

				SetCurrentValue (fCurrentValue);

				OnChanged();

				// Has user let go of the mouse button?
				if(!Asura_UserInput::KeyPressed(ASURA_KEYID_MOUSEBUTTON1))
				{
					m_bDragging = false;
				}
				return;
			}
			else
			{
				// User has clicked somewhere in the graph
				bool bClicked = Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_MOUSEBUTTON1);
				bool bStartDrag = bClicked || (m_fSelectionTimer > 0.1f && Asura_UserInput::KeyPressed(ASURA_KEYID_MOUSEBUTTON1));
				if(bStartDrag && IsMouseOver())
				{
					float fValue = GetCurrentValue();

					if (m_bForceNormalisedAlpha && (m_ucCurrentDimension == 3)) // If alpha channel of Asura_RGBA template type
					{
						fValue = (fValue * (m_fRangeMax - m_fRangeMin)) + m_fRangeMin;
					}

					Asura_Vector_2 xPos;
					xPos.x = GraphToScreenPosX(m_pxCurrentPoint->m_fTime) - ASURA_GRAPH_CTRL_PT_SIZE * 0.5f;
					xPos.y = GraphToScreenPosY(fValue) - ASURA_GRAPH_CTRL_PT_SIZE * 0.5f;			
					
					// If it's on this point, start dragging
					if( Asura_Cursor::InsideScreenSpace(xPos, ASURA_GRAPH_CTRL_PT_SIZE, ASURA_GRAPH_CTRL_PT_SIZE))
					{
						m_bDragging = true;
						return;
					}
					else if(bClicked)
					{
						// Check that we are actually inside the graph area, and not in the border.
						Asura_Vector_2 xTextSpaceBase = xBase;
						xTextSpaceBase.x /= ASURA_TEXT_OUTPUT_FONT_WIDTH;
						xTextSpaceBase.y /= ASURA_TEXT_OUTPUT_FONT_HEIGHT;
						if(Asura_Cursor::Inside( xTextSpaceBase, m_pxGraphDialog->GetGraphPixelWidth() / ASURA_TEXT_OUTPUT_FONT_WIDTH, m_pxGraphDialog->GetGraphPixelHeight() / ASURA_TEXT_OUTPUT_FONT_HEIGHT))
						{
							// Deselect
							m_ucCurrentDimension = 0xff;
							SetCurrentPoint(NULL);
						}
					}
				}
			}
		}

		// Nothing selected

		// Find closest node if n is pressed over graph
		if(Asura_UserInput::DebouncedKeyPressed('n') && IsMouseOver())
		{
			float fClosestDistance = Asura_Maths::LargeFloat;
			// Skip first
			Asura_Graph_Point<T>* pxPoint = m_pxGraph->GetControlPoints()->m_pxNext;
			// Skip last
			while(pxPoint->m_pxNext)
			{
				fClosestDistance = SelectPointIfCloser(pxPoint, fClosestDistance);
				pxPoint = pxPoint->m_pxNext;
			}
			PlaceCursorOnCurrentPoint();
		}
		
		// Has the user clicked?
		if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_MOUSEBUTTON1) && IsMouseOver())
		{
			// If control is pressed, add a point
			if(Asura_UserInput::KeyPressed(ASURA_KEYID_CTRL))
			{
				int iCursorX, iCursorY;
				Asura_Cursor::GetScreenSpaceCoords(iCursorX, iCursorY);

				float fTime = ScreenToGraphPosX(iCursorX);
				if(fTime <= m_pxGraph->GetDomainMin() || fTime >= m_pxGraph->GetDomainMax())
				{
					// Time out of range
					return;
				}
				T xValue = m_pxGraph->GetValueAtTime(fTime);
				m_pxGraph->AddPoint(xValue, fTime);
				OnChanged();
			}
			else
			{
				// Check to see if user has clicked on a particular point

				// Skip first
				Asura_Graph_Point<T>* pxPoint = m_pxGraph->GetControlPoints()->m_pxNext;
				// Skip last
				while(pxPoint->m_pxNext)
				{
					int iDimension = MouseOverControlPoint(pxPoint);
					if(iDimension >= 0)
					{
						m_ucCurrentDimension = static_cast<u_char>(iDimension);
						// If shift is pressed, delete the point
						if(Asura_UserInput::KeyPressed(ASURA_KEYID_SHIFT))
						{
							m_pxGraph->RemovePoint(pxPoint);
							m_ucCurrentDimension = 0xff;
							OnChanged();
							break;
						}
						// Select this point
						SelectPoint(pxPoint);
						break;
					}
					pxPoint = pxPoint->m_pxNext;
				}
			}
		}

		// Scaling/shifting
		if(m_bScaling || m_bShifting)
		{
			int iDeltaX, iDeltaY;
			Asura_Cursor::GetScreenSpaceDeltas( iDeltaX, iDeltaY );
			if( iDeltaY != 0 )
			{
				ShiftOrScaleValues( static_cast< float >( iDeltaY ) );
			}

			// Freeze mouse position
			Asura_Cursor::SetScreenSpaceCoords( m_iFreezeMouseX, m_iFreezeMouseY );
		}
	}
	void SelectPoint(Asura_Graph_Point<T>* pxPoint)
	{
		SetCurrentPoint(pxPoint);
		m_xOriginalValue = m_pxCurrentPoint->m_xValue;
		m_fOriginalTime = m_pxCurrentPoint->m_fTime;
		m_pxOriginalValuePoint = pxPoint;
		m_fSelectionTimer = 0.f;
		m_bDragging = false;
	}
	bool GetCurrentValues(float* pfValue, float* pfTime)
	{
		Asura_Assert(pfValue && pfTime, "Null pointers passed to GetCurrentValues");
		if(!pfValue || !pfTime) return false;
		if(m_pxCurrentPoint)
		{
			*pfValue = GetCurrentValue();
			*pfTime = m_pxCurrentPoint->m_fTime;
			return true;
		}
		return false;
	}
	void SetCurrentValues(float fValue, float fTime)
	{
		if(m_pxCurrentPoint)
		{
			SetCurrentValue(fValue );
			m_pxCurrentPoint->m_fTime = fTime;
			ClampCurrentTime();
			OnChanged();
		}
	}
	void AdjustToFit(bool bShrinkToFit, bool bGrowToFit)
	{
		Asura_Assert(bShrinkToFit || bGrowToFit, "Nothing to do");
		Asura_Graph_Point<T>* pxCurrentPointBackup = m_pxCurrentPoint;
		u_char ucCurrentDimensionBackup = m_ucCurrentDimension;
		SetCurrentPoint(m_pxGraph->GetControlPoints()->m_pxNext);
		float fGraphMaximum = Asura_Maths::LargeFloat * -1.f;
		float fGraphMinimum = Asura_Maths::LargeFloat;
		while(m_pxCurrentPoint->m_pxNext)
		{
			for(m_ucCurrentDimension = 0; m_ucCurrentDimension < GetNumberOfDimensions(); ++m_ucCurrentDimension)
			{
				float fValue = GetCurrentValue();
				fGraphMaximum = Asura_Maths::Max(fValue, fGraphMaximum);
				fGraphMinimum = Asura_Maths::Min(fValue, fGraphMinimum);
			}
			SetCurrentPoint(m_pxCurrentPoint->m_pxNext);
		}
		if(bGrowToFit)
		{
			m_fRangeMax = Asura_Maths::Max(m_fRangeMax, fGraphMaximum);
			m_fRangeMin = Asura_Maths::Min(m_fRangeMin, fGraphMinimum);
		}
		if(bShrinkToFit)
		{
			m_fRangeMax = Asura_Maths::Min(m_fRangeMax, fGraphMaximum);
			m_fRangeMin = Asura_Maths::Max(m_fRangeMin, fGraphMinimum);
		}
		SetCurrentPoint(pxCurrentPointBackup);
		m_ucCurrentDimension = ucCurrentDimensionBackup;
	}
	const Asura_Graph_Point<T>* GetCurrentPoint() const { return m_pxCurrentPoint; }

	bool CanUndo() const
	{
		return m_pxCurrentPoint && m_pxOriginalValuePoint == m_pxCurrentPoint
		 && (m_pxCurrentPoint->m_xValue != m_xOriginalValue || m_pxCurrentPoint->m_fTime != m_fOriginalTime);
	}

	void UndoPoint()
	{
		// Added check that the current point was the one for which the original values were recorded
		// RAR 09/10/2006
		if(m_pxCurrentPoint && m_pxOriginalValuePoint == m_pxCurrentPoint)
		{
			m_pxCurrentPoint->m_xValue = m_xOriginalValue;	
			m_pxCurrentPoint->m_fTime = m_fOriginalTime;
			m_pxOriginalValuePoint = 0;
			SetCurrentPoint(NULL);
			m_ucCurrentDimension = 0xff;
			OnChanged();
		}
	}

	static void UndoPoint_Static( int iData )
	{
		Asura_Graph_Gadget<T>* const pxThis = reinterpret_cast< Asura_Graph_Gadget<T>* >( iData );
		if( pxThis )
		{
			pxThis->UndoPoint();
		}
		else
		{
			Asura_Assert( false, "Invalid parameter passed to Asura_Graph_Gadget::UndoPoint_Static()." );
		}
	}

	static void SnapLeftEnd( int iData )
	{
		Asura_Graph_Gadget<T>* const pxThis = reinterpret_cast< Asura_Graph_Gadget<T>* >( iData );
		if( pxThis && pxThis->m_pxGraph )
		{
			Asura_Graph_Point<T>* const pxLeftEndpoint = pxThis->m_pxGraph->GetControlPoints();
			if( pxLeftEndpoint && pxLeftEndpoint->m_pxNext && pxLeftEndpoint->m_pxNext->m_pxNext )
			{
				// Snap the first, hidden, point, and the first real one
				pxLeftEndpoint->m_xValue = pxLeftEndpoint->m_pxNext->m_pxNext->m_xValue;
				pxLeftEndpoint->m_pxNext->m_xValue = pxLeftEndpoint->m_pxNext->m_pxNext->m_xValue;
				pxThis->OnChanged();
			}
		}
		else
		{
			Asura_Assert( false, "Invalid parameter passed to Asura_Graph_Gadget::SnapLeftEnd()." );
		}
	}

	static void SnapRightEnd( int iData )
	{
		Asura_Graph_Gadget<T>* const pxThis = reinterpret_cast< Asura_Graph_Gadget<T>* >( iData );
		if( pxThis && pxThis->m_pxGraph )
		{
			Asura_Graph_Point<T>* pxRightEndpoint = pxThis->m_pxGraph->GetControlPoints()->m_pxNext;
			if( pxRightEndpoint )
			{
				while( pxRightEndpoint->m_pxNext )
				{
					pxRightEndpoint = pxRightEndpoint->m_pxNext;
				}

				if( pxRightEndpoint->m_pxPrev && pxRightEndpoint->m_pxPrev->m_pxPrev )
				{
					// Snap the last, hidden, point, and the last real one
					pxRightEndpoint->m_xValue = pxRightEndpoint->m_pxPrev->m_pxPrev->m_xValue;
					pxRightEndpoint->m_pxPrev->m_xValue = pxRightEndpoint->m_pxPrev->m_pxPrev->m_xValue;

					pxThis->OnChanged();
				}
			}
		}
		else
		{
			Asura_Assert( false, "Invalid parameter passed to Asura_Graph_Gadget::SnapRightEnd()." );
		}
	}

	static void Copy( int iData )
	{
		Asura_Graph_Gadget<T>* const pxThis = reinterpret_cast< Asura_Graph_Gadget<T>* >( iData );
		if( pxThis && pxThis->m_pxGraph )
		{
			s_xPasteBuffer = *pxThis->m_pxGraph->GetGraphSourceData();
			Tools_Timed_Message_System::AddMessage(0xffffff, "Copied graph points.");
		}
		else
		{
			Asura_Assert( false, "Invalid parameter passed to Asura_Graph_Gadget::Copy()." );
		}
	}

	static void Paste( int iData )
	{
		Asura_Graph_Gadget<T>* const pxThis = reinterpret_cast< Asura_Graph_Gadget<T>* >( iData );
		if( pxThis && pxThis->m_pxGraph )
		{
			const Asura_Graph_Source_Data<T>* pxSource = &s_xPasteBuffer;
			Asura_Graph_Source_Data<T>* pxDest = pxThis->m_pxGraph->GetGraphSourceData();

			if(pxThis->CanPaste())
			{
				*pxDest = *pxSource;
				pxThis->m_pxCurrentPoint = 0;
				pxThis->m_pxOriginalValuePoint = 0;
				pxThis->OnChanged();
				pxThis->AdjustToFit(false,true);

				Tools_Timed_Message_System::AddMessage(0xffffff, "Pasted graph points.");
			}

		}
		else
		{
			Asura_Assert( false, "Invalid parameter passed to Asura_Graph_Gadget::Paste()." );
		}
	}

	bool CanPaste() const
	{
		const Asura_Graph_Source_Data<T>* pxSource = &s_xPasteBuffer;
		const Asura_Graph_Source_Data<T>* pxDest = m_pxGraph->GetGraphSourceData();

		//make sure the clamps in the current graph match those in the paste buffer
		//To prevent copying between completely different types of graph
		bool bClampsMatch = true;
		if(pxDest->m_bUseClampMin != pxSource->m_bUseClampMin) bClampsMatch = false;
		if(pxDest->m_bUseClampMax != pxSource->m_bUseClampMax) bClampsMatch = false;
		if(pxDest->m_bUseClampMin && pxDest->m_fClampMin != pxSource->m_fClampMin) bClampsMatch = false;
		if(pxDest->m_bUseClampMax && pxDest->m_fClampMax != pxSource->m_fClampMax) bClampsMatch = false;

		return bClampsMatch;
	}
	

private :
	void ClampCurrentTime()
	{
		// First point is always at time = domain minimum
		if(!m_pxCurrentPoint->m_pxPrev->m_pxPrev)
		{
			m_pxCurrentPoint->m_fTime = m_pxGraph->GetDomainMin();
		}
		// Last point is always at time = domain maximum
		if(!m_pxCurrentPoint->m_pxNext->m_pxNext)
		{
			m_pxCurrentPoint->m_fTime = m_pxGraph->GetDomainMax();
		}
		// Time cannot be larger than next nor smaller than previous
		if(m_pxCurrentPoint->m_fTime > m_pxCurrentPoint->m_pxNext->m_fTime)
		{
			m_pxCurrentPoint->m_fTime = m_pxCurrentPoint->m_pxNext->m_fTime;
		}
		if(m_pxCurrentPoint->m_fTime < m_pxCurrentPoint->m_pxPrev->m_fTime)
		{
			m_pxCurrentPoint->m_fTime = m_pxCurrentPoint->m_pxPrev->m_fTime;
		}
	}
	// Helper render functions
	inline float ScreenToGraphPosX(int iValue) const
	{
		const float fDomainMin = m_pxGraph->GetDomainMin();
		const float fDomainMax = m_pxGraph->GetDomainMax();

		const float fScale = fDomainMax - fDomainMin;
		return Asura_Maths::ClampToRange((fDomainMin + fScale * (iValue - GetGraphOrigin().x) / m_pxGraphDialog->GetGraphPixelWidth()), fDomainMin, fDomainMax);
	}
	inline float GraphToScreenPosX(float fTime) const
	{
		const float fDomainMin = m_pxGraph->GetDomainMin();
		const float fDomainMax = m_pxGraph->GetDomainMax();

		const float fScale = fDomainMax - fDomainMin;
		return GetGraphOrigin().x + (Asura_Maths::ClampToRange(fTime, fDomainMin, fDomainMax) - fDomainMin ) * m_pxGraphDialog->GetGraphPixelWidth() / fScale;
	}
	inline float ScreenToGraphPosY(int iValue) const
	{
		const float fScale = m_fRangeMax - m_fRangeMin;
		return Asura_Maths::ClampToRange((m_fRangeMax - fScale * (iValue - GetGraphOrigin().y) / m_pxGraphDialog->GetGraphPixelHeight()), m_fRangeMin, m_fRangeMax);
	}
	inline float GraphToScreenPosY(float fValue) const
	{
		const float fScale = m_fRangeMax - m_fRangeMin;
		return GetGraphOrigin().y + (m_fRangeMax - Asura_Maths::ClampToRange(fValue, m_fRangeMin, m_fRangeMax)) * m_pxGraphDialog->GetGraphPixelHeight() / fScale;
	}
	inline Asura_Vector_2 GetGraphOrigin() const
	{
		Asura_Vector_2 xPos = m_xOrigin + m_xOffset;
		xPos.x *= 10.0f;
		xPos.y *= 14.0f;
		xPos.x += ASURA_GRAPH_BORDER_SIZE;
		xPos.y += ASURA_GRAPH_BORDER_SIZE;
		return xPos;
	}
	inline u_char GetAlphaForPoint(const Asura_Graph_Point<T>* pxPoint) const
	{
		u_char ucAlpha;
		if(pxPoint == m_pxCurrentPoint)
		{
			ucAlpha = (unsigned char)(0xff * Asura_Timers::GetLocalUnscaledTime());
			if(ucAlpha < 0x80) ucAlpha = static_cast< unsigned char >( 0xff - ucAlpha );
		}
		else
		{
			ucAlpha = 0x80;
		}
		return ucAlpha;
	}
	inline bool MouseOverControlBox(float fTime, float fValue) const
	{
		Asura_Vector_2 xPos;
		xPos.x = GraphToScreenPosX(fTime) - ASURA_GRAPH_CTRL_PT_SIZE * 0.5f;
		xPos.y = GraphToScreenPosY(fValue) - ASURA_GRAPH_CTRL_PT_SIZE * 0.5f;
		return Asura_Cursor::InsideScreenSpace(xPos, ASURA_GRAPH_CTRL_PT_SIZE, ASURA_GRAPH_CTRL_PT_SIZE);
	}
	void OnChanged()
	{
		m_pxGraph->Update();
		if (m_pbChangedFlag)
		{
			*m_pbChangedFlag = true;
		}
	}
	const char* GetCurrentValueLabel() const
	{
		if(m_pxCurrentPoint)
		{
			return GetLabelForDimension();
		}
		else
		{
			return "-:";
		}
	}
	void RenderWhiteBackground() const
	{
		const Asura_Vector_2 xBase = GetGraphOrigin();
		Asura_Render::Overlay.Colour = IsEnabled() ? (0x00ffffff | Asura_OverlayBrightness) : (0x00555555 | Asura_OverlayBrightness);
		Asura_Render::Overlay.SetRectXY(xBase.x, xBase.y, m_pxGraphDialog->GetGraphPixelWidth(), m_pxGraphDialog->GetGraphPixelHeight());
		Asura_Render::Overlay.Render();

		Asura_Render::FlushDisplay();
	}
	bool ShiftOrScaleValues(float fAmount)
	{
		bool bHitLimit = false;
		float fShiftAmount = fAmount * 0.007f * (m_fRangeMax - m_fRangeMin) * 0.5f;//RAR 21/02/2008 halved the sensitivity
		float fMaxPossibleAmount = fShiftAmount;
		float fClampMax = Asura_Maths::LargeFloat, fClampMin = -Asura_Maths::LargeFloat;
		m_pxGraph->GetClampMax(fClampMax);
		m_pxGraph->GetClampMin(fClampMin);


		Asura_Graph_Point<T>* pxInitialPoint = m_pxCurrentPoint;
		unsigned char ucInitialDimension = m_ucCurrentDimension;

		extern bool evilg_bAsura_Graph_Gadget_LimitShiftOrScaleToCurrentPoint;
		bool bLimitToCurrentPoint = evilg_bAsura_Graph_Gadget_LimitShiftOrScaleToCurrentPoint;
		if(bLimitToCurrentPoint && !pxInitialPoint) return false;


		// Find min and max to see how far we can scale/shift within the clamp
		float fMax = -Asura_Maths::LargeFloat, fMin = Asura_Maths::LargeFloat;
		for(Asura_Graph_Point<T>* pxPoint = m_pxGraph->GetControlPoints()->m_pxNext; pxPoint->m_pxNext; pxPoint = pxPoint->m_pxNext)
		{
			if(bLimitToCurrentPoint && pxPoint != pxInitialPoint) continue;
			SelectPoint(pxPoint);
			float fValue, fTime;
			for(m_ucCurrentDimension = 0; m_ucCurrentDimension < GetNumberOfDimensions(); ++m_ucCurrentDimension)
			{
				if(!bLimitToCurrentPoint)
				{
					if(ucInitialDimension != 0xff && m_ucCurrentDimension != ucInitialDimension) continue;
				}
				GetCurrentValues(&fValue, &fTime);
				fMax = Asura_Maths::Max(fMax, fValue);
				fMin = Asura_Maths::Min(fMin, fValue);
			}
		}
		// Calculate new values
		float fNewMax, fNewMin;
		if(m_bScaling)
		{
			fNewMax = fMax * (1.f - fAmount * 0.01f);
			fNewMin = fMin * (1.f - fAmount * 0.01f);
		}
		else// shifting
		{
			fNewMax = fMax - fShiftAmount;
			fNewMin = fMin - fShiftAmount;
		}
		// Find furthest we can move.
		// TODO - handle scaling too.
		if(fNewMax > fClampMax)
		{
			if(!m_bScaling)
			{
				const float fPossibleAmount = (fMax - fClampMax);
				fMaxPossibleAmount = Asura_Maths::Max(fMaxPossibleAmount, fPossibleAmount);
			}
			bHitLimit = true;
		}
		else if(fNewMin < fClampMin)
		{
			if(!m_bScaling)
			{
				const float fPossibleAmount = (fMin - fClampMin);
				fMaxPossibleAmount = Asura_Maths::Min(fMaxPossibleAmount, fPossibleAmount);
			}
			bHitLimit = true;
		}
		// Move as far as we can
		fShiftAmount = fMaxPossibleAmount;
		if(!m_bScaling || !bHitLimit)
		{
			for(Asura_Graph_Point<T>* pxPoint = m_pxGraph->GetControlPoints()->m_pxNext; pxPoint->m_pxNext; pxPoint = pxPoint->m_pxNext)
			{
				if(bLimitToCurrentPoint && pxPoint != pxInitialPoint) continue;
				
				SelectPoint(pxPoint);
				float fValue, fTime;
				for(m_ucCurrentDimension = 0; m_ucCurrentDimension < GetNumberOfDimensions(); ++m_ucCurrentDimension)
				{
					if(ucInitialDimension != 0xff && m_ucCurrentDimension != ucInitialDimension && !bLimitToCurrentPoint)
					{
						//If there was a current dimension set before the scale/shift operation
						//then only scale that dimension. RAR 26/11/04
						continue;
					}						
					GetCurrentValues(&fValue, &fTime);
					if(m_bScaling)
					{
						fValue *= 1.f - fAmount * 0.01f;
					}
					else// shifting
					{
						fValue -= fShiftAmount;
					}
					// Clamp
					fValue = Asura_Maths::ClampToRange(fValue, fClampMin, fClampMax);
					// Move range if necessary
					if(fValue > m_fRangeMax && m_fRangeMax < fClampMax)
					{
						m_fRangeMax = fValue;
					}
					if(fValue < m_fRangeMin && m_fRangeMin > fClampMin)
					{
						m_fRangeMin = fValue;
					}
					SetCurrentValues(fValue, fTime);
				}
			}
			OnChanged();
		}
		// Cleanup
		SetCurrentPoint(pxInitialPoint);
		m_ucCurrentDimension = ucInitialDimension;
		return bHitLimit;
	}
	void SetCurrentPoint(Asura_Graph_Point<T>* pxPoint)
	{
		m_pxCurrentPoint = pxPoint;
		m_bCurrentPointChanged = true;
	}

	bool IsDimensionVisible(u_int uDimension) const {return m_ucVisibleDimension == 0xff || m_ucVisibleDimension == uDimension;}

	void RenderLineSegment(float fStartTime, float fEndTime, unsigned char ucAlpha) const
	{
		float fX1 = GraphToScreenPosX(fStartTime);
		float fX2 = GraphToScreenPosX(fEndTime);
		T xV1 = m_pxGraph->GetValueAtTime(fStartTime); 
		T xV2 = m_pxGraph->GetValueAtTime(fEndTime);

		u_int uNumDimensions = GetNumberOfDimensions();
		for(u_int i=0;i<uNumDimensions;i++)
		{
			if(!IsDimensionVisible(i)) continue;

			float fYValue1 = GetValue (xV1, (u_char) i);
			float fYValue2 = GetValue (xV2, (u_char) i);

			if (m_bForceNormalisedAlpha && (i == 3)) // If alpha channel of Asura_RGBA template type
			{
				// The alpha value goes from 0.0f to 1.0f, but we should draw this line using the full height of the graph if the m_bForceNormalisedAlpha flag is set

				fYValue1 = (fYValue1 * (m_fRangeMax - m_fRangeMin)) + m_fRangeMin;
				fYValue2 = (fYValue2 * (m_fRangeMax - m_fRangeMin)) + m_fRangeMin;
			}

			float fY1 = GraphToScreenPosY (fYValue1);
			float fY2 = GraphToScreenPosY (fYValue2);
			
			Asura_Render::Render2DLine(fX1, fY1, fX2, fY2, GetDimensionColour(i) | (ucAlpha << 24));
		}
	}

	void RenderControlPoint(const Asura_Graph_Point<T>* pxPoint) const
	{
		unsigned char ucAlpha = GetAlphaForPoint(pxPoint);
		
		u_int uNumDimensions = GetNumberOfDimensions();
		float fX = GraphToScreenPosX(pxPoint->m_fTime);
		for(u_int i=0;i<uNumDimensions;i++)
		{
			//draw the points in order so that the current dimension will be done last
			u_char ucDim = (u_char)((i+1+m_ucCurrentDimension) % uNumDimensions);
			if(!IsDimensionVisible(ucDim)) continue;

			unsigned int uColour = GetDimensionColour(ucDim);
			uColour |= (ucDim==m_ucCurrentDimension) ? (ucAlpha << 24) : 0x80000000;

			float fYValue = GetValue (pxPoint, ucDim);

			if (m_bForceNormalisedAlpha && (ucDim == 3)) // If alpha channel of Asura_RGBA template type
			{
				// The alpha value goes from 0.0f to 1.0f, but we should draw this line using the full height of the graph if the m_bForceNormalisedAlpha flag is set

				fYValue = (fYValue * (m_fRangeMax - m_fRangeMin)) + m_fRangeMin;
			}

			RenderPoint(fX, GraphToScreenPosY (fYValue), uColour, m_ucCurrentDimension == ucDim);
		}
	}

	void RenderPoint(float fX, float fY, u_int uColour, bool bEnlarge = false) const
	{
		static int iTextureIndex = -1;
		if(iTextureIndex == -1)
		{
			iTextureIndex = Asura_Texture_Management::AddTextureWithResetGroup("\\tools\\graphnode.tga", ASURA_TEXTURE_LOADED_PERMANENT);
			Asura_Texture_Management::LoadTextures();
		}
		// Just a block for now. A bit ugly but functional
		float fRadius = ASURA_GRAPH_CTRL_PT_SIZE * 0.5f;
		if(bEnlarge) fRadius += 1.5f;
		Asura_Render::Overlay.TextureIndex = iTextureIndex;
		Asura_Render::Overlay.TranslucencyMode = ASURA_TRANSLUCENCY_NORMAL;
		Asura_Render::Overlay.SetRectUV(0.f, 0.f, 1.f, 1.f);
		Asura_Render::Overlay.SetRectXY(fX - fRadius, fY - fRadius, fRadius * 2, fRadius * 2);
		Asura_Render::Overlay.Colour = uColour;
		Asura_Render::Overlay.Render();	
	}

	int MouseOverControlPoint(const Asura_Graph_Point<T>* pxPoint)
	{
		u_int uNumDimensions = GetNumberOfDimensions();
		//Test against visible dimensions
		for(u_int i=0;i<uNumDimensions;i++)
		{
			if(!IsDimensionVisible(i)) continue;

			float fYValue = GetValue (pxPoint, (u_char) i);

			if (m_bForceNormalisedAlpha && (i == 3)) // If alpha channel of Asura_RGBA template type
			{
				fYValue = (fYValue * (m_fRangeMax - m_fRangeMin)) + m_fRangeMin;
			}

			if(MouseOverControlBox(pxPoint->m_fTime, fYValue))
			{
				return i;
			}
		}

		return -1;
	}

	float SelectPointIfCloser(Asura_Graph_Point<T>* pxPoint, float fClosestDistance)
	{
		// Get cursor position
		Asura_Vector_2 xCursorPos;
		int uX, uY;
		Asura_Cursor::GetScreenSpaceCoords(uX, uY);
		xCursorPos.x = static_cast<float>(uX);
		xCursorPos.y = static_cast<float>(uY);

		//Get x position for the points
		Asura_Vector_2 xPointPos;
		xPointPos.x = GraphToScreenPosX(pxPoint->m_fTime);

		float fMinDistance = Asura_Maths::LargeFloat;
		u_int uClosestDim = 0;

		//Compare against visible points
		u_int uNumDimensions = GetNumberOfDimensions();
		for(u_int i=0;i<uNumDimensions;i++)
		{
			if(!IsDimensionVisible(i)) continue;

			float fYValue = GetValue (pxPoint, (u_char) i);

			if (m_bForceNormalisedAlpha && (i == 3)) // If alpha channel of Asura_RGBA template type
			{
				fYValue = (fYValue * (m_fRangeMax - m_fRangeMin)) + m_fRangeMin;
			}

			// Get y position
			xPointPos.y = GraphToScreenPosY (fYValue);

			float fDistance = (xPointPos - xCursorPos).Magnitude();
			if(fDistance < fMinDistance)
			{
				fMinDistance = fDistance;
				uClosestDim = i;
			}
		}

		if(fMinDistance < fClosestDistance)
		{
			// If closer, select and return new closest
			SelectPoint(pxPoint);
			m_ucCurrentDimension = (u_char) uClosestDim;
			return fMinDistance;
		}

		return fClosestDistance;
	}


	// Class specific functions - must be implemented for each type you want to use
	float GetValue(const Asura_Graph_Point<T>* pxPoint, u_char ucDimension) const {return GetValue(pxPoint->m_xValue,ucDimension);}
	float GetValue(const T& xValue, u_char ucDimension) const;
	float GetCurrentValue() const { return GetValue(m_pxCurrentPoint, m_ucCurrentDimension); }
	void SetCurrentValue(float fValue);
	u_int GetNumberOfDimensions() const;
	const char* GetLabelForDimension() const {return GetLabelForDimension(m_ucCurrentDimension);}
	const char* GetLabelForDimension(u_char uDimension) const;
	void RenderBackground() const;
	void PlaceCursorOnCurrentPoint();
	u_int GetDimensionColour(u_int uDimension) const;

	bool GetForceNormalisedAlpha () const { return m_bForceNormalisedAlpha; }
	void SetForceNormalisedAlpha (const bool bFlag) { m_bForceNormalisedAlpha = bFlag; }

	Asura_Graph<T>*			m_pxGraph;
	Asura_Graph_Dialog<T>*	m_pxGraphDialog;
	Asura_Graph_Point<T>*	m_pxCurrentPoint;
	u_int					m_uNumCustomGridlinesX;
	CustomGridline*			m_pxGraphCustomGridlinesX;
	u_int					m_uNumCustomGridlinesY;
	CustomGridline*			m_pxGraphCustomGridlinesY;
	// To determine which of x, y, z (or RGBA) we are currently editing
	unsigned char			m_ucCurrentDimension;
	unsigned char			m_ucVisibleDimension;//index of dimension being shown , or 0xff for all
	T						m_xOriginalValue;
	float					m_fOriginalTime;
	Asura_Graph_Point<T>*	m_pxOriginalValuePoint;//point for which the original values above apply to
	bool					m_bDragging;
	float					m_fRangeMax;
	float					m_fRangeMin;
	float					m_fSelectionTimer;
	float					m_fGridlineSpacingX;
	float					m_fGridlineSpacingY;
	bool					m_bCurrentPointChanged;
	bool					m_bShowDiscreteSamples;
	// Valid for the current frame; updated with render and update
	Asura_Vector_2			m_xOrigin;
	bool					*m_pbChangedFlag;
	// For scaling and shifting
	bool					m_bScaling;
	bool					m_bShifting;
	int						m_iFreezeMouseX;
	int						m_iFreezeMouseY;
	//Flags for dimension filter buttons
	u_int					m_uVisibleDimensionFlags;
	u_int					m_uPrevVisibleDimensionFlags;
	//Used only in Asura_RGBA
	bool					m_bForceNormalisedAlpha;

	//stores graph points for copy/paste
	static Asura_Graph_Source_Data<T> s_xPasteBuffer;

};







#endif//__ASURA_GRAPH_GADGET_H__
