2012-09-19 15:15:01 +00:00
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
# include "guiGameListOptionsCtrl.h"
# include "gfx/gfxDrawUtil.h"
# include "console/consoleTypes.h"
# include "console/engineAPI.h"
# include "core/strings/stringUnit.h"
//-----------------------------------------------------------------------------
// GuiGameListOptionsCtrl
//-----------------------------------------------------------------------------
GuiGameListOptionsCtrl : : GuiGameListOptionsCtrl ( )
{
}
GuiGameListOptionsCtrl : : ~ GuiGameListOptionsCtrl ( )
{
}
bool GuiGameListOptionsCtrl : : onAdd ( )
{
if ( ! Parent : : onAdd ( ) )
return false ;
if ( ! hasValidProfile ( ) )
{
GuiGameListOptionsProfile * profile ;
if ( ! Sim : : findObject ( " DefaultOptionsMenuProfile " , profile ) )
{
Con : : errorf ( " GuiGameListOptionsCtrl: %s can't be created with a profile of type %s. Please create it with a profile of type GuiGameListOptionsProfile. " ,
getName ( ) , mProfile - > getClassName ( ) ) ;
return false ;
}
else
Con : : warnf ( " GuiGameListOptionsCtrl: substituted non-GuiGameListOptionsProfile in %s for DefaultOptionsMenuProfile " , getName ( ) ) ;
setControlProfile ( profile ) ;
}
return true ;
}
void GuiGameListOptionsCtrl : : onRender ( Point2I offset , const RectI & updateRect )
{
Parent : : onRender ( offset , updateRect ) ;
GuiGameListOptionsProfile * profile = ( GuiGameListOptionsProfile * ) mProfile ;
F32 xScale = ( float ) getWidth ( ) / profile - > getRowWidth ( ) ;
S32 rowHeight = profile - > getRowHeight ( ) ;
bool profileHasArrows = profile - > hasArrows ( ) ;
Point2I arrowExtent ;
S32 arrowOffsetY ( 0 ) ;
if ( profileHasArrows )
{
arrowExtent = profile - > getArrowExtent ( ) ;
// icon is centered vertically
arrowOffsetY = ( rowHeight - arrowExtent . y ) > > 1 ;
}
GFXDrawUtil * drawer = GFX - > getDrawUtil ( ) ;
Point2I currentOffset = offset ;
Point2I arrowOffset ;
S32 columnSplit = profile - > mColumnSplit * xScale ;
S32 iconIndex ;
for ( Vector < Parent : : Row * > : : iterator row = mRows . begin ( ) ; row < mRows . end ( ) ; + + row )
{
Row * myRow = ( Row * ) * row ;
if ( row ! = mRows . begin ( ) )
{
// rows other than the first can have padding above them
currentOffset . y + = myRow - > mHeightPad ;
currentOffset . y + = rowHeight ;
}
bool hasOptions = ( myRow - > mOptions . size ( ) > 0 ) & & myRow - > mSelectedOption > - 1 ;
if ( hasOptions )
{
bool isRowSelected = ( getSelected ( ) ! = NO_ROW ) & & ( row = = & mRows [ getSelected ( ) ] ) ;
bool isRowHighlighted = ( getHighlighted ( ) ! = NO_ROW ) ? ( ( row = = & mRows [ getHighlighted ( ) ] ) & & ( ( * row ) - > mEnabled ) ) : false ;
if ( profileHasArrows )
{
// render the left arrow
bool arrowOnL = ( isRowSelected | | isRowHighlighted ) & & ( myRow - > mWrapOptions | | ( myRow - > mSelectedOption > 0 ) ) ;
iconIndex = ( arrowOnL ) ? Profile : : TEX_L_ARROW_ON : Profile : : TEX_L_ARROW_OFF ;
arrowOffset . x = currentOffset . x + columnSplit ;
arrowOffset . y = currentOffset . y + arrowOffsetY ;
drawer - > clearBitmapModulation ( ) ;
2021-07-19 06:07:08 +00:00
drawer - > drawBitmapStretchSR ( profile - > getBitmapResource ( ) , RectI ( arrowOffset , arrowExtent ) , profile - > getBitmapArrayRect ( ( U32 ) iconIndex ) ) ;
2012-09-19 15:15:01 +00:00
// render the right arrow
bool arrowOnR = ( isRowSelected | | isRowHighlighted ) & & ( myRow - > mWrapOptions | | ( myRow - > mSelectedOption < myRow - > mOptions . size ( ) - 1 ) ) ;
iconIndex = ( arrowOnR ) ? Profile : : TEX_R_ARROW_ON : Profile : : TEX_R_ARROW_OFF ;
arrowOffset . x = currentOffset . x + ( profile - > mHitAreaLowerRight . x - profile - > mRightPad ) * xScale - arrowExtent . x ;
arrowOffset . y = currentOffset . y + arrowOffsetY ;
drawer - > clearBitmapModulation ( ) ;
2021-07-19 06:07:08 +00:00
drawer - > drawBitmapStretchSR ( profile - > getBitmapResource ( ) , RectI ( arrowOffset , arrowExtent ) , profile - > getBitmapArrayRect ( ( U32 ) iconIndex ) ) ;
2012-09-19 15:15:01 +00:00
}
// get the appropriate font color
ColorI fontColor ;
if ( ! myRow - > mEnabled )
{
fontColor = profile - > mFontColorNA ;
}
else if ( isRowSelected )
{
fontColor = profile - > mFontColorSEL ;
}
else if ( isRowHighlighted )
{
fontColor = profile - > mFontColorHL ;
}
else
{
fontColor = profile - > mFontColor ;
}
// calculate text to be at the center between the arrows
GFont * font = profile - > mFont ;
StringTableEntry text = myRow - > mOptions [ myRow - > mSelectedOption ] ;
S32 textWidth = font - > getStrWidth ( text ) ;
S32 columnWidth = profile - > mHitAreaLowerRight . x * xScale - profile - > mRightPad - columnSplit ;
S32 columnCenter = columnSplit + ( columnWidth > > 1 ) ;
S32 textStartX = columnCenter - ( textWidth > > 1 ) ;
Point2I textOffset ( textStartX , 0 ) ;
// render the option text itself
Point2I textExtent ( columnWidth , rowHeight ) ;
drawer - > setBitmapModulation ( fontColor ) ;
renderJustifiedText ( currentOffset + textOffset , textExtent , text ) ;
}
}
}
void GuiGameListOptionsCtrl : : onDebugRender ( Point2I offset )
{
Parent : : onDebugRender ( offset ) ;
GuiGameListOptionsProfile * profile = ( GuiGameListOptionsProfile * ) mProfile ;
F32 xScale = ( float ) getWidth ( ) / profile - > getRowWidth ( ) ;
ColorI column1Color ( 255 , 255 , 0 ) ; // yellow
ColorI column2Color ( 0 , 255 , 0 ) ; // green
Point2I shrinker ( - 1 , - 1 ) ;
S32 rowHeight = profile - > getRowHeight ( ) ;
Point2I currentOffset ( offset ) ;
Point2I rowExtent ( getExtent ( ) . x , rowHeight ) ;
Point2I hitAreaExtent ( profile - > getHitAreaExtent ( ) ) ;
hitAreaExtent . x * = xScale ;
hitAreaExtent + = shrinker ;
Point2I column1Extent ( ( profile - > mColumnSplit - profile - > mHitAreaUpperLeft . x ) * xScale - 1 , hitAreaExtent . y ) ;
Point2I column2Extent ( hitAreaExtent . x - column1Extent . x - 1 , hitAreaExtent . y ) ;
Point2I hitAreaOffset = profile - > mHitAreaUpperLeft ;
hitAreaOffset . x * = xScale ;
RectI borderRect ;
Point2I upperLeft ;
for ( Vector < Parent : : Row * > : : iterator row = mRows . begin ( ) ; row < mRows . end ( ) ; + + row )
{
// set the top of the current row
if ( row ! = mRows . begin ( ) )
{
// rows other than the first can have padding above them
currentOffset . y + = ( * row ) - > mHeightPad ;
currentOffset . y + = rowHeight ;
}
// draw the box around column 1
upperLeft = currentOffset + hitAreaOffset ;
borderRect . point = upperLeft ;
borderRect . extent = column1Extent ;
GFX - > getDrawUtil ( ) - > drawRect ( borderRect , column1Color ) ;
// draw the box around column 2
upperLeft . x + = column1Extent . x + 1 ;
borderRect . point = upperLeft ;
borderRect . extent = column2Extent ;
GFX - > getDrawUtil ( ) - > drawRect ( borderRect , column2Color ) ;
}
}
void GuiGameListOptionsCtrl : : addRow ( const char * label , const char * optionsList , bool wrapOptions , const char * callback , S32 icon , S32 yPad , bool enabled )
{
static StringTableEntry DELIM = StringTable - > insert ( " \t " , true ) ;
Row * row = new Row ( ) ;
Vector < StringTableEntry > options ( __FILE__ , __LINE__ ) ;
S32 count = StringUnit : : getUnitCount ( optionsList , DELIM ) ;
2013-08-04 21:26:01 +00:00
for ( S32 i = 0 ; i < count ; + + i )
2012-09-19 15:15:01 +00:00
{
const char * option = StringUnit : : getUnit ( optionsList , i , DELIM ) ;
options . push_back ( StringTable - > insert ( option , true ) ) ;
}
row - > mOptions = options ;
bool hasOptions = row - > mOptions . size ( ) > 0 ;
row - > mSelectedOption = ( hasOptions ) ? 0 : NO_OPTION ;
row - > mWrapOptions = wrapOptions ;
Parent : : addRow ( row , label , callback , icon , yPad , true , ( hasOptions ) ? enabled : false ) ;
}
void GuiGameListOptionsCtrl : : setOptions ( S32 rowIndex , const char * optionsList )
{
static StringTableEntry DELIM = StringTable - > insert ( " \t " , true ) ;
if ( ! isValidRowIndex ( rowIndex ) )
{
return ;
}
Row * row = ( Row * ) mRows [ rowIndex ] ;
S32 count = StringUnit : : getUnitCount ( optionsList , DELIM ) ;
row - > mOptions . setSize ( count ) ;
2013-08-04 21:26:01 +00:00
for ( S32 i = 0 ; i < count ; + + i )
2012-09-19 15:15:01 +00:00
{
const char * option = StringUnit : : getUnit ( optionsList , i , DELIM ) ;
row - > mOptions [ i ] = StringTable - > insert ( option , true ) ;
}
if ( row - > mSelectedOption > = row - > mOptions . size ( ) )
{
row - > mSelectedOption = row - > mOptions . size ( ) - 1 ;
}
}
bool GuiGameListOptionsCtrl : : hasValidProfile ( ) const
{
GuiGameListOptionsProfile * profile = dynamic_cast < GuiGameListOptionsProfile * > ( mProfile ) ;
return profile ;
}
void GuiGameListOptionsCtrl : : enforceConstraints ( )
{
Parent : : enforceConstraints ( ) ;
}
void GuiGameListOptionsCtrl : : onMouseUp ( const GuiEvent & event )
{
S32 hitRow = getRow ( event . mousePoint ) ;
if ( ( hitRow ! = NO_ROW ) & & isRowEnabled ( hitRow ) & & ( hitRow = = getSelected ( ) ) )
{
S32 xPos = globalToLocalCoord ( event . mousePoint ) . x ;
clickOption ( ( Row * ) mRows [ getSelected ( ) ] , xPos ) ;
}
}
void GuiGameListOptionsCtrl : : clickOption ( Row * row , S32 xPos )
{
GuiGameListOptionsProfile * profile = ( GuiGameListOptionsProfile * ) mProfile ;
if ( ! profile - > hasArrows ( ) )
{
return ;
}
F32 xScale = ( float ) getWidth ( ) / profile - > getRowWidth ( ) ;
S32 bitmapArrowWidth = mProfile - > getBitmapArrayRect ( Profile : : TEX_FIRST_ARROW ) . extent . x ;
S32 leftArrowX1 = profile - > mColumnSplit * xScale ;
S32 leftArrowX2 = leftArrowX1 + bitmapArrowWidth ;
S32 rightArrowX2 = ( profile - > mHitAreaLowerRight . x - profile - > mRightPad ) * xScale ;
S32 rightArrowX1 = rightArrowX2 - bitmapArrowWidth ;
if ( ( leftArrowX1 < = xPos ) & & ( xPos < = leftArrowX2 ) )
{
changeOption ( row , - 1 ) ;
}
else if ( ( rightArrowX1 < = xPos ) & & ( xPos < = rightArrowX2 ) )
{
changeOption ( row , 1 ) ;
}
}
bool GuiGameListOptionsCtrl : : onKeyDown ( const GuiEvent & event )
{
switch ( event . keyCode )
{
case KEY_LEFT :
changeOption ( - 1 ) ;
return true ;
case KEY_RIGHT :
changeOption ( 1 ) ;
return true ;
default :
break ;
}
return Parent : : onKeyDown ( event ) ;
}
bool GuiGameListOptionsCtrl : : onGamepadAxisLeft ( const GuiEvent & event )
{
changeOption ( - 1 ) ;
return true ;
}
bool GuiGameListOptionsCtrl : : onGamepadAxisRight ( const GuiEvent & event )
{
changeOption ( 1 ) ;
return true ;
}
void GuiGameListOptionsCtrl : : changeOption ( S32 delta )
{
if ( getSelected ( ) ! = NO_ROW )
{
Row * row = ( Row * ) mRows [ getSelected ( ) ] ;
changeOption ( row , delta ) ;
}
}
void GuiGameListOptionsCtrl : : changeOption ( Row * row , S32 delta )
{
S32 optionCount = row - > mOptions . size ( ) ;
S32 newSelection = row - > mSelectedOption + delta ;
if ( optionCount = = 0 )
{
newSelection = NO_OPTION ;
}
else if ( ! row - > mWrapOptions )
{
newSelection = mClamp ( newSelection , 0 , optionCount - 1 ) ;
}
else if ( newSelection < 0 )
{
newSelection = optionCount - 1 ;
}
else if ( newSelection > = optionCount )
{
newSelection = 0 ;
}
row - > mSelectedOption = newSelection ;
static StringTableEntry LEFT = StringTable - > insert ( " LEFT " , true ) ;
static StringTableEntry RIGHT = StringTable - > insert ( " RIGHT " , true ) ;
if ( row - > mScriptCallback ! = NULL )
{
setThisControl ( ) ;
StringTableEntry direction = NULL ;
if ( delta < 0 )
{
direction = LEFT ;
}
else if ( delta > 0 )
{
direction = RIGHT ;
}
if ( ( direction ! = NULL ) & & ( Con : : isFunction ( row - > mScriptCallback ) ) )
{
Con : : executef ( row - > mScriptCallback , direction ) ;
}
}
}
StringTableEntry GuiGameListOptionsCtrl : : getCurrentOption ( S32 rowIndex ) const
{
if ( isValidRowIndex ( rowIndex ) )
{
Row * row = ( Row * ) mRows [ rowIndex ] ;
if ( row - > mSelectedOption ! = NO_OPTION )
{
return row - > mOptions [ row - > mSelectedOption ] ;
}
}
return StringTable - > insert ( " " , false ) ;
}
bool GuiGameListOptionsCtrl : : selectOption ( S32 rowIndex , const char * theOption )
{
if ( ! isValidRowIndex ( rowIndex ) )
{
return false ;
}
Row * row = ( Row * ) mRows [ rowIndex ] ;
for ( Vector < StringTableEntry > : : iterator anOption = row - > mOptions . begin ( ) ; anOption < row - > mOptions . end ( ) ; + + anOption )
{
2020-10-03 12:37:55 +00:00
if ( String : : compare ( * anOption , theOption ) = = 0 )
2012-09-19 15:15:01 +00:00
{
S32 newIndex = anOption - row - > mOptions . begin ( ) ;
row - > mSelectedOption = newIndex ;
return true ;
}
}
return false ;
}
2020-05-20 22:19:52 +00:00
void GuiGameListOptionsCtrl : : clearRows ( )
{
mRows . clear ( ) ;
}
2012-09-19 15:15:01 +00:00
//-----------------------------------------------------------------------------
// Console stuff (GuiGameListOptionsCtrl)
//-----------------------------------------------------------------------------
IMPLEMENT_CONOBJECT ( GuiGameListOptionsCtrl ) ;
ConsoleDocClass ( GuiGameListOptionsCtrl ,
" @brief A control for showing pages of options that are gamepad friendly. \n \n "
" Each row in this control allows the selection of one value from a set of "
" options using the keyboard, gamepad or mouse. The row is rendered as 2 "
" columns: the first column contains the row label, the second column "
" contains left and right arrows (for mouse picking) and the currently "
" selected value. \n \n "
" @see GuiGameListOptionsProfile \n \n "
" @ingroup GuiGame "
) ;
void GuiGameListOptionsCtrl : : initPersistFields ( )
{
2023-01-27 07:13:15 +00:00
docsURL ;
2012-09-19 15:15:01 +00:00
Parent : : initPersistFields ( ) ;
}
DefineEngineMethod ( GuiGameListOptionsCtrl , addRow , void ,
( const char * label , const char * options , bool wrapOptions , const char * callback , S32 icon , S32 yPad , bool enabled ) ,
( - 1 , 0 , true ) ,
" Add a row to the list control. \n \n "
" @param label The text to display on the row as a label. \n "
" @param options A tab separated list of options. \n "
" @param wrapOptions Specify true to allow options to wrap at each end or false to prevent wrapping. \n "
" @param callback Name of a script function to use as a callback when this row is activated. \n "
" @param icon [optional] Index of the icon to use as a marker. \n "
" @param yPad [optional] An extra amount of height padding before the row. Does nothing on the first row. \n "
" @param enabled [optional] If this row is initially enabled. " )
{
object - > addRow ( label , options , wrapOptions , callback , icon , yPad , enabled ) ;
}
DefineEngineMethod ( GuiGameListOptionsCtrl , getCurrentOption , const char * , ( S32 row ) , ,
" Gets the text for the currently selected option of the given row. \n \n "
" @param row Index of the row to get the option from. \n "
" @return A string representing the text currently displayed as the selected option on the given row. If there is no such displayed text then the empty string is returned. " )
{
return object - > getCurrentOption ( row ) ;
}
DefineEngineMethod ( GuiGameListOptionsCtrl , selectOption , bool , ( S32 row , const char * option ) , ,
" Set the row's current option to the one specified \n \n "
" @param row Index of the row to set an option on. \n "
" @param option The option to be made active. \n "
" @return True if the row contained the option and was set, false otherwise. " )
{
return object - > selectOption ( row , option ) ;
}
DefineEngineMethod ( GuiGameListOptionsCtrl , setOptions , void , ( S32 row , const char * optionsList ) , ,
" Sets the list of options on the given row. \n \n "
" @param row Index of the row to set options on. "
" @param optionsList A tab separated list of options for the control. " )
{
object - > setOptions ( row , optionsList ) ;
}
2020-05-20 22:19:52 +00:00
DefineEngineMethod ( GuiGameListOptionsCtrl , clearOptions , void , ( ) , ,
" Sets the list of options on the given row. \n \n "
" @param row Index of the row to set options on. "
" @param optionsList A tab separated list of options for the control. " )
{
object - > clearRows ( ) ;
}
2012-09-19 15:15:01 +00:00
//-----------------------------------------------------------------------------
// GuiGameListOptionsProfile
//-----------------------------------------------------------------------------
GuiGameListOptionsProfile : : GuiGameListOptionsProfile ( )
: mColumnSplit ( 0 ) ,
mRightPad ( 0 )
{
}
void GuiGameListOptionsProfile : : enforceConstraints ( )
{
Parent : : enforceConstraints ( ) ;
if ( mHitAreaUpperLeft . x > mColumnSplit | | mColumnSplit > mHitAreaLowerRight . x )
Con : : errorf ( " GuiGameListOptionsProfile: You can't create %s with a ColumnSplit outside the hit area. You set the split to %d. Please change the ColumnSplit to be in the range [%d, %d]. " ,
getName ( ) , mColumnSplit , mHitAreaUpperLeft . x , mHitAreaLowerRight . x ) ;
mColumnSplit = mClamp ( mColumnSplit , mHitAreaUpperLeft . x , mHitAreaLowerRight . x ) ;
}
//-----------------------------------------------------------------------------
// Console stuff (GuiGameListOptionsProfile)
//-----------------------------------------------------------------------------
IMPLEMENT_CONOBJECT ( GuiGameListOptionsProfile ) ;
ConsoleDocClass ( GuiGameListOptionsProfile ,
" @brief A GuiControlProfile with additional fields specific to GuiGameListOptionsCtrl. \n \n "
" @tsexample \n "
" new GuiGameListOptionsProfile() \n "
" { \n "
" columnSplit = \" 100 \" ; \n "
" rightPad = \" 4 \" ; \n "
" //Properties not specific to this control have been omitted from this example. \n "
" }; \n "
" @endtsexample \n \n "
" @ingroup GuiGame "
) ;
void GuiGameListOptionsProfile : : initPersistFields ( )
{
2023-01-27 07:13:15 +00:00
docsURL ;
2012-09-19 15:15:01 +00:00
addField ( " columnSplit " , TypeS32 , Offset ( mColumnSplit , GuiGameListOptionsProfile ) ,
" Padding between the leftmost edge of the control, and the row's left arrow. " ) ;
addField ( " rightPad " , TypeS32 , Offset ( mRightPad , GuiGameListOptionsProfile ) ,
" Padding between the rightmost edge of the control and the row's right arrow. " ) ;
Parent : : initPersistFields ( ) ;
}