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