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