1 /**
2  * @file
3  */
4 
5 /*
6 Copyright (C) 2002-2013 UFO: Alien Invasion.
7 
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
12 
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 
17 See the GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22 
23 */
24 
25 #include "ui_main.h"
26 #include "ui_internal.h"
27 #include "ui_node.h"
28 #include "ui_nodes.h"
29 #include "ui_parse.h"
30 #include "ui_input.h"
31 
32 #include "node/ui_node_abstractnode.h"
33 #include "node/ui_node_abstractscrollbar.h"
34 #include "node/ui_node_abstractoption.h"
35 #include "node/ui_node_abstractvalue.h"
36 #include "node/ui_node_bar.h"
37 #include "node/ui_node_base.h"
38 #include "node/ui_node_baseinventory.h"
39 #include "node/ui_node_battlescape.h"
40 #include "node/ui_node_button.h"
41 #include "node/ui_node_checkbox.h"
42 #include "node/ui_node_controls.h"
43 #include "node/ui_node_container.h"
44 #include "node/ui_node_data.h"
45 #include "node/ui_node_editor.h"
46 #include "node/ui_node_ekg.h"
47 #include "node/ui_node_geoscape.h"
48 #include "node/ui_node_image.h"
49 #include "node/ui_node_item.h"
50 #include "node/ui_node_linechart.h"
51 #include "node/ui_node_material_editor.h"
52 #include "node/ui_node_messagelist.h"
53 #include "node/ui_node_model.h"
54 #include "node/ui_node_option.h"
55 #include "node/ui_node_optionlist.h"
56 #include "node/ui_node_optiontree.h"
57 #include "node/ui_node_panel.h"
58 #include "node/ui_node_radar.h"
59 #include "node/ui_node_radiobutton.h"
60 #include "node/ui_node_rows.h"
61 #include "node/ui_node_selectbox.h"
62 #include "node/ui_node_sequence.h"
63 #include "node/ui_node_string.h"
64 #include "node/ui_node_special.h"
65 #include "node/ui_node_spinner.h"
66 #include "node/ui_node_tab.h"
67 #include "node/ui_node_tbar.h"
68 #include "node/ui_node_text.h"
69 #include "node/ui_node_text2.h"
70 #include "node/ui_node_textlist.h"
71 #include "node/ui_node_textentry.h"
72 #include "node/ui_node_texture.h"
73 #include "node/ui_node_timer.h"
74 #include "node/ui_node_todo.h"
75 #include "node/ui_node_video.h"
76 #include "node/ui_node_vscrollbar.h"
77 #include "node/ui_node_zone.h"
78 
79 typedef void (*registerFunction_t)(uiBehaviour_t* node);
80 
81 /**
82  * @brief List of functions to register nodes
83  * @note Functions must be sorted by node name
84  */
85 static const registerFunction_t registerFunctions[] = {
86 	UI_RegisterNullNode,
87 	UI_RegisterAbstractBaseNode,
88 	UI_RegisterAbstractNode,
89 	UI_RegisterAbstractOptionNode,
90 	UI_RegisterAbstractScrollableNode,
91 	UI_RegisterAbstractScrollbarNode,
92 	UI_RegisterAbstractValueNode,
93 	UI_RegisterBarNode,
94 	UI_RegisterBaseInventoryNode,
95 	UI_RegisterBaseLayoutNode,
96 	UI_RegisterBaseMapNode,
97 	UI_RegisterBattlescapeNode,
98 	UI_RegisterButtonNode,
99 	UI_RegisterCheckBoxNode,
100 	UI_RegisterConFuncNode,
101 	UI_RegisterContainerNode,
102 	UI_RegisterControlsNode,
103 	UI_RegisterCvarFuncNode,
104 	UI_RegisterDataNode,
105 	UI_RegisterEditorNode,
106 	UI_RegisterEKGNode,
107 	UI_RegisterFuncNode,
108 	UI_RegisterGeoscapeNode,
109 	UI_RegisterImageNode,
110 	UI_RegisterItemNode,
111 	UI_RegisterLineChartNode,
112 	UI_RegisterMaterialEditorNode,
113 	UI_RegisterMessageListNode,
114 	UI_RegisterModelNode,
115 	UI_RegisterOptionNode,
116 	UI_RegisterOptionListNode,
117 	UI_RegisterOptionTreeNode,
118 	UI_RegisterPanelNode,
119 	UI_RegisterRadarNode,
120 	UI_RegisterRadioButtonNode,
121 	UI_RegisterRowsNode,
122 	UI_RegisterSelectBoxNode,
123 	UI_RegisterSequenceNode,
124 	UI_RegisterSpinnerNode,
125 	UI_RegisterStringNode,
126 	UI_RegisterTabNode,
127 	UI_RegisterTBarNode,
128 	UI_RegisterTextNode,
129 	UI_RegisterText2Node,
130 	UI_RegisterTextEntryNode,
131 	UI_RegisterTextListNode,
132 	UI_RegisterTextureNode,
133 	UI_RegisterTimerNode,
134 	UI_RegisterTodoNode,
135 	UI_RegisterVideoNode,
136 	UI_RegisterVScrollbarNode,
137 	UI_RegisterWindowNode,
138 	UI_RegisterZoneNode
139 };
140 #define NUMBER_OF_BEHAVIOURS lengthof(registerFunctions)
141 
142 /**
143  * @brief List of all node behaviours, indexes by nodetype num.
144  */
145 static uiBehaviour_t nodeBehaviourList[NUMBER_OF_BEHAVIOURS];
146 
147 /**
148  * @brief Check the if conditions for a given node
149  * @sa V_UI_IF
150  * @returns false if the node is not drawn due to not meet if conditions
151  */
UI_CheckVisibility(uiNode_t * node)152 bool UI_CheckVisibility (uiNode_t* node)
153 {
154 	uiCallContext_t context;
155 	if (!node->visibilityCondition)
156 		return true;
157 	context.source = node;
158 	context.useCmdParam = false;
159 	return UI_GetBooleanFromExpression(node->visibilityCondition, &context);
160 }
161 
162 /**
163  * @brief Return a path from a window to a node
164  * @return A path "windowname.nodename.nodename.givennodename"
165  * @note Use a static buffer for the result
166  */
UI_GetPath(const uiNode_t * node)167 const char* UI_GetPath (const uiNode_t* node)
168 {
169 	static char result[512];
170 	const uiNode_t* nodes[8];
171 	int i = 0;
172 
173 	while (node) {
174 		assert(i < 8);
175 		nodes[i] = node;
176 		node = node->parent;
177 		i++;
178 	}
179 
180 	/** @todo we can use something faster than cat */
181 	result[0] = '\0';
182 	while (i) {
183 		i--;
184 		Q_strcat(result, sizeof(result), "%s", nodes[i]->name);
185 		if (i > 0)
186 			Q_strcat(result, sizeof(result), ".");
187 	}
188 
189 	return result;
190 }
191 
192 /**
193  * @brief Read a path and return every we can use (node and property)
194  * @details The path token must be a window name, and then node child.
195  * Reserved token 'root' and 'parent' can be used to navigate.
196  * If relativeNode is set, the path can start with reserved token
197  * 'this', 'root' and 'parent' (relative to this node).
198  * The function can return a node property by using a '\@',
199  * the path 'foo\@pos' will return the window foo and the property 'pos'
200  * from the 'window' behaviour.
201  * @param[in] path Path to read. Contain a node location with dot seprator and a facultative property
202  * @param[in] relativeNode relative node where the path start. It allow to use facultative command to start the path (this, parent, root).
203  * @param[out] resultNode Node found. Else nullptr.
204  * @param[out] resultProperty Property found. Else nullptr.
205  * TODO Speed up, evilly used function, use strncmp instead of using buffer copy (name[MAX_VAR])
206  */
UI_ReadNodePath(const char * path,const uiNode_t * relativeNode,uiNode_t ** resultNode,const value_t ** resultProperty)207 void UI_ReadNodePath (const char* path, const uiNode_t* relativeNode, uiNode_t** resultNode, const value_t** resultProperty)
208 {
209 	char name[MAX_VAR];
210 	uiNode_t* node = nullptr;
211 	const char* nextName;
212 	char nextCommand = '^';
213 
214 	*resultNode = nullptr;
215 	if (resultProperty)
216 		*resultProperty = nullptr;
217 
218 	nextName = path;
219 	while (nextName && nextName[0] != '\0') {
220 		const char* begin = nextName;
221 		char command = nextCommand;
222 		nextName = strpbrk(begin, ".@#");
223 		if (!nextName) {
224 			Q_strncpyz(name, begin, sizeof(name));
225 			nextCommand = '\0';
226 		} else {
227 			assert(nextName - begin + 1 <= sizeof(name));
228 			Q_strncpyz(name, begin, nextName - begin + 1);
229 			nextCommand = *nextName;
230 			nextName++;
231 		}
232 
233 		switch (command) {
234 		case '^':	/* first string */
235 			if (Q_streq(name, "this")) {
236 				if (relativeNode == nullptr)
237 					return;
238 				/** @todo find a way to fix the bad cast. only here to remove "discards qualifiers" warning */
239 				node = *(uiNode_t**) ((void*)&relativeNode);
240 			} else if (Q_streq(name, "parent")) {
241 				if (relativeNode == nullptr)
242 					return;
243 				node = relativeNode->parent;
244 			} else if (Q_streq(name, "root")) {
245 				if (relativeNode == nullptr)
246 					return;
247 				node = relativeNode->root;
248 			} else
249 				node = UI_GetWindow(name);
250 			break;
251 		case '.':	/* child node */
252 			if (Q_streq(name, "parent"))
253 				node = node->parent;
254 			else if (Q_streq(name, "root"))
255 				node = node->root;
256 			else
257 				node = UI_GetNode(node, name);
258 			break;
259 		case '#':	/* window index */
260 			/** @todo FIXME use a warning instead of an assert */
261 			assert(UI_Node_IsWindow(node));
262 			node = UI_WindowNodeGetIndexedChild(node, name);
263 			break;
264 		case '@':	/* property */
265 			assert(nextCommand == '\0');
266 			*resultProperty = UI_GetPropertyFromBehaviour(node->behaviour, name);
267 			*resultNode = node;
268 			return;
269 		}
270 
271 		if (!node)
272 			return;
273 	}
274 
275 	*resultNode = node;
276 	return;
277 }
278 
279 /**
280  * @brief Return a node by a path name (names with dot separation)
281  * It is a simplification facade over UI_ReadNodePath
282  * @return The requested node, else nullptr if not found
283  * @code
284  * // get keylist node from options_keys node from options window
285  * node = UI_GetNodeByPath("options.options_keys.keylist");
286  * @sa UI_ReadNodePath
287  * @endcode
288  */
UI_GetNodeByPath(const char * path)289 uiNode_t* UI_GetNodeByPath (const char* path)
290 {
291 	uiNode_t* node = nullptr;
292 	const value_t* property;
293 	UI_ReadNodePath(path, nullptr, &node, &property);
294 	/** @todo FIXME warning if it return a property */
295 	return node;
296 }
297 
298 /**
299  * @brief Allocate a node into the UI memory (do not call behaviour->new)
300  * @note It's not a dynamic memory allocation. Please only use it at the loading time
301  * @todo Assert out when we are not in parsing/loading stage
302  * @param[in] name Name of the new node, else nullptr if we don't want to edit it.
303  * @param[in] type Name of the node behavior
304  * @param[in] isDynamic Allocate a node in static or dynamic memory
305  */
UI_AllocNodeWithoutNew(const char * name,const char * type,bool isDynamic)306 static uiNode_t* UI_AllocNodeWithoutNew (const char* name, const char* type, bool isDynamic)
307 {
308 	uiNode_t* node;
309 	uiBehaviour_t* behaviour;
310 	int nodeSize;
311 
312 	behaviour = UI_GetNodeBehaviour(type);
313 	if (behaviour == nullptr)
314 		Com_Error(ERR_FATAL, "UI_AllocNodeWithoutNew: Node behaviour '%s' doesn't exist", type);
315 
316 	nodeSize = sizeof(*node) + behaviour->extraDataSize;
317 
318 	if (!isDynamic) {
319 		void* memory = UI_AllocHunkMemory(nodeSize, STRUCT_MEMORY_ALIGN, true);
320 		if (memory == nullptr)
321 			Com_Error(ERR_FATAL, "UI_AllocNodeWithoutNew: No more memory to allocate a new node - increase the cvar ui_hunksize");
322 		node = static_cast<uiNode_t*>(memory);
323 		ui_global.numNodes++;
324 	} else {
325 		node = static_cast<uiNode_t*>(Mem_PoolAlloc(nodeSize, ui_dynPool, 0));
326 		node->dynamic = true;
327 	}
328 
329 	node->behaviour = behaviour;
330 #ifdef DEBUG
331 	UI_Node_DebugCountWidget(node, 1);
332 #endif
333 	if (UI_Node_IsAbstract(node))
334 		Com_Error(ERR_FATAL, "UI_AllocNodeWithoutNew: Node behavior '%s' is abstract. We can't instantiate it.", type);
335 
336 	if (name != nullptr) {
337 		Q_strncpyz(node->name, name, sizeof(node->name));
338 		if (strlen(node->name) != strlen(name))
339 			Com_Printf("UI_AllocNodeWithoutNew: Node name \"%s\" truncated. New name is \"%s\"\n", name, node->name);
340 	}
341 
342 	/* initialize default properties */
343 	UI_Node_Loading(node);
344 
345 	return node;
346 }
347 
348 /**
349  * @brief Allocate a node into the UI memory
350  * @note It's not a dynamic memory allocation. Please only use it at the loading time
351  * @todo Assert out when we are not in parsing/loading stage
352  * @param[in] name Name of the new node, else nullptr if we don't want to edit it.
353  * @param[in] type Name of the node behavior
354  * @param[in] isDynamic Allocate a node in static or dynamic memory
355  */
UI_AllocNode(const char * name,const char * type,bool isDynamic)356 uiNode_t* UI_AllocNode (const char* name, const char* type, bool isDynamic)
357 {
358 	uiNode_t* node = UI_AllocNodeWithoutNew(name, type, isDynamic);
359 
360 	/* allocate memory */
361 	if (node->dynamic)
362 		UI_Node_NewNode(node);
363 
364 	return node;
365 }
366 
367 /**
368  * @brief Return the first visible node at a position
369  * @param[in] node Node where we must search
370  * @param[in] rx Relative x position to the parent of the node
371  * @param[in] ry Relative y position to the parent of the node
372  * @return The first visible node at position, else nullptr
373  */
UI_GetNodeInTreeAtPosition(uiNode_t * node,int rx,int ry)374 static uiNode_t* UI_GetNodeInTreeAtPosition (uiNode_t* node, int rx, int ry)
375 {
376 	uiNode_t* find;
377 
378 	if (node->invis || UI_Node_IsVirtual(node) || !UI_CheckVisibility(node))
379 		return nullptr;
380 
381 	/* relative to the node */
382 	rx -= node->box.pos[0];
383 	ry -= node->box.pos[1];
384 
385 	/* check bounding box */
386 	if (rx < 0 || ry < 0 || rx >= node->box.size[0] || ry >= node->box.size[1])
387 		return nullptr;
388 
389 	/** @todo we should improve the loop (last-to-first) */
390 	find = nullptr;
391 	if (node->firstChild) {
392 		uiNode_t* child;
393 		vec2_t clientPosition = {0, 0};
394 
395 		if (UI_Node_IsScrollableContainer(node))
396 			UI_Node_GetClientPosition(node, clientPosition);
397 
398 		rx -= clientPosition[0];
399 		ry -= clientPosition[1];
400 
401 		for (child = node->firstChild; child; child = child->next) {
402 			uiNode_t* tmp;
403 			tmp = UI_GetNodeInTreeAtPosition(child, rx, ry);
404 			if (tmp)
405 				find = tmp;
406 		}
407 
408 		rx += clientPosition[0];
409 		ry += clientPosition[1];
410 	}
411 	if (find)
412 		return find;
413 
414 	/* disable ghost/excluderect in debug mode 2 */
415 	if (UI_DebugMode() != 2) {
416 		uiExcludeRect_t* excludeRect;
417 		/* is the node tangible */
418 		if (node->ghost)
419 			return nullptr;
420 
421 		/* check excluded box */
422 		for (excludeRect = node->firstExcludeRect; excludeRect != nullptr; excludeRect = excludeRect->next) {
423 			if (rx >= excludeRect->pos[0]
424 			 && rx < excludeRect->pos[0] + excludeRect->size[0]
425 			 && ry >= excludeRect->pos[1]
426 			 && ry < excludeRect->pos[1] + excludeRect->size[1])
427 				return nullptr;
428 		}
429 	}
430 
431 	/* we are over the node */
432 	return node;
433 }
434 
435 /**
436  * @brief Return the first visible node at a position
437  */
UI_GetNodeAtPosition(int x,int y)438 uiNode_t* UI_GetNodeAtPosition (int x, int y)
439 {
440 	int pos;
441 
442 	/* find the first window under the mouse */
443 	for (pos = ui_global.windowStackPos - 1; pos >= 0; pos--) {
444 		uiNode_t* window = ui_global.windowStack[pos];
445 		uiNode_t* find;
446 
447 		/* update the layout */
448 		UI_Validate(window);
449 
450 		find = UI_GetNodeInTreeAtPosition(window, x, y);
451 		if (find)
452 			return find;
453 
454 		/* we must not search anymore */
455 		if (UI_WindowIsDropDown(window))
456 			break;
457 		if (UI_WindowIsModal(window))
458 			break;
459 		if (UI_WindowIsFullScreen(window))
460 			break;
461 	}
462 
463 	return nullptr;
464 }
465 
466 /**
467  * @brief Return a node behaviour by name
468  * @note Use a dichotomic search. nodeBehaviourList must be sorted by name.
469  * @param[in] name Behaviour name requested
470  * @return The bahaviour found, else nullptr
471  */
UI_GetNodeBehaviour(const char * name)472 uiBehaviour_t* UI_GetNodeBehaviour (const char* name)
473 {
474 	unsigned char min = 0;
475 	unsigned char max = NUMBER_OF_BEHAVIOURS;
476 
477 	while (min != max) {
478 		const int mid = (min + max) >> 1;
479 		const int diff = strcmp(nodeBehaviourList[mid].name, name);
480 		assert(mid < max);
481 		assert(mid >= min);
482 
483 		if (diff == 0)
484 			return &nodeBehaviourList[mid];
485 
486 		if (diff > 0)
487 			max = mid;
488 		else
489 			min = mid + 1;
490 	}
491 
492 	return nullptr;
493 }
494 
UI_GetNodeBehaviourByIndex(int index)495 uiBehaviour_t* UI_GetNodeBehaviourByIndex (int index)
496 {
497 	return &nodeBehaviourList[index];
498 }
499 
UI_GetNodeBehaviourCount(void)500 int UI_GetNodeBehaviourCount (void)
501 {
502 	return NUMBER_OF_BEHAVIOURS;
503 }
504 
505 /**
506  * @brief Remove all child from a node (only remove dynamic memory allocation nodes)
507  * @param node The node we want to clean
508  */
UI_DeleteAllChild(uiNode_t * node)509 void UI_DeleteAllChild (uiNode_t* node)
510 {
511 	uiNode_t* child;
512 	child = node->firstChild;
513 	while (child) {
514 		uiNode_t* next = child->next;
515 		UI_DeleteNode(child);
516 		child = next;
517 	}
518 }
519 
UI_BeforeDeletingNode(const uiNode_t * node)520 static void UI_BeforeDeletingNode (const uiNode_t* node)
521 {
522 	if (UI_GetHoveredNode() == node) {
523 		UI_InvalidateMouse();
524 	}
525 }
526 
527 /**
528  * Delete the node and remove it from his parent
529  * @param node The node we want to delete
530  */
UI_DeleteNode(uiNode_t * node)531 void UI_DeleteNode (uiNode_t* node)
532 {
533 	uiBehaviour_t* behaviour;
534 
535 	if (!node->dynamic) {
536 		Com_Printf("UI_DeleteNode: Can't delete the static node '%s'\n", UI_GetPath(node));
537 		return;
538 	}
539 
540 	UI_BeforeDeletingNode(node);
541 
542 	UI_DeleteAllChild(node);
543 	if (node->firstChild != nullptr) {
544 		Com_Printf("UI_DeleteNode: Node '%s' contain static nodes. We can't delete it.\n", UI_GetPath(node));
545 		return;
546 	}
547 
548 	if (node->parent)
549 		UI_RemoveNode(node->parent, node);
550 
551 	/* delete all allocated properties */
552 	for (behaviour = node->behaviour; behaviour; behaviour = behaviour->super) {
553 		const value_t** property = behaviour->localProperties;
554 		if (property == nullptr)
555 			continue;
556 		while (*property) {
557 			if (((*property)->type & V_UI_MASK) == V_UI_CVAR) {
558 				if (void*& mem = Com_GetValue<void*>(node, *property)) {
559 					UI_FreeStringProperty(mem);
560 					mem = 0;
561 				}
562 			}
563 
564 			/** @todo We must delete all EA_LISTENER too */
565 
566 			property++;
567 		}
568 	}
569 
570 	UI_Node_DeleteNode(node);
571 }
572 
573 /**
574  * @brief Clone a node
575  * @param[in] node Node to clone
576  * @param[in] recursive True if we also must clone subnodes
577  * @param[in] newWindow Window where the nodes must be add (this function only link node into window, not window into the new node)
578  * @param[in] newName New node name, else nullptr to use the source name
579  * @param[in] isDynamic Allocate a node in static or dynamic memory
580  * @todo exclude rect is not safe cloned.
581  * @todo actions are not cloned. It is be a problem if we use add/remove listener into a cloned node.
582  */
UI_CloneNode(const uiNode_t * node,uiNode_t * newWindow,bool recursive,const char * newName,bool isDynamic)583 uiNode_t* UI_CloneNode (const uiNode_t* node, uiNode_t* newWindow, bool recursive, const char* newName, bool isDynamic)
584 {
585 	uiNode_t* newNode = UI_AllocNodeWithoutNew(nullptr, UI_Node_GetWidgetName(node), isDynamic);
586 
587 	/* clone all data */
588 	memcpy(newNode, node, UI_Node_GetMemorySize(node));
589 	newNode->dynamic = isDynamic;
590 
591 	/* custom name */
592 	if (newName != nullptr) {
593 		Q_strncpyz(newNode->name, newName, sizeof(newNode->name));
594 		if (strlen(newNode->name) != strlen(newName))
595 			Com_Printf("UI_CloneNode: Node name \"%s\" truncated. New name is \"%s\"\n", newName, newNode->name);
596 	}
597 
598 	/* clean up node navigation */
599 	if (node->root == node && newWindow == nullptr)
600 		newWindow = newNode;
601 	newNode->root = newWindow;
602 	newNode->parent = nullptr;
603 	newNode->firstChild = nullptr;
604 	newNode->lastChild = nullptr;
605 	newNode->next = nullptr;
606 	newNode->super = node;
607 
608 	/* clone child */
609 	if (recursive) {
610 		uiNode_t* childNode;
611 		for (childNode = node->firstChild; childNode; childNode = childNode->next) {
612 			uiNode_t* newChildNode = UI_CloneNode(childNode, newWindow, recursive, nullptr, isDynamic);
613 			UI_AppendNode(newNode, newChildNode);
614 		}
615 	}
616 
617 	/* allocate memories */
618 	if (newNode->dynamic)
619 		UI_Node_NewNode(newNode);
620 
621 	UI_Node_Clone(node, newNode);
622 
623 	return newNode;
624 }
625 
UI_InitNodes(void)626 void UI_InitNodes (void)
627 {
628 	int i = 0;
629 	uiBehaviour_t* current = nodeBehaviourList;
630 
631 	/* compute list of node behaviours */
632 	for (i = 0; i < NUMBER_OF_BEHAVIOURS; i++) {
633 		OBJZERO(*current);
634 		current->registration = true;
635 		registerFunctions[i](current);
636 		current->registration = false;
637 		current++;
638 	}
639 
640 	/* check for safe data: list must be sorted by alphabet */
641 	current = nodeBehaviourList;
642 	assert(current);
643 	for (i = 0; i < NUMBER_OF_BEHAVIOURS - 1; i++) {
644 		const uiBehaviour_t* a = current;
645 		const uiBehaviour_t* b = current + 1;
646 		assert(b);
647 		if (strcmp(a->name, b->name) >= 0) {
648 #ifdef DEBUG
649 			Com_Error(ERR_FATAL, "UI_InitNodes: '%s' is before '%s'. Please order node behaviour registrations by name", a->name, b->name);
650 #else
651 			Com_Error(ERR_FATAL, "UI_InitNodes: Error: '%s' is before '%s'", a->name, b->name);
652 #endif
653 		}
654 		current++;
655 	}
656 
657 	/* finalize node behaviour initialization */
658 	current = nodeBehaviourList;
659 	for (i = 0; i < NUMBER_OF_BEHAVIOURS; i++) {
660 		UI_InitializeNodeBehaviour(current);
661 		current++;
662 	}
663 }
664 
alignBox(uiBox_t & inner,align_t direction)665 void uiBox_t::alignBox (uiBox_t& inner, align_t direction)
666 {
667 	switch (direction % 3) {
668 	case 0:	/* left */
669 		inner.pos[0] = this->pos[0];
670 		break;
671 	case 1:	/* middle */
672 		inner.pos[0] = this->pos[0] + (this->size[0] * 0.5) - (inner.size[0] * 0.5);
673 		break;
674 	case 2:	/* right */
675 		inner.pos[0] = this->pos[0] + this->size[0] - inner.size[0];
676 		break;
677 	}
678 	switch (direction / 3) {
679 	case 0:	/* top */
680 		inner.pos[1] = this->pos[1];
681 		break;
682 	case 1:	/* middle */
683 		inner.pos[1] = this->pos[1] + (this->size[1] * 0.5) - (inner.size[1] * 0.5);
684 		break;
685 	case 2:	/* bottom */
686 		inner.pos[1] = this->pos[1] + this->size[1] - inner.size[1];
687 		break;
688 	default:
689 		inner.pos[1] = this->pos[1];
690 		Com_Error(ERR_FATAL, "UI_ImageAlignBoxInBox: Align %d not supported\n", direction);
691 	}
692 }
693