diff --git a/doc/classes/NativeMenu.xml b/doc/classes/NativeMenu.xml index 159666ded8c..cc81c81a24a 100644 --- a/doc/classes/NativeMenu.xml +++ b/doc/classes/NativeMenu.xml @@ -5,6 +5,35 @@ [NativeMenu] handles low-level access to the OS native global menu bar and popup menus. + [b]Note:[/b] This is low-level API, consider using [MenuBar] with [member MenuBar.prefer_global_menu] set to [code]true[/code], and [PopupMenu] with [member PopupMenu.prefer_native_menu] set to [code]true[/code]. + To create a menu, use [method create_menu], add menu items using [code]add_*_item[/code] methods. To remove a menu, use [method free_menu]. + [codeblock] + var menu + + func _menu_callback(item_id): + if item_id == "ITEM_CUT": + cut() + elif item_id == "ITEM_COPY": + copy() + elif item_id == "ITEM_PASTE": + paste() + + func _enter_tree(): + # Create new menu and add items: + menu = NativeMenu.create_menu() + NativeMenu.add_item(menu, "Cut", _menu_callback, Callable(), "ITEM_CUT") + NativeMenu.add_item(menu, "Copy", _menu_callback, Callable(), "ITEM_COPY") + NativeMenu.add_separator(menu) + NativeMenu.add_item(menu, "Paste", _menu_callback, Callable(), "ITEM_PASTE") + + func _on_button_pressed(): + # Show popup menu at mouse position: + NativeMenu.popup(menu, DisplayServer.mouse_get_position()) + + func _exit_tree(): + # Remove menu when it's no longer needed: + NativeMenu.free_menu(menu) + [/codeblock] @@ -23,7 +52,8 @@ Returns index of the inserted item, it's not guaranteed to be the same as [param index] value. An [param accelerator] can optionally be defined, which is a keyboard shortcut that can be pressed to trigger the menu button even if it's not currently open. The [param accelerator] is generally a combination of [enum KeyModifierMask]s and [enum Key]s using bitwise OR such as [code]KEY_MASK_CTRL | KEY_A[/code] ([kbd]Ctrl + A[/kbd]). [b]Note:[/b] The [param callback] and [param key_callback] Callables need to accept exactly one Variant parameter, the parameter passed to the Callables will be the value passed to [param tag]. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. + [b]Note:[/b] [param accelerator] and [param key_callback] are ignored on Windows. @@ -41,7 +71,8 @@ Returns index of the inserted item, it's not guaranteed to be the same as [param index] value. An [param accelerator] can optionally be defined, which is a keyboard shortcut that can be pressed to trigger the menu button even if it's not currently open. The [param accelerator] is generally a combination of [enum KeyModifierMask]s and [enum Key]s using bitwise OR such as [code]KEY_MASK_CTRL | KEY_A[/code] ([kbd]Ctrl + A[/kbd]). [b]Note:[/b] The [param callback] and [param key_callback] Callables need to accept exactly one Variant parameter, the parameter passed to the Callables will be the value passed to [param tag]. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. + [b]Note:[/b] [param accelerator] and [param key_callback] are ignored on Windows. @@ -59,7 +90,8 @@ Returns index of the inserted item, it's not guaranteed to be the same as [param index] value. An [param accelerator] can optionally be defined, which is a keyboard shortcut that can be pressed to trigger the menu button even if it's not currently open. The [param accelerator] is generally a combination of [enum KeyModifierMask]s and [enum Key]s using bitwise OR such as [code]KEY_MASK_CTRL | KEY_A[/code] ([kbd]Ctrl + A[/kbd]). [b]Note:[/b] The [param callback] and [param key_callback] Callables need to accept exactly one Variant parameter, the parameter passed to the Callables will be the value passed to [param tag]. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. + [b]Note:[/b] [param accelerator] and [param key_callback] are ignored on Windows. @@ -78,7 +110,8 @@ An [param accelerator] can optionally be defined, which is a keyboard shortcut that can be pressed to trigger the menu button even if it's not currently open. The [param accelerator] is generally a combination of [enum KeyModifierMask]s and [enum Key]s using bitwise OR such as [code]KEY_MASK_CTRL | KEY_A[/code] ([kbd]Ctrl + A[/kbd]). [b]Note:[/b] Radio-checkable items just display a checkmark, but don't have any built-in checking behavior and must be checked/unchecked manually. See [method set_item_checked] for more info on how to control it. [b]Note:[/b] The [param callback] and [param key_callback] Callables need to accept exactly one Variant parameter, the parameter passed to the Callables will be the value passed to [param tag]. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. + [b]Note:[/b] [param accelerator] and [param key_callback] are ignored on Windows. @@ -95,7 +128,8 @@ Returns index of the inserted item, it's not guaranteed to be the same as [param index] value. An [param accelerator] can optionally be defined, which is a keyboard shortcut that can be pressed to trigger the menu button even if it's not currently open. The [param accelerator] is generally a combination of [enum KeyModifierMask]s and [enum Key]s using bitwise OR such as [code]KEY_MASK_CTRL | KEY_A[/code] ([kbd]Ctrl + A[/kbd]). [b]Note:[/b] The [param callback] and [param key_callback] Callables need to accept exactly one Variant parameter, the parameter passed to the Callables will be the value passed to [param tag]. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. + [b]Note:[/b] [param accelerator] and [param key_callback] are ignored on Windows. @@ -116,7 +150,8 @@ An [param accelerator] can optionally be defined, which is a keyboard shortcut that can be pressed to trigger the menu button even if it's not currently open. The [param accelerator] is generally a combination of [enum KeyModifierMask]s and [enum Key]s using bitwise OR such as [code]KEY_MASK_CTRL | KEY_A[/code] ([kbd]Ctrl + A[/kbd]). [b]Note:[/b] By default, there's no indication of the current item state, it should be changed manually. [b]Note:[/b] The [param callback] and [param key_callback] Callables need to accept exactly one Variant parameter, the parameter passed to the Callables will be the value passed to [param tag]. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. + [b]Note:[/b] [param accelerator] and [param key_callback] are ignored on Windows. @@ -134,7 +169,8 @@ An [param accelerator] can optionally be defined, which is a keyboard shortcut that can be pressed to trigger the menu button even if it's not currently open. The [param accelerator] is generally a combination of [enum KeyModifierMask]s and [enum Key]s using bitwise OR such as [code]KEY_MASK_CTRL | KEY_A[/code] ([kbd]Ctrl + A[/kbd]). [b]Note:[/b] Radio-checkable items just display a checkmark, but don't have any built-in checking behavior and must be checked/unchecked manually. See [method set_item_checked] for more info on how to control it. [b]Note:[/b] The [param callback] and [param key_callback] Callables need to accept exactly one Variant parameter, the parameter passed to the Callables will be the value passed to [param tag]. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. + [b]Note:[/b] [param accelerator] and [param key_callback] are ignored on Windows. @@ -144,7 +180,7 @@ Adds a separator between items to the global menu [param rid]. Separators also occupy an index. Returns index of the inserted item, it's not guaranteed to be the same as [param index] value. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -157,7 +193,7 @@ Adds an item that will act as a submenu of the global menu [param rid]. The [param submenu_rid] argument is the RID of the global menu that will be shown when the item is clicked. Returns index of the inserted item, it's not guaranteed to be the same as [param index] value. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -165,14 +201,14 @@ Removes all items from the global menu [param rid]. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. Creates a new global menu object. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -181,7 +217,7 @@ Returns the index of the item with the specified [param tag]. Index is automatically assigned to each item by the engine. Index can not be set manually. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -190,7 +226,7 @@ Returns the index of the item with the specified [param text]. Index is automatically assigned to each item by the engine. Index can not be set manually. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -198,7 +234,7 @@ Frees a global menu object created by this [NativeMenu]. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -216,7 +252,7 @@ Returns the callback of the item at index [param idx]. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -224,7 +260,7 @@ Returns number of items in the global menu [param rid]. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -233,7 +269,7 @@ Returns the icon of the item at index [param idx]. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -260,7 +296,7 @@ Returns number of states of a multistate item. See [method add_multistate_item] for details. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -269,7 +305,7 @@ Returns the state of a multistate item. See [method add_multistate_item] for details. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -278,7 +314,7 @@ Returns the submenu ID of the item at index [param idx]. See [method add_submenu_item] for more info on how to add a submenu. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -287,7 +323,7 @@ Returns the metadata of the specified item, which might be of any type. You can set it with [method set_item_tag], which provides a simple way of assigning context data to items. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -296,7 +332,7 @@ Returns the text of the item at index [param idx]. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -337,7 +373,7 @@ Returns global menu size. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -361,7 +397,7 @@ Returns [code]true[/code] if the specified [param feature] is supported by the current [NativeMenu], [code]false[/code] otherwise. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -369,7 +405,7 @@ Returns [code]true[/code] if [param rid] is valid global menu. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -386,7 +422,7 @@ Returns [code]true[/code] if the item at index [param idx] is checkable in some way, i.e. if it has a checkbox or radio button. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -395,7 +431,7 @@ Returns [code]true[/code] if the item at index [param idx] is checked. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -405,7 +441,7 @@ Returns [code]true[/code] if the item at index [param idx] is disabled. When it is disabled it can't be selected, or its action invoked. See [method set_item_disabled] for more info on how to disable an item. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -425,7 +461,7 @@ Returns [code]true[/code] if the item at index [param idx] has radio button-style checkability. [b]Note:[/b] This is purely cosmetic; you must add the logic for checking/unchecking items in radio groups. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -442,7 +478,7 @@ Shows the global menu at [param position] in the screen coordinates. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -452,7 +488,16 @@ Removes the item at index [param idx] from the global menu [param rid]. [b]Note:[/b] The indices of items after the removed item will be shifted by one. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. + + + + + + + + Sets the menu text layout directtion. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -473,7 +518,7 @@ Sets the callback of the item at index [param idx]. Callback is emitted when an item is pressed. [b]Note:[/b] The [param callback] Callable needs to accept exactly one Variant parameter, the parameter passed to the Callable will be the value passed to the [code]tag[/code] parameter when the menu item was created. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -483,7 +528,7 @@ Sets whether the item at index [param idx] has a checkbox. If [code]false[/code], sets the type of the item to plain text. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -493,7 +538,7 @@ Sets the checkstate status of the item at index [param idx]. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -503,7 +548,7 @@ Enables/disables the item at index [param idx]. When it is disabled, it can't be selected and its action can't be invoked. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -534,8 +579,8 @@ Replaces the [Texture2D] icon of the specified [param idx]. - [b]Note:[/b] This method is implemented only on macOS. - [b]Note:[/b] This method is not supported by macOS "_dock" menu items. + [b]Note:[/b] This method is implemented on macOS and Windows. + [b]Note:[/b] This method is not supported by macOS Dock menu items. @@ -566,7 +611,7 @@ Sets number of state of a multistate item. See [method add_multistate_item] for details. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -577,7 +622,7 @@ Sets the type of the item at the specified index [param idx] to radio button. If [code]false[/code], sets the type of the item to plain text. [b]Note:[/b] This is purely cosmetic; you must add the logic for checking/unchecking items in radio groups. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -587,7 +632,7 @@ Sets the state of a multistate item. See [method add_multistate_item] for details. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -597,7 +642,7 @@ Sets the submenu RID of the item at index [param idx]. The submenu is a global menu that would be shown when the item is clicked. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -607,7 +652,7 @@ Sets the metadata of an item, which may be of any type. You can later get it with [method get_item_tag], which provides a simple way of assigning context data to items. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -617,7 +662,7 @@ Sets the text of the item at index [param idx]. - [b]Note:[/b] This method is implemented only on macOS. + [b]Note:[/b] This method is implemented on macOS and Windows. @@ -645,6 +690,7 @@ Registers callable to emit when the menu is about to show. + [b]Note:[/b] This method is implemented only on macOS. @@ -653,6 +699,7 @@ Registers callable to emit when the menu is about to closed. + [b]Note:[/b] This method is implemented only on macOS. @@ -663,6 +710,15 @@ [NativeMenu] supports native popup menus. + + [NativeMenu] supports menu open and close callbacks. + + + [NativeMenu] supports menu item hover callback. + + + [NativeMenu] supports menu item accelerator/key callback. + Invalid special system menu ID. diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml index 1e0b4d12e04..7831ebd1b91 100644 --- a/doc/classes/PopupMenu.xml +++ b/doc/classes/PopupMenu.xml @@ -634,6 +634,9 @@ The number of items currently in the list. + + If [code]true[/code], [MenuBar] will use native menu when supported. + Sets the delay time in seconds for the submenu item to popup on mouse hovering. If the popup menu is added as a child of another (acting as a submenu), it will inherit the delay time of the parent menu item. diff --git a/platform/macos/native_menu_macos.h b/platform/macos/native_menu_macos.h index e0e15df8327..c00a510fd53 100644 --- a/platform/macos/native_menu_macos.h +++ b/platform/macos/native_menu_macos.h @@ -88,6 +88,7 @@ public: virtual Size2 get_size(const RID &p_rid) const override; virtual void popup(const RID &p_rid, const Vector2i &p_position) override; + virtual void set_interface_direction(const RID &p_rid, bool p_is_rtl) override; virtual void set_popup_open_callback(const RID &p_rid, const Callable &p_callback) override; virtual Callable get_popup_open_callback(const RID &p_rid) const override; virtual void set_popup_close_callback(const RID &p_rid, const Callable &p_callback) override; diff --git a/platform/macos/native_menu_macos.mm b/platform/macos/native_menu_macos.mm index cb88f94a28d..f00527767c6 100644 --- a/platform/macos/native_menu_macos.mm +++ b/platform/macos/native_menu_macos.mm @@ -181,6 +181,9 @@ bool NativeMenuMacOS::has_feature(Feature p_feature) const { switch (p_feature) { case FEATURE_GLOBAL_MENU: case FEATURE_POPUP_MENU: + case FEATURE_OPEN_CLOSE_CALLBACK: + case FEATURE_HOVER_CALLBACK: + case FEATURE_KEY_CALLBACK: return true; default: return false; @@ -264,6 +267,13 @@ void NativeMenuMacOS::popup(const RID &p_rid, const Vector2i &p_position) { } } +void NativeMenuMacOS::set_interface_direction(const RID &p_rid, bool p_is_rtl) { + MenuData *md = menus.get_or_null(p_rid); + ERR_FAIL_NULL(md); + + md->menu.userInterfaceLayoutDirection = p_is_rtl ? NSUserInterfaceLayoutDirectionLeftToRight : NSUserInterfaceLayoutDirectionRightToLeft; +} + void NativeMenuMacOS::set_popup_open_callback(const RID &p_rid, const Callable &p_callback) { MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL(md); diff --git a/platform/windows/SCsub b/platform/windows/SCsub index 18c5878a1ab..cf6416b8dad 100644 --- a/platform/windows/SCsub +++ b/platform/windows/SCsub @@ -17,6 +17,7 @@ common_win = [ "joypad_windows.cpp", "tts_windows.cpp", "windows_terminal_logger.cpp", + "native_menu_windows.cpp", "gl_manager_windows_native.cpp", "gl_manager_windows_angle.cpp", "wgl_detect_version.cpp", diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index eff2ec8f968..b8100434957 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -3664,6 +3664,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA // Process window messages. switch (uMsg) { + case WM_MENUCOMMAND: { + native_menu->_menu_activate(HMENU(lParam), (int)wParam); + } break; case WM_CREATE: { if (is_dark_mode_supported() && dark_title_available) { BOOL value = is_dark_mode(); @@ -5470,7 +5473,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win if (tts_enabled) { tts = memnew(TTS_Windows); } - native_menu = memnew(NativeMenu); + native_menu = memnew(NativeMenuWindows); // Enforce default keep screen on value. screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on")); diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index cc5099cbb45..910b9baa45b 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -61,6 +61,8 @@ #include "gl_manager_windows_native.h" #endif // GLES3_ENABLED +#include "native_menu_windows.h" + #include #include @@ -358,7 +360,7 @@ class DisplayServerWindows : public DisplayServer { HANDLE power_request; TTS_Windows *tts = nullptr; - NativeMenu *native_menu = nullptr; + NativeMenuWindows *native_menu = nullptr; struct WindowData { HWND hWnd; diff --git a/platform/windows/native_menu_windows.cpp b/platform/windows/native_menu_windows.cpp new file mode 100644 index 00000000000..eea30cab9ab --- /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 &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(&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 | 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 &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 &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 &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 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.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 &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(); + 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() {} diff --git a/platform/windows/native_menu_windows.h b/platform/windows/native_menu_windows.h new file mode 100644 index 00000000000..6eab56d8b6d --- /dev/null +++ b/platform/windows/native_menu_windows.h @@ -0,0 +1,151 @@ +/**************************************************************************/ +/* native_menu_windows.h */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#ifndef NATIVE_MENU_WINDOWS_H +#define NATIVE_MENU_WINDOWS_H + +#include "core/templates/hash_map.h" +#include "core/templates/rid_owner.h" +#include "servers/native_menu.h" + +#define WIN32_LEAN_AND_MEAN +#include + +class NativeMenuWindows : public NativeMenu { + GDCLASS(NativeMenuWindows, NativeMenu) + + enum GlobalMenuCheckType { + CHECKABLE_TYPE_NONE, + CHECKABLE_TYPE_CHECK_BOX, + CHECKABLE_TYPE_RADIO_BUTTON, + }; + + struct MenuItemData { + Callable callback; + Variant meta; + GlobalMenuCheckType checkable_type; + int max_states = 0; + int state = 0; + Ref img; + HBITMAP bmp = 0; + }; + + struct MenuData { + HMENU menu = 0; + bool is_rtl = false; + }; + + mutable RID_PtrOwner menus; + HashMap menu_lookup; + + HBITMAP _make_bitmap(const Ref &p_img) const; + +public: + void _menu_activate(HMENU p_menu, int p_index) const; + + virtual bool has_feature(Feature p_feature) const override; + + virtual bool has_system_menu(SystemMenus p_menu_id) const override; + virtual RID get_system_menu(SystemMenus p_menu_id) const override; + + virtual RID create_menu() override; + virtual bool has_menu(const RID &p_rid) const override; + virtual void free_menu(const RID &p_rid) override; + + virtual Size2 get_size(const RID &p_rid) const override; + virtual void popup(const RID &p_rid, const Vector2i &p_position) override; + + virtual void set_interface_direction(const RID &p_rid, bool p_is_rtl) override; + virtual void set_popup_open_callback(const RID &p_rid, const Callable &p_callback) override; + virtual Callable get_popup_open_callback(const RID &p_rid) const override; + virtual void set_popup_close_callback(const RID &p_rid, const Callable &p_callback) override; + virtual Callable get_popup_close_callback(const RID &p_rid) const override; + virtual void set_minimum_width(const RID &p_rid, float p_width) override; + virtual float get_minimum_width(const RID &p_rid) const override; + + virtual int add_submenu_item(const RID &p_rid, const String &p_label, const RID &p_submenu_rid, const Variant &p_tag = Variant(), int p_index = -1) override; + virtual int add_item(const RID &p_rid, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual int add_check_item(const RID &p_rid, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual int add_icon_item(const RID &p_rid, const Ref &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual int add_icon_check_item(const RID &p_rid, const Ref &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual int add_radio_check_item(const RID &p_rid, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual int add_icon_radio_check_item(const RID &p_rid, const Ref &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual int add_multistate_item(const RID &p_rid, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual int add_separator(const RID &p_rid, int p_index = -1) override; + + virtual int find_item_index_with_text(const RID &p_rid, const String &p_text) const override; + virtual int find_item_index_with_tag(const RID &p_rid, const Variant &p_tag) const override; + + virtual bool is_item_checked(const RID &p_rid, int p_idx) const override; + virtual bool is_item_checkable(const RID &p_rid, int p_idx) const override; + virtual bool is_item_radio_checkable(const RID &p_rid, int p_idx) const override; + virtual Callable get_item_callback(const RID &p_rid, int p_idx) const override; + virtual Callable get_item_key_callback(const RID &p_rid, int p_idx) const override; + virtual Variant get_item_tag(const RID &p_rid, int p_idx) const override; + virtual String get_item_text(const RID &p_rid, int p_idx) const override; + virtual RID get_item_submenu(const RID &p_rid, int p_idx) const override; + virtual Key get_item_accelerator(const RID &p_rid, int p_idx) const override; + virtual bool is_item_disabled(const RID &p_rid, int p_idx) const override; + virtual bool is_item_hidden(const RID &p_rid, int p_idx) const override; + virtual String get_item_tooltip(const RID &p_rid, int p_idx) const override; + virtual int get_item_state(const RID &p_rid, int p_idx) const override; + virtual int get_item_max_states(const RID &p_rid, int p_idx) const override; + virtual Ref get_item_icon(const RID &p_rid, int p_idx) const override; + virtual int get_item_indentation_level(const RID &p_rid, int p_idx) const override; + + virtual void set_item_checked(const RID &p_rid, int p_idx, bool p_checked) override; + virtual void set_item_checkable(const RID &p_rid, int p_idx, bool p_checkable) override; + virtual void set_item_radio_checkable(const RID &p_rid, int p_idx, bool p_checkable) override; + virtual void set_item_callback(const RID &p_rid, int p_idx, const Callable &p_callback) override; + virtual void set_item_key_callback(const RID &p_rid, int p_idx, const Callable &p_key_callback) override; + virtual void set_item_hover_callbacks(const RID &p_rid, int p_idx, const Callable &p_callback) override; + virtual void set_item_tag(const RID &p_rid, int p_idx, const Variant &p_tag) override; + virtual void set_item_text(const RID &p_rid, int p_idx, const String &p_text) override; + virtual void set_item_submenu(const RID &p_rid, int p_idx, const RID &p_submenu_rid) override; + virtual void set_item_accelerator(const RID &p_rid, int p_idx, Key p_keycode) override; + virtual void set_item_disabled(const RID &p_rid, int p_idx, bool p_disabled) override; + virtual void set_item_hidden(const RID &p_rid, int p_idx, bool p_hidden) override; + virtual void set_item_tooltip(const RID &p_rid, int p_idx, const String &p_tooltip) override; + virtual void set_item_state(const RID &p_rid, int p_idx, int p_state) override; + 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 get_item_count(const RID &p_rid) const override; + virtual bool is_system_menu(const RID &p_rid) const override; + + virtual void remove_item(const RID &p_rid, int p_idx) override; + virtual void clear(const RID &p_rid) override; + + NativeMenuWindows(); + ~NativeMenuWindows(); +}; + +#endif // NATIVE_MENU_WINDOWS_H diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp index a808a3b1b51..8eb455d0acb 100644 --- a/scene/gui/menu_bar.cpp +++ b/scene/gui/menu_bar.cpp @@ -192,7 +192,7 @@ bool MenuBar::is_native_menu() const { } #endif - return (NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_GLOBAL_MENU) && is_native); + return (NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_GLOBAL_MENU) && prefer_native); } void MenuBar::bind_global_menu() { @@ -767,9 +767,9 @@ int MenuBar::get_start_index() const { } void MenuBar::set_prefer_global_menu(bool p_enabled) { - if (is_native != p_enabled) { - is_native = p_enabled; - if (is_native) { + if (prefer_native != p_enabled) { + prefer_native = p_enabled; + if (prefer_native) { bind_global_menu(); } else { unbind_global_menu(); @@ -778,7 +778,7 @@ void MenuBar::set_prefer_global_menu(bool p_enabled) { } bool MenuBar::is_prefer_global_menu() const { - return is_native; + return prefer_native; } Size2 MenuBar::get_minimum_size() const { diff --git a/scene/gui/menu_bar.h b/scene/gui/menu_bar.h index 962eea593ff..631b791e1b5 100644 --- a/scene/gui/menu_bar.h +++ b/scene/gui/menu_bar.h @@ -41,7 +41,7 @@ class MenuBar : public Control { bool switch_on_hover = true; bool disable_shortcuts = false; - bool is_native = true; + bool prefer_native = true; bool flat = false; int start_index = -1; diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index dfe0649d0f7..25d999851bf 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -82,7 +82,7 @@ RID PopupMenu::bind_global_menu() { return RID(); } #endif - if (!NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_GLOBAL_MENU)) { + if (!NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_POPUP_MENU)) { return RID(); } @@ -105,6 +105,7 @@ RID PopupMenu::bind_global_menu() { global_menu = nmenu->create_menu(); } + nmenu->set_interface_direction(global_menu, control->is_layout_rtl()); nmenu->set_popup_open_callback(global_menu, callable_mp(this, &PopupMenu::_about_to_popup)); nmenu->set_popup_close_callback(global_menu, callable_mp(this, &PopupMenu::_about_to_close)); for (int i = 0; i < items.size(); i++) { @@ -1027,6 +1028,9 @@ void PopupMenu::_notification(int p_what) { case NOTIFICATION_TRANSLATION_CHANGED: { NativeMenu *nmenu = NativeMenu::get_singleton(); bool is_global = global_menu.is_valid(); + if (is_global) { + nmenu->set_interface_direction(global_menu, control->is_layout_rtl()); + } for (int i = 0; i < items.size(); i++) { Item &item = items.write[i]; item.xl_text = atr(item.text); @@ -2289,6 +2293,21 @@ void PopupMenu::scroll_to_item(int p_idx) { } } +void PopupMenu::set_prefer_native_menu(bool p_enabled) { + if (prefer_native != p_enabled) { + prefer_native = p_enabled; + if (prefer_native) { + bind_global_menu(); + } else { + unbind_global_menu(); + } + } +} + +bool PopupMenu::is_prefer_native_menu() const { + return prefer_native; +} + bool PopupMenu::activate_item_by_event(const Ref &p_event, bool p_for_global_only) { ERR_FAIL_COND_V(p_event.is_null(), false); Key code = Key::NONE; @@ -2616,6 +2635,9 @@ bool PopupMenu::_set(const StringName &p_name, const Variant &p_value) { void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("activate_item_by_event", "event", "for_global_only"), &PopupMenu::activate_item_by_event, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("set_prefer_native_menu", "enabled"), &PopupMenu::set_prefer_native_menu); + ClassDB::bind_method(D_METHOD("is_prefer_native_menu"), &PopupMenu::is_prefer_native_menu); + ClassDB::bind_method(D_METHOD("add_item", "label", "id", "accel"), &PopupMenu::add_item, DEFVAL(-1), DEFVAL(0)); ClassDB::bind_method(D_METHOD("add_icon_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_item, DEFVAL(-1), DEFVAL(0)); ClassDB::bind_method(D_METHOD("add_check_item", "label", "id", "accel"), &PopupMenu::add_check_item, DEFVAL(-1), DEFVAL(0)); @@ -2722,7 +2744,8 @@ void PopupMenu::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_state_item_selection"), "set_hide_on_state_item_selection", "is_hide_on_state_item_selection"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "submenu_popup_delay", PROPERTY_HINT_NONE, "suffix:s"), "set_submenu_popup_delay", "get_submenu_popup_delay"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_search"), "set_allow_search", "get_allow_search"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "system_menu_id", PROPERTY_HINT_ENUM, "Application Menu:2,Window Menu:3,Help Menu:4,Dock:5"), "set_system_menu", "get_system_menu"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "system_menu_id", PROPERTY_HINT_ENUM, "None:0,Application Menu:2,Window Menu:3,Help Menu:4,Dock:5"), "set_system_menu", "get_system_menu"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "prefer_native_menu"), "set_prefer_native_menu", "is_prefer_native_menu"); ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "item_"); @@ -2786,9 +2809,37 @@ void PopupMenu::_bind_methods() { } void PopupMenu::popup(const Rect2i &p_bounds) { - moved = Vector2(); - popup_time_msec = OS::get_singleton()->get_ticks_msec(); - Popup::popup(p_bounds); + bool native = global_menu.is_valid(); +#ifdef TOOLS_ENABLED + if (is_part_of_edited_scene()) { + native = false; + } +#endif + + if (native) { + NativeMenu::get_singleton()->popup(global_menu, (p_bounds != Rect2i()) ? p_bounds.position : get_position()); + } else { + moved = Vector2(); + popup_time_msec = OS::get_singleton()->get_ticks_msec(); + Popup::popup(p_bounds); + } +} + +void PopupMenu::set_visible(bool p_visible) { + bool native = global_menu.is_valid(); +#ifdef TOOLS_ENABLED + if (is_part_of_edited_scene()) { + native = false; + } +#endif + + if (native) { + if (p_visible) { + NativeMenu::get_singleton()->popup(global_menu, get_position()); + } + } else { + Popup::set_visible(p_visible); + } } PopupMenu::PopupMenu() { diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 602d5466a94..f7097fffafd 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -100,6 +100,7 @@ class PopupMenu : public Popup { RID global_menu; RID system_menu; NativeMenu::SystemMenus system_menu_id = NativeMenu::INVALID_MENU_ID; + bool prefer_native = false; bool close_allowed = false; bool activated_by_keyboard = false; @@ -322,6 +323,9 @@ public: void set_item_count(int p_count); int get_item_count() const; + void set_prefer_native_menu(bool p_enabled); + bool is_prefer_native_menu() const; + void scroll_to_item(int p_idx); bool activate_item_by_event(const Ref &p_event, bool p_for_global_only = false); @@ -357,6 +361,7 @@ public: bool get_allow_search() const; virtual void popup(const Rect2i &p_bounds = Rect2i()) override; + virtual void set_visible(bool p_visible) override; void take_mouse_focus(); diff --git a/servers/native_menu.cpp b/servers/native_menu.cpp index 4372c107077..d1894ba6c31 100644 --- a/servers/native_menu.cpp +++ b/servers/native_menu.cpp @@ -48,6 +48,7 @@ void NativeMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("get_size", "rid"), &NativeMenu::get_size); ClassDB::bind_method(D_METHOD("popup", "rid", "position"), &NativeMenu::popup); + ClassDB::bind_method(D_METHOD("set_interface_direction", "rid", "is_rtl"), &NativeMenu::set_interface_direction); ClassDB::bind_method(D_METHOD("set_popup_open_callback", "rid", "callback"), &NativeMenu::set_popup_open_callback); ClassDB::bind_method(D_METHOD("get_popup_open_callback", "rid"), &NativeMenu::get_popup_open_callback); ClassDB::bind_method(D_METHOD("set_popup_close_callback", "rid", "callback"), &NativeMenu::set_popup_close_callback); @@ -111,6 +112,9 @@ void NativeMenu::_bind_methods() { BIND_ENUM_CONSTANT(FEATURE_GLOBAL_MENU); BIND_ENUM_CONSTANT(FEATURE_POPUP_MENU); + BIND_ENUM_CONSTANT(FEATURE_OPEN_CLOSE_CALLBACK); + BIND_ENUM_CONSTANT(FEATURE_HOVER_CALLBACK); + BIND_ENUM_CONSTANT(FEATURE_KEY_CALLBACK); BIND_ENUM_CONSTANT(INVALID_MENU_ID); BIND_ENUM_CONSTANT(MAIN_MENU_ID); @@ -173,6 +177,10 @@ void NativeMenu::popup(const RID &p_rid, const Vector2i &p_position) { WARN_PRINT("Global menus are not supported on this platform."); } +void NativeMenu::set_interface_direction(const RID &p_rid, bool p_is_rtl) { + WARN_PRINT("Global menus are not supported on this platform."); +} + void NativeMenu::set_popup_open_callback(const RID &p_rid, const Callable &p_callback) { WARN_PRINT("Global menus are not supported on this platform."); } diff --git a/servers/native_menu.h b/servers/native_menu.h index bb61caa633e..f65e193972a 100644 --- a/servers/native_menu.h +++ b/servers/native_menu.h @@ -54,6 +54,9 @@ public: enum Feature { FEATURE_GLOBAL_MENU, FEATURE_POPUP_MENU, + FEATURE_OPEN_CLOSE_CALLBACK, + FEATURE_HOVER_CALLBACK, + FEATURE_KEY_CALLBACK, }; enum SystemMenus { @@ -78,6 +81,8 @@ public: virtual Size2 get_size(const RID &p_rid) const; virtual void popup(const RID &p_rid, const Vector2i &p_position); + virtual void set_interface_direction(const RID &p_rid, bool p_is_rtl); + virtual void set_popup_open_callback(const RID &p_rid, const Callable &p_callback); virtual Callable get_popup_open_callback(const RID &p_rid) const; virtual void set_popup_close_callback(const RID &p_rid, const Callable &p_callback);