qMax <qwiglydee@gmail.com>'s font patch, adds unicode support and support for drawing...
[supertux.git] / src / gui / menu.cpp
1 //  $Id$
2 //
3 //  SuperTux
4 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19
20 #include <config.h>
21
22 #include <sys/types.h>
23 #include <ctype.h>
24
25 #include <iostream>
26 #include <sstream>
27 #include <math.h>
28 #include <cstdlib>
29 #include <cstdio>
30 #include <string>
31 #include <cassert>
32 #include <stdexcept>
33
34 #include "menu.hpp"
35 #include "mainloop.hpp"
36 #include "video/drawing_context.hpp"
37 #include "gettext.hpp"
38 #include "math/vector.hpp"
39 #include "main.hpp"
40 #include "resources.hpp"
41 #include "timer.hpp"
42 #include "control/joystickkeyboardcontroller.hpp"
43
44 static const float MENU_REPEAT_INITIAL = 0.4f;
45 static const float MENU_REPEAT_RATE    = 0.1f;
46 static const float FLICK_CURSOR_TIME   = 0.5f;
47
48 extern SDL_Surface* screen;
49
50 std::vector<Menu*> Menu::last_menus;
51 std::list<Menu*> Menu::all_menus;
52 Menu* Menu::current_ = 0;
53 Menu* Menu::previous = 0;
54
55 /* just displays a Yes/No text that can be used to confirm stuff */
56 bool confirm_dialog(Surface *background, std::string text)
57 {
58   //Surface* cap_screen = Surface::CaptureScreen();
59   Menu* dialog = new Menu;
60   dialog->add_inactive(-1, text);
61   dialog->add_hl();
62   dialog->add_entry(true, _("Yes"));
63   dialog->add_entry(false, _("No"));
64   dialog->add_hl();
65
66   Menu::set_current(dialog);
67
68   DrawingContext context;
69
70   // TODO make this a screen and not another mainloop...
71   while(true)
72     {
73       SDL_Event event;
74       while (SDL_PollEvent(&event)) {
75         if(event.type == SDL_QUIT)
76           main_loop->quit();
77         main_controller->process_event(event);
78         dialog->event(event);
79       }
80
81       if(background == NULL)
82         context.draw_gradient(Color(0.8f, 0.95f, 0.85f), Color(0.8f, 0.8f, 0.8f),
83                               LAYER_BACKGROUND0);
84       else
85         context.draw_surface(background, Vector(0,0), LAYER_BACKGROUND0);
86
87       dialog->draw(context);
88       dialog->update();
89
90       switch (dialog->check())
91         {
92         case true:
93           //delete cap_screen;
94           Menu::set_current(0);
95           delete dialog;
96           return true;
97           break;
98         case false:
99           //delete cap_screen;
100           Menu::set_current(0);
101           delete dialog;
102           return false;
103           break;
104         default:
105           break;
106         }
107
108       mouse_cursor->draw(context);
109       context.do_drawing();
110       SDL_Delay(25);
111     }
112
113   return false;
114 }
115 \f
116 void
117 Menu::push_current(Menu* pmenu)
118 {
119   previous = current_;
120
121   if (current_)
122     last_menus.push_back(current_);
123
124   current_ = pmenu;
125   current_->effect_start_time = real_time;
126   current_->effect_progress   = 0.0f;
127 }
128
129 void
130 Menu::pop_current()
131 {
132   previous = current_;
133
134   if (last_menus.size() >= 1) {
135     current_ = last_menus.back();
136     current_->effect_start_time = real_time;
137     current_->effect_progress   = 0.0f;
138     last_menus.pop_back();
139   } else {
140     set_current(NULL);
141   }
142 }
143
144 void
145 Menu::set_current(Menu* menu)
146 {
147   //last_menus.clear();
148   if (current_ && current_->close == true)
149     return;
150
151   previous = current_;
152
153   if (menu) {
154     menu->effect_start_time = real_time;
155     menu->effect_progress = 0.0f;
156     current_ = menu;
157   }
158   else if (current_) {
159     current_->effect_start_time = real_time;
160     current_->effect_progress = 0.0f;
161     current_->close = true;
162   }
163
164   // just to be sure...
165   main_controller->reset();
166 }
167
168 void
169 Menu::recalc_pos()
170 {
171   if (current_)
172     current_->set_pos(SCREEN_WIDTH/2, SCREEN_HEIGHT/2);
173
174   for(std::list<Menu*>::iterator i = all_menus.begin(); i != all_menus.end(); ++i)
175     {
176       // FIXME: This is of course not quite right, since it ignores any previous set_pos() calls
177       (*i)->set_pos(SCREEN_WIDTH/2, SCREEN_HEIGHT/2);
178     }
179 }
180 \f
181 MenuItem::MenuItem(MenuItemKind _kind, int _id)
182   : kind(_kind) , id(_id)
183 {
184   toggled = false;
185   selected = false;
186   target_menu = 0;
187 }
188
189 void
190 MenuItem::change_text(const  std::string& text_)
191 {
192   text = text_;
193 }
194
195 void
196 MenuItem::change_input(const  std::string& text_)
197 {
198   input = text_;
199 }
200
201 void
202 MenuItem::set_help(const std::string& help_text)
203 {
204   std::string overflow;
205   help = normal_font->wrap_to_width(help_text, 600, &overflow);
206   while (!overflow.empty())
207     {
208       help += "\n";
209       help += normal_font->wrap_to_width(overflow, 600, &overflow);
210     }
211 }
212
213 std::string MenuItem::get_input_with_symbol(bool active_item)
214 {
215   if(!active_item) {
216     input_flickering = true;
217   } else {
218     input_flickering = ((int) (real_time / FLICK_CURSOR_TIME)) % 2;
219   }
220
221   char str[1024];
222   if(input_flickering)
223     snprintf(str, sizeof(str), "%s ",input.c_str());
224   else
225     snprintf(str, sizeof(str), "%s_",input.c_str());
226
227   std::string string = str;
228
229   return string;
230 }
231 \f
232 Menu::~Menu()
233 {
234   all_menus.remove(this);
235
236   for(std::vector<MenuItem*>::iterator i = items.begin();
237       i != items.end(); ++i)
238     delete *i;
239
240   if(current_ == this)
241     current_ = NULL;
242
243   if (previous == this)
244     previous = NULL;
245 }
246
247 Menu::Menu()
248     : close(false)
249 {
250   all_menus.push_back(this);
251
252   hit_item = -1;
253   menuaction = MENU_ACTION_NONE;
254   delete_character = 0;
255   mn_input_char = '\0';
256
257   pos_x        = SCREEN_WIDTH/2;
258   pos_y        = SCREEN_HEIGHT/2;
259   arrange_left = 0;
260   active_item  = -1;
261
262   effect_progress   = 0.0f;
263   effect_start_time = 0.0f;
264
265   checkbox.reset(new Surface("images/engine/menu/checkbox-unchecked.png"));
266   checkbox_checked.reset(new Surface("images/engine/menu/checkbox-checked.png"));
267   back.reset(new Surface("images/engine/menu/arrow-back.png"));
268   arrow_left.reset(new Surface("images/engine/menu/arrow-left.png"));
269   arrow_right.reset(new Surface("images/engine/menu/arrow-right.png"));
270 }
271
272 void
273 Menu::set_pos(float x, float y, float rw, float rh)
274 {
275   pos_x = x + get_width()  * rw;
276   pos_y = y + get_height() * rh;
277 }
278
279 /* Add an item to a menu */
280 void
281 Menu::additem(MenuItem* item)
282 {
283   items.push_back(item);
284
285   /* If a new menu is being built, the active item shouldn't be set to
286    * something that isn't selectable. Set the active_item to the first
287    * selectable item added.
288    */
289   if (active_item == -1
290       && item->kind != MN_HL
291       && item->kind != MN_LABEL
292       && item->kind != MN_INACTIVE) {
293     active_item = items.size() - 1;
294   }
295 }
296
297 MenuItem*
298 Menu::add_hl()
299 {
300   MenuItem* item = new MenuItem(MN_HL);
301   additem(item);
302   return item;
303 }
304
305 MenuItem*
306 Menu::add_label(const std::string& text)
307 {
308   MenuItem* item = new MenuItem(MN_LABEL);
309   item->text = text;
310   additem(item);
311   return item;
312 }
313
314 MenuItem*
315 Menu::add_controlfield(int id, const std::string& text,
316                 const std::string& mapping)
317 {
318   MenuItem* item = new MenuItem(MN_CONTROLFIELD, id);
319   item->change_text(text);
320         item->change_input(mapping);
321   additem(item);
322   return item;
323 }
324
325 MenuItem*
326 Menu::add_entry(int id, const std::string& text)
327 {
328   MenuItem* item = new MenuItem(MN_ACTION, id);
329   item->text = text;
330   additem(item);
331   return item;
332 }
333
334 MenuItem*
335 Menu::add_inactive(int id, const std::string& text)
336 {
337   MenuItem* item = new MenuItem(MN_INACTIVE, id);
338   item->text = text;
339   additem(item);
340   return item;
341 }
342
343 MenuItem*
344 Menu::add_toggle(int id, const std::string& text, bool toogled)
345 {
346   MenuItem* item = new MenuItem(MN_TOGGLE, id);
347   item->text = text;
348   item->toggled = toogled;
349   additem(item);
350   return item;
351 }
352
353 MenuItem*
354 Menu::add_string_select(int id, const std::string& text)
355 {
356   MenuItem* item = new MenuItem(MN_STRINGSELECT, id);
357   item->text = text;
358   additem(item);
359   return item;
360 }
361
362 MenuItem*
363 Menu::add_back(const std::string& text)
364 {
365   MenuItem* item = new MenuItem(MN_BACK);
366   item->text = text;
367   additem(item);
368   return item;
369 }
370
371 MenuItem*
372 Menu::add_submenu(const std::string& text, Menu* submenu, int id)
373 {
374   MenuItem* item = new MenuItem(MN_GOTO, id);
375   item->text = text;
376   item->target_menu = submenu;
377   additem(item);
378   return item;
379 }
380
381 void
382 Menu::clear()
383 {
384   for(std::vector<MenuItem*>::iterator i = items.begin();
385       i != items.end(); ++i) {
386     delete *i;
387   }
388   items.clear();
389   active_item = -1;
390 }
391
392 /* Process actions done on the menu */
393 void
394 Menu::update()
395 {
396   int menu_height = (int) get_height();
397   if (menu_height > SCREEN_HEIGHT)
398     { // Scrolling
399       int scroll_offset = (menu_height - SCREEN_HEIGHT) / 2 + 32;
400       pos_y = SCREEN_HEIGHT/2 - scroll_offset * ((float(active_item) / (items.size()-1)) - 0.5f) * 2.0f;
401     }
402
403   effect_progress = (real_time - effect_start_time) * 6.0f;
404
405   if(effect_progress >= 1.0f) {
406     effect_progress = 1.0f;
407
408     if (close) {
409       current_ = 0;
410       close = false;
411     }
412   }
413   else if (effect_progress <= 0.0f) {
414     effect_progress = 0.0f;
415   }
416
417   /** check main input controller... */
418   if(main_controller->pressed(Controller::UP)) {
419     menuaction = MENU_ACTION_UP;
420     menu_repeat_time = real_time + MENU_REPEAT_INITIAL;
421   }
422   if(main_controller->hold(Controller::UP) &&
423       menu_repeat_time != 0 && real_time > menu_repeat_time) {
424     menuaction = MENU_ACTION_UP;
425     menu_repeat_time = real_time + MENU_REPEAT_RATE;
426   }
427
428   if(main_controller->pressed(Controller::DOWN)) {
429     menuaction = MENU_ACTION_DOWN;
430     menu_repeat_time = real_time + MENU_REPEAT_INITIAL;
431   }
432   if(main_controller->hold(Controller::DOWN) &&
433       menu_repeat_time != 0 && real_time > menu_repeat_time) {
434     menuaction = MENU_ACTION_DOWN;
435     menu_repeat_time = real_time + MENU_REPEAT_RATE;
436   }
437
438   if(main_controller->pressed(Controller::LEFT)) {
439     menuaction = MENU_ACTION_LEFT;
440     menu_repeat_time = real_time + MENU_REPEAT_INITIAL;
441   }
442   if(main_controller->hold(Controller::LEFT) &&
443       menu_repeat_time != 0 && real_time > menu_repeat_time) {
444     menuaction = MENU_ACTION_LEFT;
445     menu_repeat_time = real_time + MENU_REPEAT_RATE;
446   }
447
448   if(main_controller->pressed(Controller::RIGHT)) {
449     menuaction = MENU_ACTION_RIGHT;
450     menu_repeat_time = real_time + MENU_REPEAT_INITIAL;
451   }
452   if(main_controller->hold(Controller::RIGHT) &&
453       menu_repeat_time != 0 && real_time > menu_repeat_time) {
454     menuaction = MENU_ACTION_RIGHT;
455     menu_repeat_time = real_time + MENU_REPEAT_RATE;
456   }
457
458   if(main_controller->pressed(Controller::ACTION)
459      || main_controller->pressed(Controller::MENU_SELECT)) {
460     menuaction = MENU_ACTION_HIT;
461   }
462   if(main_controller->pressed(Controller::PAUSE_MENU)) {
463     menuaction = MENU_ACTION_BACK;
464   }
465
466   hit_item = -1;
467   if(items.size() == 0)
468     return;
469
470   int last_active_item = active_item;
471   switch(menuaction) {
472     case MENU_ACTION_UP:
473       do {
474         if (active_item > 0)
475           --active_item;
476         else
477           active_item = int(items.size())-1;
478       } while ((items[active_item]->kind == MN_HL
479                 || items[active_item]->kind == MN_LABEL
480                 || items[active_item]->kind == MN_INACTIVE)
481                && (active_item != last_active_item));
482
483       break;
484
485     case MENU_ACTION_DOWN:
486       do {
487         if(active_item < int(items.size())-1 )
488           ++active_item;
489         else
490           active_item = 0;
491       } while ((items[active_item]->kind == MN_HL
492                 || items[active_item]->kind == MN_LABEL
493                 || items[active_item]->kind == MN_INACTIVE)
494                && (active_item != last_active_item));
495
496       break;
497
498     case MENU_ACTION_LEFT:
499       if(items[active_item]->kind == MN_STRINGSELECT) {
500         if(items[active_item]->selected > 0)
501           items[active_item]->selected--;
502         else
503           items[active_item]->selected = items[active_item]->list.size()-1;
504         
505         menu_action(items[active_item]);
506       }
507       break;
508
509     case MENU_ACTION_RIGHT:
510       if(items[active_item]->kind == MN_STRINGSELECT) {
511         if(items[active_item]->selected+1 < items[active_item]->list.size())
512           items[active_item]->selected++;
513         else
514           items[active_item]->selected = 0;
515         
516         menu_action(items[active_item]);
517       }
518       break;
519
520     case MENU_ACTION_HIT: {
521       hit_item = active_item;
522       switch (items[active_item]->kind) {
523         case MN_GOTO:
524           assert(items[active_item]->target_menu != 0);
525           Menu::push_current(items[active_item]->target_menu);
526           break;
527
528         case MN_TOGGLE:
529           items[active_item]->toggled = !items[active_item]->toggled;
530           menu_action(items[active_item]);
531           break;
532
533         case MN_CONTROLFIELD:
534           menu_action(items[active_item]);
535           break;
536
537         case MN_ACTION:
538           menu_action(items[active_item]);
539           break;
540
541         case MN_STRINGSELECT:
542           if(items[active_item]->selected+1 < items[active_item]->list.size())
543               items[active_item]->selected++;
544           else
545             items[active_item]->selected = 0;
546
547           menu_action(items[active_item]);
548           break;
549
550         case MN_TEXTFIELD:
551         case MN_NUMFIELD:
552           menuaction = MENU_ACTION_DOWN;
553           update();
554           break;
555
556         case MN_BACK:
557           Menu::pop_current();
558           break;
559         default:
560           break;
561       }
562       break;
563     }
564
565     case MENU_ACTION_REMOVE:
566       if(items[active_item]->kind == MN_TEXTFIELD
567          || items[active_item]->kind == MN_NUMFIELD)
568       {
569         if(!items[active_item]->input.empty())
570         {
571           int i = items[active_item]->input.size();
572
573           while(delete_character > 0)        /* remove characters */
574           {
575             items[active_item]->input.resize(i-1);
576             delete_character--;
577           }
578         }
579       }
580       break;
581
582     case MENU_ACTION_INPUT:
583       if(items[active_item]->kind == MN_TEXTFIELD
584          || (items[active_item]->kind == MN_NUMFIELD
585              && mn_input_char >= '0' && mn_input_char <= '9'))
586       {
587         items[active_item]->input.push_back(mn_input_char);
588       }
589       break;
590
591     case MENU_ACTION_BACK:
592       Menu::pop_current();
593       break;
594
595     case MENU_ACTION_NONE:
596       break;
597   }
598   menuaction = MENU_ACTION_NONE;
599
600   assert(active_item < int(items.size()));
601 }
602
603 int
604 Menu::check()
605 {
606   if (hit_item != -1)
607     return items[hit_item]->id;
608   else
609     return -1;
610 }
611
612 void
613 Menu::menu_action(MenuItem* )
614 {}
615
616 void
617 Menu::draw_item(DrawingContext& context, int index)
618 {
619   float menu_height = get_height();
620   float menu_width  = get_width();
621
622   MenuItem& pitem = *(items[index]);
623
624   Color text_color = default_color;
625   float x_pos       = pos_x;
626   float y_pos       = pos_y + 24*index - menu_height/2 + 12;
627   int shadow_size = 2;
628   int text_width  = int(normal_font->get_text_width(pitem.text));
629   int input_width = int(normal_font->get_text_width(pitem.input) + 10);
630   int list_width = 0;
631
632   float left  = pos_x - menu_width/2 + 16;
633   float right = pos_x + menu_width/2 - 16;
634
635   if(pitem.list.size() > 0) {
636     list_width = (int) normal_font->get_text_width(pitem.list[pitem.selected]);
637   }
638
639   if (arrange_left)
640     x_pos += 24 - menu_width/2 + (text_width + input_width + list_width)/2;
641
642   if(index == active_item)
643     {
644       shadow_size = 3;
645       text_color = active_color;
646     }
647
648   if(active_item == index)
649     {
650       float blink = (sinf(real_time * M_PI * 1.0f)/2.0f + 0.5f) * 0.5f + 0.25f;
651       context.draw_filled_rect(Rect(Vector(pos_x - menu_width/2 + 10 - 2, y_pos - 12 - 2),
652                                     Vector(pos_x + menu_width/2 - 10 + 2, y_pos + 12 + 2)),
653                                Color(1.0f, 1.0f, 1.0f, blink),
654                                14.0f,
655                                LAYER_GUI-10);
656       context.draw_filled_rect(Rect(Vector(pos_x - menu_width/2 + 10, y_pos - 12),
657                                     Vector(pos_x + menu_width/2 - 10, y_pos + 12)),
658                                Color(1.0f, 1.0f, 1.0f, 0.5f),
659                                12.0f,
660                                LAYER_GUI-10);
661     }
662
663   switch (pitem.kind)
664     {
665     case MN_INACTIVE:
666       {
667         context.draw_text(normal_font, pitem.text,
668                           Vector(pos_x, y_pos - int(normal_font->get_height()/2)),
669                           ALIGN_CENTER, LAYER_GUI, inactive_color);
670         break;
671       }
672
673     case MN_HL:
674       {
675         // TODO
676         float x = pos_x - menu_width/2;
677         float y = y_pos - 12;
678         /* Draw a horizontal line with a little 3d effect */
679         context.draw_filled_rect(Vector(x, y + 6),
680                                  Vector(menu_width, 4),
681                                  Color(0.6f, 0.7f, 1.0f, 1.0f), LAYER_GUI);
682         context.draw_filled_rect(Vector(x, y + 6),
683                                  Vector(menu_width, 2),
684                                  Color(1.0f, 1.0f, 1.0f, 1.0f), LAYER_GUI);
685         break;
686       }
687     case MN_LABEL:
688       {
689         context.draw_text(big_font, pitem.text,
690                           Vector(pos_x, y_pos - int(big_font->get_height()/2)),
691                           ALIGN_CENTER, LAYER_GUI, label_color);
692         break;
693       }
694     case MN_TEXTFIELD:
695     case MN_NUMFIELD:
696     case MN_CONTROLFIELD:
697       {
698         if(pitem.kind == MN_TEXTFIELD || pitem.kind == MN_NUMFIELD)
699           {
700             if(active_item == index)
701               context.draw_text(normal_font,
702                                 pitem.get_input_with_symbol(true),
703                                 Vector(right, y_pos - int(normal_font->get_height()/2)),
704                                 ALIGN_RIGHT, LAYER_GUI, field_color);
705             else
706               context.draw_text(normal_font,
707                                 pitem.get_input_with_symbol(false),
708                                 Vector(right, y_pos - int(normal_font->get_height()/2)),
709                                 ALIGN_RIGHT, LAYER_GUI, field_color);
710           }
711         else
712           context.draw_text(normal_font, pitem.input,
713                             Vector(right, y_pos - int(normal_font->get_height()/2)),
714                             ALIGN_RIGHT, LAYER_GUI, field_color);
715
716         context.draw_text(normal_font, pitem.text,
717                           Vector(left, y_pos - int(normal_font->get_height()/2)),
718                           ALIGN_LEFT, LAYER_GUI, text_color);
719         break;
720       }
721     case MN_STRINGSELECT:
722       {
723         float roff = arrow_left->get_width();
724         // Draw left side
725         context.draw_text(normal_font, pitem.text,
726                           Vector(left, y_pos - int(normal_font->get_height()/2)),
727                           ALIGN_LEFT, LAYER_GUI, text_color);
728
729         // Draw right side
730         context.draw_surface(arrow_left.get(),
731                              Vector(right - list_width - roff - roff, y_pos - 8),
732                              LAYER_GUI);
733         context.draw_surface(arrow_right.get(),
734                              Vector(right - roff, y_pos - 8),
735                              LAYER_GUI);
736         context.draw_text(normal_font, pitem.list[pitem.selected],
737                           Vector(right - roff, y_pos - int(normal_font->get_height()/2)),
738                           ALIGN_RIGHT, LAYER_GUI, text_color);
739         break;
740       }
741     case MN_BACK:
742       {
743         context.draw_text(normal_font, pitem.text,
744                           Vector(pos_x, y_pos - int(normal_font->get_height()/2)),
745                           ALIGN_CENTER, LAYER_GUI, text_color);
746         context.draw_surface(back.get(),
747                              Vector(x_pos + text_width/2  + 16, y_pos - 8),
748                              LAYER_GUI);
749         break;
750       }
751
752     case MN_TOGGLE:
753       {
754         context.draw_text(normal_font, pitem.text,
755                           Vector(pos_x - menu_width/2 + 16, y_pos - (normal_font->get_height()/2)),
756                           ALIGN_LEFT, LAYER_GUI, text_color);
757
758         if(pitem.toggled)
759           context.draw_surface(checkbox_checked.get(),
760                                Vector(x_pos + (menu_width/2-16) - checkbox->get_width(), y_pos - 8),
761                                LAYER_GUI + 1);
762         else
763           context.draw_surface(checkbox.get(),
764                                Vector(x_pos + (menu_width/2-16) - checkbox->get_width(), y_pos - 8),
765                                LAYER_GUI + 1);
766         break;
767       }
768     case MN_ACTION:
769       context.draw_text(normal_font, pitem.text,
770                         Vector(pos_x, y_pos - int(normal_font->get_height()/2)),
771                         ALIGN_CENTER, LAYER_GUI, text_color);
772       break;
773
774     case MN_GOTO:
775       context.draw_text(normal_font, pitem.text,
776                         Vector(pos_x, y_pos - int(normal_font->get_height()/2)),
777                         ALIGN_CENTER, LAYER_GUI, text_color);
778       break;
779     }
780 }
781
782 float
783 Menu::get_width() const
784 {
785   /* The width of the menu has to be more than the width of the text
786      with the most characters */
787   float menu_width = 0;
788   for(unsigned int i = 0; i < items.size(); ++i)
789   {
790     Font* font = normal_font;
791     if(items[i]->kind == MN_LABEL)
792       font = big_font;
793
794     float w = font->get_text_width(items[i]->text) +
795         big_font->get_text_width(items[i]->input) + 16;
796     if(items[i]->kind == MN_TOGGLE)
797       w += 32;
798
799     if(w > menu_width)
800       menu_width = w;
801   }
802
803   return menu_width + 24;
804 }
805
806 float
807 Menu::get_height() const
808 {
809   return items.size() * 24;
810 }
811
812 /* Draw the current menu. */
813 void
814 Menu::draw(DrawingContext& context)
815 {
816   if(MouseCursor::current()) {
817     MouseCursor::current()->draw(context);
818   }
819
820   float menu_width  = get_width();
821   float menu_height = get_height();
822
823   if (effect_progress != 1.0f)
824     {
825       if (close)
826         {
827           menu_width  = (current_->get_width()  * (1.0f - effect_progress));
828           menu_height = (current_->get_height() * (1.0f - effect_progress));
829         }
830       else if (Menu::previous)
831         {
832           menu_width  = (menu_width  * effect_progress) + (Menu::previous->get_width()  * (1.0f - effect_progress));
833           menu_height = (menu_height * effect_progress) + (Menu::previous->get_height() * (1.0f - effect_progress));
834           //std::cout << effect_progress << " " << this << " " << last_menus.back() << std::endl;
835         }
836       else
837         {
838           menu_width  *= effect_progress;
839           menu_height *= effect_progress;
840         }
841     }
842
843   /* Draw a transparent background */
844   context.draw_filled_rect(Rect(Vector(pos_x - menu_width/2-4, pos_y - menu_height/2 - 10-4),
845                                 Vector(pos_x + menu_width/2+4, pos_y - menu_height/2 + 10 + menu_height+4)),
846                            Color(0.2f, 0.3f, 0.4f, 0.8f), 
847                            20.0f,
848                            LAYER_GUI-10);
849
850   context.draw_filled_rect(Rect(Vector(pos_x - menu_width/2, pos_y - menu_height/2 - 10),
851                                 Vector(pos_x + menu_width/2, pos_y - menu_height/2 + 10 + menu_height)),
852                            Color(0.6f, 0.7f, 0.8f, 0.5f), 
853                            16.0f,
854                            LAYER_GUI-10);
855
856   if (!items[active_item]->help.empty())
857     {
858       int text_width  = (int) normal_font->get_text_width(items[active_item]->help);
859       int text_height = (int) normal_font->get_text_height(items[active_item]->help);
860       
861       Rect text_rect(pos_x - text_width/2 - 8, 
862                      SCREEN_HEIGHT - 48 - text_height/2 - 4,
863                      pos_x + text_width/2 + 8, 
864                      SCREEN_HEIGHT - 48 + text_height/2 + 4);
865         
866       context.draw_filled_rect(Rect(text_rect.p1 - Vector(4,4),
867                                     text_rect.p2 + Vector(4,4)),
868                                Color(0.2f, 0.3f, 0.4f, 0.8f), 
869                                16.0f,
870                                LAYER_GUI-10);
871       
872       context.draw_filled_rect(text_rect,
873                                Color(0.6f, 0.7f, 0.8f, 0.5f), 
874                                16.0f,
875                                LAYER_GUI-10);
876
877       context.draw_text(normal_font, items[active_item]->help,
878                         Vector(pos_x, SCREEN_HEIGHT - 48 - text_height/2),
879                         ALIGN_CENTER, LAYER_GUI);
880     }
881
882   if (effect_progress == 1.0f)
883     for(unsigned int i = 0; i < items.size(); ++i)
884       {
885         draw_item(context, i);
886       }
887 }
888
889 MenuItem&
890 Menu::get_item_by_id(int id)
891 {
892   for(std::vector<MenuItem*>::iterator i = items.begin();
893       i != items.end(); ++i) {
894     MenuItem& item = **i;
895
896     if(item.id == id)
897       return item;
898   }
899
900   throw std::runtime_error("MenuItem not found");
901 }
902
903 const MenuItem&
904 Menu::get_item_by_id(int id) const
905 {
906   for(std::vector<MenuItem*>::const_iterator i = items.begin();
907       i != items.end(); ++i) {
908     const MenuItem& item = **i;
909
910     if(item.id == id)
911       return item;
912   }
913
914   throw std::runtime_error("MenuItem not found");
915 }
916
917 int Menu::get_active_item_id()
918 {
919   return items[active_item]->id;
920 }
921
922 bool
923 Menu::is_toggled(int id) const
924 {
925   return get_item_by_id(id).toggled;
926 }
927
928 void
929 Menu::set_toggled(int id, bool toggled)
930 {
931   get_item_by_id(id).toggled = toggled;
932 }
933
934 Menu*
935 Menu::get_parent() const
936 {
937   if (last_menus.empty())
938     return 0;
939   else
940     return last_menus.back();
941 }
942
943 /* Check for menu event */
944 void
945 Menu::event(const SDL_Event& event)
946 {
947   if(effect_progress != 1.0f)
948     return;
949
950   switch(event.type) {
951     case SDL_MOUSEBUTTONDOWN:
952       {
953         int x = int(event.motion.x * float(SCREEN_WIDTH)/screen->w);
954         int y = int(event.motion.y * float(SCREEN_HEIGHT)/screen->h);
955
956         if(x > pos_x - get_width()/2 &&
957             x < pos_x + get_width()/2 &&
958             y > pos_y - get_height()/2 &&
959             y < pos_y + get_height()/2)
960           {
961             menuaction = MENU_ACTION_HIT;
962           }
963       }
964       break;
965
966     case SDL_MOUSEMOTION:
967       {
968         float x = event.motion.x * SCREEN_WIDTH/screen->w;
969         float y = event.motion.y * SCREEN_HEIGHT/screen->h;
970
971         if(x > pos_x - get_width()/2 &&
972             x < pos_x + get_width()/2 &&
973             y > pos_y - get_height()/2 &&
974             y < pos_y + get_height()/2)
975           {
976             int new_active_item
977               = static_cast<int> ((y - (pos_y - get_height()/2)) / 24);
978
979             /* only change the mouse focus to a selectable item */
980             if ((items[new_active_item]->kind != MN_HL)
981                 && (items[new_active_item]->kind != MN_LABEL)
982                 && (items[new_active_item]->kind != MN_INACTIVE))
983               active_item = new_active_item;
984
985             if(MouseCursor::current())
986               MouseCursor::current()->set_state(MC_LINK);
987           }
988         else
989         {
990           if(MouseCursor::current())
991             MouseCursor::current()->set_state(MC_NORMAL);
992         }
993       }
994       break;
995
996     default:
997       break;
998     }
999 }
1000
1001 void
1002 Menu::set_active_item(int id)
1003 {
1004   for(size_t i = 0; i < items.size(); ++i) {
1005     MenuItem* item = items[i];
1006     if(item->id == id) {
1007       active_item = i;
1008       break;
1009     }
1010   }
1011 }