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