/*****************************************************************************
 * $Id: game_pony_gui.cc,v 1.42 2006/08/10 03:18:58 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Lightweight GUI inside viewport.
 * @remarks @verbatim
 * - Don't confuse the game's GUI class with the underlying Gui class from gfx/.
 * - Pointers to screens and other GUI objects should be checked for NULL
 *   because during initialization some GUI objects are created before others are.  
 *   Some of the same Gui*() functions are reused to initialize a GUI object and
 *   also to synchronize with other GUI objects (hence the need to check for NULL).
 * - The dangerous C sprintf() is used instead of C++ ostringstream because
 *   it's 3x to 5x faster.
 * @endverbatim
 *//*
 * LEGAL:	COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#define GAME_PONY_GUI_CC 1
#include "game_pony_common.hh"
#include "game_pony_gui.hh"
#include "game_pony_config.hh"
#include "gfx_gui.hh"
#include "gfx_stream.hh"

// Global.
GamePonyGui gGui;

static uint sTempLen;	// dynamic length
static char sTemp[256];
static const RGBA sRgbaText     ( 0.10,  0.40,  0.50,  0.45 );
static const RGBA sRgbaGauges	( 0.95,  1.00,  0.95,  0.70 );
static const RGBA sRgbaGray		( 0.40,  0.40,  0.40,  0.70 );
static const RGBA sRgbaRed		( 1.00,  0.10,  0.10,  0.70 );
static const RGBA sRgbaBlue		( 0.10,  0.20,  0.80,  0.70 );
static const RGBA sRgbaBlue2    ( 0.20,  0.40,  0.70,  0.70 );
static const RGBA sRgbaBlue3    ( 0.15,  0.30,  0.60,  0.70 );
static const RGBA sRgbaPink     ( 0.60,  0.10,  0.10,  0.70 );

static struct
{
	fp	mZoom; ///< eng_config.cc may call GuiSetZoom() which assigns this (zoom.zoom).
	fp	mInc;
	fp	mFloor;
	fp	mCeiling;
} zoom = { 1.0, 0.10, 0.50, 2.00 };

enum eColumn
{
	eColumn_SAVE_BUTTON		= 5,
	eColumn_HELP_BUTTON		= 11,
	eColumn_ABOUT_BUTTON	= 16,
};

extern char cfgkey_joy_left_min[];
extern char cfgkey_joy_left_max[];
extern char cfgkey_joy_right_min[];
extern char cfgkey_joy_right_max[];
extern char cfgkey_joy_up_min[];
extern char cfgkey_joy_up_max[];
extern char cfgkey_joy_down_min[];
extern char cfgkey_joy_down_max[];
extern char cfgkey_joy_slow_min[];
extern char cfgkey_joy_slow_max[];
extern char cfgkey_joy_fast_min[];
extern char cfgkey_joy_fast_max[];
const char JOYCAL_SET_LEFT_MAX_STR[]	= "SET ROLL LEFT MAX";
const char JOYCAL_SET_LEFT_MIN_STR[]	= "MIN";
const char JOYCAL_SET_RIGHT_MAX_STR[]	= "SET ROLL RIGHT MAX";
const char JOYCAL_SET_RIGHT_MIN_STR[]	= "MIN";
const char JOYCAL_SET_UP_MAX_STR[]		= "SET PITCH UP MAX";
const char JOYCAL_SET_UP_MIN_STR[]		= "MIN";
const char JOYCAL_SET_DOWN_MAX_STR[]	= "SET PITCH DOWN MAX";
const char JOYCAL_SET_DOWN_MIN_STR[]	= "MIN";
const char JOYCAL_SET_SLOW_MAX_STR[]	= "SET AXIS3 SLOW MAX";
const char JOYCAL_SET_SLOW_MIN_STR[]	= "MIN";
const char JOYCAL_SET_FAST_MAX_STR[]	= "SET AXIS3 FAST MAX";
const char JOYCAL_SET_FAST_MIN_STR[]	= "MIN";

const fp GUI_FOG_DENSITY_INC		= 0.05;
const fp GUI_FOG_DENSITY_MIN		= 0.0;
const fp GUI_FOG_DENSITY_MAX		= 1.0;

const Percent GUI_CONTROL_RESPONSE_MIN	= 0;	// 0 means default not 0%
const Percent GUI_CONTROL_RESPONSE_MAX	= 100;

// Shorthand for two args.
#define STR_SIZEOF_ARGS( STR )	STR, sizeof(STR)

// Treat these as statement macros (not functions).
// PSVAR() means Print Screen with Variable string constructed by sprintf().
// PSCON() means Print Screen with Constant string defined as a static.
#define SET_PSVAR( args... )											\
{																		\
	sTempLen = sprintf( sTemp, ## args );								\
}
#define PSVAR( X, Y, FONTMODE, RGBA )									\
{																		\
	ASSERT( sTempLen > 0 );												\
	screen->Print( sTemp, sTempLen, (X), (Y), (FONTMODE), (RGBA) );		\
	DEBUG_CODE( sTempLen = 0; ) \
}
#define PSVAR2( X, Y, FONTMODE )        								\
{																		\
	ASSERT( sTempLen > 0 );												\
	screen->Print( sTemp, sTempLen, (X), (Y), (FONTMODE) );     		\
	DEBUG_CODE( sTempLen = 0; ) \
}
#define PSCON( TEXT, X, Y, FONTMODE, RGBA )								\
{																		\
	static const char str[] = (TEXT);									\
	screen->Print( str, sizeof(str), (X), (Y), (FONTMODE), (RGBA) );	\
}
#define PSCON2( TEXT, X, Y, FONTMODE )  								\
{																		\
	static const char str[] = (TEXT);									\
	screen->Print( str, sizeof(str), (X), (Y), (FONTMODE) );        	\
}

/*****************************************************************************
 * Initialize GUI.
 *****************************************************************************/
void
GamePonyGui::Init( void )
{
	mReady = mMouseButtonWasPressed = mAboutButtonWasToggled = 0;
	// (other members omitted -- assume object is static so pointers will be automatically 0)

	StartStopListeners( true );

	// Construct Gui object.
	mGui = new Gui( gEng.mView.GetGeom(), gEng.mView.GetGeom() ); // no difference
	mGui->SetZoom( zoom.mZoom );

	// Construct screens.
	InitScreens();

	mReady = true;
}
void
GamePonyGui::StartStopListeners( bool start )
{
	gEngineEvent.mResetScene.Listen(    Listener_GamePonyGui_Reset,					start );
	gEngineEvent.mPostDrawViews.Listen( Listener_GamePonyGui_PostDrawViews,			start ); // Render()
	gEngineEvent.mReshape.Listen(       Listener_GamePonyGui_Reshape,				start );
	gEngineEvent.mMouseMotion.Listen(   Listener_GamePonyGui_MouseMotionHandler,	start );
	gEngineEvent.mMouseButton.Listen(   Listener_GamePonyGui_MouseButtonHandler,	start );
}
void
GamePonyGui::Delete( void )
{
	StartStopListeners( false );
	DELETE_NULL( mGui );
}

/*****************************************************************************
 * Reset GUI.
 *****************************************************************************/
void
GamePonyGui::Reset( void )
{
	if ( mReady )
	{
		mScreenMain->SetVisi( true );
		mScreenMenu->SetVisi( false );
		mScreenHelp->SetVisi( false );
		mScreenAbout->SetVisi( false );
		mScreenJoycal->SetVisi( false );
		gJoy.SetJoystickCalibrationMode( false );
	}
}
void
Listener_GamePonyGui_Reset( void )
{
	gGui.Reset();
}

/*****************************************************************************
 * Render GUI.
 *****************************************************************************/
void
GamePonyGui::Render( void )
{
	if ( EX(mReady) )
	{
		RenderScreens();
		mGui->Render();
	}
}
void
Listener_GamePonyGui_PostDrawViews( void ) // Render
{
	if ( gEng.GetSpeed() != eSpeed_FAST ) gGui.Render();
}

