////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 RWS Inc, All Rights Reserved
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of version 2 of the GNU General Public License as published by
// the Free Software Foundation
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
// menu.CPP
// 
// History:
//		11/27/96 JMI	Started.
//
//		12/16/96	JMI	While not complete, this now contains most of the
//							features discussed.
//
//		12/21/96	JMI	Now uses RResMgr for all loaded resources except
//							ms_guiIndicator.m_im since it is not yet practical.
//							Once there is a generic loadable type, such as RFile*
//							or void*, this will be easier.  The possible work
//							around for the meantime could be a func to load an
//							image resource, allocate the gui via Create() to
//							the size of that resource, rspBlit() the image resource
//							into the gui, and Release() the image resource.
//							
//		12/21/96	JMI	Now calls ms_msgbox.SetVisible(TRUE) to show and activate
//							ms_msgbox and its children at the end of a successful
//							StartMenu().  Also, calls ms_msgbox.SetVisible(FALSE) to
//							hide and deactivate ms_msgbox and its children during
//							StopMenu().
//
//		01/19/97	JMI	Converted Do() to taking an RInputEvent* instead of a
//							long*.
//
//		01/27/97	JMI	NextItem() was bailing out one too early when it thought
//							it detected that there were no enabled menu items.
//
//		01/27/97	JMI	Now sets palette to background image.
//
//		01/30/97	JMI	Now callback can return non-zero aborting menu right
//							away once started.
//
//		02/02/97	JMI	Now the GUI can be set to use transparent BLiT'ing via
//							the Menu struct.  Now MenuItemCall with -1 for sMenuItem
//							indicates change of menu focus (but no choice yet made).
//
//		02/03/97	JMI	Split DoMenu() into DoMenuInput() and DoMenuOutput() so
//							the input and output for menus can be done at different
//							points in the user loop or whatever.
//
//		02/04/97	JMI	Changed LoadDib() call to Load() (which now supports
//							loading of DIBs).
//
//		02/04/97	JMI	Callback on StopMenu() now occurs after all deallocations.
//
//		02/10/97	JMI	rspReleaseResource() now takes a ptr to a ptr.
//
//		04/08/97	JMI	Now sucks up (and utilizes) Tab and Shift-Tab combos.
//
//		04/11/97	JMI	Now sets the new m_sFontCellHeight field in each GUI
//							that displays text in a menu.
//
//		04/21/97	JMI	Added pmenuBack to MenuAutoItems.
//
//		04/24/97	JMI	Now supports the centering flag for header text and 
//							two new flags for shadowing the header and the items.
//							Now colors are specified as RGBA and background font
//							color must be transparent (not offered as an option).
//
//		06/16/97	JMI	Now stores the current composite buffer contents on
//							StartMenu() and restores them on StopMenu().
//
//		07/01/97	JMI	Added sBackItem to MenuAutoItems so when we use pmenuBack
//							we can select the item last selected.
//
//		07/06/97	JMI	Added MenuColumnizeGuis to make the GUIs associated with
//							menu items appear in their own column.
//
//		07/21/97	JMI	Now displays disabled items with shadow and text colors
//							swapped.
//
//		08/10/97	JMI	Made MAX_MENU_ITEMS based on sizeof ami array.
//
//		08/14/97	JMI	MenuItemCall (fnChoice) now returns true to accept menu 
//							choice, false otherwise.
//
//		08/22/97	JMI	Now locks the composite buffer before accessing it.
//
//		08/25/97	JMI	Added two new palette info entries for the range of
//							mappable entries.
//
//		08/27/97	JMI	Now the cancel item cannot be disabled...good thing?
//							Probably...otherwise menus cancelled by callbacks and 
//							such would have no where to go which was kind of worrying
//							me before.  Also, Useful for having no actual cancel item.
//
//////////////////////////////////////////////////////////////////////////////
//
// This module operates a menu specified by the user via a structure, Menu.
// There are only a few basic calls, StartMenu(), DoMenuInput(), 
// DoMenuOutput(), GetCurrentMenu(), and StopMenu().  Communication back to 
// the caller is via callbacks.  
//
// The user must define a Menu struct and pass a pointer to it to StartMenu().
// At this point, the StopMenu() will automatically be called to destroy any
// existing menu.  Next, the new menu described by the Menu struct is created.
// Now, iteratively, the DoMenuInput() and DoMenuOutput() functions should be
// called to service the menu.  This will process the supplied input, if it
// pertains to the menu, and draw the menu into the supplied image.  Finally,
// when done with the menu, call StopMenu() to clean up the current menu.  
// Calls to DoMenu*() when no menu is active will simply return.
//
// Note regarding RResMgr.  The only members of the RResMgr utilized are
// Load(...) and Release(...).  This means that the user of the Menu API is
// resposible for the other stuff, such as OpenSak(), CloseSak(), Purge(),
// etc.
//
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
// C Headers -- Must be included before RSPiX.h b/c RSPiX utilizes SHMalloc.
//////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// RSPiX Headers.
// If PATHS_IN_INCLUDES macro is defined, we can utilize relative
// paths to a header file.  In this case we generally go off of our
// RSPiX root directory.  System.h MUST be included before this macro
// is evaluated.  System.h is the header that, based on the current
// platform (or more so in this case on the compiler), defines 
// PATHS_IN_INCLUDES.  Blue.h includes system.h so you can include that
// instead.
///////////////////////////////////////////////////////////////////////////////
#include "Blue.h"
#include "input.h"
#ifdef PATHS_IN_INCLUDES
	#include "ORANGE/color/colormatch.h"
	#include "ORANGE/MsgBox/MsgBox.h"
	#include "CYAN/cyan.h"
#else
	#include "colormatch.h"
	#include "MsgBox.h"
	#include "cyan.h"
#endif

//////////////////////////////////////////////////////////////////////////////
// Application headers.
//////////////////////////////////////////////////////////////////////////////
#include "menu.h"

//////////////////////////////////////////////////////////////////////////////
// Module specific macros.
//////////////////////////////////////////////////////////////////////////////

// Determines the number of elements in the passed array at compile time.
#define NUM_ELEMENTS(a)	(sizeof(a) / sizeof(a[0]) )

#define MAX_MENU_ITEMS	short(NUM_ELEMENTS(ms_pmenu->ami) - 1)

// Determines the indicator's Y position based on the supplied menu item index.
#define INDICATOR_POS_Y(sIndex)	(ms_asMenuItemPosY[sIndex] - \
											ms_guiIndicator.m_im.m_sHeight / 2)

// Determines the indiator's X position based on the supplied menu item index.
#define INDICATOR_POS_X(sIndex)	(ms_asMenuItemPosX[sIndex] - \
											ms_guiIndicator.m_im.m_sWidth - \
											ms_pmenu->menupos.sIndicatorSpacingX) 

// Determines an item index from a positive index or a negative offset from
// the number of items.
#define ITEMINDEX(sIndex)	(((sIndex) < 0) ? ms_sNumMenuItems + (sIndex) : (sIndex))

#define INVALID_MENU_ITEM	0x7fff

#define SHADOW_X_PIXELS	1
#define SHADOW_Y_PIXELS	1

