diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index 7f7dc1d288c..b8f82accfc0 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -235,6 +235,9 @@ [codeblock] "_main" - Main menu (macOS). "_dock" - Dock popup menu (macOS). + "_apple" - Apple menu (macOS, custom items added before "Services"). + "_window" - Window menu (macOS, custom items added after "Bring All to Front"). + "_help" - Help menu (macOS). [/codeblock] @@ -258,6 +261,9 @@ [codeblock] "_main" - Main menu (macOS). "_dock" - Dock popup menu (macOS). + "_apple" - Apple menu (macOS, custom items added before "Services"). + "_window" - Window menu (macOS, custom items added after "Bring All to Front"). + "_help" - Help menu (macOS). [/codeblock] @@ -281,6 +287,9 @@ [codeblock] "_main" - Main menu (macOS). "_dock" - Dock popup menu (macOS). + "_apple" - Apple menu (macOS, custom items added before "Services"). + "_window" - Window menu (macOS, custom items added after "Bring All to Front"). + "_help" - Help menu (macOS). [/codeblock] @@ -305,6 +314,9 @@ [codeblock] "_main" - Main menu (macOS). "_dock" - Dock popup menu (macOS). + "_apple" - Apple menu (macOS, custom items added before "Services"). + "_window" - Window menu (macOS, custom items added after "Bring All to Front"). + "_help" - Help menu (macOS). [/codeblock] @@ -327,6 +339,9 @@ [codeblock] "_main" - Main menu (macOS). "_dock" - Dock popup menu (macOS). + "_apple" - Apple menu (macOS, custom items added before "Services"). + "_window" - Window menu (macOS, custom items added after "Bring All to Front"). + "_help" - Help menu (macOS). [/codeblock] @@ -353,6 +368,9 @@ [codeblock] "_main" - Main menu (macOS). "_dock" - Dock popup menu (macOS). + "_apple" - Apple menu (macOS, custom items added before "Services"). + "_window" - Window menu (macOS, custom items added after "Bring All to Front"). + "_help" - Help menu (macOS). [/codeblock] @@ -376,6 +394,9 @@ [codeblock] "_main" - Main menu (macOS). "_dock" - Dock popup menu (macOS). + "_apple" - Apple menu (macOS, custom items added before "Services"). + "_window" - Window menu (macOS, custom items added after "Bring All to Front"). + "_help" - Help menu (macOS). [/codeblock] @@ -391,6 +412,9 @@ [codeblock] "_main" - Main menu (macOS). "_dock" - Dock popup menu (macOS). + "_apple" - Apple menu (macOS, custom items added before "Services"). + "_window" - Window menu (macOS, custom items added after "Bring All to Front"). + "_help" - Help menu (macOS). [/codeblock] @@ -408,6 +432,9 @@ [codeblock] "_main" - Main menu (macOS). "_dock" - Dock popup menu (macOS). + "_apple" - Apple menu (macOS, custom items added before "Services"). + "_window" - Window menu (macOS, custom items added after "Bring All to Front"). + "_help" - Help menu (macOS). [/codeblock] @@ -421,6 +448,9 @@ [codeblock] "_main" - Main menu (macOS). "_dock" - Dock popup menu (macOS). + "_apple" - Apple menu (macOS, custom items added before "Services"). + "_window" - Window menu (macOS, custom items added after "Bring All to Front"). + "_help" - Help menu (macOS). [/codeblock] @@ -549,6 +579,13 @@ [b]Note:[/b] This method is implemented only on macOS. + + + + Returns Dictionary of supported system menu IDs and names. + [b]Note:[/b] This method is implemented only on macOS. + + diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml index 3f4ec1b677a..40d5a9f4a24 100644 --- a/doc/classes/PopupMenu.xml +++ b/doc/classes/PopupMenu.xml @@ -346,6 +346,12 @@ Returns [code]true[/code] if the specified item's shortcut is disabled. + + + + Returns [code]true[/code] if the menu is bound to the special system menu. + + @@ -566,6 +572,9 @@ Sets the delay time in seconds for the submenu item to popup on mouse hovering. If the popup menu is added as a child of another (acting as a submenu), it will inherit the delay time of the parent menu item. + + If set to one of the values returned by [method DisplayServer.global_menu_get_system_menu_roots], this [PopupMenu] is bound to the special system menu. Only one [PopupMenu] can be bound to each special menu at a time. + diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 52f432135ec..3ae874b8118 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -7357,6 +7357,20 @@ EditorNode::EditorNode() { file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/file_quit", TTR("Quit"), KeyModifierMask::CMD_OR_CTRL + Key::Q), FILE_QUIT, true); } + ED_SHORTCUT_AND_COMMAND("editor/editor_settings", TTR("Editor Settings...")); + ED_SHORTCUT_OVERRIDE("editor/editor_settings", "macos", KeyModifierMask::META + Key::COMMA); +#ifdef MACOS_ENABLED + if (global_menu) { + apple_menu = memnew(PopupMenu); + apple_menu->set_system_menu_root("_apple"); + main_menu->add_child(apple_menu); + + apple_menu->add_shortcut(ED_GET_SHORTCUT("editor/editor_settings"), SETTINGS_PREFERENCES); + apple_menu->add_separator(); + apple_menu->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option)); + } +#endif + project_menu = memnew(PopupMenu); project_menu->set_name(TTR("Project")); main_menu->add_child(project_menu); @@ -7422,9 +7436,13 @@ EditorNode::EditorNode() { settings_menu->set_name(TTR("Editor")); main_menu->add_child(settings_menu); - ED_SHORTCUT_AND_COMMAND("editor/editor_settings", TTR("Editor Settings...")); - ED_SHORTCUT_OVERRIDE("editor/editor_settings", "macos", KeyModifierMask::META + Key::COMMA); +#ifdef MACOS_ENABLED + if (!global_menu) { + settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/editor_settings"), SETTINGS_PREFERENCES); + } +#else settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/editor_settings"), SETTINGS_PREFERENCES); +#endif settings_menu->add_shortcut(ED_SHORTCUT("editor/command_palette", TTR("Command Palette..."), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::P), HELP_COMMAND_PALETTE); settings_menu->add_separator(); @@ -7471,6 +7489,7 @@ EditorNode::EditorNode() { help_menu = memnew(PopupMenu); help_menu->set_name(TTR("Help")); + help_menu->set_system_menu_root("_help"); main_menu->add_child(help_menu); help_menu->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option)); diff --git a/editor/editor_node.h b/editor/editor_node.h index 8b440b798c1..c72a8f93249 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -346,6 +346,7 @@ private: EditorRunBar *project_run_bar = nullptr; VBoxContainer *main_screen_vbox = nullptr; MenuBar *main_menu = nullptr; + PopupMenu *apple_menu = nullptr; PopupMenu *file_menu = nullptr; PopupMenu *project_menu = nullptr; PopupMenu *debug_menu = nullptr; diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index f012292f84d..a19e02eb21e 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -140,6 +140,8 @@ private: String rendering_driver; NSMenu *apple_menu = nullptr; + NSMenu *window_menu = nullptr; + NSMenu *help_menu = nullptr; NSMenu *dock_menu = nullptr; struct MenuData { Callable open; @@ -226,7 +228,8 @@ private: static NSCursor *_cursor_from_selector(SEL p_selector, SEL p_fallback = nil); - bool _has_help_menu() const; + int _get_system_menu_start(const NSMenu *p_menu) const; + int _get_system_menu_count(const NSMenu *p_menu) const; NSMenuItem *_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out); public: @@ -321,6 +324,8 @@ public: virtual void global_menu_remove_item(const String &p_menu_root, int p_idx) override; virtual void global_menu_clear(const String &p_menu_root) override; + virtual Dictionary global_menu_get_system_menu_roots() const override; + virtual bool tts_is_speaking() const override; virtual bool tts_is_paused() const override; virtual TypedArray tts_get_voices() const override; diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index 407a3158277..742b552e131 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -64,6 +64,9 @@ #import #import +#define MENU_TAG_START 0x00004447 +#define MENU_TAG_END 0xFFFF4447 + const NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) const { const NSMenu *menu = nullptr; if (p_menu_root == "" || p_menu_root.to_lower() == "_main") { @@ -72,16 +75,21 @@ const NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) cons } else if (p_menu_root.to_lower() == "_dock") { // macOS dock menu. menu = dock_menu; + } else if (p_menu_root.to_lower() == "_apple") { + // macOS Apple menu. + menu = apple_menu; + } else if (p_menu_root.to_lower() == "_window") { + // macOS Window menu. + menu = window_menu; + } else if (p_menu_root.to_lower() == "_help") { + // macOS Help menu. + menu = help_menu; } else { // Submenu. if (submenu.has(p_menu_root)) { menu = submenu[p_menu_root].menu; } } - if (menu == apple_menu) { - // Do not allow to change Apple menu. - return nullptr; - } return menu; } @@ -93,6 +101,15 @@ NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) { } else if (p_menu_root.to_lower() == "_dock") { // macOS dock menu. menu = dock_menu; + } else if (p_menu_root.to_lower() == "_apple") { + // macOS Apple menu. + menu = apple_menu; + } else if (p_menu_root.to_lower() == "_window") { + // macOS Window menu. + menu = window_menu; + } else if (p_menu_root.to_lower() == "_help") { + // macOS Help menu. + menu = help_menu; } else { // Submenu. if (!submenu.has(p_menu_root)) { @@ -104,10 +121,6 @@ NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) { } menu = submenu[p_menu_root].menu; } - if (menu == apple_menu) { - // Do not allow to change Apple menu. - return nullptr; - } return menu; } @@ -820,22 +833,6 @@ String DisplayServerMacOS::get_name() const { return "macOS"; } -bool DisplayServerMacOS::_has_help_menu() const { - if ([NSApp helpMenu]) { - return true; - } else { - NSMenu *menu = [NSApp mainMenu]; - const NSMenuItem *menu_item = [menu itemAtIndex:[menu numberOfItems] - 1]; - if (menu_item) { - String menu_name = String::utf8([[menu_item title] UTF8String]); - if (menu_name == "Help" || menu_name == RTR("Help")) { - return true; - } - } - return false; - } -} - bool DisplayServerMacOS::_is_menu_opened(NSMenu *p_menu) const { if (submenu_inv.has(p_menu)) { const MenuData &md = submenu[submenu_inv[p_menu]]; @@ -854,24 +851,57 @@ bool DisplayServerMacOS::_is_menu_opened(NSMenu *p_menu) const { return false; } +int DisplayServerMacOS::_get_system_menu_start(const NSMenu *p_menu) const { + if (p_menu == [NSApp mainMenu]) { // Skip Apple menu. + return 1; + } + if (p_menu == apple_menu || p_menu == window_menu || p_menu == help_menu) { + int count = [p_menu numberOfItems]; + for (int i = 0; i < count; i++) { + NSMenuItem *menu_item = [p_menu itemAtIndex:i]; + if (menu_item.tag == MENU_TAG_START) { + return i + 1; + } + } + } + return 0; +} + +int DisplayServerMacOS::_get_system_menu_count(const NSMenu *p_menu) const { + if (p_menu == [NSApp mainMenu]) { // Skip Apple, Window and Help menu. + return [p_menu numberOfItems] - 3; + } + if (p_menu == apple_menu || p_menu == window_menu || p_menu == help_menu) { + int start = 0; + int count = [p_menu numberOfItems]; + for (int i = 0; i < count; i++) { + NSMenuItem *menu_item = [p_menu itemAtIndex:i]; + if (menu_item.tag == MENU_TAG_START) { + start = i + 1; + } + if (menu_item.tag == MENU_TAG_END) { + return i - start; + } + } + } + return [p_menu numberOfItems]; +} + NSMenuItem *DisplayServerMacOS::_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out) { NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); NSMenuItem *menu_item; - int item_count = ((menu == [NSApp mainMenu]) && _has_help_menu()) ? [menu numberOfItems] - 1 : [menu numberOfItems]; - if ((menu == [NSApp mainMenu]) && (p_label == "Help" || p_label == RTR("Help"))) { - p_index = [menu numberOfItems]; - } else if (p_index < 0) { - p_index = item_count; + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + if (p_index < 0) { + p_index = item_start + item_count; } else { - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_index++; - } - p_index = CLAMP(p_index, 0, item_count); + p_index += item_start; + p_index = CLAMP(p_index, item_start, item_start + item_count); } menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; - *r_out = (menu == [NSApp mainMenu]) ? p_index - 1 : p_index; + *r_out = p_index - item_start; return menu_item; } return nullptr; @@ -1070,19 +1100,16 @@ int DisplayServerMacOS::global_menu_add_submenu_item(const String &p_menu_root, return -1; } NSMenuItem *menu_item; - int item_count = ((menu == [NSApp mainMenu]) && _has_help_menu()) ? [menu numberOfItems] - 1 : [menu numberOfItems]; - if ((menu == [NSApp mainMenu]) && (p_label == "Help" || p_label == RTR("Help"))) { - p_index = [menu numberOfItems]; - } else if (p_index < 0) { - p_index = item_count; + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + if (p_index < 0) { + p_index = item_start + item_count; } else { - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_index++; - } - p_index = CLAMP(p_index, 0, item_count); + p_index += item_start; + p_index = CLAMP(p_index, item_start, item_start + item_count); } menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index]; - out = (menu == [NSApp mainMenu]) ? p_index - 1 : p_index; + out = p_index - item_start; GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = Callable(); @@ -1105,13 +1132,16 @@ int DisplayServerMacOS::global_menu_add_separator(const String &p_menu_root, int if (menu == [NSApp mainMenu]) { // Do not add separators into main menu. return -1; } + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); if (p_index < 0) { - p_index = [menu numberOfItems]; + p_index = item_start + item_count; } else { - p_index = CLAMP(p_index, 0, [menu numberOfItems]); + p_index += item_start; + p_index = CLAMP(p_index, item_start, item_start + item_count); } [menu insertItem:[NSMenuItem separatorItem] atIndex:p_index]; - return p_index; + return p_index - item_start; } return -1; } @@ -1121,11 +1151,8 @@ int DisplayServerMacOS::global_menu_get_item_index_from_text(const String &p_men const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]] - 1; - } else { - return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]]; - } + int item_start = _get_system_menu_start(menu); + return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]] - item_start; } return -1; @@ -1136,16 +1163,14 @@ int DisplayServerMacOS::global_menu_get_item_index_from_tag(const String &p_menu const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - for (NSInteger i = (menu == [NSApp mainMenu]) ? 1 : 0; i < [menu numberOfItems]; i++) { + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + for (NSInteger i = item_start; i < item_start + item_count; i++) { const NSMenuItem *menu_item = [menu itemAtIndex:i]; if (menu_item) { const GodotMenuItem *obj = [menu_item representedObject]; if (obj && obj->meta == p_tag) { - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - return i - 1; - } else { - return i; - } + return i - item_start; } } } @@ -1160,10 +1185,10 @@ bool DisplayServerMacOS::global_menu_is_item_checked(const String &p_menu_root, const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND_V(p_idx < 0, false); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND_V(p_idx >= item_start + item_count, false); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { return ([menu_item state] == NSControlStateValueOn); @@ -1178,10 +1203,10 @@ bool DisplayServerMacOS::global_menu_is_item_checkable(const String &p_menu_root const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND_V(p_idx < 0, false); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND_V(p_idx >= item_start + item_count, false); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1199,10 +1224,10 @@ bool DisplayServerMacOS::global_menu_is_item_radio_checkable(const String &p_men const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND_V(p_idx < 0, false); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND_V(p_idx >= item_start + item_count, false); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1220,10 +1245,10 @@ Callable DisplayServerMacOS::global_menu_get_item_callback(const String &p_menu_ const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND_V(p_idx < 0, Callable()); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Callable()); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND_V(p_idx >= item_start + item_count, Callable()); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1241,10 +1266,10 @@ Callable DisplayServerMacOS::global_menu_get_item_key_callback(const String &p_m const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND_V(p_idx < 0, Callable()); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Callable()); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND_V(p_idx >= item_start + item_count, Callable()); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1262,10 +1287,10 @@ Variant DisplayServerMacOS::global_menu_get_item_tag(const String &p_menu_root, const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND_V(p_idx < 0, Variant()); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Variant()); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND_V(p_idx >= item_start + item_count, Variant()); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1283,10 +1308,10 @@ String DisplayServerMacOS::global_menu_get_item_text(const String &p_menu_root, const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND_V(p_idx < 0, String()); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String()); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND_V(p_idx >= item_start + item_count, String()); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { return String::utf8([[menu_item title] UTF8String]); @@ -1301,10 +1326,10 @@ String DisplayServerMacOS::global_menu_get_item_submenu(const String &p_menu_roo const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND_V(p_idx < 0, String()); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String()); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND_V(p_idx >= item_start + item_count, String()); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { NSMenu *sub_menu = [menu_item submenu]; @@ -1322,10 +1347,10 @@ Key DisplayServerMacOS::global_menu_get_item_accelerator(const String &p_menu_ro const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND_V(p_idx < 0, Key::NONE); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Key::NONE); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND_V(p_idx >= item_start + item_count, Key::NONE); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { String ret = String::utf8([[menu_item keyEquivalent] UTF8String]); @@ -1358,10 +1383,10 @@ bool DisplayServerMacOS::global_menu_is_item_disabled(const String &p_menu_root, const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND_V(p_idx < 0, false); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND_V(p_idx >= item_start + item_count, false); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { return ![menu_item isEnabled]; @@ -1376,10 +1401,10 @@ bool DisplayServerMacOS::global_menu_is_item_hidden(const String &p_menu_root, i const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND_V(p_idx < 0, false); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND_V(p_idx >= item_start + item_count, false); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { return [menu_item isHidden]; @@ -1394,10 +1419,10 @@ String DisplayServerMacOS::global_menu_get_item_tooltip(const String &p_menu_roo const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND_V(p_idx < 0, String()); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String()); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND_V(p_idx >= item_start + item_count, String()); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { return String::utf8([[menu_item toolTip] UTF8String]); @@ -1412,10 +1437,10 @@ int DisplayServerMacOS::global_menu_get_item_state(const String &p_menu_root, in const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND_V(p_idx < 0, 0); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], 0); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND_V(p_idx >= item_start + item_count, 0); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1433,10 +1458,10 @@ int DisplayServerMacOS::global_menu_get_item_max_states(const String &p_menu_roo const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND_V(p_idx < 0, 0); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], 0); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND_V(p_idx >= item_start + item_count, 0); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1454,10 +1479,10 @@ Ref DisplayServerMacOS::global_menu_get_item_icon(const String &p_men const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND_V(p_idx < 0, Ref()); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Ref()); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND_V(p_idx >= item_start + item_count, Ref()); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1477,10 +1502,10 @@ int DisplayServerMacOS::global_menu_get_item_indentation_level(const String &p_m const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND_V(p_idx < 0, 0); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], 0); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND_V(p_idx >= item_start + item_count, 0); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { return [menu_item indentationLevel]; @@ -1495,10 +1520,10 @@ void DisplayServerMacOS::global_menu_set_item_checked(const String &p_menu_root, NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND(p_idx < 0); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND(p_idx >= item_start + item_count); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { if (p_checked) { @@ -1516,10 +1541,10 @@ void DisplayServerMacOS::global_menu_set_item_checkable(const String &p_menu_roo NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND(p_idx < 0); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND(p_idx >= item_start + item_count); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1535,10 +1560,10 @@ void DisplayServerMacOS::global_menu_set_item_radio_checkable(const String &p_me NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND(p_idx < 0); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND(p_idx >= item_start + item_count); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1554,10 +1579,10 @@ void DisplayServerMacOS::global_menu_set_item_callback(const String &p_menu_root NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND(p_idx < 0); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND(p_idx >= item_start + item_count); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1573,10 +1598,10 @@ void DisplayServerMacOS::global_menu_set_item_hover_callbacks(const String &p_me NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND(p_idx < 0); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND(p_idx >= item_start + item_count); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1592,10 +1617,10 @@ void DisplayServerMacOS::global_menu_set_item_key_callback(const String &p_menu_ NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND(p_idx < 0); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND(p_idx >= item_start + item_count); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1611,10 +1636,10 @@ void DisplayServerMacOS::global_menu_set_item_tag(const String &p_menu_root, int NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND(p_idx < 0); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND(p_idx >= item_start + item_count); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1630,10 +1655,10 @@ void DisplayServerMacOS::global_menu_set_item_text(const String &p_menu_root, in NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND(p_idx < 0); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND(p_idx >= item_start + item_count); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { [menu_item setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]]; @@ -1647,10 +1672,10 @@ void DisplayServerMacOS::global_menu_set_item_submenu(const String &p_menu_root, NSMenu *menu = _get_menu_root(p_menu_root); if (menu && p_submenu.is_empty()) { ERR_FAIL_COND(p_idx < 0); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND(p_idx >= item_start + item_count); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) { @@ -1673,10 +1698,10 @@ void DisplayServerMacOS::global_menu_set_item_submenu(const String &p_menu_root, return; } ERR_FAIL_COND(p_idx < 0); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND(p_idx >= item_start + item_count); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { [menu setSubmenu:sub_menu forItem:menu_item]; @@ -1690,10 +1715,10 @@ void DisplayServerMacOS::global_menu_set_item_accelerator(const String &p_menu_r NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND(p_idx < 0); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND(p_idx >= item_start + item_count); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { if (p_keycode == Key::NONE) { @@ -1713,10 +1738,10 @@ void DisplayServerMacOS::global_menu_set_item_disabled(const String &p_menu_root NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND(p_idx < 0); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND(p_idx >= item_start + item_count); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { [menu_item setEnabled:(!p_disabled)]; @@ -1730,10 +1755,10 @@ void DisplayServerMacOS::global_menu_set_item_hidden(const String &p_menu_root, NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND(p_idx < 0); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND(p_idx >= item_start + item_count); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { [menu_item setHidden:p_hidden]; @@ -1747,10 +1772,10 @@ void DisplayServerMacOS::global_menu_set_item_tooltip(const String &p_menu_root, NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND(p_idx < 0); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND(p_idx >= item_start + item_count); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { [menu_item setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]]; @@ -1764,10 +1789,10 @@ void DisplayServerMacOS::global_menu_set_item_state(const String &p_menu_root, i NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND(p_idx < 0); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND(p_idx >= item_start + item_count); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1783,10 +1808,10 @@ void DisplayServerMacOS::global_menu_set_item_max_states(const String &p_menu_ro NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND(p_idx < 0); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND(p_idx >= item_start + item_count); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1802,10 +1827,10 @@ void DisplayServerMacOS::global_menu_set_item_icon(const String &p_menu_root, in NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND(p_idx < 0); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND(p_idx >= item_start + item_count); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1832,10 +1857,10 @@ void DisplayServerMacOS::global_menu_set_item_indentation_level(const String &p_ NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND(p_idx < 0); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND(p_idx >= item_start + item_count); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { [menu_item setIndentationLevel:p_level]; @@ -1848,11 +1873,7 @@ int DisplayServerMacOS::global_menu_get_item_count(const String &p_menu_root) co const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - return [menu numberOfItems] - 1; - } else { - return [menu numberOfItems]; - } + return _get_system_menu_count(menu); } else { return 0; } @@ -1864,10 +1885,10 @@ void DisplayServerMacOS::global_menu_remove_item(const String &p_menu_root, int NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { ERR_FAIL_COND(p_idx < 0); - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_idx++; - } - ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + int item_start = _get_system_menu_start(menu); + int item_count = _get_system_menu_count(menu); + p_idx += item_start; + ERR_FAIL_COND(p_idx >= item_start + item_count); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) { ERR_PRINT("Can't remove open menu!"); @@ -1886,12 +1907,41 @@ void DisplayServerMacOS::global_menu_clear(const String &p_menu_root) { ERR_PRINT("Can't remove open menu!"); return; } - [menu removeAllItems]; - // Restore Apple menu. + + if (menu == apple_menu) { + int start = _get_system_menu_start(apple_menu); + int count = _get_system_menu_count(apple_menu); + for (int i = start + count - 1; i >= start; i--) { + [apple_menu removeItemAtIndex:i]; + } + } else if (menu == window_menu) { + int start = _get_system_menu_start(window_menu); + int count = _get_system_menu_count(window_menu); + for (int i = start + count - 1; i >= start; i--) { + [window_menu removeItemAtIndex:i]; + } + } else if (menu == help_menu) { + int start = _get_system_menu_start(help_menu); + int count = _get_system_menu_count(help_menu); + for (int i = start + count - 1; i >= start; i--) { + [help_menu removeItemAtIndex:i]; + } + } else { + [menu removeAllItems]; + } + + // Restore Apple, Window and Help menu. if (menu == [NSApp mainMenu]) { NSMenuItem *menu_item = [menu addItemWithTitle:@"" action:nil keyEquivalent:@""]; [menu setSubmenu:apple_menu forItem:menu_item]; + + menu_item = [menu addItemWithTitle:@"Window" action:nil keyEquivalent:@""]; + [menu setSubmenu:window_menu forItem:menu_item]; + + menu_item = [menu addItemWithTitle:@"Help" action:nil keyEquivalent:@""]; + [menu setSubmenu:help_menu forItem:menu_item]; } + if (submenu.has(p_menu_root)) { submenu_inv.erase(submenu[p_menu_root].menu); submenu.erase(p_menu_root); @@ -1899,6 +1949,15 @@ void DisplayServerMacOS::global_menu_clear(const String &p_menu_root) { } } +Dictionary DisplayServerMacOS::global_menu_get_system_menu_roots() const { + Dictionary out; + out["_dock"] = "@Dock"; + out["_apple"] = "@Apple"; + out["_window"] = "Window"; + out["_help"] = "Help"; + return out; +} + bool DisplayServerMacOS::tts_is_speaking() const { ERR_FAIL_NULL_V_MSG(tts, false, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); return [tts isSpeaking]; @@ -3564,6 +3623,9 @@ void DisplayServerMacOS::window_set_flag(WindowFlags p_flag, bool p_enabled, Win } break; case WINDOW_FLAG_NO_FOCUS: { wd.no_focus = p_enabled; + + NSWindow *w = wd.window_object; + w.excludedFromWindowsMenu = wd.is_popup || wd.no_focus; } break; case WINDOW_FLAG_MOUSE_PASSTHROUGH: { wd.mpass = p_enabled; @@ -3572,6 +3634,9 @@ void DisplayServerMacOS::window_set_flag(WindowFlags p_flag, bool p_enabled, Win ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be popup."); ERR_FAIL_COND_MSG([wd.window_object isVisible] && (wd.is_popup != p_enabled), "Popup flag can't changed while window is opened."); wd.is_popup = p_enabled; + + NSWindow *w = wd.window_object; + w.excludedFromWindowsMenu = wd.is_popup || wd.no_focus; } break; default: { } @@ -4488,6 +4553,13 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM [apple_menu addItem:[NSMenuItem separatorItem]]; + menu_item = [apple_menu addItemWithTitle:@"_start_" action:nil keyEquivalent:@""]; + menu_item.hidden = YES; + menu_item.tag = MENU_TAG_START; + menu_item = [apple_menu addItemWithTitle:@"_end_" action:nil keyEquivalent:@""]; + menu_item.hidden = YES; + menu_item.tag = MENU_TAG_END; + NSMenu *services = [[NSMenu alloc] initWithTitle:@""]; menu_item = [apple_menu addItemWithTitle:NSLocalizedString(@"Services", nil) action:nil keyEquivalent:@""]; [apple_menu setSubmenu:services forItem:menu_item]; @@ -4508,10 +4580,41 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM title = [NSString stringWithFormat:NSLocalizedString(@"Quit %@", nil), nsappname]; [apple_menu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; + window_menu = [[NSMenu alloc] initWithTitle:NSLocalizedString(@"Window", nil)]; + [window_menu addItemWithTitle:NSLocalizedString(@"Minimize", nil) action:@selector(performMiniaturize:) keyEquivalent:@"m"]; + [window_menu addItemWithTitle:NSLocalizedString(@"Zoom", nil) action:@selector(performZoom:) keyEquivalent:@""]; + [window_menu addItem:[NSMenuItem separatorItem]]; + [window_menu addItemWithTitle:NSLocalizedString(@"Bring All to Front", nil) action:@selector(bringAllToFront:) keyEquivalent:@""]; + [window_menu addItem:[NSMenuItem separatorItem]]; + menu_item = [window_menu addItemWithTitle:@"_start_" action:nil keyEquivalent:@""]; + menu_item.hidden = YES; + menu_item.tag = MENU_TAG_START; + menu_item = [window_menu addItemWithTitle:@"_end_" action:nil keyEquivalent:@""]; + menu_item.hidden = YES; + menu_item.tag = MENU_TAG_END; + + help_menu = [[NSMenu alloc] initWithTitle:NSLocalizedString(@"Help", nil)]; + menu_item = [help_menu addItemWithTitle:@"_start_" action:nil keyEquivalent:@""]; + menu_item.hidden = YES; + menu_item.tag = MENU_TAG_START; + menu_item = [help_menu addItemWithTitle:@"_end_" action:nil keyEquivalent:@""]; + menu_item.hidden = YES; + menu_item.tag = MENU_TAG_END; + + [NSApp setWindowsMenu:window_menu]; + [NSApp setHelpMenu:help_menu]; + // Add items to the menu bar. NSMenu *main_menu = [NSApp mainMenu]; menu_item = [main_menu addItemWithTitle:@"" action:nil keyEquivalent:@""]; [main_menu setSubmenu:apple_menu forItem:menu_item]; + + menu_item = [main_menu addItemWithTitle:NSLocalizedString(@"Window", nil) action:nil keyEquivalent:@""]; + [main_menu setSubmenu:window_menu forItem:menu_item]; + + menu_item = [main_menu addItemWithTitle:NSLocalizedString(@"Help", nil) action:nil keyEquivalent:@""]; + [main_menu setSubmenu:help_menu forItem:menu_item]; + [main_menu setAutoenablesItems:NO]; //!!!!!!!!!!!!!!!!!!!!!!!!!! diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp index 7418ba73334..7fa2653ed94 100644 --- a/scene/gui/menu_bar.cpp +++ b/scene/gui/menu_bar.cpp @@ -249,11 +249,13 @@ String MenuBar::bind_global_menu() { Vector popups = _get_popups(); for (int i = 0; i < menu_cache.size(); i++) { String submenu_name = popups[i]->bind_global_menu(); - int index = ds->global_menu_add_submenu_item("_main", menu_cache[i].name, submenu_name, global_start_idx + i); - ds->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(i)); - ds->global_menu_set_item_hidden("_main", index, menu_cache[i].hidden); - ds->global_menu_set_item_disabled("_main", index, menu_cache[i].disabled); - ds->global_menu_set_item_tooltip("_main", index, menu_cache[i].tooltip); + if (!popups[i]->is_system_menu()) { + int index = ds->global_menu_add_submenu_item("_main", menu_cache[i].name, submenu_name, global_start_idx + i); + ds->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(i)); + ds->global_menu_set_item_hidden("_main", index, menu_cache[i].hidden); + ds->global_menu_set_item_disabled("_main", index, menu_cache[i].disabled); + ds->global_menu_set_item_tooltip("_main", index, menu_cache[i].tooltip); + } } return global_menu_name; @@ -268,8 +270,10 @@ void MenuBar::unbind_global_menu() { int global_start = _find_global_start_index(); Vector popups = _get_popups(); for (int i = menu_cache.size() - 1; i >= 0; i--) { - popups[i]->unbind_global_menu(); - ds->global_menu_remove_item("_main", global_start + i); + if (!popups[i]->is_system_menu()) { + popups[i]->unbind_global_menu(); + ds->global_menu_remove_item("_main", global_start + i); + } } global_menu_name = String(); @@ -558,8 +562,10 @@ void MenuBar::add_child_notify(Node *p_child) { if (!global_menu_name.is_empty()) { String submenu_name = pm->bind_global_menu(); - int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, _find_global_start_index() + menu_cache.size() - 1); - DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(menu_cache.size() - 1)); + if (!pm->is_system_menu()) { + int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, _find_global_start_index() + menu_cache.size() - 1); + DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(menu_cache.size() - 1)); + } } update_minimum_size(); } @@ -587,14 +593,16 @@ void MenuBar::move_child_notify(Node *p_child) { menu_cache.insert(new_idx, menu); if (!global_menu_name.is_empty()) { - int global_start = _find_global_start_index(); - if (old_idx != -1) { - DisplayServer::get_singleton()->global_menu_remove_item("_main", global_start + old_idx); - } - if (new_idx != -1) { - String submenu_name = pm->bind_global_menu(); - int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, global_start + new_idx); - DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(new_idx)); + if (!pm->is_system_menu()) { + int global_start = _find_global_start_index(); + if (old_idx != -1) { + DisplayServer::get_singleton()->global_menu_remove_item("_main", global_start + old_idx); + } + if (new_idx != -1) { + String submenu_name = pm->bind_global_menu(); + int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, global_start + new_idx); + DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(new_idx)); + } } } } @@ -612,8 +620,10 @@ void MenuBar::remove_child_notify(Node *p_child) { menu_cache.remove_at(idx); if (!global_menu_name.is_empty()) { - pm->unbind_global_menu(); - DisplayServer::get_singleton()->global_menu_remove_item("_main", _find_global_start_index() + idx); + if (!pm->is_system_menu()) { + pm->unbind_global_menu(); + DisplayServer::get_singleton()->global_menu_remove_item("_main", _find_global_start_index() + idx); + } } p_child->remove_meta("_menu_name"); diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index d6b8dd02025..605a3fe3974 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -40,6 +40,8 @@ #include "scene/gui/menu_bar.h" #include "scene/theme/theme_db.h" +HashMap PopupMenu::system_menus; + String PopupMenu::bind_global_menu() { #ifdef TOOLS_ENABLED if (is_part_of_edited_scene()) { @@ -54,8 +56,20 @@ String PopupMenu::bind_global_menu() { return global_menu_name; // Already bound; } - DisplayServer *ds = DisplayServer::get_singleton(); global_menu_name = "__PopupMenu#" + itos(get_instance_id()); + if (system_menu_name.length() > 0) { + if (system_menus.has(system_menu_name)) { + WARN_PRINT(vformat("Attempting to bind PopupMenu to the special menu %s, but another menu is already bound to it. This menu: %s, current menu: %s", system_menu_name, this->get_description(), system_menus[system_menu_name]->get_description())); + } else { + const Dictionary &supported_special_names = DisplayServer::get_singleton()->global_menu_get_system_menu_roots(); + if (supported_special_names.has(system_menu_name)) { + system_menus[system_menu_name] = this; + global_menu_name = system_menu_name; + } + } + } + + DisplayServer *ds = DisplayServer::get_singleton(); ds->global_menu_set_popup_callbacks(global_menu_name, callable_mp(this, &PopupMenu::_about_to_popup), callable_mp(this, &PopupMenu::_about_to_close)); for (int i = 0; i < items.size(); i++) { Item &item = items.write[i]; @@ -105,6 +119,10 @@ void PopupMenu::unbind_global_menu() { return; } + if (global_menu_name == system_menu_name && system_menus[system_menu_name] == this) { + system_menus.erase(system_menu_name); + } + for (int i = 0; i < items.size(); i++) { Item &item = items.write[i]; if (!item.submenu.is_empty()) { @@ -120,6 +138,24 @@ void PopupMenu::unbind_global_menu() { global_menu_name = String(); } +bool PopupMenu::is_system_menu() const { + return (global_menu_name == system_menu_name) && (system_menu_name.length() > 0); +} + +void PopupMenu::set_system_menu_root(const String &p_special) { + if (is_inside_tree() && system_menu_name.length() > 0) { + unbind_global_menu(); + } + system_menu_name = p_special; + if (is_inside_tree() && system_menu_name.length() > 0) { + bind_global_menu(); + } +} + +String PopupMenu::get_system_menu_root() const { + return system_menu_name; +} + String PopupMenu::_get_accel_text(const Item &p_item) const { if (p_item.shortcut.is_valid()) { return p_item.shortcut->get_as_text(); @@ -947,6 +983,15 @@ void PopupMenu::_notification(int p_what) { if (!is_embedded()) { set_flag(FLAG_NO_FOCUS, true); } + if (system_menu_name.length() > 0) { + bind_global_menu(); + } + } break; + + case NOTIFICATION_EXIT_TREE: { + if (system_menu_name.length() > 0) { + unbind_global_menu(); + } } break; case NOTIFICATION_THEME_CHANGED: @@ -2716,11 +2761,16 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("set_allow_search", "allow"), &PopupMenu::set_allow_search); ClassDB::bind_method(D_METHOD("get_allow_search"), &PopupMenu::get_allow_search); + ClassDB::bind_method(D_METHOD("is_system_menu"), &PopupMenu::is_system_menu); + ClassDB::bind_method(D_METHOD("set_system_menu_root", "special"), &PopupMenu::set_system_menu_root); + ClassDB::bind_method(D_METHOD("get_system_menu_root"), &PopupMenu::get_system_menu_root); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_item_selection"), "set_hide_on_item_selection", "is_hide_on_item_selection"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_checkable_item_selection"), "set_hide_on_checkable_item_selection", "is_hide_on_checkable_item_selection"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_state_item_selection"), "set_hide_on_state_item_selection", "is_hide_on_state_item_selection"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "submenu_popup_delay", PROPERTY_HINT_NONE, "suffix:s"), "set_submenu_popup_delay", "get_submenu_popup_delay"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_search"), "set_allow_search", "get_allow_search"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "system_menu_root", PROPERTY_HINT_ENUM, "Dock (macOS):_dock,Apple Menu(macOS):_apple,Window Menu(macOS):_window,Help Menu(macOS):_help"), "set_system_menu_root", "get_system_menu_root"); ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "item_"); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 5d5f4a8322a..4703686c518 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -40,6 +40,8 @@ class PopupMenu : public Popup { GDCLASS(PopupMenu, Popup); + static HashMap system_menus; + struct Item { Ref icon; int icon_max_width = 0; @@ -90,6 +92,7 @@ class PopupMenu : public Popup { }; String global_menu_name; + String system_menu_name; bool close_allowed = false; bool activated_by_keyboard = false; @@ -218,6 +221,9 @@ public: String bind_global_menu(); void unbind_global_menu(); + bool is_system_menu() const; + void set_system_menu_root(const String &p_special); + String get_system_menu_root() const; void add_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE); void add_icon_item(const Ref &p_icon, const String &p_label, int p_id = -1, Key p_accel = Key::NONE); diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 6459cc74622..bb28bc0eb8b 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -267,6 +267,11 @@ void DisplayServer::global_menu_clear(const String &p_menu_root) { WARN_PRINT("Global menus not supported by this display server."); } +Dictionary DisplayServer::global_menu_get_system_menu_roots() const { + WARN_PRINT("Global menus not supported by this display server."); + return Dictionary(); +} + bool DisplayServer::tts_is_speaking() const { WARN_PRINT("TTS is not supported by this display server."); return false; @@ -652,6 +657,8 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("global_menu_remove_item", "menu_root", "idx"), &DisplayServer::global_menu_remove_item); ClassDB::bind_method(D_METHOD("global_menu_clear", "menu_root"), &DisplayServer::global_menu_clear); + ClassDB::bind_method(D_METHOD("global_menu_get_system_menu_roots"), &DisplayServer::global_menu_get_system_menu_roots); + ClassDB::bind_method(D_METHOD("tts_is_speaking"), &DisplayServer::tts_is_speaking); ClassDB::bind_method(D_METHOD("tts_is_paused"), &DisplayServer::tts_is_paused); ClassDB::bind_method(D_METHOD("tts_get_voices"), &DisplayServer::tts_get_voices); diff --git a/servers/display_server.h b/servers/display_server.h index d2e112d2241..4450677f718 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -185,6 +185,8 @@ public: virtual void global_menu_remove_item(const String &p_menu_root, int p_idx); virtual void global_menu_clear(const String &p_menu_root); + virtual Dictionary global_menu_get_system_menu_roots() const; + struct TTSUtterance { String text; String voice;