- added worldmap stuff
[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 #ifdef LINUX
14 #include <pwd.h>
15 #include <sys/types.h>
16 #include <ctype.h>
17 #endif
18
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <string.h>
22
23 #include "defines.h"
24 #include "globals.h"
25 #include "menu.h"
26 #include "screen.h"
27 #include "setup.h"
28 #include "sound.h"
29 #include "scene.h"
30 #include "leveleditor.h"
31 #include "timer.h"
32 #include "high_scores.h"
33
34 /* (global) menu variables */
35 int menuaction;
36 int show_menu;
37 int menu_change;
38 texture_type checkbox, checkbox_checked, back, arrow_left, arrow_right;
39
40 menu_type main_menu, game_menu, options_menu, highscore_menu, load_game_menu, save_game_menu;
41 menu_type* current_menu, * last_menu;
42
43 /* input implementation variables */
44 int delete_character;
45 char mn_input_char;
46
47 /* Set the current menu */
48 void menu_set_current(menu_type* pmenu)
49 {
50   if(pmenu != current_menu)
51     {
52       menu_change = YES;
53       last_menu = current_menu;
54       current_menu = pmenu;
55       timer_start(&pmenu->effect, 500);
56     }
57 }
58
59 /* Return a pointer to a new menu item */
60 menu_item_type* menu_item_create(int kind, char *text, int init_toggle, void* target_menu)
61 {
62   menu_item_type *pnew_item = (menu_item_type*) malloc(sizeof(menu_item_type));
63   pnew_item->kind = kind;
64   pnew_item->text = (char*) malloc(sizeof(char) * (strlen(text) + 1));
65   strcpy(pnew_item->text,text);
66   if(kind == MN_TOGGLE)
67     pnew_item->toggled = init_toggle;
68   else
69     pnew_item->toggled = NO;
70   pnew_item->target_menu = target_menu;
71   pnew_item->input = (char*) malloc(sizeof(char));
72   pnew_item->input[0] = '\0';
73   if(kind == MN_STRINGSELECT)
74     {
75       pnew_item->list = (string_list_type*) malloc(sizeof(string_list_type));
76       string_list_init(pnew_item->list);
77     }
78   else
79     pnew_item->list = NULL;
80   return pnew_item;
81 }
82
83 void menu_item_change_text(menu_item_type* pmenu_item, char *text)
84 {
85   if(text)
86     {
87       free(pmenu_item->text);
88       pmenu_item->text = (char*) malloc(sizeof(char )*(strlen(text)+1));
89       strcpy(pmenu_item->text,text);
90     }
91 }
92 void menu_item_change_input(menu_item_type* pmenu_item, char *text)
93 {
94   if(text)
95     {
96       free(pmenu_item->input);
97       pmenu_item->input = (char*) malloc(sizeof(char )*(strlen(text)+1));
98       strcpy(pmenu_item->input,text);
99     }
100 }
101
102 /* Free a menu and all its items */
103 void menu_free(menu_type* pmenu)
104 {
105   int i;
106   if(pmenu->num_items != 0 && pmenu->item != NULL)
107     {
108       for(i = 0; i < pmenu->num_items; ++i)
109         {
110           free(pmenu->item[i].text);
111           free(pmenu->item[i].input);
112           string_list_free(pmenu->item[i].list);
113         }
114       free(pmenu->item);
115     }
116 }
117
118 /* Initialize a menu */
119 void menu_init(menu_type* pmenu)
120 {
121   pmenu->arrange_left = 0;
122   pmenu->num_items = 0;
123   pmenu->active_item = 0;
124   pmenu->item = NULL;
125   timer_init(&pmenu->effect,NO);
126 }
127
128 /* Add an item to a menu */
129 void menu_additem(menu_type* pmenu, menu_item_type* pmenu_item)
130 {
131   ++pmenu->num_items;
132   pmenu->item = (menu_item_type*) realloc(pmenu->item, sizeof(menu_item_type) * pmenu->num_items);
133   memcpy(&pmenu->item[pmenu->num_items-1],pmenu_item,sizeof(menu_item_type));
134   free(pmenu_item);
135 }
136
137 /* Process actions done on the menu */
138 void menu_action(menu_type* pmenu)
139 {
140   int i;
141
142   if(pmenu->num_items != 0 && pmenu->item != NULL)
143     {
144       switch(menuaction)
145         {
146         case MN_UP:
147           if(pmenu->active_item > 0)
148             --pmenu->active_item;
149           else
150             pmenu->active_item = pmenu->num_items-1;
151           break;
152         case MN_DOWN:
153           if(pmenu->active_item < pmenu->num_items-1)
154             ++pmenu->active_item;
155           else
156             pmenu->active_item = 0;
157           break;
158         case MN_LEFT:
159           if(pmenu->item[pmenu->active_item].kind == MN_STRINGSELECT && pmenu->item[pmenu->active_item].list->num_items != 0)
160             {
161               if(pmenu->item[pmenu->active_item].list->active_item > 0)
162                 --pmenu->item[pmenu->active_item].list->active_item;
163               else
164                 pmenu->item[pmenu->active_item].list->active_item = pmenu->item[pmenu->active_item].list->num_items-1;
165             }
166           break;
167         case MN_RIGHT:
168           if(pmenu->item[pmenu->active_item].kind == MN_STRINGSELECT && pmenu->item[pmenu->active_item].list->num_items != 0)
169             {
170               if(pmenu->item[pmenu->active_item].list->active_item < pmenu->item[pmenu->active_item].list->num_items-1)
171                 ++pmenu->item[pmenu->active_item].list->active_item;
172               else
173                 pmenu->item[pmenu->active_item].list->active_item = 0;
174             }
175           break;
176         case MN_HIT:
177           if(pmenu->item[pmenu->active_item].kind == MN_GOTO && pmenu->item[pmenu->active_item].target_menu != NULL)
178             menu_set_current((menu_type*)pmenu->item[pmenu->active_item].target_menu);
179           else if(pmenu->item[pmenu->active_item].kind == MN_TOGGLE)
180             {
181               pmenu->item[pmenu->active_item].toggled = !pmenu->item[pmenu->active_item].toggled;
182               menu_change = YES;
183             }
184           else if(pmenu->item[pmenu->active_item].kind == MN_ACTION || pmenu->item[pmenu->active_item].kind == MN_TEXTFIELD || pmenu->item[pmenu->active_item].kind == MN_NUMFIELD)
185             {
186               pmenu->item[pmenu->active_item].toggled = YES;
187             }
188           else if(pmenu->item[pmenu->active_item].kind == MN_BACK)
189             {
190               if(last_menu != NULL)
191                 menu_set_current(last_menu);
192             }
193           break;
194         case MN_REMOVE:
195           if(pmenu->item[pmenu->active_item].kind == MN_TEXTFIELD || pmenu->item[pmenu->active_item].kind == MN_NUMFIELD)
196             {
197               if(pmenu->item[pmenu->active_item].input != NULL)
198                 {
199                   i = strlen(pmenu->item[pmenu->active_item].input);
200
201                   while(delete_character > 0)   /* remove charactes */
202                     {
203                       pmenu->item[pmenu->active_item].input[i-1] = '\0';
204                       delete_character--;
205                     }
206                 }
207             }
208           break;
209         case MN_INPUT:
210           if(pmenu->item[pmenu->active_item].kind == MN_TEXTFIELD || (pmenu->item[pmenu->active_item].kind == MN_NUMFIELD && mn_input_char >= '0' && mn_input_char <= '9'))
211             {
212               if(pmenu->item[pmenu->active_item].input != NULL)
213                 {
214                   i = strlen(pmenu->item[pmenu->active_item].input);
215                   pmenu->item[pmenu->active_item].input = (char*) realloc(pmenu->item[pmenu->active_item].input,sizeof(char)*(i + 2));
216                   pmenu->item[pmenu->active_item].input[i] = mn_input_char;
217                   pmenu->item[pmenu->active_item].input[i+1] = '\0';
218                 }
219               else
220                 {
221                   pmenu->item[pmenu->active_item].input = (char*) malloc(2*sizeof(char));
222                   pmenu->item[pmenu->active_item].input[0] = mn_input_char;
223                   pmenu->item[pmenu->active_item].input[1] = '\0';
224                 }
225             }
226           break;
227         }
228     }
229
230   if(pmenu->item[pmenu->active_item].kind == MN_DEACTIVE || pmenu->item[pmenu->active_item].kind == MN_LABEL || pmenu->item[pmenu->active_item].kind == MN_HL)
231     {
232       if(menuaction != MN_UP && menuaction != MN_DOWN)
233         menuaction = MN_DOWN;
234
235       if(pmenu->num_items > 1)
236         menu_action(pmenu);
237     }
238
239 }
240
241 /* Check, if the value of the active menu item has changed. */
242 int menu_check(menu_type* pmenu)
243 {
244   if(pmenu->num_items != 0 && pmenu->item != NULL)
245     {
246       if((pmenu->item[pmenu->active_item].kind == MN_ACTION || pmenu->item[pmenu->active_item].kind == MN_TEXTFIELD || pmenu->item[pmenu->active_item].kind == MN_NUMFIELD) && pmenu->item[pmenu->active_item].toggled == YES)
247         {
248           pmenu->item[pmenu->active_item].toggled = NO;
249           show_menu = 0;
250           return pmenu->active_item;
251         }
252       else if(pmenu->item[pmenu->active_item].kind == MN_TOGGLE || pmenu->item[pmenu->active_item].kind == MN_GOTO)
253         {
254           return pmenu->active_item;
255         }
256       else
257         return -1;
258     }
259   else
260     return -1;
261 }
262
263 /* Draw the current menu. */
264 void menu_draw(menu_type* pmenu)
265 {
266   int i, y, a, b, e, f, menu_height, menu_width;
267
268   /* The width of the menu has to be more than the width of the text with the most characters */
269   menu_width = 0;
270   for(i = 0; i < pmenu->num_items; ++i)
271     {
272       y = strlen(pmenu->item[i].text) + (pmenu->item[i].input ? strlen(pmenu->item[i].input) + 1 : 0) + strlen(string_list_active(pmenu->item[i].list));
273       if( y > menu_width )
274         {
275           menu_width = y;
276           if( pmenu->item[i].kind == MN_TOGGLE)
277             menu_width += 2;
278         }
279     }
280   if(pmenu->arrange_left == YES)
281     a = menu_width * 16;
282   else
283     a = 0;
284
285   menu_width = menu_width * 16 + 48;
286   menu_height = (pmenu->num_items) * 24;
287
288   /* Draw a transparent background */
289   fillrect(screen->w/2 - menu_width/2,screen->h/2-(((pmenu->num_items)*24)/2),menu_width,menu_height,150,150,150,100);
290
291   if(timer_check(&pmenu->effect))
292     {
293       e = timer_get_left(&pmenu->effect) / 4;
294     }
295   else
296     {
297       e = 0;
298     }
299
300   for(i = 0; i < pmenu->num_items; ++i)
301     {
302       if(pmenu->arrange_left == YES)
303         b = (a - ((strlen(pmenu->item[i].text)+strlen(pmenu->item[i].input)+ strlen(string_list_active(pmenu->item[i].list))) * 16)) / 2;
304       else
305         b = 0;
306
307       if(e != 0)
308         {
309           if(i % 2)
310             f = e;
311           else
312             f = -e;
313         }
314       else
315         f = 0;
316
317       if(pmenu->item[i].kind == MN_DEACTIVE)
318         {
319           text_drawf(&black_text,pmenu->item[i].text, - b,(i)*24 - menu_height/2 + 10 + f,A_HMIDDLE, A_VMIDDLE,2,NO_UPDATE);
320         }
321       else if(pmenu->item[i].kind == MN_HL)
322         {
323           /* Draw a horizontal line with a little 3d effect */
324           fillrect(screen->w/2 - menu_width/2,(i)*24 - menu_height/2 + 6 + screen->h /2,menu_width,4,210,50,50,225);
325           fillrect(screen->w/2 - menu_width/2,(i)*24 - menu_height/2 + 10 + screen->h /2,menu_width,2,0,0,0,255);
326         }
327       else if(pmenu->item[i].kind == MN_LABEL)
328         {
329           text_drawf(&white_big_text,pmenu->item[i].text, - b,(i)*24 - menu_height/2 + 10,A_HMIDDLE, A_VMIDDLE,2,NO_UPDATE);
330         }
331       else if(pmenu->item[i].kind == MN_TEXTFIELD || pmenu->item[i].kind == MN_NUMFIELD)
332         {
333           fillrect(-b +screen->w/2 - ((strlen(pmenu->item[i].input)*16)/2) + ((strlen(pmenu->item[i].text) + 1)*16)/2 - 1,(i)*24 - menu_height/2 + 10 + screen->h /2 - 10 + f,(strlen(pmenu->item[i].input)+1)*16 + 2,20,255,255,255,255);
334           fillrect(- b +screen->w/2 - ((strlen(pmenu->item[i].input)*16)/2) + ((strlen(pmenu->item[i].text) + 1)*16)/2,(i)*24 - menu_height/2 + 10 + screen->h /2 - 9 + f,(strlen(pmenu->item[i].input)+1)*16,18,0,0,0,128);
335           text_drawf(&gold_text,pmenu->item[i].input, - b + ((strlen(pmenu->item[i].text)+1) * 16)/2,(i)*24 - menu_height/2 + 10 + f,A_HMIDDLE, A_VMIDDLE,2,NO_UPDATE);
336           if(i == pmenu->active_item)
337             {
338               text_drawf(&blue_text,pmenu->item[i].text, - b  -(((strlen(pmenu->item[i].input)+1) * 16)/2),(i)*24 - menu_height/2 + 10 + f,A_HMIDDLE, A_VMIDDLE,3,NO_UPDATE);
339             }
340           else
341             {
342               text_drawf(&white_text,pmenu->item[i].text, - b  -(((strlen(pmenu->item[i].input)+1) * 16)/2),(i)*24 - menu_height/2 +10 + f,A_HMIDDLE, A_VMIDDLE,2,NO_UPDATE);
343             }
344         }
345       else if(pmenu->item[i].kind == MN_STRINGSELECT)
346         {
347           /* Draw arrows */
348           texture_draw(&arrow_left,-b +screen->w/2 - ((strlen(string_list_active(pmenu->item[i].list))*16)/2) + ((strlen(pmenu->item[i].text) + 1)*16)/2 - 17,(i)*24 - menu_height/2 + 10 + screen->h / 2 -8 + f,NO_UPDATE);
349           texture_draw(&arrow_right,-b +screen->w/2 - ((strlen(string_list_active(pmenu->item[i].list))*16)/2) + ((strlen(pmenu->item[i].text) + 1)*16)/2 - 1 + (strlen(string_list_active(pmenu->item[i].list))+1)*16,(i)*24 - menu_height/2 + 10 + screen->h / 2 -8 + f,NO_UPDATE);
350           /* Draw input background */
351           fillrect(-b +screen->w/2 - ((strlen(string_list_active(pmenu->item[i].list))*16)/2) + ((strlen(pmenu->item[i].text) + 1)*16)/2 - 1,(i)*24 - menu_height/2 + 10 + screen->h /2 - 10 + f,(strlen(string_list_active(pmenu->item[i].list))+1)*16 + 2,20,255,255,255,255);
352           fillrect(- b +screen->w/2 - ((strlen(string_list_active(pmenu->item[i].list))*16)/2) + ((strlen(pmenu->item[i].text) + 1)*16)/2,(i)*24 - menu_height/2 + 10 + screen->h /2 - 9 + f,(strlen(string_list_active(pmenu->item[i].list))+1)*16,18,0,0,0,128);
353
354           text_drawf(&gold_text,string_list_active(pmenu->item[i].list), - b + ((strlen(pmenu->item[i].text)+1) * 16)/2,(i)*24 - menu_height/2 + 10 + f,A_HMIDDLE, A_VMIDDLE,2,NO_UPDATE);
355           if(i == pmenu->active_item)
356             {
357               text_drawf(&blue_text,pmenu->item[i].text, - b  -(((strlen(string_list_active(pmenu->item[i].list))+1) * 16)/2),(i)*24 - menu_height/2 + 10 + f,A_HMIDDLE, A_VMIDDLE,3,NO_UPDATE);
358             }
359           else
360             {
361               text_drawf(&white_text,pmenu->item[i].text, - b  -(((strlen(string_list_active(pmenu->item[i].list))+1) * 16)/2),(i)*24 - menu_height/2 +10 + f,A_HMIDDLE, A_VMIDDLE,2,NO_UPDATE);
362             }
363         }
364       else if(i == pmenu->active_item)
365         {
366           text_drawf(&blue_text,pmenu->item[i].text, - b,(i)*24 - menu_height/2 + 10 + f ,A_HMIDDLE, A_VMIDDLE,3,NO_UPDATE);
367         }
368       else
369         {
370           text_drawf(&white_text,pmenu->item[i].text, - b,(i)*24 - menu_height/2 + 10 + f ,A_HMIDDLE, A_VMIDDLE,2,NO_UPDATE);
371         }
372       if(pmenu->item[i].kind == MN_TOGGLE)
373         {
374
375           if(pmenu->item[i].toggled == YES)
376             texture_draw(&checkbox_checked, - b + screen->w / 2 + (strlen(pmenu->item[i].text) * 16)/2  + 16,(i)*24 - menu_height/2 + 10 + screen->h / 2 -8 + f,NO_UPDATE);
377           else
378             texture_draw(&checkbox, - b + screen->w / 2 + (strlen(pmenu->item[i].text) * 16)/2 + 16,(i)*24 - menu_height/2 + 10 + screen->h / 2 - 8 + f,NO_UPDATE);
379         }
380       else if(pmenu->item[i].kind == MN_BACK)
381         {
382           texture_draw(&back, - b + screen->w / 2 + (strlen(pmenu->item[i].text) * 16)/2  + 16,(i)*24 - menu_height/2 + 10 + screen->h / 2 -8 + f,NO_UPDATE);
383         }
384     }
385 }
386
387 /* Reset/Set global defaults */
388 void menu_reset(void)
389 {
390   menu_change = NO;
391   show_menu = NO;
392   menuaction = -1;
393   current_menu = NULL;
394   last_menu = NULL;
395
396   delete_character = 0;
397   mn_input_char = '\0';
398 }
399
400 /* --- MENU --- */
401 /* Draw the current menu and execute the (menu)events */
402 void menu_process_current(void)
403 {
404   menu_change = NO;
405
406   if(current_menu != NULL)
407     {
408       menu_action(current_menu);
409       menu_draw(current_menu);
410     }
411
412   menuaction = -1;
413 }
414
415 /* Check for menu event */
416 void menu_event(SDL_keysym* keysym)
417 {
418   SDLKey key = keysym->sym;
419   SDLMod keymod;
420   char ch[2];
421   keymod = SDL_GetModState();
422
423   /* If the current unicode character is an ASCII character,
424      assign it to ch. */
425   if ( (keysym->unicode & 0xFF80) == 0 )
426     {
427       ch[0] = keysym->unicode & 0x7F;
428       ch[1] = '\0';
429     }
430   else
431     {
432       /* An International Character. */
433     }
434
435   switch(key)
436     {
437     case SDLK_UP:               /* Menu Up */
438       menuaction = MN_UP;
439       menu_change = YES;
440       break;
441     case SDLK_DOWN:             /* Menu Down */
442       menuaction = MN_DOWN;
443       menu_change = YES;
444       break;
445     case SDLK_LEFT:             /* Menu Up */
446       menuaction = MN_LEFT;
447       menu_change = YES;
448       break;
449     case SDLK_RIGHT:            /* Menu Down */
450       menuaction = MN_RIGHT;
451       menu_change = YES;
452       break;
453     case SDLK_SPACE:
454       if(current_menu->item[current_menu->active_item].kind == MN_TEXTFIELD)
455       {
456       menuaction = MN_INPUT;
457       menu_change = YES;
458       mn_input_char = ' ';
459       break;
460       }
461     case SDLK_RETURN: /* Menu Hit */
462       menuaction = MN_HIT;
463       menu_change = YES;
464       break;
465     case SDLK_DELETE:
466     case SDLK_BACKSPACE:
467       menuaction = MN_REMOVE;
468       menu_change = YES;
469       delete_character++;
470       break;
471     default:
472       if( (key >= SDLK_0 && key <= SDLK_9) || (key >= SDLK_a && key <= SDLK_z) || (key >= SDLK_SPACE && key <= SDLK_SLASH))
473         {
474           menuaction = MN_INPUT;
475           menu_change = YES;
476           mn_input_char = *ch;
477         }
478       else
479         {
480           mn_input_char = '\0';
481         }
482       break;
483     }
484
485
486   /* FIXME: NO JOYSTICK SUPPORT */
487   /*#ifdef JOY_YES
488   else if (event.type == SDL_JOYBUTTONDOWN)
489    {
490       Joystick button: Continue:
491
492      done = 1;
493    }
494   #endif*/
495 }
496