diff options
author | bruvzg <7645683+bruvzg@users.noreply.github.com> | 2024-03-07 20:42:24 +0200 |
---|---|---|
committer | bruvzg <7645683+bruvzg@users.noreply.github.com> | 2024-03-13 10:51:38 +0200 |
commit | ac7583e4498598281e4372d69d7aab10d90aeacb (patch) | |
tree | 3ca2b6ec91bce1f68db0d3e768dd4ed6ae5b9a88 /platform/windows/native_menu_windows.cpp | |
parent | 61282068f4d59cb48f35ad95391728c58d9008ab (diff) | |
download | redot-engine-ac7583e4498598281e4372d69d7aab10d90aeacb.tar.gz |
[NativeMenu] Implement native popup menu support on Windows.
Diffstat (limited to 'platform/windows/native_menu_windows.cpp')
-rw-r--r-- | platform/windows/native_menu_windows.cpp | 1149 |
1 files changed, 1149 insertions, 0 deletions
diff --git a/platform/windows/native_menu_windows.cpp b/platform/windows/native_menu_windows.cpp new file mode 100644 index 0000000000..eea30cab9a --- /dev/null +++ b/platform/windows/native_menu_windows.cpp @@ -0,0 +1,1149 @@ +/**************************************************************************/ +/* 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<Image> &p_img) const { + 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<BITMAPINFO *>(&bi), DIB_RGB_COLORS, reinterpret_cast<void **>(&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 | MNS_MODELESS; + 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; + } + TrackPopupMenuEx(md->menu, flags, p_position.x, p_position.y, hwnd, nullptr); +} + +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; +} + +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.ptrw(); + + 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.ptrw(); + + 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.ptrw(); + + 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<Texture2D> &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; + 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.ptrw(); + 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<Texture2D> &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; + 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.ptrw(); + 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.ptrw(); + + 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<Texture2D> &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; + 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.ptrw(); + 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.ptrw(); + + 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; + if (GetMenuItemInfoW(md->menu, i, true, &item)) { + if (String::utf16((const char16_t *)item.dwTypeData) == 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; + if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { + return String::utf16((const char16_t *)item.dwTypeData); + } + 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<Texture2D> NativeMenuWindows::get_item_icon(const RID &p_rid, int p_idx) const { + ERR_FAIL_COND_V(p_idx < 0, Ref<Texture2D>()); + const MenuData *md = menus.get_or_null(p_rid); + ERR_FAIL_NULL_V(md, Ref<Texture2D>()); + int count = GetMenuItemCount(md->menu); + ERR_FAIL_COND_V(p_idx >= count, Ref<Texture2D>()); + + 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<Texture2D>(); +} + +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.ptrw(); + 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<Texture2D> &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()) { + 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<Image>(); + 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() {} |