[macOS] Improve native menu open/close callbacks.

This commit is contained in:
bruvzg 2024-06-05 10:33:28 +03:00
parent 96a386f3c4
commit 1f7bf27780
No known key found for this signature in database
GPG Key ID: 7960FCF39844EC38
8 changed files with 72 additions and 10 deletions

View File

@ -473,6 +473,14 @@
[b]Note:[/b] This method is implemented on macOS and Windows. [b]Note:[/b] This method is implemented on macOS and Windows.
</description> </description>
</method> </method>
<method name="is_opened" qualifiers="const">
<return type="bool" />
<param index="0" name="rid" type="RID" />
<description>
Returns [code]true[/code] if the menu is currently opened.
[b]Note:[/b] This method is implemented only on macOS.
</description>
</method>
<method name="is_system_menu" qualifiers="const"> <method name="is_system_menu" qualifiers="const">
<return type="bool" /> <return type="bool" />
<param index="0" name="rid" type="RID" /> <param index="0" name="rid" type="RID" />
@ -699,6 +707,7 @@
<param index="1" name="callback" type="Callable" /> <param index="1" name="callback" type="Callable" />
<description> <description>
Registers callable to emit when the menu is about to show. 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. [b]Note:[/b] This method is implemented only on macOS.
</description> </description>
</method> </method>
@ -707,7 +716,7 @@
<param index="0" name="rid" type="RID" /> <param index="0" name="rid" type="RID" />
<param index="1" name="callback" type="Callable" /> <param index="1" name="callback" type="Callable" />
<description> <description>
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. [b]Note:[/b] This method is implemented only on macOS.
</description> </description>
</method> </method>

View File

@ -40,13 +40,20 @@
- (void)doNothing:(id)sender { - (void)doNothing:(id)sender {
} }
- (void)menuNeedsUpdate:(NSMenu *)menu { - (void)menuWillOpen:(NSMenu *)menu {
if (NativeMenu::get_singleton()) { if (NativeMenu::get_singleton()) {
NativeMenuMacOS *nmenu = (NativeMenuMacOS *)NativeMenu::get_singleton(); NativeMenuMacOS *nmenu = (NativeMenuMacOS *)NativeMenu::get_singleton();
nmenu->_menu_open(menu); 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 { - (void)menuDidClose:(NSMenu *)menu {
if (NativeMenu::get_singleton()) { if (NativeMenu::get_singleton()) {
NativeMenuMacOS *nmenu = (NativeMenuMacOS *)NativeMenu::get_singleton(); NativeMenuMacOS *nmenu = (NativeMenuMacOS *)NativeMenu::get_singleton();

View File

@ -73,8 +73,11 @@ class NativeMenuMacOS : public NativeMenu {
public: 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); 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(); NSMenu *_get_dock_menu();
void _menu_need_update(NSMenu *p_menu);
void _menu_open(NSMenu *p_menu); void _menu_open(NSMenu *p_menu);
void _menu_close(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; 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 void set_minimum_width(const RID &p_rid, float p_width) override;
virtual float get_minimum_width(const RID &p_rid) const 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_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_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; 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;

View File

@ -91,11 +91,22 @@ void NativeMenuMacOS::_menu_open(NSMenu *p_menu) {
if (menu_lookup.has(p_menu)) { if (menu_lookup.has(p_menu)) {
MenuData *md = menus.get_or_null(menu_lookup[p_menu]); MenuData *md = menus.get_or_null(menu_lookup[p_menu]);
if (md) { 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; 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()) { if (md->open_cb.is_valid()) {
Variant ret; Variant ret;
Callable::CallError ce; 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); md->open_cb.callp(nullptr, 0, ret, ce);
if (ce.error != Callable::CallError::CALL_OK) { 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))); ERR_PRINT(vformat("Failed to execute menu open callback: %s.", Variant::get_callable_error_text(md->open_cb, nullptr, 0, ce)));
@ -110,6 +121,15 @@ void NativeMenuMacOS::_menu_close(NSMenu *p_menu) {
MenuData *md = menus.get_or_null(menu_lookup[p_menu]); MenuData *md = menus.get_or_null(menu_lookup[p_menu]);
if (md) { if (md) {
md->is_open = false; md->is_open = false;
// 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()) { if (md->close_cb.is_valid()) {
Variant ret; Variant ret;
Callable::CallError ce; Callable::CallError ce;
@ -119,8 +139,6 @@ void NativeMenuMacOS::_menu_close(NSMenu *p_menu) {
ERR_PRINT(vformat("Failed to execute menu close callback: %s.", Variant::get_callable_error_text(md->close_cb, nullptr, 0, ce))); ERR_PRINT(vformat("Failed to execute menu close callback: %s.", Variant::get_callable_error_text(md->close_cb, nullptr, 0, ce)));
} }
} }
}
}
} }
bool NativeMenuMacOS::_is_menu_opened(NSMenu *p_menu) const { bool NativeMenuMacOS::_is_menu_opened(NSMenu *p_menu) const {
@ -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(); 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) { 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 = menus.get_or_null(p_rid);
MenuData *md_sub = menus.get_or_null(p_submenu_rid); MenuData *md_sub = menus.get_or_null(p_submenu_rid);

View File

@ -231,6 +231,11 @@ float NativeMenuWindows::get_minimum_width(const RID &p_rid) const {
return 0.f; 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) { 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 = menus.get_or_null(p_rid);
MenuData *md_sub = menus.get_or_null(p_submenu_rid); MenuData *md_sub = menus.get_or_null(p_submenu_rid);

View File

@ -90,6 +90,8 @@ public:
virtual void set_minimum_width(const RID &p_rid, float p_width) override; 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 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_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_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; 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;

View File

@ -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("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("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_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_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)); 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(); 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) { void NativeMenu::set_minimum_width(const RID &p_rid, float p_width) {
WARN_PRINT("Global menus are not supported on this platform."); WARN_PRINT("Global menus are not supported on this platform.");
} }

View File

@ -90,6 +90,8 @@ public:
virtual void set_minimum_width(const RID &p_rid, float p_width); virtual void set_minimum_width(const RID &p_rid, float p_width);
virtual float get_minimum_width(const RID &p_rid) const; 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_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_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); 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);