/** * @brief Get the line number under an absolute position * @param[in] node a text node * @param[in] x,y position on the screen * @return The line number under the position (0 = first line) */ static int UI_TextNodeGetLine (const uiNode_t* node, int x, int y) { int lineHeight; int line; assert(UI_NodeInstanceOf(node, "text")); lineHeight = EXTRADATACONST(node).lineHeight; if (lineHeight == 0) { const char* font = UI_GetFontFromNode(node); lineHeight = UI_FontGetHeight(font); } UI_NodeAbsoluteToRelativePos(node, &x, &y); y -= node->padding; /* skip position over the first line */ if (y < 0) return -1; line = (int) (y / lineHeight) + EXTRADATACONST(node).super.scrollY.viewPos; /* skip position under the last line */ if (line >= EXTRADATACONST(node).super.scrollY.fullSize) return -1; return line; }
/** * @brief Use a container node to init an item iterator */ static void UI_ContainerItemIteratorInit (containerItemIterator_t *iterator, const uiNode_t* const node) { int groupID = 0; iterator->itemID = -1; iterator->groupID = 0; iterator->node = node; iterator->filterEquipType = (itemFilterTypes_t) EXTRADATACONST(node).filterEquipType; if (EXTRADATACONST(node).displayAvailableOnTop) { /* available items */ if (EXTRADATACONST(node).displayWeapon) iterator->groupSteps[groupID++] = CII_WEAPONONLY | CII_AVAILABLEONLY; if (EXTRADATACONST(node).displayAmmo) iterator->groupSteps[groupID++] = CII_AMMOONLY | CII_AVAILABLEONLY; /* unavailable items */ if (EXTRADATACONST(node).displayUnavailableItem) { if (EXTRADATACONST(node).displayWeapon) iterator->groupSteps[groupID++] = CII_WEAPONONLY | CII_NOTAVAILABLEONLY; if (EXTRADATACONST(node).displayAmmo) iterator->groupSteps[groupID++] = CII_AMMOONLY | CII_NOTAVAILABLEONLY; } } else { const int filter = (EXTRADATACONST(node).displayUnavailableItem) ? 0 : CII_AVAILABLEONLY; if (EXTRADATACONST(node).displayWeapon) iterator->groupSteps[groupID++] = CII_WEAPONONLY | filter; if (EXTRADATACONST(node).displayAmmo) iterator->groupSteps[groupID++] = CII_AMMOONLY | filter; } iterator->groupSteps[groupID++] = CII_END; /* find the first item */ UI_ContainerItemIteratorNext(iterator); }
/** * @brief Get the line number under an absolute position * @param[in] node a text node * @param[in] x position x on the screen * @param[in] y position y on the screen * @return The line number under the position (0 = first line) */ static int UI_TextListNodeGetLine (const uiNode_t *node, int x, int y) { int lineHeight = EXTRADATACONST(node).lineHeight; if (lineHeight == 0) { const char *font = UI_GetFontFromNode(node); lineHeight = UI_FontGetHeight(font); } UI_NodeAbsoluteToRelativePos(node, &x, &y); y -= node->padding; return (int) (y / lineHeight) + EXTRADATACONST(node).super.scrollY.viewPos; }
/** * @brief Gets location of the item the mouse is over * @param[in] node The container-node. * @param[in] mouseX X location of the mouse. * @param[in] mouseY Y location of the mouse. * @param[out] contX X location in the container (index of item in row). * @param[out] contY Y location in the container (row). * @sa UI_ContainerNodeSearchInScrollableContainer */ static invList_t *UI_ContainerNodeGetItemAtPosition (const uiNode_t* const node, int mouseX, int mouseY, int* contX = NULL, int* contY = NULL) { invList_t *result = NULL; if (!ui_inventory) return NULL; /* Get coordinates inside a scrollable container (if it is one). */ if (UI_IsScrollContainerNode(node)) { Sys_Error("UI_ContainerNodeGetItemAtPosition is not usable for scrollable containers!"); } else { vec2_t nodepos; int fromX, fromY; UI_GetNodeAbsPos(node, nodepos); /* Normalize screen coordinates to container coordinates. */ fromX = (int) (mouseX - nodepos[0]) / C_UNIT; fromY = (int) (mouseY - nodepos[1]) / C_UNIT; if (contX) *contX = fromX; if (contY) *contY = fromY; result = INVSH_SearchInInventory(ui_inventory, EXTRADATACONST(node).container, fromX, fromY); } return result; }
/** * @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 Custom tooltip * @param[in] node Node we request to draw tooltip * @param[in] x Position x of the mouse * @param[in] y Position y of the mouse */ void uiBaseMapNode::drawTooltip (uiNode_t *node, int x, int y) { int col, row; getCellAtPos(node, x, y, &col, &row); if (col == -1) return; GAME_DrawBaseTooltip(EXTRADATACONST(node).baseid, x, y, col, row); }
/** * @brief Search a a key binding from a window node. * Window node store key bindings for his node child. * @param node A window node * @param key A key code, either K_ value or lowercase ascii */ uiKeyBinding_t* UI_WindowNodeGetKeyBinding (uiNode_t const* const node, unsigned int key) { uiKeyBinding_t* binding = EXTRADATACONST(node).keyList; assert(UI_NodeInstanceOf(node, "window")); while (binding) { if (binding->key == key) break; binding = binding->next; } return binding; }
/** * @brief Middle click on the basemap * @sa UI_BaseMapNodeClick * @param[in] node Node definition for the base map * @param[in] x The x screen coordinate * @param[in] y The y screen coordinate * @note relies on @c baseCurrent */ void uiBaseMapNode::onMiddleClick (uiNode_t *node, int x, int y) { assert(node); assert(node->root); int row, col; getCellAtPos(node, x, y, &col, &row); if (col == -1) return; GAME_HandleBaseClick(EXTRADATACONST(node).baseid, K_MOUSE3, col, row); }
/** * @brief True if the window is a modal. * @param node A window node * @return True if the window is a modal. */ bool UI_WindowIsModal(uiNode_t const* const node) { return EXTRADATACONST(node).modal; }
/** * @brief True if the window is a drop down. * @param node A window node * @return True if the window is a drop down. */ bool UI_WindowIsDropDown(uiNode_t const* const node) { return EXTRADATACONST(node).dropdown; }
/** * @brief Check if a window is fullscreen or not */ bool UI_WindowIsFullScreen (const uiNode_t* const node) { assert(UI_NodeInstanceOf(node, "window")); return EXTRADATACONST(node).isFullScreen; }
static void UI_PanelNodeGetClientPosition (const uiNode_t *node, vec2_t position) { position[0] = -EXTRADATACONST(node).super.scrollX.viewPos; position[1] = -EXTRADATACONST(node).super.scrollY.viewPos; }
/** * @brief Searches if there is an item at location (x/y) in a scrollable container. You can also provide an item to search for directly (x/y is ignored in that case). * @note x = x-th item in a row, y = row. i.e. x/y does not equal the "grid" coordinates as used in those containers. * @param[in] node Context node * @param[in] item Item requested * @param[in] filterType Filter used. * @todo Remove filter it is not a generic concept, and here it mean nothing * @return Item Pointer to the Item/item that is located at x/y or equals "item". * @sa Inventory::getItemAtPos */ static Item* UI_ContainerNodeGetExistingItem (const uiNode_t* node, const objDef_t* item, const itemFilterTypes_t filterType) { return INV_SearchInInventoryWithFilter(ui_inventory, EXTRADATACONST(node).super.container, item, filterType); }
/** * @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 Call into the source when the DND end */ bool uiContainerNode::onDndFinished (uiNode_t *source, bool isDropped) { item_t *dragItem = UI_DNDGetItem(); const invDef_t *sourceContainer = EXTRADATACONST(source).container; /* if the target can't finalize the DND we stop */ if (!isDropped) { return false; } assert(sourceContainer); /* on tactical mission */ if (CL_BattlescapeRunning()) { const uiNode_t *target = UI_DNDGetTargetNode(); const invDef_t *targetContainer = EXTRADATACONST(target).container; assert(targetContainer); CL_ActorInvMove(selActor, sourceContainer->id, dragInfoFromX, dragInfoFromY, targetContainer->id, dragInfoToX, dragInfoToY); } else { uiNode_t *target = UI_DNDGetTargetNode(); if (target) { invList_t *fItem; invList_t *tItem; bool moved = false; const invDef_t *targetContainer = EXTRADATACONST(target).container; assert(targetContainer); if (UI_IsScrollContainerNode(source)) { fItem = UI_ContainerNodeGetExistingItem(sourceContainer, dragItem->item, MAX_FILTERTYPES); } else fItem = INVSH_SearchInInventory(ui_inventory, sourceContainer, dragInfoFromX, dragInfoFromY); assert(fItem); /** @todo We must split the move in two. Here, we should not know how to add the item to the target (see dndDrop) */ /* Remove ammo on removing weapon from a soldier */ if (UI_IsScrollContainerNode(target) && fItem->item.ammo && fItem->item.ammo != fItem->item.item && fItem->item.ammoLeft) INV_UnloadWeapon(fItem, ui_inventory, targetContainer); /* rotate on Shift */ /** @todo enable Shift-rotate for battlescape too when issues are solved */ fItem->item.rotated = Key_IsDown(K_SHIFT) && !CL_BattlescapeRunning(); /* move the item */ moved = INV_MoveItem(ui_inventory, targetContainer, dragInfoToX, dragInfoToY, sourceContainer, fItem, &tItem); /* No need to continue move wasn't successful */ if (!moved) return false; /* Add ammo on adding weapon to a soldier */ if (UI_IsScrollContainerNode(source) && ((fItem->item.item->weapon && !fItem->item.ammoLeft) || fItem->item.item->oneshot)) INV_LoadWeapon(tItem, ui_inventory, sourceContainer, targetContainer); /* Run onChange events */ if (source->onChange) UI_ExecuteEventActions(source, source->onChange); if (source != target && target->onChange) UI_ExecuteEventActions(target, target->onChange); } } dragInfoFromX = -1; dragInfoFromY = -1; return true; }
static inline bool UI_IsScrollContainerNode (const uiNode_t* const node) { return EXTRADATACONST(node).container && EXTRADATACONST(node).container->scroll; }