From 0b9c6fe559a469ff0acd60123b8ec5b930d6b7bf Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 4 Oct 2020 18:59:51 +0200 Subject: [PATCH] - added GZDoom's menu script code. --- source/CMakeLists.txt | 2 +- wadsrc/static/zscript.txt | 16 +- .../static/zscript/ui/menu/colorpickermenu.zs | 355 +++++ wadsrc/static/zscript/ui/menu/joystickmenu.zs | 312 ++++ wadsrc/static/zscript/ui/menu/listmenu.zs | 279 ++++ .../static/zscript/ui/menu/listmenuitems.zs | 342 +++++ wadsrc/static/zscript/ui/menu/loadsavemenu.zs | 644 +++++++++ wadsrc/static/zscript/ui/menu/menu.zs | 334 +++++ wadsrc/static/zscript/ui/menu/menuitembase.zs | 50 + wadsrc/static/zscript/ui/menu/messagebox.zs | 318 ++++ wadsrc/static/zscript/ui/menu/optionmenu.zs | 601 ++++++++ .../static/zscript/ui/menu/optionmenuitems.zs | 1275 +++++++++++++++++ wadsrc/static/zscript/ui/menu/readthis.zs | 125 ++ wadsrc/static/zscript/ui/menu/reverbedit.zs | 258 ++++ .../zscript/ui/menu/search/anyoralloption.zs | 33 + wadsrc/static/zscript/ui/menu/search/menu.zs | 218 +++ wadsrc/static/zscript/ui/menu/search/query.zs | 73 + .../zscript/ui/menu/search/searchfield.zs | 52 + .../static/zscript/ui/menu/textentermenu.zs | 379 +++++ 19 files changed, 5664 insertions(+), 2 deletions(-) create mode 100644 wadsrc/static/zscript/ui/menu/colorpickermenu.zs create mode 100644 wadsrc/static/zscript/ui/menu/joystickmenu.zs create mode 100644 wadsrc/static/zscript/ui/menu/listmenu.zs create mode 100644 wadsrc/static/zscript/ui/menu/listmenuitems.zs create mode 100644 wadsrc/static/zscript/ui/menu/loadsavemenu.zs create mode 100644 wadsrc/static/zscript/ui/menu/menu.zs create mode 100644 wadsrc/static/zscript/ui/menu/menuitembase.zs create mode 100644 wadsrc/static/zscript/ui/menu/messagebox.zs create mode 100644 wadsrc/static/zscript/ui/menu/optionmenu.zs create mode 100644 wadsrc/static/zscript/ui/menu/optionmenuitems.zs create mode 100644 wadsrc/static/zscript/ui/menu/readthis.zs create mode 100644 wadsrc/static/zscript/ui/menu/reverbedit.zs create mode 100644 wadsrc/static/zscript/ui/menu/search/anyoralloption.zs create mode 100644 wadsrc/static/zscript/ui/menu/search/menu.zs create mode 100644 wadsrc/static/zscript/ui/menu/search/query.zs create mode 100644 wadsrc/static/zscript/ui/menu/search/searchfield.zs create mode 100644 wadsrc/static/zscript/ui/menu/textentermenu.zs diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 7111e0fdc..62574a5c5 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -810,7 +810,7 @@ set (PCH_SOURCES common/audio/sound/oalsound.cpp common/audio/sound/s_environment.cpp common/audio/sound/s_sound.cpp - #common/audio/sound/s_reverbedit.cpp + common/audio/sound/s_reverbedit.cpp common/audio/music/music_midi_base.cpp common/audio/music/music.cpp common/audio/music/i_music.cpp diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt index b07f4f284..44f5c6761 100644 --- a/wadsrc/static/zscript.txt +++ b/wadsrc/static/zscript.txt @@ -4,4 +4,18 @@ version "4.3" #include "zscript/constants.zs" #include "zscript/events.zs" #include "zscript/dictionary.zs" -#include "zscript/gamescreen.zs" \ No newline at end of file +#include "zscript/gamescreen.zs" + +#include "zscript/ui/menu/colorpickermenu.zs" +#include "zscript/ui/menu/joystickmenu.zs" +#include "zscript/ui/menu/listmenu.zs" +#include "zscript/ui/menu/listmenuitems.zs" +#include "zscript/ui/menu/loadsavemenu.zs" +#include "zscript/ui/menu/menu.zs" +#include "zscript/ui/menu/menuitembase.zs" +#include "zscript/ui/menu/messagebox.zs" +#include "zscript/ui/menu/optionmenu.zs" +#include "zscript/ui/menu/optionmenuitems.zs" +#include "zscript/ui/menu/readthis.zs" +#include "zscript/ui/menu/reverbedit.zs" +#include "zscript/ui/menu/textentermenu.zs" diff --git a/wadsrc/static/zscript/ui/menu/colorpickermenu.zs b/wadsrc/static/zscript/ui/menu/colorpickermenu.zs new file mode 100644 index 000000000..f8ce51874 --- /dev/null +++ b/wadsrc/static/zscript/ui/menu/colorpickermenu.zs @@ -0,0 +1,355 @@ +/* +** colorpickermenu.txt +** The color picker menu +** +**--------------------------------------------------------------------------- +** Copyright 2010-2017 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. +**--------------------------------------------------------------------------- +** +*/ + + +//============================================================================= +// +// This is only used by the color picker +// +//============================================================================= + +class OptionMenuSliderVar : OptionMenuSliderBase +{ + int mIndex; + + OptionMenuSliderVar Init(String label, int index, double min, double max, double step, int showval) + { + Super.Init(label, min, max, step, showval); + mIndex = index; + return self; + } + + override double GetSliderValue() + { + return ColorpickerMenu(Menu.GetCurrentMenu()).GetColor(mIndex); + } + + override void SetSliderValue(double val) + { + ColorpickerMenu(Menu.GetCurrentMenu()).setColor(mIndex, val); + } +} + +class ColorpickerMenu : OptionMenu +{ + float mRed; + float mGreen; + float mBlue; + + int mGridPosX; + int mGridPosY; + + int mStartItem; + + CVar mCVar; + + double GetColor(int index) + { + double v = index == 0? mRed : index == 1? mGreen : mBlue; + return v; + } + + void SetColor(int index, double val) + { + if (index == 0) mRed = val; + else if (index == 1) mGreen = val; + else mBlue = val; + } + + //============================================================================= + // + // + // + //============================================================================= + + void Init(Menu parent, String name, OptionMenuDescriptor desc, CVar cv) + { + Super.Init(parent, desc); + + mStartItem = mDesc.mItems.Size(); + mCVar = cv; + + ResetColor(); + mGridPosX = 0; + mGridPosY = 0; + + // This menu uses some features that are hard to implement in an external control lump + // so it creates its own list of menu items. + mDesc.mItems.Resize(mStartItem+8); + mDesc.mItems[mStartItem+0] = new ("OptionMenuItemStaticText").Init(name, false); + mDesc.mItems[mStartItem+1] = new ("OptionMenuItemStaticText").Init(" ", false); + mDesc.mItems[mStartItem+2] = new ("OptionMenuSliderVar").Init("$TXT_COLOR_RED", 0, 0, 255, 15, 0); + mDesc.mItems[mStartItem+3] = new ("OptionMenuSliderVar").Init("$TXT_COLOR_GREEN", 1, 0, 255, 15, 0); + mDesc.mItems[mStartItem+4] = new ("OptionMenuSliderVar").Init("$TXT_COLOR_BLUE", 2, 0, 255, 15, 0); + mDesc.mItems[mStartItem+5] = new ("OptionMenuItemStaticText").Init(" ", false); + mDesc.mItems[mStartItem+6] = new ("OptionMenuItemCommand").Init("$TXT_UNDOCHANGES", "undocolorpic"); + mDesc.mItems[mStartItem+7] = new ("OptionMenuItemStaticText").Init(" ", false); + mDesc.mSelectedItem = mStartItem + 2; + mDesc.mIndent = 0; + mDesc.CalcIndent(); + } + + //============================================================================= + // + // + // + //============================================================================= + + override bool MenuEvent (int mkey, bool fromcontroller) + { + switch (mkey) + { + case MKEY_Down: + if (mDesc.mSelectedItem == mStartItem+6) // last valid item + { + MenuSound ("menu/cursor"); + mGridPosY = 0; + // let it point to the last static item so that the super class code still has a valid item + mDesc.mSelectedItem = mStartItem+7; + return true; + } + else if (mDesc.mSelectedItem == mStartItem+7) + { + if (mGridPosY < 15) + { + MenuSound ("menu/cursor"); + mGridPosY++; + } + return true; + } + break; + + case MKEY_Up: + if (mDesc.mSelectedItem == mStartItem+7) + { + if (mGridPosY > 0) + { + MenuSound ("menu/cursor"); + mGridPosY--; + } + else + { + MenuSound ("menu/cursor"); + mDesc.mSelectedItem = mStartItem+6; + } + return true; + } + break; + + case MKEY_Left: + if (mDesc.mSelectedItem == mStartItem+7) + { + MenuSound ("menu/cursor"); + if (--mGridPosX < 0) mGridPosX = 15; + return true; + } + break; + + case MKEY_Right: + if (mDesc.mSelectedItem == mStartItem+7) + { + MenuSound ("menu/cursor"); + if (++mGridPosX > 15) mGridPosX = 0; + return true; + } + break; + + case MKEY_Enter: + if (mDesc.mSelectedItem == mStartItem+7) + { + // Choose selected palette entry + int index = mGridPosX + mGridPosY * 16; + color col = Screen.PaletteColor(index); + mRed = col.r; + mGreen = col.g; + mBlue = col.b; + MenuSound ("menu/choose"); + return true; + } + break; + } + if (mDesc.mSelectedItem >= 0 && mDesc.mSelectedItem < mStartItem+7) + { + if (mDesc.mItems[mDesc.mSelectedItem].MenuEvent(mkey, fromcontroller)) return true; + } + return Super.MenuEvent(mkey, fromcontroller); + } + + //============================================================================= + // + // + // + //============================================================================= + + override bool MouseEvent(int type, int mx, int my) + { + int olditem = mDesc.mSelectedItem; + bool res = Super.MouseEvent(type, mx, my); + + if (mDesc.mSelectedItem == -1 || mDesc.mSelectedItem == mStartItem+7) + { + int y = (-mDesc.mPosition + BigFont.GetHeight() + mDesc.mItems.Size() * OptionMenuSettings.mLinespacing) * CleanYfac_1; + int h = (screen.GetHeight() - y) / 16; + int fh = OptionMenuSettings.mLinespacing * CleanYfac_1; + int w = fh; + int yy = y + 2 * CleanYfac_1; + int indent = (screen.GetWidth() / 2); + + if (h > fh) h = fh; + else if (h < 4) return res; // no space to draw it. + + int box_y = y - 2 * CleanYfac_1; + int box_x = indent - 16*w; + + if (mx >= box_x && mx < box_x + 16*w && my >= box_y && my < box_y + 16*h) + { + int cell_x = (mx - box_x) / w; + int cell_y = (my - box_y) / h; + + if (olditem != mStartItem+7 || cell_x != mGridPosX || cell_y != mGridPosY) + { + mGridPosX = cell_x; + mGridPosY = cell_y; + } + mDesc.mSelectedItem = mStartItem+7; + if (type == MOUSE_Release) + { + MenuEvent(MKEY_Enter, true); + if (m_use_mouse == 2) mDesc.mSelectedItem = -1; + } + res = true; + } + } + return res; + } + + //============================================================================= + // + // + // + //============================================================================= + + override void Drawer() + { + Super.Drawer(); + + if (mCVar == null) return; + int y = (-mDesc.mPosition + BigFont.GetHeight() + mDesc.mItems.Size() * OptionMenuSettings.mLinespacing) * CleanYfac_1; + int fh = OptionMenuSettings.mLinespacing * CleanYfac_1; + int h = (screen.GetHeight() - y) / 16; + int w = fh; + int yy = y; + + if (h > fh) h = fh; + else if (h < 4) return; // no space to draw it. + + int indent = (screen.GetWidth() / 2); + int p = 0; + + for(int i = 0; i < 16; i++) + { + int box_x, box_y; + int x1; + + box_y = y - 2 * CleanYfac_1; + box_x = indent - 16*w; + for (x1 = 0; x1 < 16; ++x1) + { + screen.Clear (box_x, box_y, box_x + w, box_y + h, 0, p); + if ((mDesc.mSelectedItem == mStartItem+7) && + (/*p == CurrColorIndex ||*/ (i == mGridPosY && x1 == mGridPosX))) + { + int r, g, b; + Color col; + double blinky; + if (i == mGridPosY && x1 == mGridPosX) + { + r = 255; g = 128; b = 0; + } + else + { + r = 200; g = 200; b = 255; + } + // Make sure the cursors stand out against similar colors + // by pulsing them. + blinky = abs(sin(MSTime()/1000.0)) * 0.5 + 0.5; + col = Color(255, int(r*blinky), int(g*blinky), int(b*blinky)); + + screen.Clear (box_x, box_y, box_x + w, box_y + 1, col); + screen.Clear (box_x, box_y + h-1, box_x + w, box_y + h, col); + screen.Clear (box_x, box_y, box_x + 1, box_y + h, col); + screen.Clear (box_x + w - 1, box_y, box_x + w, box_y + h, col); + } + box_x += w; + p++; + } + y += h; + } + y = yy; + color newColor = Color(255, int(mRed), int(mGreen), int(mBlue)); + color oldColor = mCVar.GetInt() | 0xFF000000; + + int x = screen.GetWidth()*2/3; + + screen.Clear (x, y, x + 48*CleanXfac_1, y + 48*CleanYfac_1, oldColor); + screen.Clear (x + 48*CleanXfac_1, y, x + 48*2*CleanXfac_1, y + 48*CleanYfac_1, newColor); + + y += 49*CleanYfac_1; + screen.DrawText (SmallFont, Font.CR_GRAY, x+(48-SmallFont.StringWidth("---->")/2)*CleanXfac_1, y, "---->", DTA_CleanNoMove_1, true); + } + + override void OnDestroy() + { + if (mStartItem >= 0) + { + mDesc.mItems.Resize(mStartItem); + if (mCVar != null) + { + mCVar.SetInt(Color(int(mRed), int(mGreen), int(mBlue))); + } + mStartItem = -1; + } + } + + override void ResetColor() + { + if (mCVar != null) + { + Color clr = Color(mCVar.GetInt()); + mRed = clr.r; + mGreen = clr.g; + mBlue = clr.b; + } + } +} \ No newline at end of file diff --git a/wadsrc/static/zscript/ui/menu/joystickmenu.zs b/wadsrc/static/zscript/ui/menu/joystickmenu.zs new file mode 100644 index 000000000..48b5840e7 --- /dev/null +++ b/wadsrc/static/zscript/ui/menu/joystickmenu.zs @@ -0,0 +1,312 @@ +/* +** joystickmenu.cpp +** The joystick configuration menus +** +**--------------------------------------------------------------------------- +** Copyright 2010-2017 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. +**--------------------------------------------------------------------------- +** +*/ + +//============================================================================= +// +// +// +//============================================================================= + +class OptionMenuSliderJoySensitivity : OptionMenuSliderBase +{ + JoystickConfig mJoy; + + OptionMenuSliderJoySensitivity Init(String label, double min, double max, double step, int showval, JoystickConfig joy) + { + Super.Init(label, min, max, step, showval); + mJoy = joy; + return self; + } + + override double GetSliderValue() + { + return mJoy.GetSensitivity(); + } + + override void SetSliderValue(double val) + { + mJoy.SetSensitivity(val); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +class OptionMenuSliderJoyScale : OptionMenuSliderBase +{ + int mAxis; + int mNeg; + JoystickConfig mJoy; + + OptionMenuSliderJoyScale Init(String label, int axis, double min, double max, double step, int showval, JoystickConfig joy) + { + Super.Init(label, min, max, step, showval); + mAxis = axis; + mNeg = 1; + mJoy = joy; + return self; + } + + override double GetSliderValue() + { + double d = mJoy.GetAxisScale(mAxis); + mNeg = d < 0? -1:1; + return d; + } + + override void SetSliderValue(double val) + { + mJoy.SetAxisScale(mAxis, val * mNeg); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +class OptionMenuSliderJoyDeadZone : OptionMenuSliderBase +{ + int mAxis; + int mNeg; + JoystickConfig mJoy; + + OptionMenuSliderJoyDeadZone Init(String label, int axis, double min, double max, double step, int showval, JoystickConfig joy) + { + Super.Init(label, min, max, step, showval); + mAxis = axis; + mNeg = 1; + mJoy = joy; + return self; + } + + override double GetSliderValue() + { + double d = mJoy.GetAxisDeadZone(mAxis); + mNeg = d < 0? -1:1; + return d; + } + + override void SetSliderValue(double val) + { + mJoy.SetAxisDeadZone(mAxis, val * mNeg); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +class OptionMenuItemJoyMap : OptionMenuItemOptionBase +{ + int mAxis; + JoystickConfig mJoy; + + OptionMenuItemJoyMap Init(String label, int axis, Name values, int center, JoystickConfig joy) + { + Super.Init(label, 'none', values, null, center); + mAxis = axis; + mJoy = joy; + return self; + } + + override int GetSelection() + { + double f = mJoy.GetAxisMap(mAxis); + let opt = OptionValues.GetCount(mValues); + if (opt > 0) + { + // Map from joystick axis to menu selection. + for(int i = 0; i < opt; i++) + { + if (f ~== OptionValues.GetValue(mValues, i)) + { + return i; + } + } + } + return -1; + } + + override void SetSelection(int selection) + { + let opt = OptionValues.GetCount(mValues); + // Map from menu selection to joystick axis. + if (opt == 0 || selection >= opt) + { + selection = JoystickConfig.JOYAXIS_None; + } + else + { + selection = int(OptionValues.GetValue(mValues, selection)); + } + mJoy.SetAxisMap(mAxis, selection); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +class OptionMenuItemInverter : OptionMenuItemOptionBase +{ + int mAxis; + JoystickConfig mJoy; + + OptionMenuItemInverter Init(String label, int axis, int center, JoystickConfig joy) + { + Super.Init(label, "none", "YesNo", NULL, center); + mAxis = axis; + mJoy = joy; + return self; + } + + override int GetSelection() + { + float f = mJoy.GetAxisScale(mAxis); + return f > 0? 0:1; + } + + override void SetSelection(int Selection) + { + let f = abs(mJoy.GetAxisScale(mAxis)); + if (Selection) f*=-1; + mJoy.SetAxisScale(mAxis, f); + } +} + +//============================================================================= +// +// Executes a CCMD, action is a CCMD name +// +//============================================================================= + +class OptionMenuItemJoyConfigMenu : OptionMenuItemSubmenu +{ + JoystickConfig mJoy; + + OptionMenuItemJoyConfigMenu Init(String label, JoystickConfig joy) + { + Super.Init(label, "JoystickConfigMenu"); + mJoy = joy; + return self; + } + + override bool Activate() + { + let desc = OptionMenuDescriptor(MenuDescriptor.GetDescriptor('JoystickConfigMenu')); + if (desc != NULL) + { + SetController(OptionMenuDescriptor(desc), mJoy); + } + let res = Super.Activate(); + let joymenu = JoystickConfigMenu(Menu.GetCurrentMenu()); + if (res && joymenu != null) joymenu.mJoy = mJoy; + return res; + } + + static void SetController(OptionMenuDescriptor opt, JoystickConfig joy) + { + OptionMenuItem it; + opt.mItems.Clear(); + if (joy == NULL) + { + opt.mTitle = "$JOYMNU_CONFIG"; + it = new("OptionMenuItemStaticText").Init("$JOYMNU_INVALID", false); + opt.mItems.Push(it); + } + else + { + it = new("OptionMenuItemStaticText").Init(joy.GetName(), false); + it = new("OptionMenuItemStaticText").Init("", false); + + it = new("OptionMenuSliderJoySensitivity").Init("$JOYMNU_OVRSENS", 0, 2, 0.1, 3, joy); + opt.mItems.Push(it); + it = new("OptionMenuItemStaticText").Init(" ", false); + opt.mItems.Push(it); + + if (joy.GetNumAxes() > 0) + { + it = new("OptionMenuItemStaticText").Init("$JOYMNU_AXIS", true); + opt.mItems.Push(it); + + for (int i = 0; i < joy.GetNumAxes(); ++i) + { + it = new("OptionMenuItemStaticText").Init(" ", false); + opt.mItems.Push(it); + + it = new("OptionMenuItemJoyMap").Init(joy.GetAxisName(i), i, "JoyAxisMapNames", false, joy); + opt.mItems.Push(it); + it = new("OptionMenuSliderJoyScale").Init("$JOYMNU_OVRSENS", i, 0, 4, 0.1, 3, joy); + opt.mItems.Push(it); + it = new("OptionMenuItemInverter").Init("$JOYMNU_INVERT", i, false, joy); + opt.mItems.Push(it); + it = new("OptionMenuSliderJoyDeadZone").Init("$JOYMNU_DEADZONE", i, 0, 0.9, 0.05, 3, joy); + opt.mItems.Push(it); + } + } + else + { + it = new("OptionMenuItemStaticText").Init("$JOYMNU_NOAXES", false); + opt.mItems.Push(it); + } + } + opt.mScrollPos = 0; + opt.mSelectedItem = -1; + opt.mIndent = 0; + opt.mPosition = -25; + opt.CalcIndent(); + } + +} + +//============================================================================= +// +// +// +//============================================================================= + +class JoystickConfigMenu : OptionMenu +{ + JoystickConfig mJoy; +} + diff --git a/wadsrc/static/zscript/ui/menu/listmenu.zs b/wadsrc/static/zscript/ui/menu/listmenu.zs new file mode 100644 index 000000000..76b384ba6 --- /dev/null +++ b/wadsrc/static/zscript/ui/menu/listmenu.zs @@ -0,0 +1,279 @@ + + +class ListMenuDescriptor : MenuDescriptor native +{ + enum EScale + { + CleanScale = -1, + OptCleanScale = -2 + }; + native Array mItems; + native int mSelectedItem; + native double mSelectOfsX; + native double mSelectOfsY; + native TextureID mSelector; + native int mDisplayTop; + native double mXpos, mYpos; + native int mWLeft, mWRight; + native int mLinespacing; // needs to be stored for dynamically created menus + native int mAutoselect; // this can only be set by internal menu creation functions + native Font mFont; + native int mFontColor; + native int mFontColor2; + native bool mCenter; + native int mVirtWidth, mVirtHeight; + + native void Reset(); + int DisplayWidth() + { + if (mVirtWidth == OptCleanScale) return m_cleanscale ? CleanScale : 320; + return mVirtWidth; + } + int DisplayHeight() + { + if (mVirtWidth == OptCleanScale) return m_cleanscale ? CleanScale : 200; + return mVirtHeight; + } +} + +//============================================================================= +// +// list menu class runs a menu described by a DListMenuDescriptor +// +//============================================================================= + +class ListMenu : Menu +{ + ListMenuDescriptor mDesc; + MenuItemBase mFocusControl; + + virtual void Init(Menu parent = NULL, ListMenuDescriptor desc = NULL) + { + Super.Init(parent); + mDesc = desc; + if (desc.mCenter) + { + double center = 160; + for(int i=0; i < mDesc.mItems.Size(); i++) + { + double xpos = mDesc.mItems[i].GetX(); + int width = mDesc.mItems[i].GetWidth(); + double curx = mDesc.mSelectOfsX; + + if (width > 0 && mDesc.mItems[i].Selectable()) + { + double left = 160 - (width - curx) / 2 - curx; + if (left < center) center = left; + } + } + for(int i=0;i 0) + { + mDesc.mItems[i].SetX(center); + } + } + } + // notify all items that the menu was just created. + for(int i=0;i 0) + { + // tolower + int ch = ev.KeyChar; + ch = ch >= 65 && ch < 91 ? ch + 32 : ch; + + for(int i = mDesc.mSelectedItem + 1; i < mDesc.mItems.Size(); i++) + { + if (mDesc.mitems[i].Selectable() && mDesc.mItems[i].CheckHotkey(ch)) + { + mDesc.mSelectedItem = i; + MenuSound("menu/cursor"); + return true; + } + } + for(int i = 0; i < mDesc.mSelectedItem; i++) + { + if (mDesc.mitems[i].Selectable() && mDesc.mItems[i].CheckHotkey(ch)) + { + mDesc.mSelectedItem = i; + MenuSound("menu/cursor"); + return true; + } + } + } + return Super.OnUIEvent(ev); + } + + //============================================================================= + // + // + // + //============================================================================= + + override bool MenuEvent (int mkey, bool fromcontroller) + { + int oldSelect = mDesc.mSelectedItem; + int startedAt = mDesc.mSelectedItem; + + switch (mkey) + { + case MKEY_Up: + do + { + if (--mDesc.mSelectedItem < 0) mDesc.mSelectedItem = mDesc.mItems.Size()-1; + } + while (!mDesc.mItems[mDesc.mSelectedItem].Selectable() && mDesc.mSelectedItem != startedAt); + if (mDesc.mSelectedItem == startedAt) mDesc.mSelectedItem = oldSelect; + MenuSound("menu/cursor"); + return true; + + case MKEY_Down: + do + { + if (++mDesc.mSelectedItem >= mDesc.mItems.Size()) mDesc.mSelectedItem = 0; + } + while (!mDesc.mItems[mDesc.mSelectedItem].Selectable() && mDesc.mSelectedItem != startedAt); + if (mDesc.mSelectedItem == startedAt) mDesc.mSelectedItem = oldSelect; + MenuSound("menu/cursor"); + return true; + + case MKEY_Enter: + if (mDesc.mSelectedItem >= 0 && mDesc.mItems[mDesc.mSelectedItem].Activate()) + { + MenuSound("menu/choose"); + } + return true; + + default: + return Super.MenuEvent(mkey, fromcontroller); + } + } + + //============================================================================= + // + // + // + //============================================================================= + + override bool MouseEvent(int type, int x, int y) + { + int sel = -1; + + // convert x/y from screen to virtual coordinates, according to CleanX/Yfac use in DrawTexture + x = ((x - (screen.GetWidth() / 2)) / CleanXfac) + 160; + y = ((y - (screen.GetHeight() / 2)) / CleanYfac) + 100; + + if (mFocusControl != NULL) + { + mFocusControl.MouseEvent(type, x, y); + return true; + } + else + { + if ((mDesc.mWLeft <= 0 || x > mDesc.mWLeft) && + (mDesc.mWRight <= 0 || x < mDesc.mWRight)) + { + for(int i=0;i= 0 && mDesc.mSelectedItem < mDesc.mItems.Size()) + mDesc.mItems[mDesc.mSelectedItem].DrawSelector(mDesc.mSelectOfsX, mDesc.mSelectOfsY, mDesc.mSelector, mDesc); + Super.Drawer(); + } + + //============================================================================= + // + // + // + //============================================================================= + + override void SetFocus(MenuItemBase fc) + { + mFocusControl = fc; + } + override bool CheckFocus(MenuItemBase fc) + { + return mFocusControl == fc; + } + override void ReleaseFocus() + { + mFocusControl = NULL; + } +} + + diff --git a/wadsrc/static/zscript/ui/menu/listmenuitems.zs b/wadsrc/static/zscript/ui/menu/listmenuitems.zs new file mode 100644 index 000000000..dafbfd5d2 --- /dev/null +++ b/wadsrc/static/zscript/ui/menu/listmenuitems.zs @@ -0,0 +1,342 @@ +/* +** listmenu.cpp +** A simple menu consisting of a list of items +** +**--------------------------------------------------------------------------- +** Copyright 2010-2017 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. +**--------------------------------------------------------------------------- +** +*/ + + +class ListMenuItem : MenuItemBase +{ + protected void DrawText(ListMenuDescriptor desc, Font fnt, int color, double x, double y, String text, bool ontop = false) + { + int w = desc ? desc.DisplayWidth() : ListMenuDescriptor.CleanScale; + int h = desc ? desc.DisplayHeight() : -1; + if (w == ListMenuDescriptor.CleanScale) + { + screen.DrawText(fnt, color, x, y, text, ontop? DTA_CleanTop : DTA_Clean, true); + } + else + { + screen.DrawText(fnt, color, x, y, text, DTA_VirtualWidth, w, DTA_VirtualHeight, h, DTA_FullscreenScale, FSMode_ScaleToFit43); + } + } + + protected void DrawTexture(ListMenuDescriptor desc, TextureID tex, double x, double y, bool ontop = false) + { + int w = desc ? desc.DisplayWidth() : ListMenuDescriptor.CleanScale; + int h = desc ? desc.DisplayHeight() : -1; + if (w == ListMenuDescriptor.CleanScale) + { + screen.DrawTexture(tex, true, x, y, ontop ? DTA_CleanTop : DTA_Clean, true); + } + else + { + screen.DrawTexture(tex, true, x, y, DTA_VirtualWidth, w, DTA_VirtualHeight, h, DTA_FullscreenScale, FSMode_ScaleToFit43); + } + } + + virtual void DrawSelector(double xofs, double yofs, TextureID tex, ListMenuDescriptor desc = null) + { + if (tex.isNull()) + { + if ((Menu.MenuTime() % 8) < 6) + { + DrawText(desc, ConFont, OptionMenuSettings.mFontColorSelection, mXpos + xofs, mYpos + yofs + 8, "\xd"); + } + } + else + { + DrawTexture(desc, tex, mXpos + xofs, mYpos + yofs); + } + } + + // We cannot extend Drawer here because it is inherited from the parent class. + virtual void Draw(bool selected, ListMenuDescriptor desc) + { + Drawer(selected); // fall back to the legacy version, if not overridden + } +} + +//============================================================================= +// +// static patch +// +//============================================================================= + +class ListMenuItemStaticPatch : ListMenuItem +{ + TextureID mTexture; + bool mCentered; + String mSubstitute; + Font mFont; + int mColor; + + void Init(ListMenuDescriptor desc, double x, double y, TextureID patch, bool centered = false, String substitute = "") + { + Super.Init(x, y); + mTexture = patch; + mCentered = centered; + mSubstitute = substitute; + mFont = desc.mFont; + mColor = desc.mFontColor; + + } + + override void Draw(bool selected, ListMenuDescriptor desc) + { + if (!mTexture.Exists()) + { + return; + } + + double x = mXpos; + Vector2 vec = TexMan.GetScaledSize(mTexture); + + if (mSubstitute == "" || TexMan.OkForLocalization(mTexture, mSubstitute)) + { + if (mCentered) x -= vec.X / 2; + DrawTexture(desc, mTexture, x, abs(mYpos), mYpos < 0); + } + else + { + let font = generic_ui ? NewSmallFont : mFont; + if (mCentered) x -= font.StringWidth(mSubstitute) / 2; + DrawText(desc, font, mColor, x, abs(mYpos), mSubstitute, mYpos < 0); + } + } +} + +class ListMenuItemStaticPatchCentered : ListMenuItemStaticPatch +{ + void Init(ListMenuDescriptor desc, double x, double y, TextureID patch) + { + Super.Init(desc, x, y, patch, true); + } +} + +//============================================================================= +// +// static text +// +//============================================================================= + +class ListMenuItemStaticText : ListMenuItem +{ + String mText; + Font mFont; + int mColor; + bool mCentered; + + void Init(ListMenuDescriptor desc, double x, double y, String text, int color = -1) + { + Super.Init(x, y); + mText = text; + mFont = desc.mFont; + mColor = color >= 0? color : desc.mFontColor; + mCentered = false; + } + + void InitDirect(double x, double y, String text, Font font, int color = Font.CR_UNTRANSLATED, bool centered = false) + { + Super.Init(x, y); + mText = text; + mFont = font; + mColor = color; + mCentered = centered; + } + + override void Draw(bool selected, ListMenuDescriptor desc) + { + if (mText.Length() != 0) + { + let font = generic_ui? NewSmallFont : mFont; + + String text = Stringtable.Localize(mText); + + double x = mXpos; + if (mCentered) x -= font.StringWidth(text) / 2; + DrawText(desc, font, mColor, x, abs(mYpos), text, mYpos < 0); + } + } +} + +class ListMenuItemStaticTextCentered : ListMenuItemStaticText +{ + void Init(ListMenuDescriptor desc, double x, double y, String text, int color = -1) + { + Super.Init(desc, x, y, text, color); + mCentered = true; + } +} + +//============================================================================= +// +// selectable items +// +//============================================================================= + +class ListMenuItemSelectable : ListMenuItem +{ + int mHotkey; + int mHeight; + int mParam; + + protected void Init(double x, double y, int height, Name childmenu, int param = -1) + { + Super.Init(x, y, childmenu); + mHeight = height; + mParam = param; + mHotkey = 0; + } + + override bool CheckCoordinate(int x, int y) + { + return mEnabled && y >= mYpos && y < mYpos + mHeight; // no x check here + } + + override bool Selectable() + { + return mEnabled; + } + + override bool CheckHotkey(int c) + { + return c > 0 && c == mHotkey; + } + + override bool Activate() + { + Menu.SetMenu(mAction, mParam); + return true; + } + + override bool MouseEvent(int type, int x, int y) + { + if (type == Menu.MOUSE_Release) + { + let m = Menu.GetCurrentMenu(); + if (m != NULL && m.MenuEvent(Menu.MKEY_Enter, true)) + { + return true; + } + } + return false; + } + + override Name, int GetAction() + { + return mAction, mParam; + } +} + +//============================================================================= +// +// text item +// +//============================================================================= + +class ListMenuItemTextItem : ListMenuItemSelectable +{ + String mText; + Font mFont; + int mColor; + int mColorSelected; + + void Init(ListMenuDescriptor desc, String text, String hotkey, Name child, int param = 0) + { + Super.Init(desc.mXpos, desc.mYpos, desc.mLinespacing, child, param); + mText = text; + mFont = desc.mFont; + mColor = desc.mFontColor; + mColorSelected = desc.mFontcolor2; + mHotkey = hotkey.GetNextCodePoint(0); + } + + void InitDirect(double x, double y, int height, String hotkey, String text, Font font, int color, int color2, Name child, int param = 0) + { + Super.Init(x, y, height, child, param); + mText = text; + mFont = font; + mColor = color; + mColorSelected = color2; + int pos = 0; + mHotkey = hotkey.GetNextCodePoint(0); + } + + override void Draw(bool selected, ListMenuDescriptor desc) + { + let font = generic_ui ? NewSmallFont : mFont; + DrawText(desc, font, selected ? mColorSelected : mColor, mXpos, mYpos, mText); + } + + override int GetWidth() + { + let font = generic_ui? NewSmallFont : mFont; + return max(1, font.StringWidth(StringTable.Localize(mText))); + } +} + +//============================================================================= +// +// patch item +// +//============================================================================= + +class ListMenuItemPatchItem : ListMenuItemSelectable +{ + TextureID mTexture; + + void Init(ListMenuDescriptor desc, TextureID patch, String hotkey, Name child, int param = 0) + { + Super.Init(desc.mXpos, desc.mYpos, desc.mLinespacing, child, param); + mHotkey = hotkey.GetNextCodePoint(0); + mTexture = patch; + } + + void InitDirect(double x, double y, int height, TextureID patch, String hotkey, Name child, int param = 0) + { + Super.Init(x, y, height, child, param); + mHotkey = hotkey.GetNextCodePoint(0); + mTexture = patch; + } + + override void Draw(bool selected, ListMenuDescriptor desc) + { + DrawTexture(desc, mTexture, mXpos, mYpos); + } + + override int GetWidth() + { + return TexMan.GetSize(mTexture); + } + +} + diff --git a/wadsrc/static/zscript/ui/menu/loadsavemenu.zs b/wadsrc/static/zscript/ui/menu/loadsavemenu.zs new file mode 100644 index 000000000..eb9b71436 --- /dev/null +++ b/wadsrc/static/zscript/ui/menu/loadsavemenu.zs @@ -0,0 +1,644 @@ +/* +** loacpp +** The load game and save game menus +** +**--------------------------------------------------------------------------- +** Copyright 2001-2010 Randy Heit +** Copyright 2010-2017 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. +**--------------------------------------------------------------------------- +** +*/ + + +struct SaveGameNode native +{ + native String SaveTitle; + native readonly String Filename; + native bool bOldVersion; + native bool bMissingWads; + native bool bNoDelete; +} + +struct SavegameManager native ui +{ + native int WindowSize; + native SaveGameNode quickSaveSlot; + native readonly String SaveCommentString; + + native static SavegameManager GetManager(); + native void ReadSaveStrings(); + native void UnloadSaveData(); + + native int RemoveSaveSlot(int index); + native void LoadSavegame(int Selected); + native void DoSave(int Selected, String savegamestring); + native int ExtractSaveData(int index); + native void ClearSaveStuff(); + native bool DrawSavePic(int x, int y, int w, int h); + deprecated("4.0") void DrawSaveComment(Font font, int cr, int x, int y, int scalefactor) + { + // Unfortunately, this was broken beyond repair so it now prints nothing. + } + native void SetFileInfo(int Selected); + native int SavegameCount(); + native SaveGameNode GetSavegame(int i); + native void InsertNewSaveNode(); + native bool RemoveNewSaveNode(); + +} + + + +class LoadSaveMenu : ListMenu +{ + SavegameManager manager; + int TopItem; + int Selected; + + int savepicLeft; + int savepicTop; + int savepicWidth; + int savepicHeight; + int rowHeight; + int listboxLeft; + int listboxTop; + int listboxWidth; + + int listboxRows; + int listboxHeight; + int listboxRight; + int listboxBottom; + + int commentLeft; + int commentTop; + int commentWidth; + int commentHeight; + int commentRight; + int commentBottom; + int commentRows; + + bool mEntering; + TextEnterMenu mInput; + double FontScale; + + BrokenLines BrokenSaveComment; + + + + //============================================================================= + // + // + // + //============================================================================= + + override void Init(Menu parent, ListMenuDescriptor desc) + { + Super.Init(parent, desc); + manager = SavegameManager.GetManager(); + manager.ReadSaveStrings(); + + savepicLeft = 10; + savepicTop = 54*CleanYfac; + savepicWidth = 216*screen.GetWidth() / 640; + savepicHeight = 135*screen.GetHeight() / 400; + + FontScale = max(screen.GetHeight() / 480, 1); + rowHeight = int(max((NewConsoleFont.GetHeight() + 1) * FontScale, 1)); + + listboxLeft = savepicLeft + savepicWidth + 14; + listboxTop = savepicTop; + listboxWidth = screen.GetWidth() - listboxLeft - 10; + int listboxHeight1 = screen.GetHeight() - listboxTop - 10; + listboxRows = (listboxHeight1 - 1) / rowHeight; + listboxHeight = listboxRows * rowHeight + 1; + listboxRight = listboxLeft + listboxWidth; + listboxBottom = listboxTop + listboxHeight; + + commentLeft = savepicLeft; + commentTop = savepicTop + savepicHeight + 16; + commentWidth = savepicWidth; + //commentHeight = (51+(screen.GetHeight()>200?10:0))*CleanYfac; + commentHeight = listboxHeight - savepicHeight - 16; + commentRight = commentLeft + commentWidth; + commentBottom = commentTop + commentHeight; + commentRows = commentHeight / rowHeight; + } + + + //============================================================================= + // + // + // + //============================================================================= + + override void OnDestroy() + { + //manager.ClearSaveStuff (); + Super.OnDestroy(); + } + + //============================================================================= + // + // + // + //============================================================================= + + override void Drawer () + { + Super.Drawer(); + + SaveGameNode node; + int i; + int j; + bool didSeeSelected = false; + + // Draw picture area + if (gameaction == ga_loadgame || gameaction == ga_loadgamehidecon || gameaction == ga_savegame) + { + return; + } + + Screen.DrawFrame (savepicLeft, savepicTop, savepicWidth, savepicHeight); + if (!manager.DrawSavePic(savepicLeft, savepicTop, savepicWidth, savepicHeight)) + { + screen.Clear (savepicLeft, savepicTop, savepicLeft+savepicWidth, savepicTop+savepicHeight, 0, 0); + + if (manager.SavegameCount() > 0) + { + String text = (Selected == -1 || !manager.GetSavegame(Selected).bOldVersion)? Stringtable.Localize("$MNU_NOPICTURE") : Stringtable.Localize("$MNU_DIFFVERSION"); + int textlen = NewSmallFont.StringWidth(text) * CleanXfac; + + screen.DrawText (NewSmallFont, Font.CR_GOLD, savepicLeft+(savepicWidth-textlen)/2, + savepicTop+(savepicHeight-rowHeight)/2, text, DTA_CleanNoMove, true); + } + } + + // Draw comment area + Screen.DrawFrame (commentLeft, commentTop, commentWidth, commentHeight); + screen.Clear (commentLeft, commentTop, commentRight, commentBottom, 0, 0); + + int numlinestoprint = min(commentRows, BrokenSaveComment? BrokenSaveComment.Count() : 0); + for(int i = 0; i < numlinestoprint; i++) + { + screen.DrawText(NewConsoleFont, Font.CR_ORANGE, commentLeft / FontScale, (commentTop + rowHeight * i) / FontScale, BrokenSaveComment.StringAt(i), + DTA_VirtualWidthF, screen.GetWidth() / FontScale, DTA_VirtualHeightF, screen.GetHeight() / FontScale, DTA_KeepRatio, true); + } + + + // Draw file area + Screen.DrawFrame (listboxLeft, listboxTop, listboxWidth, listboxHeight); + screen.Clear (listboxLeft, listboxTop, listboxRight, listboxBottom, 0, 0); + + if (manager.SavegameCount() == 0) + { + String text = Stringtable.Localize("$MNU_NOFILES"); + int textlen = int(NewConsoleFont.StringWidth(text) * FontScale); + + screen.DrawText (NewConsoleFont, Font.CR_GOLD, (listboxLeft+(listboxWidth-textlen)/2) / FontScale, (listboxTop+(listboxHeight-rowHeight)/2) / FontScale, text, + DTA_VirtualWidthF, screen.GetWidth() / FontScale, DTA_VirtualHeightF, screen.GetHeight() / FontScale, DTA_KeepRatio, true); + return; + } + + j = TopItem; + for (i = 0; i < listboxRows && j < manager.SavegameCount(); i++) + { + int colr; + node = manager.GetSavegame(j); + if (node.bOldVersion) + { + colr = Font.CR_RED; + } + else if (node.bMissingWads) + { + colr = Font.CR_YELLOW; + } + else if (j == Selected) + { + colr = Font.CR_WHITE; + } + else + { + colr = Font.CR_TAN; + } + + screen.SetClipRect(listboxLeft, listboxTop+rowHeight*i, listboxRight, listboxTop+rowHeight*(i+1)); + + if (j == Selected) + { + screen.Clear (listboxLeft, listboxTop+rowHeight*i, listboxRight, listboxTop+rowHeight*(i+1), mEntering ? Color(255,255,0,0) : Color(255,0,0,255)); + didSeeSelected = true; + if (!mEntering) + { + screen.DrawText (NewConsoleFont, colr, (listboxLeft+1) / FontScale, (listboxTop+rowHeight*i + FontScale) / FontScale, node.SaveTitle, + DTA_VirtualWidthF, screen.GetWidth() / FontScale, DTA_VirtualHeightF, screen.GetHeight() / FontScale, DTA_KeepRatio, true); + } + else + { + String s = mInput.GetText() .. NewConsoleFont.GetCursor(); + int length = int(NewConsoleFont.StringWidth(s) * FontScale); + int displacement = min(0, listboxWidth - 2 - length); + screen.DrawText (NewConsoleFont, Font.CR_WHITE, (listboxLeft + 1 + displacement) / FontScale, (listboxTop+rowHeight*i + FontScale) / FontScale, s, + DTA_VirtualWidthF, screen.GetWidth() / FontScale, DTA_VirtualHeightF, screen.GetHeight() / FontScale, DTA_KeepRatio, true); + } + } + else + { + screen.DrawText (NewConsoleFont, colr, (listboxLeft+1) / FontScale, (listboxTop+rowHeight*i + FontScale) / FontScale, node.SaveTitle, + DTA_VirtualWidthF, screen.GetWidth() / FontScale, DTA_VirtualHeightF, screen.GetHeight() / FontScale, DTA_KeepRatio, true); + } + screen.ClearClipRect(); + j++; + } + } + + void UpdateSaveComment() + { + BrokenSaveComment = NewConsoleFont.BreakLines(manager.SaveCommentString, int(commentWidth / FontScale)); + } + + //============================================================================= + // + // + // + //============================================================================= + + override bool MenuEvent (int mkey, bool fromcontroller) + { + switch (mkey) + { + case MKEY_Up: + if (manager.SavegameCount() > 1) + { + if (Selected == -1) Selected = TopItem; + else + { + if (--Selected < 0) Selected = manager.SavegameCount()-1; + if (Selected < TopItem) TopItem = Selected; + else if (Selected >= TopItem + listboxRows) TopItem = MAX(0, Selected - listboxRows + 1); + } + manager.UnloadSaveData (); + manager.ExtractSaveData (Selected); + UpdateSaveComment(); + } + return true; + + case MKEY_Down: + if (manager.SavegameCount() > 1) + { + if (Selected == -1) Selected = TopItem; + else + { + if (++Selected >= manager.SavegameCount()) Selected = 0; + if (Selected < TopItem) TopItem = Selected; + else if (Selected >= TopItem + listboxRows) TopItem = MAX(0, Selected - listboxRows + 1); + } + manager.UnloadSaveData (); + manager.ExtractSaveData (Selected); + UpdateSaveComment(); + } + return true; + + case MKEY_PageDown: + if (manager.SavegameCount() > 1) + { + if (TopItem >= manager.SavegameCount() - listboxRows) + { + TopItem = 0; + if (Selected != -1) Selected = 0; + } + else + { + TopItem = MIN(TopItem + listboxRows, manager.SavegameCount() - listboxRows); + if (TopItem > Selected && Selected != -1) Selected = TopItem; + } + manager.UnloadSaveData (); + manager.ExtractSaveData (Selected); + UpdateSaveComment(); + } + return true; + + case MKEY_PageUp: + if (manager.SavegameCount() > 1) + { + if (TopItem == 0) + { + TopItem = MAX(0, manager.SavegameCount() - listboxRows); + if (Selected != -1) Selected = TopItem; + } + else + { + TopItem = MAX(TopItem - listboxRows, 0); + if (Selected >= TopItem + listboxRows) Selected = TopItem; + } + manager.UnloadSaveData (); + manager.ExtractSaveData (Selected); + UpdateSaveComment(); + } + return true; + + case MKEY_Enter: + return false; // This event will be handled by the subclasses + + case MKEY_MBYes: + { + if (Selected < manager.SavegameCount()) + { + Selected = manager.RemoveSaveSlot (Selected); + UpdateSaveComment(); + } + return true; + } + + default: + return Super.MenuEvent(mkey, fromcontroller); + } + } + + //============================================================================= + // + // + // + //============================================================================= + + override bool MouseEvent(int type, int x, int y) + { + if (x >= listboxLeft && x < listboxLeft + listboxWidth && + y >= listboxTop && y < listboxTop + listboxHeight) + { + int lineno = (y - listboxTop) / rowHeight; + + if (TopItem + lineno < manager.SavegameCount()) + { + Selected = TopItem + lineno; + manager.UnloadSaveData (); + manager.ExtractSaveData (Selected); + UpdateSaveComment(); + if (type == MOUSE_Release) + { + if (MenuEvent(MKEY_Enter, true)) + { + return true; + } + } + } + else Selected = -1; + } + else Selected = -1; + + return Super.MouseEvent(type, x, y); + } + + + //============================================================================= + // + // + // + //============================================================================= + + override bool OnUIEvent(UIEvent ev) + { + if (ev.Type == UIEvent.Type_KeyDown) + { + if (Selected != -1 && Selected < manager.SavegameCount()) + { + switch (ev.KeyChar) + { + case UIEvent.Key_F1: + manager.SetFileInfo(Selected); + UpdateSaveComment(); + return true; + + case UIEvent.Key_DEL: + { + String EndString; + EndString = String.Format("%s%s%s%s?\n\n%s", Stringtable.Localize("$MNU_DELETESG"), TEXTCOLOR_WHITE, manager.GetSavegame(Selected).SaveTitle, TEXTCOLOR_NORMAL, Stringtable.Localize("$PRESSYN")); + StartMessage (EndString, 0); + } + return true; + } + } + } + else if (ev.Type == UIEvent.Type_WheelUp) + { + if (TopItem > 0) TopItem--; + return true; + } + else if (ev.Type == UIEvent.Type_WheelDown) + { + if (TopItem < manager.SavegameCount() - listboxRows) TopItem++; + return true; + } + return Super.OnUIEvent(ev); + } + + +} + +class SaveMenu : LoadSaveMenu +{ + String mSaveName; + + //============================================================================= + // + // + // + //============================================================================= + + override void Init(Menu parent, ListMenuDescriptor desc) + { + Super.Init(parent, desc); + manager.InsertNewSaveNode(); + TopItem = 0; + Selected = manager.ExtractSaveData (-1); + UpdateSaveComment(); + } + + //============================================================================= + // + // + // + //============================================================================= + + override void OnDestroy() + { + if (manager.RemoveNewSaveNode()) + { + Selected--; + } + Super.OnDestroy(); + } + + //============================================================================= + // + // + // + //============================================================================= + + override bool MenuEvent (int mkey, bool fromcontroller) + { + if (Super.MenuEvent(mkey, fromcontroller)) + { + return true; + } + if (Selected == -1) + { + return false; + } + + if (mkey == MKEY_Enter) + { + String SavegameString = (Selected != 0)? manager.GetSavegame(Selected).SaveTitle : ""; + mInput = TextEnterMenu.OpenTextEnter(self, Menu.OptionFont(), SavegameString, -1, fromcontroller); + mInput.ActivateMenu(); + mEntering = true; + } + else if (mkey == MKEY_Input) + { + // Do not start the save here, it would cause some serious execution ordering problems. + mEntering = false; + mSaveName = mInput.GetText(); + mInput = null; + } + else if (mkey == MKEY_Abort) + { + mEntering = false; + mInput = null; + } + return false; + } + + //============================================================================= + // + // + // + //============================================================================= + + override bool MouseEvent(int type, int x, int y) + { + if (mSaveName.Length() > 0) + { + // Do not process events when saving is in progress to avoid update of the current index, + // i.e. Selected member variable must remain unchanged + return true; + } + + return Super.MouseEvent(type, x, y); + } + + //============================================================================= + // + // + // + //============================================================================= + + override bool OnUIEvent(UIEvent ev) + { + if (ev.Type == UIEvent.Type_KeyDown) + { + if (Selected != -1) + { + switch (ev.KeyChar) + { + case UIEvent.Key_DEL: + // cannot delete 'new save game' item + if (Selected == 0) return true; + break; + + case 78://'N': + Selected = TopItem = 0; + manager.UnloadSaveData (); + return true; + } + } + } + return Super.OnUIEvent(ev); + } + + //============================================================================= + // + // + // + //============================================================================= + + override void Ticker() + { + if (mSaveName.Length() > 0) + { + manager.DoSave(Selected, mSaveName); + mSaveName = ""; + } + } + +} + +//============================================================================= +// +// +// +//============================================================================= + +class LoadMenu : LoadSaveMenu +{ + //============================================================================= + // + // + // + //============================================================================= + + override void Init(Menu parent, ListMenuDescriptor desc) + { + Super.Init(parent, desc); + TopItem = 0; + Selected = manager.ExtractSaveData (-1); + UpdateSaveComment(); + } + + //============================================================================= + // + // + // + //============================================================================= + + override bool MenuEvent (int mkey, bool fromcontroller) + { + if (Super.MenuEvent(mkey, fromcontroller)) + { + return true; + } + if (Selected == -1 || manager.SavegameCount() == 0) + { + return false; + } + + if (mkey == MKEY_Enter) + { + manager.LoadSavegame(Selected); + return true; + } + return false; + } +} diff --git a/wadsrc/static/zscript/ui/menu/menu.zs b/wadsrc/static/zscript/ui/menu/menu.zs new file mode 100644 index 000000000..d47af6fc2 --- /dev/null +++ b/wadsrc/static/zscript/ui/menu/menu.zs @@ -0,0 +1,334 @@ + +struct KeyBindings native version("2.4") +{ + native static String NameKeys(int k1, int k2); + + native int, int GetKeysForCommand(String cmd); + native void SetBind(int key, String cmd); + native void UnbindACommand (String str); +} + +struct OptionValues native version("2.4") +{ + native static int GetCount(Name group); + native static String GetText(Name group, int index); + native static double GetValue(Name group, int index); + native static String GetTextValue(Name group, int index); +} + +struct JoystickConfig native version("2.4") +{ + enum EJoyAxis + { + JOYAXIS_None = -1, + JOYAXIS_Yaw, + JOYAXIS_Pitch, + JOYAXIS_Forward, + JOYAXIS_Side, + JOYAXIS_Up, + // JOYAXIS_Roll, // Ha ha. No roll for you. + NUM_JOYAXIS, + }; + + native float GetSensitivity(); + native void SetSensitivity(float scale); + + native float GetAxisScale(int axis); + native void SetAxisScale(int axis, float scale); + + native float GetAxisDeadZone(int axis); + native void SetAxisDeadZone(int axis, float zone); + + native int GetAxisMap(int axis); + native void SetAxisMap(int axis, int gameaxis); + + native String GetName(); + native int GetNumAxes(); + native String GetAxisName(int axis); + +} + +class Menu : Object native ui version("2.4") +{ + enum EMenuKey + { + MKEY_Up, + MKEY_Down, + MKEY_Left, + MKEY_Right, + MKEY_PageUp, + MKEY_PageDown, + MKEY_Enter, + MKEY_Back, + MKEY_Clear, + NUM_MKEYS, + + // These are not buttons but events sent from other menus + + MKEY_Input, + MKEY_Abort, + MKEY_MBYes, + MKEY_MBNo, + } + + enum EMenuMouse + { + MOUSE_Click, + MOUSE_Move, + MOUSE_Release + }; + + enum EMenuState + { + Off, // Menu is closed + On, // Menu is opened + WaitKey, // Menu is opened and waiting for a key in the controls menu + OnNoPause, // Menu is opened but does not pause the game + }; + + native Menu mParentMenu; + native bool mMouseCapture; + native bool mBackbuttonSelected; + native bool DontDim; + native bool DontBlur; + + native static int MenuTime(); + native static Menu GetCurrentMenu(); + native static clearscope void SetMenu(Name mnu, int param = 0); // This is not 100% safe but needs to be available - but always make sure to check that only the desired player opens it! + native static void StartMessage(String msg, int mode = 0, Name command = 'none'); + native static void SetMouseCapture(bool on); + native void Close(); + native void ActivateMenu(); + + //============================================================================= + // + // + // + //============================================================================= + + void Init(Menu parent) + { + mParentMenu = parent; + mMouseCapture = false; + mBackbuttonSelected = false; + DontDim = false; + DontBlur = false; + } + + //============================================================================= + // + // + // + //============================================================================= + + virtual bool MenuEvent (int mkey, bool fromcontroller) + { + switch (mkey) + { + case MKEY_Back: + Close(); + MenuSound (GetCurrentMenu() != null? "menu/backup" : "menu/clear"); + return true; + } + return false; + } + + + //============================================================================= + // + // + // + //============================================================================= + + protected bool MouseEventBack(int type, int x, int y) + { + if (m_show_backbutton >= 0) + { + let tex = TexMan.CheckForTexture(gameinfo.mBackButton, TexMan.Type_MiscPatch); + if (tex.IsValid()) + { + Vector2 v = TexMan.GetScaledSize(tex); + int w = int(v.X + 0.5) * CleanXfac; + int h = int(v.Y + 0.5) * CleanYfac; + if (m_show_backbutton&1) x -= screen.GetWidth() - w; + if (m_show_backbutton&2) y -= screen.GetHeight() - h; + mBackbuttonSelected = ( x >= 0 && x < w && y >= 0 && y < h); + if (mBackbuttonSelected && type == MOUSE_Release) + { + if (m_use_mouse == 2) mBackbuttonSelected = false; + MenuEvent(MKEY_Back, true); + } + return mBackbuttonSelected; + } + } + return false; + } + + //============================================================================= + // + // + // + //============================================================================= + + virtual bool OnUIEvent(UIEvent ev) + { + bool res = false; + int y = ev.MouseY; + if (ev.type == UIEvent.Type_LButtonDown) + { + res = MouseEventBack(MOUSE_Click, ev.MouseX, y); + // make the menu's mouse handler believe that the current coordinate is outside the valid range + if (res) y = -1; + res |= MouseEvent(MOUSE_Click, ev.MouseX, y); + if (res) + { + SetCapture(true); + } + + } + else if (ev.type == UIEvent.Type_MouseMove) + { + BackbuttonTime = 4*Thinker.TICRATE; + if (mMouseCapture || m_use_mouse == 1) + { + res = MouseEventBack(MOUSE_Move, ev.MouseX, y); + if (res) y = -1; + res |= MouseEvent(MOUSE_Move, ev.MouseX, y); + } + } + else if (ev.type == UIEvent.Type_LButtonUp) + { + if (mMouseCapture) + { + SetCapture(false); + res = MouseEventBack(MOUSE_Release, ev.MouseX, y); + if (res) y = -1; + res |= MouseEvent(MOUSE_Release, ev.MouseX, y); + } + } + return false; + } + + virtual bool OnInputEvent(InputEvent ev) + { + return false; + } + + //============================================================================= + // + // + // + //============================================================================= + + virtual void Drawer () + { + if (self == GetCurrentMenu() && BackbuttonAlpha > 0 && m_show_backbutton >= 0 && m_use_mouse) + { + let tex = TexMan.CheckForTexture(gameinfo.mBackButton, TexMan.Type_MiscPatch); + if (tex.IsValid()) + { + Vector2 v = TexMan.GetScaledSize(tex); + int w = int(v.X + 0.5) * CleanXfac; + int h = int(v.Y + 0.5) * CleanYfac; + int x = (!(m_show_backbutton&1))? 0:screen.GetWidth() - w; + int y = (!(m_show_backbutton&2))? 0:screen.GetHeight() - h; + if (mBackbuttonSelected && (mMouseCapture || m_use_mouse == 1)) + { + screen.DrawTexture(tex, true, x, y, DTA_CleanNoMove, true, DTA_ColorOverlay, Color(40, 255,255,255)); + } + else + { + screen.DrawTexture(tex, true, x, y, DTA_CleanNoMove, true, DTA_Alpha, BackbuttonAlpha); + } + } + } + } + + //============================================================================= + // + // + // + //============================================================================= + + void SetCapture(bool on) + { + if (mMouseCapture != on) + { + mMouseCapture = on; + SetMouseCapture(on); + } + } + + //============================================================================= + // + // + // + //============================================================================= + + virtual bool TranslateKeyboardEvents() { return true; } + virtual void SetFocus(MenuItemBase fc) {} + virtual bool CheckFocus(MenuItemBase fc) { return false; } + virtual void ReleaseFocus() {} + virtual void ResetColor() {} + virtual bool MouseEvent(int type, int mx, int my) { return true; } + virtual void Ticker() {} + virtual void OnReturn() {} + + //============================================================================= + // + // + // + //============================================================================= + + static void MenuSound(Sound snd) + { + S_StartSound (snd, CHAN_VOICE, CHANF_MAYBE_LOCAL|CHAN_UI, snd_menuvolume, ATTN_NONE); + } + + deprecated("4.0") static void DrawConText (int color, int x, int y, String str) + { + screen.DrawText (ConFont, color, x, y, str, DTA_CellX, 8 * CleanXfac, DTA_CellY, 8 * CleanYfac); + } + + static Font OptionFont() + { + return NewSmallFont; + } + + static int OptionHeight() + { + return OptionFont().GetHeight(); + } + + static int OptionWidth(String s) + { + return OptionFont().StringWidth(s); + } + + static void DrawOptionText(int x, int y, int color, String text, bool grayed = false) + { + String label = Stringtable.Localize(text); + int overlay = grayed? Color(96,48,0,0) : 0; + screen.DrawText (OptionFont(), color, x, y, text, DTA_CleanNoMove_1, true, DTA_ColorOverlay, overlay); + } + + +} + +class MenuDescriptor : Object native ui version("2.4") +{ + native Name mMenuName; + native String mNetgameMessage; + native Class mClass; + + native static MenuDescriptor GetDescriptor(Name n); +} + +// This class is only needed to give it a virtual Init method that doesn't belong to Menu itself +class GenericMenu : Menu +{ + virtual void Init(Menu parent) + { + Super.Init(parent); + } +} \ No newline at end of file diff --git a/wadsrc/static/zscript/ui/menu/menuitembase.zs b/wadsrc/static/zscript/ui/menu/menuitembase.zs new file mode 100644 index 000000000..12b13556c --- /dev/null +++ b/wadsrc/static/zscript/ui/menu/menuitembase.zs @@ -0,0 +1,50 @@ +//============================================================================= +// +// base class for menu items +// +//============================================================================= + +class MenuItemBase : Object native ui version("2.4") +{ + protected native double mXpos, mYpos; + protected native Name mAction; + native bool mEnabled; + + void Init(double xpos = 0, double ypos = 0, Name actionname = 'None') + { + mXpos = xpos; + mYpos = ypos; + mAction = actionname; + mEnabled = true; + } + + virtual bool CheckCoordinate(int x, int y) { return false; } + virtual void Ticker() {} + virtual void Drawer(bool selected) {} + virtual bool Selectable() {return false; } + virtual bool Activate() { return false; } + virtual Name, int GetAction() { return mAction, 0; } + virtual bool SetString(int i, String s) { return false; } + virtual bool, String GetString(int i) { return false, ""; } + virtual bool SetValue(int i, int value) { return false; } + virtual bool, int GetValue(int i) { return false, 0; } + virtual void Enable(bool on) { mEnabled = on; } + virtual bool MenuEvent (int mkey, bool fromcontroller) { return false; } + virtual bool MouseEvent(int type, int x, int y) { return false; } + virtual bool CheckHotkey(int c) { return false; } + virtual int GetWidth() { return 0; } + virtual int GetIndent() { return 0; } + virtual int Draw(OptionMenuDescriptor desc, int y, int indent, bool selected) { return indent; } + + void OffsetPositionY(double ydelta) { mYpos += ydelta; } + double GetY() { return mYpos; } + double GetX() { return mXpos; } + void SetX(double x) { mXpos = x; } + virtual void OnMenuCreated() {} +} + +// this is only used to parse font color ranges in MENUDEF +enum MenudefColorRange +{ + NO_COLOR = -1 +} \ No newline at end of file diff --git a/wadsrc/static/zscript/ui/menu/messagebox.zs b/wadsrc/static/zscript/ui/menu/messagebox.zs new file mode 100644 index 000000000..e8a97bf67 --- /dev/null +++ b/wadsrc/static/zscript/ui/menu/messagebox.zs @@ -0,0 +1,318 @@ +/* +** messagebox.cpp +** Confirmation, notification screens +** +**--------------------------------------------------------------------------- +** Copyright 2010-2017 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. +**--------------------------------------------------------------------------- +** +*/ + +class MessageBoxMenu : Menu +{ + BrokenLines mMessage; + voidptr Handler; + int mMessageMode; + int messageSelection; + int mMouseLeft, mMouseRight, mMouseY; + Name mAction; + + Font textFont, arrowFont; + int destWidth, destHeight; + String selector; + + native static void CallHandler(voidptr hnd); + + + //============================================================================= + // + // + // + //============================================================================= + + virtual void Init(Menu parent, String message, int messagemode, bool playsound = false, Name cmd = 'None', voidptr native_handler = null) + { + Super.Init(parent); + mAction = cmd; + messageSelection = 0; + mMouseLeft = 140; + mMouseY = 0x80000000; + textFont = null; + + if (!generic_ui) + { + if (SmallFont && SmallFont.CanPrint(message) && SmallFont.CanPrint("$TXT_YES") && SmallFont.CanPrint("$TXT_NO")) textFont = SmallFont; + else if (OriginalSmallFont && OriginalSmallFont.CanPrint(message) && OriginalSmallFont.CanPrint("$TXT_YES") && OriginalSmallFont.CanPrint("$TXT_NO")) textFont = OriginalSmallFont; + } + + if (!textFont) + { + arrowFont = textFont = NewSmallFont; + int factor = (CleanXfac+1) / 2; + destWidth = screen.GetWidth() / factor; + destHeight = screen.GetHeight() / factor; + selector = "â–¶"; + } + else + { + arrowFont = ConFont; + destWidth = CleanWidth; + destHeight = CleanHeight; + selector = "\xd"; + } + + int mr1 = destWidth/2 + 10 + textFont.StringWidth(Stringtable.Localize("$TXT_YES")); + int mr2 = destWidth/2 + 10 + textFont.StringWidth(Stringtable.Localize("$TXT_NO")); + mMouseRight = MAX(mr1, mr2); + mParentMenu = parent; + mMessage = textFont.BreakLines(Stringtable.Localize(message), generic_ui? 600 : 300); + mMessageMode = messagemode; + if (playsound) + { + MenuSound ("menu/prompt"); + } + Handler = native_handler; + } + + //============================================================================= + // + // + // + //============================================================================= + + override void Drawer () + { + int i, y; + int fontheight = textFont.GetHeight(); + + y = destHeight / 2; + + int c = mMessage.Count(); + y -= c * fontHeight / 2; + + for (i = 0; i < c; i++) + { + screen.DrawText (textFont, Font.CR_UNTRANSLATED, destWidth/2 - mMessage.StringWidth(i)/2, y, mMessage.StringAt(i), DTA_VirtualWidth, destWidth, DTA_VirtualHeight, destHeight, DTA_KeepRatio, true); + y += fontheight; + } + + if (mMessageMode == 0) + { + y += fontheight; + mMouseY = y; + screen.DrawText(textFont, messageSelection == 0? OptionMenuSettings.mFontColorSelection : OptionMenuSettings.mFontColor, destWidth / 2, y, Stringtable.Localize("$TXT_YES"), DTA_VirtualWidth, destWidth, DTA_VirtualHeight, destHeight, DTA_KeepRatio, true); + screen.DrawText(textFont, messageSelection == 1? OptionMenuSettings.mFontColorSelection : OptionMenuSettings.mFontColor, destWidth / 2, y + fontheight, Stringtable.Localize("$TXT_NO"), DTA_VirtualWidth, destWidth, DTA_VirtualHeight, destHeight, DTA_KeepRatio, true); + + if (messageSelection >= 0) + { + if ((MenuTime() % 8) < 6) + { + screen.DrawText(arrowFont, OptionMenuSettings.mFontColorSelection, + destWidth/2 - 11, y + fontheight * messageSelection, selector, DTA_VirtualWidth, destWidth, DTA_VirtualHeight, destHeight, DTA_KeepRatio, true); + } + } + } + } + + + //============================================================================= + // + // + // + //============================================================================= + + protected void CloseSound() + { + MenuSound (GetCurrentMenu() != NULL? "menu/backup" : "menu/dismiss"); + } + + //============================================================================= + // + // + // + //============================================================================= + + virtual void HandleResult(bool res) + { + if (Handler != null) + { + if (res) + { + CallHandler(Handler); + } + else + { + Close(); + CloseSound(); + } + } + else if (mParentMenu != NULL) + { + if (mMessageMode == 0) + { + if (mAction == 'None') + { + mParentMenu.MenuEvent(res? MKEY_MBYes : MKEY_MBNo, false); + Close(); + } + else + { + Close(); + if (res) SetMenu(mAction, -1); + } + CloseSound(); + } + } + } + + //============================================================================= + // + // + // + //============================================================================= + + override bool OnUIEvent(UIEvent ev) + { + if (ev.type == UIEvent.Type_KeyDown) + { + if (mMessageMode == 0) + { + // tolower + int ch = ev.KeyChar; + ch = ch >= 65 && ch <91? ch + 32 : ch; + + if (ch == 110 /*'n'*/ || ch == 32) + { + HandleResult(false); + return true; + } + else if (ch == 121 /*'y'*/) + { + HandleResult(true); + return true; + } + } + else + { + Close(); + return true; + } + return false; + } + return Super.OnUIEvent(ev); + } + + override bool OnInputEvent(InputEvent ev) + { + if (ev.type == InputEvent.Type_KeyDown) + { + Close(); + return true; + } + return Super.OnInputEvent(ev); + } + + //============================================================================= + // + // + // + //============================================================================= + + override bool MenuEvent(int mkey, bool fromcontroller) + { + if (mMessageMode == 0) + { + if (mkey == MKEY_Up || mkey == MKEY_Down) + { + MenuSound("menu/cursor"); + messageSelection = !messageSelection; + return true; + } + else if (mkey == MKEY_Enter) + { + // 0 is yes, 1 is no + HandleResult(!messageSelection); + return true; + } + else if (mkey == MKEY_Back) + { + HandleResult(false); + return true; + } + return false; + } + else + { + Close(); + CloseSound(); + return true; + } + } + + //============================================================================= + // + // + // + //============================================================================= + + override bool MouseEvent(int type, int x, int y) + { + if (mMessageMode == 1) + { + if (type == MOUSE_Click) + { + return MenuEvent(MKEY_Enter, true); + } + return false; + } + else + { + int sel = -1; + int fh = textFont.GetHeight() + 1; + + // convert x/y from screen to virtual coordinates, according to CleanX/Yfac use in DrawTexture + x = x * destWidth / screen.GetWidth(); + y = y * destHeight / screen.GetHeight(); + + if (x >= mMouseLeft && x <= mMouseRight && y >= mMouseY && y < mMouseY + 2 * fh) + { + sel = y >= mMouseY + fh; + } + messageSelection = sel; + if (type == MOUSE_Release) + { + return MenuEvent(MKEY_Enter, true); + } + return true; + } + } + + +} + + + diff --git a/wadsrc/static/zscript/ui/menu/optionmenu.zs b/wadsrc/static/zscript/ui/menu/optionmenu.zs new file mode 100644 index 000000000..20ae748ae --- /dev/null +++ b/wadsrc/static/zscript/ui/menu/optionmenu.zs @@ -0,0 +1,601 @@ +/* +** optionmenu.cpp +** Handler class for the option menus and associated items +** +**--------------------------------------------------------------------------- +** Copyright 2010-2017 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. +**--------------------------------------------------------------------------- +** +*/ + +struct FOptionMenuSettings native version("2.4") +{ + int mTitleColor; + int mFontColor; + int mFontColorValue; + int mFontColorMore; + int mFontColorHeader; + int mFontColorHighlight; + int mFontColorSelection; + int mLinespacing; +} + +class OptionMenuDescriptor : MenuDescriptor native +{ + native Array mItems; + native String mTitle; + native int mSelectedItem; + native int mDrawTop; + native int mScrollTop; + native int mScrollPos; + native int mIndent; + native int mPosition; + native bool mDontDim; + native Font mFont; + + void Reset() + { + // Reset the default settings (ignore all other values in the struct) + mPosition = 0; + mScrollTop = 0; + mIndent = 0; + mDontDim = 0; + } + + //============================================================================= + // + // + // + //============================================================================= + + void CalcIndent() + { + // calculate the menu indent + int widest = 0, thiswidth; + + for (int i = 0; i < mItems.Size(); i++) + { + thiswidth = mItems[i].GetIndent(); + if (thiswidth > widest) widest = thiswidth; + } + mIndent = widest + 4; + } +} + + +class OptionMenu : Menu +{ + OptionMenuDescriptor mDesc; + bool CanScrollUp; + bool CanScrollDown; + int VisBottom; + OptionMenuItem mFocusControl; + + //============================================================================= + // + // + // + //============================================================================= + + virtual void Init(Menu parent, OptionMenuDescriptor desc) + { + mParentMenu = parent; + mDesc = desc; + DontDim = desc.mDontDim; + + let itemCount = mDesc.mItems.size(); + if (itemCount > 0) + { + let last = mDesc.mItems[itemCount - 1]; + bool lastIsText = (last is "OptionMenuItemStaticText"); + if (lastIsText) + { + String text = last.mLabel; + bool lastIsSpace = (text == "" || text == " "); + if (lastIsSpace) + { + mDesc.mItems.Pop(); + } + } + } + + if (mDesc.mSelectedItem == -1) mDesc.mSelectedItem = FirstSelectable(); + mDesc.CalcIndent(); + + // notify all items that the menu was just created. + for(int i=0;i=0 && i < mDesc.mItems.Size()) return i; + else return -1; + } + + //============================================================================= + // + // + // + //============================================================================= + + override bool OnUIEvent(UIEvent ev) + { + if (ev.type == UIEvent.Type_WheelUp) + { + int scrollamt = MIN(2, mDesc.mScrollPos); + mDesc.mScrollPos -= scrollamt; + return true; + } + else if (ev.type == UIEvent.Type_WheelDown) + { + if (CanScrollDown) + { + if (VisBottom >= 0 && VisBottom < (mDesc.mItems.Size()-2)) + { + mDesc.mScrollPos += 2; + VisBottom += 2; + } + else + { + mDesc.mScrollPos++; + VisBottom++; + } + } + return true; + } + return Super.OnUIEvent(ev); + } + + //============================================================================= + // + // + // + //============================================================================= + + override bool MenuEvent (int mkey, bool fromcontroller) + { + int startedAt = mDesc.mSelectedItem; + + switch (mkey) + { + case MKEY_Up: + if (mDesc.mSelectedItem == -1) + { + mDesc.mSelectedItem = FirstSelectable(); + break; + } + do + { + --mDesc.mSelectedItem; + + if (mDesc.mScrollPos > 0 && + mDesc.mSelectedItem <= mDesc.mScrollTop + mDesc.mScrollPos) + { + mDesc.mScrollPos = MAX(mDesc.mSelectedItem - mDesc.mScrollTop - 1, 0); + } + + if (mDesc.mSelectedItem < 0) + { + // Figure out how many lines of text fit on the menu + int y = mDesc.mPosition; + + if (y <= 0) + { + let font = generic_ui || !mDesc.mFont? NewSmallFont : mDesc.mFont; + if (font && mDesc.mTitle.Length() > 0) + { + y = -y + font.GetHeight(); + } + else + { + y = -y; + } + } + y *= CleanYfac_1; + int rowheight = OptionMenuSettings.mLinespacing * CleanYfac_1; + int maxitems = (screen.GetHeight() - rowheight - y) / rowheight + 1; + + mDesc.mScrollPos = MAX (0, mDesc.mItems.Size() - maxitems + mDesc.mScrollTop); + mDesc.mSelectedItem = mDesc.mItems.Size()-1; + } + } + while (!mDesc.mItems[mDesc.mSelectedItem].Selectable() && mDesc.mSelectedItem != startedAt); + break; + + case MKEY_Down: + if (mDesc.mSelectedItem == -1) + { + mDesc.mSelectedItem = FirstSelectable(); + break; + } + do + { + ++mDesc.mSelectedItem; + + if (CanScrollDown && mDesc.mSelectedItem == VisBottom) + { + mDesc.mScrollPos++; + VisBottom++; + } + if (mDesc.mSelectedItem >= mDesc.mItems.Size()) + { + if (startedAt == -1) + { + mDesc.mSelectedItem = -1; + mDesc.mScrollPos = -1; + break; + } + else + { + mDesc.mSelectedItem = 0; + mDesc.mScrollPos = 0; + } + } + } + while (!mDesc.mItems[mDesc.mSelectedItem].Selectable() && mDesc.mSelectedItem != startedAt); + break; + + case MKEY_PageUp: + if (mDesc.mScrollPos > 0) + { + mDesc.mScrollPos -= VisBottom - mDesc.mScrollPos - mDesc.mScrollTop; + if (mDesc.mScrollPos < 0) + { + mDesc.mScrollPos = 0; + } + if (mDesc.mSelectedItem != -1) + { + mDesc.mSelectedItem = mDesc.mScrollTop + mDesc.mScrollPos + 1; + while (!mDesc.mItems[mDesc.mSelectedItem].Selectable()) + { + if (++mDesc.mSelectedItem >= mDesc.mItems.Size()) + { + mDesc.mSelectedItem = 0; + } + } + if (mDesc.mScrollPos > mDesc.mSelectedItem) + { + mDesc.mScrollPos = mDesc.mSelectedItem; + } + } + } + break; + + case MKEY_PageDown: + if (CanScrollDown) + { + int pagesize = VisBottom - mDesc.mScrollPos - mDesc.mScrollTop; + mDesc.mScrollPos += pagesize; + if (mDesc.mScrollPos + mDesc.mScrollTop + pagesize > mDesc.mItems.Size()) + { + mDesc.mScrollPos = mDesc.mItems.Size() - mDesc.mScrollTop - pagesize; + } + if (mDesc.mSelectedItem != -1) + { + mDesc.mSelectedItem = mDesc.mScrollTop + mDesc.mScrollPos; + while (!mDesc.mItems[mDesc.mSelectedItem].Selectable()) + { + if (++mDesc.mSelectedItem >= mDesc.mItems.Size()) + { + mDesc.mSelectedItem = 0; + } + } + if (mDesc.mScrollPos > mDesc.mSelectedItem) + { + mDesc.mScrollPos = mDesc.mSelectedItem; + } + } + } + break; + + case MKEY_Enter: + if (mDesc.mSelectedItem >= 0 && mDesc.mItems[mDesc.mSelectedItem].Activate()) + { + return true; + } + // fall through to default + default: + if (mDesc.mSelectedItem >= 0 && + mDesc.mItems[mDesc.mSelectedItem].MenuEvent(mkey, fromcontroller)) return true; + return Super.MenuEvent(mkey, fromcontroller); + } + + if (mDesc.mSelectedItem != startedAt) + { + MenuSound ("menu/cursor"); + } + return true; + } + + + //============================================================================= + // + // + // + //============================================================================= + + override bool MouseEvent(int type, int x, int y) + { + y = (y / CleanYfac_1) - mDesc.mDrawTop; + + if (mFocusControl) + { + mFocusControl.MouseEvent(type, x, y); + return true; + } + else + { + int yline = (y / OptionMenuSettings.mLinespacing); + if (yline >= mDesc.mScrollTop) + { + yline += mDesc.mScrollPos; + } + if (yline >= 0 && yline < mDesc.mItems.Size() && mDesc.mItems[yline].Selectable()) + { + if (yline != mDesc.mSelectedItem) + { + mDesc.mSelectedItem = yline; + } + mDesc.mItems[yline].MouseEvent(type, x, y); + return true; + } + } + mDesc.mSelectedItem = -1; + return Super.MouseEvent(type, x, y); + } + + + //============================================================================= + // + // + // + //============================================================================= + + override void Ticker () + { + Super.Ticker(); + for(int i = 0; i < mDesc.mItems.Size(); i++) + { + mDesc.mItems[i].Ticker(); + } + } + + //============================================================================= + // + // + // + //============================================================================= + + virtual int GetIndent() + { + int indent = max(0, (mDesc.mIndent + 40) - CleanWidth_1 / 2); + return screen.GetWidth() / 2 + indent * CleanXfac_1; + } + + override void Drawer () + { + int y = mDesc.mPosition; + + if (y <= 0) + { + let font = generic_ui || !mDesc.mFont? NewSmallFont : mDesc.mFont; + if (font && mDesc.mTitle.Length() > 0) + { + let tt = Stringtable.Localize(mDesc.mTitle); + screen.DrawText (font, OptionMenuSettings.mTitleColor, + (screen.GetWidth() - font.StringWidth(tt) * CleanXfac_1) / 2, 10*CleanYfac_1, + tt, DTA_CleanNoMove_1, true); + y = -y + font.GetHeight(); + } + else + { + y = -y; + } + } + mDesc.mDrawTop = y; + int fontheight = OptionMenuSettings.mLinespacing * CleanYfac_1; + y *= CleanYfac_1; + + int indent = GetIndent(); + + int ytop = y + mDesc.mScrollTop * 8 * CleanYfac_1; + int lastrow = screen.GetHeight() - OptionHeight() * CleanYfac_1; + + int i; + for (i = 0; i < mDesc.mItems.Size() && y <= lastrow; i++) + { + // Don't scroll the uppermost items + if (i == mDesc.mScrollTop) + { + i += mDesc.mScrollPos; + if (i >= mDesc.mItems.Size()) break; // skipped beyond end of menu + } + bool isSelected = mDesc.mSelectedItem == i; + int cur_indent = mDesc.mItems[i].Draw(mDesc, y, indent, isSelected); + if (cur_indent >= 0 && isSelected && mDesc.mItems[i].Selectable()) + { + if (((MenuTime() % 8) < 6) || GetCurrentMenu() != self) + { + DrawOptionText(cur_indent + 3 * CleanXfac_1, y, OptionMenuSettings.mFontColorSelection, "â—„"); + } + } + y += fontheight; + } + + CanScrollUp = (mDesc.mScrollPos > 0); + CanScrollDown = (i < mDesc.mItems.Size()); + VisBottom = i - 1; + + if (CanScrollUp) + { + DrawOptionText(screen.GetWidth() - 11 * CleanXfac_1, ytop, OptionMenuSettings.mFontColorSelection, "â–²"); + } + if (CanScrollDown) + { + DrawOptionText(screen.GetWidth() - 11 * CleanXfac_1 , y - 8*CleanYfac_1, OptionMenuSettings.mFontColorSelection, "â–¼"); + } + Super.Drawer(); + } + + //============================================================================= + // + // + // + //============================================================================= + + override void SetFocus(MenuItemBase fc) + { + mFocusControl = OptionMenuItem(fc); + } + + override bool CheckFocus(MenuItemBase fc) + { + return mFocusControl == fc; + } + + override void ReleaseFocus() + { + mFocusControl = NULL; + } +} + + +class GameplayMenu : OptionMenu +{ + override void Drawer () + { + Super.Drawer(); + + String s = String.Format("dmflags = %d dmflags2 = %d", dmflags, dmflags2); + screen.DrawText (OptionFont(), OptionMenuSettings.mFontColorValue, + (screen.GetWidth() - OptionWidth (s) * CleanXfac_1) / 2, 35 * CleanXfac_1, s, + DTA_CleanNoMove_1, true); + } +} + +class CompatibilityMenu : OptionMenu +{ + override void Drawer () + { + Super.Drawer(); + + String s = String.Format("compatflags = %d compatflags2 = %d", compatflags, compatflags2); + screen.DrawText (OptionFont(), OptionMenuSettings.mFontColorValue, + (screen.GetWidth() - OptionWidth (s) * CleanXfac_1) / 2, 35 * CleanXfac_1, s, + DTA_CleanNoMove_1, true); + } +} + +class GLTextureGLOptions : OptionMenu +{ + private int mWarningIndex; + private string mWarningLabel; + + override void Init(Menu parent, OptionMenuDescriptor desc) + { + super.Init(parent, desc); + + // Find index of warning item placeholder + mWarningIndex = -1; + mWarningLabel = "!HQRESIZE_WARNING!"; + + for (int i=0; i < mDesc.mItems.Size(); ++i) + { + if (mDesc.mItems[i].mLabel == mWarningLabel) + { + mWarningIndex = i; + break; + } + } + } + + override void OnDestroy() + { + // Restore warning item placeholder + if (mWarningIndex >= 0) + { + mDesc.mItems[mWarningIndex].mLabel = mWarningLabel; + } + + Super.OnDestroy(); + } + + override void Ticker() + { + Super.Ticker(); + + if (mWarningIndex >= 0) + { + string message; + + if (gl_texture_hqresizemult > 1 && gl_texture_hqresizemode > 0) + { + int multiplier = gl_texture_hqresizemult * gl_texture_hqresizemult; + + message = StringTable.Localize("$GLTEXMNU_HQRESIZEWARN"); + string mult = String.Format("%d", multiplier); + message.Replace("%d", mult); + } + + mDesc.mItems[mWarningIndex].mLabel = Font.TEXTCOLOR_CYAN .. message; + } + } +} diff --git a/wadsrc/static/zscript/ui/menu/optionmenuitems.zs b/wadsrc/static/zscript/ui/menu/optionmenuitems.zs new file mode 100644 index 000000000..799d6b455 --- /dev/null +++ b/wadsrc/static/zscript/ui/menu/optionmenuitems.zs @@ -0,0 +1,1275 @@ +/* +** optionmenuitems.txt +** Control items for option menus +** +**--------------------------------------------------------------------------- +** Copyright 2010-2017 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. +**--------------------------------------------------------------------------- +** +*/ + +class OptionMenuItem : MenuItemBase +{ + String mLabel; + bool mCentered; + + protected void Init(String label, String command, bool center = false) + { + Super.Init(0, 0, command); + mLabel = label; + mCentered = center; + } + + protected void drawText(int x, int y, int color, String text, bool grayed = false) + { + Menu.DrawOptionText(x, y, color, text, grayed); + } + + protected int drawLabel(int indent, int y, int color, bool grayed = false) + { + String label = Stringtable.Localize(mLabel); + + int x; + int w = Menu.OptionWidth(label) * CleanXfac_1; + if (!mCentered) x = indent - w; + else x = (screen.GetWidth() - w) / 2; + Menu.DrawOptionText(x, y, color, label, grayed); + return x; + } + + protected void drawValue(int indent, int y, int color, String text, bool grayed = false) + { + Menu.DrawOptionText(indent + CursorSpace(), y, color, text, grayed); + } + + + int CursorSpace() + { + return (14 * CleanXfac_1); + } + + override bool Selectable() + { + return true; + } + + override int GetIndent() + { + if (mCentered) return 0; + if (screen.GetWidth() < 640) return screen.GetWidth() / 2; + return Menu.OptionWidth(Stringtable.Localize(mLabel)); + } + + override bool MouseEvent(int type, int x, int y) +{ + if (Selectable() && type == Menu.MOUSE_Release) + { + return Menu.GetCurrentMenu().MenuEvent(Menu.MKEY_Enter, true); + } + return false; + } +} + +//============================================================================= +// +// opens a submenu, command is a submenu name +// +//============================================================================= + +class OptionMenuItemSubmenu : OptionMenuItem +{ + int mParam; + OptionMenuItemSubmenu Init(String label, Name command, int param = 0, bool centered = false) + { + Super.init(label, command, centered); + mParam = param; + return self; + } + + override int Draw(OptionMenuDescriptor desc, int y, int indent, bool selected) + { + int x = drawLabel(indent, y, selected? OptionMenuSettings.mFontColorSelection : OptionMenuSettings.mFontColorMore); + if (mCentered) + { + return x - 16*CleanXfac_1; + } + return indent; + } + + override bool Activate() + { + Menu.MenuSound("menu/choose"); + Menu.SetMenu(mAction, mParam); + return true; + } +} + +//============================================================================= +// +// opens a submenu, command is a submenu name +// +//============================================================================= + +class OptionMenuItemLabeledSubmenu : OptionMenuItemSubmenu +{ + CVar mLabelCVar; + OptionMenuItemSubmenu Init(String label, CVar labelcvar, Name command, int param = 0) + { + Super.init(label, command, false); + mLabelCVar = labelcvar; + return self; + } + + override int Draw(OptionMenuDescriptor desc, int y, int indent, bool selected) + { + drawLabel(indent, y, selected? OptionMenuSettings.mFontColorSelection : OptionMenuSettings.mFontColor); + + String text = mLabelCVar.GetString(); + if (text.Length() == 0) text = Stringtable.Localize("$notset"); + drawValue(indent, y, OptionMenuSettings.mFontColorValue, text); + return indent; + } +} + +//============================================================================= +// +// Executes a CCMD, command is a CCMD name +// +//============================================================================= + +class OptionMenuItemCommand : OptionMenuItemSubmenu +{ + private String ccmd; // do not allow access to this from the outside. + bool mCloseOnSelect; + private bool mUnsafe; + + OptionMenuItemCommand Init(String label, Name command, bool centered = false, bool closeonselect = false) + { + Super.Init(label, command, 0, centered); + ccmd = command; + mCloseOnSelect = closeonselect; + mUnsafe = true; + return self; + } + + private native static void DoCommand(String cmd, bool unsafe); // This is very intentionally limited to this menu item to prevent abuse. + + override bool Activate() + { + // This needs to perform a few checks to prevent abuse by malicious modders. + if (GetClass() != "OptionMenuItemSafeCommand") + { + let m = OptionMenu(Menu.GetCurrentMenu()); + // don't execute if no menu is active + if (m == null) return false; + // don't execute if this item cannot be found in the current menu. + if (m.GetItem(mAction) != self) return false; + } + else mUnsafe = false; + Menu.MenuSound("menu/choose"); + DoCommand(ccmd, mUnsafe); + if (mCloseOnSelect) + { + let curmenu = Menu.GetCurrentMenu(); + if (curmenu != null) curmenu.Close(); + } + return true; + } + +} + +//============================================================================= +// +// Executes a CCMD after confirmation, command is a CCMD name +// +//============================================================================= + +class OptionMenuItemSafeCommand : OptionMenuItemCommand +{ + String mPrompt; + + + OptionMenuItemSafeCommand Init(String label, Name command, String prompt = "") + { + Super.Init(label, command); + mPrompt = prompt; + return self; + } + + override bool MenuEvent (int mkey, bool fromcontroller) + { + if (mkey == Menu.MKEY_MBYes) + { + Super.Activate(); + return true; + } + return Super.MenuEvent(mkey, fromcontroller); + } + + override bool Activate() + { + String msg = mPrompt.Length() > 0 ? mPrompt : "$SAFEMESSAGE"; + msg = StringTable.Localize(msg); + String actionLabel = StringTable.localize(mLabel); + + String FullString; + FullString = String.Format("%s%s%s\n\n%s", TEXTCOLOR_WHITE, actionLabel, TEXTCOLOR_NORMAL, msg); + Menu.StartMessage(FullString, 0); + return true; + } +} + +//============================================================================= +// +// Base class for option lists +// +//============================================================================= + +class OptionMenuItemOptionBase : OptionMenuItem +{ + // command is a CVAR + Name mValues; // Entry in OptionValues table + CVar mGrayCheck; + int mCenter; + + const OP_VALUES = 0x11001; + + protected void Init(String label, Name command, Name values, CVar graycheck, int center) + { + Super.Init(label, command); + mValues = values; + mGrayCheck = graycheck; + mCenter = center; + } + + override bool SetString(int i, String newtext) + { + if (i == OP_VALUES) + { + int cnt = OptionValues.GetCount(mValues); + if (cnt >= 0) + { + mValues = newtext; + int s = GetSelection(); + if (s >= cnt) s = 0; + SetSelection(s); // readjust the CVAR if its value is outside the range now + return true; + } + } + return false; + } + + //============================================================================= + virtual int GetSelection() + { + return 0; + } + + virtual void SetSelection(int Selection) + { + } + + //============================================================================= + override int Draw(OptionMenuDescriptor desc, int y, int indent, bool selected) + { + if (mCenter) + { + indent = (screen.GetWidth() / 2); + } + drawLabel(indent, y, selected? OptionMenuSettings.mFontColorSelection : OptionMenuSettings.mFontColor, isGrayed()); + + int Selection = GetSelection(); + String text = StringTable.Localize(OptionValues.GetText(mValues, Selection)); + if (text.Length() == 0) text = "Unknown"; + drawValue(indent, y, OptionMenuSettings.mFontColorValue, text, isGrayed()); + return indent; + } + + //============================================================================= + override bool MenuEvent (int mkey, bool fromcontroller) + { + int cnt = OptionValues.GetCount(mValues); + if (cnt > 0) + { + int Selection = GetSelection(); + if (mkey == Menu.MKEY_Left) + { + if (Selection == -1) Selection = 0; + else if (--Selection < 0) Selection = cnt - 1; + } + else if (mkey == Menu.MKEY_Right || mkey == Menu.MKEY_Enter) + { + if (++Selection >= cnt) Selection = 0; + } + else + { + return Super.MenuEvent(mkey, fromcontroller); + } + SetSelection(Selection); + Menu.MenuSound("menu/change"); + } + else + { + return Super.MenuEvent(mkey, fromcontroller); + } + return true; + } + + virtual bool isGrayed() + { + return mGrayCheck != null && !mGrayCheck.GetInt(); + } + + override bool Selectable() + { + return !isGrayed(); + } +} + +//============================================================================= +// +// Change a CVAR, command is the CVAR name +// +//============================================================================= + +class OptionMenuItemOption : OptionMenuItemOptionBase +{ + CVar mCVar; + + private static native void SetCVarDescription(CVar cv, String label); + + OptionMenuItemOption Init(String label, Name command, Name values, CVar graycheck = null, int center = 0) + { + Super.Init(label, command, values, graycheck, center); + mCVar = CVar.FindCVar(mAction); + if (mCVar) SetCVarDescription(mCVar, label); + return self; + } + + //============================================================================= + override int GetSelection() + { + int Selection = -1; + int cnt = OptionValues.GetCount(mValues); + if (cnt > 0 && mCVar != null) + { + if (OptionValues.GetTextValue(mValues, 0).Length() == 0) + { + let f = mCVar.GetFloat(); + for(int i = 0; i < cnt; i++) + { + if (f ~== OptionValues.GetValue(mValues, i)) + { + Selection = i; + break; + } + } + } + else + { + String cv = mCVar.GetString(); + for(int i = 0; i < cnt; i++) + { + if (cv ~== OptionValues.GetTextValue(mValues, i)) + { + Selection = i; + break; + } + } + } + } + return Selection; + } + + override void SetSelection(int Selection) + { + int cnt = OptionValues.GetCount(mValues); + if (cnt > 0 && mCVar != null) + { + if (OptionValues.GetTextValue(mValues, 0).Length() == 0) + { + mCVar.SetFloat(OptionValues.GetValue(mValues, Selection)); + } + else + { + mCVar.SetString(OptionValues.GetTextValue(mValues, Selection)); + } + } + } +} + +//============================================================================= +// +// This class is used to capture the key to be used as the new key binding +// for a control item +// +//============================================================================= + +class EnterKey : Menu +{ + OptionMenuItemControlBase mOwner; + + void Init(Menu parent, OptionMenuItemControlBase owner) + { + Super.Init(parent); + mOwner = owner; + SetMenuMessage(1); + menuactive = Menu.WaitKey; // There should be a better way to disable GUI capture... + } + + override bool TranslateKeyboardEvents() + { + return false; + } + + private void SetMenuMessage(int which) + { + let parent = OptionMenu(mParentMenu); + if (parent != null) + { + let it = parent.GetItem('Controlmessage'); + if (it != null) + { + it.SetValue(0, which); + } + } + } + + override bool OnInputEvent(InputEvent ev) + { + // This menu checks raw keys, not GUI keys because it needs the raw codes for binding. + if (ev.type == InputEvent.Type_KeyDown) + { + mOwner.SendKey(ev.KeyScan); + menuactive = Menu.On; + SetMenuMessage(0); + Close(); + mParentMenu.MenuEvent((ev.KeyScan == InputEvent.KEY_ESCAPE)? Menu.MKEY_Abort : Menu.MKEY_Input, 0); + return true; + } + return false; + } + + override void Drawer() + { + mParentMenu.Drawer(); + } +} + +//============================================================================= +// +// // Edit a key binding, Action is the CCMD to bind +// +//============================================================================= + +class OptionMenuItemControlBase : OptionMenuItem +{ + KeyBindings mBindings; + int mInput; + bool mWaiting; + + protected void Init(String label, Name command, KeyBindings bindings) + { + Super.init(label, command); + mBindings = bindings; + mWaiting = false; + } + + //============================================================================= + override int Draw(OptionMenuDescriptor desc, int y, int indent, bool selected) + { + drawLabel(indent, y, mWaiting? OptionMenuSettings.mFontColorHighlight: + (selected? OptionMenuSettings.mFontColorSelection : OptionMenuSettings.mFontColor)); + + String description; + int Key1, Key2; + + [Key1, Key2] = mBindings.GetKeysForCommand(mAction); + description = KeyBindings.NameKeys (Key1, Key2); + if (description.Length() > 0) + { + drawValue(indent, y, Font.CR_WHITE, description); + } + else + { + drawValue(indent, y, Font.CR_BLACK, "---"); + } + return indent; + } + + //============================================================================= + override bool MenuEvent(int mkey, bool fromcontroller) + { + if (mkey == Menu.MKEY_Input) + { + mWaiting = false; + mBindings.SetBind(mInput, mAction); + return true; + } + else if (mkey == Menu.MKEY_Clear) + { + mBindings.UnbindACommand(mAction); + return true; + } + else if (mkey == Menu.MKEY_Abort) + { + mWaiting = false; + return true; + } + return false; + } + + void SendKey(int key) + { + mInput = key; + } + + override bool Activate() + { + Menu.MenuSound("menu/choose"); + mWaiting = true; + let input = new("EnterKey"); + input.Init(Menu.GetCurrentMenu(), self); + input.ActivateMenu(); + return true; + } +} + +class OptionMenuItemControl : OptionMenuItemControlBase +{ + OptionMenuItemControl Init(String label, Name command) + { + Super.Init(label, command, Bindings); + return self; + } +} + +class OptionMenuItemMapControl : OptionMenuItemControlBase +{ + OptionMenuItemMapControl Init(String label, Name command) + { + Super.Init(label, command, AutomapBindings); + return self; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +class OptionMenuItemStaticText : OptionMenuItem +{ + int mColor; + + // this function is only for use from MENUDEF, it needs to do some strange things with the color for backwards compatibility. + OptionMenuItemStaticText Init(String label, int cr = -1) + { + Super.Init(label, 'None', true); + mColor = OptionMenuSettings.mFontColor; + if ((cr & 0xffff0000) == 0x12340000) mColor = cr & 0xffff; + else if (cr > 0) mColor = OptionMenuSettings.mFontColorHeader; + return self; + } + + OptionMenuItemStaticText InitDirect(String label, int cr) + { + Super.Init(label, 'None', true); + mColor = cr; + return self; + } + + override int Draw(OptionMenuDescriptor desc, int y, int indent, bool selected) + { + drawLabel(indent, y, mColor); + return -1; + } + + override bool Selectable() + { + return false; + } + +} + +//============================================================================= +// +// +// +//============================================================================= + +class OptionMenuItemStaticTextSwitchable : OptionMenuItem +{ + int mColor; + String mAltText; + int mCurrent; + + // this function is only for use from MENUDEF, it needs to do some strange things with the color for backwards compatibility. + OptionMenuItemStaticTextSwitchable Init(String label, String label2, Name command, int cr = -1) + { + Super.Init(label, command, true); + mAltText = label2; + mCurrent = 0; + + mColor = OptionMenuSettings.mFontColor; + if ((cr & 0xffff0000) == 0x12340000) mColor = cr & 0xffff; + else if (cr > 0) mColor = OptionMenuSettings.mFontColorHeader; + return self; + } + + OptionMenuItemStaticTextSwitchable InitDirect(String label, String label2, Name command, int cr) + { + Super.Init(label, command, true); + mColor = cr; + mAltText = label2; + mCurrent = 0; + return self; + } + + override int Draw(OptionMenuDescriptor desc, int y, int indent, bool selected) + { + String txt = StringTable.Localize(mCurrent? mAltText : mLabel); + int w = Menu.OptionWidth(txt) * CleanXfac_1; + int x = (screen.GetWidth() - w) / 2; + drawText(x, y, mColor, txt); + return -1; + } + + override bool SetValue(int i, int val) + { + if (i == 0) + { + mCurrent = val; + return true; + } + return false; + } + + override bool SetString(int i, String newtext) + { + if (i == 0) + { + mAltText = newtext; + return true; + } + return false; + } + + override bool Selectable() + { + return false; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +class OptionMenuSliderBase : OptionMenuItem +{ + // command is a CVAR + double mMin, mMax, mStep; + int mShowValue; + int mDrawX; + int mSliderShort; + CVar mGrayCheck; + + protected void Init(String label, double min, double max, double step, int showval, Name command = 'none', CVar graycheck = NULL) + { + Super.Init(label, command); + mMin = min; + mMax = max; + mStep = step; + mShowValue = showval; + mDrawX = 0; + mSliderShort = 0; + mGrayCheck = graycheck; + } + + virtual double GetSliderValue() + { + return 0; + } + + virtual void SetSliderValue(double val) + { + } + + bool IsGrayed(void) + { + return mGrayCheck != NULL && !mGrayCheck.GetInt(); + } + + override bool Selectable(void) + { + return !IsGrayed(); + } + + //============================================================================= + // + // Draw a slider. Set fracdigits negative to not display the current value numerically. + // + //============================================================================= + + private void DrawSliderElement (int color, int x, int y, String str, bool grayed = false) + { + int overlay = grayed? Color(96, 48, 0, 0) : 0; + screen.DrawText (ConFont, color, x, y, str, DTA_CellX, 16 * CleanXfac_1, DTA_CellY, 16 * CleanYfac_1, DTA_ColorOverlay, overlay); + } + + protected void DrawSlider (int x, int y, double min, double max, double cur, int fracdigits, int indent, bool grayed = false) + { + String formater = String.format("%%.%df", fracdigits); // The format function cannot do the '%.*f' syntax. + String textbuf; + double range; + int maxlen = 0; + int right = x + (12*16 + 4) * CleanXfac_1; // length of slider. This uses the old ConFont and + int cy = y + CleanYFac; + + range = max - min; + double ccur = clamp(cur, min, max) - min; + + if (fracdigits >= 0) + { + textbuf = String.format(formater, max); + maxlen = Menu.OptionWidth(textbuf) * CleanXfac_1; + } + + mSliderShort = right + maxlen > screen.GetWidth(); + + if (!mSliderShort) + { + DrawSliderElement(Font.CR_WHITE, x, cy, "\x10\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x12", grayed); + DrawSliderElement(Font.FindFontColor(gameinfo.mSliderColor), x + int((5 + ((ccur * 78) / range)) * 2 * CleanXfac_1), cy, "\x13", grayed); + } + else + { + // On 320x200 we need a shorter slider + DrawSliderElement(Font.CR_WHITE, x, cy, "\x10\x11\x11\x11\x11\x11\x12", grayed); + DrawSliderElement(Font.FindFontColor(gameinfo.mSliderColor), x + int((5 + ((ccur * 38) / range)) * 2 * CleanXfac_1), cy, "\x13", grayed); + right -= 5*8*CleanXfac; + } + + if (fracdigits >= 0 && right + maxlen <= screen.GetWidth()) + { + textbuf = String.format(formater, cur); + drawText(right, y, Font.CR_DARKGRAY, textbuf, grayed); + } + } + + + //============================================================================= + override int Draw(OptionMenuDescriptor desc, int y, int indent, bool selected) + { + drawLabel(indent, y, selected? OptionMenuSettings.mFontColorSelection : OptionMenuSettings.mFontColor, IsGrayed()); + mDrawX = indent + CursorSpace(); + DrawSlider (mDrawX, y, mMin, mMax, GetSliderValue(), mShowValue, indent, IsGrayed()); + return indent; + } + + //============================================================================= + override bool MenuEvent (int mkey, bool fromcontroller) + { + double value = GetSliderValue(); + + if (mkey == Menu.MKEY_Left) + { + value -= mStep; + } + else if (mkey == Menu.MKEY_Right) + { + value += mStep; + } + else + { + return OptionMenuItem.MenuEvent(mkey, fromcontroller); + } + if (value ~== 0) value = 0; // This is to prevent formatting anomalies with very small values + SetSliderValue(clamp(value, mMin, mMax)); + Menu.MenuSound("menu/change"); + return true; + } + + override bool MouseEvent(int type, int x, int y) + { + let lm = OptionMenu(Menu.GetCurrentMenu()); + if (type != Menu.MOUSE_Click) + { + if (!lm.CheckFocus(self)) return false; + } + if (type == Menu.MOUSE_Release) + { + lm.ReleaseFocus(); + } + + int slide_left = mDrawX+16*CleanXfac_1; + int slide_right = slide_left + (10*16*CleanXfac_1 >> mSliderShort); // 10 char cells with 16 pixels each. + + if (type == Menu.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); + //Menu.MenuSound("menu/change"); + } + if (type == Menu.MOUSE_Click) + { + lm.SetFocus(self); + } + return true; + } + +} + +//============================================================================= +// +// +// +//============================================================================= + +class OptionMenuItemSlider : OptionMenuSliderBase +{ + CVar mCVar; + + OptionMenuItemSlider Init(String label, Name command, double min, double max, double step, int showval = 1, CVar graycheck = NULL) + { + Super.Init(label, min, max, step, showval, command, graycheck); + mCVar =CVar.FindCVar(command); + return self; + } + + override double GetSliderValue() + { + if (mCVar != null) + { + return mCVar.GetFloat(); + } + else + { + return 0; + } + } + + override void SetSliderValue(double val) + { + if (mCVar != null) + { + mCVar.SetFloat(val); + } + } +} + +//============================================================================= +// +// // Edit a key binding, Action is the CCMD to bind +// +//============================================================================= + +class OptionMenuItemColorPicker : OptionMenuItem +{ + CVar mCVar; + + const CPF_RESET = 0x20001; + + OptionMenuItemColorPicker Init(String label, Name command) + { + Super.Init(label, command); + CVar cv = CVar.FindCVar(command); + if (cv != null && cv.GetRealType() != CVar.CVAR_Color) cv = null; + mCVar = cv; + return self; + } + + //============================================================================= + override int Draw(OptionMenuDescriptor desc, int y, int indent, bool selected) + { + drawLabel(indent, y, selected? OptionMenuSettings.mFontColorSelection : OptionMenuSettings.mFontColor); + + if (mCVar != null) + { + int box_x = indent + CursorSpace(); + int box_y = y + CleanYfac_1; + screen.Clear (box_x, box_y, box_x + 32*CleanXfac_1, box_y + OptionMenuSettings.mLinespacing*CleanYfac_1, mCVar.GetInt() | 0xff000000); + } + return indent; + } + + override bool SetValue(int i, int v) + { + if (i == CPF_RESET && mCVar != null) + { + mCVar.ResetToDefault(); + return true; + } + return false; + } + + override bool Activate() + { + if (mCVar != null) + { + Menu.MenuSound("menu/choose"); + + // This code is a bit complicated because it should allow subclassing the + // colorpicker menu. + // New color pickers must inherit from the internal one to work here. + + let desc = MenuDescriptor.GetDescriptor('Colorpickermenu'); + if (desc != NULL && (desc.mClass == null || desc.mClass is "ColorPickerMenu")) + { + let odesc = OptionMenuDescriptor(desc); + if (odesc != null) + { + let cls = desc.mClass; + if (cls == null) cls = "ColorpickerMenu"; + let picker = ColorpickerMenu(new(cls)); + picker.Init(Menu.GetCurrentMenu(), mLabel, odesc, mCVar); + picker.ActivateMenu(); + return true; + } + } + } + return false; + } +} + +//============================================================================= +// +// [TP] OptionMenuFieldBase +// +// Base class for input fields +// +//============================================================================= + +class OptionMenuFieldBase : OptionMenuItem +{ + void Init (String label, Name command, CVar graycheck = null) + { + Super.Init(label, command); + mCVar = CVar.FindCVar(mAction); + mGrayCheck = graycheck; + } + + String GetCVarString() + { + if (mCVar == null) + return ""; + + return mCVar.GetString(); + } + + virtual String Represent() + { + return GetCVarString(); + } + + override int Draw (OptionMenuDescriptor d, int y, int indent, bool selected) + { + bool grayed = mGrayCheck != null && !mGrayCheck.GetInt(); + drawLabel(indent, y, selected ? OptionMenuSettings.mFontColorSelection : OptionMenuSettings.mFontColor, grayed); + drawValue(indent, y, OptionMenuSettings.mFontColorValue, Represent(), grayed); + return indent; + } + + override bool, String GetString (int i) + { + if (i == 0) + { + return true, GetCVarString(); + } + return false, ""; + } + + override bool SetString (int i, String s) + { + if (i == 0) + { + if (mCVar) mCVar.SetString(s); + return true; + } + return false; + } + + override bool Selectable() + { + return mGrayCheck == null || mGrayCheck.GetInt() != 0; + } + + CVar mCVar; + CVar mGrayCheck; +} + +//============================================================================= +// +// [TP] OptionMenuTextField +// +// A text input field widget, for use with string CVars. +// +//============================================================================= + +class OptionMenuItemTextField : OptionMenuFieldBase +{ + TextEnterMenu mEnter; + + OptionMenuItemTextField Init (String label, Name command, CVar graycheck = null) + { + Super.Init(label, command, graycheck); + mEnter = null; + return self; + } + + override String Represent() + { + if (mEnter) return mEnter.GetText() .. Menu.OptionFont().GetCursor(); + else + { + bool b; + String s; + [b, s] = GetString(0); + return s; + } + } + + override int Draw(OptionMenuDescriptor desc, int y, int indent, bool selected) + { + if (mEnter) + { + // reposition the text so that the cursor is visible when in entering mode. + String text = Represent(); + int tlen = Menu.OptionWidth(text) * CleanXfac_1; + int newindent = screen.GetWidth() - tlen - CursorSpace(); + if (newindent < indent) indent = newindent; + } + return Super.Draw(desc, y, indent, selected); + } + + override bool MenuEvent (int mkey, bool fromcontroller) + { + if (mkey == Menu.MKEY_Enter) + { + bool b; + String s; + [b, s] = GetString(0); + Menu.MenuSound("menu/choose"); + mEnter = TextEnterMenu.OpenTextEnter(Menu.GetCurrentMenu(), Menu.OptionFont(), s, -1, fromcontroller); + mEnter.ActivateMenu(); + return true; + } + else if (mkey == Menu.MKEY_Input) + { + SetString(0, mEnter.GetText()); + mEnter = null; + return true; + } + else if (mkey == Menu.MKEY_Abort) + { + mEnter = null; + return true; + } + + return Super.MenuEvent(mkey, fromcontroller); + } +} + + +//============================================================================= +// +// [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 OptionMenuItemNumberField : OptionMenuFieldBase +{ + OptionMenuItemNumberField Init (String label, Name command, float minimum = 0, float maximum = 100, float step = 1, CVar graycheck = null) + { + Super.Init(label, command, graycheck); + mMinimum = min(minimum, maximum); + mMaximum = max(minimum, maximum); + mStep = max(1, step); + return self; + } + + override String Represent() + { + if (mCVar == null) return ""; + return String.format("%.3f", mCVar.GetFloat()); + } + + + override bool MenuEvent (int mkey, bool fromcontroller) + { + if (mCVar) + { + float value = mCVar.GetFloat(); + + if (mkey == Menu.MKEY_Left) + { + value -= mStep; + if (value < mMinimum) value = mMaximum; + } + else if (mkey == Menu.MKEY_Right || mkey == Menu.MKEY_Enter) + { + value += mStep; + if (value > mMaximum) value = mMinimum; + } + else + return Super.MenuEvent(mkey, fromcontroller); + + mCVar.SetFloat(value); + Menu.MenuSound("menu/change"); + } + else return Super.MenuEvent(mkey, fromcontroller); + + return true; + } + + float mMinimum; + float mMaximum; + float mStep; +} + +//============================================================================= +// +// A special slider that displays plain text for special settings related +// to scaling. +// +//============================================================================= + +class OptionMenuItemScaleSlider : OptionMenuItemSlider +{ + String TextZero; + String TextNegOne; + int mClickVal; + + OptionMenuItemScaleSlider Init(String label, Name command, double min, double max, double step, String zero, String negone = "") + { + Super.Init(label, command, min, max, step, 0); + mCVar =CVar.FindCVar(command); + TextZero = zero; + TextNEgOne = negone; + mClickVal = -10; + return self; + } + + //============================================================================= + override int Draw(OptionMenuDescriptor desc, int y, int indent, bool selected) + { + drawLabel(indent, y, selected? OptionMenuSettings.mFontColorSelection : OptionMenuSettings.mFontColor); + + int Selection = int(GetSliderValue()); + if ((Selection == 0 || Selection == -1) && mClickVal <= 0) + { + String text = Selection == 0? TextZero : Selection == -1? TextNegOne : ""; + drawValue(indent, y, OptionMenuSettings.mFontColorValue, text); + } + else + { + mDrawX = indent + CursorSpace(); + DrawSlider (mDrawX, y, mMin, mMax, GetSliderValue(), mShowValue, indent); + } + return indent; + } + + override bool MouseEvent(int type, int x, int y) + { + int value = int(GetSliderValue()); + switch (type) + { + case Menu.MOUSE_Click: + mClickVal = value; + if (value <= 0) return false; + return Super.MouseEvent(type, x, y); + + case Menu.MOUSE_Move: + if (mClickVal <= 0) return false; + return Super.MouseEvent(type, x, y); + + case Menu.MOUSE_Release: + if (mClickVal <= 0) + { + mClickVal = -10; + SetSliderValue(value + 1); + return true; + } + mClickVal = -10; + return Super.MouseEvent(type, x, y); + } + return false; + } + +} + +//============================================================================= +// +// Placeholder classes for overhauled video mode menu. Do not use! +// Their sole purpose is to support mods with full copy of embedded MENUDEF +// +//============================================================================= + +class OptionMenuItemScreenResolution : OptionMenuItem +{ + String mResTexts[3]; + int mSelection; + int mHighlight; + int mMaxValid; + + enum EValues + { + SRL_INDEX = 0x30000, + SRL_SELECTION = 0x30003, + SRL_HIGHLIGHT = 0x30004, + }; + + OptionMenuItemScreenResolution Init(String command) + { + return self; + } + + override bool Selectable() + { + return false; + } +} + +class VideoModeMenu : OptionMenu +{ + static bool SetSelectedSize() + { + return false; + } +} diff --git a/wadsrc/static/zscript/ui/menu/readthis.zs b/wadsrc/static/zscript/ui/menu/readthis.zs new file mode 100644 index 000000000..58904b0f4 --- /dev/null +++ b/wadsrc/static/zscript/ui/menu/readthis.zs @@ -0,0 +1,125 @@ +/* +** readthis.cpp +** Help screens +** +**--------------------------------------------------------------------------- +** Copyright 2001-2010 Randy Heit +** 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. +**--------------------------------------------------------------------------- +** +*/ + +class ReadThisMenu : GenericMenu +{ + int mScreen; + int mInfoTic; + + //============================================================================= + // + // + // + //============================================================================= + + override void Init(Menu parent) + { + Super.Init(parent); + mScreen = 1; + mInfoTic = gametic; + } + + override void Drawer() + { + double alpha; + TextureID tex, prevpic; + + // Did the mapper choose a custom help page via MAPINFO? + if (Level.F1Pic.Length() != 0) + { + tex = TexMan.CheckForTexture(Level.F1Pic, TexMan.Type_MiscPatch); + mScreen = 1; + } + + if (!tex.IsValid()) + { + tex = TexMan.CheckForTexture(gameinfo.infoPages[mScreen-1], TexMan.Type_MiscPatch); + } + + if (mScreen > 1) + { + prevpic = TexMan.CheckForTexture(gameinfo.infoPages[mScreen-2], TexMan.Type_MiscPatch); + } + + screen.Dim(0, 1.0, 0,0, screen.GetWidth(), screen.GetHeight()); + alpha = MIN((gametic - mInfoTic) * (3. / Thinker.TICRATE), 1.); + if (alpha < 1. && prevpic.IsValid()) + { + screen.DrawTexture (prevpic, false, 0, 0, DTA_Fullscreen, true); + } + else alpha = 1; + screen.DrawTexture (tex, false, 0, 0, DTA_Fullscreen, true, DTA_Alpha, alpha); + + } + + + //============================================================================= + // + // + // + //============================================================================= + + override bool MenuEvent(int mkey, bool fromcontroller) + { + if (mkey == MKEY_Enter) + { + MenuSound("menu/choose"); + mScreen++; + mInfoTic = gametic; + if (Level.F1Pic.Length() != 0 || mScreen > gameinfo.infoPages.Size()) + { + Close(); + } + return true; + } + else return Super.MenuEvent(mkey, fromcontroller); + } + + //============================================================================= + // + // + // + //============================================================================= + + override bool MouseEvent(int type, int x, int y) + { + if (type == MOUSE_Click) + { + return MenuEvent(MKEY_Enter, true); + } + return false; + } + +} \ No newline at end of file diff --git a/wadsrc/static/zscript/ui/menu/reverbedit.zs b/wadsrc/static/zscript/ui/menu/reverbedit.zs new file mode 100644 index 000000000..65fa5158e --- /dev/null +++ b/wadsrc/static/zscript/ui/menu/reverbedit.zs @@ -0,0 +1,258 @@ + +class ReverbEdit : OptionMenu +{ + static native double GetValue(int index); + static native double SetValue(int index, double value); + static native bool GrayCheck(); + static native string, int GetSelectedEnvironment(); + static native void FillSelectMenu(String ccmd, OptionMenuDescriptor desc); + static native void FillSaveMenu(OptionMenuDescriptor desc); + static native int GetSaveSelection(int num); + static native void ToggleSaveSelection(int num); + + override void Init(Menu parent, OptionMenuDescriptor desc) + { + super.Init(parent, desc); + OnReturn(); + } + + override void OnReturn() + { + string env; + int id; + + [env, id] = GetSelectedEnvironment(); + + let li = GetItem('EvironmentName'); + if (li != NULL) + { + if (id != -1) + { + li.SetValue(0, 1); + li.SetString(0, env); + } + else + { + li.SetValue(0, 0); + } + } + li = GetItem('EvironmentID'); + if (li != NULL) + { + if (id != -1) + { + li.SetValue(0, 1); + li.SetString(0, String.Format("%d, %d", (id >> 8) & 255, id & 255)); + } + else + { + li.SetValue(0, 0); + } + } + } +} + +class ReverbSelect : OptionMenu +{ + //============================================================================= + // + // + // + //============================================================================= + + override void Init(Menu parent, OptionMenuDescriptor desc) + { + ReverbEdit.FillSelectMenu("selectenvironment", desc); + super.Init(parent, desc); + } +} + +class ReverbSave : OptionMenu +{ + //============================================================================= + // + // + // + //============================================================================= + + override void Init(Menu parent, OptionMenuDescriptor desc) + { + ReverbEdit.FillSaveMenu(desc); + super.Init(parent, desc); + } +} + +//============================================================================= +// +// Change a CVAR, command is the CVAR name +// +//============================================================================= + +class OptionMenuItemReverbSaveSelect : OptionMenuItemOptionBase +{ + int mValIndex; + + OptionMenuItemReverbSaveSelect Init(String label, int index, Name values) + { + Super.Init(label, 'None', values, null, 0); + mValIndex = index; + return self; + } + + //============================================================================= + override int GetSelection() + { + return ReverbEdit.GetSaveSelection(mValIndex); + } + + override void SetSelection(int Selection) + { + ReverbEdit.ToggleSaveSelection(mValIndex); + } +} + + +//============================================================================= +// +// opens a submenu, command is a submenu name +// +//============================================================================= + +class OptionMenuItemReverbSelect : OptionMenuItemSubMenu +{ + OptionMenuItemReverbSelect Init(String label, Name command) + { + Super.init(label, command, 0, false); + return self; + } + + + override int Draw(OptionMenuDescriptor desc, int y, int indent, bool selected) + { + int x = drawLabel(indent, y, selected? OptionMenuSettings.mFontColorSelection : OptionMenuSettings.mFontColor); + + String text = ReverbEdit.GetSelectedEnvironment(); + drawValue(indent, y, OptionMenuSettings.mFontColorValue, text); + return indent; + } +} + + +//============================================================================= +// +// +// +//============================================================================= + +class OptionMenuItemReverbOption : OptionMenuItemOptionBase +{ + int mValIndex; + + OptionMenuItemReverbOption Init(String label, int valindex, Name values) + { + Super.Init(label, "", values, null, false); + mValIndex = valindex; + return self; + } + + override bool isGrayed() + { + return ReverbEdit.GrayCheck(); + } + + override int GetSelection() + { + return int(ReverbEdit.GetValue(mValIndex)); + } + + override void SetSelection(int Selection) + { + ReverbEdit.SetValue(mValIndex, Selection); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +class OptionMenuItemSliderReverbEditOption : OptionMenuSliderBase +{ + int mValIndex; + String mEditValue; + TextEnterMenu mEnter; + + OptionMenuItemSliderReverbEditOption Init(String label, double min, double max, double step, int showval, int valindex) + { + Super.Init(label, min, max, step, showval); + mValIndex = valindex; + mEnter = null; + return self; + } + + + override double GetSliderValue() + { + return ReverbEdit.GetValue(mValIndex); + } + + override void SetSliderValue(double val) + { + ReverbEdit.SetValue(mValIndex, val); + } + + override bool Selectable() + { + return !ReverbEdit.GrayCheck(); + } + + virtual String Represent() + { + return mEnter.GetText() .. Menu.OptionFont().GetCursor(); + } + + //============================================================================= + override int Draw(OptionMenuDescriptor desc, int y, int indent, bool selected) + { + drawLabel(indent, y, selected ? OptionMenuSettings.mFontColorSelection : OptionMenuSettings.mFontColor, ReverbEdit.GrayCheck()); + + mDrawX = indent + CursorSpace(); + if (mEnter) + { + drawText(mDrawX, y, OptionMenuSettings.mFontColorValue, Represent()); + } + else + { + DrawSlider (mDrawX, y, mMin, mMax, GetSliderValue(), mShowValue, indent); + } + return indent; + } + + override bool MenuEvent (int mkey, bool fromcontroller) + { + if (mkey == Menu.MKEY_Enter) + { + Menu.MenuSound("menu/choose"); + mEnter = TextEnterMenu.OpenTextEnter(Menu.GetCurrentMenu(), Menu.OptionFont(), String.Format("%.3f", GetSliderValue()), -1, fromcontroller); + mEnter.ActivateMenu(); + return true; + } + else if (mkey == Menu.MKEY_Input) + { + String val = mEnter.GetText(); + SetSliderValue(val.toDouble()); + mEnter = null; + return true; + } + else if (mkey == Menu.MKEY_Abort) + { + mEnter = null; + return true; + } + + return Super.MenuEvent(mkey, fromcontroller); + } + +} + diff --git a/wadsrc/static/zscript/ui/menu/search/anyoralloption.zs b/wadsrc/static/zscript/ui/menu/search/anyoralloption.zs new file mode 100644 index 000000000..d38983b4b --- /dev/null +++ b/wadsrc/static/zscript/ui/menu/search/anyoralloption.zs @@ -0,0 +1,33 @@ +//============================================================================= +// +// os_AnyOrAllOption class represents an Option Item for Option Search menu. +// Changing the value of this option causes the menu to refresh the search +// results. +// +//============================================================================= + +class os_AnyOrAllOption : OptionMenuItemOption +{ + os_AnyOrAllOption Init(os_Menu menu) + { + Super.Init("", "os_isanyof", "os_isanyof_values"); + + mMenu = menu; + + return self; + } + + override bool MenuEvent(int mkey, bool fromcontroller) + { + bool result = Super.MenuEvent(mkey, fromcontroller); + + if (mKey == Menu.MKEY_Left || mKey == Menu.MKEY_Right || mkey == Menu.MKEY_Enter) + { + mMenu.search(); + } + + return result; + } + + private os_Menu mMenu; +} diff --git a/wadsrc/static/zscript/ui/menu/search/menu.zs b/wadsrc/static/zscript/ui/menu/search/menu.zs new file mode 100644 index 000000000..46efc0c6a --- /dev/null +++ b/wadsrc/static/zscript/ui/menu/search/menu.zs @@ -0,0 +1,218 @@ +//============================================================================= +// +// Option Search Menu class. +// This menu contains search field, and is dynamically filled with search +// results. +// +//============================================================================= + +class os_Menu : OptionMenu +{ + override void Init(Menu parent, OptionMenuDescriptor desc) + { + Super.Init(parent, desc); + + mDesc.mItems.clear(); + + addSearchField(); + + mDesc.mScrollPos = 0; + mDesc.mSelectedItem = 0; + mDesc.CalcIndent(); + } + + void search() + { + string text = mSearchField.GetText(); + let query = os_Query.fromString(text); + bool isAnyTermMatches = mIsAnyOfItem.mCVar.GetBool(); + + mDesc.mItems.clear(); + + addSearchField(text); + + bool found = listOptions(mDesc, "MainMenu", query, "", isAnyTermMatches); + + if (!found) { addNoResultsItem(mDesc); } + + mDesc.CalcIndent(); + } + + private void addSearchField(string query = "") + { + string searchLabel = StringTable.Localize("$OS_LABEL"); + + mSearchField = new("os_SearchField").Init(searchLabel, self, query); + mIsAnyOfItem = new("os_AnyOrAllOption").Init(self); + + mDesc.mItems.push(mSearchField); + mDesc.mItems.push(mIsAnyOfItem); + addEmptyLine(mDesc); + } + + private static bool listOptions(OptionMenuDescriptor targetDesc, + string menuName, + os_Query query, + string path, + bool isAnyTermMatches) + { + let desc = MenuDescriptor.GetDescriptor(menuName); + let listMenuDesc = ListMenuDescriptor(desc); + + if (listMenuDesc) + { + return listOptionsListMenu(listMenuDesc, targetDesc, query, path, isAnyTermMatches); + } + + let optionMenuDesc = OptionMenuDescriptor(desc); + + if (optionMenuDesc) + { + return listOptionsOptionMenu(optionMenuDesc, targetDesc, query, path, isAnyTermMatches); + } + + return false; + } + + private static bool listOptionsListMenu(ListMenuDescriptor sourceDesc, + OptionMenuDescriptor targetDesc, + os_Query query, + string path, + bool isAnyTermMatches) + { + int nItems = sourceDesc.mItems.size(); + bool found = false; + + for (int i = 0; i < nItems; ++i) + { + let item = sourceDesc.mItems[i]; + string actionN = item.GetAction(); + let textItem = ListMenuItemTextItem(item); + string newPath = textItem + ? makePath(path, StringTable.Localize(textItem.mText)) + : path; + + found |= listOptions(targetDesc, actionN, query, newPath, isAnyTermMatches); + } + + return found; + } + + private static bool listOptionsOptionMenu(OptionMenuDescriptor sourceDesc, + OptionMenuDescriptor targetDesc, + os_Query query, + string path, + bool isAnyTermMatches) + { + if (sourceDesc == targetDesc) { return false; } + + int nItems = sourceDesc.mItems.size(); + bool first = true; + bool found = false; + + for (int i = 0; i < nItems; ++i) + { + let item = sourceDesc.mItems[i]; + + if (item is "OptionMenuItemStaticText") { continue; } + + string label = StringTable.Localize(item.mLabel); + + if (!query.matches(label, isAnyTermMatches)) { continue; } + + found = true; + + if (first) + { + addEmptyLine(targetDesc); + addPathItem(targetDesc, path); + + first = false; + } + + let itemOptionBase = OptionMenuItemOptionBase(item); + + if (itemOptionBase) + { + itemOptionBase.mCenter = false; + } + + targetDesc.mItems.push(item); + } + + for (int i = 0; i < nItems; ++i) + { + let item = sourceDesc.mItems[i]; + string label = StringTable.Localize(item.mLabel); + string optionSearchTitle = StringTable.Localize("$OS_TITLE"); + + if (label == optionSearchTitle) { continue; } + + if (item is "OptionMenuItemSubMenu") + { + string newPath = makePath(path, label); + + found |= listOptions(targetDesc, item.GetAction(), query, newPath, isAnyTermMatches); + } + } + + return found; + } + + private static string makePath(string path, string label) + { + if (path.length() == 0) { return label; } + + int pathWidth = SmallFont.StringWidth(path .. "/" .. label); + int screenWidth = Screen.GetWidth(); + bool isTooWide = (pathWidth > screenWidth / 3); + string newPath = isTooWide + ? path .. "/" .. "\n" .. label + : path .. "/" .. label; + + return newPath; + } + + private static void addPathItem(OptionMenuDescriptor desc, string path) + { + Array lines; + path.split(lines, "\n"); + + int nLines = lines.size(); + + for (int i = 0; i < nLines; ++i) + { + OptionMenuItemStaticText text = new("OptionMenuItemStaticText").Init(lines[i], 1); + + desc.mItems.push(text); + } + } + + private static void addEmptyLine(OptionMenuDescriptor desc) + { + int nItems = desc.mItems.size(); + + if (nItems > 0) + { + let staticText = OptionMenuItemStaticText(desc.mItems[nItems - 1]); + + if (staticText != null && staticText.mLabel == "") { return; } + } + + let item = new("OptionMenuItemStaticText").Init(""); + + desc.mItems.push(item); + } + + private static void addNoResultsItem(OptionMenuDescriptor desc) + { + string noResults = StringTable.Localize("$OS_NO_RESULTS"); + let text = new("OptionMenuItemStaticText").Init(noResults, 0); + + addEmptyLine(desc); + desc.mItems.push(text); + } + + private os_AnyOrAllOption mIsAnyOfItem; + private os_SearchField mSearchField; +} diff --git a/wadsrc/static/zscript/ui/menu/search/query.zs b/wadsrc/static/zscript/ui/menu/search/query.zs new file mode 100644 index 000000000..70c5437c1 --- /dev/null +++ b/wadsrc/static/zscript/ui/menu/search/query.zs @@ -0,0 +1,73 @@ +//============================================================================= +// +// Option Search Query class represents a search query. +// A search query consists constists of one or more terms (words). +// +// Query matching deponds on "os_is_any_of" variable. +// If this variable is "true", the text matches the query if any of the terms +// matches the query. +// If this variable is "false", the text matches the query only if all the +// terms match the query. +// +//============================================================================= + +class os_Query +{ + static os_Query fromString(string str) + { + let query = new("os_Query"); + + str.Split(query.mQueryParts, " ", TOK_SKIPEMPTY); + + return query; + } + + bool matches(string text, bool isSearchForAny) + { + return isSearchForAny + ? matchesAny(text) + : matchesAll(text); + } + + // private: ////////////////////////////////////////////////////////////////// + + private bool matchesAny(string text) + { + int nParts = mQueryParts.size(); + + for (int i = 0; i < nParts; ++i) + { + string queryPart = mQueryParts[i]; + + if (contains(text, queryPart)) { return true; } + } + + return false; + } + + private bool matchesAll(string text) + { + int nParts = mQueryParts.size(); + + for (int i = 0; i < nParts; ++i) + { + string queryPart = mQueryParts[i]; + + if (!contains(text, queryPart)) { return false; } + } + + return true; + } + + private static bool contains(string str, string substr) + { + let lowerstr = str .MakeLower(); + let lowersubstr = substr.MakeLower(); + + bool contains = (lowerstr.IndexOf(lowersubstr) != -1); + + return contains; + } + + private Array mQueryParts; +} diff --git a/wadsrc/static/zscript/ui/menu/search/searchfield.zs b/wadsrc/static/zscript/ui/menu/search/searchfield.zs new file mode 100644 index 000000000..88135716a --- /dev/null +++ b/wadsrc/static/zscript/ui/menu/search/searchfield.zs @@ -0,0 +1,52 @@ +//============================================================================= +// +// Option Search Field class. +// +// When the search query is entered, makes Search Menu perform a search. +// +//============================================================================= + +class os_SearchField : OptionMenuItemTextField +{ + os_SearchField Init(String label, os_Menu menu, string query) + { + Super.Init(label, ""); + + mMenu = menu; + + mText = query; + + return self; + } + + override bool MenuEvent(int mkey, bool fromcontroller) + { + if (mkey == Menu.MKEY_Enter) + { + Menu.MenuSound("menu/choose"); + mEnter = TextEnterMenu.OpenTextEnter(Menu.GetCurrentMenu(), Menu.OptionFont(), mText, -1, fromcontroller); + mEnter.ActivateMenu(); + return true; + } + if (mkey == Menu.MKEY_Input) + { + mtext = mEnter.GetText(); + + mMenu.search(); + } + + return Super.MenuEvent(mkey, fromcontroller); + } + + override String Represent() + { + return mEnter + ? mEnter.GetText() .. NewSmallFont.GetCursor() + : mText; + } + + String GetText() { return mText; } + + private os_Menu mMenu; + private string mText; +} diff --git a/wadsrc/static/zscript/ui/menu/textentermenu.zs b/wadsrc/static/zscript/ui/menu/textentermenu.zs new file mode 100644 index 000000000..2f179d0d7 --- /dev/null +++ b/wadsrc/static/zscript/ui/menu/textentermenu.zs @@ -0,0 +1,379 @@ +/* +** menuinput.cpp +** The string input code +** +**--------------------------------------------------------------------------- +** Copyright 2001-2010 Randy Heit +** Copyright 2010-2017 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. +**--------------------------------------------------------------------------- +** +*/ + + +class TextEnterMenu : Menu +{ + const INPUTGRID_WIDTH = 13; + const INPUTGRID_HEIGHT = 5; + + const Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-=.,!?@'\":;[]()<>^#$%&*/_ \b"; + + String mEnterString; + int mEnterSize; + int mEnterPos; + bool mInputGridOkay; + int InputGridX; + int InputGridY; + int CursorSize; + bool AllowColors; + Font displayFont; + + //============================================================================= + // + // + // + //============================================================================= + + // [TP] Added allowcolors + private void Init(Menu parent, Font dpf, String textbuffer, int maxlen, bool showgrid, bool allowcolors) + { + Super.init(parent); + mEnterString = textbuffer; + mEnterSize = maxlen; + mInputGridOkay = (showgrid && (m_showinputgrid == 0)) || (m_showinputgrid >= 1); + if (mEnterString.Length() > 0) + { + InputGridX = INPUTGRID_WIDTH - 1; + InputGridY = INPUTGRID_HEIGHT - 1; + } + else + { + // If we are naming a new save, don't start the cursor on "end". + InputGridX = 0; + InputGridY = 0; + } + AllowColors = allowcolors; // [TP] + displayFont = dpf; + CursorSize = displayFont.StringWidth(displayFont.GetCursor()); + } + + // This had to be deprecated because the unit for maxlen is 8 pixels. + deprecated("3.8") static TextEnterMenu Open(Menu parent, String textbuffer, int maxlen, int sizemode, bool showgrid = false, bool allowcolors = false) + { + let me = new("TextEnterMenu"); + me.Init(parent, Menu.OptionFont(), textbuffer, maxlen*8, showgrid, allowcolors); + return me; + } + + static TextEnterMenu OpenTextEnter(Menu parent, Font displayfnt, String textbuffer, int maxlen, bool showgrid = false, bool allowcolors = false) + { + let me = new("TextEnterMenu"); + me.Init(parent, displayfnt, textbuffer, maxlen, showgrid, allowcolors); + return me; + } + + + //============================================================================= + // + // + // + //============================================================================= + + String GetText() + { + return mEnterString; + } + + override bool TranslateKeyboardEvents() + { + return mInputGridOkay; + } + + + //============================================================================= + // + // + // + //============================================================================= + + override bool OnUIEvent(UIEvent ev) + { + // Save game and player name string input + if (ev.Type == UIEvent.Type_Char) + { + mInputGridOkay = false; + AppendChar(ev.KeyChar); + return true; + } + int ch = ev.KeyChar; + if ((ev.Type == UIEvent.Type_KeyDown || ev.Type == UIEvent.Type_KeyRepeat) && ch == 8) + { + if (mEnterString.Length() > 0) + { + mEnterString.DeleteLastCharacter(); + } + } + else if (ev.Type == UIEvent.Type_KeyDown) + { + if (ch == UIEvent.Key_ESCAPE) + { + Menu parent = mParentMenu; + Close(); + parent.MenuEvent(MKEY_Abort, false); + return true; + } + else if (ch == 13) + { + if (mEnterString.Length() > 0) + { + // [TP] If we allow color codes, colorize the string now. + if (AllowColors) + mEnterString = mEnterString.Filter(); + + Menu parent = mParentMenu; + parent.MenuEvent(MKEY_Input, false); + Close(); + return true; + } + } + } + if (ev.Type == UIEvent.Type_KeyDown || ev.Type == UIEvent.Type_KeyRepeat) + { + return true; + } + return Super.OnUIEvent(ev); + } + + //============================================================================= + // + // + // + //============================================================================= + + override bool MouseEvent(int type, int x, int y) + { + int cell_width = 18 * CleanXfac_1; + int cell_height = 16 * CleanYfac_1; + int screen_y = screen.GetHeight() - INPUTGRID_HEIGHT * cell_height; + int screen_x = (screen.GetWidth() - INPUTGRID_WIDTH * cell_width) / 2; + + if (x >= screen_x && x < screen_x + INPUTGRID_WIDTH * cell_width && y >= screen_y) + { + InputGridX = (x - screen_x) / cell_width; + InputGridY = (y - screen_y) / cell_height; + if (type == MOUSE_Release) + { + if (MenuEvent(MKEY_Enter, true)) + { + MenuSound("menu/choose"); + if (m_use_mouse == 2) InputGridX = InputGridY = -1; + } + } + return true; + } + else + { + InputGridX = InputGridY = -1; + } + return Super.MouseEvent(type, x, y); + } + + //============================================================================= + // + // + // + //============================================================================= + + private void AppendChar(int ch) + { + String newstring = String.Format("%s%c%s", mEnterString, ch, displayFont.GetCursor()); + if (mEnterSize < 0 || displayFont.StringWidth(newstring) < mEnterSize) + { + mEnterString.AppendCharacter(ch); + } + } + + //============================================================================= + // + // + // + //============================================================================= + + override bool MenuEvent (int key, bool fromcontroller) + { + String InputGridChars = Chars; + if (key == MKEY_Back) + { + mParentMenu.MenuEvent(MKEY_Abort, false); + return Super.MenuEvent(key, fromcontroller); + } + if (fromcontroller) + { + mInputGridOkay = true; + } + + if (mInputGridOkay) + { + int ch; + + if (InputGridX == -1 || InputGridY == -1) + { + InputGridX = InputGridY = 0; + } + switch (key) + { + case MKEY_Down: + InputGridY = (InputGridY + 1) % INPUTGRID_HEIGHT; + return true; + + case MKEY_Up: + InputGridY = (InputGridY + INPUTGRID_HEIGHT - 1) % INPUTGRID_HEIGHT; + return true; + + case MKEY_Right: + InputGridX = (InputGridX + 1) % INPUTGRID_WIDTH; + return true; + + case MKEY_Left: + InputGridX = (InputGridX + INPUTGRID_WIDTH - 1) % INPUTGRID_WIDTH; + return true; + + case MKEY_Clear: + if (mEnterString.Length() > 0) + { + mEnterString.DeleteLastCharacter(); + } + return true; + + case MKEY_Enter: + if (mInputGridOkay) + { + int ch = InputGridChars.ByteAt(InputGridX + InputGridY * INPUTGRID_WIDTH); + if (ch == 0) // end + { + if (mEnterString.Length() > 0) + { + Menu parent = mParentMenu; + parent.MenuEvent(MKEY_Input, false); + Close(); + return true; + } + } + else if (ch == 8) // bs + { + if (mEnterString.Length() > 0) + { + mEnterString.DeleteLastCharacter(); + } + } + else + { + AppendChar(ch); + } + } + return true; + + default: + break; + } + } + return false; + } + + + //============================================================================= + // + // + // + //============================================================================= + + override void Drawer () + { + mParentMenu.Drawer(); + if (mInputGridOkay) + { + String InputGridChars = Chars; + int cell_width = 18 * CleanXfac_1; + int cell_height = 16 * CleanYfac_1; + int top_padding = cell_height / 2 - displayFont.GetHeight() * CleanYfac_1 / 2; + + // Darken the background behind the character grid. + screen.Dim(0, 0.8, 0, screen.GetHeight() - INPUTGRID_HEIGHT * cell_height, screen.GetWidth(), INPUTGRID_HEIGHT * cell_height); + + if (InputGridX >= 0 && InputGridY >= 0) + { + // Highlight the background behind the selected character. + screen.Dim(Color(255,248,220), 0.6, + InputGridX * cell_width - INPUTGRID_WIDTH * cell_width / 2 + screen.GetWidth() / 2, + InputGridY * cell_height - INPUTGRID_HEIGHT * cell_height + screen.GetHeight(), + cell_width, cell_height); + } + + for (int y = 0; y < INPUTGRID_HEIGHT; ++y) + { + int yy = y * cell_height - INPUTGRID_HEIGHT * cell_height + screen.GetHeight(); + for (int x = 0; x < INPUTGRID_WIDTH; ++x) + { + int xx = x * cell_width - INPUTGRID_WIDTH * cell_width / 2 + screen.GetWidth() / 2; + int ch = InputGridChars.ByteAt(y * INPUTGRID_WIDTH + x); + int width = displayFont.GetCharWidth(ch); + + // The highlighted character is yellow; the rest are dark gray. + int colr = (x == InputGridX && y == InputGridY) ? Font.CR_YELLOW : Font.CR_DARKGRAY; + Color palcolor = (x == InputGridX && y == InputGridY) ? Color(160, 120, 0) : Color(120, 120, 120); + + if (ch > 32) + { + // Draw a normal character. + screen.DrawChar(displayFont, colr, xx + cell_width/2 - width*CleanXfac_1/2, yy + top_padding, ch, DTA_CleanNoMove_1, true); + } + else if (ch == 32) + { + // Draw the space as a box outline. We also draw it 50% wider than it really is. + int x1 = xx + cell_width/2 - width * CleanXfac_1 * 3 / 4; + int x2 = x1 + width * 3 * CleanXfac_1 / 2; + int y1 = yy + top_padding; + int y2 = y1 + displayFont.GetHeight() * CleanYfac_1; + screen.Clear(x1, y1, x2, y1+CleanYfac_1, palcolor); // top + screen.Clear(x1, y2, x2, y2+CleanYfac_1, palcolor); // bottom + screen.Clear(x1, y1+CleanYfac_1, x1+CleanXfac_1, y2, palcolor); // left + screen.Clear(x2-CleanXfac_1, y1+CleanYfac_1, x2, y2, palcolor); // right + } + else if (ch == 8 || ch == 0) + { + // Draw the backspace and end "characters". + String str = ch == 8 ? "BS" : "ED"; + screen.DrawText(displayFont, colr, + xx + cell_width/2 - displayFont.StringWidth(str)*CleanXfac_1/2, + yy + top_padding, str, DTA_CleanNoMove_1, true); + } + } + } + } + Super.Drawer(); + } + +}