From 1f7bf27780931eef193866a501801f4a9b3067c9 Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Wed, 5 Jun 2024 10:33:28 +0300 Subject: [PATCH] [macOS] Improve native menu open/close callbacks. --- doc/classes/NativeMenu.xml | 11 ++++++- platform/macos/godot_menu_delegate.mm | 9 +++++- platform/macos/native_menu_macos.h | 5 +++ platform/macos/native_menu_macos.mm | 41 +++++++++++++++++++----- platform/windows/native_menu_windows.cpp | 5 +++ platform/windows/native_menu_windows.h | 2 ++ servers/display/native_menu.cpp | 7 ++++ servers/display/native_menu.h | 2 ++ 8 files changed, 72 insertions(+), 10 deletions(-) diff --git a/doc/classes/NativeMenu.xml b/doc/classes/NativeMenu.xml index 475874dee755..2b9e41410688 100644 --- a/doc/classes/NativeMenu.xml +++ b/doc/classes/NativeMenu.xml @@ -473,6 +473,14 @@ [b]Note:[/b] This method is implemented on macOS and Windows. + + + + + Returns [code]true[/code] if the menu is currently opened. + [b]Note:[/b] This method is implemented only on macOS. + + @@ -699,6 +707,7 @@ Registers callable to emit when the menu is about to show. + [b]Note:[/b] The OS can simulate menu opening to track menu item changes and global shortcuts, in which case the corresponding close callback is not triggered. Use [method is_opened] to check if the menu is currently opened. [b]Note:[/b] This method is implemented only on macOS. @@ -707,7 +716,7 @@ - Registers callable to emit when the menu is about to closed. + Registers callable to emit after the menu is closed. [b]Note:[/b] This method is implemented only on macOS. diff --git a/platform/macos/godot_menu_delegate.mm b/platform/macos/godot_menu_delegate.mm index 5c1e849715da..3f7dfac3dec6 100644 --- a/platform/macos/godot_menu_delegate.mm +++ b/platform/macos/godot_menu_delegate.mm @@ -40,13 +40,20 @@ - (void)doNothing:(id)sender { } -- (void)menuNeedsUpdate:(NSMenu *)menu { +- (void)menuWillOpen:(NSMenu *)menu { if (NativeMenu::get_singleton()) { NativeMenuMacOS *nmenu = (NativeMenuMacOS *)NativeMenu::get_singleton(); nmenu->_menu_open(menu); } } +- (void)menuNeedsUpdate:(NSMenu *)menu { + if (NativeMenu::get_singleton()) { + NativeMenuMacOS *nmenu = (NativeMenuMacOS *)NativeMenu::get_singleton(); + nmenu->_menu_need_update(menu); + } +} + - (void)menuDidClose:(NSMenu *)menu { if (NativeMenu::get_singleton()) { NativeMenuMacOS *nmenu = (NativeMenuMacOS *)NativeMenu::get_singleton(); diff --git a/platform/macos/native_menu_macos.h b/platform/macos/native_menu_macos.h index b5dbb8b9b036..42cf6740d900 100644 --- a/platform/macos/native_menu_macos.h +++ b/platform/macos/native_menu_macos.h @@ -73,8 +73,11 @@ class NativeMenuMacOS : public NativeMenu { public: void _register_system_menus(NSMenu *p_main_menu, NSMenu *p_application_menu, NSMenu *p_window_menu, NSMenu *p_help_menu, NSMenu *p_dock_menu); NSMenu *_get_dock_menu(); + + void _menu_need_update(NSMenu *p_menu); void _menu_open(NSMenu *p_menu); void _menu_close(NSMenu *p_menu); + void _menu_close_cb(const RID &p_rid); virtual bool has_feature(Feature p_feature) const override; @@ -98,6 +101,8 @@ public: virtual void set_minimum_width(const RID &p_rid, float p_width) override; virtual float get_minimum_width(const RID &p_rid) const override; + virtual bool is_opened(const RID &p_rid) const override; + virtual int add_submenu_item(const RID &p_rid, const String &p_label, const RID &p_submenu_rid, const Variant &p_tag = Variant(), int p_index = -1) override; virtual int add_item(const RID &p_rid, 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 add_check_item(const RID &p_rid, 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; diff --git a/platform/macos/native_menu_macos.mm b/platform/macos/native_menu_macos.mm index 1cf13a2d6941..97e8b6a7f120 100644 --- a/platform/macos/native_menu_macos.mm +++ b/platform/macos/native_menu_macos.mm @@ -91,11 +91,22 @@ void NativeMenuMacOS::_menu_open(NSMenu *p_menu) { if (menu_lookup.has(p_menu)) { MenuData *md = menus.get_or_null(menu_lookup[p_menu]); if (md) { + // Note: Set "is_open" flag, but do not call callback, menu items can't be modified during this call and "_menu_need_update" will be called right before it. md->is_open = true; + } + } +} + +void NativeMenuMacOS::_menu_need_update(NSMenu *p_menu) { + if (menu_lookup.has(p_menu)) { + MenuData *md = menus.get_or_null(menu_lookup[p_menu]); + if (md) { + // Note: "is_open" flag is set by "_menu_open", this method is always called before menu is shown, but might be called for the other reasons as well. if (md->open_cb.is_valid()) { Variant ret; Callable::CallError ce; + // Callback is called directly, since it's expected to modify menu items before it's shown. md->open_cb.callp(nullptr, 0, ret, ce); if (ce.error != Callable::CallError::CALL_OK) { ERR_PRINT(vformat("Failed to execute menu open callback: %s.", Variant::get_callable_error_text(md->open_cb, nullptr, 0, ce))); @@ -110,15 +121,22 @@ void NativeMenuMacOS::_menu_close(NSMenu *p_menu) { MenuData *md = menus.get_or_null(menu_lookup[p_menu]); if (md) { md->is_open = false; - if (md->close_cb.is_valid()) { - Variant ret; - Callable::CallError ce; - md->close_cb.callp(nullptr, 0, ret, ce); - if (ce.error != Callable::CallError::CALL_OK) { - ERR_PRINT(vformat("Failed to execute menu close callback: %s.", Variant::get_callable_error_text(md->close_cb, nullptr, 0, ce))); - } - } + // Callback called deferred, since it should not modify menu items during "_menu_close" call. + callable_mp(this, &NativeMenuMacOS::_menu_close_cb).call_deferred(menu_lookup[p_menu]); + } + } +} + +void NativeMenuMacOS::_menu_close_cb(const RID &p_rid) { + MenuData *md = menus.get_or_null(p_rid); + if (md->close_cb.is_valid()) { + Variant ret; + Callable::CallError ce; + + md->close_cb.callp(nullptr, 0, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute menu close callback: %s.", Variant::get_callable_error_text(md->close_cb, nullptr, 0, ce))); } } } @@ -328,6 +346,13 @@ float NativeMenuMacOS::get_minimum_width(const RID &p_rid) const { return md->menu.minimumWidth * DisplayServer::get_singleton()->screen_get_max_scale(); } +bool NativeMenuMacOS::is_opened(const RID &p_rid) const { + const MenuData *md = menus.get_or_null(p_rid); + ERR_FAIL_NULL_V(md, false); + + return md->is_open; +} + int NativeMenuMacOS::add_submenu_item(const RID &p_rid, const String &p_label, const RID &p_submenu_rid, const Variant &p_tag, int p_index) { MenuData *md = menus.get_or_null(p_rid); MenuData *md_sub = menus.get_or_null(p_submenu_rid); diff --git a/platform/windows/native_menu_windows.cpp b/platform/windows/native_menu_windows.cpp index 40a08f87df4a..cc96d3f5dedb 100644 --- a/platform/windows/native_menu_windows.cpp +++ b/platform/windows/native_menu_windows.cpp @@ -231,6 +231,11 @@ float NativeMenuWindows::get_minimum_width(const RID &p_rid) const { return 0.f; } +bool NativeMenuWindows::is_opened(const RID &p_rid) const { + // Not supported. + return false; +} + int NativeMenuWindows::add_submenu_item(const RID &p_rid, const String &p_label, const RID &p_submenu_rid, const Variant &p_tag, int p_index) { MenuData *md = menus.get_or_null(p_rid); MenuData *md_sub = menus.get_or_null(p_submenu_rid); diff --git a/platform/windows/native_menu_windows.h b/platform/windows/native_menu_windows.h index 74fd2319037a..5c4aaa52c8bd 100644 --- a/platform/windows/native_menu_windows.h +++ b/platform/windows/native_menu_windows.h @@ -90,6 +90,8 @@ public: virtual void set_minimum_width(const RID &p_rid, float p_width) override; virtual float get_minimum_width(const RID &p_rid) const override; + virtual bool is_opened(const RID &p_rid) const override; + virtual int add_submenu_item(const RID &p_rid, const String &p_label, const RID &p_submenu_rid, const Variant &p_tag = Variant(), int p_index = -1) override; virtual int add_item(const RID &p_rid, 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 add_check_item(const RID &p_rid, 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; diff --git a/servers/display/native_menu.cpp b/servers/display/native_menu.cpp index ca46560c7c0e..c7346637d8c5 100644 --- a/servers/display/native_menu.cpp +++ b/servers/display/native_menu.cpp @@ -56,6 +56,8 @@ void NativeMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("set_minimum_width", "rid", "width"), &NativeMenu::set_minimum_width); ClassDB::bind_method(D_METHOD("get_minimum_width", "rid"), &NativeMenu::get_minimum_width); + ClassDB::bind_method(D_METHOD("is_opened", "rid"), &NativeMenu::is_opened); + ClassDB::bind_method(D_METHOD("add_submenu_item", "rid", "label", "submenu_rid", "tag", "index"), &NativeMenu::add_submenu_item, DEFVAL(Variant()), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("add_item", "rid", "label", "callback", "key_callback", "tag", "accelerator", "index"), &NativeMenu::add_item, DEFVAL(Callable()), DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("add_check_item", "rid", "label", "callback", "key_callback", "tag", "accelerator", "index"), &NativeMenu::add_check_item, DEFVAL(Callable()), DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1)); @@ -200,6 +202,11 @@ Callable NativeMenu::get_popup_close_callback(const RID &p_rid) const { return Callable(); } +bool NativeMenu::is_opened(const RID &p_rid) const { + WARN_PRINT("Global menus are not supported on this platform."); + return false; +} + void NativeMenu::set_minimum_width(const RID &p_rid, float p_width) { WARN_PRINT("Global menus are not supported on this platform."); } diff --git a/servers/display/native_menu.h b/servers/display/native_menu.h index 2bc061a2162d..29d22e03aa7e 100644 --- a/servers/display/native_menu.h +++ b/servers/display/native_menu.h @@ -90,6 +90,8 @@ public: virtual void set_minimum_width(const RID &p_rid, float p_width); virtual float get_minimum_width(const RID &p_rid) const; + virtual bool is_opened(const RID &p_rid) const; + virtual int add_submenu_item(const RID &p_rid, const String &p_label, const RID &p_submenu_rid, const Variant &p_tag = Variant(), int p_index = -1); virtual int add_item(const RID &p_rid, 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 add_check_item(const RID &p_rid, 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);