e544e5f45d101a790483b9de440a5ecb05ad1e28
[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
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   if (current_ && current_->close == true)
148     return;
149
150   previous = current_;
151
152   if (menu) {
153     menu->effect_start_time = real_time;
154     menu->effect_progress = 0.0f;
155     current_ = menu;
156   }
157   else if (current_) {
158     last_menus.clear();                         //NULL new menu pointer => close all menus
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
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
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     int id = items[hit_item]->id;
608     hit_item = -1;                      //Clear event when checked out.. (we would end up in a loop when we try to leave "fake" submenu like Addons or Contrib)
609     return id;
610   }
611   else
612     return -1;
613 }
614
615 void
616 Menu::menu_action(MenuItem* )
617 {}
618
619 void
620 Menu::draw_item(DrawingContext& context, int index)
621 {
622   float menu_height = get_height();
623   float menu_width  = get_width();
624
625   MenuItem& pitem = *(items[index]);
626
627   Color text_color = default_color;
628   float x_pos       = pos_x;
629   float y_pos       = pos_y + 24*index - menu_height/2 + 12;
630   int shadow_size = 2;
631   int text_width  = int(normal_font->get_text_width(pitem.text));
632   int input_width = int(normal_font->get_text_width(pitem.input) + 10);
633   int list_width = 0;
634
635   float left  = pos_x - menu_width/2 + 16;
636   float right = pos_x + menu_width/2 - 16;
637
638   if(pitem.list.size() > 0) {
639     list_width = (int) normal_font->get_text_width(pitem.list[pitem.selected]);
640   }
641
642   if (arrange_left)
643     x_pos += 24 - menu_width/2 + (text_width + input_width + list_width)/2;
644
645   if(index == active_item)
646     {
647       shadow_size = 3;
648       text_color = active_color;
649     }
650
651   if(active_item == index)
652     {
653       float blink = (sinf(real_time * M_PI * 1.0f)/2.0f + 0.5f) * 0.5f + 0.25f;
654       context.draw_filled_rect(Rect(Vector(pos_x - menu_width/2 + 10 - 2, y_pos - 12 - 2),
655                                     Vector(pos_x + menu_width/2 - 10 + 2, y_pos + 12 + 2)),
656                                Color(1.0f, 1.0f, 1.0f, blink),
657                                14.0f,
658                                LAYER_GUI-10);
659       context.draw_filled_rect(Rect(Vector(pos_x - menu_width/2 + 10, y_pos - 12),
660                                     Vector(pos_x + menu_width/2 - 10, y_pos + 12)),
661                                Color(1.0f, 1.0f, 1.0f, 0.5f),
662                                12.0f,
663                                LAYER_GUI-10);
664     }
665
666   switch (pitem.kind)
667     {
668     case MN_INACTIVE:
669       {
670         context.draw_text(normal_font, pitem.text,
671                           Vector(pos_x, y_pos - int(normal_font->get_height()/2)),
672                           ALIGN_CENTER, LAYER_GUI, inactive_color);
673         break;
674       }
675
676     case MN_HL:
677       {
678         // TODO
679         float x = pos_x - menu_width/2;
680         float y = y_pos - 12;
681         /* Draw a horizontal line with a little 3d effect */
682         context.draw_filled_rect(Vector(x, y + 6),
683                                  Vector(menu_width, 4),
684                                  Color(0.6f, 0.7f, 1.0f, 1.0f), LAYER_GUI);
685         context.draw_filled_rect(Vector(x, y + 6),
686                                  Vector(menu_width, 2),
687                                  Color(1.0f, 1.0f, 1.0f, 1.0f), LAYER_GUI);
688         break;
689       }
690     case MN_LABEL:
691       {
692         context.draw_text(big_font, pitem.text,
693                           Vector(pos_x, y_pos - int(big_font->get_height()/2)),
694                           ALIGN_CENTER, LAYER_GUI, label_color);
695         break;
696       }
697     case MN_TEXTFIELD:
698     case MN_NUMFIELD:
699     case MN_CONTROLFIELD:
700       {
701         if(pitem.kind == MN_TEXTFIELD || pitem.kind == MN_NUMFIELD)
702           {
703             if(active_item == index)
704               context.draw_text(normal_font,
705                                 pitem.get_input_with_symbol(true),
706                                 Vector(right, y_pos - int(normal_font->get_height()/2)),
707                                 ALIGN_RIGHT, LAYER_GUI, field_color);
708             else
709               context.draw_text(normal_font,
710                                 pitem.get_input_with_symbol(false),
711                                 Vector(right, y_pos - int(normal_font->get_height()/2)),
712                                 ALIGN_RIGHT, LAYER_GUI, field_color);
713           }
714         else
715           context.draw_text(normal_font, pitem.input,
716                             Vector(right, y_pos - int(normal_font->get_height()/2)),
717                             ALIGN_RIGHT, LAYER_GUI, field_color);
718
719         context.draw_text(normal_font, pitem.text,
720                           Vector(left, y_pos - int(normal_font->get_height()/2)),
721                           ALIGN_LEFT, LAYER_GUI, text_color);
722         break;
723       }
724     case MN_STRINGSELECT:
725       {
726         float roff = arrow_left->get_width();
727         // Draw left side
728         context.draw_text(normal_font, pitem.text,
729                           Vector(left, y_pos - int(normal_font->get_height()/2)),
730                           ALIGN_LEFT, LAYER_GUI, text_color);
731
732         // Draw right side
733         context.draw_surface(arrow_left.get(),
734                              Vector(right - list_width - roff - roff, y_pos - 8),
735                              LAYER_GUI);
736         context.draw_surface(arrow_right.get(),
737                              Vector(right - roff, y_pos - 8),
738                              LAYER_GUI);
739         context.draw_text(normal_font, pitem.list[pitem.selected],
740                           Vector(right - roff, y_pos - int(normal_font->get_height()/2)),
741                           ALIGN_RIGHT, LAYER_GUI, text_color);
742         break;
743       }
744     case MN_BACK:
745       {
746         context.draw_text(normal_font, pitem.text,
747                           Vector(pos_x, y_pos - int(normal_font->get_height()/2)),
748                           ALIGN_CENTER, LAYER_GUI, text_color);
749         context.draw_surface(back.get(),
750                              Vector(x_pos + text_width/2  + 16, y_pos - 8),
751                              LAYER_GUI);
752         break;
753       }
754
755     case MN_TOGGLE:
756       {
757         context.draw_text(normal_font, pitem.text,
758                           Vector(pos_x - menu_width/2 + 16, y_pos - (normal_font->get_height()/2)),
759                           ALIGN_LEFT, LAYER_GUI, text_color);
760
761         if(pitem.toggled)
762           context.draw_surface(checkbox_checked.get(),
763                                Vector(x_pos + (menu_width/2-16) - checkbox->get_width(), y_pos - 8),
764                                LAYER_GUI + 1);
765         else
766           context.draw_surface(checkbox.get(),
767                                Vector(x_pos + (menu_width/2-16) - checkbox->get_width(), y_pos - 8),
768                                LAYER_GUI + 1);
769         break;
770       }
771     case MN_ACTION:
772       context.draw_text(normal_font, pitem.text,
773                         Vector(pos_x, y_pos - int(normal_font->get_height()/2)),
774                         ALIGN_CENTER, LAYER_GUI, text_color);
775       break;
776
777     case MN_GOTO:
778       context.draw_text(normal_font, pitem.text,
779                         Vector(pos_x, y_pos - int(normal_font->get_height()/2)),
780                         ALIGN_CENTER, LAYER_GUI, text_color);
781       break;
782     }
783 }
784
785 float
786 Menu::get_width() const
787 {
788   /* The width of the menu has to be more than the width of the text
789      with the most characters */
790   float menu_width = 0;
791   for(unsigned int i = 0; i < items.size(); ++i)
792   {
793     Font* font = normal_font;
794     if(items[i]->kind == MN_LABEL)
795       font = big_font;
796
797     float w = font->get_text_width(items[i]->text) +
798         big_font->get_text_width(items[i]->input) + 16;
799     if(items[i]->kind == MN_TOGGLE)
800       w += 32;
801
802     if(w > menu_width)
803       menu_width = w;
804   }
805
806   return menu_width + 24;
807 }
808
809 float
810 Menu::get_height() const
811 {
812   return items.size() * 24;
813 }
814
815 /* Draw the current menu. */
816 void
817 Menu::draw(DrawingContext& context)
818 {
819   if(MouseCursor::current()) {
820     MouseCursor::current()->draw(context);
821   }
822
823   float menu_width  = get_width();
824   float menu_height = get_height();
825
826   if (effect_progress != 1.0f)
827     {
828       if (close)
829         {
830           menu_width  = (current_->get_width()  * (1.0f - effect_progress));
831           menu_height = (current_->get_height() * (1.0f - effect_progress));
832         }
833       else if (Menu::previous)
834         {
835           menu_width  = (menu_width  * effect_progress) + (Menu::previous->get_width()  * (1.0f - effect_progress));
836           menu_height = (menu_height * effect_progress) + (Menu::previous->get_height() * (1.0f - effect_progress));
837           //std::cout << effect_progress << " " << this << " " << last_menus.back() << std::endl;
838         }
839       else
840         {
841           menu_width  *= effect_progress;
842           menu_height *= effect_progress;
843         }
844     }
845
846   /* Draw a transparent background */
847   context.draw_filled_rect(Rect(Vector(pos_x - menu_width/2-4, pos_y - menu_height/2 - 10-4),
848                                 Vector(pos_x + menu_width/2+4, pos_y - menu_height/2 + 10 + menu_height+4)),
849                            Color(0.2f, 0.3f, 0.4f, 0.8f), 
850                            20.0f,
851                            LAYER_GUI-10);
852
853   context.draw_filled_rect(Rect(Vector(pos_x - menu_width/2, pos_y - menu_height/2 - 10),
854                                 Vector(pos_x + menu_width/2, pos_y - menu_height/2 + 10 + menu_height)),
855                            Color(0.6f, 0.7f, 0.8f, 0.5f), 
856                            16.0f,
857                            LAYER_GUI-10);
858
859   if (!items[active_item]->help.empty())
860     {
861       int text_width  = (int) normal_font->get_text_width(items[active_item]->help);
862       int text_height = (int) normal_font->get_text_height(items[active_item]->help);
863       
864       Rect text_rect(pos_x - text_width/2 - 8, 
865                      SCREEN_HEIGHT - 48 - text_height/2 - 4,
866                      pos_x + text_width/2 + 8, 
867                      SCREEN_HEIGHT - 48 + text_height/2 + 4);
868         
869       context.draw_filled_rect(Rect(text_rect.p1 - Vector(4,4),
870                                     text_rect.p2 + Vector(4,4)),
871                                Color(0.2f, 0.3f, 0.4f, 0.8f), 
872                                16.0f,
873                                LAYER_GUI-10);
874       
875       context.draw_filled_rect(text_rect,
876                                Color(0.6f, 0.7f, 0.8f, 0.5f), 
877                                16.0f,
878                                LAYER_GUI-10);
879
880       context.draw_text(normal_font, items[active_item]->help,
881                         Vector(pos_x, SCREEN_HEIGHT - 48 - text_height/2),
882                         ALIGN_CENTER, LAYER_GUI);
883     }
884
885   if (effect_progress == 1.0f)
886     for(unsigned int i = 0; i < items.size(); ++i)
887       {
888         draw_item(context, i);
889       }
890 }
891
892 MenuItem&
893 Menu::get_item_by_id(int id)
894 {
895   for(std::vector<MenuItem*>::iterator i = items.begin();
896       i != items.end(); ++i) {
897     MenuItem& item = **i;
898
899     if(item.id == id)
900       return item;
901   }
902
903   throw std::runtime_error("MenuItem not found");
904 }
905
906 const MenuItem&
907 Menu::get_item_by_id(int id) const
908 {
909   for(std::vector<MenuItem*>::const_iterator i = items.begin();
910       i != items.end(); ++i) {
911     const MenuItem& item = **i;
912
913     if(item.id == id)
914       return item;
915   }
916
917   throw std::runtime_error("MenuItem not found");
918 }
919
920 int Menu::get_active_item_id()
921 {
922   return items[active_item]->id;
923 }
924
925 bool
926 Menu::is_toggled(int id) const
927 {
928   return get_item_by_id(id).toggled;
929 }
930
931 void
932 Menu::set_toggled(int id, bool toggled)
933 {
934   get_item_by_id(id).toggled = toggled;
935 }
936
937 Menu*
938 Menu::get_parent() const
939 {
940   if (last_menus.empty())
941     return 0;
942   else
943     return last_menus.back();
944 }
945
946 /* Check for menu event */
947 void
948 Menu::event(const SDL_Event& event)
949 {
950   if(effect_progress != 1.0f)
951     return;
952
953   switch(event.type) {
954     case SDL_MOUSEBUTTONDOWN:
955       {
956         int x = int(event.motion.x * float(SCREEN_WIDTH)/screen->w);
957         int y = int(event.motion.y * float(SCREEN_HEIGHT)/screen->h);
958
959         if(x > pos_x - get_width()/2 &&
960             x < pos_x + get_width()/2 &&
961             y > pos_y - get_height()/2 &&
962             y < pos_y + get_height()/2)
963           {
964             menuaction = MENU_ACTION_HIT;
965           }
966       }
967       break;
968
969     case SDL_MOUSEMOTION:
970       {
971         float x = event.motion.x * SCREEN_WIDTH/screen->w;
972         float y = event.motion.y * SCREEN_HEIGHT/screen->h;
973
974         if(x > pos_x - get_width()/2 &&
975             x < pos_x + get_width()/2 &&
976             y > pos_y - get_height()/2 &&
977             y < pos_y + get_height()/2)
978           {
979             int new_active_item
980               = static_cast<int> ((y - (pos_y - get_height()/2)) / 24);
981
982             /* only change the mouse focus to a selectable item */
983             if ((items[new_active_item]->kind != MN_HL)
984                 && (items[new_active_item]->kind != MN_LABEL)
985                 && (items[new_active_item]->kind != MN_INACTIVE))
986               active_item = new_active_item;
987
988             if(MouseCursor::current())
989               MouseCursor::current()->set_state(MC_LINK);
990           }
991         else
992         {
993           if(MouseCursor::current())
994             MouseCursor::current()->set_state(MC_NORMAL);
995         }
996       }
997       break;
998
999     default:
1000       break;
1001     }
1002 }
1003
1004 void
1005 Menu::set_active_item(int id)
1006 {
1007   for(size_t i = 0; i < items.size(); ++i) {
1008     MenuItem* item = items[i];
1009     if(item->id == id) {
1010       active_item = i;
1011       break;
1012     }
1013   }
1014 }