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