158bb069bfd55aa76fac1463cb82333a743f2f47
[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   input_flickering()
176 {
177   toggled = false;
178   selected = false;
179   target_menu = 0;
180 }
181
182 void
183 MenuItem::change_text(const  std::string& text_)
184 {
185   text = text_;
186 }
187
188 void
189 MenuItem::change_input(const  std::string& text_)
190 {
191   input = text_;
192 }
193
194 void
195 MenuItem::set_help(const std::string& help_text)
196 {
197   std::string overflow;
198   help = normal_font->wrap_to_width(help_text, 600, &overflow);
199   while (!overflow.empty())
200   {
201     help += "\n";
202     help += normal_font->wrap_to_width(overflow, 600, &overflow);
203   }
204 }
205
206 std::string MenuItem::get_input_with_symbol(bool active_item)
207 {
208   if(!active_item) {
209     input_flickering = true;
210   } else {
211     input_flickering = ((int) (real_time / FLICK_CURSOR_TIME)) % 2;
212   }
213
214   char str[1024];
215   if(input_flickering)
216     snprintf(str, sizeof(str), "%s ",input.c_str());
217   else
218     snprintf(str, sizeof(str), "%s_",input.c_str());
219
220   std::string string = str;
221
222   return string;
223 }
224
225 Menu::~Menu()
226 {
227   all_menus.remove(this);
228
229   for(std::vector<MenuItem*>::iterator i = items.begin();
230       i != items.end(); ++i)
231     delete *i;
232
233   if(current_ == this)
234     current_ = NULL;
235
236   if (previous == this)
237     previous = NULL;
238 }
239
240 Menu::Menu() :
241   hit_item(),
242   pos_x(),
243   pos_y(),
244   menuaction(),
245   delete_character(),
246   mn_input_char(),
247   menu_repeat_time(),
248   close(false),
249   items(),
250   effect_progress(),
251   effect_start_time(),
252   arrange_left(),
253   active_item(),
254   checkbox(),
255   checkbox_checked(),
256   back(),
257   arrow_left(),
258   arrow_right()
259 {
260   all_menus.push_back(this);
261
262   hit_item = -1;
263   menuaction = MENU_ACTION_NONE;
264   delete_character = 0;
265   mn_input_char = '\0';
266
267   pos_x        = SCREEN_WIDTH/2;
268   pos_y        = SCREEN_HEIGHT/2;
269   arrange_left = 0;
270   active_item  = -1;
271
272   effect_progress   = 0.0f;
273   effect_start_time = 0.0f;
274
275   checkbox.reset(new Surface("images/engine/menu/checkbox-unchecked.png"));
276   checkbox_checked.reset(new Surface("images/engine/menu/checkbox-checked.png"));
277   back.reset(new Surface("images/engine/menu/arrow-back.png"));
278   arrow_left.reset(new Surface("images/engine/menu/arrow-left.png"));
279   arrow_right.reset(new Surface("images/engine/menu/arrow-right.png"));
280 }
281
282 void
283 Menu::set_pos(float x, float y, float rw, float rh)
284 {
285   pos_x = x + get_width()  * rw;
286   pos_y = y + get_height() * rh;
287 }
288
289 /* Add an item to a menu */
290 void
291 Menu::additem(MenuItem* item)
292 {
293   items.push_back(item);
294
295   /* If a new menu is being built, the active item shouldn't be set to
296    * something that isn't selectable. Set the active_item to the first
297    * selectable item added.
298    */
299   if (active_item == -1
300       && item->kind != MN_HL
301       && item->kind != MN_LABEL
302       && item->kind != MN_INACTIVE) {
303     active_item = items.size() - 1;
304   }
305 }
306
307 MenuItem*
308 Menu::add_hl()
309 {
310   MenuItem* item = new MenuItem(MN_HL);
311   additem(item);
312   return item;
313 }
314
315 MenuItem*
316 Menu::add_label(const std::string& text)
317 {
318   MenuItem* item = new MenuItem(MN_LABEL);
319   item->text = text;
320   additem(item);
321   return item;
322 }
323
324 MenuItem*
325 Menu::add_controlfield(int id, const std::string& text,
326                        const std::string& mapping)
327 {
328   MenuItem* item = new MenuItem(MN_CONTROLFIELD, id);
329   item->change_text(text);
330   item->change_input(mapping);
331   additem(item);
332   return item;
333 }
334
335 MenuItem*
336 Menu::add_entry(int id, const std::string& text)
337 {
338   MenuItem* item = new MenuItem(MN_ACTION, id);
339   item->text = text;
340   additem(item);
341   return item;
342 }
343
344 MenuItem*
345 Menu::add_inactive(int id, const std::string& text)
346 {
347   MenuItem* item = new MenuItem(MN_INACTIVE, id);
348   item->text = text;
349   additem(item);
350   return item;
351 }
352
353 MenuItem*
354 Menu::add_toggle(int id, const std::string& text, bool toogled)
355 {
356   MenuItem* item = new MenuItem(MN_TOGGLE, id);
357   item->text = text;
358   item->toggled = toogled;
359   additem(item);
360   return item;
361 }
362
363 MenuItem*
364 Menu::add_string_select(int id, const std::string& text)
365 {
366   MenuItem* item = new MenuItem(MN_STRINGSELECT, id);
367   item->text = text;
368   additem(item);
369   return item;
370 }
371
372 MenuItem*
373 Menu::add_back(const std::string& text)
374 {
375   MenuItem* item = new MenuItem(MN_BACK);
376   item->text = text;
377   additem(item);
378   return item;
379 }
380
381 MenuItem*
382 Menu::add_submenu(const std::string& text, Menu* submenu, int id)
383 {
384   MenuItem* item = new MenuItem(MN_GOTO, id);
385   item->text = text;
386   item->target_menu = submenu;
387   additem(item);
388   return item;
389 }
390
391 void
392 Menu::clear()
393 {
394   for(std::vector<MenuItem*>::iterator i = items.begin();
395       i != items.end(); ++i) {
396     delete *i;
397   }
398   items.clear();
399   active_item = -1;
400 }
401
402 /* Process actions done on the menu */
403 void
404 Menu::update()
405 {
406   int menu_height = (int) get_height();
407   if (menu_height > SCREEN_HEIGHT)
408   { // Scrolling
409     int scroll_offset = (menu_height - SCREEN_HEIGHT) / 2 + 32;
410     pos_y = SCREEN_HEIGHT/2 - scroll_offset * ((float(active_item) / (items.size()-1)) - 0.5f) * 2.0f;
411   }
412
413   effect_progress = (real_time - effect_start_time) * 6.0f;
414
415   if(effect_progress >= 1.0f) {
416     effect_progress = 1.0f;
417
418     if (close) {
419       current_ = 0;
420       close = false;
421     }
422   }
423   else if (effect_progress <= 0.0f) {
424     effect_progress = 0.0f;
425   }
426
427   /** check main input controller... */
428   if(g_main_controller->pressed(Controller::UP)) {
429     menuaction = MENU_ACTION_UP;
430     menu_repeat_time = real_time + MENU_REPEAT_INITIAL;
431   }
432   if(g_main_controller->hold(Controller::UP) &&
433      menu_repeat_time != 0 && real_time > menu_repeat_time) {
434     menuaction = MENU_ACTION_UP;
435     menu_repeat_time = real_time + MENU_REPEAT_RATE;
436   }
437
438   if(g_main_controller->pressed(Controller::DOWN)) {
439     menuaction = MENU_ACTION_DOWN;
440     menu_repeat_time = real_time + MENU_REPEAT_INITIAL;
441   }
442   if(g_main_controller->hold(Controller::DOWN) &&
443      menu_repeat_time != 0 && real_time > menu_repeat_time) {
444     menuaction = MENU_ACTION_DOWN;
445     menu_repeat_time = real_time + MENU_REPEAT_RATE;
446   }
447
448   if(g_main_controller->pressed(Controller::LEFT)) {
449     menuaction = MENU_ACTION_LEFT;
450     menu_repeat_time = real_time + MENU_REPEAT_INITIAL;
451   }
452   if(g_main_controller->hold(Controller::LEFT) &&
453      menu_repeat_time != 0 && real_time > menu_repeat_time) {
454     menuaction = MENU_ACTION_LEFT;
455     menu_repeat_time = real_time + MENU_REPEAT_RATE;
456   }
457
458   if(g_main_controller->pressed(Controller::RIGHT)) {
459     menuaction = MENU_ACTION_RIGHT;
460     menu_repeat_time = real_time + MENU_REPEAT_INITIAL;
461   }
462   if(g_main_controller->hold(Controller::RIGHT) &&
463      menu_repeat_time != 0 && real_time > menu_repeat_time) {
464     menuaction = MENU_ACTION_RIGHT;
465     menu_repeat_time = real_time + MENU_REPEAT_RATE;
466   }
467
468   if(g_main_controller->pressed(Controller::ACTION)
469      || g_main_controller->pressed(Controller::MENU_SELECT)) {
470     menuaction = MENU_ACTION_HIT;
471   }
472   if(g_main_controller->pressed(Controller::PAUSE_MENU)) {
473     menuaction = MENU_ACTION_BACK;
474   }
475
476   hit_item = -1;
477   if(items.size() == 0)
478     return;
479
480   int last_active_item = active_item;
481   switch(menuaction) {
482     case MENU_ACTION_UP:
483       do {
484         if (active_item > 0)
485           --active_item;
486         else
487           active_item = int(items.size())-1;
488       } while ((items[active_item]->kind == MN_HL
489                 || items[active_item]->kind == MN_LABEL
490                 || items[active_item]->kind == MN_INACTIVE)
491                && (active_item != last_active_item));
492
493       break;
494
495     case MENU_ACTION_DOWN:
496       do {
497         if(active_item < int(items.size())-1 )
498           ++active_item;
499         else
500           active_item = 0;
501       } while ((items[active_item]->kind == MN_HL
502                 || items[active_item]->kind == MN_LABEL
503                 || items[active_item]->kind == MN_INACTIVE)
504                && (active_item != last_active_item));
505
506       break;
507
508     case MENU_ACTION_LEFT:
509       if(items[active_item]->kind == MN_STRINGSELECT) {
510         if(items[active_item]->selected > 0)
511           items[active_item]->selected--;
512         else
513           items[active_item]->selected = items[active_item]->list.size()-1;
514         
515         menu_action(items[active_item]);
516       }
517       break;
518
519     case MENU_ACTION_RIGHT:
520       if(items[active_item]->kind == MN_STRINGSELECT) {
521         if(items[active_item]->selected+1 < items[active_item]->list.size())
522           items[active_item]->selected++;
523         else
524           items[active_item]->selected = 0;
525         
526         menu_action(items[active_item]);
527       }
528       break;
529
530     case MENU_ACTION_HIT: {
531       hit_item = active_item;
532       switch (items[active_item]->kind) {
533         case MN_GOTO:
534           assert(items[active_item]->target_menu != 0);
535           Menu::push_current(items[active_item]->target_menu);
536           break;
537
538         case MN_TOGGLE:
539           items[active_item]->toggled = !items[active_item]->toggled;
540           menu_action(items[active_item]);
541           break;
542
543         case MN_CONTROLFIELD:
544           menu_action(items[active_item]);
545           break;
546
547         case MN_ACTION:
548           menu_action(items[active_item]);
549           break;
550
551         case MN_STRINGSELECT:
552           if(items[active_item]->selected+1 < items[active_item]->list.size())
553             items[active_item]->selected++;
554           else
555             items[active_item]->selected = 0;
556
557           menu_action(items[active_item]);
558           break;
559
560         case MN_TEXTFIELD:
561         case MN_NUMFIELD:
562           menuaction = MENU_ACTION_DOWN;
563           update();
564           break;
565
566         case MN_BACK:
567           Menu::pop_current();
568           break;
569         default:
570           break;
571       }
572       break;
573     }
574
575     case MENU_ACTION_REMOVE:
576       if(items[active_item]->kind == MN_TEXTFIELD
577          || items[active_item]->kind == MN_NUMFIELD)
578       {
579         if(!items[active_item]->input.empty())
580         {
581           int i = items[active_item]->input.size();
582
583           while(delete_character > 0)        /* remove characters */
584           {
585             items[active_item]->input.resize(i-1);
586             delete_character--;
587           }
588         }
589       }
590       break;
591
592     case MENU_ACTION_INPUT:
593       if(items[active_item]->kind == MN_TEXTFIELD
594          || (items[active_item]->kind == MN_NUMFIELD
595              && mn_input_char >= '0' && mn_input_char <= '9'))
596       {
597         items[active_item]->input.push_back(mn_input_char);
598       }
599       break;
600
601     case MENU_ACTION_BACK:
602       Menu::pop_current();
603       break;
604
605     case MENU_ACTION_NONE:
606       break;
607   }
608   menuaction = MENU_ACTION_NONE;
609
610   assert(active_item < int(items.size()));
611 }
612
613 int
614 Menu::check()
615 {
616   if (hit_item != -1) {
617     int id = items[hit_item]->id;
618     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)
619     return id;
620   }
621   else
622     return -1;
623 }
624
625 void
626 Menu::menu_action(MenuItem* )
627 {}
628
629 void
630 Menu::draw_item(DrawingContext& context, int index)
631 {
632   float menu_height = get_height();
633   float menu_width  = get_width();
634
635   MenuItem& pitem = *(items[index]);
636
637   Color text_color = default_color;
638   float x_pos       = pos_x;
639   float y_pos       = pos_y + 24*index - menu_height/2 + 12;
640   int shadow_size = 2;
641   int text_width  = int(normal_font->get_text_width(pitem.text));
642   int input_width = int(normal_font->get_text_width(pitem.input) + 10);
643   int list_width = 0;
644
645   float left  = pos_x - menu_width/2 + 16;
646   float right = pos_x + menu_width/2 - 16;
647
648   if(pitem.list.size() > 0) {
649     list_width = (int) normal_font->get_text_width(pitem.list[pitem.selected]);
650   }
651
652   if (arrange_left)
653     x_pos += 24 - menu_width/2 + (text_width + input_width + list_width)/2;
654
655   if(index == active_item)
656   {
657     shadow_size = 3;
658     text_color = active_color;
659   }
660
661   if(active_item == index)
662   {
663     float blink = (sinf(real_time * M_PI * 1.0f)/2.0f + 0.5f) * 0.5f + 0.25f;
664     context.draw_filled_rect(Rect(Vector(pos_x - menu_width/2 + 10 - 2, y_pos - 12 - 2),
665                                   Vector(pos_x + menu_width/2 - 10 + 2, y_pos + 12 + 2)),
666                              Color(1.0f, 1.0f, 1.0f, blink),
667                              14.0f,
668                              LAYER_GUI-10);
669     context.draw_filled_rect(Rect(Vector(pos_x - menu_width/2 + 10, y_pos - 12),
670                                   Vector(pos_x + menu_width/2 - 10, y_pos + 12)),
671                              Color(1.0f, 1.0f, 1.0f, 0.5f),
672                              12.0f,
673                              LAYER_GUI-10);
674   }
675
676   switch (pitem.kind)
677   {
678     case MN_INACTIVE:
679     {
680       context.draw_text(normal_font, pitem.text,
681                         Vector(pos_x, y_pos - int(normal_font->get_height()/2)),
682                         ALIGN_CENTER, LAYER_GUI, inactive_color);
683       break;
684     }
685
686     case MN_HL:
687     {
688       // TODO
689       float x = pos_x - menu_width/2;
690       float y = y_pos - 12;
691       /* Draw a horizontal line with a little 3d effect */
692       context.draw_filled_rect(Vector(x, y + 6),
693                                Vector(menu_width, 4),
694                                Color(0.6f, 0.7f, 1.0f, 1.0f), LAYER_GUI);
695       context.draw_filled_rect(Vector(x, y + 6),
696                                Vector(menu_width, 2),
697                                Color(1.0f, 1.0f, 1.0f, 1.0f), LAYER_GUI);
698       break;
699     }
700     case MN_LABEL:
701     {
702       context.draw_text(big_font, pitem.text,
703                         Vector(pos_x, y_pos - int(big_font->get_height()/2)),
704                         ALIGN_CENTER, LAYER_GUI, label_color);
705       break;
706     }
707     case MN_TEXTFIELD:
708     case MN_NUMFIELD:
709     case MN_CONTROLFIELD:
710     {
711       if(pitem.kind == MN_TEXTFIELD || pitem.kind == MN_NUMFIELD)
712       {
713         if(active_item == index)
714           context.draw_text(normal_font,
715                             pitem.get_input_with_symbol(true),
716                             Vector(right, y_pos - int(normal_font->get_height()/2)),
717                             ALIGN_RIGHT, LAYER_GUI, field_color);
718         else
719           context.draw_text(normal_font,
720                             pitem.get_input_with_symbol(false),
721                             Vector(right, y_pos - int(normal_font->get_height()/2)),
722                             ALIGN_RIGHT, LAYER_GUI, field_color);
723       }
724       else
725         context.draw_text(normal_font, pitem.input,
726                           Vector(right, y_pos - int(normal_font->get_height()/2)),
727                           ALIGN_RIGHT, LAYER_GUI, field_color);
728
729       context.draw_text(normal_font, pitem.text,
730                         Vector(left, y_pos - int(normal_font->get_height()/2)),
731                         ALIGN_LEFT, LAYER_GUI, text_color);
732       break;
733     }
734     case MN_STRINGSELECT:
735     {
736       float roff = arrow_left->get_width();
737       // Draw left side
738       context.draw_text(normal_font, pitem.text,
739                         Vector(left, y_pos - int(normal_font->get_height()/2)),
740                         ALIGN_LEFT, LAYER_GUI, text_color);
741
742       // Draw right side
743       context.draw_surface(arrow_left.get(),
744                            Vector(right - list_width - roff - roff, y_pos - 8),
745                            LAYER_GUI);
746       context.draw_surface(arrow_right.get(),
747                            Vector(right - roff, y_pos - 8),
748                            LAYER_GUI);
749       context.draw_text(normal_font, pitem.list[pitem.selected],
750                         Vector(right - roff, y_pos - int(normal_font->get_height()/2)),
751                         ALIGN_RIGHT, LAYER_GUI, text_color);
752       break;
753     }
754     case MN_BACK:
755     {
756       context.draw_text(normal_font, pitem.text,
757                         Vector(pos_x, y_pos - int(normal_font->get_height()/2)),
758                         ALIGN_CENTER, LAYER_GUI, text_color);
759       context.draw_surface(back.get(),
760                            Vector(x_pos + text_width/2  + 16, y_pos - 8),
761                            LAYER_GUI);
762       break;
763     }
764
765     case MN_TOGGLE:
766     {
767       context.draw_text(normal_font, pitem.text,
768                         Vector(pos_x - menu_width/2 + 16, y_pos - (normal_font->get_height()/2)),
769                         ALIGN_LEFT, LAYER_GUI, text_color);
770
771       if(pitem.toggled)
772         context.draw_surface(checkbox_checked.get(),
773                              Vector(x_pos + (menu_width/2-16) - checkbox->get_width(), y_pos - 8),
774                              LAYER_GUI + 1);
775       else
776         context.draw_surface(checkbox.get(),
777                              Vector(x_pos + (menu_width/2-16) - checkbox->get_width(), y_pos - 8),
778                              LAYER_GUI + 1);
779       break;
780     }
781     case MN_ACTION:
782       context.draw_text(normal_font, pitem.text,
783                         Vector(pos_x, y_pos - int(normal_font->get_height()/2)),
784                         ALIGN_CENTER, LAYER_GUI, text_color);
785       break;
786
787     case MN_GOTO:
788       context.draw_text(normal_font, pitem.text,
789                         Vector(pos_x, y_pos - int(normal_font->get_height()/2)),
790                         ALIGN_CENTER, LAYER_GUI, text_color);
791       break;
792   }
793 }
794
795 float
796 Menu::get_width() const
797 {
798   /* The width of the menu has to be more than the width of the text
799      with the most characters */
800   float menu_width = 0;
801   for(unsigned int i = 0; i < items.size(); ++i)
802   {
803     Font* font = normal_font;
804     if(items[i]->kind == MN_LABEL)
805       font = big_font;
806
807     float w = font->get_text_width(items[i]->text) +
808       big_font->get_text_width(items[i]->input) + 16;
809     if(items[i]->kind == MN_TOGGLE)
810       w += 32;
811
812     if(w > menu_width)
813       menu_width = w;
814   }
815
816   return menu_width + 24;
817 }
818
819 float
820 Menu::get_height() const
821 {
822   return items.size() * 24;
823 }
824
825 /* Draw the current menu. */
826 void
827 Menu::draw(DrawingContext& context)
828 {
829   if(MouseCursor::current()) {
830     MouseCursor::current()->draw(context);
831   }
832
833   float menu_width  = get_width();
834   float menu_height = get_height();
835
836   if (effect_progress != 1.0f)
837   {
838     if (close)
839     {
840       menu_width  = (current_->get_width()  * (1.0f - effect_progress));
841       menu_height = (current_->get_height() * (1.0f - effect_progress));
842     }
843     else if (Menu::previous)
844     {
845       menu_width  = (menu_width  * effect_progress) + (Menu::previous->get_width()  * (1.0f - effect_progress));
846       menu_height = (menu_height * effect_progress) + (Menu::previous->get_height() * (1.0f - effect_progress));
847       //std::cout << effect_progress << " " << this << " " << last_menus.back() << std::endl;
848     }
849     else
850     {
851       menu_width  *= effect_progress;
852       menu_height *= effect_progress;
853     }
854   }
855
856   /* Draw a transparent background */
857   context.draw_filled_rect(Rect(Vector(pos_x - menu_width/2-4, pos_y - menu_height/2 - 10-4),
858                                 Vector(pos_x + menu_width/2+4, pos_y - menu_height/2 + 10 + menu_height+4)),
859                            Color(0.2f, 0.3f, 0.4f, 0.8f), 
860                            20.0f,
861                            LAYER_GUI-10);
862
863   context.draw_filled_rect(Rect(Vector(pos_x - menu_width/2, pos_y - menu_height/2 - 10),
864                                 Vector(pos_x + menu_width/2, pos_y - menu_height/2 + 10 + menu_height)),
865                            Color(0.6f, 0.7f, 0.8f, 0.5f), 
866                            16.0f,
867                            LAYER_GUI-10);
868
869   if (!items[active_item]->help.empty())
870   {
871     int text_width  = (int) normal_font->get_text_width(items[active_item]->help);
872     int text_height = (int) normal_font->get_text_height(items[active_item]->help);
873       
874     Rect text_rect(pos_x - text_width/2 - 8, 
875                    SCREEN_HEIGHT - 48 - text_height/2 - 4,
876                    pos_x + text_width/2 + 8, 
877                    SCREEN_HEIGHT - 48 + text_height/2 + 4);
878         
879     context.draw_filled_rect(Rect(text_rect.p1 - Vector(4,4),
880                                   text_rect.p2 + Vector(4,4)),
881                              Color(0.2f, 0.3f, 0.4f, 0.8f), 
882                              16.0f,
883                              LAYER_GUI-10);
884       
885     context.draw_filled_rect(text_rect,
886                              Color(0.6f, 0.7f, 0.8f, 0.5f), 
887                              16.0f,
888                              LAYER_GUI-10);
889
890     context.draw_text(normal_font, items[active_item]->help,
891                       Vector(pos_x, SCREEN_HEIGHT - 48 - text_height/2),
892                       ALIGN_CENTER, LAYER_GUI);
893   }
894
895   if (effect_progress == 1.0f)
896     for(unsigned int i = 0; i < items.size(); ++i)
897     {
898       draw_item(context, i);
899     }
900 }
901
902 MenuItem&
903 Menu::get_item_by_id(int id)
904 {
905   for(std::vector<MenuItem*>::iterator i = items.begin();
906       i != items.end(); ++i) {
907     MenuItem& item = **i;
908
909     if(item.id == id)
910       return item;
911   }
912
913   throw std::runtime_error("MenuItem not found");
914 }
915
916 const MenuItem&
917 Menu::get_item_by_id(int id) const
918 {
919   for(std::vector<MenuItem*>::const_iterator i = items.begin();
920       i != items.end(); ++i) {
921     const MenuItem& item = **i;
922
923     if(item.id == id)
924       return item;
925   }
926
927   throw std::runtime_error("MenuItem not found");
928 }
929
930 int Menu::get_active_item_id()
931 {
932   return items[active_item]->id;
933 }
934
935 bool
936 Menu::is_toggled(int id) const
937 {
938   return get_item_by_id(id).toggled;
939 }
940
941 void
942 Menu::set_toggled(int id, bool toggled)
943 {
944   get_item_by_id(id).toggled = toggled;
945 }
946
947 Menu*
948 Menu::get_parent() const
949 {
950   if (last_menus.empty())
951     return 0;
952   else
953     return last_menus.back();
954 }
955
956 /* Check for menu event */
957 void
958 Menu::event(const SDL_Event& event)
959 {
960   if(effect_progress != 1.0f)
961     return;
962
963   switch(event.type) {
964     case SDL_MOUSEBUTTONDOWN:
965     {
966       int x = int(event.motion.x * float(SCREEN_WIDTH)/g_screen->w);
967       int y = int(event.motion.y * float(SCREEN_HEIGHT)/g_screen->h);
968
969       if(x > pos_x - get_width()/2 &&
970          x < pos_x + get_width()/2 &&
971          y > pos_y - get_height()/2 &&
972          y < pos_y + get_height()/2)
973       {
974         menuaction = MENU_ACTION_HIT;
975       }
976     }
977     break;
978
979     case SDL_MOUSEMOTION:
980     {
981       float x = event.motion.x * SCREEN_WIDTH/g_screen->w;
982       float y = event.motion.y * SCREEN_HEIGHT/g_screen->h;
983
984       if(x > pos_x - get_width()/2 &&
985          x < pos_x + get_width()/2 &&
986          y > pos_y - get_height()/2 &&
987          y < pos_y + get_height()/2)
988       {
989         int new_active_item
990           = static_cast<int> ((y - (pos_y - get_height()/2)) / 24);
991
992         /* only change the mouse focus to a selectable item */
993         if ((items[new_active_item]->kind != MN_HL)
994             && (items[new_active_item]->kind != MN_LABEL)
995             && (items[new_active_item]->kind != MN_INACTIVE))
996           active_item = new_active_item;
997
998         if(MouseCursor::current())
999           MouseCursor::current()->set_state(MC_LINK);
1000       }
1001       else
1002       {
1003         if(MouseCursor::current())
1004           MouseCursor::current()->set_state(MC_NORMAL);
1005       }
1006     }
1007     break;
1008
1009     default:
1010       break;
1011   }
1012 }
1013
1014 void
1015 Menu::set_active_item(int id)
1016 {
1017   for(size_t i = 0; i < items.size(); ++i) {
1018     MenuItem* item = items[i];
1019     if(item->id == id) {
1020       active_item = i;
1021       break;
1022     }
1023   }
1024 }
1025
1026 /* EOF */