raze/source/common/menu/optionmenuitems.h
2019-11-30 10:03:20 +01:00

997 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"
#include "v_font.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, NewSmallFont, mEditName, 256, 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;
};