[NativeMenu] Implement native popup menu support on Windows.

This commit is contained in:
bruvzg 2024-03-07 20:42:24 +02:00
parent 61282068f4
commit ac7583e449
No known key found for this signature in database
GPG key ID: 7960FCF39844EC38
15 changed files with 1501 additions and 56 deletions

View file

@ -5,6 +5,35 @@
</brief_description>
<description>
[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]
</description>
<tutorials>
</tutorials>
@ -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.
</description>
</method>
<method name="add_icon_check_item">
@ -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.
</description>
</method>
<method name="add_icon_item">
@ -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.
</description>
</method>
<method name="add_icon_radio_check_item">
@ -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.
</description>
</method>
<method name="add_item">
@ -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.
</description>
</method>
<method name="add_multistate_item">
@ -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.
</description>
</method>
<method name="add_radio_check_item">
@ -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.
</description>
</method>
<method name="add_separator">
@ -144,7 +180,7 @@
<description>
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.
</description>
</method>
<method name="add_submenu_item">
@ -157,7 +193,7 @@
<description>
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.
</description>
</method>
<method name="clear">
@ -165,14 +201,14 @@
<param index="0" name="rid" type="RID" />
<description>
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.
</description>
</method>
<method name="create_menu">
<return type="RID" />
<description>
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.
</description>
</method>
<method name="find_item_index_with_tag" qualifiers="const">
@ -181,7 +217,7 @@
<param index="1" name="tag" type="Variant" />
<description>
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.
</description>
</method>
<method name="find_item_index_with_text" qualifiers="const">
@ -190,7 +226,7 @@
<param index="1" name="text" type="String" />
<description>
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.
</description>
</method>
<method name="free_menu">
@ -198,7 +234,7 @@
<param index="0" name="rid" type="RID" />
<description>
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.
</description>
</method>
<method name="get_item_accelerator" qualifiers="const">
@ -216,7 +252,7 @@
<param index="1" name="idx" type="int" />
<description>
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.
</description>
</method>
<method name="get_item_count" qualifiers="const">
@ -224,7 +260,7 @@
<param index="0" name="rid" type="RID" />
<description>
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.
</description>
</method>
<method name="get_item_icon" qualifiers="const">
@ -233,7 +269,7 @@
<param index="1" name="idx" type="int" />
<description>
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.
</description>
</method>
<method name="get_item_indentation_level" qualifiers="const">
@ -260,7 +296,7 @@
<param index="1" name="idx" type="int" />
<description>
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.
</description>
</method>
<method name="get_item_state" qualifiers="const">
@ -269,7 +305,7 @@
<param index="1" name="idx" type="int" />
<description>
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.
</description>
</method>
<method name="get_item_submenu" qualifiers="const">
@ -278,7 +314,7 @@
<param index="1" name="idx" type="int" />
<description>
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.
</description>
</method>
<method name="get_item_tag" qualifiers="const">
@ -287,7 +323,7 @@
<param index="1" name="idx" type="int" />
<description>
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.
</description>
</method>
<method name="get_item_text" qualifiers="const">
@ -296,7 +332,7 @@
<param index="1" name="idx" type="int" />
<description>
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.
</description>
</method>
<method name="get_item_tooltip" qualifiers="const">
@ -337,7 +373,7 @@
<param index="0" name="rid" type="RID" />
<description>
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.
</description>
</method>
<method name="get_system_menu" qualifiers="const">
@ -361,7 +397,7 @@
<param index="0" name="feature" type="int" enum="NativeMenu.Feature" />
<description>
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.
</description>
</method>
<method name="has_menu" qualifiers="const">
@ -369,7 +405,7 @@
<param index="0" name="rid" type="RID" />
<description>
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.
</description>
</method>
<method name="has_system_menu" qualifiers="const">
@ -386,7 +422,7 @@
<param index="1" name="idx" type="int" />
<description>
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.
</description>
</method>
<method name="is_item_checked" qualifiers="const">
@ -395,7 +431,7 @@
<param index="1" name="idx" type="int" />
<description>
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.
</description>
</method>
<method name="is_item_disabled" qualifiers="const">
@ -405,7 +441,7 @@
<description>
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.
</description>
</method>
<method name="is_item_hidden" qualifiers="const">
@ -425,7 +461,7 @@
<description>
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.
</description>
</method>
<method name="is_system_menu" qualifiers="const">
@ -442,7 +478,7 @@
<param index="1" name="position" type="Vector2i" />
<description>
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.
</description>
</method>
<method name="remove_item">
@ -452,7 +488,16 @@
<description>
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.
</description>
</method>
<method name="set_interface_direction">
<return type="void" />
<param index="0" name="rid" type="RID" />
<param index="1" name="is_rtl" type="bool" />
<description>
Sets the menu text layout directtion.
[b]Note:[/b] This method is implemented on macOS and Windows.
</description>
</method>
<method name="set_item_accelerator">
@ -473,7 +518,7 @@
<description>
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.
</description>
</method>
<method name="set_item_checkable">
@ -483,7 +528,7 @@
<param index="2" name="checkable" type="bool" />
<description>
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.
</description>
</method>
<method name="set_item_checked">
@ -493,7 +538,7 @@
<param index="2" name="checked" type="bool" />
<description>
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.
</description>
</method>
<method name="set_item_disabled">
@ -503,7 +548,7 @@
<param index="2" name="disabled" type="bool" />
<description>
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.
</description>
</method>
<method name="set_item_hidden">
@ -534,8 +579,8 @@
<param index="2" name="icon" type="Texture2D" />
<description>
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.
</description>
</method>
<method name="set_item_indentation_level">
@ -566,7 +611,7 @@
<param index="2" name="max_states" type="int" />
<description>
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.
</description>
</method>
<method name="set_item_radio_checkable">
@ -577,7 +622,7 @@
<description>
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.
</description>
</method>
<method name="set_item_state">
@ -587,7 +632,7 @@
<param index="2" name="state" type="int" />
<description>
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.
</description>
</method>
<method name="set_item_submenu">
@ -597,7 +642,7 @@
<param index="2" name="submenu_rid" type="RID" />
<description>
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.
</description>
</method>
<method name="set_item_tag">
@ -607,7 +652,7 @@
<param index="2" name="tag" type="Variant" />
<description>
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.
</description>
</method>
<method name="set_item_text">
@ -617,7 +662,7 @@
<param index="2" name="text" type="String" />
<description>
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.
</description>
</method>
<method name="set_item_tooltip">
@ -645,6 +690,7 @@
<param index="1" name="callback" type="Callable" />
<description>
Registers callable to emit when the menu is about to show.
[b]Note:[/b] This method is implemented only on macOS.
</description>
</method>
<method name="set_popup_open_callback">
@ -653,6 +699,7 @@
<param index="1" name="callback" type="Callable" />
<description>
Registers callable to emit when the menu is about to closed.
[b]Note:[/b] This method is implemented only on macOS.
</description>
</method>
</methods>
@ -663,6 +710,15 @@
<constant name="FEATURE_POPUP_MENU" value="1" enum="Feature">
[NativeMenu] supports native popup menus.
</constant>
<constant name="FEATURE_OPEN_CLOSE_CALLBACK" value="2" enum="Feature">
[NativeMenu] supports menu open and close callbacks.
</constant>
<constant name="FEATURE_HOVER_CALLBACK" value="3" enum="Feature">
[NativeMenu] supports menu item hover callback.
</constant>
<constant name="FEATURE_KEY_CALLBACK" value="4" enum="Feature">
[NativeMenu] supports menu item accelerator/key callback.
</constant>
<constant name="INVALID_MENU_ID" value="0" enum="SystemMenus">
Invalid special system menu ID.
</constant>

