/******************************************************************************
 * $Id: gfx_gui.hh,v 1.75 2006/10/10 04:14:55 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Simple lightweight GUI inside of a viewport.
 *          Provides GUI objects such as text fonts, buttons, etc.
 *          GUI objects are drawn blended over prior rendering.
 * @remarks @verbatim
 *
 * To bring up the GUI:
 * --------------------
 *
 * 1. Construct a Gui object.
 * 2. Your application must call Gui::TimerOneSecond() (see note).
 * 3. Your application must handle mouse events and pass them to Gui::MouseEvent().
 * 4. Add a call to Gui::ReshapeViewport() in your system-specific (eg OpenGL)
 *    reshape function so that the GUI object can adjust accordingly.
 * 5. Add a call to Gui::Render() in your system-specific rendering routine
 *    to render the GUI.  The call should be one of the last drawing commands
 *    so that the GUI will appear over everything else.
 * 6. Call GUI::NewScreen() (not GuiScreen::GuiScreen() ) to make a new screen.
 *    A "screen" is just a set of GUI components that are blended over the viewport.
 *    Screens can be flipped like pages in a book, and multiple screens can be
 *    shown at the same time (overlay).
 *
 * Notes:
 * Timer management is delegated to the application.  This class doesn't use
 * its own timer.  The reason is that multiple timers would very likely cause
 * concurrency issues.  On OpenGL, using a single timer is strongly suggested.
 *
 * To use GUI objects:
 * -------------------
 *
 * 1. Construct GUI objects via a GuiScreen object rather than directly.
 *    The reasoning is that GUI objects belong to and are managed by
 *    a GuiScreen object.  Eg, to make a new button, call GuiScreen::NewButton().
 *
 * Screens:
 * --------
 *
 * A screen's visibility can be changed by GuiScreen::SetVisi().
 * If the screen isn't visible, none of its objects will be rendered.
 * Multiple screens are allowed to be visible at the same time.
 *
 * Printing in a screen:
 * ---------------------
 *
 * In your render routine, put calls to GuiScreen::Print() to display text.
 * Print() must be called every frame.
 *
 * However, GuiScreen::PrintTimer() is intended to be called once.  Thereafter,
 * the GuiScreen class will automatically print it until its timer expires.
 *
 * GuiScreen::Push/PopPrint() are provided to facilitate changing fonts while printing.
 * PushPrint() is a push-then-change operation.
 *
 *    MyRenderMainRoutine()
 *    {
 *       screen->PushPrint();
 *          screen->Print(); [...]
 *          ...
 *          MyRenderSubroutine1();
 *          MyRenderSubroutine2();
 *          ...
 *       screen->PopPrint();
 *    }
 *
 *    MyRenderSubroutine1()
 *    {
 *       screen->PushPrint();
 *          screen->Print();
 *       screen->PopPrint();
 *    }
 *
 * Buttons:
 * --------
 *
 * A callback is called whenever a GuiButton is clicked.
 * Your callback can call GuiButton::SetText() to change
 * the text shown in the button.
 *
 * @endverbatim
 *//*
 * LEGAL:	COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 ******************************************************************************/

#ifndef GFX_GUI_HH
#define GFX_GUI_HH

#include <stack>
#include "gfx_stack.hh"
#include "gfx_rgb.hh"
#include "gfx_texture.hh"
#include "gfx_font.hh"

