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