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