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 "gui/menu.hpp"
18 
19 #include "control/input_manager.hpp"
20 #include "gui/item_action.hpp"
21 #include "gui/item_back.hpp"
22 #include "gui/item_badguy_select.hpp"
23 #include "gui/item_color.hpp"
24 #include "gui/item_colorchannel.hpp"
25 #include "gui/item_colordisplay.hpp"
26 #include "gui/item_controlfield.hpp"
27 #include "gui/item_file.hpp"
28 #include "gui/item_floatfield.hpp"
29 #include "gui/item_goto.hpp"
30 #include "gui/item_hl.hpp"
31 #include "gui/item_inactive.hpp"
32 #include "gui/item_intfield.hpp"
33 #include "gui/item_label.hpp"
34 #include "gui/item_paths.hpp"
35 #include "gui/item_script.hpp"
36 #include "gui/item_script_line.hpp"
37 #include "gui/item_stringselect.hpp"
38 #include "gui/item_textfield.hpp"
39 #include "gui/item_toggle.hpp"
40 #include "gui/menu_item.hpp"
41 #include "gui/menu_manager.hpp"
42 #include "gui/mousecursor.hpp"
43 #include "math/util.hpp"
44 #include "supertux/globals.hpp"
45 #include "supertux/resources.hpp"
46 #include "video/drawing_context.hpp"
47 #include "video/renderer.hpp"
48 #include "video/video_system.hpp"
49 #include "video/viewport.hpp"
50 
51 static const float MENU_REPEAT_INITIAL = 0.4f;
52 static const float MENU_REPEAT_RATE    = 0.1f;
53 
Menu()54 Menu::Menu() :
55   m_pos(Vector(static_cast<float>(SCREEN_WIDTH) / 2.0f,
56                static_cast<float>(SCREEN_HEIGHT) / 2.0f)),
57   m_delete_character(0),
58   m_mn_input_char('\0'),
59   m_menu_repeat_time(),
60   m_menu_width(),
61   m_items(),
62   m_arrange_left(0),
63   m_active_item(-1)
64 {
65 }
66 
~Menu()67 Menu::~Menu()
68 {
69 }
70 
71 void
set_center_pos(float x,float y)72 Menu::set_center_pos(float x, float y)
73 {
74   m_pos.x = x;
75   m_pos.y = y;
76 }
77 
78 /* Add an item to a menu */
79 MenuItem&
add_item(std::unique_ptr<MenuItem> new_item)80 Menu::add_item(std::unique_ptr<MenuItem> new_item)
81 {
82   m_items.push_back(std::move(new_item));
83   MenuItem& item = *m_items.back();
84 
85   /* If a new menu is being built, the active item shouldn't be set to
86    * something that isn't selectable. Set the active_item to the first
87    * selectable item added.
88    */
89 
90   if (m_active_item == -1 && !item.skippable())
91   {
92     m_active_item = static_cast<int>(m_items.size()) - 1;
93   }
94 
95   calculate_width();
96 
97   return item;
98 }
99 
100 MenuItem&
add_item(std::unique_ptr<MenuItem> new_item,int pos_)101 Menu::add_item(std::unique_ptr<MenuItem> new_item, int pos_)
102 {
103   m_items.insert(m_items.begin()+pos_,std::move(new_item));
104   MenuItem& item = *m_items[pos_];
105 
106   // When the item is inserted before the selected item, the
107   // same menu item should be still selected.
108 
109   if (m_active_item >= pos_)
110   {
111     m_active_item++;
112   }
113 
114   calculate_width();
115 
116   return item;
117 }
118 
119 void
delete_item(int pos_)120 Menu::delete_item(int pos_)
121 {
122   m_items.erase(m_items.begin()+pos_);
123 
124   // When the item is deleted before the selected item, the
125   // same menu item should be still selected.
126 
127   if (m_active_item >= pos_)
128   {
129     do {
130       if (m_active_item > 0)
131         --m_active_item;
132       else
133         m_active_item = int(m_items.size())-1;
134     } while (m_items[m_active_item]->skippable());
135   }
136 }
137 
138 ItemHorizontalLine&
add_hl()139 Menu::add_hl()
140 {
141   auto item = std::make_unique<ItemHorizontalLine>();
142   auto item_ptr = item.get();
143   add_item(std::move(item));
144   return *item_ptr;
145 }
146 
147 ItemLabel&
add_label(const std::string & text)148 Menu::add_label(const std::string& text)
149 {
150   auto item = std::make_unique<ItemLabel>(text);
151   auto item_ptr = item.get();
152   add_item(std::move(item));
153   return *item_ptr;
154 }
155 
156 ItemControlField&
add_controlfield(int id,const std::string & text,const std::string & mapping)157 Menu::add_controlfield(int id, const std::string& text,
158                        const std::string& mapping)
159 {
160   auto item = std::make_unique<ItemControlField>(text, mapping, id);
161   auto item_ptr = item.get();
162   add_item(std::move(item));
163   return *item_ptr;
164 }
165 
166 ItemTextField&
add_textfield(const std::string & text,std::string * input,int id)167 Menu::add_textfield(const std::string& text, std::string* input, int id)
168 {
169   auto item = std::make_unique<ItemTextField>(text, input, id);
170   auto item_ptr = item.get();
171   add_item(std::move(item));
172   return *item_ptr;
173 }
174 
175 ItemScript&
add_script(const std::string & text,std::string * script,int id)176 Menu::add_script(const std::string& text, std::string* script, int id)
177 {
178   auto item = std::make_unique<ItemScript>(text, script, id);
179   auto item_ptr = item.get();
180   add_item(std::move(item));
181   return *item_ptr;
182 }
183 
184 ItemScriptLine&
add_script_line(std::string * input,int id)185 Menu::add_script_line(std::string* input, int id)
186 {
187   auto item = std::make_unique<ItemScriptLine>(input, id);
188   auto item_ptr = item.get();
189   add_item(std::move(item));
190   return *item_ptr;
191 }
192 
193 ItemIntField&
add_intfield(const std::string & text,int * input,int id)194 Menu::add_intfield(const std::string& text, int* input, int id)
195 {
196   auto item = std::make_unique<ItemIntField>(text, input, id);
197   auto item_ptr = item.get();
198   add_item(std::move(item));
199   return *item_ptr;
200 }
201 
202 ItemFloatField&
add_floatfield(const std::string & text,float * input,int id)203 Menu::add_floatfield(const std::string& text, float* input, int id)
204 {
205   auto item = std::make_unique<ItemFloatField>(text, input, id);
206   auto item_ptr = item.get();
207   add_item(std::move(item));
208   return *item_ptr;
209 }
210 
211 ItemAction&
add_entry(int id,const std::string & text)212 Menu::add_entry(int id, const std::string& text)
213 {
214   auto item = std::make_unique<ItemAction>(text, id);
215   auto item_ptr = item.get();
216   add_item(std::move(item));
217   return *item_ptr;
218 }
219 
220 ItemAction&
add_entry(const std::string & text,const std::function<void ()> & callback)221 Menu::add_entry(const std::string& text, const std::function<void()>& callback)
222 {
223   auto item = std::make_unique<ItemAction>(text, -1, callback);
224   auto item_ptr = item.get();
225   add_item(std::move(item));
226   return *item_ptr;
227 }
228 
229 ItemInactive&
add_inactive(const std::string & text)230 Menu::add_inactive(const std::string& text)
231 {
232   auto item = std::make_unique<ItemInactive>(text);
233   auto item_ptr = item.get();
234   add_item(std::move(item));
235   return *item_ptr;
236 }
237 
238 ItemToggle&
add_toggle(int id,const std::string & text,bool * toggled)239 Menu::add_toggle(int id, const std::string& text, bool* toggled)
240 {
241   auto item = std::make_unique<ItemToggle>(text, toggled, id);
242   auto item_ptr = item.get();
243   add_item(std::move(item));
244   return *item_ptr;
245 }
246 
247 ItemToggle&
add_toggle(int id,const std::string & text,const std::function<bool ()> & get_func,const std::function<void (bool)> & set_func)248 Menu::add_toggle(int id, const std::string& text,
249                  const std::function<bool()>& get_func,
250                  const std::function<void(bool)>& set_func)
251 {
252   auto item = std::make_unique<ItemToggle>(text, get_func, set_func, id);
253   auto item_ptr = item.get();
254   add_item(std::move(item));
255   return *item_ptr;
256 }
257 
258 ItemStringSelect&
add_string_select(int id,const std::string & text,int * selected,const std::vector<std::string> & strings)259 Menu::add_string_select(int id, const std::string& text, int* selected, const std::vector<std::string>& strings)
260 {
261   auto item = std::make_unique<ItemStringSelect>(text, strings, selected, id);
262   auto item_ptr = item.get();
263   add_item(std::move(item));
264   return *item_ptr;
265 }
266 
267 ItemFile&
add_file(const std::string & text,std::string * input,const std::vector<std::string> & extensions,const std::string & basedir,int id)268 Menu::add_file(const std::string& text, std::string* input, const std::vector<std::string>& extensions,
269                const std::string& basedir, int id)
270 {
271   auto item = std::make_unique<ItemFile>(text, input, extensions, basedir, id);
272   auto item_ptr = item.get();
273   add_item(std::move(item));
274   return *item_ptr;
275 }
276 
277 ItemBack&
add_back(const std::string & text,int id)278 Menu::add_back(const std::string& text, int id)
279 {
280   auto item = std::make_unique<ItemBack>(text, id);
281   auto item_ptr = item.get();
282   add_item(std::move(item));
283   return *item_ptr;
284 }
285 
286 ItemGoTo&
add_submenu(const std::string & text,int submenu,int id)287 Menu::add_submenu(const std::string& text, int submenu, int id)
288 {
289   auto item = std::make_unique<ItemGoTo>(text, submenu, id);
290   auto item_ptr = item.get();
291   add_item(std::move(item));
292   return *item_ptr;
293 }
294 
295 ItemColorChannelRGBA&
add_color_channel_rgba(float * input,Color channel,int id,bool is_linear)296 Menu::add_color_channel_rgba(float* input, Color channel, int id, bool is_linear) {
297   auto item = std::make_unique<ItemColorChannelRGBA>(input, channel, id, is_linear);
298   auto item_ptr = item.get();
299   add_item(std::move(item));
300   return *item_ptr;
301 }
302 
303 ItemColorChannelOKLab&
add_color_channel_oklab(Color * color,int channel)304 Menu::add_color_channel_oklab(Color* color, int channel) {
305   auto item = std::make_unique<ItemColorChannelOKLab>(color, channel, this);
306   auto item_ptr = item.get();
307   add_item(std::move(item));
308   return *item_ptr;
309 }
310 
311 ItemPaths&
add_path_settings(const std::string & text,PathObject & target,const std::string & path_ref)312 Menu::add_path_settings(const std::string& text, PathObject& target, const std::string& path_ref) {
313   auto item = std::make_unique<ItemPaths>(text, target, path_ref);
314   auto item_ptr = item.get();
315   add_item(std::move(item));
316   return *item_ptr;
317 }
318 
319 ItemColorDisplay&
add_color_display(Color * color,int id)320 Menu::add_color_display(Color* color, int id) {
321   auto item = std::make_unique<ItemColorDisplay>(color, id);
322   auto item_ptr = item.get();
323   add_item(std::move(item));
324   return *item_ptr;
325 }
326 
327 ItemColor&
add_color(const std::string & text,Color * color,int id)328 Menu::add_color(const std::string& text, Color* color, int id) {
329   auto item = std::make_unique<ItemColor>(text, color, id);
330   auto item_ptr = item.get();
331   add_item(std::move(item));
332   return *item_ptr;
333 }
334 
335 ItemBadguySelect&
add_badguy_select(const std::string & text,std::vector<std::string> * badguys,int id)336 Menu::add_badguy_select(const std::string& text, std::vector<std::string>* badguys, int id) {
337   auto item = std::make_unique<ItemBadguySelect>(text, badguys, id);
338   auto item_ptr = item.get();
339   add_item(std::move(item));
340   return *item_ptr;
341 }
342 
343 void
clear()344 Menu::clear()
345 {
346   m_items.clear();
347   m_active_item = -1;
348 }
349 
350 void
process_input(const Controller & controller)351 Menu::process_input(const Controller& controller)
352 {
353   { // Scrolling
354 
355     // If a help text is present, make some space at the bottom of the
356     // menu so that the last few items don't overlap with the help
357     // text.
358     float help_height = 0.0f;
359     for (auto& item : m_items) {
360       if (!item->get_help().empty()) {
361         help_height = 96.0f;
362         break;
363       }
364     }
365 
366     // Find the first and last selectable item in the current menu, so
367     // that the top most selected item gives a scroll_pos of -1.0f and
368     // the bottom most gives 1.0f, as otherwise the non-selectable
369     // header would be cut off.
370     size_t first_idx = m_items.size();
371     size_t last_idx = m_items.size();
372     for (size_t i = 0; i < m_items.size(); ++i) {
373       if (!m_items[i]->skippable()) {
374         if (first_idx == m_items.size()) {
375           first_idx = i;
376         }
377         last_idx = i;
378       }
379     }
380 
381     const float screen_height = static_cast<float>(SCREEN_HEIGHT);
382     const float menu_area = screen_height - help_height;
383     // get_height() doesn't include the border, so we manually add some
384     const float menu_height = get_height() + 32.0f;
385     const float center_y = menu_area / 2.0f;
386     if (menu_height > menu_area)
387     {
388       const float scroll_range = (menu_height - menu_area) / 2.0f;
389       const float scroll_pos = ((static_cast<float>(m_active_item - first_idx)
390                                  / static_cast<float>(last_idx - first_idx)) - 0.5f) * 2.0f;
391 
392       m_pos.y = floorf(center_y - scroll_range * scroll_pos);
393     }
394     else
395     {
396       if (help_height != 0.0f) {
397         m_pos.y = floorf(center_y);
398       }
399     }
400   }
401 
402   MenuAction menuaction = MenuAction::NONE;
403 
404   /** check main input controller... */
405   if (controller.pressed(Control::UP)) {
406     menuaction = MenuAction::UP;
407     m_menu_repeat_time = g_real_time + MENU_REPEAT_INITIAL;
408   }
409   if (controller.hold(Control::UP) &&
410      m_menu_repeat_time != 0 && g_real_time > m_menu_repeat_time) {
411     menuaction = MenuAction::UP;
412     m_menu_repeat_time = g_real_time + MENU_REPEAT_RATE;
413   }
414 
415   if (controller.pressed(Control::DOWN)) {
416     menuaction = MenuAction::DOWN;
417     m_menu_repeat_time = g_real_time + MENU_REPEAT_INITIAL;
418   }
419   if (controller.hold(Control::DOWN) &&
420      m_menu_repeat_time != 0 && g_real_time > m_menu_repeat_time) {
421     menuaction = MenuAction::DOWN;
422     m_menu_repeat_time = g_real_time + MENU_REPEAT_RATE;
423   }
424 
425   if (controller.pressed(Control::LEFT)) {
426     menuaction = MenuAction::LEFT;
427     m_menu_repeat_time = g_real_time + MENU_REPEAT_INITIAL;
428   }
429   if (controller.hold(Control::LEFT) &&
430      m_menu_repeat_time != 0 && g_real_time > m_menu_repeat_time) {
431     menuaction = MenuAction::LEFT;
432     m_menu_repeat_time = g_real_time + MENU_REPEAT_RATE;
433   }
434 
435   if (controller.pressed(Control::RIGHT)) {
436     menuaction = MenuAction::RIGHT;
437     m_menu_repeat_time = g_real_time + MENU_REPEAT_INITIAL;
438   }
439   if (controller.hold(Control::RIGHT) &&
440      m_menu_repeat_time != 0 && g_real_time > m_menu_repeat_time) {
441     menuaction = MenuAction::RIGHT;
442     m_menu_repeat_time = g_real_time + MENU_REPEAT_RATE;
443   }
444 
445   if (controller.pressed(Control::ACTION) ||
446      controller.pressed(Control::JUMP) ||
447      controller.pressed(Control::MENU_SELECT) ||
448      (!is_sensitive() && controller.pressed(Control::MENU_SELECT_SPACE))) {
449     menuaction = MenuAction::HIT;
450   }
451 
452   if (controller.pressed(Control::ESCAPE) ||
453      controller.pressed(Control::CHEAT_MENU) ||
454      controller.pressed(Control::DEBUG_MENU) ||
455      controller.pressed(Control::MENU_BACK)) {
456     menuaction = MenuAction::BACK;
457   }
458 
459   if (controller.pressed(Control::REMOVE)) {
460     menuaction = MenuAction::REMOVE;
461     m_menu_repeat_time = g_real_time + MENU_REPEAT_INITIAL;
462   }
463   if (controller.hold(Control::REMOVE) &&
464      m_menu_repeat_time != 0 && g_real_time > m_menu_repeat_time) {
465     menuaction = MenuAction::REMOVE;
466     m_menu_repeat_time = g_real_time + MENU_REPEAT_RATE;
467   }
468 
469   if (m_items.size() == 0)
470     return;
471 
472   // The menu_action() call can pop() the menu from the stack and thus
473   // delete it, so it's important that no further member variables are
474   // accessed after this call
475   process_action(menuaction);
476 }
477 
478 void
process_action(const MenuAction & menuaction)479 Menu::process_action(const MenuAction& menuaction)
480 {
481   const int last_active_item = m_active_item;
482 
483   switch (menuaction) {
484     case MenuAction::UP:
485       do {
486         if (m_active_item > 0)
487           --m_active_item;
488         else
489           m_active_item = int(m_items.size())-1;
490       } while (m_items[m_active_item]->skippable()
491                && (m_active_item != last_active_item));
492       break;
493 
494     case MenuAction::DOWN:
495       do {
496         if (m_active_item < int(m_items.size())-1 )
497           ++m_active_item;
498         else
499           m_active_item = 0;
500       } while (m_items[m_active_item]->skippable()
501                && (m_active_item != last_active_item));
502       break;
503 
504     case MenuAction::BACK:
505       if (on_back_action()) {
506         MenuManager::instance().pop_menu();
507       }
508       return;
509 
510     default:
511       break;
512   }
513 
514   if (last_active_item != m_active_item) {
515     // Selection caused by Up or Down keyboard action
516     if (last_active_item != -1)
517       m_items[last_active_item]->process_action(MenuAction::UNSELECT);
518     m_items[m_active_item]->process_action(MenuAction::SELECT);
519   }
520 
521   bool last_action = m_items[m_active_item]->no_other_action();
522   m_items[m_active_item]->process_action(menuaction);
523   if (last_action)
524     return;
525 
526   if (m_items[m_active_item]->changes_width())
527     calculate_width();
528   if (menuaction == MenuAction::HIT)
529     menu_action(*m_items[m_active_item]);
530 }
531 
532 void
draw_item(DrawingContext & context,int index)533 Menu::draw_item(DrawingContext& context, int index)
534 {
535   const float menu_height = get_height();
536   const float menu_width = get_width();
537 
538   MenuItem* pitem = m_items[index].get();
539 
540   const float x_pos = m_pos.x - menu_width / 2.0f;
541   const float y_pos = m_pos.y + 24.0f * static_cast<float>(index) - menu_height / 2.0f + 12.0f;
542 
543   pitem->draw(context, Vector(x_pos, y_pos), static_cast<int>(menu_width), m_active_item == index);
544 
545   if (m_active_item == index)
546   {
547     float blink = (sinf(g_real_time * math::PI * 1.0f)/2.0f + 0.5f) * 0.5f + 0.25f;
548     context.color().draw_filled_rect(Rectf(Vector(m_pos.x - menu_width/2 + 10 - 2, y_pos - 12 - 2),
549                                            Vector(m_pos.x + menu_width/2 - 10 + 2, y_pos + 12 + 2)),
550                                      Color(1.0f, 1.0f, 1.0f, blink),
551                                      14.0f,
552                                      LAYER_GUI-10);
553     context.color().draw_filled_rect(Rectf(Vector(m_pos.x - menu_width/2 + 10, y_pos - 12),
554                                            Vector(m_pos.x + menu_width/2 - 10, y_pos + 12)),
555                                      Color(1.0f, 1.0f, 1.0f, 0.5f),
556                                      12.0f,
557                                      LAYER_GUI-10);
558   }
559 }
560 
561 void
calculate_width()562 Menu::calculate_width()
563 {
564   /* The width of the menu has to be more than the width of the text
565      with the most characters */
566   float max_width = 0;
567   for (unsigned int i = 0; i < m_items.size(); ++i)
568   {
569     float w = static_cast<float>(m_items[i]->get_width());
570     if (w > max_width)
571       max_width = w;
572   }
573   m_menu_width = max_width;
574 }
575 
576 float
get_width() const577 Menu::get_width() const
578 {
579   return m_menu_width + 24;
580 }
581 
582 float
get_height() const583 Menu::get_height() const
584 {
585   return static_cast<float>(m_items.size() * 24);
586 }
587 
588 void
on_window_resize()589 Menu::on_window_resize()
590 {
591   m_pos.x = static_cast<float>(SCREEN_WIDTH) / 2.0f;
592   m_pos.y = static_cast<float>(SCREEN_HEIGHT) / 2.0f;
593 }
594 
595 void
draw(DrawingContext & context)596 Menu::draw(DrawingContext& context)
597 {
598   for (unsigned int i = 0; i < m_items.size(); ++i)
599   {
600     draw_item(context, i);
601   }
602 
603   if (!m_items[m_active_item]->get_help().empty())
604   {
605     const int text_width = static_cast<int>(Resources::normal_font->get_text_width(m_items[m_active_item]->get_help()));
606     const int text_height = static_cast<int>(Resources::normal_font->get_text_height(m_items[m_active_item]->get_help()));
607 
608     const Rectf text_rect(m_pos.x - static_cast<float>(text_width) / 2.0f - 8.0f,
609                           static_cast<float>(SCREEN_HEIGHT) - 48.0f - static_cast<float>(text_height) / 2.0f - 4.0f,
610                           m_pos.x + static_cast<float>(text_width) / 2.0f + 8.0f,
611                           static_cast<float>(SCREEN_HEIGHT) - 48.0f + static_cast<float>(text_height) / 2.0f + 4.0f);
612 
613     context.color().draw_filled_rect(Rectf(text_rect.p1() - Vector(4,4),
614                                            text_rect.p2() + Vector(4,4)),
615                                      Color(0.5f, 0.6f, 0.7f, 0.8f),
616                                      16.0f,
617                                      LAYER_GUI);
618 
619     context.color().draw_filled_rect(text_rect,
620                                      Color(0.8f, 0.9f, 1.0f, 0.5f),
621                                      16.0f,
622                                      LAYER_GUI);
623 
624     context.color().draw_text(Resources::normal_font, m_items[m_active_item]->get_help(),
625                               Vector(m_pos.x, static_cast<float>(SCREEN_HEIGHT) - 48.0f - static_cast<float>(text_height) / 2.0f),
626                               ALIGN_CENTER, LAYER_GUI);
627   }
628 }
629 
630 MenuItem&
get_item_by_id(int id)631 Menu::get_item_by_id(int id)
632 {
633   auto item = std::find_if(m_items.begin(), m_items.end(), [id](const std::unique_ptr<MenuItem>& i)
634   {
635     return i->get_id() == id;
636   });
637 
638   if(item != m_items.end())
639     return *item->get();
640 
641   throw std::runtime_error("MenuItem not found: " + std::to_string(id));
642 }
643 
644 const MenuItem&
get_item_by_id(int id) const645 Menu::get_item_by_id(int id) const
646 {
647   auto item = std::find_if(m_items.begin(), m_items.end(), [id](const std::unique_ptr<MenuItem>& i)
648   {
649     return i->get_id() == id;
650   });
651 
652   if(item != m_items.end())
653     return *item->get();
654 
655   throw std::runtime_error("MenuItem not found: " + std::to_string(id));
656 }
657 
get_active_item_id() const658 int Menu::get_active_item_id() const
659 {
660   return m_items[m_active_item]->get_id();
661 }
662 
663 void
event(const SDL_Event & ev)664 Menu::event(const SDL_Event& ev)
665 {
666   m_items[m_active_item]->event(ev);
667   switch (ev.type)
668   {
669     case SDL_KEYDOWN:
670     case SDL_TEXTINPUT:
671       if (((ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_BACKSPACE) ||
672          ev.type == SDL_TEXTINPUT) && m_items[m_active_item]->changes_width())
673       {
674         // Changed item value? Let's recalculate width:
675         calculate_width();
676       }
677     break;
678 
679     case SDL_MOUSEBUTTONDOWN:
680     if (ev.button.button == SDL_BUTTON_LEFT)
681     {
682       Vector mouse_pos = VideoSystem::current()->get_viewport().to_logical(ev.motion.x, ev.motion.y);
683 
684       if (mouse_pos.x > m_pos.x - get_width() / 2.0f &&
685           mouse_pos.x < m_pos.x + get_width() / 2.0f &&
686           mouse_pos.y > m_pos.y - get_height() / 2.0f &&
687           mouse_pos.y < m_pos.y + get_height() / 2.0f)
688       {
689         process_action(MenuAction::HIT);
690       }
691     }
692     break;
693 
694     case SDL_MOUSEMOTION:
695     {
696       Vector mouse_pos = VideoSystem::current()->get_viewport().to_logical(ev.motion.x, ev.motion.y);
697       float x = mouse_pos.x;
698       float y = mouse_pos.y;
699 
700       if (x > m_pos.x - get_width()/2 &&
701          x < m_pos.x + get_width()/2 &&
702          y > m_pos.y - get_height()/2 &&
703          y < m_pos.y + get_height()/2)
704       {
705         int new_active_item
706           = static_cast<int> ((y - (m_pos.y - get_height()/2)) / 24);
707 
708         /* only change the mouse focus to a selectable item */
709         if (!m_items[new_active_item]->skippable() &&
710             new_active_item != m_active_item) {
711           // Selection caused by mouse movement
712           if (m_active_item != -1)
713             process_action(MenuAction::UNSELECT);
714           m_active_item = new_active_item;
715           process_action(MenuAction::SELECT);
716         }
717 
718         if (MouseCursor::current())
719           MouseCursor::current()->set_state(MouseCursorState::LINK);
720       }
721       else
722       {
723         if (MouseCursor::current())
724           MouseCursor::current()->set_state(MouseCursorState::NORMAL);
725       }
726     }
727     break;
728 
729     default:
730       break;
731   }
732 }
733 
734 void
set_active_item(int id)735 Menu::set_active_item(int id)
736 {
737   for (size_t i = 0; i < m_items.size(); ++i) {
738     if (m_items[i]->get_id() == id) {
739       m_active_item = static_cast<int>(i);
740       break;
741     }
742   }
743 }
744 
745 bool
is_sensitive() const746 Menu::is_sensitive() const
747 {
748   return false;
749 }
750 
751 /* EOF */
752