Browse Source

Merge pull request #94061 from bruvzg/menu_is_native

[NativeMenu] Do not auto toggle check/multi-state items. Add `is_native_menu` method.
Rémi Verschelde 1 year ago
parent
commit
9804a8eb30

+ 7 - 0
doc/classes/PopupMenu.xml

@@ -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.

+ 0 - 16
platform/macos/display_server_macos.mm

@@ -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;

+ 1 - 0
platform/macos/godot_menu_item.h

@@ -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;

+ 14 - 0
platform/macos/godot_menu_item.mm

@@ -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

+ 12 - 25
platform/macos/native_menu_macos.mm

@@ -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) {
-			[menu_item setState:NSControlStateValueOn];
-		} else {
-			[menu_item setState:NSControlStateValueOff];
+		GodotMenuItem *obj = [menu_item representedObject];
+		if (obj) {
+			obj->checked = p_checked;
+			if (p_checked) {
+				[menu_item setState:NSControlStateValueOn];
+			} else {
+				[menu_item setState:NSControlStateValueOff];
+			}
 		}
 		}
 	}
 	}
 }
 }

+ 14 - 23
platform/windows/native_menu_windows.cpp

@@ -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) {
-			item.fState |= MFS_CHECKED;
-		} else {
-			item.fState &= ~MFS_CHECKED;
+		MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+		if (item_data) {
+			item_data->checked = p_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);
 	}
 	}

+ 1 - 0
platform/windows/native_menu_windows.h

@@ -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;

+ 11 - 0
scene/gui/popup_menu.cpp

@@ -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));

+ 2 - 0
scene/gui/popup_menu.h

@@ -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);