From a4b6934cc53c8f26fbd519469f0f0b413345fd67 Mon Sep 17 00:00:00 2001 From: NepDisk Date: Thu, 14 Aug 2025 22:57:24 -0400 Subject: [PATCH] Basic unified text input port --- src/CMakeLists.txt | 1 - src/Sourcefile | 1 + src/console.c | 342 +++++++-------------------------------------- src/doomdef.h | 2 +- src/lua_script.c | 9 +- src/m_menu.c | 111 +++++++-------- src/m_textinput.c | 307 ++++++++++++++++++++++++++++++++++++++++ src/m_textinput.h | 23 +++ src/v_video.c | 27 ++-- src/v_video.h | 11 +- 10 files changed, 464 insertions(+), 370 deletions(-) create mode 100644 src/m_textinput.c create mode 100644 src/m_textinput.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c67313b74..4cec6d0ad 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -441,4 +441,3 @@ if("${CMAKE_SYSTEM_NAME}" STREQUAL Windows AND NOT "${SRB2_CONFIG_INTERNAL_LIBRA COMMENT "Copying runtime DLLs" ) endif() - diff --git a/src/Sourcefile b/src/Sourcefile index 67158b82f..1c01a18cc 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -34,6 +34,7 @@ m_cond.c m_easing.c m_fixed.c m_menu.c +m_textinput.c m_memcpy.c m_misc.cpp m_perfstats.c diff --git a/src/console.c b/src/console.c index 6f1cd7dcc..9c948b284 100644 --- a/src/console.c +++ b/src/console.c @@ -32,6 +32,7 @@ #include "i_threads.h" #include "d_main.h" #include "m_menu.h" +#include "m_textinput.h" #include "filesrch.h" #include "m_misc.h" @@ -101,9 +102,7 @@ static char inputlines[32][CON_MAXPROMPTCHARS]; // hold last 32 prompt lines static INT32 inputline; // current input line number static INT32 inputhist; // line number of history input line to restore -static size_t input_cur; // position of cursor in line -static size_t input_sel; // position of selection marker (I.E.: anything between this and input_cur is "selected") -static size_t input_len; // length of current line, used to bound cursor and such +static textinput_t input; // notice: input does NOT include the "$" at the start of the line. - 11/3/16 // protos. @@ -502,7 +501,8 @@ static void CON_InputInit(void) // prepare the first prompt line memset(inputlines, 0, sizeof (inputlines)); inputline = 0; - input_cur = input_sel = input_len = 0; + + M_TextInputInit(&input, inputlines[inputline], CON_MAXPROMPTCHARS); Unlock_state(); } @@ -799,119 +799,6 @@ void CON_Ticker(void) Unlock_state(); } -// -// ---- -// -// Shortcuts for adding and deleting characters, strings, and sections -// Necessary due to moving cursor -// - -static void CON_InputClear(void) -{ - Lock_state(); - - memset(inputlines[inputline], 0, CON_MAXPROMPTCHARS); - input_cur = input_sel = input_len = 0; - - Unlock_state(); -} - -static void CON_InputSetString(const char *c) -{ - Lock_state(); - - memset(inputlines[inputline], 0, CON_MAXPROMPTCHARS); - strcpy(inputlines[inputline], c); - input_cur = input_sel = input_len = strlen(c); - - Unlock_state(); -} - -static void CON_InputAddString(const char *c) -{ - size_t csize = strlen(c); - - Lock_state(); - - if (input_len + csize > CON_MAXPROMPTCHARS-1) - { - Unlock_state(); - return; - } - if (input_cur != input_len) - memmove(&inputlines[inputline][input_cur+csize], &inputlines[inputline][input_cur], input_len-input_cur); - memcpy(&inputlines[inputline][input_cur], c, csize); - input_len += csize; - input_sel = (input_cur += csize); - - Unlock_state(); -} - -static void CON_InputDelSelection(void) -{ - size_t start, end, len; - - Lock_state(); - - if (!input_cur) - { - Unlock_state(); - return; - } - - if (input_cur > input_sel) - { - start = input_sel; - end = input_cur; - } - else - { - start = input_cur; - end = input_sel; - } - len = (end - start); - - if (end != input_len) - memmove(&inputlines[inputline][start], &inputlines[inputline][end], input_len-end); - memset(&inputlines[inputline][input_len - len], 0, len); - - input_len -= len; - input_sel = input_cur = start; - - Unlock_state(); -} - -static void CON_InputAddChar(char c) -{ - if (input_len >= CON_MAXPROMPTCHARS-1) - return; - - Lock_state(); - - if (input_cur != input_len) - memmove(&inputlines[inputline][input_cur+1], &inputlines[inputline][input_cur], input_len-input_cur); - inputlines[inputline][input_cur++] = c; - inputlines[inputline][++input_len] = 0; - input_sel = input_cur; - - Unlock_state(); -} - -static void CON_InputDelChar(void) -{ - if (!input_cur) - return; - - Lock_state(); - - if (input_cur != input_len) - memmove(&inputlines[inputline][input_cur-1], &inputlines[inputline][input_cur], input_len-input_cur); - inputlines[inputline][--input_len] = 0; - input_sel = --input_cur; - - Unlock_state(); -} - // // ---- // @@ -995,77 +882,16 @@ boolean CON_Responder(event_t *ev) } } + Lock_state(); + M_TextInputHandle(&input, key); + Unlock_state(); + // Always eat ctrl/shift/alt if console open, so the menu doesn't get ideas if (key == KEY_LSHIFT || key == KEY_RSHIFT || key == KEY_LCTRL || key == KEY_RCTRL || key == KEY_LALT || key == KEY_RALT) return true; - if (key == KEY_LEFTARROW) - { - if (input_cur != 0) - { - if (ctrldown) - input_cur = M_JumpWordReverse(inputlines[inputline], input_cur); - else - --input_cur; - } - if (!shiftdown) - input_sel = input_cur; - return true; - } - else if (key == KEY_RIGHTARROW) - { - if (input_cur < input_len) - { - if (ctrldown) - input_cur += M_JumpWord(&inputlines[inputline][input_cur]); - else - ++input_cur; - } - if (!shiftdown) - input_sel = input_cur; - return true; - } - - // backspace and delete command prompt - if (input_sel != input_cur) - { - if (key == KEY_BACKSPACE || key == KEY_DEL) - { - CON_InputDelSelection(); - return true; - } - } - else if (key == KEY_BACKSPACE) - { - if (ctrldown) - { - input_sel = M_JumpWordReverse(inputlines[inputline], input_cur); - CON_InputDelSelection(); - } - else - CON_InputDelChar(); - return true; - } - else if (key == KEY_DEL) - { - if (input_cur == input_len) - return true; - - if (ctrldown) - { - input_sel = input_cur + M_JumpWord(&inputlines[inputline][input_cur]); - CON_InputDelSelection(); - } - else - { - ++input_cur; - CON_InputDelChar(); - } - return true; - } - // ctrl modifier -- changes behavior, adds shortcuts if (ctrldown) { @@ -1076,9 +902,9 @@ boolean CON_Responder(event_t *ev) if (!completion[0]) { - if (!input_len || input_len >= 40 || strchr(inputlines[inputline], ' ')) + if (!input.length || input.length >= 40 || strchr(input.buffer, ' ')) return true; - strcpy(completion, inputlines[inputline]); + strcpy(completion, input.buffer); } len = strlen(completion); @@ -1117,42 +943,9 @@ boolean CON_Responder(event_t *ev) return true; } - if (key == 'x' || key == 'X') - { - if (input_sel > input_cur) - I_ClipboardCopy(&inputlines[inputline][input_cur], input_sel-input_cur); - else - I_ClipboardCopy(&inputlines[inputline][input_sel], input_cur-input_sel); - CON_InputDelSelection(); + // Those are already handled in M_TextInputHandle, but they do extra logic... Maybe textinput_t should have some sort of callbacks? + if (key == 'x' || key == 'X' || key == 'v' || key == 'V') completion[0] = 0; - return true; - } - else if (key == 'c' || key == 'C') - { - if (input_sel > input_cur) - I_ClipboardCopy(&inputlines[inputline][input_cur], input_sel-input_cur); - else - I_ClipboardCopy(&inputlines[inputline][input_sel], input_cur-input_sel); - return true; - } - else if (key == 'v' || key == 'V') - { - const char *paste = I_ClipboardPaste(); - if (input_sel != input_cur) - CON_InputDelSelection(); - if (paste != NULL) - CON_InputAddString(paste); - completion[0] = 0; - return true; - } - - // Select all - if (key == 'a' || key == 'A') - { - input_sel = 0; - input_cur = input_len; - return true; - } // ...why shouldn't it eat the key? if it doesn't, it just means you // can control Sonic from the console, which is silly @@ -1167,9 +960,9 @@ boolean CON_Responder(event_t *ev) // remember typing for several completions (a-la-4dos) if (!completion[0]) { - if (!input_len || input_len >= 40 || strchr(inputlines[inputline], ' ')) + if (!input.length || input.length >= 40 || strchr(input.buffer, ' ')) return true; - strcpy(completion, inputlines[inputline]); + strcpy(completion, input.buffer); skips = 0; com_skips = 0; var_skips = 0; @@ -1223,7 +1016,9 @@ boolean CON_Responder(event_t *ev) if (cmd) { - CON_InputSetString(va("%s ", cmd)); + Lock_state(); + M_TextInputSetString(&input, va("%s ", cmd)); + Unlock_state(); } else { @@ -1246,20 +1041,6 @@ boolean CON_Responder(event_t *ev) con_scrollup--; return true; } - else if (key == KEY_HOME) - { - input_cur = 0; - if (!shiftdown) - input_sel = input_cur; - return true; - } - else if (key == KEY_END) - { - input_cur = input_len; - if (!shiftdown) - input_sel = input_cur; - return true; - } // At this point we're messing with input // Clear completion @@ -1268,21 +1049,28 @@ boolean CON_Responder(event_t *ev) // command enter if (key == KEY_ENTER) { - if (!input_len) + if (!input.length) return true; // push the command - COM_BufAddText(inputlines[inputline]); + COM_BufAddText(input.buffer); COM_BufAddText("\n"); CONS_Printf("\x86""%c""\x80""%s\n", CON_PROMPTCHAR, inputlines[inputline]); + Lock_state(); + // Only add command to history if it differs from previous one - if (strcmp(inputlines[inputline], inputlines[(inputline-1) & 31])) + if (strcmp(input.buffer, inputlines[(inputline-1) & 31])) + { inputline = (inputline+1) & 31; + M_TextInputInit(&input, inputlines[inputline], CON_MAXPROMPTCHARS); + } inputhist = inputline; - CON_InputClear(); + M_TextInputClear(&input); + + Unlock_state(); return true; } @@ -1300,7 +1088,9 @@ boolean CON_Responder(event_t *ev) if (inputhist == inputline) inputhist = (inputline + 1) & 31; - CON_InputSetString(inputlines[inputhist]); + Lock_state(); + M_TextInputSetString(&input, inputlines[inputhist]); + Unlock_state(); return true; } @@ -1314,46 +1104,16 @@ boolean CON_Responder(event_t *ev) while (inputhist != inputline && !inputlines[inputhist][0]); // back to currentline + Lock_state(); if (inputhist == inputline) - CON_InputClear(); + M_TextInputClear(&input); else - CON_InputSetString(inputlines[inputhist]); + M_TextInputSetString(&input, inputlines[inputhist]); + Unlock_state(); + return true; } - // allow people to use keypad in console (good for typing IP addresses) - Calum - if (key >= KEY_KEYPAD7 && key <= KEY_KPADDEL) - { - char keypad_translation[] = {'7','8','9','-', - '4','5','6','+', - '1','2','3', - '0','.'}; - - key = keypad_translation[key - KEY_KEYPAD7]; - } - else if (key == KEY_KPADSLASH) - key = '/'; - - // same capslock code as hu_stuff.c's HU_responder. Check there for details. - if ((key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z')) - { - if (shiftdown ^ capslock) - key = shiftxform[key]; - } - else - { - if (shiftdown) - key = shiftxform[key]; - } - - // enter a char into the command prompt - if (key < 32 || key > 127) - return true; - - if (input_sel != input_cur) - CON_InputDelSelection(); - CON_InputAddChar(key); - return true; } @@ -1618,16 +1378,16 @@ static void CON_DrawInput(void) clen = con_width-13; - if (input_len <= clen) + if (input.length <= clen) { c = 0; - clen = input_len; + clen = input.length; } else // input line scrolls left if it gets too long { clen -= 2; // There will always be some extra truncation -- but where is what we'll find out - if (input_cur <= clen/2) + if (input.cursor <= clen/2) { // Close enough to right edge to show all c = 0; @@ -1638,15 +1398,15 @@ static void CON_DrawInput(void) { // Cursor in the middle (or right side) of input // Move over for the ellipsis - c = input_cur - (clen/2) + 2; + c = input.cursor - (clen/2) + 2; x += charwidth*2; lellip = 1; - if (c + clen >= input_len) + if (c + clen >= input.length) { // Cursor in the right side of input // We were too far over, so move back - c = input_len - clen; + c = input.length - clen; } else { @@ -1660,8 +1420,8 @@ static void CON_DrawInput(void) if (lellip) { x -= charwidth*3; - if (input_sel < c) - V_DrawFill(x, y, charwidth*3, (10 * con_scalefactor), 77 | V_NOSCALESTART); + if (input.select < c) + V_DrawFill(x, y, charwidth*3, (10 * con_scalefactor), 107 | V_NOSCALESTART); for (i = 0; i < 3; ++i, x += charwidth) V_DrawCharacter(x, y, '.' | cv_constextsize.value | V_GRAYMAP | V_NOSCALESTART, true); } @@ -1670,7 +1430,7 @@ static void CON_DrawInput(void) for (cend = c + clen; c < cend; ++c, x += charwidth) { - if ((input_sel > c && input_cur <= c) || (input_sel <= c && input_cur > c)) + if ((input.select > c && input.cursor <= c) || (input.select <= c && input.cursor > c)) { V_DrawFill(x, y, charwidth, (10 * con_scalefactor), 77 | V_NOSCALESTART); V_DrawCharacter(x, y, p[c] | cv_constextsize.value | V_YELLOWMAP | V_NOSCALESTART, true); @@ -1678,15 +1438,15 @@ static void CON_DrawInput(void) else V_DrawCharacter(x, y, p[c] | cv_constextsize.value | V_NOSCALESTART, true); - if (c == input_cur && con_tick >= 4) - V_DrawCharacter(x, y + (con_scalefactor*2), '_' | cv_constextsize.value | V_NOSCALESTART, true); + if (c == input.cursor && con_tick >= 4) + V_DrawCharacter(x, y + (con_scalefactor*2), '_' | cv_constextsize.value | V_NOSCALESTART, !cv_menucaps.value); } - if (cend == input_cur && con_tick >= 4) - V_DrawCharacter(x, y + (con_scalefactor*2), '_' | cv_constextsize.value | V_NOSCALESTART, true); + if (cend == input.cursor && con_tick >= 4) + V_DrawCharacter(x, y + (con_scalefactor*2), '_' | cv_constextsize.value | V_NOSCALESTART, !cv_menucaps.value); if (rellip) { - if (input_sel > cend) - V_DrawFill(x, y, charwidth*3, (10 * con_scalefactor), 77 | V_NOSCALESTART); + if (input.select > cend) + V_DrawFill(x, y, charwidth*3, (10 * con_scalefactor), 107 | V_NOSCALESTART); for (i = 0; i < 3; ++i, x += charwidth) V_DrawCharacter(x, y, '.' | cv_constextsize.value | V_GRAYMAP | V_NOSCALESTART, true); } diff --git a/src/doomdef.h b/src/doomdef.h index 3b1b4eef8..20cc7fee5 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -104,7 +104,7 @@ extern "C" { // Special Hashing. //#define NOMD5 //#define NOFILEHASH -//#define NOVERIFYIWADS +#define NOVERIFYIWADS // Uncheck this to compile debugging code //#define RANGECHECK diff --git a/src/lua_script.c b/src/lua_script.c index 4d187cae5..6cbd1ccd9 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -1824,8 +1824,13 @@ void LUA_Step(void) { if (!gL) return; - lua_settop(gL, 0); - lua_gc(gL, LUA_GCSTEP, 1); + + if (lua_gettop(gL) != 0) + { + CONS_Alert(CONS_WARNING, "Eek, there is garbage on lua stack!\n"); + lua_settop(gL, 0); + lua_gc(gL, LUA_GCSTEP, 1); + } } void LUA_Archive(savebuffer_t *save, boolean network) diff --git a/src/m_menu.c b/src/m_menu.c index b26bbae42..f42ea4bb4 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -30,7 +30,9 @@ #include "hu_stuff.h" #include "g_game.h" #include "g_input.h" +#include "v_video.h" #include "m_argv.h" +#include "m_textinput.h" // Data. #include "sounds.h" @@ -142,6 +144,9 @@ const char *quitmsg[NUM_QUITMESSAGES]; boolean fromlevelselect = false; +char menu_text_input_buf[MAXSTRINGLENGTH]; +static textinput_t menuinput; + typedef enum { LLM_CREATESERVER, @@ -1128,60 +1133,15 @@ static UINT32 M_StringCvarLength(consvar_t *cv) static boolean M_ChangeStringCvar(INT32 choice) { consvar_t *cv = currentMenu->menuitems[itemOn].cvar; - char buf[MAXSTRINGLENGTH]; - size_t len = strlen(cv->string); - if (shiftdown && choice >= 32 && choice <= 127) - choice = shiftxform[choice]; - - switch (choice) + if (M_TextInputHandle(&menuinput, choice)) { - case KEY_BACKSPACE: - if (len > 0) - { - S_StartSound(NULL,sfx_menu1); // Tails - M_Memcpy(buf, cv->string, len); - buf[len-1] = 0; - CV_Set(cv, buf); - } - return true; - case KEY_DEL: - if (cv->string[0]) - { - S_StartSound(NULL,sfx_menu1); // Tails - CV_Set(cv, ""); - } - return true; - case KEY_LEFTARROW: - case KEY_RIGHTARROW: - // TODO: navigation - return true; - default: - if (cv == &cv_dummyip) - { - // Rudimentary number and period enforcing - also allows letters so hostnames can be used instead - if (!((choice >= '-' && choice <= ':') || (choice >= 'A' && choice <= 'Z') || (choice >= 'a' && choice <= 'z')) - && !(choice >= KEY_KEYPAD7 && choice <= KEY_KPADDEL && choice != KEY_MINUSPAD && choice != KEY_PLUSPAD)) //numpad too! - break; + S_StartSound(NULL,sfx_menu1); // Tails + CV_Set(cv, menuinput.buffer); - if (choice >= KEY_KEYPAD7) - // sir, have you been golfing tonight? - choice = "789-456+1230."[choice - KEY_KEYPAD7]; - } - - if (choice < 32 || choice > 127) - break; - - if (len < M_StringCvarLength(cv) - 1) - { - S_StartSound(NULL, sfx_menu1); // Tails - M_Memcpy(buf, cv->string, len); - buf[len++] = (char)choice; - buf[len] = 0; - CV_Set(cv, buf); - } - return true; + return true; } + return false; } @@ -1282,6 +1242,20 @@ static boolean M_WipeBuffer(INT32 ch, menufunc_f *routine) // use this when routine being NULL isn't a free pass static INT32 MR_Dummy(INT32 ch) { return true; } +void M_UpdateTextInputString(void) +{ + menuitem_t *item = currentMenu->numitems ? ¤tMenu->menuitems[itemOn] : NULL; + + if (item && item->cvar && !item->cvar->PossibleValue) + { + // Just in case + memset(menu_text_input_buf, 0, sizeof menu_text_input_buf); + M_TextInputInit(&menuinput, menu_text_input_buf, sizeof menu_text_input_buf); + M_TextInputSetString(&menuinput, item->cvar->string); + CONS_Printf("Set input string to %s\n", item->cvar->string); + } +} + // // M_Responder // @@ -2260,7 +2234,24 @@ void M_DrawTextBox(INT32 x, INT32 y, INT32 width, INT32 boxlines) { // Solid color textbox. V_DrawFill(x+5, y+5, width*8+6, boxlines*8+6, 159); - //V_DrawFill(x+8, y+8, width*8, boxlines*8, 31); +} + +void M_DrawTextInput(INT32 x, INT32 y, textinput_t *input, INT32 flags) +{ + V_DrawString(x, y, V_ALLOWLOWERCASE|flags, input->buffer); + // draw text cursor for name + if (input->length && skullAnimCounter < 4) // blink cursor + V_DrawCharacter(x+V_SubStringWidth(input->buffer, input->cursor, V_ALLOWLOWERCASE), y+3, '_'|flags, false); + + // draw selection + if (input->select != input->cursor) + { + size_t start = min(input->select, input->cursor); + size_t end = max(input->select, input->cursor); + size_t len = end - start; + INT32 startx = V_SubStringWidth(input->buffer, start, V_ALLOWLOWERCASE); + V_DrawFill(x+startx, y, V_SubStringWidth(input->buffer+start, len, V_ALLOWLOWERCASE), 8, 103|V_TRANSLUCENT|flags); + } } // horizontally centered text @@ -2368,10 +2359,8 @@ static void M_DrawRightString(menuitem_t *item, INT16 x, INT16 y, INT32 vflags, w = M_StringCvarLength(cv); M_DrawTextBox(x + xofs, y + yofs, w, 1); - V_DrawString(x + xofs + 8, y + yofs + 8, vflags|V_ALLOWLOWERCASE, cv->string); - if (selected && skullAnimCounter < 4) // blink cursor - V_DrawCharacter(x + xofs + 8 + V_StringWidth(cv->string, vflags|V_ALLOWLOWERCASE), y + yofs + 8, - '_' | (vflags & ~(V_FLIP|V_PARAMMASK)), false); + if (menuinput.buffer) + M_DrawTextInput(x + xofs, y + yofs, &menuinput, vflags); } else if (item->status & IT_SLIDER) { @@ -2526,7 +2515,7 @@ static INT32 M_DrawMenuItem(menuitem_t *item, INT16 x, INT16 y, INT32 vflags, bo fixed_t scale = item->status & IT_SMALL ? FRACUNIT/2 : FRACUNIT; int font; INT32 colorflag; - INT32 (*widthfunc)(const char *string, INT32 option); + INT32 (*widthfunc)(const char *string, INT32 length, INT32 option); if (item->status & IT_SECRET) { @@ -2609,24 +2598,24 @@ static INT32 M_DrawMenuItem(menuitem_t *item, INT16 x, INT16 y, INT32 vflags, bo vflags |= V_6WIDTHSPACE; // FALLTHRU case ITF_THIN: font = TINY_FONT; - widthfunc = V_ThinStringWidth; + widthfunc = V_ThinSubStringWidth; break; case ITF_HEADER: x -= 16; // FALLTHRU default: font = HU_FONT; - widthfunc = V_StringWidth; + widthfunc = V_SubStringWidth; break; } if (item->status & IT_CENTER) - width = widthfunc(string, vflags)/2; + width = widthfunc(string, -1, vflags)/2; else if (item->status & IT_RIGHT) - width = widthfunc(string, vflags); + width = widthfunc(string, -1, vflags); if (item->status & IT_STICKER) - M_DrawSticker(x, y + 2, widthfunc(string, vflags), vflags & ~V_FLIP, true); + M_DrawSticker(x, y + 2, widthfunc(string, -1, vflags), vflags & ~V_FLIP, true); V_DrawStringScaled((x - width)<length) + memmove(&input->buffer[start], &input->buffer[end], input->length-end); + memset(&input->buffer[input->length - len], 0, len); + + input->length -= len; + + if (input->select >= end) + input->select -= len; + else if (input->select > start) + input->select = start; + + if (input->cursor >= end) + input->cursor -= len; + else if (input->cursor > start) + input->cursor = start; +} + +static void M_TextInputDelSelection(textinput_t *input) +{ + size_t start, end; + + if (input->cursor > input->select) + { + start = input->select; + end = input->cursor; + } + else + { + start = input->cursor; + end = input->select; + } + + M_TextInputDel(input, start, end); + + input->select = input->cursor = start; +} + +static void M_TextInputAddString(textinput_t *input, const char *c) +{ + size_t csize = strlen(c); + + if (input->length + csize > input->buffer_size-1) + return; + + if (input->cursor != input->length) + memmove(&input->buffer[input->cursor+csize], &input->buffer[input->cursor], input->length-input->cursor); + memcpy(&input->buffer[input->cursor], c, csize); + input->length += csize; + input->select = (input->cursor += csize); +} + +static void M_TextInputAddChar(textinput_t *input, char c) +{ + if (input->length >= input->buffer_size-1) + return; + + if (input->cursor != input->length) + memmove(&input->buffer[input->cursor+1], &input->buffer[input->cursor], input->length-input->cursor); + input->buffer[input->cursor++] = c; + input->buffer[++input->length] = 0; + input->select = input->cursor; +} + +static void M_TextInputDelChar(textinput_t *input) +{ + if (!input->cursor) + return; + + if (input->cursor != input->length) + memmove(&input->buffer[input->cursor-1], &input->buffer[input->cursor], input->length-input->cursor); + input->buffer[--input->length] = 0; + input->select = --input->cursor; +} + +static void M_TextInputToWordEnd(textinput_t *input, boolean move_sel) +{ + // Skip spaces + while (input->cursor < input->length && isspace(input->buffer[input->cursor])) + ++input->cursor; + + // Skip word + while (input->cursor < input->length && !isspace(input->buffer[input->cursor])) + ++input->cursor; + + if (move_sel) input->select = input->cursor; +} + +static void M_TextInputToWordBegin(textinput_t *input, boolean move_sel) +{ + // Hack, always move back 1 character if possible so if we press ctrl-left at a word beginning + // we move to previous word + if (input->cursor) --input->cursor; + + // Skip spaces + while (input->cursor && isspace(input->buffer[input->cursor])) + --input->cursor; + + // Skip word + while (input->cursor && !isspace(input->buffer[input->cursor])) + --input->cursor; + + // Unless we reached beginning of line, we're pointing at a space before word, so move cursor + // forward to fix that + if (input->cursor) ++input->cursor; + + if (move_sel) input->select = input->cursor; +} + +void M_TextInputInit(textinput_t *input, char *buffer, size_t buffer_size) +{ + input->buffer = buffer; + input->buffer_size = buffer_size; + + M_TextInputClear(input); +} + +void M_TextInputClear(textinput_t *input) +{ + input->cursor = 0; + input->select = 0; + input->length = 0; +} + +void M_TextInputSetString(textinput_t *input, const char *c) +{ + memset(input->buffer, 0, input->buffer_size); + strcpy(input->buffer, c); + input->cursor = input->select = input->length = strlen(c); +} + +boolean M_TextInputHandle(textinput_t *input, INT32 key) +{ + if (key == KEY_LSHIFT || key == KEY_RSHIFT + || key == KEY_LCTRL || key == KEY_RCTRL + || key == KEY_LALT || key == KEY_RALT) + return false; + + //if ((cv_keyboardlayout.value != 3 && ctrldown) || (cv_keyboardlayout.value == 3 && ctrldown && !altdown)) + if (ctrldown) + { + if (key == 'x' || key == 'X') + { + if (input->select > input->cursor) + I_ClipboardCopy(&input->buffer[input->cursor], input->select-input->cursor); + else + I_ClipboardCopy(&input->buffer[input->select], input->cursor-input->select); + M_TextInputDelSelection(input); + return true; + } + else if (key == 'c' || key == 'C') + { + if (input->select > input->cursor) + I_ClipboardCopy(&input->buffer[input->cursor], input->select-input->cursor); + else + I_ClipboardCopy(&input->buffer[input->select], input->cursor-input->select); + return true; + } + else if (key == 'v' || key == 'V') + { + const char *paste = I_ClipboardPaste(); + if (input->select != input->cursor) + M_TextInputDelSelection(input); + if (paste != NULL) + M_TextInputAddString(input, paste); + return true; + } + else if (key == 'w' || key == 'W') + { + size_t word_start, word_end, i; + word_end = i = input->cursor; + + // Unless we're pointing at the beginning of line, decrement i so we only start + // removing symbols that come before the cursor + if (i) --i; + + // We might be pointing to spaces, skip them first + while (i && isspace(input->buffer[i])) + --i; + + // Now skip the "word" + while (i && !isspace(input->buffer[i])) + --i; + + // Unless we reached beginning of line, i is pointing at first space that was found + // before word start, and we don't want to remove it + if (i) ++i; + + word_start = i; + + if (word_start != word_end) + M_TextInputDel(input, word_start, word_end); + + return true; + } + else if (key == KEY_RIGHTARROW) + M_TextInputToWordEnd(input, !shiftdown); + else if (key == KEY_LEFTARROW) + M_TextInputToWordBegin(input, !shiftdown); + + // Select all + if (key == 'a' || key == 'A') + { + input->select = 0; + input->cursor = input->length; + return true; + } + + // ...why shouldn't it eat the key? if it doesn't, it just means you + // can control Sonic from the console, which is silly + return true; + } + + if (key == KEY_LEFTARROW) + { + if (input->cursor != 0) + --input->cursor; + if (!shiftdown) + input->select = input->cursor; + return true; + } + else if (key == KEY_RIGHTARROW) + { + if (input->cursor < input->length) + ++input->cursor; + if (!shiftdown) + input->select = input->cursor; + return true; + } + else if (key == KEY_HOME) + { + input->cursor = 0; + if (!shiftdown) + input->select = input->cursor; + return true; + } + else if (key == KEY_END) + { + input->cursor = input->length; + if (!shiftdown) + input->select = input->cursor; + return true; + } + + // backspace and delete command prompt + if (input->select != input->cursor) + { + if (key == KEY_BACKSPACE || key == KEY_DEL) + { + M_TextInputDelSelection(input); + return true; + } + } + else if (key == KEY_BACKSPACE) + { + M_TextInputDelChar(input); + return true; + } + else if (key == KEY_DEL) + { + if (input->cursor == input->length) + return true; + ++input->cursor; + M_TextInputDelChar(input); + return true; + } + + // allow people to use keypad in console (good for typing IP addresses) - Calum + if (key >= KEY_KEYPAD7 && key <= KEY_KPADDEL) + { + char keypad_translation[] = {'7','8','9','-', + '4','5','6','+', + '1','2','3', + '0','.'}; + + key = keypad_translation[key - KEY_KEYPAD7]; + } + else if (key == KEY_KPADSLASH) + key = '/'; + + // same capslock code as hu_stuff.c's HU_responder. Check there for details. + key = CON_ShiftChar(key); + + // enter a char into the command prompt + if (key < 32 || key > 127) + return false; + + // add key to cmd line here + if (key >= 'A' && key <= 'Z' && !(shiftdown ^ capslock)) //this is only really necessary for dedicated servers + key = key + 'a' - 'A'; + + if (input->select != input->cursor) + M_TextInputDelSelection(input); + M_TextInputAddChar(input, key); + + return true; +} diff --git a/src/m_textinput.h b/src/m_textinput.h new file mode 100644 index 000000000..aad5e94ab --- /dev/null +++ b/src/m_textinput.h @@ -0,0 +1,23 @@ +#ifndef __M_TEXTINPUT__ +#define __M_TEXTINPUT__ + +#include "doomtype.h" + +typedef struct textinput_s { + size_t cursor; + size_t select; + size_t length; + + char *buffer; + size_t buffer_size; +} textinput_t; + +void M_TextInputInit(textinput_t *input, char *buffer, size_t buffer_size); + +void M_TextInputClear(textinput_t *input); + +void M_TextInputSetString(textinput_t *input, const char *c); + +boolean M_TextInputHandle(textinput_t *input, INT32 key); + +#endif diff --git a/src/v_video.c b/src/v_video.c index 1fa5f16d8..7cc9f0ef7 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -2952,11 +2952,14 @@ INT32 V_LevelNameHeight(const char *string) // // Find string width from hu_font chars // -INT32 V_StringWidth(const char *string, INT32 option) +INT32 V_SubStringWidth(const char *string, INT32 length, INT32 option) { INT32 c, w = 0; INT32 spacewidth = 4, charwidth = 0; - size_t i; + ssize_t i; + + if (length < 0) + length = strlen(string); switch (option & V_SPACINGMASK) { @@ -2972,7 +2975,7 @@ INT32 V_StringWidth(const char *string, INT32 option) break; } - for (i = 0; i < strlen(string); i++) + for (i = 0; string[i] && i < length; i++) { c = string[i]; if ((UINT8)c & 0x80) //color parsing! -Inuyasha 2.16.09 @@ -2994,11 +2997,14 @@ INT32 V_StringWidth(const char *string, INT32 option) // // Find string width from hu_font chars, 0.5x scale // -INT32 V_SmallStringWidth(const char *string, INT32 option) +INT32 V_SmallSubStringWidth(const char *string, INT32 length, INT32 option) { INT32 c, w = 0; INT32 spacewidth = 2, charwidth = 0; - size_t i; + ssize_t i; + + if (length < 0) + length = strlen(string); switch (option & V_SPACINGMASK) { @@ -3014,7 +3020,7 @@ INT32 V_SmallStringWidth(const char *string, INT32 option) break; } - for (i = 0; i < strlen(string); i++) + for (i = 0; string[i] && i < length; i++) { c = string[i]; if ((UINT8)c & 0x80) //color parsing! -Inuyasha 2.16.09 @@ -3033,12 +3039,15 @@ INT32 V_SmallStringWidth(const char *string, INT32 option) // // Find string width from tny_font chars // -INT32 V_ThinStringWidth(const char *string, INT32 option) +INT32 V_ThinSubStringWidth(const char *string, INT32 length, INT32 option) { INT32 c, w = 0; INT32 spacewidth = 2, charwidth = 0; boolean lowercase = (option & V_ALLOWLOWERCASE); - size_t i; + ssize_t i; + + if (length < 0) + length = strlen(string); switch (option & V_SPACINGMASK) { @@ -3055,7 +3064,7 @@ INT32 V_ThinStringWidth(const char *string, INT32 option) break; } - for (i = 0; i < strlen(string); i++) + for (i = 0; string[i] && i < length; i++) { c = string[i]; if ((UINT8)c & 0x80) //color parsing! -Inuyasha 2.16.09 diff --git a/src/v_video.h b/src/v_video.h index f2ab5aa09..5b5da8d77 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -329,13 +329,14 @@ INT16 V_LevelActNumWidth(UINT8 num); // act number width INT32 V_CreditStringWidth(const char *string); // Find string width from hu_font chars -INT32 V_StringWidth(const char *string, INT32 option); +INT32 V_SubStringWidth(const char *string, INT32 length, INT32 option); +#define V_StringWidth(string, option) V_SubStringWidth(string, -1, option) // Find string width from hu_font chars, 0.5x scale -INT32 V_SmallStringWidth(const char *string, INT32 option); +INT32 V_SmallSubStringWidth(const char *string, INT32 length, INT32 option); +#define V_SmallStringWidth(string, option) V_SmallSubStringWidth(string, -1, option) // Find string width from tny_font chars -INT32 V_ThinStringWidth(const char *string, INT32 option); -// Find string width from tny_font chars, 0.5x scale -INT32 V_SmallThinStringWidth(const char *string, INT32 option); +INT32 V_ThinSubStringWidth(const char *string, INT32 length, INT32 option); +#define V_ThinStringWidth(string, option) V_ThinSubStringWidth(string, -1, option) void V_DoPostProcessor(INT32 view, INT32 param);