/*****************************************************************************
 * Print a status message to the GUI.
 *****************************************************************************/
void
GamePonyGui::PrintStatus( const char* str ) // pass a static string
{
	static const RGBA rgba( 0.6,  0.1,  0.1,  0.7 );

	if ( mReady )
	{
		static PrintDesc desc;

		const fp yreset = 1.5;
		const fp yi     = 1.0;

		static int init = 0;
		if ( ! init++ )
		{
			desc.mPos.x		= 1;
			desc.mPos.y		= yreset;
			desc.mRgba		= rgba;
			desc.mFontMode	= eFontMode_NW;
			desc.mFontType	= eScreenFontType_THIN;
			desc.mFontSize	= eScreenFontSize_NORMAL;

		}
		desc.mText = str;

		// Don't print duplicate messages.
		if ( ! mScreenMain->IfPrintTimerDuplicate( str ) )
		{
			// Print a new message below prior ones.
			if ( mScreenMain->PrintTimerPopulation() == 0 )
				desc.mPos.y = yreset;
			else
				desc.mPos.y += yi;

			mScreenMain->PrintTimer( 4, desc );
		}
	}
}

/*****************************************************************************
 * Dual-purpose functions used to initialize GUI objects and/or to tell the GUI
 * to update itself to a change.  Some of these functions are used in interaction
 * between the keyboard and GUI as some keys and buttons control the same settings.
 *
 * NOTE:
 * Pointers to screens and other GUI objects should be checked for NULL
 * because during initialization some GUI objects are created before others are.  
 * Some of the same Gui*() functions are reused to initialize a GUI object and
 * also to synchronize with other GUI objects (hence the need to check for NULL).
 * During initialization, if a pointer is NULL, an action is skipped but it'll
 * be done later as that nonexistent object will eventually be initialized.
 *
 * A busy flag is used to prevent possible infinite recursion in case
 * GUI method A calls B and B calls A.
 *****************************************************************************/
// Tell GUI when the first keyboard press or mouse click occurs.
// Used to clear the splash screen.
void
GamePonyGui::SplashOff( void )
{
	if ( mScreenAbout ) mScreenAbout->SetVisi( false );
}
// changed to pan, chase, etc view
void
GamePonyGui::UpdateFlightView( void )
{
BUSY_BEGIN_ASSERT;
	UpdateControlResponse(); // control response differs for eye and chase views
BUSY_END;
}
void
GamePonyGui::UpdatePause( void )
{
BUSY_BEGIN_ASSERT;
	if ( mButtonPause )
	{
		int xi;
		if ( gEng.mView.IfFreeze() )
		{
			mButtonPause->SetText( STR_SIZEOF_ARGS("UNPAUSE") );
			xi = 2;
		}
		else
		{
			mButtonPause->SetText( STR_SIZEOF_ARGS("PAUSE") );
			xi = 0;
		}
		// "UN" requires scooting the leftward buttons to the left.
		// But do nothing if these buttons aren't yet constructed!
		if ( mButtonHelp  ) mButtonHelp->SetPos(  eColumn_HELP_BUTTON  + xi, 0 );
		if ( mButtonAbout ) mButtonAbout->SetPos( eColumn_ABOUT_BUTTON + xi, 0 );
	}
BUSY_END;
}
void
GamePonyGui::UpdateInputDevice( void )
{
BUSY_BEGIN_ASSERT;
	if ( mButtonInputDevice )
	{
		if ( gJoy.IfEnabled() )
			mButtonInputDevice->SetText( STR_SIZEOF_ARGS("INPUT DEVICE: KEY/JOY") );
		else
			mButtonInputDevice->SetText( STR_SIZEOF_ARGS("INPUT DEVICE: KEY") );
	}
	UpdateControlResponse();
	UpdateJoystickAxis3();
	UpdateJoycal();
	// If input device was switched to keyboard while in joystick calibration screen.
	if ( mScreenJoycal && gJoy.IfJoystickCalibrationMode() )
	{
		gJoy.SetJoystickCalibrationMode( false );
		mScreenJoycal->SetVisi( false );
	}
BUSY_END;
}
void
GamePonyGui::UpdateJoystickAxis3( void )
{
BUSY_BEGIN_ASSERT;
	if ( mButtonJoyaxis3 )
	{
		if ( gJoy.IfEnabled() )
		{
			const char* s = " ";
			switch( gJoy.GetJoystickAxis3() )
			{
				case eJoyAxis3_DISABLED: default:
				s = "DISABLED";
				break;
				case eJoyAxis3_THROTTLE:
				s = "THROTTLE";
				break;
				case eJoyAxis3_RUDDER:
				s = "RUDDER  "; // same length
				break;
			}
			mButtonJoyaxis3->SetText( sTemp, sprintf( sTemp, "JOYAXIS3: %s", s ) );
			mButtonJoyaxis3->SetVisiSens(true);
		}
		else
		{
			mButtonJoyaxis3->SetVisiSens(false);
		}
	}
BUSY_END;
}
void
GamePonyGui::UpdateJoycal( void )
{
BUSY_BEGIN_ASSERT;
	if ( mButtonJoycal )
	{
		mButtonJoycal->SetText( STR_SIZEOF_ARGS("CALIBRATE") );
		mButtonJoycal->SetVisiSens( gJoy.IfEnabled() );
	}
BUSY_END;
}
void
GamePonyGui::UpdateFogDensity( void )
{
BUSY_BEGIN_ASSERT;
	if ( mButtonFogDensityPlus )
	{
		sTempLen = sprintf( sTemp, "+ FOG DENSITY: %.2f", gEng.GetFogDensity() );
		mButtonFogDensityPlus->SetText( sTemp, sTempLen );
		mButtonFogDensityPlus->SetSens( gEng.GetFogDensity() < GUI_FOG_DENSITY_MAX );
	}
	if ( mButtonFogDensityMinus )
	{
		mButtonFogDensityMinus->SetText( STR_SIZEOF_ARGS(" - ") );
		mButtonFogDensityMinus->SetSens( gEng.GetFogDensity() > GUI_FOG_DENSITY_MIN );
	}
BUSY_END;
}
void
GamePonyGui::UpdateCollisions( void )
{
BUSY_BEGIN_ASSERT;
	if ( mButtonCollisions )
	{
		if ( gGame.IfCollisions() )
			mButtonCollisions->SetText( STR_SIZEOF_ARGS("COLLISIONS: ON") );
		else
			mButtonCollisions->SetText( STR_SIZEOF_ARGS("COLLISIONS: OFF") );
	}
BUSY_END;
}
void
GamePonyGui::UpdateControlResponse( void )
{
BUSY_BEGIN_ASSERT;
	int response = gGame.GetControlResponse();
	if ( mButtonResponsePlus )
	{
		if ( gEng.GetCurrent() == &gEng.mEye )		// 1st-person view
		{
			if ( response )
				sTempLen = sprintf( sTemp, "+ RESPONSE: %d%%", response );
			else
				sTempLen = sprintf( sTemp, "+ RESPONSE: DEFAULT" );
		}
		else							// chase view
		{
			if ( response )
				sTempLen = sprintf( sTemp, "+ RESPONSE CHASE: %d%%", response );
			else
				sTempLen = sprintf( sTemp, "+ RESPONSE CHASE: DEFAULT" );
		}
		mButtonResponsePlus->SetText( sTemp, sTempLen );
		mButtonResponsePlus->SetSens( response < GUI_CONTROL_RESPONSE_MAX );
	}
	if ( mButtonResponseMinus )
	{
		mButtonResponseMinus->SetText( STR_SIZEOF_ARGS(" - ") );
		mButtonResponseMinus->SetSens( response > GUI_CONTROL_RESPONSE_MIN );
	}
BUSY_END;
}
void
GamePonyGui::UpdateLod( void )
{
BUSY_BEGIN_ASSERT;
	if ( mButtonLod )
	{
		const string& str = gEng.LodUserString();
		mButtonLod->SetText( sTemp, sprintf( sTemp, "DETAIL: %s", str.c_str() ) );
	}
BUSY_END;
}
void
GamePonyGui::UpdateGuiZoom( void )
{
BUSY_BEGIN_ASSERT;
	if ( mButtonGuiZoomPlus )
	{
		mButtonGuiZoomPlus->SetText( sTemp, sprintf( sTemp, "+ GUI ZOOM: %.2f", zoom.mZoom ) );
		mButtonGuiZoomPlus->SetSens( zoom.mZoom < zoom.mCeiling );
	}
	if ( mButtonGuiZoomMinus )
	{
		mButtonGuiZoomMinus->SetText( STR_SIZEOF_ARGS(" - ") );
		mButtonGuiZoomMinus->SetSens( zoom.mZoom > zoom.mFloor );
	}
BUSY_END;
}

