popup_menu.cpp 94 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819
  1. /**************************************************************************/
  2. /* popup_menu.cpp */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  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 "popup_menu.compat.inc"
  32. #include "core/config/project_settings.h"
  33. #include "core/input/input.h"
  34. #include "core/os/keyboard.h"
  35. #include "core/os/os.h"
  36. #include "core/string/print_string.h"
  37. #include "core/string/translation.h"
  38. #include "scene/gui/menu_bar.h"
  39. #include "scene/theme/theme_db.h"
  40. String PopupMenu::bind_global_menu() {
  41. #ifdef TOOLS_ENABLED
  42. if (is_part_of_edited_scene()) {
  43. return String();
  44. }
  45. #endif
  46. if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) {
  47. return String();
  48. }
  49. if (!global_menu_name.is_empty()) {
  50. return global_menu_name; // Already bound;
  51. }
  52. DisplayServer *ds = DisplayServer::get_singleton();
  53. global_menu_name = "__PopupMenu#" + itos(get_instance_id());
  54. ds->global_menu_set_popup_callbacks(global_menu_name, callable_mp(this, &PopupMenu::_about_to_popup), callable_mp(this, &PopupMenu::_about_to_close));
  55. for (int i = 0; i < items.size(); i++) {
  56. Item &item = items.write[i];
  57. if (item.separator) {
  58. ds->global_menu_add_separator(global_menu_name);
  59. } else {
  60. int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), i);
  61. if (!item.submenu.is_empty()) {
  62. PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(item.submenu));
  63. if (pm) {
  64. String submenu_name = pm->bind_global_menu();
  65. ds->global_menu_set_item_submenu(global_menu_name, index, submenu_name);
  66. item.submenu_bound = true;
  67. }
  68. }
  69. if (item.checkable_type == Item::CHECKABLE_TYPE_CHECK_BOX) {
  70. ds->global_menu_set_item_checkable(global_menu_name, index, true);
  71. } else if (item.checkable_type == Item::CHECKABLE_TYPE_RADIO_BUTTON) {
  72. ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
  73. }
  74. ds->global_menu_set_item_checked(global_menu_name, index, item.checked);
  75. ds->global_menu_set_item_disabled(global_menu_name, index, item.disabled);
  76. ds->global_menu_set_item_max_states(global_menu_name, index, item.max_states);
  77. ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
  78. ds->global_menu_set_item_state(global_menu_name, index, item.state);
  79. ds->global_menu_set_item_indentation_level(global_menu_name, index, item.indent);
  80. ds->global_menu_set_item_tooltip(global_menu_name, index, item.tooltip);
  81. if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
  82. Array events = item.shortcut->get_events();
  83. for (int j = 0; j < events.size(); j++) {
  84. Ref<InputEventKey> ie = events[j];
  85. if (ie.is_valid()) {
  86. ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
  87. break;
  88. }
  89. }
  90. } else if (item.accel != Key::NONE) {
  91. ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
  92. }
  93. }
  94. }
  95. return global_menu_name;
  96. }
  97. void PopupMenu::unbind_global_menu() {
  98. if (global_menu_name.is_empty()) {
  99. return;
  100. }
  101. for (int i = 0; i < items.size(); i++) {
  102. Item &item = items.write[i];
  103. if (!item.submenu.is_empty()) {
  104. PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(item.submenu));
  105. if (pm) {
  106. pm->unbind_global_menu();
  107. }
  108. item.submenu_bound = false;
  109. }
  110. }
  111. DisplayServer::get_singleton()->global_menu_clear(global_menu_name);
  112. global_menu_name = String();
  113. }
  114. String PopupMenu::_get_accel_text(const Item &p_item) const {
  115. if (p_item.shortcut.is_valid()) {
  116. return p_item.shortcut->get_as_text();
  117. } else if (p_item.accel != Key::NONE) {
  118. return keycode_get_string(p_item.accel);
  119. }
  120. return String();
  121. }
  122. Size2 PopupMenu::_get_item_icon_size(int p_idx) const {
  123. const PopupMenu::Item &item = items[p_idx];
  124. Size2 icon_size = item.get_icon_size();
  125. int max_width = 0;
  126. if (theme_cache.icon_max_width > 0) {
  127. max_width = theme_cache.icon_max_width;
  128. }
  129. if (item.icon_max_width > 0 && (max_width == 0 || item.icon_max_width < max_width)) {
  130. max_width = item.icon_max_width;
  131. }
  132. if (max_width > 0 && icon_size.width > max_width) {
  133. icon_size.height = icon_size.height * max_width / icon_size.width;
  134. icon_size.width = max_width;
  135. }
  136. return icon_size;
  137. }
  138. Size2 PopupMenu::_get_contents_minimum_size() const {
  139. Size2 minsize = theme_cache.panel_style->get_minimum_size(); // Accounts for margin in the margin container
  140. 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
  141. float max_w = 0.0;
  142. float icon_w = 0.0;
  143. int check_w = MAX(theme_cache.checked->get_width(), theme_cache.radio_checked->get_width()) + theme_cache.h_separation;
  144. int accel_max_w = 0;
  145. bool has_check = false;
  146. for (int i = 0; i < items.size(); i++) {
  147. Size2 item_size;
  148. const_cast<PopupMenu *>(this)->_shape_item(i);
  149. Size2 icon_size = _get_item_icon_size(i);
  150. item_size.height = _get_item_height(i);
  151. icon_w = MAX(icon_size.width, icon_w);
  152. item_size.width += items[i].indent * theme_cache.indent;
  153. if (items[i].checkable_type && !items[i].separator) {
  154. has_check = true;
  155. }
  156. item_size.width += items[i].text_buf->get_size().x;
  157. item_size.height += theme_cache.v_separation;
  158. if (items[i].accel != Key::NONE || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) {
  159. int accel_w = theme_cache.h_separation * 2;
  160. accel_w += items[i].accel_text_buf->get_size().x;
  161. accel_max_w = MAX(accel_w, accel_max_w);
  162. }
  163. if (!items[i].submenu.is_empty()) {
  164. item_size.width += theme_cache.submenu->get_width();
  165. }
  166. max_w = MAX(max_w, item_size.width);
  167. minsize.height += item_size.height;
  168. }
  169. int item_side_padding = theme_cache.item_start_padding + theme_cache.item_end_padding;
  170. minsize.width += max_w + icon_w + accel_max_w + item_side_padding;
  171. if (has_check) {
  172. minsize.width += check_w;
  173. }
  174. if (is_inside_tree()) {
  175. int height_limit = get_usable_parent_rect().size.height;
  176. if (minsize.height > height_limit) {
  177. minsize.height = height_limit;
  178. }
  179. }
  180. return minsize;
  181. }
  182. int PopupMenu::_get_item_height(int p_idx) const {
  183. ERR_FAIL_INDEX_V(p_idx, items.size(), 0);
  184. Size2 icon_size = _get_item_icon_size(p_idx);
  185. int icon_height = icon_size.height;
  186. if (items[p_idx].checkable_type && !items[p_idx].separator) {
  187. icon_height = MAX(icon_height, MAX(theme_cache.checked->get_height(), theme_cache.radio_checked->get_height()));
  188. }
  189. int text_height = items[p_idx].text_buf->get_size().height;
  190. if (text_height == 0 && !items[p_idx].separator) {
  191. text_height = theme_cache.font->get_height(theme_cache.font_size);
  192. }
  193. int separator_height = 0;
  194. if (items[p_idx].separator) {
  195. separator_height = MAX(theme_cache.separator_style->get_minimum_size().height, MAX(theme_cache.labeled_separator_left->get_minimum_size().height, theme_cache.labeled_separator_right->get_minimum_size().height));
  196. }
  197. return MAX(separator_height, MAX(text_height, icon_height));
  198. }
  199. int PopupMenu::_get_items_total_height() const {
  200. // Get total height of all items by taking max of icon height and font height
  201. int items_total_height = 0;
  202. for (int i = 0; i < items.size(); i++) {
  203. items_total_height += _get_item_height(i) + theme_cache.v_separation;
  204. }
  205. // Subtract a separator which is not needed for the last item.
  206. return items_total_height - theme_cache.v_separation;
  207. }
  208. int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
  209. if (p_over.x < 0 || p_over.x >= get_size().width) {
  210. return -1;
  211. }
  212. // Accounts for margin in the margin container
  213. Point2 ofs = theme_cache.panel_style->get_offset() + Point2(0, theme_cache.v_separation / 2);
  214. if (ofs.y > p_over.y) {
  215. return -1;
  216. }
  217. for (int i = 0; i < items.size(); i++) {
  218. ofs.y += i > 0 ? theme_cache.v_separation : (float)theme_cache.v_separation / 2;
  219. ofs.y += _get_item_height(i);
  220. if (p_over.y - control->get_position().y < ofs.y) {
  221. return i;
  222. }
  223. }
  224. return -1;
  225. }
  226. void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) {
  227. Node *n = get_node_or_null(items[p_over].submenu);
  228. ERR_FAIL_NULL_MSG(n, "Item subnode does not exist: '" + items[p_over].submenu + "'.");
  229. Popup *submenu_popup = Object::cast_to<Popup>(n);
  230. ERR_FAIL_NULL_MSG(submenu_popup, "Item subnode is not a Popup: '" + items[p_over].submenu + "'.");
  231. if (submenu_popup->is_visible()) {
  232. return; // Already visible.
  233. }
  234. Point2 this_pos = get_position();
  235. Rect2 this_rect(this_pos, get_size());
  236. float scroll_offset = control->get_position().y;
  237. submenu_popup->reset_size(); // Shrink the popup size to its contents.
  238. Size2 submenu_size = submenu_popup->get_size();
  239. Point2 submenu_pos;
  240. if (control->is_layout_rtl()) {
  241. submenu_pos = this_pos + Point2(-submenu_size.width, items[p_over]._ofs_cache + scroll_offset - theme_cache.v_separation / 2);
  242. } else {
  243. submenu_pos = this_pos + Point2(this_rect.size.width, items[p_over]._ofs_cache + scroll_offset - theme_cache.v_separation / 2);
  244. }
  245. // Fix pos if going outside parent rect.
  246. if (submenu_pos.x < get_parent_rect().position.x) {
  247. submenu_pos.x = this_pos.x + submenu_size.width;
  248. }
  249. if (submenu_pos.x + submenu_size.width > get_parent_rect().position.x + get_parent_rect().size.width) {
  250. submenu_pos.x = this_pos.x - submenu_size.width;
  251. }
  252. submenu_popup->set_position(submenu_pos);
  253. PopupMenu *submenu_pum = Object::cast_to<PopupMenu>(submenu_popup);
  254. if (!submenu_pum) {
  255. submenu_popup->popup();
  256. return;
  257. }
  258. submenu_pum->activated_by_keyboard = p_by_keyboard;
  259. // If not triggered by the mouse, start the popup with its first enabled item focused.
  260. if (p_by_keyboard) {
  261. for (int i = 0; i < submenu_pum->get_item_count(); i++) {
  262. if (!submenu_pum->is_item_disabled(i)) {
  263. submenu_pum->set_focused_item(i);
  264. break;
  265. }
  266. }
  267. }
  268. submenu_pum->popup();
  269. // Set autohide areas.
  270. Rect2 safe_area = this_rect;
  271. safe_area.position.y += items[p_over]._ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2;
  272. safe_area.size.y = items[p_over]._height_cache + theme_cache.v_separation;
  273. Viewport *vp = submenu_popup->get_embedder();
  274. if (vp) {
  275. vp->subwindow_set_popup_safe_rect(submenu_popup, safe_area);
  276. } else {
  277. DisplayServer::get_singleton()->window_set_popup_safe_rect(submenu_popup->get_window_id(), safe_area);
  278. }
  279. // Make the position of the parent popup relative to submenu popup.
  280. this_rect.position = this_rect.position - submenu_pum->get_position();
  281. // Autohide area above the submenu item.
  282. submenu_pum->clear_autohide_areas();
  283. 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 + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2));
  284. // If there is an area below the submenu item, add an autohide area there.
  285. if (items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset <= control->get_size().height) {
  286. int from = items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset + theme_cache.v_separation / 2 + theme_cache.panel_style->get_offset().height;
  287. submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from));
  288. }
  289. }
  290. void PopupMenu::_parent_focused() {
  291. if (is_embedded()) {
  292. Point2 mouse_pos_adjusted;
  293. Window *window_parent = Object::cast_to<Window>(get_parent()->get_viewport());
  294. while (window_parent) {
  295. if (!window_parent->is_embedded()) {
  296. mouse_pos_adjusted += window_parent->get_position();
  297. break;
  298. }
  299. window_parent = Object::cast_to<Window>(window_parent->get_parent()->get_viewport());
  300. }
  301. Rect2 safe_area = get_embedder()->subwindow_get_popup_safe_rect(this);
  302. Point2 pos = DisplayServer::get_singleton()->mouse_get_position() - mouse_pos_adjusted;
  303. if (safe_area == Rect2i() || !safe_area.has_point(pos)) {
  304. Popup::_parent_focused();
  305. } else {
  306. grab_focus();
  307. }
  308. }
  309. }
  310. void PopupMenu::_submenu_timeout() {
  311. if (mouse_over == submenu_over) {
  312. _activate_submenu(mouse_over);
  313. }
  314. submenu_over = -1;
  315. }
  316. void PopupMenu::gui_input(const Ref<InputEvent> &p_event) {
  317. ERR_FAIL_COND(p_event.is_null());
  318. if (!items.is_empty()) {
  319. Input *input = Input::get_singleton();
  320. Ref<InputEventJoypadMotion> joypadmotion_event = p_event;
  321. Ref<InputEventJoypadButton> joypadbutton_event = p_event;
  322. bool is_joypad_event = (joypadmotion_event.is_valid() || joypadbutton_event.is_valid());
  323. if (p_event->is_action("ui_down", true) && p_event->is_pressed()) {
  324. if (is_joypad_event) {
  325. if (!input->is_action_just_pressed("ui_down", true)) {
  326. return;
  327. }
  328. set_process_internal(true);
  329. }
  330. int search_from = mouse_over + 1;
  331. if (search_from >= items.size()) {
  332. search_from = 0;
  333. }
  334. bool match_found = false;
  335. for (int i = search_from; i < items.size(); i++) {
  336. if (!items[i].separator && !items[i].disabled) {
  337. mouse_over = i;
  338. emit_signal(SNAME("id_focused"), i);
  339. scroll_to_item(i);
  340. control->queue_redraw();
  341. set_input_as_handled();
  342. match_found = true;
  343. break;
  344. }
  345. }
  346. if (!match_found) {
  347. // If the last item is not selectable, try re-searching from the start.
  348. for (int i = 0; i < search_from; i++) {
  349. if (!items[i].separator && !items[i].disabled) {
  350. mouse_over = i;
  351. emit_signal(SNAME("id_focused"), i);
  352. scroll_to_item(i);
  353. control->queue_redraw();
  354. set_input_as_handled();
  355. break;
  356. }
  357. }
  358. }
  359. } else if (p_event->is_action("ui_up", true) && p_event->is_pressed()) {
  360. if (is_joypad_event) {
  361. if (!input->is_action_just_pressed("ui_up", true)) {
  362. return;
  363. }
  364. set_process_internal(true);
  365. }
  366. int search_from = mouse_over - 1;
  367. if (search_from < 0) {
  368. search_from = items.size() - 1;
  369. }
  370. bool match_found = false;
  371. for (int i = search_from; i >= 0; i--) {
  372. if (!items[i].separator && !items[i].disabled) {
  373. mouse_over = i;
  374. emit_signal(SNAME("id_focused"), i);
  375. scroll_to_item(i);
  376. control->queue_redraw();
  377. set_input_as_handled();
  378. match_found = true;
  379. break;
  380. }
  381. }
  382. if (!match_found) {
  383. // If the first item is not selectable, try re-searching from the end.
  384. for (int i = items.size() - 1; i >= search_from; i--) {
  385. if (!items[i].separator && !items[i].disabled) {
  386. mouse_over = i;
  387. emit_signal(SNAME("id_focused"), i);
  388. scroll_to_item(i);
  389. control->queue_redraw();
  390. set_input_as_handled();
  391. break;
  392. }
  393. }
  394. }
  395. } else if (p_event->is_action("ui_left", true) && p_event->is_pressed()) {
  396. Node *n = get_parent();
  397. if (n) {
  398. if (Object::cast_to<PopupMenu>(n)) {
  399. hide();
  400. set_input_as_handled();
  401. } else if (Object::cast_to<MenuBar>(n)) {
  402. Object::cast_to<MenuBar>(n)->gui_input(p_event);
  403. set_input_as_handled();
  404. return;
  405. }
  406. }
  407. } else if (p_event->is_action("ui_right", true) && p_event->is_pressed()) {
  408. if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator && !items[mouse_over].submenu.is_empty() && submenu_over != mouse_over) {
  409. _activate_submenu(mouse_over, true);
  410. set_input_as_handled();
  411. } else {
  412. Node *n = get_parent();
  413. if (n && Object::cast_to<MenuBar>(n)) {
  414. Object::cast_to<MenuBar>(n)->gui_input(p_event);
  415. set_input_as_handled();
  416. return;
  417. }
  418. }
  419. } else if (p_event->is_action("ui_accept", true) && p_event->is_pressed()) {
  420. if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator) {
  421. if (!items[mouse_over].submenu.is_empty() && submenu_over != mouse_over) {
  422. _activate_submenu(mouse_over, true);
  423. } else {
  424. activate_item(mouse_over);
  425. }
  426. set_input_as_handled();
  427. }
  428. }
  429. }
  430. // Make an area which does not include v scrollbar, so that items are not activated when dragging scrollbar.
  431. Rect2 item_clickable_area = scroll_container->get_rect();
  432. if (scroll_container->get_v_scroll_bar()->is_visible_in_tree()) {
  433. if (is_layout_rtl()) {
  434. item_clickable_area.position.x += scroll_container->get_v_scroll_bar()->get_size().width;
  435. } else {
  436. item_clickable_area.size.width -= scroll_container->get_v_scroll_bar()->get_size().width;
  437. }
  438. }
  439. Ref<InputEventMouseButton> b = p_event;
  440. if (b.is_valid()) {
  441. if (!item_clickable_area.has_point(b->get_position())) {
  442. return;
  443. }
  444. MouseButton button_idx = b->get_button_index();
  445. if (!b->is_pressed()) {
  446. // Activate the item on release of either the left mouse button or
  447. // any mouse button held down when the popup was opened.
  448. // This allows for opening the popup and triggering an action in a single mouse click.
  449. if (button_idx == MouseButton::LEFT || initial_button_mask.has_flag(mouse_button_to_mask(button_idx))) {
  450. bool was_during_grabbed_click = during_grabbed_click;
  451. during_grabbed_click = false;
  452. initial_button_mask.clear();
  453. // Disable clicks under a time threshold to avoid selection right when opening the popup.
  454. uint64_t now = OS::get_singleton()->get_ticks_msec();
  455. uint64_t diff = now - popup_time_msec;
  456. if (diff < 150) {
  457. return;
  458. }
  459. int over = _get_mouse_over(b->get_position());
  460. if (over < 0) {
  461. if (!was_during_grabbed_click) {
  462. hide();
  463. }
  464. return;
  465. }
  466. if (items[over].separator || items[over].disabled) {
  467. return;
  468. }
  469. if (!items[over].submenu.is_empty()) {
  470. _activate_submenu(over);
  471. return;
  472. }
  473. activate_item(over);
  474. }
  475. }
  476. }
  477. Ref<InputEventMouseMotion> m = p_event;
  478. if (m.is_valid()) {
  479. if (m->get_velocity().is_zero_approx()) {
  480. return;
  481. }
  482. activated_by_keyboard = false;
  483. for (const Rect2 &E : autohide_areas) {
  484. if (!Rect2(Point2(), get_size()).has_point(m->get_position()) && E.has_point(m->get_position())) {
  485. _close_pressed();
  486. return;
  487. }
  488. }
  489. if (!item_clickable_area.has_point(m->get_position())) {
  490. return;
  491. }
  492. int over = _get_mouse_over(m->get_position());
  493. int id = (over < 0 || items[over].separator || items[over].disabled) ? -1 : (items[over].id >= 0 ? items[over].id : over);
  494. if (id < 0) {
  495. mouse_over = -1;
  496. control->queue_redraw();
  497. return;
  498. }
  499. if (!items[over].submenu.is_empty() && submenu_over != over) {
  500. submenu_over = over;
  501. submenu_timer->start();
  502. }
  503. if (over != mouse_over) {
  504. mouse_over = over;
  505. control->queue_redraw();
  506. }
  507. }
  508. Ref<InputEventKey> k = p_event;
  509. if (allow_search && k.is_valid() && k->get_unicode() && k->is_pressed()) {
  510. uint64_t now = OS::get_singleton()->get_ticks_msec();
  511. uint64_t diff = now - search_time_msec;
  512. uint64_t max_interval = uint64_t(GLOBAL_GET("gui/timers/incremental_search_max_interval_msec"));
  513. search_time_msec = now;
  514. if (diff > max_interval) {
  515. search_string = "";
  516. }
  517. if (String::chr(k->get_unicode()) != search_string) {
  518. search_string += String::chr(k->get_unicode());
  519. }
  520. for (int i = mouse_over + 1; i <= items.size(); i++) {
  521. if (i == items.size()) {
  522. if (mouse_over <= 0) {
  523. break;
  524. } else {
  525. i = 0;
  526. }
  527. }
  528. if (i == mouse_over) {
  529. break;
  530. }
  531. if (items[i].text.findn(search_string) == 0) {
  532. mouse_over = i;
  533. emit_signal(SNAME("id_focused"), i);
  534. scroll_to_item(i);
  535. control->queue_redraw();
  536. set_input_as_handled();
  537. break;
  538. }
  539. }
  540. }
  541. }
  542. void PopupMenu::_draw_items() {
  543. control->set_custom_minimum_size(Size2(0, _get_items_total_height()));
  544. RID ci = control->get_canvas_item();
  545. Size2 margin_size;
  546. margin_size.width = margin_container->get_margin_size(SIDE_LEFT) + margin_container->get_margin_size(SIDE_RIGHT);
  547. margin_size.height = margin_container->get_margin_size(SIDE_TOP) + margin_container->get_margin_size(SIDE_BOTTOM);
  548. // Space between the item content and the sides of popup menu.
  549. bool rtl = control->is_layout_rtl();
  550. // In Item::checkable_type enum order (less the non-checkable member), with disabled repeated at the end.
  551. Ref<Texture2D> check[] = { theme_cache.checked, theme_cache.radio_checked, theme_cache.checked_disabled, theme_cache.radio_checked_disabled };
  552. Ref<Texture2D> uncheck[] = { theme_cache.unchecked, theme_cache.radio_unchecked, theme_cache.unchecked_disabled, theme_cache.radio_unchecked_disabled };
  553. Ref<Texture2D> submenu;
  554. if (rtl) {
  555. submenu = theme_cache.submenu_mirrored;
  556. } else {
  557. submenu = theme_cache.submenu;
  558. }
  559. float scroll_width = scroll_container->get_v_scroll_bar()->is_visible_in_tree() ? scroll_container->get_v_scroll_bar()->get_size().width : 0;
  560. float display_width = control->get_size().width - scroll_width;
  561. // Find the widest icon and whether any items have a checkbox, and store the offsets for each.
  562. float icon_ofs = 0.0;
  563. bool has_check = false;
  564. for (int i = 0; i < items.size(); i++) {
  565. if (items[i].separator) {
  566. continue;
  567. }
  568. Size2 icon_size = _get_item_icon_size(i);
  569. icon_ofs = MAX(icon_size.width, icon_ofs);
  570. if (items[i].checkable_type) {
  571. has_check = true;
  572. }
  573. }
  574. if (icon_ofs > 0.0) {
  575. icon_ofs += theme_cache.h_separation;
  576. }
  577. float check_ofs = 0.0;
  578. if (has_check) {
  579. for (int i = 0; i < 4; i++) {
  580. check_ofs = MAX(check_ofs, check[i]->get_width());
  581. check_ofs = MAX(check_ofs, uncheck[i]->get_width());
  582. }
  583. check_ofs += theme_cache.h_separation;
  584. }
  585. Point2 ofs;
  586. // Loop through all items and draw each.
  587. for (int i = 0; i < items.size(); i++) {
  588. // For the first item only add half a separation. For all other items, add a whole separation to the offset.
  589. ofs.y += i > 0 ? theme_cache.v_separation : (float)theme_cache.v_separation / 2;
  590. _shape_item(i);
  591. Point2 item_ofs = ofs;
  592. Size2 icon_size = _get_item_icon_size(i);
  593. float h = _get_item_height(i);
  594. if (i == mouse_over) {
  595. if (rtl) {
  596. theme_cache.hover_style->draw(ci, Rect2(item_ofs + Point2(scroll_width, -theme_cache.v_separation / 2), Size2(display_width, h + theme_cache.v_separation)));
  597. } else {
  598. theme_cache.hover_style->draw(ci, Rect2(item_ofs + Point2(0, -theme_cache.v_separation / 2), Size2(display_width, h + theme_cache.v_separation)));
  599. }
  600. }
  601. String text = items[i].xl_text;
  602. // Separator
  603. item_ofs.x += items[i].indent * theme_cache.indent;
  604. if (items[i].separator) {
  605. if (!text.is_empty() || items[i].icon.is_valid()) {
  606. int content_size = items[i].text_buf->get_size().width + theme_cache.h_separation * 2;
  607. if (items[i].icon.is_valid()) {
  608. content_size += icon_size.width + theme_cache.h_separation;
  609. }
  610. int content_center = display_width / 2;
  611. int content_left = content_center - content_size / 2;
  612. int content_right = content_center + content_size / 2;
  613. if (content_left > item_ofs.x) {
  614. int sep_h = theme_cache.labeled_separator_left->get_minimum_size().height;
  615. int sep_ofs = Math::floor((h - sep_h) / 2.0);
  616. theme_cache.labeled_separator_left->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(MAX(0, content_left - item_ofs.x), sep_h)));
  617. }
  618. if (content_right < display_width) {
  619. int sep_h = theme_cache.labeled_separator_right->get_minimum_size().height;
  620. int sep_ofs = Math::floor((h - sep_h) / 2.0);
  621. theme_cache.labeled_separator_right->draw(ci, Rect2(Point2(content_right, item_ofs.y + sep_ofs), Size2(MAX(0, display_width - content_right), sep_h)));
  622. }
  623. } else {
  624. int sep_h = theme_cache.separator_style->get_minimum_size().height;
  625. int sep_ofs = Math::floor((h - sep_h) / 2.0);
  626. theme_cache.separator_style->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(display_width, sep_h)));
  627. }
  628. }
  629. Color icon_color(1, 1, 1, items[i].disabled && !items[i].separator ? 0.5 : 1);
  630. icon_color *= items[i].icon_modulate;
  631. // For non-separator items, add some padding for the content.
  632. if (!items[i].separator) {
  633. item_ofs.x += theme_cache.item_start_padding;
  634. }
  635. // Checkboxes
  636. if (items[i].checkable_type && !items[i].separator) {
  637. int disabled = int(items[i].disabled) * 2;
  638. Texture2D *icon = (items[i].checked ? check[items[i].checkable_type - 1 + disabled] : uncheck[items[i].checkable_type - 1 + disabled]).ptr();
  639. if (rtl) {
  640. 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);
  641. } else {
  642. icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon->get_height()) / 2.0)), icon_color);
  643. }
  644. }
  645. int separator_ofs = (display_width - items[i].text_buf->get_size().width) / 2;
  646. // Icon
  647. if (items[i].icon.is_valid()) {
  648. const Point2 icon_offset = Point2(0, Math::floor((h - icon_size.height) / 2.0));
  649. Point2 icon_pos;
  650. if (items[i].separator) {
  651. separator_ofs -= (icon_size.width + theme_cache.h_separation) / 2;
  652. if (rtl) {
  653. icon_pos = Size2(control->get_size().width - item_ofs.x - separator_ofs - icon_size.width, item_ofs.y);
  654. } else {
  655. icon_pos = item_ofs + Size2(separator_ofs, 0);
  656. separator_ofs += icon_size.width + theme_cache.h_separation;
  657. }
  658. } else {
  659. if (rtl) {
  660. icon_pos = Size2(control->get_size().width - item_ofs.x - check_ofs - icon_size.width, item_ofs.y);
  661. } else {
  662. icon_pos = item_ofs + Size2(check_ofs, 0);
  663. }
  664. }
  665. items[i].icon->draw_rect(ci, Rect2(icon_pos + icon_offset, icon_size), false, icon_color);
  666. }
  667. // Submenu arrow on right hand side.
  668. if (!items[i].submenu.is_empty()) {
  669. if (rtl) {
  670. submenu->draw(ci, Point2(scroll_width + theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.item_end_padding, item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color);
  671. } else {
  672. submenu->draw(ci, Point2(display_width - theme_cache.panel_style->get_margin(SIDE_RIGHT) - submenu->get_width() - theme_cache.item_end_padding, item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color);
  673. }
  674. }
  675. // Text
  676. if (items[i].separator) {
  677. if (!text.is_empty()) {
  678. Vector2 text_pos = Point2(separator_ofs, item_ofs.y + Math::floor((h - items[i].text_buf->get_size().y) / 2.0));
  679. if (theme_cache.font_separator_outline_size > 0 && theme_cache.font_separator_outline_color.a > 0) {
  680. items[i].text_buf->draw_outline(ci, text_pos, theme_cache.font_separator_outline_size, theme_cache.font_separator_outline_color);
  681. }
  682. items[i].text_buf->draw(ci, text_pos, theme_cache.font_separator_color);
  683. }
  684. } else {
  685. item_ofs.x += icon_ofs + check_ofs;
  686. if (rtl) {
  687. 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));
  688. if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) {
  689. items[i].text_buf->draw_outline(ci, text_pos, theme_cache.font_outline_size, theme_cache.font_outline_color);
  690. }
  691. items[i].text_buf->draw(ci, text_pos, items[i].disabled ? theme_cache.font_disabled_color : (i == mouse_over ? theme_cache.font_hover_color : theme_cache.font_color));
  692. } else {
  693. Vector2 text_pos = item_ofs + Point2(0, Math::floor((h - items[i].text_buf->get_size().y) / 2.0));
  694. if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) {
  695. items[i].text_buf->draw_outline(ci, text_pos, theme_cache.font_outline_size, theme_cache.font_outline_color);
  696. }
  697. items[i].text_buf->draw(ci, text_pos, items[i].disabled ? theme_cache.font_disabled_color : (i == mouse_over ? theme_cache.font_hover_color : theme_cache.font_color));
  698. }
  699. }
  700. // Accelerator / Shortcut
  701. if (items[i].accel != Key::NONE || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) {
  702. if (rtl) {
  703. item_ofs.x = scroll_width + theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.item_end_padding;
  704. } else {
  705. item_ofs.x = display_width - theme_cache.panel_style->get_margin(SIDE_RIGHT) - items[i].accel_text_buf->get_size().x - theme_cache.item_end_padding;
  706. }
  707. Vector2 text_pos = item_ofs + Point2(0, Math::floor((h - items[i].text_buf->get_size().y) / 2.0));
  708. if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) {
  709. items[i].accel_text_buf->draw_outline(ci, text_pos, theme_cache.font_outline_size, theme_cache.font_outline_color);
  710. }
  711. items[i].accel_text_buf->draw(ci, text_pos, i == mouse_over ? theme_cache.font_hover_color : theme_cache.font_accelerator_color);
  712. }
  713. // Cache the item vertical offset from the first item and the height.
  714. items.write[i]._ofs_cache = ofs.y;
  715. items.write[i]._height_cache = h;
  716. ofs.y += h;
  717. }
  718. }
  719. void PopupMenu::_draw_background() {
  720. RID ci2 = margin_container->get_canvas_item();
  721. theme_cache.panel_style->draw(ci2, Rect2(Point2(), margin_container->get_size()));
  722. }
  723. void PopupMenu::_minimum_lifetime_timeout() {
  724. close_allowed = true;
  725. // If the mouse still isn't in this popup after timer expires, close.
  726. if (!activated_by_keyboard && !get_visible_rect().has_point(get_mouse_position())) {
  727. _close_pressed();
  728. }
  729. }
  730. void PopupMenu::_close_pressed() {
  731. // Only apply minimum lifetime to submenus.
  732. PopupMenu *parent_pum = Object::cast_to<PopupMenu>(get_parent());
  733. if (!parent_pum) {
  734. Popup::_close_pressed();
  735. return;
  736. }
  737. // If the timer has expired, close. If timer is still running, do nothing.
  738. if (close_allowed) {
  739. close_allowed = false;
  740. Popup::_close_pressed();
  741. } else if (minimum_lifetime_timer->is_stopped()) {
  742. minimum_lifetime_timer->start();
  743. }
  744. }
  745. void PopupMenu::_shape_item(int p_idx) {
  746. if (items.write[p_idx].dirty) {
  747. items.write[p_idx].text_buf->clear();
  748. Ref<Font> font = items[p_idx].separator ? theme_cache.font_separator : theme_cache.font;
  749. int font_size = items[p_idx].separator ? theme_cache.font_separator_size : theme_cache.font_size;
  750. if (items[p_idx].text_direction == Control::TEXT_DIRECTION_INHERITED) {
  751. items.write[p_idx].text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
  752. } else {
  753. items.write[p_idx].text_buf->set_direction((TextServer::Direction)items[p_idx].text_direction);
  754. }
  755. items.write[p_idx].text_buf->add_string(items.write[p_idx].xl_text, font, font_size, items[p_idx].language);
  756. items.write[p_idx].accel_text_buf->clear();
  757. items.write[p_idx].accel_text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
  758. items.write[p_idx].accel_text_buf->add_string(_get_accel_text(items.write[p_idx]), font, font_size);
  759. items.write[p_idx].dirty = false;
  760. }
  761. }
  762. void PopupMenu::_menu_changed() {
  763. emit_signal(SNAME("menu_changed"));
  764. }
  765. void PopupMenu::add_child_notify(Node *p_child) {
  766. Window::add_child_notify(p_child);
  767. if (Object::cast_to<PopupMenu>(p_child) && !global_menu_name.is_empty()) {
  768. String node_name = p_child->get_name();
  769. PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(node_name));
  770. for (int i = 0; i < items.size(); i++) {
  771. if (items[i].submenu == node_name) {
  772. String submenu_name = pm->bind_global_menu();
  773. DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, i, submenu_name);
  774. items.write[i].submenu_bound = true;
  775. }
  776. }
  777. }
  778. _menu_changed();
  779. }
  780. void PopupMenu::remove_child_notify(Node *p_child) {
  781. Window::remove_child_notify(p_child);
  782. PopupMenu *pm = Object::cast_to<PopupMenu>(p_child);
  783. if (!pm) {
  784. return;
  785. }
  786. if (Object::cast_to<PopupMenu>(p_child) && !global_menu_name.is_empty()) {
  787. String node_name = p_child->get_name();
  788. for (int i = 0; i < items.size(); i++) {
  789. if (items[i].submenu == node_name) {
  790. DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, i, String());
  791. items.write[i].submenu_bound = false;
  792. }
  793. }
  794. pm->unbind_global_menu();
  795. }
  796. _menu_changed();
  797. }
  798. void PopupMenu::_notification(int p_what) {
  799. switch (p_what) {
  800. case NOTIFICATION_ENTER_TREE: {
  801. PopupMenu *pm = Object::cast_to<PopupMenu>(get_parent());
  802. if (pm) {
  803. // Inherit submenu's popup delay time from parent menu.
  804. float pm_delay = pm->get_submenu_popup_delay();
  805. set_submenu_popup_delay(pm_delay);
  806. }
  807. if (!is_embedded()) {
  808. set_flag(FLAG_NO_FOCUS, true);
  809. }
  810. } break;
  811. case NOTIFICATION_THEME_CHANGED:
  812. case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
  813. case NOTIFICATION_TRANSLATION_CHANGED: {
  814. DisplayServer *ds = DisplayServer::get_singleton();
  815. bool is_global = !global_menu_name.is_empty();
  816. for (int i = 0; i < items.size(); i++) {
  817. Item &item = items.write[i];
  818. item.xl_text = atr(item.text);
  819. item.dirty = true;
  820. if (is_global) {
  821. ds->global_menu_set_item_text(global_menu_name, i, item.xl_text);
  822. }
  823. _shape_item(i);
  824. }
  825. child_controls_changed();
  826. _menu_changed();
  827. control->queue_redraw();
  828. } break;
  829. case NOTIFICATION_WM_MOUSE_ENTER: {
  830. grab_focus();
  831. } break;
  832. case NOTIFICATION_WM_MOUSE_EXIT: {
  833. if (mouse_over >= 0 && (items[mouse_over].submenu.is_empty() || submenu_over != -1)) {
  834. mouse_over = -1;
  835. control->queue_redraw();
  836. }
  837. } break;
  838. case NOTIFICATION_POST_POPUP: {
  839. initial_button_mask = Input::get_singleton()->get_mouse_button_mask();
  840. during_grabbed_click = (bool)initial_button_mask;
  841. } break;
  842. case NOTIFICATION_INTERNAL_PROCESS: {
  843. Input *input = Input::get_singleton();
  844. if (input->is_action_just_released("ui_up") || input->is_action_just_released("ui_down")) {
  845. gamepad_event_delay_ms = DEFAULT_GAMEPAD_EVENT_DELAY_MS;
  846. set_process_internal(false);
  847. return;
  848. }
  849. gamepad_event_delay_ms -= get_process_delta_time();
  850. if (gamepad_event_delay_ms <= 0) {
  851. if (input->is_action_pressed("ui_down")) {
  852. gamepad_event_delay_ms = GAMEPAD_EVENT_REPEAT_RATE_MS + gamepad_event_delay_ms;
  853. int search_from = mouse_over + 1;
  854. if (search_from >= items.size()) {
  855. search_from = 0;
  856. }
  857. bool match_found = false;
  858. for (int i = search_from; i < items.size(); i++) {
  859. if (!items[i].separator && !items[i].disabled) {
  860. mouse_over = i;
  861. emit_signal(SNAME("id_focused"), i);
  862. scroll_to_item(i);
  863. control->queue_redraw();
  864. match_found = true;
  865. break;
  866. }
  867. }
  868. if (!match_found) {
  869. // If the last item is not selectable, try re-searching from the start.
  870. for (int i = 0; i < search_from; i++) {
  871. if (!items[i].separator && !items[i].disabled) {
  872. mouse_over = i;
  873. emit_signal(SNAME("id_focused"), i);
  874. scroll_to_item(i);
  875. control->queue_redraw();
  876. break;
  877. }
  878. }
  879. }
  880. }
  881. if (input->is_action_pressed("ui_up")) {
  882. gamepad_event_delay_ms = GAMEPAD_EVENT_REPEAT_RATE_MS + gamepad_event_delay_ms;
  883. int search_from = mouse_over - 1;
  884. if (search_from < 0) {
  885. search_from = items.size() - 1;
  886. }
  887. bool match_found = false;
  888. for (int i = search_from; i >= 0; i--) {
  889. if (!items[i].separator && !items[i].disabled) {
  890. mouse_over = i;
  891. emit_signal(SNAME("id_focused"), i);
  892. scroll_to_item(i);
  893. control->queue_redraw();
  894. match_found = true;
  895. break;
  896. }
  897. }
  898. if (!match_found) {
  899. // If the first item is not selectable, try re-searching from the end.
  900. for (int i = items.size() - 1; i >= search_from; i--) {
  901. if (!items[i].separator && !items[i].disabled) {
  902. mouse_over = i;
  903. emit_signal(SNAME("id_focused"), i);
  904. scroll_to_item(i);
  905. control->queue_redraw();
  906. break;
  907. }
  908. }
  909. }
  910. }
  911. }
  912. // Only used when using operating system windows.
  913. if (!activated_by_keyboard && !is_embedded() && autohide_areas.size()) {
  914. Point2 mouse_pos = DisplayServer::get_singleton()->mouse_get_position();
  915. mouse_pos -= get_position();
  916. for (const Rect2 &E : autohide_areas) {
  917. if (!Rect2(Point2(), get_size()).has_point(mouse_pos) && E.has_point(mouse_pos)) {
  918. _close_pressed();
  919. return;
  920. }
  921. }
  922. }
  923. } break;
  924. case NOTIFICATION_VISIBILITY_CHANGED: {
  925. if (!is_visible()) {
  926. if (mouse_over >= 0) {
  927. mouse_over = -1;
  928. control->queue_redraw();
  929. }
  930. for (int i = 0; i < items.size(); i++) {
  931. if (items[i].submenu.is_empty()) {
  932. continue;
  933. }
  934. Node *n = get_node(items[i].submenu);
  935. if (!n) {
  936. continue;
  937. }
  938. PopupMenu *pm = Object::cast_to<PopupMenu>(n);
  939. if (!pm || !pm->is_visible()) {
  940. continue;
  941. }
  942. pm->hide();
  943. }
  944. set_process_internal(false);
  945. } else {
  946. if (!is_embedded()) {
  947. set_process_internal(true);
  948. }
  949. // Set margin on the margin container
  950. margin_container->begin_bulk_theme_override();
  951. margin_container->add_theme_constant_override("margin_left", theme_cache.panel_style->get_margin(Side::SIDE_LEFT));
  952. margin_container->add_theme_constant_override("margin_top", theme_cache.panel_style->get_margin(Side::SIDE_TOP));
  953. margin_container->add_theme_constant_override("margin_right", theme_cache.panel_style->get_margin(Side::SIDE_RIGHT));
  954. margin_container->add_theme_constant_override("margin_bottom", theme_cache.panel_style->get_margin(Side::SIDE_BOTTOM));
  955. margin_container->end_bulk_theme_override();
  956. }
  957. } break;
  958. }
  959. }
  960. /* Methods to add items with or without icon, checkbox, shortcut.
  961. * Be sure to keep them in sync when adding new properties in the Item struct.
  962. */
  963. #define ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel) \
  964. item.text = p_label; \
  965. item.xl_text = atr(p_label); \
  966. item.id = p_id == -1 ? items.size() : p_id; \
  967. item.accel = p_accel;
  968. void PopupMenu::add_item(const String &p_label, int p_id, Key p_accel) {
  969. Item item;
  970. ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
  971. items.push_back(item);
  972. if (!global_menu_name.is_empty()) {
  973. DisplayServer *ds = DisplayServer::get_singleton();
  974. int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
  975. if (item.accel != Key::NONE) {
  976. ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
  977. }
  978. }
  979. _shape_item(items.size() - 1);
  980. control->queue_redraw();
  981. child_controls_changed();
  982. notify_property_list_changed();
  983. _menu_changed();
  984. }
  985. void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) {
  986. Item item;
  987. ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
  988. item.icon = p_icon;
  989. items.push_back(item);
  990. if (!global_menu_name.is_empty()) {
  991. DisplayServer *ds = DisplayServer::get_singleton();
  992. int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
  993. if (item.accel != Key::NONE) {
  994. ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
  995. }
  996. ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
  997. }
  998. _shape_item(items.size() - 1);
  999. control->queue_redraw();
  1000. child_controls_changed();
  1001. notify_property_list_changed();
  1002. _menu_changed();
  1003. }
  1004. void PopupMenu::add_check_item(const String &p_label, int p_id, Key p_accel) {
  1005. Item item;
  1006. ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
  1007. item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
  1008. items.push_back(item);
  1009. if (!global_menu_name.is_empty()) {
  1010. DisplayServer *ds = DisplayServer::get_singleton();
  1011. int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
  1012. if (item.accel != Key::NONE) {
  1013. ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
  1014. }
  1015. ds->global_menu_set_item_checkable(global_menu_name, index, true);
  1016. }
  1017. _shape_item(items.size() - 1);
  1018. control->queue_redraw();
  1019. child_controls_changed();
  1020. notify_property_list_changed();
  1021. _menu_changed();
  1022. }
  1023. void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) {
  1024. Item item;
  1025. ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
  1026. item.icon = p_icon;
  1027. item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
  1028. items.push_back(item);
  1029. if (!global_menu_name.is_empty()) {
  1030. DisplayServer *ds = DisplayServer::get_singleton();
  1031. int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
  1032. if (item.accel != Key::NONE) {
  1033. ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
  1034. }
  1035. ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
  1036. ds->global_menu_set_item_checkable(global_menu_name, index, true);
  1037. }
  1038. _shape_item(items.size() - 1);
  1039. control->queue_redraw();
  1040. child_controls_changed();
  1041. notify_property_list_changed();
  1042. _menu_changed();
  1043. }
  1044. void PopupMenu::add_radio_check_item(const String &p_label, int p_id, Key p_accel) {
  1045. Item item;
  1046. ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
  1047. item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
  1048. items.push_back(item);
  1049. if (!global_menu_name.is_empty()) {
  1050. DisplayServer *ds = DisplayServer::get_singleton();
  1051. int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
  1052. if (item.accel != Key::NONE) {
  1053. ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
  1054. }
  1055. ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
  1056. }
  1057. _shape_item(items.size() - 1);
  1058. control->queue_redraw();
  1059. child_controls_changed();
  1060. notify_property_list_changed();
  1061. _menu_changed();
  1062. }
  1063. void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) {
  1064. Item item;
  1065. ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
  1066. item.icon = p_icon;
  1067. item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
  1068. items.push_back(item);
  1069. if (!global_menu_name.is_empty()) {
  1070. DisplayServer *ds = DisplayServer::get_singleton();
  1071. int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
  1072. if (item.accel != Key::NONE) {
  1073. ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
  1074. }
  1075. ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
  1076. ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
  1077. }
  1078. _shape_item(items.size() - 1);
  1079. control->queue_redraw();
  1080. child_controls_changed();
  1081. notify_property_list_changed();
  1082. _menu_changed();
  1083. }
  1084. void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_id, Key p_accel) {
  1085. Item item;
  1086. ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
  1087. item.max_states = p_max_states;
  1088. item.state = p_default_state;
  1089. items.push_back(item);
  1090. if (!global_menu_name.is_empty()) {
  1091. DisplayServer *ds = DisplayServer::get_singleton();
  1092. int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
  1093. if (item.accel != Key::NONE) {
  1094. ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
  1095. }
  1096. ds->global_menu_set_item_max_states(global_menu_name, index, item.max_states);
  1097. ds->global_menu_set_item_state(global_menu_name, index, item.state);
  1098. }
  1099. _shape_item(items.size() - 1);
  1100. control->queue_redraw();
  1101. child_controls_changed();
  1102. _menu_changed();
  1103. notify_property_list_changed();
  1104. }
  1105. #define ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, p_allow_echo) \
  1106. ERR_FAIL_COND_MSG(p_shortcut.is_null(), "Cannot add item with invalid Shortcut."); \
  1107. _ref_shortcut(p_shortcut); \
  1108. item.text = p_shortcut->get_name(); \
  1109. item.xl_text = atr(item.text); \
  1110. item.id = p_id == -1 ? items.size() : p_id; \
  1111. item.shortcut = p_shortcut; \
  1112. item.shortcut_is_global = p_global; \
  1113. item.allow_echo = p_allow_echo;
  1114. void PopupMenu::add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_global, bool p_allow_echo) {
  1115. Item item;
  1116. ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, p_allow_echo);
  1117. items.push_back(item);
  1118. if (!global_menu_name.is_empty()) {
  1119. DisplayServer *ds = DisplayServer::get_singleton();
  1120. int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
  1121. if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
  1122. Array events = item.shortcut->get_events();
  1123. for (int j = 0; j < events.size(); j++) {
  1124. Ref<InputEventKey> ie = events[j];
  1125. if (ie.is_valid()) {
  1126. ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
  1127. break;
  1128. }
  1129. }
  1130. }
  1131. }
  1132. _shape_item(items.size() - 1);
  1133. control->queue_redraw();
  1134. child_controls_changed();
  1135. notify_property_list_changed();
  1136. _menu_changed();
  1137. }
  1138. void PopupMenu::add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id, bool p_global, bool p_allow_echo) {
  1139. Item item;
  1140. ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, p_allow_echo);
  1141. item.icon = p_icon;
  1142. items.push_back(item);
  1143. if (!global_menu_name.is_empty()) {
  1144. DisplayServer *ds = DisplayServer::get_singleton();
  1145. int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
  1146. if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
  1147. Array events = item.shortcut->get_events();
  1148. for (int j = 0; j < events.size(); j++) {
  1149. Ref<InputEventKey> ie = events[j];
  1150. if (ie.is_valid()) {
  1151. ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
  1152. break;
  1153. }
  1154. }
  1155. }
  1156. ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
  1157. }
  1158. _shape_item(items.size() - 1);
  1159. control->queue_redraw();
  1160. child_controls_changed();
  1161. notify_property_list_changed();
  1162. _menu_changed();
  1163. }
  1164. void PopupMenu::add_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
  1165. Item item;
  1166. ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, false); // Echo for check shortcuts doesn't make sense.
  1167. item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
  1168. items.push_back(item);
  1169. if (!global_menu_name.is_empty()) {
  1170. DisplayServer *ds = DisplayServer::get_singleton();
  1171. int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
  1172. if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
  1173. Array events = item.shortcut->get_events();
  1174. for (int j = 0; j < events.size(); j++) {
  1175. Ref<InputEventKey> ie = events[j];
  1176. if (ie.is_valid()) {
  1177. ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
  1178. break;
  1179. }
  1180. }
  1181. }
  1182. ds->global_menu_set_item_checkable(global_menu_name, index, true);
  1183. }
  1184. _shape_item(items.size() - 1);
  1185. control->queue_redraw();
  1186. child_controls_changed();
  1187. notify_property_list_changed();
  1188. _menu_changed();
  1189. }
  1190. void PopupMenu::add_icon_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
  1191. Item item;
  1192. ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, false);
  1193. item.icon = p_icon;
  1194. item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
  1195. items.push_back(item);
  1196. if (!global_menu_name.is_empty()) {
  1197. DisplayServer *ds = DisplayServer::get_singleton();
  1198. int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
  1199. if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
  1200. Array events = item.shortcut->get_events();
  1201. for (int j = 0; j < events.size(); j++) {
  1202. Ref<InputEventKey> ie = events[j];
  1203. if (ie.is_valid()) {
  1204. ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
  1205. break;
  1206. }
  1207. }
  1208. }
  1209. ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
  1210. ds->global_menu_set_item_checkable(global_menu_name, index, true);
  1211. }
  1212. _shape_item(items.size() - 1);
  1213. control->queue_redraw();
  1214. child_controls_changed();
  1215. notify_property_list_changed();
  1216. _menu_changed();
  1217. }
  1218. void PopupMenu::add_radio_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
  1219. Item item;
  1220. ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, false);
  1221. item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
  1222. items.push_back(item);
  1223. if (!global_menu_name.is_empty()) {
  1224. DisplayServer *ds = DisplayServer::get_singleton();
  1225. int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
  1226. if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
  1227. Array events = item.shortcut->get_events();
  1228. for (int j = 0; j < events.size(); j++) {
  1229. Ref<InputEventKey> ie = events[j];
  1230. if (ie.is_valid()) {
  1231. ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
  1232. break;
  1233. }
  1234. }
  1235. }
  1236. ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
  1237. }
  1238. _shape_item(items.size() - 1);
  1239. control->queue_redraw();
  1240. child_controls_changed();
  1241. notify_property_list_changed();
  1242. _menu_changed();
  1243. }
  1244. void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
  1245. Item item;
  1246. ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, false);
  1247. item.icon = p_icon;
  1248. item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
  1249. items.push_back(item);
  1250. if (!global_menu_name.is_empty()) {
  1251. DisplayServer *ds = DisplayServer::get_singleton();
  1252. int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
  1253. if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
  1254. Array events = item.shortcut->get_events();
  1255. for (int j = 0; j < events.size(); j++) {
  1256. Ref<InputEventKey> ie = events[j];
  1257. if (ie.is_valid()) {
  1258. ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
  1259. break;
  1260. }
  1261. }
  1262. }
  1263. ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
  1264. ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
  1265. }
  1266. _shape_item(items.size() - 1);
  1267. control->queue_redraw();
  1268. child_controls_changed();
  1269. notify_property_list_changed();
  1270. _menu_changed();
  1271. }
  1272. void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, int p_id) {
  1273. String submenu_name_safe = p_submenu.replace("@", "_"); // Allow special characters for auto-generated names.
  1274. if (submenu_name_safe.validate_node_name() != submenu_name_safe) {
  1275. ERR_FAIL_MSG(vformat("Invalid node name '%s' for a submenu, the following characters are not allowed:\n%s", p_submenu, String::get_invalid_node_name_characters(true)));
  1276. }
  1277. Item item;
  1278. item.text = p_label;
  1279. item.xl_text = atr(p_label);
  1280. item.id = p_id == -1 ? items.size() : p_id;
  1281. item.submenu = p_submenu;
  1282. items.push_back(item);
  1283. if (!global_menu_name.is_empty()) {
  1284. DisplayServer *ds = DisplayServer::get_singleton();
  1285. int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
  1286. PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(item.submenu)); // Find first menu with this name.
  1287. if (pm) {
  1288. String submenu_name = pm->bind_global_menu();
  1289. ds->global_menu_set_item_submenu(global_menu_name, index, submenu_name);
  1290. items.write[index].submenu_bound = true;
  1291. }
  1292. }
  1293. _shape_item(items.size() - 1);
  1294. control->queue_redraw();
  1295. child_controls_changed();
  1296. notify_property_list_changed();
  1297. _menu_changed();
  1298. }
  1299. #undef ITEM_SETUP_WITH_ACCEL
  1300. #undef ITEM_SETUP_WITH_SHORTCUT
  1301. /* Methods to modify existing items. */
  1302. void PopupMenu::set_item_text(int p_idx, const String &p_text) {
  1303. if (p_idx < 0) {
  1304. p_idx += get_item_count();
  1305. }
  1306. ERR_FAIL_INDEX(p_idx, items.size());
  1307. if (items[p_idx].text == p_text) {
  1308. return;
  1309. }
  1310. items.write[p_idx].text = p_text;
  1311. items.write[p_idx].xl_text = atr(p_text);
  1312. items.write[p_idx].dirty = true;
  1313. if (!global_menu_name.is_empty()) {
  1314. DisplayServer::get_singleton()->global_menu_set_item_text(global_menu_name, p_idx, items[p_idx].xl_text);
  1315. }
  1316. _shape_item(p_idx);
  1317. control->queue_redraw();
  1318. child_controls_changed();
  1319. _menu_changed();
  1320. }
  1321. void PopupMenu::set_item_text_direction(int p_idx, Control::TextDirection p_text_direction) {
  1322. if (p_idx < 0) {
  1323. p_idx += get_item_count();
  1324. }
  1325. ERR_FAIL_INDEX(p_idx, items.size());
  1326. ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
  1327. if (items[p_idx].text_direction != p_text_direction) {
  1328. items.write[p_idx].text_direction = p_text_direction;
  1329. items.write[p_idx].dirty = true;
  1330. control->queue_redraw();
  1331. }
  1332. }
  1333. void PopupMenu::set_item_language(int p_idx, const String &p_language) {
  1334. if (p_idx < 0) {
  1335. p_idx += get_item_count();
  1336. }
  1337. ERR_FAIL_INDEX(p_idx, items.size());
  1338. if (items[p_idx].language != p_language) {
  1339. items.write[p_idx].language = p_language;
  1340. items.write[p_idx].dirty = true;
  1341. control->queue_redraw();
  1342. }
  1343. }
  1344. void PopupMenu::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) {
  1345. if (p_idx < 0) {
  1346. p_idx += get_item_count();
  1347. }
  1348. ERR_FAIL_INDEX(p_idx, items.size());
  1349. if (items[p_idx].icon == p_icon) {
  1350. return;
  1351. }
  1352. items.write[p_idx].icon = p_icon;
  1353. if (!global_menu_name.is_empty()) {
  1354. DisplayServer::get_singleton()->global_menu_set_item_icon(global_menu_name, p_idx, items[p_idx].icon);
  1355. }
  1356. control->queue_redraw();
  1357. child_controls_changed();
  1358. _menu_changed();
  1359. }
  1360. void PopupMenu::set_item_icon_max_width(int p_idx, int p_width) {
  1361. if (p_idx < 0) {
  1362. p_idx += get_item_count();
  1363. }
  1364. ERR_FAIL_INDEX(p_idx, items.size());
  1365. if (items[p_idx].icon_max_width == p_width) {
  1366. return;
  1367. }
  1368. items.write[p_idx].icon_max_width = p_width;
  1369. control->queue_redraw();
  1370. child_controls_changed();
  1371. _menu_changed();
  1372. }
  1373. void PopupMenu::set_item_icon_modulate(int p_idx, const Color &p_modulate) {
  1374. if (p_idx < 0) {
  1375. p_idx += get_item_count();
  1376. }
  1377. ERR_FAIL_INDEX(p_idx, items.size());
  1378. if (items[p_idx].icon_modulate == p_modulate) {
  1379. return;
  1380. }
  1381. items.write[p_idx].icon_modulate = p_modulate;
  1382. control->queue_redraw();
  1383. }
  1384. void PopupMenu::set_item_checked(int p_idx, bool p_checked) {
  1385. if (p_idx < 0) {
  1386. p_idx += get_item_count();
  1387. }
  1388. ERR_FAIL_INDEX(p_idx, items.size());
  1389. if (items[p_idx].checked == p_checked) {
  1390. return;
  1391. }
  1392. items.write[p_idx].checked = p_checked;
  1393. if (!global_menu_name.is_empty()) {
  1394. DisplayServer::get_singleton()->global_menu_set_item_checked(global_menu_name, p_idx, p_checked);
  1395. }
  1396. control->queue_redraw();
  1397. child_controls_changed();
  1398. _menu_changed();
  1399. }
  1400. void PopupMenu::set_item_id(int p_idx, int p_id) {
  1401. if (p_idx < 0) {
  1402. p_idx += get_item_count();
  1403. }
  1404. ERR_FAIL_INDEX(p_idx, items.size());
  1405. if (items[p_idx].id == p_id) {
  1406. return;
  1407. }
  1408. items.write[p_idx].id = p_id;
  1409. if (!global_menu_name.is_empty()) {
  1410. DisplayServer::get_singleton()->global_menu_set_item_tag(global_menu_name, p_idx, p_id);
  1411. }
  1412. control->queue_redraw();
  1413. child_controls_changed();
  1414. _menu_changed();
  1415. }
  1416. void PopupMenu::set_item_accelerator(int p_idx, Key p_accel) {
  1417. if (p_idx < 0) {
  1418. p_idx += get_item_count();
  1419. }
  1420. ERR_FAIL_INDEX(p_idx, items.size());
  1421. if (items[p_idx].accel == p_accel) {
  1422. return;
  1423. }
  1424. items.write[p_idx].accel = p_accel;
  1425. items.write[p_idx].dirty = true;
  1426. if (!global_menu_name.is_empty()) {
  1427. DisplayServer::get_singleton()->global_menu_set_item_accelerator(global_menu_name, p_idx, p_accel);
  1428. }
  1429. control->queue_redraw();
  1430. child_controls_changed();
  1431. _menu_changed();
  1432. }
  1433. void PopupMenu::set_item_metadata(int p_idx, const Variant &p_meta) {
  1434. if (p_idx < 0) {
  1435. p_idx += get_item_count();
  1436. }
  1437. ERR_FAIL_INDEX(p_idx, items.size());
  1438. if (items[p_idx].metadata == p_meta) {
  1439. return;
  1440. }
  1441. items.write[p_idx].metadata = p_meta;
  1442. child_controls_changed();
  1443. _menu_changed();
  1444. }
  1445. void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) {
  1446. if (p_idx < 0) {
  1447. p_idx += get_item_count();
  1448. }
  1449. ERR_FAIL_INDEX(p_idx, items.size());
  1450. if (items[p_idx].disabled == p_disabled) {
  1451. return;
  1452. }
  1453. items.write[p_idx].disabled = p_disabled;
  1454. if (!global_menu_name.is_empty()) {
  1455. DisplayServer::get_singleton()->global_menu_set_item_disabled(global_menu_name, p_idx, p_disabled);
  1456. }
  1457. control->queue_redraw();
  1458. child_controls_changed();
  1459. _menu_changed();
  1460. }
  1461. void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) {
  1462. if (p_idx < 0) {
  1463. p_idx += get_item_count();
  1464. }
  1465. ERR_FAIL_INDEX(p_idx, items.size());
  1466. if (items[p_idx].submenu == p_submenu) {
  1467. return;
  1468. }
  1469. if (!global_menu_name.is_empty()) {
  1470. if (items[p_idx].submenu_bound) {
  1471. PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(items[p_idx].submenu));
  1472. if (pm) {
  1473. DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, p_idx, String());
  1474. pm->unbind_global_menu();
  1475. }
  1476. items.write[p_idx].submenu_bound = false;
  1477. }
  1478. }
  1479. items.write[p_idx].submenu = p_submenu;
  1480. if (!global_menu_name.is_empty()) {
  1481. if (!items[p_idx].submenu.is_empty()) {
  1482. PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(items[p_idx].submenu));
  1483. if (pm) {
  1484. String submenu_name = pm->bind_global_menu();
  1485. DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, p_idx, submenu_name);
  1486. items.write[p_idx].submenu_bound = true;
  1487. }
  1488. }
  1489. }
  1490. control->queue_redraw();
  1491. child_controls_changed();
  1492. _menu_changed();
  1493. }
  1494. void PopupMenu::toggle_item_checked(int p_idx) {
  1495. ERR_FAIL_INDEX(p_idx, items.size());
  1496. items.write[p_idx].checked = !items[p_idx].checked;
  1497. if (!global_menu_name.is_empty()) {
  1498. DisplayServer::get_singleton()->global_menu_set_item_checked(global_menu_name, p_idx, items[p_idx].checked);
  1499. }
  1500. control->queue_redraw();
  1501. child_controls_changed();
  1502. _menu_changed();
  1503. }
  1504. String PopupMenu::get_item_text(int p_idx) const {
  1505. ERR_FAIL_INDEX_V(p_idx, items.size(), "");
  1506. return items[p_idx].text;
  1507. }
  1508. String PopupMenu::get_item_xl_text(int p_idx) const {
  1509. ERR_FAIL_INDEX_V(p_idx, items.size(), "");
  1510. return items[p_idx].xl_text;
  1511. }
  1512. Control::TextDirection PopupMenu::get_item_text_direction(int p_idx) const {
  1513. ERR_FAIL_INDEX_V(p_idx, items.size(), Control::TEXT_DIRECTION_INHERITED);
  1514. return items[p_idx].text_direction;
  1515. }
  1516. String PopupMenu::get_item_language(int p_idx) const {
  1517. ERR_FAIL_INDEX_V(p_idx, items.size(), "");
  1518. return items[p_idx].language;
  1519. }
  1520. int PopupMenu::get_item_idx_from_text(const String &text) const {
  1521. for (int idx = 0; idx < items.size(); idx++) {
  1522. if (items[idx].text == text) {
  1523. return idx;
  1524. }
  1525. }
  1526. return -1;
  1527. }
  1528. Ref<Texture2D> PopupMenu::get_item_icon(int p_idx) const {
  1529. ERR_FAIL_INDEX_V(p_idx, items.size(), Ref<Texture2D>());
  1530. return items[p_idx].icon;
  1531. }
  1532. int PopupMenu::get_item_icon_max_width(int p_idx) const {
  1533. ERR_FAIL_INDEX_V(p_idx, items.size(), 0);
  1534. return items[p_idx].icon_max_width;
  1535. }
  1536. Color PopupMenu::get_item_icon_modulate(int p_idx) const {
  1537. ERR_FAIL_INDEX_V(p_idx, items.size(), Color());
  1538. return items[p_idx].icon_modulate;
  1539. }
  1540. Key PopupMenu::get_item_accelerator(int p_idx) const {
  1541. ERR_FAIL_INDEX_V(p_idx, items.size(), Key::NONE);
  1542. return items[p_idx].accel;
  1543. }
  1544. Variant PopupMenu::get_item_metadata(int p_idx) const {
  1545. ERR_FAIL_INDEX_V(p_idx, items.size(), Variant());
  1546. return items[p_idx].metadata;
  1547. }
  1548. bool PopupMenu::is_item_disabled(int p_idx) const {
  1549. ERR_FAIL_INDEX_V(p_idx, items.size(), false);
  1550. return items[p_idx].disabled;
  1551. }
  1552. bool PopupMenu::is_item_checked(int p_idx) const {
  1553. ERR_FAIL_INDEX_V(p_idx, items.size(), false);
  1554. return items[p_idx].checked;
  1555. }
  1556. int PopupMenu::get_item_id(int p_idx) const {
  1557. ERR_FAIL_INDEX_V(p_idx, items.size(), 0);
  1558. return items[p_idx].id;
  1559. }
  1560. int PopupMenu::get_item_index(int p_id) const {
  1561. for (int i = 0; i < items.size(); i++) {
  1562. if (items[i].id == p_id) {
  1563. return i;
  1564. }
  1565. }
  1566. return -1;
  1567. }
  1568. String PopupMenu::get_item_submenu(int p_idx) const {
  1569. ERR_FAIL_INDEX_V(p_idx, items.size(), "");
  1570. return items[p_idx].submenu;
  1571. }
  1572. String PopupMenu::get_item_tooltip(int p_idx) const {
  1573. ERR_FAIL_INDEX_V(p_idx, items.size(), "");
  1574. return items[p_idx].tooltip;
  1575. }
  1576. Ref<Shortcut> PopupMenu::get_item_shortcut(int p_idx) const {
  1577. ERR_FAIL_INDEX_V(p_idx, items.size(), Ref<Shortcut>());
  1578. return items[p_idx].shortcut;
  1579. }
  1580. int PopupMenu::get_item_indent(int p_idx) const {
  1581. ERR_FAIL_INDEX_V(p_idx, items.size(), 0);
  1582. return items[p_idx].indent;
  1583. }
  1584. int PopupMenu::get_item_max_states(int p_idx) const {
  1585. ERR_FAIL_INDEX_V(p_idx, items.size(), -1);
  1586. return items[p_idx].max_states;
  1587. }
  1588. int PopupMenu::get_item_state(int p_idx) const {
  1589. ERR_FAIL_INDEX_V(p_idx, items.size(), -1);
  1590. return items[p_idx].state;
  1591. }
  1592. void PopupMenu::set_item_as_separator(int p_idx, bool p_separator) {
  1593. if (p_idx < 0) {
  1594. p_idx += get_item_count();
  1595. }
  1596. ERR_FAIL_INDEX(p_idx, items.size());
  1597. if (items[p_idx].separator == p_separator) {
  1598. return;
  1599. }
  1600. items.write[p_idx].separator = p_separator;
  1601. control->queue_redraw();
  1602. }
  1603. bool PopupMenu::is_item_separator(int p_idx) const {
  1604. ERR_FAIL_INDEX_V(p_idx, items.size(), false);
  1605. return items[p_idx].separator;
  1606. }
  1607. void PopupMenu::set_item_as_checkable(int p_idx, bool p_checkable) {
  1608. if (p_idx < 0) {
  1609. p_idx += get_item_count();
  1610. }
  1611. ERR_FAIL_INDEX(p_idx, items.size());
  1612. int type = (int)(p_checkable ? Item::CHECKABLE_TYPE_CHECK_BOX : Item::CHECKABLE_TYPE_NONE);
  1613. if (type == items[p_idx].checkable_type) {
  1614. return;
  1615. }
  1616. items.write[p_idx].checkable_type = p_checkable ? Item::CHECKABLE_TYPE_CHECK_BOX : Item::CHECKABLE_TYPE_NONE;
  1617. if (!global_menu_name.is_empty()) {
  1618. DisplayServer::get_singleton()->global_menu_set_item_checkable(global_menu_name, p_idx, p_checkable);
  1619. }
  1620. control->queue_redraw();
  1621. _menu_changed();
  1622. }
  1623. void PopupMenu::set_item_as_radio_checkable(int p_idx, bool p_radio_checkable) {
  1624. if (p_idx < 0) {
  1625. p_idx += get_item_count();
  1626. }
  1627. ERR_FAIL_INDEX(p_idx, items.size());
  1628. int type = (int)(p_radio_checkable ? Item::CHECKABLE_TYPE_RADIO_BUTTON : Item::CHECKABLE_TYPE_NONE);
  1629. if (type == items[p_idx].checkable_type) {
  1630. return;
  1631. }
  1632. items.write[p_idx].checkable_type = p_radio_checkable ? Item::CHECKABLE_TYPE_RADIO_BUTTON : Item::CHECKABLE_TYPE_NONE;
  1633. if (!global_menu_name.is_empty()) {
  1634. DisplayServer::get_singleton()->global_menu_set_item_radio_checkable(global_menu_name, p_idx, p_radio_checkable);
  1635. }
  1636. control->queue_redraw();
  1637. _menu_changed();
  1638. }
  1639. void PopupMenu::set_item_tooltip(int p_idx, const String &p_tooltip) {
  1640. if (p_idx < 0) {
  1641. p_idx += get_item_count();
  1642. }
  1643. ERR_FAIL_INDEX(p_idx, items.size());
  1644. if (items[p_idx].tooltip == p_tooltip) {
  1645. return;
  1646. }
  1647. items.write[p_idx].tooltip = p_tooltip;
  1648. if (!global_menu_name.is_empty()) {
  1649. DisplayServer::get_singleton()->global_menu_set_item_tooltip(global_menu_name, p_idx, p_tooltip);
  1650. }
  1651. control->queue_redraw();
  1652. _menu_changed();
  1653. }
  1654. void PopupMenu::set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bool p_global) {
  1655. if (p_idx < 0) {
  1656. p_idx += get_item_count();
  1657. }
  1658. ERR_FAIL_INDEX(p_idx, items.size());
  1659. 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()) {
  1660. return;
  1661. }
  1662. if (items[p_idx].shortcut.is_valid()) {
  1663. _unref_shortcut(items[p_idx].shortcut);
  1664. }
  1665. items.write[p_idx].shortcut = p_shortcut;
  1666. items.write[p_idx].shortcut_is_global = p_global;
  1667. items.write[p_idx].dirty = true;
  1668. if (items[p_idx].shortcut.is_valid()) {
  1669. _ref_shortcut(items[p_idx].shortcut);
  1670. }
  1671. if (!global_menu_name.is_empty()) {
  1672. DisplayServer *ds = DisplayServer::get_singleton();
  1673. ds->global_menu_set_item_accelerator(global_menu_name, p_idx, Key::NONE);
  1674. if (!items[p_idx].shortcut_is_disabled && items[p_idx].shortcut.is_valid() && items[p_idx].shortcut->has_valid_event()) {
  1675. Array events = items[p_idx].shortcut->get_events();
  1676. for (int j = 0; j < events.size(); j++) {
  1677. Ref<InputEventKey> ie = events[j];
  1678. if (ie.is_valid()) {
  1679. ds->global_menu_set_item_accelerator(global_menu_name, p_idx, ie->get_keycode_with_modifiers());
  1680. break;
  1681. }
  1682. }
  1683. }
  1684. }
  1685. control->queue_redraw();
  1686. _menu_changed();
  1687. }
  1688. void PopupMenu::set_item_indent(int p_idx, int p_indent) {
  1689. if (p_idx < 0) {
  1690. p_idx += get_item_count();
  1691. }
  1692. ERR_FAIL_INDEX(p_idx, items.size());
  1693. if (items.write[p_idx].indent == p_indent) {
  1694. return;
  1695. }
  1696. items.write[p_idx].indent = p_indent;
  1697. if (!global_menu_name.is_empty()) {
  1698. DisplayServer::get_singleton()->global_menu_set_item_indentation_level(global_menu_name, p_idx, p_indent);
  1699. }
  1700. control->queue_redraw();
  1701. child_controls_changed();
  1702. _menu_changed();
  1703. }
  1704. void PopupMenu::set_item_multistate(int p_idx, int p_state) {
  1705. if (p_idx < 0) {
  1706. p_idx += get_item_count();
  1707. }
  1708. ERR_FAIL_INDEX(p_idx, items.size());
  1709. if (items[p_idx].state == p_state) {
  1710. return;
  1711. }
  1712. items.write[p_idx].state = p_state;
  1713. if (!global_menu_name.is_empty()) {
  1714. DisplayServer::get_singleton()->global_menu_set_item_state(global_menu_name, p_idx, p_state);
  1715. }
  1716. control->queue_redraw();
  1717. _menu_changed();
  1718. }
  1719. void PopupMenu::set_item_shortcut_disabled(int p_idx, bool p_disabled) {
  1720. if (p_idx < 0) {
  1721. p_idx += get_item_count();
  1722. }
  1723. ERR_FAIL_INDEX(p_idx, items.size());
  1724. if (items[p_idx].shortcut_is_disabled == p_disabled) {
  1725. return;
  1726. }
  1727. items.write[p_idx].shortcut_is_disabled = p_disabled;
  1728. if (!global_menu_name.is_empty()) {
  1729. DisplayServer *ds = DisplayServer::get_singleton();
  1730. ds->global_menu_set_item_accelerator(global_menu_name, p_idx, Key::NONE);
  1731. if (!items[p_idx].shortcut_is_disabled && items[p_idx].shortcut.is_valid() && items[p_idx].shortcut->has_valid_event()) {
  1732. Array events = items[p_idx].shortcut->get_events();
  1733. for (int j = 0; j < events.size(); j++) {
  1734. Ref<InputEventKey> ie = events[j];
  1735. if (ie.is_valid()) {
  1736. ds->global_menu_set_item_accelerator(global_menu_name, p_idx, ie->get_keycode_with_modifiers());
  1737. break;
  1738. }
  1739. }
  1740. }
  1741. }
  1742. control->queue_redraw();
  1743. _menu_changed();
  1744. }
  1745. void PopupMenu::toggle_item_multistate(int p_idx) {
  1746. ERR_FAIL_INDEX(p_idx, items.size());
  1747. if (0 >= items[p_idx].max_states) {
  1748. return;
  1749. }
  1750. ++items.write[p_idx].state;
  1751. if (items.write[p_idx].max_states <= items[p_idx].state) {
  1752. items.write[p_idx].state = 0;
  1753. }
  1754. if (!global_menu_name.is_empty()) {
  1755. DisplayServer::get_singleton()->global_menu_set_item_state(global_menu_name, p_idx, items[p_idx].state);
  1756. }
  1757. control->queue_redraw();
  1758. _menu_changed();
  1759. }
  1760. bool PopupMenu::is_item_checkable(int p_idx) const {
  1761. ERR_FAIL_INDEX_V(p_idx, items.size(), false);
  1762. return items[p_idx].checkable_type;
  1763. }
  1764. bool PopupMenu::is_item_radio_checkable(int p_idx) const {
  1765. ERR_FAIL_INDEX_V(p_idx, items.size(), false);
  1766. return items[p_idx].checkable_type == Item::CHECKABLE_TYPE_RADIO_BUTTON;
  1767. }
  1768. bool PopupMenu::is_item_shortcut_global(int p_idx) const {
  1769. ERR_FAIL_INDEX_V(p_idx, items.size(), false);
  1770. return items[p_idx].shortcut_is_global;
  1771. }
  1772. bool PopupMenu::is_item_shortcut_disabled(int p_idx) const {
  1773. ERR_FAIL_INDEX_V(p_idx, items.size(), false);
  1774. return items[p_idx].shortcut_is_disabled;
  1775. }
  1776. void PopupMenu::set_focused_item(int p_idx) {
  1777. if (p_idx != -1) {
  1778. ERR_FAIL_INDEX(p_idx, items.size());
  1779. }
  1780. if (mouse_over == p_idx) {
  1781. return;
  1782. }
  1783. mouse_over = p_idx;
  1784. if (mouse_over != -1) {
  1785. scroll_to_item(mouse_over);
  1786. }
  1787. control->queue_redraw();
  1788. }
  1789. int PopupMenu::get_focused_item() const {
  1790. return mouse_over;
  1791. }
  1792. void PopupMenu::set_item_count(int p_count) {
  1793. ERR_FAIL_COND(p_count < 0);
  1794. int prev_size = items.size();
  1795. if (prev_size == p_count) {
  1796. return;
  1797. }
  1798. DisplayServer *ds = DisplayServer::get_singleton();
  1799. bool is_global = !global_menu_name.is_empty();
  1800. if (is_global && prev_size > p_count) {
  1801. for (int i = prev_size - 1; i >= p_count; i--) {
  1802. ds->global_menu_remove_item(global_menu_name, i);
  1803. }
  1804. }
  1805. items.resize(p_count);
  1806. if (prev_size < p_count) {
  1807. for (int i = prev_size; i < p_count; i++) {
  1808. items.write[i].id = i;
  1809. if (is_global) {
  1810. ds->global_menu_add_item(global_menu_name, String(), callable_mp(this, &PopupMenu::activate_item), Callable(), i);
  1811. }
  1812. }
  1813. }
  1814. control->queue_redraw();
  1815. child_controls_changed();
  1816. notify_property_list_changed();
  1817. _menu_changed();
  1818. }
  1819. int PopupMenu::get_item_count() const {
  1820. return items.size();
  1821. }
  1822. void PopupMenu::scroll_to_item(int p_idx) {
  1823. ERR_FAIL_INDEX(p_idx, items.size());
  1824. // Calculate the position of the item relative to the visible area.
  1825. int item_y = items[p_idx]._ofs_cache;
  1826. int visible_height = scroll_container->get_size().height;
  1827. int relative_y = item_y - scroll_container->get_v_scroll();
  1828. // If item is not fully visible, adjust scroll.
  1829. if (relative_y < 0) {
  1830. scroll_container->set_v_scroll(item_y);
  1831. } else if (relative_y + items[p_idx]._height_cache > visible_height) {
  1832. scroll_container->set_v_scroll(item_y + items[p_idx]._height_cache - visible_height);
  1833. }
  1834. }
  1835. bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only) {
  1836. ERR_FAIL_COND_V(p_event.is_null(), false);
  1837. Key code = Key::NONE;
  1838. Ref<InputEventKey> k = p_event;
  1839. if (k.is_valid()) {
  1840. code = k->get_keycode();
  1841. if (code == Key::NONE) {
  1842. code = (Key)k->get_unicode();
  1843. }
  1844. if (k->is_ctrl_pressed()) {
  1845. code |= KeyModifierMask::CTRL;
  1846. }
  1847. if (k->is_alt_pressed()) {
  1848. code |= KeyModifierMask::ALT;
  1849. }
  1850. if (k->is_meta_pressed()) {
  1851. code |= KeyModifierMask::META;
  1852. }
  1853. if (k->is_shift_pressed()) {
  1854. code |= KeyModifierMask::SHIFT;
  1855. }
  1856. }
  1857. for (int i = 0; i < items.size(); i++) {
  1858. if (is_item_disabled(i) || items[i].shortcut_is_disabled || (!items[i].allow_echo && p_event->is_echo())) {
  1859. continue;
  1860. }
  1861. if (items[i].shortcut.is_valid() && items[i].shortcut->matches_event(p_event) && (items[i].shortcut_is_global || !p_for_global_only)) {
  1862. activate_item(i);
  1863. return true;
  1864. }
  1865. if (code != Key::NONE && items[i].accel == code) {
  1866. activate_item(i);
  1867. return true;
  1868. }
  1869. if (!items[i].submenu.is_empty()) {
  1870. Node *n = get_node(items[i].submenu);
  1871. if (!n) {
  1872. continue;
  1873. }
  1874. PopupMenu *pm = Object::cast_to<PopupMenu>(n);
  1875. if (!pm) {
  1876. continue;
  1877. }
  1878. if (pm->activate_item_by_event(p_event, p_for_global_only)) {
  1879. return true;
  1880. }
  1881. }
  1882. }
  1883. return false;
  1884. }
  1885. void PopupMenu::_about_to_popup() {
  1886. ERR_MAIN_THREAD_GUARD;
  1887. emit_signal(SNAME("about_to_popup"));
  1888. }
  1889. void PopupMenu::_about_to_close() {
  1890. ERR_MAIN_THREAD_GUARD;
  1891. emit_signal(SNAME("popup_hide"));
  1892. }
  1893. void PopupMenu::activate_item(int p_idx) {
  1894. ERR_FAIL_INDEX(p_idx, items.size());
  1895. ERR_FAIL_COND(items[p_idx].separator);
  1896. int id = items[p_idx].id >= 0 ? items[p_idx].id : p_idx;
  1897. //hide all parent PopupMenus
  1898. Node *next = get_parent();
  1899. PopupMenu *pop = Object::cast_to<PopupMenu>(next);
  1900. while (pop) {
  1901. // We close all parents that are chained together,
  1902. // with hide_on_item_selection enabled
  1903. if (items[p_idx].checkable_type) {
  1904. if (!hide_on_checkable_item_selection || !pop->is_hide_on_checkable_item_selection()) {
  1905. break;
  1906. }
  1907. } else if (0 < items[p_idx].max_states) {
  1908. if (!hide_on_multistate_item_selection || !pop->is_hide_on_multistate_item_selection()) {
  1909. break;
  1910. }
  1911. } else if (!hide_on_item_selection || !pop->is_hide_on_item_selection()) {
  1912. break;
  1913. }
  1914. pop->hide();
  1915. next = next->get_parent();
  1916. pop = Object::cast_to<PopupMenu>(next);
  1917. }
  1918. // Hides popup by default; unless otherwise specified
  1919. // by using set_hide_on_item_selection and set_hide_on_checkable_item_selection
  1920. bool need_hide = true;
  1921. if (items[p_idx].checkable_type) {
  1922. if (!hide_on_checkable_item_selection) {
  1923. need_hide = false;
  1924. }
  1925. } else if (0 < items[p_idx].max_states) {
  1926. if (!hide_on_multistate_item_selection) {
  1927. need_hide = false;
  1928. }
  1929. } else if (!hide_on_item_selection) {
  1930. need_hide = false;
  1931. }
  1932. if (need_hide) {
  1933. hide();
  1934. }
  1935. emit_signal(SNAME("id_pressed"), id);
  1936. emit_signal(SNAME("index_pressed"), p_idx);
  1937. }
  1938. void PopupMenu::remove_item(int p_idx) {
  1939. ERR_FAIL_INDEX(p_idx, items.size());
  1940. if (items[p_idx].shortcut.is_valid()) {
  1941. _unref_shortcut(items[p_idx].shortcut);
  1942. }
  1943. items.remove_at(p_idx);
  1944. if (!global_menu_name.is_empty()) {
  1945. DisplayServer::get_singleton()->global_menu_remove_item(global_menu_name, p_idx);
  1946. }
  1947. control->queue_redraw();
  1948. child_controls_changed();
  1949. _menu_changed();
  1950. }
  1951. void PopupMenu::add_separator(const String &p_text, int p_id) {
  1952. Item sep;
  1953. sep.separator = true;
  1954. sep.id = p_id;
  1955. if (!p_text.is_empty()) {
  1956. sep.text = p_text;
  1957. sep.xl_text = atr(p_text);
  1958. }
  1959. items.push_back(sep);
  1960. if (!global_menu_name.is_empty()) {
  1961. DisplayServer::get_singleton()->global_menu_add_separator(global_menu_name);
  1962. }
  1963. control->queue_redraw();
  1964. _menu_changed();
  1965. }
  1966. void PopupMenu::clear(bool p_free_submenus) {
  1967. for (const Item &I : items) {
  1968. if (I.shortcut.is_valid()) {
  1969. _unref_shortcut(I.shortcut);
  1970. }
  1971. if (p_free_submenus && !I.submenu.is_empty()) {
  1972. Node *submenu = get_node_or_null(I.submenu);
  1973. if (submenu) {
  1974. remove_child(submenu);
  1975. submenu->queue_free();
  1976. }
  1977. }
  1978. }
  1979. if (!global_menu_name.is_empty()) {
  1980. for (int i = 0; i < items.size(); i++) {
  1981. Item &item = items.write[i];
  1982. if (!item.submenu.is_empty()) {
  1983. PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(item.submenu));
  1984. if (pm) {
  1985. pm->unbind_global_menu();
  1986. }
  1987. item.submenu_bound = false;
  1988. }
  1989. }
  1990. DisplayServer::get_singleton()->global_menu_clear(global_menu_name);
  1991. }
  1992. items.clear();
  1993. mouse_over = -1;
  1994. control->queue_redraw();
  1995. child_controls_changed();
  1996. notify_property_list_changed();
  1997. _menu_changed();
  1998. }
  1999. void PopupMenu::_ref_shortcut(Ref<Shortcut> p_sc) {
  2000. if (!shortcut_refcount.has(p_sc)) {
  2001. shortcut_refcount[p_sc] = 1;
  2002. p_sc->connect_changed(callable_mp(this, &PopupMenu::_shortcut_changed));
  2003. } else {
  2004. shortcut_refcount[p_sc] += 1;
  2005. }
  2006. }
  2007. void PopupMenu::_unref_shortcut(Ref<Shortcut> p_sc) {
  2008. ERR_FAIL_COND(!shortcut_refcount.has(p_sc));
  2009. shortcut_refcount[p_sc]--;
  2010. if (shortcut_refcount[p_sc] == 0) {
  2011. p_sc->disconnect_changed(callable_mp(this, &PopupMenu::_shortcut_changed));
  2012. shortcut_refcount.erase(p_sc);
  2013. }
  2014. }
  2015. void PopupMenu::_shortcut_changed() {
  2016. for (int i = 0; i < items.size(); i++) {
  2017. items.write[i].dirty = true;
  2018. }
  2019. control->queue_redraw();
  2020. }
  2021. // Hide on item selection determines whether or not the popup will close after item selection
  2022. void PopupMenu::set_hide_on_item_selection(bool p_enabled) {
  2023. hide_on_item_selection = p_enabled;
  2024. }
  2025. bool PopupMenu::is_hide_on_item_selection() const {
  2026. return hide_on_item_selection;
  2027. }
  2028. void PopupMenu::set_hide_on_checkable_item_selection(bool p_enabled) {
  2029. hide_on_checkable_item_selection = p_enabled;
  2030. }
  2031. bool PopupMenu::is_hide_on_checkable_item_selection() const {
  2032. return hide_on_checkable_item_selection;
  2033. }
  2034. void PopupMenu::set_hide_on_multistate_item_selection(bool p_enabled) {
  2035. hide_on_multistate_item_selection = p_enabled;
  2036. }
  2037. bool PopupMenu::is_hide_on_multistate_item_selection() const {
  2038. return hide_on_multistate_item_selection;
  2039. }
  2040. void PopupMenu::set_submenu_popup_delay(float p_time) {
  2041. if (p_time <= 0) {
  2042. p_time = 0.01;
  2043. }
  2044. submenu_timer->set_wait_time(p_time);
  2045. }
  2046. float PopupMenu::get_submenu_popup_delay() const {
  2047. return submenu_timer->get_wait_time();
  2048. }
  2049. void PopupMenu::set_allow_search(bool p_allow) {
  2050. allow_search = p_allow;
  2051. }
  2052. bool PopupMenu::get_allow_search() const {
  2053. return allow_search;
  2054. }
  2055. String PopupMenu::get_tooltip(const Point2 &p_pos) const {
  2056. int over = _get_mouse_over(p_pos);
  2057. if (over < 0 || over >= items.size()) {
  2058. return "";
  2059. }
  2060. return items[over].tooltip;
  2061. }
  2062. void PopupMenu::add_autohide_area(const Rect2 &p_area) {
  2063. autohide_areas.push_back(p_area);
  2064. }
  2065. void PopupMenu::clear_autohide_areas() {
  2066. autohide_areas.clear();
  2067. }
  2068. void PopupMenu::take_mouse_focus() {
  2069. ERR_FAIL_COND(!is_inside_tree());
  2070. if (get_parent()) {
  2071. get_parent()->get_viewport()->pass_mouse_focus_to(this, control);
  2072. }
  2073. }
  2074. bool PopupMenu::_set(const StringName &p_name, const Variant &p_value) {
  2075. Vector<String> components = String(p_name).split("/", true, 2);
  2076. if (components.size() >= 2 && components[0].begins_with("item_") && components[0].trim_prefix("item_").is_valid_int()) {
  2077. int item_index = components[0].trim_prefix("item_").to_int();
  2078. String property = components[1];
  2079. if (property == "text") {
  2080. set_item_text(item_index, p_value);
  2081. return true;
  2082. } else if (property == "icon") {
  2083. set_item_icon(item_index, p_value);
  2084. return true;
  2085. } else if (property == "checkable") {
  2086. bool radio_checkable = (int)p_value == Item::CHECKABLE_TYPE_RADIO_BUTTON;
  2087. if (radio_checkable) {
  2088. set_item_as_radio_checkable(item_index, true);
  2089. } else {
  2090. bool checkable = p_value;
  2091. set_item_as_checkable(item_index, checkable);
  2092. }
  2093. return true;
  2094. } else if (property == "checked") {
  2095. set_item_checked(item_index, p_value);
  2096. return true;
  2097. } else if (property == "id") {
  2098. set_item_id(item_index, p_value);
  2099. return true;
  2100. } else if (property == "disabled") {
  2101. set_item_disabled(item_index, p_value);
  2102. return true;
  2103. } else if (property == "separator") {
  2104. set_item_as_separator(item_index, p_value);
  2105. return true;
  2106. }
  2107. }
  2108. #ifndef DISABLE_DEPRECATED
  2109. // Compatibility.
  2110. if (p_name == "items") {
  2111. Array arr = p_value;
  2112. ERR_FAIL_COND_V(arr.size() % 10, false);
  2113. clear();
  2114. for (int i = 0; i < arr.size(); i += 10) {
  2115. String text = arr[i + 0];
  2116. Ref<Texture2D> icon = arr[i + 1];
  2117. // For compatibility, use false/true for no/checkbox and integers for other values
  2118. bool checkable = arr[i + 2];
  2119. bool radio_checkable = (int)arr[i + 2] == Item::CHECKABLE_TYPE_RADIO_BUTTON;
  2120. bool checked = arr[i + 3];
  2121. bool disabled = arr[i + 4];
  2122. int id = arr[i + 5];
  2123. int accel = arr[i + 6];
  2124. Variant meta = arr[i + 7];
  2125. String subm = arr[i + 8];
  2126. bool sep = arr[i + 9];
  2127. int idx = get_item_count();
  2128. add_item(text, id);
  2129. set_item_icon(idx, icon);
  2130. if (checkable) {
  2131. if (radio_checkable) {
  2132. set_item_as_radio_checkable(idx, true);
  2133. } else {
  2134. set_item_as_checkable(idx, true);
  2135. }
  2136. }
  2137. set_item_checked(idx, checked);
  2138. set_item_disabled(idx, disabled);
  2139. set_item_id(idx, id);
  2140. set_item_metadata(idx, meta);
  2141. set_item_as_separator(idx, sep);
  2142. set_item_accelerator(idx, (Key)accel);
  2143. set_item_submenu(idx, subm);
  2144. }
  2145. }
  2146. #endif
  2147. return false;
  2148. }
  2149. bool PopupMenu::_get(const StringName &p_name, Variant &r_ret) const {
  2150. Vector<String> components = String(p_name).split("/", true, 2);
  2151. if (components.size() >= 2 && components[0].begins_with("item_") && components[0].trim_prefix("item_").is_valid_int()) {
  2152. int item_index = components[0].trim_prefix("item_").to_int();
  2153. String property = components[1];
  2154. if (property == "text") {
  2155. r_ret = get_item_text(item_index);
  2156. return true;
  2157. } else if (property == "icon") {
  2158. r_ret = get_item_icon(item_index);
  2159. return true;
  2160. } else if (property == "checkable") {
  2161. if (item_index >= 0 && item_index < items.size()) {
  2162. r_ret = items[item_index].checkable_type;
  2163. return true;
  2164. } else {
  2165. r_ret = Item::CHECKABLE_TYPE_NONE;
  2166. ERR_FAIL_V(true);
  2167. }
  2168. } else if (property == "checked") {
  2169. r_ret = is_item_checked(item_index);
  2170. return true;
  2171. } else if (property == "id") {
  2172. r_ret = get_item_id(item_index);
  2173. return true;
  2174. } else if (property == "disabled") {
  2175. r_ret = is_item_disabled(item_index);
  2176. return true;
  2177. } else if (property == "separator") {
  2178. r_ret = is_item_separator(item_index);
  2179. return true;
  2180. }
  2181. }
  2182. return false;
  2183. }
  2184. void PopupMenu::_get_property_list(List<PropertyInfo> *p_list) const {
  2185. for (int i = 0; i < items.size(); i++) {
  2186. p_list->push_back(PropertyInfo(Variant::STRING, vformat("item_%d/text", i)));
  2187. PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("item_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D");
  2188. pi.usage &= ~(get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0);
  2189. p_list->push_back(pi);
  2190. pi = PropertyInfo(Variant::INT, vformat("item_%d/checkable", i), PROPERTY_HINT_ENUM, "No,As checkbox,As radio button");
  2191. pi.usage &= ~(!is_item_checkable(i) ? PROPERTY_USAGE_STORAGE : 0);
  2192. p_list->push_back(pi);
  2193. pi = PropertyInfo(Variant::BOOL, vformat("item_%d/checked", i));
  2194. pi.usage &= ~(!is_item_checked(i) ? PROPERTY_USAGE_STORAGE : 0);
  2195. p_list->push_back(pi);
  2196. pi = PropertyInfo(Variant::INT, vformat("item_%d/id", i), PROPERTY_HINT_RANGE, "0,10,1,or_greater");
  2197. p_list->push_back(pi);
  2198. pi = PropertyInfo(Variant::BOOL, vformat("item_%d/disabled", i));
  2199. pi.usage &= ~(!is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0);
  2200. p_list->push_back(pi);
  2201. pi = PropertyInfo(Variant::BOOL, vformat("item_%d/separator", i));
  2202. pi.usage &= ~(!is_item_separator(i) ? PROPERTY_USAGE_STORAGE : 0);
  2203. p_list->push_back(pi);
  2204. }
  2205. }
  2206. void PopupMenu::_bind_methods() {
  2207. ClassDB::bind_method(D_METHOD("activate_item_by_event", "event", "for_global_only"), &PopupMenu::activate_item_by_event, DEFVAL(false));
  2208. ClassDB::bind_method(D_METHOD("add_item", "label", "id", "accel"), &PopupMenu::add_item, DEFVAL(-1), DEFVAL(0));
  2209. ClassDB::bind_method(D_METHOD("add_icon_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_item, DEFVAL(-1), DEFVAL(0));
  2210. ClassDB::bind_method(D_METHOD("add_check_item", "label", "id", "accel"), &PopupMenu::add_check_item, DEFVAL(-1), DEFVAL(0));
  2211. ClassDB::bind_method(D_METHOD("add_icon_check_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_check_item, DEFVAL(-1), DEFVAL(0));
  2212. ClassDB::bind_method(D_METHOD("add_radio_check_item", "label", "id", "accel"), &PopupMenu::add_radio_check_item, DEFVAL(-1), DEFVAL(0));
  2213. ClassDB::bind_method(D_METHOD("add_icon_radio_check_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_radio_check_item, DEFVAL(-1), DEFVAL(0));
  2214. 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));
  2215. ClassDB::bind_method(D_METHOD("add_shortcut", "shortcut", "id", "global", "allow_echo"), &PopupMenu::add_shortcut, DEFVAL(-1), DEFVAL(false), DEFVAL(false));
  2216. ClassDB::bind_method(D_METHOD("add_icon_shortcut", "texture", "shortcut", "id", "global", "allow_echo"), &PopupMenu::add_icon_shortcut, DEFVAL(-1), DEFVAL(false), DEFVAL(false));
  2217. ClassDB::bind_method(D_METHOD("add_check_shortcut", "shortcut", "id", "global"), &PopupMenu::add_check_shortcut, DEFVAL(-1), DEFVAL(false));
  2218. ClassDB::bind_method(D_METHOD("add_icon_check_shortcut", "texture", "shortcut", "id", "global"), &PopupMenu::add_icon_check_shortcut, DEFVAL(-1), DEFVAL(false));
  2219. ClassDB::bind_method(D_METHOD("add_radio_check_shortcut", "shortcut", "id", "global"), &PopupMenu::add_radio_check_shortcut, DEFVAL(-1), DEFVAL(false));
  2220. ClassDB::bind_method(D_METHOD("add_icon_radio_check_shortcut", "texture", "shortcut", "id", "global"), &PopupMenu::add_icon_radio_check_shortcut, DEFVAL(-1), DEFVAL(false));
  2221. ClassDB::bind_method(D_METHOD("add_submenu_item", "label", "submenu", "id"), &PopupMenu::add_submenu_item, DEFVAL(-1));
  2222. ClassDB::bind_method(D_METHOD("set_item_text", "index", "text"), &PopupMenu::set_item_text);
  2223. ClassDB::bind_method(D_METHOD("set_item_text_direction", "index", "direction"), &PopupMenu::set_item_text_direction);
  2224. ClassDB::bind_method(D_METHOD("set_item_language", "index", "language"), &PopupMenu::set_item_language);
  2225. ClassDB::bind_method(D_METHOD("set_item_icon", "index", "icon"), &PopupMenu::set_item_icon);
  2226. ClassDB::bind_method(D_METHOD("set_item_icon_max_width", "index", "width"), &PopupMenu::set_item_icon_max_width);
  2227. ClassDB::bind_method(D_METHOD("set_item_icon_modulate", "index", "modulate"), &PopupMenu::set_item_icon_modulate);
  2228. ClassDB::bind_method(D_METHOD("set_item_checked", "index", "checked"), &PopupMenu::set_item_checked);
  2229. ClassDB::bind_method(D_METHOD("set_item_id", "index", "id"), &PopupMenu::set_item_id);
  2230. ClassDB::bind_method(D_METHOD("set_item_accelerator", "index", "accel"), &PopupMenu::set_item_accelerator);
  2231. ClassDB::bind_method(D_METHOD("set_item_metadata", "index", "metadata"), &PopupMenu::set_item_metadata);
  2232. ClassDB::bind_method(D_METHOD("set_item_disabled", "index", "disabled"), &PopupMenu::set_item_disabled);
  2233. ClassDB::bind_method(D_METHOD("set_item_submenu", "index", "submenu"), &PopupMenu::set_item_submenu);
  2234. ClassDB::bind_method(D_METHOD("set_item_as_separator", "index", "enable"), &PopupMenu::set_item_as_separator);
  2235. ClassDB::bind_method(D_METHOD("set_item_as_checkable", "index", "enable"), &PopupMenu::set_item_as_checkable);
  2236. ClassDB::bind_method(D_METHOD("set_item_as_radio_checkable", "index", "enable"), &PopupMenu::set_item_as_radio_checkable);
  2237. ClassDB::bind_method(D_METHOD("set_item_tooltip", "index", "tooltip"), &PopupMenu::set_item_tooltip);
  2238. ClassDB::bind_method(D_METHOD("set_item_shortcut", "index", "shortcut", "global"), &PopupMenu::set_item_shortcut, DEFVAL(false));
  2239. ClassDB::bind_method(D_METHOD("set_item_indent", "index", "indent"), &PopupMenu::set_item_indent);
  2240. ClassDB::bind_method(D_METHOD("set_item_multistate", "index", "state"), &PopupMenu::set_item_multistate);
  2241. ClassDB::bind_method(D_METHOD("set_item_shortcut_disabled", "index", "disabled"), &PopupMenu::set_item_shortcut_disabled);
  2242. ClassDB::bind_method(D_METHOD("toggle_item_checked", "index"), &PopupMenu::toggle_item_checked);
  2243. ClassDB::bind_method(D_METHOD("toggle_item_multistate", "index"), &PopupMenu::toggle_item_multistate);
  2244. ClassDB::bind_method(D_METHOD("get_item_text", "index"), &PopupMenu::get_item_text);
  2245. ClassDB::bind_method(D_METHOD("get_item_text_direction", "index"), &PopupMenu::get_item_text_direction);
  2246. ClassDB::bind_method(D_METHOD("get_item_language", "index"), &PopupMenu::get_item_language);
  2247. ClassDB::bind_method(D_METHOD("get_item_icon", "index"), &PopupMenu::get_item_icon);
  2248. ClassDB::bind_method(D_METHOD("get_item_icon_max_width", "index"), &PopupMenu::get_item_icon_max_width);
  2249. ClassDB::bind_method(D_METHOD("get_item_icon_modulate", "index"), &PopupMenu::get_item_icon_modulate);
  2250. ClassDB::bind_method(D_METHOD("is_item_checked", "index"), &PopupMenu::is_item_checked);
  2251. ClassDB::bind_method(D_METHOD("get_item_id", "index"), &PopupMenu::get_item_id);
  2252. ClassDB::bind_method(D_METHOD("get_item_index", "id"), &PopupMenu::get_item_index);
  2253. ClassDB::bind_method(D_METHOD("get_item_accelerator", "index"), &PopupMenu::get_item_accelerator);
  2254. ClassDB::bind_method(D_METHOD("get_item_metadata", "index"), &PopupMenu::get_item_metadata);
  2255. ClassDB::bind_method(D_METHOD("is_item_disabled", "index"), &PopupMenu::is_item_disabled);
  2256. ClassDB::bind_method(D_METHOD("get_item_submenu", "index"), &PopupMenu::get_item_submenu);
  2257. ClassDB::bind_method(D_METHOD("is_item_separator", "index"), &PopupMenu::is_item_separator);
  2258. ClassDB::bind_method(D_METHOD("is_item_checkable", "index"), &PopupMenu::is_item_checkable);
  2259. ClassDB::bind_method(D_METHOD("is_item_radio_checkable", "index"), &PopupMenu::is_item_radio_checkable);
  2260. ClassDB::bind_method(D_METHOD("is_item_shortcut_disabled", "index"), &PopupMenu::is_item_shortcut_disabled);
  2261. ClassDB::bind_method(D_METHOD("get_item_tooltip", "index"), &PopupMenu::get_item_tooltip);
  2262. ClassDB::bind_method(D_METHOD("get_item_shortcut", "index"), &PopupMenu::get_item_shortcut);
  2263. ClassDB::bind_method(D_METHOD("get_item_indent", "index"), &PopupMenu::get_item_indent);
  2264. ClassDB::bind_method(D_METHOD("set_focused_item", "index"), &PopupMenu::set_focused_item);
  2265. ClassDB::bind_method(D_METHOD("get_focused_item"), &PopupMenu::get_focused_item);
  2266. ClassDB::bind_method(D_METHOD("set_item_count", "count"), &PopupMenu::set_item_count);
  2267. ClassDB::bind_method(D_METHOD("get_item_count"), &PopupMenu::get_item_count);
  2268. ClassDB::bind_method(D_METHOD("scroll_to_item", "index"), &PopupMenu::scroll_to_item);
  2269. ClassDB::bind_method(D_METHOD("remove_item", "index"), &PopupMenu::remove_item);
  2270. ClassDB::bind_method(D_METHOD("add_separator", "label", "id"), &PopupMenu::add_separator, DEFVAL(String()), DEFVAL(-1));
  2271. ClassDB::bind_method(D_METHOD("clear", "free_submenus"), &PopupMenu::clear, DEFVAL(false));
  2272. ClassDB::bind_method(D_METHOD("set_hide_on_item_selection", "enable"), &PopupMenu::set_hide_on_item_selection);
  2273. ClassDB::bind_method(D_METHOD("is_hide_on_item_selection"), &PopupMenu::is_hide_on_item_selection);
  2274. ClassDB::bind_method(D_METHOD("set_hide_on_checkable_item_selection", "enable"), &PopupMenu::set_hide_on_checkable_item_selection);
  2275. ClassDB::bind_method(D_METHOD("is_hide_on_checkable_item_selection"), &PopupMenu::is_hide_on_checkable_item_selection);
  2276. ClassDB::bind_method(D_METHOD("set_hide_on_state_item_selection", "enable"), &PopupMenu::set_hide_on_multistate_item_selection);
  2277. ClassDB::bind_method(D_METHOD("is_hide_on_state_item_selection"), &PopupMenu::is_hide_on_multistate_item_selection);
  2278. ClassDB::bind_method(D_METHOD("set_submenu_popup_delay", "seconds"), &PopupMenu::set_submenu_popup_delay);
  2279. ClassDB::bind_method(D_METHOD("get_submenu_popup_delay"), &PopupMenu::get_submenu_popup_delay);
  2280. ClassDB::bind_method(D_METHOD("set_allow_search", "allow"), &PopupMenu::set_allow_search);
  2281. ClassDB::bind_method(D_METHOD("get_allow_search"), &PopupMenu::get_allow_search);
  2282. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_item_selection"), "set_hide_on_item_selection", "is_hide_on_item_selection");
  2283. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_checkable_item_selection"), "set_hide_on_checkable_item_selection", "is_hide_on_checkable_item_selection");
  2284. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_state_item_selection"), "set_hide_on_state_item_selection", "is_hide_on_state_item_selection");
  2285. ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "submenu_popup_delay", PROPERTY_HINT_NONE, "suffix:s"), "set_submenu_popup_delay", "get_submenu_popup_delay");
  2286. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_search"), "set_allow_search", "get_allow_search");
  2287. ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "item_");
  2288. ADD_SIGNAL(MethodInfo("id_pressed", PropertyInfo(Variant::INT, "id")));
  2289. ADD_SIGNAL(MethodInfo("id_focused", PropertyInfo(Variant::INT, "id")));
  2290. ADD_SIGNAL(MethodInfo("index_pressed", PropertyInfo(Variant::INT, "index")));
  2291. ADD_SIGNAL(MethodInfo("menu_changed"));
  2292. BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, PopupMenu, panel_style, "panel");
  2293. BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, PopupMenu, hover_style, "hover");
  2294. BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, PopupMenu, separator_style, "separator");
  2295. BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, PopupMenu, labeled_separator_left);
  2296. BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, PopupMenu, labeled_separator_right);
  2297. BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, PopupMenu, v_separation);
  2298. BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, PopupMenu, h_separation);
  2299. BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, PopupMenu, indent);
  2300. BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, PopupMenu, item_start_padding);
  2301. BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, PopupMenu, item_end_padding);
  2302. BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, PopupMenu, icon_max_width);
  2303. BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, PopupMenu, checked);
  2304. BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, PopupMenu, checked_disabled);
  2305. BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, PopupMenu, unchecked);
  2306. BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, PopupMenu, unchecked_disabled);
  2307. BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, PopupMenu, radio_checked);
  2308. BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, PopupMenu, radio_checked_disabled);
  2309. BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, PopupMenu, radio_unchecked);
  2310. BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, PopupMenu, radio_unchecked_disabled);
  2311. BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, PopupMenu, submenu);
  2312. BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, PopupMenu, submenu_mirrored);
  2313. BIND_THEME_ITEM(Theme::DATA_TYPE_FONT, PopupMenu, font);
  2314. BIND_THEME_ITEM(Theme::DATA_TYPE_FONT_SIZE, PopupMenu, font_size);
  2315. BIND_THEME_ITEM(Theme::DATA_TYPE_FONT, PopupMenu, font_separator);
  2316. BIND_THEME_ITEM(Theme::DATA_TYPE_FONT_SIZE, PopupMenu, font_separator_size);
  2317. BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, PopupMenu, font_color);
  2318. BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, PopupMenu, font_hover_color);
  2319. BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, PopupMenu, font_disabled_color);
  2320. BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, PopupMenu, font_accelerator_color);
  2321. BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_CONSTANT, PopupMenu, font_outline_size, "outline_size");
  2322. BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, PopupMenu, font_outline_color);
  2323. BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, PopupMenu, font_separator_color);
  2324. BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_CONSTANT, PopupMenu, font_separator_outline_size, "separator_outline_size");
  2325. BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, PopupMenu, font_separator_outline_color);
  2326. }
  2327. void PopupMenu::popup(const Rect2i &p_bounds) {
  2328. moved = Vector2();
  2329. popup_time_msec = OS::get_singleton()->get_ticks_msec();
  2330. Popup::popup(p_bounds);
  2331. }
  2332. PopupMenu::PopupMenu() {
  2333. // Margin Container
  2334. margin_container = memnew(MarginContainer);
  2335. margin_container->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
  2336. add_child(margin_container, false, INTERNAL_MODE_FRONT);
  2337. margin_container->connect("draw", callable_mp(this, &PopupMenu::_draw_background));
  2338. // Scroll Container
  2339. scroll_container = memnew(ScrollContainer);
  2340. scroll_container->set_clip_contents(true);
  2341. margin_container->add_child(scroll_container);
  2342. // The control which will display the items
  2343. control = memnew(Control);
  2344. control->set_clip_contents(false);
  2345. control->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
  2346. control->set_h_size_flags(Control::SIZE_EXPAND_FILL);
  2347. control->set_v_size_flags(Control::SIZE_EXPAND_FILL);
  2348. scroll_container->add_child(control, false, INTERNAL_MODE_FRONT);
  2349. control->connect("draw", callable_mp(this, &PopupMenu::_draw_items));
  2350. connect("window_input", callable_mp(this, &PopupMenu::gui_input));
  2351. submenu_timer = memnew(Timer);
  2352. submenu_timer->set_wait_time(0.3);
  2353. submenu_timer->set_one_shot(true);
  2354. submenu_timer->connect("timeout", callable_mp(this, &PopupMenu::_submenu_timeout));
  2355. add_child(submenu_timer, false, INTERNAL_MODE_FRONT);
  2356. minimum_lifetime_timer = memnew(Timer);
  2357. minimum_lifetime_timer->set_wait_time(0.3);
  2358. minimum_lifetime_timer->set_one_shot(true);
  2359. minimum_lifetime_timer->connect("timeout", callable_mp(this, &PopupMenu::_minimum_lifetime_timeout));
  2360. add_child(minimum_lifetime_timer, false, INTERNAL_MODE_FRONT);
  2361. }
  2362. PopupMenu::~PopupMenu() {
  2363. }