popup_menu.cpp 70 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135
  1. /*************************************************************************/
  2. /* popup_menu.cpp */
  3. /*************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /*************************************************************************/
  8. /* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
  9. /* Copyright (c) 2014-2022 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/config/project_settings.h"
  32. #include "core/input/input.h"
  33. #include "core/os/keyboard.h"
  34. #include "core/os/os.h"
  35. #include "core/string/print_string.h"
  36. #include "core/string/translation.h"
  37. #include "scene/gui/menu_bar.h"
  38. String PopupMenu::_get_accel_text(const Item &p_item) const {
  39. if (p_item.shortcut.is_valid()) {
  40. return p_item.shortcut->get_as_text();
  41. } else if (p_item.accel != Key::NONE) {
  42. return keycode_get_string(p_item.accel);
  43. }
  44. return String();
  45. }
  46. Size2 PopupMenu::_get_contents_minimum_size() const {
  47. int vseparation = get_theme_constant(SNAME("v_separation"));
  48. int hseparation = get_theme_constant(SNAME("h_separation"));
  49. Size2 minsize = get_theme_stylebox(SNAME("panel"))->get_minimum_size(); // Accounts for margin in the margin container
  50. minsize.x += scroll_container->get_v_scroll_bar()->get_size().width * 2; // Adds a buffer so that the scrollbar does not render over the top of content
  51. float max_w = 0.0;
  52. float icon_w = 0.0;
  53. int check_w = MAX(get_theme_icon(SNAME("checked"))->get_width(), get_theme_icon(SNAME("radio_checked"))->get_width()) + hseparation;
  54. int accel_max_w = 0;
  55. bool has_check = false;
  56. for (int i = 0; i < items.size(); i++) {
  57. Size2 size;
  58. Size2 icon_size = items[i].get_icon_size();
  59. size.height = _get_item_height(i);
  60. icon_w = MAX(icon_size.width, icon_w);
  61. size.width += items[i].indent * get_theme_constant(SNAME("indent"));
  62. if (items[i].checkable_type && !items[i].separator) {
  63. has_check = true;
  64. }
  65. size.width += items[i].text_buf->get_size().x;
  66. size.height += vseparation;
  67. if (items[i].accel != Key::NONE || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) {
  68. int accel_w = hseparation * 2;
  69. accel_w += items[i].accel_text_buf->get_size().x;
  70. accel_max_w = MAX(accel_w, accel_max_w);
  71. }
  72. if (!items[i].submenu.is_empty()) {
  73. size.width += get_theme_icon(SNAME("submenu"))->get_width();
  74. }
  75. max_w = MAX(max_w, size.width);
  76. minsize.height += size.height;
  77. }
  78. int item_side_padding = get_theme_constant(SNAME("item_start_padding")) + get_theme_constant(SNAME("item_end_padding"));
  79. minsize.width += max_w + icon_w + accel_max_w + item_side_padding;
  80. if (has_check) {
  81. minsize.width += check_w;
  82. }
  83. if (is_inside_tree()) {
  84. int height_limit = get_usable_parent_rect().size.height;
  85. if (minsize.height > height_limit) {
  86. minsize.height = height_limit;
  87. }
  88. }
  89. return minsize;
  90. }
  91. int PopupMenu::_get_item_height(int p_item) const {
  92. ERR_FAIL_INDEX_V(p_item, items.size(), 0);
  93. int icon_height = items[p_item].get_icon_size().height;
  94. if (items[p_item].checkable_type && !items[p_item].separator) {
  95. icon_height = MAX(icon_height, MAX(get_theme_icon(SNAME("checked"))->get_height(), get_theme_icon(SNAME("radio_checked"))->get_height()));
  96. }
  97. int text_height = items[p_item].text_buf->get_size().height;
  98. if (text_height == 0 && !items[p_item].separator) {
  99. text_height = get_theme_font(SNAME("font"))->get_height(get_theme_font_size(SNAME("font_size")));
  100. }
  101. int separator_height = 0;
  102. if (items[p_item].separator) {
  103. separator_height = MAX(get_theme_stylebox(SNAME("separator"))->get_minimum_size().height, MAX(get_theme_stylebox(SNAME("labeled_separator_left"))->get_minimum_size().height, get_theme_stylebox(SNAME("labeled_separator_right"))->get_minimum_size().height));
  104. }
  105. return MAX(separator_height, MAX(text_height, icon_height));
  106. }
  107. int PopupMenu::_get_items_total_height() const {
  108. int vsep = get_theme_constant(SNAME("v_separation"));
  109. // Get total height of all items by taking max of icon height and font height
  110. int items_total_height = 0;
  111. for (int i = 0; i < items.size(); i++) {
  112. items_total_height += _get_item_height(i) + vsep;
  113. }
  114. // Subtract a separator which is not needed for the last item.
  115. return items_total_height - vsep;
  116. }
  117. int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
  118. if (p_over.x < 0 || p_over.x >= get_size().width) {
  119. return -1;
  120. }
  121. Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); // Accounts for margin in the margin container
  122. int vseparation = get_theme_constant(SNAME("v_separation"));
  123. Point2 ofs = style->get_offset() + Point2(0, vseparation / 2);
  124. if (ofs.y > p_over.y) {
  125. return -1;
  126. }
  127. for (int i = 0; i < items.size(); i++) {
  128. ofs.y += i > 0 ? vseparation : (float)vseparation / 2;
  129. ofs.y += _get_item_height(i);
  130. if (p_over.y - control->get_position().y < ofs.y) {
  131. return i;
  132. }
  133. }
  134. return -1;
  135. }
  136. void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) {
  137. Node *n = get_node(items[p_over].submenu);
  138. ERR_FAIL_COND_MSG(!n, "Item subnode does not exist: " + items[p_over].submenu + ".");
  139. Popup *submenu_popup = Object::cast_to<Popup>(n);
  140. ERR_FAIL_COND_MSG(!submenu_popup, "Item subnode is not a Popup: " + items[p_over].submenu + ".");
  141. if (submenu_popup->is_visible()) {
  142. return; // Already visible.
  143. }
  144. Ref<StyleBox> style = get_theme_stylebox(SNAME("panel"));
  145. int vsep = get_theme_constant(SNAME("v_separation"));
  146. Point2 this_pos = get_position();
  147. Rect2 this_rect(this_pos, get_size());
  148. float scroll_offset = control->get_position().y;
  149. submenu_popup->reset_size(); // Shrink the popup size to its contents.
  150. Size2 submenu_size = submenu_popup->get_size();
  151. Point2 submenu_pos;
  152. if (control->is_layout_rtl()) {
  153. submenu_pos = this_pos + Point2(-submenu_size.width, items[p_over]._ofs_cache + scroll_offset);
  154. } else {
  155. submenu_pos = this_pos + Point2(this_rect.size.width, items[p_over]._ofs_cache + scroll_offset);
  156. }
  157. // Fix pos if going outside parent rect.
  158. if (submenu_pos.x < get_parent_rect().position.x) {
  159. submenu_pos.x = this_pos.x + submenu_size.width;
  160. }
  161. if (submenu_pos.x + submenu_size.width > get_parent_rect().position.x + get_parent_rect().size.width) {
  162. submenu_pos.x = this_pos.x - submenu_size.width;
  163. }
  164. submenu_popup->set_position(submenu_pos);
  165. PopupMenu *submenu_pum = Object::cast_to<PopupMenu>(submenu_popup);
  166. if (!submenu_pum) {
  167. submenu_popup->popup();
  168. return;
  169. }
  170. submenu_pum->activated_by_keyboard = p_by_keyboard;
  171. // If not triggered by the mouse, start the popup with its first enabled item focused.
  172. if (p_by_keyboard) {
  173. for (int i = 0; i < submenu_pum->get_item_count(); i++) {
  174. if (!submenu_pum->is_item_disabled(i)) {
  175. submenu_pum->set_current_index(i);
  176. break;
  177. }
  178. }
  179. }
  180. submenu_pum->popup();
  181. // Set autohide areas.
  182. Rect2 safe_area = this_rect;
  183. safe_area.position.y += items[p_over]._ofs_cache + scroll_offset + style->get_offset().height - vsep / 2;
  184. safe_area.size.y = items[p_over]._height_cache;
  185. DisplayServer::get_singleton()->window_set_popup_safe_rect(submenu_popup->get_window_id(), safe_area);
  186. // Make the position of the parent popup relative to submenu popup.
  187. this_rect.position = this_rect.position - submenu_pum->get_position();
  188. // Autohide area above the submenu item.
  189. submenu_pum->clear_autohide_areas();
  190. submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, items[p_over]._ofs_cache + scroll_offset + style->get_offset().height - vsep / 2));
  191. // If there is an area below the submenu item, add an autohide area there.
  192. if (items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset <= control->get_size().height) {
  193. int from = items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset + vsep / 2 + style->get_offset().height;
  194. submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from));
  195. }
  196. }
  197. void PopupMenu::_parent_focused() {
  198. if (is_embedded()) {
  199. Point2 mouse_pos_adjusted;
  200. Window *window_parent = Object::cast_to<Window>(get_parent()->get_viewport());
  201. while (window_parent) {
  202. if (!window_parent->is_embedded()) {
  203. mouse_pos_adjusted += window_parent->get_position();
  204. break;
  205. }
  206. window_parent = Object::cast_to<Window>(window_parent->get_parent()->get_viewport());
  207. }
  208. Rect2 safe_area = DisplayServer::get_singleton()->window_get_popup_safe_rect(get_window_id());
  209. Point2 pos = DisplayServer::get_singleton()->mouse_get_position() - mouse_pos_adjusted;
  210. if (safe_area == Rect2i() || !safe_area.has_point(pos)) {
  211. Popup::_parent_focused();
  212. } else {
  213. grab_focus();
  214. }
  215. }
  216. }
  217. void PopupMenu::_submenu_timeout() {
  218. if (mouse_over == submenu_over) {
  219. _activate_submenu(mouse_over);
  220. }
  221. submenu_over = -1;
  222. }
  223. void PopupMenu::gui_input(const Ref<InputEvent> &p_event) {
  224. ERR_FAIL_COND(p_event.is_null());
  225. if (!items.is_empty()) {
  226. if (p_event->is_action("ui_down") && p_event->is_pressed()) {
  227. int search_from = mouse_over + 1;
  228. if (search_from >= items.size()) {
  229. search_from = 0;
  230. }
  231. bool match_found = false;
  232. for (int i = search_from; i < items.size(); i++) {
  233. if (!items[i].separator && !items[i].disabled) {
  234. mouse_over = i;
  235. emit_signal(SNAME("id_focused"), i);
  236. scroll_to_item(i);
  237. control->queue_redraw();
  238. set_input_as_handled();
  239. match_found = true;
  240. break;
  241. }
  242. }
  243. if (!match_found) {
  244. // If the last item is not selectable, try re-searching from the start.
  245. for (int i = 0; i < search_from; i++) {
  246. if (!items[i].separator && !items[i].disabled) {
  247. mouse_over = i;
  248. emit_signal(SNAME("id_focused"), i);
  249. scroll_to_item(i);
  250. control->queue_redraw();
  251. set_input_as_handled();
  252. break;
  253. }
  254. }
  255. }
  256. } else if (p_event->is_action("ui_up") && p_event->is_pressed()) {
  257. int search_from = mouse_over - 1;
  258. if (search_from < 0) {
  259. search_from = items.size() - 1;
  260. }
  261. bool match_found = false;
  262. for (int i = search_from; i >= 0; i--) {
  263. if (!items[i].separator && !items[i].disabled) {
  264. mouse_over = i;
  265. emit_signal(SNAME("id_focused"), i);
  266. scroll_to_item(i);
  267. control->queue_redraw();
  268. set_input_as_handled();
  269. match_found = true;
  270. break;
  271. }
  272. }
  273. if (!match_found) {
  274. // If the first item is not selectable, try re-searching from the end.
  275. for (int i = items.size() - 1; i >= search_from; i--) {
  276. if (!items[i].separator && !items[i].disabled) {
  277. mouse_over = i;
  278. emit_signal(SNAME("id_focused"), i);
  279. scroll_to_item(i);
  280. control->queue_redraw();
  281. set_input_as_handled();
  282. break;
  283. }
  284. }
  285. }
  286. } else if (p_event->is_action("ui_left") && p_event->is_pressed()) {
  287. Node *n = get_parent();
  288. if (n) {
  289. if (Object::cast_to<PopupMenu>(n)) {
  290. hide();
  291. set_input_as_handled();
  292. } else if (Object::cast_to<MenuBar>(n)) {
  293. Object::cast_to<MenuBar>(n)->gui_input(p_event);
  294. set_input_as_handled();
  295. return;
  296. }
  297. }
  298. } else if (p_event->is_action("ui_right") && p_event->is_pressed()) {
  299. if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator && !items[mouse_over].submenu.is_empty() && submenu_over != mouse_over) {
  300. _activate_submenu(mouse_over, true);
  301. set_input_as_handled();
  302. } else {
  303. Node *n = get_parent();
  304. if (n && Object::cast_to<MenuBar>(n)) {
  305. Object::cast_to<MenuBar>(n)->gui_input(p_event);
  306. set_input_as_handled();
  307. return;
  308. }
  309. }
  310. } else if (p_event->is_action("ui_accept") && p_event->is_pressed()) {
  311. if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator) {
  312. if (!items[mouse_over].submenu.is_empty() && submenu_over != mouse_over) {
  313. _activate_submenu(mouse_over, true);
  314. } else {
  315. activate_item(mouse_over);
  316. }
  317. set_input_as_handled();
  318. }
  319. }
  320. }
  321. // Make an area which does not include v scrollbar, so that items are not activated when dragging scrollbar.
  322. Rect2 item_clickable_area = scroll_container->get_rect();
  323. if (scroll_container->get_v_scroll_bar()->is_visible_in_tree()) {
  324. if (is_layout_rtl()) {
  325. item_clickable_area.position.x += scroll_container->get_v_scroll_bar()->get_size().width;
  326. } else {
  327. item_clickable_area.size.width -= scroll_container->get_v_scroll_bar()->get_size().width;
  328. }
  329. }
  330. Ref<InputEventMouseButton> b = p_event;
  331. if (b.is_valid()) {
  332. if (!item_clickable_area.has_point(b->get_position())) {
  333. return;
  334. }
  335. MouseButton button_idx = b->get_button_index();
  336. if (!b->is_pressed()) {
  337. // Activate the item on release of either the left mouse button or
  338. // any mouse button held down when the popup was opened.
  339. // This allows for opening the popup and triggering an action in a single mouse click.
  340. if (button_idx == MouseButton::LEFT || (initial_button_mask & mouse_button_to_mask(button_idx)) != MouseButton::NONE) {
  341. bool was_during_grabbed_click = during_grabbed_click;
  342. during_grabbed_click = false;
  343. initial_button_mask = MouseButton::NONE;
  344. // Disable clicks under a time threshold to avoid selection right when opening the popup.
  345. uint64_t now = OS::get_singleton()->get_ticks_msec();
  346. uint64_t diff = now - popup_time_msec;
  347. if (diff < 150) {
  348. return;
  349. }
  350. int over = _get_mouse_over(b->get_position());
  351. if (over < 0) {
  352. if (!was_during_grabbed_click) {
  353. hide();
  354. }
  355. return;
  356. }
  357. if (items[over].separator || items[over].disabled) {
  358. return;
  359. }
  360. if (!items[over].submenu.is_empty()) {
  361. _activate_submenu(over);
  362. return;
  363. }
  364. activate_item(over);
  365. }
  366. }
  367. }
  368. Ref<InputEventMouseMotion> m = p_event;
  369. if (m.is_valid()) {
  370. if (m->get_velocity().is_equal_approx(Vector2())) {
  371. return;
  372. }
  373. activated_by_keyboard = false;
  374. for (const Rect2 &E : autohide_areas) {
  375. if (!Rect2(Point2(), get_size()).has_point(m->get_position()) && E.has_point(m->get_position())) {
  376. _close_pressed();
  377. return;
  378. }
  379. }
  380. if (!item_clickable_area.has_point(m->get_position())) {
  381. return;
  382. }
  383. int over = _get_mouse_over(m->get_position());
  384. int id = (over < 0 || items[over].separator || items[over].disabled) ? -1 : (items[over].id >= 0 ? items[over].id : over);
  385. if (id < 0) {
  386. mouse_over = -1;
  387. control->queue_redraw();
  388. return;
  389. }
  390. if (!items[over].submenu.is_empty() && submenu_over != over) {
  391. submenu_over = over;
  392. submenu_timer->start();
  393. }
  394. if (over != mouse_over) {
  395. mouse_over = over;
  396. control->queue_redraw();
  397. }
  398. }
  399. Ref<InputEventKey> k = p_event;
  400. if (allow_search && k.is_valid() && k->get_unicode() && k->is_pressed()) {
  401. uint64_t now = OS::get_singleton()->get_ticks_msec();
  402. uint64_t diff = now - search_time_msec;
  403. uint64_t max_interval = uint64_t(GLOBAL_DEF("gui/timers/incremental_search_max_interval_msec", 2000));
  404. search_time_msec = now;
  405. if (diff > max_interval) {
  406. search_string = "";
  407. }
  408. if (String::chr(k->get_unicode()) != search_string) {
  409. search_string += String::chr(k->get_unicode());
  410. }
  411. for (int i = mouse_over + 1; i <= items.size(); i++) {
  412. if (i == items.size()) {
  413. if (mouse_over <= 0) {
  414. break;
  415. } else {
  416. i = 0;
  417. }
  418. }
  419. if (i == mouse_over) {
  420. break;
  421. }
  422. if (items[i].text.findn(search_string) == 0) {
  423. mouse_over = i;
  424. emit_signal(SNAME("id_focused"), i);
  425. scroll_to_item(i);
  426. control->queue_redraw();
  427. set_input_as_handled();
  428. break;
  429. }
  430. }
  431. }
  432. }
  433. void PopupMenu::_draw_items() {
  434. control->set_custom_minimum_size(Size2(0, _get_items_total_height()));
  435. RID ci = control->get_canvas_item();
  436. Size2 margin_size;
  437. margin_size.width = margin_container->get_theme_constant(SNAME("margin_right")) + margin_container->get_theme_constant(SNAME("margin_left"));
  438. margin_size.height = margin_container->get_theme_constant(SNAME("margin_top")) + margin_container->get_theme_constant(SNAME("margin_bottom"));
  439. // Space between the item content and the sides of popup menu.
  440. int item_start_padding = get_theme_constant(SNAME("item_start_padding"));
  441. int item_end_padding = get_theme_constant(SNAME("item_end_padding"));
  442. bool rtl = control->is_layout_rtl();
  443. Ref<StyleBox> style = get_theme_stylebox(SNAME("panel"));
  444. Ref<StyleBox> hover = get_theme_stylebox(SNAME("hover"));
  445. // In Item::checkable_type enum order (less the non-checkable member), with disabled repeated at the end.
  446. Ref<Texture2D> check[] = { get_theme_icon(SNAME("checked")), get_theme_icon(SNAME("radio_checked")), get_theme_icon(SNAME("checked_disabled")), get_theme_icon(SNAME("radio_checked_disabled")) };
  447. Ref<Texture2D> uncheck[] = { get_theme_icon(SNAME("unchecked")), get_theme_icon(SNAME("radio_unchecked")), get_theme_icon(SNAME("unchecked_disabled")), get_theme_icon(SNAME("radio_unchecked_disabled")) };
  448. Ref<Texture2D> submenu;
  449. if (rtl) {
  450. submenu = get_theme_icon(SNAME("submenu_mirrored"));
  451. } else {
  452. submenu = get_theme_icon(SNAME("submenu"));
  453. }
  454. Ref<StyleBox> separator = get_theme_stylebox(SNAME("separator"));
  455. Ref<StyleBox> labeled_separator_left = get_theme_stylebox(SNAME("labeled_separator_left"));
  456. Ref<StyleBox> labeled_separator_right = get_theme_stylebox(SNAME("labeled_separator_right"));
  457. int vseparation = get_theme_constant(SNAME("v_separation"));
  458. int hseparation = get_theme_constant(SNAME("h_separation"));
  459. Color font_color = get_theme_color(SNAME("font_color"));
  460. Color font_disabled_color = get_theme_color(SNAME("font_disabled_color"));
  461. Color font_accelerator_color = get_theme_color(SNAME("font_accelerator_color"));
  462. Color font_hover_color = get_theme_color(SNAME("font_hover_color"));
  463. Color font_separator_color = get_theme_color(SNAME("font_separator_color"));
  464. float scroll_width = scroll_container->get_v_scroll_bar()->is_visible_in_tree() ? scroll_container->get_v_scroll_bar()->get_size().width : 0;
  465. float display_width = control->get_size().width - scroll_width;
  466. // Find the widest icon and whether any items have a checkbox, and store the offsets for each.
  467. float icon_ofs = 0.0;
  468. bool has_check = false;
  469. for (int i = 0; i < items.size(); i++) {
  470. if (items[i].separator) {
  471. continue;
  472. }
  473. icon_ofs = MAX(items[i].get_icon_size().width, icon_ofs);
  474. if (items[i].checkable_type) {
  475. has_check = true;
  476. }
  477. }
  478. if (icon_ofs > 0.0) {
  479. icon_ofs += hseparation;
  480. }
  481. float check_ofs = 0.0;
  482. if (has_check) {
  483. for (int i = 0; i < 4; i++) {
  484. check_ofs = MAX(check_ofs, check[i]->get_width());
  485. check_ofs = MAX(check_ofs, uncheck[i]->get_width());
  486. }
  487. check_ofs += hseparation;
  488. }
  489. Point2 ofs = Point2();
  490. // Loop through all items and draw each.
  491. for (int i = 0; i < items.size(); i++) {
  492. // For the first item only add half a separation. For all other items, add a whole separation to the offset.
  493. ofs.y += i > 0 ? vseparation : (float)vseparation / 2;
  494. _shape_item(i);
  495. Point2 item_ofs = ofs;
  496. Size2 icon_size = items[i].get_icon_size();
  497. float h = _get_item_height(i);
  498. if (i == mouse_over) {
  499. if (rtl) {
  500. hover->draw(ci, Rect2(item_ofs + Point2(scroll_width, -vseparation / 2), Size2(display_width, h + vseparation)));
  501. } else {
  502. hover->draw(ci, Rect2(item_ofs + Point2(0, -vseparation / 2), Size2(display_width, h + vseparation)));
  503. }
  504. }
  505. String text = items[i].xl_text;
  506. // Separator
  507. item_ofs.x += items[i].indent * get_theme_constant(SNAME("indent"));
  508. if (items[i].separator) {
  509. if (!text.is_empty() || !items[i].icon.is_null()) {
  510. int content_size = items[i].text_buf->get_size().width + hseparation * 2;
  511. if (!items[i].icon.is_null()) {
  512. content_size += icon_size.width + hseparation;
  513. }
  514. int content_center = display_width / 2;
  515. int content_left = content_center - content_size / 2;
  516. int content_right = content_center + content_size / 2;
  517. if (content_left > item_ofs.x) {
  518. int sep_h = labeled_separator_left->get_center_size().height + labeled_separator_left->get_minimum_size().height;
  519. int sep_ofs = Math::floor((h - sep_h) / 2.0);
  520. labeled_separator_left->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(MAX(0, content_left - item_ofs.x), sep_h)));
  521. }
  522. if (content_right < display_width) {
  523. int sep_h = labeled_separator_right->get_center_size().height + labeled_separator_right->get_minimum_size().height;
  524. int sep_ofs = Math::floor((h - sep_h) / 2.0);
  525. labeled_separator_right->draw(ci, Rect2(Point2(content_right, item_ofs.y + sep_ofs), Size2(MAX(0, display_width - content_right), sep_h)));
  526. }
  527. } else {
  528. int sep_h = separator->get_center_size().height + separator->get_minimum_size().height;
  529. int sep_ofs = Math::floor((h - sep_h) / 2.0);
  530. separator->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(display_width, sep_h)));
  531. }
  532. }
  533. Color icon_color(1, 1, 1, items[i].disabled && !items[i].separator ? 0.5 : 1);
  534. // For non-separator items, add some padding for the content.
  535. item_ofs.x += item_start_padding;
  536. // Checkboxes
  537. if (items[i].checkable_type && !items[i].separator) {
  538. int disabled = int(items[i].disabled) * 2;
  539. Texture2D *icon = (items[i].checked ? check[items[i].checkable_type - 1 + disabled] : uncheck[items[i].checkable_type - 1 + disabled]).ptr();
  540. if (rtl) {
  541. icon->draw(ci, Size2(control->get_size().width - item_ofs.x - icon->get_width(), item_ofs.y) + Point2(0, Math::floor((h - icon->get_height()) / 2.0)), icon_color);
  542. } else {
  543. icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon->get_height()) / 2.0)), icon_color);
  544. }
  545. }
  546. int separator_ofs = (display_width - items[i].text_buf->get_size().width) / 2;
  547. // Icon
  548. if (!items[i].icon.is_null()) {
  549. if (items[i].separator) {
  550. separator_ofs -= (icon_size.width + hseparation) / 2;
  551. if (rtl) {
  552. items[i].icon->draw(ci, Size2(control->get_size().width - item_ofs.x - separator_ofs - icon_size.width, item_ofs.y) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color);
  553. } else {
  554. items[i].icon->draw(ci, item_ofs + Size2(separator_ofs, 0) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color);
  555. }
  556. } else {
  557. if (rtl) {
  558. items[i].icon->draw(ci, Size2(control->get_size().width - item_ofs.x - check_ofs - icon_size.width, item_ofs.y) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color);
  559. } else {
  560. items[i].icon->draw(ci, item_ofs + Size2(check_ofs, 0) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color);
  561. }
  562. }
  563. }
  564. // Submenu arrow on right hand side.
  565. if (!items[i].submenu.is_empty()) {
  566. if (rtl) {
  567. submenu->draw(ci, Point2(scroll_width + style->get_margin(SIDE_LEFT) + item_end_padding, item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color);
  568. } else {
  569. submenu->draw(ci, Point2(display_width - style->get_margin(SIDE_RIGHT) - submenu->get_width() - item_end_padding, item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color);
  570. }
  571. }
  572. Color font_outline_color = get_theme_color(SNAME("font_outline_color"));
  573. int outline_size = get_theme_constant(SNAME("outline_size"));
  574. // Text
  575. if (items[i].separator) {
  576. Color font_separator_outline_color = get_theme_color(SNAME("font_separator_outline_color"));
  577. int separator_outline_size = get_theme_constant(SNAME("separator_outline_size"));
  578. if (!text.is_empty()) {
  579. Vector2 text_pos = Point2(separator_ofs, item_ofs.y + Math::floor((h - items[i].text_buf->get_size().y) / 2.0));
  580. if (!rtl && !items[i].icon.is_null()) {
  581. text_pos.x += icon_size.width + hseparation;
  582. }
  583. if (separator_outline_size > 0 && font_separator_outline_color.a > 0) {
  584. items[i].text_buf->draw_outline(ci, text_pos, separator_outline_size, font_separator_outline_color);
  585. }
  586. items[i].text_buf->draw(ci, text_pos, font_separator_color);
  587. }
  588. } else {
  589. item_ofs.x += icon_ofs + check_ofs;
  590. if (rtl) {
  591. Vector2 text_pos = Size2(control->get_size().width - items[i].text_buf->get_size().width - item_ofs.x, item_ofs.y) + Point2(0, Math::floor((h - items[i].text_buf->get_size().y) / 2.0));
  592. if (outline_size > 0 && font_outline_color.a > 0) {
  593. items[i].text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color);
  594. }
  595. items[i].text_buf->draw(ci, text_pos, items[i].disabled ? font_disabled_color : (i == mouse_over ? font_hover_color : font_color));
  596. } else {
  597. Vector2 text_pos = item_ofs + Point2(0, Math::floor((h - items[i].text_buf->get_size().y) / 2.0));
  598. if (outline_size > 0 && font_outline_color.a > 0) {
  599. items[i].text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color);
  600. }
  601. items[i].text_buf->draw(ci, text_pos, items[i].disabled ? font_disabled_color : (i == mouse_over ? font_hover_color : font_color));
  602. }
  603. }
  604. // Accelerator / Shortcut
  605. if (items[i].accel != Key::NONE || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) {
  606. if (rtl) {
  607. item_ofs.x = scroll_width + style->get_margin(SIDE_LEFT) + item_end_padding;
  608. } else {
  609. item_ofs.x = display_width - style->get_margin(SIDE_RIGHT) - items[i].accel_text_buf->get_size().x - item_end_padding;
  610. }
  611. Vector2 text_pos = item_ofs + Point2(0, Math::floor((h - items[i].text_buf->get_size().y) / 2.0));
  612. if (outline_size > 0 && font_outline_color.a > 0) {
  613. items[i].accel_text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color);
  614. }
  615. items[i].accel_text_buf->draw(ci, text_pos, i == mouse_over ? font_hover_color : font_accelerator_color);
  616. }
  617. // Cache the item vertical offset from the first item and the height.
  618. items.write[i]._ofs_cache = ofs.y;
  619. items.write[i]._height_cache = h;
  620. ofs.y += h;
  621. }
  622. }
  623. void PopupMenu::_draw_background() {
  624. Ref<StyleBox> style = get_theme_stylebox(SNAME("panel"));
  625. RID ci2 = margin_container->get_canvas_item();
  626. style->draw(ci2, Rect2(Point2(), margin_container->get_size()));
  627. }
  628. void PopupMenu::_minimum_lifetime_timeout() {
  629. close_allowed = true;
  630. // If the mouse still isn't in this popup after timer expires, close.
  631. if (!activated_by_keyboard && !get_visible_rect().has_point(get_mouse_position())) {
  632. _close_pressed();
  633. }
  634. }
  635. void PopupMenu::_close_pressed() {
  636. // Only apply minimum lifetime to submenus.
  637. PopupMenu *parent_pum = Object::cast_to<PopupMenu>(get_parent());
  638. if (!parent_pum) {
  639. Popup::_close_pressed();
  640. return;
  641. }
  642. // If the timer has expired, close. If timer is still running, do nothing.
  643. if (close_allowed) {
  644. close_allowed = false;
  645. Popup::_close_pressed();
  646. } else if (minimum_lifetime_timer->is_stopped()) {
  647. minimum_lifetime_timer->start();
  648. }
  649. }
  650. void PopupMenu::_shape_item(int p_item) {
  651. if (items.write[p_item].dirty) {
  652. items.write[p_item].text_buf->clear();
  653. Ref<Font> font = get_theme_font(items[p_item].separator ? SNAME("font_separator") : SNAME("font"));
  654. int font_size = get_theme_font_size(items[p_item].separator ? SNAME("font_separator_size") : SNAME("font_size"));
  655. if (items[p_item].text_direction == Control::TEXT_DIRECTION_INHERITED) {
  656. items.write[p_item].text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
  657. } else {
  658. items.write[p_item].text_buf->set_direction((TextServer::Direction)items[p_item].text_direction);
  659. }
  660. items.write[p_item].text_buf->add_string(items.write[p_item].xl_text, font, font_size, items[p_item].language);
  661. items.write[p_item].accel_text_buf->clear();
  662. items.write[p_item].accel_text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
  663. items.write[p_item].accel_text_buf->add_string(_get_accel_text(items.write[p_item]), font, font_size);
  664. items.write[p_item].dirty = false;
  665. }
  666. }
  667. void PopupMenu::_menu_changed() {
  668. emit_signal(SNAME("menu_changed"));
  669. }
  670. void PopupMenu::add_child_notify(Node *p_child) {
  671. Window::add_child_notify(p_child);
  672. PopupMenu *pm = Object::cast_to<PopupMenu>(p_child);
  673. if (!pm) {
  674. return;
  675. }
  676. p_child->connect("menu_changed", callable_mp(this, &PopupMenu::_menu_changed));
  677. _menu_changed();
  678. }
  679. void PopupMenu::remove_child_notify(Node *p_child) {
  680. Window::remove_child_notify(p_child);
  681. PopupMenu *pm = Object::cast_to<PopupMenu>(p_child);
  682. if (!pm) {
  683. return;
  684. }
  685. p_child->disconnect("menu_changed", callable_mp(this, &PopupMenu::_menu_changed));
  686. _menu_changed();
  687. }
  688. void PopupMenu::_notification(int p_what) {
  689. switch (p_what) {
  690. case NOTIFICATION_ENTER_TREE: {
  691. PopupMenu *pm = Object::cast_to<PopupMenu>(get_parent());
  692. if (pm) {
  693. // Inherit submenu's popup delay time from parent menu.
  694. float pm_delay = pm->get_submenu_popup_delay();
  695. set_submenu_popup_delay(pm_delay);
  696. }
  697. } break;
  698. case NOTIFICATION_THEME_CHANGED:
  699. case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
  700. case NOTIFICATION_TRANSLATION_CHANGED: {
  701. for (int i = 0; i < items.size(); i++) {
  702. items.write[i].xl_text = atr(items[i].text);
  703. items.write[i].dirty = true;
  704. _shape_item(i);
  705. }
  706. child_controls_changed();
  707. _menu_changed();
  708. control->queue_redraw();
  709. } break;
  710. case NOTIFICATION_WM_MOUSE_ENTER: {
  711. grab_focus();
  712. } break;
  713. case NOTIFICATION_WM_MOUSE_EXIT: {
  714. if (mouse_over >= 0 && (items[mouse_over].submenu.is_empty() || submenu_over != -1)) {
  715. mouse_over = -1;
  716. control->queue_redraw();
  717. }
  718. } break;
  719. case NOTIFICATION_POST_POPUP: {
  720. initial_button_mask = Input::get_singleton()->get_mouse_button_mask();
  721. during_grabbed_click = (bool)initial_button_mask;
  722. } break;
  723. case NOTIFICATION_INTERNAL_PROCESS: {
  724. // Only used when using operating system windows.
  725. if (!activated_by_keyboard && !is_embedded() && autohide_areas.size()) {
  726. Point2 mouse_pos = DisplayServer::get_singleton()->mouse_get_position();
  727. mouse_pos -= get_position();
  728. for (const Rect2 &E : autohide_areas) {
  729. if (!Rect2(Point2(), get_size()).has_point(mouse_pos) && E.has_point(mouse_pos)) {
  730. _close_pressed();
  731. return;
  732. }
  733. }
  734. }
  735. } break;
  736. case NOTIFICATION_VISIBILITY_CHANGED: {
  737. if (!is_visible()) {
  738. if (mouse_over >= 0) {
  739. mouse_over = -1;
  740. control->queue_redraw();
  741. }
  742. for (int i = 0; i < items.size(); i++) {
  743. if (items[i].submenu.is_empty()) {
  744. continue;
  745. }
  746. Node *n = get_node(items[i].submenu);
  747. if (!n) {
  748. continue;
  749. }
  750. PopupMenu *pm = Object::cast_to<PopupMenu>(n);
  751. if (!pm || !pm->is_visible()) {
  752. continue;
  753. }
  754. pm->hide();
  755. }
  756. set_process_internal(false);
  757. } else {
  758. if (!is_embedded()) {
  759. set_process_internal(true);
  760. }
  761. // Set margin on the margin container
  762. Ref<StyleBox> panel_style = get_theme_stylebox(SNAME("panel"));
  763. margin_container->add_theme_constant_override("margin_left", panel_style->get_margin(Side::SIDE_LEFT));
  764. margin_container->add_theme_constant_override("margin_top", panel_style->get_margin(Side::SIDE_TOP));
  765. margin_container->add_theme_constant_override("margin_right", panel_style->get_margin(Side::SIDE_RIGHT));
  766. margin_container->add_theme_constant_override("margin_bottom", panel_style->get_margin(Side::SIDE_BOTTOM));
  767. }
  768. } break;
  769. }
  770. }
  771. /* Methods to add items with or without icon, checkbox, shortcut.
  772. * Be sure to keep them in sync when adding new properties in the Item struct.
  773. */
  774. #define ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel) \
  775. item.text = p_label; \
  776. item.xl_text = atr(p_label); \
  777. item.id = p_id == -1 ? items.size() : p_id; \
  778. item.accel = p_accel;
  779. void PopupMenu::add_item(const String &p_label, int p_id, Key p_accel) {
  780. Item item;
  781. ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
  782. items.push_back(item);
  783. _shape_item(items.size() - 1);
  784. control->queue_redraw();
  785. child_controls_changed();
  786. notify_property_list_changed();
  787. _menu_changed();
  788. }
  789. void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) {
  790. Item item;
  791. ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
  792. item.icon = p_icon;
  793. items.push_back(item);
  794. _shape_item(items.size() - 1);
  795. control->queue_redraw();
  796. child_controls_changed();
  797. notify_property_list_changed();
  798. _menu_changed();
  799. }
  800. void PopupMenu::add_check_item(const String &p_label, int p_id, Key p_accel) {
  801. Item item;
  802. ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
  803. item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
  804. items.push_back(item);
  805. _shape_item(items.size() - 1);
  806. control->queue_redraw();
  807. child_controls_changed();
  808. _menu_changed();
  809. }
  810. void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) {
  811. Item item;
  812. ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
  813. item.icon = p_icon;
  814. item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
  815. items.push_back(item);
  816. _shape_item(items.size() - 1);
  817. control->queue_redraw();
  818. child_controls_changed();
  819. }
  820. void PopupMenu::add_radio_check_item(const String &p_label, int p_id, Key p_accel) {
  821. Item item;
  822. ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
  823. item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
  824. items.push_back(item);
  825. _shape_item(items.size() - 1);
  826. control->queue_redraw();
  827. child_controls_changed();
  828. _menu_changed();
  829. }
  830. void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) {
  831. Item item;
  832. ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
  833. item.icon = p_icon;
  834. item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
  835. items.push_back(item);
  836. _shape_item(items.size() - 1);
  837. control->queue_redraw();
  838. child_controls_changed();
  839. _menu_changed();
  840. }
  841. void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_id, Key p_accel) {
  842. Item item;
  843. ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
  844. item.max_states = p_max_states;
  845. item.state = p_default_state;
  846. items.push_back(item);
  847. _shape_item(items.size() - 1);
  848. control->queue_redraw();
  849. child_controls_changed();
  850. _menu_changed();
  851. }
  852. #define ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global) \
  853. ERR_FAIL_COND_MSG(p_shortcut.is_null(), "Cannot add item with invalid Shortcut."); \
  854. _ref_shortcut(p_shortcut); \
  855. item.text = p_shortcut->get_name(); \
  856. item.xl_text = atr(item.text); \
  857. item.id = p_id == -1 ? items.size() : p_id; \
  858. item.shortcut = p_shortcut; \
  859. item.shortcut_is_global = p_global;
  860. void PopupMenu::add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
  861. Item item;
  862. ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
  863. items.push_back(item);
  864. _shape_item(items.size() - 1);
  865. control->queue_redraw();
  866. child_controls_changed();
  867. _menu_changed();
  868. }
  869. void PopupMenu::add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
  870. Item item;
  871. ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
  872. item.icon = p_icon;
  873. items.push_back(item);
  874. _shape_item(items.size() - 1);
  875. control->queue_redraw();
  876. child_controls_changed();
  877. _menu_changed();
  878. }
  879. void PopupMenu::add_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
  880. Item item;
  881. ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
  882. item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
  883. items.push_back(item);
  884. _shape_item(items.size() - 1);
  885. control->queue_redraw();
  886. child_controls_changed();
  887. _menu_changed();
  888. }
  889. void PopupMenu::add_icon_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
  890. Item item;
  891. ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
  892. item.icon = p_icon;
  893. item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
  894. items.push_back(item);
  895. _shape_item(items.size() - 1);
  896. control->queue_redraw();
  897. child_controls_changed();
  898. _menu_changed();
  899. }
  900. void PopupMenu::add_radio_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
  901. Item item;
  902. ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
  903. item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
  904. items.push_back(item);
  905. _shape_item(items.size() - 1);
  906. control->queue_redraw();
  907. child_controls_changed();
  908. _menu_changed();
  909. }
  910. void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
  911. Item item;
  912. ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
  913. item.icon = p_icon;
  914. item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
  915. items.push_back(item);
  916. _shape_item(items.size() - 1);
  917. control->queue_redraw();
  918. child_controls_changed();
  919. _menu_changed();
  920. }
  921. void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, int p_id) {
  922. Item item;
  923. item.text = p_label;
  924. item.xl_text = atr(p_label);
  925. item.id = p_id == -1 ? items.size() : p_id;
  926. item.submenu = p_submenu;
  927. items.push_back(item);
  928. _shape_item(items.size() - 1);
  929. control->queue_redraw();
  930. child_controls_changed();
  931. _menu_changed();
  932. }
  933. #undef ITEM_SETUP_WITH_ACCEL
  934. #undef ITEM_SETUP_WITH_SHORTCUT
  935. /* Methods to modify existing items. */
  936. void PopupMenu::set_item_text(int p_idx, const String &p_text) {
  937. if (p_idx < 0) {
  938. p_idx += get_item_count();
  939. }
  940. ERR_FAIL_INDEX(p_idx, items.size());
  941. if (items[p_idx].text == p_text) {
  942. return;
  943. }
  944. items.write[p_idx].text = p_text;
  945. items.write[p_idx].xl_text = atr(p_text);
  946. items.write[p_idx].dirty = true;
  947. _shape_item(p_idx);
  948. control->queue_redraw();
  949. child_controls_changed();
  950. _menu_changed();
  951. }
  952. void PopupMenu::set_item_text_direction(int p_item, Control::TextDirection p_text_direction) {
  953. if (p_item < 0) {
  954. p_item += get_item_count();
  955. }
  956. ERR_FAIL_INDEX(p_item, items.size());
  957. ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
  958. if (items[p_item].text_direction != p_text_direction) {
  959. items.write[p_item].text_direction = p_text_direction;
  960. items.write[p_item].dirty = true;
  961. control->queue_redraw();
  962. }
  963. }
  964. void PopupMenu::set_item_language(int p_item, const String &p_language) {
  965. if (p_item < 0) {
  966. p_item += get_item_count();
  967. }
  968. ERR_FAIL_INDEX(p_item, items.size());
  969. if (items[p_item].language != p_language) {
  970. items.write[p_item].language = p_language;
  971. items.write[p_item].dirty = true;
  972. control->queue_redraw();
  973. }
  974. }
  975. void PopupMenu::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) {
  976. if (p_idx < 0) {
  977. p_idx += get_item_count();
  978. }
  979. ERR_FAIL_INDEX(p_idx, items.size());
  980. if (items[p_idx].icon == p_icon) {
  981. return;
  982. }
  983. items.write[p_idx].icon = p_icon;
  984. control->queue_redraw();
  985. child_controls_changed();
  986. _menu_changed();
  987. }
  988. void PopupMenu::set_item_checked(int p_idx, bool p_checked) {
  989. if (p_idx < 0) {
  990. p_idx += get_item_count();
  991. }
  992. ERR_FAIL_INDEX(p_idx, items.size());
  993. if (items[p_idx].checked == p_checked) {
  994. return;
  995. }
  996. items.write[p_idx].checked = p_checked;
  997. control->queue_redraw();
  998. child_controls_changed();
  999. _menu_changed();
  1000. }
  1001. void PopupMenu::set_item_id(int p_idx, int p_id) {
  1002. if (p_idx < 0) {
  1003. p_idx += get_item_count();
  1004. }
  1005. ERR_FAIL_INDEX(p_idx, items.size());
  1006. if (items[p_idx].id == p_id) {
  1007. return;
  1008. }
  1009. items.write[p_idx].id = p_id;
  1010. control->queue_redraw();
  1011. child_controls_changed();
  1012. _menu_changed();
  1013. }
  1014. void PopupMenu::set_item_accelerator(int p_idx, Key p_accel) {
  1015. if (p_idx < 0) {
  1016. p_idx += get_item_count();
  1017. }
  1018. ERR_FAIL_INDEX(p_idx, items.size());
  1019. if (items[p_idx].accel == p_accel) {
  1020. return;
  1021. }
  1022. items.write[p_idx].accel = p_accel;
  1023. items.write[p_idx].dirty = true;
  1024. control->queue_redraw();
  1025. child_controls_changed();
  1026. _menu_changed();
  1027. }
  1028. void PopupMenu::set_item_metadata(int p_idx, const Variant &p_meta) {
  1029. if (p_idx < 0) {
  1030. p_idx += get_item_count();
  1031. }
  1032. ERR_FAIL_INDEX(p_idx, items.size());
  1033. if (items[p_idx].metadata == p_meta) {
  1034. return;
  1035. }
  1036. items.write[p_idx].metadata = p_meta;
  1037. control->queue_redraw();
  1038. child_controls_changed();
  1039. _menu_changed();
  1040. }
  1041. void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) {
  1042. if (p_idx < 0) {
  1043. p_idx += get_item_count();
  1044. }
  1045. ERR_FAIL_INDEX(p_idx, items.size());
  1046. if (items[p_idx].disabled == p_disabled) {
  1047. return;
  1048. }
  1049. items.write[p_idx].disabled = p_disabled;
  1050. control->queue_redraw();
  1051. child_controls_changed();
  1052. _menu_changed();
  1053. }
  1054. void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) {
  1055. if (p_idx < 0) {
  1056. p_idx += get_item_count();
  1057. }
  1058. ERR_FAIL_INDEX(p_idx, items.size());
  1059. if (items[p_idx].submenu == p_submenu) {
  1060. return;
  1061. }
  1062. items.write[p_idx].submenu = p_submenu;
  1063. control->queue_redraw();
  1064. child_controls_changed();
  1065. _menu_changed();
  1066. }
  1067. void PopupMenu::toggle_item_checked(int p_idx) {
  1068. ERR_FAIL_INDEX(p_idx, items.size());
  1069. items.write[p_idx].checked = !items[p_idx].checked;
  1070. control->queue_redraw();
  1071. child_controls_changed();
  1072. _menu_changed();
  1073. }
  1074. String PopupMenu::get_item_text(int p_idx) const {
  1075. ERR_FAIL_INDEX_V(p_idx, items.size(), "");
  1076. return items[p_idx].text;
  1077. }
  1078. Control::TextDirection PopupMenu::get_item_text_direction(int p_item) const {
  1079. ERR_FAIL_INDEX_V(p_item, items.size(), Control::TEXT_DIRECTION_INHERITED);
  1080. return items[p_item].text_direction;
  1081. }
  1082. String PopupMenu::get_item_language(int p_item) const {
  1083. ERR_FAIL_INDEX_V(p_item, items.size(), "");
  1084. return items[p_item].language;
  1085. }
  1086. int PopupMenu::get_item_idx_from_text(const String &text) const {
  1087. for (int idx = 0; idx < items.size(); idx++) {
  1088. if (items[idx].text == text) {
  1089. return idx;
  1090. }
  1091. }
  1092. return -1;
  1093. }
  1094. Ref<Texture2D> PopupMenu::get_item_icon(int p_idx) const {
  1095. ERR_FAIL_INDEX_V(p_idx, items.size(), Ref<Texture2D>());
  1096. return items[p_idx].icon;
  1097. }
  1098. Key PopupMenu::get_item_accelerator(int p_idx) const {
  1099. ERR_FAIL_INDEX_V(p_idx, items.size(), Key::NONE);
  1100. return items[p_idx].accel;
  1101. }
  1102. Variant PopupMenu::get_item_metadata(int p_idx) const {
  1103. ERR_FAIL_INDEX_V(p_idx, items.size(), Variant());
  1104. return items[p_idx].metadata;
  1105. }
  1106. bool PopupMenu::is_item_disabled(int p_idx) const {
  1107. ERR_FAIL_INDEX_V(p_idx, items.size(), false);
  1108. return items[p_idx].disabled;
  1109. }
  1110. bool PopupMenu::is_item_checked(int p_idx) const {
  1111. ERR_FAIL_INDEX_V(p_idx, items.size(), false);
  1112. return items[p_idx].checked;
  1113. }
  1114. int PopupMenu::get_item_id(int p_idx) const {
  1115. ERR_FAIL_INDEX_V(p_idx, items.size(), 0);
  1116. return items[p_idx].id;
  1117. }
  1118. int PopupMenu::get_item_index(int p_id) const {
  1119. for (int i = 0; i < items.size(); i++) {
  1120. if (items[i].id == p_id) {
  1121. return i;
  1122. }
  1123. }
  1124. return -1;
  1125. }
  1126. String PopupMenu::get_item_submenu(int p_idx) const {
  1127. ERR_FAIL_INDEX_V(p_idx, items.size(), "");
  1128. return items[p_idx].submenu;
  1129. }
  1130. String PopupMenu::get_item_tooltip(int p_idx) const {
  1131. ERR_FAIL_INDEX_V(p_idx, items.size(), "");
  1132. return items[p_idx].tooltip;
  1133. }
  1134. Ref<Shortcut> PopupMenu::get_item_shortcut(int p_idx) const {
  1135. ERR_FAIL_INDEX_V(p_idx, items.size(), Ref<Shortcut>());
  1136. return items[p_idx].shortcut;
  1137. }
  1138. int PopupMenu::get_item_indent(int p_idx) const {
  1139. ERR_FAIL_INDEX_V(p_idx, items.size(), 0);
  1140. return items[p_idx].indent;
  1141. }
  1142. int PopupMenu::get_item_max_states(int p_idx) const {
  1143. ERR_FAIL_INDEX_V(p_idx, items.size(), -1);
  1144. return items[p_idx].max_states;
  1145. }
  1146. int PopupMenu::get_item_state(int p_idx) const {
  1147. ERR_FAIL_INDEX_V(p_idx, items.size(), -1);
  1148. return items[p_idx].state;
  1149. }
  1150. void PopupMenu::set_item_as_separator(int p_idx, bool p_separator) {
  1151. if (p_idx < 0) {
  1152. p_idx += get_item_count();
  1153. }
  1154. ERR_FAIL_INDEX(p_idx, items.size());
  1155. if (items[p_idx].separator == p_separator) {
  1156. return;
  1157. }
  1158. items.write[p_idx].separator = p_separator;
  1159. control->queue_redraw();
  1160. }
  1161. bool PopupMenu::is_item_separator(int p_idx) const {
  1162. ERR_FAIL_INDEX_V(p_idx, items.size(), false);
  1163. return items[p_idx].separator;
  1164. }
  1165. void PopupMenu::set_item_as_checkable(int p_idx, bool p_checkable) {
  1166. if (p_idx < 0) {
  1167. p_idx += get_item_count();
  1168. }
  1169. ERR_FAIL_INDEX(p_idx, items.size());
  1170. int type = (int)(p_checkable ? Item::CHECKABLE_TYPE_CHECK_BOX : Item::CHECKABLE_TYPE_NONE);
  1171. if (type == items[p_idx].checkable_type) {
  1172. return;
  1173. }
  1174. items.write[p_idx].checkable_type = p_checkable ? Item::CHECKABLE_TYPE_CHECK_BOX : Item::CHECKABLE_TYPE_NONE;
  1175. control->queue_redraw();
  1176. _menu_changed();
  1177. }
  1178. void PopupMenu::set_item_as_radio_checkable(int p_idx, bool p_radio_checkable) {
  1179. if (p_idx < 0) {
  1180. p_idx += get_item_count();
  1181. }
  1182. ERR_FAIL_INDEX(p_idx, items.size());
  1183. int type = (int)(p_radio_checkable ? Item::CHECKABLE_TYPE_RADIO_BUTTON : Item::CHECKABLE_TYPE_NONE);
  1184. if (type == items[p_idx].checkable_type) {
  1185. return;
  1186. }
  1187. items.write[p_idx].checkable_type = p_radio_checkable ? Item::CHECKABLE_TYPE_RADIO_BUTTON : Item::CHECKABLE_TYPE_NONE;
  1188. control->queue_redraw();
  1189. _menu_changed();
  1190. }
  1191. void PopupMenu::set_item_tooltip(int p_idx, const String &p_tooltip) {
  1192. if (p_idx < 0) {
  1193. p_idx += get_item_count();
  1194. }
  1195. ERR_FAIL_INDEX(p_idx, items.size());
  1196. if (items[p_idx].tooltip == p_tooltip) {
  1197. return;
  1198. }
  1199. items.write[p_idx].tooltip = p_tooltip;
  1200. control->queue_redraw();
  1201. _menu_changed();
  1202. }
  1203. void PopupMenu::set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bool p_global) {
  1204. if (p_idx < 0) {
  1205. p_idx += get_item_count();
  1206. }
  1207. ERR_FAIL_INDEX(p_idx, items.size());
  1208. if (items[p_idx].shortcut == p_shortcut && items[p_idx].shortcut_is_global == p_global && items[p_idx].shortcut.is_valid() == p_shortcut.is_valid()) {
  1209. return;
  1210. }
  1211. if (items[p_idx].shortcut.is_valid()) {
  1212. _unref_shortcut(items[p_idx].shortcut);
  1213. }
  1214. items.write[p_idx].shortcut = p_shortcut;
  1215. items.write[p_idx].shortcut_is_global = p_global;
  1216. items.write[p_idx].dirty = true;
  1217. if (items[p_idx].shortcut.is_valid()) {
  1218. _ref_shortcut(items[p_idx].shortcut);
  1219. }
  1220. control->queue_redraw();
  1221. _menu_changed();
  1222. }
  1223. void PopupMenu::set_item_indent(int p_idx, int p_indent) {
  1224. if (p_idx < 0) {
  1225. p_idx += get_item_count();
  1226. }
  1227. ERR_FAIL_INDEX(p_idx, items.size());
  1228. if (items.write[p_idx].indent == p_indent) {
  1229. return;
  1230. }
  1231. items.write[p_idx].indent = p_indent;
  1232. control->queue_redraw();
  1233. child_controls_changed();
  1234. _menu_changed();
  1235. }
  1236. void PopupMenu::set_item_multistate(int p_idx, int p_state) {
  1237. if (p_idx < 0) {
  1238. p_idx += get_item_count();
  1239. }
  1240. ERR_FAIL_INDEX(p_idx, items.size());
  1241. if (items[p_idx].state == p_state) {
  1242. return;
  1243. }
  1244. items.write[p_idx].state = p_state;
  1245. control->queue_redraw();
  1246. _menu_changed();
  1247. }
  1248. void PopupMenu::set_item_shortcut_disabled(int p_idx, bool p_disabled) {
  1249. if (p_idx < 0) {
  1250. p_idx += get_item_count();
  1251. }
  1252. ERR_FAIL_INDEX(p_idx, items.size());
  1253. if (items[p_idx].shortcut_is_disabled == p_disabled) {
  1254. return;
  1255. }
  1256. items.write[p_idx].shortcut_is_disabled = p_disabled;
  1257. control->queue_redraw();
  1258. _menu_changed();
  1259. }
  1260. void PopupMenu::toggle_item_multistate(int p_idx) {
  1261. ERR_FAIL_INDEX(p_idx, items.size());
  1262. if (0 >= items[p_idx].max_states) {
  1263. return;
  1264. }
  1265. ++items.write[p_idx].state;
  1266. if (items.write[p_idx].max_states <= items[p_idx].state) {
  1267. items.write[p_idx].state = 0;
  1268. }
  1269. control->queue_redraw();
  1270. _menu_changed();
  1271. }
  1272. bool PopupMenu::is_item_checkable(int p_idx) const {
  1273. ERR_FAIL_INDEX_V(p_idx, items.size(), false);
  1274. return items[p_idx].checkable_type;
  1275. }
  1276. bool PopupMenu::is_item_radio_checkable(int p_idx) const {
  1277. ERR_FAIL_INDEX_V(p_idx, items.size(), false);
  1278. return items[p_idx].checkable_type == Item::CHECKABLE_TYPE_RADIO_BUTTON;
  1279. }
  1280. bool PopupMenu::is_item_shortcut_global(int p_idx) const {
  1281. ERR_FAIL_INDEX_V(p_idx, items.size(), false);
  1282. return items[p_idx].shortcut_is_global;
  1283. }
  1284. bool PopupMenu::is_item_shortcut_disabled(int p_idx) const {
  1285. ERR_FAIL_INDEX_V(p_idx, items.size(), false);
  1286. return items[p_idx].shortcut_is_disabled;
  1287. }
  1288. void PopupMenu::set_current_index(int p_idx) {
  1289. if (p_idx != -1) {
  1290. ERR_FAIL_INDEX(p_idx, items.size());
  1291. }
  1292. if (mouse_over == p_idx) {
  1293. return;
  1294. }
  1295. mouse_over = p_idx;
  1296. if (mouse_over != -1) {
  1297. scroll_to_item(mouse_over);
  1298. }
  1299. control->queue_redraw();
  1300. }
  1301. int PopupMenu::get_current_index() const {
  1302. return mouse_over;
  1303. }
  1304. void PopupMenu::set_item_count(int p_count) {
  1305. ERR_FAIL_COND(p_count < 0);
  1306. int prev_size = items.size();
  1307. if (prev_size == p_count) {
  1308. return;
  1309. }
  1310. items.resize(p_count);
  1311. if (prev_size < p_count) {
  1312. for (int i = prev_size; i < p_count; i++) {
  1313. items.write[i].id = i;
  1314. }
  1315. }
  1316. control->queue_redraw();
  1317. child_controls_changed();
  1318. notify_property_list_changed();
  1319. _menu_changed();
  1320. }
  1321. int PopupMenu::get_item_count() const {
  1322. return items.size();
  1323. }
  1324. void PopupMenu::scroll_to_item(int p_item) {
  1325. ERR_FAIL_INDEX(p_item, items.size());
  1326. // Scroll item into view (upwards).
  1327. if (items[p_item]._ofs_cache - scroll_container->get_v_scroll() < -control->get_position().y) {
  1328. scroll_container->set_v_scroll(items[p_item]._ofs_cache + control->get_position().y);
  1329. }
  1330. // Scroll item into view (downwards).
  1331. if (items[p_item]._ofs_cache + items[p_item]._height_cache - scroll_container->get_v_scroll() > -control->get_position().y + scroll_container->get_size().height) {
  1332. scroll_container->set_v_scroll(items[p_item]._ofs_cache + items[p_item]._height_cache + control->get_position().y);
  1333. }
  1334. }
  1335. bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only) {
  1336. Key code = Key::NONE;
  1337. Ref<InputEventKey> k = p_event;
  1338. if (k.is_valid()) {
  1339. code = k->get_keycode();
  1340. if (code == Key::NONE) {
  1341. code = (Key)k->get_unicode();
  1342. }
  1343. if (k->is_ctrl_pressed()) {
  1344. code |= KeyModifierMask::CTRL;
  1345. }
  1346. if (k->is_alt_pressed()) {
  1347. code |= KeyModifierMask::ALT;
  1348. }
  1349. if (k->is_meta_pressed()) {
  1350. code |= KeyModifierMask::META;
  1351. }
  1352. if (k->is_shift_pressed()) {
  1353. code |= KeyModifierMask::SHIFT;
  1354. }
  1355. }
  1356. for (int i = 0; i < items.size(); i++) {
  1357. if (is_item_disabled(i) || items[i].shortcut_is_disabled) {
  1358. continue;
  1359. }
  1360. if (items[i].shortcut.is_valid() && items[i].shortcut->matches_event(p_event) && (items[i].shortcut_is_global || !p_for_global_only)) {
  1361. activate_item(i);
  1362. return true;
  1363. }
  1364. if (code != Key::NONE && items[i].accel == code) {
  1365. activate_item(i);
  1366. return true;
  1367. }
  1368. if (!items[i].submenu.is_empty()) {
  1369. Node *n = get_node(items[i].submenu);
  1370. if (!n) {
  1371. continue;
  1372. }
  1373. PopupMenu *pm = Object::cast_to<PopupMenu>(n);
  1374. if (!pm) {
  1375. continue;
  1376. }
  1377. if (pm->activate_item_by_event(p_event, p_for_global_only)) {
  1378. return true;
  1379. }
  1380. }
  1381. }
  1382. return false;
  1383. }
  1384. void PopupMenu::activate_item(int p_item) {
  1385. ERR_FAIL_INDEX(p_item, items.size());
  1386. ERR_FAIL_COND(items[p_item].separator);
  1387. int id = items[p_item].id >= 0 ? items[p_item].id : p_item;
  1388. //hide all parent PopupMenus
  1389. Node *next = get_parent();
  1390. PopupMenu *pop = Object::cast_to<PopupMenu>(next);
  1391. while (pop) {
  1392. // We close all parents that are chained together,
  1393. // with hide_on_item_selection enabled
  1394. if (items[p_item].checkable_type) {
  1395. if (!hide_on_checkable_item_selection || !pop->is_hide_on_checkable_item_selection()) {
  1396. break;
  1397. }
  1398. } else if (0 < items[p_item].max_states) {
  1399. if (!hide_on_multistate_item_selection || !pop->is_hide_on_multistate_item_selection()) {
  1400. break;
  1401. }
  1402. } else if (!hide_on_item_selection || !pop->is_hide_on_item_selection()) {
  1403. break;
  1404. }
  1405. pop->hide();
  1406. next = next->get_parent();
  1407. pop = Object::cast_to<PopupMenu>(next);
  1408. }
  1409. // Hides popup by default; unless otherwise specified
  1410. // by using set_hide_on_item_selection and set_hide_on_checkable_item_selection
  1411. bool need_hide = true;
  1412. if (items[p_item].checkable_type) {
  1413. if (!hide_on_checkable_item_selection) {
  1414. need_hide = false;
  1415. }
  1416. } else if (0 < items[p_item].max_states) {
  1417. if (!hide_on_multistate_item_selection) {
  1418. need_hide = false;
  1419. }
  1420. } else if (!hide_on_item_selection) {
  1421. need_hide = false;
  1422. }
  1423. if (need_hide) {
  1424. hide();
  1425. }
  1426. emit_signal(SNAME("id_pressed"), id);
  1427. emit_signal(SNAME("index_pressed"), p_item);
  1428. }
  1429. void PopupMenu::remove_item(int p_idx) {
  1430. ERR_FAIL_INDEX(p_idx, items.size());
  1431. if (items[p_idx].shortcut.is_valid()) {
  1432. _unref_shortcut(items[p_idx].shortcut);
  1433. }
  1434. items.remove_at(p_idx);
  1435. control->queue_redraw();
  1436. child_controls_changed();
  1437. _menu_changed();
  1438. }
  1439. void PopupMenu::add_separator(const String &p_text, int p_id) {
  1440. Item sep;
  1441. sep.separator = true;
  1442. sep.id = p_id;
  1443. if (!p_text.is_empty()) {
  1444. sep.text = p_text;
  1445. sep.xl_text = atr(p_text);
  1446. }
  1447. items.push_back(sep);
  1448. control->queue_redraw();
  1449. _menu_changed();
  1450. }
  1451. void PopupMenu::clear() {
  1452. for (int i = 0; i < items.size(); i++) {
  1453. if (items[i].shortcut.is_valid()) {
  1454. _unref_shortcut(items[i].shortcut);
  1455. }
  1456. }
  1457. items.clear();
  1458. mouse_over = -1;
  1459. control->queue_redraw();
  1460. child_controls_changed();
  1461. notify_property_list_changed();
  1462. _menu_changed();
  1463. }
  1464. void PopupMenu::_ref_shortcut(Ref<Shortcut> p_sc) {
  1465. if (!shortcut_refcount.has(p_sc)) {
  1466. shortcut_refcount[p_sc] = 1;
  1467. p_sc->connect("changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw));
  1468. } else {
  1469. shortcut_refcount[p_sc] += 1;
  1470. }
  1471. }
  1472. void PopupMenu::_unref_shortcut(Ref<Shortcut> p_sc) {
  1473. ERR_FAIL_COND(!shortcut_refcount.has(p_sc));
  1474. shortcut_refcount[p_sc]--;
  1475. if (shortcut_refcount[p_sc] == 0) {
  1476. p_sc->disconnect("changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw));
  1477. shortcut_refcount.erase(p_sc);
  1478. }
  1479. }
  1480. // Hide on item selection determines whether or not the popup will close after item selection
  1481. void PopupMenu::set_hide_on_item_selection(bool p_enabled) {
  1482. hide_on_item_selection = p_enabled;
  1483. }
  1484. bool PopupMenu::is_hide_on_item_selection() const {
  1485. return hide_on_item_selection;
  1486. }
  1487. void PopupMenu::set_hide_on_checkable_item_selection(bool p_enabled) {
  1488. hide_on_checkable_item_selection = p_enabled;
  1489. }
  1490. bool PopupMenu::is_hide_on_checkable_item_selection() const {
  1491. return hide_on_checkable_item_selection;
  1492. }
  1493. void PopupMenu::set_hide_on_multistate_item_selection(bool p_enabled) {
  1494. hide_on_multistate_item_selection = p_enabled;
  1495. }
  1496. bool PopupMenu::is_hide_on_multistate_item_selection() const {
  1497. return hide_on_multistate_item_selection;
  1498. }
  1499. void PopupMenu::set_submenu_popup_delay(float p_time) {
  1500. if (p_time <= 0) {
  1501. p_time = 0.01;
  1502. }
  1503. submenu_timer->set_wait_time(p_time);
  1504. }
  1505. float PopupMenu::get_submenu_popup_delay() const {
  1506. return submenu_timer->get_wait_time();
  1507. }
  1508. void PopupMenu::set_allow_search(bool p_allow) {
  1509. allow_search = p_allow;
  1510. }
  1511. bool PopupMenu::get_allow_search() const {
  1512. return allow_search;
  1513. }
  1514. String PopupMenu::get_tooltip(const Point2 &p_pos) const {
  1515. int over = _get_mouse_over(p_pos);
  1516. if (over < 0 || over >= items.size()) {
  1517. return "";
  1518. }
  1519. return items[over].tooltip;
  1520. }
  1521. void PopupMenu::set_parent_rect(const Rect2 &p_rect) {
  1522. parent_rect = p_rect;
  1523. }
  1524. void PopupMenu::get_translatable_strings(List<String> *p_strings) const {
  1525. for (int i = 0; i < items.size(); i++) {
  1526. if (!items[i].xl_text.is_empty()) {
  1527. p_strings->push_back(items[i].xl_text);
  1528. }
  1529. }
  1530. }
  1531. void PopupMenu::add_autohide_area(const Rect2 &p_area) {
  1532. autohide_areas.push_back(p_area);
  1533. }
  1534. void PopupMenu::clear_autohide_areas() {
  1535. autohide_areas.clear();
  1536. }
  1537. void PopupMenu::take_mouse_focus() {
  1538. ERR_FAIL_COND(!is_inside_tree());
  1539. if (get_parent()) {
  1540. get_parent()->get_viewport()->pass_mouse_focus_to(this, control);
  1541. }
  1542. }
  1543. bool PopupMenu::_set(const StringName &p_name, const Variant &p_value) {
  1544. Vector<String> components = String(p_name).split("/", true, 2);
  1545. if (components.size() >= 2 && components[0].begins_with("item_") && components[0].trim_prefix("item_").is_valid_int()) {
  1546. int item_index = components[0].trim_prefix("item_").to_int();
  1547. String property = components[1];
  1548. if (property == "text") {
  1549. set_item_text(item_index, p_value);
  1550. return true;
  1551. } else if (property == "icon") {
  1552. set_item_icon(item_index, p_value);
  1553. return true;
  1554. } else if (property == "checkable") {
  1555. bool radio_checkable = (int)p_value == Item::CHECKABLE_TYPE_RADIO_BUTTON;
  1556. if (radio_checkable) {
  1557. set_item_as_radio_checkable(item_index, true);
  1558. } else {
  1559. bool checkable = p_value;
  1560. set_item_as_checkable(item_index, checkable);
  1561. }
  1562. return true;
  1563. } else if (property == "checked") {
  1564. set_item_checked(item_index, p_value);
  1565. return true;
  1566. } else if (property == "id") {
  1567. set_item_id(item_index, p_value);
  1568. return true;
  1569. } else if (property == "disabled") {
  1570. set_item_disabled(item_index, p_value);
  1571. return true;
  1572. } else if (property == "separator") {
  1573. set_item_as_separator(item_index, p_value);
  1574. return true;
  1575. }
  1576. }
  1577. #ifndef DISABLE_DEPRECATED
  1578. // Compatibility.
  1579. if (p_name == "items") {
  1580. Array arr = p_value;
  1581. ERR_FAIL_COND_V(arr.size() % 10, false);
  1582. clear();
  1583. for (int i = 0; i < arr.size(); i += 10) {
  1584. String text = arr[i + 0];
  1585. Ref<Texture2D> icon = arr[i + 1];
  1586. // For compatibility, use false/true for no/checkbox and integers for other values
  1587. bool checkable = arr[i + 2];
  1588. bool radio_checkable = (int)arr[i + 2] == Item::CHECKABLE_TYPE_RADIO_BUTTON;
  1589. bool checked = arr[i + 3];
  1590. bool disabled = arr[i + 4];
  1591. int id = arr[i + 5];
  1592. int accel = arr[i + 6];
  1593. Variant meta = arr[i + 7];
  1594. String subm = arr[i + 8];
  1595. bool sep = arr[i + 9];
  1596. int idx = get_item_count();
  1597. add_item(text, id);
  1598. set_item_icon(idx, icon);
  1599. if (checkable) {
  1600. if (radio_checkable) {
  1601. set_item_as_radio_checkable(idx, true);
  1602. } else {
  1603. set_item_as_checkable(idx, true);
  1604. }
  1605. }
  1606. set_item_checked(idx, checked);
  1607. set_item_disabled(idx, disabled);
  1608. set_item_id(idx, id);
  1609. set_item_metadata(idx, meta);
  1610. set_item_as_separator(idx, sep);
  1611. set_item_accelerator(idx, (Key)accel);
  1612. set_item_submenu(idx, subm);
  1613. }
  1614. }
  1615. #endif
  1616. return false;
  1617. }
  1618. bool PopupMenu::_get(const StringName &p_name, Variant &r_ret) const {
  1619. Vector<String> components = String(p_name).split("/", true, 2);
  1620. if (components.size() >= 2 && components[0].begins_with("item_") && components[0].trim_prefix("item_").is_valid_int()) {
  1621. int item_index = components[0].trim_prefix("item_").to_int();
  1622. String property = components[1];
  1623. if (property == "text") {
  1624. r_ret = get_item_text(item_index);
  1625. return true;
  1626. } else if (property == "icon") {
  1627. r_ret = get_item_icon(item_index);
  1628. return true;
  1629. } else if (property == "checkable") {
  1630. r_ret = this->items[item_index].checkable_type;
  1631. return true;
  1632. } else if (property == "checked") {
  1633. r_ret = is_item_checked(item_index);
  1634. return true;
  1635. } else if (property == "id") {
  1636. r_ret = get_item_id(item_index);
  1637. return true;
  1638. } else if (property == "disabled") {
  1639. r_ret = is_item_disabled(item_index);
  1640. return true;
  1641. } else if (property == "separator") {
  1642. r_ret = is_item_separator(item_index);
  1643. return true;
  1644. }
  1645. }
  1646. return false;
  1647. }
  1648. void PopupMenu::_get_property_list(List<PropertyInfo> *p_list) const {
  1649. for (int i = 0; i < items.size(); i++) {
  1650. p_list->push_back(PropertyInfo(Variant::STRING, vformat("item_%d/text", i)));
  1651. PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("item_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D");
  1652. pi.usage &= ~(get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0);
  1653. p_list->push_back(pi);
  1654. pi = PropertyInfo(Variant::INT, vformat("item_%d/checkable", i), PROPERTY_HINT_ENUM, "No,As checkbox,As radio button");
  1655. pi.usage &= ~(!is_item_checkable(i) ? PROPERTY_USAGE_STORAGE : 0);
  1656. p_list->push_back(pi);
  1657. pi = PropertyInfo(Variant::BOOL, vformat("item_%d/checked", i));
  1658. pi.usage &= ~(!is_item_checked(i) ? PROPERTY_USAGE_STORAGE : 0);
  1659. p_list->push_back(pi);
  1660. pi = PropertyInfo(Variant::INT, vformat("item_%d/id", i), PROPERTY_HINT_RANGE, "0,10,1,or_greater");
  1661. p_list->push_back(pi);
  1662. pi = PropertyInfo(Variant::BOOL, vformat("item_%d/disabled", i));
  1663. pi.usage &= ~(!is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0);
  1664. p_list->push_back(pi);
  1665. pi = PropertyInfo(Variant::BOOL, vformat("item_%d/separator", i));
  1666. pi.usage &= ~(!is_item_separator(i) ? PROPERTY_USAGE_STORAGE : 0);
  1667. p_list->push_back(pi);
  1668. }
  1669. }
  1670. void PopupMenu::_bind_methods() {
  1671. ClassDB::bind_method(D_METHOD("add_item", "label", "id", "accel"), &PopupMenu::add_item, DEFVAL(-1), DEFVAL(0));
  1672. ClassDB::bind_method(D_METHOD("add_icon_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_item, DEFVAL(-1), DEFVAL(0));
  1673. ClassDB::bind_method(D_METHOD("add_check_item", "label", "id", "accel"), &PopupMenu::add_check_item, DEFVAL(-1), DEFVAL(0));
  1674. ClassDB::bind_method(D_METHOD("add_icon_check_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_check_item, DEFVAL(-1), DEFVAL(0));
  1675. ClassDB::bind_method(D_METHOD("add_radio_check_item", "label", "id", "accel"), &PopupMenu::add_radio_check_item, DEFVAL(-1), DEFVAL(0));
  1676. ClassDB::bind_method(D_METHOD("add_icon_radio_check_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_radio_check_item, DEFVAL(-1), DEFVAL(0));
  1677. 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));
  1678. ClassDB::bind_method(D_METHOD("add_shortcut", "shortcut", "id", "global"), &PopupMenu::add_shortcut, DEFVAL(-1), DEFVAL(false));
  1679. ClassDB::bind_method(D_METHOD("add_icon_shortcut", "texture", "shortcut", "id", "global"), &PopupMenu::add_icon_shortcut, DEFVAL(-1), DEFVAL(false));
  1680. ClassDB::bind_method(D_METHOD("add_check_shortcut", "shortcut", "id", "global"), &PopupMenu::add_check_shortcut, DEFVAL(-1), DEFVAL(false));
  1681. ClassDB::bind_method(D_METHOD("add_icon_check_shortcut", "texture", "shortcut", "id", "global"), &PopupMenu::add_icon_check_shortcut, DEFVAL(-1), DEFVAL(false));
  1682. ClassDB::bind_method(D_METHOD("add_radio_check_shortcut", "shortcut", "id", "global"), &PopupMenu::add_radio_check_shortcut, DEFVAL(-1), DEFVAL(false));
  1683. ClassDB::bind_method(D_METHOD("add_icon_radio_check_shortcut", "texture", "shortcut", "id", "global"), &PopupMenu::add_icon_radio_check_shortcut, DEFVAL(-1), DEFVAL(false));
  1684. ClassDB::bind_method(D_METHOD("add_submenu_item", "label", "submenu", "id"), &PopupMenu::add_submenu_item, DEFVAL(-1));
  1685. ClassDB::bind_method(D_METHOD("set_item_text", "index", "text"), &PopupMenu::set_item_text);
  1686. ClassDB::bind_method(D_METHOD("set_item_text_direction", "index", "direction"), &PopupMenu::set_item_text_direction);
  1687. ClassDB::bind_method(D_METHOD("set_item_language", "index", "language"), &PopupMenu::set_item_language);
  1688. ClassDB::bind_method(D_METHOD("set_item_icon", "index", "icon"), &PopupMenu::set_item_icon);
  1689. ClassDB::bind_method(D_METHOD("set_item_checked", "index", "checked"), &PopupMenu::set_item_checked);
  1690. ClassDB::bind_method(D_METHOD("set_item_id", "index", "id"), &PopupMenu::set_item_id);
  1691. ClassDB::bind_method(D_METHOD("set_item_accelerator", "index", "accel"), &PopupMenu::set_item_accelerator);
  1692. ClassDB::bind_method(D_METHOD("set_item_metadata", "index", "metadata"), &PopupMenu::set_item_metadata);
  1693. ClassDB::bind_method(D_METHOD("set_item_disabled", "index", "disabled"), &PopupMenu::set_item_disabled);
  1694. ClassDB::bind_method(D_METHOD("set_item_submenu", "index", "submenu"), &PopupMenu::set_item_submenu);
  1695. ClassDB::bind_method(D_METHOD("set_item_as_separator", "index", "enable"), &PopupMenu::set_item_as_separator);
  1696. ClassDB::bind_method(D_METHOD("set_item_as_checkable", "index", "enable"), &PopupMenu::set_item_as_checkable);
  1697. ClassDB::bind_method(D_METHOD("set_item_as_radio_checkable", "index", "enable"), &PopupMenu::set_item_as_radio_checkable);
  1698. ClassDB::bind_method(D_METHOD("set_item_tooltip", "index", "tooltip"), &PopupMenu::set_item_tooltip);
  1699. ClassDB::bind_method(D_METHOD("set_item_shortcut", "index", "shortcut", "global"), &PopupMenu::set_item_shortcut, DEFVAL(false));
  1700. ClassDB::bind_method(D_METHOD("set_item_indent", "index", "indent"), &PopupMenu::set_item_indent);
  1701. ClassDB::bind_method(D_METHOD("set_item_multistate", "index", "state"), &PopupMenu::set_item_multistate);
  1702. ClassDB::bind_method(D_METHOD("set_item_shortcut_disabled", "index", "disabled"), &PopupMenu::set_item_shortcut_disabled);
  1703. ClassDB::bind_method(D_METHOD("toggle_item_checked", "index"), &PopupMenu::toggle_item_checked);
  1704. ClassDB::bind_method(D_METHOD("toggle_item_multistate", "index"), &PopupMenu::toggle_item_multistate);
  1705. ClassDB::bind_method(D_METHOD("get_item_text", "index"), &PopupMenu::get_item_text);
  1706. ClassDB::bind_method(D_METHOD("get_item_text_direction", "index"), &PopupMenu::get_item_text_direction);
  1707. ClassDB::bind_method(D_METHOD("get_item_language", "index"), &PopupMenu::get_item_language);
  1708. ClassDB::bind_method(D_METHOD("get_item_icon", "index"), &PopupMenu::get_item_icon);
  1709. ClassDB::bind_method(D_METHOD("is_item_checked", "index"), &PopupMenu::is_item_checked);
  1710. ClassDB::bind_method(D_METHOD("get_item_id", "index"), &PopupMenu::get_item_id);
  1711. ClassDB::bind_method(D_METHOD("get_item_index", "id"), &PopupMenu::get_item_index);
  1712. ClassDB::bind_method(D_METHOD("get_item_accelerator", "index"), &PopupMenu::get_item_accelerator);
  1713. ClassDB::bind_method(D_METHOD("get_item_metadata", "index"), &PopupMenu::get_item_metadata);
  1714. ClassDB::bind_method(D_METHOD("is_item_disabled", "index"), &PopupMenu::is_item_disabled);
  1715. ClassDB::bind_method(D_METHOD("get_item_submenu", "index"), &PopupMenu::get_item_submenu);
  1716. ClassDB::bind_method(D_METHOD("is_item_separator", "index"), &PopupMenu::is_item_separator);
  1717. ClassDB::bind_method(D_METHOD("is_item_checkable", "index"), &PopupMenu::is_item_checkable);
  1718. ClassDB::bind_method(D_METHOD("is_item_radio_checkable", "index"), &PopupMenu::is_item_radio_checkable);
  1719. ClassDB::bind_method(D_METHOD("is_item_shortcut_disabled", "index"), &PopupMenu::is_item_shortcut_disabled);
  1720. ClassDB::bind_method(D_METHOD("get_item_tooltip", "index"), &PopupMenu::get_item_tooltip);
  1721. ClassDB::bind_method(D_METHOD("get_item_shortcut", "index"), &PopupMenu::get_item_shortcut);
  1722. ClassDB::bind_method(D_METHOD("get_item_indent", "index"), &PopupMenu::get_item_indent);
  1723. ClassDB::bind_method(D_METHOD("set_current_index", "index"), &PopupMenu::set_current_index);
  1724. ClassDB::bind_method(D_METHOD("get_current_index"), &PopupMenu::get_current_index);
  1725. ClassDB::bind_method(D_METHOD("set_item_count", "count"), &PopupMenu::set_item_count);
  1726. ClassDB::bind_method(D_METHOD("get_item_count"), &PopupMenu::get_item_count);
  1727. ClassDB::bind_method(D_METHOD("scroll_to_item", "index"), &PopupMenu::scroll_to_item);
  1728. ClassDB::bind_method(D_METHOD("remove_item", "index"), &PopupMenu::remove_item);
  1729. ClassDB::bind_method(D_METHOD("add_separator", "label", "id"), &PopupMenu::add_separator, DEFVAL(String()), DEFVAL(-1));
  1730. ClassDB::bind_method(D_METHOD("clear"), &PopupMenu::clear);
  1731. ClassDB::bind_method(D_METHOD("set_hide_on_item_selection", "enable"), &PopupMenu::set_hide_on_item_selection);
  1732. ClassDB::bind_method(D_METHOD("is_hide_on_item_selection"), &PopupMenu::is_hide_on_item_selection);
  1733. ClassDB::bind_method(D_METHOD("set_hide_on_checkable_item_selection", "enable"), &PopupMenu::set_hide_on_checkable_item_selection);
  1734. ClassDB::bind_method(D_METHOD("is_hide_on_checkable_item_selection"), &PopupMenu::is_hide_on_checkable_item_selection);
  1735. ClassDB::bind_method(D_METHOD("set_hide_on_state_item_selection", "enable"), &PopupMenu::set_hide_on_multistate_item_selection);
  1736. ClassDB::bind_method(D_METHOD("is_hide_on_state_item_selection"), &PopupMenu::is_hide_on_multistate_item_selection);
  1737. ClassDB::bind_method(D_METHOD("set_submenu_popup_delay", "seconds"), &PopupMenu::set_submenu_popup_delay);
  1738. ClassDB::bind_method(D_METHOD("get_submenu_popup_delay"), &PopupMenu::get_submenu_popup_delay);
  1739. ClassDB::bind_method(D_METHOD("set_allow_search", "allow"), &PopupMenu::set_allow_search);
  1740. ClassDB::bind_method(D_METHOD("get_allow_search"), &PopupMenu::get_allow_search);
  1741. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_item_selection"), "set_hide_on_item_selection", "is_hide_on_item_selection");
  1742. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_checkable_item_selection"), "set_hide_on_checkable_item_selection", "is_hide_on_checkable_item_selection");
  1743. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_state_item_selection"), "set_hide_on_state_item_selection", "is_hide_on_state_item_selection");
  1744. ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "submenu_popup_delay", PROPERTY_HINT_NONE, "suffix:s"), "set_submenu_popup_delay", "get_submenu_popup_delay");
  1745. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_search"), "set_allow_search", "get_allow_search");
  1746. ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "item_");
  1747. ADD_SIGNAL(MethodInfo("id_pressed", PropertyInfo(Variant::INT, "id")));
  1748. ADD_SIGNAL(MethodInfo("id_focused", PropertyInfo(Variant::INT, "id")));
  1749. ADD_SIGNAL(MethodInfo("index_pressed", PropertyInfo(Variant::INT, "index")));
  1750. ADD_SIGNAL(MethodInfo("menu_changed"));
  1751. }
  1752. void PopupMenu::popup(const Rect2 &p_bounds) {
  1753. moved = Vector2();
  1754. popup_time_msec = OS::get_singleton()->get_ticks_msec();
  1755. Popup::popup(p_bounds);
  1756. }
  1757. PopupMenu::PopupMenu() {
  1758. // Margin Container
  1759. margin_container = memnew(MarginContainer);
  1760. margin_container->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
  1761. add_child(margin_container, false, INTERNAL_MODE_FRONT);
  1762. margin_container->connect("draw", callable_mp(this, &PopupMenu::_draw_background));
  1763. // Scroll Container
  1764. scroll_container = memnew(ScrollContainer);
  1765. scroll_container->set_clip_contents(true);
  1766. margin_container->add_child(scroll_container);
  1767. // The control which will display the items
  1768. control = memnew(Control);
  1769. control->set_clip_contents(false);
  1770. control->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
  1771. control->set_h_size_flags(Control::SIZE_EXPAND_FILL);
  1772. control->set_v_size_flags(Control::SIZE_EXPAND_FILL);
  1773. scroll_container->add_child(control, false, INTERNAL_MODE_FRONT);
  1774. control->connect("draw", callable_mp(this, &PopupMenu::_draw_items));
  1775. connect("window_input", callable_mp(this, &PopupMenu::gui_input));
  1776. submenu_timer = memnew(Timer);
  1777. submenu_timer->set_wait_time(0.3);
  1778. submenu_timer->set_one_shot(true);
  1779. submenu_timer->connect("timeout", callable_mp(this, &PopupMenu::_submenu_timeout));
  1780. add_child(submenu_timer, false, INTERNAL_MODE_FRONT);
  1781. minimum_lifetime_timer = memnew(Timer);
  1782. minimum_lifetime_timer->set_wait_time(0.3);
  1783. minimum_lifetime_timer->set_one_shot(true);
  1784. minimum_lifetime_timer->connect("timeout", callable_mp(this, &PopupMenu::_minimum_lifetime_timeout));
  1785. add_child(minimum_lifetime_timer, false, INTERNAL_MODE_FRONT);
  1786. }
  1787. PopupMenu::~PopupMenu() {
  1788. }