diff --git a/doc/classes/NativeMenu.xml b/doc/classes/NativeMenu.xml
index 499be957ff0..d8d91d1480c 100644
--- a/doc/classes/NativeMenu.xml
+++ b/doc/classes/NativeMenu.xml
@@ -216,7 +216,7 @@
- Returns the index of the item with the submenu specified by [param submenu_rid]. Indices are automatically assigned to each item by the engine, and cannot be set manually.
+ Returns the index of the item with the submenu specified by [param submenu_rid]. Indices are automatically assigned to each item by the engine.
[b]Note:[/b] This method is implemented on macOS and Windows.
@@ -225,7 +225,7 @@
- Returns the index of the item with the specified [param tag]. Indices are automatically assigned to each item by the engine, and cannot be set manually.
+ Returns the index of the item with the specified [param tag]. Indices are automatically assigned to each item by the engine.
[b]Note:[/b] This method is implemented on macOS and Windows.
@@ -234,7 +234,7 @@
- Returns the index of the item with the specified [param text]. Indices are automatically assigned to each item by the engine, and cannot be set manually.
+ Returns the index of the item with the specified [param text]. Indices are automatically assigned to each item by the engine.
[b]Note:[/b] This method is implemented on macOS and Windows.
@@ -618,6 +618,18 @@
[b]Note:[/b] This method is implemented only on macOS.
+
+
+
+
+
+
+ Changes the index of the item at index [param idx] to be at index [param target_idx]. This can be used to move an item above other items.
+ Returns the new index of the moved item, it's not guaranteed to be the same as [param target_idx].
+ [b]Note:[/b] The indices of any items between index [param idx] and index [param target_idx] will be shifted by one.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
+
+
diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml
index 036e45da39c..8676051bc79 100644
--- a/doc/classes/PopupMenu.xml
+++ b/doc/classes/PopupMenu.xml
@@ -270,7 +270,7 @@
- Returns the ID of the item at the given [param index]. [code]id[/code] can be manually assigned, while index can not.
+ Returns the ID of the item at the given [param index].
@@ -284,7 +284,7 @@
- Returns the index of the item containing the specified [param id]. Index is automatically assigned to each item by the engine and can not be set manually.
+ Returns the index of the item containing the specified [param id]. The index is automatically assigned to each item by the engine when added and represents the order items will be displayed.
@@ -536,6 +536,15 @@
Sets the horizontal offset of the item at the given [param index].
+
+
+
+
+
+ Changes the index of the item at index [param index] to be at index [param target_index]. This can be used to move an item above other items. The moved item will keep the same ID, even if it was generated from the original index.
+ [b]Note:[/b] The indices of any items between index [param index] and index [param target_index] will be shifted by one.
+
+
diff --git a/platform/macos/native_menu_macos.h b/platform/macos/native_menu_macos.h
index def625cf25c..bf5dde4c493 100644
--- a/platform/macos/native_menu_macos.h
+++ b/platform/macos/native_menu_macos.h
@@ -152,6 +152,7 @@ public:
virtual void set_item_max_states(const RID &p_rid, int p_idx, int p_max_states) override;
virtual void set_item_icon(const RID &p_rid, int p_idx, const Ref &p_icon) override;
virtual void set_item_indentation_level(const RID &p_rid, int p_idx, int p_level) override;
+ virtual int set_item_index(const RID &p_rid, int p_idx, int p_target_idx) override;
virtual int get_item_count(const RID &p_rid) const override;
virtual bool is_system_menu(const RID &p_rid) const override;
diff --git a/platform/macos/native_menu_macos.mm b/platform/macos/native_menu_macos.mm
index 613799bef2b..2b3c54f7c5f 100644
--- a/platform/macos/native_menu_macos.mm
+++ b/platform/macos/native_menu_macos.mm
@@ -1319,6 +1319,29 @@ void NativeMenuMacOS::set_item_indentation_level(const RID &p_rid, int p_idx, in
}
}
+int NativeMenuMacOS::set_item_index(const RID &p_rid, int p_idx, int p_target_idx) {
+ ERR_FAIL_COND_V(p_idx < 0, -1);
+
+ MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+ int item_start = _get_system_menu_start(md->menu);
+ int item_count = _get_system_menu_count(md->menu);
+ p_idx += item_start;
+ ERR_FAIL_COND_V(p_idx >= item_start + item_count, -1);
+ ERR_FAIL_INDEX_V(p_target_idx, item_count, -1);
+ p_target_idx += item_start;
+
+ NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
+ if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) {
+ ERR_FAIL_V_MSG(-1, "Can't move open menu!");
+ }
+ if (menu_item) {
+ [md->menu removeItemAtIndex:p_idx];
+ [md->menu insertItem:menu_item atIndex:p_target_idx];
+ }
+ return p_target_idx - item_start;
+}
+
int NativeMenuMacOS::get_item_count(const RID &p_rid) const {
const MenuData *md = menus.get_or_null(p_rid);
ERR_FAIL_NULL_V(md, 0);
diff --git a/platform/windows/native_menu_windows.cpp b/platform/windows/native_menu_windows.cpp
index ea1c118167f..63d0f22c3c3 100644
--- a/platform/windows/native_menu_windows.cpp
+++ b/platform/windows/native_menu_windows.cpp
@@ -1122,6 +1122,58 @@ void NativeMenuWindows::set_item_indentation_level(const RID &p_rid, int p_idx,
// Not supported.
}
+int NativeMenuWindows::set_item_index(const RID &p_rid, int p_idx, int p_target_idx) {
+ ERR_FAIL_COND_V(p_idx < 0, -1);
+ const MenuData *md = menus.get_or_null(p_rid);
+ ERR_FAIL_NULL_V(md, -1);
+ int count = GetMenuItemCount(md->menu);
+ ERR_FAIL_COND_V(p_idx >= count, -1);
+ ERR_FAIL_INDEX_V(p_target_idx, count, -1);
+
+ // Get item text separately.
+ MENUITEMINFOW item;
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_STRING;
+ item.dwTypeData = nullptr;
+ Char16String str;
+ if (!GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ return -1;
+ }
+ item.cch++;
+ str.resize_uninitialized(item.cch);
+ item.dwTypeData = (LPWSTR)str.ptrw();
+ if (!GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ return -1;
+ }
+
+ ZeroMemory(&item, sizeof(item));
+ item.cbSize = sizeof(item);
+ item.fMask = MIIM_FTYPE | MIIM_DATA | MIIM_STRING | MIIM_STATE | MIIM_SUBMENU | MIIM_BITMAP;
+ if (!GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
+ return -1;
+ }
+
+ item.dwTypeData = (LPWSTR)str.get_data();
+
+ if (!RemoveMenu(md->menu, p_idx, MF_BYPOSITION)) {
+ return -1;
+ }
+ if (!InsertMenuItemW(md->menu, p_target_idx, true, &item)) {
+ // Delete item if failed to insert.
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ if (item_data->bmp) {
+ DeleteObject(item_data->bmp);
+ }
+ memdelete(item_data);
+ }
+ return -1;
+ }
+
+ return p_target_idx;
+}
+
int NativeMenuWindows::get_item_count(const RID &p_rid) const {
const MenuData *md = menus.get_or_null(p_rid);
ERR_FAIL_NULL_V(md, 0);
diff --git a/platform/windows/native_menu_windows.h b/platform/windows/native_menu_windows.h
index 785772c84a1..63a8ccc7e67 100644
--- a/platform/windows/native_menu_windows.h
+++ b/platform/windows/native_menu_windows.h
@@ -142,6 +142,7 @@ public:
virtual void set_item_max_states(const RID &p_rid, int p_idx, int p_max_states) override;
virtual void set_item_icon(const RID &p_rid, int p_idx, const Ref &p_icon) override;
virtual void set_item_indentation_level(const RID &p_rid, int p_idx, int p_level) override;
+ virtual int set_item_index(const RID &p_rid, int p_idx, int p_target_idx) override;
virtual int get_item_count(const RID &p_rid) const override;
virtual bool is_system_menu(const RID &p_rid) const override;
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 775f6334dd1..0d5a7936304 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -2633,6 +2633,39 @@ void PopupMenu::set_item_shortcut_disabled(int p_idx, bool p_disabled) {
_menu_changed();
}
+void PopupMenu::set_item_index(int p_idx, int p_target_idx) {
+ if (p_idx < 0) {
+ p_idx += get_item_count();
+ }
+ ERR_FAIL_INDEX(p_idx, items.size());
+
+ if (p_target_idx < 0) {
+ p_target_idx += get_item_count();
+ }
+ ERR_FAIL_INDEX(p_target_idx, items.size());
+
+ if (p_idx == p_target_idx) {
+ return;
+ }
+
+ Item item = items[p_idx];
+ items.remove_at(p_idx);
+ items.insert(p_target_idx, item);
+
+ if (global_menu.is_valid()) {
+ NativeMenu *nmenu = NativeMenu::get_singleton();
+ nmenu->set_item_index(global_menu, p_idx, p_target_idx);
+ // Update tags of all affected items to their new index.
+ for (int i = MIN(p_idx, p_target_idx); i <= MAX(p_idx, p_target_idx); i++) {
+ nmenu->set_item_tag(global_menu, i, i);
+ }
+ }
+
+ queue_accessibility_update();
+ control->queue_redraw();
+ _menu_changed();
+}
+
void PopupMenu::toggle_item_multistate(int p_idx) {
ERR_FAIL_INDEX(p_idx, items.size());
if (0 >= items[p_idx].max_states) {
@@ -3189,6 +3222,7 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_item_multistate", "index", "state"), &PopupMenu::set_item_multistate);
ClassDB::bind_method(D_METHOD("set_item_multistate_max", "index", "max_states"), &PopupMenu::set_item_max_states);
ClassDB::bind_method(D_METHOD("set_item_shortcut_disabled", "index", "disabled"), &PopupMenu::set_item_shortcut_disabled);
+ ClassDB::bind_method(D_METHOD("set_item_index", "index", "target_index"), &PopupMenu::set_item_index);
ClassDB::bind_method(D_METHOD("toggle_item_checked", "index"), &PopupMenu::toggle_item_checked);
ClassDB::bind_method(D_METHOD("toggle_item_multistate", "index"), &PopupMenu::toggle_item_multistate);
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index a600310ce43..6f5cbdfde31 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -332,6 +332,7 @@ public:
void set_item_multistate(int p_idx, int p_state);
void toggle_item_multistate(int p_idx);
void set_item_shortcut_disabled(int p_idx, bool p_disabled);
+ void set_item_index(int p_idx, int p_target_idx);
void toggle_item_checked(int p_idx);
diff --git a/servers/display/native_menu.cpp b/servers/display/native_menu.cpp
index d1a5bfd78c0..a833e56be04 100644
--- a/servers/display/native_menu.cpp
+++ b/servers/display/native_menu.cpp
@@ -109,6 +109,7 @@ void NativeMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_item_max_states", "rid", "idx", "max_states"), &NativeMenu::set_item_max_states);
ClassDB::bind_method(D_METHOD("set_item_icon", "rid", "idx", "icon"), &NativeMenu::set_item_icon);
ClassDB::bind_method(D_METHOD("set_item_indentation_level", "rid", "idx", "level"), &NativeMenu::set_item_indentation_level);
+ ClassDB::bind_method(D_METHOD("set_item_index", "rid", "idx", "target_idx"), &NativeMenu::set_item_index);
ClassDB::bind_method(D_METHOD("get_item_count", "rid"), &NativeMenu::get_item_count);
ClassDB::bind_method(D_METHOD("is_system_menu", "rid"), &NativeMenu::is_system_menu);
@@ -444,6 +445,11 @@ void NativeMenu::set_item_indentation_level(const RID &p_rid, int p_idx, int p_l
WARN_PRINT("Global menus are not supported on this platform.");
}
+int NativeMenu::set_item_index(const RID &p_rid, int p_idx, int p_target_idx) {
+ WARN_PRINT("Global menus are not supported on this platform.");
+ return -1;
+}
+
int NativeMenu::get_item_count(const RID &p_rid) const {
WARN_PRINT("Global menus are not supported on this platform.");
return 0;
diff --git a/servers/display/native_menu.h b/servers/display/native_menu.h
index a58dce6cdb3..47d7f8e1fb1 100644
--- a/servers/display/native_menu.h
+++ b/servers/display/native_menu.h
@@ -140,6 +140,7 @@ public:
virtual void set_item_max_states(const RID &p_rid, int p_idx, int p_max_states);
virtual void set_item_icon(const RID &p_rid, int p_idx, const Ref &p_icon);
virtual void set_item_indentation_level(const RID &p_rid, int p_idx, int p_level);
+ virtual int set_item_index(const RID &p_rid, int p_idx, int p_target_idx);
virtual int get_item_count(const RID &p_rid) const;
virtual bool is_system_menu(const RID &p_rid) const;