#ifdef SYS_ENDIAN_LITTLE
	#define ALPHA(u32)	((u32 & 0x000000FF) >> 0)
	#define RED(u32)		((u32 & 0x0000FF00) >> 8)
	#define GREEN(u32)	((u32 & 0x00FF0000) >> 16)
	#define BLUE(u32)		((u32 & 0xFF000000) >> 24)
#else
	#define ALPHA(u32)	((u32 & 0xFF000000) >> 24)
	#define RED(u32)		((u32 & 0x00FF0000) >> 16)
	#define GREEN(u32)	((u32 & 0x0000FF00) >> 8)
	#define BLUE(u32)		((u32 & 0x000000FF) >> 0)
#endif

//////////////////////////////////////////////////////////////////////////////
// Module specific typedefs.
//////////////////////////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////////////////////////
// Exported (extern) variables.
//////////////////////////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////////////////////////
// Module specific (static) variables / Instantiate class statics.
//////////////////////////////////////////////////////////////////////////////

static Menu*		ms_pmenu	= NULL;			// Pointer to current menu.

static RMsgBox		ms_msgbox;					// Menu container.

static int16_t		ms_sNumMenuItems	= 0;	// Current number of menu items.

static RFont*		ms_pfontItems	= NULL;	// Font for menu items.
static RFont*		ms_pfontHeader	= NULL;	// Font for menu header.

static RPrint		ms_printItems;				// Print used for guis.
static RPrint		ms_printHeader;			// Print used for header.

static RImage*		ms_pimBackground	= NULL;	// Dlg's background image.

static RGuiItem	ms_guiIndicator;			// Current menu item indicator.

static RTxt			ms_txtHeader;				// Header text item.

static int16_t		ms_sCurItem			= 0;	// Current menu item index.
static int16_t		ms_sNextMenuItem	= INVALID_MENU_ITEM;	// Item to select on next
																			// StartMenu().

static int16_t		ms_asMenuItemPosX[MAX_MENU_ITEMS];	// Array of item X positions.
static int16_t		ms_asMenuItemPosY[MAX_MENU_ITEMS];	// Array of item Y positions.

#ifdef MOBILE
static int16_t		ms_asMenuItemMouseHeight[MAX_MENU_ITEMS];	// Array of item heights which can be used for mouse
#endif

static int16_t		ms_sItemsPerColumn;			// Number of items per column

static int16_t		ms_sCancel			= FALSE;	// TRUE to cancel current menu.

static int16_t		ms_sItemsX			= 0;		// X position of menu items.
static int16_t		ms_sItemsY			= 0;		// Y position of menu items.

static RResMgr*	ms_presmgr			= NULL;

static RImage		ms_imPreMenu;				// Contents of composite buffer
														// before the menu was drawn.

static int16_t		ms_sPreMenuX;				// Location of ms_imPreMenu on screen.
static int16_t		ms_sPreMenuY;				// Location of ms_imPreMenu on screen.

static RImage*		ms_pimComposite	= NULL;	// Composite buffer.

//////////////////////////////////////////////////////////////////////////////
// Module specific (static) protos.
//////////////////////////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////////////////////////
// Internal Functions.
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
//
// Critical callage for our msgbox.
//
//////////////////////////////////////////////////////////////////////////////
static uint32_t CriticalCall(	// Returns item to select or 0.
	RMsgBox*	/*pmsgbox*/)	// MsgBox.
	{
	// For now, we expect the caller of this Menu API to call rspDoSystem(),
	// RHot::Do(), etc.

	return 0;	// No item preferred.
	}

//////////////////////////////////////////////////////////////////////////////
//
// Draws the background for ms_msgbox.
//
//////////////////////////////////////////////////////////////////////////////
static void BackCall(	// Returns nothing.
	RGuiItem* pgui,		// The RGuiItem being composed (this).
	RImage* pim,			// Image to draw into.  Try to stay within 
								// prc please.
	RRect* prc)				// Where to in image.
	{
	ASSERT(ms_pmenu != NULL);
	ASSERT(pim != NULL);
	ASSERT(prc != NULL);
	ASSERT(pgui != NULL);

	// If this is our ms_msgbox . . .
	if (pgui == &ms_msgbox)
		{
		// Fill background before any further drawing.
		rspRect(
			ms_pmenu->menuback.u32BackColor,			// Color to draw with.
			pim,												// Dst image.
			prc->sX, prc->sY, prc->sW, prc->sH);	// Dst rectangle w/i image.

		if (ms_pmenu->menuflags & MenuBackTiled)
			{
			// Determine amount to offset to center tiled area.
			int16_t	sOffsetX	= (prc->sW % ms_pimBackground->m_sWidth) / 2;
			int16_t	sOffsetY	= (prc->sH % ms_pimBackground->m_sHeight) / 2;
			int16_t	sMaxX		= prc->sW - (prc->sW % ms_pimBackground->m_sWidth);
			int16_t	sMaxY		= prc->sH - (prc->sH % ms_pimBackground->m_sHeight);
			// Tile.
			int16_t	sX, sY;
			for (	sY = prc->sY + sOffsetY; 
					sY < sMaxY; 
					sY += ms_pimBackground->m_sHeight)
				{
				for (	sX = prc->sX + sOffsetX; 
						sX < sMaxX; 
						sX += ms_pimBackground->m_sWidth)
					{
					rspBlit(
						ms_pimBackground,		// Source image.
						pim,						// Dest image.
						0, 0,						// Source position.
						sX,						// Dest position.
						sY,
						ms_pimBackground->m_sWidth,	// Width to blit.
						ms_pimBackground->m_sHeight,	// Height to blit.
						NULL,								// Dest clip rect.
						NULL);							// Source clip rect.
					}
				}
			}
		else
			{
			// Center.
			rspBlit(
				ms_pimBackground,		// Source image.
				pim,						// Dest image.
				0, 0,						// Source position.
				prc->sX + prc->sW / 2 - ms_pimBackground->m_sWidth / 2,	// Dest position.
				prc->sY + prc->sH / 2 - ms_pimBackground->m_sHeight / 2,
				ms_pimBackground->m_sWidth,	// Width to blit.
				ms_pimBackground->m_sHeight,	// Height to blit.
				NULL,								// Dest clip rect.
				NULL);							// Source clip rect.
			}
		}
	else
		{
		// Clear background to 0 so it will be transparent when we convert it to
		// an FSPR8.
		rspRect(
			0,													// Color to draw with.
			pim,												// Dst image.
			prc->sX, prc->sY, prc->sW, prc->sH);	// Dst rectangle w/i image.
		}

	}

//////////////////////////////////////////////////////////////////////////////
//
// Copies select attributes from one gui to another.
//
//////////////////////////////////////////////////////////////////////////////
inline void SetGuiAttributes(
	RGuiItem*	pguiDst,			// Gui to set attributes to.
	RGuiItem*	pguiSrc)			// Gui to get attributes from.
	{
	pguiDst->m_u32TextColor	= pguiSrc->m_u32TextColor;
	pguiDst->m_u32BackColor	= pguiSrc->m_u32BackColor;
	pguiDst->m_pprint			= pguiSrc->m_pprint;
	}

