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;