The menu code sucks!

- Joystick devices now send key up events for any buttons that are held
  down when they are destroyed.
- Changed the joystick enable-y menu items to be nonrepeatable so that you
  can't create several device scans per second. Changing them with a
  controller is also disabled so that you can't, for example, toggle XInput
  support using an XInput controller and have things go haywire when the
  game receives an infinite number of key down events when the controller
  is reinitialized with the other input system.
- Changed menu input to use a consolidated set of buttons, so most menus
  can be navigated with a controller as well as a keyboard.
- Changed the way that X/Y analog axes are converted to digital axes.
  Previously, this was done by checking if each axis was outside its deadzone.
  Now they are checked together based on their angle, so straight up/down/
  left/right are much easier to achieve.


SVN r1739 (trunk)
This commit is contained in:
Randy Heit 2009-07-26 03:25:18 +00:00
parent 1c8d442c32
commit a90bde274e
12 changed files with 624 additions and 291 deletions

View file

@ -1,4 +1,20 @@
July 25, 2009 (Changes by Graf Zahl)
July 25, 2009
- Joystick devices now send key up events for any buttons that are held
down when they are destroyed.
- Changed the joystick enable-y menu items to be nonrepeatable so that you
can't create several device scans per second. Changing them with a
controller is also disabled so that you can't, for example, toggle XInput
support using an XInput controller and have things go haywire when the
game receives an infinite number of key down events when the controller
is reinitialized with the other input system.
- Changed menu input to use a consolidated set of buttons, so most menus
can be navigated with a controller as well as a keyboard.
- Changed the way that X/Y analog axes are converted to digital axes.
Previously, this was done by checking if each axis was outside its deadzone.
Now they are checked together based on their angle, so straight up/down/
left/right are much easier to achieve.
July 25, 2009 (Changes by Graf Zahl)
- Fixed: The composer for complex multipatch textures did not clear the palette
buffer before filling it.

View file

@ -421,7 +421,7 @@ FButtonStatus *FindButton (unsigned int key)
return bit ? bit->Button : NULL;
}
void FButtonStatus::PressKey (int keynum)
bool FButtonStatus::PressKey (int keynum)
{
int i, open;
@ -445,22 +445,26 @@ void FButtonStatus::PressKey (int keynum)
}
else if (Keys[i] == keynum)
{ // Key is already down; do nothing
return;
return false;
}
}
if (open < 0)
{ // No free key slots, so do nothing
Printf ("More than %u keys pressed for a single action!\n", MAX_KEYS);
return;
return false;
}
Keys[open] = keynum;
}
BYTE wasdown = bDown;
bDown = bWentDown = true;
// Returns true if this key caused the button to go down.
return !wasdown;
}
void FButtonStatus::ReleaseKey (int keynum)
bool FButtonStatus::ReleaseKey (int keynum)
{
int i, numdown, match;
BYTE wasdown = bDown;
keynum &= KEY_DBLCLICKED-1;
@ -488,7 +492,7 @@ void FButtonStatus::ReleaseKey (int keynum)
}
if (match < 0)
{ // Key was not down; do nothing
return;
return false;
}
Keys[match] = 0;
bWentUp = true;
@ -497,6 +501,8 @@ void FButtonStatus::ReleaseKey (int keynum)
bDown = false;
}
}
// Returns true if releasing this key caused the button to go up.
return wasdown && !bDown;
}
void ResetButtonTriggers ()

View file

@ -140,8 +140,8 @@ struct FButtonStatus
BYTE bWentUp; // Button went up this tic
BYTE padTo16Bytes;
void PressKey (int keynum);
void ReleaseKey (int keynum);
bool PressKey (int keynum); // Returns true if this key caused the button to be pressed.
bool ReleaseKey (int keynum); // Returns true if this key is no longer pressed.
void ResetTriggers () { bWentDown = bWentUp = false; }
};

View file

@ -156,6 +156,14 @@ enum ESkillLevels
#define KEY_MOUSE8 0x107
#define KEY_FIRSTJOYBUTTON 0x108
#define KEY_JOY1 (KEY_FIRSTJOYBUTTON+0)
#define KEY_JOY2 (KEY_FIRSTJOYBUTTON+1)
#define KEY_JOY3 (KEY_FIRSTJOYBUTTON+2)
#define KEY_JOY4 (KEY_FIRSTJOYBUTTON+3)
#define KEY_JOY5 (KEY_FIRSTJOYBUTTON+4)
#define KEY_JOY6 (KEY_FIRSTJOYBUTTON+5)
#define KEY_JOY7 (KEY_FIRSTJOYBUTTON+6)
#define KEY_JOY8 (KEY_FIRSTJOYBUTTON+7)
#define KEY_LASTJOYBUTTON 0x187
#define KEY_JOYPOV1_UP 0x188
#define KEY_JOYPOV1_RIGHT 0x189

View file