//////////////////////////////////////////////////////////////////////////////
//
// Moves the next item in the specified "direction".
//
//////////////////////////////////////////////////////////////////////////////
inline bool NextItem(	// Returns true, if new item selected.
	int16_t sDirVert,		// Specifies direction (and amount) to move by being
								// negative or positive.  If 0 when sDirHorz is 0, 
								// the current item is checked and, if disabled, 
								// sDirVert is set to 1.
	int16_t sDirHorz)		// Specifies direction (and amount) to move by being
								// negative or positive.  If 0 when sDirVert is 0, 
								// the current item is checked and, if disabled, 
								// sDirVert is set to 1.
	{
	bool	bNewSelection	= false;	// Assume no new selection.

	ASSERT(ms_pmenu != NULL);

	int16_t	sNumItemsTraversed	= 0;
	int16_t	sOldItem					= ms_sCurItem;

	do
		{
		// Move current item vertically.
		ms_sCurItem	+= sDirVert;
		// Move current item horizontally.
		ms_sCurItem	+= (sDirHorz * ms_sItemsPerColumn);

		// If direction is 0 . . .
		if (sDirVert == 0 && sDirHorz == 0)
			{
			// Make sDir forward 1.
			sDirVert	= 1;
			// This feature allows the caller to have the current item checked
			// and, if disabled, the next enabled item will be chosen.
			}
		else
			{
			sNumItemsTraversed++;
			}

		// Check bounds.
		if (	(ms_sCurItem > ms_sNumMenuItems)
			||	(ms_sCurItem == ms_sNumMenuItems && sDirHorz) )
			{
			if (ms_sItemsPerColumn > 0)
				{
				ms_sCurItem	= ms_sCurItem % ms_sItemsPerColumn;
				}
			else
				{
				ms_sCurItem	= 0;
				}
			}
		else if (ms_sCurItem == ms_sNumMenuItems)
			{
			ms_sCurItem	= 0;
			}
		else if (	(ms_sCurItem < -1)
					||	(ms_sCurItem == -1 && sDirHorz) )
			{
			if (ms_sItemsPerColumn > 0)
				{
#if 0
				short	sMaxMenuItem	= MAX(ms_sNumMenuItems - 1, 0);
				ms_sCurItem	= MIN(
					sMaxMenuItem, 
					short(sMaxMenuItem / ms_sItemsPerColumn * ms_sItemsPerColumn + (ms_sItemsPerColumn + ms_sCurItem) ) );
#else
				// Determine location of item in next column.
				int16_t	sMaxMenuItem	= MAX(ms_sNumMenuItems - 1, 0);
				ms_sCurItem	= sMaxMenuItem / ms_sItemsPerColumn * ms_sItemsPerColumn + (ms_sItemsPerColumn + ms_sCurItem);
				// If too large . . .
				if (ms_sCurItem >= ms_sNumMenuItems)
					{
					ASSERT(sDirHorz < 0);
					// Go back another column.
					ms_sCurItem	+= (sDirHorz * ms_sItemsPerColumn);
					}
#endif
				}
			else
				{
				ms_sCurItem	= 0;
				}
			}
		else if (ms_sCurItem == -1)
			{
			ms_sCurItem	= MAX(ms_sNumMenuItems - 1, 0);
			}

		} while (ms_pmenu->ami[ms_sCurItem].sEnabled == FALSE
			&& sNumItemsTraversed <= ms_sNumMenuItems);

	// If we've tried all menu items . . .
	if (sNumItemsTraversed > ms_sNumMenuItems)
		{
		// Cancel this menu.
		ms_sCancel	= TRUE;
		}
	else
		{
		// If there is a callback . . .
		if (ms_pmenu->menucallbacks.fnChoice != NULL)
			{
			// If there was a change in the selected item . . .
			if (ms_sCurItem != sOldItem)
				{
				bNewSelection	= true;
				// Notify caller of change in focused item.
				(*(ms_pmenu->menucallbacks.fnChoice))(ms_pmenu, -1);
				}
			}
		}

	// Set indicator vertical position based on new item.
	// Set indicator horziontal position based on new item.
	ms_guiIndicator.Move(
		INDICATOR_POS_X(ms_sCurItem),
		INDICATOR_POS_Y(ms_sCurItem) );
	
	// Set focus to this item's gui or to no item, if none.
	ms_msgbox.SetFocus(ms_pmenu->ami[ms_sCurItem].pgui);

	return bNewSelection;
	}

