- cleaned up my last_menu patch a bit more
[supertux.git] / src / menu.cpp
1 //  $Id$
2 // 
3 //  SuperTux
4 //  Copyright (C) 2004 Tobias Glaesser <tobi.web@gmx.de>
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program 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 this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19
20 #ifndef WIN32
21 #include <sys/types.h>
22 #include <ctype.h>
23 #endif
24
25 #include <iostream>
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
29
30 #include "defines.h"
31 #include "globals.h"
32 #include "menu.h"
33 #include "screen.h"
34 #include "setup.h"
35 #include "sound.h"
36 #include "scene.h"
37 #include "leveleditor.h"
38 #include "timer.h"
39 #include "high_scores.h"
40
41 Surface* checkbox;
42 Surface* checkbox_checked;
43 Surface* back;
44 Surface* arrow_left;
45 Surface* arrow_right;
46
47 Menu* main_menu      = 0;
48 Menu* game_menu      = 0;
49 Menu* worldmap_menu  = 0;
50 Menu* options_menu   = 0;
51 Menu* options_controls_menu   = 0;
52 Menu* highscore_menu = 0;
53 Menu* load_game_menu = 0;
54 Menu* save_game_menu = 0;
55 Menu* contrib_menu   = 0;
56 Menu* contrib_subset_menu   = 0;
57
58 std::stack<Menu*> Menu::last_menus;
59 Menu* Menu::current_ = 0;
60
61 void
62 Menu::push_current(Menu* pmenu)
63 {
64   if (current_)
65     last_menus.push(current_);
66   
67   set_current(pmenu);
68 }
69
70 void
71 Menu::pop_current()
72 {
73   if (!last_menus.empty())
74     {
75       set_current(last_menus.top());
76       last_menus.pop();
77     }
78   else
79     {
80       set_current(0);
81     }
82 }
83
84 void
85 Menu::set_current(Menu* menu)
86 {
87   if (menu)
88     menu->effect.start(500);
89   
90   current_ = menu;
91 }
92
93 /* Return a pointer to a new menu item */
94 MenuItem*
95 MenuItem::create(MenuItemKind kind_, const char *text_, int init_toggle_, Menu* target_menu_)
96 {
97   MenuItem *pnew_item = new MenuItem;
98   
99   pnew_item->kind = kind_;
100   pnew_item->text = (char*) malloc(sizeof(char) * (strlen(text_) + 1));
101   strcpy(pnew_item->text, text_);
102
103   if(kind_ == MN_TOGGLE)
104     pnew_item->toggled = init_toggle_;
105   else
106     pnew_item->toggled = false;
107
108   pnew_item->target_menu = target_menu_;
109   pnew_item->input = (char*) malloc(sizeof(char));
110   pnew_item->input[0] = '\0';
111
112   if(kind_ == MN_STRINGSELECT)
113     {
114       pnew_item->list = (string_list_type*) malloc(sizeof(string_list_type));
115       string_list_init(pnew_item->list);
116     }
117   else
118     pnew_item->list = NULL;
119   return pnew_item;
120 }
121
122 void
123 MenuItem::change_text(const  char *text_)
124 {
125   if (text_)
126     {
127       free(text);
128       text = (char*) malloc(sizeof(char )*(strlen(text_)+1));
129       strcpy(text, text_);
130     }
131 }
132
133 void
134 MenuItem::change_input(const  char *text_)
135 {
136   if(text)
137     {
138       free(input);
139       input = (char*) malloc(sizeof(char )*(strlen(text_)+1));
140       strcpy(input, text_);
141     }
142 }
143
144 /* Free a menu and all its items */
145 Menu::~Menu()
146 {
147   if(item.size() != 0)
148     {
149       for(unsigned int i = 0; i < item.size(); ++i)
150         {
151           free(item[i].text);
152           free(item[i].input);
153           string_list_free(item[i].list);
154         }
155     }
156 }
157
158
159 Menu::Menu()
160 {
161   menuaction = MENU_ACTION_NONE;
162   delete_character = 0;
163   mn_input_char = '\0';
164   
165   pos_x        = screen->w/2;
166   pos_y        = screen->h/2;
167   has_backitem = false;
168   arrange_left = 0;
169   active_item  = 0;
170   effect.init(false);
171 }
172
173 void Menu::set_pos(int x, int y, float rw, float rh)
174 {
175   pos_x = x + (int)((float)width() * rw);
176   pos_y = y + (int)((float)height() * rh);
177 }
178
179 void
180 Menu::additem(MenuItemKind kind_, const std::string& text_, int toggle_, Menu* menu_)
181 {
182   if(kind_ == MN_BACK)
183     has_backitem = true;
184
185   additem(MenuItem::create(kind_, text_.c_str(), toggle_, menu_));
186 }
187
188 /* Add an item to a menu */
189 void
190 Menu::additem(MenuItem* pmenu_item)
191 {
192   if(pmenu_item->kind == MN_BACK)
193     has_backitem = true;
194
195   item.push_back(*pmenu_item);
196   delete pmenu_item;
197 }
198
199 void
200 Menu::clear()
201 {
202   item.clear();
203 }
204
205 /* Process actions done on the menu */
206 void
207 Menu::action()
208 {
209   if(item.size() != 0)
210     {
211       switch(menuaction)
212         {
213         case MENU_ACTION_UP:
214           if (active_item > 0)
215             --active_item;
216           else
217             active_item = int(item.size())-1;
218           break;
219
220         case MENU_ACTION_DOWN:
221           if(active_item < int(item.size())-1)
222             ++active_item;
223           else
224             active_item = 0;
225           break;
226
227         case MENU_ACTION_LEFT:
228           if(item[active_item].kind == MN_STRINGSELECT
229               && item[active_item].list->num_items != 0)
230             {
231               if(item[active_item].list->active_item > 0)
232                 --item[active_item].list->active_item;
233               else
234                 item[active_item].list->active_item = item[active_item].list->num_items-1;
235             }
236           break;
237
238         case MENU_ACTION_RIGHT:
239           if(item[active_item].kind == MN_STRINGSELECT
240               && item[active_item].list->num_items != 0)
241             {
242               if(item[active_item].list->active_item < item[active_item].list->num_items-1)
243                 ++item[active_item].list->active_item;
244               else
245                 item[active_item].list->active_item = 0;
246             }
247           break;
248
249         case MENU_ACTION_HIT:
250           {
251             switch (item[active_item].kind)
252               {
253               case MN_GOTO:
254                 if (item[active_item].target_menu != NULL)
255                   Menu::push_current(item[active_item].target_menu);
256                 else
257                   puts("NULLL");
258                 break;
259
260               case MN_TOGGLE:
261                 item[active_item].toggled = !item[active_item].toggled;
262                 break;
263
264               case MN_ACTION:
265               case MN_TEXTFIELD:
266               case MN_NUMFIELD:
267               case MN_CONTROLFIELD:
268                 item[active_item].toggled = true;
269                 break;
270
271               case MN_BACK:
272                 Menu::pop_current();
273                 break;
274               default:
275                 break;
276               }
277           }
278           break;
279
280         case MENU_ACTION_REMOVE:
281           if(item[active_item].kind == MN_TEXTFIELD
282               || item[active_item].kind == MN_NUMFIELD)
283             {
284               if(item[active_item].input != NULL)
285                 {
286                   int i = strlen(item[active_item].input);
287
288                   while(delete_character > 0)   /* remove charactes */
289                     {
290                       item[active_item].input[i-1] = '\0';
291                       delete_character--;
292                     }
293                 }
294             }
295           break;
296
297         case MENU_ACTION_INPUT:
298           if(item[active_item].kind == MN_TEXTFIELD
299               || (item[active_item].kind == MN_NUMFIELD && mn_input_char >= '0' && mn_input_char <= '9'))
300             {
301               if(item[active_item].input != NULL)
302                 {
303                   int i = strlen(item[active_item].input);
304                   item[active_item].input = (char*) realloc(item[active_item].input,sizeof(char)*(i + 2));
305                   item[active_item].input[i] = mn_input_char;
306                   item[active_item].input[i+1] = '\0';
307                 }
308               else
309                 {
310                   item[active_item].input = (char*) malloc(2*sizeof(char));
311                   item[active_item].input[0] = mn_input_char;
312                   item[active_item].input[1] = '\0';
313                 }
314             }
315           break;
316
317         case MENU_ACTION_NONE:
318           break;
319         }
320     }
321
322   MenuItem& new_item = item[active_item];
323   if(new_item.kind == MN_DEACTIVE
324       || new_item.kind == MN_LABEL
325       || new_item.kind == MN_HL)
326     {
327       // Skip the horzontal line item
328       if (menuaction != MENU_ACTION_UP && menuaction != MENU_ACTION_DOWN)
329         menuaction = MENU_ACTION_DOWN;
330
331       if (item.size() > 1)
332         action();
333     }
334
335   menuaction = MENU_ACTION_NONE;
336 }
337
338 int
339 Menu::check()
340 {
341   if (item.size() != 0)
342     {
343       if((item[active_item].kind == MN_ACTION
344           || item[active_item].kind == MN_TEXTFIELD
345           || item[active_item].kind == MN_NUMFIELD)
346           && item[active_item].toggled)
347         {
348           item[active_item].toggled = false;
349           Menu::set_current(0);
350           return active_item;
351         }
352       else if(item[active_item].kind == MN_TOGGLE || item[active_item].kind == MN_GOTO)
353         {
354           return active_item;
355         }
356       else
357         return -1;
358     }
359   else
360     return -1;
361 }
362
363 void
364 Menu::draw_item(int index, // Position of the current item in the menu
365                 int menu_width,
366                 int menu_height)
367 {
368   const MenuItem& pitem = item[index];
369
370   int font_width  = 16;
371   int effect_offset = 0;
372   {
373     int effect_time = 0;
374
375     if(effect.check())
376       effect_time = effect.get_left() / 4;
377
378     effect_offset = (index % 2) ? effect_time : -effect_time;
379   }
380
381   int x_pos       = pos_x;
382   int y_pos       = pos_y + 24*index - menu_height/2 + 12 + effect_offset;
383   int shadow_size = 2;
384   int text_width  = strlen(pitem.text) * font_width;
385   int input_width = strlen(pitem.input) * font_width;
386   int list_width  = strlen(string_list_active(pitem.list)) * font_width;
387   Text* text_font = white_text;
388
389   if (arrange_left)
390     x_pos += 24 - menu_width/2 + (text_width + input_width + list_width)/2;
391
392   if(index == active_item)
393     {
394       shadow_size = 3;
395       text_font = blue_text;
396     }
397
398   switch (pitem.kind)
399     {
400     case MN_DEACTIVE:
401       {
402         black_text->draw_align(pitem.text,
403                                x_pos, y_pos,
404                                A_HMIDDLE, A_VMIDDLE, 2);
405         break;
406       }
407
408     case MN_HL:
409       {
410         int x = pos_x - menu_width/2;
411         int y = y_pos - 12 - effect_offset;
412         /* Draw a horizontal line with a little 3d effect */
413         fillrect(x, y + 6,
414                  menu_width, 4,
415                  150,200,255,225);
416         fillrect(x, y + 6,
417                  menu_width, 2,
418                  255,255,255,255);
419         break;
420       }
421     case MN_LABEL:
422       {
423         white_big_text->draw_align(pitem.text,
424                                    x_pos, y_pos,
425                                    A_HMIDDLE, A_VMIDDLE, 2);
426         break;
427       }
428     case MN_TEXTFIELD:
429     case MN_NUMFIELD:
430     case MN_CONTROLFIELD:
431       {
432         int input_pos = input_width/2;
433         int text_pos  = (text_width + font_width)/2;
434
435         fillrect(x_pos - input_pos + text_pos - 1, y_pos - 10,
436                  input_width + font_width + 2, 20,
437                  255,255,255,255);
438         fillrect(x_pos - input_pos + text_pos, y_pos - 9,
439                  input_width + font_width, 18,
440                  0,0,0,128);
441
442         gold_text->draw_align(pitem.input,
443                               x_pos + text_pos, y_pos,
444                               A_HMIDDLE, A_VMIDDLE, 2);
445
446         text_font->draw_align(pitem.text,
447                               x_pos - (input_width + font_width)/2, y_pos,
448                               A_HMIDDLE, A_VMIDDLE, shadow_size);
449         break;
450       }
451     case MN_STRINGSELECT:
452       {
453         int list_pos_2 = list_width + font_width;
454         int list_pos   = list_width/2;
455         int text_pos   = (text_width + font_width)/2;
456
457         /* Draw arrows */
458         arrow_left->draw(  x_pos - list_pos + text_pos - 17, y_pos - 8);
459         arrow_right->draw( x_pos - list_pos + text_pos - 1 + list_pos_2, y_pos - 8);
460
461         /* Draw input background */
462         fillrect(x_pos - list_pos + text_pos - 1, y_pos - 10,
463                  list_pos_2 + 2, 20,
464                  255,255,255,255);
465         fillrect(x_pos - list_pos + text_pos, y_pos - 9,
466                  list_pos_2, 18,
467                  0,0,0,128);
468
469         gold_text->draw_align(string_list_active(pitem.list),
470                         x_pos + text_pos, y_pos,
471                         A_HMIDDLE, A_VMIDDLE,2);
472
473         text_font->draw_align(pitem.text,
474                         x_pos - list_pos_2/2, y_pos,
475                         A_HMIDDLE, A_VMIDDLE, shadow_size);
476         break;
477       }
478     case MN_BACK:
479       {
480         text_font->draw_align(pitem.text, x_pos, y_pos, A_HMIDDLE, A_VMIDDLE, shadow_size);
481         back->draw( x_pos + text_width/2  + font_width, y_pos - 8);
482         break;
483       }
484
485     case MN_TOGGLE:
486       {
487         text_font->draw_align(pitem.text, x_pos, y_pos, A_HMIDDLE, A_VMIDDLE, shadow_size);
488
489         if(pitem.toggled)
490           checkbox_checked->draw(
491                        x_pos + (text_width+font_width)/2,
492                        y_pos - 8);
493         else
494           checkbox->draw(
495                        x_pos + (text_width+font_width)/2,
496                        y_pos - 8);
497         break;
498       }
499     case MN_ACTION:
500       text_font->draw_align(pitem.text, x_pos, y_pos, A_HMIDDLE, A_VMIDDLE, shadow_size);
501       break;
502
503     case MN_GOTO:
504       text_font->draw_align(pitem.text, x_pos, y_pos, A_HMIDDLE, A_VMIDDLE, shadow_size);
505       break;
506     }
507 }
508
509 int Menu::width()
510 {
511   /* The width of the menu has to be more than the width of the text
512      with the most characters */
513   int menu_width = 0;
514   for(unsigned int i = 0; i < item.size(); ++i)
515     {
516       int w = strlen(item[i].text) + (item[i].input ? strlen(item[i].input) + 1 : 0) + strlen(string_list_active(item[i].list));
517       if( w > menu_width )
518         {
519           menu_width = w;
520           if( item[i].kind == MN_TOGGLE)
521             menu_width += 2;
522         }
523     }
524
525   return (menu_width * 16 + 24);
526 }
527
528 int Menu::height()
529 {
530   return item.size() * 24;
531 }
532
533 /* Draw the current menu. */
534 void
535 Menu::draw()
536 {
537   int menu_height = height();
538   int menu_width = width();
539
540   /* Draw a transparent background */
541   fillrect(pos_x - menu_width/2,
542            pos_y - 24*item.size()/2 - 10,
543            menu_width,menu_height + 20,
544            150,180,200,125);
545
546   for(unsigned int i = 0; i < item.size(); ++i)
547     {
548       draw_item(i, menu_width, menu_height);
549     }
550 }
551
552 /* Check for menu event */
553 void
554 Menu::event(SDL_Event& event)
555 {
556   SDLKey key;
557   switch(event.type)
558     {
559     case SDL_KEYDOWN:
560       key = event.key.keysym.sym;
561       SDLMod keymod;
562       char ch[2];
563       keymod = SDL_GetModState();
564       int x,y;
565
566       /* If the current unicode character is an ASCII character,
567          assign it to ch. */
568       if ( (event.key.keysym.unicode & 0xFF80) == 0 )
569         {
570           ch[0] = event.key.keysym.unicode & 0x7F;
571           ch[1] = '\0';
572         }
573       else
574         {
575           /* An International Character. */
576         }
577
578       switch(key)
579         {
580         case SDLK_UP:           /* Menu Up */
581           menuaction = MENU_ACTION_UP;
582           break;
583         case SDLK_DOWN:         /* Menu Down */
584           menuaction = MENU_ACTION_DOWN;
585           break;
586         case SDLK_LEFT:         /* Menu Up */
587           menuaction = MENU_ACTION_LEFT;
588           break;
589         case SDLK_RIGHT:                /* Menu Down */
590           menuaction = MENU_ACTION_RIGHT;
591           break;
592         case SDLK_SPACE:
593           if(item[active_item].kind == MN_TEXTFIELD)
594             {
595               menuaction = MENU_ACTION_INPUT;
596               mn_input_char = ' ';
597               break;
598             }
599         case SDLK_RETURN: /* Menu Hit */
600           menuaction = MENU_ACTION_HIT;
601           break;
602         case SDLK_DELETE:
603         case SDLK_BACKSPACE:
604           menuaction = MENU_ACTION_REMOVE;
605           delete_character++;
606           break;
607         case SDLK_ESCAPE:
608           Menu::pop_current();
609           break;
610         default:
611           if( (key >= SDLK_0 && key <= SDLK_9) || (key >= SDLK_a && key <= SDLK_z) || (key >= SDLK_SPACE && key <= SDLK_SLASH))
612             {
613               menuaction = MENU_ACTION_INPUT;
614               mn_input_char = *ch;
615             }
616           else
617             {
618               mn_input_char = '\0';
619             }
620           break;
621         }
622       break;
623     case  SDL_JOYAXISMOTION:
624       if(event.jaxis.axis == JOY_Y)
625         {
626           if (event.jaxis.value > 1024)
627             menuaction = MENU_ACTION_DOWN;
628           else if (event.jaxis.value < -1024)
629             menuaction = MENU_ACTION_UP;
630         }
631       break;
632     case  SDL_JOYBUTTONDOWN:
633       menuaction = MENU_ACTION_HIT;
634       break;
635     case SDL_MOUSEBUTTONDOWN:
636       x = event.motion.x;
637       y = event.motion.y;
638       if(x > pos_x - width()/2 &&
639          x < pos_x + width()/2 &&
640          y > pos_y - height()/2 &&
641          y < pos_y + height()/2)
642         {
643           menuaction = MENU_ACTION_HIT;
644         }
645       break;
646     case SDL_MOUSEMOTION:
647       x = event.motion.x;
648       y = event.motion.y;
649       if(x > pos_x - width()/2 &&
650          x < pos_x + width()/2 &&
651          y > pos_y - height()/2 &&
652          y < pos_y + height()/2)
653         {
654           active_item = (y - (pos_y - height()/2)) / 24;
655           mouse_cursor->set_state(MC_LINK);
656         }
657       else
658         {
659           mouse_cursor->set_state(MC_NORMAL);
660         }
661       break;
662     default:
663       break;
664     }
665 }
666
667
668 // EOF //