Merge pull request 'Unify Text Input' (#78) from unified-text-input into blankart-dev

Reviewed-on: https://codeberg.org/NepDisk/blankart/pulls/78
This commit is contained in:
NepDisk 2025-08-15 18:28:15 +02:00
commit 8463d6abcc
13 changed files with 724 additions and 653 deletions

View file

@ -441,4 +441,3 @@ if("${CMAKE_SYSTEM_NAME}" STREQUAL Windows AND NOT "${SRB2_CONFIG_INTERNAL_LIBRA
COMMENT "Copying runtime DLLs"
)
endif()

View file

@ -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

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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

View file

@ -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<MAXPLAYERS); i++)
{
// filter: (code needs optimization pls help I'm bad with C)
if (w_chat[3])
if (w_chat_buf[3])
{
char playernum[3];
UINT32 n;
// right, that's half important: (w_chat[4] may be a space since /pm0 msg is perfectly acceptable!)
if ( ( ((w_chat[3] != 0) && ((w_chat[3] < '0') || (w_chat[3] > '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)

View file

@ -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)

View file

@ -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(&currentMenu->menuitems[itemOn]));
do if (++newItemOn >= currentMenu->numitems)
newItemOn = 0;
while (oldItemOn != newItemOn && !M_ItemSelectable(&currentMenu->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(&currentMenu->menuitems[itemOn]));
do if (--newItemOn < 0)
newItemOn = currentMenu->numitems - 1;
while (oldItemOn != newItemOn && !M_ItemSelectable(&currentMenu->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(&currentMenu->menuitems[itemOn]))
{
for (i = 0; i < currentMenu->numitems; i++)
{
if (M_ItemSelectable(&currentMenu->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)<<FRACBITS, y<<FRACBITS, scale, FRACUNIT, FRACUNIT, (selected ? highlightflags : colorflag)|vflags, font, string);
@ -3195,15 +3177,14 @@ INT32 MR_HandleImageDef(INT32 choice)
if (itemOn >= (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:

308
src/m_textinput.c Normal file
View file

@ -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;
}

23
src/m_textinput.h Normal file
View file

@ -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

View file

@ -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

View file

@ -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)<<FRACBITS;
return w/2 + FRACUNIT; // +FRACUNIT because otherwise it's offset wrong.
}*/
//
// Find maximum length for substring taken from current string to fit into given width
//
INT32 V_SubStringLengthToFit(const char *string, INT32 width, INT32 option)
{
INT32 c, w = 0;
INT32 spacewidth = 4, charwidth = 0;
INT32 i;
switch (option & V_SPACINGMASK)
{
case V_MONOSPACE:
spacewidth = 8;
/* FALLTHRU */
case V_OLDSPACING:
charwidth = 8;
break;
case V_6WIDTHSPACE:
spacewidth = 6;
break;
default:
break;
}
for (i = 0; string[i] && w < width; i++)
{
c = string[i];
if ((UINT8)c >= 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;

View file

@ -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);