/*****************************************************************************
 * Misc. GUI public functions.
 * These interface functions can safely called before the GUI is even constructed.
 *****************************************************************************/
// Toggled by menu button or ESC key.
void
GamePonyGui::ToggleMenu( void )
{
	if ( mReady )
	{
		bool f = ! mScreenMenu->IfVisi();
		mScreenMain->SetVisi( true );
		mScreenMenu->SetVisi( f );
		// leave alone other screens
	}
}
void
GamePonyGui::SetZoom( fp z )
{
	zoom.mZoom = z; // InitGui() will read this in case gui==NULL
	if ( mReady )
	{
		mGui->SetZoom( z );
		UpdateGuiZoom();
	}
}
fp
GamePonyGui::GetZoom( void )
{
	if ( mReady )
		return mGui->GetZoom();
	else
	{
		DEBUG_CODE( *gStreamDebug << "GuiGetZoom: gui not ready \n"; )
		return 1.0; // oops
	}
}
void
GamePonyGui::Reshape( const Geom<int>& viewport, const Geom<int>& window )
{
	if ( mReady )
	{
		mGui->ReshapeViewport( viewport, window );
	}
	else
	{
		DEBUG_CODE( *gStreamDebug << "GuiReshape: gui not ready \n"; );
	}
}
void
Listener_GamePonyGui_Reshape( Geom<int>& geom )
{
	gGui.Reshape( geom, geom );	// "Tautological, become it has", Yoda.
}

/*****************************************************************************
 * This should be called by the gfx timer handler.
 * See gfx_gui.hh.
 *****************************************************************************/
void
GamePonyGui::TimerOneSecond( void )
{
	if ( mGui && gEng.GetSpeed() != eSpeed_FAST ) mGui->AppTimerOneSecond();
}

/*****************************************************************************
 * This should be called by the gfx mouse motion handler.
 * See gfx_gui.hh.
 *****************************************************************************/
void
GamePonyGui::MouseMotionHandler( float x, float y )
{
	if ( mGui ) mGui->AppMouseMotionHandler( x, y );
}
void
Listener_GamePonyGui_MouseMotionHandler( std::pair<int,int> duo )
{
	gGui.MouseMotionHandler( duo.first, duo.second );
}

/*****************************************************************************
 * This should be called by the gfx mouse motion handler.
 * See gfx_gui.hh.
 *****************************************************************************/
