/* =========================================================================== 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; }