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