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