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