namespace gfx {

const uint GUIBASE_PUSH_FONT_SOFT_LIMIT = 30;

class GuiScreen;
class GuiObject;
class GuiButton;
class Font;

// -----------------------------------------------------------------------------
// Fonts.
enum eScreenFontType
{
	eScreenFontType_NORMAL,
	eScreenFontType_THIN
};
enum eScreenFontSize
{
	eScreenFontSize_SMALLEST,
	eScreenFontSize_SMALLER,
	eScreenFontSize_SMALL,
	eScreenFontSize_NORMAL,
	eScreenFontSize_LARGE,
	eScreenFontSize_LARGER,
	eScreenFontSize_LARGEST,
};
////////////////////////////////////////////////////////////////////////////////
// Gfont is a wrapper around underlying lower-level Font class.
// A Gfont can be repointed to a different Font object.
class Gfont
{
//PREVENT_COPYING(Gfont)
public:
        			Gfont( FontPtrConst font, eScreenFontSize size, float zoom, const RGBA rgba );
    				~Gfont();
    void            SetFont( FontPtrConst font )     { mFont = font; }
    FontPtrConst    GetFont( void ) const            { return mFont; }
    void            SetSize( eScreenFontSize size )  { mSize = size; }
    eScreenFontSize GetSize( void ) const            { return mSize; }
    void            SetZoom( float zoom );
    float           GetZoom( void ) const            { return mZoom; }
    void            SetRgba( const RGBA rgba )       { mRgba = rgba; }
    RGBA            GetRgba( void ) const            { return mRgba; }
    int             Print( const char* text, uint textLen, float x, float y, eFontMode fontMode );
    int             Print( const char* text, uint textLen, float x, float y, eFontMode fontMode, const RGBA rgba );

private:
	FontPtr 			mFont;      // underlying font object: may be repointed but never deleted
	eScreenFontSize		mSize;      // parameters to underlying font object
	float				mZoom;
	RGBA				mRgba;
};

// -----------------------------------------------------------------------------
// Mouse.
typedef void (*fnMouseButtonCallback)(GuiObject* obj, uint buttons);
enum eMouseEvent
{
	eMouseEvent_NONE			= 0,
	eMouseEvent_MOTION_ENTER	= (1<<0),
	eMouseEvent_MOTION_EXIT		= (1<<1),
	eMouseEvent_BUTTON_1		= (1<<2),
	eMouseEvent_BUTTON_2		= (1<<3),
	eMouseEvent_BUTTON_3		= (1<<4),
	eMouseEvent_BUTTON_MASK		= (eMouseEvent_BUTTON_1|eMouseEvent_BUTTON_2|eMouseEvent_BUTTON_3)
};

////////////////////////////////////////////////////////////////////////////////
/// @brief GUI base class.
class GuiBase
{
    PREVENT_COPYING(GuiBase)
public:
						GuiBase( void );
	virtual				~GuiBase();
	virtual void		Render( void );
	// screens
	GuiScreen*			NewScreen( void );
	void				DeleteScreen( GuiScreen* screen );
	// viewport
	void				ReshapeViewport( const Geom<int>& viewport, const Geom<int>& window );
	void				SetZoom( float f ) { mZoom = f; }
	float				GetZoom( void )    { return mZoom; }
	float				GetZoomViewport( void ) { return mZoomViewport; }
public:
	/// Your application must call this every second.
	void				AppTimerOneSecond( void );
	/// Your application must pass mouse events to this.
	void				AppMouseMotionHandler( float x, float y );
	/// Your application must pass mouse events to this.
	void				AppMouseButtonHandler( eMouseEvent button, bool down );
	// mouse
	bool				GetMousePos( float& x /*OUT*/, float& y /*OUT*/ ); ///< returns false if mouse outside viewport
	void				SetMousePos( const float x, const float y );
	virtual void		MousePos2ViewportPos( const Pos2d<float>& in, Pos2d<float>& out ) = 0;
	uint				GetMouseButtons( void );
	void				SetMouseButtons( uint buttons );
	/// font wrappers for related Gui* classes
	FontPtrConst		GetButtonFont( void ) { return mLfontButton; }

private:
	void				MouseHandler( bool motionEvent );
	FontPtrConst		GetFont( eScreenFontType type );

public:
	Geom<float>				mViewport;			///< actually integral but float simplifies code
	Geom<float>				mViewportInitial;
	Geom<float>				mWindow;
protected:
	float					mZoom;				///< zoom is a base scale
	float					mZoomViewport;		///< zoomViewport is an additional scale for viewport geom
	float					mViewportDelta[2];	///< [x,y] compensates for viewport's offsets to window
	Dlist<GuiScreen>		mScreenDlist;
	Pos2d<float>			mMousePos;
	uint					mMouseButtons;		///< a bit for each button

protected:
	// --------- logical fonts --------------------
	/// These are pointer to actual Font objects.
	/// Must be assigned by the derived Gui class.
	Font*					mLfontText;
	Font*					mLfontTextThin;
	Font*					mLfontButton;
	// --------- logical fonts --------------------

friend class GuiScreen;
friend class GuiObject;
friend class GuiPrint;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief This class serves an argument structure to GuiScreen::PrintTimer().
class PrintDesc
{
    PREVENT_COPYING(PrintDesc)
public:
						PrintDesc( void );
public:
	string				mText;
	Pos2d<float>		mPos;
	RGBA        		mRgba;
	eFontMode			mFontMode;
	eScreenFontType		mFontType;
	eScreenFontSize		mFontSize;

friend class GuiPrint;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief Screen class.
/// @remarks
/// ########################################
/// ## Users should call Gui::NewScreen() ##
/// ########################################
class GuiScreen
{
    PREVENT_COPYING(GuiScreen)
public:
	explicit			GuiScreen( GuiBase& gui );
						~GuiScreen();
	void				Render( void );
	// visibility
	void				SetVisi( bool f )	{ mVisible = f; }
	bool				IfVisi( void )      { return mVisible; }
	void				ToggleVisi( void )	{ mVisible = ! mVisible; }
	// sensitivity
	void				SetSens( bool f )	{ mSensitive = f; }
	void				ToggleSens( void )	{ mSensitive = ! mSensitive; }
	bool				IfSens( void )		{ return IfVisi() && mSensitive; }
	// shorthand
	void				SetVisiSens( bool f ) { SetVisi(f); SetSens(f); }
	// printing, fonts
	int					Print( const char* text, uint textLen, float x, float y, eFontMode fontMode );
	int					Print( const char* text, uint textLen, float x, float y, eFontMode fontMode, const RGBA rgba );
	void				PrintTimer( int seconds, const PrintDesc& desc );
						// amount of messages that are still alive
	uint				PrintTimerPopulation( void ) { return mPrintTimerCnt; }
						// can be used to avoid printing duplicate messages
	bool				IfPrintTimerDuplicate( const char* str );
	void				SetPrintColor( const RGBA rgba );
	// PushPrint() minus the RGBA arg keeps the current color.
	void				PushPrint( eScreenFontType type, eScreenFontSize size ); // push-then-change
	void				PushPrint( eScreenFontType type, eScreenFontSize size, const RGBA rgba );  // push-then-change
	void				PopPrint( void );
private:
    void                PushFont( Gfont& gfont, eScreenFontType type, eScreenFontSize size, const RGBA rgba );
    void                PopFont( Gfont& gfont );
public:
	// button
	GuiButton*			NewButton( float x, float y, eFontMode fontMode, fnMouseButtonCallback callback );
	void				DeleteButton( GuiButton* button );

private:
	void				LinkObject( GuiObject* obj );
	void				UnlinkObject( GuiObject* obj );

