/**************************************************************************/ /* native_menu_windows.cpp */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /**************************************************************************/ /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ #include "native_menu_windows.h" #include "display_server_windows.h" #include "scene/resources/image_texture.h" HBITMAP NativeMenuWindows::_make_bitmap(const Ref &p_img) const { p_img->convert(Image::FORMAT_RGBA8); Vector2i texture_size = p_img->get_size(); UINT image_size = texture_size.width * texture_size.height; COLORREF *buffer = nullptr; BITMAPV5HEADER bi; ZeroMemory(&bi, sizeof(bi)); bi.bV5Size = sizeof(bi); bi.bV5Width = texture_size.width; bi.bV5Height = -texture_size.height; bi.bV5Planes = 1; bi.bV5BitCount = 32; bi.bV5Compression = BI_BITFIELDS; bi.bV5RedMask = 0x00ff0000; bi.bV5GreenMask = 0x0000ff00; bi.bV5BlueMask = 0x000000ff; bi.bV5AlphaMask = 0xff000000; HDC dc = GetDC(nullptr); HBITMAP bitmap = CreateDIBSection(dc, reinterpret_cast(&bi), DIB_RGB_COLORS, reinterpret_cast(&buffer), nullptr, 0); for (UINT index = 0; index < image_size; index++) { int row_index = floor(index / texture_size.width); int column_index = (index % int(texture_size.width)); const Color &c = p_img->get_pixel(column_index, row_index); *(buffer + index) = c.to_argb32(); } ReleaseDC(nullptr, dc); return bitmap; } void NativeMenuWindows::_menu_activate(HMENU p_menu, int p_index) const { if (menu_lookup.has(p_menu)) { MenuData *md = menus.get_or_null(menu_lookup[p_menu]); if (md) { int count = GetMenuItemCount(md->menu); if (p_index >= 0 && p_index < count) { MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_STATE | MIIM_DATA; 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; const Variant *args[1] = { &item_data->meta }; item_data->callback.callp(args, 1, ret, ce); if (ce.error != Callable::CallError::CALL_OK) { ERR_PRINT(vformat("Failed to execute menu callback: %s.", Variant::get_callable_error_text(item_data->callback, args, 1, ce))); } } } } } } } } bool NativeMenuWindows::has_feature(Feature p_feature) const { switch (p_feature) { // case FEATURE_GLOBAL_MENU: // case FEATURE_OPEN_CLOSE_CALLBACK: // case FEATURE_HOVER_CALLBACK: // case FEATURE_KEY_CALLBACK: case FEATURE_POPUP_MENU: return true; default: return false; } } bool NativeMenuWindows::has_system_menu(SystemMenus p_menu_id) const { return false; } RID NativeMenuWindows::get_system_menu(SystemMenus p_menu_id) const { return RID(); } RID NativeMenuWindows::create_menu() { MenuData *md = memnew(MenuData); md->menu = CreatePopupMenu(); MENUINFO menu_info; ZeroMemory(&menu_info, sizeof(menu_info)); menu_info.cbSize = sizeof(menu_info); menu_info.fMask = MIM_STYLE; menu_info.dwStyle = MNS_NOTIFYBYPOS; SetMenuInfo(md->menu, &menu_info); RID rid = menus.make_rid(md); menu_lookup[md->menu] = rid; return rid; } bool NativeMenuWindows::has_menu(const RID &p_rid) const { return menus.owns(p_rid); } void NativeMenuWindows::free_menu(const RID &p_rid) { MenuData *md = menus.get_or_null(p_rid); if (md) { clear(p_rid); DestroyMenu(md->menu); menus.free(p_rid); menu_lookup.erase(md->menu); memdelete(md); } } Size2 NativeMenuWindows::get_size(const RID &p_rid) const { const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL_V(md, Size2()); Size2 size; int count = GetMenuItemCount(md->menu); for (int i = 0; i < count; i++) { RECT rect; if (GetMenuItemRect(NULL, md->menu, i, &rect)) { size.x = MAX(size.x, rect.right - rect.left); size.y += rect.bottom - rect.top; } } return size; } void NativeMenuWindows::popup(const RID &p_rid, const Vector2i &p_position) { const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL(md); HWND hwnd = (HWND)DisplayServer::get_singleton()->window_get_native_handle(DisplayServer::WINDOW_HANDLE, DisplayServer::MAIN_WINDOW_ID); UINT flags = TPM_HORIZONTAL | TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON | TPM_VERPOSANIMATION; if (md->is_rtl) { flags |= TPM_LAYOUTRTL; } SetForegroundWindow(hwnd); TrackPopupMenuEx(md->menu, flags, p_position.x, p_position.y, hwnd, nullptr); PostMessage(hwnd, WM_NULL, 0, 0); } void NativeMenuWindows::set_interface_direction(const RID &p_rid, bool p_is_rtl) { MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL(md); if (md->is_rtl == p_is_rtl) { return; } md->is_rtl = p_is_rtl; } void NativeMenuWindows::set_popup_open_callback(const RID &p_rid, const Callable &p_callback) { // Not supported. } Callable NativeMenuWindows::get_popup_open_callback(const RID &p_rid) const { // Not supported. return Callable(); } void NativeMenuWindows::set_popup_close_callback(const RID &p_rid, const Callable &p_callback) { // Not supported. } Callable NativeMenuWindows::get_popup_close_callback(const RID &p_rid) const { // Not supported. return Callable(); } void NativeMenuWindows::set_minimum_width(const RID &p_rid, float p_width) { // Not supported. } float NativeMenuWindows::get_minimum_width(const RID &p_rid) const { // Not supported. return 0.f; } bool NativeMenuWindows::is_opened(const RID &p_rid) const { // Not supported. return false; } int NativeMenuWindows::add_submenu_item(const RID &p_rid, const String &p_label, const RID &p_submenu_rid, const Variant &p_tag, int p_index) { MenuData *md = menus.get_or_null(p_rid); MenuData *md_sub = menus.get_or_null(p_submenu_rid); ERR_FAIL_NULL_V(md, -1); ERR_FAIL_NULL_V(md_sub, -1); ERR_FAIL_COND_V_MSG(md->menu == md_sub->menu, -1, "Can't set submenu to self!"); if (p_index == -1) { p_index = GetMenuItemCount(md->menu); } else { p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu)); } MenuItemData *item_data = memnew(MenuItemData); item_data->meta = p_tag; item_data->checkable_type = CHECKABLE_TYPE_NONE; item_data->max_states = 0; item_data->state = 0; Char16String label = p_label.utf16(); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA | MIIM_SUBMENU; item.fType = MFT_STRING; item.hSubMenu = md_sub->menu; item.dwItemData = (ULONG_PTR)item_data; item.dwTypeData = (LPWSTR)label.get_data(); if (!InsertMenuItemW(md->menu, p_index, true, &item)) { memdelete(item_data); return -1; } return p_index; } int NativeMenuWindows::add_item(const RID &p_rid, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) { const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL_V(md, -1); if (p_index == -1) { p_index = GetMenuItemCount(md->menu); } else { p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu)); } MenuItemData *item_data = memnew(MenuItemData); item_data->callback = p_callback; item_data->meta = p_tag; item_data->checkable_type = CHECKABLE_TYPE_NONE; item_data->max_states = 0; item_data->state = 0; Char16String label = p_label.utf16(); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA; item.fType = MFT_STRING; item.dwItemData = (ULONG_PTR)item_data; item.dwTypeData = (LPWSTR)label.get_data(); if (!InsertMenuItemW(md->menu, p_index, true, &item)) { memdelete(item_data); return -1; } return p_index; } int NativeMenuWindows::add_check_item(const RID &p_rid, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) { const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL_V(md, -1); if (p_index == -1) { p_index = GetMenuItemCount(md->menu); } else { p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu)); } MenuItemData *item_data = memnew(MenuItemData); item_data->callback = p_callback; item_data->meta = p_tag; item_data->checkable_type = CHECKABLE_TYPE_CHECK_BOX; item_data->max_states = 0; item_data->state = 0; Char16String label = p_label.utf16(); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA; item.fType = MFT_STRING; item.dwItemData = (ULONG_PTR)item_data; item.dwTypeData = (LPWSTR)label.get_data(); if (!InsertMenuItemW(md->menu, p_index, true, &item)) { memdelete(item_data); return -1; } return p_index; } int NativeMenuWindows::add_icon_item(const RID &p_rid, const Ref &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) { const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL_V(md, -1); if (p_index == -1) { p_index = GetMenuItemCount(md->menu); } else { p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu)); } MenuItemData *item_data = memnew(MenuItemData); item_data->callback = p_callback; item_data->meta = p_tag; item_data->checkable_type = CHECKABLE_TYPE_NONE; item_data->max_states = 0; item_data->state = 0; if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { item_data->img = p_icon->get_image(); item_data->img = item_data->img->duplicate(); if (item_data->img->is_compressed()) { item_data->img->decompress(); } item_data->bmp = _make_bitmap(item_data->img); } Char16String label = p_label.utf16(); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA | MIIM_BITMAP; item.fType = MFT_STRING; item.dwItemData = (ULONG_PTR)item_data; item.dwTypeData = (LPWSTR)label.get_data(); item.hbmpItem = item_data->bmp; if (!InsertMenuItemW(md->menu, p_index, true, &item)) { memdelete(item_data); return -1; } return p_index; } int NativeMenuWindows::add_icon_check_item(const RID &p_rid, const Ref &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) { const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL_V(md, -1); if (p_index == -1) { p_index = GetMenuItemCount(md->menu); } else { p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu)); } MenuItemData *item_data = memnew(MenuItemData); item_data->callback = p_callback; item_data->meta = p_tag; item_data->checkable_type = CHECKABLE_TYPE_CHECK_BOX; item_data->max_states = 0; item_data->state = 0; if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { item_data->img = p_icon->get_image(); item_data->img = item_data->img->duplicate(); if (item_data->img->is_compressed()) { item_data->img->decompress(); } item_data->bmp = _make_bitmap(item_data->img); } Char16String label = p_label.utf16(); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA | MIIM_BITMAP; item.fType = MFT_STRING; item.dwItemData = (ULONG_PTR)item_data; item.dwTypeData = (LPWSTR)label.get_data(); item.hbmpItem = item_data->bmp; if (!InsertMenuItemW(md->menu, p_index, true, &item)) { memdelete(item_data); return -1; } return p_index; } int NativeMenuWindows::add_radio_check_item(const RID &p_rid, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) { const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL_V(md, -1); if (p_index == -1) { p_index = GetMenuItemCount(md->menu); } else { p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu)); } MenuItemData *item_data = memnew(MenuItemData); item_data->callback = p_callback; item_data->meta = p_tag; item_data->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON; item_data->max_states = 0; item_data->state = 0; Char16String label = p_label.utf16(); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA; item.fType = MFT_STRING | MFT_RADIOCHECK; item.dwItemData = (ULONG_PTR)item_data; item.dwTypeData = (LPWSTR)label.get_data(); if (!InsertMenuItemW(md->menu, p_index, true, &item)) { memdelete(item_data); return -1; } return p_index; } int NativeMenuWindows::add_icon_radio_check_item(const RID &p_rid, const Ref &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) { const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL_V(md, -1); if (p_index == -1) { p_index = GetMenuItemCount(md->menu); } else { p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu)); } MenuItemData *item_data = memnew(MenuItemData); item_data->callback = p_callback; item_data->meta = p_tag; item_data->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON; item_data->max_states = 0; item_data->state = 0; if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { item_data->img = p_icon->get_image(); item_data->img = item_data->img->duplicate(); if (item_data->img->is_compressed()) { item_data->img->decompress(); } item_data->bmp = _make_bitmap(item_data->img); } Char16String label = p_label.utf16(); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA | MIIM_BITMAP; item.fType = MFT_STRING | MFT_RADIOCHECK; item.dwItemData = (ULONG_PTR)item_data; item.dwTypeData = (LPWSTR)label.get_data(); item.hbmpItem = item_data->bmp; if (!InsertMenuItemW(md->menu, p_index, true, &item)) { memdelete(item_data); return -1; } return p_index; } int NativeMenuWindows::add_multistate_item(const RID &p_rid, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) { const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL_V(md, -1); if (p_index == -1) { p_index = GetMenuItemCount(md->menu); } else { p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu)); } MenuItemData *item_data = memnew(MenuItemData); item_data->callback = p_callback; item_data->meta = p_tag; item_data->checkable_type = CHECKABLE_TYPE_NONE; item_data->max_states = p_max_states; item_data->state = p_default_state; Char16String label = p_label.utf16(); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA; item.fType = MFT_STRING; item.dwItemData = (ULONG_PTR)item_data; item.dwTypeData = (LPWSTR)label.get_data(); if (!InsertMenuItemW(md->menu, p_index, true, &item)) { memdelete(item_data); return -1; } return p_index; } int NativeMenuWindows::add_separator(const RID &p_rid, int p_index) { const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL_V(md, -1); if (p_index == -1) { p_index = GetMenuItemCount(md->menu); } else { p_index = CLAMP(p_index, 0, GetMenuItemCount(md->menu)); } MenuItemData *item_data = memnew(MenuItemData); item_data->checkable_type = CHECKABLE_TYPE_NONE; item_data->max_states = 0; item_data->state = 0; MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_FTYPE | MIIM_DATA; item.fType = MFT_SEPARATOR; item.dwItemData = (ULONG_PTR)item_data; if (!InsertMenuItemW(md->menu, p_index, true, &item)) { memdelete(item_data); return -1; } return p_index; } int NativeMenuWindows::find_item_index_with_text(const RID &p_rid, const String &p_text) const { const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL_V(md, -1); MENUITEMINFOW item; int count = GetMenuItemCount(md->menu); for (int i = 0; i < count; i++) { ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_STRING; item.dwTypeData = nullptr; if (GetMenuItemInfoW(md->menu, i, true, &item)) { item.cch++; Char16String str; str.resize(item.cch); item.dwTypeData = (LPWSTR)str.ptrw(); if (GetMenuItemInfoW(md->menu, i, true, &item)) { if (String::utf16((const char16_t *)str.get_data()) == p_text) { return i; } } } } return -1; } int NativeMenuWindows::find_item_index_with_tag(const RID &p_rid, const Variant &p_tag) const { const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL_V(md, -1); MENUITEMINFOW item; int count = GetMenuItemCount(md->menu); for (int i = 0; i < count; i++) { ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_DATA; if (GetMenuItemInfoW(md->menu, i, true, &item)) { MenuItemData *item_data = (MenuItemData *)item.dwItemData; if (item_data) { if (item_data->meta == p_tag) { return i; } } } } return -1; } bool NativeMenuWindows::is_item_checked(const RID &p_rid, int p_idx) const { ERR_FAIL_COND_V(p_idx < 0, false); const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL_V(md, false); int count = GetMenuItemCount(md->menu); ERR_FAIL_COND_V(p_idx >= count, false); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_STATE; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { return (item.fState & MFS_CHECKED) == MFS_CHECKED; } return false; } bool NativeMenuWindows::is_item_checkable(const RID &p_rid, int p_idx) const { ERR_FAIL_COND_V(p_idx < 0, false); const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL_V(md, false); int count = GetMenuItemCount(md->menu); ERR_FAIL_COND_V(p_idx >= count, false); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_DATA; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { MenuItemData *item_data = (MenuItemData *)item.dwItemData; if (item_data) { return item_data->checkable_type == CHECKABLE_TYPE_CHECK_BOX; } } return false; } bool NativeMenuWindows::is_item_radio_checkable(const RID &p_rid, int p_idx) const { ERR_FAIL_COND_V(p_idx < 0, false); const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL_V(md, false); int count = GetMenuItemCount(md->menu); ERR_FAIL_COND_V(p_idx >= count, false); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_DATA; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { MenuItemData *item_data = (MenuItemData *)item.dwItemData; if (item_data) { return item_data->checkable_type == CHECKABLE_TYPE_RADIO_BUTTON; } } return false; } Callable NativeMenuWindows::get_item_callback(const RID &p_rid, int p_idx) const { ERR_FAIL_COND_V(p_idx < 0, Callable()); const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL_V(md, Callable()); int count = GetMenuItemCount(md->menu); ERR_FAIL_COND_V(p_idx >= count, Callable()); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_DATA; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { MenuItemData *item_data = (MenuItemData *)item.dwItemData; if (item_data) { return item_data->callback; } } return Callable(); } Callable NativeMenuWindows::get_item_key_callback(const RID &p_rid, int p_idx) const { // Not supported. return Callable(); } Variant NativeMenuWindows::get_item_tag(const RID &p_rid, int p_idx) const { ERR_FAIL_COND_V(p_idx < 0, Variant()); const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL_V(md, Variant()); int count = GetMenuItemCount(md->menu); ERR_FAIL_COND_V(p_idx >= count, Variant()); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_DATA; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { MenuItemData *item_data = (MenuItemData *)item.dwItemData; if (item_data) { return item_data->meta; } } return Variant(); } String NativeMenuWindows::get_item_text(const RID &p_rid, int p_idx) const { ERR_FAIL_COND_V(p_idx < 0, String()); const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL_V(md, String()); int count = GetMenuItemCount(md->menu); ERR_FAIL_COND_V(p_idx >= count, String()); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_STRING; item.dwTypeData = nullptr; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { item.cch++; Char16String str; str.resize(item.cch); item.dwTypeData = (LPWSTR)str.ptrw(); if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { return String::utf16((const char16_t *)str.get_data()); } } return String(); } RID NativeMenuWindows::get_item_submenu(const RID &p_rid, int p_idx) const { ERR_FAIL_COND_V(p_idx < 0, RID()); const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL_V(md, RID()); int count = GetMenuItemCount(md->menu); ERR_FAIL_COND_V(p_idx >= count, RID()); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_SUBMENU; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { if (menu_lookup.has(item.hSubMenu)) { return menu_lookup[item.hSubMenu]; } } return RID(); } Key NativeMenuWindows::get_item_accelerator(const RID &p_rid, int p_idx) const { // Not supported. return Key::NONE; } bool NativeMenuWindows::is_item_disabled(const RID &p_rid, int p_idx) const { ERR_FAIL_COND_V(p_idx < 0, false); const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL_V(md, false); int count = GetMenuItemCount(md->menu); ERR_FAIL_COND_V(p_idx >= count, false); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_STATE; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { return (item.fState & MFS_DISABLED) == MFS_DISABLED; } return false; } bool NativeMenuWindows::is_item_hidden(const RID &p_rid, int p_idx) const { // Not supported. return false; } String NativeMenuWindows::get_item_tooltip(const RID &p_rid, int p_idx) const { // Not supported. return String(); } int NativeMenuWindows::get_item_state(const RID &p_rid, int p_idx) const { 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); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_DATA; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { MenuItemData *item_data = (MenuItemData *)item.dwItemData; if (item_data) { return item_data->state; } } return -1; } int NativeMenuWindows::get_item_max_states(const RID &p_rid, int p_idx) const { 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); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_DATA; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { MenuItemData *item_data = (MenuItemData *)item.dwItemData; if (item_data) { return item_data->max_states; } } return -1; } Ref NativeMenuWindows::get_item_icon(const RID &p_rid, int p_idx) const { ERR_FAIL_COND_V(p_idx < 0, Ref()); const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL_V(md, Ref()); int count = GetMenuItemCount(md->menu); ERR_FAIL_COND_V(p_idx >= count, Ref()); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_DATA; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { MenuItemData *item_data = (MenuItemData *)item.dwItemData; if (item_data) { return ImageTexture::create_from_image(item_data->img); } } return Ref(); } int NativeMenuWindows::get_item_indentation_level(const RID &p_rid, int p_idx) const { // Not supported. return 0; } void NativeMenuWindows::set_item_checked(const RID &p_rid, int p_idx, bool p_checked) { ERR_FAIL_COND(p_idx < 0); const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL(md); int count = GetMenuItemCount(md->menu); ERR_FAIL_COND(p_idx >= count); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_STATE; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { if (p_checked) { item.fState |= MFS_CHECKED; } else { item.fState &= ~MFS_CHECKED; } SetMenuItemInfoW(md->menu, p_idx, true, &item); } } void NativeMenuWindows::set_item_checkable(const RID &p_rid, int p_idx, bool p_checkable) { ERR_FAIL_COND(p_idx < 0); const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL(md); int count = GetMenuItemCount(md->menu); ERR_FAIL_COND(p_idx >= count); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_FTYPE | MIIM_DATA; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { MenuItemData *item_data = (MenuItemData *)item.dwItemData; if (item_data) { item.fType &= ~MFT_RADIOCHECK; item_data->checkable_type = (p_checkable) ? CHECKABLE_TYPE_CHECK_BOX : CHECKABLE_TYPE_NONE; SetMenuItemInfoW(md->menu, p_idx, true, &item); } } } void NativeMenuWindows::set_item_radio_checkable(const RID &p_rid, int p_idx, bool p_checkable) { ERR_FAIL_COND(p_idx < 0); const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL(md); int count = GetMenuItemCount(md->menu); ERR_FAIL_COND(p_idx >= count); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_FTYPE | MIIM_DATA; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { MenuItemData *item_data = (MenuItemData *)item.dwItemData; if (item_data) { if (p_checkable) { item.fType |= MFT_RADIOCHECK; item_data->checkable_type = CHECKABLE_TYPE_CHECK_BOX; } else { item.fType &= ~MFT_RADIOCHECK; item_data->checkable_type = CHECKABLE_TYPE_NONE; } SetMenuItemInfoW(md->menu, p_idx, true, &item); } } } void NativeMenuWindows::set_item_callback(const RID &p_rid, int p_idx, const Callable &p_callback) { ERR_FAIL_COND(p_idx < 0); const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL(md); int count = GetMenuItemCount(md->menu); ERR_FAIL_COND(p_idx >= count); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_DATA; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { MenuItemData *item_data = (MenuItemData *)item.dwItemData; if (item_data) { item_data->callback = p_callback; } } } void NativeMenuWindows::set_item_key_callback(const RID &p_rid, int p_idx, const Callable &p_key_callback) { // Not supported. } void NativeMenuWindows::set_item_hover_callbacks(const RID &p_rid, int p_idx, const Callable &p_callback) { // Not supported. } void NativeMenuWindows::set_item_tag(const RID &p_rid, int p_idx, const Variant &p_tag) { ERR_FAIL_COND(p_idx < 0); const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL(md); int count = GetMenuItemCount(md->menu); ERR_FAIL_COND(p_idx >= count); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_DATA; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { MenuItemData *item_data = (MenuItemData *)item.dwItemData; if (item_data) { item_data->meta = p_tag; } } } void NativeMenuWindows::set_item_text(const RID &p_rid, int p_idx, const String &p_text) { ERR_FAIL_COND(p_idx < 0); const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL(md); int count = GetMenuItemCount(md->menu); ERR_FAIL_COND(p_idx >= count); Char16String label = p_text.utf16(); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { item.dwTypeData = (LPWSTR)label.get_data(); SetMenuItemInfoW(md->menu, p_idx, true, &item); } } void NativeMenuWindows::set_item_submenu(const RID &p_rid, int p_idx, const RID &p_submenu_rid) { ERR_FAIL_COND(p_idx < 0); const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL(md); int count = GetMenuItemCount(md->menu); ERR_FAIL_COND(p_idx >= count); MenuData *md_sub = menus.get_or_null(p_submenu_rid); ERR_FAIL_COND_MSG(md->menu == md_sub->menu, "Can't set submenu to self!"); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_SUBMENU; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { if (p_submenu_rid.is_valid()) { item.hSubMenu = md_sub->menu; } else { item.hSubMenu = 0; } SetMenuItemInfoW(md->menu, p_idx, true, &item); } } void NativeMenuWindows::set_item_accelerator(const RID &p_rid, int p_idx, Key p_keycode) { // Not supported. } void NativeMenuWindows::set_item_disabled(const RID &p_rid, int p_idx, bool p_disabled) { ERR_FAIL_COND(p_idx < 0); const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL(md); int count = GetMenuItemCount(md->menu); ERR_FAIL_COND(p_idx >= count); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_STATE; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { if (p_disabled) { item.fState |= MFS_DISABLED; } else { item.fState &= ~MFS_DISABLED; } SetMenuItemInfoW(md->menu, p_idx, true, &item); } } void NativeMenuWindows::set_item_hidden(const RID &p_rid, int p_idx, bool p_hidden) { // Not supported. } void NativeMenuWindows::set_item_tooltip(const RID &p_rid, int p_idx, const String &p_tooltip) { // Not supported. } void NativeMenuWindows::set_item_state(const RID &p_rid, int p_idx, int p_state) { ERR_FAIL_COND(p_idx < 0); const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL(md); int count = GetMenuItemCount(md->menu); ERR_FAIL_COND(p_idx >= count); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_DATA; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { MenuItemData *item_data = (MenuItemData *)item.dwItemData; if (item_data) { item_data->state = p_state; } } } void NativeMenuWindows::set_item_max_states(const RID &p_rid, int p_idx, int p_max_states) { ERR_FAIL_COND(p_idx < 0); const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL(md); int count = GetMenuItemCount(md->menu); ERR_FAIL_COND(p_idx >= count); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_DATA; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { MenuItemData *item_data = (MenuItemData *)item.dwItemData; if (item_data) { item_data->max_states = p_max_states; } } } void NativeMenuWindows::set_item_icon(const RID &p_rid, int p_idx, const Ref &p_icon) { ERR_FAIL_COND(p_idx < 0); const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL(md); int count = GetMenuItemCount(md->menu); ERR_FAIL_COND(p_idx >= count); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_DATA | MIIM_BITMAP; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { MenuItemData *item_data = (MenuItemData *)item.dwItemData; if (item_data) { if (item_data->bmp) { DeleteObject(item_data->bmp); } if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { item_data->img = p_icon->get_image(); item_data->img = item_data->img->duplicate(); if (item_data->img->is_compressed()) { item_data->img->decompress(); } item_data->bmp = _make_bitmap(item_data->img); } else { item_data->img = Ref(); item_data->bmp = 0; } item.hbmpItem = item_data->bmp; SetMenuItemInfoW(md->menu, p_idx, true, &item); } } } void NativeMenuWindows::set_item_indentation_level(const RID &p_rid, int p_idx, int p_level) { // Not supported. } 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); return GetMenuItemCount(md->menu); } bool NativeMenuWindows::is_system_menu(const RID &p_rid) const { return false; } void NativeMenuWindows::remove_item(const RID &p_rid, int p_idx) { ERR_FAIL_COND(p_idx < 0); const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL(md); int count = GetMenuItemCount(md->menu); ERR_FAIL_COND(p_idx >= count); MENUITEMINFOW item; ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_DATA; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { MenuItemData *item_data = (MenuItemData *)item.dwItemData; if (item_data) { if (item_data->bmp) { DeleteObject(item_data->bmp); } memdelete(item_data); } } RemoveMenu(md->menu, p_idx, MF_BYPOSITION); } void NativeMenuWindows::clear(const RID &p_rid) { const MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL(md); MENUITEMINFOW item; int count = GetMenuItemCount(md->menu); for (int i = 0; i < count; i++) { ZeroMemory(&item, sizeof(item)); item.cbSize = sizeof(item); item.fMask = MIIM_DATA; if (GetMenuItemInfoW(md->menu, 0, true, &item)) { MenuItemData *item_data = (MenuItemData *)item.dwItemData; if (item_data) { if (item_data->bmp) { DeleteObject(item_data->bmp); } memdelete(item_data); } } RemoveMenu(md->menu, 0, MF_BYPOSITION); } } NativeMenuWindows::NativeMenuWindows() {} NativeMenuWindows::~NativeMenuWindows() {}