native_menu_macos.mm 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383
  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. #include "native_menu_macos.h"
  31. #include "display_server_macos.h"
  32. #include "godot_menu_item.h"
  33. #include "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) inView:nil];
  269. }
  270. }
  271. void NativeMenuMacOS::set_interface_direction(const RID &p_rid, bool p_is_rtl) {
  272. MenuData *md = menus.get_or_null(p_rid);
  273. ERR_FAIL_NULL(md);
  274. md->menu.userInterfaceLayoutDirection = p_is_rtl ? NSUserInterfaceLayoutDirectionRightToLeft : NSUserInterfaceLayoutDirectionLeftToRight;
  275. }
  276. void NativeMenuMacOS::set_popup_open_callback(const RID &p_rid, const Callable &p_callback) {
  277. MenuData *md = menus.get_or_null(p_rid);
  278. ERR_FAIL_NULL(md);
  279. md->open_cb = p_callback;
  280. }
  281. Callable NativeMenuMacOS::get_popup_open_callback(const RID &p_rid) const {
  282. const MenuData *md = menus.get_or_null(p_rid);
  283. ERR_FAIL_NULL_V(md, Callable());
  284. return md->open_cb;
  285. }
  286. void NativeMenuMacOS::set_popup_close_callback(const RID &p_rid, const Callable &p_callback) {
  287. MenuData *md = menus.get_or_null(p_rid);
  288. ERR_FAIL_NULL(md);
  289. md->close_cb = p_callback;
  290. }
  291. Callable NativeMenuMacOS::get_popup_close_callback(const RID &p_rid) const {
  292. const MenuData *md = menus.get_or_null(p_rid);
  293. ERR_FAIL_NULL_V(md, Callable());
  294. return md->close_cb;
  295. }
  296. void NativeMenuMacOS::set_minimum_width(const RID &p_rid, float p_width) {
  297. MenuData *md = menus.get_or_null(p_rid);
  298. ERR_FAIL_NULL(md);
  299. md->menu.minimumWidth = p_width / DisplayServer::get_singleton()->screen_get_max_scale();
  300. }
  301. float NativeMenuMacOS::get_minimum_width(const RID &p_rid) const {
  302. const MenuData *md = menus.get_or_null(p_rid);
  303. ERR_FAIL_NULL_V(md, 0.0);
  304. return md->menu.minimumWidth * DisplayServer::get_singleton()->screen_get_max_scale();
  305. }
  306. bool NativeMenuMacOS::is_opened(const RID &p_rid) const {
  307. const MenuData *md = menus.get_or_null(p_rid);
  308. ERR_FAIL_NULL_V(md, false);
  309. return md->is_open;
  310. }
  311. 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) {
  312. MenuData *md = menus.get_or_null(p_rid);
  313. MenuData *md_sub = menus.get_or_null(p_submenu_rid);
  314. ERR_FAIL_NULL_V(md, -1);
  315. ERR_FAIL_NULL_V(md_sub, -1);
  316. ERR_FAIL_COND_V_MSG(md->menu == md_sub->menu, -1, "Can't set submenu to self!");
  317. 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!");
  318. NSMenuItem *menu_item;
  319. int item_start = _get_system_menu_start(md->menu);
  320. int item_count = _get_system_menu_count(md->menu);
  321. if (p_index < 0) {
  322. p_index = item_start + item_count;
  323. } else {
  324. p_index += item_start;
  325. p_index = CLAMP(p_index, item_start, item_start + item_count);
  326. }
  327. menu_item = [md->menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index];
  328. GodotMenuItem *obj = [[GodotMenuItem alloc] init];
  329. obj->meta = p_tag;
  330. [menu_item setRepresentedObject:obj];
  331. [md_sub->menu setTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()]];
  332. [md->menu setSubmenu:md_sub->menu forItem:menu_item];
  333. return p_index - item_start;
  334. }
  335. NSMenuItem *NativeMenuMacOS::_menu_add_item(NSMenu *p_menu, const String &p_label, Key p_accel, int p_index, int *r_out) {
  336. if (p_menu) {
  337. String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
  338. NSMenuItem *menu_item;
  339. int item_start = _get_system_menu_start(p_menu);
  340. int item_count = _get_system_menu_count(p_menu);
  341. if (p_index < 0) {
  342. p_index = item_start + item_count;
  343. } else {
  344. p_index += item_start;
  345. p_index = CLAMP(p_index, item_start, item_start + item_count);
  346. }
  347. 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];
  348. *r_out = p_index - item_start;
  349. return menu_item;
  350. }
  351. return nullptr;
  352. }
  353. 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) {
  354. MenuData *md = menus.get_or_null(p_rid);
  355. ERR_FAIL_NULL_V(md, -1);
  356. int out = -1;
  357. NSMenuItem *menu_item = _menu_add_item(md->menu, p_label, p_accel, p_index, &out);
  358. if (menu_item) {
  359. GodotMenuItem *obj = [[GodotMenuItem alloc] init];
  360. obj->callback = p_callback;
  361. obj->key_callback = p_key_callback;
  362. obj->meta = p_tag;
  363. [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
  364. [menu_item setRepresentedObject:obj];
  365. }
  366. return out;
  367. }
  368. 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) {
  369. MenuData *md = menus.get_or_null(p_rid);
  370. ERR_FAIL_NULL_V(md, -1);
  371. int out = -1;
  372. NSMenuItem *menu_item = _menu_add_item(md->menu, p_label, p_accel, p_index, &out);
  373. if (menu_item) {
  374. GodotMenuItem *obj = [[GodotMenuItem alloc] init];
  375. obj->callback = p_callback;
  376. obj->key_callback = p_key_callback;
  377. obj->meta = p_tag;
  378. obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
  379. [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
  380. [menu_item setRepresentedObject:obj];
  381. }
  382. return out;
  383. }
  384. 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) {
  385. MenuData *md = menus.get_or_null(p_rid);
  386. ERR_FAIL_NULL_V(md, -1);
  387. int out = -1;
  388. NSMenuItem *menu_item = _menu_add_item(md->menu, p_label, p_accel, p_index, &out);
  389. if (menu_item) {
  390. GodotMenuItem *obj = [[GodotMenuItem alloc] init];
  391. obj->callback = p_callback;
  392. obj->key_callback = p_key_callback;
  393. obj->meta = p_tag;
  394. DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
  395. if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) {
  396. obj->img = p_icon->get_image();
  397. obj->img = obj->img->duplicate();
  398. if (obj->img->is_compressed()) {
  399. obj->img->decompress();
  400. }
  401. NSImage *image = ds->_convert_to_nsimg(obj->img);
  402. [image setSize:NSMakeSize(16, 16)];
  403. [menu_item setImage:image];
  404. }
  405. [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
  406. [menu_item setRepresentedObject:obj];
  407. }
  408. return out;
  409. }
  410. 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) {
  411. MenuData *md = menus.get_or_null(p_rid);
  412. ERR_FAIL_NULL_V(md, -1);
  413. int out = -1;
  414. NSMenuItem *menu_item = _menu_add_item(md->menu, p_label, p_accel, p_index, &out);
  415. if (menu_item) {
  416. GodotMenuItem *obj = [[GodotMenuItem alloc] init];
  417. obj->callback = p_callback;
  418. obj->key_callback = p_key_callback;
  419. obj->meta = p_tag;
  420. obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
  421. DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
  422. if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) {
  423. obj->img = p_icon->get_image();
  424. obj->img = obj->img->duplicate();
  425. if (obj->img->is_compressed()) {
  426. obj->img->decompress();
  427. }
  428. NSImage *image = ds->_convert_to_nsimg(obj->img);
  429. [image setSize:NSMakeSize(16, 16)];
  430. [menu_item setImage:image];
  431. }
  432. [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
  433. [menu_item setRepresentedObject:obj];
  434. }
  435. return out;
  436. }
  437. 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) {
  438. MenuData *md = menus.get_or_null(p_rid);
  439. ERR_FAIL_NULL_V(md, -1);
  440. int out = -1;
  441. NSMenuItem *menu_item = _menu_add_item(md->menu, p_label, p_accel, p_index, &out);
  442. if (menu_item) {
  443. GodotMenuItem *obj = [[GodotMenuItem alloc] init];
  444. obj->callback = p_callback;
  445. obj->key_callback = p_key_callback;
  446. obj->meta = p_tag;
  447. obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON;
  448. [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
  449. [menu_item setRepresentedObject:obj];
  450. }
  451. return out;
  452. }
  453. 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) {
  454. MenuData *md = menus.get_or_null(p_rid);
  455. ERR_FAIL_NULL_V(md, -1);
  456. int out = -1;
  457. NSMenuItem *menu_item = _menu_add_item(md->menu, p_label, p_accel, p_index, &out);
  458. if (menu_item) {
  459. GodotMenuItem *obj = [[GodotMenuItem alloc] init];
  460. obj->callback = p_callback;
  461. obj->key_callback = p_key_callback;
  462. obj->meta = p_tag;
  463. obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON;
  464. DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
  465. if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) {
  466. obj->img = p_icon->get_image();
  467. obj->img = obj->img->duplicate();
  468. if (obj->img->is_compressed()) {
  469. obj->img->decompress();
  470. }
  471. NSImage *image = ds->_convert_to_nsimg(obj->img);
  472. [image setSize:NSMakeSize(16, 16)];
  473. [menu_item setImage:image];
  474. }
  475. [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
  476. [menu_item setRepresentedObject:obj];
  477. }
  478. return out;
  479. }
  480. 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) {
  481. MenuData *md = menus.get_or_null(p_rid);
  482. ERR_FAIL_NULL_V(md, -1);
  483. int out = -1;
  484. NSMenuItem *menu_item = _menu_add_item(md->menu, p_label, p_accel, p_index, &out);
  485. if (menu_item) {
  486. GodotMenuItem *obj = [[GodotMenuItem alloc] init];
  487. obj->callback = p_callback;
  488. obj->key_callback = p_key_callback;
  489. obj->meta = p_tag;
  490. obj->max_states = p_max_states;
  491. obj->state = p_default_state;
  492. [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
  493. [menu_item setRepresentedObject:obj];
  494. }
  495. return out;
  496. }
  497. int NativeMenuMacOS::add_separator(const RID &p_rid, int p_index) {
  498. MenuData *md = menus.get_or_null(p_rid);
  499. ERR_FAIL_NULL_V(md, -1);
  500. if (md->menu == [NSApp mainMenu]) { // Do not add separators into main menu.
  501. return -1;
  502. }
  503. int item_start = _get_system_menu_start(md->menu);
  504. int item_count = _get_system_menu_count(md->menu);
  505. if (p_index < 0) {
  506. p_index = item_start + item_count;
  507. } else {
  508. p_index += item_start;
  509. p_index = CLAMP(p_index, item_start, item_start + item_count);
  510. }
  511. [md->menu insertItem:[NSMenuItem separatorItem] atIndex:p_index];
  512. return p_index - item_start;
  513. }
  514. int NativeMenuMacOS::find_item_index_with_text(const RID &p_rid, const String &p_text) const {
  515. const MenuData *md = menus.get_or_null(p_rid);
  516. ERR_FAIL_NULL_V(md, -1);
  517. int item_start = _get_system_menu_start(md->menu);
  518. int index = [md->menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
  519. if (index >= 0) {
  520. return index - item_start;
  521. }
  522. return -1;
  523. }
  524. int NativeMenuMacOS::find_item_index_with_tag(const RID &p_rid, const Variant &p_tag) const {
  525. const MenuData *md = menus.get_or_null(p_rid);
  526. ERR_FAIL_NULL_V(md, -1);
  527. int item_start = _get_system_menu_start(md->menu);
  528. int item_count = _get_system_menu_count(md->menu);
  529. for (NSInteger i = item_start; i < item_start + item_count; i++) {
  530. const NSMenuItem *menu_item = [md->menu itemAtIndex:i];
  531. if (menu_item) {
  532. const GodotMenuItem *obj = [menu_item representedObject];
  533. if (obj && obj->meta == p_tag) {
  534. return i - item_start;
  535. }
  536. }
  537. }
  538. return -1;
  539. }
  540. bool NativeMenuMacOS::is_item_checked(const RID &p_rid, int p_idx) const {
  541. ERR_FAIL_COND_V(p_idx < 0, false);
  542. const MenuData *md = menus.get_or_null(p_rid);
  543. ERR_FAIL_NULL_V(md, false);
  544. int item_start = _get_system_menu_start(md->menu);
  545. int item_count = _get_system_menu_count(md->menu);
  546. p_idx += item_start;
  547. ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
  548. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  549. if (menu_item) {
  550. const GodotMenuItem *obj = [menu_item representedObject];
  551. if (obj) {
  552. return obj->checked;
  553. }
  554. }
  555. return false;
  556. }
  557. bool NativeMenuMacOS::is_item_checkable(const RID &p_rid, int p_idx) const {
  558. ERR_FAIL_COND_V(p_idx < 0, false);
  559. const MenuData *md = menus.get_or_null(p_rid);
  560. ERR_FAIL_NULL_V(md, false);
  561. int item_start = _get_system_menu_start(md->menu);
  562. int item_count = _get_system_menu_count(md->menu);
  563. p_idx += item_start;
  564. ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
  565. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  566. if (menu_item) {
  567. GodotMenuItem *obj = [menu_item representedObject];
  568. if (obj) {
  569. return obj->checkable_type == CHECKABLE_TYPE_CHECK_BOX;
  570. }
  571. }
  572. return false;
  573. }
  574. bool NativeMenuMacOS::is_item_radio_checkable(const RID &p_rid, int p_idx) const {
  575. ERR_FAIL_COND_V(p_idx < 0, false);
  576. const MenuData *md = menus.get_or_null(p_rid);
  577. ERR_FAIL_NULL_V(md, false);
  578. int item_start = _get_system_menu_start(md->menu);
  579. int item_count = _get_system_menu_count(md->menu);
  580. p_idx += item_start;
  581. ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
  582. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  583. if (menu_item) {
  584. GodotMenuItem *obj = [menu_item representedObject];
  585. if (obj) {
  586. return obj->checkable_type == CHECKABLE_TYPE_RADIO_BUTTON;
  587. }
  588. }
  589. return false;
  590. }
  591. Callable NativeMenuMacOS::get_item_callback(const RID &p_rid, int p_idx) const {
  592. ERR_FAIL_COND_V(p_idx < 0, Callable());
  593. const MenuData *md = menus.get_or_null(p_rid);
  594. ERR_FAIL_NULL_V(md, Callable());
  595. int item_start = _get_system_menu_start(md->menu);
  596. int item_count = _get_system_menu_count(md->menu);
  597. p_idx += item_start;
  598. ERR_FAIL_COND_V(p_idx >= item_start + item_count, Callable());
  599. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  600. if (menu_item) {
  601. GodotMenuItem *obj = [menu_item representedObject];
  602. if (obj) {
  603. return obj->callback;
  604. }
  605. }
  606. return Callable();
  607. }
  608. Callable NativeMenuMacOS::get_item_key_callback(const RID &p_rid, int p_idx) const {
  609. ERR_FAIL_COND_V(p_idx < 0, Callable());
  610. const MenuData *md = menus.get_or_null(p_rid);
  611. ERR_FAIL_NULL_V(md, Callable());
  612. int item_start = _get_system_menu_start(md->menu);
  613. int item_count = _get_system_menu_count(md->menu);
  614. p_idx += item_start;
  615. ERR_FAIL_COND_V(p_idx >= item_start + item_count, Callable());
  616. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  617. if (menu_item) {
  618. GodotMenuItem *obj = [menu_item representedObject];
  619. if (obj) {
  620. return obj->key_callback;
  621. }
  622. }
  623. return Callable();
  624. }
  625. Variant NativeMenuMacOS::get_item_tag(const RID &p_rid, int p_idx) const {
  626. ERR_FAIL_COND_V(p_idx < 0, Variant());
  627. const MenuData *md = menus.get_or_null(p_rid);
  628. ERR_FAIL_NULL_V(md, Variant());
  629. int item_start = _get_system_menu_start(md->menu);
  630. int item_count = _get_system_menu_count(md->menu);
  631. p_idx += item_start;
  632. ERR_FAIL_COND_V(p_idx >= item_start + item_count, Variant());
  633. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  634. if (menu_item) {
  635. GodotMenuItem *obj = [menu_item representedObject];
  636. if (obj) {
  637. return obj->meta;
  638. }
  639. }
  640. return Variant();
  641. }
  642. String NativeMenuMacOS::get_item_text(const RID &p_rid, int p_idx) const {
  643. ERR_FAIL_COND_V(p_idx < 0, String());
  644. const MenuData *md = menus.get_or_null(p_rid);
  645. ERR_FAIL_NULL_V(md, String());
  646. int item_start = _get_system_menu_start(md->menu);
  647. int item_count = _get_system_menu_count(md->menu);
  648. p_idx += item_start;
  649. ERR_FAIL_COND_V(p_idx >= item_start + item_count, String());
  650. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  651. if (menu_item) {
  652. return String::utf8([[menu_item title] UTF8String]);
  653. }
  654. return String();
  655. }
  656. RID NativeMenuMacOS::get_item_submenu(const RID &p_rid, int p_idx) const {
  657. ERR_FAIL_COND_V(p_idx < 0, RID());
  658. const MenuData *md = menus.get_or_null(p_rid);
  659. ERR_FAIL_NULL_V(md, RID());
  660. int item_start = _get_system_menu_start(md->menu);
  661. int item_count = _get_system_menu_count(md->menu);
  662. p_idx += item_start;
  663. ERR_FAIL_COND_V(p_idx >= item_start + item_count, RID());
  664. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  665. if (menu_item) {
  666. NSMenu *sub_menu = [menu_item submenu];
  667. if (sub_menu && menu_lookup.has(sub_menu)) {
  668. return menu_lookup[sub_menu];
  669. }
  670. }
  671. return RID();
  672. }
  673. Key NativeMenuMacOS::get_item_accelerator(const RID &p_rid, int p_idx) const {
  674. ERR_FAIL_COND_V(p_idx < 0, Key::NONE);
  675. const MenuData *md = menus.get_or_null(p_rid);
  676. ERR_FAIL_NULL_V(md, Key::NONE);
  677. int item_start = _get_system_menu_start(md->menu);
  678. int item_count = _get_system_menu_count(md->menu);
  679. p_idx += item_start;
  680. ERR_FAIL_COND_V(p_idx >= item_start + item_count, Key::NONE);
  681. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  682. if (menu_item) {
  683. String ret = String::utf8([[menu_item keyEquivalent] UTF8String]);
  684. Key keycode = find_keycode(ret);
  685. NSUInteger mask = [menu_item keyEquivalentModifierMask];
  686. if (mask & NSEventModifierFlagControl) {
  687. keycode |= KeyModifierMask::CTRL;
  688. }
  689. if (mask & NSEventModifierFlagOption) {
  690. keycode |= KeyModifierMask::ALT;
  691. }
  692. if (mask & NSEventModifierFlagShift) {
  693. keycode |= KeyModifierMask::SHIFT;
  694. }
  695. if (mask & NSEventModifierFlagCommand) {
  696. keycode |= KeyModifierMask::META;
  697. }
  698. if (mask & NSEventModifierFlagNumericPad) {
  699. keycode |= KeyModifierMask::KPAD;
  700. }
  701. return keycode;
  702. }
  703. return Key::NONE;
  704. }
  705. bool NativeMenuMacOS::is_item_disabled(const RID &p_rid, int p_idx) const {
  706. ERR_FAIL_COND_V(p_idx < 0, false);
  707. const MenuData *md = menus.get_or_null(p_rid);
  708. ERR_FAIL_NULL_V(md, false);
  709. int item_start = _get_system_menu_start(md->menu);
  710. int item_count = _get_system_menu_count(md->menu);
  711. p_idx += item_start;
  712. ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
  713. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  714. if (menu_item) {
  715. return ![menu_item isEnabled];
  716. }
  717. return false;
  718. }
  719. bool NativeMenuMacOS::is_item_hidden(const RID &p_rid, int p_idx) const {
  720. ERR_FAIL_COND_V(p_idx < 0, false);
  721. const MenuData *md = menus.get_or_null(p_rid);
  722. ERR_FAIL_NULL_V(md, false);
  723. int item_start = _get_system_menu_start(md->menu);
  724. int item_count = _get_system_menu_count(md->menu);
  725. p_idx += item_start;
  726. ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
  727. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  728. if (menu_item) {
  729. return [menu_item isHidden];
  730. }
  731. return false;
  732. }
  733. String NativeMenuMacOS::get_item_tooltip(const RID &p_rid, int p_idx) const {
  734. ERR_FAIL_COND_V(p_idx < 0, String());
  735. const MenuData *md = menus.get_or_null(p_rid);
  736. ERR_FAIL_NULL_V(md, String());
  737. int item_start = _get_system_menu_start(md->menu);
  738. int item_count = _get_system_menu_count(md->menu);
  739. p_idx += item_start;
  740. ERR_FAIL_COND_V(p_idx >= item_start + item_count, String());
  741. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  742. if (menu_item) {
  743. return String::utf8([[menu_item toolTip] UTF8String]);
  744. }
  745. return String();
  746. }
  747. int NativeMenuMacOS::get_item_state(const RID &p_rid, int p_idx) const {
  748. ERR_FAIL_COND_V(p_idx < 0, 0);
  749. const MenuData *md = menus.get_or_null(p_rid);
  750. ERR_FAIL_NULL_V(md, 0);
  751. int item_start = _get_system_menu_start(md->menu);
  752. int item_count = _get_system_menu_count(md->menu);
  753. p_idx += item_start;
  754. ERR_FAIL_COND_V(p_idx >= item_start + item_count, 0);
  755. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  756. if (menu_item) {
  757. GodotMenuItem *obj = [menu_item representedObject];
  758. if (obj) {
  759. return obj->state;
  760. }
  761. }
  762. return 0;
  763. }
  764. int NativeMenuMacOS::get_item_max_states(const RID &p_rid, int p_idx) const {
  765. ERR_FAIL_COND_V(p_idx < 0, 0);
  766. const MenuData *md = menus.get_or_null(p_rid);
  767. ERR_FAIL_NULL_V(md, 0);
  768. int item_start = _get_system_menu_start(md->menu);
  769. int item_count = _get_system_menu_count(md->menu);
  770. p_idx += item_start;
  771. ERR_FAIL_COND_V(p_idx >= item_start + item_count, 0);
  772. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  773. if (menu_item) {
  774. GodotMenuItem *obj = [menu_item representedObject];
  775. if (obj) {
  776. return obj->max_states;
  777. }
  778. }
  779. return 0;
  780. }
  781. Ref<Texture2D> NativeMenuMacOS::get_item_icon(const RID &p_rid, int p_idx) const {
  782. ERR_FAIL_COND_V(p_idx < 0, Ref<Texture2D>());
  783. const MenuData *md = menus.get_or_null(p_rid);
  784. ERR_FAIL_NULL_V(md, Ref<Texture2D>());
  785. int item_start = _get_system_menu_start(md->menu);
  786. int item_count = _get_system_menu_count(md->menu);
  787. p_idx += item_start;
  788. ERR_FAIL_COND_V(p_idx >= item_start + item_count, Ref<Texture2D>());
  789. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  790. if (menu_item) {
  791. GodotMenuItem *obj = [menu_item representedObject];
  792. if (obj) {
  793. if (obj->img.is_valid()) {
  794. return ImageTexture::create_from_image(obj->img);
  795. }
  796. }
  797. }
  798. return Ref<Texture2D>();
  799. }
  800. int NativeMenuMacOS::get_item_indentation_level(const RID &p_rid, int p_idx) const {
  801. ERR_FAIL_COND_V(p_idx < 0, 0);
  802. const MenuData *md = menus.get_or_null(p_rid);
  803. ERR_FAIL_NULL_V(md, 0);
  804. int item_start = _get_system_menu_start(md->menu);
  805. int item_count = _get_system_menu_count(md->menu);
  806. p_idx += item_start;
  807. ERR_FAIL_COND_V(p_idx >= item_start + item_count, 0);
  808. const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  809. if (menu_item) {
  810. return [menu_item indentationLevel];
  811. }
  812. return 0;
  813. }
  814. void NativeMenuMacOS::set_item_checked(const RID &p_rid, int p_idx, bool p_checked) {
  815. ERR_FAIL_COND(p_idx < 0);
  816. MenuData *md = menus.get_or_null(p_rid);
  817. ERR_FAIL_NULL(md);
  818. int item_start = _get_system_menu_start(md->menu);
  819. int item_count = _get_system_menu_count(md->menu);
  820. p_idx += item_start;
  821. ERR_FAIL_COND(p_idx >= item_start + item_count);
  822. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  823. if (menu_item) {
  824. GodotMenuItem *obj = [menu_item representedObject];
  825. if (obj) {
  826. obj->checked = p_checked;
  827. if (p_checked) {
  828. [menu_item setState:NSControlStateValueOn];
  829. } else {
  830. [menu_item setState:NSControlStateValueOff];
  831. }
  832. }
  833. }
  834. }
  835. void NativeMenuMacOS::set_item_checkable(const RID &p_rid, int p_idx, bool p_checkable) {
  836. ERR_FAIL_COND(p_idx < 0);
  837. MenuData *md = menus.get_or_null(p_rid);
  838. ERR_FAIL_NULL(md);
  839. int item_start = _get_system_menu_start(md->menu);
  840. int item_count = _get_system_menu_count(md->menu);
  841. p_idx += item_start;
  842. ERR_FAIL_COND(p_idx >= item_start + item_count);
  843. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  844. if (menu_item) {
  845. GodotMenuItem *obj = [menu_item representedObject];
  846. ERR_FAIL_NULL(obj);
  847. obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_CHECK_BOX : CHECKABLE_TYPE_NONE;
  848. }
  849. }
  850. void NativeMenuMacOS::set_item_radio_checkable(const RID &p_rid, int p_idx, bool p_checkable) {
  851. ERR_FAIL_COND(p_idx < 0);
  852. MenuData *md = menus.get_or_null(p_rid);
  853. ERR_FAIL_NULL(md);
  854. int item_start = _get_system_menu_start(md->menu);
  855. int item_count = _get_system_menu_count(md->menu);
  856. p_idx += item_start;
  857. ERR_FAIL_COND(p_idx >= item_start + item_count);
  858. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  859. if (menu_item) {
  860. GodotMenuItem *obj = [menu_item representedObject];
  861. ERR_FAIL_NULL(obj);
  862. obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_RADIO_BUTTON : CHECKABLE_TYPE_NONE;
  863. }
  864. }
  865. void NativeMenuMacOS::set_item_callback(const RID &p_rid, int p_idx, const Callable &p_callback) {
  866. ERR_FAIL_COND(p_idx < 0);
  867. MenuData *md = menus.get_or_null(p_rid);
  868. ERR_FAIL_NULL(md);
  869. int item_start = _get_system_menu_start(md->menu);
  870. int item_count = _get_system_menu_count(md->menu);
  871. p_idx += item_start;
  872. ERR_FAIL_COND(p_idx >= item_start + item_count);
  873. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  874. if (menu_item) {
  875. GodotMenuItem *obj = [menu_item representedObject];
  876. ERR_FAIL_NULL(obj);
  877. obj->callback = p_callback;
  878. }
  879. }
  880. void NativeMenuMacOS::set_item_key_callback(const RID &p_rid, int p_idx, const Callable &p_key_callback) {
  881. ERR_FAIL_COND(p_idx < 0);
  882. MenuData *md = menus.get_or_null(p_rid);
  883. ERR_FAIL_NULL(md);
  884. int item_start = _get_system_menu_start(md->menu);
  885. int item_count = _get_system_menu_count(md->menu);
  886. p_idx += item_start;
  887. ERR_FAIL_COND(p_idx >= item_start + item_count);
  888. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  889. if (menu_item) {
  890. GodotMenuItem *obj = [menu_item representedObject];
  891. ERR_FAIL_NULL(obj);
  892. obj->key_callback = p_key_callback;
  893. }
  894. }
  895. void NativeMenuMacOS::set_item_hover_callbacks(const RID &p_rid, int p_idx, const Callable &p_callback) {
  896. ERR_FAIL_COND(p_idx < 0);
  897. MenuData *md = menus.get_or_null(p_rid);
  898. ERR_FAIL_NULL(md);
  899. int item_start = _get_system_menu_start(md->menu);
  900. int item_count = _get_system_menu_count(md->menu);
  901. p_idx += item_start;
  902. ERR_FAIL_COND(p_idx >= item_start + item_count);
  903. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  904. if (menu_item) {
  905. GodotMenuItem *obj = [menu_item representedObject];
  906. ERR_FAIL_NULL(obj);
  907. obj->hover_callback = p_callback;
  908. }
  909. }
  910. void NativeMenuMacOS::set_item_tag(const RID &p_rid, int p_idx, const Variant &p_tag) {
  911. ERR_FAIL_COND(p_idx < 0);
  912. MenuData *md = menus.get_or_null(p_rid);
  913. ERR_FAIL_NULL(md);
  914. int item_start = _get_system_menu_start(md->menu);
  915. int item_count = _get_system_menu_count(md->menu);
  916. p_idx += item_start;
  917. ERR_FAIL_COND(p_idx >= item_start + item_count);
  918. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  919. if (menu_item) {
  920. GodotMenuItem *obj = [menu_item representedObject];
  921. ERR_FAIL_NULL(obj);
  922. obj->meta = p_tag;
  923. }
  924. }
  925. void NativeMenuMacOS::set_item_text(const RID &p_rid, int p_idx, const String &p_text) {
  926. ERR_FAIL_COND(p_idx < 0);
  927. MenuData *md = menus.get_or_null(p_rid);
  928. ERR_FAIL_NULL(md);
  929. int item_start = _get_system_menu_start(md->menu);
  930. int item_count = _get_system_menu_count(md->menu);
  931. p_idx += item_start;
  932. ERR_FAIL_COND(p_idx >= item_start + item_count);
  933. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  934. if (menu_item) {
  935. [menu_item setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
  936. NSMenu *sub_menu = [menu_item submenu];
  937. if (sub_menu) {
  938. [sub_menu setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
  939. }
  940. }
  941. }
  942. void NativeMenuMacOS::set_item_submenu(const RID &p_rid, int p_idx, const RID &p_submenu_rid) {
  943. ERR_FAIL_COND(p_idx < 0);
  944. MenuData *md = menus.get_or_null(p_rid);
  945. ERR_FAIL_NULL(md);
  946. if (p_submenu_rid.is_valid()) {
  947. MenuData *md_sub = menus.get_or_null(p_submenu_rid);
  948. ERR_FAIL_NULL(md_sub);
  949. ERR_FAIL_COND_MSG(md->menu == md_sub->menu, "Can't set submenu to self!");
  950. ERR_FAIL_COND_MSG([md_sub->menu supermenu], "Can't set submenu to menu that is already a submenu of some other menu!");
  951. int item_start = _get_system_menu_start(md->menu);
  952. int item_count = _get_system_menu_count(md->menu);
  953. p_idx += item_start;
  954. ERR_FAIL_COND(p_idx >= item_start + item_count);
  955. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  956. if (menu_item) {
  957. [md->menu setSubmenu:md_sub->menu forItem:menu_item];
  958. }
  959. } else {
  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. if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) {
  967. ERR_PRINT("Can't remove open menu!");
  968. return;
  969. }
  970. [md->menu setSubmenu:nil forItem:menu_item];
  971. }
  972. }
  973. }
  974. void NativeMenuMacOS::set_item_accelerator(const RID &p_rid, int p_idx, Key p_keycode) {
  975. ERR_FAIL_COND(p_idx < 0);
  976. MenuData *md = menus.get_or_null(p_rid);
  977. ERR_FAIL_NULL(md);
  978. int item_start = _get_system_menu_start(md->menu);
  979. int item_count = _get_system_menu_count(md->menu);
  980. p_idx += item_start;
  981. ERR_FAIL_COND(p_idx >= item_start + item_count);
  982. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  983. if (menu_item) {
  984. if (p_keycode == Key::NONE) {
  985. [menu_item setKeyEquivalent:@""];
  986. } else {
  987. [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_keycode)];
  988. String keycode = KeyMappingMacOS::keycode_get_native_string(p_keycode & KeyModifierMask::CODE_MASK);
  989. [menu_item setKeyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
  990. }
  991. }
  992. }
  993. void NativeMenuMacOS::set_item_disabled(const RID &p_rid, int p_idx, bool p_disabled) {
  994. ERR_FAIL_COND(p_idx < 0);
  995. MenuData *md = menus.get_or_null(p_rid);
  996. ERR_FAIL_NULL(md);
  997. int item_start = _get_system_menu_start(md->menu);
  998. int item_count = _get_system_menu_count(md->menu);
  999. p_idx += item_start;
  1000. ERR_FAIL_COND(p_idx >= item_start + item_count);
  1001. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  1002. if (menu_item) {
  1003. [menu_item setEnabled:(!p_disabled)];
  1004. }
  1005. }
  1006. void NativeMenuMacOS::set_item_hidden(const RID &p_rid, int p_idx, bool p_hidden) {
  1007. ERR_FAIL_COND(p_idx < 0);
  1008. MenuData *md = menus.get_or_null(p_rid);
  1009. ERR_FAIL_NULL(md);
  1010. int item_start = _get_system_menu_start(md->menu);
  1011. int item_count = _get_system_menu_count(md->menu);
  1012. p_idx += item_start;
  1013. ERR_FAIL_COND(p_idx >= item_start + item_count);
  1014. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  1015. if (menu_item) {
  1016. [menu_item setHidden:p_hidden];
  1017. }
  1018. }
  1019. void NativeMenuMacOS::set_item_tooltip(const RID &p_rid, int p_idx, const String &p_tooltip) {
  1020. ERR_FAIL_COND(p_idx < 0);
  1021. MenuData *md = menus.get_or_null(p_rid);
  1022. ERR_FAIL_NULL(md);
  1023. int item_start = _get_system_menu_start(md->menu);
  1024. int item_count = _get_system_menu_count(md->menu);
  1025. p_idx += item_start;
  1026. ERR_FAIL_COND(p_idx >= item_start + item_count);
  1027. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  1028. if (menu_item) {
  1029. [menu_item setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]];
  1030. }
  1031. }
  1032. void NativeMenuMacOS::set_item_state(const RID &p_rid, int p_idx, int p_state) {
  1033. ERR_FAIL_COND(p_idx < 0);
  1034. MenuData *md = menus.get_or_null(p_rid);
  1035. ERR_FAIL_NULL(md);
  1036. int item_start = _get_system_menu_start(md->menu);
  1037. int item_count = _get_system_menu_count(md->menu);
  1038. p_idx += item_start;
  1039. ERR_FAIL_COND(p_idx >= item_start + item_count);
  1040. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  1041. if (menu_item) {
  1042. GodotMenuItem *obj = [menu_item representedObject];
  1043. ERR_FAIL_NULL(obj);
  1044. obj->state = p_state;
  1045. }
  1046. }
  1047. void NativeMenuMacOS::set_item_max_states(const RID &p_rid, int p_idx, int p_max_states) {
  1048. ERR_FAIL_COND(p_idx < 0);
  1049. MenuData *md = menus.get_or_null(p_rid);
  1050. ERR_FAIL_NULL(md);
  1051. int item_start = _get_system_menu_start(md->menu);
  1052. int item_count = _get_system_menu_count(md->menu);
  1053. p_idx += item_start;
  1054. ERR_FAIL_COND(p_idx >= item_start + item_count);
  1055. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  1056. if (menu_item) {
  1057. GodotMenuItem *obj = [menu_item representedObject];
  1058. ERR_FAIL_NULL(obj);
  1059. obj->max_states = p_max_states;
  1060. }
  1061. }
  1062. void NativeMenuMacOS::set_item_icon(const RID &p_rid, int p_idx, const Ref<Texture2D> &p_icon) {
  1063. ERR_FAIL_COND(p_idx < 0);
  1064. MenuData *md = menus.get_or_null(p_rid);
  1065. ERR_FAIL_NULL(md);
  1066. int item_start = _get_system_menu_start(md->menu);
  1067. int item_count = _get_system_menu_count(md->menu);
  1068. p_idx += item_start;
  1069. ERR_FAIL_COND(p_idx >= item_start + item_count);
  1070. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  1071. if (menu_item) {
  1072. GodotMenuItem *obj = [menu_item representedObject];
  1073. ERR_FAIL_NULL(obj);
  1074. DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
  1075. if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) {
  1076. obj->img = p_icon->get_image();
  1077. obj->img = obj->img->duplicate();
  1078. if (obj->img->is_compressed()) {
  1079. obj->img->decompress();
  1080. }
  1081. NSImage *image = ds->_convert_to_nsimg(obj->img);
  1082. [image setSize:NSMakeSize(16, 16)];
  1083. [menu_item setImage:image];
  1084. } else {
  1085. obj->img = Ref<Image>();
  1086. [menu_item setImage:nil];
  1087. }
  1088. }
  1089. }
  1090. void NativeMenuMacOS::set_item_indentation_level(const RID &p_rid, int p_idx, int p_level) {
  1091. ERR_FAIL_COND(p_idx < 0);
  1092. MenuData *md = menus.get_or_null(p_rid);
  1093. ERR_FAIL_NULL(md);
  1094. int item_start = _get_system_menu_start(md->menu);
  1095. int item_count = _get_system_menu_count(md->menu);
  1096. p_idx += item_start;
  1097. ERR_FAIL_COND(p_idx >= item_start + item_count);
  1098. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  1099. if (menu_item) {
  1100. [menu_item setIndentationLevel:p_level];
  1101. }
  1102. }
  1103. int NativeMenuMacOS::get_item_count(const RID &p_rid) const {
  1104. const MenuData *md = menus.get_or_null(p_rid);
  1105. ERR_FAIL_NULL_V(md, 0);
  1106. return _get_system_menu_count(md->menu);
  1107. }
  1108. bool NativeMenuMacOS::is_system_menu(const RID &p_rid) const {
  1109. const MenuData *md = menus.get_or_null(p_rid);
  1110. ERR_FAIL_NULL_V(md, false);
  1111. return md->is_system;
  1112. }
  1113. void NativeMenuMacOS::remove_item(const RID &p_rid, int p_idx) {
  1114. ERR_FAIL_COND(p_idx < 0);
  1115. MenuData *md = menus.get_or_null(p_rid);
  1116. ERR_FAIL_NULL(md);
  1117. int item_start = _get_system_menu_start(md->menu);
  1118. int item_count = _get_system_menu_count(md->menu);
  1119. p_idx += item_start;
  1120. ERR_FAIL_COND(p_idx >= item_start + item_count);
  1121. NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
  1122. if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) {
  1123. ERR_PRINT("Can't remove open menu!");
  1124. return;
  1125. }
  1126. [md->menu removeItemAtIndex:p_idx];
  1127. }
  1128. void NativeMenuMacOS::clear(const RID &p_rid) {
  1129. MenuData *md = menus.get_or_null(p_rid);
  1130. ERR_FAIL_NULL(md);
  1131. ERR_FAIL_COND_MSG(_is_menu_opened(md->menu), "Can't remove open menu!");
  1132. if (p_rid == application_menu || p_rid == window_menu || p_rid == help_menu) {
  1133. int start = _get_system_menu_start(md->menu);
  1134. int count = _get_system_menu_count(md->menu);
  1135. for (int i = start + count - 1; i >= start; i--) {
  1136. [md->menu removeItemAtIndex:i];
  1137. }
  1138. } else {
  1139. [md->menu removeAllItems];
  1140. }
  1141. if (p_rid == main_menu) {
  1142. // Restore Apple, Window and Help menu.
  1143. MenuData *md_app = menus.get_or_null(application_menu);
  1144. if (md_app) {
  1145. NSMenuItem *menu_item = [md->menu addItemWithTitle:@"" action:nil keyEquivalent:@""];
  1146. [md->menu setSubmenu:md_app->menu forItem:menu_item];
  1147. }
  1148. MenuData *md_win = menus.get_or_null(window_menu);
  1149. if (md_win) {
  1150. NSMenuItem *menu_item = [md->menu addItemWithTitle:@"Window" action:nil keyEquivalent:@""];
  1151. [md->menu setSubmenu:md_win->menu forItem:menu_item];
  1152. }
  1153. MenuData *md_hlp = menus.get_or_null(help_menu);
  1154. if (md_hlp) {
  1155. NSMenuItem *menu_item = [md->menu addItemWithTitle:@"Help" action:nil keyEquivalent:@""];
  1156. [md->menu setSubmenu:md_hlp->menu forItem:menu_item];
  1157. }
  1158. }
  1159. }
  1160. NativeMenuMacOS::NativeMenuMacOS() {}
  1161. NativeMenuMacOS::~NativeMenuMacOS() {
  1162. if (main_menu.is_valid()) {
  1163. MenuData *md = menus.get_or_null(main_menu);
  1164. if (md) {
  1165. clear(main_menu);
  1166. menus.free(main_menu);
  1167. menu_lookup.erase(md->menu);
  1168. md->menu = nullptr;
  1169. main_menu_ns = nullptr;
  1170. memdelete(md);
  1171. }
  1172. }
  1173. if (application_menu.is_valid()) {
  1174. MenuData *md = menus.get_or_null(application_menu);
  1175. if (md) {
  1176. clear(application_menu);
  1177. menus.free(application_menu);
  1178. menu_lookup.erase(md->menu);
  1179. md->menu = nullptr;
  1180. application_menu_ns = nullptr;
  1181. memdelete(md);
  1182. }
  1183. }
  1184. if (window_menu.is_valid()) {
  1185. MenuData *md = menus.get_or_null(window_menu);
  1186. if (md) {
  1187. clear(window_menu);
  1188. menus.free(window_menu);
  1189. menu_lookup.erase(md->menu);
  1190. md->menu = nullptr;
  1191. window_menu_ns = nullptr;
  1192. memdelete(md);
  1193. }
  1194. }
  1195. if (help_menu.is_valid()) {
  1196. MenuData *md = menus.get_or_null(help_menu);
  1197. if (md) {
  1198. clear(help_menu);
  1199. menus.free(help_menu);
  1200. menu_lookup.erase(md->menu);
  1201. md->menu = nullptr;
  1202. help_menu_ns = nullptr;
  1203. memdelete(md);
  1204. }
  1205. }
  1206. if (dock_menu.is_valid()) {
  1207. MenuData *md = menus.get_or_null(dock_menu);
  1208. if (md) {
  1209. clear(dock_menu);
  1210. menus.free(dock_menu);
  1211. menu_lookup.erase(md->menu);
  1212. md->menu = nullptr;
  1213. dock_menu_ns = nullptr;
  1214. memdelete(md);
  1215. }
  1216. }
  1217. }