raze-gles/source/common/menu/optionmenuitems.h
Christoph Oelckers 90c5ea6acc - updated option menu code to latest scripted version
This mainly means being able to use the generic font.
This also adds more generalization to the menu sound handling, plus an option to turn menu sound off. This is motivated by the pig sounds which RR uses in the menu.
2019-11-29 01:28:13 +01:00

996 lines
24 KiB
C++

/*
** optionmenuitems.h
** Control items for option menus
**
**---------------------------------------------------------------------------
** Copyright 2010 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include "v_text.h"
#include "v_draw.h"
#include "gstrings.h"
void M_DrawConText (int color, int x, int y, const char *str);
void M_SetVideoMode();
//=============================================================================
//
// opens a submenu, action is a submenu name
//
//=============================================================================
class FOptionMenuItemSubmenu : public FOptionMenuItem
{
int mParam;
public:
FOptionMenuItemSubmenu(const char *label, const char *menu, int param = 0)
: FOptionMenuItem(label, menu)
{
mParam = param;
}
int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) override
{
drawLabel(indent, y, selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColorMore);
return indent;
}
bool Activate(FName caller) override
{
M_MenuSound(CursorSound);
M_SetMenu(mAction, mParam);
return true;
}
};
//=============================================================================
//
// opens a submenu, command is a submenu name
//
//=============================================================================
class FOptionMenuItemLabeledSubmenu : public FOptionMenuItemSubmenu
{
FBaseCVar *mLabelCVar;
FOptionMenuItemLabeledSubmenu(const char * label, FBaseCVar *labelcvar, FName command, int param = 0)
: FOptionMenuItemSubmenu(label, command, param)
{
mLabelCVar = labelcvar;
}
int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) override
{
drawLabel(indent, y, selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColor);
auto text = mLabelCVar->GetHumanString();
if (text[0] == 0) text = GStrings("notset");
drawValue(indent, y, OptionSettings.mFontColorValue, text);
return indent;
}
};
//=============================================================================
//
// Executes a CCMD, action is a CCMD name
//
//=============================================================================
class FOptionMenuItemCommand : public FOptionMenuItemSubmenu
{
public:
FOptionMenuItemCommand(const char *label, const char *menu)
: FOptionMenuItemSubmenu(label, menu)
{
}
bool Activate(FName caller) override
{
M_MenuSound(AdvanceSound);
C_DoCommand(mAction);
return true;
}
};
//=============================================================================
//
// Executes a CCMD after confirmation, action is a CCMD name
//
//=============================================================================
class FOptionMenuItemSafeCommand : public FOptionMenuItemCommand
{
// action is a CCMD
FString mPrompt;
int mScriptId; // shouldn't be used, but just in case.
public:
FOptionMenuItemSafeCommand(const char *label, const char *menu, const char *prompt = nullptr, int scriptid = INT_MAX)
: FOptionMenuItemCommand(label, menu)
{
mPrompt = prompt;
mScriptId = scriptid;
}
bool MenuEvent (int mkey, bool fromcontroller) override
{
if (mkey == MKEY_MBYes)
{
C_DoCommand(mAction);
return true;
}
return FOptionMenuItemCommand::MenuEvent(mkey, fromcontroller);
}
bool Activate(FName caller) override
{
auto msg = mPrompt.IsNotEmpty()? mPrompt.GetChars() : "$SAFEMESSAGE";
if (*msg == '$') msg = GStrings(msg+1);
auto actionLabel = mLabel.GetChars();
if (*actionLabel == '$') actionLabel = GStrings(actionLabel+1);
FStringf FullString("%s%s%s\n\n%s", TEXTCOLOR_WHITE, actionLabel, TEXTCOLOR_NORMAL, msg);
M_StartMessage(FullString, 0, mScriptId);
return true;
}
};
//=============================================================================
//
// Base class for option lists
//
//=============================================================================
class FOptionMenuItemOptionBase : public FOptionMenuItem
{
protected:
// action is a CVAR
FName mValues; // Entry in OptionValues table
FBaseCVar *mGrayCheck;
int mCenter;
public:
enum
{
OP_VALUES = 0x11001
};
FOptionMenuItemOptionBase(const char *label, const char *menu, const char *values, const char *graycheck, int center)
: FOptionMenuItem(label, menu)
{
mValues = values;
mGrayCheck = (FBoolCVar*)FindCVar(graycheck, NULL);
mCenter = center;
}
bool SetString(int i, const char *newtext) override
{
if (i == OP_VALUES)
{
FOptionValues **opt = OptionValues.CheckKey(newtext);
mValues = newtext;
if (opt != NULL && *opt != NULL)
{
int s = GetSelection();
if (s >= (int)(*opt)->mValues.Size()) s = 0;
SetSelection(s); // readjust the CVAR if its value is outside the range now
return true;
}
}
return false;
}
//=============================================================================
virtual int GetSelection() = 0;
virtual void SetSelection(int Selection) = 0;
//=============================================================================
int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) override
{
if (mCenter)
{
indent = (screen->GetWidth() / 2);
}
drawLabel(indent, y, selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColor, isGrayed());
int Selection = GetSelection();
const char *text;
FOptionValues **opt = OptionValues.CheckKey(mValues);
if (Selection < 0 || opt == NULL || *opt == NULL)
{
text = "Unknown";
}
else
{
text = (*opt)->mValues[Selection].Text;
}
if (*text == '$') text = GStrings(text + 1);
drawValue(indent, y, OptionSettings.mFontColorValue, text, isGrayed());
return indent;
}
//=============================================================================
bool MenuEvent (int mkey, bool fromcontroller) override
{
FOptionValues **opt = OptionValues.CheckKey(mValues);
if (opt != NULL && *opt != NULL && (*opt)->mValues.Size() > 0)
{
int Selection = GetSelection();
if (mkey == MKEY_Left)
{
if (Selection == -1) Selection = 0;
else if (--Selection < 0) Selection = (*opt)->mValues.Size()-1;
}
else if (mkey == MKEY_Right || mkey == MKEY_Enter)
{
if (++Selection >= (int)(*opt)->mValues.Size()) Selection = 0;
}
else
{
return FOptionMenuItem::MenuEvent(mkey, fromcontroller);
}
SetSelection(Selection);
M_MenuSound(ChangeSound);
}
return true;
}
virtual bool isGrayed()
{
return mGrayCheck != NULL && !(mGrayCheck->GetGenericRep(CVAR_Bool).Bool);
}
bool Selectable() override
{
return !isGrayed();
}
};
//=============================================================================
//
// Change a CVAR, action is the CVAR name
//
//=============================================================================
class FOptionMenuItemOption : public FOptionMenuItemOptionBase
{
// action is a CVAR
FBaseCVar *mCVar;
public:
FOptionMenuItemOption(const char *label, const char *menu, const char *values, const char *graycheck, int center)
: FOptionMenuItemOptionBase(label, menu, values, graycheck, center)
{
mCVar = FindCVar(mAction, NULL);
}
//=============================================================================
int GetSelection() override
{
int Selection = -1;
FOptionValues **opt = OptionValues.CheckKey(mValues);
if (opt != NULL && *opt != NULL && mCVar != NULL && (*opt)->mValues.Size() > 0)
{
if ((*opt)->mValues[0].TextValue.IsEmpty())
{
UCVarValue cv = mCVar->GetGenericRep(CVAR_Float);
for(unsigned i = 0; i < (*opt)->mValues.Size(); i++)
{
if (fabs(cv.Float - (*opt)->mValues[i].Value) < FLT_EPSILON)
{
Selection = i;
break;
}
}
}
else
{
UCVarValue cv = mCVar->GetGenericRep(CVAR_String);
for(unsigned i = 0; i < (*opt)->mValues.Size(); i++)
{
if ((*opt)->mValues[i].TextValue.CompareNoCase(cv.String) == 0)
{
Selection = i;
break;
}
}
}
}
return Selection;
}
void SetSelection(int Selection) override
{
UCVarValue value;
FOptionValues **opt = OptionValues.CheckKey(mValues);
if (opt != NULL && *opt != NULL && mCVar != NULL && (*opt)->mValues.Size() > 0)
{
if ((*opt)->mValues[0].TextValue.IsEmpty())
{
value.Float = (float)(*opt)->mValues[Selection].Value;
mCVar->SetGenericRep (value, CVAR_Float);
}
else
{
value.String = (*opt)->mValues[Selection].TextValue.LockBuffer();
mCVar->SetGenericRep (value, CVAR_String);
(*opt)->mValues[Selection].TextValue.UnlockBuffer();
}
}
}
};
//=============================================================================
//
// This class is used to capture the key to be used as the new key binding
// for a control item
//
//=============================================================================
class DEnterKey : public DMenu
{
int *pKey;
public:
DEnterKey(DMenu *parent, int *keyptr)
: DMenu(parent)
{
pKey = keyptr;
SetMenuMessage(1);
menuactive = MENU_WaitKey; // There should be a better way to disable GUI capture...
}
bool TranslateKeyboardEvents() override
{
return false;
}
void SetMenuMessage(int which)
{
DOptionMenu *m = static_cast<DOptionMenu*>(mParentMenu);
if (m)
{
FListMenuItem *it = m->GetItem(NAME_Controlmessage);
if (it != NULL)
{
it->SetValue(0, which);
}
}
}
bool Responder(event_t *ev) override
{
if (ev->type == EV_KeyDown)
{
*pKey = ev->data1;
menuactive = MENU_On;
SetMenuMessage(0);
Close();
mParentMenu->MenuEvent((ev->data1 == KEY_ESCAPE)? MKEY_Abort : MKEY_Input, 0);
return true;
}
return false;
}
void Drawer() override
{
mParentMenu->Drawer();
}
};
//=============================================================================
//
// // Edit a key binding, Action is the CCMD to bind
//
//=============================================================================
class FOptionMenuItemControl : public FOptionMenuItem
{
FKeyBindings *mBindings;
int mInput;
bool mWaiting;
public:
FOptionMenuItemControl(const char *label, const char *menu, FKeyBindings *bindings)
: FOptionMenuItem(label, menu)
{
mBindings = bindings;
mWaiting = false;
}
//=============================================================================
int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) override
{
drawLabel(indent, y, mWaiting? OptionSettings.mFontColorHighlight:
(selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColor));
auto binds = mBindings->GetKeysForCommand(mAction);
auto description = C_NameKeys(binds.Data(), binds.Size());
if (description.Len() > 0)
{
drawValue(indent, y, CR_WHITE, description);
}
else
{
drawValue(indent, y, CR_BLACK, "---");
}
return indent;
}
//=============================================================================
bool MenuEvent(int mkey, bool fromcontroller) override
{
if (mkey == MKEY_Input)
{
mWaiting = false;
mBindings->SetBind(mInput, mAction);
return true;
}
else if (mkey == MKEY_Clear)
{
mBindings->UnbindACommand(mAction);
return true;
}
else if (mkey == MKEY_Abort)
{
mWaiting = false;
return true;
}
return false;
}
bool Activate(FName caller) override
{
M_MenuSound(AdvanceSound);
mWaiting = true;
DMenu *input = new DEnterKey(DMenu::CurrentMenu, &mInput);
M_ActivateMenu(input);
return true;
}
};
//=============================================================================
//
//
//
//=============================================================================
class FOptionMenuItemStaticText : public FOptionMenuItem
{
EColorRange mColor;
public:
FOptionMenuItemStaticText(const char *label, EColorRange color = CR_UNDEFINED)
: FOptionMenuItem(label, NAME_None, true)
{
mColor = color == CR_UNDEFINED? (EColorRange)OptionSettings.mFontColor : color;
}
int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) override
{
drawLabel(indent, y, mColor);
return -1;
}
bool Selectable() override
{
return false;
}
};
//=============================================================================
//
//
//
//=============================================================================
class FOptionMenuItemStaticTextSwitchable : public FOptionMenuItem
{
EColorRange mColor;
FString mAltText;
int mCurrent;
public:
FOptionMenuItemStaticTextSwitchable(const char *label, const char *label2, FName action, EColorRange color = CR_UNDEFINED)
: FOptionMenuItem(label, action, true)
{
mColor = color == CR_UNDEFINED? (EColorRange)OptionSettings.mFontColor : color;
mAltText = label2;
mCurrent = 0;
}
int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) override
{
const char *txt = mCurrent? mAltText.GetChars() : mLabel.GetChars();
if (*txt == '$') txt = GStrings(txt + 1);
int w = OptionWidth(txt) * CleanXfac_1;
int x = (screen->GetWidth() - w) / 2;
drawText(x, y, mColor, txt);
return -1;
}
bool SetValue(int i, int val) override
{
if (i == 0)
{
mCurrent = val;
return true;
}
return false;
}
bool SetString(int i, const char *newtext) override
{
if (i == 0)
{
mAltText = newtext;
return true;
}
return false;
}
bool Selectable() override
{
return false;
}
};
//=============================================================================
//
//
//
//=============================================================================
class FOptionMenuSliderBase : public FOptionMenuItem
{
// action is a CVAR
double mMin, mMax, mStep;
int mShowValue;
int mDrawX;
int mSliderShort;
public:
FOptionMenuSliderBase(const char *label, double min, double max, double step, int showval, FName command = NAME_None)
: FOptionMenuItem(label, command)
{
mMin = min;
mMax = max;
mStep = step;
mShowValue = showval;
mDrawX = 0;
mSliderShort = 0;
}
virtual double GetSliderValue() = 0;
virtual void SetSliderValue(double val) = 0;
//=============================================================================
//
// Draw a slider. Set fracdigits negative to not display the current value numerically.
//
//=============================================================================
void DrawSliderElement (int color, int x, int y, const char * str)
{
DrawText (&twod, ConFont, color, x, y, str, DTA_CellX, 16 * CleanXfac_1, DTA_CellY, 16 * CleanYfac_1);
}
void DrawSlider (int x, int y, double min, double max, double cur, int fracdigits, int indent)
{
char textbuf[16];
double range;
int maxlen = 0;
int right = x + (12*8 + 4) * CleanXfac_1;
int cy = y + (OptionSettings.mLinespacing-8)*CleanYfac_1;
range = max - min;
double ccur = clamp(cur, min, max) - min;
if (fracdigits >= 0)
{
snprintf(textbuf, countof(textbuf), "%.*f", fracdigits, max);
maxlen = SmallFont->StringWidth(textbuf) * CleanXfac_1;
}
mSliderShort = right + maxlen > screen->GetWidth();
if (!mSliderShort)
{
DrawSliderElement(CR_WHITE, x, cy, "\x10\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x12");
DrawSliderElement(CR_ORANGE, x + int((5 + ((ccur * 78) / range)) * CleanXfac_1), cy, "\x13");
}
else
{
// On 320x200 we need a shorter slider
DrawSliderElement(CR_WHITE, x, cy, "\x10\x11\x11\x11\x11\x11\x12");
DrawSliderElement(CR_ORANGE, x + int((5 + ((ccur * 38) / range)) * CleanXfac_1), cy, "\x13");
right -= 5*8*CleanXfac_1;
}
if (fracdigits >= 0 && right + maxlen <= screen->GetWidth())
{
snprintf(textbuf, countof(textbuf), "%.*f", fracdigits, cur);
drawText(right, y, CR_DARKGRAY, textbuf);
}
}
//=============================================================================
int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) override
{
drawLabel(indent, y, selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColor);
mDrawX = indent + CursorSpace();
DrawSlider (mDrawX, y, mMin, mMax, GetSliderValue(), mShowValue, indent);
return indent;
}
//=============================================================================
bool MenuEvent (int mkey, bool fromcontroller) override
{
double value = GetSliderValue();
if (mkey == MKEY_Left)
{
value -= mStep;
}
else if (mkey == MKEY_Right)
{
value += mStep;
}
else
{
return FOptionMenuItem::MenuEvent(mkey, fromcontroller);
}
SetSliderValue(clamp(value, mMin, mMax));
M_MenuSound(ChangeSound);
return true;
}
bool MouseEvent(int type, int x, int y) override
{
DOptionMenu *lm = static_cast<DOptionMenu*>(DMenu::CurrentMenu);
if (type != DMenu::MOUSE_Click)
{
if (!lm->CheckFocus(this)) return false;
}
if (type == DMenu::MOUSE_Release)
{
lm->ReleaseFocus();
}
int slide_left = mDrawX+8*CleanXfac_1;
int slide_right = slide_left + (10*8*CleanXfac_1 >> mSliderShort); // 10 char cells with 8 pixels each.
if (type == DMenu::MOUSE_Click)
{
if (x < slide_left || x >= slide_right) return true;
}
x = clamp(x, slide_left, slide_right);
double v = mMin + ((x - slide_left) * (mMax - mMin)) / (slide_right - slide_left);
if (v != GetSliderValue())
{
SetSliderValue(v);
M_MenuSound(ChangeSound);
}
if (type == DMenu::MOUSE_Click)
{
lm->SetFocus(this);
}
return true;
}
};
//=============================================================================
//
//
//
//=============================================================================
class FOptionMenuSliderCVar : public FOptionMenuSliderBase
{
FBaseCVar *mCVar;
public:
FOptionMenuSliderCVar(const char *label, const char *menu, double min, double max, double step, int showval)
: FOptionMenuSliderBase(label, min, max, step, showval)
{
mCVar = FindCVar(menu, NULL);
}
double GetSliderValue() override
{
if (mCVar != NULL)
{
return mCVar->GetGenericRep(CVAR_Float).Float;
}
else
{
return 0;
}
}
void SetSliderValue(double val) override
{
if (mCVar != NULL)
{
UCVarValue value;
value.Float = (float)val;
mCVar->SetGenericRep(value, CVAR_Float);
}
}
};
//=============================================================================
//
//
//
//=============================================================================
class FOptionMenuSliderVar : public FOptionMenuSliderBase
{
float *mPVal;
public:
FOptionMenuSliderVar(const char *label, float *pVal, double min, double max, double step, int showval)
: FOptionMenuSliderBase(label, min, max, step, showval)
{
mPVal = pVal;
}
double GetSliderValue() override
{
return *mPVal;
}
void SetSliderValue(double val) override
{
*mPVal = (float)val;
}
};
//=============================================================================
//
// [TP] FOptionMenuFieldBase
//
// Base class for input fields
//
//=============================================================================
class FOptionMenuFieldBase : public FOptionMenuItem
{
public:
FOptionMenuFieldBase ( const char* label, const char* menu, const char* graycheck ) :
FOptionMenuItem ( label, menu ),
mCVar ( FindCVar( mAction, NULL )),
mGrayCheck (( graycheck && strlen( graycheck )) ? FindCVar( graycheck, NULL ) : NULL ) {}
const char* GetCVarString()
{
if ( mCVar == NULL )
return "";
return mCVar->GetGenericRep( CVAR_String ).String;
}
virtual FString Represent()
{
return GetCVarString();
}
int Draw ( FOptionMenuDescriptor*, int y, int indent, bool selected ) override
{
bool grayed = mGrayCheck != NULL && !( mGrayCheck->GetGenericRep( CVAR_Bool ).Bool );
drawLabel(indent, y, selected ? OptionSettings.mFontColorSelection : OptionSettings.mFontColor, grayed);
drawValue(indent, y, OptionSettings.mFontColorValue, Represent(), grayed);
return indent;
}
bool GetString ( int i, char* s, int len ) override
{
if ( i == 0 )
{
strncpy( s, GetCVarString(), len );
s[len - 1] = '\0';
return true;
}
return false;
}
bool SetString ( int i, const char* s ) override
{
if ( i == 0 )
{
if ( mCVar )
{
UCVarValue vval;
vval.String = s;
mCVar->SetGenericRep( vval, CVAR_String );
}
return true;
}
return false;
}
protected:
// Action is a CVar in this class and derivatives.
FBaseCVar* mCVar;
FBaseCVar* mGrayCheck;
};
//=============================================================================
//
// [TP] FOptionMenuTextField
//
// A text input field widget, for use with string CVars.
//
//=============================================================================
class FOptionMenuTextField : public FOptionMenuFieldBase
{
public:
FOptionMenuTextField ( const char *label, const char* menu, const char* graycheck ) :
FOptionMenuFieldBase ( label, menu, graycheck ),
mEntering ( false ) {}
FString Represent() override
{
FString text = mEntering ? mEditName : FString(GetCVarString());
if ( mEntering )
text += '_';
return text;
}
int Draw(FOptionMenuDescriptor*desc, int y, int indent, bool selected) override
{
if (mEntering)
{
// reposition the text so that the cursor is visible when in entering mode.
FString text = Represent();
int tlen = SmallFont->StringWidth(text) * CleanXfac_1;
int newindent = screen->GetWidth() - tlen - CursorSpace();
if (newindent < indent) indent = newindent;
}
return FOptionMenuFieldBase::Draw(desc, y, indent, selected);
}
bool MenuEvent ( int mkey, bool fromcontroller ) override
{
if ( mkey == MKEY_Enter )
{
M_MenuSound(AdvanceSound);
mEditName = GetCVarString();
mEntering = true;
DMenu* input = new DTextEnterMenu ( DMenu::CurrentMenu, mEditName, sizeof mEditName, 2, fromcontroller );
M_ActivateMenu( input );
return true;
}
else if ( mkey == MKEY_Input )
{
if ( mCVar )
{
UCVarValue vval;
vval.String = mEditName;
mCVar->SetGenericRep( vval, CVAR_String );
}
mEntering = false;
return true;
}
else if ( mkey == MKEY_Abort )
{
mEntering = false;
return true;
}
return FOptionMenuItem::MenuEvent( mkey, fromcontroller );
}
private:
bool mEntering;
FString mEditName;
};
//=============================================================================
//
// [TP] FOptionMenuNumberField
//
// A numeric input field widget, for use with number CVars where sliders are inappropriate (i.e.
// where the user is interested in the exact value specifically)
//
//=============================================================================
class FOptionMenuNumberField : public FOptionMenuFieldBase
{
public:
FOptionMenuNumberField ( const char *label, const char* menu, float minimum, float maximum,
float step, const char* graycheck )
: FOptionMenuFieldBase ( label, menu, graycheck ),
mMinimum ( minimum ),
mMaximum ( maximum ),
mStep ( step )
{
if ( mMaximum <= mMinimum )
std::swap( mMinimum, mMaximum );
if ( mStep <= 0 )
mStep = 1;
}
bool MenuEvent ( int mkey, bool fromcontroller ) override
{
if ( mCVar )
{
float value = mCVar->GetGenericRep( CVAR_Float ).Float;
if ( mkey == MKEY_Left )
{
value -= mStep;
if ( value < mMinimum )
value = mMaximum;
}
else if ( mkey == MKEY_Right || mkey == MKEY_Enter )
{
value += mStep;
if ( value > mMaximum )
value = mMinimum;
}
else
return FOptionMenuItem::MenuEvent( mkey, fromcontroller );
UCVarValue vval;
vval.Float = value;
mCVar->SetGenericRep( vval, CVAR_Float );
M_MenuSound(ChangeSound);
}
return true;
}
private:
float mMinimum;
float mMaximum;
float mStep;
};