/** * @brief Generic tooltip function */ int UI_DrawTooltip (const char* string, int x, int y, int maxWidth) { const char* font = "f_small"; int height = 0, width = 0; if (Q_strnull(string) || !font) return 0; R_FontTextSize(font, string, maxWidth, LONGLINES_WRAP, &width, &height, nullptr, nullptr); if (!width) return 0; x += 5; y += 5; if (x + width + 3 > VID_NORM_WIDTH) x -= width + 10; if (y + height + 3 > VID_NORM_HEIGHT) y -= height + 10; UI_DrawFill(x - 1, y - 1, width + 4, height + 4, tooltipBG); R_Color(tooltipColor); UI_DrawString(font, ALIGN_UL, x + 1, y + 1, x + 1, maxWidth, 0, string); R_Color(nullptr); return width; }
/** * @brief Return a tab located at a screen position * @param[in] node A tab node * @param[in] x The x position of the screen to test * @param[in] y The x position of the screen to test * @return A uiNode_t, or NULL if nothing. * @todo improve test when we are on a junction * @todo improve test when we are on a chopped tab */ static uiNode_t* UI_TabNodeTabAtPosition (const uiNode_t *node, int x, int y) { const char *font; uiNode_t* option; uiNode_t* prev = NULL; int allowedWidth; UI_NodeAbsoluteToRelativePos(node, &x, &y); /** @todo this dont work when an option is hidden */ allowedWidth = node->size[0] - TILE_WIDTH * (EXTRADATACONST(node).count + 1); /* Bounded box test (shound not need, but there are problem) */ if (x < 0 || y < 0 || x >= node->size[0] || y >= node->size[1]) return NULL; font = UI_GetFontFromNode(node); /* Text box test */ for (option = node->firstChild; option; option = option->next) { int tabWidth; const char *label; assert(option->behaviour == ui_optionBehaviour); /* skip hidden options */ if (option->invis) continue; if (x < TILE_WIDTH / 2) return prev; label = OPTIONEXTRADATA(option).label; if (label[0] == '_') label = _(label + 1); R_FontTextSize(font, label, 0, LONGLINES_PRETTYCHOP, &tabWidth, NULL, NULL, NULL); if (OPTIONEXTRADATA(option).icon && OPTIONEXTRADATA(option).icon->size[0] < allowedWidth) { tabWidth += OPTIONEXTRADATA(option).icon->size[0]; } if (tabWidth > allowedWidth) { if (allowedWidth > 0) tabWidth = allowedWidth; else tabWidth = 0; } if (x < tabWidth + TILE_WIDTH) return option; allowedWidth -= tabWidth; x -= tabWidth + TILE_WIDTH; prev = option; } if (x < TILE_WIDTH / 2) return prev; return NULL; }
/** * @brief Handles line breaks and drawing for shared data id * @param[in] node The context node * @param[in] text The test to draw else nullptr * @param[in] list The test to draw else nullptr * @param[in] noDraw If true, calling of this function only update the cache (real number of lines) * @note text or list but be used, not both */ void uiTextNode::drawText (uiNode_t* node, const char* text, const linkedList_t* list, bool noDraw) { static char textCopy[UI_TEXTNODE_BUFFERSIZE]; char newFont[MAX_VAR]; const char* oldFont = nullptr; vec4_t colorHover; vec4_t colorSelectedHover; char* cur, *tab, *end; int fullSizeY; const char* font = UI_GetFontFromNode(node); vec2_t pos; int x, y, width; int viewSizeY; UI_GetNodeAbsPos(node, pos); if (isSizeChange(node)) { int lineHeight = EXTRADATA(node).lineHeight; if (lineHeight == 0) { const char* font = UI_GetFontFromNode(node); lineHeight = UI_FontGetHeight(font); } viewSizeY = node->box.size[1] / lineHeight; } else { viewSizeY = EXTRADATA(node).super.scrollY.viewSize; } /* text box */ x = pos[0] + node->padding; y = pos[1] + node->padding; width = node->box.size[0] - node->padding - node->padding; if (text) { Q_strncpyz(textCopy, text, sizeof(textCopy)); } else if (list) { Q_strncpyz(textCopy, CL_Translate((const char*)list->data), sizeof(textCopy)); } else return; /**< Nothing to draw */ cur = textCopy; /* Hover darkening effect for normal text lines. */ VectorScale(node->color, 0.8, colorHover); colorHover[3] = node->color[3]; /* Hover darkening effect for selected text lines. */ VectorScale(node->selectedColor, 0.8, colorSelectedHover); colorSelectedHover[3] = node->selectedColor[3]; /* fix position of the start of the draw according to the align */ switch (node->contentAlign % 3) { case 0: /* left */ break; case 1: /* middle */ x += width / 2; break; case 2: /* right */ x += width; break; } R_Color(node->color); fullSizeY = 0; do { bool haveTab; int x1; /* variable x position */ /* new line starts from node x position */ x1 = x; if (oldFont) { font = oldFont; oldFont = nullptr; } /* text styles and inline images */ if (cur[0] == '^') { switch (toupper(cur[1])) { case 'B': Com_sprintf(newFont, sizeof(newFont), "%s_bold", font); oldFont = font; font = newFont; cur += 2; /* don't print the format string */ break; } } /* get the position of the next newline - otherwise end will be null */ end = strchr(cur, '\n'); if (end) /* set the \n to \0 to draw only this part (before the \n) with our font renderer */ /* let end point to the next char after the \n (or \0 now) */ *end++ = '\0'; /* highlighting */ if (fullSizeY == EXTRADATA(node).textLineSelected && EXTRADATA(node).textLineSelected >= 0) { /* Draw current line in "selected" color (if the linenumber is stored). */ R_Color(node->selectedColor); } else { R_Color(node->color); } if (node->state && EXTRADATA(node).mousefx && fullSizeY == EXTRADATA(node).lineUnderMouse) { /* Highlight line if mousefx is true. */ /** @todo what about multiline text that should be highlighted completely? */ if (fullSizeY == EXTRADATA(node).textLineSelected && EXTRADATA(node).textLineSelected >= 0) { R_Color(colorSelectedHover); } else { R_Color(colorHover); } } /* tabulation, we assume all the tabs fit on a single line */ haveTab = strchr(cur, '\t') != nullptr; if (haveTab) { while (cur && *cur) { int tabwidth; tab = strchr(cur, '\t'); /* use tab stop as given via property definition * or use 1/3 of the node size (width) */ if (!EXTRADATA(node).tabWidth) tabwidth = width / 3; else tabwidth = EXTRADATA(node).tabWidth; if (tab) { int numtabs = strspn(tab, "\t"); tabwidth *= numtabs; while (*tab == '\t') *tab++ = '\0'; } else { /* maximize width for the last element */ tabwidth = width - (x1 - x); if (tabwidth < 0) tabwidth = 0; } /* minimize width for element outside node */ if ((x1 - x) + tabwidth > width) tabwidth = width - (x1 - x); /* make sure it is positive */ if (tabwidth < 0) tabwidth = 0; if (tabwidth != 0) UI_DrawString(font, (align_t)node->contentAlign, x1, y, x1, tabwidth - 1, EXTRADATA(node).lineHeight, cur, viewSizeY, EXTRADATA(node).super.scrollY.viewPos, &fullSizeY, false, LONGLINES_PRETTYCHOP); /* next */ x1 += tabwidth; cur = tab; } fullSizeY++; } /*Com_Printf("until newline - lines: %i\n", lines);*/ /* the conditional expression at the end is a hack to draw "/n/n" as a blank line */ /* prevent line from being drawn if there is nothing that should be drawn after it */ if (cur && (cur[0] || end || list)) { /* is it a white line? */ if (!cur) { fullSizeY++; } else { if (noDraw) { int lines = 0; R_FontTextSize(font, cur, width, (longlines_t)EXTRADATA(node).longlines, nullptr, nullptr, &lines, nullptr); fullSizeY += lines; } else UI_DrawString(font, (align_t)node->contentAlign, x1, y, x, width, EXTRADATA(node).lineHeight, cur, viewSizeY, EXTRADATA(node).super.scrollY.viewPos, &fullSizeY, true, (longlines_t)EXTRADATA(node).longlines); } } if (EXTRADATA(node).mousefx) R_Color(node->color); /* restore original color */ /* now set cur to the next char after the \n (see above) */ cur = end; if (!cur && list) { list = list->next; if (list) { Q_strncpyz(textCopy, CL_Translate((const char*)list->data), sizeof(textCopy)); cur = textCopy; } } } while (cur); /* update scroll status */ setScrollY(node, -1, viewSizeY, fullSizeY); R_Color(nullptr); }
void uiTabNode::draw (uiNode_t *node) { ui_tabStatus_t lastStatus = UI_TAB_NOTHING; uiNode_t* option; uiNode_t* overMouseOption = NULL; const char *ref; const char *font; int currentX; int allowedWidth; vec2_t pos; const char* image = UI_GetReferenceString(node, node->image); if (!image) image = "ui/tab"; ref = UI_AbstractOptionGetCurrentValue(node); if (ref == NULL) return; font = UI_GetFontFromNode(node); if (node->state) { overMouseOption = UI_TabNodeTabAtPosition(node, mousePosX, mousePosY); } UI_GetNodeAbsPos(node, pos); currentX = pos[0]; option = node->firstChild; assert(option->behaviour == ui_optionBehaviour); /** @todo this dont work when an option is hidden */ allowedWidth = node->box.size[0] - TILE_WIDTH * (EXTRADATA(node).count + 1); while (option) { int fontHeight; int fontWidth; int tabWidth; int textPos; bool drawIcon = false; ui_tabStatus_t status = UI_TAB_NORMAL; assert(option->behaviour == ui_optionBehaviour); /* skip hidden options */ if (option->invis) { option = option->next; continue; } /* Check the status of the current tab */ if (Q_streq(OPTIONEXTRADATA(option).value, ref)) { status = UI_TAB_SELECTED; } else if (option->disabled || node->disabled) { status = UI_TAB_DISABLED; } else if (option == overMouseOption) { status = UI_TAB_HIGHLIGHTED; } /* Display */ UI_TabNodeDrawJunction(image, currentX, pos[1], lastStatus, status); currentX += TILE_WIDTH; const char *label = CL_Translate(OPTIONEXTRADATA(option).label); R_FontTextSize(font, label, 0, LONGLINES_PRETTYCHOP, &fontWidth, &fontHeight, NULL, NULL); tabWidth = fontWidth; if (OPTIONEXTRADATA(option).icon && OPTIONEXTRADATA(option).icon->size[0] < allowedWidth) { tabWidth += OPTIONEXTRADATA(option).icon->size[0]; drawIcon = true; } if (tabWidth > allowedWidth) { if (allowedWidth > 0) tabWidth = allowedWidth; else tabWidth = 0; } if (tabWidth > 0) { UI_TabNodeDrawPlain(image, currentX, pos[1], tabWidth, status); } textPos = currentX; if (drawIcon) { uiSpriteStatus_t iconStatus = SPRITE_STATUS_NORMAL; if (status == UI_TAB_DISABLED) { iconStatus = SPRITE_STATUS_DISABLED; } UI_DrawSpriteInBox(OPTIONEXTRADATA(option).flipIcon, OPTIONEXTRADATA(option).icon, iconStatus, currentX, pos[1], OPTIONEXTRADATA(option).icon->size[0], TILE_HEIGHT); textPos += OPTIONEXTRADATA(option).icon->size[0]; } /** @todo fontWidth can be =0, maybe a bug from the font cache */ OPTIONEXTRADATA(option).truncated = tabWidth < fontWidth || tabWidth == 0; UI_DrawString(font, ALIGN_UL, textPos, pos[1] + ((node->box.size[1] - fontHeight) / 2), textPos, tabWidth + 1, 0, label, 0, 0, NULL, false, LONGLINES_PRETTYCHOP); currentX += tabWidth; allowedWidth -= tabWidth; /* Next */ lastStatus = status; option = option->next; } /* Display last junction and end of header */ UI_TabNodeDrawJunction(image, currentX, pos[1], lastStatus, UI_TAB_NOTHING); currentX += TILE_WIDTH; if (currentX < pos[0] + node->box.size[0]) UI_TabNodeDrawPlain(image, currentX, pos[1], pos[0] + node->box.size[0] - currentX, UI_TAB_NOTHING); }
/** * @note this function is a copy-paste of UI_ContainerNodeDrawItems (with remove of unneeded code) */ static Item* UI_BaseInventoryNodeGetItem (const uiNode_t* const node, int mouseX, int mouseY, int* contX, int* contY) { bool outOfNode = false; vec2_t nodepos; int items = 0; int rowHeight = 0; const int cellWidth = node->box.size[0] / EXTRADATACONST(node).columns; int tempX, tempY; containerItemIterator_t iterator; int currentHeight = 0; if (!contX) contX = &tempX; if (!contY) contY = &tempY; UI_GetNodeAbsPos(node, nodepos); UI_ContainerItemIteratorInit(&iterator, node); for (; iterator.itemID < csi.numODs; UI_ContainerItemIteratorNext(&iterator)) { const int id = iterator.itemID; const objDef_t* obj = INVSH_GetItemByIDX(id); vec2_t pos; vec2_t ammopos; const int col = items % EXTRADATACONST(node).columns; int cellHeight = 0; Item* icItem = iterator.itemFound; int height; /* skip items over and bellow the node view */ if (outOfNode || currentHeight < EXTRADATACONST(node).scrollY.viewPos) { int outHeight; R_FontTextSize("f_verysmall", _(obj->name), cellWidth - 5, LONGLINES_WRAP, nullptr, &outHeight, nullptr, nullptr); outHeight += obj->sy * C_UNIT + 10; if (outHeight > rowHeight) rowHeight = outHeight; if (outOfNode || currentHeight + rowHeight < EXTRADATACONST(node).scrollY.viewPos) { if (col == EXTRADATACONST(node).columns - 1) { currentHeight += rowHeight; rowHeight = 0; } items++; continue; } } Vector2Copy(nodepos, pos); pos[0] += cellWidth * col; pos[1] += currentHeight - EXTRADATACONST(node).scrollY.viewPos; /* check item */ if (mouseY < pos[1]) break; if (mouseX >= pos[0] && mouseX < pos[0] + obj->sx * C_UNIT && mouseY >= pos[1] && mouseY < pos[1] + obj->sy * C_UNIT) { if (icItem) { *contX = icItem->getX(); *contY = icItem->getY(); return icItem; } return nullptr; } pos[1] += obj->sy * C_UNIT; cellHeight += obj->sy * C_UNIT; /* save position for ammo */ Vector2Copy(pos, ammopos); ammopos[0] += obj->sx * C_UNIT + 10; /* draw the item name. */ R_FontTextSize("f_verysmall", _(obj->name), cellWidth - 5, LONGLINES_WRAP, nullptr, &height, nullptr, nullptr); cellHeight += height; /* draw ammos of weapon */ if (obj->weapon && EXTRADATACONST(node).displayAmmoOfWeapon) { for (int ammoIdx = 0; ammoIdx < obj->numAmmos; ammoIdx++) { const objDef_t* objammo = obj->ammos[ammoIdx]; /* skip unusable ammo */ if (!GAME_ItemIsUseable(objammo)) continue; /* find and skip none existing ammo */ icItem = UI_ContainerNodeGetExistingItem(node, objammo, (itemFilterTypes_t) EXTRADATACONST(node).filterEquipType); if (!icItem) continue; /* check ammo (ammopos in on the left-lower corner) */ if (mouseX < ammopos[0] || mouseY >= ammopos[1]) break; if (mouseX >= ammopos[0] && mouseX < ammopos[0] + objammo->sx * C_UNIT && mouseY >= ammopos[1] - objammo->sy * C_UNIT && mouseY < ammopos[1]) { *contX = icItem->getX(); *contY = icItem->getY(); return icItem; } ammopos[0] += objammo->sx * C_UNIT; } } cellHeight += 10; if (cellHeight > rowHeight) { rowHeight = cellHeight; } /* add a margin between rows */ if (col == EXTRADATACONST(node).columns - 1) { currentHeight += rowHeight; rowHeight = 0; if (currentHeight - EXTRADATACONST(node).scrollY.viewPos >= node->box.size[1]) return nullptr; } /* count items */ items++; } *contX = NONE; *contY = NONE; return nullptr; }
/** * @brief Draw the base inventory * @return The full height requested by the current view (not the node height) */ static int UI_BaseInventoryNodeDrawItems (uiNode_t* node, const objDef_t* highlightType) { bool outOfNode = false; vec2_t nodepos; int items = 0; int rowHeight = 0; const int cellWidth = node->box.size[0] / EXTRADATA(node).columns; containerItemIterator_t iterator; int currentHeight = 0; UI_GetNodeAbsPos(node, nodepos); UI_ContainerItemIteratorInit(&iterator, node); for (; iterator.itemID < csi.numODs; UI_ContainerItemIteratorNext(&iterator)) { const int id = iterator.itemID; const objDef_t* obj = INVSH_GetItemByIDX(id); Item tempItem(obj, nullptr, 1); vec3_t pos; vec3_t ammopos; const float* color; bool isHighlight = false; int amount; const int col = items % EXTRADATA(node).columns; int cellHeight = 0; const Item* icItem = iterator.itemFound; /* skip items over and bellow the node view */ if (outOfNode || currentHeight < EXTRADATA(node).scrollY.viewPos) { int height; R_FontTextSize("f_verysmall", _(obj->name), cellWidth - 5, LONGLINES_WRAP, nullptr, &height, nullptr, nullptr); height += obj->sy * C_UNIT + 10; if (height > rowHeight) rowHeight = height; if (outOfNode || currentHeight + rowHeight < EXTRADATA(node).scrollY.viewPos) { if (col == EXTRADATA(node).columns - 1) { currentHeight += rowHeight; rowHeight = 0; } items++; continue; } } Vector2Copy(nodepos, pos); pos[0] += cellWidth * col; pos[1] += currentHeight - EXTRADATA(node).scrollY.viewPos; pos[2] = 0; if (highlightType) { if (obj->isAmmo()) isHighlight = obj->isLoadableInWeapon(highlightType); else isHighlight = highlightType->isLoadableInWeapon(obj); } if (icItem != nullptr) { if (isHighlight) color = colorLoadable; else color = colorDefault; } else { if (isHighlight) color = colorDisabledLoadable; else color = colorDisabledHiden; } if (icItem) amount = icItem->getAmount(); else amount = 0; /* draw item */ pos[0] += obj->sx * C_UNIT / 2.0; pos[1] += obj->sy * C_UNIT / 2.0; UI_DrawItem(node, pos, &tempItem, -1, -1, scale, color); UI_DrawString("f_verysmall", ALIGN_LC, pos[0] + obj->sx * C_UNIT / 2.0, pos[1] + obj->sy * C_UNIT / 2.0, pos[0] + obj->sx * C_UNIT / 2.0, cellWidth - 5, /* maxWidth */ 0, va("x%i", amount)); pos[0] -= obj->sx * C_UNIT / 2.0; pos[1] += obj->sy * C_UNIT / 2.0; cellHeight += obj->sy * C_UNIT; /* save position for ammo */ Vector2Copy(pos, ammopos); ammopos[2] = 0; ammopos[0] += obj->sx * C_UNIT + 10; /* draw the item name. */ cellHeight += UI_DrawString("f_verysmall", ALIGN_UL, pos[0], pos[1], pos[0], cellWidth - 5, /* max width */ 0, _(obj->name)); /* draw ammos of weapon */ if (obj->weapon && EXTRADATA(node).displayAmmoOfWeapon) { for (int ammoIdx = 0; ammoIdx < obj->numAmmos; ammoIdx++) { tempItem.setDef(obj->ammos[ammoIdx]); /* skip weapos that are their own ammo -- oneshot and such */ if (obj == tempItem.def()) continue; /* skip unusable ammo */ if (!GAME_ItemIsUseable(tempItem.def())) continue; /* find and skip none existing ammo */ icItem = UI_ContainerNodeGetExistingItem(node, tempItem.def(), (itemFilterTypes_t) EXTRADATA(node).filterEquipType); if (!icItem) continue; /* Calculate the center of the item model/image. */ ammopos[0] += icItem->def()->sx * C_UNIT / 2.0; ammopos[1] -= icItem->def()->sy * C_UNIT / 2.0; UI_DrawItem(node, ammopos, &tempItem, -1, -1, scale, colorDefault); UI_DrawString("f_verysmall", ALIGN_LC, ammopos[0] + icItem->def()->sx * C_UNIT / 2.0, ammopos[1] + icItem->def()->sy * C_UNIT / 2.0, ammopos[0] + icItem->def()->sx * C_UNIT / 2.0, cellWidth - 5 - ammopos[0], /* maxWidth */ 0, va("x%i", icItem->getAmount())); ammopos[0] += icItem->def()->sx * C_UNIT / 2.0; ammopos[1] += icItem->def()->sy * C_UNIT / 2.0; } } cellHeight += 10; if (cellHeight > rowHeight) { rowHeight = cellHeight; } /* add a marge between rows */ if (col == EXTRADATA(node).columns - 1) { currentHeight += rowHeight; rowHeight = 0; if (currentHeight - EXTRADATA(node).scrollY.viewPos >= node->box.size[1]) outOfNode = true; } /* count items */ items++; } if (rowHeight != 0) { currentHeight += rowHeight; } return currentHeight; }