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.
 			</description>
 		</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">
 			<return type="bool" />
 			<description>
@@ -636,6 +642,7 @@
 		</member>
 		<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.
+			[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 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.

+ 0 - 16
platform/macos/display_server_macos.mm

@@ -568,23 +568,7 @@ void DisplayServerMacOS::menu_callback(id p_sender) {
 	}
 
 	GodotMenuItem *value = [p_sender representedObject];
-
 	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()) {
 			MenuCall mc;
 			mc.tag = value->meta;

+ 1 - 0
platform/macos/godot_menu_item.h

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

+ 14 - 0
platform/macos/godot_menu_item.mm

@@ -31,4 +31,18 @@
 #include "godot_menu_item.h"
 
 @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

+ 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];
 
 	GodotMenuItem *obj = [[GodotMenuItem alloc] init];
-	obj->callback = Callable();
-	obj->key_callback = Callable();
 	obj->meta = p_tag;
-	obj->checkable_type = CHECKABLE_TYPE_NONE;
-	obj->max_states = 0;
-	obj->state = 0;
 	[menu_item setRepresentedObject:obj];
 
 	[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->key_callback = p_key_callback;
 		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 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->meta = p_tag;
 		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 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->key_callback = p_key_callback;
 		obj->meta = p_tag;
-		obj->checkable_type = CHECKABLE_TYPE_NONE;
-		obj->max_states = 0;
-		obj->state = 0;
 		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()) {
 			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->meta = p_tag;
 		obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
-		obj->max_states = 0;
-		obj->state = 0;
 		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()) {
 			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->meta = p_tag;
 		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 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->meta = p_tag;
 		obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON;
-		obj->max_states = 0;
-		obj->state = 0;
 		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()) {
 			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->key_callback = p_key_callback;
 		obj->meta = p_tag;
-		obj->checkable_type = CHECKABLE_TYPE_NONE;
 		obj->max_states = p_max_states;
 		obj->state = p_default_state;
 		[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);
 	const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
 	if (menu_item) {
-		return ([menu_item state] == NSControlStateValueOn);
+		const GodotMenuItem *obj = [menu_item representedObject];
+		if (obj) {
+			return obj->checked;
+		}
 	}
 	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);
 	NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
 	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)) {
 					MenuItemData *item_data = (MenuItemData *)item.dwItemData;
 					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()) {
 							Variant ret;
 							Callable::CallError ce;
@@ -619,9 +603,12 @@ bool NativeMenuWindows::is_item_checked(const RID &p_rid, int p_idx) const {
 	MENUITEMINFOW item;
 	ZeroMemory(&item, sizeof(item));
 	item.cbSize = sizeof(item);
-	item.fMask = MIIM_STATE;
+	item.fMask = MIIM_STATE | MIIM_DATA;
 	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;
 }
@@ -861,12 +848,16 @@ void NativeMenuWindows::set_item_checked(const RID &p_rid, int p_idx, bool p_che
 	MENUITEMINFOW item;
 	ZeroMemory(&item, 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 (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);
 	}

+ 1 - 0
platform/windows/native_menu_windows.h

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

+ 11 - 0
scene/gui/popup_menu.cpp

@@ -2314,6 +2314,16 @@ bool PopupMenu::is_prefer_native_menu() const {
 	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) {
 	ERR_FAIL_COND_V(p_event.is_null(), false);
 	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("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_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);
 	bool is_prefer_native_menu() const;
 
+	bool is_native_menu() const;
+
 	void scroll_to_item(int p_idx);
 
 	bool activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only = false);