Merge pull request #94061 from bruvzg/menu_is_native

[NativeMenu] Do not auto toggle check/multi-state items. Add `is_native_menu` method.
This commit is contained in:
Rémi Verschelde 2024-07-08 11:48:33 +02:00
commit 9804a8eb30
No known key found for this signature in database
GPG Key ID: C3336907360768E1
9 changed files with 62 additions and 64 deletions

View File

@ -395,6 +395,12 @@
Returns [code]true[/code] if the specified item's shortcut is disabled. Returns [code]true[/code] if the specified item's shortcut is disabled.
</description> </description>
</method> </method>
<method name="is_native_menu" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if the system native menu is supported and currently used by this [PopupMenu].
</description>
</method>
<method name="is_system_menu" qualifiers="const"> <method name="is_system_menu" qualifiers="const">
<return type="bool" /> <return type="bool" />
<description> <description>
@ -636,6 +642,7 @@
</member> </member>
<member name="prefer_native_menu" type="bool" setter="set_prefer_native_menu" getter="is_prefer_native_menu" default="false"> <member name="prefer_native_menu" type="bool" setter="set_prefer_native_menu" getter="is_prefer_native_menu" default="false">
If [code]true[/code], [MenuBar] will use native menu when supported. If [code]true[/code], [MenuBar] will use native menu when supported.
[b]Note:[/b] If [PopupMenu] is linked to [StatusIndicator], [MenuBar], or another [PopupMenu] item it can use native menu regardless of this property, use [method is_native_menu] to check it.
</member> </member>
<member name="submenu_popup_delay" type="float" setter="set_submenu_popup_delay" getter="get_submenu_popup_delay" default="0.3"> <member name="submenu_popup_delay" type="float" setter="set_submenu_popup_delay" getter="get_submenu_popup_delay" default="0.3">
Sets the delay time in seconds for the submenu item to popup on mouse hovering. If the popup menu is added as a child of another (acting as a submenu), it will inherit the delay time of the parent menu item. Sets the delay time in seconds for the submenu item to popup on mouse hovering. If the popup menu is added as a child of another (acting as a submenu), it will inherit the delay time of the parent menu item.

View File

