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