diff --git a/port/include/input.h b/port/include/input.h index b0a38298f..916cd72ba 100644 --- a/port/include/input.h +++ b/port/include/input.h @@ -19,9 +19,29 @@ enum virtkey { /* same order as SDL scancodes */ VK_KEYBOARD_BEGIN = 0, + VK_A = 4, + VK_Z = 29, + VK_1 = 30, + VK_9 = 38, + VK_0 = 39, VK_RETURN = 40, VK_ESCAPE = 41, + VK_BACKSPACE = 42, + VK_SPACE = 44, + VK_MINUS = 45, + VK_LEFTBRACKET = 47, + VK_RIGHTBRACKET = 48, + VK_SEMICOLON = 51, + VK_GRAVE = 53, + VK_COMMA = 54, + VK_PERIOD = 55, + VK_F1 = 58, + VK_F9 = 66, VK_DELETE = 76, + VK_LCTRL = 224, + VK_LSHIFT = 225, + VK_RCTRL = 228, + VK_RSHIFT = 229, /* same order as SDL mouse buttons */ VK_MOUSE_BEGIN = 512, @@ -45,6 +65,17 @@ enum virtkey { VK_TOTAL_COUNT = VK_JOY_BEGIN + INPUT_MAX_CONTROLLERS * INPUT_MAX_CONTROLLER_BUTTONS, }; +enum keymod { + /* same order as SDL keymods */ + KM_LSHIFT = 0x0001, + KM_RSHIFT = 0x0002, + KM_LCTRL = 0x0040, + KM_RCTRL = 0x0080, + KM_CAPS = 0x2000, + KM_CTRL = KM_LCTRL | KM_RCTRL, + KM_SHIFT = KM_LSHIFT | KM_RSHIFT +}; + enum contkey { CK_C_R, CK_C_L, @@ -217,4 +248,19 @@ s32 inputAutoLockMouse(s32 wantlock); // show/hide mouse cursor; if mouse lock is on the cursor is always hidden void inputMouseShowCursor(s32 show); +void inputStartTextInput(void); +void inputStopTextInput(void); +s32 inputIsTextInputActive(void); + +void inputClearLastTextChar(void); +char inputGetLastTextChar(void); + +s32 inputTextHandler(char *out, const u32 outSize, s32 *curCol, s32 oskCharsOnly); + +void inputClearClipboard(void); +const char *inputGetClipboard(void); + +// returns keymod values +u32 inputGetKeyModState(void); + #endif diff --git a/port/src/input.c b/port/src/input.c index d02177e44..24f693dbf 100644 --- a/port/src/input.c +++ b/port/src/input.c @@ -88,6 +88,10 @@ static f32 mouseSensX = 1.5f; static f32 mouseSensY = 1.5f; static s32 lastKey = 0; +static char lastChar = 0; +static s32 textInput = 0; + +static char *clipboardText = NULL; static const char *ckNames[CK_TOTAL_COUNT] = { "R_CBUTTONS", @@ -523,6 +527,12 @@ static int inputEventFilter(void *data, SDL_Event *event) } break; + case SDL_TEXTINPUT: + if (!lastChar && event->text.text[0] && (u8)event->text.text[0] < 0x80) { + lastChar = event->text.text[0]; + } + break; + default: break; } @@ -734,6 +744,14 @@ s32 inputReadController(s32 idx, OSContPad *npad) npad->button = 0; + if (textInput) { + npad->stick_x = 0; + npad->stick_y = 0; + npad->rstick_x = 0; + npad->rstick_y = 0; + return 0; + } + for (u32 i = 0; i < CONT_NUM_BUTTONS; ++i) { if (inputBindPressed(idx, i)) { npad->button |= 1U << i; @@ -1330,6 +1348,116 @@ s32 inputGetLastKey(void) return lastKey; } +void inputStartTextInput(void) +{ + lastChar = 0; + lastKey = 0; + textInput = 1; + SDL_StartTextInput(); +} + +void inputClearLastTextChar(void) +{ + lastChar = 0; +} + +char inputGetLastTextChar(void) +{ + return lastChar; +} + +static inline s32 filterChar(const char ch) +{ + return isalnum(ch) || ch == ' ' || ch == '?' || ch == '!' || ch == '.'; +} + +s32 inputTextHandler(char *out, const u32 outSize, s32 *curCol, s32 oskCharsOnly) +{ + const s32 ctrlHeld = inputGetKeyModState() & KM_CTRL; + + if (!ctrlHeld) { + const char chr = inputGetLastTextChar(); + inputClearLastTextChar(); + const s32 valid = chr && (oskCharsOnly ? filterChar(chr) : isprint(chr)); + if (valid) { + if (*curCol < outSize - 1) { + out[(*curCol)++] = chr; + out[*curCol] = '\0'; + } + } + } + + const s32 key = inputGetLastKey(); + inputClearLastKey(); + if (ctrlHeld && (key == VK_A + ('v' - 'a'))) { + // CTRL+V; paste from clipboard + const char *clip = inputGetClipboard(); + if (clip) { + const s32 remain = outSize - *curCol - 1; + inputClearClipboard(); + *curCol += snprintf(out + *curCol, remain, "%s", clip); + if (*curCol > outSize) { + *curCol = outSize; + } + } + } else if (key == VK_BACKSPACE) { + if (*curCol) { + out[--*curCol] = '\0'; + } else { + out[0] = '\0'; + } + } else if (key == VK_RETURN) { + if (out[0] && *curCol) { + return 1; + } + } else if (key == VK_ESCAPE) { + return -1; + } + + return 0; +} + +void inputClearClipboard(void) +{ + if (clipboardText) { + SDL_free(clipboardText); + clipboardText = NULL; + } +} + +const char *inputGetClipboard(void) +{ + if (!clipboardText) { + char *text = SDL_GetClipboardText(); + if (text) { + clipboardText = text; + // remove non-printable and multibyte chars + for (; *text; ++text) { + if ((u8)*text < 0x20 || (u8)*text >= 0x7F) { + *text = '?'; + } + } + } + } + return clipboardText; +} + +void inputStopTextInput(void) +{ + SDL_StopTextInput(); + textInput = 0; +} + +s32 inputIsTextInputActive(void) +{ + return textInput; +} + +u32 inputGetKeyModState(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..970b62ddb 100644 --- a/src/game/menu.c +++ b/src/game/menu.c @@ -141,6 +141,7 @@ s32 g_MpPlayerNum = 0; #ifndef PLATFORM_N64 s32 g_MenuMouseControl = true; s32 g_MenuUsingMouse = false; +s32 g_MenuKeyboardPlayer = -1; #endif void menuPlaySound(s32 menusound) @@ -602,7 +603,11 @@ void menuCalculateItemSize(struct menuitem *item, s16 *width, s16 *height, struc break; case MENUITEMTYPE_KEYBOARD: *width = 130; +#ifndef PLATFORM_N64 + *height = 84; +#else *height = 73; +#endif break; case MENUITEMTYPE_LIST: if (item->param2 > 0) { @@ -4713,6 +4718,14 @@ void menuProcessInput(void) if (dialog && inputs.mousemoved) { g_MenuUsingMouse = true; } + if (inputs.back && g_MenuKeyboardPlayer != -1) { + // eat the back input so that other menus don't quit when we hit ESC + inputs.back = false; + if (g_MenuKeyboardPlayer == menu->playernum) { + g_MenuKeyboardPlayer = -1; + inputStopTextInput(); + } + } } #endif diff --git a/src/game/menuitem.c b/src/game/menuitem.c index c95acb89e..7bf3a37ea 100644 --- a/src/game/menuitem.c +++ b/src/game/menuitem.c @@ -22,6 +22,12 @@ #include "lib/str.h" #include "data.h" #include "types.h" +#ifndef PLATFORM_N64 +#include "input.h" +#define MENU_KEYBOARD_ROWS 6 +#else +#define MENU_KEYBOARD_ROWS 5 +#endif u8 g_MpSelectedPlayersForStats[MAX_PLAYERS]; @@ -1164,7 +1170,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 < MENU_KEYBOARD_ROWS + 1; row++) { gdl = menugfxDrawFilledRect(gdl, context->x + 4, context->y + row * 11 + 13, context->x + 124, context->y + row * 11 + 14, 0x00ffff7f, 0x00ffff7f); } @@ -1181,13 +1187,20 @@ Gfx *menuitemKeyboardRender(Gfx *gdl, struct menurendercontext *context) context->x + col * 12 + 5, context->y + rowspan * 11 + 14, 0x00ffff7f, 0x00ffff7f); } +#if MENU_KEYBOARD_ROWS > 5 + gdl = menugfxDrawFilledRect(gdl, context->x + 4, context->y + 5 * 11 + 13, + context->x + 5, context->y + 6 * 11 + 14, 0x00ffff7f, 0x00ffff7f); + gdl = menugfxDrawFilledRect(gdl, context->x + 10 * 12 + 4, context->y + 5 * 11 + 13, + context->x + 10 * 12 + 5, context->y + 6 * 11 + 14, 0x00ffff7f, 0x00ffff7f); +#endif + gdl = text0f153628(gdl); x = context->x + 10; y = context->y + 2; for (col = 0; col < 10; col++) { - for (row = 0; row < 5; row++) { + for (row = 0; row < MENU_KEYBOARD_ROWS; row++) { if (context->dialog->transitionfrac < 0) { textcolour = g_MenuColours[context->dialog->type].item_unfocused; } else { @@ -1282,6 +1295,19 @@ Gfx *menuitemKeyboardRender(Gfx *gdl, struct menurendercontext *context) g_MenuWave1Colours[context->dialog->type].item_disabled); } +#ifndef PLATFORM_N64 + // Dim the Caps button if keyboard typing is enabled + if (index == 1 && g_MenuKeyboardPlayer == g_MpPlayerNum) { + textcolour = colourBlend( + g_MenuColours[context->dialog->type2].item_disabled, + g_MenuColours[context->dialog->type].item_disabled, + context->dialog->colourweight); + textSetWaveColours( + g_MenuWave2Colours[context->dialog->type].item_disabled, + g_MenuWave1Colours[context->dialog->type].item_disabled); + } +#endif + 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)) { @@ -1290,6 +1316,23 @@ Gfx *menuitemKeyboardRender(Gfx *gdl, struct menurendercontext *context) g_MenuWave1Colours[context->dialog->type].item_unfocused); } } + } else if (row == 5) { +#ifndef PLATFORM_N64 + if (col == 0) { + char *kbtext; + // make the button yellow if it's active + if (g_MenuKeyboardPlayer == g_MpPlayerNum) { + textcolour = (textcolour & 0xff) | 0xffff0000; + kbtext = (char *)"ESC: CANCEL ENTER: OK"; + } else { + kbtext = (char *)"TYPE WITH KEYBOARD"; + } + ++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]; @@ -1331,6 +1374,8 @@ Gfx *menuitemKeyboardRender(Gfx *gdl, struct menurendercontext *context) if (data->col == 0) { x2 += 12; } + } else if (data->row == 5) { + x2 = context->x + 9 * 12 + 16; } gdl = menugfxDrawLine(gdl, x1, y1, x2, y1 + 1, -1, -1); // top @@ -1373,7 +1418,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 * 6; const s32 mx = inputs->mousex; const s32 my = inputs->mousey; if (mx > dleft && mx < dright && my > dtop && my < dbottom) { @@ -1389,6 +1434,8 @@ bool menuitemKeyboardTick(struct menuitem *item, struct menuinputs *inputs, u32 } else { kb->col = 8; } + } else if (kb->row == 5) { + kb->col = 0; } } } @@ -1417,10 +1464,10 @@ bool menuitemKeyboardTick(struct menuitem *item, struct menuinputs *inputs, u32 kb->row += inputs->updown; if (kb->row < 0) { - kb->row = 4; + kb->row = MENU_KEYBOARD_ROWS - 1; } - if (kb->row > 4) { + if (kb->row > MENU_KEYBOARD_ROWS - 1) { kb->row = 0; } @@ -1444,6 +1491,10 @@ bool menuitemKeyboardTick(struct menuitem *item, struct menuinputs *inputs, u32 } } + if (kb->row == 5) { + kb->col = 0; + } + if (prevcol != kb->col || prevrow != kb->row) { menuPlaySound(MENUSOUND_KEYBOARDFOCUS); } @@ -1467,6 +1518,37 @@ bool menuitemKeyboardTick(struct menuitem *item, struct menuinputs *inputs, u32 inputs->start = false; } +#ifndef PLATFORM_N64 + if (g_MenuKeyboardPlayer == g_MpPlayerNum) { + // match caps state to keyboard shift/caps if typing with keyboard + const u32 kmod = inputGetKeyModState(); + kb->capslock = (kmod & KM_CAPS) != 0; + if (kmod & KM_SHIFT) { + kb->capslock = !kb->capslock; + } + // handle text input + s32 prevpos = strlen(kb->string); + s32 pos = prevpos; + s32 result = inputTextHandler(kb->string, sizeof(kb->string), &pos, true); + if (result == -1) { + // cancel + kb->row = 5; + kb->col = 0; + inputs->select = true; + } else if (result == 1) { + // accept + kb->row = 4; + kb->col = 8; + inputs->select = true; + g_MenuKeyboardPlayer = -1; + inputStopTextInput(); + } else if (prevpos < pos) { + // added char + menuPlaySound(MENUSOUND_FOCUS); + } + } +#endif + if (inputs->select) { if (kb->row == 4) { // DEL @@ -1504,6 +1586,19 @@ bool menuitemKeyboardTick(struct menuitem *item, struct menuinputs *inputs, u32 inputs->select = false; } } + } else if (kb->row == 5) { +#ifndef PLATFORM_N64 + if (g_MenuKeyboardPlayer == -1) { + g_MenuKeyboardPlayer = g_MpPlayerNum; + inputClearLastTextChar(); + inputStartTextInput(); + menuPlaySound(MENUSOUND_SELECT); + } else { + g_MenuKeyboardPlayer = -1; + inputStopTextInput(); + menuPlaySound(MENUSOUND_KEYBOARDCANCEL); + } +#endif } else { // Pressed A on number or letter s32 appended = false; diff --git a/src/include/data.h b/src/include/data.h index 24c106292..07086603f 100644 --- a/src/include/data.h +++ b/src/include/data.h @@ -538,6 +538,7 @@ extern const struct weathercfg g_DefaultWeatherConfig; extern const struct weathercfg *g_CurWeatherConfig; extern s32 g_MenuUsingMouse; +extern s32 g_MenuKeyboardPlayer; extern f32 g_ViShakeIntensityMult; extern u32 g_TexFilter2D;