From d1aaa914f35b6ee23722cf84998eb5e645d82aa2 Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Tue, 11 Jul 2023 11:17:35 +0300 Subject: [PATCH] [macOS] Add `about_to_open` and `popup_hide` callback for the global menus, move part of logic to the PopupMenu to allow live menu modification. --- doc/classes/DisplayServer.xml | 40 +++ platform/macos/display_server_macos.h | 17 +- platform/macos/display_server_macos.mm | 173 +++++++++- platform/macos/godot_menu_delegate.mm | 28 ++ platform/macos/godot_menu_item.h | 1 + scene/gui/menu_bar.cpp | 230 ++++++------ scene/gui/menu_bar.h | 24 +- scene/gui/popup_menu.cpp | 461 ++++++++++++++++++++++++- scene/gui/popup_menu.h | 9 + servers/display_server.cpp | 21 ++ servers/display_server.h | 5 + 11 files changed, 878 insertions(+), 131 deletions(-) diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index 7bbfd8077a5..a47b20f2e2b 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -577,6 +577,16 @@ [b]Note:[/b] This method is implemented only on macOS. + + + + + + Returns [code]true[/code] if the item at index [param idx] is hidden. + See [method global_menu_set_item_hidden] for more info on how to hide an item. + [b]Note:[/b] This method is implemented only on macOS. + + @@ -648,6 +658,27 @@ [b]Note:[/b] This method is implemented only on macOS. + + + + + + + Hides/shows the item at index [param idx]. When it is hidden, an item does not appear in a menu and its action cannot be invoked. + [b]Note:[/b] This method is implemented only on macOS. + + + + + + + + + Sets the callback of the item at index [param idx]. The callback is emitted when an item is hovered. + [b]Note:[/b] The [param callback] Callable needs to accept exactly one Variant parameter, the parameter passed to the Callable will be the value passed to the [code]tag[/code] parameter when the menu item was created. + [b]Note:[/b] This method is implemented only on macOS. + + @@ -751,6 +782,15 @@ [b]Note:[/b] This method is implemented only on macOS. + + + + + + + Registers callables to emit when the menu is respectively about to show or closed. + + diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index 7dbe6a59701..2ca9e493b7a 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -139,7 +139,14 @@ private: NSMenu *apple_menu = nullptr; NSMenu *dock_menu = nullptr; - HashMap submenu; + struct MenuData { + Callable open; + Callable close; + NSMenu *menu = nullptr; + bool is_open = false; + }; + HashMap submenu; + HashMap submenu_inv; struct WarpEvent { NSTimeInterval timestamp; @@ -197,6 +204,7 @@ private: const NSMenu *_get_menu_root(const String &p_menu_root) const; NSMenu *_get_menu_root(const String &p_menu_root); + bool _is_menu_opened(NSMenu *p_menu) const; WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect); void _update_window_style(WindowData p_wd); @@ -223,6 +231,8 @@ private: public: NSMenu *get_dock_menu() const; void menu_callback(id p_sender); + void menu_open(NSMenu *p_menu); + void menu_close(NSMenu *p_menu); bool has_window(WindowID p_window) const; WindowData &get_window(WindowID p_window); @@ -254,6 +264,8 @@ public: virtual bool has_feature(Feature p_feature) const override; virtual String get_name() const override; + virtual void global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callback = Callable(), const Callable &p_close_callback = Callable()) override; + virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override; virtual int global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; virtual int global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; @@ -277,6 +289,7 @@ public: virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) const override; virtual Key global_menu_get_item_accelerator(const String &p_menu_root, int p_idx) const override; virtual bool global_menu_is_item_disabled(const String &p_menu_root, int p_idx) const override; + virtual bool global_menu_is_item_hidden(const String &p_menu_root, int p_idx) const override; virtual String global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const override; virtual int global_menu_get_item_state(const String &p_menu_root, int p_idx) const override; virtual int global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const override; @@ -288,11 +301,13 @@ public: virtual void global_menu_set_item_radio_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override; virtual void global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback) override; virtual void global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback) override; + virtual void global_menu_set_item_hover_callbacks(const String &p_menu_root, int p_idx, const Callable &p_callback) override; virtual void global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag) override; virtual void global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text) override; virtual void global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu) override; virtual void global_menu_set_item_accelerator(const String &p_menu_root, int p_idx, Key p_keycode) override; virtual void global_menu_set_item_disabled(const String &p_menu_root, int p_idx, bool p_disabled) override; + virtual void global_menu_set_item_hidden(const String &p_menu_root, int p_idx, bool p_hidden) override; virtual void global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) override; virtual void global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state) override; virtual void global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states) override; diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index bcd8f5c4e5c..338eaf54ad5 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -75,7 +75,7 @@ const NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) cons } else { // Submenu. if (submenu.has(p_menu_root)) { - menu = submenu[p_menu_root]; + menu = submenu[p_menu_root].menu; } } if (menu == apple_menu) { @@ -99,9 +99,10 @@ NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) { NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]]; [n_menu setAutoenablesItems:NO]; [n_menu setDelegate:menu_delegate]; - submenu[p_menu_root] = n_menu; + submenu[p_menu_root].menu = n_menu; + submenu_inv[n_menu] = p_menu_root; } - menu = submenu[p_menu_root]; + menu = submenu[p_menu_root].menu; } if (menu == apple_menu) { // Do not allow to change Apple menu. @@ -609,6 +610,30 @@ NSMenu *DisplayServerMacOS::get_dock_menu() const { return dock_menu; } +void DisplayServerMacOS::menu_open(NSMenu *p_menu) { + if (submenu_inv.has(p_menu)) { + MenuData &md = submenu[submenu_inv[p_menu]]; + md.is_open = true; + if (md.open.is_valid()) { + Variant ret; + Callable::CallError ce; + md.open.callp(nullptr, 0, ret, ce); + } + } +} + +void DisplayServerMacOS::menu_close(NSMenu *p_menu) { + if (submenu_inv.has(p_menu)) { + MenuData &md = submenu[submenu_inv[p_menu]]; + md.is_open = false; + if (md.close.is_valid()) { + Variant ret; + Callable::CallError ce; + md.close.callp(nullptr, 0, ret, ce); + } + } +} + void DisplayServerMacOS::menu_callback(id p_sender) { if (![p_sender representedObject]) { return; @@ -816,6 +841,24 @@ bool DisplayServerMacOS::_has_help_menu() const { } } +bool DisplayServerMacOS::_is_menu_opened(NSMenu *p_menu) const { + if (submenu_inv.has(p_menu)) { + const MenuData &md = submenu[submenu_inv[p_menu]]; + if (md.is_open) { + return true; + } + } + for (NSInteger i = (p_menu == [NSApp mainMenu]) ? 1 : 0; i < [p_menu numberOfItems]; i++) { + const NSMenuItem *menu_item = [p_menu itemAtIndex:i]; + if ([menu_item submenu]) { + if (_is_menu_opened([menu_item submenu])) { + return true; + } + } + } + return false; +} + 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) { @@ -999,6 +1042,23 @@ int DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_roo return out; } +void DisplayServerMacOS::global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callback, const Callable &p_close_callback) { + _THREAD_SAFE_METHOD_ + + if (p_menu_root != "" && p_menu_root.to_lower() != "_main" && p_menu_root.to_lower() != "_dock") { + // Submenu. + if (!submenu.has(p_menu_root)) { + NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]]; + [n_menu setAutoenablesItems:NO]; + [n_menu setDelegate:menu_delegate]; + submenu[p_menu_root].menu = n_menu; + submenu_inv[n_menu] = p_menu_root; + } + submenu[p_menu_root].open = p_open_callback; + submenu[p_menu_root].close = p_close_callback; + } +} + int DisplayServerMacOS::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) { _THREAD_SAFE_METHOD_ @@ -1252,13 +1312,9 @@ String DisplayServerMacOS::global_menu_get_item_submenu(const String &p_menu_roo ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String()); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { - const NSMenu *sub_menu = [menu_item submenu]; - if (sub_menu) { - for (const KeyValue &E : submenu) { - if (E.value == sub_menu) { - return E.key; - } - } + NSMenu *sub_menu = [menu_item submenu]; + if (sub_menu && submenu_inv.has(sub_menu)) { + return submenu_inv[sub_menu]; } } } @@ -1319,6 +1375,24 @@ bool DisplayServerMacOS::global_menu_is_item_disabled(const String &p_menu_root, return false; } +bool DisplayServerMacOS::global_menu_is_item_hidden(const String &p_menu_root, int p_idx) const { + _THREAD_SAFE_METHOD_ + + 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); + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + return [menu_item isHidden]; + } + } + return false; +} + String DisplayServerMacOS::global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const { _THREAD_SAFE_METHOD_ @@ -1498,6 +1572,25 @@ void DisplayServerMacOS::global_menu_set_item_callback(const String &p_menu_root } } +void DisplayServerMacOS::global_menu_set_item_hover_callbacks(const String &p_menu_root, int p_idx, const Callable &p_callback) { + _THREAD_SAFE_METHOD_ + + 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]); + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GodotMenuItem *obj = [menu_item representedObject]; + ERR_FAIL_COND(!obj); + obj->hover_callback = p_callback; + } + } +} + void DisplayServerMacOS::global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback) { _THREAD_SAFE_METHOD_ @@ -1557,6 +1650,23 @@ void DisplayServerMacOS::global_menu_set_item_submenu(const String &p_menu_root, _THREAD_SAFE_METHOD_ 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]); + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) { + ERR_PRINT("Can't remove open menu!"); + return; + } + [menu setSubmenu:nil forItem:menu_item]; + } + return; + } + NSMenu *sub_menu = _get_menu_root(p_submenu); if (menu && sub_menu) { if (sub_menu == menu) { @@ -1591,9 +1701,13 @@ void DisplayServerMacOS::global_menu_set_item_accelerator(const String &p_menu_r ERR_FAIL_COND(p_idx >= [menu numberOfItems]); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { - [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_keycode)]; - String keycode = KeyMappingMacOS::keycode_get_native_string(p_keycode & KeyModifierMask::CODE_MASK); - [menu_item setKeyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + if (p_keycode == Key::NONE) { + [menu_item setKeyEquivalent:@""]; + } else { + [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_keycode)]; + String keycode = KeyMappingMacOS::keycode_get_native_string(p_keycode & KeyModifierMask::CODE_MASK); + [menu_item setKeyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + } } } } @@ -1615,6 +1729,23 @@ void DisplayServerMacOS::global_menu_set_item_disabled(const String &p_menu_root } } +void DisplayServerMacOS::global_menu_set_item_hidden(const String &p_menu_root, int p_idx, bool p_hidden) { + _THREAD_SAFE_METHOD_ + + 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]); + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + [menu_item setHidden:p_hidden]; + } + } +} + void DisplayServerMacOS::global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) { _THREAD_SAFE_METHOD_ @@ -1742,6 +1873,11 @@ void DisplayServerMacOS::global_menu_remove_item(const String &p_menu_root, int p_idx++; } ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) { + ERR_PRINT("Can't remove open menu!"); + return; + } [menu removeItemAtIndex:p_idx]; } } @@ -1751,6 +1887,10 @@ void DisplayServerMacOS::global_menu_clear(const String &p_menu_root) { NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { + if (_is_menu_opened(menu)) { + ERR_PRINT("Can't remove open menu!"); + return; + } [menu removeAllItems]; // Restore Apple menu. if (menu == [NSApp mainMenu]) { @@ -1758,6 +1898,7 @@ void DisplayServerMacOS::global_menu_clear(const String &p_menu_root) { [menu setSubmenu:apple_menu forItem:menu_item]; } if (submenu.has(p_menu_root)) { + submenu_inv.erase(submenu[p_menu_root].menu); submenu.erase(p_menu_root); } } @@ -4188,15 +4329,19 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM nsappname = [[NSProcessInfo processInfo] processName]; } + menu_delegate = [[GodotMenuDelegate alloc] init]; + // Setup Dock menu. dock_menu = [[NSMenu alloc] initWithTitle:@"_dock"]; [dock_menu setAutoenablesItems:NO]; + [dock_menu setDelegate:menu_delegate]; // Setup Apple menu. apple_menu = [[NSMenu alloc] initWithTitle:@""]; title = [NSString stringWithFormat:NSLocalizedString(@"About %@", nil), nsappname]; [apple_menu addItemWithTitle:title action:@selector(showAbout:) keyEquivalent:@""]; [apple_menu setAutoenablesItems:NO]; + [apple_menu setDelegate:menu_delegate]; [apple_menu addItem:[NSMenuItem separatorItem]]; @@ -4226,8 +4371,6 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM [main_menu setSubmenu:apple_menu forItem:menu_item]; [main_menu setAutoenablesItems:NO]; - menu_delegate = [[GodotMenuDelegate alloc] init]; - //!!!!!!!!!!!!!!!!!!!!!!!!!! //TODO - do Vulkan and OpenGL support checks, driver selection and fallback rendering_driver = p_rendering_driver; diff --git a/platform/macos/godot_menu_delegate.mm b/platform/macos/godot_menu_delegate.mm index ebfe8b1f6d1..1bfcfa7d9e2 100644 --- a/platform/macos/godot_menu_delegate.mm +++ b/platform/macos/godot_menu_delegate.mm @@ -39,6 +39,34 @@ - (void)doNothing:(id)sender { } +- (void)menuNeedsUpdate:(NSMenu *)menu { + if (DisplayServer::get_singleton()) { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + ds->menu_open(menu); + } +} + +- (void)menuDidClose:(NSMenu *)menu { + if (DisplayServer::get_singleton()) { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + ds->menu_close(menu); + } +} + +- (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item { + if (item) { + GodotMenuItem *value = [item representedObject]; + if (value && value->hover_callback != Callable()) { + // If custom callback is set, use it. + Variant tag = value->meta; + Variant *tagp = &tag; + Variant ret; + Callable::CallError ce; + value->hover_callback.callp((const Variant **)&tagp, 1, ret, ce); + } + } +} + - (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action { NSString *ev_key = [[event charactersIgnoringModifiers] lowercaseString]; NSUInteger ev_modifiers = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask; diff --git a/platform/macos/godot_menu_item.h b/platform/macos/godot_menu_item.h index 8876ceae6a7..b816ea1b3a0 100644 --- a/platform/macos/godot_menu_item.h +++ b/platform/macos/godot_menu_item.h @@ -46,6 +46,7 @@ enum GlobalMenuCheckType { @public Callable callback; Callable key_callback; + Callable hover_callback; Variant meta; GlobalMenuCheckType checkable_type; int max_states; diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp index 13a42d04076..371d6c69af4 100644 --- a/scene/gui/menu_bar.cpp +++ b/scene/gui/menu_bar.cpp @@ -202,52 +202,6 @@ void MenuBar::_popup_visibility_changed(bool p_visible) { } } -void MenuBar::_update_submenu(const String &p_menu_name, PopupMenu *p_child) { - int count = p_child->get_item_count(); - global_menus.insert(p_menu_name); - for (int i = 0; i < count; i++) { - if (p_child->is_item_separator(i)) { - DisplayServer::get_singleton()->global_menu_add_separator(p_menu_name); - } else if (!p_child->get_item_submenu(i).is_empty()) { - Node *n = p_child->get_node_or_null(p_child->get_item_submenu(i)); - ERR_FAIL_NULL_MSG(n, "Item subnode does not exist: '" + p_child->get_item_submenu(i) + "'."); - PopupMenu *pm = Object::cast_to(n); - ERR_FAIL_NULL_MSG(pm, "Item subnode is not a PopupMenu: '" + p_child->get_item_submenu(i) + "'."); - - DisplayServer::get_singleton()->global_menu_add_submenu_item(p_menu_name, atr(p_child->get_item_text(i)), p_menu_name + "/" + itos(i)); - _update_submenu(p_menu_name + "/" + itos(i), pm); - } else { - int index = DisplayServer::get_singleton()->global_menu_add_item(p_menu_name, atr(p_child->get_item_text(i)), callable_mp(p_child, &PopupMenu::activate_item), Callable(), i); - - if (p_child->is_item_checkable(i)) { - DisplayServer::get_singleton()->global_menu_set_item_checkable(p_menu_name, index, true); - } - if (p_child->is_item_radio_checkable(i)) { - DisplayServer::get_singleton()->global_menu_set_item_radio_checkable(p_menu_name, index, true); - } - DisplayServer::get_singleton()->global_menu_set_item_checked(p_menu_name, index, p_child->is_item_checked(i)); - DisplayServer::get_singleton()->global_menu_set_item_disabled(p_menu_name, index, p_child->is_item_disabled(i)); - DisplayServer::get_singleton()->global_menu_set_item_max_states(p_menu_name, index, p_child->get_item_max_states(i)); - DisplayServer::get_singleton()->global_menu_set_item_icon(p_menu_name, index, p_child->get_item_icon(i)); - DisplayServer::get_singleton()->global_menu_set_item_state(p_menu_name, index, p_child->get_item_state(i)); - DisplayServer::get_singleton()->global_menu_set_item_indentation_level(p_menu_name, index, p_child->get_item_indent(i)); - DisplayServer::get_singleton()->global_menu_set_item_tooltip(p_menu_name, index, p_child->get_item_tooltip(i)); - if (!p_child->is_item_shortcut_disabled(i) && p_child->get_item_shortcut(i).is_valid() && p_child->get_item_shortcut(i)->has_valid_event()) { - Array events = p_child->get_item_shortcut(i)->get_events(); - for (int j = 0; j < events.size(); j++) { - Ref ie = events[j]; - if (ie.is_valid()) { - DisplayServer::get_singleton()->global_menu_set_item_accelerator(p_menu_name, index, ie->get_keycode_with_modifiers()); - break; - } - } - } else if (p_child->get_item_accelerator(i) != Key::NONE) { - DisplayServer::get_singleton()->global_menu_set_item_accelerator(p_menu_name, i, p_child->get_item_accelerator(i)); - } - } - } -} - bool MenuBar::is_native_menu() const { #ifdef TOOLS_ENABLED if (is_part_of_edited_scene()) { @@ -258,52 +212,67 @@ bool MenuBar::is_native_menu() const { return (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU) && is_native); } -void MenuBar::_clear_menu() { +String MenuBar::bind_global_menu() { +#ifdef TOOLS_ENABLED + if (is_part_of_edited_scene()) { + return String(); + } +#endif if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) { - return; + return String(); } - // Remove root menu items. - int count = DisplayServer::get_singleton()->global_menu_get_item_count("_main"); - for (int i = count - 1; i >= 0; i--) { - if (global_menus.has(DisplayServer::get_singleton()->global_menu_get_item_submenu("_main", i))) { - DisplayServer::get_singleton()->global_menu_remove_item("_main", i); + if (!global_menu_name.is_empty()) { + return global_menu_name; // Already bound. + } + + DisplayServer *ds = DisplayServer::get_singleton(); + global_menu_name = "__MenuBar#" + itos(get_instance_id()); + + int global_start_idx = -1; + int count = ds->global_menu_get_item_count("_main"); + String prev_tag; + for (int i = 0; i < count; i++) { + String tag = ds->global_menu_get_item_tag("_main", i).operator String().get_slice("#", 1); + if (!tag.is_empty() && tag != prev_tag) { + if (i >= start_index) { + global_start_idx = i; + break; + } } + prev_tag = tag; } - // Erase submenu contents. - for (const String &E : global_menus) { - DisplayServer::get_singleton()->global_menu_clear(E); + if (global_start_idx == -1) { + global_start_idx = count; } - global_menus.clear(); + + 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); + } + + return global_menu_name; } -void MenuBar::_update_menu() { - _clear_menu(); - - if (!is_visible_in_tree()) { +void MenuBar::unbind_global_menu() { + if (global_menu_name.is_empty()) { return; } - int index = start_index; - if (is_native_menu()) { - Vector popups = _get_popups(); - String root_name = "MenuBar<" + String::num_int64((uint64_t)this, 16) + ">"; - for (int i = 0; i < popups.size(); i++) { - if (menu_cache[i].hidden) { - continue; - } - String menu_name = atr(String(popups[i]->get_meta("_menu_name", popups[i]->get_name()))); - - index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", menu_name, root_name + "/" + itos(i), index); - if (menu_cache[i].disabled) { - DisplayServer::get_singleton()->global_menu_set_item_disabled("_main", index, true); - } - _update_submenu(root_name + "/" + itos(i), popups[i]); - index++; - } + DisplayServer *ds = DisplayServer::get_singleton(); + 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); } - update_minimum_size(); - queue_redraw(); + + global_menu_name = String(); } void MenuBar::_notification(int p_what) { @@ -312,25 +281,43 @@ void MenuBar::_notification(int p_what) { if (get_menu_count() > 0) { _refresh_menu_names(); } + if (is_native_menu()) { + bind_global_menu(); + } } break; case NOTIFICATION_EXIT_TREE: { - _clear_menu(); + unbind_global_menu(); } break; case NOTIFICATION_MOUSE_EXIT: { focused_menu = -1; selected_menu = -1; queue_redraw(); } break; - case NOTIFICATION_TRANSLATION_CHANGED: + case NOTIFICATION_TRANSLATION_CHANGED: { + DisplayServer *ds = DisplayServer::get_singleton(); + bool is_global = !global_menu_name.is_empty(); + int global_start = _find_global_start_index(); + for (int i = 0; i < menu_cache.size(); i++) { + shape(menu_cache.write[i]); + if (is_global) { + ds->global_menu_set_item_text("_main", global_start + i, atr(menu_cache[i].name)); + } + } + } break; case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_THEME_CHANGED: { for (int i = 0; i < menu_cache.size(); i++) { shape(menu_cache.write[i]); } - _update_menu(); } break; case NOTIFICATION_VISIBILITY_CHANGED: { - _update_menu(); + if (is_native_menu()) { + if (is_visible_in_tree()) { + bind_global_menu(); + } else { + unbind_global_menu(); + } + } } break; case NOTIFICATION_DRAW: { if (is_native_menu()) { @@ -512,14 +499,20 @@ void MenuBar::shape(Menu &p_menu) { } void MenuBar::_refresh_menu_names() { + DisplayServer *ds = DisplayServer::get_singleton(); + bool is_global = !global_menu_name.is_empty(); + int global_start = _find_global_start_index(); + Vector popups = _get_popups(); for (int i = 0; i < popups.size(); i++) { if (!popups[i]->has_meta("_menu_name") && String(popups[i]->get_name()) != get_menu_title(i)) { menu_cache.write[i].name = popups[i]->get_name(); shape(menu_cache.write[i]); + if (is_global) { + ds->global_menu_set_item_text("_main", global_start + i, atr(menu_cache[i].name)); + } } } - _update_menu(); } Vector MenuBar::_get_popups() const { @@ -560,11 +553,14 @@ void MenuBar::add_child_notify(Node *p_child) { menu_cache.push_back(menu); p_child->connect("renamed", callable_mp(this, &MenuBar::_refresh_menu_names)); - p_child->connect("menu_changed", callable_mp(this, &MenuBar::_update_menu)); p_child->connect("about_to_popup", callable_mp(this, &MenuBar::_popup_visibility_changed).bind(true)); p_child->connect("popup_hide", callable_mp(this, &MenuBar::_popup_visibility_changed).bind(false)); - _update_menu(); + 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)); + } } void MenuBar::move_child_notify(Node *p_child) { @@ -586,9 +582,20 @@ void MenuBar::move_child_notify(Node *p_child) { } Menu menu = menu_cache[old_idx]; menu_cache.remove_at(old_idx); - menu_cache.insert(get_menu_idx_from_control(pm), menu); + int new_idx = get_menu_idx_from_control(pm); + menu_cache.insert(new_idx, menu); - _update_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)); + } + } } void MenuBar::remove_child_notify(Node *p_child) { @@ -603,15 +610,17 @@ 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); + } + p_child->remove_meta("_menu_name"); p_child->remove_meta("_menu_tooltip"); p_child->disconnect("renamed", callable_mp(this, &MenuBar::_refresh_menu_names)); - p_child->disconnect("menu_changed", callable_mp(this, &MenuBar::_update_menu)); p_child->disconnect("about_to_popup", callable_mp(this, &MenuBar::_popup_visibility_changed)); p_child->disconnect("popup_hide", callable_mp(this, &MenuBar::_popup_visibility_changed)); - - _update_menu(); } void MenuBar::_bind_methods() { @@ -699,7 +708,8 @@ void MenuBar::set_text_direction(Control::TextDirection p_text_direction) { ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); if (text_direction != p_text_direction) { text_direction = p_text_direction; - _update_menu(); + update_minimum_size(); + queue_redraw(); } } @@ -710,7 +720,8 @@ Control::TextDirection MenuBar::get_text_direction() const { void MenuBar::set_language(const String &p_language) { if (language != p_language) { language = p_language; - _update_menu(); + update_minimum_size(); + queue_redraw(); } } @@ -732,7 +743,10 @@ bool MenuBar::is_flat() const { void MenuBar::set_start_index(int p_index) { if (start_index != p_index) { start_index = p_index; - _update_menu(); + if (!global_menu_name.is_empty()) { + unbind_global_menu(); + bind_global_menu(); + } } } @@ -742,11 +756,12 @@ int MenuBar::get_start_index() const { void MenuBar::set_prefer_global_menu(bool p_enabled) { if (is_native != p_enabled) { - if (is_native) { - _clear_menu(); - } is_native = p_enabled; - _update_menu(); + if (is_native) { + bind_global_menu(); + } else { + unbind_global_menu(); + } } } @@ -790,7 +805,9 @@ void MenuBar::set_menu_title(int p_menu, const String &p_title) { } menu_cache.write[p_menu].name = p_title; shape(menu_cache.write[p_menu]); - _update_menu(); + if (!global_menu_name.is_empty()) { + DisplayServer::get_singleton()->global_menu_set_item_text("_main", _find_global_start_index() + p_menu, atr(menu_cache[p_menu].name)); + } } String MenuBar::get_menu_title(int p_menu) const { @@ -802,7 +819,10 @@ void MenuBar::set_menu_tooltip(int p_menu, const String &p_tooltip) { ERR_FAIL_INDEX(p_menu, menu_cache.size()); PopupMenu *pm = get_menu_popup(p_menu); pm->set_meta("_menu_tooltip", p_tooltip); - menu_cache.write[p_menu].name = p_tooltip; + menu_cache.write[p_menu].tooltip = p_tooltip; + if (!global_menu_name.is_empty()) { + DisplayServer::get_singleton()->global_menu_set_item_tooltip("_main", _find_global_start_index() + p_menu, p_tooltip); + } } String MenuBar::get_menu_tooltip(int p_menu) const { @@ -813,7 +833,9 @@ String MenuBar::get_menu_tooltip(int p_menu) const { void MenuBar::set_menu_disabled(int p_menu, bool p_disabled) { ERR_FAIL_INDEX(p_menu, menu_cache.size()); menu_cache.write[p_menu].disabled = p_disabled; - _update_menu(); + if (!global_menu_name.is_empty()) { + DisplayServer::get_singleton()->global_menu_set_item_disabled("_main", _find_global_start_index() + p_menu, p_disabled); + } } bool MenuBar::is_menu_disabled(int p_menu) const { @@ -824,7 +846,9 @@ bool MenuBar::is_menu_disabled(int p_menu) const { void MenuBar::set_menu_hidden(int p_menu, bool p_hidden) { ERR_FAIL_INDEX(p_menu, menu_cache.size()); menu_cache.write[p_menu].hidden = p_hidden; - _update_menu(); + if (!global_menu_name.is_empty()) { + DisplayServer::get_singleton()->global_menu_set_item_hidden("_main", _find_global_start_index() + p_menu, p_hidden); + } } bool MenuBar::is_menu_hidden(int p_menu) const { diff --git a/scene/gui/menu_bar.h b/scene/gui/menu_bar.h index 4d6e76d9b67..ba4df5f2294 100644 --- a/scene/gui/menu_bar.h +++ b/scene/gui/menu_bar.h @@ -66,7 +66,6 @@ class MenuBar : public Control { } }; Vector menu_cache; - HashSet global_menus; int focused_menu = -1; int selected_menu = -1; @@ -114,9 +113,23 @@ class MenuBar : public Control { void _open_popup(int p_index, bool p_focus_item = false); void _popup_visibility_changed(bool p_visible); - void _update_submenu(const String &p_menu_name, PopupMenu *p_child); - void _clear_menu(); - void _update_menu(); + + String global_menu_name; + + int _find_global_start_index() { + if (global_menu_name.is_empty()) { + return -1; + } + + DisplayServer *ds = DisplayServer::get_singleton(); + int count = ds->global_menu_get_item_count("_main"); + for (int i = 0; i < count; i++) { + if (ds->global_menu_get_item_tag("_main", i).operator String().begins_with(global_menu_name)) { + return i; + } + } + return -1; + } protected: virtual void shortcut_input(const Ref &p_event) override; @@ -130,6 +143,9 @@ protected: public: virtual void gui_input(const Ref &p_event) override; + String bind_global_menu(); + void unbind_global_menu(); + void set_switch_on_hover(bool p_enabled); bool is_switch_on_hover(); void set_disable_shortcuts(bool p_disabled); diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 54fd8b87454..e3b0a18325a 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -40,6 +40,86 @@ #include "scene/gui/menu_bar.h" #include "scene/theme/theme_db.h" +String PopupMenu::bind_global_menu() { +#ifdef TOOLS_ENABLED + if (is_part_of_edited_scene()) { + return String(); + } +#endif + if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) { + return String(); + } + + if (!global_menu_name.is_empty()) { + return global_menu_name; // Already bound; + } + + DisplayServer *ds = DisplayServer::get_singleton(); + global_menu_name = "__PopupMenu#" + itos(get_instance_id()); + 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]; + if (item.separator) { + ds->global_menu_add_separator(global_menu_name); + } else { + int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), i); + if (!item.submenu.is_empty()) { + PopupMenu *pm = Object::cast_to(get_node_or_null(item.submenu)); + if (pm) { + String submenu_name = pm->bind_global_menu(); + ds->global_menu_set_item_submenu(global_menu_name, index, submenu_name); + item.submenu_bound = true; + } + } + if (item.checkable_type == Item::CHECKABLE_TYPE_CHECK_BOX) { + ds->global_menu_set_item_checkable(global_menu_name, index, true); + } else if (item.checkable_type == Item::CHECKABLE_TYPE_RADIO_BUTTON) { + ds->global_menu_set_item_radio_checkable(global_menu_name, index, true); + } + ds->global_menu_set_item_checked(global_menu_name, index, item.checked); + ds->global_menu_set_item_disabled(global_menu_name, index, item.disabled); + ds->global_menu_set_item_max_states(global_menu_name, index, item.max_states); + ds->global_menu_set_item_icon(global_menu_name, index, item.icon); + ds->global_menu_set_item_state(global_menu_name, index, item.state); + ds->global_menu_set_item_indentation_level(global_menu_name, index, item.indent); + ds->global_menu_set_item_tooltip(global_menu_name, index, item.tooltip); + if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) { + Array events = item.shortcut->get_events(); + for (int j = 0; j < events.size(); j++) { + Ref ie = events[j]; + if (ie.is_valid()) { + ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers()); + break; + } + } + } else if (item.accel != Key::NONE) { + ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel); + } + } + } + return global_menu_name; +} + +void PopupMenu::unbind_global_menu() { + if (global_menu_name.is_empty()) { + return; + } + + for (int i = 0; i < items.size(); i++) { + Item &item = items.write[i]; + if (!item.submenu.is_empty()) { + PopupMenu *pm = Object::cast_to(get_node_or_null(item.submenu)); + if (pm) { + pm->unbind_global_menu(); + } + item.submenu_bound = false; + } + } + DisplayServer::get_singleton()->global_menu_clear(global_menu_name); + + global_menu_name = String(); +} + String PopupMenu::_get_accel_text(const Item &p_item) const { if (p_item.shortcut.is_valid()) { return p_item.shortcut->get_as_text(); @@ -821,11 +901,17 @@ void PopupMenu::_menu_changed() { void PopupMenu::add_child_notify(Node *p_child) { Window::add_child_notify(p_child); - PopupMenu *pm = Object::cast_to(p_child); - if (!pm) { - return; + if (Object::cast_to(p_child) && !global_menu_name.is_empty()) { + String node_name = p_child->get_name(); + PopupMenu *pm = Object::cast_to(get_node_or_null(node_name)); + for (int i = 0; i < items.size(); i++) { + if (items[i].submenu == node_name) { + String submenu_name = pm->bind_global_menu(); + DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, i, submenu_name); + items.write[i].submenu_bound = true; + } + } } - p_child->connect("menu_changed", callable_mp(this, &PopupMenu::_menu_changed)); _menu_changed(); } @@ -836,7 +922,16 @@ void PopupMenu::remove_child_notify(Node *p_child) { if (!pm) { return; } - p_child->disconnect("menu_changed", callable_mp(this, &PopupMenu::_menu_changed)); + if (Object::cast_to(p_child) && !global_menu_name.is_empty()) { + String node_name = p_child->get_name(); + for (int i = 0; i < items.size(); i++) { + if (items[i].submenu == node_name) { + DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, i, String()); + items.write[i].submenu_bound = false; + } + } + pm->unbind_global_menu(); + } _menu_changed(); } @@ -857,9 +952,15 @@ void PopupMenu::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_TRANSLATION_CHANGED: { + DisplayServer *ds = DisplayServer::get_singleton(); + bool is_global = !global_menu_name.is_empty(); for (int i = 0; i < items.size(); i++) { - items.write[i].xl_text = atr(items[i].text); - items.write[i].dirty = true; + Item &item = items.write[i]; + item.xl_text = atr(item.text); + item.dirty = true; + if (is_global) { + ds->global_menu_set_item_text(global_menu_name, i, item.xl_text); + } _shape_item(i); } @@ -1031,6 +1132,14 @@ void PopupMenu::add_item(const String &p_label, int p_id, Key p_accel) { ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); items.push_back(item); + if (!global_menu_name.is_empty()) { + DisplayServer *ds = DisplayServer::get_singleton(); + int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1); + if (item.accel != Key::NONE) { + ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel); + } + } + _shape_item(items.size() - 1); control->queue_redraw(); @@ -1045,6 +1154,15 @@ void PopupMenu::add_icon_item(const Ref &p_icon, const String &p_labe item.icon = p_icon; items.push_back(item); + if (!global_menu_name.is_empty()) { + DisplayServer *ds = DisplayServer::get_singleton(); + int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1); + if (item.accel != Key::NONE) { + ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel); + } + ds->global_menu_set_item_icon(global_menu_name, index, item.icon); + } + _shape_item(items.size() - 1); control->queue_redraw(); @@ -1059,10 +1177,20 @@ void PopupMenu::add_check_item(const String &p_label, int p_id, Key p_accel) { item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; items.push_back(item); + if (!global_menu_name.is_empty()) { + DisplayServer *ds = DisplayServer::get_singleton(); + int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1); + if (item.accel != Key::NONE) { + ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel); + } + ds->global_menu_set_item_checkable(global_menu_name, index, true); + } + _shape_item(items.size() - 1); control->queue_redraw(); child_controls_changed(); + notify_property_list_changed(); _menu_changed(); } @@ -1073,10 +1201,22 @@ void PopupMenu::add_icon_check_item(const Ref &p_icon, const String & item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; items.push_back(item); + if (!global_menu_name.is_empty()) { + DisplayServer *ds = DisplayServer::get_singleton(); + int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1); + if (item.accel != Key::NONE) { + ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel); + } + ds->global_menu_set_item_icon(global_menu_name, index, item.icon); + ds->global_menu_set_item_checkable(global_menu_name, index, true); + } + _shape_item(items.size() - 1); control->queue_redraw(); child_controls_changed(); + notify_property_list_changed(); + _menu_changed(); } void PopupMenu::add_radio_check_item(const String &p_label, int p_id, Key p_accel) { @@ -1085,10 +1225,20 @@ void PopupMenu::add_radio_check_item(const String &p_label, int p_id, Key p_acce item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; items.push_back(item); + if (!global_menu_name.is_empty()) { + DisplayServer *ds = DisplayServer::get_singleton(); + int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1); + if (item.accel != Key::NONE) { + ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel); + } + ds->global_menu_set_item_radio_checkable(global_menu_name, index, true); + } + _shape_item(items.size() - 1); control->queue_redraw(); child_controls_changed(); + notify_property_list_changed(); _menu_changed(); } @@ -1099,10 +1249,21 @@ void PopupMenu::add_icon_radio_check_item(const Ref &p_icon, const St item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; items.push_back(item); + if (!global_menu_name.is_empty()) { + DisplayServer *ds = DisplayServer::get_singleton(); + int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1); + if (item.accel != Key::NONE) { + ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel); + } + ds->global_menu_set_item_icon(global_menu_name, index, item.icon); + ds->global_menu_set_item_radio_checkable(global_menu_name, index, true); + } + _shape_item(items.size() - 1); control->queue_redraw(); child_controls_changed(); + notify_property_list_changed(); _menu_changed(); } @@ -1113,11 +1274,22 @@ void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int item.state = p_default_state; items.push_back(item); + if (!global_menu_name.is_empty()) { + DisplayServer *ds = DisplayServer::get_singleton(); + int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1); + if (item.accel != Key::NONE) { + ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel); + } + ds->global_menu_set_item_max_states(global_menu_name, index, item.max_states); + ds->global_menu_set_item_state(global_menu_name, index, item.state); + } + _shape_item(items.size() - 1); control->queue_redraw(); child_controls_changed(); _menu_changed(); + notify_property_list_changed(); } #define ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, p_allow_echo) \ @@ -1135,10 +1307,26 @@ void PopupMenu::add_shortcut(const Ref &p_shortcut, int p_id, bool p_g ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, p_allow_echo); items.push_back(item); + if (!global_menu_name.is_empty()) { + DisplayServer *ds = DisplayServer::get_singleton(); + int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1); + if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) { + Array events = item.shortcut->get_events(); + for (int j = 0; j < events.size(); j++) { + Ref ie = events[j]; + if (ie.is_valid()) { + ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers()); + break; + } + } + } + } + _shape_item(items.size() - 1); control->queue_redraw(); child_controls_changed(); + notify_property_list_changed(); _menu_changed(); } @@ -1148,10 +1336,27 @@ void PopupMenu::add_icon_shortcut(const Ref &p_icon, const Refglobal_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1); + if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) { + Array events = item.shortcut->get_events(); + for (int j = 0; j < events.size(); j++) { + Ref ie = events[j]; + if (ie.is_valid()) { + ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers()); + break; + } + } + } + ds->global_menu_set_item_icon(global_menu_name, index, item.icon); + } + _shape_item(items.size() - 1); control->queue_redraw(); child_controls_changed(); + notify_property_list_changed(); _menu_changed(); } @@ -1161,10 +1366,27 @@ void PopupMenu::add_check_shortcut(const Ref &p_shortcut, int p_id, bo item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; items.push_back(item); + if (!global_menu_name.is_empty()) { + DisplayServer *ds = DisplayServer::get_singleton(); + int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1); + if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) { + Array events = item.shortcut->get_events(); + for (int j = 0; j < events.size(); j++) { + Ref ie = events[j]; + if (ie.is_valid()) { + ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers()); + break; + } + } + } + ds->global_menu_set_item_checkable(global_menu_name, index, true); + } + _shape_item(items.size() - 1); control->queue_redraw(); child_controls_changed(); + notify_property_list_changed(); _menu_changed(); } @@ -1175,10 +1397,28 @@ void PopupMenu::add_icon_check_shortcut(const Ref &p_icon, const Ref< item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; items.push_back(item); + if (!global_menu_name.is_empty()) { + DisplayServer *ds = DisplayServer::get_singleton(); + int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1); + if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) { + Array events = item.shortcut->get_events(); + for (int j = 0; j < events.size(); j++) { + Ref ie = events[j]; + if (ie.is_valid()) { + ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers()); + break; + } + } + } + ds->global_menu_set_item_icon(global_menu_name, index, item.icon); + ds->global_menu_set_item_checkable(global_menu_name, index, true); + } + _shape_item(items.size() - 1); control->queue_redraw(); child_controls_changed(); + notify_property_list_changed(); _menu_changed(); } @@ -1188,10 +1428,27 @@ void PopupMenu::add_radio_check_shortcut(const Ref &p_shortcut, int p_ item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; items.push_back(item); + if (!global_menu_name.is_empty()) { + DisplayServer *ds = DisplayServer::get_singleton(); + int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1); + if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) { + Array events = item.shortcut->get_events(); + for (int j = 0; j < events.size(); j++) { + Ref ie = events[j]; + if (ie.is_valid()) { + ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers()); + break; + } + } + } + ds->global_menu_set_item_radio_checkable(global_menu_name, index, true); + } + _shape_item(items.size() - 1); control->queue_redraw(); child_controls_changed(); + notify_property_list_changed(); _menu_changed(); } @@ -1202,10 +1459,28 @@ void PopupMenu::add_icon_radio_check_shortcut(const Ref &p_icon, cons item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; items.push_back(item); + if (!global_menu_name.is_empty()) { + DisplayServer *ds = DisplayServer::get_singleton(); + int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1); + if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) { + Array events = item.shortcut->get_events(); + for (int j = 0; j < events.size(); j++) { + Ref ie = events[j]; + if (ie.is_valid()) { + ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers()); + break; + } + } + } + ds->global_menu_set_item_icon(global_menu_name, index, item.icon); + ds->global_menu_set_item_radio_checkable(global_menu_name, index, true); + } + _shape_item(items.size() - 1); control->queue_redraw(); child_controls_changed(); + notify_property_list_changed(); _menu_changed(); } @@ -1217,10 +1492,22 @@ void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, item.submenu = p_submenu; items.push_back(item); + if (!global_menu_name.is_empty()) { + DisplayServer *ds = DisplayServer::get_singleton(); + int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1); + PopupMenu *pm = Object::cast_to(get_node_or_null(item.submenu)); // Find first menu with this name. + if (pm) { + String submenu_name = pm->bind_global_menu(); + ds->global_menu_set_item_submenu(global_menu_name, index, submenu_name); + items.write[index].submenu_bound = true; + } + } + _shape_item(items.size() - 1); control->queue_redraw(); child_controls_changed(); + notify_property_list_changed(); _menu_changed(); } @@ -1240,6 +1527,10 @@ void PopupMenu::set_item_text(int p_idx, const String &p_text) { items.write[p_idx].text = p_text; items.write[p_idx].xl_text = atr(p_text); items.write[p_idx].dirty = true; + + if (!global_menu_name.is_empty()) { + DisplayServer::get_singleton()->global_menu_set_item_text(global_menu_name, p_idx, items[p_idx].xl_text); + } _shape_item(p_idx); control->queue_redraw(); @@ -1284,6 +1575,10 @@ void PopupMenu::set_item_icon(int p_idx, const Ref &p_icon) { items.write[p_idx].icon = p_icon; + if (!global_menu_name.is_empty()) { + DisplayServer::get_singleton()->global_menu_set_item_icon(global_menu_name, p_idx, items[p_idx].icon); + } + control->queue_redraw(); child_controls_changed(); _menu_changed(); @@ -1332,6 +1627,10 @@ void PopupMenu::set_item_checked(int p_idx, bool p_checked) { items.write[p_idx].checked = p_checked; + if (!global_menu_name.is_empty()) { + DisplayServer::get_singleton()->global_menu_set_item_checked(global_menu_name, p_idx, p_checked); + } + control->queue_redraw(); child_controls_changed(); _menu_changed(); @@ -1349,6 +1648,10 @@ void PopupMenu::set_item_id(int p_idx, int p_id) { items.write[p_idx].id = p_id; + if (!global_menu_name.is_empty()) { + DisplayServer::get_singleton()->global_menu_set_item_tag(global_menu_name, p_idx, p_id); + } + control->queue_redraw(); child_controls_changed(); _menu_changed(); @@ -1367,6 +1670,10 @@ void PopupMenu::set_item_accelerator(int p_idx, Key p_accel) { items.write[p_idx].accel = p_accel; items.write[p_idx].dirty = true; + if (!global_menu_name.is_empty()) { + DisplayServer::get_singleton()->global_menu_set_item_accelerator(global_menu_name, p_idx, p_accel); + } + control->queue_redraw(); child_controls_changed(); _menu_changed(); @@ -1383,7 +1690,6 @@ void PopupMenu::set_item_metadata(int p_idx, const Variant &p_meta) { } items.write[p_idx].metadata = p_meta; - control->queue_redraw(); child_controls_changed(); _menu_changed(); } @@ -1399,6 +1705,11 @@ void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) { } items.write[p_idx].disabled = p_disabled; + + if (!global_menu_name.is_empty()) { + DisplayServer::get_singleton()->global_menu_set_item_disabled(global_menu_name, p_idx, p_disabled); + } + control->queue_redraw(); child_controls_changed(); _menu_changed(); @@ -1414,7 +1725,30 @@ void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) { return; } + if (!global_menu_name.is_empty()) { + if (items[p_idx].submenu_bound) { + PopupMenu *pm = Object::cast_to(get_node_or_null(items[p_idx].submenu)); + if (pm) { + DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, p_idx, String()); + pm->unbind_global_menu(); + } + items.write[p_idx].submenu_bound = false; + } + } + items.write[p_idx].submenu = p_submenu; + + if (!global_menu_name.is_empty()) { + if (!items[p_idx].submenu.is_empty()) { + PopupMenu *pm = Object::cast_to(get_node_or_null(items[p_idx].submenu)); + if (pm) { + String submenu_name = pm->bind_global_menu(); + DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, p_idx, submenu_name); + items.write[p_idx].submenu_bound = true; + } + } + } + control->queue_redraw(); child_controls_changed(); _menu_changed(); @@ -1423,6 +1757,11 @@ void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) { void PopupMenu::toggle_item_checked(int p_idx) { ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].checked = !items[p_idx].checked; + + if (!global_menu_name.is_empty()) { + DisplayServer::get_singleton()->global_menu_set_item_checked(global_menu_name, p_idx, items[p_idx].checked); + } + control->queue_redraw(); child_controls_changed(); _menu_changed(); @@ -1569,6 +1908,11 @@ void PopupMenu::set_item_as_checkable(int p_idx, bool p_checkable) { } items.write[p_idx].checkable_type = p_checkable ? Item::CHECKABLE_TYPE_CHECK_BOX : Item::CHECKABLE_TYPE_NONE; + + if (!global_menu_name.is_empty()) { + DisplayServer::get_singleton()->global_menu_set_item_checkable(global_menu_name, p_idx, p_checkable); + } + control->queue_redraw(); _menu_changed(); } @@ -1585,6 +1929,11 @@ void PopupMenu::set_item_as_radio_checkable(int p_idx, bool p_radio_checkable) { } items.write[p_idx].checkable_type = p_radio_checkable ? Item::CHECKABLE_TYPE_RADIO_BUTTON : Item::CHECKABLE_TYPE_NONE; + + if (!global_menu_name.is_empty()) { + DisplayServer::get_singleton()->global_menu_set_item_radio_checkable(global_menu_name, p_idx, p_radio_checkable); + } + control->queue_redraw(); _menu_changed(); } @@ -1600,6 +1949,11 @@ void PopupMenu::set_item_tooltip(int p_idx, const String &p_tooltip) { } items.write[p_idx].tooltip = p_tooltip; + + if (!global_menu_name.is_empty()) { + DisplayServer::get_singleton()->global_menu_set_item_tooltip(global_menu_name, p_idx, p_tooltip); + } + control->queue_redraw(); _menu_changed(); } @@ -1625,6 +1979,21 @@ void PopupMenu::set_item_shortcut(int p_idx, const Ref &p_shortcut, bo _ref_shortcut(items[p_idx].shortcut); } + if (!global_menu_name.is_empty()) { + DisplayServer *ds = DisplayServer::get_singleton(); + ds->global_menu_set_item_accelerator(global_menu_name, p_idx, Key::NONE); + if (!items[p_idx].shortcut_is_disabled && items[p_idx].shortcut.is_valid() && items[p_idx].shortcut->has_valid_event()) { + Array events = items[p_idx].shortcut->get_events(); + for (int j = 0; j < events.size(); j++) { + Ref ie = events[j]; + if (ie.is_valid()) { + ds->global_menu_set_item_accelerator(global_menu_name, p_idx, ie->get_keycode_with_modifiers()); + break; + } + } + } + } + control->queue_redraw(); _menu_changed(); } @@ -1640,6 +2009,10 @@ void PopupMenu::set_item_indent(int p_idx, int p_indent) { } items.write[p_idx].indent = p_indent; + if (!global_menu_name.is_empty()) { + DisplayServer::get_singleton()->global_menu_set_item_indentation_level(global_menu_name, p_idx, p_indent); + } + control->queue_redraw(); child_controls_changed(); _menu_changed(); @@ -1656,6 +2029,11 @@ void PopupMenu::set_item_multistate(int p_idx, int p_state) { } items.write[p_idx].state = p_state; + + if (!global_menu_name.is_empty()) { + DisplayServer::get_singleton()->global_menu_set_item_state(global_menu_name, p_idx, p_state); + } + control->queue_redraw(); _menu_changed(); } @@ -1671,6 +2049,22 @@ void PopupMenu::set_item_shortcut_disabled(int p_idx, bool p_disabled) { } items.write[p_idx].shortcut_is_disabled = p_disabled; + + if (!global_menu_name.is_empty()) { + DisplayServer *ds = DisplayServer::get_singleton(); + ds->global_menu_set_item_accelerator(global_menu_name, p_idx, Key::NONE); + if (!items[p_idx].shortcut_is_disabled && items[p_idx].shortcut.is_valid() && items[p_idx].shortcut->has_valid_event()) { + Array events = items[p_idx].shortcut->get_events(); + for (int j = 0; j < events.size(); j++) { + Ref ie = events[j]; + if (ie.is_valid()) { + ds->global_menu_set_item_accelerator(global_menu_name, p_idx, ie->get_keycode_with_modifiers()); + break; + } + } + } + } + control->queue_redraw(); _menu_changed(); } @@ -1686,6 +2080,10 @@ void PopupMenu::toggle_item_multistate(int p_idx) { items.write[p_idx].state = 0; } + if (!global_menu_name.is_empty()) { + DisplayServer::get_singleton()->global_menu_set_item_state(global_menu_name, p_idx, items[p_idx].state); + } + control->queue_redraw(); _menu_changed(); } @@ -1739,11 +2137,23 @@ void PopupMenu::set_item_count(int p_count) { return; } + DisplayServer *ds = DisplayServer::get_singleton(); + bool is_global = !global_menu_name.is_empty(); + + if (is_global && prev_size > p_count) { + for (int i = prev_size - 1; i >= p_count; i--) { + ds->global_menu_remove_item(global_menu_name, i); + } + } + items.resize(p_count); if (prev_size < p_count) { for (int i = prev_size; i < p_count; i++) { items.write[i].id = i; + if (is_global) { + ds->global_menu_add_item(global_menu_name, String(), callable_mp(this, &PopupMenu::activate_item), Callable(), i); + } } } @@ -1828,6 +2238,16 @@ bool PopupMenu::activate_item_by_event(const Ref &p_event, bool p_fo return false; } +void PopupMenu::_about_to_popup() { + ERR_MAIN_THREAD_GUARD; + emit_signal(SNAME("about_to_popup")); +} + +void PopupMenu::_about_to_close() { + ERR_MAIN_THREAD_GUARD; + emit_signal(SNAME("popup_hide")); +} + void PopupMenu::activate_item(int p_idx) { ERR_FAIL_INDEX(p_idx, items.size()); ERR_FAIL_COND(items[p_idx].separator); @@ -1890,6 +2310,11 @@ void PopupMenu::remove_item(int p_idx) { } items.remove_at(p_idx); + + if (!global_menu_name.is_empty()) { + DisplayServer::get_singleton()->global_menu_remove_item(global_menu_name, p_idx); + } + control->queue_redraw(); child_controls_changed(); _menu_changed(); @@ -1904,6 +2329,11 @@ void PopupMenu::add_separator(const String &p_text, int p_id) { sep.xl_text = atr(p_text); } items.push_back(sep); + + if (!global_menu_name.is_empty()) { + DisplayServer::get_singleton()->global_menu_add_separator(global_menu_name); + } + control->queue_redraw(); _menu_changed(); } @@ -1922,7 +2352,22 @@ void PopupMenu::clear(bool p_free_submenus) { } } } + + if (!global_menu_name.is_empty()) { + for (int i = 0; i < items.size(); i++) { + Item &item = items.write[i]; + if (!item.submenu.is_empty()) { + PopupMenu *pm = Object::cast_to(get_node_or_null(item.submenu)); + if (pm) { + pm->unbind_global_menu(); + } + item.submenu_bound = false; + } + } + DisplayServer::get_singleton()->global_menu_clear(global_menu_name); + } items.clear(); + mouse_over = -1; control->queue_redraw(); child_controls_changed(); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index f123d084006..5d5f4a8322a 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -75,6 +75,7 @@ class PopupMenu : public Popup { bool shortcut_is_global = false; bool shortcut_is_disabled = false; bool allow_echo = false; + bool submenu_bound = false; // Returns (0,0) if icon is null. Size2 get_icon_size() const { @@ -88,6 +89,8 @@ class PopupMenu : public Popup { } }; + String global_menu_name; + bool close_allowed = false; bool activated_by_keyboard = false; @@ -213,6 +216,9 @@ public: virtual void _parent_focused() override; + String bind_global_menu(); + void unbind_global_menu(); + 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); void add_check_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE); @@ -293,6 +299,9 @@ public: bool activate_item_by_event(const Ref &p_event, bool p_for_global_only = false); void activate_item(int p_idx); + void _about_to_popup(); + void _about_to_close(); + void remove_item(int p_idx); void add_separator(const String &p_text = String(), int p_id = -1); diff --git a/servers/display_server.cpp b/servers/display_server.cpp index c8516a0966c..6459cc74622 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -82,6 +82,10 @@ int DisplayServer::global_menu_add_multistate_item(const String &p_menu_root, co return -1; } +void DisplayServer::global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callbacs, const Callable &p_close_callback) { + WARN_PRINT("Global menus not supported by this display server."); +} + int DisplayServer::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) { WARN_PRINT("Global menus not supported by this display server."); return -1; @@ -106,6 +110,10 @@ void DisplayServer::global_menu_set_item_callback(const String &p_menu_root, int WARN_PRINT("Global menus not supported by this display server."); } +void DisplayServer::global_menu_set_item_hover_callbacks(const String &p_menu_root, int p_idx, const Callable &p_callback) { + WARN_PRINT("Global menus not supported by this display server."); +} + void DisplayServer::global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback) { WARN_PRINT("Global menus not supported by this display server."); } @@ -160,6 +168,11 @@ bool DisplayServer::global_menu_is_item_disabled(const String &p_menu_root, int return false; } +bool DisplayServer::global_menu_is_item_hidden(const String &p_menu_root, int p_idx) const { + WARN_PRINT("Global menus not supported by this display server."); + return false; +} + String DisplayServer::global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const { WARN_PRINT("Global menus not supported by this display server."); return String(); @@ -217,6 +230,10 @@ void DisplayServer::global_menu_set_item_disabled(const String &p_menu_root, int WARN_PRINT("Global menus not supported by this display server."); } +void DisplayServer::global_menu_set_item_hidden(const String &p_menu_root, int p_idx, bool p_hidden) { + WARN_PRINT("Global menus not supported by this display server."); +} + void DisplayServer::global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) { WARN_PRINT("Global menus not supported by this display server."); } @@ -581,6 +598,7 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("has_feature", "feature"), &DisplayServer::has_feature); ClassDB::bind_method(D_METHOD("get_name"), &DisplayServer::get_name); + ClassDB::bind_method(D_METHOD("global_menu_set_popup_callbacks", "menu_root", "open_callback", "close_callback"), &DisplayServer::global_menu_set_popup_callbacks); ClassDB::bind_method(D_METHOD("global_menu_add_submenu_item", "menu_root", "label", "submenu", "index"), &DisplayServer::global_menu_add_submenu_item, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("global_menu_add_item", "menu_root", "label", "callback", "key_callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_item, DEFVAL(Callable()), DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("global_menu_add_check_item", "menu_root", "label", "callback", "key_callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_check_item, DEFVAL(Callable()), DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1)); @@ -604,6 +622,7 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("global_menu_get_item_submenu", "menu_root", "idx"), &DisplayServer::global_menu_get_item_submenu); ClassDB::bind_method(D_METHOD("global_menu_get_item_accelerator", "menu_root", "idx"), &DisplayServer::global_menu_get_item_accelerator); ClassDB::bind_method(D_METHOD("global_menu_is_item_disabled", "menu_root", "idx"), &DisplayServer::global_menu_is_item_disabled); + ClassDB::bind_method(D_METHOD("global_menu_is_item_hidden", "menu_root", "idx"), &DisplayServer::global_menu_is_item_hidden); ClassDB::bind_method(D_METHOD("global_menu_get_item_tooltip", "menu_root", "idx"), &DisplayServer::global_menu_get_item_tooltip); ClassDB::bind_method(D_METHOD("global_menu_get_item_state", "menu_root", "idx"), &DisplayServer::global_menu_get_item_state); ClassDB::bind_method(D_METHOD("global_menu_get_item_max_states", "menu_root", "idx"), &DisplayServer::global_menu_get_item_max_states); @@ -614,12 +633,14 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("global_menu_set_item_checkable", "menu_root", "idx", "checkable"), &DisplayServer::global_menu_set_item_checkable); ClassDB::bind_method(D_METHOD("global_menu_set_item_radio_checkable", "menu_root", "idx", "checkable"), &DisplayServer::global_menu_set_item_radio_checkable); ClassDB::bind_method(D_METHOD("global_menu_set_item_callback", "menu_root", "idx", "callback"), &DisplayServer::global_menu_set_item_callback); + ClassDB::bind_method(D_METHOD("global_menu_set_item_hover_callbacks", "menu_root", "idx", "callback"), &DisplayServer::global_menu_set_item_hover_callbacks); ClassDB::bind_method(D_METHOD("global_menu_set_item_key_callback", "menu_root", "idx", "key_callback"), &DisplayServer::global_menu_set_item_key_callback); ClassDB::bind_method(D_METHOD("global_menu_set_item_tag", "menu_root", "idx", "tag"), &DisplayServer::global_menu_set_item_tag); ClassDB::bind_method(D_METHOD("global_menu_set_item_text", "menu_root", "idx", "text"), &DisplayServer::global_menu_set_item_text); ClassDB::bind_method(D_METHOD("global_menu_set_item_submenu", "menu_root", "idx", "submenu"), &DisplayServer::global_menu_set_item_submenu); ClassDB::bind_method(D_METHOD("global_menu_set_item_accelerator", "menu_root", "idx", "keycode"), &DisplayServer::global_menu_set_item_accelerator); ClassDB::bind_method(D_METHOD("global_menu_set_item_disabled", "menu_root", "idx", "disabled"), &DisplayServer::global_menu_set_item_disabled); + ClassDB::bind_method(D_METHOD("global_menu_set_item_hidden", "menu_root", "idx", "hidden"), &DisplayServer::global_menu_set_item_hidden); ClassDB::bind_method(D_METHOD("global_menu_set_item_tooltip", "menu_root", "idx", "tooltip"), &DisplayServer::global_menu_set_item_tooltip); ClassDB::bind_method(D_METHOD("global_menu_set_item_state", "menu_root", "idx", "state"), &DisplayServer::global_menu_set_item_state); ClassDB::bind_method(D_METHOD("global_menu_set_item_max_states", "menu_root", "idx", "max_states"), &DisplayServer::global_menu_set_item_max_states); diff --git a/servers/display_server.h b/servers/display_server.h index 71bfd7b6075..b314a298a78 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -130,6 +130,8 @@ public: virtual bool has_feature(Feature p_feature) const = 0; virtual String get_name() const = 0; + virtual void global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callback = Callable(), const Callable &p_close_callback = Callable()); + virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1); virtual int global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1); virtual int global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1); @@ -153,6 +155,7 @@ public: virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) const; virtual Key global_menu_get_item_accelerator(const String &p_menu_root, int p_idx) const; virtual bool global_menu_is_item_disabled(const String &p_menu_root, int p_idx) const; + virtual bool global_menu_is_item_hidden(const String &p_menu_root, int p_idx) const; virtual String global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const; virtual int global_menu_get_item_state(const String &p_menu_root, int p_idx) const; virtual int global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const; @@ -164,11 +167,13 @@ public: virtual void global_menu_set_item_radio_checkable(const String &p_menu_root, int p_idx, bool p_checkable); virtual void global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback); virtual void global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback); + virtual void global_menu_set_item_hover_callbacks(const String &p_menu_root, int p_idx, const Callable &p_callback); virtual void global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag); virtual void global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text); virtual void global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu); virtual void global_menu_set_item_accelerator(const String &p_menu_root, int p_idx, Key p_keycode); virtual void global_menu_set_item_disabled(const String &p_menu_root, int p_idx, bool p_disabled); + virtual void global_menu_set_item_hidden(const String &p_menu_root, int p_idx, bool p_hidden); virtual void global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip); virtual void global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state); virtual void global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states);