@ -33,6 +33,9 @@ CUSTOM_CVAR(Bool, use_joystick, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG|CVAR_NOINI
// PRIVATE DATA DEFINITIONS ------------------------------------------------
// Bits 0 is X+, 1 is X-, 2 is Y+, and 3 is Y-.
static BYTE JoyAngleButtons[8] = { 1, 1+4, 4, 2+4, 2, 2+8, 8, 1+8 };
// CODE --------------------------------------------------------------------
//==========================================================================
@ -170,26 +173,69 @@ void M_SaveJoystickConfig(IJoystickConfig *joy)
double Joy_RemoveDeadZone(double axisval, double deadzone, BYTE *buttons)
{
BYTE butt;
// Cancel out deadzone.
if (fabs(axisval) < deadzone)
{
axisval = 0;
*buttons = 0;
butt = 0;
}
// Make the dead zone the new 0.
else if (axisval < 0)
{
axisval = (axisval + deadzone) / (1.0 - deadzone);
*buttons = 2; // button minus
butt = 2; // button minus
}
else
{
axisval = (axisval - deadzone) / (1.0 - deadzone);
*buttons = 1; // button plus
butt = 1; // button plus
}
if (buttons != NULL)
{
*buttons = butt;
}
return axisval;
}
//===========================================================================
//
// Joy_XYAxesToButtons
//
// Given two axes, returns a button set for them. They should have already
// been sent through Joy_RemoveDeadZone. For axes that share the same
// physical stick, the angle the stick forms should determine whether or
// not the four component buttons are present. Going by deadzone alone gives
// you huge areas where you have to buttons pressed and thin strips where
// you only have one button pressed. For DirectInput gamepads, there is
// not much standard for how the right stick is presented, so we can only
// do this for the left stick for those, since X and Y axes are pretty
// standard. For XInput and Raw PS2 controllers, both sticks are processed
// through here.
//
//===========================================================================
int Joy_XYAxesToButtons(double x, double y)
{
if (x == 0 && y == 0)
{
return 0;
}
double rad = atan2(y, x);
if (rad < 0)
{
rad += 2*M_PI;
}
// The circle is divided into eight segments for corresponding
// button combinations. Each segment is pi/4 radians wide. We offset
// by half this so that the segments are centered around the ideal lines
// their buttons represent instead of being right on the lines.
rad += M_PI/8; // Offset
rad *= 4/M_PI; // Convert range from [0,2pi) to [0,8)
return JoyAngleButtons[int(rad) & 7];
}
//===========================================================================
//
// Joy_GenerateButtonEvents

View file

@ -51,6 +51,7 @@ void M_SaveJoystickConfig(IJoystickConfig *joy);
void Joy_GenerateButtonEvents(int oldbuttons, int newbuttons, int numbuttons, int base);
void Joy_GenerateButtonEvents(int oldbuttons, int newbuttons, int numbuttons, const int *keys);
int Joy_XYAxesToButtons(double x, double y);
double Joy_RemoveDeadZone(double axisval, double deadzone, BYTE *buttons);
// These ought to be provided by a system-specific i_input.cpp.

View file

@ -79,6 +79,9 @@
#define SELECTOR_XOFFSET (-28)
#define SELECTOR_YOFFSET (-1)
#define KEY_REPEAT_DELAY (TICRATE*5/12)
#define KEY_REPEAT_RATE (3)
// TYPES -------------------------------------------------------------------
struct FSaveGameNode : public Node
@ -149,6 +152,7 @@ static void M_ExtractSaveData (const FSaveGameNode *file);
static void M_UnloadSaveData ();
static void M_InsertSaveNode (FSaveGameNode *node);
static bool M_SaveLoadResponder (event_t *ev);
static void M_SaveLoadButtonHandler(EMenuKey key);
static void M_DeleteSaveResponse (int choice);
static void M_DrawMainMenu ();
@ -205,6 +209,8 @@ int skullAnimCounter; // skull animation counter
bool drawSkull; // [RH] don't always draw skull
bool M_DemoNoPlay;
bool OptionsActive;
FButtonStatus MenuButtons[NUM_MKEYS];
int MenuButtonTickers[NUM_MKEYS];
// PRIVATE DATA DEFINITIONS ------------------------------------------------
@ -2783,60 +2789,80 @@ bool M_Responder (event_t *ev)
{
int ch;
int i;
EMenuKey mkey = NUM_MKEYS;
bool keyup = true;
ch = -1;
if (chatmodeon)
{
return false;
}
if (menuactive == MENU_Off && ev->type == EV_KeyDown)
{
// Pop-up menu?
if (ev->data1 == KEY_ESCAPE)
{
M_StartControlPanel (true);
M_SetupNextMenu (TopLevelMenu);
M_StartControlPanel(true);
M_SetupNextMenu(TopLevelMenu);
return true;
}
// If devparm is set, pressing F1 always takes a screenshot no matter
// what it's bound to. (for those who don't bother to read the docs)
if (devparm && ev->data1 == KEY_F1)
{
G_ScreenShot (NULL);
G_ScreenShot(NULL);
return true;
}
return false;
}
else if (ev->type == EV_GUI_Event && menuactive == MENU_On && !chatmodeon)
if (menuactive == MENU_WaitKey && OptionsActive)
{
if (ev->subtype == EV_GUI_KeyDown || ev->subtype == EV_GUI_KeyRepeat)
{
ch = ev->data1;
M_OptResponder(ev);
return true;
}
else if (ev->subtype == EV_GUI_Char && genStringEnter)
if (menuactive != MENU_On && menuactive != MENU_OnNoPause &&
!genStringEnter && !messageToPrint)
{
return false;
}
// There are a few input sources we are interested in:
//
// EV_KeyDown / EV_KeyUp : joysticks/gamepads/controllers
// EV_GUI_KeyDown / EV_GUI_KeyUp : the keyboard
// EV_GUI_Char : printable characters, which we want in string input mode
//
// This code previously listened for EV_GUI_KeyRepeat to handle repeating
// in the menus, but that doesn't work with gamepads, so now we combine
// the multiple inputs into buttons and handle the repetition manually.
//
// FIXME: genStringEnter and messageToPrint do not play well with game
// controllers. In fact, you can still interact with the rest of
// the menu using a controller while the menu waits for input. Especially
// bad if you, say, select quit from the main menu. (Ideally,
// genStringEnter will pop up a keypad if it detects a controller is
// being used to navigate the menu. At the very least, it should
// disable the controller.)
if (ev->type == EV_GUI_Event)
{
// Save game and player name string input
if (genStringEnter)
{
if (ev->subtype == EV_GUI_Char)
{
ch = ev->data1;
if (saveCharIndex < genStringLen &&
(genStringEnter==2 || (size_t)SmallFont->StringWidth (savegamestring) < (genStringLen-1)*8))
(genStringEnter == 2 || (size_t)SmallFont->StringWidth(savegamestring) < (genStringLen-1)*8))
{
savegamestring[saveCharIndex] = ch;
savegamestring[saveCharIndex] = (char)ev->data1;
savegamestring[++saveCharIndex] = 0;
}
return true;
}
else if (ev->subtype == EV_GUI_Char && messageToPrint && messageNeedsInput)
if (ev->subtype == EV_GUI_KeyDown || ev->subtype == EV_GUI_KeyUp)
{
ch = ev->data1;
}
}
if (OptionsActive && !chatmodeon)
{
M_OptResponder (ev);
return true;
}
if (ch == -1)
return false;
// Save Game string input
// [RH] and Player Name string input
if (genStringEnter)
{
switch (ch)
@ -2866,10 +2892,36 @@ bool M_Responder (event_t *ev)
}
return true;
}
// Take care of any messages that need input
}
}
if (ev->subtype != EV_GUI_KeyDown && ev->subtype != EV_GUI_KeyUp)
{
return false;
}
ch = ev->data1;
keyup = ev->subtype == EV_GUI_KeyUp;
switch (ch)
{
case GK_ESCAPE: mkey = MKEY_Back; break;
case GK_RETURN: mkey = MKEY_Enter; break;
case GK_UP: mkey = MKEY_Up; break;
case GK_DOWN: mkey = MKEY_Down; break;
case GK_LEFT: mkey = MKEY_Left; break;
case GK_RIGHT: mkey = MKEY_Right; break;
case GK_BACKSPACE: mkey = MKEY_Clear; break;
case GK_PGUP: mkey = MKEY_PageUp; break;
case GK_PGDN: mkey = MKEY_PageDown; break;
default:
if (ch == ' ' && currentMenu == &PSetupDef)
{
mkey = MKEY_Clear;
}
else if (!keyup && !OptionsActive)
{
ch = tolower (ch);
if (messageToPrint)
{
// Take care of any messages that need input
ch = tolower (ch);
if (messageNeedsInput)
{
@ -2882,7 +2934,7 @@ bool M_Responder (event_t *ev)
// EV_GUI_KeyDown.)
if (ev->subtype != EV_GUI_Char && ch != GK_ESCAPE)
{
return false;
// return false;
}
if (ch != ' ' && ch != 'n' && ch != 'y' && ch != GK_ESCAPE)
{
@ -2897,70 +2949,186 @@ bool M_Responder (event_t *ev)
if (menuactive != MENU_Off)
{
M_DeactivateMenuInput ();
M_DeactivateMenuInput();
}
SB_state = screen->GetPageCount (); // refresh the statbar
BorderNeedRefresh = screen->GetPageCount ();
S_Sound (CHAN_VOICE | CHAN_UI, "menu/dismiss", 1, ATTN_NONE);
SB_state = screen->GetPageCount(); // refresh the status bar
BorderNeedRefresh = screen->GetPageCount();
S_Sound(CHAN_VOICE | CHAN_UI, "menu/dismiss", 1, ATTN_NONE);
return true;
}
if (ch == GK_ESCAPE)
else
{
// [RH] Escape now moves back one menu instead of quitting
// the menu system. Thus, backspace is ignored.
currentMenu->lastOn = itemOn;
M_PopMenuStack ();
// Search for a menu item associated with the pressed key.
for (i = (itemOn + 1) % currentMenu->numitems;
i != itemOn;
i = (i + 1) % currentMenu->numitems)
{
if (currentMenu->menuitems[i].alphaKey == ch)
{
break;
}
}
if (currentMenu->menuitems[i].alphaKey == ch)
{
itemOn = i;
S_Sound(CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE);
return true;
}
}
}
break;
}
}
else if (ev->type == EV_KeyDown || ev->type == EV_KeyUp)
{
keyup = ev->type == EV_KeyUp;
ch = ev->data1;
switch (ch)
{
case KEY_JOY1:
case KEY_PAD_A:
mkey = MKEY_Enter;
break;
if (currentMenu == &SaveDef || currentMenu == &LoadDef)
case KEY_JOY2:
case KEY_PAD_B:
mkey = MKEY_Back;
break;
case KEY_JOY3:
case KEY_PAD_X:
mkey = MKEY_Clear;
break;
case KEY_JOY5:
case KEY_PAD_LSHOULDER:
mkey = MKEY_PageUp;
break;
case KEY_JOY6:
case KEY_PAD_RSHOULDER:
mkey = MKEY_PageDown;
break;
case KEY_PAD_DPAD_UP:
case KEY_PAD_LTHUMB_UP:
case KEY_JOYAXIS1MINUS:
case KEY_JOYPOV1_UP:
mkey = MKEY_Up;
break;
case KEY_PAD_DPAD_DOWN:
case KEY_PAD_LTHUMB_DOWN:
case KEY_JOYAXIS1PLUS:
case KEY_JOYPOV1_DOWN:
mkey = MKEY_Down;
break;
case KEY_PAD_DPAD_LEFT:
case KEY_PAD_LTHUMB_LEFT:
case KEY_JOYAXIS2MINUS:
case KEY_JOYPOV1_LEFT:
mkey = MKEY_Left;
break;
case KEY_PAD_DPAD_RIGHT:
case KEY_PAD_LTHUMB_RIGHT:
case KEY_JOYAXIS2PLUS:
case KEY_JOYPOV1_RIGHT:
mkey = MKEY_Right;
break;
}
}
if (mkey != NUM_MKEYS)
{
if (keyup)
{
MenuButtons[mkey].ReleaseKey(ch);
}
else
{
if (MenuButtons[mkey].PressKey(ch))
{
if (mkey <= MKEY_PageDown)
{
MenuButtonTickers[mkey] = KEY_REPEAT_DELAY;
}
M_ButtonHandler(mkey, false);
}
}
}
if (ev->type == EV_GUI_Event && (currentMenu == &SaveDef || currentMenu == &LoadDef))
{
return M_SaveLoadResponder (ev);
}
// Keys usable within menu
switch (ch)
// Eat key downs, but let the rest through.
return !keyup;
}
void M_ButtonHandler(EMenuKey key, bool repeat)
{
if (OptionsActive)
{
case GK_DOWN:
M_OptButtonHandler(key, repeat);
return;
}
if (key == MKEY_Back)
{
// Save the cursor position on the current menu, and pop it off the stack
// to go back to the previous menu.
currentMenu->lastOn = itemOn;
M_PopMenuStack();
return;
}
if (currentMenu == &SaveDef || currentMenu == &LoadDef)
{
M_SaveLoadButtonHandler(key);
return;
}
switch (key)
{
case MKEY_Down:
do
{
if (itemOn+1 > currentMenu->numitems-1)
if (itemOn + 1 >= currentMenu->numitems)
itemOn = 0;
else itemOn++;
S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE);
} while(currentMenu->menuitems[itemOn].status==-1);
return true;
} while (currentMenu->menuitems[itemOn].status == -1);
break;
case GK_UP:
case MKEY_Up:
do
{
if (!itemOn)
itemOn = currentMenu->numitems-1;
if (itemOn == 0)
itemOn = currentMenu->numitems - 1;
else itemOn--;
S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE);
} while(currentMenu->menuitems[itemOn].status==-1);
return true;
} while (currentMenu->menuitems[itemOn].status == -1);
break;
case GK_LEFT:
case MKEY_Left:
if (currentMenu->menuitems[itemOn].routine &&
currentMenu->menuitems[itemOn].status == 2)
{
S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE);
currentMenu->menuitems[itemOn].routine(0);
}
return true;
break;
case GK_RIGHT:
case MKEY_Right:
if (currentMenu->menuitems[itemOn].routine &&
currentMenu->menuitems[itemOn].status == 2)
{
S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE);
currentMenu->menuitems[itemOn].routine(1);
}
return true;
break;
case '\r':
case MKEY_Enter:
if (currentMenu->menuitems[itemOn].routine &&
currentMenu->menuitems[itemOn].status)
{
@ -2976,64 +3144,26 @@ bool M_Responder (event_t *ev)
S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", 1, ATTN_NONE);
}
}
return true;
break;
case ' ':
case MKEY_Clear:
if (currentMenu == &PSetupDef)
{
PlayerRotation ^= 8;
break;
}
// intentional fall-through
default:
if (ch)
{
ch = tolower (ch);
for (i = (itemOn + 1) % currentMenu->numitems;
i != itemOn;
i = (i + 1) % currentMenu->numitems)
{
if (currentMenu->menuitems[i].alphaKey == ch)
{
break;
}
}
if (currentMenu->menuitems[i].alphaKey == ch)
{
itemOn = i;
S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE);
return true;
}
}
break;
}
// [RH] Menu eats all keydown events while active
return (ev->subtype == EV_GUI_KeyDown);
}
bool M_SaveLoadResponder (event_t *ev)
static void M_SaveLoadButtonHandler(EMenuKey key)
{
char workbuf[512];
if (SelSaveGame != NULL && SelSaveGame->Succ != NULL)
if (SelSaveGame == NULL || SelSaveGame->Succ == NULL)
{
switch (ev->data1)
{
case GK_F1:
if (!SelSaveGame->Filename.IsEmpty())
{
mysnprintf (workbuf, countof(workbuf), "File on disk:\n%s", SelSaveGame->Filename.GetChars());
if (SaveComment != NULL)
{
V_FreeBrokenLines (SaveComment);
return;
}
SaveComment = V_BreakLines (SmallFont, 216*screen->GetWidth()/640/CleanXfac, workbuf);
}
break;
case GK_UP:
switch (key)
{
case MKEY_Up:
if (SelSaveGame != SaveGames.Head)
{
if (SelSaveGame == TopSaveGame)
@ -3050,7 +3180,7 @@ bool M_SaveLoadResponder (event_t *ev)
M_ExtractSaveData (SelSaveGame);
break;
case GK_DOWN:
case MKEY_Down:
if (SelSaveGame != SaveGames.TailPred)
{
SelSaveGame = static_cast<FSaveGameNode *>(SelSaveGame->Succ);
@ -3064,6 +3194,43 @@ bool M_SaveLoadResponder (event_t *ev)
M_ExtractSaveData (SelSaveGame);
break;
case MKEY_Enter:
if (currentMenu == &LoadDef)
{
M_LoadSelect (SelSaveGame);
}
else
{
M_SaveSelect (SelSaveGame);
}
break;
}
}
static bool M_SaveLoadResponder (event_t *ev)
{
if (ev->subtype != EV_GUI_KeyDown)
{
return false;
}
if (SelSaveGame != NULL && SelSaveGame->Succ != NULL)
{
switch (ev->data1)
{
case GK_F1:
if (!SelSaveGame->Filename.IsEmpty())
{
char workbuf[512];
mysnprintf (workbuf, countof(workbuf), "File on disk:\n%s", SelSaveGame->Filename.GetChars());
if (SaveComment != NULL)
{
V_FreeBrokenLines (SaveComment);
}
SaveComment = V_BreakLines (SmallFont, 216*screen->GetWidth()/640/CleanXfac, workbuf);
}
break;
case GK_DEL:
case '\b':
if (SelSaveGame != &NewSaveNode)
@ -3082,20 +3249,9 @@ bool M_SaveLoadResponder (event_t *ev)
M_UnloadSaveData ();
}
break;
case '\r':
if (currentMenu == &LoadDef)
{
M_LoadSelect (SelSaveGame);
}
else
{
M_SaveSelect (SelSaveGame);
}
}
}
return (ev->subtype == EV_GUI_KeyDown);
return true;
}
static void M_LoadSelect (const FSaveGameNode *file)
@ -3167,6 +3323,10 @@ void M_StartControlPanel (bool makeSound)
if (menuactive == MENU_On)
return;
for (int i = 0; i < NUM_MKEYS; ++i)
{
MenuButtons[i].ResetTriggers();
}
drawSkull = true;
MenuStackDepth = 0;
currentMenu = TopLevelMenu;
@ -3437,7 +3597,21 @@ void M_Ticker (void)
skullAnimCounter = 8;
}
if (currentMenu == &PSetupDef || currentMenu == &ClassMenuDef)
M_PlayerSetupTicker ();
{
M_PlayerSetupTicker();
}
for (int i = 0; i < NUM_MKEYS; ++i)
{
if (MenuButtons[i].bDown)
{
if (MenuButtonTickers[i] > 0 && --MenuButtonTickers[i] <= 0)
{
MenuButtonTickers[i] = KEY_REPEAT_RATE;
M_ButtonHandler(EMenuKey(i), true);
}
}
}
}

View file

@ -124,6 +124,7 @@ typedef enum {
struct IJoystickConfig;
void UpdateJoystickMenu(IJoystickConfig *selected);
// Yeargh! It's a monster!
struct menuitem_t
{
itemtype type;
@ -150,7 +151,7 @@ struct menuitem_t
int key2;
char *res2;
void *extra;
float discretecenter;
float discretecenter; // 1 to center or 2 to disable repeat (do I even use centered discretes?)
} c;
union {
float step;
@ -238,6 +239,25 @@ struct menustack_t
bool drawSkull;
};
enum EMenuKey
{
MKEY_Up,
MKEY_Down,
MKEY_Left,
MKEY_Right,
MKEY_PageUp,
MKEY_PageDown,
//----------------- Keys past here do not repeat.
MKEY_Enter,
MKEY_Back, // Back to previous menu
MKEY_Clear, // Clear keybinding/flip player sprite preview
NUM_MKEYS
};
void M_ButtonHandler(EMenuKey key, bool repeat);
void M_OptButtonHandler(EMenuKey key, bool repeat);
extern value_t YesNo[2];
extern value_t NoYes[2];
extern value_t OnOff[2];

View file

@ -86,6 +86,8 @@
// Data.
#include "m_menu.h"
extern FButtonStatus MenuButtons[NUM_MKEYS];
EXTERN_CVAR(Bool, nomonsterinterpolation)
EXTERN_CVAR(Int, showendoom)
EXTERN_CVAR(Bool, hud_althud)
@ -1509,7 +1511,7 @@ static void CalcIndent (menu_t *menu)
{
item = menu->items + i;
if (item->type != whitetext && item->type != redtext && item->type != screenres &&
item->type != joymore && (item->type != discrete || !item->c.discretecenter))
item->type != joymore && (item->type != discrete || item->c.discretecenter != 1))
{
thiswidth = SmallFont->StringWidth (item->label);
if (thiswidth > widest)
@ -1681,7 +1683,7 @@ void M_OptDrawer ()
item = CurrentMenu->items + i;
overlay = 0;
if (item->type == discrete && item->c.discretecenter)
if (item->type == discrete && item->c.discretecenter == 1)
{
indent = 160;
}
@ -2095,50 +2097,79 @@ void M_OptDrawer ()
}
}
void M_OptResponder (event_t *ev)
void M_OptResponder(event_t *ev)
{
menuitem_t *item;
int ch = tolower (ev->data1);
UCVarValue value;
item = CurrentMenu->items + CurrentItem;
menuitem_t *item = CurrentMenu->items + CurrentItem;
if (menuactive == MENU_WaitKey && ev->type == EV_KeyDown)
{
if (ev->data1 != KEY_ESCAPE)
{
C_ChangeBinding (item->e.command, ev->data1);
M_BuildKeyList (CurrentMenu->items, CurrentMenu->numitems);
C_ChangeBinding(item->e.command, ev->data1);
M_BuildKeyList(CurrentMenu->items, CurrentMenu->numitems);
}
menuactive = MENU_On;
CurrentMenu->items[0].label = OldMessage;
CurrentMenu->items[0].type = OldType;
return;
}
else if (ev->type == EV_GUI_Event && ev->subtype == EV_GUI_KeyDown && tolower(ev->data1) == 't')
{
// Test selected resolution
if (CurrentMenu == &ModesMenu)
{
if (!(item->type == screenres &&
GetSelectedSize (CurrentItem, &NewWidth, &NewHeight)))
{
NewWidth = SCREENWIDTH;
NewHeight = SCREENHEIGHT;
}
OldWidth = SCREENWIDTH;
OldHeight = SCREENHEIGHT;
OldBits = DisplayBits;
NewBits = BitTranslate[DummyDepthCvar];
setmodeneeded = true;
testingmode = I_GetTime(false) + 5 * TICRATE;
S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", 1, ATTN_NONE);
SetModesMenu (NewWidth, NewHeight, NewBits);
}
}
}
if (ev->subtype == EV_GUI_KeyRepeat)
{
if (ch != GK_LEFT && ch != GK_RIGHT && ch != GK_UP && ch != GK_DOWN)
{
return;
}
}
else if (ev->subtype != EV_GUI_KeyDown)
{
return;
}
void M_OptButtonHandler(EMenuKey key, bool repeat)
{
menuitem_t *item;
UCVarValue value;
item = CurrentMenu->items + CurrentItem;
if (item->type == bitflag &&
(ch == GK_LEFT || ch == GK_RIGHT || ch == '\r')
(key == MKEY_Left || key == MKEY_Right || key == MKEY_Enter)
&& !demoplayback)
{
*(item->a.intcvar) = (*(item->a.intcvar)) ^ item->e.flagmask;
return;
}
switch (ch)
// The controls that manipulate joystick interfaces can only be changed from the
// keyboard, because I can't think of a good way to avoid problems otherwise.
if (item->type == discrete && item->c.discretecenter == 2 && (key == MKEY_Left || key == MKEY_Right))
{
case GK_DOWN:
if (repeat)
{
return;
}
for (int i = 0; i < FButtonStatus::MAX_KEYS; ++i)
{
if (MenuButtons[key].Keys[i] >= KEY_FIRSTJOYBUTTON)
{
return;
}
}
}
switch (key)
{
case MKEY_Down:
if (CurrentMenu->numitems > 1)
{
int modecol;
@ -2187,7 +2218,7 @@ void M_OptResponder (event_t *ev)
}
break;
case GK_UP:
case MKEY_Up:
if (CurrentMenu->numitems > 1)
{
int modecol;
@ -2255,7 +2286,7 @@ void M_OptResponder (event_t *ev)
}
break;
case GK_PGUP:
case MKEY_PageUp:
if (CurrentMenu->scrollpos > 0)
{
CurrentMenu->scrollpos -= VisBottom - CurrentMenu->scrollpos - CurrentMenu->scrolltop;
@ -2277,7 +2308,7 @@ void M_OptResponder (event_t *ev)
}
break;
case GK_PGDN:
case MKEY_PageDown:
if (CanScrollDown)
{
int pagesize = VisBottom - CurrentMenu->scrollpos - CurrentMenu->scrolltop;
@ -2300,7 +2331,7 @@ void M_OptResponder (event_t *ev)
}
break;
case GK_LEFT:
case MKEY_Left:
switch (item->type)
{
case slider:
@ -2497,7 +2528,7 @@ void M_OptResponder (event_t *ev)
}
break;
case GK_RIGHT:
case MKEY_Right:
switch (item->type)
{
case slider:
@ -2697,14 +2728,14 @@ void M_OptResponder (event_t *ev)
}
break;
case '\b':
case MKEY_Clear:
if (item->type == control)
{
C_UnbindACommand (item->e.command);
item->b.key1 = item->c.key2 = 0;
}
break;
/*
case '0':
case '1':
case '2':
@ -2732,8 +2763,8 @@ void M_OptResponder (event_t *ev)
}
// Otherwise, fall through to '\r' below
}
case '\r':
*/
case MKEY_Enter:
if (CurrentMenu == &ModesMenu && item->type == screenres)
{
if (!GetSelectedSize (CurrentItem, &NewWidth, &NewHeight))
@ -2839,7 +2870,7 @@ void M_OptResponder (event_t *ev)
}
break;
case GK_ESCAPE:
case MKEY_Back:
CurrentMenu->lastOn = CurrentItem;
if (CurrentMenu->EscapeHandler != NULL)
{
@ -2847,30 +2878,6 @@ void M_OptResponder (event_t *ev)
}
M_PopMenuStack ();
break;
default:
if (ch == 't')
{
// Test selected resolution
if (CurrentMenu == &ModesMenu)
{
if (!(item->type == screenres &&
GetSelectedSize (CurrentItem, &NewWidth, &NewHeight)))
{
NewWidth = SCREENWIDTH;
NewHeight = SCREENHEIGHT;
}
OldWidth = SCREENWIDTH;
OldHeight = SCREENHEIGHT;
OldBits = DisplayBits;
NewBits = BitTranslate[DummyDepthCvar];
setmodeneeded = true;
testingmode = I_GetTime(false) + 5 * TICRATE;
S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", 1, ATTN_NONE);
SetModesMenu (NewWidth, NewHeight, NewBits);
}
}
break;
}
}
@ -3203,6 +3210,7 @@ void UpdateJoystickMenu(IJoystickConfig *selected)
item.label = "Enable controller support";
item.a.cvar = &use_joystick;
item.b.numvalues = 2;
item.c.discretecenter = 2;
item.e.values = YesNo;
JoystickItems.Push(item);
@ -3222,6 +3230,7 @@ void UpdateJoystickMenu(IJoystickConfig *selected)
item.type = redtext;
item.label = " ";
item.c.discretecenter = 0;
JoystickItems.Push(item);
if (Joysticks.Size() == 0)

View file

@ -296,6 +296,8 @@ FDInputJoystick::FDInputJoystick(const GUID *instance, FString &name)
FDInputJoystick::~FDInputJoystick()
{
unsigned int i;
if (Device != NULL)
{
M_SaveJoystickConfig(this);
@ -306,6 +308,31 @@ FDInputJoystick::~FDInputJoystick()
{
delete[] DataFormat.rgodf;
}
// Send key ups before destroying this.
if (Axes.Size() == 1)
{
Joy_GenerateButtonEvents(Axes[0].ButtonValue, 0, 2, KEY_JOYAXIS1PLUS);
}
else
{
Joy_GenerateButtonEvents(Axes[1].ButtonValue, 0, 4, KEY_JOYAXIS1PLUS);
for (i = 2; i < Axes.Size(); ++i)
{
Joy_GenerateButtonEvents(Axes[i].ButtonValue, 0, 2, KEY_JOYAXIS1PLUS + i*2);
}
}
for (i = 0; i < Buttons.Size(); ++i)
{
if (Buttons[i].Value)
{
event_t ev = { EV_KeyUp };
ev.data1 = KEY_FIRSTJOYBUTTON + i;
}
}
for (i = 0; i < POVs.Size(); ++i)
{
Joy_GenerateButtonEvents(POVs[i].Value, 0, 4, KEY_JOYPOV1_UP + i*4);
}
}
//===========================================================================
@ -399,17 +426,24 @@ void FDInputJoystick::ProcessInput()
AxisInfo *info = &Axes[i];
LONG value = *(LONG *)(state + info->Ofs);
double axisval;
BYTE buttonstate;
BYTE buttonstate = 0;
// Scale to [-1.0, 1.0]
axisval = (value - info->Min) * 2.0 / (info->Max - info->Min) - 1.0;
// Cancel out dead zone
axisval = Joy_RemoveDeadZone(axisval, info->DeadZone, &buttonstate);
info->Value = float(axisval);
if (i < NUM_JOYAXISBUTTONS)
if (i < NUM_JOYAXISBUTTONS && (i > 2 || Axes.Size() == 1))
{
Joy_GenerateButtonEvents(info->ButtonValue, buttonstate, 2, KEY_JOYAXIS1PLUS + i*2);
}
else if (i == 1)
{
// Since we sorted the axes, we know that the first two are definitely X and Y.
// They are probably a single stick, so use angular position to determine buttons.
buttonstate = Joy_XYAxesToButtons(axisval, Axes[0].Value);
Joy_GenerateButtonEvents(info->ButtonValue, buttonstate, 4, KEY_JOYAXIS1PLUS);
}
info->ButtonValue = buttonstate;
}

View file

@ -136,7 +136,7 @@ protected:
void Detached();
void NeutralInput();
static void ProcessThumbstick(int value, AxisInfo *axis, int base);
static void ProcessThumbstick(int value1, AxisInfo *axis1, int value2, AxisInfo *axis2, int base);
friend class FRawPS2Manager;
};
@ -375,6 +375,8 @@ FRawPS2Controller::FRawPS2Controller(HANDLE handle, EAdapterType type, int seque
FRawPS2Controller::~FRawPS2Controller()
{
// Make sure to send any key ups before destroying this.
NeutralInput();
M_SaveJoystickConfig(this);
}
@ -463,19 +465,19 @@ bool FRawPS2Controller::ProcessInput(RAWHID *raw, int code)
}
// Convert axes to floating point and cancel out deadzones.
ProcessThumbstick(rawdata[desc->LeftX], &Axes[AXIS_ThumbLX], KEY_PAD_LTHUMB_RIGHT);
ProcessThumbstick(rawdata[desc->LeftY], &Axes[AXIS_ThumbLY], KEY_PAD_LTHUMB_DOWN);
ProcessThumbstick(rawdata[desc->LeftX], &Axes[AXIS_ThumbLX],
rawdata[desc->LeftY], &Axes[AXIS_ThumbLY], KEY_PAD_LTHUMB_RIGHT);
// If we know we are digital, ignore the right stick.
if (digital)
{
ProcessThumbstick(0x80, &Axes[AXIS_ThumbRX], KEY_PAD_RTHUMB_RIGHT);
ProcessThumbstick(0x80, &Axes[AXIS_ThumbRY], KEY_PAD_RTHUMB_DOWN);
ProcessThumbstick(0x80, &Axes[AXIS_ThumbRX],
0x80, &Axes[AXIS_ThumbRY], KEY_PAD_RTHUMB_RIGHT);
}
else
{
ProcessThumbstick(rawdata[desc->RightX], &Axes[AXIS_ThumbRX], KEY_PAD_RTHUMB_RIGHT);
ProcessThumbstick(rawdata[desc->RightY], &Axes[AXIS_ThumbRY], KEY_PAD_RTHUMB_DOWN);
ProcessThumbstick(rawdata[desc->RightX], &Axes[AXIS_ThumbRX],
rawdata[desc->RightY], &Axes[AXIS_ThumbRY], KEY_PAD_RTHUMB_RIGHT);
}
// Generate events for buttons that have changed.
@ -506,21 +508,27 @@ bool FRawPS2Controller::ProcessInput(RAWHID *raw, int code)
//
// FRawPS2Controller :: ProcessThumbstick STATIC
//
// Converts one axis of a thumb stick to floating point, cancels out the
// deadzone, and generates button up/down events for that axis.
// Converts both axie of a thumb stick to floating point, cancels out the
// deadzone, and generates button up/down events for them.
//
//==========================================================================
void FRawPS2Controller::ProcessThumbstick(int value, AxisInfo *axis, int base)
void FRawPS2Controller::ProcessThumbstick(int value1, AxisInfo *axis1, int value2, AxisInfo *axis2, int base)
{
BYTE buttonstate;
double axisval;
double axisval1, axisval2;
axisval = value * (2.0 / 255) - 1.0;
axisval = Joy_RemoveDeadZone(axisval, axis->DeadZone, &buttonstate);
Joy_GenerateButtonEvents(axis->ButtonValue, buttonstate, 2, base);
axis->ButtonValue = buttonstate;
axis->Value = float(axisval);
axisval1 = value1 * (2.0 / 255) - 1.0;
axisval2 = value2 * (2.0 / 255) - 1.0;
axisval1 = Joy_RemoveDeadZone(axisval1, axis1->DeadZone, NULL);
axisval2 = Joy_RemoveDeadZone(axisval2, axis2->DeadZone, NULL);
axis1->Value = float(axisval1);
axis2->Value = float(axisval2);
// We store all four buttons in the first axis and ignore the second.
buttonstate = Joy_XYAxesToButtons(axisval1, axisval2);
Joy_GenerateButtonEvents(axis1->ButtonValue, buttonstate, 4, base);
axis1->ButtonValue = buttonstate;
}
//==========================================================================
@ -573,11 +581,9 @@ void FRawPS2Controller::Detached()
void FRawPS2Controller::NeutralInput()
{
int i;
for (i = 0; i < NUM_AXES; ++i)
for (int i = 0; i < NUM_AXES; i += 2)
{
ProcessThumbstick(0x80, &Axes[i], KEY_PAD_LTHUMB_RIGHT + i*2);
ProcessThumbstick(0x80, &Axes[i], 0x80, &Axes[i+1], KEY_PAD_LTHUMB_RIGHT + i*2);
}
Joy_GenerateButtonEvents(LastButtons, 0, 16, ButtonKeys);
LastButtons = 0;

View file

@ -105,7 +105,7 @@ protected:
void Attached();
void Detached();
static void ProcessThumbstick(int value, AxisInfo *axis, int base);
static void ProcessThumbstick(int value1, AxisInfo *axis1, int value2, AxisInfo *axis2, int base);
static void ProcessTrigger(int value, AxisInfo *axis, int base);
};
@ -195,6 +195,12 @@ FXInputController::FXInputController(int index)
FXInputController::~FXInputController()
{
// Send button up events before destroying this.
ProcessThumbstick(0, &Axes[AXIS_ThumbLX], 0, &Axes[AXIS_ThumbLY], KEY_PAD_LTHUMB_RIGHT);
ProcessThumbstick(0, &Axes[AXIS_ThumbRX], 0, &Axes[AXIS_ThumbRY], KEY_PAD_RTHUMB_RIGHT);
ProcessTrigger(0, &Axes[AXIS_LeftTrigger], KEY_PAD_LTRIGGER);
ProcessTrigger(0, &Axes[AXIS_RightTrigger], KEY_PAD_RTRIGGER);
Joy_GenerateButtonEvents(LastButtons, 0, 16, KEY_PAD_DPAD_UP);
M_SaveJoystickConfig(this);
}
@ -239,10 +245,10 @@ void FXInputController::ProcessInput()
// Convert axes to floating point and cancel out deadzones.
// XInput's Y axes are reversed compared to DirectInput.
ProcessThumbstick(state.Gamepad.sThumbLX, &Axes[AXIS_ThumbLX], KEY_PAD_LTHUMB_RIGHT);
ProcessThumbstick(-state.Gamepad.sThumbLY, &Axes[AXIS_ThumbLY], KEY_PAD_LTHUMB_DOWN);
ProcessThumbstick(state.Gamepad.sThumbRX, &Axes[AXIS_ThumbRX], KEY_PAD_RTHUMB_RIGHT);
ProcessThumbstick(-state.Gamepad.sThumbRY, &Axes[AXIS_ThumbRY], KEY_PAD_RTHUMB_DOWN);
ProcessThumbstick(state.Gamepad.sThumbLX, &Axes[AXIS_ThumbLX],
-state.Gamepad.sThumbLY, &Axes[AXIS_ThumbLY], KEY_PAD_LTHUMB_RIGHT);
ProcessThumbstick(state.Gamepad.sThumbRX, &Axes[AXIS_ThumbRX],
-state.Gamepad.sThumbRY, &Axes[AXIS_ThumbRY], KEY_PAD_RTHUMB_RIGHT);
ProcessTrigger(state.Gamepad.bLeftTrigger, &Axes[AXIS_LeftTrigger], KEY_PAD_LTRIGGER);
ProcessTrigger(state.Gamepad.bRightTrigger, &Axes[AXIS_RightTrigger], KEY_PAD_RTRIGGER);
@ -257,21 +263,28 @@ void FXInputController::ProcessInput()
//
// FXInputController :: ProcessThumbstick STATIC
//
// Converts one axis of a thumb stick to floating point, cancels out the
// deadzone, and generates button up/down events for that axis.
// Converts both axes of a thumb stick to floating point, cancels out the
// deadzone, and generates button up/down events for them.
//
//==========================================================================
void FXInputController::ProcessThumbstick(int value, AxisInfo *axis, int base)
void FXInputController::ProcessThumbstick(int value1, AxisInfo *axis1,
int value2, AxisInfo *axis2, int base)
{
BYTE buttonstate;
double axisval;
double axisval1, axisval2;
axisval = (value - SHRT_MIN) * 2.0 / 65536 - 1.0;
axisval = Joy_RemoveDeadZone(axisval, axis->DeadZone, &buttonstate);
Joy_GenerateButtonEvents(axis->ButtonValue, buttonstate, 2, base);
axis->ButtonValue = buttonstate;
axis->Value = float(axisval);
axisval1 = (value1 - SHRT_MIN) * 2.0 / 65536 - 1.0;
axisval2 = (value2 - SHRT_MIN) * 2.0 / 65536 - 1.0;
axisval1 = Joy_RemoveDeadZone(axisval1, axis1->DeadZone, NULL);
axisval2 = Joy_RemoveDeadZone(axisval2, axis2->DeadZone, NULL);
axis1->Value = float(axisval1);
axis2->Value = float(axisval2);
// We store all four buttons in the first axis and ignore the second.
buttonstate = Joy_XYAxesToButtons(axisval1, axisval2);
Joy_GenerateButtonEvents(axis1->ButtonValue, buttonstate, 4, base);
axis1->ButtonValue = buttonstate;
}
//==========================================================================
@ -331,9 +344,9 @@ void FXInputController::Detached()
int i;
Connected = false;
for (i = 0; i < 4; ++i)
for (i = 0; i < 4; i += 2)
{
ProcessThumbstick(0, &Axes[i], KEY_PAD_LTHUMB_RIGHT + i*2);
ProcessThumbstick(0, &Axes[i], 0, &Axes[i+1], KEY_PAD_LTHUMB_RIGHT + i*2);
}
for (i = 0; i < 2; ++i)
{