popup_menu.cpp 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559
  1. /*************************************************************************/
  2. /* popup_menu.cpp */
  3. /*************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /*************************************************************************/
  8. /* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
  9. /* Copyright (c) 2014-2021 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 "core/os/input.h"
  32. #include "core/os/keyboard.h"
  33. #include "core/os/os.h"
  34. #include "core/print_string.h"
  35. #include "core/translation.h"
  36. String PopupMenu::_get_accel_text(int p_item) const {
  37. ERR_FAIL_INDEX_V(p_item, items.size(), String());
  38. if (items[p_item].shortcut.is_valid())
  39. return items[p_item].shortcut->get_as_text();
  40. else if (items[p_item].accel)
  41. return keycode_get_string(items[p_item].accel);
  42. return String();
  43. }
  44. Size2 PopupMenu::get_minimum_size() const {
  45. int vseparation = get_constant("vseparation");
  46. int hseparation = get_constant("hseparation");
  47. Size2 minsize = get_stylebox("panel")->get_minimum_size();
  48. Ref<Font> font = get_font("font");
  49. float max_w = 0;
  50. float icon_w = 0;
  51. int font_h = font->get_height();
  52. int check_w = MAX(get_icon("checked")->get_width(), get_icon("radio_checked")->get_width()) + hseparation;
  53. int accel_max_w = 0;
  54. bool has_check = false;
  55. for (int i = 0; i < items.size(); i++) {
  56. Size2 size;
  57. if (!items[i].icon.is_null()) {
  58. Size2 icon_size = items[i].icon->get_size();
  59. size.height = MAX(icon_size.height, font_h);
  60. icon_w = MAX(icon_size.width + hseparation, icon_w);
  61. } else {
  62. size.height = font_h;
  63. }
  64. size.width += items[i].h_ofs;
  65. if (items[i].checkable_type)
  66. has_check = true;
  67. String text = items[i].xl_text;
  68. size.width += font->get_string_size(text).width;
  69. if (i > 0)
  70. size.height += vseparation;
  71. if (items[i].accel || (items[i].shortcut.is_valid() && items[i].shortcut->is_valid())) {
  72. int accel_w = hseparation * 2;
  73. accel_w += font->get_string_size(_get_accel_text(i)).width;
  74. accel_max_w = MAX(accel_w, accel_max_w);
  75. }
  76. if (items[i].submenu != "")
  77. size.width += get_icon("submenu")->get_width();
  78. max_w = MAX(max_w, size.width);
  79. minsize.height += size.height;
  80. }
  81. minsize.width += max_w + icon_w + accel_max_w;
  82. if (has_check)
  83. minsize.width += check_w;
  84. return minsize;
  85. }
  86. int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
  87. if (p_over.x < 0 || p_over.x >= get_size().width)
  88. return -1;
  89. Ref<StyleBox> style = get_stylebox("panel");
  90. Point2 ofs = style->get_offset();
  91. if (ofs.y > p_over.y)
  92. return -1;
  93. Ref<Font> font = get_font("font");
  94. int vseparation = get_constant("vseparation");
  95. float font_h = font->get_height();
  96. for (int i = 0; i < items.size(); i++) {
  97. ofs.y += vseparation;
  98. float h;
  99. if (!items[i].icon.is_null()) {
  100. Size2 icon_size = items[i].icon->get_size();
  101. h = MAX(icon_size.height, font_h);
  102. } else {
  103. h = font_h;
  104. }
  105. ofs.y += h;
  106. if (p_over.y < ofs.y) {
  107. return i;
  108. }
  109. }
  110. return -1;
  111. }
  112. void PopupMenu::_activate_submenu(int over) {
  113. Node *n = get_node(items[over].submenu);
  114. ERR_FAIL_COND_MSG(!n, "Item subnode does not exist: " + items[over].submenu + ".");
  115. Popup *pm = Object::cast_to<Popup>(n);
  116. ERR_FAIL_COND_MSG(!pm, "Item subnode is not a Popup: " + items[over].submenu + ".");
  117. if (pm->is_visible_in_tree())
  118. return; //already visible!
  119. Point2 p = get_global_position();
  120. Rect2 pr(p, get_size());
  121. Ref<StyleBox> style = get_stylebox("panel");
  122. Point2 pos = p + Point2(get_size().width, items[over]._ofs_cache - style->get_offset().y) * get_global_transform().get_scale();
  123. Size2 size = pm->get_size();
  124. // fix pos
  125. if (pos.x + size.width > get_viewport_rect().size.width)
  126. pos.x = p.x - size.width;
  127. pm->set_position(pos);
  128. pm->set_scale(get_global_transform().get_scale());
  129. pm->popup();
  130. PopupMenu *pum = Object::cast_to<PopupMenu>(pm);
  131. if (pum) {
  132. pr.position -= pum->get_global_position();
  133. pum->clear_autohide_areas();
  134. pum->add_autohide_area(Rect2(pr.position.x, pr.position.y, pr.size.x, items[over]._ofs_cache));
  135. if (over < items.size() - 1) {
  136. int from = items[over + 1]._ofs_cache;
  137. pum->add_autohide_area(Rect2(pr.position.x, pr.position.y + from, pr.size.x, pr.size.y - from));
  138. }
  139. }
  140. }
  141. void PopupMenu::_submenu_timeout() {
  142. if (mouse_over == submenu_over)
  143. _activate_submenu(mouse_over);
  144. submenu_over = -1;
  145. }
  146. void PopupMenu::_scroll(float p_factor, const Point2 &p_over) {
  147. int vseparation = get_constant("vseparation");
  148. Ref<Font> font = get_font("font");
  149. float dy = (vseparation + font->get_height()) * 3 * p_factor * get_global_transform().get_scale().y;
  150. if (dy > 0) {
  151. const float global_top = get_global_position().y;
  152. const float limit = global_top < 0 ? -global_top : 0;
  153. dy = MIN(dy, limit);
  154. } else if (dy < 0) {
  155. const float global_bottom = get_global_position().y + get_size().y * get_global_transform().get_scale().y;
  156. const float viewport_height = get_viewport_rect().size.y;
  157. const float limit = global_bottom > viewport_height ? global_bottom - viewport_height : 0;
  158. dy = -MIN(-dy, limit);
  159. }
  160. set_position(get_position() + Vector2(0, dy));
  161. Ref<InputEventMouseMotion> ie;
  162. ie.instance();
  163. ie->set_position(p_over - Vector2(0, dy));
  164. _gui_input(ie);
  165. }
  166. void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) {
  167. if (p_event->is_action("ui_down") && p_event->is_pressed()) {
  168. int search_from = mouse_over + 1;
  169. if (search_from >= items.size())
  170. search_from = 0;
  171. bool match_found = false;
  172. for (int i = search_from; i < items.size(); i++) {
  173. if (i < 0 || i >= items.size())
  174. continue;
  175. if (!items[i].separator && !items[i].disabled) {
  176. mouse_over = i;
  177. emit_signal("id_focused", i);
  178. update();
  179. accept_event();
  180. match_found = true;
  181. break;
  182. }
  183. }
  184. if (!match_found) {
  185. // If the last item is not selectable, try re-searching from the start.
  186. for (int i = 0; i < search_from; i++) {
  187. if (!items[i].separator && !items[i].disabled) {
  188. mouse_over = i;
  189. emit_signal("id_focused", i);
  190. update();
  191. accept_event();
  192. break;
  193. }
  194. }
  195. }
  196. } else if (p_event->is_action("ui_up") && p_event->is_pressed()) {
  197. int search_from = mouse_over - 1;
  198. if (search_from < 0)
  199. search_from = items.size() - 1;
  200. bool match_found = false;
  201. for (int i = search_from; i >= 0; i--) {
  202. if (i >= items.size())
  203. continue;
  204. if (!items[i].separator && !items[i].disabled) {
  205. mouse_over = i;
  206. emit_signal("id_focused", i);
  207. update();
  208. accept_event();
  209. match_found = true;
  210. break;
  211. }
  212. }
  213. if (!match_found) {
  214. // If the first item is not selectable, try re-searching from the end.
  215. for (int i = items.size() - 1; i >= search_from; i--) {
  216. if (!items[i].separator && !items[i].disabled) {
  217. mouse_over = i;
  218. emit_signal("id_focused", i);
  219. update();
  220. accept_event();
  221. break;
  222. }
  223. }
  224. }
  225. } else if (p_event->is_action("ui_left") && p_event->is_pressed()) {
  226. Node *n = get_parent();
  227. if (n && Object::cast_to<PopupMenu>(n)) {
  228. hide();
  229. accept_event();
  230. }
  231. } else if (p_event->is_action("ui_right") && p_event->is_pressed()) {
  232. if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator && items[mouse_over].submenu != "" && submenu_over != mouse_over) {
  233. _activate_submenu(mouse_over);
  234. accept_event();
  235. }
  236. } else if (p_event->is_action("ui_accept") && p_event->is_pressed()) {
  237. if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator) {
  238. if (items[mouse_over].submenu != "" && submenu_over != mouse_over) {
  239. _activate_submenu(mouse_over);
  240. } else {
  241. activate_item(mouse_over);
  242. }
  243. accept_event();
  244. }
  245. }
  246. Ref<InputEventMouseButton> b = p_event;
  247. if (b.is_valid()) {
  248. if (b->is_pressed())
  249. return;
  250. int button_idx = b->get_button_index();
  251. switch (button_idx) {
  252. case BUTTON_WHEEL_DOWN: {
  253. if (get_global_position().y + get_size().y * get_global_transform().get_scale().y > get_viewport_rect().size.y) {
  254. _scroll(-b->get_factor(), b->get_position());
  255. }
  256. } break;
  257. case BUTTON_WHEEL_UP: {
  258. if (get_global_position().y < 0) {
  259. _scroll(b->get_factor(), b->get_position());
  260. }
  261. } break;
  262. default: {
  263. // Allow activating item by releasing the LMB or any that was down when the popup appeared
  264. if (button_idx == BUTTON_LEFT || (initial_button_mask & (1 << (button_idx - 1)))) {
  265. bool was_during_grabbed_click = during_grabbed_click;
  266. during_grabbed_click = false;
  267. initial_button_mask = 0;
  268. int over = _get_mouse_over(b->get_position());
  269. if (invalidated_click) {
  270. invalidated_click = false;
  271. break;
  272. }
  273. if (over < 0) {
  274. if (!was_during_grabbed_click) {
  275. hide();
  276. }
  277. break; //non-activable
  278. }
  279. if (items[over].separator || items[over].disabled)
  280. break;
  281. if (items[over].submenu != "") {
  282. _activate_submenu(over);
  283. return;
  284. }
  285. activate_item(over);
  286. }
  287. }
  288. }
  289. //update();
  290. }
  291. Ref<InputEventMouseMotion> m = p_event;
  292. if (m.is_valid()) {
  293. if (invalidated_click) {
  294. moved += m->get_relative();
  295. if (moved.length() > 4)
  296. invalidated_click = false;
  297. }
  298. for (List<Rect2>::Element *E = autohide_areas.front(); E; E = E->next()) {
  299. if (!Rect2(Point2(), get_size()).has_point(m->get_position()) && E->get().has_point(m->get_position())) {
  300. call_deferred("hide");
  301. return;
  302. }
  303. }
  304. int over = _get_mouse_over(m->get_position());
  305. int id = (over < 0 || items[over].separator || items[over].disabled) ? -1 : (items[over].id >= 0 ? items[over].id : over);
  306. if (id < 0) {
  307. mouse_over = -1;
  308. update();
  309. return;
  310. }
  311. if (items[over].submenu != "" && submenu_over != over) {
  312. submenu_over = over;
  313. submenu_timer->start();
  314. }
  315. if (over != mouse_over) {
  316. mouse_over = over;
  317. update();
  318. }
  319. }
  320. Ref<InputEventPanGesture> pan_gesture = p_event;
  321. if (pan_gesture.is_valid()) {
  322. if (get_global_position().y + get_size().y > get_viewport_rect().size.y || get_global_position().y < 0) {
  323. _scroll(-pan_gesture->get_delta().y, pan_gesture->get_position());
  324. }
  325. }
  326. Ref<InputEventKey> k = p_event;
  327. if (allow_search && k.is_valid() && k->get_unicode() && k->is_pressed()) {
  328. uint64_t now = OS::get_singleton()->get_ticks_msec();
  329. uint64_t diff = now - search_time_msec;
  330. uint64_t max_interval = uint64_t(GLOBAL_DEF("gui/timers/incremental_search_max_interval_msec", 2000));
  331. search_time_msec = now;
  332. if (diff > max_interval) {
  333. search_string = "";
  334. }
  335. if (String::chr(k->get_unicode()) != search_string)
  336. search_string += String::chr(k->get_unicode());
  337. for (int i = mouse_over + 1; i <= items.size(); i++) {
  338. if (i == items.size()) {
  339. if (mouse_over <= 0)
  340. break;
  341. else
  342. i = 0;
  343. }
  344. if (i == mouse_over)
  345. break;
  346. if (items[i].text.findn(search_string) == 0) {
  347. mouse_over = i;
  348. emit_signal("id_focused", i);
  349. update();
  350. accept_event();
  351. break;
  352. }
  353. }
  354. }
  355. }
  356. bool PopupMenu::has_point(const Point2 &p_point) const {
  357. if (parent_rect.has_point(p_point))
  358. return true;
  359. for (const List<Rect2>::Element *E = autohide_areas.front(); E; E = E->next()) {
  360. if (E->get().has_point(p_point))
  361. return true;
  362. }
  363. return Control::has_point(p_point);
  364. }
  365. void PopupMenu::_notification(int p_what) {
  366. switch (p_what) {
  367. case NOTIFICATION_ENTER_TREE: {
  368. PopupMenu *pm = Object::cast_to<PopupMenu>(get_parent());
  369. if (pm) {
  370. // Inherit submenu's popup delay time from parent menu
  371. float pm_delay = pm->get_submenu_popup_delay();
  372. set_submenu_popup_delay(pm_delay);
  373. }
  374. } break;
  375. case NOTIFICATION_TRANSLATION_CHANGED: {
  376. for (int i = 0; i < items.size(); i++) {
  377. items.write[i].xl_text = tr(items[i].text);
  378. }
  379. minimum_size_changed();
  380. update();
  381. } break;
  382. case NOTIFICATION_DRAW: {
  383. RID ci = get_canvas_item();
  384. Size2 size = get_size();
  385. Ref<StyleBox> style = get_stylebox("panel");
  386. Ref<StyleBox> hover = get_stylebox("hover");
  387. Ref<Font> font = get_font("font");
  388. // In Item::checkable_type enum order (less the non-checkable member)
  389. Ref<Texture> check[] = { get_icon("checked"), get_icon("radio_checked") };
  390. Ref<Texture> uncheck[] = { get_icon("unchecked"), get_icon("radio_unchecked") };
  391. Ref<Texture> submenu = get_icon("submenu");
  392. Ref<StyleBox> separator = get_stylebox("separator");
  393. Ref<StyleBox> labeled_separator_left = get_stylebox("labeled_separator_left");
  394. Ref<StyleBox> labeled_separator_right = get_stylebox("labeled_separator_right");
  395. style->draw(ci, Rect2(Point2(), get_size()));
  396. Point2 ofs = style->get_offset();
  397. int vseparation = get_constant("vseparation");
  398. int hseparation = get_constant("hseparation");
  399. Color font_color = get_color("font_color");
  400. Color font_color_disabled = get_color("font_color_disabled");
  401. Color font_color_accel = get_color("font_color_accel");
  402. Color font_color_hover = get_color("font_color_hover");
  403. Color font_color_separator = get_color("font_color_separator");
  404. float font_h = font->get_height();
  405. // Add the check and the wider icon to the offset of all items.
  406. float icon_ofs = 0.0;
  407. bool has_check = false;
  408. for (int i = 0; i < items.size(); i++) {
  409. if (!items[i].icon.is_null())
  410. icon_ofs = MAX(items[i].icon->get_size().width, icon_ofs);
  411. if (items[i].checkable_type)
  412. has_check = true;
  413. }
  414. if (icon_ofs > 0.0)
  415. icon_ofs += hseparation;
  416. float check_ofs = 0.0;
  417. if (has_check)
  418. check_ofs = MAX(get_icon("checked")->get_width(), get_icon("radio_checked")->get_width()) + hseparation;
  419. for (int i = 0; i < items.size(); i++) {
  420. if (i > 0)
  421. ofs.y += vseparation;
  422. Point2 item_ofs = ofs;
  423. Size2 icon_size;
  424. float h;
  425. if (!items[i].icon.is_null()) {
  426. icon_size = items[i].icon->get_size();
  427. h = MAX(icon_size.height, font_h);
  428. } else {
  429. h = font_h;
  430. }
  431. if (i == mouse_over) {
  432. hover->draw(ci, Rect2(item_ofs + Point2(-hseparation, -vseparation / 2), Size2(get_size().width - style->get_minimum_size().width + hseparation * 2, h + vseparation)));
  433. }
  434. String text = items[i].xl_text;
  435. item_ofs.x += items[i].h_ofs;
  436. if (items[i].separator) {
  437. int sep_h = separator->get_center_size().height + separator->get_minimum_size().height;
  438. if (text != String()) {
  439. int ss = font->get_string_size(text).width;
  440. int center = (get_size().width) / 2;
  441. int l = center - ss / 2;
  442. int r = center + ss / 2;
  443. if (l > item_ofs.x) {
  444. labeled_separator_left->draw(ci, Rect2(item_ofs + Point2(0, Math::floor((h - sep_h) / 2.0)), Size2(MAX(0, l - item_ofs.x), sep_h)));
  445. }
  446. if (r < get_size().width - style->get_margin(MARGIN_RIGHT)) {
  447. labeled_separator_right->draw(ci, Rect2(Point2(r, item_ofs.y + Math::floor((h - sep_h) / 2.0)), Size2(MAX(0, get_size().width - style->get_margin(MARGIN_RIGHT) - r), sep_h)));
  448. }
  449. } else {
  450. separator->draw(ci, Rect2(item_ofs + Point2(0, Math::floor((h - sep_h) / 2.0)), Size2(get_size().width - style->get_minimum_size().width, sep_h)));
  451. }
  452. }
  453. Color icon_color(1, 1, 1, items[i].disabled ? 0.5 : 1);
  454. if (items[i].checkable_type) {
  455. Texture *icon = (items[i].checked ? check[items[i].checkable_type - 1] : uncheck[items[i].checkable_type - 1]).ptr();
  456. icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon->get_height()) / 2.0)), icon_color);
  457. }
  458. if (!items[i].icon.is_null()) {
  459. items[i].icon->draw(ci, item_ofs + Size2(check_ofs, 0) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color);
  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), icon_color);
  463. }
  464. item_ofs.y += font->get_ascent();
  465. if (items[i].separator) {
  466. if (text != String()) {
  467. int center = (get_size().width - font->get_string_size(text).width) / 2;
  468. font->draw(ci, Point2(center, item_ofs.y + Math::floor((h - font_h) / 2.0)), text, font_color_separator);
  469. }
  470. } else {
  471. item_ofs.x += icon_ofs + check_ofs;
  472. 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));
  473. }
  474. if (items[i].accel || (items[i].shortcut.is_valid() && items[i].shortcut->is_valid())) {
  475. //accelerator
  476. String text2 = _get_accel_text(i);
  477. item_ofs.x = size.width - style->get_margin(MARGIN_RIGHT) - font->get_string_size(text2).width;
  478. font->draw(ci, item_ofs + Point2(0, Math::floor((h - font_h) / 2.0)), text2, i == mouse_over ? font_color_hover : font_color_accel);
  479. }
  480. items.write[i]._ofs_cache = ofs.y;
  481. ofs.y += h;
  482. }
  483. } break;
  484. case MainLoop::NOTIFICATION_WM_FOCUS_OUT: {
  485. if (hide_on_window_lose_focus)
  486. hide();
  487. } break;
  488. case NOTIFICATION_MOUSE_ENTER: {
  489. grab_focus();
  490. } break;
  491. case NOTIFICATION_MOUSE_EXIT: {
  492. if (mouse_over >= 0 && (items[mouse_over].submenu == "" || submenu_over != -1)) {
  493. mouse_over = -1;
  494. update();
  495. }
  496. } break;
  497. case NOTIFICATION_POST_POPUP: {
  498. initial_button_mask = Input::get_singleton()->get_mouse_button_mask();
  499. during_grabbed_click = (bool)initial_button_mask;
  500. } break;
  501. case NOTIFICATION_POPUP_HIDE: {
  502. if (mouse_over >= 0) {
  503. mouse_over = -1;
  504. update();
  505. }
  506. for (int i = 0; i < items.size(); i++) {
  507. if (items[i].submenu == "")
  508. continue;
  509. Node *n = get_node(items[i].submenu);
  510. if (!n)
  511. continue;
  512. PopupMenu *pm = Object::cast_to<PopupMenu>(n);
  513. if (!pm || !pm->is_visible())
  514. continue;
  515. pm->hide();
  516. }
  517. } break;
  518. }
  519. }
  520. /* Methods to add items with or without icon, checkbox, shortcut.
  521. * Be sure to keep them in sync when adding new properties in the Item struct.
  522. */
  523. #define ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel) \
  524. item.text = p_label; \
  525. item.xl_text = tr(p_label); \
  526. item.id = p_id == -1 ? items.size() : p_id; \
  527. item.accel = p_accel;
  528. void PopupMenu::add_item(const String &p_label, int p_id, uint32_t p_accel) {
  529. Item item;
  530. ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
  531. items.push_back(item);
  532. update();
  533. minimum_size_changed();
  534. }
  535. void PopupMenu::add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_id, uint32_t p_accel) {
  536. Item item;
  537. ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
  538. item.icon = p_icon;
  539. items.push_back(item);
  540. update();
  541. minimum_size_changed();
  542. }
  543. void PopupMenu::add_check_item(const String &p_label, int p_id, uint32_t p_accel) {
  544. Item item;
  545. ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
  546. item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
  547. items.push_back(item);
  548. update();
  549. minimum_size_changed();
  550. }
  551. void PopupMenu::add_icon_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_id, uint32_t p_accel) {
  552. Item item;
  553. ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
  554. item.icon = p_icon;
  555. item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
  556. items.push_back(item);
  557. update();
  558. minimum_size_changed();
  559. }
  560. void PopupMenu::add_radio_check_item(const String &p_label, int p_id, uint32_t p_accel) {
  561. Item item;
  562. ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
  563. item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
  564. items.push_back(item);
  565. update();
  566. minimum_size_changed();
  567. }
  568. void PopupMenu::add_icon_radio_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_id, uint32_t p_accel) {
  569. Item item;
  570. ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
  571. item.icon = p_icon;
  572. item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
  573. items.push_back(item);
  574. update();
  575. minimum_size_changed();
  576. }
  577. void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_id, uint32_t p_accel) {
  578. Item item;
  579. ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
  580. item.max_states = p_max_states;
  581. item.state = p_default_state;
  582. items.push_back(item);
  583. update();
  584. minimum_size_changed();
  585. }
  586. #define ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global) \
  587. ERR_FAIL_COND_MSG(p_shortcut.is_null(), "Cannot add item with invalid ShortCut."); \
  588. _ref_shortcut(p_shortcut); \
  589. item.text = p_shortcut->get_name(); \
  590. item.xl_text = tr(item.text); \
  591. item.id = p_id == -1 ? items.size() : p_id; \
  592. item.shortcut = p_shortcut; \
  593. item.shortcut_is_global = p_global;
  594. void PopupMenu::add_shortcut(const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) {
  595. Item item;
  596. ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
  597. items.push_back(item);
  598. update();
  599. minimum_size_changed();
  600. }
  601. void PopupMenu::add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) {
  602. Item item;
  603. ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
  604. item.icon = p_icon;
  605. items.push_back(item);
  606. update();
  607. minimum_size_changed();
  608. }
  609. void PopupMenu::add_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) {
  610. Item item;
  611. ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
  612. item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
  613. items.push_back(item);
  614. update();
  615. minimum_size_changed();
  616. }
  617. void PopupMenu::add_icon_check_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) {
  618. Item item;
  619. ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
  620. item.icon = p_icon;
  621. item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
  622. items.push_back(item);
  623. update();
  624. minimum_size_changed();
  625. }
  626. void PopupMenu::add_radio_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) {
  627. Item item;
  628. ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
  629. item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
  630. items.push_back(item);
  631. update();
  632. minimum_size_changed();
  633. }
  634. void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) {
  635. Item item;
  636. ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
  637. item.icon = p_icon;
  638. item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
  639. items.push_back(item);
  640. update();
  641. minimum_size_changed();
  642. }
  643. void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, int p_id) {
  644. Item item;
  645. item.text = p_label;
  646. item.xl_text = tr(p_label);
  647. item.id = p_id == -1 ? items.size() : p_id;
  648. item.submenu = p_submenu;
  649. items.push_back(item);
  650. update();
  651. minimum_size_changed();
  652. }
  653. #undef ITEM_SETUP_WITH_ACCEL
  654. #undef ITEM_SETUP_WITH_SHORTCUT
  655. /* Methods to modify existing items. */
  656. void PopupMenu::set_item_text(int p_idx, const String &p_text) {
  657. ERR_FAIL_INDEX(p_idx, items.size());
  658. items.write[p_idx].text = p_text;
  659. items.write[p_idx].xl_text = tr(p_text);
  660. update();
  661. minimum_size_changed();
  662. }
  663. void PopupMenu::set_item_icon(int p_idx, const Ref<Texture> &p_icon) {
  664. ERR_FAIL_INDEX(p_idx, items.size());
  665. items.write[p_idx].icon = p_icon;
  666. update();
  667. minimum_size_changed();
  668. }
  669. void PopupMenu::set_item_checked(int p_idx, bool p_checked) {
  670. ERR_FAIL_INDEX(p_idx, items.size());
  671. items.write[p_idx].checked = p_checked;
  672. update();
  673. minimum_size_changed();
  674. }
  675. void PopupMenu::set_item_id(int p_idx, int p_id) {
  676. ERR_FAIL_INDEX(p_idx, items.size());
  677. items.write[p_idx].id = p_id;
  678. update();
  679. minimum_size_changed();
  680. }
  681. void PopupMenu::set_item_accelerator(int p_idx, uint32_t p_accel) {
  682. ERR_FAIL_INDEX(p_idx, items.size());
  683. items.write[p_idx].accel = p_accel;
  684. update();
  685. minimum_size_changed();
  686. }
  687. void PopupMenu::set_item_metadata(int p_idx, const Variant &p_meta) {
  688. ERR_FAIL_INDEX(p_idx, items.size());
  689. items.write[p_idx].metadata = p_meta;
  690. update();
  691. minimum_size_changed();
  692. }
  693. void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) {
  694. ERR_FAIL_INDEX(p_idx, items.size());
  695. items.write[p_idx].disabled = p_disabled;
  696. update();
  697. minimum_size_changed();
  698. }
  699. void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) {
  700. ERR_FAIL_INDEX(p_idx, items.size());
  701. items.write[p_idx].submenu = p_submenu;
  702. update();
  703. minimum_size_changed();
  704. }
  705. void PopupMenu::toggle_item_checked(int p_idx) {
  706. ERR_FAIL_INDEX(p_idx, items.size());
  707. items.write[p_idx].checked = !items[p_idx].checked;
  708. update();
  709. minimum_size_changed();
  710. }
  711. String PopupMenu::get_item_text(int p_idx) const {
  712. ERR_FAIL_INDEX_V(p_idx, items.size(), "");
  713. return items[p_idx].text;
  714. }
  715. int PopupMenu::get_item_idx_from_text(const String &text) const {
  716. for (int idx = 0; idx < items.size(); idx++) {
  717. if (items[idx].text == text)
  718. return idx;
  719. }
  720. return -1;
  721. }
  722. Ref<Texture> PopupMenu::get_item_icon(int p_idx) const {
  723. ERR_FAIL_INDEX_V(p_idx, items.size(), Ref<Texture>());
  724. return items[p_idx].icon;
  725. }
  726. uint32_t PopupMenu::get_item_accelerator(int p_idx) const {
  727. ERR_FAIL_INDEX_V(p_idx, items.size(), 0);
  728. return items[p_idx].accel;
  729. }
  730. Variant PopupMenu::get_item_metadata(int p_idx) const {
  731. ERR_FAIL_INDEX_V(p_idx, items.size(), Variant());
  732. return items[p_idx].metadata;
  733. }
  734. bool PopupMenu::is_item_disabled(int p_idx) const {
  735. ERR_FAIL_INDEX_V(p_idx, items.size(), false);
  736. return items[p_idx].disabled;
  737. }
  738. bool PopupMenu::is_item_checked(int p_idx) const {
  739. ERR_FAIL_INDEX_V(p_idx, items.size(), false);
  740. return items[p_idx].checked;
  741. }
  742. int PopupMenu::get_item_id(int p_idx) const {
  743. ERR_FAIL_INDEX_V(p_idx, items.size(), 0);
  744. return items[p_idx].id;
  745. }
  746. int PopupMenu::get_item_index(int p_id) const {
  747. for (int i = 0; i < items.size(); i++) {
  748. if (items[i].id == p_id)
  749. return i;
  750. }
  751. return -1;
  752. }
  753. String PopupMenu::get_item_submenu(int p_idx) const {
  754. ERR_FAIL_INDEX_V(p_idx, items.size(), "");
  755. return items[p_idx].submenu;
  756. }
  757. String PopupMenu::get_item_tooltip(int p_idx) const {
  758. ERR_FAIL_INDEX_V(p_idx, items.size(), "");
  759. return items[p_idx].tooltip;
  760. }
  761. Ref<ShortCut> PopupMenu::get_item_shortcut(int p_idx) const {
  762. ERR_FAIL_INDEX_V(p_idx, items.size(), Ref<ShortCut>());
  763. return items[p_idx].shortcut;
  764. }
  765. int PopupMenu::get_item_state(int p_idx) const {
  766. ERR_FAIL_INDEX_V(p_idx, items.size(), -1);
  767. return items[p_idx].state;
  768. }
  769. void PopupMenu::set_item_as_separator(int p_idx, bool p_separator) {
  770. ERR_FAIL_INDEX(p_idx, items.size());
  771. items.write[p_idx].separator = p_separator;
  772. update();
  773. }
  774. bool PopupMenu::is_item_separator(int p_idx) const {
  775. ERR_FAIL_INDEX_V(p_idx, items.size(), false);
  776. return items[p_idx].separator;
  777. }
  778. void PopupMenu::set_item_as_checkable(int p_idx, bool p_checkable) {
  779. ERR_FAIL_INDEX(p_idx, items.size());
  780. items.write[p_idx].checkable_type = p_checkable ? Item::CHECKABLE_TYPE_CHECK_BOX : Item::CHECKABLE_TYPE_NONE;
  781. update();
  782. }
  783. void PopupMenu::set_item_as_radio_checkable(int p_idx, bool p_radio_checkable) {
  784. ERR_FAIL_INDEX(p_idx, items.size());
  785. items.write[p_idx].checkable_type = p_radio_checkable ? Item::CHECKABLE_TYPE_RADIO_BUTTON : Item::CHECKABLE_TYPE_NONE;
  786. update();
  787. }
  788. void PopupMenu::set_item_tooltip(int p_idx, const String &p_tooltip) {
  789. ERR_FAIL_INDEX(p_idx, items.size());
  790. items.write[p_idx].tooltip = p_tooltip;
  791. update();
  792. }
  793. void PopupMenu::set_item_shortcut(int p_idx, const Ref<ShortCut> &p_shortcut, bool p_global) {
  794. ERR_FAIL_INDEX(p_idx, items.size());
  795. if (items[p_idx].shortcut.is_valid()) {
  796. _unref_shortcut(items[p_idx].shortcut);
  797. }
  798. items.write[p_idx].shortcut = p_shortcut;
  799. items.write[p_idx].shortcut_is_global = p_global;
  800. if (items[p_idx].shortcut.is_valid()) {
  801. _ref_shortcut(items[p_idx].shortcut);
  802. }
  803. update();
  804. }
  805. void PopupMenu::set_item_h_offset(int p_idx, int p_offset) {
  806. ERR_FAIL_INDEX(p_idx, items.size());
  807. items.write[p_idx].h_ofs = p_offset;
  808. update();
  809. minimum_size_changed();
  810. }
  811. void PopupMenu::set_item_multistate(int p_idx, int p_state) {
  812. ERR_FAIL_INDEX(p_idx, items.size());
  813. items.write[p_idx].state = p_state;
  814. update();
  815. }
  816. void PopupMenu::set_item_shortcut_disabled(int p_idx, bool p_disabled) {
  817. ERR_FAIL_INDEX(p_idx, items.size());
  818. items.write[p_idx].shortcut_is_disabled = p_disabled;
  819. update();
  820. }
  821. void PopupMenu::toggle_item_multistate(int p_idx) {
  822. ERR_FAIL_INDEX(p_idx, items.size());
  823. if (0 >= items[p_idx].max_states) {
  824. return;
  825. }
  826. ++items.write[p_idx].state;
  827. if (items.write[p_idx].max_states <= items[p_idx].state)
  828. items.write[p_idx].state = 0;
  829. update();
  830. }
  831. bool PopupMenu::is_item_checkable(int p_idx) const {
  832. ERR_FAIL_INDEX_V(p_idx, items.size(), false);
  833. return items[p_idx].checkable_type;
  834. }
  835. bool PopupMenu::is_item_radio_checkable(int p_idx) const {
  836. ERR_FAIL_INDEX_V(p_idx, items.size(), false);
  837. return items[p_idx].checkable_type == Item::CHECKABLE_TYPE_RADIO_BUTTON;
  838. }
  839. bool PopupMenu::is_item_shortcut_disabled(int p_idx) const {
  840. ERR_FAIL_INDEX_V(p_idx, items.size(), false);
  841. return items[p_idx].shortcut_is_disabled;
  842. }
  843. int PopupMenu::get_current_index() const {
  844. return mouse_over;
  845. }
  846. int PopupMenu::get_item_count() const {
  847. return items.size();
  848. }
  849. bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only) {
  850. uint32_t code = 0;
  851. Ref<InputEventKey> k = p_event;
  852. if (k.is_valid()) {
  853. code = k->get_scancode();
  854. if (code == 0)
  855. code = k->get_unicode();
  856. if (k->get_control())
  857. code |= KEY_MASK_CTRL;
  858. if (k->get_alt())
  859. code |= KEY_MASK_ALT;
  860. if (k->get_metakey())
  861. code |= KEY_MASK_META;
  862. if (k->get_shift())
  863. code |= KEY_MASK_SHIFT;
  864. }
  865. for (int i = 0; i < items.size(); i++) {
  866. if (is_item_disabled(i) || items[i].shortcut_is_disabled)
  867. continue;
  868. if (items[i].shortcut.is_valid() && items[i].shortcut->is_shortcut(p_event) && (items[i].shortcut_is_global || !p_for_global_only)) {
  869. activate_item(i);
  870. return true;
  871. }
  872. if (code != 0 && items[i].accel == code) {
  873. activate_item(i);
  874. return true;
  875. }
  876. if (items[i].submenu != "") {
  877. Node *n = get_node(items[i].submenu);
  878. if (!n)
  879. continue;
  880. PopupMenu *pm = Object::cast_to<PopupMenu>(n);
  881. if (!pm)
  882. continue;
  883. if (pm->activate_item_by_event(p_event, p_for_global_only)) {
  884. return true;
  885. }
  886. }
  887. }
  888. return false;
  889. }
  890. void PopupMenu::activate_item(int p_item) {
  891. ERR_FAIL_INDEX(p_item, items.size());
  892. ERR_FAIL_COND(items[p_item].separator);
  893. int id = items[p_item].id >= 0 ? items[p_item].id : p_item;
  894. //hide all parent PopupMenus
  895. Node *next = get_parent();
  896. PopupMenu *pop = Object::cast_to<PopupMenu>(next);
  897. while (pop) {
  898. // We close all parents that are chained together,
  899. // with hide_on_item_selection enabled
  900. if (items[p_item].checkable_type) {
  901. if (!hide_on_checkable_item_selection || !pop->is_hide_on_checkable_item_selection())
  902. break;
  903. } else if (0 < items[p_item].max_states) {
  904. if (!hide_on_multistate_item_selection || !pop->is_hide_on_multistate_item_selection())
  905. break;
  906. } else if (!hide_on_item_selection || !pop->is_hide_on_item_selection())
  907. break;
  908. pop->hide();
  909. next = next->get_parent();
  910. pop = Object::cast_to<PopupMenu>(next);
  911. }
  912. // Hides popup by default; unless otherwise specified
  913. // by using set_hide_on_item_selection and set_hide_on_checkable_item_selection
  914. bool need_hide = true;
  915. if (items[p_item].checkable_type) {
  916. if (!hide_on_checkable_item_selection)
  917. need_hide = false;
  918. } else if (0 < items[p_item].max_states) {
  919. if (!hide_on_multistate_item_selection)
  920. need_hide = false;
  921. } else if (!hide_on_item_selection)
  922. need_hide = false;
  923. emit_signal("id_pressed", id);
  924. emit_signal("index_pressed", p_item);
  925. if (need_hide) {
  926. hide();
  927. }
  928. }
  929. void PopupMenu::remove_item(int p_idx) {
  930. ERR_FAIL_INDEX(p_idx, items.size());
  931. if (items[p_idx].shortcut.is_valid()) {
  932. _unref_shortcut(items[p_idx].shortcut);
  933. }
  934. items.remove(p_idx);
  935. update();
  936. minimum_size_changed();
  937. }
  938. void PopupMenu::add_separator(const String &p_text, int p_id) {
  939. Item sep;
  940. sep.separator = true;
  941. sep.id = p_id;
  942. if (p_text != String()) {
  943. sep.text = p_text;
  944. sep.xl_text = tr(p_text);
  945. }
  946. items.push_back(sep);
  947. update();
  948. }
  949. void PopupMenu::clear() {
  950. for (int i = 0; i < items.size(); i++) {
  951. if (items[i].shortcut.is_valid()) {
  952. _unref_shortcut(items[i].shortcut);
  953. }
  954. }
  955. items.clear();
  956. mouse_over = -1;
  957. update();
  958. minimum_size_changed();
  959. }
  960. Array PopupMenu::_get_items() const {
  961. Array items;
  962. for (int i = 0; i < get_item_count(); i++) {
  963. items.push_back(get_item_text(i));
  964. items.push_back(get_item_icon(i));
  965. // For compatibility, use false/true for no/checkbox and integers for other values
  966. int ct = this->items[i].checkable_type;
  967. items.push_back(Variant(ct <= Item::CHECKABLE_TYPE_CHECK_BOX ? is_item_checkable(i) : ct));
  968. items.push_back(is_item_checked(i));
  969. items.push_back(is_item_disabled(i));
  970. items.push_back(get_item_id(i));
  971. items.push_back(get_item_accelerator(i));
  972. items.push_back(get_item_metadata(i));
  973. items.push_back(get_item_submenu(i));
  974. items.push_back(is_item_separator(i));
  975. }
  976. return items;
  977. }
  978. void PopupMenu::_ref_shortcut(Ref<ShortCut> p_sc) {
  979. if (!shortcut_refcount.has(p_sc)) {
  980. shortcut_refcount[p_sc] = 1;
  981. p_sc->connect("changed", this, "update");
  982. } else {
  983. shortcut_refcount[p_sc] += 1;
  984. }
  985. }
  986. void PopupMenu::_unref_shortcut(Ref<ShortCut> p_sc) {
  987. ERR_FAIL_COND(!shortcut_refcount.has(p_sc));
  988. shortcut_refcount[p_sc]--;
  989. if (shortcut_refcount[p_sc] == 0) {
  990. p_sc->disconnect("changed", this, "update");
  991. shortcut_refcount.erase(p_sc);
  992. }
  993. }
  994. void PopupMenu::_set_items(const Array &p_items) {
  995. ERR_FAIL_COND(p_items.size() % 10);
  996. clear();
  997. for (int i = 0; i < p_items.size(); i += 10) {
  998. String text = p_items[i + 0];
  999. Ref<Texture> icon = p_items[i + 1];
  1000. // For compatibility, use false/true for no/checkbox and integers for other values
  1001. bool checkable = p_items[i + 2];
  1002. bool radio_checkable = (int)p_items[i + 2] == Item::CHECKABLE_TYPE_RADIO_BUTTON;
  1003. bool checked = p_items[i + 3];
  1004. bool disabled = p_items[i + 4];
  1005. int id = p_items[i + 5];
  1006. int accel = p_items[i + 6];
  1007. Variant meta = p_items[i + 7];
  1008. String subm = p_items[i + 8];
  1009. bool sep = p_items[i + 9];
  1010. int idx = get_item_count();
  1011. add_item(text, id);
  1012. set_item_icon(idx, icon);
  1013. if (checkable) {
  1014. if (radio_checkable) {
  1015. set_item_as_radio_checkable(idx, true);
  1016. } else {
  1017. set_item_as_checkable(idx, true);
  1018. }
  1019. }
  1020. set_item_checked(idx, checked);
  1021. set_item_disabled(idx, disabled);
  1022. set_item_id(idx, id);
  1023. set_item_metadata(idx, meta);
  1024. set_item_as_separator(idx, sep);
  1025. set_item_accelerator(idx, accel);
  1026. set_item_submenu(idx, subm);
  1027. }
  1028. }
  1029. // Hide on item selection determines whether or not the popup will close after item selection
  1030. void PopupMenu::set_hide_on_item_selection(bool p_enabled) {
  1031. hide_on_item_selection = p_enabled;
  1032. }
  1033. bool PopupMenu::is_hide_on_item_selection() const {
  1034. return hide_on_item_selection;
  1035. }
  1036. void PopupMenu::set_hide_on_checkable_item_selection(bool p_enabled) {
  1037. hide_on_checkable_item_selection = p_enabled;
  1038. }
  1039. bool PopupMenu::is_hide_on_checkable_item_selection() const {
  1040. return hide_on_checkable_item_selection;
  1041. }
  1042. void PopupMenu::set_hide_on_multistate_item_selection(bool p_enabled) {
  1043. hide_on_multistate_item_selection = p_enabled;
  1044. }
  1045. bool PopupMenu::is_hide_on_multistate_item_selection() const {
  1046. return hide_on_multistate_item_selection;
  1047. }
  1048. void PopupMenu::set_submenu_popup_delay(float p_time) {
  1049. if (p_time <= 0)
  1050. p_time = 0.01;
  1051. submenu_timer->set_wait_time(p_time);
  1052. }
  1053. float PopupMenu::get_submenu_popup_delay() const {
  1054. return submenu_timer->get_wait_time();
  1055. }
  1056. void PopupMenu::set_allow_search(bool p_allow) {
  1057. allow_search = p_allow;
  1058. }
  1059. bool PopupMenu::get_allow_search() const {
  1060. return allow_search;
  1061. }
  1062. void PopupMenu::set_hide_on_window_lose_focus(bool p_enabled) {
  1063. hide_on_window_lose_focus = p_enabled;
  1064. }
  1065. bool PopupMenu::is_hide_on_window_lose_focus() const {
  1066. return hide_on_window_lose_focus;
  1067. }
  1068. String PopupMenu::get_tooltip(const Point2 &p_pos) const {
  1069. int over = _get_mouse_over(p_pos);
  1070. if (over < 0 || over >= items.size())
  1071. return "";
  1072. return items[over].tooltip;
  1073. }
  1074. void PopupMenu::set_parent_rect(const Rect2 &p_rect) {
  1075. parent_rect = p_rect;
  1076. }
  1077. void PopupMenu::get_translatable_strings(List<String> *p_strings) const {
  1078. for (int i = 0; i < items.size(); i++) {
  1079. if (items[i].xl_text != "")
  1080. p_strings->push_back(items[i].xl_text);
  1081. }
  1082. }
  1083. void PopupMenu::add_autohide_area(const Rect2 &p_area) {
  1084. autohide_areas.push_back(p_area);
  1085. }
  1086. void PopupMenu::clear_autohide_areas() {
  1087. autohide_areas.clear();
  1088. }
  1089. void PopupMenu::_bind_methods() {
  1090. ClassDB::bind_method(D_METHOD("_gui_input"), &PopupMenu::_gui_input);
  1091. ClassDB::bind_method(D_METHOD("add_item", "label", "id", "accel"), &PopupMenu::add_item, DEFVAL(-1), DEFVAL(0));
  1092. ClassDB::bind_method(D_METHOD("add_icon_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_item, DEFVAL(-1), DEFVAL(0));
  1093. ClassDB::bind_method(D_METHOD("add_check_item", "label", "id", "accel"), &PopupMenu::add_check_item, DEFVAL(-1), DEFVAL(0));
  1094. ClassDB::bind_method(D_METHOD("add_icon_check_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_check_item, DEFVAL(-1), DEFVAL(0));
  1095. ClassDB::bind_method(D_METHOD("add_radio_check_item", "label", "id", "accel"), &PopupMenu::add_radio_check_item, DEFVAL(-1), DEFVAL(0));
  1096. ClassDB::bind_method(D_METHOD("add_icon_radio_check_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_radio_check_item, DEFVAL(-1), DEFVAL(0));
  1097. ClassDB::bind_method(D_METHOD("add_multistate_item", "label", "max_states", "default_state", "id", "accel"), &PopupMenu::add_multistate_item, DEFVAL(0), DEFVAL(-1), DEFVAL(0));
  1098. ClassDB::bind_method(D_METHOD("add_shortcut", "shortcut", "id", "global"), &PopupMenu::add_shortcut, DEFVAL(-1), DEFVAL(false));
  1099. ClassDB::bind_method(D_METHOD("add_icon_shortcut", "texture", "shortcut", "id", "global"), &PopupMenu::add_icon_shortcut, DEFVAL(-1), DEFVAL(false));
  1100. ClassDB::bind_method(D_METHOD("add_check_shortcut", "shortcut", "id", "global"), &PopupMenu::add_check_shortcut, DEFVAL(-1), DEFVAL(false));
  1101. ClassDB::bind_method(D_METHOD("add_icon_check_shortcut", "texture", "shortcut", "id", "global"), &PopupMenu::add_icon_check_shortcut, DEFVAL(-1), DEFVAL(false));
  1102. ClassDB::bind_method(D_METHOD("add_radio_check_shortcut", "shortcut", "id", "global"), &PopupMenu::add_radio_check_shortcut, DEFVAL(-1), DEFVAL(false));
  1103. ClassDB::bind_method(D_METHOD("add_icon_radio_check_shortcut", "texture", "shortcut", "id", "global"), &PopupMenu::add_icon_radio_check_shortcut, DEFVAL(-1), DEFVAL(false));
  1104. ClassDB::bind_method(D_METHOD("add_submenu_item", "label", "submenu", "id"), &PopupMenu::add_submenu_item, DEFVAL(-1));
  1105. ClassDB::bind_method(D_METHOD("set_item_text", "idx", "text"), &PopupMenu::set_item_text);
  1106. ClassDB::bind_method(D_METHOD("set_item_icon", "idx", "icon"), &PopupMenu::set_item_icon);
  1107. ClassDB::bind_method(D_METHOD("set_item_checked", "idx", "checked"), &PopupMenu::set_item_checked);
  1108. ClassDB::bind_method(D_METHOD("set_item_id", "idx", "id"), &PopupMenu::set_item_id);
  1109. ClassDB::bind_method(D_METHOD("set_item_accelerator", "idx", "accel"), &PopupMenu::set_item_accelerator);
  1110. ClassDB::bind_method(D_METHOD("set_item_metadata", "idx", "metadata"), &PopupMenu::set_item_metadata);
  1111. ClassDB::bind_method(D_METHOD("set_item_disabled", "idx", "disabled"), &PopupMenu::set_item_disabled);
  1112. ClassDB::bind_method(D_METHOD("set_item_submenu", "idx", "submenu"), &PopupMenu::set_item_submenu);
  1113. ClassDB::bind_method(D_METHOD("set_item_as_separator", "idx", "enable"), &PopupMenu::set_item_as_separator);
  1114. ClassDB::bind_method(D_METHOD("set_item_as_checkable", "idx", "enable"), &PopupMenu::set_item_as_checkable);
  1115. ClassDB::bind_method(D_METHOD("set_item_as_radio_checkable", "idx", "enable"), &PopupMenu::set_item_as_radio_checkable);
  1116. ClassDB::bind_method(D_METHOD("set_item_tooltip", "idx", "tooltip"), &PopupMenu::set_item_tooltip);
  1117. ClassDB::bind_method(D_METHOD("set_item_shortcut", "idx", "shortcut", "global"), &PopupMenu::set_item_shortcut, DEFVAL(false));
  1118. ClassDB::bind_method(D_METHOD("set_item_multistate", "idx", "state"), &PopupMenu::set_item_multistate);
  1119. ClassDB::bind_method(D_METHOD("set_item_shortcut_disabled", "idx", "disabled"), &PopupMenu::set_item_shortcut_disabled);
  1120. ClassDB::bind_method(D_METHOD("toggle_item_checked", "idx"), &PopupMenu::toggle_item_checked);
  1121. ClassDB::bind_method(D_METHOD("toggle_item_multistate", "idx"), &PopupMenu::toggle_item_multistate);
  1122. ClassDB::bind_method(D_METHOD("get_item_text", "idx"), &PopupMenu::get_item_text);
  1123. ClassDB::bind_method(D_METHOD("get_item_icon", "idx"), &PopupMenu::get_item_icon);
  1124. ClassDB::bind_method(D_METHOD("is_item_checked", "idx"), &PopupMenu::is_item_checked);
  1125. ClassDB::bind_method(D_METHOD("get_item_id", "idx"), &PopupMenu::get_item_id);
  1126. ClassDB::bind_method(D_METHOD("get_item_index", "id"), &PopupMenu::get_item_index);
  1127. ClassDB::bind_method(D_METHOD("get_item_accelerator", "idx"), &PopupMenu::get_item_accelerator);
  1128. ClassDB::bind_method(D_METHOD("get_item_metadata", "idx"), &PopupMenu::get_item_metadata);
  1129. ClassDB::bind_method(D_METHOD("is_item_disabled", "idx"), &PopupMenu::is_item_disabled);
  1130. ClassDB::bind_method(D_METHOD("get_item_submenu", "idx"), &PopupMenu::get_item_submenu);
  1131. ClassDB::bind_method(D_METHOD("is_item_separator", "idx"), &PopupMenu::is_item_separator);
  1132. ClassDB::bind_method(D_METHOD("is_item_checkable", "idx"), &PopupMenu::is_item_checkable);
  1133. ClassDB::bind_method(D_METHOD("is_item_radio_checkable", "idx"), &PopupMenu::is_item_radio_checkable);
  1134. ClassDB::bind_method(D_METHOD("is_item_shortcut_disabled", "idx"), &PopupMenu::is_item_shortcut_disabled);
  1135. ClassDB::bind_method(D_METHOD("get_item_tooltip", "idx"), &PopupMenu::get_item_tooltip);
  1136. ClassDB::bind_method(D_METHOD("get_item_shortcut", "idx"), &PopupMenu::get_item_shortcut);
  1137. ClassDB::bind_method(D_METHOD("get_current_index"), &PopupMenu::get_current_index);
  1138. ClassDB::bind_method(D_METHOD("get_item_count"), &PopupMenu::get_item_count);
  1139. ClassDB::bind_method(D_METHOD("remove_item", "idx"), &PopupMenu::remove_item);
  1140. ClassDB::bind_method(D_METHOD("add_separator", "label", "id"), &PopupMenu::add_separator, DEFVAL(String()), DEFVAL(-1));
  1141. ClassDB::bind_method(D_METHOD("clear"), &PopupMenu::clear);
  1142. ClassDB::bind_method(D_METHOD("_set_items"), &PopupMenu::_set_items);
  1143. ClassDB::bind_method(D_METHOD("_get_items"), &PopupMenu::_get_items);
  1144. ClassDB::bind_method(D_METHOD("set_hide_on_item_selection", "enable"), &PopupMenu::set_hide_on_item_selection);
  1145. ClassDB::bind_method(D_METHOD("is_hide_on_item_selection"), &PopupMenu::is_hide_on_item_selection);
  1146. ClassDB::bind_method(D_METHOD("set_hide_on_checkable_item_selection", "enable"), &PopupMenu::set_hide_on_checkable_item_selection);
  1147. ClassDB::bind_method(D_METHOD("is_hide_on_checkable_item_selection"), &PopupMenu::is_hide_on_checkable_item_selection);
  1148. ClassDB::bind_method(D_METHOD("set_hide_on_state_item_selection", "enable"), &PopupMenu::set_hide_on_multistate_item_selection);
  1149. ClassDB::bind_method(D_METHOD("is_hide_on_state_item_selection"), &PopupMenu::is_hide_on_multistate_item_selection);
  1150. ClassDB::bind_method(D_METHOD("set_submenu_popup_delay", "seconds"), &PopupMenu::set_submenu_popup_delay);
  1151. ClassDB::bind_method(D_METHOD("get_submenu_popup_delay"), &PopupMenu::get_submenu_popup_delay);
  1152. ClassDB::bind_method(D_METHOD("set_hide_on_window_lose_focus", "enable"), &PopupMenu::set_hide_on_window_lose_focus);
  1153. ClassDB::bind_method(D_METHOD("is_hide_on_window_lose_focus"), &PopupMenu::is_hide_on_window_lose_focus);
  1154. ClassDB::bind_method(D_METHOD("set_allow_search", "allow"), &PopupMenu::set_allow_search);
  1155. ClassDB::bind_method(D_METHOD("get_allow_search"), &PopupMenu::get_allow_search);
  1156. ClassDB::bind_method(D_METHOD("_submenu_timeout"), &PopupMenu::_submenu_timeout);
  1157. ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "items", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_items", "_get_items");
  1158. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_item_selection"), "set_hide_on_item_selection", "is_hide_on_item_selection");
  1159. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_checkable_item_selection"), "set_hide_on_checkable_item_selection", "is_hide_on_checkable_item_selection");
  1160. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_state_item_selection"), "set_hide_on_state_item_selection", "is_hide_on_state_item_selection");
  1161. ADD_PROPERTY(PropertyInfo(Variant::REAL, "submenu_popup_delay"), "set_submenu_popup_delay", "get_submenu_popup_delay");
  1162. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_search"), "set_allow_search", "get_allow_search");
  1163. ADD_SIGNAL(MethodInfo("id_pressed", PropertyInfo(Variant::INT, "id")));
  1164. ADD_SIGNAL(MethodInfo("id_focused", PropertyInfo(Variant::INT, "id")));
  1165. ADD_SIGNAL(MethodInfo("index_pressed", PropertyInfo(Variant::INT, "index")));
  1166. }
  1167. void PopupMenu::popup(const Rect2 &p_bounds) {
  1168. grab_click_focus();
  1169. moved = Vector2();
  1170. invalidated_click = true;
  1171. Popup::popup(p_bounds);
  1172. }
  1173. PopupMenu::PopupMenu() {
  1174. mouse_over = -1;
  1175. submenu_over = -1;
  1176. initial_button_mask = 0;
  1177. during_grabbed_click = false;
  1178. allow_search = false;
  1179. search_time_msec = 0;
  1180. search_string = "";
  1181. set_focus_mode(FOCUS_ALL);
  1182. set_as_toplevel(true);
  1183. set_hide_on_item_selection(true);
  1184. set_hide_on_checkable_item_selection(true);
  1185. set_hide_on_multistate_item_selection(false);
  1186. set_hide_on_window_lose_focus(true);
  1187. submenu_timer = memnew(Timer);
  1188. submenu_timer->set_wait_time(0.3);
  1189. submenu_timer->set_one_shot(true);
  1190. submenu_timer->connect("timeout", this, "_submenu_timeout");
  1191. add_child(submenu_timer);
  1192. }
  1193. PopupMenu::~PopupMenu() {
  1194. }