@ -568,23 +568,7 @@ void DisplayServerMacOS::menu_callback(id p_sender) {
} }
GodotMenuItem *value = [p_sender representedObject]; GodotMenuItem *value = [p_sender representedObject];
if (value) { if (value) {
if (value->max_states > 0) {
value->state++;
if (value->state >= value->max_states) {
value->state = 0;
}
}
if (value->checkable_type == CHECKABLE_TYPE_CHECK_BOX) {
if ([p_sender state] == NSControlStateValueOff) {
[p_sender setState:NSControlStateValueOn];
} else {
[p_sender setState:NSControlStateValueOff];
}
}
if (value->callback.is_valid()) { if (value->callback.is_valid()) {
MenuCall mc; MenuCall mc;
mc.tag = value->meta; mc.tag = value->meta;

View File

@ -52,6 +52,7 @@ enum GlobalMenuCheckType {
Callable hover_callback; Callable hover_callback;
Variant meta; Variant meta;
GlobalMenuCheckType checkable_type; GlobalMenuCheckType checkable_type;
bool checked;
int max_states; int max_states;
int state; int state;
Ref<Image> img; Ref<Image> img;

View File

@ -31,4 +31,18 @@
#include "godot_menu_item.h" #include "godot_menu_item.h"
@implementation GodotMenuItem @implementation GodotMenuItem
- (id)init {
self = [super init];
self->callback = Callable();
self->key_callback = Callable();
self->checkable_type = GlobalMenuCheckType::CHECKABLE_TYPE_NONE;
self->checked = false;
self->max_states = 0;
self->state = 0;
return self;
}
@end @end

View File

@ -373,12 +373,7 @@ int NativeMenuMacOS::add_submenu_item(const RID &p_rid, const String &p_label, c
menu_item = [md->menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index]; menu_item = [md->menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index];
GodotMenuItem *obj = [[GodotMenuItem alloc] init]; GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = Callable();
obj->key_callback = Callable();
obj->meta = p_tag; obj->meta = p_tag;
obj->checkable_type = CHECKABLE_TYPE_NONE;
obj->max_states = 0;
obj->state = 0;
[menu_item setRepresentedObject:obj]; [menu_item setRepresentedObject:obj];
[md_sub->menu setTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()]]; [md_sub->menu setTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()]];
@ -417,9 +412,6 @@ int NativeMenuMacOS::add_item(const RID &p_rid, const String &p_label, const Cal
obj->callback = p_callback; obj->callback = p_callback;
obj->key_callback = p_key_callback; obj->key_callback = p_key_callback;
obj->meta = p_tag; obj->meta = p_tag;
obj->checkable_type = CHECKABLE_TYPE_NONE;
obj->max_states = 0;
obj->state = 0;
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj]; [menu_item setRepresentedObject:obj];
} }
@ -438,8 +430,6 @@ int NativeMenuMacOS::add_check_item(const RID &p_rid, const String &p_label, con
obj->key_callback = p_key_callback; obj->key_callback = p_key_callback;
obj->meta = p_tag; obj->meta = p_tag;
obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX; obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
obj->max_states = 0;
obj->state = 0;
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj]; [menu_item setRepresentedObject:obj];
} }
@ -457,9 +447,6 @@ int NativeMenuMacOS::add_icon_item(const RID &p_rid, const Ref<Texture2D> &p_ico
obj->callback = p_callback; obj->callback = p_callback;
obj->key_callback = p_key_callback; obj->key_callback = p_key_callback;
obj->meta = p_tag; obj->meta = p_tag;
obj->checkable_type = CHECKABLE_TYPE_NONE;
obj->max_states = 0;
obj->state = 0;
DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) {
obj->img = p_icon->get_image(); obj->img = p_icon->get_image();
@ -489,8 +476,6 @@ int NativeMenuMacOS::add_icon_check_item(const RID &p_rid, const Ref<Texture2D>
obj->key_callback = p_key_callback; obj->key_callback = p_key_callback;
obj->meta = p_tag; obj->meta = p_tag;
obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX; obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
obj->max_states = 0;
obj->state = 0;
DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) {
obj->img = p_icon->get_image(); obj->img = p_icon->get_image();
@ -520,8 +505,6 @@ int NativeMenuMacOS::add_radio_check_item(const RID &p_rid, const String &p_labe
obj->key_callback = p_key_callback; obj->key_callback = p_key_callback;
obj->meta = p_tag; obj->meta = p_tag;
obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON; obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON;
obj->max_states = 0;
obj->state = 0;
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj]; [menu_item setRepresentedObject:obj];
} }
@ -540,8 +523,6 @@ int NativeMenuMacOS::add_icon_radio_check_item(const RID &p_rid, const Ref<Textu
obj->key_callback = p_key_callback; obj->key_callback = p_key_callback;
obj->meta = p_tag; obj->meta = p_tag;
obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON; obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON;
obj->max_states = 0;
obj->state = 0;
DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) {
obj->img = p_icon->get_image(); obj->img = p_icon->get_image();
@ -570,7 +551,6 @@ int NativeMenuMacOS::add_multistate_item(const RID &p_rid, const String &p_label
obj->callback = p_callback; obj->callback = p_callback;
obj->key_callback = p_key_callback; obj->key_callback = p_key_callback;
obj->meta = p_tag; obj->meta = p_tag;
obj->checkable_type = CHECKABLE_TYPE_NONE;
obj->max_states = p_max_states; obj->max_states = p_max_states;
obj->state = p_default_state; obj->state = p_default_state;
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
@ -640,7 +620,10 @@ bool NativeMenuMacOS::is_item_checked(const RID &p_rid, int p_idx) const {
ERR_FAIL_COND_V(p_idx >= item_start + item_count, false); ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx]; const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
if (menu_item) { if (menu_item) {
return ([menu_item state] == NSControlStateValueOn); const GodotMenuItem *obj = [menu_item representedObject];
if (obj) {
return obj->checked;
}
} }
return false; return false;
} }
@ -958,10 +941,14 @@ void NativeMenuMacOS::set_item_checked(const RID &p_rid, int p_idx, bool p_check
ERR_FAIL_COND(p_idx >= item_start + item_count); ERR_FAIL_COND(p_idx >= item_start + item_count);
NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx]; NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
if (menu_item) { if (menu_item) {
if (p_checked) { GodotMenuItem *obj = [menu_item representedObject];
[menu_item setState:NSControlStateValueOn]; if (obj) {
} else { obj->checked = p_checked;
[menu_item setState:NSControlStateValueOff]; if (p_checked) {
[menu_item setState:NSControlStateValueOn];
} else {
[menu_item setState:NSControlStateValueOff];
}
} }
} }
} }

View File

