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_parse.h"
28 #include "ui_sprite.h"
29 #include "ui_render.h"
30 
31 const value_t ui_spriteProperties[] = {
32 	{"size", V_POS, offsetof(uiSprite_t, size), MEMBER_SIZEOF(uiSprite_t, size)},
33 	{"single", V_BOOL, offsetof(uiSprite_t, single), 0},
34 	{"blend", V_BOOL, offsetof(uiSprite_t, blend), 0},
35 	{"pack64", V_BOOL, offsetof(uiSprite_t, pack64), 0},
36 	{"tiled_17_1_3", V_BOOL, offsetof(uiSprite_t, tiled_17_1_3), 0},
37 	{"tiled_25_1_3", V_BOOL, offsetof(uiSprite_t, tiled_25_1_3), 0},
38 	{"tiled_popup", V_BOOL, offsetof(uiSprite_t, tiled_popup), 0},
39 	{"border", V_INT, offsetof(uiSprite_t, border), MEMBER_SIZEOF(uiSprite_t, border)},
40 
41 	{"texl", V_POS, offsetof(uiSprite_t, pos[SPRITE_STATUS_NORMAL]), MEMBER_SIZEOF(uiSprite_t, pos[SPRITE_STATUS_NORMAL])},
42 	{"hoveredtexl", V_POS, offsetof(uiSprite_t, pos[SPRITE_STATUS_HOVER]), MEMBER_SIZEOF(uiSprite_t, pos[SPRITE_STATUS_HOVER])},
43 	{"disabledtexl", V_POS, offsetof(uiSprite_t, pos[SPRITE_STATUS_DISABLED]), MEMBER_SIZEOF(uiSprite_t, pos[SPRITE_STATUS_DISABLED])},
44 	{"clickedtexl", V_POS, offsetof(uiSprite_t, pos[SPRITE_STATUS_CLICKED]), MEMBER_SIZEOF(uiSprite_t, pos[SPRITE_STATUS_CLICKED])},
45 
46 	{"image", (valueTypes_t)V_REF_OF_STRING, offsetof(uiSprite_t, image[SPRITE_STATUS_NORMAL]), 0},
47 	{"hoveredimage", (valueTypes_t)V_REF_OF_STRING, offsetof(uiSprite_t, image[SPRITE_STATUS_HOVER]), 0},
48 	{"disabledimage", (valueTypes_t)V_REF_OF_STRING, offsetof(uiSprite_t, image[SPRITE_STATUS_DISABLED]), 0},
49 	{"clickedimage", (valueTypes_t)V_REF_OF_STRING, offsetof(uiSprite_t, image[SPRITE_STATUS_CLICKED]), 0},
50 
51 	{"color", V_COLOR, offsetof(uiSprite_t, color[SPRITE_STATUS_NORMAL]), MEMBER_SIZEOF(uiSprite_t, color[SPRITE_STATUS_NORMAL])},
52 	{"hoveredcolor", V_COLOR, offsetof(uiSprite_t, color[SPRITE_STATUS_HOVER]), MEMBER_SIZEOF(uiSprite_t, color[SPRITE_STATUS_HOVER])},
53 	{"disabledcolor", V_COLOR, offsetof(uiSprite_t, color[SPRITE_STATUS_DISABLED]), MEMBER_SIZEOF(uiSprite_t, color[SPRITE_STATUS_DISABLED])},
54 	{"clickedcolor", V_COLOR, offsetof(uiSprite_t, color[SPRITE_STATUS_CLICKED]), MEMBER_SIZEOF(uiSprite_t, color[SPRITE_STATUS_CLICKED])},
55 
56 	{nullptr, V_NULL, 0, 0}
57 };
58 
59 /**
60  * @brief Search a file name inside pics/ according to the sprite name
61  * If it exists, generate a "single" sprite using the size of the image
62  * @param name Name of the sprite
63  * @return A sprite, else nullptr
64  */
UI_AutoGenerateSprite(const char * name)65 static uiSprite_t* UI_AutoGenerateSprite (const char* name)
66 {
67 	uiSprite_t* sprite = nullptr;
68 	const char* suffix[SPRITE_STATUS_MAX] = {"", "_hovered", "_disabled", "_clicked"};
69 	char basePicNameBuf[MAX_QPATH];
70 	const image_t* pic;
71 	int i;
72 
73 	Q_strncpyz(basePicNameBuf, name, sizeof(basePicNameBuf));
74 
75 	pic = UI_LoadImage(basePicNameBuf);
76 	if (pic == nullptr)
77 		return nullptr;
78 
79 	sprite = UI_AllocStaticSprite(basePicNameBuf);
80 	sprite->image[SPRITE_STATUS_NORMAL] = UI_AllocStaticString(basePicNameBuf, 0);
81 	sprite->size[0] = pic->width;
82 	sprite->size[1] = pic->height;
83 	for (i = 1; i < SPRITE_STATUS_MAX; i++) {
84 		char picNameBuf[MAX_QPATH];
85 		Com_sprintf(picNameBuf, sizeof(picNameBuf), "%s%s", basePicNameBuf, suffix[i]);
86 		pic = UI_LoadImage(picNameBuf);
87 		if (pic != nullptr)
88 			sprite->image[i] = UI_AllocStaticString(picNameBuf, 0);
89 	}
90 	return sprite;
91 }
92 
93 /**
94  * @brief Check if an sprite name exists
95  * @param[in] name Name of the sprite
96  * @return True if the sprite exists
97  * @note not very fast; if we use it often we should improve the search
98  */
UI_SpriteExists(const char * name)99 static bool UI_SpriteExists (const char* name)
100 {
101 	int i;
102 	for (i = 0; i < ui_global.numSprites; i++) {
103 		if (strncmp(name, ui_global.sprites[i].name, MEMBER_SIZEOF(uiSprite_t, name)) != 0)
104 			continue;
105 		return true;
106 	}
107 	return false;
108 }
109 
110 /**
111  * @brief Return an sprite by is name
112  * @param[in] name Name of the sprite
113  * @return The requested sprite, else nullptr
114  * @note not very fast; if we use it often we should improve the search
115  */
UI_GetSpriteByName(const char * name)116 uiSprite_t* UI_GetSpriteByName (const char* name)
117 {
118 	int i;
119 	for (i = 0; i < ui_global.numSprites; i++) {
120 		if (Q_streq(name, ui_global.sprites[i].name))
121 			return &ui_global.sprites[i];
122 	}
123 	return UI_AutoGenerateSprite(name);
124 }
125 
126 /**
127  * @brief Allocate an sprite to the UI static memory
128  * @note Its not a dynamic memory allocation. Please only use it at the loading time
129  * @param[in] name Name of the sprite
130  * @todo Assert out when we are not in parsing/loading stage
131  */
UI_AllocStaticSprite(const char * name)132 uiSprite_t* UI_AllocStaticSprite (const char* name)
133 {
134 	uiSprite_t* result;
135 
136 	if (UI_SpriteExists(name))
137 		Com_Error(ERR_FATAL, "UI_AllocStaticSprite: \"%s\" sprite already allocated. Check your scripts.", name);
138 
139 	if (ui_global.numSprites >= UI_MAX_SPRITES)
140 		Com_Error(ERR_FATAL, "UI_AllocStaticSprite: UI_MAX_SPRITES hit");
141 
142 	result = &ui_global.sprites[ui_global.numSprites];
143 	ui_global.numSprites++;
144 
145 	OBJZERO(*result);
146 	Q_strncpyz(result->name, name, sizeof(result->name));
147 	return result;
148 }
149 
150 /**
151  * Template to draw a tiled texture with
152  * corner size of 17 pixels, middle size of 1 pixel,
153  * margin between tiles of 3 pixels
154  */
155 static const int tile_template_17_1_3[] = {
156 	17, 1, 17,
157 	17, 1, 17,
158 	3
159 };
160 
161 /**
162  * Template to draw a tiled texture with
163  * corner size of 17 pixels, middle size of 1 pixel,
164  * margin between tiles of 3 pixels
165  */
166 static const int tile_template_25_1_3[] = {
167 	25, 1, 25,
168 	25, 1, 25,
169 	3
170 };
171 
172 /**
173  * Template to draw a tiled texture with
174  * used for popup windows
175  */
176 static const int tile_template_popup[] = {
177 	20, 1, 19,
178 	46, 1, 19,
179 	3
180 };
181 
182 /**
183  * @param[in] status The state of the sprite node
184  * @param[in] sprite Context sprite
185  * @param[in] posX Absolute X position of the top-left corner
186  * @param[in] posY Absolute Y position of the top-left corner
187  * @param[in] sizeX Width of the bounded box
188  * @param[in] sizeY Height of the bounded box
189  * @todo use named const for status
190  */
UI_DrawSpriteInBox(bool flip,const uiSprite_t * sprite,uiSpriteStatus_t status,int posX,int posY,int sizeX,int sizeY)191 void UI_DrawSpriteInBox (bool flip, const uiSprite_t* sprite, uiSpriteStatus_t status, int posX, int posY, int sizeX, int sizeY)
192 {
193 	float texX;
194 	float texY;
195 	const char* image;
196 
197 	/** @todo Add warning */
198 	if (status >= SPRITE_STATUS_MAX)
199 		return;
200 
201 	/** @todo merge all this cases */
202 	if (sprite->single || sprite->blend) {
203 		texX = sprite->pos[SPRITE_STATUS_NORMAL][0];
204 		texY = sprite->pos[SPRITE_STATUS_NORMAL][1];
205 		image = sprite->image[SPRITE_STATUS_NORMAL];
206 	} else if (sprite->pack64) {
207 		texX = sprite->pos[SPRITE_STATUS_NORMAL][0];
208 		texY = sprite->pos[SPRITE_STATUS_NORMAL][1] + (64 * status);
209 		image = sprite->image[SPRITE_STATUS_NORMAL];
210 	} else {
211 		texX = sprite->pos[status][0];
212 		texY = sprite->pos[status][1];
213 		image = sprite->image[status];
214 		if (!image) {
215 			if (texX == 0 && texY == 0) {
216 				texX = sprite->pos[SPRITE_STATUS_NORMAL][0];
217 				texY = sprite->pos[SPRITE_STATUS_NORMAL][1];
218 				image = sprite->image[SPRITE_STATUS_NORMAL];
219 			} else {
220 				image = sprite->image[SPRITE_STATUS_NORMAL];
221 			}
222 		}
223 	}
224 	if (!image)
225 		return;
226 
227 	if (sprite->blend) {
228 		const vec_t* color = sprite->color[status];
229 		R_Color(color);
230 	}
231 
232 	if (sprite->tiled_17_1_3) {
233 		const vec2_t pos = Vector2FromInt(posX, posY);
234 		const vec2_t size = Vector2FromInt(sizeX, sizeY);
235 		UI_DrawPanel(pos, size, image, texX, texY, tile_template_17_1_3);
236 	} else if (sprite->tiled_25_1_3) {
237 		const vec2_t pos = Vector2FromInt(posX, posY);
238 		const vec2_t size = Vector2FromInt(sizeX, sizeY);
239 		UI_DrawPanel(pos, size, image, texX, texY, tile_template_25_1_3);
240 	} else if (sprite->tiled_popup) {
241 		const vec2_t pos = Vector2FromInt(posX, posY);
242 		const vec2_t size = Vector2FromInt(sizeX, sizeY);
243 		UI_DrawPanel(pos, size, image, texX, texY, tile_template_popup);
244 	} else if (sprite->border != 0) {
245 		const vec2_t pos = Vector2FromInt(posX, posY);
246 		const vec2_t size = Vector2FromInt(sizeX, sizeY);
247 		UI_DrawBorderedPanel(pos, size, image, texX, texY, sprite->size[0], sprite->size[1], sprite->border);
248 	} else {
249 		posX += (sizeX - sprite->size[0]) / 2;
250 		posY += (sizeY - sprite->size[1]) / 2;
251 		UI_DrawNormImageByName(flip, posX, posY, sprite->size[0], sprite->size[1],
252 			texX + sprite->size[0], texY + sprite->size[1], texX, texY, image);
253 	}
254 
255 	if (sprite->blend)
256 		R_Color(nullptr);
257 }
258