native_menu_macos.mm 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403
  1. /**************************************************************************/
  2. /* native_menu_macos.mm */
  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. #import "native_menu_macos.h"
  31. #import "display_server_macos.h"
  32. #import "godot_menu_item.h"
  33. #import "key_mapping_macos.h"
  34. #include "scene/resources/image_texture.h"
  35. void NativeMenuMacOS::_register_system_menus(NSMenu *p_main_menu, NSMenu *p_application_menu, NSMenu *p_window_menu, NSMenu *p_help_menu, NSMenu *p_dock_menu) {
  36. {
  37. MenuData *md = memnew(MenuData);
  38. md->menu = p_main_menu;
  39. md->is_system = true;
  40. main_menu = menus.make_rid(md);
  41. main_menu_ns = p_main_menu;
  42. menu_lookup[md->menu] = main_menu;
  43. }
  44. {
  45. MenuData *md = memnew(MenuData);
  46. md->menu = p_application_menu;
  47. md->is_system = true;
  48. application_menu = menus.make_rid(md);
  49. application_menu_ns = p_application_menu;
  50. menu_lookup[md->menu] = application_menu;
  51. }
  52. {
  53. MenuData *md = memnew(MenuData);
  54. md->menu = p_window_menu;
  55. md->is_system = true;
  56. window_menu = menus.make_rid(md);
  57. window_menu_ns = p_window_menu;
  58. menu_lookup[md->menu] = window_menu;
  59. }
  60. {
  61. MenuData *md = memnew(MenuData);
  62. md->menu = p_help_menu;
  63. md->is_system = true;
  64. help_menu = menus.make_rid(md);
  65. help_menu_ns = p_help_menu;
  66. menu_lookup[md->menu] = help_menu;
  67. }
  68. {
  69. MenuData *md = memnew(MenuData);
  70. md->menu = p_dock_menu;
  71. md->is_system = true;
  72. dock_menu = menus.make_rid(md);
  73. dock_menu_ns = p_dock_menu;
  74. menu_lookup[md->menu] = dock_menu;
  75. }
  76. }
  77. NSMenu *NativeMenuMacOS::_get_dock_menu() {
  78. MenuData *md = menus.get_or_null(dock_menu);
  79. if (md) {
  80. return md->menu;
  81. }
  82. return nullptr;
  83. }
  84. void NativeMenuMacOS::_menu_open(NSMenu *p_menu) {
  85. if (menu_lookup.has(p_menu)) {
  86. MenuData *md = menus.get_or_null(menu_lookup[p_menu]);
  87. if (md) {
  88. // Note: Set "is_open" flag, but do not call callback, menu items can't be modified during this call and "_menu_need_update" will be called right before it.
  89. md->is_open = true;
  90. }
  91. }
  92. }
  93. void NativeMenuMacOS::_menu_need_update(NSMenu *p_menu) {
  94. if (menu_lookup.has(p_menu)) {
  95. MenuData *md = menus.get_or_null(menu_lookup[p_menu]);
  96. if (md) {
  97. // Note: "is_open" flag is set by "_menu_open", this method is always called before menu is shown, but might be called for the other reasons as well.
  98. if (md->open_cb.is_valid()) {
  99. Variant ret;
  100. Callable::CallError ce;
  101. // Callback is called directly, since it's expected to modify menu items before it's shown.
  102. md->open_cb.callp(nullptr, 0, ret, ce);
  103. if (ce.error != Callable::CallError::CALL_OK) {
  104. ERR_PRINT(vformat("Failed to execute menu open callback: %s.", Variant::get_callable_error_text(md->open_cb, nullptr, 0, ce)));
  105. }
  106. }
  107. }
  108. }
  109. }
  110. void NativeMenuMacOS::_menu_close(NSMenu *p_menu) {
  111. if (menu_lookup.has(p_menu)) {
  112. MenuData *md = menus.get_or_null(menu_lookup[p_menu]);
  113. if (md) {
  114. md->is_open = false;
  115. // Callback called deferred, since it should not modify menu items during "_menu_close" call.
  116. callable_mp(this, &NativeMenuMacOS::_menu_close_cb).call_deferred(menu_lookup[p_menu]);
  117. }
  118. }
  119. }
  120. void NativeMenuMacOS::_menu_close_cb(const RID &p_rid) {
  121. MenuData *md = menus.get_or_null(p_rid);
  122. if (md->close_cb.is_valid()) {
  123. Variant ret;
  124. Callable::CallError ce;
  125. md->close_cb.callp(nullptr, 0, ret, ce);
  126. if (ce.error != Callable::CallError::CALL_OK) {
  127. ERR_PRINT(vformat("Failed to execute menu close callback: %s.", Variant::get_callable_error_text(md->close_cb, nullptr, 0, ce)));
  128. }
  129. }
  130. }
  131. bool NativeMenuMacOS::_is_menu_opened(NSMenu *p_menu) const {
  132. if (menu_lookup.has(p_menu)) {
  133. const MenuData *md = menus.get_or_null(menu_lookup[p_menu]);
  134. if (md && md->is_open) {
  135. return true;
  136. }
  137. }
  138. for (NSInteger i = (p_menu == [NSApp mainMenu]) ? 1 : 0; i < [p_menu numberOfItems]; i++) {
  139. const NSMenuItem *menu_item = [p_menu itemAtIndex:i];
  140. if ([menu_item submenu]) {
  141. if (_is_menu_opened([menu_item submenu])) {
  142. return true;
  143. }
  144. }
  145. }
  146. return false;
  147. }
  148. int NativeMenuMacOS::_get_system_menu_start(const NSMenu *p_menu) const {
  149. if (p_menu == [NSApp mainMenu]) { // Skip Apple menu.
  150. return 1;
  151. }
  152. if (p_menu == application_menu_ns || p_menu == window_menu_ns || p_menu == help_menu_ns) {
  153. int count = [p_menu numberOfItems];
  154. for (int i = 0; i < count; i++) {
  155. NSMenuItem *menu_item = [p_menu itemAtIndex:i];
  156. if (menu_item.tag == MENU_TAG_START) {
  157. return i + 1;
  158. }
  159. }
  160. }
  161. return 0;
  162. }
  163. int NativeMenuMacOS::_get_system_menu_count(const NSMenu *p_menu) const {
  164. if (p_menu == [NSApp mainMenu]) { // Skip Apple, Window and Help menu.
  165. return [p_menu numberOfItems] - 3;
  166. }
  167. if (p_menu == application_menu_ns || p_menu == window_menu_ns || p_menu == help_menu_ns) {
  168. int start = 0;
  169. int count = [p_menu numberOfItems];
  170. for (int i = 0; i < count; i++) {
  171. NSMenuItem *menu_item = [p_menu itemAtIndex:i];
  172. if (menu_item.tag == MENU_TAG_START) {
  173. start = i + 1;
  174. }
  175. if (menu_item.tag == MENU_TAG_END) {
  176. return i - start;
  177. }
  178. }
  179. }
  180. return [p_menu numberOfItems];
  181. }
  182. bool NativeMenuMacOS::has_feature(Feature p_feature) const {
  183. switch (p_feature) {
  184. case FEATURE_GLOBAL_MENU:
  185. case FEATURE_POPUP_MENU:
  186. case FEATURE_OPEN_CLOSE_CALLBACK:
  187. case FEATURE_HOVER_CALLBACK:
  188. case FEATURE_KEY_CALLBACK:
  189. return true;
  190. default:
  191. return false;
  192. }
  193. }
  194. bool NativeMenuMacOS::has_system_menu(SystemMenus p_menu_id) const {
  195. switch (p_menu_id) {
  196. case MAIN_MENU_ID:
  197. case APPLICATION_MENU_ID:
  198. case WINDOW_MENU_ID:
  199. case HELP_MENU_ID:
  200. case DOCK_MENU_ID:
  201. return true;
  202. default:
  203. return false;
  204. }
  205. }
  206. RID NativeMenuMacOS::get_system_menu(SystemMenus p_menu_id) const {
  207. switch (p_menu_id) {
  208. case MAIN_MENU_ID:
  209. return main_menu;
  210. case APPLICATION_MENU_ID:
  211. return application_menu;
  212. case WINDOW_MENU_ID:
  213. return window_menu;
  214. case HELP_MENU_ID:
  215. return help_menu;
  216. case DOCK_MENU_ID:
  217. return dock_menu;
  218. default:
  219. return RID();
  220. }
  221. }
  222. RID NativeMenuMacOS::create_menu() {
  223. MenuData *md = memnew(MenuData);
  224. md->menu = [[NSMenu alloc] initWithTitle:@""];
  225. [md->menu setAutoenablesItems:NO];
  226. DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
  227. if (ds) {
  228. ds->set_menu_delegate(md->menu);
  229. }
  230. RID rid = menus.make_rid(md);
  231. menu_lookup[md->menu] = rid;
  232. return rid;
  233. }
  234. bool NativeMenuMacOS::has_menu(const RID &p_rid) const {
  235. return menus.owns(p_rid);
  236. }
  237. void NativeMenuMacOS::free_menu(const RID &p_rid) {
  238. MenuData *md = menus.get_or_null(p_rid);
  239. if (md && !md->is_system) {
  240. clear(p_rid);
  241. menus.free(p_rid);
  242. menu_lookup.erase(md->menu);
  243. md->menu = nullptr;
  244. memdelete(md);
  245. }
  246. }
  247. NSMenu *NativeMenuMacOS::get_native_menu_handle(const RID &p_rid) {
  248. MenuData *md = menus.get_or_null(p_rid);
  249. ERR_FAIL_NULL_V(md, nullptr);
  250. return md->menu;
  251. }
  252. Size2 NativeMenuMacOS::get_size(const RID &p_rid) const {
  253. const MenuData *md = menus.get_or_null(p_rid);
  254. ERR_FAIL_NULL_V(md, Size2());
  255. return Size2(md->menu.size.width, md->menu.size.height) * DisplayServer::get_singleton()->screen_get_max_scale();
  256. }
  257. void NativeMenuMacOS::popup(const RID &p_rid, const Vector2i &p_position) {
  258. const MenuData *md = menus.get_or_null(p_rid);
  259. ERR_FAIL_NULL(md);
  260. DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
  261. if (ds) {
  262. Point2i position = p_position;
  263. // macOS native y-coordinate relative to _get_screens_origin() is negative,
  264. // Godot passes a positive value.
  265. position.y *= -1;
  266. position += ds->_get_screens_origin();
  267. position /= ds->screen_get_max_scale();
  268. [md->menu popUpMenuPositioningItem:nil atLocation:NSMakePoint(position.x, position.y - 5) inView:nil]; // Menu vertical position doesn't include rounded corners, add `5` display pixels to better align it with Godot buttons.
  269. ds->release_pressed_events(); // Note: context menu block main loop and consume events, pressed keys and mouse buttons should be released manually.
  270. ds->sync_mouse_state();
  271. }
  272. }
  273. void NativeMenuMacOS::set_interface_direction(const RID &p_rid, bool p_is_rtl) {
  274. MenuData *md = menus.get_or_null(p_rid);
  275. ERR_FAIL_NULL(md);
  276. md->menu.userInterfaceLayoutDirection = p_is_rtl ? NSUserInterfaceLayoutDirectionRightToLeft : NSUserInterfaceLayoutDirectionLeftToRight;
  277. }
  278. void NativeMenuMacOS::set_popup_open_callback(const RID &p_rid, const Callable &p_callback) {
  279. MenuData *md = menus.get_or_null(p_rid);
  280. ERR_FAIL_NULL(md);
  281. md->open_cb = p_callback;
  282. }
  283. Callable NativeMenuMacOS::get_popup_open_callback(const RID &p_rid) const {
  284. const MenuData *md = menus.get_or_null(p_rid);
  285. ERR_FAIL_NULL_V(md, Callable());
  286. return md->open_cb;
  287. }
  288. void NativeMenuMacOS::set_popup_close_callback(const RID &p_rid, const Callable &p_callback) {
  289. MenuData *md = menus.get_or_null(p_rid);
  290. ERR_FAIL_NULL(md);
  291. md->close_cb = p_callback;
  292. }
  293. Callable NativeMenuMacOS::get_popup_close_callback(const RID &p_rid) const {
  294. const MenuData *md = menus.get_or_null(p_rid);
  295. ERR_FAIL_NULL_V(md, Callable());
  296. return md->close_cb;
  297. }
  298. void NativeMenuMacOS::set_minimum_width(const RID &p_rid, float p_width) {
  299. MenuData *md = menus.get_or_null(p_rid);
  300. ERR_FAIL_NULL(md);
  301. md->menu.minimumWidth = p_width / DisplayServer::get_singleton()->screen_get_max_scale();
  302. }
  303. float NativeMenuMacOS::get_minimum_width(const RID &p_rid) const {
  304. const MenuData *md = menus.get_or_null(p_rid);
  305. ERR_FAIL_NULL_V(md, 0.0);
  306. return md->menu.minimumWidth * DisplayServer::get_singleton()->screen_get_max_scale();
  307. }
  308. bool NativeMenuMacOS::is_opened(const RID &p_rid) const {
  309. const MenuData *md = menus.get_or_null(p_rid);
  310. ERR_FAIL_NULL_V(md, false);
  311. return md->is_open;
  312. }
  313. int NativeMenuMacOS::add_submenu_item(const RID &p_rid, const String &p_label, const RID &p_submenu_rid, const Variant &p_tag, int p_index) {
  314. MenuData *md = menus.get_or_null(p_rid);
  315. MenuData *md_sub = menus.get_or_null(p_submenu_rid);
  316. ERR_FAIL_NULL_V(md, -1);
  317. ERR_FAIL_NULL_V(md_sub, -1);
  318. ERR_FAIL_COND_V_MSG(md->menu == md_sub->menu, -1, "Can't set submenu to self!");
  319. ERR_FAIL_COND_V_MSG([md_sub->menu supermenu], -1, "Can't set submenu to menu that is already a submenu of some other menu!");
  320. NSMenuItem *menu_item;
  321. int item_start = _get_system_menu_start(md->menu);
  322. int item_count = _get_system_menu_count(md->menu);
  323. if (p_index < 0) {
  324. p_index = item_start + item_count;
  325. } else {
  326. p_index += item_start;
  327. p_index = CLAMP(p_index, item_start, item_start + item_count);
  328. }
  329. menu_item = [md->menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index];
  330. GodotMenuItem *obj = [[GodotMenuItem alloc] init];
  331. obj->meta = p_tag;
  332. [menu_item setRepresentedObject:obj];
  333. [md_sub->menu setTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()]];
  334. [md->menu setSubmenu:md_sub->menu forItem:menu_item];
  335. return p_index - item_start;
  336. }
  337. NSMenuItem *NativeMenuMacOS::_menu_add_item(NSMenu *p_menu, const String &p_label, Key p_accel, int p_index, int *r_out) {
  338. if (p_menu) {
  339. String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
  340. NSMenuItem *menu_item;
  341. int item_start = _get_system_menu_start(p_menu);
  342. int item_count = _get_system_menu_count(p_menu);
  343. if (p_index < 0) {
  344. p_index = item_start + item_count;
  345. } else {
  346. p_index += item_start;
  347. p_index = CLAMP(p_index, item_start, item_start + item_count);
  348. }
  349. menu_item = [p_menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
  350. *r_out = p_index - item_start;
  351. return menu_item;
  352. }
  353. return nullptr;
  354. }
  355. int NativeMenuMacOS::add_item(const RID &p_rid, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
  356. MenuData *md = menus.get_or_null(p_rid);
  357. ERR_FAIL_NULL_V(md, -1);
  358. int out = -1;
  359. NSMenuItem *menu_item = _menu_add_item(md->menu, p_label, p_accel, p_index, &out);
  360. if (menu_item) {
  361. GodotMenuItem *obj = [[GodotMenuItem alloc] init];
  362. obj->callback = p_callback;
  363. obj->key_callback = p_key_callback;
  364. obj->meta = p_tag;
  365. obj->accel = p_accel;
  366. [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
  367. [menu_item setRepresentedObject:obj];
  368. }
  369. return out;
  370. }
  371. int NativeMenuMacOS::add_check_item(const RID &p_rid, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
  372. MenuData *md = menus.get_or_null(p_rid);
  373. ERR_FAIL_NULL_V(md, -1);
  374. int out = -1;
  375. NSMenuItem *menu_item = _menu_add_item(md->menu, p_label, p_accel, p_index, &out);
  376. if (menu_item) {
  377. GodotMenuItem *obj = [[GodotMenuItem alloc] init];
  378. obj->callback = p_callback;
  379. obj->key_callback = p_key_callback;
  380. obj->meta = p_tag;
  381. obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
  382. obj->accel = p_accel;
  383. [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
  384. [menu_item setRepresentedObject:obj];
  385. }
  386. return out;
  387. }
  388. int NativeMenuMacOS::add_icon_item(const RID &p_rid, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
  389. MenuData *md = menus.get_or_null(p_rid);
  390. ERR_FAIL_NULL_V(md, -1);
  391. int out = -1;
  392. NSMenuItem *menu_item = _menu_add_item(md->menu, p_label, p_accel, p_index, &out);
  393. if (menu_item) {
  394. GodotMenuItem *obj = [[GodotMenuItem alloc] init];
  395. obj->callback = p_callback;
  396. obj->key_callback = p_key_callback;
  397. obj->meta = p_tag;
  398. obj->accel = p_accel;
  399. DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
  400. if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) {
  401. obj->img = p_icon->get_image();
  402. obj->img = obj->img->duplicate();
  403. if (obj->img->is_compressed()) {
  404. obj->img->decompress();
  405. }
  406. NSImage *image = ds->_convert_to_nsimg(obj->img);
  407. [image setSize:NSMakeSize(16, 16)];
  408. [menu_item setImage:image];
  409. }
  410. [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
  411. [menu_item setRepresentedObject:obj];
  412. }
  413. return out;
  414. }
  415. int NativeMenuMacOS::add_icon_check_item(const RID &p_rid, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
  416. MenuData *md = menus.get_or_null(p_rid);
  417. ERR_FAIL_NULL_V(md, -1);
  418. int out = -1;
  419. NSMenuItem *menu_item = _menu_add_item(md->menu, p_label, p_accel, p_index, &out);
  420. if (menu_item) {
  421. GodotMenuItem *obj = [[GodotMenuItem alloc] init];
  422. obj->callback = p_callback;
  423. obj->key_callback = p_key_callback;
  424. obj->meta = p_tag;
  425. obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
  426. obj->accel = p_accel;
  427. DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
  428. if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) {
  429. obj->img = p_icon->get_image();
  430. obj->img = obj->img->duplicate();
  431. if (obj->img->is_compressed()) {
  432. obj->img->decompress();
  433. }
  434. NSImage *image = ds->_convert_to_nsimg(obj->img);
  435. [image setSize:NSMakeSize(16, 16)];
  436. [menu_item setImage:image];
  437. }
  438. [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
  439. [menu_item setRepresentedObject:obj];
  440. }
  441. return out;
  442. }
  443. int NativeMenuMacOS::add_radio_check_item(const RID &p_rid, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
  444. MenuData *md = menus.get_or_null(p_rid);
  445. ERR_FAIL_NULL_V(md, -1);
  446. int out = -1;
  447. NSMenuItem *menu_item = _menu_add_item(md->menu, p_label, p_accel, p_index, &out);
  448. if (menu_item) {
  449. GodotMenuItem *obj = [[GodotMenuItem alloc] init];
  450. obj->callback = p_callback;
  451. obj->key_callback = p_key_callback;
  452. obj->meta = p_tag;
  453. obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON;
  454. obj->accel = p_accel;
  455. [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
  456. [menu_item setRepresentedObject:obj];
  457. }
  458. return out;
  459. }
  460. int NativeMenuMacOS::add_icon_radio_check_item(const RID &p_rid, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
  461. MenuData *md = menus.get_or_null(p_rid);
  462. ERR_FAIL_NULL_V(md, -1);
  463. int out = -1;
  464. NSMenuItem *menu_item = _menu_add_item(md->menu, p_label, p_accel, p_index, &out);
  465. if (menu_item) {
  466. GodotMenuItem *obj = [[GodotMenuItem alloc] init];
  467. obj->callback = p_callback;
  468. obj->key_callback = p_key_callback;
  469. obj->meta = p_tag;
  470. obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON;
  471. obj->accel = p_accel;
  472. DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
  473. if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) {
  474. obj->img = p_icon->get_image();
  475. obj->img = obj->img->duplicate();
  476. if (obj->img->is_compressed()) {
  477. obj->img->decompress();
  478. }
  479. NSImage *image = ds->_convert_to_nsimg(obj->img);
  480. [image setSize:NSMakeSize(16, 16)];
  481. [menu_item setImage:image];
  482. }
  483. [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
  484. [menu_item setRepresentedObject:obj];
  485. }
  486. return out;
  487. }
  488. int NativeMenuMacOS::add_multistate_item(const RID &p_rid, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
  489. MenuData *md = menus.get_or_null(p_rid);
  490. ERR_FAIL_NULL_V(md, -1);
  491. int out = -1;
  492. NSMenuItem *menu_item = _menu_add_item(md->menu, p_label, p_accel, p_index, &out);
  493. if (menu_item) {
  494. GodotMenuItem *obj = [[GodotMenuItem alloc] init];
  495. obj->callback = p_callback;
  496. obj->key_callback = p_key_callback;
  497. obj->meta = p_tag;
  498. obj->max_states = p_max_states;
  499. obj->state = p_default_state;
  500. obj->accel = p_accel;
  501. [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
  502. [menu_item setRepresentedObject:obj];
  503. }
  504. return out;
  505. }
  506. int NativeMenuMacOS::add_separator(const RID &p_rid, int p_index) {
  507. MenuData *md = menus.get_or_null(p_rid);
  508. ERR_FAIL_NULL_V(md, -1);
  509. if (md->menu == [NSApp mainMenu]) { // Do not add separators into main menu.
  510. return -1;
  511. }
  512. int item_start = _get_system_menu_start(md->menu);
  513. int item_count = _get_system_menu_count(md->menu);
  514. if (p_index < 0) {
  515. p_index = item_start + item_count;
  516. } else {
  517. p_index += item_start;
  518. p_index = CLAMP(p_index, item_start, item_start + item_count);
  519. }
  520. [md->menu insertItem:[NSMenuItem separatorItem] atIndex:p_index];
  521. return p_index - item_start;
  522. }
  523. int NativeMenuMacOS::find_item_index_with_text(const RID &p_rid, const String &p_text) const {
  524. const MenuData *md = menus.get_or_null(p_rid);
  525. ERR_FAIL_NULL_V(md, -1);
  526. int item_start = _get_system_menu_start(md->menu);
  527. int index = [md->menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
  528. if (index >= 0) {
  529. return index - item_start;
  530. }
  531. return -1;
  532. }
  533. int NativeMenuMacOS::find_item_index_with_tag(const RID &p_rid, const Variant &p_tag) const {
  534. const MenuData *md = menus.get_or_null(p_rid);
  535. ERR_FAIL_NULL_V(md, -1);
  536. int item_start = _get_system_menu_start(md->menu);
  537. int item_count = _get_system_menu_count(md->menu);
  538. for (NSInteger i = item_start; i < item_start + item_count; i++) {
  539. const NSMenuItem *menu_item = [md->menu itemAtIndex:i];
  540. if (menu_item) {
  541. const GodotMenuItem *obj = [menu_item representedObject];
  542. if (obj && obj->meta == p_tag) {
  543. return i - item_start;
  544. }
  545. }
  546. }
  547. return -1;
  548. }
  549. bool NativeMenuMacOS::is_item_checked(const RID &p_rid, int p_idx) const {
  550. ERR_FAIL_COND_V(p_idx < 0, false);
  551. const MenuData *md = menus.get_or_null(p_rid);
  552. ERR_FAIL_NULL_V(md, false);
  553. int item_start = _get_system_menu_start(md->menu);
  554. int item_count = _get_system_menu_count(md->menu);
  555. p_idx += item_start;
  556. ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
  557. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  558. if (menu_item) {
  559. const GodotMenuItem *obj = [menu_item representedObject];
  560. if (obj) {
  561. return obj->checked;
  562. }
  563. }
  564. return false;
  565. }
  566. bool NativeMenuMacOS::is_item_checkable(const RID &p_rid, int p_idx) const {
  567. ERR_FAIL_COND_V(p_idx < 0, false);
  568. const MenuData *md = menus.get_or_null(p_rid);
  569. ERR_FAIL_NULL_V(md, false);
  570. int item_start = _get_system_menu_start(md->menu);
  571. int item_count = _get_system_menu_count(md->menu);
  572. p_idx += item_start;
  573. ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
  574. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  575. if (menu_item) {
  576. GodotMenuItem *obj = [menu_item representedObject];
  577. if (obj) {
  578. return obj->checkable_type == CHECKABLE_TYPE_CHECK_BOX;
  579. }
  580. }
  581. return false;
  582. }
  583. bool NativeMenuMacOS::is_item_radio_checkable(const RID &p_rid, int p_idx) const {
  584. ERR_FAIL_COND_V(p_idx < 0, false);
  585. const MenuData *md = menus.get_or_null(p_rid);
  586. ERR_FAIL_NULL_V(md, false);
  587. int item_start = _get_system_menu_start(md->menu);
  588. int item_count = _get_system_menu_count(md->menu);
  589. p_idx += item_start;
  590. ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
  591. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  592. if (menu_item) {
  593. GodotMenuItem *obj = [menu_item representedObject];
  594. if (obj) {
  595. return obj->checkable_type == CHECKABLE_TYPE_RADIO_BUTTON;
  596. }
  597. }
  598. return false;
  599. }
  600. Callable NativeMenuMacOS::get_item_callback(const RID &p_rid, int p_idx) const {
  601. ERR_FAIL_COND_V(p_idx < 0, Callable());
  602. const MenuData *md = menus.get_or_null(p_rid);
  603. ERR_FAIL_NULL_V(md, Callable());
  604. int item_start = _get_system_menu_start(md->menu);
  605. int item_count = _get_system_menu_count(md->menu);
  606. p_idx += item_start;
  607. ERR_FAIL_COND_V(p_idx >= item_start + item_count, Callable());
  608. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  609. if (menu_item) {
  610. GodotMenuItem *obj = [menu_item representedObject];
  611. if (obj) {
  612. return obj->callback;
  613. }
  614. }
  615. return Callable();
  616. }
  617. Callable NativeMenuMacOS::get_item_key_callback(const RID &p_rid, int p_idx) const {
  618. ERR_FAIL_COND_V(p_idx < 0, Callable());
  619. const MenuData *md = menus.get_or_null(p_rid);
  620. ERR_FAIL_NULL_V(md, Callable());
  621. int item_start = _get_system_menu_start(md->menu);
  622. int item_count = _get_system_menu_count(md->menu);
  623. p_idx += item_start;
  624. ERR_FAIL_COND_V(p_idx >= item_start + item_count, Callable());
  625. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  626. if (menu_item) {
  627. GodotMenuItem *obj = [menu_item representedObject];
  628. if (obj) {
  629. return obj->key_callback;
  630. }
  631. }
  632. return Callable();
  633. }
  634. Variant NativeMenuMacOS::get_item_tag(const RID &p_rid, int p_idx) const {
  635. ERR_FAIL_COND_V(p_idx < 0, Variant());
  636. const MenuData *md = menus.get_or_null(p_rid);
  637. ERR_FAIL_NULL_V(md, Variant());
  638. int item_start = _get_system_menu_start(md->menu);
  639. int item_count = _get_system_menu_count(md->menu);
  640. p_idx += item_start;
  641. ERR_FAIL_COND_V(p_idx >= item_start + item_count, Variant());
  642. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  643. if (menu_item) {
  644. GodotMenuItem *obj = [menu_item representedObject];
  645. if (obj) {
  646. return obj->meta;
  647. }
  648. }
  649. return Variant();
  650. }
  651. String NativeMenuMacOS::get_item_text(const RID &p_rid, int p_idx) const {
  652. ERR_FAIL_COND_V(p_idx < 0, String());
  653. const MenuData *md = menus.get_or_null(p_rid);
  654. ERR_FAIL_NULL_V(md, String());
  655. int item_start = _get_system_menu_start(md->menu);
  656. int item_count = _get_system_menu_count(md->menu);
  657. p_idx += item_start;
  658. ERR_FAIL_COND_V(p_idx >= item_start + item_count, String());
  659. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  660. if (menu_item) {
  661. return String::utf8([[menu_item title] UTF8String]);
  662. }
  663. return String();
  664. }
  665. RID NativeMenuMacOS::get_item_submenu(const RID &p_rid, int p_idx) const {
  666. ERR_FAIL_COND_V(p_idx < 0, RID());
  667. const MenuData *md = menus.get_or_null(p_rid);
  668. ERR_FAIL_NULL_V(md, RID());
  669. int item_start = _get_system_menu_start(md->menu);
  670. int item_count = _get_system_menu_count(md->menu);
  671. p_idx += item_start;
  672. ERR_FAIL_COND_V(p_idx >= item_start + item_count, RID());
  673. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  674. if (menu_item) {
  675. NSMenu *sub_menu = [menu_item submenu];
  676. if (sub_menu && menu_lookup.has(sub_menu)) {
  677. return menu_lookup[sub_menu];
  678. }
  679. }
  680. return RID();
  681. }
  682. Key NativeMenuMacOS::get_item_accelerator(const RID &p_rid, int p_idx) const {
  683. ERR_FAIL_COND_V(p_idx < 0, Key::NONE);
  684. const MenuData *md = menus.get_or_null(p_rid);
  685. ERR_FAIL_NULL_V(md, Key::NONE);
  686. int item_start = _get_system_menu_start(md->menu);
  687. int item_count = _get_system_menu_count(md->menu);
  688. p_idx += item_start;
  689. ERR_FAIL_COND_V(p_idx >= item_start + item_count, Key::NONE);
  690. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  691. if (menu_item) {
  692. String ret = String::utf8([[menu_item keyEquivalent] UTF8String]);
  693. Key keycode = find_keycode(ret);
  694. NSUInteger mask = [menu_item keyEquivalentModifierMask];
  695. if (mask & NSEventModifierFlagControl) {
  696. keycode |= KeyModifierMask::CTRL;
  697. }
  698. if (mask & NSEventModifierFlagOption) {
  699. keycode |= KeyModifierMask::ALT;
  700. }
  701. if (mask & NSEventModifierFlagShift) {
  702. keycode |= KeyModifierMask::SHIFT;
  703. }
  704. if (mask & NSEventModifierFlagCommand) {
  705. keycode |= KeyModifierMask::META;
  706. }
  707. if (mask & NSEventModifierFlagNumericPad) {
  708. keycode |= KeyModifierMask::KPAD;
  709. }
  710. return keycode;
  711. }
  712. return Key::NONE;
  713. }
  714. bool NativeMenuMacOS::is_item_disabled(const RID &p_rid, int p_idx) const {
  715. ERR_FAIL_COND_V(p_idx < 0, false);
  716. const MenuData *md = menus.get_or_null(p_rid);
  717. ERR_FAIL_NULL_V(md, false);
  718. int item_start = _get_system_menu_start(md->menu);
  719. int item_count = _get_system_menu_count(md->menu);
  720. p_idx += item_start;
  721. ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
  722. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  723. if (menu_item) {
  724. return ![menu_item isEnabled];
  725. }
  726. return false;
  727. }
  728. bool NativeMenuMacOS::is_item_hidden(const RID &p_rid, int p_idx) const {
  729. ERR_FAIL_COND_V(p_idx < 0, false);
  730. const MenuData *md = menus.get_or_null(p_rid);
  731. ERR_FAIL_NULL_V(md, false);
  732. int item_start = _get_system_menu_start(md->menu);
  733. int item_count = _get_system_menu_count(md->menu);
  734. p_idx += item_start;
  735. ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
  736. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  737. if (menu_item) {
  738. return [menu_item isHidden];
  739. }
  740. return false;
  741. }
  742. String NativeMenuMacOS::get_item_tooltip(const RID &p_rid, int p_idx) const {
  743. ERR_FAIL_COND_V(p_idx < 0, String());
  744. const MenuData *md = menus.get_or_null(p_rid);
  745. ERR_FAIL_NULL_V(md, String());
  746. int item_start = _get_system_menu_start(md->menu);
  747. int item_count = _get_system_menu_count(md->menu);
  748. p_idx += item_start;
  749. ERR_FAIL_COND_V(p_idx >= item_start + item_count, String());
  750. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  751. if (menu_item) {
  752. return String::utf8([[menu_item toolTip] UTF8String]);
  753. }
  754. return String();
  755. }
  756. int NativeMenuMacOS::get_item_state(const RID &p_rid, int p_idx) const {
  757. ERR_FAIL_COND_V(p_idx < 0, 0);
  758. const MenuData *md = menus.get_or_null(p_rid);
  759. ERR_FAIL_NULL_V(md, 0);
  760. int item_start = _get_system_menu_start(md->menu);
  761. int item_count = _get_system_menu_count(md->menu);
  762. p_idx += item_start;
  763. ERR_FAIL_COND_V(p_idx >= item_start + item_count, 0);
  764. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  765. if (menu_item) {
  766. GodotMenuItem *obj = [menu_item representedObject];
  767. if (obj) {
  768. return obj->state;
  769. }
  770. }
  771. return 0;
  772. }
  773. int NativeMenuMacOS::get_item_max_states(const RID &p_rid, int p_idx) const {
  774. ERR_FAIL_COND_V(p_idx < 0, 0);
  775. const MenuData *md = menus.get_or_null(p_rid);
  776. ERR_FAIL_NULL_V(md, 0);
  777. int item_start = _get_system_menu_start(md->menu);
  778. int item_count = _get_system_menu_count(md->menu);
  779. p_idx += item_start;
  780. ERR_FAIL_COND_V(p_idx >= item_start + item_count, 0);
  781. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  782. if (menu_item) {
  783. GodotMenuItem *obj = [menu_item representedObject];
  784. if (obj) {
  785. return obj->max_states;
  786. }
  787. }
  788. return 0;
  789. }
  790. Ref<Texture2D> NativeMenuMacOS::get_item_icon(const RID &p_rid, int p_idx) const {
  791. ERR_FAIL_COND_V(p_idx < 0, Ref<Texture2D>());
  792. const MenuData *md = menus.get_or_null(p_rid);
  793. ERR_FAIL_NULL_V(md, Ref<Texture2D>());
  794. int item_start = _get_system_menu_start(md->menu);
  795. int item_count = _get_system_menu_count(md->menu);
  796. p_idx += item_start;
  797. ERR_FAIL_COND_V(p_idx >= item_start + item_count, Ref<Texture2D>());
  798. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  799. if (menu_item) {
  800. GodotMenuItem *obj = [menu_item representedObject];
  801. if (obj) {
  802. if (obj->img.is_valid()) {
  803. return ImageTexture::create_from_image(obj->img);
  804. }
  805. }
  806. }
  807. return Ref<Texture2D>();
  808. }
  809. int NativeMenuMacOS::get_item_indentation_level(const RID &p_rid, int p_idx) const {
  810. ERR_FAIL_COND_V(p_idx < 0, 0);
  811. const MenuData *md = menus.get_or_null(p_rid);
  812. ERR_FAIL_NULL_V(md, 0);
  813. int item_start = _get_system_menu_start(md->menu);
  814. int item_count = _get_system_menu_count(md->menu);
  815. p_idx += item_start;
  816. ERR_FAIL_COND_V(p_idx >= item_start + item_count, 0);
  817. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  818. if (menu_item) {
  819. return [menu_item indentationLevel];
  820. }
  821. return 0;
  822. }
  823. void NativeMenuMacOS::set_item_checked(const RID &p_rid, int p_idx, bool p_checked) {
  824. ERR_FAIL_COND(p_idx < 0);
  825. MenuData *md = menus.get_or_null(p_rid);
  826. ERR_FAIL_NULL(md);
  827. int item_start = _get_system_menu_start(md->menu);
  828. int item_count = _get_system_menu_count(md->menu);
  829. p_idx += item_start;
  830. ERR_FAIL_COND(p_idx >= item_start + item_count);
  831. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  832. if (menu_item) {
  833. GodotMenuItem *obj = [menu_item representedObject];
  834. if (obj) {
  835. obj->checked = p_checked;
  836. if (p_checked) {
  837. [menu_item setState:NSControlStateValueOn];
  838. } else {
  839. [menu_item setState:NSControlStateValueOff];
  840. }
  841. }
  842. }
  843. }
  844. void NativeMenuMacOS::set_item_checkable(const RID &p_rid, int p_idx, bool p_checkable) {
  845. ERR_FAIL_COND(p_idx < 0);
  846. MenuData *md = menus.get_or_null(p_rid);
  847. ERR_FAIL_NULL(md);
  848. int item_start = _get_system_menu_start(md->menu);
  849. int item_count = _get_system_menu_count(md->menu);
  850. p_idx += item_start;
  851. ERR_FAIL_COND(p_idx >= item_start + item_count);
  852. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  853. if (menu_item) {
  854. GodotMenuItem *obj = [menu_item representedObject];
  855. ERR_FAIL_NULL(obj);
  856. obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_CHECK_BOX : CHECKABLE_TYPE_NONE;
  857. }
  858. }
  859. void NativeMenuMacOS::set_item_radio_checkable(const RID &p_rid, int p_idx, bool p_checkable) {
  860. ERR_FAIL_COND(p_idx < 0);
  861. MenuData *md = menus.get_or_null(p_rid);
  862. ERR_FAIL_NULL(md);
  863. int item_start = _get_system_menu_start(md->menu);
  864. int item_count = _get_system_menu_count(md->menu);
  865. p_idx += item_start;
  866. ERR_FAIL_COND(p_idx >= item_start + item_count);
  867. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  868. if (menu_item) {
  869. GodotMenuItem *obj = [menu_item representedObject];
  870. ERR_FAIL_NULL(obj);
  871. obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_RADIO_BUTTON : CHECKABLE_TYPE_NONE;
  872. }
  873. }
  874. void NativeMenuMacOS::set_item_callback(const RID &p_rid, int p_idx, const Callable &p_callback) {
  875. ERR_FAIL_COND(p_idx < 0);
  876. MenuData *md = menus.get_or_null(p_rid);
  877. ERR_FAIL_NULL(md);
  878. int item_start = _get_system_menu_start(md->menu);
  879. int item_count = _get_system_menu_count(md->menu);
  880. p_idx += item_start;
  881. ERR_FAIL_COND(p_idx >= item_start + item_count);
  882. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  883. if (menu_item) {
  884. GodotMenuItem *obj = [menu_item representedObject];
  885. ERR_FAIL_NULL(obj);
  886. obj->callback = p_callback;
  887. }
  888. }
  889. void NativeMenuMacOS::set_item_key_callback(const RID &p_rid, int p_idx, const Callable &p_key_callback) {
  890. ERR_FAIL_COND(p_idx < 0);
  891. MenuData *md = menus.get_or_null(p_rid);
  892. ERR_FAIL_NULL(md);
  893. int item_start = _get_system_menu_start(md->menu);
  894. int item_count = _get_system_menu_count(md->menu);
  895. p_idx += item_start;
  896. ERR_FAIL_COND(p_idx >= item_start + item_count);
  897. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  898. if (menu_item) {
  899. GodotMenuItem *obj = [menu_item representedObject];
  900. ERR_FAIL_NULL(obj);
  901. obj->key_callback = p_key_callback;
  902. }
  903. }
  904. void NativeMenuMacOS::set_item_hover_callbacks(const RID &p_rid, int p_idx, const Callable &p_callback) {
  905. ERR_FAIL_COND(p_idx < 0);
  906. MenuData *md = menus.get_or_null(p_rid);
  907. ERR_FAIL_NULL(md);
  908. int item_start = _get_system_menu_start(md->menu);
  909. int item_count = _get_system_menu_count(md->menu);
  910. p_idx += item_start;
  911. ERR_FAIL_COND(p_idx >= item_start + item_count);
  912. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  913. if (menu_item) {
  914. GodotMenuItem *obj = [menu_item representedObject];
  915. ERR_FAIL_NULL(obj);
  916. obj->hover_callback = p_callback;
  917. }
  918. }
  919. void NativeMenuMacOS::set_item_tag(const RID &p_rid, int p_idx, const Variant &p_tag) {
  920. ERR_FAIL_COND(p_idx < 0);
  921. MenuData *md = menus.get_or_null(p_rid);
  922. ERR_FAIL_NULL(md);
  923. int item_start = _get_system_menu_start(md->menu);
  924. int item_count = _get_system_menu_count(md->menu);
  925. p_idx += item_start;
  926. ERR_FAIL_COND(p_idx >= item_start + item_count);
  927. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  928. if (menu_item) {
  929. GodotMenuItem *obj = [menu_item representedObject];
  930. ERR_FAIL_NULL(obj);
  931. obj->meta = p_tag;
  932. }
  933. }
  934. void NativeMenuMacOS::set_item_text(const RID &p_rid, int p_idx, const String &p_text) {
  935. ERR_FAIL_COND(p_idx < 0);
  936. MenuData *md = menus.get_or_null(p_rid);
  937. ERR_FAIL_NULL(md);
  938. int item_start = _get_system_menu_start(md->menu);
  939. int item_count = _get_system_menu_count(md->menu);
  940. p_idx += item_start;
  941. ERR_FAIL_COND(p_idx >= item_start + item_count);
  942. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  943. if (menu_item) {
  944. [menu_item setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
  945. NSMenu *sub_menu = [menu_item submenu];
  946. if (sub_menu) {
  947. [sub_menu setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
  948. }
  949. }
  950. }
  951. void NativeMenuMacOS::set_item_submenu(const RID &p_rid, int p_idx, const RID &p_submenu_rid) {
  952. ERR_FAIL_COND(p_idx < 0);
  953. MenuData *md = menus.get_or_null(p_rid);
  954. ERR_FAIL_NULL(md);
  955. if (p_submenu_rid.is_valid()) {
  956. MenuData *md_sub = menus.get_or_null(p_submenu_rid);
  957. ERR_FAIL_NULL(md_sub);
  958. ERR_FAIL_COND_MSG(md->menu == md_sub->menu, "Can't set submenu to self!");
  959. ERR_FAIL_COND_MSG([md_sub->menu supermenu], "Can't set submenu to menu that is already a submenu of some other menu!");
  960. int item_start = _get_system_menu_start(md->menu);
  961. int item_count = _get_system_menu_count(md->menu);
  962. p_idx += item_start;
  963. ERR_FAIL_COND(p_idx >= item_start + item_count);
  964. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  965. if (menu_item) {
  966. [md->menu setSubmenu:md_sub->menu forItem:menu_item];
  967. [menu_item setAction:nil];
  968. [menu_item setKeyEquivalent:@""];
  969. }
  970. } else {
  971. int item_start = _get_system_menu_start(md->menu);
  972. int item_count = _get_system_menu_count(md->menu);
  973. p_idx += item_start;
  974. ERR_FAIL_COND(p_idx >= item_start + item_count);
  975. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  976. if (menu_item) {
  977. if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) {
  978. ERR_PRINT("Can't remove open menu!");
  979. return;
  980. }
  981. GodotMenuItem *obj = [menu_item representedObject];
  982. String keycode = KeyMappingMacOS::keycode_get_native_string(obj->accel & KeyModifierMask::CODE_MASK);
  983. [md->menu setSubmenu:nil forItem:menu_item];
  984. [menu_item setAction:@selector(globalMenuCallback:)];
  985. [menu_item setKeyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
  986. }
  987. }
  988. }
  989. void NativeMenuMacOS::set_item_accelerator(const RID &p_rid, int p_idx, Key p_keycode) {
  990. ERR_FAIL_COND(p_idx < 0);
  991. MenuData *md = menus.get_or_null(p_rid);
  992. ERR_FAIL_NULL(md);
  993. int item_start = _get_system_menu_start(md->menu);
  994. int item_count = _get_system_menu_count(md->menu);
  995. p_idx += item_start;
  996. ERR_FAIL_COND(p_idx >= item_start + item_count);
  997. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  998. if (menu_item) {
  999. GodotMenuItem *obj = [menu_item representedObject];
  1000. obj->accel = p_keycode;
  1001. NSMenu *sub_menu = [menu_item submenu];
  1002. if (!sub_menu) {
  1003. if (p_keycode == Key::NONE) {
  1004. [menu_item setKeyEquivalent:@""];
  1005. } else {
  1006. [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_keycode)];
  1007. String keycode = KeyMappingMacOS::keycode_get_native_string(p_keycode & KeyModifierMask::CODE_MASK);
  1008. [menu_item setKeyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
  1009. }
  1010. }
  1011. }
  1012. }
  1013. void NativeMenuMacOS::set_item_disabled(const RID &p_rid, int p_idx, bool p_disabled) {
  1014. ERR_FAIL_COND(p_idx < 0);
  1015. MenuData *md = menus.get_or_null(p_rid);
  1016. ERR_FAIL_NULL(md);
  1017. int item_start = _get_system_menu_start(md->menu);
  1018. int item_count = _get_system_menu_count(md->menu);
  1019. p_idx += item_start;
  1020. ERR_FAIL_COND(p_idx >= item_start + item_count);
  1021. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  1022. if (menu_item) {
  1023. [menu_item setEnabled:(!p_disabled)];
  1024. }
  1025. }
  1026. void NativeMenuMacOS::set_item_hidden(const RID &p_rid, int p_idx, bool p_hidden) {
  1027. ERR_FAIL_COND(p_idx < 0);
  1028. MenuData *md = menus.get_or_null(p_rid);
  1029. ERR_FAIL_NULL(md);
  1030. int item_start = _get_system_menu_start(md->menu);
  1031. int item_count = _get_system_menu_count(md->menu);
  1032. p_idx += item_start;
  1033. ERR_FAIL_COND(p_idx >= item_start + item_count);
  1034. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  1035. if (menu_item) {
  1036. [menu_item setHidden:p_hidden];
  1037. }
  1038. }
  1039. void NativeMenuMacOS::set_item_tooltip(const RID &p_rid, int p_idx, const String &p_tooltip) {
  1040. ERR_FAIL_COND(p_idx < 0);
  1041. MenuData *md = menus.get_or_null(p_rid);
  1042. ERR_FAIL_NULL(md);
  1043. int item_start = _get_system_menu_start(md->menu);
  1044. int item_count = _get_system_menu_count(md->menu);
  1045. p_idx += item_start;
  1046. ERR_FAIL_COND(p_idx >= item_start + item_count);
  1047. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  1048. if (menu_item) {
  1049. [menu_item setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]];
  1050. }
  1051. }
  1052. void NativeMenuMacOS::set_item_state(const RID &p_rid, int p_idx, int p_state) {
  1053. ERR_FAIL_COND(p_idx < 0);
  1054. MenuData *md = menus.get_or_null(p_rid);
  1055. ERR_FAIL_NULL(md);
  1056. int item_start = _get_system_menu_start(md->menu);
  1057. int item_count = _get_system_menu_count(md->menu);
  1058. p_idx += item_start;
  1059. ERR_FAIL_COND(p_idx >= item_start + item_count);
  1060. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  1061. if (menu_item) {
  1062. GodotMenuItem *obj = [menu_item representedObject];
  1063. ERR_FAIL_NULL(obj);
  1064. obj->state = p_state;
  1065. }
  1066. }
  1067. void NativeMenuMacOS::set_item_max_states(const RID &p_rid, int p_idx, int p_max_states) {
  1068. ERR_FAIL_COND(p_idx < 0);
  1069. MenuData *md = menus.get_or_null(p_rid);
  1070. ERR_FAIL_NULL(md);
  1071. int item_start = _get_system_menu_start(md->menu);
  1072. int item_count = _get_system_menu_count(md->menu);
  1073. p_idx += item_start;
  1074. ERR_FAIL_COND(p_idx >= item_start + item_count);
  1075. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  1076. if (menu_item) {
  1077. GodotMenuItem *obj = [menu_item representedObject];
  1078. ERR_FAIL_NULL(obj);
  1079. obj->max_states = p_max_states;
  1080. }
  1081. }
  1082. void NativeMenuMacOS::set_item_icon(const RID &p_rid, int p_idx, const Ref<Texture2D> &p_icon) {
  1083. ERR_FAIL_COND(p_idx < 0);
  1084. MenuData *md = menus.get_or_null(p_rid);
  1085. ERR_FAIL_NULL(md);
  1086. int item_start = _get_system_menu_start(md->menu);
  1087. int item_count = _get_system_menu_count(md->menu);
  1088. p_idx += item_start;
  1089. ERR_FAIL_COND(p_idx >= item_start + item_count);
  1090. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  1091. if (menu_item) {
  1092. GodotMenuItem *obj = [menu_item representedObject];
  1093. ERR_FAIL_NULL(obj);
  1094. DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
  1095. if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) {
  1096. obj->img = p_icon->get_image();
  1097. obj->img = obj->img->duplicate();
  1098. if (obj->img->is_compressed()) {
  1099. obj->img->decompress();
  1100. }
  1101. NSImage *image = ds->_convert_to_nsimg(obj->img);
  1102. [image setSize:NSMakeSize(16, 16)];
  1103. [menu_item setImage:image];
  1104. } else {
  1105. obj->img = Ref<Image>();
  1106. [menu_item setImage:nil];
  1107. }
  1108. }
  1109. }
  1110. void NativeMenuMacOS::set_item_indentation_level(const RID &p_rid, int p_idx, int p_level) {
  1111. ERR_FAIL_COND(p_idx < 0);
  1112. MenuData *md = menus.get_or_null(p_rid);
  1113. ERR_FAIL_NULL(md);
  1114. int item_start = _get_system_menu_start(md->menu);
  1115. int item_count = _get_system_menu_count(md->menu);
  1116. p_idx += item_start;
  1117. ERR_FAIL_COND(p_idx >= item_start + item_count);
  1118. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  1119. if (menu_item) {
  1120. [menu_item setIndentationLevel:p_level];
  1121. }
  1122. }
  1123. int NativeMenuMacOS::get_item_count(const RID &p_rid) const {
  1124. const MenuData *md = menus.get_or_null(p_rid);
  1125. ERR_FAIL_NULL_V(md, 0);
  1126. return _get_system_menu_count(md->menu);
  1127. }
  1128. bool NativeMenuMacOS::is_system_menu(const RID &p_rid) const {
  1129. const MenuData *md = menus.get_or_null(p_rid);
  1130. ERR_FAIL_NULL_V(md, false);
  1131. return md->is_system;
  1132. }
  1133. void NativeMenuMacOS::remove_item(const RID &p_rid, int p_idx) {
  1134. ERR_FAIL_COND(p_idx < 0);
  1135. MenuData *md = menus.get_or_null(p_rid);
  1136. ERR_FAIL_NULL(md);
  1137. int item_start = _get_system_menu_start(md->menu);
  1138. int item_count = _get_system_menu_count(md->menu);
  1139. p_idx += item_start;
  1140. ERR_FAIL_COND(p_idx >= item_start + item_count);
  1141. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  1142. if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) {
  1143. ERR_PRINT("Can't remove open menu!");
  1144. return;
  1145. }
  1146. [md->menu removeItemAtIndex:p_idx];
  1147. }
  1148. void NativeMenuMacOS::clear(const RID &p_rid) {
  1149. MenuData *md = menus.get_or_null(p_rid);
  1150. ERR_FAIL_NULL(md);
  1151. ERR_FAIL_COND_MSG(_is_menu_opened(md->menu), "Can't remove open menu!");
  1152. if (p_rid == application_menu || p_rid == window_menu || p_rid == help_menu) {
  1153. int start = _get_system_menu_start(md->menu);
  1154. int count = _get_system_menu_count(md->menu);
  1155. for (int i = start + count - 1; i >= start; i--) {
  1156. [md->menu removeItemAtIndex:i];
  1157. }
  1158. } else {
  1159. [md->menu removeAllItems];
  1160. }
  1161. if (p_rid == main_menu) {
  1162. // Restore Apple, Window and Help menu.
  1163. MenuData *md_app = menus.get_or_null(application_menu);
  1164. if (md_app) {
  1165. NSMenuItem *menu_item = [md->menu addItemWithTitle:@"" action:nil keyEquivalent:@""];
  1166. [md->menu setSubmenu:md_app->menu forItem:menu_item];
  1167. }
  1168. MenuData *md_win = menus.get_or_null(window_menu);
  1169. if (md_win) {
  1170. NSMenuItem *menu_item = [md->menu addItemWithTitle:@"Window" action:nil keyEquivalent:@""];
  1171. [md->menu setSubmenu:md_win->menu forItem:menu_item];
  1172. }
  1173. MenuData *md_hlp = menus.get_or_null(help_menu);
  1174. if (md_hlp) {
  1175. NSMenuItem *menu_item = [md->menu addItemWithTitle:@"Help" action:nil keyEquivalent:@""];
  1176. [md->menu setSubmenu:md_hlp->menu forItem:menu_item];
  1177. }
  1178. }
  1179. }
  1180. NativeMenuMacOS::NativeMenuMacOS() {}
  1181. NativeMenuMacOS::~NativeMenuMacOS() {
  1182. if (main_menu.is_valid()) {
  1183. MenuData *md = menus.get_or_null(main_menu);
  1184. if (md) {
  1185. clear(main_menu);
  1186. menus.free(main_menu);
  1187. menu_lookup.erase(md->menu);
  1188. md->menu = nullptr;
  1189. main_menu_ns = nullptr;
  1190. memdelete(md);
  1191. }
  1192. }
  1193. if (application_menu.is_valid()) {
  1194. MenuData *md = menus.get_or_null(application_menu);
  1195. if (md) {
  1196. clear(application_menu);
  1197. menus.free(application_menu);
  1198. menu_lookup.erase(md->menu);
  1199. md->menu = nullptr;
  1200. application_menu_ns = nullptr;
  1201. memdelete(md);
  1202. }
  1203. }
  1204. if (window_menu.is_valid()) {
  1205. MenuData *md = menus.get_or_null(window_menu);
  1206. if (md) {
  1207. clear(window_menu);
  1208. menus.free(window_menu);
  1209. menu_lookup.erase(md->menu);
  1210. md->menu = nullptr;
  1211. window_menu_ns = nullptr;
  1212. memdelete(md);
  1213. }
  1214. }
  1215. if (help_menu.is_valid()) {
  1216. MenuData *md = menus.get_or_null(help_menu);
  1217. if (md) {
  1218. clear(help_menu);
  1219. menus.free(help_menu);
  1220. menu_lookup.erase(md->menu);
  1221. md->menu = nullptr;
  1222. help_menu_ns = nullptr;
  1223. memdelete(md);
  1224. }
  1225. }
  1226. if (dock_menu.is_valid()) {
  1227. MenuData *md = menus.get_or_null(dock_menu);
  1228. if (md) {
  1229. clear(dock_menu);
  1230. menus.free(dock_menu);
  1231. menu_lookup.erase(md->menu);
  1232. md->menu = nullptr;
  1233. dock_menu_ns = nullptr;
  1234. memdelete(md);
  1235. }
  1236. }
  1237. }