@ -81,22 +81,6 @@ void NativeMenuWindows::_menu_activate(HMENU p_menu, int p_index) const {
if (GetMenuItemInfoW(md->menu, p_index, true, &item)) { if (GetMenuItemInfoW(md->menu, p_index, true, &item)) {
MenuItemData *item_data = (MenuItemData *)item.dwItemData; MenuItemData *item_data = (MenuItemData *)item.dwItemData;
if (item_data) { if (item_data) {
if (item_data->max_states > 0) {
item_data->state++;
if (item_data->state >= item_data->max_states) {
item_data->state = 0;
}
}
if (item_data->checkable_type == CHECKABLE_TYPE_CHECK_BOX) {
if ((item.fState & MFS_CHECKED) == MFS_CHECKED) {
item.fState &= ~MFS_CHECKED;
} else {
item.fState |= MFS_CHECKED;
}
SetMenuItemInfoW(md->menu, p_index, true, &item);
}
if (item_data->callback.is_valid()) { if (item_data->callback.is_valid()) {
Variant ret; Variant ret;
Callable::CallError ce; Callable::CallError ce;
@ -619,9 +603,12 @@ bool NativeMenuWindows::is_item_checked(const RID &p_rid, int p_idx) const {
MENUITEMINFOW item; MENUITEMINFOW item;
ZeroMemory(&item, sizeof(item)); ZeroMemory(&item, sizeof(item));
item.cbSize = sizeof(item); item.cbSize = sizeof(item);
item.fMask = MIIM_STATE; item.fMask = MIIM_STATE | MIIM_DATA;
if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
return (item.fState & MFS_CHECKED) == MFS_CHECKED; MenuItemData *item_data = (MenuItemData *)item.dwItemData;
if (item_data) {
return item_data->checked;
}
} }
return false; return false;
} }
@ -861,12 +848,16 @@ void NativeMenuWindows::set_item_checked(const RID &p_rid, int p_idx, bool p_che
MENUITEMINFOW item; MENUITEMINFOW item;
ZeroMemory(&item, sizeof(item)); ZeroMemory(&item, sizeof(item));
item.cbSize = sizeof(item); item.cbSize = sizeof(item);
item.fMask = MIIM_STATE; item.fMask = MIIM_STATE | MIIM_DATA;
if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
if (p_checked) { MenuItemData *item_data = (MenuItemData *)item.dwItemData;
item.fState |= MFS_CHECKED; if (item_data) {
} else { item_data->checked = p_checked;
item.fState &= ~MFS_CHECKED; if (p_checked) {
item.fState |= MFS_CHECKED;
} else {
item.fState &= ~MFS_CHECKED;
}
} }
SetMenuItemInfoW(md->menu, p_idx, true, &item); SetMenuItemInfoW(md->menu, p_idx, true, &item);
} }

View File

@ -51,6 +51,7 @@ class NativeMenuWindows : public NativeMenu {
Callable callback; Callable callback;
Variant meta; Variant meta;
GlobalMenuCheckType checkable_type; GlobalMenuCheckType checkable_type;
bool checked = false;
int max_states = 0; int max_states = 0;
int state = 0; int state = 0;
Ref<Image> img; Ref<Image> img;

View File

@ -2314,6 +2314,16 @@ bool PopupMenu::is_prefer_native_menu() const {
return prefer_native; return prefer_native;
} }
bool PopupMenu::is_native_menu() const {
#ifdef TOOLS_ENABLED
if (is_part_of_edited_scene()) {
return false;
}
#endif
return global_menu.is_valid();
}
bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only) { bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only) {
ERR_FAIL_COND_V(p_event.is_null(), false); ERR_FAIL_COND_V(p_event.is_null(), false);
Key code = Key::NONE; Key code = Key::NONE;
@ -2643,6 +2653,7 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_prefer_native_menu", "enabled"), &PopupMenu::set_prefer_native_menu); ClassDB::bind_method(D_METHOD("set_prefer_native_menu", "enabled"), &PopupMenu::set_prefer_native_menu);
ClassDB::bind_method(D_METHOD("is_prefer_native_menu"), &PopupMenu::is_prefer_native_menu); ClassDB::bind_method(D_METHOD("is_prefer_native_menu"), &PopupMenu::is_prefer_native_menu);
ClassDB::bind_method(D_METHOD("is_native_menu"), &PopupMenu::is_native_menu);
ClassDB::bind_method(D_METHOD("add_item", "label", "id", "accel"), &PopupMenu::add_item, DEFVAL(-1), DEFVAL(0)); ClassDB::bind_method(D_METHOD("add_item", "label", "id", "accel"), &PopupMenu::add_item, DEFVAL(-1), DEFVAL(0));
ClassDB::bind_method(D_METHOD("add_icon_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_item, DEFVAL(-1), DEFVAL(0)); ClassDB::bind_method(D_METHOD("add_icon_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_item, DEFVAL(-1), DEFVAL(0));

View File

@ -330,6 +330,8 @@ public:
void set_prefer_native_menu(bool p_enabled); void set_prefer_native_menu(bool p_enabled);
bool is_prefer_native_menu() const; bool is_prefer_native_menu() const;
bool is_native_menu() const;
void scroll_to_item(int p_idx); void scroll_to_item(int p_idx);
bool activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only = false); bool activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only = false);