View file

@ -634,6 +634,9 @@
<member name="item_count" type="int" setter="set_item_count" getter="get_item_count" default="0">
The number of items currently in the list.
</member>
<member name="prefer_native_menu" type="bool" setter="set_prefer_native_menu" getter="is_prefer_native_menu" default="false">
If [code]true[/code], [MenuBar] will use native menu when supported.
</member>
<member name="submenu_popup_delay" type="float" setter="set_submenu_popup_delay" getter="get_submenu_popup_delay" default="0.3">
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.
</member>

View file

@ -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;

View file

@ -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);

View file

@ -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",

View file

@ -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"));

View file

@ -61,6 +61,8 @@
#include "gl_manager_windows_native.h"
#endif // GLES3_ENABLED
#include "native_menu_windows.h"
#include <io.h>
#include <stdio.h>
@ -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;

File diff suppressed because it is too large Load diff

View file

@ -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 <windows.h>
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<Image> img;
HBITMAP bmp = 0;
};
struct MenuData {
HMENU menu = 0;
bool is_rtl = false;
};
mutable RID_PtrOwner<MenuData> menus;
HashMap<HMENU, RID> menu_lookup;
HBITMAP _make_bitmap(const Ref<Image> &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<Texture2D> &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<Texture2D> &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<Texture2D> &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<Texture2D> 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<Texture2D> &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

View file

@ -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 {

View file

@ -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;

View file

@ -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<InputEvent> &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() {

View file

@ -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<InputEvent> &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();

View file

@ -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.");
}

View file

@ -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);