1 /**
2 * @file
3 * @todo move it as an inheritance of panel behaviour?
4 */
5
6 /*
7 Copyright (C) 2002-2013 UFO: Alien Invasion.
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
18 See the GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24 */
25
26 #include "../ui_main.h"
27 #include "../ui_parse.h"
28 #include "../ui_font.h"
29 #include "../ui_nodes.h"
30 #include "../ui_internal.h"
31 #include "../ui_render.h"
32 #include "../ui_sprite.h"
33 #include "ui_node_window.h"
34 #include "ui_node_panel.h"
35 #include "ui_node_abstractnode.h"
36
37 #include "../../client.h" /* gettext _() */
38
39 #define EXTRADATA_TYPE windowExtraData_t
40 #define EXTRADATA(node) UI_EXTRADATA(node, EXTRADATA_TYPE)
41 #define EXTRADATACONST(node) UI_EXTRADATACONST(node, EXTRADATA_TYPE)
42
43 #define TOP_HEIGHT 46
44
45 static const int CONTROLS_IMAGE_DIMENSIONS = 25;
46 static const int CONTROLS_PADDING = 18;
47
48 static const vec4_t modalBackground = {0, 0, 0, 0.6};
49 static const vec4_t anamorphicBorder = {0, 0, 0, 1};
50
51 /**
52 * @brief Get a node from child index
53 * @return A child node by his name, else nullptr
54 */
UI_WindowNodeGetIndexedChild(uiNode_t * const node,const char * childName)55 uiNode_t* UI_WindowNodeGetIndexedChild (uiNode_t* const node, const char* childName)
56 {
57 node_index_t* a;
58 unsigned int hash;
59
60 hash = Com_HashKey(childName, INDEXEDCHILD_HASH_SIZE);
61 for (a = EXTRADATA(node).index_hash[hash]; a; a = a->hash_next) {
62 if (Q_streq(childName, a->node->name)) {
63 return a->node;
64 }
65 }
66 return nullptr;
67 }
68
69 /**
70 * @brief Add a node to the child index
71 */
UI_WindowNodeAddIndexedNode(uiNode_t * const node,uiNode_t * const child)72 bool UI_WindowNodeAddIndexedNode (uiNode_t* const node, uiNode_t* const child)
73 {
74 node_index_t* a;
75 unsigned int hash;
76
77 hash = Com_HashKey(child->name, INDEXEDCHILD_HASH_SIZE);
78 for (a = EXTRADATA(node).index_hash[hash]; a; a = a->hash_next) {
79 if (Q_streq(child->name, a->node->name)) {
80 /** @todo display a warning, we must not override a node name here */
81 break;
82 }
83 }
84
85 if (!a) {
86 a = Mem_PoolAllocType(node_index_t, ui_sysPool);
87 a->next = EXTRADATA(node).index;
88 a->hash_next = EXTRADATA(node).index_hash[hash];
89 EXTRADATA(node).index_hash[hash] = a;
90 EXTRADATA(node).index = a;
91 }
92
93 return false;
94 }
95
96 /**
97 * @brief Remove a node from the child index
98 */
UI_WindowNodeRemoveIndexedNode(uiNode_t * const node,uiNode_t * const child)99 bool UI_WindowNodeRemoveIndexedNode (uiNode_t* const node, uiNode_t* const child)
100 {
101 /** @todo FIXME implement it */
102 return false;
103 }
104
105 /**
106 * @brief Check if a window is fullscreen or not
107 */
UI_WindowIsFullScreen(const uiNode_t * const node)108 bool UI_WindowIsFullScreen (const uiNode_t* const node)
109 {
110 assert(UI_NodeInstanceOf(node, "window"));
111 return EXTRADATACONST(node).isFullScreen;
112 }
113
draw(uiNode_t * node)114 void uiWindowNode::draw (uiNode_t* node)
115 {
116 const char* text;
117 vec2_t pos;
118 const char* font = UI_GetFontFromNode(node);
119
120 UI_GetNodeAbsPos(node, pos);
121
122 /* black border for anamorphic mode */
123 /** @todo it should be over the window */
124 /** @todo why not using glClear here with glClearColor set to black here? */
125 if (UI_WindowIsFullScreen(node)) {
126 /* top */
127 if (pos[1] != 0)
128 UI_DrawFill(0, 0, viddef.virtualWidth, pos[1], anamorphicBorder);
129 /* left-right */
130 if (pos[0] != 0)
131 UI_DrawFill(0, pos[1], pos[0], node->box.size[1], anamorphicBorder);
132 if (pos[0] + node->box.size[0] < viddef.virtualWidth) {
133 const int width = viddef.virtualWidth - (pos[0] + node->box.size[0]);
134 UI_DrawFill(viddef.virtualWidth - width, pos[1], width, node->box.size[1], anamorphicBorder);
135 }
136 /* bottom */
137 if (pos[1] + node->box.size[1] < viddef.virtualHeight) {
138 const int height = viddef.virtualHeight - (pos[1] + node->box.size[1]);
139 UI_DrawFill(0, viddef.virtualHeight - height, viddef.virtualWidth, height, anamorphicBorder);
140 }
141 }
142
143 /* hide background if window is modal */
144 if (EXTRADATA(node).modal && ui_global.windowStack[ui_global.windowStackPos - 1] == node)
145 UI_DrawFill(0, 0, viddef.virtualWidth, viddef.virtualHeight, modalBackground);
146
147 if (EXTRADATA(node).background) {
148 UI_DrawSpriteInBox(false, EXTRADATA(node).background, SPRITE_STATUS_NORMAL, pos[0], pos[1], node->box.size[0], node->box.size[1]);
149 }
150
151 /* draw the title */
152 text = UI_GetReferenceString(node, node->text);
153 if (text)
154 UI_DrawStringInBox(font, ALIGN_CC, pos[0] + node->padding, pos[1] + node->padding, node->box.size[0] - node->padding - node->padding, TOP_HEIGHT + 10 - node->padding - node->padding, text);
155 }
156
doLayout(uiNode_t * node)157 void uiWindowNode::doLayout (uiNode_t* node)
158 {
159 if (!node->invalidated)
160 return;
161
162 /* use a the space */
163 if (EXTRADATA(node).fill) {
164 if (node->box.size[0] != viddef.virtualWidth) {
165 node->box.size[0] = viddef.virtualWidth;
166 }
167 if (node->box.size[1] != viddef.virtualHeight) {
168 node->box.size[1] = viddef.virtualHeight;
169 }
170 }
171
172 /* move fullscreen window on the center of the screen */
173 if (UI_WindowIsFullScreen(node)) {
174 node->box.pos[0] = (int) ((viddef.virtualWidth - node->box.size[0]) / 2);
175 node->box.pos[1] = (int) ((viddef.virtualHeight - node->box.size[1]) / 2);
176 }
177
178 /** @todo check and fix here window outside the screen */
179
180 if (EXTRADATA(node).starLayout) {
181 UI_StarLayout(node);
182 }
183
184 /* super */
185 uiLocatedNode::doLayout(node);
186 }
187
188 /**
189 * @brief Called when we init the node on the screen
190 */
onWindowOpened(uiNode_t * node,linkedList_t * params)191 void uiWindowNode::onWindowOpened (uiNode_t* node, linkedList_t* params)
192 {
193 uiLocatedNode::onWindowOpened(node, nullptr);
194
195 /* script callback */
196 if (EXTRADATA(node).onWindowOpened)
197 UI_ExecuteEventActionsEx(node, EXTRADATA(node).onWindowOpened, params);
198
199 UI_Invalidate(node);
200 }
201
202 /**
203 * @brief Called when we close the node on the screen
204 */
onWindowClosed(uiNode_t * node)205 void uiWindowNode::onWindowClosed (uiNode_t* node)
206 {
207 uiLocatedNode::onWindowClosed(node);
208
209 /* script callback */
210 if (EXTRADATA(node).onWindowClosed)
211 UI_ExecuteEventActions(node, EXTRADATA(node).onWindowClosed);
212 }
213
214 /**
215 * @brief Called when a windows gets active again after some other window was popped from the stack
216 */
onWindowActivate(uiNode_t * node)217 void uiWindowNode::onWindowActivate (uiNode_t* node)
218 {
219 uiLocatedNode::onWindowActivate(node);
220
221 /* script callback */
222 if (EXTRADATA(node).onWindowActivate)
223 UI_ExecuteEventActions(node, EXTRADATA(node).onWindowActivate);
224 }
225
226 /**
227 * @brief Called at the begin of the load from script
228 */
onLoading(uiNode_t * node)229 void uiWindowNode::onLoading (uiNode_t* node)
230 {
231 node->box.size[0] = VID_NORM_WIDTH;
232 node->box.size[1] = VID_NORM_HEIGHT;
233 node->font = "f_big";
234 node->padding = 5;
235 }
236
237 /**
238 * @brief Called at the end of the load from script
239 */
onLoaded(uiNode_t * node)240 void uiWindowNode::onLoaded (uiNode_t* node)
241 {
242 /* create a drag zone, if it is requested */
243 if (EXTRADATA(node).dragButton) {
244 uiNode_t* control = UI_AllocNode("move_window_button", "controls", node->dynamic);
245 control->root = node;
246 control->box.size[0] = node->box.size[0];
247 control->box.size[1] = TOP_HEIGHT;
248 control->box.pos[0] = 0;
249 control->box.pos[1] = 0;
250 control->tooltip = _("Drag to move window");
251 UI_AppendNode(node, control);
252 }
253
254 /* create a close button, if it is requested */
255 if (EXTRADATA(node).closeButton) {
256 uiNode_t* button = UI_AllocNode("close_window_button", "button", node->dynamic);
257 const int positionFromRight = CONTROLS_PADDING;
258 static const char* closeCommand = "ui_close <path:root>;";
259
260 button->root = node;
261 UI_NodeSetProperty(button, UI_GetPropertyFromBehaviour(button->behaviour, "icon"), "icons/system_close");
262 /** @todo Once @c image_t is known on the client, use @c image->width resp. @c image->height here */
263 button->box.size[0] = CONTROLS_IMAGE_DIMENSIONS;
264 button->box.size[1] = CONTROLS_IMAGE_DIMENSIONS;
265 button->box.pos[0] = node->box.size[0] - positionFromRight - button->box.size[0];
266 button->box.pos[1] = CONTROLS_PADDING;
267 button->tooltip = _("Close the window");
268 button->onClick = UI_AllocStaticCommandAction(closeCommand);
269 UI_AppendNode(node, button);
270 }
271
272 EXTRADATA(node).isFullScreen = node->box.size[0] == VID_NORM_WIDTH
273 && node->box.size[1] == VID_NORM_HEIGHT;
274
275 if (EXTRADATA(node).starLayout)
276 UI_Invalidate(node);
277 }
278
clone(const uiNode_t * source,uiNode_t * clone)279 void uiWindowNode::clone (const uiNode_t* source, uiNode_t* clone)
280 {
281 /* clean up index */
282 EXTRADATA(clone).index = nullptr;
283 OBJZERO(EXTRADATA(clone).index_hash);
284 }
285
286 /**
287 * @brief Get the noticePosition from a window node.
288 * @param node A window node
289 * @return A position, else nullptr if no notice position
290 */
UI_WindowNodeGetNoticePosition(uiNode_t * node)291 vec_t* UI_WindowNodeGetNoticePosition(uiNode_t* node)
292 {
293 if (Vector2Empty(EXTRADATA(node).noticePos))
294 return nullptr;
295 return EXTRADATA(node).noticePos;
296 }
297
298 /**
299 * @brief True if the window is a drop down.
300 * @param node A window node
301 * @return True if the window is a drop down.
302 */
UI_WindowIsDropDown(uiNode_t const * const node)303 bool UI_WindowIsDropDown(uiNode_t const* const node)
304 {
305 return EXTRADATACONST(node).dropdown;
306 }
307
308 /**
309 * @brief True if the window is a modal.
310 * @param node A window node
311 * @return True if the window is a modal.
312 */
UI_WindowIsModal(uiNode_t const * const node)313 bool UI_WindowIsModal(uiNode_t const* const node)
314 {
315 return EXTRADATACONST(node).modal;
316 }
317
318 /**
319 * @brief Add a key binding to a window node.
320 * Window node store key bindings for his node child.
321 * @param node A window node
322 * @param binding Key binding to link with the window (structure should not be already linked somewhere)
323 * @todo Rework that function to remove possible wrong use of that function
324 */
UI_WindowNodeRegisterKeyBinding(uiNode_t * node,uiKeyBinding_t * binding)325 void UI_WindowNodeRegisterKeyBinding (uiNode_t* node, uiKeyBinding_t* binding)
326 {
327 assert(UI_NodeInstanceOf(node, "window"));
328 binding->next = EXTRADATA(node).keyList;
329 EXTRADATA(node).keyList = binding;
330 }
331
332 const uiKeyBinding_t* binding;
333
334 /**
335 * @brief Search a a key binding from a window node.
336 * Window node store key bindings for his node child.
337 * @param node A window node
338 * @param key A key code, either K_ value or lowercase ascii
339 */
UI_WindowNodeGetKeyBinding(uiNode_t const * const node,unsigned int key)340 uiKeyBinding_t* UI_WindowNodeGetKeyBinding (uiNode_t const* const node, unsigned int key)
341 {
342 uiKeyBinding_t* binding = EXTRADATACONST(node).keyList;
343 assert(UI_NodeInstanceOf(node, "window"));
344 while (binding) {
345 if (binding->key == key)
346 break;
347 binding = binding->next;
348 }
349 return binding;
350 }
351
UI_RegisterWindowNode(uiBehaviour_t * behaviour)352 void UI_RegisterWindowNode (uiBehaviour_t* behaviour)
353 {
354 behaviour->name = "window";
355 behaviour->manager = UINodePtr(new uiWindowNode());
356 behaviour->extraDataSize = sizeof(EXTRADATA_TYPE);
357
358 /* In windows where notify messages appear (like e.g. the video options window when you have to restart the game until
359 * the settings take effects) you can define the position of those messages with this option. */
360 UI_RegisterExtradataNodeProperty(behaviour, "noticepos", V_POS, windowExtraData_t, noticePos);
361 /* Create subnode allowing to move the window when we click on the header. Updating this attribute at runtime will change nothing. */
362 UI_RegisterExtradataNodeProperty(behaviour, "dragbutton", V_BOOL, windowExtraData_t, dragButton);
363 /* Add a button on the top right the window to close it. Updating this attribute at runtime will change nothing. */
364 UI_RegisterExtradataNodeProperty(behaviour, "closebutton", V_BOOL, windowExtraData_t, closeButton);
365 /* If true, the user can't select something outside the modal window. He must first close the window. */
366 UI_RegisterExtradataNodeProperty(behaviour, "modal", V_BOOL, windowExtraData_t, modal);
367 /* If true, the window will be closed if the user clicks outside of the window. */
368 UI_RegisterExtradataNodeProperty(behaviour, "dropdown", V_BOOL, windowExtraData_t, dropdown);
369 /* If true, the user can't use ''ESC'' key to close the window. */
370 UI_RegisterExtradataNodeProperty(behaviour, "preventtypingescape", V_BOOL, windowExtraData_t, preventTypingEscape);
371 /* If true, the window is filled according to the widescreen. */
372 UI_RegisterExtradataNodeProperty(behaviour, "fill", V_BOOL, windowExtraData_t, fill);
373 /* If true, the window content position is updated according to the "star" layout when the window size change.
374 * @todo Need more documentation.
375 */
376 UI_RegisterExtradataNodeProperty(behaviour, "starlayout", V_BOOL, windowExtraData_t, starLayout);
377
378 /* Invoked when the window is added to the rendering stack. */
379 UI_RegisterExtradataNodeProperty(behaviour, "onWindowOpened", V_UI_ACTION, windowExtraData_t, onWindowOpened);
380 /* Invoked when the window is removed from the rendering stack. */
381 UI_RegisterExtradataNodeProperty(behaviour, "onWindowClosed", V_UI_ACTION, windowExtraData_t, onWindowClosed);
382 /* Called when a windows gets active again after some other window was popped from the stack. */
383 UI_RegisterExtradataNodeProperty(behaviour, "onWindowActivate", V_UI_ACTION, windowExtraData_t, onWindowActivate);
384 /* Invoked after all UI scripts are loaded. */
385 UI_RegisterExtradataNodeProperty(behaviour, "onScriptLoaded", V_UI_ACTION, windowExtraData_t, onScriptLoaded);
386
387 /* Sprite used to display the background */
388 UI_RegisterExtradataNodeProperty(behaviour, "background", V_UI_SPRITEREF, EXTRADATA_TYPE, background);
389 }
390