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