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