#ifdef MOBILE
bool MouseChooseItem(int x, int y)
{
	for (int i=0;i < ms_sNumMenuItems; i++)
	{
		int top = ms_asMenuItemPosY[i] - ms_asMenuItemMouseHeight[i] / 2;
		int bottom =  ms_asMenuItemPosY[i] + ms_asMenuItemMouseHeight[i] / 2;
		TRACE("MouseChooseItem %d  top=%d  bot=%d",i,top,bottom);
		if ((y >= top) && (y < bottom)) //Check if Y is in a controls area
		{
			if (ms_pmenu->ami[i].sEnabled) //Check item is actually enabled
			{
				ms_sCurItem = i;
				(*(ms_pmenu->menucallbacks.fnChoice))(ms_pmenu, -1);
				// Set focus to this item's gui or to no item, if none.
				ms_msgbox.SetFocus(ms_pmenu->ami[ms_sCurItem].pgui);

				return true;
			}
		}
	}
	return false;
}
#endif
//////////////////////////////////////////////////////////////////////////////
// External Functions.
//////////////////////////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////////////////////////
//
// Sets up the menu described to be run via DoMenu*() calls.
//
//////////////////////////////////////////////////////////////////////////////
extern int16_t StartMenu(				// Returns 0 on success.
	Menu*	pmenu,						// In:  Pointer to Menu describing menu.
	RResMgr* presmgr,					// In:  Resource manager ptr.
	RImage*	pimDst)					// In:  Src for erase data.  Erase data is
											// updated back over the menu on StopMenu()
											// calls.
	{
	int16_t	sRes	= 0;	// Assume success.

	// End any existing menu.
	StopMenu();

	// If there is a new menu . . .
	if (pmenu != NULL)
		{
        rspKeyRepeat(true);

		U32	u32TextColor;
		U32	u32TextShadowColor;
		int16_t	sMapStartIndex;
		int16_t	sMapNumEntries;

		// Get composite buffer.
		ms_pimComposite	= pimDst;

		// Set new menu.
		ms_pmenu		= pmenu;

		ms_sCancel	= FALSE;

		// Get info on composite buffer.
		RImage*	pimComposite	= NULL;

		rspNameBuffers(&pimComposite);

		ASSERT(pimComposite != NULL);

		// If there is a callback . . .
		if (ms_pmenu->menucallbacks.fnInit != NULL)
			{
			if ((*(ms_pmenu->menucallbacks.fnInit))(ms_pmenu, TRUE) == 0)
				{
				}
			else
				{
				TRACE("StartMenu(): StartMenu() aborted by callback.\n");
				// Abort right away.
				ms_sCancel	= TRUE;
				}
			}

		ASSERT(presmgr != NULL);
		// Store pointer to desired res mgr.
		ms_presmgr	= presmgr;

		// Determine number of menu items.
		ms_sNumMenuItems	= 0;
		while (pmenu->ami[ms_sNumMenuItems].pszText != NULL)
			{
			ms_sNumMenuItems++;
			}

		ms_sNumMenuItems	= MIN(ms_sNumMenuItems, MAX_MENU_ITEMS);

		// Set GUI settings.
		ms_msgbox.m_sTransparent	= ms_pmenu->menugui.sTransparent;

		// If there is a desired font for menu header . . .
		if (ms_pmenu->menuheader.pszFontFile != NULL)
			{
			// Attempt to allocate get font resource . . .
			if (rspGetResource(
				ms_presmgr,
				ms_pmenu->menuheader.pszFontFile,
				&ms_pfontHeader) == 0)
				{
				// Successfully loaded font.
				ms_printHeader.SetFont(ms_pmenu->menuheader.sHeight, ms_pfontHeader);
				}
			else
				{
				TRACE("StartMenu(): Unable to load font file \"%s\" via ms_presmgr->\n",
					ms_pmenu->menuheader.pszFontFile);
				sRes	= -2;

				goto Done;
				}
			}
		else
			{
			// If there is no font specified in ms_pmenu, we must use the one
			// currently being used by the general GUI items.

			// In order to use the default font, one must exist!
			// If no font has been set in RGuiItem::ms_printItems, we're done.
			if (RGuiItem::ms_print.GetFont() != NULL)
				{
				// Use current font from default RPrint used by all GUI items.
				ms_printHeader.SetFont(ms_pmenu->menuheader.sHeight, RGuiItem::ms_print.GetFont());
				}
			else
				{
				TRACE("StartMenu(): Menu does not specify a font and neither does "
					"RGuiItem::ms_print.\n");
				sRes	= -3;

				goto Done;
				}
			}

		if (ms_pmenu->menuflags & MenuHeaderTextShadow)
			{
			ms_txtHeader.m_sTextShadowOffsetX	= SHADOW_X_PIXELS;
			ms_txtHeader.m_sTextShadowOffsetY	= SHADOW_Y_PIXELS;
			ms_txtHeader.m_sTextEffects			= RGuiItem::Shadow;
			}
		else
			{
			ms_txtHeader.m_sTextEffects			= 0;
			}

		// If there is a desired font for menu items . . .
		if (ms_pmenu->menuitemsfont.pszFile != NULL)
			{
			// Attempt to allocate get font resource . . .
			if (rspGetResource(
				ms_presmgr,
				ms_pmenu->menuitemsfont.pszFile,
				&ms_pfontItems) == 0)
				{
				// Successfully loaded font.
				ms_printItems.SetFont(ms_pmenu->menuitemsfont.sHeight, ms_pfontItems);
				}
			else
				{
				TRACE("StartMenu(): Unable to load font file \"%s\" via ms_presmgr->\n",
					ms_pmenu->menuitemsfont.pszFile);
				sRes	= -2;

				goto Done;
				}
			}
		else
			{
			// If there is no font specified in ms_pmenu, we must use the one
			// currently being used by the general GUI items.

			// In order to use the default font, one must exist!
			// If no font has been set in RGuiItem::ms_printItems, we're done.
			if (RGuiItem::ms_print.GetFont() != NULL)
				{
				// Use current font from default RPrint used by all GUI items.
				ms_printItems.SetFont(ms_pmenu->menuitemsfont.sHeight, RGuiItem::ms_print.GetFont());
				}
			else
				{
				TRACE("StartMenu(): Menu does not specify a font and neither does "
					"RGuiItem::ms_print.\n");
				sRes	= -3;

				goto Done;
				}
			}

		// Set dlg's attributes based on ms_pmenu.
		// Set dlg's attributes that are not based on ms_pmenu.
		ms_msgbox.m_sBorderThickness	= 0;	// No border.
		// Make sure dlg's background is drawn by this module.
		ms_msgbox.m_backcall				= BackCall;
		// Use our print.
		ms_msgbox.m_pprint				= &ms_printItems;
		// Use this text height.
		ms_msgbox.m_sFontCellHeight	= ms_pmenu->menuitemsfont.sHeight;

		// Set callback for iterative calls.
		ms_msgbox.m_mbcUser				= CriticalCall;
	
		// Compute position of dialog.
		int16_t	sX;
		int16_t	sY;
		int16_t sW;
		int16_t	sH;

		if (ms_pmenu->menuflags & MenuPosCenter)
			{
			// Centered menu.
			sW	= ms_pmenu->menupos.sW;
			sH	= ms_pmenu->menupos.sH;
			sX	= pimComposite->m_sWidth / 2 - sW / 2;
			sY	= pimComposite->m_sHeight / 2 - sH / 2;
			}
		else
			{
			if (ms_pmenu->menuflags & MenuPosFullscreen)
				{
				// Full screen menu.
				sX	= 0;
				sY	= 0;
				sW	= pimComposite->m_sWidth;
				sH	= pimComposite->m_sHeight;
				}
			else
				{
				// Menu at specified location.
				sX	= ms_pmenu->menupos.sX;
				sY	= ms_pmenu->menupos.sY;
				sW	= ms_pmenu->menupos.sW;
				sH	= ms_pmenu->menupos.sH;
				}
			}

		// If there is a background . . .
		if (ms_pmenu->menuback.pszFile != NULL)
			{
			// Load background . . .
			if (rspGetResource(
				ms_presmgr,
				ms_pmenu->menuback.pszFile,
				&ms_pimBackground) == 0)
				{
				// Successfully loaded dlg background.
				// If paletted . . .
				if (ms_pimBackground->m_pPalette != NULL)
					{
					int16_t	sEntrySize	= ms_pimBackground->m_pPalette->m_sPalEntrySize;
					int16_t	sStartIndex	= ms_pmenu->menuback.sSetStartIndex;

					// Set palette.
					rspSetPaletteEntries(
						sStartIndex,
						ms_pmenu->menuback.sSetNumEntries,
						ms_pimBackground->m_pPalette->Red(sStartIndex),
						ms_pimBackground->m_pPalette->Green(sStartIndex),
						ms_pimBackground->m_pPalette->Blue(sStartIndex),
						sEntrySize);

					// Update hardware.
					rspUpdatePalette();
					}
				}
			else
				{
				TRACE("StartMenu(): Unable to load \"%s\" via ms_presmgr.\n", 
					ms_pmenu->menuback.pszFile);
				sRes	= -6;

				goto Done;
				}
			}

		// Get color indices.
		U8	au8Red[256];
		U8	au8Green[256];
		U8	au8Blue[256];
		rspGetPaletteEntries(
			0,				// Palette entry to start copying to (has no effect on source!)
			256,			// Number of palette entries to do
			au8Red,		// Pointer to first red component to copy to
			au8Green,	// Pointer to first green component to copy to
			au8Blue,		// Pointer to first blue component to copy to
			sizeof(U8)	// Number of bytes by which to increment pointers after each copy
			);

		sMapStartIndex	= ms_pmenu->menuback.sMapStartIndex;
		sMapNumEntries	= ms_pmenu->menuback.sMapNumEntries;
		if (sMapNumEntries == 0)
			{
			sMapNumEntries	= 256;
			}

		u32TextColor
			= rspMatchColorRGB(											// Out: Matched index.
				RED(ms_pmenu->menuitemsfont.u32ForeColor),		// In:  Pixel's red value.                                 
				GREEN(ms_pmenu->menuitemsfont.u32ForeColor),		// In:  Pixel's green value.                               
				BLUE(ms_pmenu->menuitemsfont.u32ForeColor),		// In:  Pixel's blue value.                                
				sMapStartIndex,											// In:  Min mappable index (affects source).               
				sMapNumEntries,											// In:  Num mappable indices (affects source).             
				au8Red,														// In:  Beginning of red color table.                      
				au8Green,													// In:  Beginning of green color table.                    
				au8Blue,														// In:  Beginning of blue color table.                     
				sizeof(U8)													// In:  Size to increment between each index in each table.
				);

		u32TextShadowColor
			= rspMatchColorRGB(											// Out: Matched index.
				RED(ms_pmenu->menuitemsfont.u32ShadowColor),		// In:  Pixel's red value.                                 
				GREEN(ms_pmenu->menuitemsfont.u32ShadowColor),	// In:  Pixel's green value.                               
				BLUE(ms_pmenu->menuitemsfont.u32ShadowColor),	// In:  Pixel's blue value.                                
				sMapStartIndex,											// In:  Min mappable index (affects source).               
				sMapNumEntries,											// In:  Num mappable indices (affects source).             
				au8Red,														// In:  Beginning of red color table.                      
				au8Green,													// In:  Beginning of green color table.                    
				au8Blue,														// In:  Beginning of blue color table.                     
				sizeof(U8)													// In:  Size to increment between each index in each table.
				);

		ms_msgbox.m_u32BackColor		= 0;

		ms_txtHeader.m_u32TextColor
			= rspMatchColorRGB(										// Out: Matched index.
				RED(ms_pmenu->menuheader.u32ForeColor),		// In:  Pixel's red value.                                 
				GREEN(ms_pmenu->menuheader.u32ForeColor),		// In:  Pixel's green value.                               
				BLUE(ms_pmenu->menuheader.u32ForeColor),		// In:  Pixel's blue value.                                
				sMapStartIndex,										// In:  Min mappable index (affects source).               
				sMapNumEntries,										// In:  Num mappable indices (affects source).             
				au8Red,													// In:  Beginning of red color table.                      
				au8Green,												// In:  Beginning of green color table.                    
				au8Blue,													// In:  Beginning of blue color table.                     
				sizeof(U8)												// In:  Size to increment between each index in each table.
				);

		ms_txtHeader.m_u32TextShadowColor
			= rspMatchColorRGB(										// Out: Matched index.
				RED(ms_pmenu->menuheader.u32ShadowColor),		// In:  Pixel's red value.                                 
				GREEN(ms_pmenu->menuheader.u32ShadowColor),	// In:  Pixel's green value.                               
				BLUE(ms_pmenu->menuheader.u32ShadowColor),	// In:  Pixel's blue value.                                
				sMapStartIndex,										// In:  Min mappable index (affects source).               
				sMapNumEntries,										// In:  Num mappable indices (affects source).             
				au8Red,													// In:  Beginning of red color table.                      
				au8Green,												// In:  Beginning of green color table.                    
				au8Blue,													// In:  Beginning of blue color table.                     
				sizeof(U8)												// In:  Size to increment between each index in each table.
				);

		ms_txtHeader.m_u32BackColor		= 0;

		// Create dialog . . .
		if (ms_msgbox.Create(sX, sY, sW, sH, pimComposite->m_sDepth) == 0)
			{
			// Successfully created dialog.
			}
		else
			{
			TRACE("StartMenu(): Unable to create ms_msgbox.\n");
			sRes	= -5;

			goto Done;
			}

		ms_sItemsX	= ms_pmenu->menupos.sItemX;
		ms_sItemsY	= ms_pmenu->menupos.sItemY;

		// If negative . . .
		if (ms_sItemsX < 0)
			{
			// Offset from center.
			ms_sItemsX	+= ms_msgbox.m_im.m_sWidth / 2;
			}

		// If negative . . .
		if (ms_sItemsY < 0)
			{
			// Offset from center.
			ms_sItemsY	+=	ms_msgbox.m_im.m_sHeight / 2;
			}

		// If there is menu header text . . .
		if (ms_pmenu->menuheader.pszHeaderText[0] != '\0')
			{
			int16_t	sPosX	= ms_pmenu->menupos.sHeaderX;
			int16_t	sPosY	= ms_pmenu->menupos.sHeaderY;

			if (sPosX < 0)
				{
				// Offset from center.
				sPosX	+=	ms_msgbox.m_im.m_sWidth / 2;
				}

			if (sPosY < 0)
				{
				// Offset from center.
				sPosY	+=	ms_msgbox.m_im.m_sHeight / 2;
				}

			ms_txtHeader.m_pprint					= &ms_printHeader;
			ms_txtHeader.m_sBorderThickness		= 0;
			ms_txtHeader.m_sFontCellHeight		= ms_pmenu->menuheader.sHeight;

			// Determine width for header . . .
			// Store text width.
			int16_t sTextWidth	= ms_txtHeader.m_pprint->GetWidth(ms_pmenu->menuheader.pszHeaderText);
			// Active when visible.
			ms_txtHeader.m_sActive	= TRUE;
			ms_txtHeader.SetVisible(TRUE);

			// If centering specified . . .
			if (ms_pmenu->menuflags & MenuHeaderTextCenter)
				{
				sPosX	= ms_msgbox.m_im.m_sWidth / 2 - sTextWidth / 2;
				}
			
			// Update header item.
			ms_txtHeader.SetText(ms_pmenu->menuheader.pszHeaderText);
			ms_txtHeader.m_sX						= sPosX;
			ms_txtHeader.m_sY						= sPosY;

			// Reserve space for borders, if there are any.
			int16_t sTotalBorderThickness	= ms_txtHeader.GetTopLeftBorderThickness()
													+ ms_txtHeader.GetBottomRightBorderThickness();

			// Create item . . .
			if (ms_txtHeader.Create(
				sPosX, sPosY,
				sTextWidth + sTotalBorderThickness + SHADOW_X_PIXELS,
				ms_txtHeader.m_sFontCellHeight + sTotalBorderThickness + SHADOW_Y_PIXELS,
				ms_msgbox.m_im.m_sDepth) == 0)
				{
				// Successfully created header item.
				ms_txtHeader.m_sTransparent			= TRUE;
				ms_txtHeader.m_u32TransparentColor	= 0;
				// Add to msg box . . .
				ms_txtHeader.SetParent(&ms_msgbox);
				}
			else
				{
				TRACE("StartMenu(): ms_txtHeader.Create() failed.\n");
				sRes	= -11;

				goto Done;
				}
			}

		ms_sItemsPerColumn	= 0;

		if (ms_sNumMenuItems > 0)
			{
			int16_t	sMaxItemX	= ms_pmenu->menupos.sMaxItemX;
			int16_t	sMaxItemY	= ms_pmenu->menupos.sMaxItemY;

			// If less than 1 . . .
			if (sMaxItemX < 1)
				{
				sMaxItemX	+= ms_msgbox.m_im.m_sWidth;
				}

			// If less than 1 . . .
			if (sMaxItemY < 1)
				{
				sMaxItemY	+= ms_msgbox.m_im.m_sHeight;
				}

			int16_t	sPosX				= ms_sItemsX;
			int16_t sIndex			= 0;

			while (sIndex < ms_sNumMenuItems && sPosX < sMaxItemX)
				{
				int16_t	sPosY					= ms_sItemsY;
				int16_t	sMaxGuiPosX			= sPosX;
				int16_t	sMaxItemExtentX	= sPosX;
				int16_t	sStartIndex			= sIndex;	// First index this column.

				RTxt*	ptxt;
				for ( ; sIndex < ms_sNumMenuItems; sIndex++)
					{
					// Color is based on whether the item is enabled or not.
					if (ms_pmenu->ami[sIndex].sEnabled)
						{
						ms_msgbox.m_u32TextColor			= u32TextColor;
						ms_msgbox.m_u32TextShadowColor	= u32TextShadowColor;
						}
					else
						{
						ms_msgbox.m_u32TextColor			= u32TextShadowColor;
						ms_msgbox.m_u32TextShadowColor	= u32TextColor;
						}

					// Add a text item and get a pointer to it.
					// Note that the item is stored and maintained by RMsgBox.
					ptxt	= ms_msgbox.AddText(
						ms_pmenu->ami[sIndex].pszText,
						sPosX, sPosY,
						sIndex,
						SHADOW_X_PIXELS,
						SHADOW_Y_PIXELS);

					if (ptxt != NULL)
						{
						// If shadow specified . . .
						if (ms_pmenu->menuflags & MenuItemTextShadow)
							{
							ptxt->m_sTextShadowOffsetX	= SHADOW_X_PIXELS;
							ptxt->m_sTextShadowOffsetY	= SHADOW_Y_PIXELS;
							ptxt->m_sTextEffects			= RGuiItem::Shadow;
							// Compose with shadow.
							ptxt->Compose();
							}

						// Remember pos of vertical center of this item.
						ms_asMenuItemPosY[sIndex]	= sPosY + ptxt->m_im.m_sHeight / 2;
						// Remember x position of this item.
						ms_asMenuItemPosX[sIndex]	= sPosX;
#ifdef MOBILE
						// Remember height of this item. Height of item + spacing between them
						ms_asMenuItemMouseHeight[sIndex]	= ptxt->m_im.m_sHeight + ms_pmenu->menupos.sItemSpacingY;
#endif
						// If there is a gui . . .
						if (ms_pmenu->ami[sIndex].pgui != NULL)
							{
							// Put gui item just to right of text and
							// centered vertically with text.
							ms_pmenu->ami[sIndex].pgui->Move(
								sPosX + ptxt->m_im.m_sWidth + ms_pmenu->menupos.sIndicatorSpacingX,
								sPosY + ptxt->m_im.m_sHeight / 2 - ms_pmenu->ami[sIndex].pgui->m_im.m_sHeight / 2);

							// Remember the position of the furthest GUI.
							sMaxGuiPosX	= MAX(sMaxGuiPosX, ms_pmenu->ami[sIndex].pgui->m_sX);

							ms_pmenu->ami[sIndex].pgui->SetParent(&ms_msgbox);
							ms_pmenu->ami[sIndex].pgui->SetVisible(TRUE);

							// Remember the extent of the furthest item.
							sMaxItemExtentX	= MAX(
								sMaxItemExtentX, 
								int16_t(ms_pmenu->ami[sIndex].pgui->m_sX + ms_pmenu->ami[sIndex].pgui->m_im.m_sWidth) );
							}
						else
							{
							// Remember the extent of the furthest item.
							sMaxItemExtentX	= MAX(
								sMaxItemExtentX, 
								int16_t(ptxt->m_sX + ptxt->m_im.m_sWidth) );
							}

						ptxt->m_sTransparent				= TRUE;
						ptxt->m_u32TransparentColor	= 0;

						// Move to pos for next item.
						sPosY += ptxt->m_im.m_sHeight + ms_pmenu->menupos.sItemSpacingY;

						// If this next position is too far . . .
						if (sPosY + ptxt->m_im.m_sHeight > sMaxItemY)
							{
							sIndex++;
							break;
							}
						}
					else
						{
						TRACE("StartMenu(): ms_msgbox.AddText() failed for item %s.\n", 
							ms_pmenu->ami[sIndex].pszText);
						sRes	= -10;

						goto Done;
						}
					}

				if (ms_sItemsPerColumn == 0)
					{
					ms_sItemsPerColumn	= sIndex;
					}

				// If columnize GUIs specified . . .
				if (ms_pmenu->menuflags & MenuColumnizeGuis)
					{
					// End index EXCLUSIVE is either an entire column
					// or a partial (for the last column).
					int16_t	sEndIndex	= MIN(sIndex, int16_t(sStartIndex + ms_sItemsPerColumn) );

					for (sIndex = sStartIndex; sIndex < sEndIndex; sIndex++)
						{
						// If there is a gui . . .
						if (ms_pmenu->ami[sIndex].pgui != NULL)
							{
							// Shift GUI over to furthest GUI position.
							ms_pmenu->ami[sIndex].pgui->Move(
								sMaxGuiPosX,
								ms_pmenu->ami[sIndex].pgui->m_sY);
							}
						}
					}

				// Next column.
				sPosX	= sMaxItemExtentX + ms_pmenu->menupos.sIndicatorSpacingX + ms_guiIndicator.m_im.m_sWidth;
				}

			// Store number of menu items that fit.
			ms_sNumMenuItems	= sIndex;
			}

		// If we went back to this menu . . .
		if (ms_sNextMenuItem != INVALID_MENU_ITEM)
			{
			// We don't need to use the ITEMINDEX macro if we set this
			// value, but just in case a callback decides to use the
			// extended indexing . . ..
			ms_sCurItem	= ITEMINDEX(ms_sNextMenuItem);
			// Clear back item.
			ms_sNextMenuItem	= INVALID_MENU_ITEM;
			}
		else
			{
			// Start at default item.
			ms_sCurItem	= ITEMINDEX(ms_pmenu->menuautoitems.sDefaultItem);
			}

		// If there is a menu indicator . . .
		if (ms_pmenu->menuindicator.pszFile != NULL)
			{
			// Here we would like to load the resource right into ms_guiIndicator.m_im, but
			// cannot since that is already instantiated and the res manager takes care of
			// instantiation of resources.  So, we must get the resource, allocate
			// ms_guiIndicator.m_im such that the indicator image will fit, and then blit
			// it in from the resource.
			RImage*	pimIndicator;
			if (rspGetResource(
				ms_presmgr,
				ms_pmenu->menuindicator.pszFile, 
				&pimIndicator) == 0)
				{
				// Successfully loaded image.

				// Allocate indicator such that it can contain the image . . .
				if (ms_guiIndicator.m_im.CreateImage(
					pimIndicator->m_sWidth,
					pimIndicator->m_sHeight,
					RImage::BMP8
					) == 0)
					{
					// Blt into indicator.
					rspBlit(
						pimIndicator,
						&(ms_guiIndicator.m_im),
						0, 0,
						0, 0,
						pimIndicator->m_sWidth,
						pimIndicator->m_sHeight);

					// Make indicator a child of ms_msgbox.
					ms_guiIndicator.SetParent(&ms_msgbox);
					
					if (ms_pmenu->menuindicator.type != RImage::NOT_SUPPORTED)
						{
						if (ms_guiIndicator.m_im.Convert(ms_pmenu->menuindicator.type) == ms_pmenu->menuindicator.type)
							{
							// Successfully converted image.
							}
						else
							{
							TRACE("StartMenu(): Unable to convert indicator to desired image type (%s).\n",
								RImage::ms_astrTypeNames[ms_pmenu->menuindicator.type]);
							sRes	= -8;

							goto Done;
							}

						// Set indicator position.
						ms_guiIndicator.m_sX	= ms_sItemsX - ms_guiIndicator.m_im.m_sWidth
														- ms_pmenu->menupos.sIndicatorSpacingX;

						// This will set ms_guiIndicator.m_sY and will skip to the first enabled entry
						// which starting with ms_sCurItem.
						NextItem(0, 0);
						}
					}
				else
					{
					TRACE("StartMenu(): Unable to load \"%s\".\n", ms_pmenu->menuindicator.pszFile);
					sRes	= -7;

					goto Done;
					}

				// Release right away.
				rspReleaseResource(ms_presmgr, &pimIndicator);
				}
			else
				{
				TRACE("StartMenu(): Unabled to load \"%s\".\n", ms_pmenu->menuindicator.pszFile);
				sRes	= -7;

				goto Done;
				}
			}
		else
			{
			// Make indicator top level (so it won't show up when ms_msgbox is drawn).
			// Note we could make the indicator not visible, instead.
			ms_guiIndicator.SetParent(NULL);
			}

		// If successful . . .
		if (sRes == 0 && ms_pimComposite)
			{
			// Destory any existing contents.
			ms_imPreMenu.DestroyData();

			// Create a space to store the current composite buffer's contents . . .
			if (ms_imPreMenu.CreateImage(
				ms_msgbox.m_im.m_sWidth,
				ms_msgbox.m_im.m_sHeight,
				RImage::BMP8) == 0)
				{
				// We must lock the composite buffer before reading from it.
				rspLockBuffer();

				// Store screen contents.
				rspBlit(
					ms_pimComposite,				// Src.
					&ms_imPreMenu,					// Dst.
					ms_msgbox.m_sX,				// Src x.
					ms_msgbox.m_sY,				// Src y.
					0,									// Dst x.
					0,									// Dst y.
					ms_msgbox.m_im.m_sWidth,	// Both.
					ms_msgbox.m_im.m_sHeight,	// Both.
					NULL,								// Dst clip.
					NULL);							// Src clip.

				// Unlock, we're done.
				rspUnlockBuffer();
				}
			else
				{
				TRACE("StartMenu(): ms_imPreMenu.CreateImage() failed.\n");
				sRes	= -20;

				goto Done;
				}
			}

Done:
		// If any errors occurred . . .
		if (sRes != 0)
			{
			StopMenu();

			TRACE("StartMenu(): Menu not started due to start up error.\n");
			}
		else
			{
			// Show and activate msg box and children.
			ms_msgbox.SetVisible(TRUE);
			}
		}

	return sRes;
	}

