1 /*************************************************************************/
2 /* popup_menu.cpp */
3 /*************************************************************************/
4 /* This file is part of: */
5 /* GODOT ENGINE */
6 /* https://godotengine.org */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
9 /* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
10 /* */
11 /* Permission is hereby granted, free of charge, to any person obtaining */
12 /* a copy of this software and associated documentation files (the */
13 /* "Software"), to deal in the Software without restriction, including */
14 /* without limitation the rights to use, copy, modify, merge, publish, */
15 /* distribute, sublicense, and/or sell copies of the Software, and to */
16 /* permit persons to whom the Software is furnished to do so, subject to */
17 /* the following conditions: */
18 /* */
19 /* The above copyright notice and this permission notice shall be */
20 /* included in all copies or substantial portions of the Software. */
21 /* */
22 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24 /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
25 /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26 /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29 /*************************************************************************/
30 #include "popup_menu.h"
31 #include "os/input.h"
32 #include "os/keyboard.h"
33 #include "print_string.h"
34 #include "translation.h"
35
_get_accel_text(int p_item) const36 String PopupMenu::_get_accel_text(int p_item) const {
37
38 ERR_FAIL_INDEX_V(p_item, items.size(), String());
39
40 if (items[p_item].shortcut.is_valid())
41 return items[p_item].shortcut->get_as_text();
42 else if (items[p_item].accel)
43 return keycode_get_string(items[p_item].accel);
44 return String();
45
46 /*
47 String atxt;
48 if (p_accel&KEY_MASK_SHIFT)
49 atxt+="Shift+";
50 if (p_accel&KEY_MASK_ALT)
51 atxt+="Alt+";
52 if (p_accel&KEY_MASK_CTRL)
53 atxt+="Ctrl+";
54 if (p_accel&KEY_MASK_META)
55 atxt+="Meta+";
56
57 p_accel&=KEY_CODE_MASK;
58
59 atxt+=String::chr(p_accel).to_upper();
60
61 return atxt;
62 */
63 }
64
get_minimum_size() const65 Size2 PopupMenu::get_minimum_size() const {
66
67 int vseparation = get_constant("vseparation");
68 int hseparation = get_constant("hseparation");
69
70 Size2 minsize = get_stylebox("panel")->get_minimum_size();
71 Ref<Font> font = get_font("font");
72
73 float max_w = 0;
74 int font_h = font->get_height();
75 int check_w = get_icon("checked")->get_width();
76 int accel_max_w = 0;
77
78 for (int i = 0; i < items.size(); i++) {
79
80 Size2 size;
81 if (!items[i].icon.is_null()) {
82
83 Size2 icon_size = items[i].icon->get_size();
84 size.height = MAX(icon_size.height, font_h);
85 size.width += icon_size.width;
86 size.width += hseparation;
87 } else {
88
89 size.height = font_h;
90 }
91
92 if (items[i].checkable) {
93
94 size.width += check_w + hseparation;
95 }
96
97 String text = items[i].shortcut.is_valid() ? String(tr(items[i].shortcut->get_name())) : items[i].text;
98 size.width += font->get_string_size(text).width;
99 if (i > 0)
100 size.height += vseparation;
101
102 if (items[i].accel || (items[i].shortcut.is_valid() && items[i].shortcut->is_valid())) {
103
104 int accel_w = hseparation * 2;
105 accel_w += font->get_string_size(_get_accel_text(i)).width;
106 accel_max_w = MAX(accel_w, accel_max_w);
107 }
108
109 if (items[i].submenu != "") {
110
111 size.width += get_icon("submenu")->get_width();
112 }
113
114 minsize.height += size.height;
115 max_w = MAX(max_w, size.width);
116 }
117
118 minsize.width += max_w + accel_max_w;
119
120 return minsize;
121 }
122
_get_mouse_over(const Point2 & p_over) const123 int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
124
125 if (p_over.x < 0 || p_over.x >= get_size().width)
126 return -1;
127
128 Ref<StyleBox> style = get_stylebox("panel");
129
130 Point2 ofs = style->get_offset();
131
132 if (ofs.y > p_over.y)
133 return -1;
134
135 Ref<Font> font = get_font("font");
136 int vseparation = get_constant("vseparation");
137 // int hseparation = get_constant("hseparation");
138 float font_h = font->get_height();
139
140 for (int i = 0; i < items.size(); i++) {
141
142 if (i > 0)
143 ofs.y += vseparation;
144 float h;
145
146 if (!items[i].icon.is_null()) {
147
148 Size2 icon_size = items[i].icon->get_size();
149 h = MAX(icon_size.height, font_h);
150 } else {
151
152 h = font_h;
153 }
154
155 ofs.y += h;
156 if (p_over.y < ofs.y) {
157 return i;
158 }
159 }
160
161 return -1;
162 }
163
_activate_submenu(int over)164 void PopupMenu::_activate_submenu(int over) {
165
166 Node *n = get_node(items[over].submenu);
167 ERR_EXPLAIN("item subnode does not exist: " + items[over].submenu);
168 ERR_FAIL_COND(!n);
169 Popup *pm = n->cast_to<Popup>();
170 ERR_EXPLAIN("item subnode is not a Popup: " + items[over].submenu);
171 ERR_FAIL_COND(!pm);
172 if (pm->is_visible())
173 return; //already visible!
174
175 Point2 p = get_global_pos();
176 Rect2 pr(p, get_size());
177 Ref<StyleBox> style = get_stylebox("panel");
178
179 Point2 pos = p + Point2(get_size().width, items[over]._ofs_cache - style->get_offset().y);
180 Size2 size = pm->get_size();
181 // fix pos
182 if (pos.x + size.width > get_viewport_rect().size.width)
183 pos.x = p.x - size.width;
184
185 pm->set_pos(pos);
186 pm->popup();
187
188 PopupMenu *pum = pm->cast_to<PopupMenu>();
189 if (pum) {
190
191 pr.pos -= pum->get_global_pos();
192 pum->clear_autohide_areas();
193 pum->add_autohide_area(Rect2(pr.pos.x, pr.pos.y, pr.size.x, items[over]._ofs_cache));
194 if (over < items.size() - 1) {
195 int from = items[over + 1]._ofs_cache;
196 pum->add_autohide_area(Rect2(pr.pos.x, pr.pos.y + from, pr.size.x, pr.size.y - from));
197 }
198 }
199 }
200
_submenu_timeout()201 void PopupMenu::_submenu_timeout() {
202
203 if (mouse_over == submenu_over) {
204 _activate_submenu(mouse_over);
205 submenu_over = -1;
206 }
207 }
208
_input_event(const InputEvent & p_event)209 void PopupMenu::_input_event(const InputEvent &p_event) {
210
211 switch (p_event.type) {
212
213 case InputEvent::KEY: {
214
215 if (!p_event.key.pressed)
216 break;
217
218 switch (p_event.key.scancode) {
219
220 case KEY_DOWN: {
221
222 for (int i = mouse_over + 1; i < items.size(); i++) {
223
224 if (i < 0 || i >= items.size())
225 continue;
226
227 if (!items[i].separator && !items[i].disabled) {
228
229 mouse_over = i;
230 update();
231 break;
232 }
233 }
234 } break;
235 case KEY_UP: {
236
237 for (int i = mouse_over - 1; i >= 0; i--) {
238
239 if (i < 0 || i >= items.size())
240 continue;
241
242 if (!items[i].separator && !items[i].disabled) {
243
244 mouse_over = i;
245 update();
246 break;
247 }
248 }
249 } break;
250 case KEY_RETURN:
251 case KEY_ENTER: {
252
253 if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator) {
254
255 activate_item(mouse_over);
256 }
257 } break;
258 }
259
260 } break;
261
262 case InputEvent::MOUSE_BUTTON: {
263
264 const InputEventMouseButton &b = p_event.mouse_button;
265 if (b.pressed)
266 break;
267
268 switch (b.button_index) {
269
270 case BUTTON_WHEEL_DOWN: {
271
272 if (get_global_pos().y + get_size().y > get_viewport_rect().size.y) {
273
274 int vseparation = get_constant("vseparation");
275 Ref<Font> font = get_font("font");
276
277 Point2 pos = get_pos();
278 int s = (vseparation + font->get_height()) * 3;
279 pos.y -= (s * b.factor);
280 set_pos(pos);
281
282 //update hover
283 InputEvent ie;
284 ie.type = InputEvent::MOUSE_MOTION;
285 ie.mouse_motion.x = b.x;
286 ie.mouse_motion.y = b.y + s;
287 _input_event(ie);
288 }
289 } break;
290 case BUTTON_WHEEL_UP: {
291
292 if (get_global_pos().y < 0) {
293
294 int vseparation = get_constant("vseparation");
295 Ref<Font> font = get_font("font");
296
297 Point2 pos = get_pos();
298 int s = (vseparation + font->get_height()) * 3;
299 pos.y += (s * b.factor);
300 set_pos(pos);
301
302 //update hover
303 InputEvent ie;
304 ie.type = InputEvent::MOUSE_MOTION;
305 ie.mouse_motion.x = b.x;
306 ie.mouse_motion.y = b.y - s;
307 _input_event(ie);
308 }
309 } break;
310 case BUTTON_LEFT: {
311
312 int over = _get_mouse_over(Point2(b.x, b.y));
313
314 if (invalidated_click) {
315 invalidated_click = false;
316 break;
317 }
318 if (over < 0) {
319 hide();
320 break; //non-activable
321 }
322
323 if (items[over].separator || items[over].disabled)
324 break;
325
326 if (items[over].submenu != "") {
327
328 _activate_submenu(over);
329 return;
330 }
331 activate_item(over);
332
333 } break;
334 }
335
336 //update();
337 } break;
338 case InputEvent::MOUSE_MOTION: {
339
340 if (invalidated_click) {
341 moved += Vector2(p_event.mouse_motion.relative_x, p_event.mouse_motion.relative_y);
342 if (moved.length() > 4)
343 invalidated_click = false;
344 }
345
346 const InputEventMouseMotion &m = p_event.mouse_motion;
347 for (List<Rect2>::Element *E = autohide_areas.front(); E; E = E->next()) {
348
349 if (!Rect2(Point2(), get_size()).has_point(Point2(m.x, m.y)) && E->get().has_point(Point2(m.x, m.y))) {
350 call_deferred("hide");
351 return;
352 }
353 }
354
355 int over = _get_mouse_over(Point2(m.x, m.y));
356 int id = (over < 0 || items[over].separator || items[over].disabled) ? -1 : (items[over].ID >= 0 ? items[over].ID : over);
357
358 if (id < 0) {
359 mouse_over = -1;
360 update();
361 break;
362 }
363
364 if (items[over].submenu != "" && submenu_over != over) {
365 submenu_over = over;
366 submenu_timer->start();
367 }
368
369 if (over != mouse_over) {
370 mouse_over = over;
371 update();
372 }
373 } break;
374 }
375 }
376
has_point(const Point2 & p_point) const377 bool PopupMenu::has_point(const Point2 &p_point) const {
378
379 if (parent_rect.has_point(p_point))
380 return true;
381 for (const List<Rect2>::Element *E = autohide_areas.front(); E; E = E->next()) {
382
383 if (E->get().has_point(p_point))
384 return true;
385 }
386
387 return Control::has_point(p_point);
388 }
389
_notification(int p_what)390 void PopupMenu::_notification(int p_what) {
391
392 switch (p_what) {
393
394 case NOTIFICATION_DRAW: {
395
396 RID ci = get_canvas_item();
397 Size2 size = get_size();
398
399 Ref<StyleBox> style = get_stylebox("panel");
400 Ref<StyleBox> hover = get_stylebox("hover");
401 Ref<Font> font = get_font("font");
402 Ref<Texture> check = get_icon("checked");
403 Ref<Texture> uncheck = get_icon("unchecked");
404 Ref<Texture> submenu = get_icon("submenu");
405 Ref<StyleBox> separator = get_stylebox("separator");
406
407 style->draw(ci, Rect2(Point2(), get_size()));
408 Point2 ofs = style->get_offset();
409 int vseparation = get_constant("vseparation");
410 int hseparation = get_constant("hseparation");
411 Color font_color = get_color("font_color");
412 Color font_color_disabled = get_color("font_color_disabled");
413 Color font_color_accel = get_color("font_color_accel");
414 Color font_color_hover = get_color("font_color_hover");
415 float font_h = font->get_height();
416
417 for (int i = 0; i < items.size(); i++) {
418
419 if (i > 0)
420 ofs.y += vseparation;
421 Point2 item_ofs = ofs;
422 float h;
423 Size2 icon_size;
424
425 if (!items[i].icon.is_null()) {
426
427 icon_size = items[i].icon->get_size();
428 h = MAX(icon_size.height, font_h);
429 } else {
430
431 h = font_h;
432 }
433
434 if (i == mouse_over) {
435
436 hover->draw(ci, Rect2(ofs + Point2(-hseparation, -vseparation), Size2(get_size().width - style->get_minimum_size().width + hseparation * 2, h + vseparation * 2)));
437 }
438
439 if (items[i].separator) {
440
441 int sep_h = separator->get_center_size().height + separator->get_minimum_size().height;
442 separator->draw(ci, Rect2(ofs + Point2(0, Math::floor((h - sep_h) / 2.0)), Size2(get_size().width - style->get_minimum_size().width, sep_h)));
443 }
444
445 if (items[i].checkable) {
446
447 if (items[i].checked)
448 check->draw(ci, item_ofs + Point2(0, Math::floor((h - check->get_height()) / 2.0)));
449 else
450 uncheck->draw(ci, item_ofs + Point2(0, Math::floor((h - check->get_height()) / 2.0)));
451
452 item_ofs.x += check->get_width() + hseparation;
453 }
454
455 if (!items[i].icon.is_null()) {
456 items[i].icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon_size.height) / 2.0)));
457 item_ofs.x += items[i].icon->get_width();
458 item_ofs.x += hseparation;
459 }
460
461 if (items[i].submenu != "") {
462 submenu->draw(ci, Point2(size.width - style->get_margin(MARGIN_RIGHT) - submenu->get_width(), item_ofs.y + Math::floor(h - submenu->get_height()) / 2));
463 }
464
465 item_ofs.y += font->get_ascent();
466 String text = items[i].shortcut.is_valid() ? String(tr(items[i].shortcut->get_name())) : items[i].text;
467 if (!items[i].separator) {
468
469 font->draw(ci, item_ofs + Point2(0, Math::floor((h - font_h) / 2.0)), text, items[i].disabled ? font_color_disabled : (i == mouse_over ? font_color_hover : font_color));
470 }
471
472 if (items[i].accel || (items[i].shortcut.is_valid() && items[i].shortcut->is_valid())) {
473 //accelerator
474 String text = _get_accel_text(i);
475 item_ofs.x = size.width - style->get_margin(MARGIN_RIGHT) - font->get_string_size(text).width;
476 font->draw(ci, item_ofs + Point2(0, Math::floor((h - font_h) / 2.0)), text, i == mouse_over ? font_color_hover : font_color_accel);
477 }
478
479 items[i]._ofs_cache = ofs.y;
480
481 ofs.y += h;
482 }
483
484 } break;
485 case NOTIFICATION_MOUSE_ENTER: {
486
487 grab_focus();
488 } break;
489 case NOTIFICATION_MOUSE_EXIT: {
490
491 if (mouse_over >= 0) {
492 mouse_over = -1;
493 update();
494 }
495 } break;
496 }
497 }
498
add_icon_item(const Ref<Texture> & p_icon,const String & p_label,int p_ID,uint32_t p_accel)499 void PopupMenu::add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID, uint32_t p_accel) {
500
501 Item item;
502 item.icon = p_icon;
503 item.text = p_label;
504 item.accel = p_accel;
505 item.ID = p_ID;
506 items.push_back(item);
507 update();
508 }
add_item(const String & p_label,int p_ID,uint32_t p_accel)509 void PopupMenu::add_item(const String &p_label, int p_ID, uint32_t p_accel) {
510
511 Item item;
512 item.text = XL_MESSAGE(p_label);
513 item.accel = p_accel;
514 item.ID = p_ID;
515 items.push_back(item);
516 update();
517 }
518
add_submenu_item(const String & p_label,const String & p_submenu,int p_ID)519 void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, int p_ID) {
520
521 Item item;
522 item.text = XL_MESSAGE(p_label);
523 item.ID = p_ID;
524 item.submenu = p_submenu;
525 items.push_back(item);
526 update();
527 }
528
add_icon_check_item(const Ref<Texture> & p_icon,const String & p_label,int p_ID,uint32_t p_accel)529 void PopupMenu::add_icon_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID, uint32_t p_accel) {
530
531 Item item;
532 item.icon = p_icon;
533 item.text = XL_MESSAGE(p_label);
534 item.accel = p_accel;
535 item.ID = p_ID;
536 item.checkable = true;
537 items.push_back(item);
538 update();
539 }
add_check_item(const String & p_label,int p_ID,uint32_t p_accel)540 void PopupMenu::add_check_item(const String &p_label, int p_ID, uint32_t p_accel) {
541
542 Item item;
543 item.text = XL_MESSAGE(p_label);
544 item.accel = p_accel;
545 item.ID = p_ID;
546 item.checkable = true;
547 items.push_back(item);
548 update();
549 }
550
add_icon_shortcut(const Ref<Texture> & p_icon,const Ref<ShortCut> & p_shortcut,int p_ID)551 void PopupMenu::add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_ID) {
552
553 ERR_FAIL_COND(p_shortcut.is_null());
554
555 _ref_shortcut(p_shortcut);
556
557 Item item;
558 item.ID = p_ID;
559 item.icon = p_icon;
560 item.shortcut = p_shortcut;
561 items.push_back(item);
562 update();
563 }
564
add_shortcut(const Ref<ShortCut> & p_shortcut,int p_ID)565 void PopupMenu::add_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID) {
566
567 ERR_FAIL_COND(p_shortcut.is_null());
568
569 _ref_shortcut(p_shortcut);
570
571 Item item;
572 item.ID = p_ID;
573 item.shortcut = p_shortcut;
574 items.push_back(item);
575 update();
576 }
add_icon_check_shortcut(const Ref<Texture> & p_icon,const Ref<ShortCut> & p_shortcut,int p_ID)577 void PopupMenu::add_icon_check_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_ID) {
578
579 ERR_FAIL_COND(p_shortcut.is_null());
580
581 _ref_shortcut(p_shortcut);
582
583 Item item;
584 item.ID = p_ID;
585 item.shortcut = p_shortcut;
586 item.checkable = true;
587 item.icon = p_icon;
588 items.push_back(item);
589 update();
590 }
591
add_check_shortcut(const Ref<ShortCut> & p_shortcut,int p_ID)592 void PopupMenu::add_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID) {
593
594 ERR_FAIL_COND(p_shortcut.is_null());
595
596 _ref_shortcut(p_shortcut);
597
598 Item item;
599 item.ID = p_ID;
600 item.shortcut = p_shortcut;
601 item.checkable = true;
602 items.push_back(item);
603 update();
604 }
605
set_item_text(int p_idx,const String & p_text)606 void PopupMenu::set_item_text(int p_idx, const String &p_text) {
607
608 ERR_FAIL_INDEX(p_idx, items.size());
609 items[p_idx].text = XL_MESSAGE(p_text);
610
611 update();
612 }
set_item_icon(int p_idx,const Ref<Texture> & p_icon)613 void PopupMenu::set_item_icon(int p_idx, const Ref<Texture> &p_icon) {
614
615 ERR_FAIL_INDEX(p_idx, items.size());
616 items[p_idx].icon = p_icon;
617
618 update();
619 }
set_item_checked(int p_idx,bool p_checked)620 void PopupMenu::set_item_checked(int p_idx, bool p_checked) {
621
622 ERR_FAIL_INDEX(p_idx, items.size());
623
624 items[p_idx].checked = p_checked;
625
626 update();
627 }
set_item_ID(int p_idx,int p_ID)628 void PopupMenu::set_item_ID(int p_idx, int p_ID) {
629
630 ERR_FAIL_INDEX(p_idx, items.size());
631 items[p_idx].ID = p_ID;
632
633 update();
634 }
635
set_item_accelerator(int p_idx,uint32_t p_accel)636 void PopupMenu::set_item_accelerator(int p_idx, uint32_t p_accel) {
637
638 ERR_FAIL_INDEX(p_idx, items.size());
639 items[p_idx].accel = p_accel;
640
641 update();
642 }
643
set_item_metadata(int p_idx,const Variant & p_meta)644 void PopupMenu::set_item_metadata(int p_idx, const Variant &p_meta) {
645
646 ERR_FAIL_INDEX(p_idx, items.size());
647 items[p_idx].metadata = p_meta;
648 update();
649 }
650
set_item_disabled(int p_idx,bool p_disabled)651 void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) {
652
653 ERR_FAIL_INDEX(p_idx, items.size());
654 items[p_idx].disabled = p_disabled;
655 update();
656 }
657
set_item_submenu(int p_idx,const String & p_submenu)658 void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) {
659
660 ERR_FAIL_INDEX(p_idx, items.size());
661 items[p_idx].submenu = p_submenu;
662 update();
663 }
664
toggle_item_checked(int p_idx)665 void PopupMenu::toggle_item_checked(int p_idx) {
666
667 ERR_FAIL_INDEX(p_idx, items.size());
668 items[p_idx].checked = !items[p_idx].checked;
669 update();
670 }
671
get_item_text(int p_idx) const672 String PopupMenu::get_item_text(int p_idx) const {
673
674 ERR_FAIL_INDEX_V(p_idx, items.size(), "");
675 return items[p_idx].text;
676 }
get_item_icon(int p_idx) const677 Ref<Texture> PopupMenu::get_item_icon(int p_idx) const {
678
679 ERR_FAIL_INDEX_V(p_idx, items.size(), Ref<Texture>());
680 return items[p_idx].icon;
681 }
682
get_item_accelerator(int p_idx) const683 uint32_t PopupMenu::get_item_accelerator(int p_idx) const {
684
685 ERR_FAIL_INDEX_V(p_idx, items.size(), 0);
686 return items[p_idx].accel;
687 }
688
get_item_metadata(int p_idx) const689 Variant PopupMenu::get_item_metadata(int p_idx) const {
690
691 ERR_FAIL_INDEX_V(p_idx, items.size(), Variant());
692 return items[p_idx].metadata;
693 }
694
is_item_disabled(int p_idx) const695 bool PopupMenu::is_item_disabled(int p_idx) const {
696
697 ERR_FAIL_INDEX_V(p_idx, items.size(), false);
698 return items[p_idx].disabled;
699 }
700
is_item_checked(int p_idx) const701 bool PopupMenu::is_item_checked(int p_idx) const {
702
703 ERR_FAIL_INDEX_V(p_idx, items.size(), false);
704 return items[p_idx].checked;
705 }
706
get_item_ID(int p_idx) const707 int PopupMenu::get_item_ID(int p_idx) const {
708
709 ERR_FAIL_INDEX_V(p_idx, items.size(), 0);
710 return items[p_idx].ID;
711 }
712
get_item_index(int p_ID) const713 int PopupMenu::get_item_index(int p_ID) const {
714
715 for (int i = 0; i < items.size(); i++) {
716
717 if (items[i].ID == p_ID)
718 return i;
719 }
720
721 return -1;
722 }
723
get_item_submenu(int p_idx) const724 String PopupMenu::get_item_submenu(int p_idx) const {
725
726 ERR_FAIL_INDEX_V(p_idx, items.size(), "");
727 return items[p_idx].submenu;
728 }
729
get_item_tooltip(int p_idx) const730 String PopupMenu::get_item_tooltip(int p_idx) const {
731
732 ERR_FAIL_INDEX_V(p_idx, items.size(), "");
733 return items[p_idx].tooltip;
734 }
735
get_item_shortcut(int p_idx) const736 Ref<ShortCut> PopupMenu::get_item_shortcut(int p_idx) const {
737
738 ERR_FAIL_INDEX_V(p_idx, items.size(), Ref<ShortCut>());
739 return items[p_idx].shortcut;
740 }
741
set_item_as_separator(int p_idx,bool p_separator)742 void PopupMenu::set_item_as_separator(int p_idx, bool p_separator) {
743
744 ERR_FAIL_INDEX(p_idx, items.size());
745 items[p_idx].separator = p_separator;
746 update();
747 }
748
is_item_separator(int p_idx) const749 bool PopupMenu::is_item_separator(int p_idx) const {
750 ERR_FAIL_INDEX_V(p_idx, items.size(), false);
751 return items[p_idx].separator;
752 }
753
set_item_as_checkable(int p_idx,bool p_checkable)754 void PopupMenu::set_item_as_checkable(int p_idx, bool p_checkable) {
755
756 ERR_FAIL_INDEX(p_idx, items.size());
757 items[p_idx].checkable = p_checkable;
758 update();
759 }
760
set_item_tooltip(int p_idx,const String & p_tooltip)761 void PopupMenu::set_item_tooltip(int p_idx, const String &p_tooltip) {
762
763 ERR_FAIL_INDEX(p_idx, items.size());
764 items[p_idx].tooltip = p_tooltip;
765 update();
766 }
767
set_item_shortcut(int p_idx,const Ref<ShortCut> & p_shortcut)768 void PopupMenu::set_item_shortcut(int p_idx, const Ref<ShortCut> &p_shortcut) {
769 ERR_FAIL_INDEX(p_idx, items.size());
770 if (items[p_idx].shortcut.is_valid()) {
771 _unref_shortcut(items[p_idx].shortcut);
772 }
773 items[p_idx].shortcut = p_shortcut;
774
775 if (items[p_idx].shortcut.is_valid()) {
776 _ref_shortcut(items[p_idx].shortcut);
777 }
778
779 update();
780 }
781
is_item_checkable(int p_idx) const782 bool PopupMenu::is_item_checkable(int p_idx) const {
783 ERR_FAIL_INDEX_V(p_idx, items.size(), false);
784 return items[p_idx].checkable;
785 }
786
get_item_count() const787 int PopupMenu::get_item_count() const {
788
789 return items.size();
790 }
791
activate_item_by_event(const InputEvent & p_event)792 bool PopupMenu::activate_item_by_event(const InputEvent &p_event) {
793
794 uint32_t code = 0;
795 if (p_event.type == InputEvent::KEY) {
796 code = p_event.key.scancode;
797 if (code == 0)
798 code = p_event.key.unicode;
799 if (p_event.key.mod.control)
800 code |= KEY_MASK_CTRL;
801 if (p_event.key.mod.alt)
802 code |= KEY_MASK_ALT;
803 if (p_event.key.mod.meta)
804 code |= KEY_MASK_META;
805 if (p_event.key.mod.shift)
806 code |= KEY_MASK_SHIFT;
807 }
808
809 int il = items.size();
810 for (int i = 0; i < il; i++) {
811 if (is_item_disabled(i))
812 continue;
813
814 if (items[i].shortcut.is_valid() && items[i].shortcut->is_shortcut(p_event)) {
815 activate_item(i);
816 return true;
817 }
818
819 if (code != 0 && items[i].accel == code) {
820 activate_item(i);
821 return true;
822 }
823
824 if (items[i].submenu != "") {
825 Node *n = get_node(items[i].submenu);
826 if (!n)
827 continue;
828
829 PopupMenu *pm = n->cast_to<PopupMenu>();
830 if (!pm)
831 continue;
832
833 if (pm->activate_item_by_event(p_event)) {
834 return true;
835 }
836 }
837 }
838 return false;
839 }
840
activate_item(int p_item)841 void PopupMenu::activate_item(int p_item) {
842
843 ERR_FAIL_INDEX(p_item, items.size());
844 ERR_FAIL_COND(items[p_item].separator);
845 int id = items[p_item].ID >= 0 ? items[p_item].ID : p_item;
846 emit_signal("item_pressed", id);
847
848 //hide all parent PopupMenue's
849 Node *next = get_parent();
850 PopupMenu *pop = next->cast_to<PopupMenu>();
851 while (pop) {
852 // We close all parents that are chained together,
853 // with hide_on_item_selection enabled
854 if (hide_on_item_selection && pop->is_hide_on_item_selection()) {
855 pop->hide();
856 next = next->get_parent();
857 pop = next->cast_to<PopupMenu>();
858 } else {
859 // Break out of loop when the next parent has
860 // hide_on_item_selection disabled
861 break;
862 }
863 }
864 // Hides popup by default; unless otherwise specified
865 // by using set_hide_on_item_selection
866 if (hide_on_item_selection) {
867 hide();
868 }
869 }
870
remove_item(int p_idx)871 void PopupMenu::remove_item(int p_idx) {
872
873 ERR_FAIL_INDEX(p_idx, items.size());
874
875 if (items[p_idx].shortcut.is_valid()) {
876 _unref_shortcut(items[p_idx].shortcut);
877 }
878
879 items.remove(p_idx);
880 update();
881 }
882
add_separator()883 void PopupMenu::add_separator() {
884
885 Item sep;
886 sep.separator = true;
887 sep.ID = -1;
888 items.push_back(sep);
889 update();
890 }
891
clear()892 void PopupMenu::clear() {
893
894 for (int i = 0; i < items.size(); i++) {
895 if (items[i].shortcut.is_valid()) {
896 _unref_shortcut(items[i].shortcut);
897 }
898 }
899 items.clear();
900 mouse_over = -1;
901 update();
902 }
903
_get_items() const904 Array PopupMenu::_get_items() const {
905
906 Array items;
907 for (int i = 0; i < get_item_count(); i++) {
908
909 items.push_back(get_item_text(i));
910 items.push_back(get_item_icon(i));
911 items.push_back(is_item_checkable(i));
912 items.push_back(is_item_checked(i));
913 items.push_back(is_item_disabled(i));
914
915 items.push_back(get_item_ID(i));
916 items.push_back(get_item_accelerator(i));
917 items.push_back(get_item_metadata(i));
918 items.push_back(get_item_submenu(i));
919 items.push_back(is_item_separator(i));
920 }
921
922 return items;
923 }
924
_ref_shortcut(Ref<ShortCut> p_sc)925 void PopupMenu::_ref_shortcut(Ref<ShortCut> p_sc) {
926
927 if (!shortcut_refcount.has(p_sc)) {
928 shortcut_refcount[p_sc] = 1;
929 p_sc->connect("changed", this, "update");
930 } else {
931 shortcut_refcount[p_sc] += 1;
932 }
933 }
934
_unref_shortcut(Ref<ShortCut> p_sc)935 void PopupMenu::_unref_shortcut(Ref<ShortCut> p_sc) {
936
937 ERR_FAIL_COND(!shortcut_refcount.has(p_sc));
938 shortcut_refcount[p_sc]--;
939 if (shortcut_refcount[p_sc] == 0) {
940 p_sc->disconnect("changed", this, "update");
941 shortcut_refcount.erase(p_sc);
942 }
943 }
944
_set_items(const Array & p_items)945 void PopupMenu::_set_items(const Array &p_items) {
946
947 ERR_FAIL_COND(p_items.size() % 10);
948 clear();
949
950 for (int i = 0; i < p_items.size(); i += 10) {
951
952 String text = p_items[i + 0];
953 Ref<Texture> icon = p_items[i + 1];
954 bool checkable = p_items[i + 2];
955 bool checked = p_items[i + 3];
956 bool disabled = p_items[i + 4];
957
958 int id = p_items[i + 5];
959 int accel = p_items[i + 6];
960 Variant meta = p_items[i + 7];
961 String subm = p_items[i + 8];
962 bool sep = p_items[i + 9];
963
964 int idx = get_item_count();
965 add_item(text, id);
966 set_item_icon(idx, icon);
967 set_item_as_checkable(idx, checkable);
968 set_item_checked(idx, checked);
969 set_item_disabled(idx, disabled);
970 set_item_ID(idx, id);
971 set_item_metadata(idx, meta);
972 set_item_as_separator(idx, sep);
973 set_item_accelerator(idx, accel);
974 set_item_submenu(idx, subm);
975 }
976 }
977
978 // Hide on item selection determines whether or not the popup will close after item selection
set_hide_on_item_selection(bool p_enabled)979 void PopupMenu::set_hide_on_item_selection(bool p_enabled) {
980
981 hide_on_item_selection = p_enabled;
982 }
983
is_hide_on_item_selection()984 bool PopupMenu::is_hide_on_item_selection() {
985
986 return hide_on_item_selection;
987 }
988
get_tooltip(const Point2 & p_pos) const989 String PopupMenu::get_tooltip(const Point2 &p_pos) const {
990
991 int over = _get_mouse_over(p_pos);
992 if (over < 0 || over >= items.size())
993 return "";
994 return items[over].tooltip;
995 }
996
set_parent_rect(const Rect2 & p_rect)997 void PopupMenu::set_parent_rect(const Rect2 &p_rect) {
998
999 parent_rect = p_rect;
1000 }
1001
get_translatable_strings(List<String> * p_strings) const1002 void PopupMenu::get_translatable_strings(List<String> *p_strings) const {
1003
1004 for (int i = 0; i < items.size(); i++) {
1005
1006 if (items[i].text != "")
1007 p_strings->push_back(items[i].text);
1008 }
1009 }
1010
add_autohide_area(const Rect2 & p_area)1011 void PopupMenu::add_autohide_area(const Rect2 &p_area) {
1012
1013 autohide_areas.push_back(p_area);
1014 }
1015
clear_autohide_areas()1016 void PopupMenu::clear_autohide_areas() {
1017
1018 autohide_areas.clear();
1019 }
1020
_bind_methods()1021 void PopupMenu::_bind_methods() {
1022
1023 ObjectTypeDB::bind_method(_MD("_input_event"), &PopupMenu::_input_event);
1024 ObjectTypeDB::bind_method(_MD("add_icon_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_item, DEFVAL(-1), DEFVAL(0));
1025 ObjectTypeDB::bind_method(_MD("add_item", "label", "id", "accel"), &PopupMenu::add_item, DEFVAL(-1), DEFVAL(0));
1026 ObjectTypeDB::bind_method(_MD("add_icon_check_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_check_item, DEFVAL(-1), DEFVAL(0));
1027 ObjectTypeDB::bind_method(_MD("add_check_item", "label", "id", "accel"), &PopupMenu::add_check_item, DEFVAL(-1), DEFVAL(0));
1028 ObjectTypeDB::bind_method(_MD("add_submenu_item", "label", "submenu", "id"), &PopupMenu::add_submenu_item, DEFVAL(-1));
1029
1030 ObjectTypeDB::bind_method(_MD("add_icon_shortcut", "texture", "shortcut:ShortCut", "id"), &PopupMenu::add_icon_shortcut, DEFVAL(-1));
1031 ObjectTypeDB::bind_method(_MD("add_shortcut", "shortcut:ShortCut", "id"), &PopupMenu::add_shortcut, DEFVAL(-1));
1032 ObjectTypeDB::bind_method(_MD("add_icon_check_shortcut", "texture", "shortcut:ShortCut", "id"), &PopupMenu::add_icon_check_shortcut, DEFVAL(-1));
1033 ObjectTypeDB::bind_method(_MD("add_check_shortcut", "shortcut:ShortCut", "id"), &PopupMenu::add_check_shortcut, DEFVAL(-1));
1034
1035 ObjectTypeDB::bind_method(_MD("set_item_text", "idx", "text"), &PopupMenu::set_item_text);
1036 ObjectTypeDB::bind_method(_MD("set_item_icon", "idx", "icon"), &PopupMenu::set_item_icon);
1037 ObjectTypeDB::bind_method(_MD("set_item_checked", "idx", "checked"), &PopupMenu::set_item_checked);
1038 ObjectTypeDB::bind_method(_MD("set_item_ID", "idx", "id"), &PopupMenu::set_item_ID);
1039 ObjectTypeDB::bind_method(_MD("set_item_accelerator", "idx", "accel"), &PopupMenu::set_item_accelerator);
1040 ObjectTypeDB::bind_method(_MD("set_item_metadata", "idx", "metadata"), &PopupMenu::set_item_metadata);
1041 ObjectTypeDB::bind_method(_MD("set_item_disabled", "idx", "disabled"), &PopupMenu::set_item_disabled);
1042 ObjectTypeDB::bind_method(_MD("set_item_submenu", "idx", "submenu"), &PopupMenu::set_item_submenu);
1043 ObjectTypeDB::bind_method(_MD("set_item_as_separator", "idx", "enable"), &PopupMenu::set_item_as_separator);
1044 ObjectTypeDB::bind_method(_MD("set_item_as_checkable", "idx", "enable"), &PopupMenu::set_item_as_checkable);
1045 ObjectTypeDB::bind_method(_MD("set_item_tooltip", "idx", "tooltip"), &PopupMenu::set_item_tooltip);
1046 ObjectTypeDB::bind_method(_MD("set_item_shortcut", "idx", "shortcut:ShortCut"), &PopupMenu::set_item_shortcut);
1047
1048 ObjectTypeDB::bind_method(_MD("toggle_item_checked", "idx"), &PopupMenu::toggle_item_checked);
1049
1050 ObjectTypeDB::bind_method(_MD("get_item_text", "idx"), &PopupMenu::get_item_text);
1051 ObjectTypeDB::bind_method(_MD("get_item_icon", "idx"), &PopupMenu::get_item_icon);
1052 ObjectTypeDB::bind_method(_MD("is_item_checked", "idx"), &PopupMenu::is_item_checked);
1053 ObjectTypeDB::bind_method(_MD("get_item_ID", "idx"), &PopupMenu::get_item_ID);
1054 ObjectTypeDB::bind_method(_MD("get_item_index", "id"), &PopupMenu::get_item_index);
1055 ObjectTypeDB::bind_method(_MD("get_item_accelerator", "idx"), &PopupMenu::get_item_accelerator);
1056 ObjectTypeDB::bind_method(_MD("get_item_metadata", "idx"), &PopupMenu::get_item_metadata);
1057 ObjectTypeDB::bind_method(_MD("is_item_disabled", "idx"), &PopupMenu::is_item_disabled);
1058 ObjectTypeDB::bind_method(_MD("get_item_submenu", "idx"), &PopupMenu::get_item_submenu);
1059 ObjectTypeDB::bind_method(_MD("is_item_separator", "idx"), &PopupMenu::is_item_separator);
1060 ObjectTypeDB::bind_method(_MD("is_item_checkable", "idx"), &PopupMenu::is_item_checkable);
1061 ObjectTypeDB::bind_method(_MD("get_item_tooltip", "idx"), &PopupMenu::get_item_tooltip);
1062 ObjectTypeDB::bind_method(_MD("get_item_shortcut:ShortCut", "idx"), &PopupMenu::get_item_shortcut);
1063
1064 ObjectTypeDB::bind_method(_MD("get_item_count"), &PopupMenu::get_item_count);
1065
1066 ObjectTypeDB::bind_method(_MD("remove_item", "idx"), &PopupMenu::remove_item);
1067
1068 ObjectTypeDB::bind_method(_MD("add_separator"), &PopupMenu::add_separator);
1069 ObjectTypeDB::bind_method(_MD("clear"), &PopupMenu::clear);
1070
1071 ObjectTypeDB::bind_method(_MD("_set_items"), &PopupMenu::_set_items);
1072 ObjectTypeDB::bind_method(_MD("_get_items"), &PopupMenu::_get_items);
1073
1074 ObjectTypeDB::bind_method(_MD("set_hide_on_item_selection", "enable"), &PopupMenu::set_hide_on_item_selection);
1075 ObjectTypeDB::bind_method(_MD("is_hide_on_item_selection"), &PopupMenu::is_hide_on_item_selection);
1076
1077 ObjectTypeDB::bind_method(_MD("_submenu_timeout"), &PopupMenu::_submenu_timeout);
1078
1079 ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "items", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), _SCS("_set_items"), _SCS("_get_items"));
1080 ADD_PROPERTYNO(PropertyInfo(Variant::BOOL, "hide_on_item_selection"), _SCS("set_hide_on_item_selection"), _SCS("is_hide_on_item_selection"));
1081
1082 ADD_SIGNAL(MethodInfo("item_pressed", PropertyInfo(Variant::INT, "ID")));
1083 }
1084
set_invalidate_click_until_motion()1085 void PopupMenu::set_invalidate_click_until_motion() {
1086 moved = Vector2();
1087 invalidated_click = true;
1088 }
1089
PopupMenu()1090 PopupMenu::PopupMenu() {
1091
1092 mouse_over = -1;
1093
1094 set_focus_mode(FOCUS_ALL);
1095 set_as_toplevel(true);
1096 set_hide_on_item_selection(true);
1097
1098 submenu_timer = memnew(Timer);
1099 submenu_timer->set_wait_time(0.3);
1100 submenu_timer->set_one_shot(true);
1101 submenu_timer->connect("timeout", this, "_submenu_timeout");
1102 add_child(submenu_timer);
1103 }
1104
~PopupMenu()1105 PopupMenu::~PopupMenu() {
1106 }
1107