1 /**
2 * @file
3 * @todo auto tooltip for chopped options
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_behaviour.h"
28 #include "../ui_parse.h"
29 #include "../ui_actions.h"
30 #include "../ui_font.h"
31 #include "../ui_input.h"
32 #include "../ui_sound.h"
33 #include "../ui_sprite.h"
34 #include "../ui_render.h"
35 #include "../ui_tooltip.h"
36 #include "ui_node_tab.h"
37 #include "ui_node_abstractnode.h"
38 #include "ui_node_abstractoption.h"
39
40 #include "../../cl_language.h"
41 #include "../../input/cl_input.h"
42
43 #define EXTRADATA_TYPE abstractOptionExtraData_t
44 #define EXTRADATA(node) UI_EXTRADATA(node, EXTRADATA_TYPE)
45 #define EXTRADATACONST(node) UI_EXTRADATACONST(node, EXTRADATA_TYPE)
46
47 typedef enum {
48 UI_TAB_NOTHING = 0,
49 UI_TAB_NORMAL = 1,
50 UI_TAB_SELECTED = 2,
51 UI_TAB_HIGHLIGHTED = 3,
52 UI_TAB_DISABLED = 4
53 } ui_tabStatus_t;
54
55 static const int TILE_WIDTH = 33;
56 static const int TILE_HEIGHT = 36;
57 static const int TILE_SIZE = 40;
58
59 /**
60 * @brief Return a tab located at a screen position
61 * @param[in] node A tab node
62 * @param[in] x The x position of the screen to test
63 * @param[in] y The x position of the screen to test
64 * @return A uiNode_t, or nullptr if nothing.
65 * @todo improve test when we are on a junction
66 * @todo improve test when we are on a chopped tab
67 */
UI_TabNodeTabAtPosition(const uiNode_t * node,int x,int y)68 static uiNode_t* UI_TabNodeTabAtPosition (const uiNode_t* node, int x, int y)
69 {
70 const char* font;
71 uiNode_t* option;
72 uiNode_t* prev = nullptr;
73 int allowedWidth;
74
75 UI_NodeAbsoluteToRelativePos(node, &x, &y);
76
77 /** @todo this dont work when an option is hidden */
78 allowedWidth = node->box.size[0] - TILE_WIDTH * (EXTRADATACONST(node).count + 1);
79
80 /* Bounded box test (shound not need, but there are problem) */
81 if (x < 0 || y < 0 || x >= node->box.size[0] || y >= node->box.size[1])
82 return nullptr;
83
84 font = UI_GetFontFromNode(node);
85
86 /* Text box test */
87 for (option = node->firstChild; option; option = option->next) {
88 int tabWidth;
89 assert(option->behaviour == ui_optionBehaviour);
90
91 /* skip hidden options */
92 if (option->invis)
93 continue;
94
95 if (x < TILE_WIDTH / 2)
96 return prev;
97
98 const char* label = CL_Translate(OPTIONEXTRADATA(option).label);
99
100 R_FontTextSize(font, label, 0, LONGLINES_PRETTYCHOP, &tabWidth, nullptr, nullptr, nullptr);
101 if (OPTIONEXTRADATA(option).icon && OPTIONEXTRADATA(option).icon->size[0] < allowedWidth) {
102 tabWidth += OPTIONEXTRADATA(option).icon->size[0];
103 }
104 if (tabWidth > allowedWidth) {
105 if (allowedWidth > 0)
106 tabWidth = allowedWidth;
107 else
108 tabWidth = 0;
109 }
110
111 if (x < tabWidth + TILE_WIDTH)
112 return option;
113
114 allowedWidth -= tabWidth;
115 x -= tabWidth + TILE_WIDTH;
116 prev = option;
117 }
118 if (x < TILE_WIDTH / 2)
119 return prev;
120 return nullptr;
121 }
122
123 /**
124 * @brief Handles tab clicks
125 */
onLeftClick(uiNode_t * node,int x,int y)126 void uiTabNode::onLeftClick (uiNode_t* node, int x, int y)
127 {
128 uiNode_t* option;
129
130 if (UI_AbstractOptionGetCurrentValue(node) == nullptr)
131 return;
132
133 option = UI_TabNodeTabAtPosition(node, x, y);
134 if (option == nullptr)
135 return;
136
137 if (option->disabled)
138 return;
139
140 /* only execute the click stuff if the selectbox is active */
141 if (node->state)
142 UI_AbstractOptionSetCurrentValue(node, OPTIONEXTRADATA(option).value);
143
144 UI_PlaySound("click1");
145 }
146
147 /**
148 * @brief Normalized access to the texture structure of tab to display the plain part of a tab
149 * @param[in] image The normalized tab texture to use
150 * @param[in] x The upper-left position of the screen to draw the texture
151 * @param[in] y The upper-left position of the screen to draw the texture
152 * @param[in] width The width size of the screen to use (stretch)
153 * @param[in] type The status of the tab we display
154 */
UI_TabNodeDrawPlain(const char * image,int x,int y,int width,ui_tabStatus_t type)155 static inline void UI_TabNodeDrawPlain (const char* image, int x, int y, int width, ui_tabStatus_t type)
156 {
157 /* Hack sl=1 to not use the pixel on the left border on the texture (create graphic bug) */
158 UI_DrawNormImageByName(false, x, y, width, TILE_HEIGHT, TILE_WIDTH + TILE_SIZE * 0, TILE_HEIGHT + TILE_SIZE * type,
159 1 + TILE_SIZE * 0, 0 + TILE_SIZE * type, image);
160 }
161
162 /**
163 * @brief Normalized access to the texture structure of tab to display a junction between each tabs
164 * @param[in] image The normalized tab texture to use
165 * @param[in] x The upper-left position of the screen to draw the texture
166 * @param[in] y The upper-left position of the screen to draw the texture
167 * @param[in] leftType The status of the left tab of the junction we display
168 * @param[in] rightType The status of the right tab of the junction we display
169 */
UI_TabNodeDrawJunction(const char * image,int x,int y,ui_tabStatus_t leftType,ui_tabStatus_t rightType)170 static inline void UI_TabNodeDrawJunction (const char* image, int x, int y, ui_tabStatus_t leftType, ui_tabStatus_t rightType)
171 {
172 UI_DrawNormImageByName(false, x, y, TILE_WIDTH, TILE_HEIGHT, TILE_WIDTH + TILE_SIZE * (1 + rightType), TILE_HEIGHT + TILE_SIZE * leftType,
173 0 + TILE_SIZE * (1 + rightType), 0 + TILE_SIZE * leftType, image);
174 }
175
draw(uiNode_t * node)176 void uiTabNode::draw (uiNode_t* node)
177 {
178 ui_tabStatus_t lastStatus = UI_TAB_NOTHING;
179 uiNode_t* option;
180 uiNode_t* overMouseOption = nullptr;
181 const char* ref;
182 const char* font;
183 int currentX;
184 int allowedWidth;
185 vec2_t pos;
186
187 const char* image = UI_GetReferenceString(node, node->image);
188 if (!image)
189 image = "ui/tab";
190
191 ref = UI_AbstractOptionGetCurrentValue(node);
192 if (ref == nullptr)
193 return;
194
195 font = UI_GetFontFromNode(node);
196
197 if (node->state) {
198 overMouseOption = UI_TabNodeTabAtPosition(node, mousePosX, mousePosY);
199 }
200
201 UI_GetNodeAbsPos(node, pos);
202 currentX = pos[0];
203 option = node->firstChild;
204 assert(option->behaviour == ui_optionBehaviour);
205 /** @todo this dont work when an option is hidden */
206 allowedWidth = node->box.size[0] - TILE_WIDTH * (EXTRADATA(node).count + 1);
207
208 while (option) {
209 int fontHeight;
210 int fontWidth;
211 int tabWidth;
212 int textPos;
213 bool drawIcon = false;
214 ui_tabStatus_t status = UI_TAB_NORMAL;
215 assert(option->behaviour == ui_optionBehaviour);
216
217 /* skip hidden options */
218 if (option->invis) {
219 option = option->next;
220 continue;
221 }
222
223 /* Check the status of the current tab */
224 if (Q_streq(OPTIONEXTRADATA(option).value, ref)) {
225 status = UI_TAB_SELECTED;
226 } else if (option->disabled || node->disabled) {
227 status = UI_TAB_DISABLED;
228 } else if (option == overMouseOption) {
229 status = UI_TAB_HIGHLIGHTED;
230 }
231
232 /* Display */
233 UI_TabNodeDrawJunction(image, currentX, pos[1], lastStatus, status);
234 currentX += TILE_WIDTH;
235
236 const char* label = CL_Translate(OPTIONEXTRADATA(option).label);
237
238 R_FontTextSize(font, label, 0, LONGLINES_PRETTYCHOP, &fontWidth, &fontHeight, nullptr, nullptr);
239 tabWidth = fontWidth;
240 if (OPTIONEXTRADATA(option).icon && OPTIONEXTRADATA(option).icon->size[0] < allowedWidth) {
241 tabWidth += OPTIONEXTRADATA(option).icon->size[0];
242 drawIcon = true;
243 }
244 if (tabWidth > allowedWidth) {
245 if (allowedWidth > 0)
246 tabWidth = allowedWidth;
247 else
248 tabWidth = 0;
249 }
250
251 if (tabWidth > 0) {
252 UI_TabNodeDrawPlain(image, currentX, pos[1], tabWidth, status);
253 }
254
255 textPos = currentX;
256 if (drawIcon) {
257 uiSpriteStatus_t iconStatus = SPRITE_STATUS_NORMAL;
258 if (status == UI_TAB_DISABLED) {
259 iconStatus = SPRITE_STATUS_DISABLED;
260 }
261 UI_DrawSpriteInBox(OPTIONEXTRADATA(option).flipIcon, OPTIONEXTRADATA(option).icon, iconStatus, currentX, pos[1], OPTIONEXTRADATA(option).icon->size[0], TILE_HEIGHT);
262 textPos += OPTIONEXTRADATA(option).icon->size[0];
263 }
264
265 /** @todo fontWidth can be =0, maybe a bug from the font cache */
266 OPTIONEXTRADATA(option).truncated = tabWidth < fontWidth || tabWidth == 0;
267 UI_DrawString(font, ALIGN_UL, textPos, pos[1] + ((node->box.size[1] - fontHeight) / 2),
268 textPos, tabWidth + 1, 0, label, 0, 0, nullptr, false, LONGLINES_PRETTYCHOP);
269 currentX += tabWidth;
270 allowedWidth -= tabWidth;
271
272 /* Next */
273 lastStatus = status;
274 option = option->next;
275 }
276
277 /* Display last junction and end of header */
278 UI_TabNodeDrawJunction(image, currentX, pos[1], lastStatus, UI_TAB_NOTHING);
279 currentX += TILE_WIDTH;
280 if (currentX < pos[0] + node->box.size[0])
281 UI_TabNodeDrawPlain(image, currentX, pos[1], pos[0] + node->box.size[0] - currentX, UI_TAB_NOTHING);
282 }
283
284 /**
285 * @brief Custom tooltip of tab node
286 * @param[in] node Node we request to draw tooltip
287 * @param[in] x Position x of the mouse
288 * @param[in] y Position y of the mouse
289 */
drawTooltip(const uiNode_t * node,int x,int y) const290 void uiTabNode::drawTooltip (const uiNode_t* node, int x, int y) const
291 {
292 uiNode_t* option;
293 const int tooltipWidth = 250;
294
295 option = UI_TabNodeTabAtPosition(node, x, y);
296 if (option == nullptr)
297 return;
298
299 if (!OPTIONEXTRADATA(option).truncated)
300 return;
301
302 const char* label = CL_Translate(OPTIONEXTRADATA(option).label);
303 UI_DrawTooltip(label, x, y, tooltipWidth);
304 }
305
306 /** called when the window is pushed
307 * check cvar then, reduce runtime check
308 * @todo move cvar check to AbstractOption
309 */
onWindowOpened(uiNode_t * node,linkedList_t * params)310 void uiTabNode::onWindowOpened (uiNode_t* node, linkedList_t* params)
311 {
312 /* no cvar given? */
313 if (!(EXTRADATA(node).cvar))
314 return;
315
316 /* not a cvar? */
317 char const* const cvarName = Q_strstart(EXTRADATA(node).cvar, "*cvar:");
318 if (!cvarName) {
319 /* normalize */
320 Com_Printf("UI_TabNodeInit: node '%s' doesn't have a valid cvar assigned (\"%s\" read)\n", UI_GetPath(node), EXTRADATA(node).cvar);
321 EXTRADATA(node).cvar = nullptr;
322 return;
323 }
324
325 /* cvar do not exists? */
326 if (Cvar_FindVar(cvarName) == nullptr) {
327 /* search default value, if possible */
328 uiNode_t* option = node->firstChild;
329 assert(option->behaviour == ui_optionBehaviour);
330 Cvar_ForceSet(cvarName, OPTIONEXTRADATA(option).value);
331 }
332 }
333
UI_RegisterTabNode(uiBehaviour_t * behaviour)334 void UI_RegisterTabNode (uiBehaviour_t* behaviour)
335 {
336 behaviour->name = "tab";
337 behaviour->extends = "abstractoption";
338 behaviour->manager = UINodePtr(new uiTabNode());
339 behaviour->drawItselfChild = true;
340 }
341