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_input.h"
28 #include "ui_node.h"
29 #include "ui_popup.h"
30 #include "node/ui_node_abstractnode.h"
31 #include "node/ui_node_window.h"
32 #include "node/ui_node_battlescape.h"
33 
34 #include "../cl_video.h"
35 #include "../input/cl_input.h"
36 #include "../input/cl_keys.h"
37 
38 #define WINDOWEXTRADATA(node) UI_EXTRADATA(node, windowExtraData_t)
39 #define WINDOWEXTRADATACONST(node)  UI_EXTRADATACONST(node, windowExtraData_t)
40 
41 /**
42  * @brief Window name use as alternative for option
43  */
44 static cvar_t* ui_sys_main;
45 
46 /**
47  * @brief Main window of a stack
48  */
49 static cvar_t* ui_sys_active;
50 
51 /**
52  * @brief Returns the ID of the last fullscreen ID. Before this, window should be hidden.
53  * @return The last full screen window on the screen, else 0. If the stack is empty, return -1
54  */
UI_GetLastFullScreenWindow(void)55 int UI_GetLastFullScreenWindow (void)
56 {
57 	/* stack pos */
58 	int pos = ui_global.windowStackPos - 1;
59 	while (pos > 0) {
60 		if (UI_WindowIsFullScreen(ui_global.windowStack[pos]))
61 			break;
62 		pos--;
63 	}
64 	/* if we find nothing we return 0 */
65 	return pos;
66 }
67 
68 /**
69  * @brief Move the window on top of compatible windows.
70  * "Compatible" mean non full screen windows, and windows
71  * with the same window parent.
72  * @param window Window we want to move
73  */
UI_MoveWindowOnTop(uiNode_t * window)74 void UI_MoveWindowOnTop (uiNode_t* window)
75 {
76 	int i, j;
77 
78 	if (UI_WindowIsFullScreen(window))
79 		return;
80 
81 	/* get window index */
82 	for (i = 0; i < ui_global.windowStackPos; i++) {
83 		if (ui_global.windowStack[i] == window)
84 			break;
85 	}
86 
87 	/* search the last compatible window */
88 	for (j = i; j < ui_global.windowStackPos; j++) {
89 		if (UI_WindowIsFullScreen(ui_global.windowStack[j]))
90 			break;
91 		if (WINDOWEXTRADATA(window).parent != WINDOWEXTRADATA(ui_global.windowStack[j]).parent)
92 			break;
93 	}
94 	if (i + 1 == j)
95 		return;
96 
97 	/* translate windows */
98 	for (; i < j - 1; i++) {
99 		ui_global.windowStack[i] = ui_global.windowStack[i+1];
100 	}
101 	/* add the current window on top */
102 	ui_global.windowStack[i] = window;
103 }
104 
105 /**
106  * @brief Remove the window from the window stack
107  * @param[in] window The window to remove from the stack
108  * @todo Why dont we call onClose?
109  */
UI_DeleteWindowFromStack(uiNode_t * window)110 static void UI_DeleteWindowFromStack (uiNode_t* window)
111 {
112 	int i;
113 
114 	/* get window index */
115 	for (i = 0; i < ui_global.windowStackPos; i++) {
116 		if (ui_global.windowStack[i] == window)
117 			break;
118 	}
119 
120 	/* update stack */
121 	if (i < ui_global.windowStackPos) {
122 		ui_global.windowStackPos--;
123 		for (; i < ui_global.windowStackPos; i++)
124 			ui_global.windowStack[i] = ui_global.windowStack[i + 1];
125 		UI_InvalidateMouse();
126 	}
127 }
128 
129 /**
130  * @brief Searches the position in the current window stack for a given window id
131  * @return -1 if the window is not on the stack
132  */
UI_GetWindowPositionFromStackByName(const char * name)133 static inline int UI_GetWindowPositionFromStackByName (const char* name)
134 {
135 	for (int i = 0; i < ui_global.windowStackPos; i++)
136 		if (Q_streq(ui_global.windowStack[i]->name, name))
137 			return i;
138 
139 	return -1;
140 }
141 
142 /**
143  * @brief Insert a window at a position of the stack
144  * @param[in] window The window to insert
145  * @param[in] position Where we want to add the window (0 is the deeper element of the stack)
146  */
UI_InsertWindowIntoStack(uiNode_t * window,int position)147 static inline void UI_InsertWindowIntoStack (uiNode_t* window, int position)
148 {
149 	assert(position <= ui_global.windowStackPos);
150 	assert(position > 0);
151 	assert(window != nullptr);
152 
153 	/* create space for the new window */
154 	for (int i = ui_global.windowStackPos; i > position; i--) {
155 		ui_global.windowStack[i] = ui_global.windowStack[i - 1];
156 	}
157 	/* insert */
158 	ui_global.windowStack[position] = window;
159 	ui_global.windowStackPos++;
160 }
161 
162 /**
163  * @brief Push a window onto the window stack
164  * @param[in] name Name of the window to push onto window stack
165  * @param[in] parentName Window name to link as parent-child (else nullptr)
166  * @param[in] params List of string parameters to send to the onWindowOpened method.
167  * It can be nullptr when there is no parameters, else this object must be freed by the caller.
168  * @return A pointer to @c uiNode_t
169  */
UI_PushWindow(const char * name,const char * parentName,linkedList_t * params)170 uiNode_t* UI_PushWindow (const char* name, const char* parentName, linkedList_t* params)
171 {
172 	UI_ReleaseInput();
173 
174 	uiNode_t* window = UI_GetWindow(name);
175 	if (window == nullptr) {
176 		Com_Printf("Window \"%s\" not found.\n", name);
177 		return nullptr;
178 	}
179 
180 	UI_DeleteWindowFromStack(window);
181 
182 	if (ui_global.windowStackPos < UI_MAX_WINDOWSTACK)
183 		if (parentName) {
184 			const int parentPos = UI_GetWindowPositionFromStackByName(parentName);
185 			if (parentPos == -1) {
186 				Com_Printf("Didn't find parent window \"%s\" for window push of \"%s\"\n", parentName, name);
187 				return nullptr;
188 			}
189 			UI_InsertWindowIntoStack(window, parentPos + 1);
190 			WINDOWEXTRADATA(window).parent = ui_global.windowStack[parentPos];
191 		} else
192 			ui_global.windowStack[ui_global.windowStackPos++] = window;
193 	else
194 		Com_Printf("Window stack overflow\n");
195 
196 	UI_Node_WindowOpened(window, params);
197 
198 	/* change from e.g. console mode to game input mode (fetch input) */
199 	Key_SetDest(key_game);
200 
201 	UI_InvalidateMouse();
202 	return window;
203 }
204 
205 /**
206  * @brief Complete function for ui_push
207  * @sa Cmd_AddParamCompleteFunction
208  * @sa UI_PushWindow
209  * @note Does not really complete the input - but shows at least all parsed windows
210  */
UI_CompleteWithWindow(const char * partial,const char ** match)211 int UI_CompleteWithWindow (const char* partial, const char** match)
212 {
213 	int n = 0;
214 	for (uiNode_t** i = ui_global.windows, ** const end = i + ui_global.numWindows; i != end; ++i) {
215 		char const* const name = (*i)->name;
216 		if (Cmd_GenericCompleteFunction(name, partial, match)) {
217 			Com_Printf("%s\n", name);
218 			++n;
219 		}
220 	}
221 	return n;
222 }
223 
224 /**
225  * @brief Console function to push a child window onto the window stack
226  * @sa UI_PushWindow
227  */
UI_PushChildWindow_f(void)228 static void UI_PushChildWindow_f (void)
229 {
230 	if (Cmd_Argc() > 1)
231 		UI_PushWindow(Cmd_Argv(1), Cmd_Argv(2));
232 	else
233 		Com_Printf("Usage: %s <name> <parentname>\n", Cmd_Argv(0));
234 }
235 
236 /**
237  * @brief Console function to push a window onto the window stack
238  * @sa UI_PushWindow
239  */
UI_PushWindow_f(void)240 static void UI_PushWindow_f (void)
241 {
242 	if (Cmd_Argc() == 0) {
243 		Com_Printf("Usage: %s <name> <params>\n", Cmd_Argv(0));
244 		return;
245 	}
246 
247 	linkedList_t* params = nullptr;
248 	for (int i = 2; i < Cmd_Argc(); i++) {
249 		LIST_AddString(&params, Cmd_Argv(i));
250 	}
251 	UI_PushWindow(Cmd_Argv(1), nullptr, params);
252 	LIST_Delete(&params);
253 }
254 
255 /**
256  * @brief Console function to push a dropdown window at a position. It work like UI_PushWindow but move the window at the right position
257  * @sa UI_PushWindow
258  * @note The usage is "ui_push_dropdown sourcenode pointposition destinationnode pointposition"
259  * sourcenode must be a node into the window we want to push,
260  * we will move the window to have sourcenode over destinationnode
261  * pointposition select for each node a position (like a corner).
262  */
UI_PushDropDownWindow_f(void)263 static void UI_PushDropDownWindow_f (void)
264 {
265 	if (Cmd_Argc() != 4 && Cmd_Argc() != 5) {
266 		Com_Printf("Usage: %s <source-anchor> <point-in-source-anchor> <dest-anchor> <point-in-dest-anchor>\n", Cmd_Argv(0));
267 		return;
268 	}
269 
270 	/* get the source anchor */
271 	uiNode_t* node = UI_GetNodeByPath(Cmd_Argv(1));
272 	if (node == nullptr) {
273 		Com_Printf("UI_PushDropDownWindow_f: Node '%s' doesn't exist\n", Cmd_Argv(1));
274 		return;
275 	}
276 	size_t writtenBytes;
277 	int direction;
278 	int result = Com_ParseValue(&direction, Cmd_Argv(2), V_INT, 0, sizeof(direction), &writtenBytes);
279 	if (result != RESULT_OK) {
280 		Com_Printf("UI_PushDropDownWindow_f: '%s' in not a V_INT\n", Cmd_Argv(2));
281 		return;
282 	}
283 	vec2_t source;
284 	vec2_t destination;
285 	UI_NodeGetPoint(node, source, direction);
286 	UI_NodeRelativeToAbsolutePoint(node, source);
287 
288 	/* get the destination anchor */
289 	if (Q_streq(Cmd_Argv(4), "mouse")) {
290 		destination[0] = mousePosX;
291 		destination[1] = mousePosY;
292 	} else {
293 		/* get the source anchor */
294 		node = UI_GetNodeByPath(Cmd_Argv(3));
295 		if (node == nullptr) {
296 			Com_Printf("UI_PushDropDownWindow_f: Node '%s' doesn't exist\n", Cmd_Argv(3));
297 			return;
298 		}
299 		result = Com_ParseValue(&direction, Cmd_Argv(4), V_INT, 0, sizeof(direction), &writtenBytes);
300 		if (result != RESULT_OK) {
301 			Com_Printf("UI_PushDropDownWindow_f: '%s' in not a V_INT\n", Cmd_Argv(4));
302 			return;
303 		}
304 		UI_NodeGetPoint(node, destination, direction);
305 		UI_NodeRelativeToAbsolutePoint(node, destination);
306 	}
307 
308 	/* update the window and push it */
309 	node = UI_GetNodeByPath(Cmd_Argv(1));
310 	if (node == nullptr) {
311 		Com_Printf("UI_PushDropDownWindow_f: Node '%s' doesn't exist\n", Cmd_Argv(1));
312 		return;
313 	}
314 	node = node->root;
315 	node->box.pos[0] += destination[0] - source[0];
316 	node->box.pos[1] += destination[1] - source[1];
317 	UI_PushWindow(node->name);
318 }
319 
UI_RemoveWindowAtPositionFromStack(int position)320 static void UI_RemoveWindowAtPositionFromStack (int position)
321 {
322 	assert(position < ui_global.windowStackPos);
323 	assert(position >= 0);
324 
325 	/* create space for the new window */
326 	for (int i = position; i < ui_global.windowStackPos; i++) {
327 		ui_global.windowStack[i] = ui_global.windowStack[i + 1];
328 	}
329 	ui_global.windowStack[ui_global.windowStackPos--] = nullptr;
330 }
331 
UI_CloseAllWindow(void)332 static void UI_CloseAllWindow (void)
333 {
334 	for (int i = ui_global.windowStackPos - 1; i >= 0; i--) {
335 		uiNode_t* window = ui_global.windowStack[i];
336 
337 		UI_Node_WindowClosed(window);
338 
339 		/* safe: unlink window */
340 		WINDOWEXTRADATA(window).parent = nullptr;
341 		ui_global.windowStackPos--;
342 		ui_global.windowStack[ui_global.windowStackPos] = nullptr;
343 	}
344 }
345 
346 /**
347  * @brief Init the stack to start with a window, and have an alternative window with ESC
348  * @note Illustration about when/how we should use this http://ufoai.org/wiki/index.php/Image:UI_InitStack.jpg
349  * @param[in] activeWindow The first active window of the stack, else nullptr
350  * @param[in] mainWindow The alternative window, else nullptr if nothing
351  * @todo remove Cvar_Set we have direct access to the cvar
352  * @todo We should only call it a very few time. When we switch from/to this different par of the game: main-option-interface / geoscape-and-base / battlescape
353  */
UI_InitStack(const char * activeWindow,const char * mainWindow)354 void UI_InitStack (const char* activeWindow, const char* mainWindow)
355 {
356 	UI_FinishInit();
357 	UI_PopWindow(true);
358 
359 	assert(activeWindow != nullptr);
360 	Cvar_Set("ui_sys_active", "%s", activeWindow);
361 	/* prevent calls before UI script initialization */
362 	if (ui_global.numWindows != 0) {
363 		UI_PushWindow(activeWindow);
364 	}
365 
366 	if (mainWindow)
367 		Cvar_Set("ui_sys_main", "%s", mainWindow);
368 }
369 
370 /**
371  * @brief Check if a named window is on the stack if active windows
372  */
UI_IsWindowOnStack(const char * name)373 bool UI_IsWindowOnStack (const char* name)
374 {
375 	return UI_GetWindowPositionFromStackByName(name) != -1;
376 }
377 
378 /**
379  * @todo Find  better name
380  */
UI_CloseWindowByRef(uiNode_t * window)381 static void UI_CloseWindowByRef (uiNode_t* window)
382 {
383 	/** @todo If the focus is not on the window we close, we don't need to remove it */
384 	UI_ReleaseInput();
385 
386 	assert(ui_global.windowStackPos);
387 	int i = UI_GetWindowPositionFromStackByName(window->name);
388 	if (i == -1) {
389 		Com_Printf("Window '%s' is not on the active stack\n", window->name);
390 		return;
391 	}
392 
393 	/* close child */
394 	while (i + 1 < ui_global.windowStackPos) {
395 		uiNode_t* m = ui_global.windowStack[i + 1];
396 		if (WINDOWEXTRADATA(m).parent != window) {
397 			break;
398 		}
399 
400 		UI_Node_WindowClosed(window);
401 		WINDOWEXTRADATA(m).parent = nullptr;
402 		UI_RemoveWindowAtPositionFromStack(i + 1);
403 	}
404 
405 	/* close the window */
406 	UI_Node_WindowClosed(window);
407 	WINDOWEXTRADATA(window).parent = nullptr;
408 	UI_RemoveWindowAtPositionFromStack(i);
409 
410 	UI_InvalidateMouse();
411 }
412 
UI_CloseWindow(const char * name)413 void UI_CloseWindow (const char* name)
414 {
415 	uiNode_t* window = UI_GetWindow(name);
416 	if (window == nullptr) {
417 		Com_Printf("Window '%s' not found\n", name);
418 		return;
419 	}
420 
421 	/* found the correct add it to stack or bring it on top */
422 	UI_CloseWindowByRef(window);
423 }
424 
425 /**
426  * @brief Pops a window from the window stack
427  * @param[in] all If true pop all windows from stack
428  * @sa UI_PopWindow_f
429  */
UI_PopWindow(bool all)430 void UI_PopWindow (bool all)
431 {
432 	uiNode_t* oldfirst = ui_global.windowStack[0];
433 
434 	if (all) {
435 		UI_CloseAllWindow();
436 	} else {
437 		uiNode_t* mainWindow = ui_global.windowStack[ui_global.windowStackPos - 1];
438 		if (!ui_global.windowStackPos)
439 			return;
440 		if (WINDOWEXTRADATA(mainWindow).parent)
441 			mainWindow = WINDOWEXTRADATA(mainWindow).parent;
442 		UI_CloseWindowByRef(mainWindow);
443 
444 		if (ui_global.windowStackPos == 0) {
445 			/* ui_sys_main contains the window that is the very first window and should be
446 			 * pushed back onto the stack (otherwise there would be no window at all
447 			 * right now) */
448 			if (Q_streq(oldfirst->name, ui_sys_main->string)) {
449 				if (ui_sys_active->string[0] != '\0')
450 					UI_PushWindow(ui_sys_active->string);
451 				if (!ui_global.windowStackPos)
452 					UI_PushWindow(ui_sys_main->string);
453 			} else {
454 				if (ui_sys_main->string[0] != '\0')
455 					UI_PushWindow(ui_sys_main->string);
456 				if (!ui_global.windowStackPos)
457 					UI_PushWindow(ui_sys_active->string);
458 			}
459 		}
460 
461 		uiNode_t* activeWindow = UI_GetActiveWindow();
462 		UI_Node_WindowActivate(activeWindow);
463 	}
464 
465 	/* change from e.g. console mode to game input mode (fetch input) */
466 	Key_SetDest(key_game);
467 }
468 
469 /**
470  * @brief Console function to close a named window
471  * @sa UI_PushWindow
472  */
UI_CloseWindow_f(void)473 static void UI_CloseWindow_f (void)
474 {
475 	if (Cmd_Argc() != 2) {
476 		Com_Printf("Usage: %s <name>\n", Cmd_Argv(0));
477 		return;
478 	}
479 
480 	UI_CloseWindow(Cmd_Argv(1));
481 }
482 
UI_PopWindowWithEscKey(void)483 void UI_PopWindowWithEscKey (void)
484 {
485 	/* nothing if stack is empty */
486 	if (ui_global.windowStackPos == 0)
487 		return;
488 
489 	/* some window can prevent escape */
490 	const uiNode_t* window = ui_global.windowStack[ui_global.windowStackPos - 1];
491 	if (WINDOWEXTRADATACONST(window).preventTypingEscape)
492 		return;
493 
494 	UI_PopWindow();
495 }
496 
497 /**
498  * @brief Console function to pop a window from the window stack
499  * @sa UI_PopWindow
500  */
UI_PopWindow_f(void)501 static void UI_PopWindow_f (void)
502 {
503 	if (Cmd_Argc() > 1) {
504 		Com_Printf("Usage: %s\n", Cmd_Argv(0));
505 		return;
506 	}
507 
508 	UI_PopWindow();
509 }
510 
511 /**
512  * @brief Returns the current active window from the window stack or nullptr if there is none
513  * @return uiNode_t pointer from window stack
514  * @sa UI_GetWindow
515  */
UI_GetActiveWindow(void)516 uiNode_t* UI_GetActiveWindow (void)
517 {
518 	return (ui_global.windowStackPos > 0 ? ui_global.windowStack[ui_global.windowStackPos - 1] : nullptr);
519 }
520 
521 /**
522  * @brief Returns the name of the current window
523  * @return Active window name, else empty string
524  * @sa UI_GetActiveWIndow
525  */
UI_GetActiveWindowName(void)526 const char* UI_GetActiveWindowName (void)
527 {
528 	const uiNode_t* window = UI_GetActiveWindow();
529 	if (window == nullptr)
530 		return "";
531 	return window->name;
532 }
533 
534 /**
535  * @brief Check if a point is over a window from the stack
536  * @sa IN_Parse
537  */
UI_IsMouseOnWindow(void)538 bool UI_IsMouseOnWindow (void)
539 {
540 	if (UI_GetMouseCapture() != nullptr)
541 		return true;
542 
543 	if (ui_global.windowStackPos != 0) {
544 		if (WINDOWEXTRADATA(ui_global.windowStack[ui_global.windowStackPos - 1]).dropdown)
545 			return true;
546 	}
547 
548 	const uiNode_t* hovered = UI_GetHoveredNode();
549 	if (hovered) {
550 		/* else if it is a render node */
551 		if (UI_Node_IsBattleScape(hovered)) {
552 			return false;
553 		}
554 		return true;
555 	}
556 
557 	return true;
558 }
559 
560 /**
561  * @brief Searches all windows for the specified one
562  * @param[in] name Name of the window we search
563  * @return The window found, else nullptr
564  * @note Use dichotomic search
565  * @sa UI_GetActiveWindow
566  */
UI_GetWindow(const char * name)567 uiNode_t* UI_GetWindow (const char* name)
568 {
569 	unsigned char min = 0;
570 	unsigned char max = ui_global.numWindows;
571 
572 	while (min != max) {
573 		const int mid = (min + max) >> 1;
574 		const int diff = strcmp(ui_global.windows[mid]->name, name);
575 		assert(mid < max);
576 		assert(mid >= min);
577 
578 		if (diff == 0)
579 			return ui_global.windows[mid];
580 
581 		if (diff > 0)
582 			max = mid;
583 		else
584 			min = mid + 1;
585 	}
586 
587 	return nullptr;
588 }
589 
590 /**
591  * @brief Invalidate all windows of the current stack.
592  */
UI_InvalidateStack(void)593 void UI_InvalidateStack (void)
594 {
595 	for (int pos = 0; pos < ui_global.windowStackPos; pos++) {
596 		UI_Invalidate(ui_global.windowStack[pos]);
597 	}
598 	Cvar_SetValue("ui_sys_screenwidth", viddef.virtualWidth);
599 	Cvar_SetValue("ui_sys_screenheight", viddef.virtualHeight);
600 }
601 
602 /**
603  * @brief Sets new x and y coordinates for a given window
604  * @todo remove that
605  */
UI_SetNewWindowPos(uiNode_t * window,int x,int y)606 void UI_SetNewWindowPos (uiNode_t* window, int x, int y)
607 {
608 	if (window)
609 		Vector2Set(window->box.pos, x, y);
610 }
611 
612 /**
613  * @brief Add a new window to the list of all windows
614  * @note Sort windows by alphabet
615  */
UI_InsertWindow(uiNode_t * window)616 void UI_InsertWindow (uiNode_t* window)
617 {
618 
619 	if (ui_global.numWindows >= UI_MAX_WINDOWS)
620 		Com_Error(ERR_FATAL, "UI_InsertWindow: hit UI_MAX_WINDOWS");
621 
622 	/* search the insertion position */
623 	int pos;
624 	for (pos = 0; pos < ui_global.numWindows; pos++) {
625 		const uiNode_t* node = ui_global.windows[pos];
626 		if (strcmp(window->name, node->name) < 0)
627 			break;
628 	}
629 
630 	/* create the space */
631 	for (int i = ui_global.numWindows - 1; i >= pos; i--)
632 		ui_global.windows[i + 1] = ui_global.windows[i];
633 
634 	/* insert */
635 	ui_global.windows[pos] = window;
636 	ui_global.numWindows++;
637 }
638 
639 /**
640  * @brief Finish windows initialization
641  * @note private function
642  */
UI_FinishWindowsInit(void)643 void UI_FinishWindowsInit (void)
644 {
645 	for (int i = 0; i < ui_global.numWindows; i++) {
646 		uiNode_t* window = ui_global.windows[i];
647 		if (WINDOWEXTRADATA(window).onScriptLoaded)
648 			UI_ExecuteEventActions(window, WINDOWEXTRADATA(window).onScriptLoaded);
649 	}
650 }
651 
UI_InitStack_f(void)652 static void UI_InitStack_f (void)
653 {
654 	if (Cmd_Argc() < 2) {
655 		Com_Printf("Usage: %s <mainwindow> [<optionwindow>]\n", Cmd_Argv(0));
656 		return;
657 	}
658 
659 	const char* mainWindow = Cmd_Argv(1);
660 	const char* optionWindow = nullptr;
661 	if (Cmd_Argc() == 3) {
662 		optionWindow = Cmd_Argv(2);
663 	}
664 
665 	UI_InitStack(mainWindow, optionWindow);
666 }
667 
668 /**
669  * @brief Display in the conde the tree of nodes
670  */
UI_DebugTree(const uiNode_t * node,int depth)671 static void UI_DebugTree (const uiNode_t* node, int depth)
672 {
673 	for (int i = 0; i < depth; i++) {
674 		Com_Printf("    ");
675 	}
676 	Com_Printf("+ %s %s\n", UI_Node_GetWidgetName(node), node->name);
677 
678 	const uiNode_t* child = node->firstChild;
679 	while (child) {
680 		UI_DebugTree(child, depth + 1);
681 		child = child->next;
682 	}
683 }
684 
UI_DebugTree_f(void)685 static void UI_DebugTree_f (void)
686 {
687 	if (Cmd_Argc() != 2) {
688 		Com_Printf("Usage: %s <mainwindow>\n", Cmd_Argv(0));
689 		return;
690 	}
691 
692 	const char* window = Cmd_Argv(1);
693 	const uiNode_t* node = UI_GetWindow(window);
694 	UI_DebugTree(node, 0);
695 }
696 
UI_Popup_f(void)697 static void UI_Popup_f (void)
698 {
699 	if (Cmd_Argc() != 3) {
700 		Com_Printf("Usage: %s <header> <body>\n", Cmd_Argv(0));
701 		return;
702 	}
703 
704 	const char* header = Cmd_Argv(1);
705 	const char* body = Cmd_Argv(2);
706 	UI_Popup(header, body);
707 }
708 
UI_InitWindows(void)709 void UI_InitWindows (void)
710 {
711 	ui_sys_main = Cvar_Get("ui_sys_main", "", 0, "This is the main window id that is at the very first window stack - also see ui_sys_active");
712 	ui_sys_active = Cvar_Get("ui_sys_active", "", 0, "The active window we will return to when hitting esc once - also see ui_sys_main");
713 
714 	/* add command */
715 	Cmd_AddCommand("ui_push", UI_PushWindow_f, "Push a window to the window stack");
716 	Cmd_AddParamCompleteFunction("ui_push", UI_CompleteWithWindow);
717 	Cmd_AddCommand("ui_push_dropdown", UI_PushDropDownWindow_f, "Push a dropdown window at a position");
718 	Cmd_AddCommand("ui_push_child", UI_PushChildWindow_f, "Push a window to the windowstack with a big dependency to a parent window");
719 	Cmd_AddCommand("ui_pop", UI_PopWindow_f, "Pops the current window from the stack");
720 	Cmd_AddCommand("ui_close", UI_CloseWindow_f, "Close a window");
721 	Cmd_AddCommand("ui_initstack", UI_InitStack_f, "Initialize the window stack with a main and an option window.");
722 	Cmd_AddCommand("ui_tree", UI_DebugTree_f, "Display a tree of nodes from a window into the console.");
723 	Cmd_AddCommand("ui_popup", UI_Popup_f, "Shows a popup window");
724 	Cmd_AddParamCompleteFunction("ui_tree", UI_CompleteWithWindow);
725 }
726