	GuiBase&				mGui;
	bool					mVisible;		// whether to render all objects or none
	bool					mSensitive;		// whether will respond to events
	Dlink<GuiScreen>*		mLinkScreen;	// link in GuiBase's screen list
	uint					mPrintTimerCnt;	// amount of PrintTimer() that are alive
	Gfont					mGfont;         // font used by Print()
	Dlist<GuiObject>		mObjectDlist;	// list of objects in this screen
    std::stack<Gfont>       mGfontStack;
friend class GuiBase;
friend class GuiObject;
};

enum eGuiObject
{
	eGuiObject_INVALID,
	eGuiObject_BASE,
	eGuiObject_PRINT,
	eGuiObject_BUTTON,
};

////////////////////////////////////////////////////////////////////////////////
// GUI object base class.
class GuiObject
{
    PREVENT_COPYING(GuiObject)
public:
						GuiObject( GuiScreen& screen, float x, float y, fnMouseButtonCallback buttonCallback );
	virtual				~GuiObject();
	virtual void		Render( void ) = 0;
	// visibility
	void            	SetVisi( bool f )	{ mVisible = f; }
	bool            	IfVisi( void )		{ return mScreen.IfVisi() && mVisible; }
	void            	ToggleVisi( void )	{ mVisible = ! mVisible; }
	// sensitivity
	void				SetSens( bool f )	{ mSensitive = f; }
	void				ToggleSens( void )	{ mSensitive = ! mSensitive; }
	bool				IfSens( void )		{ return IfVisi() && mSensitive; }
	// shorthand
	void				SetVisiSens( bool f ) { SetVisi(f); SetSens(f); }
	// position
	void        		GetPos( float& x /*OUT*/, float& y /*OUT*/ );
	void            	SetPos( float x, float y );
protected:
	bool				IfHasArea( void ) { return int(mRect.x2 - mRect.x1) | int(mRect.y2 - mRect.y1); }
	void				SetZeroArea( void ) { mRect.x1 = mRect.x2 = mRect.y1 = mRect.y2 = 0; }
protected:

