From da07772b1c897212c36f706b792410b2159eec7d Mon Sep 17 00:00:00 2001 From: Matthew Coppola Date: Sun, 15 Dec 2024 21:33:33 -0500 Subject: [PATCH] Allow typing with keyboard in menuitemKeyboard --- port/include/input.h | 26 +++++ port/src/input.c | 54 +++++++++ src/game/menu.c | 29 +++++ src/game/menuitem.c | 261 ++++++++++++++++++++++++++++++++++++------- src/include/data.h | 2 + 5 files changed, 329 insertions(+), 43 deletions(-) diff --git a/port/include/input.h b/port/include/input.h index b0a38298f..cbfd4a88c 100644 --- a/port/include/input.h +++ b/port/include/input.h @@ -21,6 +21,7 @@ enum virtkey { VK_KEYBOARD_BEGIN = 0, VK_RETURN = 40, VK_ESCAPE = 41, + VK_BACKSPACE = 42, VK_DELETE = 76, /* same order as SDL mouse buttons */ @@ -87,6 +88,14 @@ enum mouselockmode { MLOCK_AUTO = 2 }; +enum keymod { + /* same order as SDL keymods */ + KM_LSHIFT = 0x0001, + KM_RSHIFT = 0x0002, + KM_CAPS = 0x2000, + KM_SHIFT = KM_LSHIFT | KM_RSHIFT +}; + // returns bitmask of connected controllers or -1 if failed s32 inputInit(void); @@ -140,6 +149,9 @@ s32 inputAssignController(s32 cidx, s32 id); s32 inputKeyPressed(u32 vk); s32 inputKeyJustPressed(u32 vk); +// same as inputKeyJustPressed but returns true again if key repeat activates +s32 inputKeyJustPressedWithRepeat(u32 vk); + // idx is controller index, contbtn is one of the CONT_ constants s32 inputButtonPressed(s32 idx, u32 contbtn); @@ -217,4 +229,18 @@ s32 inputAutoLockMouse(s32 wantlock); // show/hide mouse cursor; if mouse lock is on the cursor is always hidden void inputMouseShowCursor(s32 show); +// enables/disables/queries text input event processing, respectively +void inputStartTextInput(void); +void inputStopTextInput(void); +s32 inputIsTextInputActive(void); + +// fills textInputBuffer with textInput chars +void inputSetTextInput(char *textInput); + +// returns pointer to textInputBuffer +char *inputGetTextInput(void); + +// returns keymod values +u32 inputGetModState(void); + #endif diff --git a/port/src/input.c b/port/src/input.c index 59a842d71..d4f016d7f 100644 --- a/port/src/input.c +++ b/port/src/input.c @@ -87,6 +87,10 @@ static f32 mouseSensX = 1.5f; static f32 mouseSensY = 1.5f; static s32 lastKey = 0; +static s32 keyRepeat = 0; + +// buffer used to store text input during a text input event +static char textInputBuffer[11] = {0}; static const char *ckNames[CK_TOTAL_COUNT] = { "R_CBUTTONS", @@ -496,6 +500,7 @@ static int inputEventFilter(void *data, SDL_Event *event) if (!lastKey) { lastKey = VK_KEYBOARD_BEGIN + event->key.keysym.scancode; } + keyRepeat = event->key.repeat; break; case SDL_CONTROLLERBUTTONDOWN: @@ -521,6 +526,9 @@ static int inputEventFilter(void *data, SDL_Event *event) } } break; + case SDL_TEXTINPUT: + inputSetTextInput(event->text.text); + break; default: break; @@ -658,6 +666,9 @@ s32 inputInit(void) SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC); } + // SDL enables text input event handling by default + inputStopTextInput(); + // try to load controller db from an external file in the save folder if (fsFileSize("$S/" CONTROLLERDB_FNAME)) { const char *dbpath = fsFullPath("$S/" CONTROLLERDB_FNAME); @@ -1114,6 +1125,11 @@ s32 inputKeyJustPressed(u32 vk) return result; } +s32 inputKeyJustPressedWithRepeat(u32 vk) +{ + return inputKeyJustPressed(vk) || (keyRepeat && inputKeyPressed(vk)); +} + static inline u32 inputContToContKey(const u32 cont) { if (cont == 0) { @@ -1316,6 +1332,44 @@ s32 inputGetLastKey(void) return lastKey; } +void inputStartTextInput(void) +{ + SDL_StartTextInput(); +} + +void inputStopTextInput(void) +{ + SDL_StopTextInput(); + textInputBuffer[0] = '\0'; +} + +s32 inputIsTextInputActive(void) +{ + return SDL_IsTextInputActive(); +} + +void inputSetTextInput(char *textInput) +{ + for (size_t i = 0; i < sizeof textInputBuffer - 1; ++i) { + if (textInput && i < strlen(textInput)) { + textInputBuffer[i] = textInput[i]; + } else { + textInputBuffer[i] = '\0'; + } + } +} + +char *inputGetTextInput(void) +{ + return textInputBuffer; +} + + +u32 inputGetModState(void) +{ + return SDL_GetModState(); +} + PD_CONSTRUCTOR static void inputConfigInit(void) { configRegisterInt("Input.MouseEnabled", &mouseEnabled, 0, 1); diff --git a/src/game/menu.c b/src/game/menu.c index e2bf7d9a6..bef1ab4e8 100644 --- a/src/game/menu.c +++ b/src/game/menu.c @@ -141,6 +141,8 @@ s32 g_MpPlayerNum = 0; #ifndef PLATFORM_N64 s32 g_MenuMouseControl = true; s32 g_MenuUsingMouse = false; +s32 g_MenuTypeWithKeyboard = false; +s32 g_MenuTypeBackspace = false; #endif void menuPlaySound(s32 menusound) @@ -602,7 +604,11 @@ void menuCalculateItemSize(struct menuitem *item, s16 *width, s16 *height, struc break; case MENUITEMTYPE_KEYBOARD: *width = 130; +#ifndef PLATFORM_N64 + *height = inputGetAssignedControllerId(g_MpPlayerNum) == -1 ? 84 : 73; +#else *height = 73; +#endif break; case MENUITEMTYPE_LIST: if (item->param2 > 0) { @@ -4695,6 +4701,8 @@ void menuProcessInput(void) inputs.back2 = 0; #ifndef PLATFORM_N64 + s32 usingcontroller = inputGetAssignedControllerId(g_MpPlayerNum) != -1; + inputs.mousemoved = false; inputs.mousescroll = 0; inputs.mousex = 0; @@ -4703,6 +4711,11 @@ void menuProcessInput(void) if (menu->playernum == 0) { // ESC always acts as back inputs.back = inputKeyJustPressed(VK_ESCAPE); + if (inputs.back && g_MenuTypeWithKeyboard) { + if (inputIsTextInputActive()) { + inputStopTextInput(); + } + } if (inputMouseIsEnabled() && !inputMouseIsLocked() && g_MenuMouseControl) { inputs.mousemoved = inputMouseGetPosition(&inputs.mousex, &inputs.mousey); inputs.mousescroll = inputKeyPressed(VK_MOUSE_WHEEL_DN) - inputKeyPressed(VK_MOUSE_WHEEL_UP); @@ -4713,6 +4726,14 @@ void menuProcessInput(void) if (dialog && inputs.mousemoved) { g_MenuUsingMouse = true; } + g_MenuTypeBackspace = inputKeyJustPressedWithRepeat(VK_BACKSPACE); + + if (usingcontroller && g_MenuTypeWithKeyboard) { + g_MenuTypeWithKeyboard = false; + if (inputIsTextInputActive()) { + inputStopTextInput(); + } + } } #endif @@ -4783,7 +4804,11 @@ void menuProcessInput(void) #endif if (buttonsnow & B_BUTTON) { +#ifndef PLATFORM_N64 + inputs.back = inputIsTextInputActive() && !usingcontroller ? 0 : 1; +#else inputs.back = 1; +#endif } if (buttonsnow & Z_TRIG) { @@ -4795,7 +4820,11 @@ void menuProcessInput(void) } if (buttons & R_TRIG) { +#ifndef PLATFORM_N64 + inputs.shoulder = inputIsTextInputActive() && !usingcontroller ? 0 : 1; +#else inputs.shoulder = 1; +#endif } if ((stickx < 0 ? -stickx : stickx) < (thisstickx < 0 ? -thisstickx : thisstickx)) { diff --git a/src/game/menuitem.c b/src/game/menuitem.c index c95acb89e..c71548a9b 100644 --- a/src/game/menuitem.c +++ b/src/game/menuitem.c @@ -22,6 +22,9 @@ #include "lib/str.h" #include "data.h" #include "types.h" +#ifndef PLATFORM_N64 +#include "input.h" +#endif u8 g_MpSelectedPlayersForStats[MAX_PLAYERS]; @@ -1059,6 +1062,15 @@ Gfx *menuitemDropdownOverlay(Gfx *gdl, s16 x, s16 y, s16 x2, s16 y2, struct menu return gdl; } +#ifndef PLATFORM_N64 +static s32 menuitemKeyboardGetNumRows(s32 rows, s32 usingcontroller) +{ + return rows + !usingcontroller; +} +#else +#define menuitemKeyboardGetNumRows(rows, usingcontroller) (rows) +#endif + bool menuitemKeyboardIsStringEmptyOrSpaces(char *text) { s32 i; @@ -1110,6 +1122,9 @@ Gfx *menuitemKeyboardRender(Gfx *gdl, struct menurendercontext *context) u32 alpha; s32 textheight; s32 textwidth; +#ifndef PLATFORM_N64 + s32 usingcontroller = inputGetAssignedControllerId(g_MpPlayerNum) != -1; +#endif label[1] = '\n'; label[2] = '\0'; @@ -1164,7 +1179,7 @@ Gfx *menuitemKeyboardRender(Gfx *gdl, struct menurendercontext *context) gdl = text0f153838(gdl); // Render horizontal grid lines - for (row = 0; row < 6; row++) { + for (row = 0; row < menuitemKeyboardGetNumRows(6, usingcontroller); row++) { gdl = menugfxDrawFilledRect(gdl, context->x + 4, context->y + row * 11 + 13, context->x + 124, context->y + row * 11 + 14, 0x00ffff7f, 0x00ffff7f); } @@ -1177,6 +1192,12 @@ Gfx *menuitemKeyboardRender(Gfx *gdl, struct menurendercontext *context) rowspan = 4; } +#ifndef PLATFORM_N64 + if (!usingcontroller && (col == 0 || col == 10)) { + rowspan = 6; + } +#endif + gdl = menugfxDrawFilledRect(gdl, context->x + col * 12 + 4, context->y + 13, context->x + col * 12 + 5, context->y + rowspan * 11 + 14, 0x00ffff7f, 0x00ffff7f); } @@ -1187,7 +1208,7 @@ Gfx *menuitemKeyboardRender(Gfx *gdl, struct menurendercontext *context) y = context->y + 2; for (col = 0; col < 10; col++) { - for (row = 0; row < 5; row++) { + for (row = 0; row < menuitemKeyboardGetNumRows(5, usingcontroller); row++) { if (context->dialog->transitionfrac < 0) { textcolour = g_MenuColours[context->dialog->type].item_unfocused; } else { @@ -1206,6 +1227,12 @@ Gfx *menuitemKeyboardRender(Gfx *gdl, struct menurendercontext *context) textcolour = (textcolour & 0xff) | 0xffff0000; } +#ifndef PLATFORM_N64 + if (g_MenuTypeWithKeyboard && !usingcontroller && col == 0 && row == 5) { + textcolour = (textcolour & 0xff) | 0xffff0000; + } +#endif + // If this button is the focused one, set highlighted colour if (col == data->col && row == data->row) { alpha = menuGetSinOscFrac(40) * 255; @@ -1262,6 +1289,33 @@ Gfx *menuitemKeyboardRender(Gfx *gdl, struct menurendercontext *context) textMeasure(&textheight, &textwidth, langGet(labels[index]), g_CharsHandelGothicXs, g_FontHandelGothicXs, 0); x = (buttonwidth - textwidth) / 2 + x; +#ifndef PLATFORM_N64 + // Dim the CAPSLOCK button if typing with keyboard + if (index == 1 && g_MenuTypeWithKeyboard && !usingcontroller) { + if (data->capseffective) { + // CAPS button - make it yellow + textcolour = (textcolour & 0xff) | 0x7f7f0000; + } else { + if (context->dialog->transitionfrac < 0) { + textcolour = g_MenuColours[context->dialog->type].item_disabled; + } else { + textcolour = colourBlend( + g_MenuColours[context->dialog->type2].item_disabled, + g_MenuColours[context->dialog->type].item_disabled, + context->dialog->colourweight); + } + + if (context->dialog->dimmed) { + textcolour = (colourBlend(textcolour, 0, 127) & 0xffffff00) | (textcolour & 0xff); + } + + textSetWaveColours( + g_MenuWave2Colours[context->dialog->type].item_disabled, + g_MenuWave1Colours[context->dialog->type].item_disabled); + } + } +#endif + // Dim the OK button if string is empty if (index == 3 && menuitemKeyboardIsStringEmptyOrSpaces(data->string)) { if (context->dialog->transitionfrac < 0) { @@ -1284,13 +1338,29 @@ Gfx *menuitemKeyboardRender(Gfx *gdl, struct menurendercontext *context) gdl = textRenderProjected(gdl, &x, &y, langGet(labels[index]), g_CharsHandelGothicXs, g_FontHandelGothicXs, textcolour, context->width, context->height, 0, 0); - if (index == 3 && menuitemKeyboardIsStringEmptyOrSpaces(data->string)) { + if ((index == 3 && menuitemKeyboardIsStringEmptyOrSpaces(data->string)) +#ifndef PLATFORM_N64 + || (index == 1 && !usingcontroller && g_MenuTypeWithKeyboard) +#endif + ) { textSetWaveColours( g_MenuWave2Colours[context->dialog->type].item_unfocused, g_MenuWave1Colours[context->dialog->type].item_unfocused); } } - } else { + } +#ifndef PLATFORM_N64 + else if (row == 5) { + static char *kbtext = "TYPE WITH KEYBOARD"; + if (col == 0) { + ++y; + textMeasure(&textheight, &textwidth, kbtext, g_CharsHandelGothicXs, g_FontHandelGothicXs, 0); + x = context->x + (context->width - textwidth) / 2; + gdl = textRenderProjected(gdl, &x, &y, kbtext, g_CharsHandelGothicXs, g_FontHandelGothicXs, textcolour, context->width, context->height, 0, 0); + } + } +#endif + else { // Alpha-numeric cell label[0] = g_KeyboardKeys[row][col]; @@ -1333,6 +1403,14 @@ Gfx *menuitemKeyboardRender(Gfx *gdl, struct menurendercontext *context) } } +#ifndef PLATFORM_N64 + if (data->row == 5 && !usingcontroller) { + if (data->col == 0) { + x2 += 108; + } + } +#endif + gdl = menugfxDrawLine(gdl, x1, y1, x2, y1 + 1, -1, -1); // top gdl = menugfxDrawLine(gdl, x2, y1, x2 + 1, y2 + 1, -1, -1); // right gdl = menugfxDrawLine(gdl, x1, y2, x2, y2 + 1, -1, -1); // bottom @@ -1361,6 +1439,9 @@ bool menuitemKeyboardTick(struct menuitem *item, struct menuinputs *inputs, u32 struct menuitemdata_keyboard *kb = &data->keyboard; union handlerdata handlerdata; s32 delete = false; +#ifndef PLATFORM_N64 + s32 usingcontroller = inputGetAssignedControllerId(g_MpPlayerNum) != -1; +#endif if (tickflags & MENUTICKFLAG_ITEMISFOCUSED) { s16 prevcol = kb->col; @@ -1373,7 +1454,7 @@ bool menuitemKeyboardTick(struct menuitem *item, struct menuinputs *inputs, u32 const s32 dleft = dialog->x + 4; const s32 dright = dleft + 12 * 10; const s32 dtop = menuitemGetTop(item, dialog) + 12; - const s32 dbottom = dtop + 11 * 5; + const s32 dbottom = dtop + 11 * (5 + !usingcontroller); const s32 mx = inputs->mousex; const s32 my = inputs->mousey; if (mx > dleft && mx < dright && my > dtop && my < dbottom) { @@ -1390,57 +1471,80 @@ bool menuitemKeyboardTick(struct menuitem *item, struct menuinputs *inputs, u32 kb->col = 8; } } + if (kb->row == 5 && !usingcontroller) { + kb->col = 0; + } } } + + if (usingcontroller && kb->row == 5) { + kb->row = 4; + kb->col = 0; + } + + if (g_MenuTypeWithKeyboard && !usingcontroller) { + inputStartTextInput(); + } else #endif + { + // Handle left/right movement + // In most cases the loop only runs once, but on row 4 the buttons span + // across multiple columns so the loop will run again until the column + // number is valid. + if (inputs->leftright != 0) { + do { + kb->col += inputs->leftright; - // Handle left/right movement - // In most cases the loop only runs once, but on row 4 the buttons span - // across multiple columns so the loop will run again until the column - // number is valid. - if (inputs->leftright != 0) { - do { - kb->col += inputs->leftright; + if (kb->col < 0) { + kb->col = 9; + } - if (kb->col < 0) { - kb->col = 9; - } - - if (kb->col > 9) { - kb->col = 0; - } - } while (kb->row == 4 && kb->col != 0 && kb->col != 2 && kb->col != 5 && kb->col != 8); - } - - // Handle up/down movement - if (inputs->updown) { - kb->row += inputs->updown; - - if (kb->row < 0) { - kb->row = 4; + if (kb->col > 9) { + kb->col = 0; + } + } while ((kb->row == 4 && kb->col != 0 && kb->col != 2 && kb->col != 5 && kb->col != 8) +#ifndef PLATFORM_N64 + || (!usingcontroller && kb->row == 5 && kb->col != 0) +#endif + ); } - if (kb->row > 4) { - kb->row = 0; - } + // Handle up/down movement + if (inputs->updown) { + kb->row += inputs->updown; - // If moving onto row 4, bump column to a valid one - if (kb->row == 4) { - if (kb->col == 9) { - kb->col = 8; + if (kb->row < 0) { + kb->row = menuitemKeyboardGetNumRows(4, usingcontroller); } - if (kb->col == 7 || kb->col == 6) { - kb->col = 5; + if (kb->row > menuitemKeyboardGetNumRows(4, usingcontroller)) { + kb->row = 0; } - if (kb->col == 3 || kb->col == 4) { - kb->col = 2; + // If moving onto row 4, bump column to a valid one + if (kb->row == 4) { + if (kb->col == 9) { + kb->col = 8; + } + + if (kb->col == 7 || kb->col == 6) { + kb->col = 5; + } + + if (kb->col == 3 || kb->col == 4) { + kb->col = 2; + } + + if (kb->col == 1) { + kb->col = 0; + } } - if (kb->col == 1) { +#ifndef PLATFORM_N64 + if (kb->row == 5 && !usingcontroller) { kb->col = 0; } +#endif } } @@ -1462,6 +1566,10 @@ bool menuitemKeyboardTick(struct menuitem *item, struct menuinputs *inputs, u32 menuPopDialog(); item->handler(MENUOP_SET, item, &handlerdata); + +#ifndef PLATFORM_N64 + inputStopTextInput(); +#endif } inputs->start = false; @@ -1475,7 +1583,12 @@ bool menuitemKeyboardTick(struct menuitem *item, struct menuinputs *inputs, u32 } // CAPS - if (kb->col == 2) { + + if (kb->col == 2 +#ifndef PLATFORM_N64 + && (!g_MenuTypeWithKeyboard || usingcontroller) +#endif + ) { kb->capslock = 1 - kb->capslock; } @@ -1502,9 +1615,30 @@ bool menuitemKeyboardTick(struct menuitem *item, struct menuinputs *inputs, u32 } inputs->select = false; + +#ifndef PLATFORM_N64 + inputStopTextInput(); +#endif } } - } else { + } +#ifndef PLATFORM_N64 + else if (!usingcontroller && kb->row == 5 && kb->col == 0) { + g_MenuTypeWithKeyboard ^= 1; + + if (g_MenuTypeWithKeyboard) { + menuPlaySound(MENUSOUND_TOGGLEON); + inputStartTextInput(); + } else { + if (kb->capslock) { + kb->capslock = 0; + } + menuPlaySound(MENUSOUND_TOGGLEOFF); + inputStopTextInput(); + } + } +#endif + else { // Pressed A on number or letter s32 appended = false; s32 i; @@ -1543,6 +1677,43 @@ bool menuitemKeyboardTick(struct menuitem *item, struct menuinputs *inputs, u32 } } } +#ifndef PLATFORM_N64 + else if (g_MenuTypeWithKeyboard && !usingcontroller) { + delete = g_MenuTypeBackspace; + kb->capslock = (inputGetModState() & KM_CAPS) ? 1 : 0; + + if (strlen(inputGetTextInput())) { + s32 textwidth, textheight, charwidth; + size_t insertidx = strlen(kb->string); + textMeasure(&textheight, &textwidth, kb->string, g_CharsHandelGothicSm, g_FontHandelGothicSm, 0); + char *textinput = inputGetTextInput(); + size_t textinputlen = textinput ? strlen(textinput) : 0; + + for (size_t j = 0; j < textinputlen && insertidx < sizeof kb->string - 1; ++j) { + u8 key[2] = {0}; + key[0] = textinput[j]; + + if ((key[0] >= ' ' && key[0] <= '!') || + (key[0] == '.') || + (key[0] >= '0' && key[0] <= '9') || + (key[0] == '?') || + (key[0] >= 'A' && key[0] <= 'Z') || + (key[0] >= 'a' && key[0] <= 'z')) { + textMeasure(&textheight, &charwidth, key, g_CharsHandelGothicSm, g_FontHandelGothicSm, 0); + textwidth += charwidth; + + if (textwidth <= 58) { + kb->string[insertidx++] = key[0]; + } + + menuPlaySound(MENUSOUND_FOCUS); + } + } + + inputSetTextInput(NULL); + } + } +#endif // Handle deleting if (delete && kb->string[0] != '\0') { @@ -1567,7 +1738,11 @@ bool menuitemKeyboardTick(struct menuitem *item, struct menuinputs *inputs, u32 u32 prev = kb->capseffective; kb->capseffective = kb->capslock; - if (inputs->shoulder) { + if (inputs->shoulder +#ifndef PLATFORM_N64 + || (g_MenuTypeWithKeyboard && !usingcontroller && (inputGetModState() & KM_SHIFT)) +#endif + ) { // Invert kb->capseffective = 1 - kb->capseffective; } diff --git a/src/include/data.h b/src/include/data.h index 24c106292..207860c5d 100644 --- a/src/include/data.h +++ b/src/include/data.h @@ -554,6 +554,8 @@ extern s32 g_TickExtraSleep; extern s32 g_MusicDisableMpDeath; extern s32 g_BgunGeMuzzleFlashes; extern s32 g_FileAutoSelect; +extern s32 g_MenuTypeWithKeyboard; +extern s32 g_MenuTypeBackspace; #define PLAYER_EXTCFG() g_PlayerExtCfg[g_Vars.currentplayerstats->mpindex & 3] #define PLAYER_DEFAULT_FOV (PLAYER_EXTCFG().fovy)