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