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/g_demo.c b/src/g_demo.c index 1f34d1cc5..c8b740afe 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -28,6 +28,7 @@ #include "m_misc.h" #include "m_menu.h" #include "m_argv.h" +#include "m_textinput.h" #include "hu_stuff.h" #include "z_zone.h" #include "i_video.h" @@ -2776,7 +2777,19 @@ void G_BeginRecording(void) // Full replay title demobuf.p += 64; - snprintf(demo.titlename, 64, "%s - %s", G_BuildMapTitle(gamemap), modeattacking ? "Record Attack" : connectedservername); + { + char demotitlename[65]; + char *title = G_BuildMapTitle(gamemap); + // Print to a separate temp buffer instead of demo.titlename, so we can use it in M_TextInputSetString + snprintf(demotitlename, 64, "%s - %s", title, modeattacking ? "Time Attack" : connectedservername); + + // Init just in case it isn't initialized already + M_TextInputInit(&demo.titlenameinput, demo.titlename, sizeof(demo.titlename)); + + // This will indirectly assign to demo.titlename too + M_TextInputSetString(&demo.titlenameinput, demotitlename); + Z_Free(title); + } // demo checksum demobuf.p += sizeof(UINT64); @@ -4410,7 +4423,6 @@ void G_SaveDemo(void) boolean G_DemoTitleResponder(event_t *ev) { - size_t len; INT32 ch; if (ev->type != ev_keydown) @@ -4431,28 +4443,7 @@ boolean G_DemoTitleResponder(event_t *ev) return true; } - if ((ch >= HU_FONTSTART && ch <= HU_FONTEND && fontv[HU_FONT].font[ch-HU_FONTSTART]) - || ch == ' ') // Allow spaces, of course - { - len = strlen(demo.titlename); - if (len < 64) - { - demo.titlename[len+1] = 0; - demo.titlename[len] = CON_ShiftChar(ch); - } - } - else if (ch == KEY_BACKSPACE) - { - if (shiftdown) - memset(demo.titlename, 0, sizeof(demo.titlename)); - else - { - len = strlen(demo.titlename); - - if (len > 0) - demo.titlename[len-1] = 0; - } - } + M_TextInputHandle(&demo.titlenameinput, ch); return true; } diff --git a/src/g_demo.h b/src/g_demo.h index 228822085..9271d2240 100644 --- a/src/g_demo.h +++ b/src/g_demo.h @@ -17,6 +17,7 @@ #include "doomdef.h" #include "doomstat.h" #include "d_event.h" +#include "m_textinput.h" #ifdef __cplusplus extern "C" { @@ -46,6 +47,7 @@ typedef enum // Publicly-accessible demo vars struct demovars_s { char titlename[65]; + textinput_t titlenameinput; boolean recording, playback, timing; UINT16 version; // Current file format of the demo being played boolean title; // Title Screen demo can be cancelled by any key diff --git a/src/hu_stuff.c b/src/hu_stuff.c index ff71e987f..5c74c5691 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -44,6 +44,8 @@ #include "p_local.h" // camera[] #include "p_tick.h" +#include "m_textinput.h" + #ifdef HWRENDER #include "hardware/hw_main.h" #endif @@ -87,8 +89,8 @@ patch_t *frameslash; // framerate stuff. Used in screen.c static player_t *plr; boolean hu_keystrokes; // :) boolean chat_on; // entering a chat message? -static char w_chat[HU_MAXMSGLEN + 1]; -static size_t c_input = 0; // let's try to make the chat input less shitty. +static char w_chat_buf[HU_MAXMSGLEN + 1]; +static textinput_t w_chat; static boolean headsupactive = false; boolean hu_showscores; // draw rankings static char hu_tick; @@ -920,71 +922,6 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) #endif } -// Handles key input and string input -// -/*static inline boolean HU_keyInChatString(char *s, char ch) -{ - size_t l; - - if ((ch >= HU_FONTSTART && ch <= HU_FONTEND && fontv[HU_FONT].font[ch-HU_FONTSTART]) - || ch == ' ') // Allow spaces, of course - { - l = strlen(s); - if (l < HU_MAXMSGLEN - 1) - { - if (c_input >= strlen(s)) // don't do anything complicated - { - s[l++] = ch; - s[l]=0; - } - else - { - // move everything past c_input for new characters: - size_t m = HU_MAXMSGLEN-1; - while (m>=c_input) - { - if (s[m]) - s[m+1] = (s[m]); - if (m == 0) // prevent overflow - break; - m--; - } - s[c_input] = ch; // and replace this. - } - c_input++; - return true; - } - return false; - } - else if (ch == KEY_BACKSPACE) - { - size_t i = c_input; - - if (c_input <= 0) - return false; - - if (!s[i-1]) - return false; - - if (i >= strlen(s)-1) - { - s[strlen(s)-1] = 0; - c_input--; - return false; - } - - for (; (i < HU_MAXMSGLEN); i++) - { - s[i-1] = s[i]; - } - c_input--; - } - else if (ch != KEY_ENTER) - return false; // did not eat key - - return true; // ate the key -}*/ - // // static void HU_TickSongCredits(void) @@ -1109,8 +1046,8 @@ static boolean HU_chatboxContainsOnlySpaces(void) { size_t i; - for (i = 0; w_chat[i]; i++) - if (w_chat[i] != ' ') + for (i = 0; w_chat_buf[i]; i++) + if (w_chat_buf[i] != ' ') return false; return true; @@ -1128,16 +1065,15 @@ static void HU_sendChatMessage(void) return; // copy printable characters and terminating '\0' only. - for (ci = 2; w_chat[ci-2]; ci++) + for (ci = 2; w_chat_buf[ci-2]; ci++) { - char c = w_chat[ci-2]; + char c = w_chat_buf[ci-2]; if (c >= ' ' && !(c & 0x80)) buf[ci] = c; }; buf[ci] = '\0'; - memset(w_chat, '\0', sizeof(w_chat)); - c_input = 0; + M_TextInputClear(&w_chat); // last minute mute check if (CHAT_MUTE) @@ -1212,9 +1148,8 @@ static void HU_sendChatMessage(void) void HU_clearChatChars(void) { - memset(w_chat, '\0', sizeof(w_chat)); + M_TextInputClear(&w_chat); chat_on = false; - c_input = 0; I_UpdateMouseGrab(); } @@ -1256,7 +1191,8 @@ boolean HU_Responder(event_t *ev) && netgame && !OLD_MUTE) // check for old chat mute, still let the players open the chat incase they want to scroll otherwise. { chat_on = true; - w_chat[0] = 0; + w_chat_buf[0] = 0; + M_TextInputInit(&w_chat, w_chat_buf, sizeof w_chat_buf); teamtalk = false; chat_scrollmedown = true; typelines = 1; @@ -1266,7 +1202,8 @@ boolean HU_Responder(event_t *ev) && netgame && !OLD_MUTE) { chat_on = true; - w_chat[0] = 0; + w_chat_buf[0] = 0; + M_TextInputInit(&w_chat, w_chat_buf, sizeof w_chat_buf); teamtalk = G_GametypeHasTeams(); // Don't teamtalk if we don't have teams. chat_scrollmedown = true; typelines = 1; @@ -1290,39 +1227,14 @@ boolean HU_Responder(event_t *ev) && !G_ControlBoundToKey(0, gc_talkkey, ev->data1, false)) return false; - c = CON_ShiftChar(c); + M_TextInputHandle(&w_chat, c); - // pasting. pasting is cool. chat is a bit limited, though :( - if ((c == 'v' || c == 'V') && ctrldown) - { - const char *paste; - size_t chatlen; - size_t pastelen; - - if (CHAT_MUTE) - return true; - - paste = I_ClipboardPaste(); - if (paste == NULL) - return true; - - chatlen = strlen(w_chat); - pastelen = strlen(paste); - if (chatlen+pastelen > HU_MAXMSGLEN) - return true; // we can't paste this!! - - memmove(&w_chat[c_input + pastelen], &w_chat[c_input], pastelen); - memcpy(&w_chat[c_input], paste, pastelen); // copy all of that. - c_input += pastelen; - return true; - } - else if (c == KEY_ENTER) + if (c == KEY_ENTER) { if (!CHAT_MUTE) HU_sendChatMessage(); chat_on = false; - c_input = 0; // reset input cursor chat_scrollmedown = true; // you hit enter, so you might wanna autoscroll to see what you just sent. :) I_UpdateMouseGrab(); } @@ -1332,7 +1244,6 @@ boolean HU_Responder(event_t *ev) && c >= NUMKEYS)) // If it's not a keyboard key, then the chat button is used as a toggle. { chat_on = false; - c_input = 0; // reset input cursor I_UpdateMouseGrab(); } else if ((c == KEY_UPARROW || c == KEY_MOUSEWHEELUP) && chat_scroll > 0 && !OLDCHAT) // CHAT SCROLLING YAYS! @@ -1347,45 +1258,6 @@ boolean HU_Responder(event_t *ev) justscrolleddown = true; chat_scrolltime = 4; } - else if (c == KEY_LEFTARROW && c_input != 0 && !OLDCHAT) // i said go back - { - if (ctrldown) - c_input = M_JumpWordReverse(w_chat, c_input); - else - c_input--; - } - else if (c == KEY_RIGHTARROW && c_input < strlen(w_chat) && !OLDCHAT) // don't need to check for admin or w/e here since the chat won't ever contain anything if it's muted. - { - if (ctrldown) - c_input += M_JumpWord(&w_chat[c_input]); - else - c_input++; - } - else if ((c >= HU_FONTSTART && c <= HU_FONTEND && fontv[HU_FONT].font[c-HU_FONTSTART]) - || c == ' ') // Allow spaces, of course - { - if (CHAT_MUTE || strlen(w_chat) >= HU_MAXMSGLEN) - return true; - - memmove(&w_chat[c_input + 1], &w_chat[c_input], strlen(w_chat) - c_input + 1); - w_chat[c_input] = c; - c_input++; - } - else if (c == KEY_BACKSPACE) - { - if (CHAT_MUTE || c_input <= 0) - return true; - - memmove(&w_chat[c_input - 1], &w_chat[c_input], strlen(w_chat) - c_input + 1); - c_input--; - } - else if (c == KEY_DEL) - { - if (CHAT_MUTE || c_input >= strlen(w_chat)) - return true; - - memmove(&w_chat[c_input], &w_chat[c_input + 1], strlen(w_chat) - c_input); - } return true; } @@ -1742,8 +1614,9 @@ static void HU_DrawChat(void) INT32 charwidth = 4, charheight = 6; INT32 boxw = cv_chatwidth.value; INT32 t = 0, c = 0, y = chaty - (typelines*charheight); - UINT32 i = 0, saylen = strlen(w_chat); // You learn new things everyday! - INT32 cflag = 0; + UINT32 i = 0, saylen = strlen(w_chat_buf); // You learn new things everyday! + INT32 cflag = 0;\ + size_t select_start = 0, select_end = 0; const char *ntalk = "Say: ", *ttalk = "Team: "; const char *talk = ntalk; const char *mute = "Chat has been muted."; @@ -1807,13 +1680,19 @@ static void HU_DrawChat(void) i = 0; typelines = 1; - if ((strlen(w_chat) == 0 || c_input == 0) && hu_tick < 4) - V_DrawChatCharacter(chatx + 2 + c, y+1, '_' |V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, true, NULL); + if ((w_chat.cursor == 0 || w_chat.length == 0) && hu_tick < 4) + V_DrawChatCharacter(chatx+2+c+charwidth*w_chat.cursor, y+1, '_'|V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, !cv_menucaps.value, NULL); - while (w_chat[i]) + if (w_chat.select != w_chat.cursor) + { + select_start = min(w_chat.select, w_chat.cursor); + select_end = max(w_chat.select, w_chat.cursor); + } + + while (w_chat_buf[i]) { boolean skippedline = false; - if (c_input == (i+1)) + if (w_chat.cursor == (i+1)) { INT32 cursorx = (c+charwidth < boxw-charwidth) ? (chatx + 2 + c+charwidth) : (chatx+1); // we may have to go down. INT32 cursory = (cursorx != chatx+1) ? (y) : (y+charheight); @@ -1828,10 +1707,13 @@ static void HU_DrawChat(void) } //Hurdler: isn't it better like that? - if (w_chat[i] < HU_FONTSTART) - ++i; - else - V_DrawChatCharacter(chatx + c + 2, y, w_chat[i++] | V_SNAPTOBOTTOM|V_SNAPTOLEFT | t, true, NULL); + if (w_chat_buf[i] >= HU_FONTSTART) + V_DrawChatCharacter(chatx + c + 2, y, w_chat_buf[i] | V_SNAPTOBOTTOM|V_SNAPTOLEFT | t, !cv_menucaps.value, NULL); + + // Draw selection + if (i >= select_start && i < select_end) + V_DrawFill(chatx + c + 2, y-1, charwidth, charheight, 103|V_TRANSLUCENT|V_SNAPTOBOTTOM|V_SNAPTOLEFT|t); + ++i; c += charwidth; if (c > boxw-(charwidth*2) && !skippedline) @@ -1843,7 +1725,7 @@ static void HU_DrawChat(void) } // handle /pm list. It's messy, horrible and I don't care. - if (strnicmp(w_chat, "/pm", 3) == 0 && vid.width >= 400 && !teamtalk) // 320x200 unsupported kthxbai + if (strnicmp(w_chat_buf, "/pm", 3) == 0 && vid.width >= 400 && !teamtalk) // 320x200 unsupported kthxbai { INT32 count = 0; INT32 p_dispy = chaty - charheight -1; @@ -1864,34 +1746,34 @@ static void HU_DrawChat(void) for(i=0; (i '9'))) || ((w_chat[4] != 0) && (((w_chat[4] < '0') || (w_chat[4] > '9'))))) && (w_chat[4] != ' ')) + // right, that's half important: (w_chat_buf[4] may be a space since /pm0 msg is perfectly acceptable!) + if ( ( ((w_chat_buf[3] != 0) && ((w_chat_buf[3] < '0') || (w_chat_buf[3] > '9'))) || ((w_chat_buf[4] != 0) && (((w_chat_buf[4] < '0') || (w_chat_buf[4] > '9'))))) && (w_chat_buf[4] != ' ')) break; - strncpy(playernum, w_chat+3, 3); + strncpy(playernum, w_chat_buf+3, 3); n = atoi(playernum); // turn that into a number // special cases: - if ((n == 0) && !(w_chat[4] == '0')) + if ((n == 0) && !(w_chat_buf[4] == '0')) { if (!(i<10)) continue; } - else if ((n == 1) && !(w_chat[3] == '0')) + else if ((n == 1) && !(w_chat_buf[3] == '0')) { if (!((i == 1) || ((i >= 10) && (i <= 19)))) continue; } - else if ((n == 2) && !(w_chat[3] == '0')) + else if ((n == 2) && !(w_chat_buf[3] == '0')) { if (!((i == 2) || ((i >= 20) && (i <= 29)))) continue; } - else if ((n == 3) && !(w_chat[3] == '0')) + else if ((n == 3) && !(w_chat_buf[3] == '0')) { if (!((i == 3) || ((i >= 30) && (i <= 31)))) continue; @@ -1932,6 +1814,7 @@ static void HU_DrawChat_Old(void) size_t i = 0; const char *ntalk = "Say: ", *ttalk = "Say-Team: "; const char *talk = ntalk; + size_t select_start = 0, select_end = 0; INT32 charwidth = 8 * con_scalefactor; //(fontv[HU_FONT].font['A'-HU_FONTSTART]->width) * con_scalefactor; INT32 charheight = 8 * con_scalefactor; //(fontv[HU_FONT].font['A'-HU_FONTSTART]->height) * con_scalefactor; if (teamtalk) @@ -1955,19 +1838,24 @@ static void HU_DrawChat_Old(void) else { //charwidth = (fontv[HU_FONT].font[talk[i]-HU_FONTSTART]->width) * con_scalefactor; - V_DrawCharacter(HU_INPUTX + c, y, talk[i++] | cv_constextsize.value | V_NOSCALESTART, true); + V_DrawCharacter(HU_INPUTX + c, y, talk[i++] | cv_constextsize.value | V_NOSCALESTART, !cv_menucaps.value); } c += charwidth; } - if ((strlen(w_chat) == 0 || c_input == 0) && hu_tick < 4) - V_DrawCharacter(HU_INPUTX+c, y+2*con_scalefactor, '_' |cv_constextsize.value | V_NOSCALESTART|t, true); + if ((w_chat.cursor == 0 || w_chat.length == 0) && hu_tick < 4) + V_DrawCharacter(HU_INPUTX+c+charwidth*w_chat.cursor, y+2*con_scalefactor, '_'|cv_constextsize.value|V_NOSCALESTART|t, !cv_menucaps.value); + + if (w_chat.select != w_chat.cursor) + { + select_start = min(w_chat.select, w_chat.cursor); + select_end = max(w_chat.select, w_chat.cursor); + } i = 0; - while (w_chat[i]) + while (w_chat_buf[i]) { - - if (c_input == (i+1) && hu_tick < 4) + if (w_chat.cursor == (i+1) && hu_tick < 4) { INT32 cursorx = (HU_INPUTX+c+charwidth < vid.width) ? (HU_INPUTX + c + charwidth) : (HU_INPUTX); // we may have to go down. INT32 cursory = (cursorx != HU_INPUTX) ? (y) : (y+charheight); @@ -1975,17 +1863,18 @@ static void HU_DrawChat_Old(void) } //Hurdler: isn't it better like that? - if (w_chat[i] < HU_FONTSTART) - { - ++i; - //charwidth = 4 * con_scalefactor; - } - else + if (w_chat_buf[i] >= HU_FONTSTART) { //charwidth = (fontv[HU_FONT].font[w_chat[i]-HU_FONTSTART]->width) * con_scalefactor; - V_DrawCharacter(HU_INPUTX + c, y, w_chat[i++] | cv_constextsize.value | V_NOSCALESTART | t, true); + V_DrawCharacter(HU_INPUTX + c, y, w_chat_buf[i] | cv_constextsize.value | V_NOSCALESTART | t, !cv_menucaps.value); } + // Draw selection + if (i >= select_start && i < select_end) + V_DrawFill(HU_INPUTX + c, y, charwidth, charheight, 103|V_TRANSLUCENT|cv_constextsize.value|V_NOSCALESTART|t); + + ++i; + c += charwidth; if (c >= vid.width) { @@ -1993,9 +1882,6 @@ static void HU_DrawChat_Old(void) y += charheight; } } - - if (hu_tick < 4) - V_DrawCharacter(HU_INPUTX + c, y, '_' | cv_constextsize.value |V_NOSCALESTART|t, true); } static void HU_DrawCEcho(void) 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..5bb3894c3 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; +static char menu_text_input_buf[MAXSTRINGLENGTH]; +static textinput_t menuinput; + typedef enum { LLM_CREATESERVER, @@ -255,9 +260,25 @@ static INT16 M_GetMenuIndex(menutype_t type, const char *name) return M_GetMenuItem(type, name) - menudefs[type].menuitems; } +static boolean M_ItemSelectable(menuitem_t *item) +{ + return (item->status & IT_INTERACT) && !(item->status & (IT_HIDDEN|IT_GRAYEDOUT|IT_SECRET)); +} + +// TODO this ought to be controlled by the item argument... +static UINT32 M_StringCvarLength(consvar_t *cv) +{ + if (cv == &cv_dummyip) + return 28; + else if (cv == &cv_dummyname || cv == &cv_playername[0] || cv == &cv_playername[1] || cv == &cv_playername[2] || cv == &cv_playername[3]) + return MAXPLAYERNAME + 1; + else + return MAXSTRINGLENGTH; +} + // an array of macros for getting/setting menuitem properties #define M_IsItemOn(t, n) (itemOn == M_GetMenuIndex(t, n)) -#define M_SetItemOn(t, n) (itemOn = M_GetMenuIndex(t, n)) +#define M_SetItemOn(t, n) (M_RawSetItemOn(&menudefs[t], M_GetMenuIndex(t, n))) #define M_SetItemRoutine(t, n, v) (M_GetMenuItem(t, n)->routine = v) #define M_SetItemCvar(t, n, v) (M_GetMenuItem(t, n)->cvar = v) #define M_SetItemArgument(t, n, v) (M_GetMenuItem(t, n)->argument = v) @@ -266,6 +287,41 @@ static INT16 M_GetMenuIndex(menutype_t type, const char *name) #define M_GetItemX(t, n) (M_GetMenuItem(t, n)->x) #define M_GetItemY(t, n) (M_GetMenuItem(t, n)->y) +static void M_RawSetItemOn(menu_t *menu, INT16 index) +{ + INT16 i; + itemOn = index; + + if (!menu->numitems) + return; + + // in case of... + if (itemOn >= menu->numitems) + itemOn = menu->numitems - 1; + + // the curent item can be disabled, + // this code go up until an enabled item found + if (menu->numitems && !M_ItemSelectable(&menu->menuitems[itemOn])) + { + for (i = 0; i < menu->numitems; i++) + { + if (M_ItemSelectable(&menu->menuitems[i])) + { + itemOn = i; + break; + } + } + } + + // update text input for string cvars + consvar_t *cv = menu->menuitems[itemOn].cvar; + if (cv && !cv->PossibleValue) + { + M_TextInputInit(&menuinput, menu_text_input_buf, M_StringCvarLength(cv)); + M_TextInputSetString(&menuinput, cv->string); + } +} + static void M_ChangeItemStatus(menutype_t type, const char *name, menuitemflags_t flag, boolean cond) { if (cond) @@ -294,11 +350,6 @@ static void M_SetItemPatch(menutype_t type, const char *name, const char *string #define M_SetItemDisabled(t, n, c) M_ChangeItemStatus(t, n, IT_GRAYEDOUT, c) #define M_SetItemSecret(t, n, c) M_ChangeItemStatus(t, n, IT_SECRET, c) -static boolean M_ItemSelectable(menuitem_t *item) -{ - return (item->status & IT_INTERACT) && !(item->status & (IT_HIDDEN|IT_GRAYEDOUT|IT_SECRET)); -} - // bruh... static UINT32 M_ServersPerPage(void) { @@ -1115,76 +1166,6 @@ static void M_ChangeCvar(menuitem_t *item, SINT8 direction) M_GetFollowerState(); // update follower state } -static UINT32 M_StringCvarLength(consvar_t *cv) -{ - if (cv == &cv_dummyip) - return 28; - else if (cv == &cv_dummyname || cv == &cv_playername[0] || cv == &cv_playername[1] || cv == &cv_playername[2] || cv == &cv_playername[3]) - return MAXPLAYERNAME + 1; - else - return MAXSTRINGLENGTH; -} - -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) - { - 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; - - 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 false; -} - // lock out further input in a tic when important buttons are pressed // (in other words -- stop bullshit happening by mashing buttons in fades) static INT32 noFurtherInput = 0; @@ -1195,7 +1176,7 @@ static void Command_Manual_f(void) return; M_StartControlPanel(); M_EnterMenu(MN_HELP, true, 0); - itemOn = 0; + M_SetItemOn(MN_HELP, "PAGE0"); } // arbitrary keyboard shortcuts because fuck you @@ -1502,7 +1483,7 @@ boolean M_Responder(event_t *ev) M_StartControlPanel(); M_EnterMenu(MN_OP_MAIN, true, 0); M_EnterMenu(MN_OP_SOUND, true, 0); - itemOn = 0; + M_SetItemOn(MN_OP_SOUND, "SOUND"); return true; case KEY_F5: // Video Mode @@ -1572,8 +1553,13 @@ boolean M_Responder(event_t *ev) { if (item->cvar->flags & CV_CALL && M_WipeBuffer(ch, MR_Dummy)) return true; - if (M_ChangeStringCvar(ch)) + + if (M_TextInputHandle(&menuinput, ch)) + { + S_StartSound(NULL, sfx_menu1); // Tails + CV_Set(item->cvar, menuinput.buffer); return true; + } } if (menustack[0] == MN_PLAYBACK && !con_destlines) @@ -1594,6 +1580,7 @@ boolean M_Responder(event_t *ev) } INT16 oldItemOn = itemOn; // prevent infinite loop + INT16 newItemOn = itemOn; // Keys usable within menu switch (ch) @@ -1602,10 +1589,11 @@ boolean M_Responder(event_t *ev) if (!item) return true; - do if (++itemOn >= currentMenu->numitems) - itemOn = 0; - while (oldItemOn != itemOn && !M_ItemSelectable(¤tMenu->menuitems[itemOn])); + do if (++newItemOn >= currentMenu->numitems) + newItemOn = 0; + while (oldItemOn != newItemOn && !M_ItemSelectable(¤tMenu->menuitems[newItemOn])); + M_RawSetItemOn(currentMenu, newItemOn); S_StartSound(NULL, sfx_menu1); return true; @@ -1613,10 +1601,11 @@ boolean M_Responder(event_t *ev) if (!item) return true; - do if (--itemOn < 0) - itemOn = currentMenu->numitems - 1; - while (oldItemOn != itemOn && !M_ItemSelectable(¤tMenu->menuitems[itemOn])); + do if (--newItemOn < 0) + newItemOn = currentMenu->numitems - 1; + while (oldItemOn != newItemOn && !M_ItemSelectable(¤tMenu->menuitems[newItemOn])); + M_RawSetItemOn(currentMenu, newItemOn); S_StartSound(NULL, sfx_menu1); return true; @@ -1933,7 +1922,6 @@ void M_ClearMenus(boolean callexitmenufunc) // static void M_SetupNextMenu(menutype_t menunum, boolean callexit) { - INT16 i; menu_t *menudef = &menudefs[menunum]; // If you're going from a menu to itself, why are you running the quitroutine? You're not quitting it! -SH @@ -1943,30 +1931,9 @@ static void M_SetupNextMenu(menutype_t menunum, boolean callexit) M_HandleMenuPresState(menunum); currentMenu = menudef; - itemOn = currentMenu->lastOn; + M_RawSetItemOn(currentMenu, currentMenu->lastOn); hidetitlemap = false; - - if (!currentMenu->numitems) - return; - - // in case of... - if (itemOn >= currentMenu->numitems) - itemOn = currentMenu->numitems - 1; - - // the curent item can be disabled, - // this code go up until an enabled item found - if (!M_ItemSelectable(¤tMenu->menuitems[itemOn])) - { - for (i = 0; i < currentMenu->numitems; i++) - { - if (M_ItemSelectable(¤tMenu->menuitems[i])) - { - itemOn = i; - break; - } - } - } } // pop the active menu from the stack @@ -2260,7 +2227,23 @@ 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, INT32 flags, textinput_t *input) +{ + // draw text cursor for name + if (skullAnimCounter < 4) // blink cursor + V_DrawCharacter(x+V_SubStringWidth(input->buffer, input->cursor, flags|V_ALLOWLOWERCASE), y+3, '_'|(flags & ~(V_FLIP|V_PARAMMASK)), 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, flags|V_ALLOWLOWERCASE); + V_DrawFill(x+startx, y, V_SubStringWidth(input->buffer+start, len, V_ALLOWLOWERCASE), 8, (flags & ~(V_ALPHAMASK|V_PARAMMASK))|103|V_TRANSLUCENT); + } } // horizontally centered text @@ -2369,9 +2352,8 @@ static void M_DrawRightString(menuitem_t *item, INT16 x, INT16 y, INT32 vflags, 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 (selected) + M_DrawTextInput(x + xofs + 8, y + yofs + 8, vflags|V_ALLOWLOWERCASE, &menuinput); } else if (item->status & IT_SLIDER) { @@ -2526,7 +2508,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 +2591,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)<= (INT16)(currentMenu->numitems-1)) break; S_StartSound(NULL, sfx_menu1); - itemOn++; + M_RawSetItemOn(currentMenu, itemOn + 1); break; case KEY_LEFTARROW: if (!itemOn) break; - S_StartSound(NULL, sfx_menu1); - itemOn--; + M_RawSetItemOn(currentMenu, itemOn - 1); break; default: @@ -6370,7 +6351,7 @@ static void M_ConnectMenu(INT32 choice) // first page of servers CV_SetValue(&cv_dummyserverpage, 0); M_EnterMenu(MN_MP_CONNECT, true, 0); - itemOn = 0; + M_SetItemOn(MN_MP_CONNECT, "SORTING"); #if defined (MASTERSERVER) && defined (HAVE_THREADS) I_lock_mutex(&ms_QueryId_mutex); @@ -8006,7 +7987,7 @@ INT32 MR_HandleMonitorToggles(INT32 choice) if (((column*height)+row) >= currentMenu->numitems) column = 0; next = min(((column*height)+row), currentMenu->numitems-1); - itemOn = next; + M_RawSetItemOn(currentMenu, next); break; case KEY_LEFTARROW: @@ -8019,7 +8000,7 @@ INT32 MR_HandleMonitorToggles(INT32 choice) next = max(((column*height)+row), 0); if (next >= currentMenu->numitems) next = currentMenu->numitems-1; - itemOn = next; + M_RawSetItemOn(currentMenu, next); break; case KEY_DOWNARROW: @@ -8028,7 +8009,7 @@ INT32 MR_HandleMonitorToggles(INT32 choice) if (((column*height)+row) >= currentMenu->numitems) row = 0; next = min(((column*height)+row), currentMenu->numitems-1); - itemOn = next; + M_RawSetItemOn(currentMenu, next); break; case KEY_UPARROW: @@ -8041,7 +8022,7 @@ INT32 MR_HandleMonitorToggles(INT32 choice) next = max(((column*height)+row), 0); if (next >= currentMenu->numitems) next = currentMenu->numitems-1; - itemOn = next; + M_RawSetItemOn(currentMenu, next); break; case KEY_ENTER: diff --git a/src/m_textinput.c b/src/m_textinput.c new file mode 100644 index 000000000..8d2c6b9e3 --- /dev/null +++ b/src/m_textinput.c @@ -0,0 +1,308 @@ +#include "m_textinput.h" +#include "i_system.h" +#include "keys.h" +#include "console.h" + +static void M_TextInputDel(textinput_t *input, size_t start, size_t end) +{ + size_t len; + + len = (end - start); + + if (end != input->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); + input->buffer[input->length] = 0; +} + +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/st_stuff.c b/src/st_stuff.c index e414fb5eb..fc96f3a03 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -815,27 +815,83 @@ static void ST_overlayDrawer(void) } } -void ST_DrawDemoTitleEntry(void) +static void ST_DrawTextInput(INT32 x, INT32 y, textinput_t *input, INT32 flags) { static UINT8 skullAnimCounter = 0; - char *nametodraw; + static const INT32 MAXINPUTWIDTH = (MAXSTRINGLENGTH-1)*8; if (renderisnewtic) skullAnimCounter++; - skullAnimCounter %= 8; - nametodraw = demo.titlename; - while (V_StringWidth(nametodraw, 0) > MAXSTRINGLENGTH*8 - 8) - nametodraw++; + char nametodraw[MAXSTRINGLENGTH*2+1] = {0}; -#define x (BASEVIDWIDTH/2 - 139) -#define y (BASEVIDHEIGHT/2) + size_t drawstart = 0; + size_t drawend = 0; // Only used for selection + + INT32 skullx = x; + + while (V_SubStringWidth(input->buffer+drawstart, input->cursor-drawstart, V_ALLOWLOWERCASE) > MAXINPUTWIDTH) + ++drawstart; + + size_t drawlength = V_SubStringLengthToFit(input->buffer+drawstart, MAXINPUTWIDTH+8, V_ALLOWLOWERCASE)+1; + drawend = drawstart + drawlength; + + memcpy(nametodraw, input->buffer+drawstart, drawlength); + + + if (input->length) + skullx += V_SubStringWidth(nametodraw, input->cursor-drawstart, V_ALLOWLOWERCASE); + + V_DrawString(x, y, V_ALLOWLOWERCASE|flags, nametodraw); + + // draw text cursor for name + if (skullAnimCounter < 4) // blink cursor + V_DrawCharacter(skullx, 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); + + INT32 startx = 0; + INT32 width = 0; + + // I couldn't figure out one formula so here's bunch of separate cases + if (start < drawstart && end > drawend) // Selection covers whole visible portion of demo name + { + startx = -2; + width = V_StringWidth(nametodraw, V_ALLOWLOWERCASE); + } + else if (start < drawstart) // Only left side of selection is off visible part + { + startx = -2; + size_t len = (end - start) - (drawstart - start); + width = V_SubStringWidth(nametodraw, len, V_ALLOWLOWERCASE)+2; + } + else if (end > drawend) // Only right side of selection is off visible part + { + startx = V_SubStringWidth(nametodraw, start-drawstart, V_ALLOWLOWERCASE); + width = V_StringWidth(nametodraw+(start-drawstart), V_ALLOWLOWERCASE)+2; + } + else // All selection is on visible part + { + startx = V_SubStringWidth(nametodraw, start-drawstart, V_ALLOWLOWERCASE); + width = V_SubStringWidth(nametodraw+(start-drawstart), end-start, V_ALLOWLOWERCASE); + } + + V_DrawFill(x+startx, y, width, 8, 103|V_TRANSLUCENT|flags); + } +} + +void ST_DrawDemoTitleEntry(void) +{ + #define x (BASEVIDWIDTH/2 - 139) + #define y (BASEVIDHEIGHT/2) M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1); - V_DrawString(x + 8, y + 12, V_ALLOWLOWERCASE, nametodraw); - if (skullAnimCounter < 4) - V_DrawCharacter(x + 8 + V_StringWidth(nametodraw, 0), y + 12, - '_' | 0x80, false); + + ST_DrawTextInput(x + 8, y + 12, &demo.titlenameinput, 0); M_DrawTextBox(x + 30, y - 24, 26, 1); V_DrawString(x + 38, y - 16, V_ALLOWLOWERCASE, "Enter the name of the replay."); @@ -843,8 +899,8 @@ void ST_DrawDemoTitleEntry(void) M_DrawTextBox(x + 50, y + 20, 20, 1); V_DrawThinString(x + 58, y + 28, V_ALLOWLOWERCASE, "Escape - Cancel"); V_DrawRightAlignedThinString(x + 220, y + 28, V_ALLOWLOWERCASE, "Enter - Confirm"); -#undef x -#undef y + #undef x + #undef y } // MayonakaStatic: draw Midnight Channel's TV-like borders diff --git a/src/v_video.c b/src/v_video.c index 1fa5f16d8..033be0ea8 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 @@ -3070,7 +3079,7 @@ INT32 V_ThinStringWidth(const char *string, INT32 option) else { w += (charwidth ? charwidth - : ((option & V_6WIDTHSPACE && i < strlen(string)-1) ? max(1, fontv[TINY_FONT].font[c]->width-1) // Reuse this flag for the alternate bunched-up spacing + : ((option & V_6WIDTHSPACE && i < (ssize_t)strlen(string)-1) ? max(1, fontv[TINY_FONT].font[c]->width-1) // Reuse this flag for the alternate bunched-up spacing : fontv[TINY_FONT].font[c]->width)); } } @@ -3082,10 +3091,57 @@ 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_SmallThinStringWidth(const char *string, INT32 option) { INT32 w = V_ThinStringWidth(string, option)<= 0x80 && (UINT8)c <= 0x8F) //color parsing! -Inuyasha 2.16.09 + continue; + + c = toupper(c) - HU_FONTSTART; + + if (c < 0 || c >= HU_FONTSIZE || !fontv[HU_FONT].font[c]) + w += spacewidth; + else + w += (charwidth ? charwidth : fontv[HU_FONT].font[c]->width); + } + + return max(i-1, 0); } boolean *heatshifter = NULL; diff --git a/src/v_video.h b/src/v_video.h index f2ab5aa09..b404c1ff0 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -329,13 +329,16 @@ 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) + +INT32 V_SubStringLengthToFit(const char *string, INT32 width, INT32 option); void V_DoPostProcessor(INT32 view, INT32 param);