1 /*
2  * Copyright (C) 2011-2013 Me and My Shadow
3  *
4  * This file is part of Me and My Shadow.
5  *
6  * Me and My Shadow is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Me and My Shadow is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Me and My Shadow.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "GameState.h"
21 #include "Functions.h"
22 #include "FileManager.h"
23 #include "Globals.h"
24 #include "LevelSelect.h"
25 #include "GUIObject.h"
26 #include "GUIListBox.h"
27 #include "GUIScrollBar.h"
28 #include "InputManager.h"
29 #include <stdio.h>
30 #include <string>
31 #include <sstream>
32 #include <iostream>
33 
34 #include "libs/tinyformat/tinyformat.h"
35 
36 using namespace std;
37 
38 ////////////////////NUMBER////////////////////////
Number(ImageManager & imageManager,SDL_Renderer & renderer)39 Number::Number(ImageManager& imageManager, SDL_Renderer& renderer){
40 	image=NULL;
41 	number=0;
42 	medal=0;
43 	selected=false;
44 	locked=false;
45 
46 	//Set the default dimensions.
47 	box.x=0;
48 	box.y=0;
49 	box.h=50;
50 	box.w=50;
51 
52 	//Load the medals image.
53     medals=imageManager.loadTexture(getDataPath()+"gfx/medals.png", renderer);
54     //To make sure it can be added to a vector, stop here rather than generate a massive error.
55     static_assert(std::is_move_constructible<Number>::value, "Not move constructable!");
56 }
57 
init(SDL_Renderer & renderer,int number,SDL_Rect box)58 void Number::init(SDL_Renderer& renderer,int number,SDL_Rect box){
59 	//Write our text, number+1 since the counting doens't start with 0, but with 1.
60 	std::stringstream text;
61 	text << (number + 1);
62 
63 	init(renderer, text.str(), box, number);
64 }
65 
init(SDL_Renderer & renderer,const std::string & text,SDL_Rect box,int number)66 void Number::init(SDL_Renderer& renderer,const std::string& text,SDL_Rect box,int number){
67 	//First set the number and update our status.
68 	this->number = number;
69 
70 	//Create the text image. Also check which font to use.
71 	image = textureFromText(renderer,
72 		(text.size() >= 3) ? (*fontGUISmall) : (*fontGUI),
73 		text.c_str(), objThemes.getTextColor(true));
74 
75 	//Set the new location of the number.
76 	this->box.x=box.x;
77 	this->box.y=box.y;
78 
79 	//Load background blocks.
80 	objThemes.getBlock(TYPE_BLOCK,true)->createInstance(&block);
81 	block.changeState("unlocked");
82 
83 	objThemes.getBlock(TYPE_SHADOW_BLOCK,true)->createInstance(&blockLocked);
84 	blockLocked.changeState("locked");
85 }
86 
show(SDL_Renderer & renderer,int dy)87 void Number::show(SDL_Renderer& renderer, int dy){
88 	//First draw the background, also apply the yOffset(dy).
89 	if(!locked)
90         block.draw(renderer,box.x,box.y-dy);
91 	else
92         blockLocked.draw(renderer,box.x,box.y-dy);
93 	//Now draw the text image over the background.
94 	//We draw it centered inside the box.
95 	applyTexture(box.x + 25 - (textureWidth(*image) / 2), box.y - 2 + 25 - (textureHeight(*image) / 2) - dy, image, renderer);
96 
97 	//Draw the selection mark.
98 	if(selected){
99         drawGUIBox(box.x,box.y-dy,50,50,renderer,0xFFFFFF23);
100 	}
101 
102 	//Draw the medal.
103     if(medal>0&&medals){
104         const SDL_Rect srcRect={(medal-1)*30,0,30,30};
105         const SDL_Rect dstRect={box.x+30,(box.y+30)-dy,30,30};
106         SDL_RenderCopy(&renderer,medals.get(),&srcRect,&dstRect);
107 	}
108 }
109 
setLocked(bool locked)110 void Number::setLocked(bool locked){
111 	this->locked=locked;
112 }
113 
setMedal(int medal)114 void Number::setMedal(int medal){
115 	this->medal=medal;
116 }
117 
118 
119 /////////////////////LEVEL SELECT/////////////////////
LevelSelect(ImageManager & imageManager,SDL_Renderer & renderer,const char * titleText,LevelPackManager::LevelPackLists packType)120 LevelSelect::LevelSelect(ImageManager& imageManager,SDL_Renderer& renderer, const char* titleText, LevelPackManager::LevelPackLists packType){
121 	//clear the selected level
122 	selectedNumber=NULL;
123 
124 	//Calculate the LEVELS_PER_ROW and LEVEL_ROWS if they aren't calculated already.
125 	calcRows();
126 
127 	//Render the title.
128 	title = titleTextureFromText(renderer, titleText, objThemes.getTextColor(false), SCREEN_WIDTH);
129 
130 	//create GUI (test only)
131 	GUIObject* obj;
132 	if(GUIObjectRoot){
133 		delete GUIObjectRoot;
134 		GUIObjectRoot=NULL;
135 	}
136 
137     GUIObjectRoot=new GUIObject(imageManager,renderer,0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
138 
139 	//the level select scroll bar
140     levelScrollBar=new GUIScrollBar(imageManager,renderer,SCREEN_WIDTH*0.9,184,16,SCREEN_HEIGHT-344,ScrollBarVertical,0,0,0,1,4,true,false);
141 	GUIObjectRoot->addChild(levelScrollBar);
142 
143 	//level pack description
144     levelpackDescription=new GUILabel(imageManager,renderer,0,140,SCREEN_WIDTH,32,"",0,true,true,GUIGravityCenter);
145 	GUIObjectRoot->addChild(levelpackDescription);
146 
147     levelpacks=new GUISingleLineListBox(imageManager,renderer,(SCREEN_WIDTH-500)/2,104,500,32);
148 	levelpacks->name="cmdLvlPack";
149 	levelpacks->eventCallback=this;
150 	vector<pair<string,string> > v=getLevelPackManager()->enumLevelPacks(packType);
151 	levelpacks->addItems(v);
152 	levelpacks->value=0;
153 
154 	//Check if we can find the lastlevelpack.
155 	for(vector<pair<string,string> >::iterator i=v.begin(); i!=v.end(); ++i){
156 		if(i->first==getSettings()->getValue("lastlevelpack")){
157 			levelpacks->value=i-v.begin();
158 		}
159 	}
160 
161 	//Load the progress.
162 	levels=getLevelPackManager()->getLevelPack(v[levelpacks->value].first);
163 	levels->loadProgress();
164 
165 	//And add the levelpack single line listbox to the GUIObjectRoot.
166 	GUIObjectRoot->addChild(levelpacks);
167 
168     obj=new GUIButton(imageManager,renderer,20,20,-1,32,_("Back"));
169 	obj->name="cmdBack";
170 	obj->eventCallback=this;
171 	GUIObjectRoot->addChild(obj);
172 
173 	section=1;
174 	section2 = 1;
175 }
176 
~LevelSelect()177 LevelSelect::~LevelSelect(){
178 	if(GUIObjectRoot){
179 		delete GUIObjectRoot;
180 		GUIObjectRoot=NULL;
181 	}
182 	levelScrollBar=NULL;
183 	levelpackDescription=NULL;
184 
185 	selectedNumber=NULL;
186 }
187 
calcRows()188 void LevelSelect::calcRows(){
189 	//Calculate the number of rows and the number of levels per row.
190 	LEVELS_PER_ROW=(SCREEN_WIDTH*0.8)/64;
191 	int LEVEL_ROWS=(SCREEN_HEIGHT-344)/64;
192 	LEVELS_DISPLAYED_IN_SCREEN=LEVELS_PER_ROW*LEVEL_ROWS;
193 }
194 
selectNumberKeyboard(ImageManager & imageManager,SDL_Renderer & renderer,int x,int y)195 void LevelSelect::selectNumberKeyboard(ImageManager& imageManager, SDL_Renderer& renderer, int x,int y){
196 	isKeyboardOnly = true;
197 
198 	if(section==2){
199 		//Move selection
200 		int realNumber=-1;
201 		if(selectedNumber)
202 			realNumber=selectedNumber->getNumber()+x+(y*LEVELS_PER_ROW);
203 
204 		int delta = (x + y < 0) ? -1 : 1;
205 
206 		for (;;) {
207 			//If selection is outside of the map grid, change section
208 			if (realNumber<0 || realNumber>(int)numbers.size() - 1){
209 				section = 1;
210 				for (int i = 0; i < (int)numbers.size(); i++){
211 					numbers[i].selected = false;
212 				}
213 				selectNumber(imageManager, renderer, -1, false);
214 				break;
215 			} else {
216 				//If not, move selection
217 				if (!numbers[realNumber].getLocked()){
218 					for (int i = 0; i < (int)numbers.size(); i++){
219 						numbers[i].selected = (i == realNumber);
220 					}
221 					selectNumber(imageManager, renderer, realNumber, false);
222 
223 					//FIXME: ad-hoc scrollbar scrolling code
224 					levelScrollBar->value = clamp(realNumber / LEVELS_PER_ROW, levelScrollBar->minValue, levelScrollBar->maxValue);
225 					break;
226 				}
227 			}
228 			realNumber += delta;
229 		}
230 	}else if(section==1){
231 		if (x != 0) {
232 			//Loop through levelpacks and update GUI
233 			levelpacks->value += x;
234 
235 			if (levelpacks->value<0){
236 				levelpacks->value = levelpacks->item.size() - 1;
237 			} else if (levelpacks->value>(int)levelpacks->item.size() - 1){
238 				levelpacks->value = 0;
239 			}
240 
241 			GUIEventCallback_OnEvent(imageManager, renderer, "cmdLvlPack", static_cast<GUIObject*>(levelpacks), 0);
242 		}
243 
244 		//If down is pressed, change section
245 		if(y==1){
246 			section=2;
247 			if (!numbers.empty()) {
248 				selectNumber(imageManager, renderer, 0, false);
249 				numbers[0].selected = true;
250 
251 				//FIXME: ad-hoc scrollbar scrolling code
252 				//scroll the scrollbar to top
253 				levelScrollBar->value = 0;
254 			}
255 		}
256 	}
257 }
258 
handleEvents(ImageManager & imageManager,SDL_Renderer & renderer)259 void LevelSelect::handleEvents(ImageManager& imageManager, SDL_Renderer& renderer){
260 	//Check for an SDL_QUIT event.
261 	if(event.type==SDL_QUIT){
262 		setNextState(STATE_EXIT);
263 	}
264 
265 	//Check for a mouse click.
266 	if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT){
267         checkMouse(imageManager, renderer);
268 	}
269 
270 	//Check focus movement
271 	if(inputMgr.isKeyDownEvent(INPUTMGR_RIGHT)){
272         selectNumberKeyboard(imageManager, renderer, 1,0);
273 	}else if(inputMgr.isKeyDownEvent(INPUTMGR_LEFT)){
274         selectNumberKeyboard(imageManager, renderer, -1,0);
275 	}else if(inputMgr.isKeyDownEvent(INPUTMGR_UP)){
276         selectNumberKeyboard(imageManager, renderer, 0,-1);
277 	}else if(inputMgr.isKeyDownEvent(INPUTMGR_DOWN)){
278         selectNumberKeyboard(imageManager, renderer, 0,1);
279 	}
280 
281 	if (inputMgr.isKeyDownEvent(INPUTMGR_TAB)) {
282 		isKeyboardOnly = true;
283 
284 		int mod = SDL_GetModState();
285 
286 		section += (mod & KMOD_SHIFT) ? -1 : 1;
287 		if (section < 1) section = 3;
288 		else if (section > 3) section = 1;
289 
290 		if (section == 2 && (selectedNumber == NULL || selectedNumber->getNumber() < 0) && !numbers.empty()) {
291 			selectNumber(imageManager, renderer, 0, false);
292 			numbers[0].selected = true;
293 		}
294 	}
295 
296 	//Check if enter is pressed
297 	if (isKeyboardOnly && section == 2 && inputMgr.isKeyDownEvent(INPUTMGR_SELECT) && selectedNumber) {
298 		int n = selectedNumber->getNumber();
299 		if (n >= 0) {
300 			selectNumber(imageManager, renderer, n, true);
301 		}
302 	}
303 
304 	//Check if escape is pressed.
305 	if(inputMgr.isKeyDownEvent(INPUTMGR_ESCAPE)){
306 		setNextState(STATE_MENU);
307 	}
308 
309 	//Check for scrolling down and up.
310 	if (event.type == SDL_MOUSEWHEEL && levelScrollBar){
311 		//TODO - tweak the scroll amount
312 		levelScrollBar->value += event.wheel.y > 0 ? -1 : 1;
313 		if (levelScrollBar->value > levelScrollBar->maxValue) {
314 			levelScrollBar->value = levelScrollBar->maxValue;
315 		}
316 		if (levelScrollBar->value < 0) {
317 			levelScrollBar->value = 0;
318 		}
319 	}
320 }
321 
checkMouse(ImageManager & imageManager,SDL_Renderer & renderer)322 void LevelSelect::checkMouse(ImageManager &imageManager, SDL_Renderer &renderer){
323 	int x,y,dy=0,m=numbers.size();
324 
325 	//Get the current mouse location.
326 	SDL_GetMouseState(&x,&y);
327 
328 	//Check if there's a scrollbar, if so get the value.
329 	if(levelScrollBar)
330 		dy=levelScrollBar->value;
331 	//Upper bound of levels we'd like to display.
332 	if(m>dy*LEVELS_PER_ROW+LEVELS_DISPLAYED_IN_SCREEN)
333 		m=dy*LEVELS_PER_ROW+LEVELS_DISPLAYED_IN_SCREEN;
334 	y+=dy*64;
335 
336 	SDL_Rect mouse={x,y,0,0};
337 
338 	for(int n=dy*LEVELS_PER_ROW; n<m; n++){
339 		if(!numbers[n].getLocked()){
340 			if(pointOnRect(mouse,numbers[n].box)==true){
341 				if(numbers[n].selected){
342                     selectNumber(imageManager, renderer, n,true);
343 				}else{
344 					//Select current level
345 					for(int i=0;i<levels->getLevelCount();i++){
346 						numbers[i].selected=(i==n);
347 					}
348                     selectNumber(imageManager, renderer,n,false);
349 				}
350 				section=2;
351 				break;
352 			}
353 		}
354 	}
355 }
356 
logic(ImageManager &,SDL_Renderer &)357 void LevelSelect::logic(ImageManager&, SDL_Renderer&){}
358 
render(ImageManager &,SDL_Renderer & renderer)359 void LevelSelect::render(ImageManager&, SDL_Renderer& renderer){
360 	int x,y,dy=0,m=numbers.size();
361 	int idx=-1;
362 
363 	//Get the current mouse location.
364 	SDL_GetMouseState(&x,&y);
365 
366 	if(levelScrollBar)
367 		dy=levelScrollBar->value;
368 	//Upper bound of levels we'd like to display.
369 	if(m>dy*LEVELS_PER_ROW+LEVELS_DISPLAYED_IN_SCREEN)
370 		m=dy*LEVELS_PER_ROW+LEVELS_DISPLAYED_IN_SCREEN;
371 	y+=dy*64;
372 
373 	SDL_Rect mouse={x,y,0,0};
374 
375 	//Draw background.
376     objThemes.getBackground(true)->draw(renderer);
377 	objThemes.getBackground(true)->updateAnimation();
378 	//Draw the title.
379     drawTitleTexture(SCREEN_WIDTH, *title, renderer);
380 
381 	//Draw highlight and do some calculations in keyboard-only mode.
382 	int realNumber = -1;
383 	if (isKeyboardOnly) {
384 		levelpacks->state = (section == 1) ? 0x100 : 0;
385 		if (selectedNumber)
386 			realNumber = selectedNumber->getNumber();
387 	}
388 
389 	//Loop through the level blocks and draw them.
390 	for(int n=dy*LEVELS_PER_ROW;n<m;n++){
391         numbers[n].show(renderer,dy*64);
392 		if (!numbers[n].getLocked()) {
393 			if (isKeyboardOnly) {
394 				if (realNumber == n) idx = n;
395 			} else {
396 				if (pointOnRect(mouse, numbers[n].box)) idx = n;
397 			}
398 		}
399 	}
400 
401 	//Show the tool tip text.
402 	if(idx>=0){
403         renderTooltip(renderer,idx,dy);
404 	}
405 }
406 
resize(ImageManager & imageManager,SDL_Renderer & renderer)407 void LevelSelect::resize(ImageManager& imageManager,SDL_Renderer& renderer){
408 	levelScrollBar->left = SCREEN_WIDTH*0.9;
409 	levelScrollBar->top = 184;
410 	levelScrollBar->width = 16;
411 	levelScrollBar->height = SCREEN_HEIGHT - 344;
412 
413 	calcRows();
414     refresh(imageManager,renderer,false);
415 
416 	//NOTE: We don't need to recreate the listbox and the back button, only resize the list.
417 	levelpacks->left=(SCREEN_WIDTH-500)/2;
418 	levelpackDescription->width = SCREEN_WIDTH;
419 }
420 
GUIEventCallback_OnEvent(ImageManager & imageManager,SDL_Renderer & renderer,std::string name,GUIObject * obj,int eventType)421 void LevelSelect::GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType){
422 	if(name=="cmdLvlPack"){
423 		getSettings()->setValue("lastlevelpack",static_cast<GUISingleLineListBox*>(obj)->item[obj->value].first);
424 	}else if(name=="cmdBack"){
425 		setNextState(STATE_MENU);
426 		return;
427 	}else{
428 		return;
429 	}
430 
431 	//new: reset the level list scroll bar
432 	if(levelScrollBar)
433 		levelScrollBar->value=0;
434 
435 	levels=getLevelPackManager()->getLevelPack(static_cast<GUISingleLineListBox*>(obj)->item[obj->value].first);
436 
437 	//Load the progress file.
438 	levels->loadProgress();
439 
440 	//And refresh the numbers.
441     refresh(imageManager, renderer);
442 
443 	//invalidate the tooltip
444 	toolTip.number = -1;
445 }
446