/** * @brief Add a callback of a function into a node event. There can be more than on listener. * @param[in,out] node The node to add the listener to. * @param[in] property The property of the node to add the listener to. * @param[in] functionNode The node of the listener callback. */ void UI_AddListener (uiNode_t *node, const value_t *property, const uiNode_t *functionNode) { uiAction_t *lastAction; uiAction_t *action; uiAction_t *value; if (node->dynamic) { Com_Printf("UI_AddListener: '%s' is a dynamic node. We can't listen it.\n", UI_GetPath(node)); return; } if (functionNode->dynamic) { Com_Printf("UI_AddListener: '%s' is a dynamic node. It can't be a listener callback.\n", UI_GetPath(functionNode)); return; } /* create the call action */ action = (uiAction_t*) Mem_PoolAlloc(sizeof(*action), ui_sysPool, 0); value = (uiAction_t*) Mem_PoolAlloc(sizeof(*action), ui_sysPool, 0); value->d.terminal.d1.constString = Mem_PoolStrDup(UI_GetPath(functionNode), ui_sysPool, 0); value->next = NULL; action->type = EA_LISTENER; action->d.nonTerminal.left = value; /** @todo It is a hack, we should remove that */ action->d.terminal.d2.constData = &functionNode->onClick; action->next = NULL; /* insert the action */ lastAction = *(uiAction_t**)((char*)node + property->ofs); if (lastAction) { while (lastAction->next) lastAction = lastAction->next; lastAction->next = action; } else *(uiAction_t**)((char*)node + property->ofs) = action; }
/** * @brief Add a callback of a function into a node event. There can be more than on listener. * @param[in,out] node The node to add the listener to. * @param[in] property The property of the node to add the listener to. * @param[in] functionNode The node of the listener callback. */ void UI_AddListener (uiNode_t *node, const value_t *property, const uiNode_t *functionNode) { if (node->dynamic) { Com_Printf("UI_AddListener: '%s' is a dynamic node. We can't listen it.\n", UI_GetPath(node)); return; } if (functionNode->dynamic) { Com_Printf("UI_AddListener: '%s' is a dynamic node. It can't be a listener callback.\n", UI_GetPath(functionNode)); return; } /* create the call action */ uiAction_t* const action = Mem_PoolAllocType(uiAction_t, ui_sysPool); uiAction_t* const value = Mem_PoolAllocType(uiAction_t, ui_sysPool); value->d.terminal.d1.constString = Mem_PoolStrDup(UI_GetPath(functionNode), ui_sysPool, 0); value->next = NULL; action->type = EA_LISTENER; action->d.nonTerminal.left = value; /** @todo It is a hack, we should remove that */ action->d.terminal.d2.constData = &functionNode->onClick; action->next = NULL; /* insert the action */ uiAction_t** anchor = &Com_GetValue<uiAction_t*>(node, property); while (*anchor) anchor = &(*anchor)->next; *anchor = action; }
/** * Parse a string according to a property type, and allocate a raw value to the static memory * * @param action Action to initialize * @param node Current node we are parsing, only used for error message * @param property Type of the value to parse, if nullptr the string is not stored as string * @param string String value to parse * @return True if the action is initialized * @todo remove node param and catch error where we call that function */ bool UI_InitRawActionValue (uiAction_t* action, uiNode_t* node, const value_t* property, const char* string) { if (property == nullptr) { action->type = EA_VALUE_STRING; action->d.terminal.d1.data = UI_AllocStaticString(string, 0); action->d.terminal.d2.integer = 0; return true; } if (property->type == V_UI_SPRITEREF) { uiSprite_t* sprite = UI_GetSpriteByName(string); if (sprite == nullptr) { Com_Printf("UI_ParseSetAction: sprite '%s' not found (%s)\n", string, UI_GetPath(node)); return false; } action->type = EA_VALUE_RAW; action->d.terminal.d1.data = sprite; action->d.terminal.d2.integer = property->type; return true; } else { const int baseType = property->type & V_UI_MASK; if (baseType != 0 && baseType != V_UI_CVAR) { Com_Printf("UI_ParseRawValue: setter for property '%s' (type %d, 0x%X) is not supported (%s)\n", property->string, property->type, property->type, UI_GetPath(node)); return false; } ui_global.curadata = (byte*) Com_AlignPtr(ui_global.curadata, (valueTypes_t) (property->type & V_BASETYPEMASK)); action->type = EA_VALUE_RAW; action->d.terminal.d1.data = ui_global.curadata; action->d.terminal.d2.integer = property->type; /** @todo we should hide use of ui_global.curadata */ ui_global.curadata += Com_EParseValue(ui_global.curadata, string, (valueTypes_t) (property->type & V_BASETYPEMASK), 0, property->size); return true; } }
static bool UI_ParseEventProperty (uiNode_t* node, const value_t* event, const char** text, const char** token, const char* errhead) { /* add new actions to end of list */ uiAction_t** action = &Com_GetValue<uiAction_t*>(node, event); for (; *action; action = &(*action)->next) {} /* get the action body */ *token = Com_EParse(text, errhead, node->name); if (!*text) return false; if ((*token)[0] != '{') { Com_Printf("UI_ParseEventProperty: Event '%s' without body (%s)\n", event->string, UI_GetPath(node)); return false; } *action = UI_ParseActionList(node, text, token); if (*action == nullptr) return false; /* block terminal already read */ assert((*token)[0] == '}'); return true; }
/** * @brief Return a float from a node property * @param[in] node Requested node * @param[in] property Requested property * @return Return the float value of a property, else 0, if the type is not supported * @note If the type is not supported, a waring is reported to the console */ float UI_GetFloatFromNodeProperty (const uiNode_t* node, const value_t* property) { assert(node); if (property->type == V_FLOAT) { return Com_GetValue<float>(node, property); } else if ((property->type & V_UI_MASK) == V_UI_CVAR) { void* const b = Com_GetValue<void*>(node, property); if (char const* const cvarName = Q_strstart((char const*)b, "*cvar:")) { const cvar_t* cvar = Cvar_Get(cvarName, "", 0, "UI script cvar property"); return cvar->value; } else if (property->type == V_CVAR_OR_FLOAT) { return *(const float*) b; } else if (property->type == V_CVAR_OR_STRING) { return atof((const char*)b); } } else if (property->type == V_INT) { return Com_GetValue<int>(node, property); } else if (property->type == V_BOOL) { return Com_GetValue<bool>(node, property); } else { #ifdef DEBUG Com_Printf("UI_GetFloatFromNodeProperty: Unimplemented float getter for property '%s@%s'. If it should return a float, request it.\n", UI_GetPath(node), property->string); #else Com_Printf("UI_GetFloatFromNodeProperty: Property '%s@%s' can't return a float\n", UI_GetPath(node), property->string); #endif } return 0; }
/** * @brief Returns the absolute position of a node in the screen. * Screen position is not used for the node rendering cause we use OpenGL * translations. But this function is need for some R_functions methodes. * @param[in] node Context node * @param[out] pos Absolute position into the screen */ void UI_GetNodeScreenPos (const uiNode_t* node, vec2_t pos) { assert(node); assert(pos); /* if we request the position of a non drawable node, there is a problem */ if (node->behaviour->isVirtual) Com_Error(ERR_FATAL, "UI_GetNodeAbsPos: Node '%s' doesn't have a position", node->name); Vector2Set(pos, 0, 0); while (node) { #ifdef DEBUG if (node->box.pos[0] != (int)node->box.pos[0] || node->box.pos[1] != (int)node->box.pos[1]) Com_Error(ERR_FATAL, "UI_GetNodeAbsPos: Node '%s' position %f,%f is not integer", UI_GetPath(node), node->box.pos[0], node->box.pos[1]); #endif pos[0] += node->box.pos[0]; pos[1] += node->box.pos[1]; node = node->parent; if (node && UI_Node_IsScrollableContainer(node)) { vec2_t clientPosition = {0, 0}; UI_Node_GetClientPosition(node, clientPosition); pos[0] += clientPosition[0]; pos[1] += clientPosition[1]; } } }
const char* UI_Node_GetText (uiNode_t* node) { #ifdef DEBUG if (node->text == nullptr) { Com_Printf("warning: requesting uninitialized node->text from [%s]\n", UI_GetPath(node)); } #endif // DEBUG return (node->text? node->text : ""); }
static inline void UI_ExecuteSetAction (const uiAction_t* action, const uiCallContext_t *context) { const char* path; uiNode_t *node; const value_t *property; const uiAction_t *left; uiAction_t *right; left = action->d.nonTerminal.left; if (left == NULL) { Com_Printf("UI_ExecuteSetAction: Action without left operand skipped.\n"); return; } right = action->d.nonTerminal.right; if (right == NULL) { Com_Printf("UI_ExecuteSetAction: Action without right operand skipped.\n"); return; } if (left->type == EA_VALUE_CVARNAME || left->type == EA_VALUE_CVARNAME_WITHINJECTION) { const char *cvarName; const char* textValue; if (left->type == EA_VALUE_CVARNAME) cvarName = left->d.terminal.d1.constString; else cvarName = UI_GenInjectedString(left->d.terminal.d1.constString, false, context); textValue = UI_GetStringFromExpression(right, context); if (textValue[0] == '_') textValue = _(textValue + 1); Cvar_ForceSet(cvarName, textValue); return; } /* search the node */ if (left->type == EA_VALUE_PATHPROPERTY) path = left->d.terminal.d1.constString; else if (left->type == EA_VALUE_PATHPROPERTY_WITHINJECTION) path = UI_GenInjectedString(left->d.terminal.d1.constString, false, context); else Com_Error(ERR_FATAL, "UI_ExecuteSetAction: Property setter with wrong type '%d'", left->type); UI_ReadNodePath(path, context->source, &node, &property); if (!node) { Com_Printf("UI_ExecuteSetAction: node \"%s\" doesn't exist (source: %s)\n", path, UI_GetPath(context->source)); return; } if (!property) { Com_Printf("UI_ExecuteSetAction: property \"%s\" doesn't exist (source: %s)\n", path, UI_GetPath(context->source)); return; } UI_NodeSetPropertyFromActionValue(node, property, context, right); }
/** * @sa UI_ParseNodeProperties * @brief parse all sequencial properties into a block * @note allow to use an extra block * @code * foobehaviour foonode { * { properties } * // the function stop reading here * nodes * } * foobehaviour foonode { * properties * // the function stop reading here * nodes * } * @endcode */ static bool UI_ParseNodeProperties (uiNode_t* node, const char** text, const char** token) { const char* errhead = "UI_ParseNodeProperties: unexpected end of file (node"; bool nextTokenAlreadyRead = false; if ((*token)[0] != '{') nextTokenAlreadyRead = true; do { const value_t* val; int result; /* get new token */ if (!nextTokenAlreadyRead) { *token = Com_EParse(text, errhead, node->name); if (!*text) return false; } else { nextTokenAlreadyRead = false; } /* is finished */ if ((*token)[0] == '}') break; /* find the property */ val = UI_GetPropertyFromBehaviour(node->behaviour, *token); if (!val) { /* unknown token, print message and continue */ Com_Printf("UI_ParseNodeProperties: unknown property \"%s\", node ignored (node %s)\n", *token, UI_GetPath(node)); return false; } /* get parameter values */ result = UI_ParseProperty(node, val, node->name, text, token); if (!result) { Com_Printf("UI_ParseNodeProperties: Problem with parsing of node property '%s@%s'. See upper\n", UI_GetPath(node), val->string); return false; } } while (*text); return true; }
/** * @brief Execute a tree of actions from a source * @param[in] context Context node * @param[in] firstAction Action to execute */ static void UI_ExecuteActions (const uiAction_t* firstAction, uiCallContext_t* context) { static int callnumber = 0; if (callnumber++ > 20) { Com_Printf("UI_ExecuteActions: Break possible infinite recursion, source [%s]\n", UI_GetPath(context->source)); return; } for (const uiAction_t* action = firstAction; action && !context->breakLoop; action = action->next) { UI_ExecuteAction(action, context); } callnumber--; }
void uiTextNode::onLoaded (uiNode_t* node) { int lineheight = EXTRADATA(node).lineHeight; /* auto compute lineheight */ /* we don't overwrite EXTRADATA(node).lineHeight, because "0" is dynamically replaced by font height on draw function */ if (lineheight == 0) { /* the font is used */ const char* font = UI_GetFontFromNode(node); lineheight = UI_FontGetHeight(font); } /* auto compute rows (super.viewSizeY) */ if (EXTRADATA(node).super.scrollY.viewSize == 0) { if (node->box.size[1] != 0 && lineheight != 0) { EXTRADATA(node).super.scrollY.viewSize = node->box.size[1] / lineheight; } else { EXTRADATA(node).super.scrollY.viewSize = 1; Com_Printf("UI_TextNodeLoaded: node '%s' has no rows value\n", UI_GetPath(node)); } } /* auto compute height */ if (node->box.size[1] == 0) { node->box.size[1] = EXTRADATA(node).super.scrollY.viewSize * lineheight; } /* is text slot exists */ if (EXTRADATA(node).dataID >= UI_MAX_DATAID) Com_Error(ERR_DROP, "Error in node %s - max shared data id num exceeded (num: %i, max: %i)", UI_GetPath(node), EXTRADATA(node).dataID, UI_MAX_DATAID); #ifdef DEBUG if (EXTRADATA(node).super.scrollY.viewSize != (int)(node->box.size[1] / lineheight)) { Com_Printf("UI_TextNodeLoaded: rows value (%i) of node '%s' differs from size (%.0f) and format (%i) values\n", EXTRADATA(node).super.scrollY.viewSize, UI_GetPath(node), node->box.size[1], lineheight); } #endif if (node->text == nullptr && EXTRADATA(node).dataID == TEXT_NULL) Com_Printf("UI_TextNodeLoaded: 'textid' property of node '%s' is not set\n", UI_GetPath(node)); }
/** * Delete the node and remove it from his parent * @param node The node we want to delete */ void UI_DeleteNode (uiNode_t* node) { if (!node->dynamic) { Com_Printf("UI_DeleteNode: Can't delete the static node '%s'\n", UI_GetPath(node)); return; } UI_BeforeDeletingNode(node); UI_DeleteAllChild(node); if (node->firstChild != nullptr) { Com_Printf("UI_DeleteNode: Node '%s' contain static nodes. We can't delete it.\n", UI_GetPath(node)); return; } if (node->parent) UI_RemoveNode(node->parent, node); /* delete all allocated properties */ for (uiBehaviour_t* behaviour = node->behaviour; behaviour; behaviour = behaviour->super) { const value_t** property = behaviour->localProperties; if (property == nullptr) continue; while (*property) { if (((*property)->type & V_UI_MASK) == V_UI_CVAR) { if (void*& mem = Com_GetValue<void*>(node, *property)) { UI_FreeStringProperty(mem); mem = 0; } } /** @todo We must delete all EA_LISTENER too */ property++; } } UI_Node_DeleteNode(node); }
/** * @brief Callback every time the parent window is opened (pushed into the active window stack) */ static void UI_CvarListenerNodeBind (uiNode_t* node) { cvarChangeListener_t* l; l = Cvar_RegisterChangeListener(node->name, UI_CvarListenerNodeCallback); if (l == nullptr) { Com_Printf("Node %s is not binded: cvar %s not found\n", UI_GetPath(node), node->name); return; } if (LIST_GetPointer(static_cast<linkedList_t*>(l->data), node) == nullptr) { LIST_AddPointer(reinterpret_cast<linkedList_t**>(&l->data), node); } }
/** * Delete the node and remove it from his parent * @param node The node we want to delete */ void UI_DeleteNode (uiNode_t* node) { uiBehaviour_t *behaviour; if (!node->dynamic) return; UI_BeforeDeletingNode(node); UI_DeleteAllChild(node); if (node->firstChild != NULL) { Com_Printf("UI_DeleteNode: Node '%s' contain static nodes. We can't delete it.", UI_GetPath(node)); return; } if (node->parent) UI_RemoveNode(node->parent, node); /* delete all allocated properties */ for (behaviour = node->behaviour; behaviour; behaviour = behaviour->super) { const value_t *property = behaviour->properties; if (property == NULL) continue; while (property->string != NULL) { if ((property->type & V_UI_MASK) == V_UI_CVAR) { void *mem = ((byte *) node + property->ofs); if (*(void**)mem != NULL) { UI_FreeStringProperty(*(void**)mem); *(void**)mem = NULL; } } /** @todo We must delete all EA_LISTENER too */ property++; } } if (node->behaviour->deleteNode) node->behaviour->deleteNode(node); }
/** * @brief Handled after the end of the load of the node from the script (all data and/or child are set) */ void uiImageNode::onLoaded (uiNode_t* node) { /* update the size when its possible */ if (Vector2Empty(node->box.size)) { if (EXTRADATA(node).texl[0] != 0 || EXTRADATA(node).texh[0]) { node->box.size[0] = EXTRADATA(node).texh[0] - EXTRADATA(node).texl[0]; node->box.size[1] = EXTRADATA(node).texh[1] - EXTRADATA(node).texl[1]; } else if (node->image) { const image_t* image = UI_LoadImage(node->image); if (image) { node->box.size[0] = image->width; node->box.size[1] = image->height; } } } #ifdef DEBUG if (Vector2Empty(node->box.size)) { if (node->onClick || node->onRightClick || node->onMouseEnter || node->onMouseLeave || node->onWheelUp || node->onWheelDown || node->onWheel || node->onMiddleClick) { Com_DPrintf(DEBUG_CLIENT, "Node '%s' is an active image without size\n", UI_GetPath(node)); } } #endif }
/** * @brief set a node property from the command line * @todo Unify path syntaxe to allow to create a common autocompletion */ static void UI_NodeSetProperty_f (void) { uiNode_t* node; const value_t* property; if (Cmd_Argc() != 4) { Com_Printf("Usage: %s <nodepath> <prop> <value>\n", Cmd_Argv(0)); return; } node = UI_GetNodeByPath(Cmd_Argv(1)); if (!node) { Com_Printf("UI_NodeSetProperty_f: Node '%s' not found\n", Cmd_Argv(1)); return; } property = UI_GetPropertyFromBehaviour(node->behaviour, Cmd_Argv(2)); if (!property) { Com_Printf("Property '%s@%s' doesn't exist\n", UI_GetPath(node), Cmd_Argv(2)); return; } UI_NodeSetProperty(node, property, Cmd_Argv(3)); }
static bool UI_ParseExcludeRect (uiNode_t* node, const char** text, const char** token, const char* errhead) { uiExcludeRect_t rect; uiExcludeRect_t* newRect; /* get parameters */ *token = Com_EParse(text, errhead, node->name); if (!*text) return false; if ((*token)[0] != '{') { Com_Printf("UI_ParseExcludeRect: node with bad excluderect ignored (node \"%s\")\n", UI_GetPath(node)); return true; } do { *token = Com_EParse(text, errhead, node->name); if (!*text) return false; /** @todo move it into a property array */ if (Q_streq(*token, "pos")) { *token = Com_EParse(text, errhead, node->name); if (!*text) return false; Com_EParseValue(&rect, *token, V_POS, offsetof(uiExcludeRect_t, pos), sizeof(vec2_t)); } else if (Q_streq(*token, "size")) { *token = Com_EParse(text, errhead, node->name); if (!*text) return false; Com_EParseValue(&rect, *token, V_POS, offsetof(uiExcludeRect_t, size), sizeof(vec2_t)); } } while ((*token)[0] != '}'); newRect = (uiExcludeRect_t*) UI_AllocHunkMemory(sizeof(*newRect), STRUCT_MEMORY_ALIGN, false); if (newRect == nullptr) { Com_Printf("UI_ParseExcludeRect: ui hunk memory exceeded."); return false; } /* move data to final memory and link to node */ *newRect = rect; newRect->next = node->firstExcludeRect; node->firstExcludeRect = newRect; return true; }
/** * @brief Parse actions and return action list * @return The first element from a list of action * @sa ea_t * @todo need cleanup, compute action out of the final memory; reduce number of var */ static uiAction_t* UI_ParseActionList (uiNode_t* node, const char** text, const char** token) { const char* errhead = "UI_ParseActionList: unexpected end of file (in event)"; uiAction_t* firstAction; uiAction_t* lastAction; uiAction_t* action; lastAction = nullptr; firstAction = nullptr; /* prevent bad position */ if ((*token)[0] != '{') { Com_Printf("UI_ParseActionList: token \"{\" expected, but \"%s\" found (in event) (node: %s)\n", *token, UI_GetPath(node)); return nullptr; } while (true) { bool result; int type = EA_NULL; /* get new token */ *token = Com_EParse(text, errhead, nullptr); if (!*token) return nullptr; if ((*token)[0] == '}') break; type = UI_GetActionTokenType(*token, EA_ACTION); /* setter form */ if (type == EA_NULL && (*token)[0] == '*') type = EA_ASSIGN; /* unknown, we break the parsing */ if (type == EA_NULL) { Com_Printf("UI_ParseActionList: unknown token \"%s\" ignored (in event) (node: %s)\n", *token, UI_GetPath(node)); return nullptr; } /* add the action */ action = UI_AllocStaticAction(); /** @todo better to append the action after initialization */ if (lastAction) lastAction->next = action; if (!firstAction) firstAction = action; action->type = type; /* decode action */ switch (action->type) { case EA_CMD: /* get parameter values */ *token = Com_EParse(text, errhead, nullptr); if (!*text) return nullptr; /* get the value */ action->d.terminal.d1.constString = UI_AllocStaticString(*token, 0); break; case EA_ASSIGN: result = UI_ParseSetAction(node, action, text, token, errhead); if (!result) return nullptr; break; case EA_CALL: result = UI_ParseCallAction(node, action, text, token, errhead); if (!result) return nullptr; break; case EA_DELETE: { uiAction_t* expression; expression = UI_ParseExpression(text); if (expression == nullptr) return nullptr; if (expression->type != EA_VALUE_CVARNAME) { Com_Printf("UI_ParseActionList: \"delete\" keyword only support cvarname (node: %s)\n", UI_GetPath(node)); return nullptr; } action->d.nonTerminal.left = expression; break; } case EA_ELIF: /* check previous action */ if (!lastAction || (lastAction->type != EA_IF && lastAction->type != EA_ELIF)) { Com_Printf("UI_ParseActionList: 'elif' must be set after an 'if' or an 'elif' (node: %s)\n", UI_GetPath(node)); return nullptr; } /* then it execute EA_IF, no break */ case EA_WHILE: case EA_IF: { uiAction_t* expression; /* get the condition */ expression = UI_ParseExpression(text); if (expression == nullptr) return nullptr; action->d.nonTerminal.left = expression; /* get the action block */ *token = Com_EParse(text, errhead, nullptr); if (!*text) return nullptr; action->d.nonTerminal.right = UI_ParseActionList(node, text, token); if (action->d.nonTerminal.right == nullptr) { switch (action->type) { case EA_IF: Com_Printf("UI_ParseActionList: block expected after \"if\" (node: %s)\n", UI_GetPath(node)); break; case EA_ELIF: Com_Printf("UI_ParseActionList: block expected after \"elif\" (node: %s)\n", UI_GetPath(node)); break; case EA_WHILE: Com_Printf("UI_ParseActionList: block expected after \"while\" (node: %s)\n", UI_GetPath(node)); break; default: Com_Printf("UI_ParseActionList: cannot determine statement type (node: %s)\n", UI_GetPath(node)); } return nullptr; } break; } case EA_FORCHILDIN: { uiAction_t* expression; /* get the node */ expression = UI_ParseExpression(text); if (expression == nullptr) { return nullptr; } action->d.nonTerminal.left = expression; /* check the type */ type = action->d.nonTerminal.left->type; if (type != EA_VALUE_PATHNODE && type != EA_VALUE_PATHNODE_WITHINJECTION) { Com_Printf("UI_ParseActionList: Node property expected. Type '%x' found\n", type); return nullptr; } /* get the action block */ *token = Com_EParse(text, errhead, nullptr); if (!*text) return nullptr; action->d.nonTerminal.right = UI_ParseActionList(node, text, token); if (action->d.nonTerminal.right == nullptr) { Com_Printf("UI_ParseActionList: block expected after \"forchildin\" (node: %s)\n", UI_GetPath(node)); return nullptr; } break; } case EA_ELSE: /* check previous action */ if (!lastAction || (lastAction->type != EA_IF && lastAction->type != EA_ELIF)) { Com_Printf("UI_ParseActionList: 'else' must be set after an 'if' or an 'elif' (node: %s)\n", UI_GetPath(node)); return nullptr; } /* get the action block */ *token = Com_EParse(text, errhead, nullptr); if (!*text) return nullptr; action->d.nonTerminal.left = nullptr; action->d.nonTerminal.right = UI_ParseActionList(node, text, token); if (action->d.nonTerminal.right == nullptr) { Com_Printf("UI_ParseActionList: block expected after \"else\" (node: %s)\n", UI_GetPath(node)); return nullptr; } break; default: assert(false); } /* step */ lastAction = action; } assert((*token)[0] == '}'); /* return non nullptr value */ if (firstAction == nullptr) { firstAction = UI_AllocStaticAction(); } return firstAction; }
/** * @brief Parser for call command */ static bool UI_ParseCallAction (uiNode_t* node, uiAction_t* action, const char** text, const char** token, const char* errhead) { uiAction_t* expression; uiAction_t* lastParam = nullptr; int paramID = 0; expression = UI_ParseExpression(text); if (expression == nullptr) return false; if (expression->type != EA_VALUE_PATHNODE_WITHINJECTION && expression->type != EA_VALUE_PATHNODE && expression->type != EA_VALUE_PATHPROPERTY && expression->type != EA_VALUE_PATHPROPERTY_WITHINJECTION) { Com_Printf("UI_ParseCallAction: \"call\" keyword only support pathnode and pathproperty (node: %s)\n", UI_GetPath(node)); return false; } action->d.nonTerminal.left = expression; /* check parameters */ *token = Com_EParse(text, errhead, nullptr); if ((*token)[0] == '\0') return false; /* there is no parameters */ if (!Q_streq(*token, "(")) { Com_UnParseLastToken(); return true; } /* read parameters */ do { uiAction_t* param; paramID++; /* parameter */ param = UI_ParseExpression(text); if (param == nullptr) { Com_Printf("UI_ParseCallAction: problem with the %i parameter\n", paramID); return false; } if (lastParam == nullptr) action->d.nonTerminal.right = param; else lastParam->next = param; lastParam = param; /* separator */ *token = Com_EParse(text, errhead, nullptr); if (!*token) return false; if (!Q_streq(*token, ",")) { if (Q_streq(*token, ")")) break; Com_UnParseLastToken(); Com_Printf("UI_ParseCallAction: Invalidate end of 'call' after param %i\n", paramID); return false; } } while(true); return true; }
/** * @brief Parser for setter command */ static bool UI_ParseSetAction (uiNode_t* node, uiAction_t* action, const char** text, const char** token, const char* errhead) { const value_t* property; int type; uiAction_t* localAction; assert((*token)[0] == '*'); Com_UnParseLastToken(); action->d.nonTerminal.left = UI_ParseExpression(text); type = action->d.nonTerminal.left->type; if (type != EA_VALUE_CVARNAME && type != EA_VALUE_CVARNAME_WITHINJECTION && type != EA_VALUE_PATHPROPERTY && type != EA_VALUE_PATHPROPERTY_WITHINJECTION) { Com_Printf("UI_ParseSetAction: Cvar or Node property expected. Type '%i' found\n", type); return false; } /* must use "equal" char between name and value */ *token = Com_EParse(text, errhead, nullptr); if (!*text) return false; if (!Q_streq(*token, "=")) { Com_Printf("UI_ParseSetAction: Assign sign '=' expected between variable and value. '%s' found in node %s.\n", *token, UI_GetPath(node)); return false; } /* get the value */ if (type == EA_VALUE_CVARNAME || type == EA_VALUE_CVARNAME_WITHINJECTION) { action->d.nonTerminal.right = UI_ParseExpression(text); return true; } property = (const value_t*) action->d.nonTerminal.left->d.terminal.d2.data; *token = Com_EParse(text, errhead, nullptr); if (!*text) return false; if (Q_streq(*token, "{")) { uiAction_t* actionList; if (property != nullptr && property->type != V_UI_ACTION) { Com_Printf("UI_ParseSetAction: Property %s@%s do not expect code block.\n", UI_GetPath(node), property->string); return false; } actionList = UI_ParseActionList(node, text, token); if (actionList == nullptr) return false; localAction = UI_AllocStaticAction(); localAction->type = EA_VALUE_RAW; localAction->d.terminal.d1.data = actionList; localAction->d.terminal.d2.integer = V_UI_ACTION; action->d.nonTerminal.right = localAction; return true; } if (Q_streq(*token, "(")) { Com_UnParseLastToken(); action->d.nonTerminal.right = UI_ParseExpression(text); return true; } /* @todo everything should come from UI_ParseExpression */ if (UI_IsInjectedString(*token)) { localAction = UI_AllocStaticAction(); localAction->type = EA_VALUE_STRING_WITHINJECTION; localAction->d.terminal.d1.data = UI_AllocStaticString(*token, 0); action->d.nonTerminal.right = localAction; return true; } localAction = UI_AllocStaticAction(); UI_InitRawActionValue(localAction, node, property, *token); action->d.nonTerminal.right = localAction; return true; }
const char *UI_AbstractOptionGetCurrentValue (uiNode_t * node) { /* no cvar given? */ if (!EXTRADATA(node).cvar || !*EXTRADATA(node).cvar) { Com_Printf("UI_AbstractOptionGetCurrentValue: node '%s' doesn't have a valid cvar assigned\n", UI_GetPath(node)); return NULL; } /* not a cvar? */ if (!Q_strstart(EXTRADATA(node).cvar, "*cvar:")) return NULL; return UI_GetReferenceString(node, EXTRADATA(node).cvar); }
static void UI_VScrollbarNodeLoaded (uiNode_t *node) { #ifdef DEBUG if (node->size[1] - (ELEMENT_HEIGHT * 4) < 0) Com_DPrintf(DEBUG_CLIENT, "Node '%s' too small. It can create graphical glitches\n", UI_GetPath(node)); #endif }
/** * @brief Call after the script initialized the node */ static void UI_ConFuncNodeLoaded (uiNode_t *node) { /* register confunc non inherited */ if (node->super == NULL) { /* don't add a callback twice */ if (!Cmd_Exists(node->name)) { Cmd_AddCommand(node->name, UI_ConfuncCommand_f, "Confunc callback"); Cmd_AddUserdata(node->name, node); } else { Com_Printf("UI_ParseNodeBody: Command name for confunc '%s' already registered\n", UI_GetPath(node)); } } else { uiNode_t *dummy; /* convert a confunc to an "inherited" confunc if it is possible */ if (Cmd_Exists(node->name)) { if (UI_ConFuncIsVirtual(node)) return; } dummy = UI_AllocNode(node->name, "confunc", qfalse); Cmd_AddCommand(node->name, UI_ConfuncCommand_f, "Inherited confunc callback"); Cmd_AddUserdata(dummy->name, dummy); } }
/** * @brief Get a node and a property from an expression * @param expression Expression tree to analyse * @param context Call context * @param[out] property A node property * @return A node (else nullptr, if no node found) and a property (else nullptr if no/wrong property defined) */ uiNode_t* UI_GetNodeFromExpression (uiAction_t *expression, const uiCallContext_t *context, const value_t **property) { if (property != nullptr) *property = nullptr; switch (expression->type & EA_HIGHT_MASK) { case EA_VALUE: switch (expression->type) { case EA_VALUE_VAR: { uiValue_t *variable = UI_GetVariable(context, expression->d.terminal.d1.integer); switch (variable->type) { case EA_VALUE_NODE: return variable->value.node; default: break; } } break; case EA_VALUE_PATHNODE: case EA_VALUE_PATHNODE_WITHINJECTION: { uiNode_t *node; const value_t *propertyTmp; const char *path = expression->d.terminal.d1.constString; if (expression->type == EA_VALUE_PATHNODE_WITHINJECTION) path = UI_GenInjectedString(path, false, context); UI_ReadNodePath(path, context->source, &node, &propertyTmp); if (!node) { Com_Printf("UI_GetNodeFromExpression: Node '%s' wasn't found; nullptr returned\n", path); return nullptr; } if (propertyTmp != nullptr) Com_Printf("UI_GetNodeFromExpression: No property expected, but path '%s' contain a property. Property ignored.\n", path); return node; } case EA_VALUE_PATHPROPERTY: case EA_VALUE_PATHPROPERTY_WITHINJECTION: { uiNode_t *node; const value_t *propertyTmp; const char *path = expression->d.terminal.d1.constString; if (expression->type == EA_VALUE_PATHPROPERTY_WITHINJECTION) path = UI_GenInjectedString(path, false, context); UI_ReadNodePath(path, context->source, &node, &propertyTmp); if (!node) { Com_Printf("UI_GetNodeFromExpression: Node '%s' wasn't found; nullptr returned\n", path); return nullptr; } if (property == nullptr) { if (propertyTmp != nullptr) Com_Printf("UI_GetNodeFromExpression: No property expected, but path '%s' contain a property. Property ignored.\n", path); } else { *property = propertyTmp; } return node; } case EA_VALUE_THIS: return context->source; case EA_VALUE_PARENT: return context->source->parent; case EA_VALUE_WINDOW: return context->source->root; default: break; } case EA_OPERATOR_UNARY: switch (expression->type) { case EA_OPERATOR_PATHPROPERTYFROM: { uiNode_t *relativeTo = UI_GetNodeFromExpression(expression->d.nonTerminal.left, context, nullptr); uiNode_t *node; const value_t *propertyTmp; const char* path = expression->d.terminal.d2.constString; UI_ReadNodePath(path, relativeTo, &node, &propertyTmp); if (!node) { Com_Printf("UI_GetNodeFromExpression: Path '%s' from node '%s' found no node; nullptr returned\n", path, UI_GetPath(relativeTo)); return nullptr; } if (property == nullptr) { if (propertyTmp != nullptr) Com_Printf("UI_GetNodeFromExpression: No property expected, but path '%s' from node '%s' found no node; nullptr returned\n", path, UI_GetPath(relativeTo)); } else { *property = propertyTmp; } return node; } default: break; } default: break; } return nullptr; }
void uiLineChartNode::draw (uiNode_t *node) { lineStrip_t *lineStrip; const int dataId = EXTRADATA(node).dataId; vec3_t pos; if (dataId == 0) return; if (ui_global.sharedData[dataId].type != UI_SHARED_LINESTRIP) { Com_Printf("UI_LineStripNodeDraw: Node '%s' have use an invalide dataId type. LineStrip expected. dataId invalidated\n", UI_GetPath(node)); EXTRADATA(node).dataId = 0; return; } UI_GetNodeAbsPos(node, pos); pos[2] = 0; UI_Transform(pos, nullptr, nullptr); /* Draw axes */ if (EXTRADATA(node).displayAxes) { int axes[6]; axes[0] = 0; axes[1] = 0; axes[2] = 0; axes[3] = (int) node->box.size[1]; axes[4] = (int) node->box.size[0]; axes[5] = (int) node->box.size[1]; R_Color(EXTRADATA(node).axesColor); R_DrawLineStrip(3, axes); } /* Draw all linestrips. */ lineStrip = ui_global.sharedData[dataId].data.lineStrip; for (; lineStrip; lineStrip = lineStrip->next) { /* Draw this line if it's valid. */ if (lineStrip->pointList && lineStrip->numPoints > 0) { R_Color(lineStrip->color); R_DrawLineStrip(lineStrip->numPoints, lineStrip->pointList); } } R_Color(nullptr); UI_Transform(nullptr, nullptr, nullptr); }
/** * @brief Replace injection identifiers (e.g. <eventParam>) by a value * @note The injection identifier can be every node value - e.g. <image> or <width>. * It's also possible to do something like * @code * cmd "set someCvar <min>/<max>" * @endcode */ const char* UI_GenInjectedString (const char* input, bool addNewLine, const uiCallContext_t *context) { static char cmd[256]; int length = sizeof(cmd) - (addNewLine ? 2 : 1); static char propertyName[256]; const char *cin = input; char *cout = cmd; while (length && cin[0] != '\0') { if (cin[0] == '<') { /* read propertyName between '<' and '>' */ const char *next = UI_GenCommandReadProperty(cin, propertyName, sizeof(propertyName)); if (next) { /* cvar injection */ if (char const* const rest = Q_strstart(propertyName, "cvar:")) { cvar_t const* const cvar = Cvar_Get(rest); const int l = snprintf(cout, length, "%s", cvar->string); cout += l; cin = next; length -= l; continue; } else if (char const* const path = Q_strstart(propertyName, "node:")) { uiNode_t *node; const value_t *property; const char* string; int l; UI_ReadNodePath(path, context->source, &node, &property); if (!node) { Com_Printf("UI_GenInjectedString: Node '%s' wasn't found; '' returned\n", path); #ifdef DEBUG Com_Printf("UI_GenInjectedString: Path relative to '%s'\n", UI_GetPath(context->source)); #endif string = ""; } else if (!property) { Com_Printf("UI_GenInjectedString: Property '%s' wasn't found; '' returned\n", path); string = ""; } else { string = UI_GetStringFromNodeProperty(node, property); if (string == NULL) { Com_Printf("UI_GenInjectedString: String getter for '%s' property do not exists; '' injected\n", path); string = ""; } } l = snprintf(cout, length, "%s", string); cout += l; cin = next; length -= l; continue; /* source path injection */ } else if (char const* const command = Q_strstart(propertyName, "path:")) { if (context->source) { const uiNode_t *node = NULL; if (Q_streq(command, "root")) node = context->source->root; else if (Q_streq(command, "this")) node = context->source; else if (Q_streq(command, "parent")) node = context->source->parent; else Com_Printf("UI_GenCommand: Command '%s' for path injection unknown\n", command); if (node) { const int l = snprintf(cout, length, "%s", UI_GetPath(node)); cout += l; cin = next; length -= l; continue; } } /* no prefix */ } else { /* source property injection */ if (context->source) { /* find property definition */ const value_t *property = UI_GetPropertyFromBehaviour(context->source->behaviour, propertyName); if (property) { const char* value; int l; /* inject the property value */ value = UI_GetStringFromNodeProperty(context->source, property); if (value == NULL) value = ""; l = snprintf(cout, length, "%s", value); cout += l; cin = next; length -= l; continue; } } /* param injection */ if (UI_GetParamNumber(context) != 0) { int arg; const int checked = sscanf(propertyName, "%d", &arg); if (checked == 1 && arg >= 1 && arg <= UI_GetParamNumber(context)) { const int l = snprintf(cout, length, "%s", UI_GetParam(context, arg)); cout += l; cin = next; length -= l; continue; } } } } } *cout++ = *cin++; length--; } /* is buffer too small? */ assert(cin[0] == '\0'); if (addNewLine) *cout++ = '\n'; *cout++ = '\0'; /* copy the result into a free va slot */ return va("%s", cmd); }
/** * @brief Read a node body * @note Node header already read, we are over the node name, or '{' * @code * Allowed syntax * { properties } * OR * { nodes } * OR * { { properties } nodes } * @endcode */ static bool UI_ParseNodeBody (uiNode_t* node, const char** text, const char** token, const char* errhead) { bool result = true; if ((*token)[0] != '{') { /* read the body block start */ *token = Com_EParse(text, errhead, node->name); if (!*text) return false; if ((*token)[0] != '{') { Com_Printf("UI_ParseNodeBody: node doesn't have body, token '%s' read (node \"%s\")\n", *token, UI_GetPath(node)); ui_global.numNodes--; return false; } } /* functions are a special case */ if (UI_Node_IsFunction(node)) { result = UI_ParseFunction(node, text, token); } else { /* check the content */ *token = Com_EParse(text, errhead, node->name); if (!*text) return false; if ((*token)[0] == '{') { /* we have a special block for properties */ result = UI_ParseNodeProperties(node, text, token); if (!result) return false; /* move token over the next node behaviour */ *token = Com_EParse(text, errhead, node->name); if (!*text) return false; /* and then read all nodes */ while ((*token)[0] != '}') { uiNode_t* newNode = UI_ParseNode(node, text, token, errhead); if (!newNode) return false; *token = Com_EParse(text, errhead, node->name); if (*text == nullptr) return false; } } else if (UI_GetPropertyFromBehaviour(node->behaviour, *token)) { /* we should have a block with properties only */ result = UI_ParseNodeProperties(node, text, token); } else { /* we should have a block with nodes only */ while ((*token)[0] != '}') { uiNode_t* newNode = UI_ParseNode(node, text, token, errhead); if (!newNode) return false; *token = Com_EParse(text, errhead, node->name); if (*text == nullptr) return false; } } } if (!result) { Com_Printf("UI_ParseNodeBody: node with bad body ignored (node \"%s\")\n", UI_GetPath(node)); ui_global.numNodes--; return false; } /* already check on UI_ParseNodeProperties */ assert((*token)[0] == '}'); return true; }
/** * @brief parse a node * @sa UI_ParseNodeProperties * @todo we can think about merging UI_ParseNodeProperties here * @note first token already read * @note dont read more than the need token (last right token is '}' of end of node) */ static uiNode_t* UI_ParseNode (uiNode_t* parent, const char** text, const char** token, const char* errhead) { uiNode_t* node = nullptr; uiBehaviour_t* behaviour; uiNode_t* component = nullptr; /* get the behaviour */ behaviour = UI_GetNodeBehaviour(*token); if (!behaviour) { component = UI_GetComponent(*token); } if (behaviour == nullptr && component == nullptr) { Com_Printf("UI_ParseNode: node behaviour/component '%s' doesn't exist (%s)\n", *token, UI_GetPath(parent)); return nullptr; } /* get the name */ *token = Com_EParse(text, errhead, ""); if (!*text) return nullptr; if (!UI_TokenIsName(*token, Com_GetType(text) == TT_QUOTED_WORD)) { Com_Printf("UI_ParseNode: \"%s\" is not a well formed node name ([a-zA-Z_][a-zA-Z0-9_]*)\n", *token); return nullptr; } if (UI_TokenIsReserved(*token)) { Com_Printf("UI_ParseNode: \"%s\" is a reserved token, we can't call a node with it\n", *token); return nullptr; } /* test if node already exists */ /* Already existing node should only come from inherited node, we should not have 2 definitions of the same node into the same window. */ if (parent) node = UI_GetNode(parent, *token); /* reuse a node */ if (node) { const uiBehaviour_t* test = (behaviour != nullptr) ? behaviour : (component != nullptr) ? component->behaviour : nullptr; if (node->behaviour != test) { Com_Printf("UI_ParseNode: we can't change node type (node \"%s\")\n", UI_GetPath(node)); return nullptr; } Com_DPrintf(DEBUG_CLIENT, "... over-riding node %s\n", UI_GetPath(node)); /* else initialize a component */ } else if (component) { node = UI_CloneNode(component, nullptr, true, *token, false); if (parent) { if (parent->root) UI_UpdateRoot(node, parent->root); UI_AppendNode(parent, node); } /* else initialize a new node */ } else { node = UI_AllocNode(*token, behaviour->name, false); node->parent = parent; if (parent) node->root = parent->root; /** @todo move it into caller */ if (parent) UI_AppendNode(parent, node); } /* get body */ const bool result = UI_ParseNodeBody(node, text, token, errhead); if (!result) return nullptr; /* validate properties */ UI_Node_Loaded(node); return node; }
static inline void UI_ExecuteCallAction (const uiAction_t* action, const uiCallContext_t *context) { uiNode_t* callNode = NULL; uiAction_t* param; uiAction_t* left = action->d.nonTerminal.left; uiCallContext_t newContext; const value_t* callProperty = NULL; const char* path = left->d.terminal.d1.constString; if (left->type == EA_VALUE_PATHPROPERTY || left->type == EA_VALUE_PATHNODE) path = left->d.terminal.d1.constString; else if (left->type == EA_VALUE_PATHPROPERTY_WITHINJECTION || left->type == EA_VALUE_PATHNODE_WITHINJECTION) path = UI_GenInjectedString(left->d.terminal.d1.constString, false, context); UI_ReadNodePath(path, context->source, &callNode, &callProperty); if (callNode == NULL) { Com_Printf("UI_ExecuteCallAction: Node from path \"%s\" not found (relative to \"%s\").\n", path, UI_GetPath(context->source)); return; } if (callProperty != NULL && callProperty->type != V_UI_ACTION && callProperty->type != V_UI_NODEMETHOD) { Com_Printf("UI_ExecuteCallAction: Call operand %d unsupported. (%s)\n", callProperty->type, UI_GetPath(callNode)); return; } newContext.source = callNode; newContext.params = NULL; newContext.paramNumber = 0; newContext.varNumber = 0; newContext.varPosition = context->varPosition + context->varNumber; if (action->type == EA_LISTENER) { newContext.useCmdParam = context->useCmdParam; if (!newContext.useCmdParam) { linkedList_t *p = context->params; while (p) { const char* value = (char*) p->data; LIST_AddString(&newContext.params, value); newContext.paramNumber++; p = p->next; } } } else { newContext.useCmdParam = false; param = action->d.nonTerminal.right; while (param) { const char* value; value = UI_GetStringFromExpression(param, context); LIST_AddString(&newContext.params, value); newContext.paramNumber++; param = param->next; } } if (callProperty == NULL || callProperty->type == V_UI_ACTION) { uiAction_t const* const actionsRef = callProperty ? Com_GetValue<uiAction_t*>(callNode, callProperty) : callNode->onClick; UI_ExecuteActions(actionsRef, &newContext); } else if (callProperty->type == V_UI_NODEMETHOD) { uiNodeMethod_t func = (uiNodeMethod_t) callProperty->ofs; func(callNode, &newContext); } else { /* unreachable, already checked few line before */ assert(false); } LIST_Delete(&newContext.params); }
/** called when the window is pushed * check cvar then, reduce runtime check * @todo move cvar check to AbstractOption */ void uiTabNode::onWindowOpened (uiNode_t *node, linkedList_t *params) { /* no cvar given? */ if (!(EXTRADATA(node).cvar)) return; /* not a cvar? */ char const* const cvarName = Q_strstart(EXTRADATA(node).cvar, "*cvar:"); if (!cvarName) { /* normalize */ Com_Printf("UI_TabNodeInit: node '%s' doesn't have a valid cvar assigned (\"%s\" read)\n", UI_GetPath(node), EXTRADATA(node).cvar); EXTRADATA(node).cvar = NULL; return; } /* cvar do not exists? */ if (Cvar_FindVar(cvarName) == NULL) { /* search default value, if possible */ uiNode_t* option = node->firstChild; assert(option->behaviour == ui_optionBehaviour); Cvar_ForceSet(cvarName, OPTIONEXTRADATA(option).value); } }