/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2000-2013 Darklegion Development
Copyright (C) 2015-2019 GrangerHub
This file is part of Tremulous.
Tremulous is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 3 of the License,
or (at your option) any later version.
Tremulous is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Tremulous; if not, see
===========================================================================
*/
#include "ui_shared.h"
#define SCROLL_TIME_START 500
#define SCROLL_TIME_ADJUST 150
#define SCROLL_TIME_ADJUSTOFFSET 40
#define SCROLL_TIME_FLOOR 20
typedef struct {
int nextScrollTime;
int nextAdjustTime;
int adjustValue;
int scrollKey;
float xStart;
float yStart;
itemDef_t *item;
qboolean scrollDir;
} scrollInfo_t;
static scrollInfo_t scrollInfo;
// prevent compiler warnings
void voidFunction(void *var) { return; }
qboolean voidFunction2(itemDef_t *var1, int var2) { return qfalse; }
static CaptureFunc *captureFunc = voidFunction;
static int captureFuncExpiry = 0;
static void *captureData = NULL;
static itemDef_t *itemCapture = NULL; // item that has the mouse captured ( if any )
displayContextDef_t *DC = NULL;
static qboolean g_waitingForKey = qfalse;
static qboolean g_editingField = qfalse;
static itemDef_t *g_bindItem = NULL;
static itemDef_t *g_editItem = NULL;
static itemDef_t *g_comboBoxItem = NULL;
menuDef_t Menus[MAX_MENUS]; // defined menus
int menuCount = 0; // how many
menuDef_t *menuStack[MAX_OPEN_MENUS];
int openMenuCount = 0;
#define DOUBLE_CLICK_DELAY 300
static int lastListBoxClickTime = 0;
itemDataType_t Item_DataType(itemDef_t *item);
void Item_RunScript(itemDef_t *item, const char *s);
void Item_SetupKeywordHash(void);
static ID_INLINE qboolean Item_IsEditField(itemDef_t *item);
static ID_INLINE qboolean Item_IsListBox(itemDef_t *item);
static void Item_ListBox_SetStartPos(itemDef_t *item, int startPos);
void Menu_SetupKeywordHash(void);
int BindingIDFromName(const char *name);
qboolean Item_Bind_HandleKey(itemDef_t *item, int key, qboolean down);
itemDef_t *Menu_SetPrevCursorItem(menuDef_t *menu);
itemDef_t *Menu_SetNextCursorItem(menuDef_t *menu);
static qboolean Menu_OverActiveItem(menuDef_t *menu, float x, float y);
/*
===============
UI_InstallCaptureFunc
===============
*/
void UI_InstallCaptureFunc(CaptureFunc *f, void *data, int timeout)
{
captureFunc = f;
captureData = data;
if (timeout > 0)
captureFuncExpiry = DC->realTime + timeout;
else
captureFuncExpiry = 0;
}
/*
===============
UI_RemoveCaptureFunc
===============
*/
void UI_RemoveCaptureFunc(void)
{
captureFunc = voidFunction;
captureData = NULL;
captureFuncExpiry = 0;
}
#ifdef CGAME
#define MEM_POOL_SIZE 128 * 1024
#else
#define MEM_POOL_SIZE 1024 * 1024
#endif
static char UI_memoryPool[MEM_POOL_SIZE];
static int allocPoint, outOfMemory;
/*
===============
UI_Alloc
===============
*/
void *UI_Alloc(int size)
{
char *p;
if (allocPoint + size > MEM_POOL_SIZE)
{
outOfMemory = qtrue;
if (DC->Print)
DC->Print("UI_Alloc: Failure. Out of memory!\n");
// DC->trap_Print(S_COLOR_YELLOW"WARNING: UI Out of Memory!\n");
return NULL;
}
p = &UI_memoryPool[allocPoint];
allocPoint += (size + 15) & ~15;
return p;
}
/*
===============
UI_InitMemory
===============
*/
void UI_InitMemory(void)
{
allocPoint = 0;
outOfMemory = qfalse;
}
qboolean UI_OutOfMemory() { return outOfMemory; }
#define HASH_TABLE_SIZE 2048
/*
================
return a hash value for the string
================
*/
static long hashForString(const char *str)
{
int i;
long hash;
char letter;
hash = 0;
i = 0;
while (str[i] != '\0')
{
letter = tolower(str[i]);
hash += (long)(letter) * (i + 119);
i++;
}
hash &= (HASH_TABLE_SIZE - 1);
return hash;
}
typedef struct stringDef_s {
struct stringDef_s *next;
const char *str;
} stringDef_t;
static int strPoolIndex = 0;
static char strPool[STRING_POOL_SIZE];
static int strHandleCount = 0;
static stringDef_t *strHandle[HASH_TABLE_SIZE];
// Make a copy of a string for later use. Can safely be called on the
// same string repeatedly. Redundant on string literals or global
// constants.
const char *String_Alloc(const char *p)
{
int len;
long hash;
stringDef_t *str, *last;
if (p == NULL)
return NULL;
if (*p == 0)
return "";
hash = hashForString(p);
str = strHandle[hash];
while (str)
{
if (strcmp(p, str->str) == 0)
return str->str;
str = str->next;
}
len = strlen(p);
if (len + strPoolIndex + 1 < STRING_POOL_SIZE)
{
int ph = strPoolIndex;
strcpy(&strPool[strPoolIndex], p);
strPoolIndex += len + 1;
str = strHandle[hash];
last = str;
while (str && str->next)
{
last = str;
str = str->next;
}
str = UI_Alloc(sizeof(stringDef_t));
str->next = NULL;
str->str = &strPool[ph];
if (last)
last->next = str;
else
strHandle[hash] = str;
return &strPool[ph];
}
else
{
Com_Error(ERR_DROP, "String_Alloc( %s ): string pool full!", p);
return NULL; // not that we return at all
}
}
void String_Report(void)
{
float f;
Com_Printf("Memory/String Pool Info\n");
Com_Printf("----------------\n");
f = strPoolIndex;
f /= STRING_POOL_SIZE;
f *= 100;
Com_Printf("String Pool is %.1f%% full, %i bytes out of %i used.\n", f, strPoolIndex, STRING_POOL_SIZE);
f = allocPoint;
f /= MEM_POOL_SIZE;
f *= 100;
Com_Printf("Memory Pool is %.1f%% full, %i bytes out of %i used.\n", f, allocPoint, MEM_POOL_SIZE);
}
/*
=================
String_Init
=================
*/
void String_Init(void)
{
int i;
for (i = 0; i < HASH_TABLE_SIZE; i++)
strHandle[i] = 0;
strHandleCount = 0;
strPoolIndex = 0;
menuCount = 0;
openMenuCount = 0;
UI_InitMemory();
Item_SetupKeywordHash();
Menu_SetupKeywordHash();
if (DC && DC->getBindingBuf)
Controls_GetConfig();
}
/*
=================
PC_SourceWarning
=================
*/
__attribute__((format(printf, 2, 3))) void PC_SourceWarning(int handle, char *format, ...)
{
int line;
char filename[128];
va_list argptr;
static char string[4096];
va_start(argptr, format);
Q_vsnprintf(string, sizeof(string), format, argptr);
va_end(argptr);
filename[0] = '\0';
line = 0;
trap_Parse_SourceFileAndLine(handle, filename, &line);
Com_Printf(S_COLOR_YELLOW "WARNING: %s, line %d: %s\n", filename, line, string);
}
/*
=================
PC_SourceError
=================
*/
__attribute__((format(printf, 2, 3))) void PC_SourceError(int handle, char *format, ...)
{
int line;
char filename[128];
va_list argptr;
static char string[4096];
va_start(argptr, format);
Q_vsnprintf(string, sizeof(string), format, argptr);
va_end(argptr);
filename[0] = '\0';
line = 0;
trap_Parse_SourceFileAndLine(handle, filename, &line);
Com_Printf(S_COLOR_RED "ERROR: %s, line %d: %s\n", filename, line, string);
}
/*
=================
LerpColor
=================
*/
void LerpColor(vec4_t a, vec4_t b, vec4_t c, float t)
{
int i;
// lerp and clamp each component
for (i = 0; i < 4; i++)
{
c[i] = a[i] + t * (b[i] - a[i]);
if (c[i] < 0)
c[i] = 0;
else if (c[i] > 1.0)
c[i] = 1.0;
}
}
/*
=================
Float_Parse
=================
*/
qboolean Float_Parse(char **p, float *f)
{
char *token;
token = COM_ParseExt(p, qfalse);
if (token && token[0] != 0)
{
*f = atof(token);
return qtrue;
}
else
return qfalse;
}
#define MAX_EXPR_ELEMENTS 32
typedef enum { EXPR_OPERATOR, EXPR_VALUE } exprType_t;
typedef struct exprToken_s {
exprType_t type;
union {
char op;
float val;
} u;
} exprToken_t;
typedef struct exprList_s {
exprToken_t l[MAX_EXPR_ELEMENTS];
int f, b;
} exprList_t;
/*
=================
OpPrec
Return a value reflecting operator precedence
=================
*/
static ID_INLINE int OpPrec(char op)
{
switch (op)
{
case '*':
return 4;
case '/':
return 3;
case '+':
return 2;
case '-':
return 1;
case '(':
return 0;
default:
return -1;
}
}
/*
=================
PC_Expression_Parse
=================
*/
static qboolean PC_Expression_Parse(int handle, float *f)
{
pc_token_t token;
int unmatchedParentheses = 0;
exprList_t stack, fifo;
exprToken_t value;
qboolean expectingNumber = qtrue;
#define FULL(a) (a.b >= (MAX_EXPR_ELEMENTS - 1))
#define EMPTY(a) (a.f > a.b)
#define PUSH_VAL(a, v) \
{ \
if (FULL(a)) \
return qfalse; \
a.b++; \
a.l[a.b].type = EXPR_VALUE; \
a.l[a.b].u.val = v; \
}
#define PUSH_OP(a, o) \
{ \
if (FULL(a)) \
return qfalse; \
a.b++; \
a.l[a.b].type = EXPR_OPERATOR; \
a.l[a.b].u.op = o; \
}
#define POP_STACK(a) \
{ \
if (EMPTY(a)) \
return qfalse; \
value = a.l[a.b]; \
a.b--; \
}
#define PEEK_STACK_OP(a) (a.l[a.b].u.op)
#define PEEK_STACK_VAL(a) (a.l[a.b].u.val)
#define POP_FIFO(a) \
{ \
if (EMPTY(a)) \
return qfalse; \
value = a.l[a.f]; \
a.f++; \
}
stack.f = fifo.f = 0;
stack.b = fifo.b = -1;
while (trap_Parse_ReadToken(handle, &token))
{
if (!unmatchedParentheses && token.string[0] == ')')
break;
// Special case to catch negative numbers
if (expectingNumber && token.string[0] == '-')
{
if (!trap_Parse_ReadToken(handle, &token))
return qfalse;
token.floatvalue = -token.floatvalue;
}
if (token.type == TT_NUMBER)
{
if (!expectingNumber)
return qfalse;
expectingNumber = !expectingNumber;
PUSH_VAL(fifo, token.floatvalue);
}
else
{
switch (token.string[0])
{
case '(':
unmatchedParentheses++;
PUSH_OP(stack, '(');
break;
case ')':
unmatchedParentheses--;
if (unmatchedParentheses < 0)
return qfalse;
while (!EMPTY(stack) && PEEK_STACK_OP(stack) != '(')
{
POP_STACK(stack);
PUSH_OP(fifo, value.u.op);
}
// Pop the '('
POP_STACK(stack);
break;
case '*':
case '/':
case '+':
case '-':
if (expectingNumber)
return qfalse;
expectingNumber = !expectingNumber;
if (EMPTY(stack))
{
PUSH_OP(stack, token.string[0]);
}
else
{
while (!EMPTY(stack) && OpPrec(token.string[0]) < OpPrec(PEEK_STACK_OP(stack)))
{
POP_STACK(stack);
PUSH_OP(fifo, value.u.op);
}
PUSH_OP(stack, token.string[0]);
}
break;
default:
// Unknown token
return qfalse;
}
}
}
while (!EMPTY(stack))
{
POP_STACK(stack);
PUSH_OP(fifo, value.u.op);
}
while (!EMPTY(fifo))
{
POP_FIFO(fifo);
if (value.type == EXPR_VALUE)
{
PUSH_VAL(stack, value.u.val);
}
else if (value.type == EXPR_OPERATOR)
{
char op = value.u.op;
float operand1, operand2, result;
POP_STACK(stack);
operand2 = value.u.val;
POP_STACK(stack);
operand1 = value.u.val;
switch (op)
{
case '*':
result = operand1 * operand2;
break;
case '/':
result = operand1 / operand2;
break;
case '+':
result = operand1 + operand2;
break;
case '-':
result = operand1 - operand2;
break;
default:
Com_Error(ERR_FATAL, "Unknown operator '%c' in postfix string", op);
return qfalse;
}
PUSH_VAL(stack, result);
}
}
POP_STACK(stack);
*f = value.u.val;
return qtrue;
#undef FULL
#undef EMPTY
#undef PUSH_VAL
#undef PUSH_OP
#undef POP_STACK
#undef PEEK_STACK_OP
#undef PEEK_STACK_VAL
#undef POP_FIFO
}
/*
=================
PC_Float_Parse
=================
*/
qboolean PC_Float_Parse(int handle, float *f)
{
pc_token_t token;
int negative = qfalse;
if (!trap_Parse_ReadToken(handle, &token))
return qfalse;
if (token.string[0] == '(')
return PC_Expression_Parse(handle, f);
if (token.string[0] == '-')
{
if (!trap_Parse_ReadToken(handle, &token))
return qfalse;
negative = qtrue;
}
if (token.type != TT_NUMBER)
{
PC_SourceError(handle, "expected float but found %s\n", token.string);
return qfalse;
}
if (negative)
*f = -token.floatvalue;
else
*f = token.floatvalue;
return qtrue;
}
/*
=================
Color_Parse
=================
*/
qboolean Color_Parse(char **p, vec4_t *c)
{
int i;
float f;
for (i = 0; i < 4; i++)
{
if (!Float_Parse(p, &f))
return qfalse;
(*c)[i] = f;
}
return qtrue;
}
/*
=================
PC_Color_Parse
=================
*/
qboolean PC_Color_Parse(int handle, vec4_t *c)
{
int i;
float f;
for (i = 0; i < 4; i++)
{
if (!PC_Float_Parse(handle, &f))
return qfalse;
(*c)[i] = f;
}
return qtrue;
}
/*
=================
Int_Parse
=================
*/
qboolean Int_Parse(char **p, int *i)
{
char *token;
token = COM_ParseExt(p, qfalse);
if (token && token[0] != 0)
{
*i = atoi(token);
return qtrue;
}
else
return qfalse;
}
/*
=================
PC_Int_Parse
=================
*/
qboolean PC_Int_Parse(int handle, int *i)
{
pc_token_t token;
int negative = qfalse;
if (!trap_Parse_ReadToken(handle, &token))
return qfalse;
if (token.string[0] == '(')
{
float f;
if (PC_Expression_Parse(handle, &f))
{
*i = (int)f;
return qtrue;
}
else
return qfalse;
}
if (token.string[0] == '-')
{
if (!trap_Parse_ReadToken(handle, &token))
return qfalse;
negative = qtrue;
}
if (token.type != TT_NUMBER)
{
PC_SourceError(handle, "expected integer but found %s\n", token.string);
return qfalse;
}
*i = token.intvalue;
if (negative)
*i = -*i;
return qtrue;
}
/*
=================
Rect_Parse
=================
*/
qboolean Rect_Parse(char **p, rectDef_t *r)
{
if (Float_Parse(p, &r->x))
{
if (Float_Parse(p, &r->y))
{
if (Float_Parse(p, &r->w))
{
if (Float_Parse(p, &r->h))
return qtrue;
}
}
}
return qfalse;
}
/*
=================
PC_Rect_Parse
=================
*/
qboolean PC_Rect_Parse(int handle, rectDef_t *r)
{
if (PC_Float_Parse(handle, &r->x))
{
if (PC_Float_Parse(handle, &r->y))
{
if (PC_Float_Parse(handle, &r->w))
{
if (PC_Float_Parse(handle, &r->h))
return qtrue;
}
}
}
return qfalse;
}
/*
=================
String_Parse
=================
*/
qboolean String_Parse(char **p, const char **out)
{
char *token;
token = COM_ParseExt(p, qfalse);
if (token && token[0] != 0)
{
*(out) = String_Alloc(token);
return qtrue;
}
return qfalse;
}
/*
=================
PC_String_Parse
=================
*/
qboolean PC_String_Parse(int handle, const char **out)
{
pc_token_t token;
if (!trap_Parse_ReadToken(handle, &token))
return qfalse;
*(out) = String_Alloc(token.string);
return qtrue;
}
/*
=================
PC_Script_Parse
=================
*/
qboolean PC_Script_Parse(int handle, const char **out)
{
char script[1024];
pc_token_t token;
memset(script, 0, sizeof(script));
// scripts start with { and have ; separated command lists.. commands are command, arg..
// basically we want everything between the { } as it will be interpreted at run time
if (!trap_Parse_ReadToken(handle, &token))
return qfalse;
if (Q_stricmp(token.string, "{") != 0)
return qfalse;
while (1)
{
if (!trap_Parse_ReadToken(handle, &token))
return qfalse;
if (Q_stricmp(token.string, "}") == 0)
{
*out = String_Alloc(script);
return qtrue;
}
if (token.string[1] != '\0')
Q_strcat(script, 1024, va("\"%s\"", token.string));
else
Q_strcat(script, 1024, token.string);
Q_strcat(script, 1024, " ");
}
return qfalse;
}
// display, window, menu, item code
//
/*
==================
Init_Display
Initializes the display with a structure to all the drawing routines
==================
*/
void Init_Display(displayContextDef_t *dc) { DC = dc; }
// type and style painting
void GradientBar_Paint(rectDef_t *rect, vec4_t color)
{
// gradient bar takes two paints
DC->setColor(color);
DC->drawHandlePic(rect->x, rect->y, rect->w, rect->h, DC->Assets.gradientBar);
DC->setColor(NULL);
}
/*
==================
Window_Init
Initializes a window structure ( Window ) with defaults
==================
*/
void Window_Init(Window *w)
{
memset(w, 0, sizeof(Window));
w->borderSize = 1;
w->foreColor[0] = w->foreColor[1] = w->foreColor[2] = w->foreColor[3] = 1.0;
w->cinematic = -1;
}
void Fade(int *flags, float *f, float clamp, int *nextTime, int offsetTime, qboolean bFlags, float fadeAmount)
{
if (*flags & (WINDOW_FADINGOUT | WINDOW_FADINGIN))
{
if (DC->realTime > *nextTime)
{
*nextTime = DC->realTime + offsetTime;
if (*flags & WINDOW_FADINGOUT)
{
*f -= fadeAmount;
if (bFlags && *f <= 0.0)
*flags &= ~(WINDOW_FADINGOUT | WINDOW_VISIBLE);
}
else
{
*f += fadeAmount;
if (*f >= clamp)
{
*f = clamp;
if (bFlags)
*flags &= ~WINDOW_FADINGIN;
}
}
}
}
}
static void Window_Paint(Window *w, float fadeAmount, float fadeClamp, float fadeCycle)
{
vec4_t color;
rectDef_t fillRect = w->rect;
if (DC->getCVarValue("ui_developer"))
{
color[0] = color[1] = color[2] = color[3] = 1;
DC->drawRect(w->rect.x, w->rect.y, w->rect.w, w->rect.h, 1, color);
}
if (w == NULL || (w->style == 0 && w->border == 0))
return;
if (w->border != 0)
{
fillRect.x += w->borderSize;
fillRect.y += w->borderSize;
fillRect.w -= w->borderSize + 1;
fillRect.h -= w->borderSize + 1;
}
if (w->style == WINDOW_STYLE_FILLED)
{
// box, but possible a shader that needs filled
if (w->background)
{
Fade(&w->flags, &w->backColor[3], fadeClamp, &w->nextTime, fadeCycle, qtrue, fadeAmount);
DC->setColor(w->backColor);
DC->drawHandlePic(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background);
DC->setColor(NULL);
}
else
DC->fillRect(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->backColor);
}
else if (w->style == WINDOW_STYLE_GRADIENT)
{
GradientBar_Paint(&fillRect, w->backColor);
// gradient bar
}
else if (w->style == WINDOW_STYLE_SHADER)
{
if (w->flags & WINDOW_FORECOLORSET)
DC->setColor(w->foreColor);
DC->drawHandlePic(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background);
DC->setColor(NULL);
}
else if (w->style == WINDOW_STYLE_CINEMATIC)
{
if (w->cinematic == -1)
{
w->cinematic = DC->playCinematic(w->cinematicName, fillRect.x, fillRect.y, fillRect.w, fillRect.h);
if (w->cinematic == -1)
w->cinematic = -2;
}
if (w->cinematic >= 0)
{
DC->runCinematicFrame(w->cinematic);
DC->drawCinematic(w->cinematic, fillRect.x, fillRect.y, fillRect.w, fillRect.h);
}
}
}
static void Border_Paint(Window *w)
{
if (w == NULL || (w->style == 0 && w->border == 0))
return;
if (w->border == WINDOW_BORDER_FULL)
{
// full
DC->drawRect(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize, w->borderColor);
}
else if (w->border == WINDOW_BORDER_HORZ)
{
// top/bottom
DC->setColor(w->borderColor);
DC->drawTopBottom(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize);
DC->setColor(NULL);
}
else if (w->border == WINDOW_BORDER_VERT)
{
// left right
DC->setColor(w->borderColor);
DC->drawSides(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize);
DC->setColor(NULL);
}
else if (w->border == WINDOW_BORDER_KCGRADIENT)
{
// this is just two gradient bars along each horz edge
rectDef_t r = w->rect;
r.h = w->borderSize;
GradientBar_Paint(&r, w->borderColor);
r.y = w->rect.y + w->rect.h - 1;
GradientBar_Paint(&r, w->borderColor);
}
}
void Item_SetScreenCoords(itemDef_t *item, float x, float y)
{
if (item == NULL)
return;
if (item->window.border != 0)
{
x += item->window.borderSize;
y += item->window.borderSize;
}
item->window.rect.x = x + item->window.rectClient.x;
item->window.rect.y = y + item->window.rectClient.y;
item->window.rect.w = item->window.rectClient.w;
item->window.rect.h = item->window.rectClient.h;
// force the text rects to recompute
item->textRect.w = 0;
item->textRect.h = 0;
}
// FIXME: consolidate this with nearby stuff
void Item_UpdatePosition(itemDef_t *item)
{
float x, y;
menuDef_t *menu;
if (item == NULL || item->parent == NULL)
return;
menu = item->parent;
x = menu->window.rect.x;
y = menu->window.rect.y;
if (menu->window.border != 0)
{
x += menu->window.borderSize;
y += menu->window.borderSize;
}
Item_SetScreenCoords(item, x, y);
}
// menus
void Menu_UpdatePosition(menuDef_t *menu)
{
int i;
float x, y;
if (menu == NULL)
return;
x = menu->window.rect.x;
y = menu->window.rect.y;
if (menu->window.border != 0)
{
x += menu->window.borderSize;
y += menu->window.borderSize;
}
for (i = 0; i < menu->itemCount; i++)
Item_SetScreenCoords(menu->items[i], x, y);
}
static void Menu_AspectiseRect(int bias, Rectangle *rect)
{
switch (bias)
{
case ALIGN_LEFT:
rect->x *= DC->aspectScale;
rect->w *= DC->aspectScale;
break;
case ALIGN_CENTER:
rect->x = (rect->x * DC->aspectScale) + (320.0f - (320.0f * DC->aspectScale));
rect->w *= DC->aspectScale;
break;
case ALIGN_RIGHT:
rect->x = 640.0f - ((640.0f - rect->x) * DC->aspectScale);
rect->w *= DC->aspectScale;
break;
default:
case ASPECT_NONE:
break;
}
}
void Menu_AspectCompensate(menuDef_t *menu)
{
int i;
if (menu->window.aspectBias != ASPECT_NONE)
{
Menu_AspectiseRect(menu->window.aspectBias, &menu->window.rect);
for (i = 0; i < menu->itemCount; i++)
{
menu->items[i]->window.rectClient.x *= DC->aspectScale;
menu->items[i]->window.rectClient.w *= DC->aspectScale;
menu->items[i]->textalignx *= DC->aspectScale;
}
}
else
{
for (i = 0; i < menu->itemCount; i++)
{
Menu_AspectiseRect(menu->items[i]->window.aspectBias, &menu->items[i]->window.rectClient);
if (menu->items[i]->window.aspectBias != ASPECT_NONE)
menu->items[i]->textalignx *= DC->aspectScale;
}
}
}
void Menu_PostParse(menuDef_t *menu)
{
int i, j;
if (menu == NULL)
return;
if (menu->fullScreen)
{
menu->window.rect.x = 0;
menu->window.rect.y = 0;
menu->window.rect.w = 640;
menu->window.rect.h = 480;
}
Menu_AspectCompensate(menu);
Menu_UpdatePosition(menu);
// Push lists to the end of the array as they can potentially be drawn on top
// of other elements
for (i = 0; i < menu->itemCount; i++)
{
itemDef_t *item = menu->items[i];
if (Item_IsListBox(item))
{
for (j = i; j < menu->itemCount - 1; j++)
menu->items[j] = menu->items[j + 1];
menu->items[j] = item;
}
}
}
itemDef_t *Menu_ClearFocus(menuDef_t *menu)
{
int i;
itemDef_t *ret = NULL;
if (menu == NULL)
return NULL;
for (i = 0; i < menu->itemCount; i++)
{
if (menu->items[i]->window.flags & WINDOW_HASFOCUS)
ret = menu->items[i];
menu->items[i]->window.flags &= ~WINDOW_HASFOCUS;
if (menu->items[i]->leaveFocus)
Item_RunScript(menu->items[i], menu->items[i]->leaveFocus);
}
return ret;
}
qboolean IsVisible(int flags) { return (flags & WINDOW_VISIBLE && !(flags & WINDOW_FADINGOUT)); }
qboolean Rect_ContainsPoint(rectDef_t *rect, float x, float y)
{
if (rect)
{
if (x > rect->x && x < rect->x + rect->w && y > rect->y && y < rect->y + rect->h)
return qtrue;
}
return qfalse;
}
int Menu_ItemsMatchingGroup(menuDef_t *menu, const char *name)
{
int i;
int count = 0;
for (i = 0; i < menu->itemCount; i++)
{
if (Q_stricmp(menu->items[i]->window.name, name) == 0 ||
(menu->items[i]->window.group && Q_stricmp(menu->items[i]->window.group, name) == 0))
{
count++;
}
}
return count;
}
itemDef_t *Menu_GetMatchingItemByNumber(menuDef_t *menu, int index, const char *name)
{
int i;
int count = 0;
for (i = 0; i < menu->itemCount; i++)
{
if (Q_stricmp(menu->items[i]->window.name, name) == 0 ||
(menu->items[i]->window.group && Q_stricmp(menu->items[i]->window.group, name) == 0))
{
if (count == index)
return menu->items[i];
count++;
}
}
return NULL;
}
void Script_SetColor(itemDef_t *item, char **args)
{
const char *name;
int i;
float f;
vec4_t *out;
// expecting type of color to set and 4 args for the color
if (String_Parse(args, &name))
{
out = NULL;
if (Q_stricmp(name, "backcolor") == 0)
{
out = &item->window.backColor;
item->window.flags |= WINDOW_BACKCOLORSET;
}
else if (Q_stricmp(name, "forecolor") == 0)
{
out = &item->window.foreColor;
item->window.flags |= WINDOW_FORECOLORSET;
}
else if (Q_stricmp(name, "bordercolor") == 0)
out = &item->window.borderColor;
if (out)
{
for (i = 0; i < 4; i++)
{
if (!Float_Parse(args, &f))
return;
(*out)[i] = f;
}
}
}
}
void Script_SetAsset(itemDef_t *item, char **args)
{
const char *name;
// expecting name to set asset to
if (String_Parse(args, &name))
{
// check for a model
if (item->type == ITEM_TYPE_MODEL)
{
}
}
}
void Script_SetBackground(itemDef_t *item, char **args)
{
const char *name;
// expecting name to set asset to
if (String_Parse(args, &name))
item->window.background = DC->registerShaderNoMip(name);
}
itemDef_t *Menu_FindItemByName(menuDef_t *menu, const char *p)
{
int i;
if (menu == NULL || p == NULL)
return NULL;
for (i = 0; i < menu->itemCount; i++)
{
if (Q_stricmp(p, menu->items[i]->window.name) == 0)
return menu->items[i];
}
return NULL;
}
void Script_SetItemColor(itemDef_t *item, char **args)
{
const char *itemname;
const char *name;
vec4_t color;
int i;
vec4_t *out;
// expecting type of color to set and 4 args for the color
if (String_Parse(args, &itemname) && String_Parse(args, &name))
{
itemDef_t *item2;
int j;
int count = Menu_ItemsMatchingGroup(item->parent, itemname);
if (!Color_Parse(args, &color))
return;
for (j = 0; j < count; j++)
{
item2 = Menu_GetMatchingItemByNumber(item->parent, j, itemname);
if (item2 != NULL)
{
out = NULL;
if (Q_stricmp(name, "backcolor") == 0)
out = &item2->window.backColor;
else if (Q_stricmp(name, "forecolor") == 0)
{
out = &item2->window.foreColor;
item2->window.flags |= WINDOW_FORECOLORSET;
}
else if (Q_stricmp(name, "bordercolor") == 0)
out = &item2->window.borderColor;
if (out)
{
for (i = 0; i < 4; i++)
(*out)[i] = color[i];
}
}
}
}
}
void Menu_ShowItemByName(menuDef_t *menu, const char *p, qboolean bShow)
{
itemDef_t *item;
int i;
int count = Menu_ItemsMatchingGroup(menu, p);
for (i = 0; i < count; i++)
{
item = Menu_GetMatchingItemByNumber(menu, i, p);
if (item != NULL)
{
if (bShow)
item->window.flags |= WINDOW_VISIBLE;
else
{
item->window.flags &= ~WINDOW_VISIBLE;
// stop cinematics playing in the window
if (item->window.cinematic >= 0)
{
DC->stopCinematic(item->window.cinematic);
item->window.cinematic = -1;
}
}
}
}
}
void Menu_FadeItemByName(menuDef_t *menu, const char *p, qboolean fadeOut)
{
itemDef_t *item;
int i;
int count = Menu_ItemsMatchingGroup(menu, p);
for (i = 0; i < count; i++)
{
item = Menu_GetMatchingItemByNumber(menu, i, p);
if (item != NULL)
{
if (fadeOut)
{
item->window.flags |= (WINDOW_FADINGOUT | WINDOW_VISIBLE);
item->window.flags &= ~WINDOW_FADINGIN;
}
else
{
item->window.flags |= (WINDOW_VISIBLE | WINDOW_FADINGIN);
item->window.flags &= ~WINDOW_FADINGOUT;
}
}
}
}
menuDef_t *Menus_FindByName(const char *p)
{
int i;
for (i = 0; i < menuCount; i++)
{
if (Q_stricmp(Menus[i].window.name, p) == 0)
return &Menus[i];
}
return NULL;
}
static void Menu_RunCloseScript(menuDef_t *menu)
{
if (menu && menu->window.flags & WINDOW_VISIBLE && menu->onClose)
{
itemDef_t item;
item.parent = menu;
Item_RunScript(&item, menu->onClose);
}
}
static void Menus_Close(menuDef_t *menu)
{
if (menu != NULL)
{
Menu_RunCloseScript(menu);
menu->window.flags &= ~(WINDOW_VISIBLE | WINDOW_HASFOCUS);
if (openMenuCount > 0)
openMenuCount--;
if (openMenuCount > 0)
Menus_Activate(menuStack[openMenuCount - 1]);
}
}
void Menus_CloseByName(const char *p) { Menus_Close(Menus_FindByName(p)); }
void Menus_CloseAll(void)
{
int i;
// Close any menus on the stack first
if (openMenuCount > 0)
{
for (i = openMenuCount; i > 0; i--)
Menus_Close(menuStack[i - 1]);
openMenuCount = 0;
}
// Close all other menus
for (i = 0; i < menuCount; i++)
Menus_Close(&Menus[i]);
g_editingField = qfalse;
g_waitingForKey = qfalse;
g_comboBoxItem = NULL;
}
void Script_Show(itemDef_t *item, char **args)
{
const char *name;
if (String_Parse(args, &name))
Menu_ShowItemByName(item->parent, name, qtrue);
}
void Script_Hide(itemDef_t *item, char **args)
{
const char *name;
if (String_Parse(args, &name))
Menu_ShowItemByName(item->parent, name, qfalse);
}
void Script_FadeIn(itemDef_t *item, char **args)
{
const char *name;
if (String_Parse(args, &name))
Menu_FadeItemByName(item->parent, name, qfalse);
}
void Script_FadeOut(itemDef_t *item, char **args)
{
const char *name;
if (String_Parse(args, &name))
Menu_FadeItemByName(item->parent, name, qtrue);
}
void Script_Open(itemDef_t *item, char **args)
{
const char *name;
if (String_Parse(args, &name))
Menus_ActivateByName(name);
}
void Script_ConditionalOpen(itemDef_t *item, char **args)
{
const char *cvar;
const char *name1;
const char *name2;
float val;
if (String_Parse(args, &cvar) && String_Parse(args, &name1) && String_Parse(args, &name2))
{
val = DC->getCVarValue(cvar);
if (val == 0.0f)
Menus_ActivateByName(name2);
else
Menus_ActivateByName(name1);
}
}
void Script_Close(itemDef_t *item, char **args)
{
const char *name;
(void)item;
if (String_Parse(args, &name))
Menus_CloseByName(name);
}
void Menu_TransitionItemByName(
menuDef_t *menu, const char *p, rectDef_t rectFrom, rectDef_t rectTo, int time, float amt)
{
itemDef_t *item;
int i;
int count = Menu_ItemsMatchingGroup(menu, p);
for (i = 0; i < count; i++)
{
item = Menu_GetMatchingItemByNumber(menu, i, p);
if (item != NULL)
{
item->window.flags |= (WINDOW_INTRANSITION | WINDOW_VISIBLE);
item->window.offsetTime = time;
memcpy(&item->window.rectClient, &rectFrom, sizeof(rectDef_t));
memcpy(&item->window.rectEffects, &rectTo, sizeof(rectDef_t));
item->window.rectEffects2.x = fabs(rectTo.x - rectFrom.x) / amt;
item->window.rectEffects2.y = fabs(rectTo.y - rectFrom.y) / amt;
item->window.rectEffects2.w = fabs(rectTo.w - rectFrom.w) / amt;
item->window.rectEffects2.h = fabs(rectTo.h - rectFrom.h) / amt;
Item_UpdatePosition(item);
}
}
}
void Script_Transition(itemDef_t *item, char **args)
{
const char *name;
rectDef_t rectFrom, rectTo;
int time;
float amt;
if (String_Parse(args, &name))
{
if (Rect_Parse(args, &rectFrom) && Rect_Parse(args, &rectTo) && Int_Parse(args, &time) &&
Float_Parse(args, &amt))
{
Menu_TransitionItemByName(item->parent, name, rectFrom, rectTo, time, amt);
}
}
}
void Menu_OrbitItemByName(menuDef_t *menu, const char *p, float x, float y, float cx, float cy, int time)
{
itemDef_t *item;
int i;
int count = Menu_ItemsMatchingGroup(menu, p);
for (i = 0; i < count; i++)
{
item = Menu_GetMatchingItemByNumber(menu, i, p);
if (item != NULL)
{
item->window.flags |= (WINDOW_ORBITING | WINDOW_VISIBLE);
item->window.offsetTime = time;
item->window.rectEffects.x = cx;
item->window.rectEffects.y = cy;
item->window.rectClient.x = x;
item->window.rectClient.y = y;
Item_UpdatePosition(item);
}
}
}
void Script_Orbit(itemDef_t *item, char **args)
{
const char *name;
float cx, cy, x, y;
int time;
if (String_Parse(args, &name))
{
if (Float_Parse(args, &x) && Float_Parse(args, &y) && Float_Parse(args, &cx) && Float_Parse(args, &cy) &&
Int_Parse(args, &time))
{
Menu_OrbitItemByName(item->parent, name, x, y, cx, cy, time);
}
}
}
void Script_SetFocus(itemDef_t *item, char **args)
{
const char *name;
itemDef_t *focusItem;
if (String_Parse(args, &name))
{
focusItem = Menu_FindItemByName(item->parent, name);
if (focusItem && !(focusItem->window.flags & WINDOW_DECORATION))
{
Menu_ClearFocus(item->parent);
focusItem->window.flags |= WINDOW_HASFOCUS;
if (focusItem->onFocus)
Item_RunScript(focusItem, focusItem->onFocus);
// Edit fields get activated too
if (Item_IsEditField(focusItem))
{
g_editingField = qtrue;
g_editItem = focusItem;
}
if (DC->Assets.itemFocusSound)
DC->startLocalSound(DC->Assets.itemFocusSound, CHAN_LOCAL_SOUND);
}
}
}
void Script_Reset(itemDef_t *item, char **args)
{
const char *name;
itemDef_t *resetItem;
if (String_Parse(args, &name))
{
resetItem = Menu_FindItemByName(item->parent, name);
if (resetItem)
{
if (Item_IsListBox(resetItem))
{
resetItem->cursorPos = DC->feederInitialise(resetItem->feederID);
Item_ListBox_SetStartPos(resetItem, 0);
DC->feederSelection(resetItem->feederID, resetItem->cursorPos);
}
}
}
}
void Script_SetPlayerModel(itemDef_t *item, char **args)
{
const char *name;
(void)item;
if (String_Parse(args, &name))
DC->setCVar("model", name);
}
void Script_SetPlayerHead(itemDef_t *item, char **args)
{
const char *name;
(void)item;
if (String_Parse(args, &name))
DC->setCVar("headmodel", name);
}
void Script_SetCvar(itemDef_t *item, char **args)
{
const char *cvar, *val;
(void)item;
if (String_Parse(args, &cvar) && String_Parse(args, &val))
DC->setCVar(cvar, val);
}
void Script_Exec(itemDef_t *item, char **args)
{
const char *val;
(void)item;
if (String_Parse(args, &val))
DC->executeText(EXEC_APPEND, va("%s\n", val));
}
void Script_Play(itemDef_t *item, char **args)
{
const char *val;
(void)item;
if (String_Parse(args, &val))
DC->startLocalSound(DC->registerSound(val, qfalse), CHAN_LOCAL_SOUND);
}
void Script_playLooped(itemDef_t *item, char **args)
{
const char *val;
(void)item;
if (String_Parse(args, &val))
{
DC->stopBackgroundTrack();
DC->startBackgroundTrack(val, val);
}
}
static ID_INLINE float UI_EmoticonHeight(fontInfo_t *font, float scale)
{
return font->glyphs[(int)'['].height * scale * font->glyphScale;
}
static ID_INLINE float UI_EmoticonWidth(fontInfo_t *font, float scale)
{
return UI_EmoticonHeight(font, scale) * DC->aspectScale;
}
void UI_EscapeEmoticons(char *dest, const char *src, int destsize)
{
int len;
qboolean escaped;
for (; *src && destsize > 1; src++, destsize--)
{
if (UI_Text_IsEmoticon(src, &escaped, &len, NULL, NULL) && !escaped)
{
*dest++ = '[';
destsize--;
}
*dest++ = *src;
}
*dest++ = '\0';
}
qboolean UI_Text_IsEmoticon(const char *s, qboolean *escaped, int *length, qhandle_t *h, int *width)
{
const char *p = s;
char emoticon[MAX_EMOTICON_NAME_LEN];
int i;
if (*p != '[')
return qfalse;
p++;
if (*p == '[')
{
*escaped = qtrue;
p++;
}
else
*escaped = qfalse;
for (*length = 0; p[*length] != ']'; (*length)++)
{
if (!p[*length] || *length == MAX_EMOTICON_NAME_LEN - 1)
return qfalse;
emoticon[*length] = p[*length];
}
emoticon[*length] = '\0';
for (i = 0; i < DC->Assets.emoticonCount; i++)
if (!Q_stricmp(DC->Assets.emoticons[i].name, emoticon))
break;
if (i == DC->Assets.emoticonCount)
return qfalse;
if (h)
*h = DC->Assets.emoticons[i].shader;
if (width)
*width = DC->Assets.emoticons[i].width;
(*length) += 2;
if (*escaped)
(*length)++;
return qtrue;
}
static float UI_Parse_Indent(const char **text)
{
char indentWidth[32];
char *indentWidthPtr;
const char *p = *text;
int numDigits;
float pixels;
while (isdigit(*p) || *p == '.')
p++;
if (*p != INDENT_MARKER)
return 0.0f;
numDigits = (p - *text);
if (numDigits > sizeof(indentWidth) - 1)
return 0.0f;
strncpy(indentWidth, *text, numDigits);
indentWidth[numDigits] = '\0';
indentWidthPtr = indentWidth;
if (!Float_Parse(&indentWidthPtr, &pixels))
return 0.0f;
(*text) += (numDigits + 1);
return pixels;
}
static ID_INLINE fontInfo_t *UI_FontForScale(float scale)
{
if (scale <= DC->smallFontScale)
return &DC->Assets.smallFont;
else if (scale >= DC->bigFontScale)
return &DC->Assets.bigFont;
else
return &DC->Assets.textFont;
}
float UI_Char_Width(const char **text, float scale)
{
glyphInfo_t *glyph;
fontInfo_t *font;
int emoticonLen;
qboolean emoticonEscaped;
int emoticonWidth;
if (text && *text)
{
if (Q_IsColorString(*text))
{
*text += 2;
return 0.0f;
}
if (**text == INDENT_MARKER)
{
(*text)++;
return 0.0f;
}
font = UI_FontForScale(scale);
if (UI_Text_IsEmoticon(*text, &emoticonEscaped, &emoticonLen, NULL, &emoticonWidth))
{
if (emoticonEscaped)
(*text)++;
else
{
*text += emoticonLen;
return emoticonWidth * UI_EmoticonWidth(font, scale);
}
}
(*text)++;
glyph = &font->glyphs[(int)**text];
return glyph->xSkip * DC->aspectScale * scale * font->glyphScale;
}
return 0.0f;
}
float UI_Text_Width(const char *text, float scale)
{
float out;
const char *s = text;
float indentWidth = 0.0f;
out = 0.0f;
if (text)
{
indentWidth = UI_Parse_Indent(&s);
while (*s)
out += UI_Char_Width(&s, scale);
}
return out + indentWidth;
}
float UI_Text_Height(const char *text, float scale)
{
float max;
glyphInfo_t *glyph;
float useScale;
const char *s = text;
fontInfo_t *font = UI_FontForScale(scale);
useScale = scale * font->glyphScale;
max = 0;
if (text)
{
while (s && *s)
{
if (Q_IsColorString(s))
{
s += 2;
continue;
}
else
{
glyph = &font->glyphs[(int)*s];
if (max < glyph->height)
max = glyph->height;
s++;
}
}
}
return max * useScale;
}
float UI_Text_EmWidth(float scale) { return UI_Text_Width("M", scale); }
float UI_Text_EmHeight(float scale) { return UI_Text_Height("M", scale); }
/*
================
UI_AdjustFrom640
Adjusted for resolution and screen aspect ratio
================
*/
void UI_AdjustFrom640(float *x, float *y, float *w, float *h)
{
*x *= DC->xscale;
*y *= DC->yscale;
*w *= DC->xscale;
*h *= DC->yscale;
}
/*
================
UI_SetClipRegion
=================
*/
void UI_SetClipRegion(float x, float y, float w, float h)
{
vec4_t clip;
UI_AdjustFrom640(&x, &y, &w, &h);
clip[0] = x;
clip[1] = y;
clip[2] = x + w;
clip[3] = y + h;
trap_R_SetClipRegion(clip);
}
/*
================
UI_ClearClipRegion
=================
*/
void UI_ClearClipRegion(void) { trap_R_SetClipRegion(NULL); }
static void UI_Text_PaintChar(float x, float y, float scale, glyphInfo_t *glyph, float size)
{
float w, h;
w = glyph->imageWidth;
h = glyph->imageHeight;
if (size > 0.0f)
{
float half = size * 0.5f * scale;
x -= half;
y -= half;
w += size;
h += size;
}
w *= (DC->aspectScale * scale);
h *= scale;
y -= (glyph->top * scale);
UI_AdjustFrom640(&x, &y, &w, &h);
DC->drawStretchPic(x, y, w, h, glyph->s, glyph->t, glyph->s2, glyph->t2, glyph->glyph);
}
static void UI_Text_Paint_Generic(float x, float y, float scale, float gapAdjust, const char *text, vec4_t color,
int style, int limit, float *maxX, int cursorPos, char cursor)
{
const char *s = text;
int len;
int count = 0;
vec4_t newColor;
fontInfo_t *font = UI_FontForScale(scale);
glyphInfo_t *glyph;
float useScale;
qhandle_t emoticonHandle = 0;
float emoticonH, emoticonW;
qboolean emoticonEscaped;
int emoticonLen = 0;
int emoticonWidth;
int cursorX = -1;
if (!text)
return;
useScale = scale * font->glyphScale;
emoticonH = UI_EmoticonHeight(font, scale);
emoticonW = UI_EmoticonWidth(font, scale);
len = strlen(text);
if (limit > 0 && len > limit)
len = limit;
DC->setColor(color);
memcpy(&newColor[0], &color[0], sizeof(vec4_t));
x += UI_Parse_Indent(&s);
while (s && *s && count < len)
{
const char *t = s;
float charWidth = UI_Char_Width(&t, scale);
glyph = &font->glyphs[(int)*s];
if (maxX && charWidth + x > *maxX)
{
*maxX = 0;
break;
}
if (cursorPos < 0)
{
if (Q_IsColorString(s))
{
memcpy(newColor, g_color_table[ColorIndex(*(s + 1))], sizeof(newColor));
newColor[3] = color[3];
DC->setColor(newColor);
s += 2;
continue;
}
if (*s == INDENT_MARKER)
{
s++;
continue;
}
if (UI_Text_IsEmoticon(s, &emoticonEscaped, &emoticonLen, &emoticonHandle, &emoticonWidth))
{
if (emoticonEscaped)
s++;
else
{
float yadj = useScale * glyph->top;
DC->setColor(NULL);
DC->drawHandlePic(x, y - yadj, (emoticonW * emoticonWidth), emoticonH, emoticonHandle);
DC->setColor(newColor);
x += (emoticonW * emoticonWidth) + gapAdjust;
s += emoticonLen;
count += emoticonWidth;
continue;
}
}
}
if (style == ITEM_TEXTSTYLE_SHADOWED || style == ITEM_TEXTSTYLE_SHADOWEDMORE)
{
int ofs;
if (style == ITEM_TEXTSTYLE_SHADOWED)
ofs = 1;
else
ofs = 2;
colorBlack[3] = newColor[3];
DC->setColor(colorBlack);
UI_Text_PaintChar(x + ofs, y + ofs, useScale, glyph, 0.0f);
DC->setColor(newColor);
colorBlack[3] = 1.0f;
}
else if (style == ITEM_TEXTSTYLE_NEON)
{
vec4_t glow;
memcpy(&glow[0], &newColor[0], sizeof(vec4_t));
glow[3] *= 0.2f;
DC->setColor(glow);
UI_Text_PaintChar(x, y, useScale, glyph, 6.0f);
UI_Text_PaintChar(x, y, useScale, glyph, 4.0f);
DC->setColor(newColor);
UI_Text_PaintChar(x, y, useScale, glyph, 2.0f);
DC->setColor(colorWhite);
}
UI_Text_PaintChar(x, y, useScale, glyph, 0.0f);
if (count == cursorPos)
cursorX = x;
x += (glyph->xSkip * DC->aspectScale * useScale) + gapAdjust;
s++;
count++;
}
if (maxX)
*maxX = x;
// paint cursor
if (cursorPos >= 0)
{
if (cursorPos == len)
cursorX = x;
if (cursorX >= 0 && !((DC->realTime / BLINK_DIVISOR) & 1))
{
glyph = &font->glyphs[(int)cursor];
UI_Text_PaintChar(cursorX, y, useScale, glyph, 0.0f);
}
}
DC->setColor(NULL);
}
void UI_Text_Paint_Limit(
float *maxX, float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit)
{
UI_Text_Paint_Generic(x, y, scale, adjust, text, color, ITEM_TEXTSTYLE_NORMAL, limit, maxX, -1, 0);
}
void UI_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style)
{
UI_Text_Paint_Generic(x, y, scale, adjust, text, color, style, limit, NULL, -1, 0);
}
void UI_Text_PaintWithCursor(
float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style)
{
UI_Text_Paint_Generic(x, y, scale, 0.0, text, color, style, limit, NULL, cursorPos, cursor);
}
commandDef_t commandList[] = {
{"close", &Script_Close}, // menu
{"conditionalopen", &Script_ConditionalOpen}, // menu
{"exec", &Script_Exec}, // group/name
{"fadein", &Script_FadeIn}, // group/name
{"fadeout", &Script_FadeOut}, // group/name
{"hide", &Script_Hide}, // group/name
{"open", &Script_Open}, // menu
{"orbit", &Script_Orbit}, // group/name
{"play", &Script_Play}, // group/name
{"playlooped", &Script_playLooped}, // group/name
{"reset", &Script_Reset}, // resets the state of the item argument
{"setasset", &Script_SetAsset}, // works on this
{"setbackground", &Script_SetBackground}, // works on this
{"setcolor", &Script_SetColor}, // works on this
{"setcvar", &Script_SetCvar}, // group/name
{"setfocus", &Script_SetFocus}, // sets this background color to team color
{"setitemcolor", &Script_SetItemColor}, // group/name
{"setplayerhead", &Script_SetPlayerHead}, // sets this background color to team color
{"setplayermodel", &Script_SetPlayerModel}, // sets this background color to team color
{"show", &Script_Show}, // group/name
{"transition", &Script_Transition}, // group/name
};
static size_t scriptCommandCount = ARRAY_LEN(commandList);
// despite what lcc thinks, we do not get cmdcmp here
static int commandComp(const void *a, const void *b) { return Q_stricmp((const char *)a, ((commandDef_t *)b)->name); }
void Item_RunScript(itemDef_t *item, const char *s)
{
char script[1024], *p;
commandDef_t *cmd;
memset(script, 0, sizeof(script));
if (item && s && s[0])
{
Q_strcat(script, 1024, s);
p = script;
while (1)
{
const char *command;
// expect command then arguments, ; ends command, NULL ends script
if (!String_Parse(&p, &command))
return;
if (command[0] == ';' && command[1] == '\0')
continue;
cmd = bsearch(command, commandList, scriptCommandCount, sizeof(commandDef_t), commandComp);
if (cmd)
cmd->handler(item, &p);
else
// not in our auto list, pass to handler
DC->runScript(&p);
}
}
}
qboolean Item_EnableShowViaCvar(itemDef_t *item, int flag)
{
char script[1024], *p;
memset(script, 0, sizeof(script));
if (item && item->enableCvar && *item->enableCvar && item->cvarTest && *item->cvarTest)
{
char buff[1024];
DC->getCVarString(item->cvarTest, buff, sizeof(buff));
Q_strcat(script, 1024, item->enableCvar);
p = script;
while (1)
{
const char *val;
// expect value then ; or NULL, NULL ends list
if (!String_Parse(&p, &val))
return (item->cvarFlags & flag) ? qfalse : qtrue;
if (val[0] == ';' && val[1] == '\0')
continue;
// enable it if any of the values are true
if (item->cvarFlags & flag)
{
if (Q_stricmp(buff, val) == 0)
return qtrue;
}
else
{
// disable it if any of the values are true
if (Q_stricmp(buff, val) == 0)
return qfalse;
}
}
return (item->cvarFlags & flag) ? qfalse : qtrue;
}
return qtrue;
}
// will optionaly set focus to this item
qboolean Item_SetFocus(itemDef_t *item, float x, float y)
{
int i;
itemDef_t *oldFocus;
sfxHandle_t *sfx = &DC->Assets.itemFocusSound;
qboolean playSound = qfalse;
menuDef_t *parent;
// sanity check, non-null, not a decoration and does not already have the focus
if (item == NULL || item->window.flags & WINDOW_DECORATION || item->window.flags & WINDOW_HASFOCUS ||
!(item->window.flags & WINDOW_VISIBLE))
{
return qfalse;
}
parent = (menuDef_t *)item->parent;
// items can be enabled and disabled based on cvars
if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE))
return qfalse;
if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE) && !Item_EnableShowViaCvar(item, CVAR_SHOW))
return qfalse;
oldFocus = Menu_ClearFocus(item->parent);
if (item->type == ITEM_TYPE_TEXT)
{
rectDef_t r;
r = item->textRect;
r.y -= r.h;
if (Rect_ContainsPoint(&r, x, y))
{
item->window.flags |= WINDOW_HASFOCUS;
if (item->focusSound)
sfx = &item->focusSound;
playSound = qtrue;
}
else
{
if (oldFocus)
{
oldFocus->window.flags |= WINDOW_HASFOCUS;
if (oldFocus->onFocus)
Item_RunScript(oldFocus, oldFocus->onFocus);
}
}
}
else
{
item->window.flags |= WINDOW_HASFOCUS;
if (item->onFocus)
Item_RunScript(item, item->onFocus);
if (item->focusSound)
sfx = &item->focusSound;
playSound = qtrue;
}
if (playSound && sfx)
DC->startLocalSound(*sfx, CHAN_LOCAL_SOUND);
for (i = 0; i < parent->itemCount; i++)
{
if (parent->items[i] == item)
{
parent->cursorItem = i;
break;
}
}
return qtrue;
}
static float Item_ListBox_HeightForNumItems(itemDef_t *item, int numItems)
{
listBoxDef_t *listPtr = item->typeData.list;
return (listPtr->elementHeight * numItems) + 2.0f;
}
static int Item_ListBox_NumItemsForItemHeight(itemDef_t *item)
{
listBoxDef_t *listPtr = item->typeData.list;
if (item->type == ITEM_TYPE_COMBOBOX)
return listPtr->dropItems;
else
return ((item->window.rect.h - 2.0f) / listPtr->elementHeight);
}
int Item_ListBox_MaxScroll(itemDef_t *item)
{
int total = DC->feederCount(item->feederID);
int max = total - Item_ListBox_NumItemsForItemHeight(item);
if (max < 0)
return 0;
return max;
}
static float oldComboBoxY;
static float oldComboBoxH;
static qboolean Item_ComboBox_MaybeCastToListBox(itemDef_t *item)
{
listBoxDef_t *listPtr = item->typeData.list;
qboolean cast = g_comboBoxItem != NULL && (item->type == ITEM_TYPE_COMBOBOX);
if (cast)
{
oldComboBoxY = item->window.rect.y;
oldComboBoxH = item->window.rect.h;
item->window.rect.y += item->window.rect.h;
item->window.rect.h = Item_ListBox_HeightForNumItems(item, listPtr->dropItems);
item->type = ITEM_TYPE_LISTBOX;
}
return cast;
}
static void Item_ComboBox_MaybeUnCastFromListBox(itemDef_t *item, qboolean unCast)
{
if (unCast)
{
item->window.rect.y = oldComboBoxY;
item->window.rect.h = oldComboBoxH;
item->type = ITEM_TYPE_COMBOBOX;
}
}
static void Item_ListBox_SetStartPos(itemDef_t *item, int startPos)
{
listBoxDef_t *listPtr = item->typeData.list;
int total = DC->feederCount(item->feederID);
int max = Item_ListBox_MaxScroll(item);
if (startPos < 0)
listPtr->startPos = 0;
else if (startPos > max)
listPtr->startPos = max;
else
listPtr->startPos = startPos;
listPtr->endPos = listPtr->startPos + MIN((total - listPtr->startPos), Item_ListBox_NumItemsForItemHeight(item));
}
float Item_ListBox_ThumbPosition(itemDef_t *item)
{
float max, pos, size;
float startPos = (float)item->typeData.list->startPos;
max = Item_ListBox_MaxScroll(item);
size = SCROLLBAR_SLIDER_HEIGHT(item);
if (max > 0.0f)
pos = (size - SCROLLBAR_ARROW_HEIGHT) / max;
else
pos = 0.0f;
pos *= startPos;
return SCROLLBAR_SLIDER_Y(item) + pos;
}
float Item_ListBox_ThumbDrawPosition(itemDef_t *item)
{
if (itemCapture == item)
{
float min = SCROLLBAR_SLIDER_Y(item);
float max = min + SCROLLBAR_SLIDER_HEIGHT(item) - SCROLLBAR_ARROW_HEIGHT;
float halfThumbSize = SCROLLBAR_ARROW_HEIGHT / 2.0f;
if (DC->cursory >= min + halfThumbSize && DC->cursory <= max + halfThumbSize)
return DC->cursory - halfThumbSize;
}
return Item_ListBox_ThumbPosition(item);
}
float Item_Slider_ThumbPosition(itemDef_t *item)
{
float value, range, x;
editFieldDef_t *editDef = item->typeData.edit;
if (item->text)
x = item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET;
else
x = item->window.rect.x;
if (editDef == NULL && item->cvar)
return x;
value = DC->getCVarValue(item->cvar);
if (value < editDef->minVal)
value = editDef->minVal;
else if (value > editDef->maxVal)
value = editDef->maxVal;
range = editDef->maxVal - editDef->minVal;
value -= editDef->minVal;
value /= range;
value *= SLIDER_WIDTH;
x += value;
return x;
}
static float Item_Slider_VScale(itemDef_t *item)
{
if (SLIDER_THUMB_HEIGHT > item->window.rect.h)
return item->window.rect.h / SLIDER_THUMB_HEIGHT;
else
return 1.0f;
}
int Item_Slider_OverSlider(itemDef_t *item, float x, float y)
{
rectDef_t r;
float vScale = Item_Slider_VScale(item);
r.x = Item_Slider_ThumbPosition(item) - (SLIDER_THUMB_WIDTH / 2);
r.y = item->textRect.y - item->textRect.h + ((item->textRect.h - (SLIDER_THUMB_HEIGHT * vScale)) / 2.0f);
r.w = SLIDER_THUMB_WIDTH;
r.h = SLIDER_THUMB_HEIGHT * vScale;
if (Rect_ContainsPoint(&r, x, y))
return WINDOW_LB_THUMB;
return 0;
}
int Item_ListBox_OverLB(itemDef_t *item, float x, float y)
{
rectDef_t r;
int thumbstart;
r.x = SCROLLBAR_SLIDER_X(item);
r.y = SCROLLBAR_Y(item);
r.w = SCROLLBAR_ARROW_WIDTH;
r.h = SCROLLBAR_ARROW_HEIGHT;
if (Rect_ContainsPoint(&r, x, y))
return WINDOW_LB_UPARROW;
r.y = SCROLLBAR_SLIDER_Y(item) + SCROLLBAR_SLIDER_HEIGHT(item);
if (Rect_ContainsPoint(&r, x, y))
return WINDOW_LB_DOWNARROW;
thumbstart = Item_ListBox_ThumbPosition(item);
r.y = thumbstart;
if (Rect_ContainsPoint(&r, x, y))
return WINDOW_LB_THUMB;
r.y = SCROLLBAR_SLIDER_Y(item);
r.h = thumbstart - r.y;
if (Rect_ContainsPoint(&r, x, y))
return WINDOW_LB_PGUP;
r.y = thumbstart + SCROLLBAR_ARROW_HEIGHT;
r.h = (SCROLLBAR_SLIDER_Y(item) + SCROLLBAR_SLIDER_HEIGHT(item)) - r.y;
if (Rect_ContainsPoint(&r, x, y))
return WINDOW_LB_PGDN;
return 0;
}
void Item_ListBox_MouseEnter(itemDef_t *item, float x, float y)
{
rectDef_t r;
listBoxDef_t *listPtr = item->typeData.list;
int listBoxFlags = (WINDOW_LB_UPARROW | WINDOW_LB_DOWNARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN);
int total = DC->feederCount(item->feederID);
item->window.flags &= ~listBoxFlags;
item->window.flags |= Item_ListBox_OverLB(item, x, y);
if (!(item->window.flags & listBoxFlags))
{
r.x = SCROLLBAR_X(item);
r.y = SCROLLBAR_Y(item);
r.w = SCROLLBAR_W(item);
r.h = listPtr->elementHeight * MIN(Item_ListBox_NumItemsForItemHeight(item), total);
if (Rect_ContainsPoint(&r, x, y))
{
listPtr->cursorPos = (int)((y - r.y) / listPtr->elementHeight) + listPtr->startPos;
if (listPtr->cursorPos >= listPtr->endPos)
listPtr->cursorPos = listPtr->endPos - 1;
}
else
listPtr->cursorPos = -1;
}
}
void Item_MouseEnter(itemDef_t *item, float x, float y)
{
rectDef_t r;
if (item)
{
r = item->textRect;
r.y -= r.h;
// in the text rect?
// items can be enabled and disabled based on cvars
if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE))
return;
if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE) && !Item_EnableShowViaCvar(item, CVAR_SHOW))
return;
if (Rect_ContainsPoint(&r, x, y))
{
if (!(item->window.flags & WINDOW_MOUSEOVERTEXT))
{
Item_RunScript(item, item->mouseEnterText);
item->window.flags |= WINDOW_MOUSEOVERTEXT;
}
if (!(item->window.flags & WINDOW_MOUSEOVER))
{
Item_RunScript(item, item->mouseEnter);
item->window.flags |= WINDOW_MOUSEOVER;
}
}
else
{
// not in the text rect
if (item->window.flags & WINDOW_MOUSEOVERTEXT)
{
// if we were
Item_RunScript(item, item->mouseExitText);
item->window.flags &= ~WINDOW_MOUSEOVERTEXT;
}
if (!(item->window.flags & WINDOW_MOUSEOVER))
{
Item_RunScript(item, item->mouseEnter);
item->window.flags |= WINDOW_MOUSEOVER;
}
if (item->type == ITEM_TYPE_LISTBOX)
Item_ListBox_MouseEnter(item, x, y);
}
}
}
void Item_MouseLeave(itemDef_t *item)
{
if (item)
{
if (item->window.flags & WINDOW_MOUSEOVERTEXT)
{
Item_RunScript(item, item->mouseExitText);
item->window.flags &= ~WINDOW_MOUSEOVERTEXT;
}
Item_RunScript(item, item->mouseExit);
item->window.flags &= ~(WINDOW_LB_DOWNARROW | WINDOW_LB_UPARROW);
}
}
itemDef_t *Menu_HitTest(menuDef_t *menu, float x, float y)
{
int i;
for (i = 0; i < menu->itemCount; i++)
{
if (Rect_ContainsPoint(&menu->items[i]->window.rect, x, y))
return menu->items[i];
}
return NULL;
}
void Item_SetMouseOver(itemDef_t *item, qboolean focus)
{
if (item)
{
if (focus)
item->window.flags |= WINDOW_MOUSEOVER;
else
item->window.flags &= ~WINDOW_MOUSEOVER;
}
}
qboolean Item_OwnerDraw_HandleKey(itemDef_t *item, int key)
{
if (item && DC->ownerDrawHandleKey)
return DC->ownerDrawHandleKey(item->window.ownerDraw, key);
return qfalse;
}
qboolean Item_ListBox_HandleKey(itemDef_t *item, int key, qboolean down, qboolean force)
{
listBoxDef_t *listPtr = item->typeData.list;
int count = DC->feederCount(item->feederID);
int viewmax;
if (force ||
(Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_HASFOCUS))
{
viewmax = Item_ListBox_NumItemsForItemHeight(item);
switch (key)
{
case K_MOUSE1:
case K_MOUSE2:
if (item->window.flags & WINDOW_LB_UPARROW)
Item_ListBox_SetStartPos(item, listPtr->startPos - 1);
else if (item->window.flags & WINDOW_LB_DOWNARROW)
Item_ListBox_SetStartPos(item, listPtr->startPos + 1);
else if (item->window.flags & WINDOW_LB_PGUP)
Item_ListBox_SetStartPos(item, listPtr->startPos - viewmax);
else if (item->window.flags & WINDOW_LB_PGDN)
Item_ListBox_SetStartPos(item, listPtr->startPos + viewmax);
else if (item->window.flags & WINDOW_LB_THUMB)
break; // Handled by capture function
else
{
// Select an item
qboolean runDoubleClick = qfalse;
// Mouse isn't over an item
if (listPtr->cursorPos < 0)
break;
if (item->cursorPos != listPtr->cursorPos)
{
item->cursorPos = listPtr->cursorPos;
DC->feederSelection(item->feederID, item->cursorPos);
}
runDoubleClick = DC->realTime < lastListBoxClickTime && listPtr->doubleClick;
lastListBoxClickTime = DC->realTime + DOUBLE_CLICK_DELAY;
// Made a selection, so close combobox
if (g_comboBoxItem != NULL)
{
if (listPtr->doubleClick)
runDoubleClick = qtrue;
g_comboBoxItem = NULL;
}
if (runDoubleClick)
Item_RunScript(item, listPtr->doubleClick);
}
break;
case K_MWHEELUP:
Item_ListBox_SetStartPos(item, listPtr->startPos - 1);
break;
case K_MWHEELDOWN:
Item_ListBox_SetStartPos(item, listPtr->startPos + 1);
break;
case K_ENTER:
// Invoke the doubleClick handler when enter is pressed
if (listPtr->doubleClick)
Item_RunScript(item, listPtr->doubleClick);
break;
case K_PGUP:
case K_KP_PGUP:
if (!listPtr->notselectable)
{
listPtr->cursorPos -= viewmax;
if (listPtr->cursorPos < 0)
listPtr->cursorPos = 0;
if (listPtr->cursorPos < listPtr->startPos)
Item_ListBox_SetStartPos(item, listPtr->cursorPos);
if (listPtr->cursorPos >= listPtr->startPos + viewmax)
Item_ListBox_SetStartPos(item, listPtr->cursorPos - viewmax + 1);
item->cursorPos = listPtr->cursorPos;
DC->feederSelection(item->feederID, item->cursorPos);
}
else
Item_ListBox_SetStartPos(item, listPtr->startPos - viewmax);
break;
case K_PGDN:
case K_KP_PGDN:
if (!listPtr->notselectable)
{
listPtr->cursorPos += viewmax;
if (listPtr->cursorPos < listPtr->startPos)
Item_ListBox_SetStartPos(item, listPtr->cursorPos);
if (listPtr->cursorPos >= count)
listPtr->cursorPos = count - 1;
if (listPtr->cursorPos >= listPtr->startPos + viewmax)
Item_ListBox_SetStartPos(item, listPtr->cursorPos - viewmax + 1);
item->cursorPos = listPtr->cursorPos;
DC->feederSelection(item->feederID, item->cursorPos);
}
else
Item_ListBox_SetStartPos(item, listPtr->startPos + viewmax);
break;
default:
// Not handled
return qfalse;
}
return qtrue;
}
return qfalse;
}
qboolean Item_ComboBox_HandleKey(itemDef_t *item, int key, qboolean down, qboolean force)
{
if (g_comboBoxItem != NULL)
{
qboolean result;
qboolean cast = Item_ComboBox_MaybeCastToListBox(item);
result = Item_ListBox_HandleKey(item, key, down, force);
Item_ComboBox_MaybeUnCastFromListBox(item, cast);
if (!result)
g_comboBoxItem = NULL;
return result;
}
else
{
if (force ||
(Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_HASFOCUS))
{
if (key == K_MOUSE1 || key == K_MOUSE2)
{
g_comboBoxItem = item;
return qtrue;
}
}
}
return qfalse;
}
qboolean Item_YesNo_HandleKey(itemDef_t *item, int key)
{
if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_HASFOCUS &&
item->cvar)
{
if (key == K_MOUSE1 || key == K_ENTER || key == K_MOUSE2 || key == K_MOUSE3)
{
DC->setCVar(item->cvar, va("%i", !DC->getCVarValue(item->cvar)));
return qtrue;
}
}
return qfalse;
}
int Item_Multi_CountSettings(itemDef_t *item)
{
if (item->typeData.multi == NULL)
return 0;
return item->typeData.multi->count;
}
int Item_Multi_FindCvarByValue(itemDef_t *item)
{
char buff[1024];
float value = 0;
int i;
multiDef_t *multiPtr = item->typeData.multi;
if (multiPtr)
{
if (multiPtr->strDef)
DC->getCVarString(item->cvar, buff, sizeof(buff));
else
value = DC->getCVarValue(item->cvar);
for (i = 0; i < multiPtr->count; i++)
{
if (multiPtr->strDef)
{
if (Q_stricmp(buff, multiPtr->cvarStr[i]) == 0)
return i;
}
else
{
if (multiPtr->cvarValue[i] == value)
return i;
}
}
}
return 0;
}
const char *Item_Multi_Setting(itemDef_t *item)
{
char buff[1024];
float value = 0;
int i;
multiDef_t *multiPtr = item->typeData.multi;
if (multiPtr)
{
if (multiPtr->strDef)
DC->getCVarString(item->cvar, buff, sizeof(buff));
else
value = DC->getCVarValue(item->cvar);
for (i = 0; i < multiPtr->count; i++)
{
if (multiPtr->strDef)
{
if (Q_stricmp(buff, multiPtr->cvarStr[i]) == 0)
return multiPtr->cvarList[i];
}
else
{
if (multiPtr->cvarValue[i] == value)
return multiPtr->cvarList[i];
}
}
}
return "";
}
qboolean Item_Cycle_HandleKey(itemDef_t *item, int key)
{
cycleDef_t *cyclePtr = item->typeData.cycle;
qboolean mouseOver = Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory);
int count = DC->feederCount(item->feederID);
if (cyclePtr)
{
if (item->window.flags & WINDOW_HASFOCUS)
{
if ((mouseOver && key == K_MOUSE1) || key == K_ENTER || key == K_RIGHTARROW || key == K_DOWNARROW)
{
if (count > 0)
cyclePtr->cursorPos = (cyclePtr->cursorPos + 1) % count;
DC->feederSelection(item->feederID, cyclePtr->cursorPos);
return qtrue;
}
else if ((mouseOver && key == K_MOUSE2) || key == K_LEFTARROW || key == K_UPARROW)
{
if (count > 0)
cyclePtr->cursorPos = (count + cyclePtr->cursorPos - 1) % count;
DC->feederSelection(item->feederID, cyclePtr->cursorPos);
return qtrue;
}
}
}
return qfalse;
}
qboolean Item_Multi_HandleKey(itemDef_t *item, int key)
{
qboolean mouseOver = Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory);
int max = Item_Multi_CountSettings(item);
qboolean changed = qfalse;
if (item->typeData.multi)
{
if (item->window.flags & WINDOW_HASFOCUS && item->cvar && max > 0)
{
int current;
if ((mouseOver && key == K_MOUSE1) || key == K_ENTER || key == K_RIGHTARROW || key == K_DOWNARROW)
{
current = (Item_Multi_FindCvarByValue(item) + 1) % max;
changed = qtrue;
}
else if ((mouseOver && key == K_MOUSE2) || key == K_LEFTARROW || key == K_UPARROW)
{
current = (Item_Multi_FindCvarByValue(item) + max - 1) % max;
changed = qtrue;
}
if (changed)
{
if (item->typeData.multi->strDef)
DC->setCVar(item->cvar, item->typeData.multi->cvarStr[current]);
else
{
float value = item->typeData.multi->cvarValue[current];
if (((float)((int)value)) == value)
DC->setCVar(item->cvar, va("%i", (int)value));
else
DC->setCVar(item->cvar, va("%f", value));
}
return qtrue;
}
}
}
return qfalse;
}
#define MIN_FIELD_WIDTH 10
#define EDIT_CURSOR_WIDTH 10
static void Item_TextField_CalcPaintOffset(itemDef_t *item, char *buff)
{
editFieldDef_t *editPtr = item->typeData.edit;
if (item->cursorPos < editPtr->paintOffset)
editPtr->paintOffset = item->cursorPos;
else
{
// If there is a maximum field width
if (editPtr->maxFieldWidth > 0)
{
// If the cursor is at the end of the string, maximise the amount of the
// string that's visible
if (buff[item->cursorPos + 1] == '\0')
{
while (UI_Text_Width(&buff[editPtr->paintOffset], item->textscale) <=
(editPtr->maxFieldWidth - EDIT_CURSOR_WIDTH) &&
editPtr->paintOffset > 0)
editPtr->paintOffset--;
}
buff[item->cursorPos + 1] = '\0';
// Shift paintOffset so that the cursor is visible
while (UI_Text_Width(&buff[editPtr->paintOffset], item->textscale) >
(editPtr->maxFieldWidth - EDIT_CURSOR_WIDTH))
editPtr->paintOffset++;
}
}
}
qboolean Item_TextField_HandleKey(itemDef_t *item, int key)
{
char buff[1024];
int len;
itemDef_t *newItem = NULL;
editFieldDef_t *editPtr = item->typeData.edit;
qboolean releaseFocus = qtrue;
if (item->cvar)
{
Com_Memset(buff, 0, sizeof(buff));
DC->getCVarString(item->cvar, buff, sizeof(buff));
len = strlen(buff);
if (len < item->cursorPos)
item->cursorPos = len;
if (editPtr->maxChars && len > editPtr->maxChars)
len = editPtr->maxChars;
if (key & K_CHAR_FLAG)
{
key &= ~K_CHAR_FLAG;
if (key == 'h' - 'a' + 1)
{
// ctrl-h is backspace
if (item->cursorPos > 0)
{
memmove(&buff[item->cursorPos - 1], &buff[item->cursorPos], len + 1 - item->cursorPos);
item->cursorPos--;
}
DC->setCVar(item->cvar, buff);
}
else if (key < 32 || !item->cvar)
{
// Ignore any non printable chars
releaseFocus = qfalse;
goto exit;
}
else if (item->type == ITEM_TYPE_NUMERICFIELD && (key < '0' || key > '9'))
{
// Ignore non-numeric characters
releaseFocus = qfalse;
goto exit;
}
else
{
if (!DC->getOverstrikeMode())
{
if ((len == MAX_EDITFIELD - 1) || (editPtr->maxChars && len >= editPtr->maxChars))
{
// Reached maximum field length
releaseFocus = qfalse;
goto exit;
}
memmove(&buff[item->cursorPos + 1], &buff[item->cursorPos], len + 1 - item->cursorPos);
}
else
{
// Reached maximum field length
if (editPtr->maxChars && item->cursorPos >= editPtr->maxChars)
{
releaseFocus = qfalse;
goto exit;
}
}
buff[item->cursorPos] = key;
DC->setCVar(item->cvar, buff);
if (item->cursorPos < len + 1)
item->cursorPos++;
}
}
else
{
switch (key)
{
case K_DEL:
case K_KP_DEL:
if (item->cursorPos < len)
{
memmove(buff + item->cursorPos, buff + item->cursorPos + 1, len - item->cursorPos);
DC->setCVar(item->cvar, buff);
}
break;
case K_RIGHTARROW:
case K_KP_RIGHTARROW:
if (item->cursorPos < len)
item->cursorPos++;
break;
case K_LEFTARROW:
case K_KP_LEFTARROW:
if (item->cursorPos > 0)
item->cursorPos--;
break;
case K_HOME:
case K_KP_HOME:
item->cursorPos = 0;
break;
case K_END:
case K_KP_END:
item->cursorPos = len;
break;
case K_INS:
case K_KP_INS:
DC->setOverstrikeMode(!DC->getOverstrikeMode());
break;
case K_TAB:
case K_DOWNARROW:
case K_KP_DOWNARROW:
case K_UPARROW:
case K_KP_UPARROW:
// Ignore these keys from the say field
if (item->type == ITEM_TYPE_SAYFIELD)
break;
newItem = Menu_SetNextCursorItem(item->parent);
if (newItem && Item_IsEditField(newItem))
{
g_editItem = newItem;
}
else
{
releaseFocus = qtrue;
goto exit;
}
break;
case K_MOUSE1:
case K_MOUSE2:
case K_MOUSE3:
case K_MOUSE4:
// Ignore these buttons from the say field
if (item->type == ITEM_TYPE_SAYFIELD)
break;
// FALLTHROUGH
case K_ENTER:
case K_KP_ENTER:
case K_ESCAPE:
releaseFocus = qtrue;
goto exit;
default:
break;
}
}
releaseFocus = qfalse;
}
exit:
Item_TextField_CalcPaintOffset(item, buff);
return !releaseFocus;
}
static void _Scroll_ListBox_AutoFunc(scrollInfo_t *si)
{
if (DC->realTime > si->nextScrollTime)
{
// need to scroll which is done by simulating a click to the item
// this is done a bit sideways as the autoscroll "knows" that the item is a listbox
// so it calls it directly
Item_ListBox_HandleKey(si->item, si->scrollKey, qtrue, qfalse);
si->nextScrollTime = DC->realTime + si->adjustValue;
}
if (DC->realTime > si->nextAdjustTime)
{
si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST;
if (si->adjustValue > SCROLL_TIME_FLOOR)
si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET;
}
}
static void Scroll_ListBox_AutoFunc(void *p)
{
scrollInfo_t *si = (scrollInfo_t *)p;
qboolean cast = Item_ComboBox_MaybeCastToListBox(si->item);
_Scroll_ListBox_AutoFunc(si);
Item_ComboBox_MaybeUnCastFromListBox(si->item, cast);
}
static void _Scroll_ListBox_ThumbFunc(scrollInfo_t *si)
{
rectDef_t r;
int pos, max;
if (DC->cursory != si->yStart)
{
r.x = si->item->window.rect.x + si->item->window.rect.w - SCROLLBAR_ARROW_WIDTH - 1;
r.y = si->item->window.rect.y + SCROLLBAR_ARROW_HEIGHT + 1;
r.w = SCROLLBAR_ARROW_WIDTH;
r.h = si->item->window.rect.h - (SCROLLBAR_ARROW_HEIGHT * 2) - 2;
max = Item_ListBox_MaxScroll(si->item);
//
pos = (DC->cursory - r.y - SCROLLBAR_ARROW_HEIGHT / 2) * max / (r.h - SCROLLBAR_ARROW_HEIGHT);
if (pos < 0)
pos = 0;
else if (pos > max)
pos = max;
Item_ListBox_SetStartPos(si->item, pos);
si->yStart = DC->cursory;
}
if (DC->realTime > si->nextScrollTime)
{
// need to scroll which is done by simulating a click to the item
// this is done a bit sideways as the autoscroll "knows" that the item is a listbox
// so it calls it directly
Item_ListBox_HandleKey(si->item, si->scrollKey, qtrue, qfalse);
si->nextScrollTime = DC->realTime + si->adjustValue;
}
if (DC->realTime > si->nextAdjustTime)
{
si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST;
if (si->adjustValue > SCROLL_TIME_FLOOR)
si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET;
}
}
static void Scroll_ListBox_ThumbFunc(void *p)
{
scrollInfo_t *si = (scrollInfo_t *)p;
qboolean cast = Item_ComboBox_MaybeCastToListBox(si->item);
_Scroll_ListBox_ThumbFunc(si);
Item_ComboBox_MaybeUnCastFromListBox(si->item, cast);
}
static void Scroll_Slider_ThumbFunc(void *p)
{
float x, value, cursorx;
scrollInfo_t *si = (scrollInfo_t *)p;
if (si->item->text)
x = si->item->textRect.x + si->item->textRect.w + ITEM_VALUE_OFFSET;
else
x = si->item->window.rect.x;
cursorx = DC->cursorx;
if (cursorx < x)
cursorx = x;
else if (cursorx > x + SLIDER_WIDTH)
cursorx = x + SLIDER_WIDTH;
value = cursorx - x;
value /= SLIDER_WIDTH;
value *= si->item->typeData.edit->maxVal - si->item->typeData.edit->minVal;
value += si->item->typeData.edit->minVal;
DC->setCVar(si->item->cvar, va("%f", value));
}
void Item_StartCapture(itemDef_t *item, int key)
{
int flags;
// Don't allow captureFunc to be overridden
if (captureFunc != voidFunction)
return;
switch (item->type)
{
case ITEM_TYPE_LISTBOX:
case ITEM_TYPE_COMBOBOX:
{
qboolean cast = Item_ComboBox_MaybeCastToListBox(item);
flags = Item_ListBox_OverLB(item, DC->cursorx, DC->cursory);
Item_ComboBox_MaybeUnCastFromListBox(item, cast);
if (flags & (WINDOW_LB_UPARROW | WINDOW_LB_DOWNARROW))
{
scrollInfo.nextScrollTime = DC->realTime + SCROLL_TIME_START;
scrollInfo.nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST;
scrollInfo.adjustValue = SCROLL_TIME_START;
scrollInfo.scrollKey = key;
scrollInfo.scrollDir = (flags & WINDOW_LB_UPARROW) ? qtrue : qfalse;
scrollInfo.item = item;
UI_InstallCaptureFunc(Scroll_ListBox_AutoFunc, &scrollInfo, 0);
itemCapture = item;
}
else if (flags & WINDOW_LB_THUMB)
{
scrollInfo.scrollKey = key;
scrollInfo.item = item;
scrollInfo.xStart = DC->cursorx;
scrollInfo.yStart = DC->cursory;
UI_InstallCaptureFunc(Scroll_ListBox_ThumbFunc, &scrollInfo, 0);
itemCapture = item;
}
break;
}
case ITEM_TYPE_SLIDER:
{
flags = Item_Slider_OverSlider(item, DC->cursorx, DC->cursory);
if (flags & WINDOW_LB_THUMB)
{
scrollInfo.scrollKey = key;
scrollInfo.item = item;
scrollInfo.xStart = DC->cursorx;
scrollInfo.yStart = DC->cursory;
UI_InstallCaptureFunc(Scroll_Slider_ThumbFunc, &scrollInfo, 0);
itemCapture = item;
}
break;
}
}
}
void Item_StopCapture(itemDef_t *item) {}
qboolean Item_Slider_HandleKey(itemDef_t *item, int key, qboolean down)
{
float x, value, width;
if (item->window.flags & WINDOW_HASFOCUS && item->cvar &&
Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory))
{
if (item->typeData.edit && (key == K_ENTER || key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3))
{
rectDef_t testRect;
width = SLIDER_WIDTH;
if (item->text)
x = item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET;
else
x = item->window.rect.x;
testRect = item->window.rect;
value = (float)SLIDER_THUMB_WIDTH / 2;
testRect.x = x - value;
testRect.w = SLIDER_WIDTH + value;
if (Rect_ContainsPoint(&testRect, DC->cursorx, DC->cursory))
{
value = (float)(DC->cursorx - x) / width;
value *= (item->typeData.edit->maxVal - item->typeData.edit->minVal);
value += item->typeData.edit->minVal;
DC->setCVar(item->cvar, va("%f", value));
return qtrue;
}
}
}
return qfalse;
}
qboolean Item_HandleKey(itemDef_t *item, int key, qboolean down)
{
if (itemCapture)
{
Item_StopCapture(itemCapture);
itemCapture = NULL;
UI_RemoveCaptureFunc();
}
else
{
if (down && (key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3))
Item_StartCapture(item, key);
}
if (!down)
return qfalse;
// Edit fields are handled specially
if (Item_IsEditField(item))
return qfalse;
switch (item->type)
{
case ITEM_TYPE_BUTTON:
return qfalse;
case ITEM_TYPE_RADIOBUTTON:
return qfalse;
case ITEM_TYPE_CHECKBOX:
return qfalse;
case ITEM_TYPE_CYCLE:
return Item_Cycle_HandleKey(item, key);
case ITEM_TYPE_LISTBOX:
return Item_ListBox_HandleKey(item, key, down, qfalse);
case ITEM_TYPE_COMBOBOX:
return Item_ComboBox_HandleKey(item, key, down, qfalse);
case ITEM_TYPE_YESNO:
return Item_YesNo_HandleKey(item, key);
case ITEM_TYPE_MULTI:
return Item_Multi_HandleKey(item, key);
case ITEM_TYPE_OWNERDRAW:
return Item_OwnerDraw_HandleKey(item, key);
case ITEM_TYPE_BIND:
return Item_Bind_HandleKey(item, key, down);
case ITEM_TYPE_SLIDER:
return Item_Slider_HandleKey(item, key, down);
default:
return qfalse;
}
}
void Item_Action(itemDef_t *item)
{
if (item)
Item_RunScript(item, item->action);
}
itemDef_t *Menu_SetPrevCursorItem(menuDef_t *menu)
{
qboolean wrapped = qfalse;
int oldCursor = menu->cursorItem;
if (menu->cursorItem < 0)
{
menu->cursorItem = menu->itemCount - 1;
wrapped = qtrue;
}
while (menu->cursorItem > -1)
{
menu->cursorItem--;
if (menu->cursorItem < 0 && !wrapped)
{
wrapped = qtrue;
menu->cursorItem = menu->itemCount - 1;
}
if (Item_SetFocus(menu->items[menu->cursorItem], DC->cursorx, DC->cursory))
{
Menu_HandleMouseMove(menu, menu->items[menu->cursorItem]->window.rect.x + 1,
menu->items[menu->cursorItem]->window.rect.y + 1);
return menu->items[menu->cursorItem];
}
}
menu->cursorItem = oldCursor;
return NULL;
}
itemDef_t *Menu_SetNextCursorItem(menuDef_t *menu)
{
qboolean wrapped = qfalse;
int oldCursor = menu->cursorItem;
if (menu->cursorItem == -1)
{
menu->cursorItem = 0;
wrapped = qtrue;
}
while (menu->cursorItem < menu->itemCount)
{
menu->cursorItem++;
if (menu->cursorItem >= menu->itemCount && !wrapped)
{
wrapped = qtrue;
menu->cursorItem = 0;
}
if (Item_SetFocus(menu->items[menu->cursorItem], DC->cursorx, DC->cursory))
{
Menu_HandleMouseMove(menu, menu->items[menu->cursorItem]->window.rect.x + 1,
menu->items[menu->cursorItem]->window.rect.y + 1);
return menu->items[menu->cursorItem];
}
}
menu->cursorItem = oldCursor;
return NULL;
}
static void Window_CloseCinematic(Window *window)
{
if (window->style == WINDOW_STYLE_CINEMATIC && window->cinematic >= 0)
{
DC->stopCinematic(window->cinematic);
window->cinematic = -1;
}
}
static void Menu_CloseCinematics(menuDef_t *menu)
{
if (menu)
{
int i;
Window_CloseCinematic(&menu->window);
for (i = 0; i < menu->itemCount; i++)
{
Window_CloseCinematic(&menu->items[i]->window);
if (menu->items[i]->type == ITEM_TYPE_OWNERDRAW)
DC->stopCinematic(0 - menu->items[i]->window.ownerDraw);
}
}
}
static void Display_CloseCinematics(void)
{
int i;
for (i = 0; i < menuCount; i++)
Menu_CloseCinematics(&Menus[i]);
}
void Menus_Activate(menuDef_t *menu)
{
int i;
qboolean onTopOfMenuStack = qfalse;
if (openMenuCount > 0 && menuStack[openMenuCount - 1] == menu)
onTopOfMenuStack = qtrue;
menu->window.flags |= (WINDOW_HASFOCUS | WINDOW_VISIBLE);
// If being opened for the first time
if (!onTopOfMenuStack)
{
if (menu->onOpen)
{
itemDef_t item;
item.parent = menu;
Item_RunScript(&item, menu->onOpen);
}
if (menu->soundName && *menu->soundName)
DC->startBackgroundTrack(menu->soundName, menu->soundName);
Display_CloseCinematics();
Menu_HandleMouseMove(menu, DC->cursorx, DC->cursory); // force the item under the cursor to focus
for (i = 0; i < menu->itemCount; i++) // reset selection in listboxes when opened
{
if (Item_IsListBox(menu->items[i]))
{
menu->items[i]->cursorPos = DC->feederInitialise(menu->items[i]->feederID);
Item_ListBox_SetStartPos(menu->items[i], 0);
DC->feederSelection(menu->items[i]->feederID, menu->items[i]->cursorPos);
}
else if (menu->items[i]->type == ITEM_TYPE_CYCLE)
{
menu->items[i]->typeData.cycle->cursorPos = DC->feederInitialise(menu->items[i]->feederID);
}
}
if (openMenuCount < MAX_OPEN_MENUS)
menuStack[openMenuCount++] = menu;
}
}
qboolean Menus_ReplaceActive(menuDef_t *menu)
{
int i;
menuDef_t *active;
if (openMenuCount < 1)
return qfalse;
active = menuStack[openMenuCount - 1];
if (!(active->window.flags & WINDOW_HASFOCUS) || !(active->window.flags & WINDOW_VISIBLE))
{
return qfalse;
}
if (menu == active)
return qfalse;
if (menu->itemCount != active->itemCount)
{
Com_Printf(S_COLOR_YELLOW "WARNING: Menus_ReplaceActive: expecting %i menu items, found %i\n", menu->itemCount,
active->itemCount);
return qfalse;
}
for (i = 0; i < menu->itemCount; i++)
{
if (menu->items[i]->type != active->items[i]->type)
{
Com_Printf(S_COLOR_YELLOW "WARNING: Menus_ReplaceActive: type mismatch on item %i\n", i + 1);
return qfalse;
}
}
active->window.flags &= ~(WINDOW_FADINGOUT | WINDOW_VISIBLE);
menu->window.flags |= (WINDOW_HASFOCUS | WINDOW_VISIBLE);
menuStack[openMenuCount - 1] = menu;
if (menu->onOpen)
{
itemDef_t item;
item.parent = menu;
Item_RunScript(&item, menu->onOpen);
}
// set the cursor position on the new menu to match the active one
for (i = 0; i < menu->itemCount; i++)
{
menu->items[i]->cursorPos = active->items[i]->cursorPos;
menu->items[i]->feederID = active->items[i]->feederID;
switch (Item_DataType(menu->items[i]))
{
case TYPE_LIST:
menu->items[i]->typeData.list->startPos = active->items[i]->typeData.list->startPos;
menu->items[i]->typeData.list->cursorPos = active->items[i]->typeData.list->cursorPos;
break;
case TYPE_COMBO:
menu->items[i]->typeData.cycle->cursorPos = active->items[i]->typeData.cycle->cursorPos;
break;
default:
break;
}
}
return qtrue;
}
int Display_VisibleMenuCount(void)
{
int i, count;
count = 0;
for (i = 0; i < menuCount; i++)
{
if (Menus[i].window.flags & (WINDOW_FORCED | WINDOW_VISIBLE))
count++;
}
return count;
}
void Menus_HandleOOBClick(menuDef_t *menu, int key, qboolean down)
{
if (menu)
{
int i;
// basically the behaviour we are looking for is if there are windows in the stack.. see if
// the cursor is within any of them.. if not close them otherwise activate them and pass the
// key on.. force a mouse move to activate focus and script stuff
if (down && menu->window.flags & WINDOW_OOB_CLICK)
Menus_Close(menu);
for (i = 0; i < menuCount; i++)
{
if (Menu_OverActiveItem(&Menus[i], DC->cursorx, DC->cursory))
{
Menus_Close(menu);
Menus_Activate(&Menus[i]);
Menu_HandleMouseMove(&Menus[i], DC->cursorx, DC->cursory);
Menu_HandleKey(&Menus[i], key, down);
}
}
if (Display_VisibleMenuCount() == 0)
{
if (DC->Pause)
DC->Pause(qfalse);
}
Display_CloseCinematics();
}
}
static rectDef_t *Item_CorrectedTextRect(itemDef_t *item)
{
static rectDef_t rect;
memset(&rect, 0, sizeof(rectDef_t));
if (item)
{
rect = item->textRect;
if (rect.w)
rect.y -= rect.h;
}
return ▭
}
void Menu_HandleKey(menuDef_t *menu, int key, qboolean down)
{
int i;
itemDef_t *item = NULL;
if (g_waitingForKey && down)
{
Item_Bind_HandleKey(g_bindItem, key, down);
return;
}
if (g_editingField && down)
{
if (!Item_TextField_HandleKey(g_editItem, key))
{
g_editingField = qfalse;
Item_RunScript(g_editItem, g_editItem->onTextEntry);
g_editItem = NULL;
return;
}
else
{
Item_RunScript(g_editItem, g_editItem->onCharEntry);
}
}
if (menu == NULL)
return;
// see if the mouse is within the window bounds and if so is this a mouse click
if (down && !(menu->window.flags & WINDOW_POPUP) &&
!Rect_ContainsPoint(&menu->window.rect, DC->cursorx, DC->cursory))
{
static qboolean inHandleKey = qfalse;
if (!inHandleKey && (key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3))
{
inHandleKey = qtrue;
Menus_HandleOOBClick(menu, key, down);
inHandleKey = qfalse;
return;
}
}
if (g_comboBoxItem == NULL)
{
// get the item with focus
for (i = 0; i < menu->itemCount; i++)
{
if (menu->items[i]->window.flags & WINDOW_HASFOCUS)
item = menu->items[i];
}
}
else
item = g_comboBoxItem;
if (item != NULL)
{
if (Item_HandleKey(item, key, down))
{
Item_Action(item);
return;
}
}
if (!down)
return;
// default handling
switch (key)
{
case K_F12:
if (DC->getCVarValue("developer"))
DC->executeText(EXEC_APPEND, "screenshot\n");
break;
case K_KP_UPARROW:
case K_UPARROW:
Menu_SetPrevCursorItem(menu);
break;
case K_ESCAPE:
if (!g_waitingForKey && menu->onESC)
{
itemDef_t it;
it.parent = menu;
Item_RunScript(&it, menu->onESC);
}
break;
case K_TAB:
case K_KP_DOWNARROW:
case K_DOWNARROW:
Menu_SetNextCursorItem(menu);
break;
case K_MOUSE1:
case K_MOUSE2:
if (item)
{
if (item->type == ITEM_TYPE_TEXT)
{
if (Rect_ContainsPoint(Item_CorrectedTextRect(item), DC->cursorx, DC->cursory))
Item_Action(item);
}
else if (Item_IsEditField(item))
{
if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory))
{
char buffer[MAX_STRING_CHARS] = {0};
if (item->cvar)
DC->getCVarString(item->cvar, buffer, sizeof(buffer));
item->cursorPos = strlen(buffer);
Item_TextField_CalcPaintOffset(item, buffer);
g_editingField = qtrue;
g_editItem = item;
}
}
else
{
if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory))
Item_Action(item);
}
}
break;
case K_JOY1:
case K_JOY2:
case K_JOY3:
case K_JOY4:
case K_AUX1:
case K_AUX2:
case K_AUX3:
case K_AUX4:
case K_AUX5:
case K_AUX6:
case K_AUX7:
case K_AUX8:
case K_AUX9:
case K_AUX10:
case K_AUX11:
case K_AUX12:
case K_AUX13:
case K_AUX14:
case K_AUX15:
case K_AUX16:
break;
case K_KP_ENTER:
case K_ENTER:
if (item)
{
if (Item_IsEditField(item))
{
char buffer[MAX_STRING_CHARS] = {0};
if (item->cvar)
DC->getCVarString(item->cvar, buffer, sizeof(buffer));
item->cursorPos = strlen(buffer);
Item_TextField_CalcPaintOffset(item, buffer);
g_editingField = qtrue;
g_editItem = item;
}
else
Item_Action(item);
}
break;
}
}
void ToWindowCoords(float *x, float *y, Window *window)
{
if (window->border != 0)
{
*x += window->borderSize;
*y += window->borderSize;
}
*x += window->rect.x;
*y += window->rect.y;
}
void Rect_ToWindowCoords(rectDef_t *rect, Window *window) { ToWindowCoords(&rect->x, &rect->y, window); }
void Item_SetTextExtents(itemDef_t *item, const char *text)
{
const char *textPtr = (text) ? text : item->text;
qboolean cvarContent;
// It's hard to make a policy on what should be aligned statically and what
// should be aligned dynamically; there are reasonable cases for both. If
// it continues to be a problem then there should probably be an item keyword
// for it; but for the moment only adjusting the alignment of ITEM_TYPE_TEXT
// seems to suffice.
cvarContent = (item->cvar && item->textalignment != ALIGN_LEFT && item->type == ITEM_TYPE_TEXT);
if (textPtr == NULL)
return;
// as long as the item isn't dynamic content (ownerdraw or cvar), this
// keeps us from computing the widths and heights more than once
if (item->textRect.w == 0.0f || cvarContent ||
(item->type == ITEM_TYPE_OWNERDRAW && item->textalignment != ALIGN_LEFT))
{
float originalWidth = 0.0f;
if (item->textalignment == ALIGN_CENTER || item->textalignment == ALIGN_RIGHT)
{
if (cvarContent)
{
char buff[MAX_CVAR_VALUE_STRING];
DC->getCVarString(item->cvar, buff, sizeof(buff));
originalWidth = UI_Text_Width(item->text, item->textscale) + UI_Text_Width(buff, item->textscale);
}
else
originalWidth = UI_Text_Width(item->text, item->textscale);
}
item->textRect.w = UI_Text_Width(textPtr, item->textscale);
item->textRect.h = UI_Text_Height(textPtr, item->textscale);
if (item->textvalignment == VALIGN_BOTTOM)
item->textRect.y = item->textaligny + item->window.rect.h;
else if (item->textvalignment == VALIGN_CENTER)
item->textRect.y = item->textaligny + ((item->textRect.h + item->window.rect.h) / 2.0f);
else if (item->textvalignment == VALIGN_TOP)
item->textRect.y = item->textaligny + item->textRect.h;
if (item->textalignment == ALIGN_LEFT)
item->textRect.x = item->textalignx;
else if (item->textalignment == ALIGN_CENTER)
item->textRect.x = item->textalignx + ((item->window.rect.w - originalWidth) / 2.0f);
else if (item->textalignment == ALIGN_RIGHT)
item->textRect.x = item->textalignx + item->window.rect.w - originalWidth;
ToWindowCoords(&item->textRect.x, &item->textRect.y, &item->window);
}
}
void Item_TextColor(itemDef_t *item, vec4_t *newColor)
{
vec4_t lowLight;
menuDef_t *parent = (menuDef_t *)item->parent;
Fade(&item->window.flags, &item->window.foreColor[3], parent->fadeClamp, &item->window.nextTime, parent->fadeCycle,
qtrue, parent->fadeAmount);
if (item->window.flags & WINDOW_HASFOCUS)
memcpy(newColor, &parent->focusColor, sizeof(vec4_t));
else if (item->textStyle == ITEM_TEXTSTYLE_BLINK && !((DC->realTime / BLINK_DIVISOR) & 1))
{
lowLight[0] = 0.8 * item->window.foreColor[0];
lowLight[1] = 0.8 * item->window.foreColor[1];
lowLight[2] = 0.8 * item->window.foreColor[2];
lowLight[3] = 0.8 * item->window.foreColor[3];
LerpColor(item->window.foreColor, lowLight, *newColor, 0.5 + 0.5 * sin(DC->realTime / PULSE_DIVISOR));
}
else
{
memcpy(newColor, &item->window.foreColor, sizeof(vec4_t));
// items can be enabled and disabled based on cvars
}
if (item->enableCvar != NULL && *item->enableCvar && item->cvarTest != NULL && *item->cvarTest)
{
if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE))
memcpy(newColor, &parent->disableColor, sizeof(vec4_t));
}
}
static void SkipColorCodes(const char **text, char *lastColor)
{
while (Q_IsColorString(*text))
{
lastColor[0] = (*text)[0];
lastColor[1] = (*text)[1];
(*text) += 2;
}
}
static void SkipWhiteSpace(const char **text, char *lastColor)
{
while (**text)
{
SkipColorCodes(text, lastColor);
if (**text != '\n' && isspace(**text))
(*text)++;
else
break;
}
}
const char *Item_Text_Wrap(const char *text, float scale, float width)
{
static char out[8192] = "";
char *paint = out;
char c[3] = "";
const char *p;
const char *eos;
float indentWidth = 0.0f;
if (!text)
return NULL;
p = text;
eos = p + strlen(p);
if ((eos - p) >= sizeof(out))
return NULL;
*paint = '\0';
while (*p)
{
float textWidth = 0.0f;
const char *eol = p;
const char *q = p;
float testWidth = width - indentWidth;
SkipColorCodes(&q, c);
while (q && textWidth < testWidth)
{
qboolean previousCharIsSpace = qfalse;
// Remaining string is too short to wrap
if (q >= eos)
{
eol = eos;
break;
}
if (q > p && *q == INDENT_MARKER)
{
indentWidth = textWidth;
eol = p;
}
// Some color escapes might still be present
SkipColorCodes(&q, c);
// Manual line break
if (*q == '\n')
{
eol = q + 1;
break;
}
if (!previousCharIsSpace && isspace(*q))
eol = q;
textWidth += UI_Char_Width(&q, scale);
}
// No split has taken place, so just split mid-word
if (eol == p)
eol = q;
// Note that usage of strcat and strlen is deliberately being
// avoided here as it becomes surprisingly expensive on larger
// blocks of text
// Copy text
strncpy(paint, p, eol - p);
paint += (eol - p);
*paint = '\0';
p = eol;
if (paint - out > 0 && *(paint - 1) == '\n')
{
// The line is deliberately broken, clear the color and
// any current indent
c[0] = '\0';
indentWidth = 0.0f;
}
else
{
// Add a \n if it's not there already
*paint++ = '\n';
*paint = '\0';
// Insert a pixel indent on the next line
if (indentWidth > 0.0f)
{
const char *indentMarkerText = va("%f%c", indentWidth, INDENT_MARKER);
int indentMarkerTextLength = strlen(indentMarkerText);
strncpy(paint, indentMarkerText, indentMarkerTextLength);
paint += indentMarkerTextLength;
*paint = '\0';
}
// Skip leading whitespace on next line and save the
// last color code
SkipWhiteSpace(&p, c);
}
if (c[0])
{
*paint++ = c[0];
*paint++ = c[1];
*paint = '\0';
}
}
return out;
}
#define MAX_WRAP_CACHE 16
#define MAX_WRAP_LINES 32
#define MAX_WRAP_TEXT 512
typedef struct {
char text[MAX_WRAP_TEXT * MAX_WRAP_LINES];
rectDef_t rect;
float scale;
char lines[MAX_WRAP_LINES][MAX_WRAP_TEXT];
float lineCoords[MAX_WRAP_LINES][2];
int numLines;
} wrapCache_t;
static wrapCache_t wrapCache[MAX_WRAP_CACHE];
static qboolean cacheCreationFailed = qfalse;
static int cacheWriteIndex = 0;
static int cacheReadIndex = 0;
static int cacheReadLineNum = 0;
static void UI_CreateCacheEntry(const char *text, const rectDef_t *rect, float scale)
{
wrapCache_t *cacheEntry = &wrapCache[cacheWriteIndex];
if (strlen(text) >= sizeof(cacheEntry->text))
{
cacheCreationFailed = qtrue;
return;
}
strcpy(cacheEntry->text, text);
cacheEntry->rect = *rect;
cacheEntry->scale = scale;
cacheEntry->numLines = 0;
}
static void UI_AddCacheEntryLine(const char *text, float x, float y)
{
wrapCache_t *cacheEntry = &wrapCache[cacheWriteIndex];
if (cacheCreationFailed)
return;
if (cacheEntry->numLines >= MAX_WRAP_LINES || strlen(text) >= sizeof(cacheEntry->lines[0]))
{
cacheCreationFailed = qtrue;
return;
}
strcpy(cacheEntry->lines[cacheEntry->numLines], text);
cacheEntry->lineCoords[cacheEntry->numLines][0] = x;
cacheEntry->lineCoords[cacheEntry->numLines][1] = y;
cacheEntry->numLines++;
}
static void UI_FinishCacheEntry(void)
{
if (cacheCreationFailed)
{
wrapCache[cacheWriteIndex].text[0] = '\0';
wrapCache[cacheWriteIndex].numLines = 0;
cacheCreationFailed = qfalse;
}
else
cacheWriteIndex = (cacheWriteIndex + 1) % MAX_WRAP_CACHE;
}
static qboolean UI_CheckWrapCache(const char *text, const rectDef_t *rect, float scale)
{
int i;
for (i = 0; i < MAX_WRAP_CACHE; i++)
{
wrapCache_t *cacheEntry = &wrapCache[i];
if (rect->x != cacheEntry->rect.x || rect->y != cacheEntry->rect.y || rect->w != cacheEntry->rect.w ||
rect->h != cacheEntry->rect.h)
continue;
if (strcmp(text, cacheEntry->text))
continue;
if (cacheEntry->scale != scale)
continue;
cacheReadIndex = i;
cacheReadLineNum = 0;
return qtrue;
}
return qfalse;
}
static qboolean UI_NextWrapLine(const char **text, float *x, float *y)
{
wrapCache_t *cacheEntry = &wrapCache[cacheReadIndex];
if (cacheReadLineNum >= cacheEntry->numLines)
return qfalse;
*text = cacheEntry->lines[cacheReadLineNum];
*x = cacheEntry->lineCoords[cacheReadLineNum][0];
*y = cacheEntry->lineCoords[cacheReadLineNum][1];
cacheReadLineNum++;
return qtrue;
}
void Item_Text_Wrapped_Paint(itemDef_t *item)
{
char text[1024];
const char *p, *textPtr;
float x, y, w, h;
vec4_t color;
qboolean useWrapCache = (qboolean)DC->getCVarValue("ui_textWrapCache");
if (item->text == NULL)
{
if (item->cvar == NULL)
return;
else
{
DC->getCVarString(item->cvar, text, sizeof(text));
textPtr = text;
}
}
else
textPtr = item->text;
if (*textPtr == '\0')
return;
Item_TextColor(item, &color);
// Check if this block is cached
if (useWrapCache && UI_CheckWrapCache(textPtr, &item->window.rect, item->textscale))
{
while (UI_NextWrapLine(&p, &x, &y))
{
UI_Text_Paint(x, y, item->textscale, color, p, 0, 0, item->textStyle);
}
}
else
{
char buff[1024];
float fontHeight = UI_Text_EmHeight(item->textscale);
const float lineSpacing = fontHeight * 0.4f;
float lineHeight = fontHeight + lineSpacing;
float textHeight;
int textLength;
int firstLine, paintLines, totalLines, lineNum;
float paintY;
int i;
if (useWrapCache)
UI_CreateCacheEntry(textPtr, &item->window.rect, item->textscale);
x = item->window.rect.x + item->textalignx;
y = item->window.rect.y + item->textaligny;
w = item->window.rect.w - (2.0f * item->textalignx);
h = item->window.rect.h - (2.0f * item->textaligny);
textPtr = Item_Text_Wrap(textPtr, item->textscale, w);
textLength = strlen(textPtr);
// Count lines
totalLines = 0;
for (i = 0; i < textLength; i++)
{
if (textPtr[i] == '\n')
totalLines++;
}
if (textLength && textPtr[textLength - 1] != '\n')
{
totalLines++; // count the last, non-newline-terminated line
textLength++; // a '\0' will mark the end of the last line
}
paintLines = (int)floor((h + lineSpacing) / lineHeight);
if (totalLines > paintLines)
firstLine = totalLines - paintLines;
else
{
firstLine = 0;
paintLines = totalLines;
}
textHeight = (paintLines * lineHeight) - lineSpacing;
switch (item->textvalignment)
{
default:
case VALIGN_BOTTOM:
paintY = y + (h - textHeight);
break;
case VALIGN_CENTER:
paintY = y + ((h - textHeight) / 2.0f);
break;
case VALIGN_TOP:
paintY = y;
break;
}
p = textPtr;
// skip the first few lines
for (lineNum = 0; lineNum < firstLine; lineNum++)
p = strchr(p, '\n') + 1;
for (i = p - textPtr; i < textLength && lineNum < firstLine + paintLines; i++)
{
unsigned long lineLength = &textPtr[i] - p;
if (lineLength >= sizeof(buff))
break;
if (textPtr[i] == '\n' || textPtr[i] == '\0')
{
itemDef_t lineItem;
memset(&lineItem, 0, sizeof(itemDef_t));
strncpy(buff, p, lineLength);
buff[lineLength] = '\0';
p = &textPtr[i + 1];
lineItem.type = ITEM_TYPE_TEXT;
lineItem.textscale = item->textscale;
lineItem.textStyle = item->textStyle;
lineItem.text = buff;
lineItem.textalignment = item->textalignment;
lineItem.textvalignment = VALIGN_TOP;
lineItem.textalignx = 0.0f;
lineItem.textaligny = 0.0f;
lineItem.textRect.w = 0.0f;
lineItem.textRect.h = 0.0f;
lineItem.window.rect.x = x;
lineItem.window.rect.y = paintY + ((lineNum - firstLine) * lineHeight);
lineItem.window.rect.w = w;
lineItem.window.rect.h = lineHeight;
lineItem.window.border = item->window.border;
lineItem.window.borderSize = item->window.borderSize;
if (DC->getCVarValue("ui_developer"))
{
vec4_t color;
color[0] = color[2] = color[3] = 1.0f;
color[1] = 0.0f;
DC->drawRect(lineItem.window.rect.x, lineItem.window.rect.y, lineItem.window.rect.w,
lineItem.window.rect.h, 1, color);
}
Item_SetTextExtents(&lineItem, buff);
UI_Text_Paint(lineItem.textRect.x, lineItem.textRect.y, lineItem.textscale, color, buff, 0, 0,
lineItem.textStyle);
if (useWrapCache)
UI_AddCacheEntryLine(buff, lineItem.textRect.x, lineItem.textRect.y);
lineNum++;
}
}
if (useWrapCache)
UI_FinishCacheEntry();
}
}
/*
==============
UI_DrawTextBlock
==============
*/
void UI_DrawTextBlock(rectDef_t *rect, float text_x, float text_y, vec4_t color, float scale, int textalign,
int textvalign, int textStyle, const char *text)
{
static menuDef_t dummyParent;
static itemDef_t textItem;
textItem.text = text;
textItem.parent = &dummyParent;
memcpy(textItem.window.foreColor, color, sizeof(vec4_t));
textItem.window.flags = 0;
textItem.window.rect.x = rect->x;
textItem.window.rect.y = rect->y;
textItem.window.rect.w = rect->w;
textItem.window.rect.h = rect->h;
textItem.window.border = 0;
textItem.window.borderSize = 0.0f;
textItem.textRect.x = 0.0f;
textItem.textRect.y = 0.0f;
textItem.textRect.w = 0.0f;
textItem.textRect.h = 0.0f;
textItem.textalignment = textalign;
textItem.textvalignment = textvalign;
textItem.textalignx = text_x;
textItem.textaligny = text_y;
textItem.textscale = scale;
textItem.textStyle = textStyle;
// Utilise existing wrap code
Item_Text_Wrapped_Paint(&textItem);
}
void Item_Text_Paint(itemDef_t *item)
{
char text[1024];
const char *textPtr;
vec4_t color;
if (item->window.flags & WINDOW_WRAPPED)
{
Item_Text_Wrapped_Paint(item);
return;
}
if (item->text == NULL)
{
if (item->cvar == NULL)
return;
else
{
DC->getCVarString(item->cvar, text, sizeof(text));
textPtr = text;
}
}
else
textPtr = item->text;
// this needs to go here as it sets extents for cvar types as well
Item_SetTextExtents(item, textPtr);
if (*textPtr == '\0')
return;
Item_TextColor(item, &color);
UI_Text_Paint(item->textRect.x, item->textRect.y, item->textscale, color, textPtr, 0, 0, item->textStyle);
}
void Item_TextField_Paint(itemDef_t *item)
{
char buff[1024];
vec4_t newColor;
menuDef_t *parent;
int offset = (item->text && *item->text) ? ITEM_VALUE_OFFSET : 0;
editFieldDef_t *editPtr = item->typeData.edit;
char cursor = DC->getOverstrikeMode() ? '|' : '_';
qboolean editing = (item->window.flags & WINDOW_HASFOCUS && g_editingField);
const int cursorWidth = editing ? EDIT_CURSOR_WIDTH : 0;
// FIXME: causes duplicate printing if item->text is not set (NULL)
Item_Text_Paint(item);
buff[0] = '\0';
if (item->cvar)
DC->getCVarString(item->cvar, buff, sizeof(buff));
// maxFieldWidth hasn't been set, so use the item's rect
if (editPtr->maxFieldWidth == 0)
{
editPtr->maxFieldWidth =
item->window.rect.w - (item->textRect.w + offset + (item->textRect.x - item->window.rect.x));
if (editPtr->maxFieldWidth < MIN_FIELD_WIDTH)
editPtr->maxFieldWidth = MIN_FIELD_WIDTH;
}
if (!editing)
editPtr->paintOffset = 0;
// Shorten string to max viewable
while (UI_Text_Width(buff + editPtr->paintOffset, item->textscale) > (editPtr->maxFieldWidth - cursorWidth) &&
strlen(buff) > 0)
buff[strlen(buff) - 1] = '\0';
parent = (menuDef_t *)item->parent;
if (item->window.flags & WINDOW_HASFOCUS)
memcpy(newColor, &parent->focusColor, sizeof(vec4_t));
else
memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t));
if (editing)
{
UI_Text_PaintWithCursor(item->textRect.x + item->textRect.w + offset, item->textRect.y, item->textscale,
newColor, buff + editPtr->paintOffset, item->cursorPos - editPtr->paintOffset, cursor,
editPtr->maxPaintChars, item->textStyle);
}
else
{
UI_Text_Paint(item->textRect.x + item->textRect.w + offset, item->textRect.y, item->textscale, newColor,
buff + editPtr->paintOffset, 0, editPtr->maxPaintChars, item->textStyle);
}
}
void Item_YesNo_Paint(itemDef_t *item)
{
vec4_t newColor;
float value;
int offset;
menuDef_t *parent = (menuDef_t *)item->parent;
value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0;
if (item->window.flags & WINDOW_HASFOCUS)
memcpy(newColor, &parent->focusColor, sizeof(vec4_t));
else
memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t));
offset = (item->text && *item->text) ? ITEM_VALUE_OFFSET : 0;
if (item->text)
{
Item_Text_Paint(item);
UI_Text_Paint(item->textRect.x + item->textRect.w + offset, item->textRect.y, item->textscale, newColor,
(value != 0) ? "Yes" : "No", 0, 0, item->textStyle);
}
else
UI_Text_Paint(item->textRect.x, item->textRect.y, item->textscale, newColor, (value != 0) ? "Yes" : "No", 0, 0,
item->textStyle);
}
void Item_Multi_Paint(itemDef_t *item)
{
vec4_t newColor;
const char *text = "";
menuDef_t *parent = (menuDef_t *)item->parent;
if (item->window.flags & WINDOW_HASFOCUS)
memcpy(newColor, &parent->focusColor, sizeof(vec4_t));
else
memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t));
text = Item_Multi_Setting(item);
if (item->text)
{
Item_Text_Paint(item);
UI_Text_Paint(item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, item->textRect.y, item->textscale,
newColor, text, 0, 0, item->textStyle);
}
else
UI_Text_Paint(item->textRect.x, item->textRect.y, item->textscale, newColor, text, 0, 0, item->textStyle);
}
void Item_Cycle_Paint(itemDef_t *item)
{
vec4_t newColor;
const char *text = "";
menuDef_t *parent = (menuDef_t *)item->parent;
if (item->window.flags & WINDOW_HASFOCUS)
memcpy(newColor, &parent->focusColor, sizeof(vec4_t));
else
memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t));
if (item->typeData.cycle)
text = DC->feederItemText(item->feederID, item->typeData.cycle->cursorPos, 0, NULL);
if (item->text)
{
Item_Text_Paint(item);
UI_Text_Paint(item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, item->textRect.y, item->textscale,
newColor, text, 0, 0, item->textStyle);
}
else
UI_Text_Paint(item->textRect.x, item->textRect.y, item->textscale, newColor, text, 0, 0, item->textStyle);
}
typedef struct {
char *command;
int id;
int defaultbind1;
int defaultbind2;
int bind1;
int bind2;
} bind_t;
static bind_t g_bindings[] = {{"+scores", K_TAB, -1, -1, -1, -1}, {"+button2", K_ENTER, -1, -1, -1, -1},
{"+speed", K_SHIFT, -1, -1, -1, -1}, {"+button6", 'z', -1, -1, -1, -1}, // human dodging
{"+button8", 'x', -1, -1, -1, -1}, {"+forward", K_UPARROW, -1, -1, -1, -1}, {"+back", K_DOWNARROW, -1, -1, -1, -1},
{"+moveleft", ',', -1, -1, -1, -1}, {"+moveright", '.', -1, -1, -1, -1}, {"+moveup", K_SPACE, -1, -1, -1, -1},
{"+movedown", 'c', -1, -1, -1, -1}, {"+left", K_LEFTARROW, -1, -1, -1, -1},
{"+right", K_RIGHTARROW, -1, -1, -1, -1}, {"+strafe", K_ALT, -1, -1, -1, -1}, {"+lookup", K_PGDN, -1, -1, -1, -1},
{"+lookdown", K_DEL, -1, -1, -1, -1}, {"+mlook", '/', -1, -1, -1, -1}, {"centerview", K_END, -1, -1, -1, -1},
{"+zoom", -1, -1, -1, -1, -1}, {"weapon 1", '1', -1, -1, -1, -1}, {"weapon 2", '2', -1, -1, -1, -1},
{"weapon 3", '3', -1, -1, -1, -1}, {"weapon 4", '4', -1, -1, -1, -1}, {"weapon 5", '5', -1, -1, -1, -1},
{"weapon 6", '6', -1, -1, -1, -1}, {"weapon 7", '7', -1, -1, -1, -1}, {"weapon 8", '8', -1, -1, -1, -1},
{"weapon 9", '9', -1, -1, -1, -1}, {"weapon 10", '0', -1, -1, -1, -1}, {"weapon 11", -1, -1, -1, -1, -1},
{"weapon 12", -1, -1, -1, -1, -1}, {"weapon 13", -1, -1, -1, -1, -1}, {"+attack", K_MOUSE1, -1, -1, -1, -1},
{"+button5", K_MOUSE2, -1, -1, -1, -1}, // secondary attack
{"reload", 'r', -1, -1, -1, -1}, // reload
{"buy ammo", 'b', -1, -1, -1, -1}, // buy ammo
{"itemact medkit", 'm', -1, -1, -1, -1}, // use medkit
{"+button7", 'q', -1, -1, -1, -1}, // buildable use
{"deconstruct", 'e', -1, -1, -1, -1}, // buildable destroy
{"weapprev", '[', -1, -1, -1, -1}, {"weapnext", ']', -1, -1, -1, -1}, {"+button3", K_MOUSE3, -1, -1, -1, -1},
{"+button4", K_MOUSE4, -1, -1, -1, -1}, {"vote yes", K_F1, -1, -1, -1, -1}, {"vote no", K_F2, -1, -1, -1, -1},
{"teamvote yes", K_F3, -1, -1, -1, -1}, {"teamvote no", K_F4, -1, -1, -1, -1},
{"scoresUp", K_KP_PGUP, -1, -1, -1, -1}, {"scoresDown", K_KP_PGDN, -1, -1, -1, -1},
{"screenshotJPEG", -1, -1, -1, -1, -1}, {"messagemode", -1, -1, -1, -1, -1}, {"messagemode2", -1, -1, -1, -1, -1}};
static const size_t g_bindCount = ARRAY_LEN(g_bindings);
/*
=================
Controls_GetKeyAssignment
=================
*/
static void Controls_GetKeyAssignment(char *command, int *twokeys)
{
int count;
int j;
char b[256];
twokeys[0] = twokeys[1] = -1;
count = 0;
for (j = 0; j < 256; j++)
{
DC->getBindingBuf(j, b, 256);
if (*b == 0)
continue;
if (!Q_stricmp(b, command))
{
twokeys[count] = j;
count++;
if (count == 2)
break;
}
}
}
/*
=================
Controls_GetConfig
Iterate each command, get its numeric binding
=================
*/
void Controls_GetConfig(void)
{
size_t i;
int twokeys[2];
for (i = 0; i < g_bindCount; i++)
{
Controls_GetKeyAssignment(g_bindings[i].command, twokeys);
g_bindings[i].bind1 = twokeys[0];
g_bindings[i].bind2 = twokeys[1];
}
}
/*
=================
Controls_SetConfig
Iterate each command, get its numeric binding
=================
*/
void Controls_SetConfig(qboolean restart)
{
unsigned int i;
(void)restart;
for (i = 0; i < g_bindCount; i++)
{
if (g_bindings[i].bind1 != -1)
{
DC->setBinding(g_bindings[i].bind1, g_bindings[i].command);
if (g_bindings[i].bind2 != -1)
DC->setBinding(g_bindings[i].bind2, g_bindings[i].command);
}
}
DC->executeText(EXEC_APPEND, "in_restart\n");
}
/*
=================
Controls_SetDefaults
Iterate each command, set its default binding
=================
*/
void Controls_SetDefaults(void)
{
unsigned int i;
for (i = 0; i < g_bindCount; i++)
{
g_bindings[i].bind1 = g_bindings[i].defaultbind1;
g_bindings[i].bind2 = g_bindings[i].defaultbind2;
}
}
int BindingIDFromName(const char *name)
{
size_t i;
for (i = 0; i < g_bindCount; i++)
{
if (Q_stricmp(name, g_bindings[i].command) == 0)
return i;
}
return -1;
}
char g_nameBind1[32];
char g_nameBind2[32];
void BindingFromName(const char *cvar)
{
int i, b1, b2;
// iterate each command, set its default binding
for (i = 0; i < g_bindCount; i++)
{
if (Q_stricmp(cvar, g_bindings[i].command) == 0)
{
b1 = g_bindings[i].bind1;
if (b1 == -1)
break;
DC->keynumToStringBuf(b1, g_nameBind1, 32);
Q_strupr(g_nameBind1);
b2 = g_bindings[i].bind2;
if (b2 != -1)
{
DC->keynumToStringBuf(b2, g_nameBind2, 32);
Q_strupr(g_nameBind2);
strcat(g_nameBind1, " or ");
strcat(g_nameBind1, g_nameBind2);
}
return;
}
}
strcpy(g_nameBind1, "???");
}
void Item_Slider_Paint(itemDef_t *item)
{
vec4_t newColor;
float x, y;
menuDef_t *parent = (menuDef_t *)item->parent;
float vScale = Item_Slider_VScale(item);
if (item->window.flags & WINDOW_HASFOCUS)
memcpy(newColor, &parent->focusColor, sizeof(vec4_t));
else
memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t));
if (item->text)
{
Item_Text_Paint(item);
x = item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET;
y = item->textRect.y - item->textRect.h + ((item->textRect.h - (SLIDER_HEIGHT * vScale)) / 2.0f);
}
else
{
x = item->window.rect.x;
y = item->window.rect.y;
}
DC->setColor(newColor);
DC->drawHandlePic(x, y, SLIDER_WIDTH, SLIDER_HEIGHT * vScale, DC->Assets.sliderBar);
y = item->textRect.y - item->textRect.h + ((item->textRect.h - (SLIDER_THUMB_HEIGHT * vScale)) / 2.0f);
x = Item_Slider_ThumbPosition(item);
DC->drawHandlePic(
x - (SLIDER_THUMB_WIDTH / 2), y, SLIDER_THUMB_WIDTH, SLIDER_THUMB_HEIGHT * vScale, DC->Assets.sliderThumb);
}
void Item_Bind_Paint(itemDef_t *item)
{
vec4_t newColor, lowLight;
float value;
int maxChars = 0;
menuDef_t *parent = (menuDef_t *)item->parent;
if (item->typeData.edit)
maxChars = item->typeData.edit->maxPaintChars;
value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0;
if (item->window.flags & WINDOW_HASFOCUS)
{
if (g_bindItem == item)
{
lowLight[0] = 0.8f * parent->focusColor[0];
lowLight[1] = 0.8f * parent->focusColor[1];
lowLight[2] = 0.8f * parent->focusColor[2];
lowLight[3] = 0.8f * parent->focusColor[3];
LerpColor(parent->focusColor, lowLight, newColor, 0.5 + 0.5 * sin(DC->realTime / PULSE_DIVISOR));
}
else
memcpy(&newColor, &parent->focusColor, sizeof(vec4_t));
}
else
memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t));
if (item->text)
{
Item_Text_Paint(item);
if (g_bindItem == item && g_waitingForKey)
{
UI_Text_Paint(item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, item->textRect.y, item->textscale,
newColor, "Press key", 0, maxChars, item->textStyle);
}
else
{
BindingFromName(item->cvar);
UI_Text_Paint(item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, item->textRect.y, item->textscale,
newColor, g_nameBind1, 0, maxChars, item->textStyle);
}
}
else
UI_Text_Paint(item->textRect.x, item->textRect.y, item->textscale, newColor, (value != 0) ? "FIXME" : "FIXME",
0, maxChars, item->textStyle);
}
qboolean Display_KeyBindPending(void) { return g_waitingForKey; }
qboolean Item_Bind_HandleKey(itemDef_t *item, int key, qboolean down)
{
int id;
if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && !g_waitingForKey)
{
if (down && (key == K_MOUSE1 || key == K_ENTER))
{
g_waitingForKey = qtrue;
g_bindItem = item;
}
return qtrue;
}
else
{
if (!g_waitingForKey || g_bindItem == NULL)
return qtrue;
if (key & K_CHAR_FLAG)
return qtrue;
switch (key)
{
case K_ESCAPE:
g_waitingForKey = qfalse;
return qtrue;
case K_BACKSPACE:
id = BindingIDFromName(item->cvar);
if (id != -1)
{
g_bindings[id].bind1 = -1;
g_bindings[id].bind2 = -1;
}
Controls_SetConfig(qtrue);
g_waitingForKey = qfalse;
g_bindItem = NULL;
return qtrue;
case '`':
return qtrue;
}
}
if (key != -1)
{
unsigned int i;
for (i = 0; i < g_bindCount; i++)
{
if (g_bindings[i].bind2 == key)
g_bindings[i].bind2 = -1;
if (g_bindings[i].bind1 == key)
{
g_bindings[i].bind1 = g_bindings[i].bind2;
g_bindings[i].bind2 = -1;
}
}
}
id = BindingIDFromName(item->cvar);
if (id != -1)
{
if (key == -1)
{
if (g_bindings[id].bind1 != -1)
{
DC->setBinding(g_bindings[id].bind1, "");
g_bindings[id].bind1 = -1;
}
if (g_bindings[id].bind2 != -1)
{
DC->setBinding(g_bindings[id].bind2, "");
g_bindings[id].bind2 = -1;
}
}
else if (g_bindings[id].bind1 == -1)
g_bindings[id].bind1 = key;
else if (g_bindings[id].bind1 != key && g_bindings[id].bind2 == -1)
g_bindings[id].bind2 = key;
else
{
DC->setBinding(g_bindings[id].bind1, "");
DC->setBinding(g_bindings[id].bind2, "");
g_bindings[id].bind1 = key;
g_bindings[id].bind2 = -1;
}
}
Controls_SetConfig(qtrue);
g_waitingForKey = qfalse;
return qtrue;
}
void Item_Model_Paint(itemDef_t *item)
{
float x, y, w, h;
refdef_t refdef;
refEntity_t ent;
vec3_t mins, maxs, origin;
vec3_t angles;
modelDef_t *modelPtr = item->typeData.model;
if (modelPtr == NULL)
return;
// setup the refdef
memset(&refdef, 0, sizeof(refdef));
refdef.rdflags = RDF_NOWORLDMODEL;
AxisClear(refdef.viewaxis);
x = item->window.rect.x + 1;
y = item->window.rect.y + 1;
w = item->window.rect.w - 2;
h = item->window.rect.h - 2;
UI_AdjustFrom640(&x, &y, &w, &h);
refdef.x = x;
refdef.y = y;
refdef.width = w;
refdef.height = h;
DC->modelBounds(item->asset, mins, maxs);
origin[2] = -0.5 * (mins[2] + maxs[2]);
origin[1] = 0.5 * (mins[1] + maxs[1]);
// calculate distance so the model nearly fills the box
if (qtrue)
{
float len = 0.5 * (maxs[2] - mins[2]);
origin[0] = len / 0.268; // len / tan( fov/2 )
// origin[0] = len / tan(w/2);
}
else
origin[0] = item->textscale;
refdef.fov_x = (modelPtr->fov_x) ? modelPtr->fov_x : w;
refdef.fov_y = (modelPtr->fov_y) ? modelPtr->fov_y : h;
// refdef.fov_x = (int)((float)refdef.width / 640.0f * 90.0f);
// xx = refdef.width / tan( refdef.fov_x / 360 * M_PI );
// refdef.fov_y = atan2( refdef.height, xx );
// refdef.fov_y *= ( 360 / M_PI );
DC->clearScene();
refdef.time = DC->realTime;
// add the model
memset(&ent, 0, sizeof(ent));
// adjust = 5.0 * sin( (float)uis.realtime / 500 );
// adjust = 360 % (int)((float)uis.realtime / 1000);
// VectorSet( angles, 0, 0, 1 );
// use item storage to track
if (modelPtr->rotationSpeed)
{
if (DC->realTime > item->window.nextTime)
{
item->window.nextTime = DC->realTime + modelPtr->rotationSpeed;
modelPtr->angle = (int)(modelPtr->angle + 1) % 360;
}
}
VectorSet(angles, 0, modelPtr->angle, 0);
AnglesToAxis(angles, ent.axis);
ent.hModel = item->asset;
VectorCopy(origin, ent.origin);
VectorCopy(origin, ent.lightingOrigin);
ent.renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW;
VectorCopy(ent.origin, ent.oldorigin);
DC->addRefEntityToScene(&ent);
DC->renderScene(&refdef);
}
void Item_ListBoxRow_Paint(itemDef_t *item, int row, int renderPos, qboolean highlight, qboolean scrollbar)
{
float x, y, w;
listBoxDef_t *listPtr = item->typeData.list;
menuDef_t *menu = (menuDef_t *)item->parent;
float one, two;
one = 1.0f * DC->aspectScale;
two = 2.0f * DC->aspectScale;
x = SCROLLBAR_X(item);
y = SCROLLBAR_Y(item) + (listPtr->elementHeight * renderPos);
w = item->window.rect.w - (two * item->window.borderSize);
if (scrollbar)
w -= SCROLLBAR_ARROW_WIDTH;
if (listPtr->elementStyle == LISTBOX_IMAGE)
{
qhandle_t image = DC->feederItemImage(item->feederID, row);
UI_SetClipRegion(x, y, listPtr->elementWidth, listPtr->elementHeight);
if (image)
DC->drawHandlePic(x + one, y + 1.0f, listPtr->elementWidth - two, listPtr->elementHeight - 2.0f, image);
if (highlight && row == item->cursorPos)
{
DC->drawRect(
x, y, listPtr->elementWidth, listPtr->elementHeight, item->window.borderSize, item->window.borderColor);
}
UI_ClearClipRegion();
}
else
{
const float m = UI_Text_EmHeight(item->textscale);
char text[MAX_STRING_CHARS];
qhandle_t optionalImage;
if (listPtr->numColumns > 0)
{
int j;
for (j = 0; j < listPtr->numColumns; j++)
{
float columnPos;
float width, height, yOffset;
if (menu->window.aspectBias != ASPECT_NONE || item->window.aspectBias != ASPECT_NONE)
{
columnPos = (listPtr->columnInfo[j].pos + 4.0f) * DC->aspectScale;
width = listPtr->columnInfo[j].width * DC->aspectScale;
}
else
{
columnPos = (listPtr->columnInfo[j].pos + 4.0f);
width = listPtr->columnInfo[j].width;
}
height = listPtr->columnInfo[j].width;
yOffset = y + ((listPtr->elementHeight - height) / 2.0f);
Q_strncpyz(text, DC->feederItemText(item->feederID, row, j, &optionalImage), sizeof(text));
UI_SetClipRegion(x + columnPos, yOffset, width, height);
if (optionalImage >= 0)
DC->drawHandlePic(x + columnPos, yOffset, width, height, optionalImage);
else if (text[0])
{
float alignOffset = 0.0f, tw;
tw = UI_Text_Width(text, item->textscale);
switch (listPtr->columnInfo[j].align)
{
case ALIGN_LEFT:
alignOffset = 0.0f;
break;
case ALIGN_RIGHT:
alignOffset = width - tw;
break;
case ALIGN_CENTER:
alignOffset = (width / 2.0f) - (tw / 2.0f);
break;
default:
alignOffset = 0.0f;
}
UI_Text_Paint(x + columnPos + alignOffset, y + m + ((listPtr->elementHeight - m) / 2.0f),
item->textscale, item->window.foreColor, text, 0, 0, item->textStyle);
}
UI_ClearClipRegion();
}
}
else
{
float offset;
if (menu->window.aspectBias != ASPECT_NONE || item->window.aspectBias != ASPECT_NONE)
offset = 4.0f * DC->aspectScale;
else
offset = 4.0f;
Q_strncpyz(text, DC->feederItemText(item->feederID, row, 0, &optionalImage), sizeof(text));
UI_SetClipRegion(x, y, w, listPtr->elementHeight);
if (optionalImage >= 0)
DC->drawHandlePic(x + offset, y, listPtr->elementHeight, listPtr->elementHeight, optionalImage);
else if (text[0])
{
UI_Text_Paint(x + offset, y + m + ((listPtr->elementHeight - m) / 2.0f), item->textscale,
item->window.foreColor, text, 0, 0, item->textStyle);
}
UI_ClearClipRegion();
}
if (highlight && row == item->cursorPos)
DC->fillRect(x, y, w, listPtr->elementHeight, item->window.outlineColor);
}
}
void Item_ListBox_Paint(itemDef_t *item)
{
float size;
int i;
listBoxDef_t *listPtr = item->typeData.list;
int count = DC->feederCount(item->feederID);
qboolean scrollbar = !(listPtr->noscrollbar && count > Item_ListBox_NumItemsForItemHeight(item));
if (scrollbar)
{
float x = SCROLLBAR_SLIDER_X(item);
float y = SCROLLBAR_Y(item);
float thumbY = Item_ListBox_ThumbDrawPosition(item);
// Up arrow
DC->drawHandlePic(x, y, SCROLLBAR_ARROW_WIDTH, SCROLLBAR_ARROW_HEIGHT, DC->Assets.scrollBarArrowUp);
y = SCROLLBAR_SLIDER_Y(item);
// Scroll bar
size = SCROLLBAR_SLIDER_HEIGHT(item);
DC->drawHandlePic(x, y, SCROLLBAR_ARROW_WIDTH, size, DC->Assets.scrollBar);
y = SCROLLBAR_SLIDER_Y(item) + size;
// Down arrow
DC->drawHandlePic(x, y, SCROLLBAR_ARROW_WIDTH, SCROLLBAR_ARROW_HEIGHT, DC->Assets.scrollBarArrowDown);
// Thumb
DC->drawHandlePic(x, thumbY, SCROLLBAR_ARROW_WIDTH, SCROLLBAR_ARROW_HEIGHT, DC->Assets.scrollBarThumb);
}
// Paint rows
for (i = listPtr->startPos; i < listPtr->endPos; i++)
Item_ListBoxRow_Paint(item, i, i - listPtr->startPos, qtrue, scrollbar);
}
void Item_Paint(itemDef_t *item);
void Item_ComboBox_Paint(itemDef_t *item)
{
float x, y, h;
x = SCROLLBAR_SLIDER_X(item);
y = SCROLLBAR_Y(item);
h = item->window.rect.h - 2.0f;
// Down arrow
DC->drawHandlePic(x, y, SCROLLBAR_ARROW_WIDTH, h, DC->Assets.scrollBarArrowDown);
Item_ListBoxRow_Paint(item, item->cursorPos, 0, qfalse, qtrue);
if (g_comboBoxItem != NULL)
{
qboolean cast = Item_ComboBox_MaybeCastToListBox(item);
Item_Paint(item);
Item_ComboBox_MaybeUnCastFromListBox(item, cast);
}
}
void Item_ListBox_Update(itemDef_t *item)
{
listBoxDef_t *listPtr = item->typeData.list;
int feederCount = DC->feederCount(item->feederID);
if (listPtr->lastFeederCount != feederCount)
{
if (listPtr->resetonfeederchange)
{
item->cursorPos = DC->feederInitialise(item->feederID);
Item_ListBox_SetStartPos(item, 0);
DC->feederSelection(item->feederID, item->cursorPos);
}
else
{
// Make sure endPos is up-to-date
Item_ListBox_SetStartPos(item, listPtr->startPos);
// If the selection is off the end now, select the last element
if (item->cursorPos >= feederCount)
item->cursorPos = feederCount - 1;
}
}
listPtr->lastFeederCount = feederCount;
}
void Item_OwnerDraw_Paint(itemDef_t *item)
{
menuDef_t *parent;
const char *text;
if (item == NULL)
return;
parent = (menuDef_t *)item->parent;
if (DC->ownerDrawItem)
{
vec4_t color, lowLight;
Fade(&item->window.flags, &item->window.foreColor[3], parent->fadeClamp, &item->window.nextTime,
parent->fadeCycle, qtrue, parent->fadeAmount);
memcpy(&color, &item->window.foreColor, sizeof(color));
if (item->numColors > 0 && DC->getValue)
{
// if the value is within one of the ranges then set color to that, otherwise leave at default
int i;
float f = DC->getValue(item->window.ownerDraw);
for (i = 0; i < item->numColors; i++)
{
if (f >= item->colorRanges[i].low && f <= item->colorRanges[i].high)
{
memcpy(&color, &item->colorRanges[i].color, sizeof(color));
break;
}
}
}
if (item->window.flags & WINDOW_HASFOCUS)
memcpy(color, &parent->focusColor, sizeof(vec4_t));
else if (item->textStyle == ITEM_TEXTSTYLE_BLINK && !((DC->realTime / BLINK_DIVISOR) & 1))
{
lowLight[0] = 0.8 * item->window.foreColor[0];
lowLight[1] = 0.8 * item->window.foreColor[1];
lowLight[2] = 0.8 * item->window.foreColor[2];
lowLight[3] = 0.8 * item->window.foreColor[3];
LerpColor(item->window.foreColor, lowLight, color, 0.5 + 0.5 * sin(DC->realTime / PULSE_DIVISOR));
}
if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE))
Com_Memcpy(color, parent->disableColor, sizeof(vec4_t));
if (DC->ownerDrawText && (text = DC->ownerDrawText(item->window.ownerDraw)))
{
if (item->text && *item->text)
{
Item_Text_Paint(item);
UI_Text_Paint(item->textRect.x + item->textRect.w + ITEM_VALUE_OFFSET, item->textRect.y,
item->textscale, color, text, 0, 0, item->textStyle);
}
else
{
item->text = text;
Item_Text_Paint(item);
item->text = NULL;
}
}
else
{
DC->ownerDrawItem(item->window.rect.x, item->window.rect.y, item->window.rect.w, item->window.rect.h,
item->textalignx, item->textaligny, item->window.ownerDraw, item->window.ownerDrawFlags,
item->alignment, item->textalignment, item->textvalignment, item->window.borderSize, item->textscale,
color, item->window.backColor, item->window.background, item->textStyle);
}
}
}
void Item_Update(itemDef_t *item)
{
if (item == NULL)
return;
if (Item_IsListBox(item))
Item_ListBox_Update(item);
}
void Item_Paint(itemDef_t *item)
{
vec4_t red;
menuDef_t *parent = (menuDef_t *)item->parent;
red[0] = red[3] = 1;
red[1] = red[2] = 0;
if (item == NULL)
return;
if (item->window.flags & WINDOW_ORBITING)
{
if (DC->realTime > item->window.nextTime)
{
float rx, ry, a, c, s, w, h;
item->window.nextTime = DC->realTime + item->window.offsetTime;
// translate
w = item->window.rectClient.w / 2;
h = item->window.rectClient.h / 2;
rx = item->window.rectClient.x + w - item->window.rectEffects.x;
ry = item->window.rectClient.y + h - item->window.rectEffects.y;
a = 3 * M_PI / 180;
c = cos(a);
s = sin(a);
item->window.rectClient.x = (rx * c - ry * s) + item->window.rectEffects.x - w;
item->window.rectClient.y = (rx * s + ry * c) + item->window.rectEffects.y - h;
Item_UpdatePosition(item);
}
}
if (item->window.flags & WINDOW_INTRANSITION)
{
if (DC->realTime > item->window.nextTime)
{
int done = 0;
item->window.nextTime = DC->realTime + item->window.offsetTime;
// transition the x,y
if (item->window.rectClient.x == item->window.rectEffects.x)
done++;
else
{
if (item->window.rectClient.x < item->window.rectEffects.x)
{
item->window.rectClient.x += item->window.rectEffects2.x;
if (item->window.rectClient.x > item->window.rectEffects.x)
{
item->window.rectClient.x = item->window.rectEffects.x;
done++;
}
}
else
{
item->window.rectClient.x -= item->window.rectEffects2.x;
if (item->window.rectClient.x < item->window.rectEffects.x)
{
item->window.rectClient.x = item->window.rectEffects.x;
done++;
}
}
}
if (item->window.rectClient.y == item->window.rectEffects.y)
done++;
else
{
if (item->window.rectClient.y < item->window.rectEffects.y)
{
item->window.rectClient.y += item->window.rectEffects2.y;
if (item->window.rectClient.y > item->window.rectEffects.y)
{
item->window.rectClient.y = item->window.rectEffects.y;
done++;
}
}
else
{
item->window.rectClient.y -= item->window.rectEffects2.y;
if (item->window.rectClient.y < item->window.rectEffects.y)
{
item->window.rectClient.y = item->window.rectEffects.y;
done++;
}
}
}
if (item->window.rectClient.w == item->window.rectEffects.w)
done++;
else
{
if (item->window.rectClient.w < item->window.rectEffects.w)
{
item->window.rectClient.w += item->window.rectEffects2.w;
if (item->window.rectClient.w > item->window.rectEffects.w)
{
item->window.rectClient.w = item->window.rectEffects.w;
done++;
}
}
else
{
item->window.rectClient.w -= item->window.rectEffects2.w;
if (item->window.rectClient.w < item->window.rectEffects.w)
{
item->window.rectClient.w = item->window.rectEffects.w;
done++;
}
}
}
if (item->window.rectClient.h == item->window.rectEffects.h)
done++;
else
{
if (item->window.rectClient.h < item->window.rectEffects.h)
{
item->window.rectClient.h += item->window.rectEffects2.h;
if (item->window.rectClient.h > item->window.rectEffects.h)
{
item->window.rectClient.h = item->window.rectEffects.h;
done++;
}
}
else
{
item->window.rectClient.h -= item->window.rectEffects2.h;
if (item->window.rectClient.h < item->window.rectEffects.h)
{
item->window.rectClient.h = item->window.rectEffects.h;
done++;
}
}
}
Item_UpdatePosition(item);
if (done == 4)
item->window.flags &= ~WINDOW_INTRANSITION;
}
}
if (item->window.ownerDrawFlags && DC->ownerDrawVisible)
{
if (!DC->ownerDrawVisible(item->window.ownerDrawFlags))
item->window.flags &= ~WINDOW_VISIBLE;
else
item->window.flags |= WINDOW_VISIBLE;
}
if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE))
{
if (!Item_EnableShowViaCvar(item, CVAR_SHOW))
return;
}
if (item->window.flags & WINDOW_TIMEDVISIBLE)
{
}
if (!(item->window.flags & WINDOW_VISIBLE))
return;
Window_Paint(&item->window, parent->fadeAmount, parent->fadeClamp, parent->fadeCycle);
if (DC->getCVarValue("ui_developer"))
{
vec4_t color;
rectDef_t *r = Item_CorrectedTextRect(item);
color[1] = color[3] = 1;
color[0] = color[2] = 0;
DC->drawRect(r->x, r->y, r->w, r->h, 1, color);
}
switch (item->type)
{
case ITEM_TYPE_OWNERDRAW:
Item_OwnerDraw_Paint(item);
break;
case ITEM_TYPE_TEXT:
case ITEM_TYPE_BUTTON:
Item_Text_Paint(item);
break;
case ITEM_TYPE_RADIOBUTTON:
break;
case ITEM_TYPE_CHECKBOX:
break;
case ITEM_TYPE_CYCLE:
Item_Cycle_Paint(item);
break;
case ITEM_TYPE_LISTBOX:
Item_ListBox_Paint(item);
break;
case ITEM_TYPE_COMBOBOX:
Item_ComboBox_Paint(item);
break;
case ITEM_TYPE_MODEL:
Item_Model_Paint(item);
break;
case ITEM_TYPE_YESNO:
Item_YesNo_Paint(item);
break;
case ITEM_TYPE_MULTI:
Item_Multi_Paint(item);
break;
case ITEM_TYPE_BIND:
Item_Bind_Paint(item);
break;
case ITEM_TYPE_SLIDER:
Item_Slider_Paint(item);
break;
default:
if (Item_IsEditField(item))
Item_TextField_Paint(item);
break;
}
Border_Paint(&item->window);
}
void Menu_Init(menuDef_t *menu)
{
memset(menu, 0, sizeof(menuDef_t));
menu->cursorItem = -1;
menu->fadeAmount = DC->Assets.fadeAmount;
menu->fadeClamp = DC->Assets.fadeClamp;
menu->fadeCycle = DC->Assets.fadeCycle;
Window_Init(&menu->window);
menu->window.aspectBias = ALIGN_CENTER;
}
itemDef_t *Menu_GetFocusedItem(menuDef_t *menu)
{
int i;
if (menu)
{
for (i = 0; i < menu->itemCount; i++)
{
if (menu->items[i]->window.flags & WINDOW_HASFOCUS)
return menu->items[i];
}
}
return NULL;
}
menuDef_t *Menu_GetFocused(void)
{
int i;
for (i = 0; i < menuCount; i++)
{
if (Menus[i].window.flags & WINDOW_HASFOCUS && Menus[i].window.flags & WINDOW_VISIBLE)
return &Menus[i];
}
return NULL;
}
void Menu_ScrollFeeder(menuDef_t *menu, int feeder, qboolean down)
{
if (menu)
{
int i;
for (i = 0; i < menu->itemCount; i++)
{
itemDef_t *item = menu->items[i];
if (item->feederID == feeder)
{
qboolean cast = Item_ComboBox_MaybeCastToListBox(item);
Item_ListBox_HandleKey(item, down ? K_DOWNARROW : K_UPARROW, qtrue, qtrue);
Item_ComboBox_MaybeUnCastFromListBox(item, cast);
return;
}
}
}
}
void Menu_SetFeederSelection(menuDef_t *menu, int feeder, int index, const char *name)
{
if (menu == NULL)
{
if (name == NULL)
menu = Menu_GetFocused();
else
menu = Menus_FindByName(name);
}
if (menu)
{
int i;
for (i = 0; i < menu->itemCount; i++)
{
if (menu->items[i]->feederID == feeder)
{
if (Item_IsListBox(menu->items[i]) && index == 0)
{
menu->items[i]->typeData.list->cursorPos = 0;
Item_ListBox_SetStartPos(menu->items[i], 0);
}
menu->items[i]->cursorPos = index;
DC->feederSelection(menu->items[i]->feederID, menu->items[i]->cursorPos);
return;
}
}
}
}
qboolean Menus_AnyFullScreenVisible(void)
{
int i;
for (i = 0; i < menuCount; i++)
{
if (Menus[i].window.flags & WINDOW_VISIBLE && Menus[i].fullScreen)
return qtrue;
}
return qfalse;
}
menuDef_t *Menus_ActivateByName(const char *p)
{
int i;
menuDef_t *m = NULL;
// Activate one menu
for (i = 0; i < menuCount; i++)
{
if (Q_stricmp(Menus[i].window.name, p) == 0)
{
m = &Menus[i];
Menus_Activate(m);
break;
}
}
// Defocus the others
for (i = 0; i < menuCount; i++)
{
if (Q_stricmp(Menus[i].window.name, p) != 0)
Menus[i].window.flags &= ~WINDOW_HASFOCUS;
}
return m;
}
menuDef_t *Menus_ReplaceActiveByName(const char *p)
{
int i;
menuDef_t *m = NULL;
// Activate one menu
for (i = 0; i < menuCount; i++)
{
if (Q_stricmp(Menus[i].window.name, p) == 0)
{
m = &Menus[i];
if (!Menus_ReplaceActive(m))
return NULL;
break;
}
}
return m;
}
void Item_Init(itemDef_t *item)
{
memset(item, 0, sizeof(itemDef_t));
item->textscale = 0.55f;
Window_Init(&item->window);
item->window.aspectBias = ASPECT_NONE;
}
static qboolean Item_HandleMouseMove(itemDef_t *item, float x, float y, int pass, qboolean focusSet)
{
if (Rect_ContainsPoint(&item->window.rect, x, y))
{
if (pass == 1)
{
if (item->type == ITEM_TYPE_TEXT && item->text)
{
if (!Rect_ContainsPoint(Item_CorrectedTextRect(item), x, y))
return qtrue;
}
// if we are over an item
if (IsVisible(item->window.flags))
{
// different one
Item_MouseEnter(item, x, y);
if (!focusSet)
focusSet = Item_SetFocus(item, x, y);
}
}
return qtrue;
}
return qfalse;
}
void Menu_HandleMouseMove(menuDef_t *menu, float x, float y)
{
int i, pass;
qboolean focusSet = qfalse;
qboolean result;
qboolean cast;
if (menu == NULL)
return;
if (!(menu->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED)))
return;
if (itemCapture)
{
// Item_MouseMove(itemCapture, x, y);
return;
}
if (g_waitingForKey || g_editingField)
return;
if (g_comboBoxItem != NULL)
{
Item_SetFocus(g_comboBoxItem, x, y);
focusSet = qtrue;
}
// FIXME: this is the whole issue of focus vs. mouse over..
// need a better overall solution as i don't like going through everything twice
for (pass = 0; pass < 2; pass++)
{
for (i = 0; i < menu->itemCount; i++)
{
itemDef_t *item = menu->items[i];
// turn off focus each item
// menu->items[i].window.flags &= ~WINDOW_HASFOCUS;
if (!(item->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED)))
continue;
// items can be enabled and disabled based on cvars
if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE))
continue;
if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE) && !Item_EnableShowViaCvar(item, CVAR_SHOW))
continue;
cast = Item_ComboBox_MaybeCastToListBox(item);
result = Item_HandleMouseMove(item, x, y, pass, focusSet);
Item_ComboBox_MaybeUnCastFromListBox(item, cast);
if (!result && item->window.flags & WINDOW_MOUSEOVER)
{
Item_MouseLeave(item);
Item_SetMouseOver(item, qfalse);
}
}
}
}
void Menu_Update(menuDef_t *menu)
{
int i;
if (menu == NULL)
return;
for (i = 0; i < menu->itemCount; i++)
Item_Update(menu->items[i]);
}
void Menu_Paint(menuDef_t *menu, qboolean forcePaint)
{
int i;
if (menu == NULL)
return;
if (!(menu->window.flags & WINDOW_VISIBLE) && !forcePaint)
return;
if (menu->window.ownerDrawFlags && DC->ownerDrawVisible && !DC->ownerDrawVisible(menu->window.ownerDrawFlags))
return;
if (forcePaint)
menu->window.flags |= WINDOW_FORCED;
// draw the background if necessary
if (menu->fullScreen)
{
// implies a background shader
// FIXME: make sure we have a default shader if fullscreen is set with no background
DC->drawHandlePic(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, menu->window.background);
}
// paint the background and or border
Window_Paint(&menu->window, menu->fadeAmount, menu->fadeClamp, menu->fadeCycle);
Border_Paint(&menu->window);
for (i = 0; i < menu->itemCount; i++)
Item_Paint(menu->items[i]);
if (DC->getCVarValue("ui_developer"))
{
vec4_t color;
color[0] = color[2] = color[3] = 1;
color[1] = 0;
DC->drawRect(menu->window.rect.x, menu->window.rect.y, menu->window.rect.w, menu->window.rect.h, 1, color);
}
}
/*
===============
Keyword Hash
===============
*/
#define KEYWORDHASH_SIZE 512
typedef struct keywordHash_s {
char *keyword;
qboolean (*func)(itemDef_t *item, int handle);
int param;
struct keywordHash_s *next;
} keywordHash_t;
int KeywordHash_Key(char *keyword)
{
int register hash, i;
hash = 0;
for (i = 0; keyword[i] != '\0'; i++)
{
if (keyword[i] >= 'A' && keyword[i] <= 'Z')
hash += (keyword[i] + ('a' - 'A')) * (119 + i);
else
hash += keyword[i] * (119 + i);
}
hash = (hash ^ (hash >> 10) ^ (hash >> 20)) & (KEYWORDHASH_SIZE - 1);
return hash;
}
void KeywordHash_Add(keywordHash_t *table[], keywordHash_t *key)
{
int hash;
hash = KeywordHash_Key(key->keyword);
/*
if(table[hash]) int collision = qtrue;
*/
key->next = table[hash];
table[hash] = key;
}
keywordHash_t *KeywordHash_Find(keywordHash_t *table[], char *keyword)
{
keywordHash_t *key;
int hash;
hash = KeywordHash_Key(keyword);
for (key = table[hash]; key; key = key->next)
{
if (!Q_stricmp(key->keyword, keyword))
return key;
}
return NULL;
}
/*
===============
Item_DataType
Give a numeric representation of which typeData union element this item uses
===============
*/
itemDataType_t Item_DataType(itemDef_t *item)
{
switch (item->type)
{
default:
case ITEM_TYPE_NONE:
return TYPE_NONE;
case ITEM_TYPE_LISTBOX:
case ITEM_TYPE_COMBOBOX:
return TYPE_LIST;
case ITEM_TYPE_CYCLE:
return TYPE_COMBO;
case ITEM_TYPE_EDITFIELD:
case ITEM_TYPE_NUMERICFIELD:
case ITEM_TYPE_SAYFIELD:
case ITEM_TYPE_YESNO:
case ITEM_TYPE_BIND:
case ITEM_TYPE_SLIDER:
case ITEM_TYPE_TEXT:
return TYPE_EDIT;
case ITEM_TYPE_MULTI:
return TYPE_MULTI;
case ITEM_TYPE_MODEL:
return TYPE_MODEL;
}
}
/*
===============
Item_IsEditField
===============
*/
static ID_INLINE qboolean Item_IsEditField(itemDef_t *item)
{
switch (item->type)
{
case ITEM_TYPE_EDITFIELD:
case ITEM_TYPE_NUMERICFIELD:
case ITEM_TYPE_SAYFIELD:
return qtrue;
default:
return qfalse;
}
}
/*
===============
Item_IsListBox
===============
*/
static ID_INLINE qboolean Item_IsListBox(itemDef_t *item)
{
switch (item->type)
{
case ITEM_TYPE_LISTBOX:
case ITEM_TYPE_COMBOBOX:
return qtrue;
default:
return qfalse;
}
}
/*
===============
Item Keyword Parse functions
===============
*/
// name
qboolean ItemParse_name(itemDef_t *item, int handle)
{
if (!PC_String_Parse(handle, &item->window.name))
return qfalse;
return qtrue;
}
// name
qboolean ItemParse_focusSound(itemDef_t *item, int handle)
{
const char *temp;
if (!PC_String_Parse(handle, &temp))
return qfalse;
item->focusSound = DC->registerSound(temp, qfalse);
return qtrue;
}
// text
qboolean ItemParse_text(itemDef_t *item, int handle)
{
if (!PC_String_Parse(handle, &item->text))
return qfalse;
return qtrue;
}
// group
qboolean ItemParse_group(itemDef_t *item, int handle)
{
if (!PC_String_Parse(handle, &item->window.group))
return qfalse;
return qtrue;
}
// asset_model
qboolean ItemParse_asset_model(itemDef_t *item, int handle)
{
const char *temp;
if (!PC_String_Parse(handle, &temp))
return qfalse;
item->asset = DC->registerModel(temp);
item->typeData.model->angle = rand() % 360;
return qtrue;
}
// asset_shader
qboolean ItemParse_asset_shader(itemDef_t *item, int handle)
{
const char *temp;
if (!PC_String_Parse(handle, &temp))
return qfalse;
item->asset = DC->registerShaderNoMip(temp);
return qtrue;
}
// model_origin
qboolean ItemParse_model_origin(itemDef_t *item, int handle)
{
return (PC_Float_Parse(handle, &item->typeData.model->origin[0]) &&
PC_Float_Parse(handle, &item->typeData.model->origin[1]) &&
PC_Float_Parse(handle, &item->typeData.model->origin[2]));
}
// model_fovx
qboolean ItemParse_model_fovx(itemDef_t *item, int handle)
{
return PC_Float_Parse(handle, &item->typeData.model->fov_x);
}
// model_fovy
qboolean ItemParse_model_fovy(itemDef_t *item, int handle)
{
return PC_Float_Parse(handle, &item->typeData.model->fov_y);
}
// model_rotation
qboolean ItemParse_model_rotation(itemDef_t *item, int handle)
{
return PC_Int_Parse(handle, &item->typeData.model->rotationSpeed);
}
// model_angle
qboolean ItemParse_model_angle(itemDef_t *item, int handle)
{
return PC_Int_Parse(handle, &item->typeData.model->angle);
}
// rect
qboolean ItemParse_rect(itemDef_t *item, int handle)
{
if (!PC_Rect_Parse(handle, &item->window.rectClient))
return qfalse;
return qtrue;
}
// aspectBias
qboolean ItemParse_aspectBias(itemDef_t *item, int handle)
{
if (!PC_Int_Parse(handle, &item->window.aspectBias))
return qfalse;
return qtrue;
}
// style
qboolean ItemParse_style(itemDef_t *item, int handle)
{
if (!PC_Int_Parse(handle, &item->window.style))
return qfalse;
return qtrue;
}
// decoration
qboolean ItemParse_decoration(itemDef_t *item, int handle)
{
(void)handle;
item->window.flags |= WINDOW_DECORATION;
return qtrue;
}
// notselectable
qboolean ItemParse_notselectable(itemDef_t *item, int handle)
{
(void)handle;
item->typeData.list->notselectable = qtrue;
return qtrue;
}
// noscrollbar
qboolean ItemParse_noscrollbar(itemDef_t *item, int handle)
{
(void)handle;
item->typeData.list->noscrollbar = qtrue;
return qtrue;
}
// resetonfeederchange
qboolean ItemParse_resetonfeederchange(itemDef_t *item, int handle)
{
(void)handle;
item->typeData.list->resetonfeederchange = qtrue;
return qtrue;
}
// auto wrapped
qboolean ItemParse_wrapped(itemDef_t *item, int handle)
{
(void)handle;
item->window.flags |= WINDOW_WRAPPED;
return qtrue;
}
// type
qboolean ItemParse_type(itemDef_t *item, int handle)
{
if (item->type != ITEM_TYPE_NONE)
{
PC_SourceError(handle, "item already has a type");
return qfalse;
}
if (!PC_Int_Parse(handle, &item->type))
return qfalse;
if (item->type == ITEM_TYPE_NONE)
{
PC_SourceError(handle, "type must not be none");
return qfalse;
}
// allocate the relevant type data
switch (item->type)
{
case ITEM_TYPE_LISTBOX:
case ITEM_TYPE_COMBOBOX:
item->typeData.list = UI_Alloc(sizeof(listBoxDef_t));
memset(item->typeData.list, 0, sizeof(listBoxDef_t));
break;
case ITEM_TYPE_CYCLE:
item->typeData.cycle = UI_Alloc(sizeof(cycleDef_t));
memset(item->typeData.cycle, 0, sizeof(cycleDef_t));
break;
case ITEM_TYPE_EDITFIELD:
case ITEM_TYPE_SAYFIELD:
case ITEM_TYPE_NUMERICFIELD:
case ITEM_TYPE_YESNO:
case ITEM_TYPE_BIND:
case ITEM_TYPE_SLIDER:
case ITEM_TYPE_TEXT:
item->typeData.edit = UI_Alloc(sizeof(editFieldDef_t));
memset(item->typeData.edit, 0, sizeof(editFieldDef_t));
if (item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_SAYFIELD)
item->typeData.edit->maxPaintChars = MAX_EDITFIELD;
break;
case ITEM_TYPE_MULTI:
item->typeData.multi = UI_Alloc(sizeof(multiDef_t));
memset(item->typeData.multi, 0, sizeof(multiDef_t));
break;
case ITEM_TYPE_MODEL:
item->typeData.model = UI_Alloc(sizeof(modelDef_t));
memset(item->typeData.model, 0, sizeof(modelDef_t));
break;
default:
break;
}
return qtrue;
}
// elementwidth, used for listbox image elements
qboolean ItemParse_elementwidth(itemDef_t *item, int handle)
{
return PC_Float_Parse(handle, &item->typeData.list->elementWidth);
}
// elementheight, used for listbox image elements
qboolean ItemParse_elementheight(itemDef_t *item, int handle)
{
return PC_Float_Parse(handle, &item->typeData.list->elementHeight);
}
// dropitems, number of items to drop from a combobox
qboolean ItemParse_dropitems(itemDef_t *item, int handle)
{
return PC_Int_Parse(handle, &item->typeData.list->dropItems);
}
// feeder
qboolean ItemParse_feeder(itemDef_t *item, int handle)
{
if (!PC_Int_Parse(handle, &item->feederID))
return qfalse;
return qtrue;
}
// elementtype, used to specify what type of elements a listbox contains
// uses textstyle for storage
qboolean ItemParse_elementtype(itemDef_t *item, int handle)
{
return PC_Int_Parse(handle, &item->typeData.list->elementStyle);
}
// columns sets a number of columns and an x pos and width per..
qboolean ItemParse_columns(itemDef_t *item, int handle)
{
int i;
if (!PC_Int_Parse(handle, &item->typeData.list->numColumns))
return qfalse;
if (item->typeData.list->numColumns > MAX_LB_COLUMNS)
{
PC_SourceError(handle, "exceeded maximum allowed columns (%d)", MAX_LB_COLUMNS);
return qfalse;
}
for (i = 0; i < item->typeData.list->numColumns; i++)
{
int pos, width, align;
if (!PC_Int_Parse(handle, &pos) || !PC_Int_Parse(handle, &width) || !PC_Int_Parse(handle, &align))
return qfalse;
item->typeData.list->columnInfo[i].pos = pos;
item->typeData.list->columnInfo[i].width = width;
item->typeData.list->columnInfo[i].align = align;
}
return qtrue;
}
qboolean ItemParse_border(itemDef_t *item, int handle)
{
if (!PC_Int_Parse(handle, &item->window.border))
return qfalse;
return qtrue;
}
qboolean ItemParse_bordersize(itemDef_t *item, int handle)
{
if (!PC_Float_Parse(handle, &item->window.borderSize))
return qfalse;
return qtrue;
}
// FIXME: why does this require a parameter? visible MENU_FALSE does nothing
qboolean ItemParse_visible(itemDef_t *item, int handle)
{
int i;
if (!PC_Int_Parse(handle, &i))
return qfalse;
if (i)
item->window.flags |= WINDOW_VISIBLE;
return qtrue;
}
// ownerdraw , implies ITEM_TYPE_OWNERDRAW
qboolean ItemParse_ownerdraw(itemDef_t *item, int handle)
{
if (!PC_Int_Parse(handle, &item->window.ownerDraw))
return qfalse;
if (item->type != ITEM_TYPE_NONE && item->type != ITEM_TYPE_OWNERDRAW)
{
PC_SourceError(handle, "ownerdraws cannot have an item type");
return qfalse;
}
item->type = ITEM_TYPE_OWNERDRAW;
return qtrue;
}
qboolean ItemParse_align(itemDef_t *item, int handle)
{
if (!PC_Int_Parse(handle, &item->alignment))
return qfalse;
return qtrue;
}
qboolean ItemParse_textalign(itemDef_t *item, int handle)
{
if (!PC_Int_Parse(handle, &item->textalignment))
return qfalse;
return qtrue;
}
qboolean ItemParse_textvalign(itemDef_t *item, int handle)
{
if (!PC_Int_Parse(handle, &item->textvalignment))
return qfalse;
return qtrue;
}
qboolean ItemParse_textalignx(itemDef_t *item, int handle)
{
if (!PC_Float_Parse(handle, &item->textalignx))
return qfalse;
return qtrue;
}
qboolean ItemParse_textaligny(itemDef_t *item, int handle)
{
if (!PC_Float_Parse(handle, &item->textaligny))
return qfalse;
return qtrue;
}
qboolean ItemParse_textscale(itemDef_t *item, int handle)
{
if (!PC_Float_Parse(handle, &item->textscale))
return qfalse;
return qtrue;
}
qboolean ItemParse_textstyle(itemDef_t *item, int handle)
{
if (!PC_Int_Parse(handle, &item->textStyle))
return qfalse;
return qtrue;
}
qboolean ItemParse_backcolor(itemDef_t *item, int handle)
{
int i;
float f;
for (i = 0; i < 4; i++)
{
if (!PC_Float_Parse(handle, &f))
return qfalse;
item->window.backColor[i] = f;
}
return qtrue;
}
qboolean ItemParse_forecolor(itemDef_t *item, int handle)
{
int i;
float f;
for (i = 0; i < 4; i++)
{
if (!PC_Float_Parse(handle, &f))
return qfalse;
item->window.foreColor[i] = f;
item->window.flags |= WINDOW_FORECOLORSET;
}
return qtrue;
}
qboolean ItemParse_bordercolor(itemDef_t *item, int handle)
{
int i;
float f;
for (i = 0; i < 4; i++)
{
if (!PC_Float_Parse(handle, &f))
return qfalse;
item->window.borderColor[i] = f;
}
return qtrue;
}
qboolean ItemParse_outlinecolor(itemDef_t *item, int handle)
{
if (!PC_Color_Parse(handle, &item->window.outlineColor))
return qfalse;
return qtrue;
}
qboolean ItemParse_background(itemDef_t *item, int handle)
{
const char *temp;
if (!PC_String_Parse(handle, &temp))
return qfalse;
item->window.background = DC->registerShaderNoMip(temp);
return qtrue;
}
qboolean ItemParse_cinematic(itemDef_t *item, int handle)
{
if (!PC_String_Parse(handle, &item->window.cinematicName))
return qfalse;
return qtrue;
}
qboolean ItemParse_doubleClick(itemDef_t *item, int handle)
{
return (item->typeData.list && PC_Script_Parse(handle, &item->typeData.list->doubleClick));
}
qboolean ItemParse_onFocus(itemDef_t *item, int handle)
{
if (!PC_Script_Parse(handle, &item->onFocus))
return qfalse;
return qtrue;
}
qboolean ItemParse_leaveFocus(itemDef_t *item, int handle)
{
if (!PC_Script_Parse(handle, &item->leaveFocus))
return qfalse;
return qtrue;
}
qboolean ItemParse_mouseEnter(itemDef_t *item, int handle)
{
if (!PC_Script_Parse(handle, &item->mouseEnter))
return qfalse;
return qtrue;
}
qboolean ItemParse_mouseExit(itemDef_t *item, int handle)
{
if (!PC_Script_Parse(handle, &item->mouseExit))
return qfalse;
return qtrue;
}
qboolean ItemParse_mouseEnterText(itemDef_t *item, int handle)
{
if (!PC_Script_Parse(handle, &item->mouseEnterText))
return qfalse;
return qtrue;
}
qboolean ItemParse_mouseExitText(itemDef_t *item, int handle)
{
if (!PC_Script_Parse(handle, &item->mouseExitText))
return qfalse;
return qtrue;
}
qboolean ItemParse_onTextEntry(itemDef_t *item, int handle)
{
if (!PC_Script_Parse(handle, &item->onTextEntry))
return qfalse;
return qtrue;
}
qboolean ItemParse_onCharEntry(itemDef_t *item, int handle)
{
if (!PC_Script_Parse(handle, &item->onCharEntry))
return qfalse;
return qtrue;
}
qboolean ItemParse_action(itemDef_t *item, int handle)
{
if (!PC_Script_Parse(handle, &item->action))
return qfalse;
return qtrue;
}
qboolean ItemParse_cvarTest(itemDef_t *item, int handle)
{
if (!PC_String_Parse(handle, &item->cvarTest))
return qfalse;
return qtrue;
}
qboolean ItemParse_cvar(itemDef_t *item, int handle)
{
if (!PC_String_Parse(handle, &item->cvar))
return qfalse;
if (Item_DataType(item) == TYPE_EDIT)
{
item->typeData.edit->minVal = -1;
item->typeData.edit->maxVal = -1;
item->typeData.edit->defVal = -1;
}
return qtrue;
}
qboolean ItemParse_maxChars(itemDef_t *item, int handle)
{
return PC_Int_Parse(handle, &item->typeData.edit->maxChars);
}
qboolean ItemParse_maxPaintChars(itemDef_t *item, int handle)
{
return PC_Int_Parse(handle, &item->typeData.edit->maxPaintChars);
}
qboolean ItemParse_maxFieldWidth(itemDef_t *item, int handle)
{
if (!PC_Int_Parse(handle, &item->typeData.edit->maxFieldWidth))
return qfalse;
if (item->typeData.edit->maxFieldWidth < MIN_FIELD_WIDTH)
{
PC_SourceError(handle, "max field width must be at least %d", MIN_FIELD_WIDTH);
return qfalse;
}
return qtrue;
}
qboolean ItemParse_cvarFloat(itemDef_t *item, int handle)
{
return (PC_String_Parse(handle, &item->cvar) && PC_Float_Parse(handle, &item->typeData.edit->defVal) &&
PC_Float_Parse(handle, &item->typeData.edit->minVal) &&
PC_Float_Parse(handle, &item->typeData.edit->maxVal));
}
qboolean ItemParse_cvarStrList(itemDef_t *item, int handle)
{
pc_token_t token;
multiDef_t *multiPtr;
int pass;
multiPtr = item->typeData.multi;
multiPtr->count = 0;
multiPtr->strDef = qtrue;
if (!trap_Parse_ReadToken(handle, &token))
return qfalse;
if (*token.string != '{')
return qfalse;
pass = 0;
while (1)
{
if (!trap_Parse_ReadToken(handle, &token))
{
PC_SourceError(handle, "end of file inside menu item\n");
return qfalse;
}
if (*token.string == '}')
return qtrue;
if (*token.string == ',' || *token.string == ';')
continue;
if (pass == 0)
{
multiPtr->cvarList[multiPtr->count] = String_Alloc(token.string);
pass = 1;
}
else
{
multiPtr->cvarStr[multiPtr->count] = String_Alloc(token.string);
pass = 0;
multiPtr->count++;
if (multiPtr->count >= MAX_MULTI_CVARS)
{
PC_SourceError(handle, "cvar string list may not exceed %d cvars", MAX_MULTI_CVARS);
return qfalse;
}
}
}
return qfalse;
}
qboolean ItemParse_cvarFloatList(itemDef_t *item, int handle)
{
pc_token_t token;
multiDef_t *multiPtr;
multiPtr = item->typeData.multi;
multiPtr->count = 0;
multiPtr->strDef = qfalse;
if (!trap_Parse_ReadToken(handle, &token))
return qfalse;
if (*token.string != '{')
return qfalse;
while (1)
{
if (!trap_Parse_ReadToken(handle, &token))
{
PC_SourceError(handle, "end of file inside menu item\n");
return qfalse;
}
if (*token.string == '}')
return qtrue;
if (*token.string == ',' || *token.string == ';')
continue;
multiPtr->cvarList[multiPtr->count] = String_Alloc(token.string);
if (!PC_Float_Parse(handle, &multiPtr->cvarValue[multiPtr->count]))
return qfalse;
multiPtr->count++;
if (multiPtr->count >= MAX_MULTI_CVARS)
{
PC_SourceError(handle, "cvar string list may not exceed %d cvars", MAX_MULTI_CVARS);
return qfalse;
}
}
return qfalse;
}
qboolean ItemParse_addColorRange(itemDef_t *item, int handle)
{
colorRangeDef_t color;
if (PC_Float_Parse(handle, &color.low) && PC_Float_Parse(handle, &color.high) &&
PC_Color_Parse(handle, &color.color))
{
if (item->numColors < MAX_COLOR_RANGES)
{
memcpy(&item->colorRanges[item->numColors], &color, sizeof(color));
item->numColors++;
}
else
{
PC_SourceError(handle, "may not exceed %d color ranges", MAX_COLOR_RANGES);
return qfalse;
}
return qtrue;
}
return qfalse;
}
qboolean ItemParse_ownerdrawFlag(itemDef_t *item, int handle)
{
int i;
if (!PC_Int_Parse(handle, &i))
return qfalse;
item->window.ownerDrawFlags |= i;
return qtrue;
}
qboolean ItemParse_enableCvar(itemDef_t *item, int handle)
{
if (PC_Script_Parse(handle, &item->enableCvar))
{
item->cvarFlags = CVAR_ENABLE;
return qtrue;
}
return qfalse;
}
qboolean ItemParse_disableCvar(itemDef_t *item, int handle)
{
if (PC_Script_Parse(handle, &item->enableCvar))
{
item->cvarFlags = CVAR_DISABLE;
return qtrue;
}
return qfalse;
}
qboolean ItemParse_showCvar(itemDef_t *item, int handle)
{
if (PC_Script_Parse(handle, &item->enableCvar))
{
item->cvarFlags = CVAR_SHOW;
return qtrue;
}
return qfalse;
}
qboolean ItemParse_hideCvar(itemDef_t *item, int handle)
{
if (PC_Script_Parse(handle, &item->enableCvar))
{
item->cvarFlags = CVAR_HIDE;
return qtrue;
}
return qfalse;
}
keywordHash_t itemParseKeywords[] = {{"name", ItemParse_name, TYPE_ANY, NULL}, {"type", ItemParse_type, TYPE_ANY, NULL},
{"text", ItemParse_text, TYPE_ANY, NULL}, {"group", ItemParse_group, TYPE_ANY, NULL},
{"asset_model", ItemParse_asset_model, TYPE_MODEL, NULL},
{"asset_shader", ItemParse_asset_shader, TYPE_ANY, NULL}, // ?
{"model_origin", ItemParse_model_origin, TYPE_MODEL, NULL}, {"model_fovx", ItemParse_model_fovx, TYPE_MODEL, NULL},
{"model_fovy", ItemParse_model_fovy, TYPE_MODEL, NULL},
{"model_rotation", ItemParse_model_rotation, TYPE_MODEL, NULL},
{"model_angle", ItemParse_model_angle, TYPE_MODEL, NULL}, {"rect", ItemParse_rect, TYPE_ANY, NULL},
{"aspectBias", ItemParse_aspectBias, TYPE_ANY, NULL}, {"style", ItemParse_style, TYPE_ANY, NULL},
{"decoration", ItemParse_decoration, TYPE_ANY, NULL}, {"notselectable", ItemParse_notselectable, TYPE_LIST, NULL},
{"noscrollbar", ItemParse_noscrollbar, TYPE_LIST, NULL},
{"resetonfeederchange", ItemParse_resetonfeederchange, TYPE_LIST, NULL},
{"wrapped", ItemParse_wrapped, TYPE_ANY, NULL}, {"elementwidth", ItemParse_elementwidth, TYPE_LIST, NULL},
{"elementheight", ItemParse_elementheight, TYPE_LIST, NULL}, {"dropitems", ItemParse_dropitems, TYPE_LIST, NULL},
{"feeder", ItemParse_feeder, TYPE_ANY, NULL}, {"elementtype", ItemParse_elementtype, TYPE_LIST, NULL},
{"columns", ItemParse_columns, TYPE_LIST, NULL}, {"border", ItemParse_border, TYPE_ANY, NULL},
{"bordersize", ItemParse_bordersize, TYPE_ANY, NULL}, {"visible", ItemParse_visible, TYPE_ANY, NULL},
{"ownerdraw", ItemParse_ownerdraw, TYPE_ANY, NULL}, {"align", ItemParse_align, TYPE_ANY, NULL},
{"textalign", ItemParse_textalign, TYPE_ANY, NULL}, {"textvalign", ItemParse_textvalign, TYPE_ANY, NULL},
{"textalignx", ItemParse_textalignx, TYPE_ANY, NULL}, {"textaligny", ItemParse_textaligny, TYPE_ANY, NULL},
{"textscale", ItemParse_textscale, TYPE_ANY, NULL}, {"textstyle", ItemParse_textstyle, TYPE_ANY, NULL},
{"backcolor", ItemParse_backcolor, TYPE_ANY, NULL}, {"forecolor", ItemParse_forecolor, TYPE_ANY, NULL},
{"bordercolor", ItemParse_bordercolor, TYPE_ANY, NULL}, {"outlinecolor", ItemParse_outlinecolor, TYPE_ANY, NULL},
{"background", ItemParse_background, TYPE_ANY, NULL}, {"onFocus", ItemParse_onFocus, TYPE_ANY, NULL},
{"leaveFocus", ItemParse_leaveFocus, TYPE_ANY, NULL}, {"mouseEnter", ItemParse_mouseEnter, TYPE_ANY, NULL},
{"mouseExit", ItemParse_mouseExit, TYPE_ANY, NULL}, {"mouseEnterText", ItemParse_mouseEnterText, TYPE_ANY, NULL},
{"mouseExitText", ItemParse_mouseExitText, TYPE_ANY, NULL}, {"onTextEntry", ItemParse_onTextEntry, TYPE_ANY, NULL},
{"onCharEntry", ItemParse_onCharEntry, TYPE_ANY, NULL}, {"action", ItemParse_action, TYPE_ANY, NULL},
{"cvar", ItemParse_cvar, TYPE_ANY, NULL}, {"maxChars", ItemParse_maxChars, TYPE_EDIT, NULL},
{"maxPaintChars", ItemParse_maxPaintChars, TYPE_EDIT, NULL},
{"maxFieldWidth", ItemParse_maxFieldWidth, TYPE_EDIT, NULL}, {"focusSound", ItemParse_focusSound, TYPE_ANY, NULL},
{"cvarFloat", ItemParse_cvarFloat, TYPE_EDIT, NULL}, {"cvarStrList", ItemParse_cvarStrList, TYPE_MULTI, NULL},
{"cvarFloatList", ItemParse_cvarFloatList, TYPE_MULTI, NULL},
{"addColorRange", ItemParse_addColorRange, TYPE_ANY, NULL},
{"ownerdrawFlag", ItemParse_ownerdrawFlag, TYPE_ANY, NULL}, // hm.
{"enableCvar", ItemParse_enableCvar, TYPE_ANY, NULL}, {"cvarTest", ItemParse_cvarTest, TYPE_ANY, NULL},
{"disableCvar", ItemParse_disableCvar, TYPE_ANY, NULL}, {"showCvar", ItemParse_showCvar, TYPE_ANY, NULL},
{"hideCvar", ItemParse_hideCvar, TYPE_ANY, NULL}, {"cinematic", ItemParse_cinematic, TYPE_ANY, NULL},
{"doubleclick", ItemParse_doubleClick, TYPE_LIST, NULL}, {NULL, voidFunction2, 0, NULL}};
keywordHash_t *itemParseKeywordHash[KEYWORDHASH_SIZE];
/*
===============
Item_SetupKeywordHash
===============
*/
void Item_SetupKeywordHash(void)
{
int i;
memset(itemParseKeywordHash, 0, sizeof(itemParseKeywordHash));
for (i = 0; itemParseKeywords[i].keyword; i++)
KeywordHash_Add(itemParseKeywordHash, &itemParseKeywords[i]);
}
/*
===============
Item_Parse
===============
*/
qboolean Item_Parse(int handle, itemDef_t *item)
{
pc_token_t token;
keywordHash_t *key;
if (!trap_Parse_ReadToken(handle, &token))
return qfalse;
if (*token.string != '{')
return qfalse;
while (1)
{
if (!trap_Parse_ReadToken(handle, &token))
{
PC_SourceError(handle, "end of file inside menu item\n");
return qfalse;
}
if (*token.string == '}')
return qtrue;
key = KeywordHash_Find(itemParseKeywordHash, token.string);
if (!key)
{
PC_SourceError(handle, "unknown menu item keyword %s", token.string);
continue;
}
// do type-checks
if (key->param != TYPE_ANY)
{
itemDataType_t test = Item_DataType(item);
if (test != key->param)
{
if (test == TYPE_NONE)
PC_SourceError(handle,
"menu item keyword %s requires "
"type specification",
token.string);
else
PC_SourceError(handle,
"menu item keyword %s is incompatible with "
"specified item type",
token.string);
continue;
}
}
if (!key->func(item, handle))
{
PC_SourceError(handle, "couldn't parse menu item keyword %s", token.string);
return qfalse;
}
}
return qfalse;
}
// Item_InitControls
// init's special control types
void Item_InitControls(itemDef_t *item)
{
if (item == NULL)
return;
if (Item_IsListBox(item))
{
item->cursorPos = 0;
if (item->typeData.list)
{
item->typeData.list->cursorPos = 0;
Item_ListBox_SetStartPos(item, 0);
item->typeData.list->cursorPos = 0;
}
}
}
/*
===============
Menu Keyword Parse functions
===============
*/
qboolean MenuParse_font(itemDef_t *item, int handle)
{
menuDef_t *menu = (menuDef_t *)item;
if (!PC_String_Parse(handle, &menu->font))
return qfalse;
if (!DC->Assets.fontRegistered)
{
DC->registerFont(menu->font, 48, &DC->Assets.textFont);
DC->Assets.fontRegistered = qtrue;
}
return qtrue;
}
qboolean MenuParse_name(itemDef_t *item, int handle)
{
menuDef_t *menu = (menuDef_t *)item;
if (!PC_String_Parse(handle, &menu->window.name))
return qfalse;
return qtrue;
}
qboolean MenuParse_fullscreen(itemDef_t *item, int handle)
{
menuDef_t *menu = (menuDef_t *)item;
if (!PC_Int_Parse(handle, (int *)&menu->fullScreen))
return qfalse;
return qtrue;
}
qboolean MenuParse_rect(itemDef_t *item, int handle)
{
menuDef_t *menu = (menuDef_t *)item;
if (!PC_Rect_Parse(handle, &menu->window.rect))
return qfalse;
return qtrue;
}
qboolean MenuParse_aspectBias(itemDef_t *item, int handle)
{
menuDef_t *menu = (menuDef_t *)item;
if (!PC_Int_Parse(handle, &menu->window.aspectBias))
return qfalse;
return qtrue;
}
qboolean MenuParse_style(itemDef_t *item, int handle)
{
menuDef_t *menu = (menuDef_t *)item;
if (!PC_Int_Parse(handle, &menu->window.style))
return qfalse;
return qtrue;
}
qboolean MenuParse_visible(itemDef_t *item, int handle)
{
int i;
menuDef_t *menu = (menuDef_t *)item;
if (!PC_Int_Parse(handle, &i))
return qfalse;
if (i)
menu->window.flags |= WINDOW_VISIBLE;
return qtrue;
}
qboolean MenuParse_onOpen(itemDef_t *item, int handle)
{
menuDef_t *menu = (menuDef_t *)item;
if (!PC_Script_Parse(handle, &menu->onOpen))
return qfalse;
return qtrue;
}
qboolean MenuParse_onClose(itemDef_t *item, int handle)
{
menuDef_t *menu = (menuDef_t *)item;
if (!PC_Script_Parse(handle, &menu->onClose))
return qfalse;
return qtrue;
}
qboolean MenuParse_onESC(itemDef_t *item, int handle)
{
menuDef_t *menu = (menuDef_t *)item;
if (!PC_Script_Parse(handle, &menu->onESC))
return qfalse;
return qtrue;
}
qboolean MenuParse_border(itemDef_t *item, int handle)
{
menuDef_t *menu = (menuDef_t *)item;
if (!PC_Int_Parse(handle, &menu->window.border))
return qfalse;
return qtrue;
}
qboolean MenuParse_borderSize(itemDef_t *item, int handle)
{
menuDef_t *menu = (menuDef_t *)item;
if (!PC_Float_Parse(handle, &menu->window.borderSize))
return qfalse;
return qtrue;
}
qboolean MenuParse_backcolor(itemDef_t *item, int handle)
{
int i;
float f;
menuDef_t *menu = (menuDef_t *)item;
for (i = 0; i < 4; i++)
{
if (!PC_Float_Parse(handle, &f))
return qfalse;
menu->window.backColor[i] = f;
}
return qtrue;
}
qboolean MenuParse_forecolor(itemDef_t *item, int handle)
{
int i;
float f;
menuDef_t *menu = (menuDef_t *)item;
for (i = 0; i < 4; i++)
{
if (!PC_Float_Parse(handle, &f))
return qfalse;
menu->window.foreColor[i] = f;
menu->window.flags |= WINDOW_FORECOLORSET;
}
return qtrue;
}
qboolean MenuParse_bordercolor(itemDef_t *item, int handle)
{
int i;
float f;
menuDef_t *menu = (menuDef_t *)item;
for (i = 0; i < 4; i++)
{
if (!PC_Float_Parse(handle, &f))
return qfalse;
menu->window.borderColor[i] = f;
}
return qtrue;
}
qboolean MenuParse_focuscolor(itemDef_t *item, int handle)
{
int i;
float f;
menuDef_t *menu = (menuDef_t *)item;
for (i = 0; i < 4; i++)
{
if (!PC_Float_Parse(handle, &f))
return qfalse;
menu->focusColor[i] = f;
}
return qtrue;
}
qboolean MenuParse_disablecolor(itemDef_t *item, int handle)
{
int i;
float f;
menuDef_t *menu = (menuDef_t *)item;
for (i = 0; i < 4; i++)
{
if (!PC_Float_Parse(handle, &f))
return qfalse;
menu->disableColor[i] = f;
}
return qtrue;
}
qboolean MenuParse_outlinecolor(itemDef_t *item, int handle)
{
menuDef_t *menu = (menuDef_t *)item;
if (!PC_Color_Parse(handle, &menu->window.outlineColor))
return qfalse;
return qtrue;
}
qboolean MenuParse_background(itemDef_t *item, int handle)
{
const char *buff;
menuDef_t *menu = (menuDef_t *)item;
if (!PC_String_Parse(handle, &buff))
return qfalse;
menu->window.background = DC->registerShaderNoMip(buff);
return qtrue;
}
qboolean MenuParse_cinematic(itemDef_t *item, int handle)
{
menuDef_t *menu = (menuDef_t *)item;
if (!PC_String_Parse(handle, &menu->window.cinematicName))
return qfalse;
return qtrue;
}
qboolean MenuParse_ownerdrawFlag(itemDef_t *item, int handle)
{
int i;
menuDef_t *menu = (menuDef_t *)item;
if (!PC_Int_Parse(handle, &i))
return qfalse;
menu->window.ownerDrawFlags |= i;
return qtrue;
}
qboolean MenuParse_ownerdraw(itemDef_t *item, int handle)
{
menuDef_t *menu = (menuDef_t *)item;
if (!PC_Int_Parse(handle, &menu->window.ownerDraw))
return qfalse;
return qtrue;
}
// decoration
qboolean MenuParse_popup(itemDef_t *item, int handle)
{
menuDef_t *menu = (menuDef_t *)item;
menu->window.flags |= WINDOW_POPUP;
(void)handle;
return qtrue;
}
qboolean MenuParse_outOfBounds(itemDef_t *item, int handle)
{
menuDef_t *menu = (menuDef_t *)item;
menu->window.flags |= WINDOW_OOB_CLICK;
(void)handle;
return qtrue;
}
qboolean MenuParse_soundLoop(itemDef_t *item, int handle)
{
menuDef_t *menu = (menuDef_t *)item;
if (!PC_String_Parse(handle, &menu->soundName))
return qfalse;
return qtrue;
}
qboolean MenuParse_fadeClamp(itemDef_t *item, int handle)
{
menuDef_t *menu = (menuDef_t *)item;
if (!PC_Float_Parse(handle, &menu->fadeClamp))
return qfalse;
return qtrue;
}
qboolean MenuParse_fadeAmount(itemDef_t *item, int handle)
{
menuDef_t *menu = (menuDef_t *)item;
if (!PC_Float_Parse(handle, &menu->fadeAmount))
return qfalse;
return qtrue;
}
qboolean MenuParse_fadeCycle(itemDef_t *item, int handle)
{
menuDef_t *menu = (menuDef_t *)item;
if (!PC_Int_Parse(handle, &menu->fadeCycle))
return qfalse;
return qtrue;
}
qboolean MenuParse_itemDef(itemDef_t *item, int handle)
{
menuDef_t *menu = (menuDef_t *)item;
if (menu->itemCount < MAX_MENUITEMS)
{
menu->items[menu->itemCount] = UI_Alloc(sizeof(itemDef_t));
Item_Init(menu->items[menu->itemCount]);
if (!Item_Parse(handle, menu->items[menu->itemCount]))
return qfalse;
Item_InitControls(menu->items[menu->itemCount]);
menu->items[menu->itemCount++]->parent = menu;
}
else
{
PC_SourceError(handle, "itemDefs per menu may not exceed %d", MAX_MENUITEMS);
return qfalse;
}
return qtrue;
}
keywordHash_t menuParseKeywords[] = {{"font", MenuParse_font, 0, NULL}, {"name", MenuParse_name, 0, NULL},
{"fullscreen", MenuParse_fullscreen, 0, NULL}, {"rect", MenuParse_rect, 0, NULL},
{"aspectBias", MenuParse_aspectBias, 0, NULL}, {"style", MenuParse_style, 0, NULL},
{"visible", MenuParse_visible, 0, NULL}, {"onOpen", MenuParse_onOpen, 0, NULL},
{"onClose", MenuParse_onClose, 0, NULL}, {"onESC", MenuParse_onESC, 0, NULL}, {"border", MenuParse_border, 0, NULL},
{"borderSize", MenuParse_borderSize, 0, NULL}, {"backcolor", MenuParse_backcolor, 0, NULL},
{"forecolor", MenuParse_forecolor, 0, NULL}, {"bordercolor", MenuParse_bordercolor, 0, NULL},
{"focuscolor", MenuParse_focuscolor, 0, NULL}, {"disablecolor", MenuParse_disablecolor, 0, NULL},
{"outlinecolor", MenuParse_outlinecolor, 0, NULL}, {"background", MenuParse_background, 0, NULL},
{"ownerdraw", MenuParse_ownerdraw, 0, NULL}, {"ownerdrawFlag", MenuParse_ownerdrawFlag, 0, NULL},
{"outOfBoundsClick", MenuParse_outOfBounds, 0, NULL}, {"soundLoop", MenuParse_soundLoop, 0, NULL},
{"itemDef", MenuParse_itemDef, 0, NULL}, {"cinematic", MenuParse_cinematic, 0, NULL},
{"popup", MenuParse_popup, 0, NULL}, {"fadeClamp", MenuParse_fadeClamp, 0, NULL},
{"fadeCycle", MenuParse_fadeCycle, 0, NULL}, {"fadeAmount", MenuParse_fadeAmount, 0, NULL},
{NULL, voidFunction2, 0, NULL}};
keywordHash_t *menuParseKeywordHash[KEYWORDHASH_SIZE];
/*
===============
Menu_SetupKeywordHash
===============
*/
void Menu_SetupKeywordHash(void)
{
int i;
memset(menuParseKeywordHash, 0, sizeof(menuParseKeywordHash));
for (i = 0; menuParseKeywords[i].keyword; i++)
KeywordHash_Add(menuParseKeywordHash, &menuParseKeywords[i]);
}
/*
===============
Menu_Parse
===============
*/
qboolean Menu_Parse(int handle, menuDef_t *menu)
{
pc_token_t token;
keywordHash_t *key;
if (!trap_Parse_ReadToken(handle, &token))
return qfalse;
if (*token.string != '{')
return qfalse;
while (1)
{
memset(&token, 0, sizeof(pc_token_t));
if (!trap_Parse_ReadToken(handle, &token))
{
PC_SourceError(handle, "end of file inside menu\n");
return qfalse;
}
if (*token.string == '}')
return qtrue;
key = KeywordHash_Find(menuParseKeywordHash, token.string);
if (!key)
{
PC_SourceError(handle, "unknown menu keyword %s", token.string);
continue;
}
if (!key->func((itemDef_t *)menu, handle))
{
PC_SourceError(handle, "couldn't parse menu keyword %s", token.string);
return qfalse;
}
}
return qfalse;
}
/*
===============
Menu_New
===============
*/
void Menu_New(int handle)
{
menuDef_t *menu = &Menus[menuCount];
if (menuCount < MAX_MENUS)
{
Menu_Init(menu);
if (Menu_Parse(handle, menu))
{
Menu_PostParse(menu);
menuCount++;
}
}
}
int Menu_Count(void) { return menuCount; }
void Menu_UpdateAll(void)
{
int i;
for (i = 0; i < openMenuCount; i++)
Menu_Update(menuStack[i]);
}
void Menu_PaintAll(void)
{
int i;
if (g_editingField || g_waitingForKey)
DC->setCVar("ui_hideCursor", "1");
else
DC->setCVar("ui_hideCursor", "0");
if (captureFunc != voidFunction)
{
if (captureFuncExpiry > 0 && DC->realTime > captureFuncExpiry)
UI_RemoveCaptureFunc();
else
captureFunc(captureData);
}
for (i = 0; i < openMenuCount; i++)
Menu_Paint(menuStack[i], qfalse);
if (DC->getCVarValue("ui_developer"))
{
vec4_t v = {1, 1, 1, 1};
UI_Text_Paint(5, 25, .5, v, va("fps: %f", DC->FPS), 0, 0, 0);
}
}
void Menu_Reset(void) { menuCount = 0; }
displayContextDef_t *Display_GetContext(void) { return DC; }
void *Display_CaptureItem(int x, int y)
{
int i;
for (i = 0; i < menuCount; i++)
{
if (Rect_ContainsPoint(&Menus[i].window.rect, x, y))
return &Menus[i];
}
return NULL;
}
// FIXME:
qboolean Display_MouseMove(void *p, float x, float y)
{
int i;
menuDef_t *menu = p;
if (menu == NULL)
{
menu = Menu_GetFocused();
if (menu)
{
if (menu->window.flags & WINDOW_POPUP)
{
Menu_HandleMouseMove(menu, x, y);
return qtrue;
}
}
for (i = 0; i < menuCount; i++)
Menu_HandleMouseMove(&Menus[i], x, y);
}
else
{
menu->window.rect.x += x;
menu->window.rect.y += y;
Menu_UpdatePosition(menu);
}
return qtrue;
}
int Display_CursorType(int x, int y)
{
int i;
for (i = 0; i < menuCount; i++)
{
rectDef_t r2;
r2.x = Menus[i].window.rect.x - 3;
r2.y = Menus[i].window.rect.y - 3;
r2.w = r2.h = 7;
if (Rect_ContainsPoint(&r2, x, y))
return CURSOR_SIZER;
}
return CURSOR_ARROW;
}
void Display_HandleKey(int key, qboolean down, int x, int y)
{
menuDef_t *menu = Display_CaptureItem(x, y);
if (menu == NULL)
menu = Menu_GetFocused();
if (menu)
Menu_HandleKey(menu, key, down);
}
static void Window_CacheContents(Window *window)
{
if (window)
{
if (window->cinematicName)
{
int cin = DC->playCinematic(window->cinematicName, 0, 0, 0, 0);
DC->stopCinematic(cin);
}
}
}
static void Item_CacheContents(itemDef_t *item)
{
if (item)
Window_CacheContents(&item->window);
}
static void Menu_CacheContents(menuDef_t *menu)
{
if (menu)
{
int i;
Window_CacheContents(&menu->window);
for (i = 0; i < menu->itemCount; i++)
Item_CacheContents(menu->items[i]);
if (menu->soundName && *menu->soundName)
DC->registerSound(menu->soundName, qfalse);
}
}
void Display_CacheAll(void)
{
int i;
for (i = 0; i < menuCount; i++)
Menu_CacheContents(&Menus[i]);
}
static qboolean Menu_OverActiveItem(menuDef_t *menu, float x, float y)
{
if (menu && menu->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED))
{
if (Rect_ContainsPoint(&menu->window.rect, x, y))
{
int i;
for (i = 0; i < menu->itemCount; i++)
{
if (!(menu->items[i]->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED)))
continue;
if (menu->items[i]->window.flags & WINDOW_DECORATION)
continue;
if (Rect_ContainsPoint(&menu->items[i]->window.rect, x, y))
{
itemDef_t *overItem = menu->items[i];
if (overItem->type == ITEM_TYPE_TEXT && overItem->text)
{
if (Rect_ContainsPoint(Item_CorrectedTextRect(overItem), x, y))
return qtrue;
else
continue;
}
else
return qtrue;
}
}
}
}
return qfalse;
}