//////////////////////////////////////////////////////////////////////////////
//
// Performs one iteration of menu input logic.  Call this repeatedly in
// conjunction with DoMenuOutput() to run the menu.
//
//////////////////////////////////////////////////////////////////////////////
extern void DoMenuInput(		// Returns nothing.
	RInputEvent* pie,				// In:  Most recent user input event.
	int16_t UseJoystick)
										// Out: pie->sUsed = TRUE, if used.
	{
	// If there is a current menu . . .
	if (ms_pmenu != NULL)
		{
		int16_t	sChooseCurrent	= FALSE;

		// If there is an unused key event . . .
		if (pie->sUsed == FALSE && pie->type == RInputEvent::Key)
			{
			switch (pie->lKey)
				{
				case RSP_GK_DOWN:
				case '\t':
				case RSP_GKF_CONTROL | '\t':
					if (NextItem(1, 0) == true)
						{
						// Absorb key.
						pie->sUsed	= TRUE;
						}

					break;

				case RSP_GK_UP:
				case RSP_GKF_SHIFT | '\t':
				case RSP_GKF_SHIFT | RSP_GKF_CONTROL | '\t':
					if (NextItem(-1, 0) == true)
						{
						// Absorb key.
						pie->sUsed	= TRUE;
						}

					break;

				case RSP_GK_LEFT:
					if (NextItem(0, -1) == true)
						{
						// Absorb key.
						pie->sUsed	= TRUE;
						}

					break;

				case RSP_GK_RIGHT:
					if (NextItem(0, 1) == true)
						{
						// Absorb key.
						pie->sUsed	= TRUE;
						}

					break;

				case 27:
					ms_sCancel	= TRUE;

					// Absorb key.
					pie->sUsed	= TRUE;

					break;

				case '\r':
					sChooseCurrent	= TRUE;

					// Absorb key.
					pie->sUsed	= TRUE;

					break;
				}
			}

		if (UseJoystick)
		{
			XInputState xis = {};	// Current XInput state			
			GetXInputState(&xis);

			// Menu up.
			if (xis.ButtonState[XINPUT_BUTTON_MENUUP] == XInputState::Press
				|| xis.AxisState[XINPUT_AXIS_MENUUP] == XInputState::Press)
				NextItem(-1, 0);

			// Menu down.
			if (xis.ButtonState[XINPUT_BUTTON_MENUDOWN] == XInputState::Press
				|| xis.AxisState[XINPUT_AXIS_MENUDOWN] == XInputState::Press)
				NextItem(1, 0);

			// Menu left.
			if (xis.ButtonState[XINPUT_BUTTON_MENULEFT] == XInputState::Press
				|| xis.AxisState[XINPUT_AXIS_MENULEFT] == XInputState::Press)
			{
				NextItem(0, -1);
				//!! HACK
				pie->lKey = RSP_GK_LEFT;
				pie->sUsed = 0;
				pie->type = RInputEvent::Key;
			}

			// Menu right.
			if (xis.ButtonState[XINPUT_BUTTON_MENURIGHT] == XInputState::Press
				|| xis.AxisState[XINPUT_AXIS_MENURIGHT] == XInputState::Press)
			{
				NextItem(0, 1);
				//!! HACK
				pie->lKey = RSP_GK_RIGHT;
				pie->sUsed = 0;
				pie->type = RInputEvent::Key;
			}

			// Cancel/goback.
			if (xis.ButtonState[XINPUT_BUTTON_BACK] == XInputState::Press
				|| xis.ButtonState[XINPUT_BUTTON_BACK2] == XInputState::Press)
				ms_sCancel = TRUE;

			// Confirm.
			if (xis.ButtonState[XINPUT_BUTTON_CONFIRM] == XInputState::Press
				|| xis.ButtonState[XINPUT_BUTTON_START] == XInputState::Press)
				sChooseCurrent = TRUE;
		}

#ifdef MOBILE
		if (pie->sUsed == FALSE && pie->type == RInputEvent::Mouse)
		{
			//TRACE("Mouse menu %d  %d  %d", pie->sPosX , pie->sPosY, pie->sButtons);
			//TRACE("Mouse menu POS %d  %d  %d", ms_pmenu->menupos.sX,ms_pmenu->menupos.sY   ,ms_pmenu->menupos.sItemY);
			//TRACE("Mousemsgbox %d  %d",ms_msgbox.m_sX,ms_msgbox.m_sY);

			if ( pie->sButtons)
			{
				//Don't forget to subtract the messagebox position
				if (MouseChooseItem(pie->sPosX - ms_msgbox.m_sX, pie->sPosY -  ms_msgbox.m_sY))
				{
					sChooseCurrent	= TRUE;
					pie->sUsed	= TRUE;
				}
			}
			//pie->sUsed	= TRUE;
		}
#endif

		// If cancelled . . .
		if (ms_sCancel != FALSE)
			{
			int16_t	sCancelItem	= ITEMINDEX(ms_pmenu->menuautoitems.sCancelItem);
			// If enabled . . .
//			if (ms_pmenu->ami[sCancelItem].sEnabled != FALSE)
				{
				// Choose cancel item.
				ms_sCurItem		= sCancelItem;
				sChooseCurrent	= TRUE;
				}

			// Reset cancel flag.
			ms_sCancel		= FALSE;
			}

		if (sChooseCurrent != FALSE)
			{
			bool	bAcceptChoice	= true;

			// If there is a callback . . .
			if (ms_pmenu->menucallbacks.fnChoice != NULL)
				{
				// See if callback will allow this choice . . .
				bAcceptChoice = (*(ms_pmenu->menucallbacks.fnChoice))(ms_pmenu, ms_sCurItem);
				}

			// If choice is still okay . . .
			if (bAcceptChoice == true)
				{
				// If there is still a current menu . . .
				if (ms_pmenu != NULL)
					{
					Menu*	pmenuNext	= ms_pmenu->ami[ms_sCurItem].pmenu;
					// If this is the cancel item . . .
					if (ms_sCurItem == ITEMINDEX(ms_pmenu->menuautoitems.sCancelItem))
						{
						// If there is a go back menu . . .
						if (ms_pmenu->menuautoitems.pmenuBack != NULL)
							{
							// Start menu to go back to.
							pmenuNext			= ms_pmenu->menuautoitems.pmenuBack;
							// Should we clear the go back menu item (pmenuBack)
							// here? ***
							// Set selection for new menu.
							ms_sNextMenuItem	= ms_pmenu->menuautoitems.sBackItem;
							}
						}
					else
						{
						if (pmenuNext != NULL)
							{
							// Store menu to go back to.
							pmenuNext->menuautoitems.pmenuBack	= ms_pmenu;
							// Store item to go back to.
							pmenuNext->menuautoitems.sBackItem	= ms_sCurItem;
							}
						}

					// If there is a menu . . .
					if (pmenuNext != NULL)
						{
						// Start the new menu.
						StartMenu(pmenuNext, ms_presmgr, ms_pimComposite);
						}
					}
				}
			}

		// Make sure menu is still active (even after callbacks) . . .
		if (ms_pmenu != NULL)
			{
			// Call msgbox.
			ms_msgbox.DoModeless(pie);
			}
		}
	}