void
GamePonyGui::MouseButtonHandler( eMouseEvent button, bool down )
{
	if ( mGui ) mGui->AppMouseButtonHandler( button, down );
}
void
Listener_GamePonyGui_MouseButtonHandler( std::pair<eMouseEvent,bool> duo )
{
	gGui.MouseButtonHandler( duo.first, duo.second );
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////  private methods  /////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Create all screens.
 *****************************************************************************/
void
GamePonyGui::InitScreens( void )
{
	InitScreenMain();
	InitScreenMenu();
	InitScreenHelp();
	InitScreenAbout();
	InitScreenJoycal();
}

/*****************************************************************************
 * Create main screen.
 *****************************************************************************/
void
GamePonyGui::InitScreenMain( void )
{
	// Construct main screen.
	mScreenMain = mGui->NewScreen();
	mScreenMain->SetVisiSens(true);

	// Menu button.
	mButtonMenu = mScreenMain->NewButton( 0, 0, eFontMode_NE, GamePonyGui_GuiButtonCallback );
	mButtonMenu->SetText( STR_SIZEOF_ARGS("MENU") );
	mButtonMenu->SetVisiSens(true);

	// Pause button.
	mButtonPause = mScreenMain->NewButton( 5, 0, eFontMode_NE, GamePonyGui_GuiButtonCallback );
	UpdatePause();
	mButtonPause->SetVisiSens(true);
}

/*****************************************************************************
 * Create menu screen.
 *****************************************************************************/
void
GamePonyGui::InitScreenMenu( void )
{
	// Construct menu screen.
	mScreenMenu = mGui->NewScreen();
	mScreenMenu->SetVisi(false);
	mScreenMenu->SetSens(true);

	int yl = 0;			// left  side
	int yr = 0;			// right side
	const int yi = 2;	// increment
	const int PLUS_MINUS_PADDING = 3;
	
	// Exit button.
	mButtonExit = mScreenMenu->NewButton( 0, yl, eFontMode_NW, GamePonyGui_GuiButtonCallback );
	mButtonExit->SetText( STR_SIZEOF_ARGS("EXIT") );
	mButtonExit->SetVisiSens(true);
//	yl += yi;

	// Save button.
	mButtonSave = mScreenMenu->NewButton( eColumn_SAVE_BUTTON, yl, eFontMode_NW, GamePonyGui_GuiButtonCallback );
	mButtonSave->SetText( STR_SIZEOF_ARGS("SAVE VIEW") );
	mButtonSave->SetVisiSens(true);
	yl += yi;

	// Help button.
	mButtonHelp = mScreenMenu->NewButton( eColumn_HELP_BUTTON, yr, eFontMode_NE, GamePonyGui_GuiButtonCallback );
	mButtonHelp->SetText( STR_SIZEOF_ARGS("HELP") );
	mButtonHelp->SetVisiSens(true);
//	yr += yi;

	// About button.
	mButtonAbout = mScreenMenu->NewButton( eColumn_ABOUT_BUTTON, yr, eFontMode_NE, GamePonyGui_GuiButtonCallback );
	mButtonAbout->SetText( STR_SIZEOF_ARGS("ABOUT") );
	mButtonAbout->SetVisiSens(true);
	yr += yi;

	// Collisions button.
	mButtonCollisions = mScreenMenu->NewButton( 0, yr, eFontMode_NE, GamePonyGui_GuiButtonCallback );
	mButtonCollisions->SetVisiSens(true);
	UpdateCollisions();
	yr += yi;

	// Level-of-detail (LOD) button.
	mButtonLod = mScreenMenu->NewButton( 0, yr, eFontMode_NE, GamePonyGui_GuiButtonCallback );
	mButtonLod->SetVisiSens(true);
	UpdateLod();
	yr += yi;

	// Fog density +/- buttons.
	mButtonFogDensityPlus = mScreenMenu->NewButton( PLUS_MINUS_PADDING, yr, eFontMode_NE, GamePonyGui_GuiButtonCallback );
	mButtonFogDensityPlus->SetVisi(true);
	mButtonFogDensityMinus = mScreenMenu->NewButton( 0, yr, eFontMode_NE, GamePonyGui_GuiButtonCallback );
	mButtonFogDensityMinus->SetVisi(true);
	UpdateFogDensity(); // sets sensitivity
	yr += yi;

	// GUI zoom +/- buttons.
	mButtonGuiZoomPlus = mScreenMenu->NewButton( PLUS_MINUS_PADDING, yr, eFontMode_NE, GamePonyGui_GuiButtonCallback );
	mButtonGuiZoomPlus->SetVisi(true);
	mButtonGuiZoomMinus = mScreenMenu->NewButton( 0, yr, eFontMode_NE, GamePonyGui_GuiButtonCallback );
	mButtonGuiZoomMinus->SetVisi(true);
	UpdateGuiZoom();	// sets sensitivity
	yr += yi;

	// Control responsiveness +/- buttons.
	mButtonResponsePlus = mScreenMenu->NewButton( PLUS_MINUS_PADDING, yr, eFontMode_NE, GamePonyGui_GuiButtonCallback );
	mButtonResponsePlus->SetVisi(true);
	mButtonResponseMinus = mScreenMenu->NewButton( 0, yr, eFontMode_NE, GamePonyGui_GuiButtonCallback );
	mButtonResponseMinus->SetVisi(true);
	UpdateControlResponse(); // sets sensitivity
	yr += yi;

	// Input device button.
	mButtonInputDevice = mScreenMenu->NewButton( 0, yr, eFontMode_NE, GamePonyGui_GuiButtonCallback );
	mButtonInputDevice->SetVisiSens(true);
	UpdateInputDevice();
	yr += yi;

	// Joystick axis3 button.
	mButtonJoyaxis3 = mScreenMenu->NewButton( 0, yr, eFontMode_NE, GamePonyGui_GuiButtonCallback );
	UpdateJoystickAxis3(); // sets visibility/sensitivity
	yr += yi;

	// Joystick calibration button.
	mButtonJoycal = mScreenMenu->NewButton( 0, yr, eFontMode_NE, GamePonyGui_GuiButtonCallback );
	UpdateJoycal(); // sets visibility/sensitivity
	yr += yi;
}

/*****************************************************************************
 * Initialize Help screen.
 *****************************************************************************/
void
GamePonyGui::InitScreenHelp( void )
{
	// Construct screen.
	mScreenHelp = mGui->NewScreen();
	mScreenHelp->SetVisi(false);
	mScreenHelp->SetSens(false); // has no objects to respond to events
}

/*****************************************************************************
 * Initialize About screen.
 *****************************************************************************/
void
GamePonyGui::InitScreenAbout( void )
{
	// Construct screen.
	mScreenAbout = mGui->NewScreen();
	mScreenAbout->SetVisi(true);  // the about screen is the splash screen
	mScreenAbout->SetSens(false); // has no objects to respond to events
}

/*****************************************************************************
 * Create joystick calibration screen (never visible if no joystick).
 *****************************************************************************/
void
GamePonyGui::InitScreenJoycal( void )
{
	// Construct joystick calibration screen.
	mScreenJoycal = mGui->NewScreen();
	mScreenJoycal->SetVisi(false);
	mScreenJoycal->SetSens(true);

	// Coords for a column of buttons such as:
	//
	//		RESET TO DEFAULTS
	//		SET LEFT MAX: 12345
	//		         MIN: 12345
	//		SET RIGHT MAX: 12345
	//		          MIN: 12345
	//
	const int x1 = 18;
	const int x2 = 24;	// offset for abbrev "MIN" buttons
	int       y  = 6;
	const int yi = 2;	// increment

	// RESET TO DEFAULTS.
	mButtonJoycalReset = mScreenJoycal->NewButton( x1, y, eFontMode_NW, GamePonyGui_GuiButtonCallback );
	mButtonJoycalReset->SetText( STR_SIZEOF_ARGS("RESET TO DEFAULTS") );
	mButtonJoycalReset->SetVisiSens( true );
	y += yi;

	// The set of SET [axis] MAX/MIN buttons.
	//
#define INIT_JOYCAL_BUTTON( X, VAL, BUTTON, STR )											\
{																							\
	(BUTTON) = mScreenJoycal->NewButton( (X), y, eFontMode_NW, GamePonyGui_GuiButtonCallback ); \
	(BUTTON)->SetText( sTemp, sprintf( sTemp, "%s: %05d", STR, ABS(VAL) ) );				\
	(BUTTON)->SetVisi( true );																\
	(BUTTON)->SetSens( false );																\
	y += yi;																				\
}
	INIT_JOYCAL_BUTTON( x1, gJoy.mCalibration.mLeft.mHi,  mButtonJoycalLeftMax,  JOYCAL_SET_LEFT_MAX_STR  );
	INIT_JOYCAL_BUTTON( x2, gJoy.mCalibration.mLeft.mLo,  mButtonJoycalLeftMin,  JOYCAL_SET_LEFT_MIN_STR );

	INIT_JOYCAL_BUTTON( x1, gJoy.mCalibration.mRight.mHi, mButtonJoycalRightMax, JOYCAL_SET_RIGHT_MAX_STR );
	INIT_JOYCAL_BUTTON( x2, gJoy.mCalibration.mRight.mLo, mButtonJoycalRightMin, JOYCAL_SET_RIGHT_MIN_STR );

	INIT_JOYCAL_BUTTON( x1, gJoy.mCalibration.mUp.mHi,    mButtonJoycalUpMax,    JOYCAL_SET_UP_MAX_STR );
	INIT_JOYCAL_BUTTON( x2, gJoy.mCalibration.mUp.mLo,    mButtonJoycalUpMin,    JOYCAL_SET_UP_MIN_STR );

	INIT_JOYCAL_BUTTON( x1, gJoy.mCalibration.mDown.mHi,  mButtonJoycalDownMax,  JOYCAL_SET_DOWN_MAX_STR );
	INIT_JOYCAL_BUTTON( x2, gJoy.mCalibration.mDown.mLo,  mButtonJoycalDownMin,  JOYCAL_SET_DOWN_MIN_STR );

	INIT_JOYCAL_BUTTON( x1, gJoy.mCalibration.mSlow.mHi,  mButtonJoycalSlowMax,  JOYCAL_SET_SLOW_MAX_STR );
	INIT_JOYCAL_BUTTON( x2, gJoy.mCalibration.mSlow.mLo,  mButtonJoycalSlowMin,  JOYCAL_SET_SLOW_MIN_STR );

	INIT_JOYCAL_BUTTON( x1, gJoy.mCalibration.mFast.mHi,  mButtonJoycalFastMax,  JOYCAL_SET_FAST_MAX_STR );
	INIT_JOYCAL_BUTTON( x2, gJoy.mCalibration.mFast.mLo,  mButtonJoycalFastMin,  JOYCAL_SET_FAST_MIN_STR );
}

/*****************************************************************************
 * Render GUI screens.
 *****************************************************************************/
void
GamePonyGui::RenderScreens( void )
{
	RenderScreenMain();
	RenderScreenMenu();
	RenderScreenHelp();
	RenderScreenAbout();
	RenderScreenJoycal();
}

/*****************************************************************************
 * Render main screen.
 *****************************************************************************/
void
GamePonyGui::RenderScreenMain( void )
{
	GuiScreen*& screen = mScreenMain;
	screen->PushPrint( eScreenFontType_NORMAL, eScreenFontSize_NORMAL, sRgbaText );

	// Altimeter.
	int alt = ABS( int(gEng.mEye.mCm[Oy]) );
	SET_PSVAR( "ALT: %d", alt );
	PSVAR( 0, 0, eFontMode_SW, (alt > 300 ? sRgbaGauges : sRgbaRed) );

	// Throttle.
	// In pan mode, throttle is N/A.
	int percent = gEng.GetCurrent()->GetThrottle();
	if ( gEng.GetCurrent() == &gEng.mEye  &&  gEng.mEye.mEyeMode == Eye::eEyeMode_PAN  &&  ! gJoy.IfEnabled() )
		percent = 0;
	SET_PSVAR( "THROT: %d%% ", percent );
	PSVAR( 0, 0, eFontMode_SE, sRgbaGauges );

	// If paused.
	if ( UX(gEng.mView.IfFreeze() && ! gEng.IfQuiet() && ! mScreenJoycal->IfVisi() ) )
	{
		screen->PushPrint( eScreenFontType_NORMAL, eScreenFontSize_LARGER );

		PSCON( "(PAUSED)",
               0, 0, eFontMode_CENTER, sRgbaGray );
		PSCON( "(PRESS END OR CTRL-P)",
               0, 1, eFontMode_CENTER, sRgbaGray );

		screen->PopPrint();
	}

	// Crash/collision?
	if ( EX(gGame.IfCollisions()) )
	{
		if (    (gEng.GetCurrent() == gGame.mPlayer1Craft  &&  gGame.mPlayer1Craft->IfDestroyed() )
	         || (gEng.GetCurrent() == gGame.mPlayer2Craft  &&  gGame.mPlayer2Craft->IfDestroyed() ) )
		{
			PSCON( "GAME OVER",
                   0, 10, eFontMode_CENTER, sRgbaRed );
			PSCON( "DEMOTED TO GARBAGE SCOW CAPTAIN",
                   0, 12, eFontMode_CENTER, sRgbaRed );
		}
	}

	// If verbose.
	if ( UX(gEng.IfVerbose()) )
	{
		int y = 3;
		const int yi = 1;

		screen->PushPrint( eScreenFontType_THIN, eScreenFontSize_SMALL );

		SET_PSVAR( "FPS = %d", gEng.mView.GetFPS() );
		PSVAR( 0, y, eFontMode_SW, sRgbaGauges );
		y += yi;

		const Array<3>& qt = gEng.mEye.GetQuadrant()->GetTuple();
		SET_PSVAR( "QUADRANT[%d,%d,%d]", qt[XX], qt[YY], qt[ZZ] );
		PSVAR( 0, y, eFontMode_SW, sRgbaGauges );
		y += yi;

		SET_PSVAR( "OBJECTS = %d  IN THIS QUADRANT = %d",
                   gWorld.GetObjectCnt(),
                   gWorld.GetObjectCntInCenterQuadrant() );
		PSVAR( 0, y, eFontMode_SW, sRgbaGauges );
		y += yi;

#if STATS
		SET_PSVAR( "TRACERS = %d", gGame.mStatsBase.mTracers );
		PSVAR( 0, y, eFontMode_SW, sRgbaGauges );
		y += yi;

		SET_PSVAR( "CACHE MISSES: VERTEXS=%d NORMALS=%d", gEng.mStats.mCacheMissVertexs, gEng.mStats.mCacheMissNormals );
		PSVAR( 0, y, eFontMode_SW, sRgbaGauges );
		y += yi;

		SET_PSVAR( "LAND QUADS DRAWN = %4d TEXTURED = %4d", gGame.mStats.mLandQuadsDrawn, gGame.mStats.mLandQuadsDrawnTextured );
		PSVAR( 0, y, eFontMode_SW, sRgbaGauges );
		y += yi;

		SET_PSVAR( "HILLS = %d DRAWN = %d", gGame.mStats.mHills, gGame.mStats.mHillsDrawn );
		PSVAR( 0, y, eFontMode_SW, sRgbaGauges );
		y += yi;

		SET_PSVAR( "LAKES = %d DRAWN = %d", gGame.mStats.mLakes, gGame.mStats.mLakesDrawn );
		PSVAR( 0, y, eFontMode_SW, sRgbaGauges );
		y += yi;

		SET_PSVAR( "TREES = %d DRAWN = %d", gGame.mStats.mTrees, gGame.mStats.mTreesDrawn );
		PSVAR( 0, y, eFontMode_SW, sRgbaGauges );
		y += yi;

		SET_PSVAR( "OBJGRPS VIS = %d INVIS = %d", gEng.mStats.mObjectGroupVisible, gEng.mStats.mObjectGroupInvisible );
		PSVAR( 0, y, eFontMode_SW, sRgbaGauges );
		y += yi;
#if DEBUG
		SET_PSVAR( "MEM = %dM", MemUsed() / 1000000 );
		PSVAR( 0, y, eFontMode_SW, sRgbaGauges );
		y += yi;
#endif

#endif // STATS

		screen->PopPrint();
	}

	screen->PopPrint();
}

/*****************************************************************************
 * Render menu screen.
 *****************************************************************************/
void
GamePonyGui::RenderScreenMenu( void )
{
	// nothing
}

/*****************************************************************************
 * Render help screen
 *****************************************************************************/
void
GamePonyGui::RenderScreenHelp( void )
{
	GuiScreen*& screen = mScreenHelp;
	if ( ! screen->IfVisi() ) return;
	screen->PushPrint( eScreenFontType_NORMAL, eScreenFontSize_NORMAL, sRgbaText );

	int			y	= 2;
	const int col1	= 0;
	const int col2	= 12;

	PSCON2( "ESC",						col1, y, eFontMode_NW );
	PSCON2( "MENU",						col2, y, eFontMode_NW ); ++y;

	PSCON2( "CTRL-P/END",				col1, y, eFontMode_NW );
	PSCON2( "PAUSE",					col2, y, eFontMode_NW ); ++y;

	PSCON2( "CTRL-C",					col1, y, eFontMode_NW );
	PSCON2( "EXIT PROGRAM",				col2, y, eFontMode_NW ); ++y;

	PSCON2( "CURSOR",					col1, y, eFontMode_NW );
	PSCON2( "CONTROL",					col2, y, eFontMode_NW ); ++y;

	PSCON2( "SHIFT/CTRL/ALT",			col1, y, eFontMode_NW );
	PSCON2( "FAST/SLOW/CHASE CONTROL",	col2, y, eFontMode_NW ); ++y;

	PSCON2( "SHIFT+0..9",				col1, y, eFontMode_NW );
	PSCON2( "ADJUST CONTROL RESPONSE",	col2, y, eFontMode_NW ); ++y;

	PSCON2( "0..9",						col1, y, eFontMode_NW );
	PSCON2( "THROTTLE",					col2, y, eFontMode_NW ); ++y;

	PSCON2( "SPACE",					col1, y, eFontMode_NW );
	PSCON2( "FIRE",						col2, y, eFontMode_NW ); ++y;

	PSCON2( "F1",						col1, y, eFontMode_NW );
	PSCON2( "FIRST-PERSON MODE",		col2, y, eFontMode_NW ); ++y;

	PSCON2( "F2",						col1, y, eFontMode_NW );
	PSCON2( "PAN MODE",					col2, y, eFontMode_NW ); ++y;

	PSCON2( "F3",						col1, y, eFontMode_NW );
	PSCON2( "FLY CRAFT #1",				col2, y, eFontMode_NW ); ++y;

	PSCON2( "F4",						col1, y, eFontMode_NW );
	PSCON2( "FLY CRAFT #2",				col2, y, eFontMode_NW ); ++y;

	PSCON2( "F5",						col1, y, eFontMode_NW );
	PSCON2( "SWITCH INPUT DEVICE",		col2, y, eFontMode_NW ); ++y;

	PSCON2( "F12",						col1, y, eFontMode_NW );
	PSCON2( "TOGGLE CHASE PLANE VIEW",	col2, y, eFontMode_NW ); ++y;

	PSCON2( "+/-",						col1, y, eFontMode_NW );
	PSCON2( "ZOOM CHASE PLANE VIEW",	col2, y, eFontMode_NW ); ++y;

	PSCON2( "BACKSPACE",				col1, y, eFontMode_NW );
	PSCON2( "RESET VIEW",				col2, y, eFontMode_NW ); ++y;

	PSCON2( "CTRL-S",					col1, y, eFontMode_NW );
	PSCON2( "SAVE VIEW",				col2, y, eFontMode_NW ); ++y;

	PSCON2( "E,R",						col1, y, eFontMode_NW );
	PSCON2( "MOVE FORWARD/BACKWARD",	col2, y, eFontMode_NW ); ++y;

	PSCON2( "D,F",						col1, y, eFontMode_NW );
	PSCON2( "MOVE LEFT/RIGHT",			col2, y, eFontMode_NW ); ++y;

	PSCON2( "C,V",						col1, y, eFontMode_NW );
	PSCON2( "MOVE UP/DOWN",				col2, y, eFontMode_NW ); ++y;

	PSCON2( "WEB:",						col1, y, eFontMode_NW );
	PSCON2( WEBSITE_UCASE_STR,			col2, y, eFontMode_NW );

	screen->PopPrint();
}

/*****************************************************************************
 * Print About.
 *****************************************************************************/
void
GamePonyGui::RenderScreenAbout( void )
{
	if ( gEng.IfQuiet() ) return;

	GuiScreen*& screen = mScreenAbout;
	if ( ! screen->IfVisi() ) return;
	screen->PushPrint( eScreenFontType_NORMAL, eScreenFontSize_NORMAL, sRgbaText );

	if ( UX( gGame.IfEasterEgg() ) )
	{
		PSCON( "LOOK!  UP IN THE SKY!",
               0, -12.0, eFontMode_CENTER, sRgbaBlue );
		PSCON( "IT'S UNDERDOG!",
               0, -10.0, eFontMode_CENTER, sRgbaBlue );
	}
	else
	{
		// Show splash.

		// Compensate for zoom.
		const int yo1 = mGui->GetZoom() > 1.2 ? 4 : 0;
		const int yo2 = mGui->GetZoom() > 1.2 ? 8 : 0;

		screen->PushPrint( eScreenFontType_THIN, eScreenFontSize_LARGE );
		PSCON( PROGRAM_LONG_NAME_UCASE_STR,
               0, -10.0 + yo1, eFontMode_CENTER, sRgbaBlue );
		screen->PopPrint();

		PSCON( "BY " AUTHORS_UCASE_STR,
               0, -13.0 + yo2, eFontMode_CENTER, sRgbaBlue2 );

		// Verbosely show About screen if ABOUT button was clicked.
		// Let splash omit this.
		if ( mAboutButtonWasToggled )
		{
			screen->PushPrint( eScreenFontType_THIN, eScreenFontSize_NORMAL );

			PSCON( "GAME MODULE: " GAME_MODULE_NAME_UCASE_STR,
                   0, -10.0 + yo2, eFontMode_CENTER, sRgbaBlue3 );

			PSCON( "WEBSITE: " WEBSITE_UCASE_STR,
                   0, -8.0 + yo2, eFontMode_CENTER, sRgbaBlue3 );

			PSCON( "LICENSE: GNU GPL NO WARRANTY",
                   0, -6.0 + yo2, eFontMode_CENTER, sRgbaBlue3 );

			PSCON( "CREDIT AND THANKS TO:",
                   0, -2.0 + yo2, eFontMode_CENTER, sRgbaBlue3 );

			PSCON( "THOMAS DREXL (GL-117 FLIGHT SIM)",
                   0,  0.0 + yo2, eFontMode_CENTER, sRgbaBlue3 );

			screen->PopPrint();
		}
	}

	screen->PopPrint();
}

/*****************************************************************************
 * Render the screen for calibrating a joystick.
 *
 * How this works:
 *
 * The various joystick values are shown in one column.
 * "?????" is shown until the user moves the joystick to give it a value
 * and corresponding button is set insensitive until the value is valid.
 * The user can move the joystick in the various directions, then when
 * the user finds a value acceptable, the user can click the now sensitive
 * button which stores the value in the config file.
 *****************************************************************************/
void
GamePonyGui::RenderScreenJoycal( void )
{
	if ( ! gJoy.IfEnabled() || ! gJoy.IfJoystickCalibrationMode() ) return;

	GuiScreen*& screen = mScreenJoycal;
	if ( ! screen->IfVisi() ) return;
	screen->PushPrint( eScreenFontType_NORMAL, eScreenFontSize_SMALL, sRgbaText );

	PSCON2( "MOVE ANY JOYSTICK AXIS THEN CLICK",
            1, 3, eFontMode_NW );
	PSCON2( "CALIBRATION BUTTONS.  MIN SELECTS DEADZONE.",
            1, 4, eFontMode_NW );

	Joyval joyval = gJoy.GetJoyval();

	// Print the joystick values in one column.
	int x  = 0;
	int y  = 6;
	int yi = 2;
#define JOYCAL_PRINT( X, Y, VAL, STR )										\
{																			\
	if ( (VAL) == Joyval::eJoyval_INVALID )									\
		sTempLen = sprintf( sTemp, "????? " STR );							\
	else																	\
		sTempLen = sprintf( sTemp, "%05d " STR, ABS(VAL) );					\
	screen->Print( sTemp, sTempLen, (X), (Y), eFontMode_NW, sRgbaGray );	\
}
	// "PITCH UP" is used as "UP" is confusing because pulling down
	// on the joystick flies the craft up.
	JOYCAL_PRINT( x, y, joyval.mLeft,	"ROLL  LEFT" );		y += yi;
	JOYCAL_PRINT( x, y, joyval.mRight,	"ROLL  RIGHT" );	y += yi;
	JOYCAL_PRINT( x, y, joyval.mUp,		"PITCH UP" );		y += yi;
	JOYCAL_PRINT( x, y, joyval.mDown,	"PITCH DOWN" );		y += yi;
	if ( gJoy.GetJoystickAxis3() != eJoyAxis3_DISABLED )
	{
		JOYCAL_PRINT( x, y, joyval.mSlow, "AXIS3 SLOW" );	y += yi;
		JOYCAL_PRINT( x, y, joyval.mFast, "AXIS3 FAST" );	y += yi;
	}

	// Now set the visibility/sensitivity of the related buttons.
	if ( joyval.mLeft != Joyval::eJoyval_INVALID )     // left
	{
		mButtonJoycalLeftMin->SetSens( true );
		mButtonJoycalLeftMax->SetSens( true );
	}

	if ( joyval.mRight != Joyval::eJoyval_INVALID )	// right
	{
		mButtonJoycalRightMin->SetSens( true );
		mButtonJoycalRightMax->SetSens( true );
	}

	if ( joyval.mUp != Joyval::eJoyval_INVALID )       // up
	{
		mButtonJoycalUpMin->SetSens( true );
		mButtonJoycalUpMax->SetSens( true );
	}

	if ( joyval.mDown != Joyval::eJoyval_INVALID )     // down
	{
		mButtonJoycalDownMin->SetSens( true );
		mButtonJoycalDownMax->SetSens( true );
	}

	if ( gJoy.GetJoystickAxis3() != eJoyAxis3_DISABLED )        // axis3 slow/fast
	{
		mButtonJoycalSlowMin->SetVisi( true );
		mButtonJoycalSlowMax->SetVisi( true );
		mButtonJoycalFastMin->SetVisi( true );
		mButtonJoycalFastMax->SetVisi( true );

		if ( joyval.mSlow != Joyval::eJoyval_INVALID )	// axis3 slow
		{
			mButtonJoycalSlowMin->SetSens( true );
			mButtonJoycalSlowMax->SetSens( true );
		}

		if ( joyval.mFast != Joyval::eJoyval_INVALID )	// axis3 fast
		{
			mButtonJoycalFastMin->SetSens( true );
			mButtonJoycalFastMax->SetSens( true );
		}
	}
	else // no axis3
	{
		mButtonJoycalSlowMin->SetVisiSens( false );
		mButtonJoycalSlowMax->SetVisiSens( false );
		mButtonJoycalFastMin->SetVisiSens( false );
		mButtonJoycalFastMax->SetVisiSens( false );
	}

	// And that's all this function does.
	// The button handler does the rest.

	screen->PopPrint();
}

/*****************************************************************************
 * Called by underlying gfx Gui class when a Gui button was clicked.
 *****************************************************************************/
void
GamePonyGui_GuiButtonCallback( GuiObject* obj, uint buttons )
{
	gGui.GuiButtonCallback( obj, buttons );
}
void
GamePonyGui::GuiButtonCallback( GuiObject* obj, uint buttons )
{
	int writeConfig = false;		// set by any case that affects config file

	if ( ! mMouseButtonWasPressed ) SplashOff();
	mMouseButtonWasPressed = true;

	Joyval joyval = gJoy.GetJoyval();

	// MENU
	if ( obj == mButtonMenu && (buttons & eMouseEvent_BUTTON_1) )
	{
		ToggleMenu();
	}
	// EXIT
	else if ( obj == mButtonExit && (buttons & eMouseEvent_BUTTON_1) )
	{
		gEng.Exit(0);
	}
	// SAVE VIEW
	else if ( obj == mButtonSave && (buttons & eMouseEvent_BUTTON_1) )
	{
		gGame.SaveState( true ); // save view
		PrintStatus( "SAVED VIEW.  PASS -VIEW ARG TO RELOAD IT." );
	}
	// PAUSE
	else if ( obj == mButtonPause && (buttons & eMouseEvent_BUTTON_1) )
	{
		gEng.mView.ToggleFreeze();
		UpdatePause();
	}
	// ABOUT
	else if ( obj == mButtonAbout && (buttons & eMouseEvent_BUTTON_1) )
	{
		mAboutButtonWasToggled = true;
		mScreenAbout->ToggleVisi();
		TurnScreensOff( mScreenAbout );
	}
	// HELP
	else if ( obj == mButtonHelp && (buttons & eMouseEvent_BUTTON_1) )
	{
		mScreenHelp->ToggleVisi();
		TurnScreensOff( mScreenHelp );
	}
	// INPUT DEVICE
	else if ( obj == mButtonInputDevice && (buttons & eMouseEvent_BUTTON_1) )
	{
		gJoy.Enable( ! gJoy.IfEnabled() );
		UpdateInputDevice();

		++writeConfig;
	}
	// JOYSTICK AXIS3
	else if ( obj == mButtonJoyaxis3 && (buttons & eMouseEvent_BUTTON_1) )
	{
		if ( gJoy.IfEnabled() )
		{
			switch( gJoy.GetJoystickAxis3() )
			{
				case eJoyAxis3_DISABLED:
				gJoy.SetJoystickAxis3( eJoyAxis3_THROTTLE );
				break;

				case eJoyAxis3_THROTTLE:
				gJoy.SetJoystickAxis3( eJoyAxis3_RUDDER );
				break;

				case eJoyAxis3_RUDDER: default:
				gJoy.SetJoystickAxis3( eJoyAxis3_DISABLED );
				break;
			}

			UpdateJoystickAxis3();

			++writeConfig;
		}
	}
	// CALIBRATE JOYSTICK
	else if ( obj == mButtonJoycal && (buttons & eMouseEvent_BUTTON_1) )
	{
		if ( gJoy.IfEnabled() )
		{
			mScreenJoycal->ToggleVisi();
			TurnScreensOff( mScreenJoycal );
			gJoy.SetJoystickCalibrationMode( mScreenJoycal->IfVisi() );
		}

		//++writeConfig;
	}
	// FOG DENSITY (+)
	else if ( obj == mButtonFogDensityPlus && (buttons & eMouseEvent_BUTTON_1) )
	{
		fp fogDensity = gEng.GetFogDensity() + GUI_FOG_DENSITY_INC;
		fogDensity = MIN( fogDensity, GUI_FOG_DENSITY_MAX );
		gEng.SetFogDensity( fogDensity );
		gEng.EnableFog( true );
		UpdateFogDensity();

		++writeConfig;
	}
	// FOG DENSITY (-)
	else if ( obj == mButtonFogDensityMinus && (buttons & eMouseEvent_BUTTON_1) )
	{
		fp fogDensity = gEng.GetFogDensity() - GUI_FOG_DENSITY_INC;
		fogDensity = MAX( fogDensity, GUI_FOG_DENSITY_MIN );
		gEng.SetFogDensity( fogDensity );
		gEng.EnableFog( true );
		UpdateFogDensity();

		++writeConfig;
	}
	// COLLISIONS
	else if ( obj == mButtonCollisions && (buttons & eMouseEvent_BUTTON_1) )
	{
		gGame.ToggleCollisions();
		UpdateCollisions();

		if ( gGame.IfRude() && ! gGame.IfCollisions() )
			PrintStatus( "WIMP MODE ENABLED" );

		++writeConfig;
	}
	// RESPONSE (+)
	else if ( obj == mButtonResponsePlus && (buttons & eMouseEvent_BUTTON_1) )
	{
		gGame.IncControlResponse( 1 );
		UpdateControlResponse();

		++writeConfig;
	}
	// RESPONSE (-)
	else if ( obj == mButtonResponseMinus && (buttons & eMouseEvent_BUTTON_1) )
	{
		gGame.IncControlResponse( -1 );
		UpdateControlResponse();

		++writeConfig;
	}
	// DETAIL (LOD)
	else if ( obj == mButtonLod && (buttons & eMouseEvent_BUTTON_1) )
	{
		gEng.IncLodUser();
		UpdateLod();

		PrintStatus( "SOME CHANGES EFFECTIVE AFTER RESTART" );

		++writeConfig;
	}
	// GUI ZOOM +
	else if ( obj == mButtonGuiZoomPlus && (buttons & eMouseEvent_BUTTON_1) )
	{
		zoom.mZoom += zoom.mInc;
		if ( zoom.mZoom > zoom.mCeiling ) zoom.mZoom = zoom.mCeiling;//zoom.floor;
		mGui->SetZoom( zoom.mZoom );
		UpdateGuiZoom();

		++writeConfig;
	}
	// GUI ZOOM -
	else if ( obj == mButtonGuiZoomMinus && (buttons & eMouseEvent_BUTTON_1) )
	{
		zoom.mZoom -= zoom.mInc;
		if ( zoom.mZoom < zoom.mFloor ) zoom.mZoom = zoom.mFloor;//zoom.ceiling;
		mGui->SetZoom( zoom.mZoom );
		UpdateGuiZoom();

		++writeConfig;
	}
	// joystick calibration SET LEFT MIN
	else if ( obj == mButtonJoycalLeftMin && (buttons & eMouseEvent_BUTTON_1) )
	{
		WriteJoycalConfig( mButtonJoycalLeftMin, JOYCAL_SET_LEFT_MIN_STR, joyval.mLeft, cfgkey_joy_left_min );
	}
	// joystick calibration SET LEFT MAX
	else if ( obj == mButtonJoycalLeftMax && (buttons & eMouseEvent_BUTTON_1) )
	{
		WriteJoycalConfig( mButtonJoycalLeftMax, JOYCAL_SET_LEFT_MAX_STR, joyval.mLeft, cfgkey_joy_left_max );
	}
	// joystick calibration SET RIGHT MIN
	else if ( obj == mButtonJoycalRightMin && (buttons & eMouseEvent_BUTTON_1) )
	{
		WriteJoycalConfig( mButtonJoycalRightMin, JOYCAL_SET_RIGHT_MIN_STR, joyval.mRight, cfgkey_joy_right_min );
	}
	// joystick calibration SET RIGHT MAX
	else if ( obj == mButtonJoycalRightMax && (buttons & eMouseEvent_BUTTON_1) )
	{
		WriteJoycalConfig( mButtonJoycalRightMax, JOYCAL_SET_RIGHT_MAX_STR, joyval.mRight, cfgkey_joy_right_max );
	}
	// joystick calibration SET UP MIN
	else if ( obj == mButtonJoycalUpMin && (buttons & eMouseEvent_BUTTON_1) )
	{
		WriteJoycalConfig( mButtonJoycalUpMin, JOYCAL_SET_UP_MIN_STR, joyval.mUp, cfgkey_joy_up_min );
	}
	// joystick calibration SET UP MAX
	else if ( obj == mButtonJoycalUpMax && (buttons & eMouseEvent_BUTTON_1) )
	{
		WriteJoycalConfig( mButtonJoycalUpMax, JOYCAL_SET_UP_MAX_STR, joyval.mUp, cfgkey_joy_up_max );
	}
	// joystick calibration SET DOWN MIN
	else if ( obj == mButtonJoycalDownMin && (buttons & eMouseEvent_BUTTON_1) )
	{
		WriteJoycalConfig( mButtonJoycalDownMin, JOYCAL_SET_DOWN_MIN_STR, joyval.mDown, cfgkey_joy_down_min );
	}
	// joystick calibration SET DOWN MAX
	else if ( obj == mButtonJoycalDownMax && (buttons & eMouseEvent_BUTTON_1) )
	{
		WriteJoycalConfig( mButtonJoycalDownMax, JOYCAL_SET_DOWN_MAX_STR, joyval.mDown, cfgkey_joy_down_max );
	}
	// joystick calibration SET SLOW MIN
	else if ( obj == mButtonJoycalSlowMin && (buttons & eMouseEvent_BUTTON_1) )
	{
		WriteJoycalConfig( mButtonJoycalSlowMin, JOYCAL_SET_SLOW_MIN_STR, joyval.mSlow, cfgkey_joy_slow_min );
	}
	// joystick calibration SET SLOW MAX
	else if ( obj == mButtonJoycalSlowMax && (buttons & eMouseEvent_BUTTON_1) )
	{
		WriteJoycalConfig( mButtonJoycalSlowMax, JOYCAL_SET_SLOW_MAX_STR, joyval.mSlow, cfgkey_joy_slow_max );
	}
	// joystick calibration SET FAST MIN
	else if ( obj == mButtonJoycalFastMin && (buttons & eMouseEvent_BUTTON_1) )
	{
		WriteJoycalConfig( mButtonJoycalFastMin, JOYCAL_SET_FAST_MIN_STR, joyval.mFast, cfgkey_joy_fast_min );
	}
	// joystick calibration SET FAST MAX
	else if ( obj == mButtonJoycalFastMax && (buttons & eMouseEvent_BUTTON_1) )
	{
		WriteJoycalConfig( mButtonJoycalFastMax, JOYCAL_SET_FAST_MAX_STR, joyval.mFast, cfgkey_joy_fast_max );
	}
	// joystick calibration RESET TO DEFAULTS
	else if ( obj == mButtonJoycalReset && (buttons & eMouseEvent_BUTTON_1) )
	{
		ResetJoycalConfig();
	}

	// ------------

	GFX_POST_REDISPLAY();

	if ( writeConfig )
	{
		if ( ! gGame.SaveState(false) ) // save all except view
		{
			PrintStatus( "ERROR: COULDN'T SAVE CONFIG" );
			*gStreamError << "ERROR: COULDN'T SAVE CONFIG" << endl;
		}
	}
}

/*****************************************************************************
 * Write a joystick axis value to config file.
 *****************************************************************************/
void
GamePonyGui::WriteJoycalConfig( GuiButton* button, const char* str, int val, const char* key )
{
	button->SetRGBA( sRgbaPink );
	button->SetText( sTemp, sprintf( sTemp, "%s %05d", str, ABS(val) ) );
	int errors = 0;
	ConfigFile& config = gGame.GetConfigFile();
	errors = ! config.Write( key, val );
	if ( errors ) *gStreamError << "ERROR: failed to save joystick configuration" << endl;
}

/*****************************************************************************
 * Reset joystick calibration to default values.
 *****************************************************************************/
void
GamePonyGui::ResetJoycalConfig( void )
{
	gJoy.ResetJoyval();
	int errors = 0;
	ConfigFile& config = gGame.GetConfigFile();

	// Tell joystick driver wrapper to revert to defaults.
	gJoy.CalibrateDefault(); // resets gJoy.calibration.*

	// Update joystick calibration GUI buttons.
	#define SET_JOYCAL_BUTTON( BUTTON, VAL, STR )				\
	{															\
		sTempLen = sprintf( sTemp, "%s: %05d", STR, ABS(VAL) );	\
		(BUTTON)->SetText( sTemp, sTempLen );					\
		(BUTTON)->SetVisi( true );								\
		(BUTTON)->SetSens( false );								\
	}
	SET_JOYCAL_BUTTON( mButtonJoycalLeftMin,  gJoy.mCalibration.mLeft.mLo,  JOYCAL_SET_LEFT_MIN_STR  );
	SET_JOYCAL_BUTTON( mButtonJoycalLeftMax,  gJoy.mCalibration.mLeft.mHi,  JOYCAL_SET_LEFT_MAX_STR  );
	SET_JOYCAL_BUTTON( mButtonJoycalRightMin, gJoy.mCalibration.mRight.mLo, JOYCAL_SET_RIGHT_MIN_STR );
	SET_JOYCAL_BUTTON( mButtonJoycalRightMax, gJoy.mCalibration.mRight.mHi, JOYCAL_SET_RIGHT_MAX_STR );
	SET_JOYCAL_BUTTON( mButtonJoycalUpMin,    gJoy.mCalibration.mUp.mLo,    JOYCAL_SET_UP_MIN_STR    );
	SET_JOYCAL_BUTTON( mButtonJoycalUpMax,    gJoy.mCalibration.mUp.mHi,    JOYCAL_SET_UP_MAX_STR    );
	SET_JOYCAL_BUTTON( mButtonJoycalDownMin,  gJoy.mCalibration.mDown.mLo,  JOYCAL_SET_DOWN_MIN_STR  );
	SET_JOYCAL_BUTTON( mButtonJoycalDownMax,  gJoy.mCalibration.mDown.mHi,  JOYCAL_SET_DOWN_MAX_STR  );
	SET_JOYCAL_BUTTON( mButtonJoycalSlowMin,  gJoy.mCalibration.mSlow.mLo,  JOYCAL_SET_SLOW_MIN_STR  );
	SET_JOYCAL_BUTTON( mButtonJoycalSlowMax,  gJoy.mCalibration.mSlow.mHi,  JOYCAL_SET_SLOW_MAX_STR  );
	SET_JOYCAL_BUTTON( mButtonJoycalFastMin,  gJoy.mCalibration.mFast.mLo,  JOYCAL_SET_FAST_MIN_STR  );
	SET_JOYCAL_BUTTON( mButtonJoycalFastMax,  gJoy.mCalibration.mFast.mHi,  JOYCAL_SET_FAST_MAX_STR  );

	// Remove joystick calibration settings from config file.
	if ( ! config.Remove( cfgkey_joy_left_min ) ) ++errors;
	config.Remove( cfgkey_joy_left_max  );
	config.Remove( cfgkey_joy_right_min );
	config.Remove( cfgkey_joy_right_max );
	config.Remove( cfgkey_joy_up_min    );
	config.Remove( cfgkey_joy_up_max    );
	config.Remove( cfgkey_joy_down_min  );
	config.Remove( cfgkey_joy_down_max  );
	config.Remove( cfgkey_joy_slow_min  );
	config.Remove( cfgkey_joy_slow_max  );
	config.Remove( cfgkey_joy_fast_min  );
	config.Remove( cfgkey_joy_fast_max  );

	if ( errors ) *gStreamError << "ERROR: while restoring default joystick calibration" << endl;
}

/*****************************************************************************
 * Turn screens off except the one passed.
 *****************************************************************************/
void
GamePonyGui::TurnScreensOff( GuiScreen* notme )
{
	if ( notme != mScreenHelp )
		mScreenHelp->SetVisi( false );

	if ( notme != mScreenAbout )
		mScreenAbout->SetVisi( false );

	if ( notme != mScreenJoycal && mScreenJoycal->IfVisi() )
	{
		mScreenJoycal->SetVisi( false );
		gJoy.SetJoystickCalibrationMode( false );
	}
}