	GuiBase&				mGui;			// reference to Gui object
	GuiScreen&				mScreen;		// which screen object belongs to
	eGuiObject				mType;			// which type of GuiObject
	bool					mVisible;		// ###!!! Use *Visi*() functions if possible !!!###
	bool					mSensitive;		// ###!!! Use *Sens*() functions if possible !!!###
	fnMouseButtonCallback	mMouseButtonCallback; // may be NULL
	uint					mMouseEvent;	// mouse event
	Pos2d<float>			mPos;			// position of object on 2D viewport
	// Defines the area the object occupies on the viewport.
	// If x2-x1==0 && y2-y1==0 then it has zero area
	// and will be ignored by the mouse handler.
	Rect<float>				mRect;
	Dlink<GuiObject>*		mLinkObject;	// link in GuiScreen's object list

friend class GuiBase;
friend class GuiScreen;
};

////////////////////////////////////////////////////////////////////////////////
// GuiPrint class.  Used for messages that expire, not for regular Print().
class GuiPrint : public GuiObject
{
    PREVENT_COPYING(GuiPrint)
private:
	typedef GuiObject Parent;

public:
						GuiPrint( GuiScreen& screen, int seconds, const PrintDesc& desc );
	virtual				~GuiPrint();
	void				Render( void );

protected:
	string					mText;
	int						mTimer;			// decremented every second
	RGBA					mRgbaBase;
	eFontMode				mFontMode;
	eScreenFontType			mFontType;
	eScreenFontSize			mFontSize;
	FontPtr 				mFont;

friend class GuiBase;
friend class GuiScreen;
};

////////////////////////////////////////////////////////////////////////////////
// Button base class.
// ##############################################
// ## Users should call GuiScreen::NewButton() ##
// ##############################################
class GuiButtonBase : public GuiObject
{
    PREVENT_COPYING(GuiButtonBase)
private:
	typedef GuiObject Parent;

public:
						GuiButtonBase( GuiScreen& sreen, float x, float y, eFontMode fontMode, fnMouseButtonCallback callback );
	virtual				~GuiButtonBase();
	void				SetText( const char* text, uint textLen );
	void				SetText( const string& text );
	const string&		GetText( void ) { return mText; }
	void				SetRGBA( const RGBA rgba ) { mRgbaBase = rgba; }
	virtual void		ResetRGBA( void ) = 0;

protected:
	eFontMode				mFontMode;
	string					mText;
	RGBA					mRgbaBase;
};

} // namespace gfx

#include "gfx_gfxsys.hh"

#endif // GFX_GUI_HH