//////////////////////////////////////////////////////////////////////////////
//
// Performs one iteration of menu output logic.  Call this repeatedly in
// conjunction with DoMenuInput() to run the menu.
//
//////////////////////////////////////////////////////////////////////////////
extern void DoMenuOutput(	// Returns nothing.
	RImage*	pimDst)			// In:  Destination image for menu BLiTs.
	{
	// If there is a current menu . . .
	if (ms_pmenu != NULL)
		{
		// Draw msgbox.
		ms_msgbox.Draw(pimDst);
		}
	}

//////////////////////////////////////////////////////////////////////////////
//
// Get the current menu.
//
//////////////////////////////////////////////////////////////////////////////
extern Menu* GetCurrentMenu(void)	// Returns the a pointer to the current
												// menu or NULL if there is none.
	{
	return ms_pmenu;
	}

//////////////////////////////////////////////////////////////////////////////
//
// Call this to release all memory being used by the menu system
// It should be safe to call this function even if StartMenu()  
// failed, and it should be safe to call it multiple times -- in
// both cases, it shouldn't do anything.                        
//
//////////////////////////////////////////////////////////////////////////////
extern int16_t StopMenu(void)		// Returns 0 on success.
	{
	int16_t	sRes	= 0;	// Assume success.

	// If we have a current menu . . .
	if (ms_pmenu != NULL)
		{
		// Clean up.

		// Destroy any dynamic dlg data.
		ms_msgbox.Destroy();

		// Remove all child items created via ms_msgbox.
		ms_msgbox.RemoveAll();

		// Hide and deactivate msg box and remaining children.
		ms_msgbox.SetVisible(FALSE);

		int16_t	sIndex;
		for (sIndex	= 0; sIndex < ms_sNumMenuItems; sIndex++)
			{
			// If there is a gui . . .
			if (ms_pmenu->ami[sIndex].pgui != NULL)
				{
				ms_pmenu->ami[sIndex].pgui->SetParent(NULL);
				}
			}

		// Destroy background data.
		if (ms_pimBackground != NULL)
			{
			ms_presmgr->Release(ms_pimBackground);
			ms_pimBackground	= NULL;
			}

		// Destroy indicator data.
		ms_guiIndicator.Destroy();

		// Delete fonts.
		if (ms_pfontItems != NULL)
			{
			ms_presmgr->Release(ms_pfontItems);
			ms_pfontItems	= NULL;
			}

		if (ms_pfontHeader != NULL)
			{
			ms_presmgr->Release(ms_pfontHeader);
			ms_pfontHeader	= NULL;
			}

		ms_sNumMenuItems	= 0;

		// If there's a callback . . .
		if (ms_pmenu->menucallbacks.fnInit != NULL)
			{
			// Let the callback know we're cleaning up.
			(*ms_pmenu->menucallbacks.fnInit)(ms_pmenu, FALSE);
			}

		// Clear current menu pointer.
		ms_pmenu	= NULL;

		if (ms_pimComposite != NULL)
			{
			// We must lock the composite buffer before writing to it.
			rspLockBuffer();

			// Restore screen contents.
			rspBlit(
				&ms_imPreMenu,					// Src.
				ms_pimComposite,				// Dst.
				0,									// Src x.
				0,									// Src y.
				ms_msgbox.m_sX,				// Dst x.
				ms_msgbox.m_sY,				// Dst y.
				ms_msgbox.m_im.m_sWidth,	// Both.
				ms_msgbox.m_im.m_sHeight,	// Both.
				NULL,								// Dst clip.
				NULL);							// Src clip.


			// Unlock, we're done.
			rspUnlockBuffer();

			// Destory contents.
			ms_imPreMenu.DestroyData();

			// Clear pointer.
			ms_pimComposite	= NULL;
			}

            rspKeyRepeat(false);
		}

	return sRes;
	}

//////////////////////////////////////////////////////////////////////////////
//
// Get the Menu's background image.
//
//////////////////////////////////////////////////////////////////////////////
extern RImage* GetCurrentMenuBackground(void)	// Returns a pointer to the
																// current background image
																// or NULL, if none.
	{
	return ms_pimBackground;
	}

//////////////////////////////////////////////////////////////////////////////
//
// Get the Menu's GUI.
//
//////////////////////////////////////////////////////////////////////////////
extern RGuiItem* GetCurrentMenuBox(void)	// Returns a pointer to the current
														// menu GUI or NULL, if none.
	{
	return (RGuiItem*)&ms_msgbox;
	}

///////////////////////////////////////////////////////////////////////////////
// EOF
///////////////////////////////////////////////////////////////////////////////
