Implement `MenuBar` control to wrap `PopupMenu`s or native menu, use native menu for editor.

This commit is contained in:
bruvzg 2022-08-01 12:28:16 +03:00
parent 9bb6cc591c
commit 8c56a7416b
No known key found for this signature in database
GPG Key ID: 7960FCF39844EC38
25 changed files with 1951 additions and 374 deletions

View File

@ -144,7 +144,7 @@
</description>
</method>
<method name="global_menu_add_check_item">
<return type="void" />
<return type="int" />
<param index="0" name="menu_root" type="String" />
<param index="1" name="label" type="String" />
<param index="2" name="callback" type="Callable" />
@ -153,16 +153,17 @@
<param index="5" name="index" type="int" default="-1" />
<description>
Adds a new checkable item with text [param label] to the global menu with ID [param menu_root].
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 on macOS.
[b]Supported system menu IDs:[/b]
[codeblock]
"" - Main menu (macOS).
"_main" - Main menu (macOS).
"_dock" - Dock popup menu (macOS).
[/codeblock]
</description>
</method>
<method name="global_menu_add_icon_check_item">
<return type="void" />
<return type="int" />
<param index="0" name="menu_root" type="String" />
<param index="1" name="icon" type="Texture2D" />
<param index="2" name="label" type="String" />
@ -172,16 +173,17 @@
<param index="6" name="index" type="int" default="-1" />
<description>
Adds a new checkable item with text [param label] and icon [param icon] to the global menu with ID [param menu_root].
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 on macOS.
[b]Supported system menu IDs:[/b]
[codeblock]
"" - Main menu (macOS).
"_main" - Main menu (macOS).
"_dock" - Dock popup menu (macOS).
[/codeblock]
</description>
</method>
<method name="global_menu_add_icon_item">
<return type="void" />
<return type="int" />
<param index="0" name="menu_root" type="String" />
<param index="1" name="icon" type="Texture2D" />
<param index="2" name="label" type="String" />
@ -191,16 +193,17 @@
<param index="6" name="index" type="int" default="-1" />
<description>
Adds a new item with text [param label] and icon [param icon] to the global menu with ID [param menu_root].
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 on macOS.
[b]Supported system menu IDs:[/b]
[codeblock]
"" - Main menu (macOS).
"_main" - Main menu (macOS).
"_dock" - Dock popup menu (macOS).
[/codeblock]
</description>
</method>
<method name="global_menu_add_icon_radio_check_item">
<return type="void" />
<return type="int" />
<param index="0" name="menu_root" type="String" />
<param index="1" name="icon" type="Texture2D" />
<param index="2" name="label" type="String" />
@ -210,17 +213,18 @@
<param index="6" name="index" type="int" default="-1" />
<description>
Adds a new radio-checkable item with text [param label] and icon [param icon] to the global menu with ID [param menu_root].
Returns index of the inserted item, it's not guaranteed to be the same as [param index] value.
[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 global_menu_set_item_checked] for more info on how to control it.
[b]Note:[/b] This method is implemented on macOS.
[b]Supported system menu IDs:[/b]
[codeblock]
"" - Main menu (macOS).
"_main" - Main menu (macOS).
"_dock" - Dock popup menu (macOS).
[/codeblock]
</description>
</method>
<method name="global_menu_add_item">
<return type="void" />
<return type="int" />
<param index="0" name="menu_root" type="String" />
<param index="1" name="label" type="String" />
<param index="2" name="callback" type="Callable" />
@ -229,16 +233,17 @@
<param index="5" name="index" type="int" default="-1" />
<description>
Adds a new item with text [param label] to the global menu with ID [param menu_root].
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 on macOS.
[b]Supported system menu IDs:[/b]
[codeblock]
"" - Main menu (macOS).
"_main" - Main menu (macOS).
"_dock" - Dock popup menu (macOS).
[/codeblock]
</description>
</method>
<method name="global_menu_add_multistate_item">
<return type="void" />
<return type="int" />
<param index="0" name="menu_root" type="String" />
<param index="1" name="labe" type="String" />
<param index="2" name="max_states" type="int" />
@ -250,16 +255,18 @@
<description>
Adds a new item with text [param labe] to the global menu with ID [param menu_root].
Contrarily to normal binary items, multistate items can have more than two states, as defined by [param max_states]. Each press or activate of the item will increase the state by one. The default value is defined by [param default_state].
Returns index of the inserted item, it's not guaranteed to be the same as [param index] value.
[b]Note:[/b] By default, there's no indication of the current item state, it should be changed manually.
[b]Note:[/b] This method is implemented on macOS.
[b]Supported system menu IDs:[/b]
[codeblock]
"" - Main menu (macOS).
"_main" - Main menu (macOS).
"_dock" - Dock popup menu (macOS).
[/codeblock]
</description>
</method>
<method name="global_menu_add_radio_check_item">
<return type="void" />
<return type="int" />
<param index="0" name="menu_root" type="String" />
<param index="1" name="label" type="String" />
<param index="2" name="callback" type="Callable" />
@ -268,41 +275,44 @@
<param index="5" name="index" type="int" default="-1" />
<description>
Adds a new radio-checkable item with text [param label] to the global menu with ID [param menu_root].
Returns index of the inserted item, it's not guaranteed to be the same as [param index] value.
[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 global_menu_set_item_checked] for more info on how to control it.
[b]Note:[/b] This method is implemented on macOS.
[b]Supported system menu IDs:[/b]
[codeblock]
"" - Main menu (macOS).
"_main" - Main menu (macOS).
"_dock" - Dock popup menu (macOS).
[/codeblock]
</description>
</method>
<method name="global_menu_add_separator">
<return type="void" />
<return type="int" />
<param index="0" name="menu_root" type="String" />
<param index="1" name="index" type="int" default="-1" />
<description>
Adds a separator between items to the global menu with ID [param menu_root]. 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 on macOS.
[b]Supported system menu IDs:[/b]
[codeblock]
"" - Main menu (macOS).
"_main" - Main menu (macOS).
"_dock" - Dock popup menu (macOS).
[/codeblock]
</description>
</method>
<method name="global_menu_add_submenu_item">
<return type="void" />
<return type="int" />
<param index="0" name="menu_root" type="String" />
<param index="1" name="label" type="String" />
<param index="2" name="submenu" type="String" />
<param index="3" name="index" type="int" default="-1" />
<description>
Adds an item that will act as a submenu of the global menu [param menu_root]. The [param submenu] argument is the ID of the global menu root 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 on macOS.
[b]Supported system menu IDs:[/b]
[codeblock]
"" - Main menu (macOS).
"_main" - Main menu (macOS).
"_dock" - Dock popup menu (macOS).
[/codeblock]
</description>
@ -315,7 +325,7 @@
[b]Note:[/b] This method is implemented on macOS.
[b]Supported system menu IDs:[/b]
[codeblock]
"" - Main menu (macOS).
"_main" - Main menu (macOS).
"_dock" - Dock popup menu (macOS).
[/codeblock]
</description>
@ -347,6 +357,15 @@
[b]Note:[/b] This method is implemented on macOS.
</description>
</method>
<method name="global_menu_get_item_indentation_level" qualifiers="const">
<return type="int" />
<param index="0" name="menu_root" type="String" />
<param index="1" name="idx" type="int" />
<description>
Returns the horizontal offset of the item at the given [param idx].
[b]Note:[/b] This method is implemented on macOS.
</description>
</method>
<method name="global_menu_get_item_index_from_tag" qualifiers="const">
<return type="int" />
<param index="0" name="menu_root" type="String" />
@ -528,6 +547,16 @@
[b]Note:[/b] This method is not supported by macOS "_dock" menu items.
</description>
</method>
<method name="global_menu_set_item_indentation_level">
<return type="void" />
<param index="0" name="menu_root" type="String" />
<param index="1" name="idx" type="int" />
<param index="2" name="level" type="int" />
<description>
Sets the horizontal offset of the item at the given [param idx].
[b]Note:[/b] This method is implemented on macOS.
</description>
</method>
<method name="global_menu_set_item_max_states">
<return type="void" />
<param index="0" name="menu_root" type="String" />

View File

@ -553,6 +553,10 @@
<member name="interface/editor/unfocused_low_processor_mode_sleep_usec" type="float" setter="" getter="">
When the editor window is unfocused, the amount of sleeping between frames when the low-processor usage mode is enabled (in microseconds). Higher values will result in lower CPU/GPU usage, which can improve battery life on laptops (in addition to improving the running project's performance if the editor has to redraw continuously). However, higher values will result in a less responsive editor. The default value is set to limit the editor to 20 FPS when the editor window is unfocused. See also [member interface/editor/low_processor_mode_sleep_usec].
</member>
<member name="interface/editor/use_embedded_menu" type="bool" setter="" getter="">
If [code]true[/code], editor main menu is using embedded [MenuBar] instead of system global menu.
Specific to the macOS platform.
</member>
<member name="interface/inspector/max_array_dictionary_items_per_page" type="int" setter="" getter="">
The number of [Array] or [Dictionary] items to display on each "page" in the inspector. Higher values allow viewing more values per page, but take more time to load. This increased load time is noticeable when selecting nodes that have array or dictionary properties in the editor.
</member>

172
doc/classes/MenuBar.xml Normal file
View File

@ -0,0 +1,172 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="MenuBar" inherits="Control" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A horizontal menu bar, which displays [PopupMenu]s or system global menu.
</brief_description>
<description>
New items can be created by adding [PopupMenu] nodes to his node.
</description>
<tutorials>
</tutorials>
<methods>
<method name="get_menu_count" qualifiers="const">
<return type="int" />
<description>
Returns number of menu items.
</description>
</method>
<method name="get_menu_popup" qualifiers="const">
<return type="PopupMenu" />
<param index="0" name="menu" type="int" />
<description>
Returns [PopupMenu] associated with menu item.
</description>
</method>
<method name="get_menu_title" qualifiers="const">
<return type="String" />
<param index="0" name="menu" type="int" />
<description>
Returns menu item title.
</description>
</method>
<method name="get_menu_tooltip" qualifiers="const">
<return type="String" />
<param index="0" name="menu" type="int" />
<description>
Returns menu item tooltip.
</description>
</method>
<method name="is_menu_disabled" qualifiers="const">
<return type="bool" />
<param index="0" name="menu" type="int" />
<description>
Returns [code]true[/code], if menu item is disabled.
</description>
</method>
<method name="is_menu_hidden" qualifiers="const">
<return type="bool" />
<param index="0" name="menu" type="int" />
<description>
Returns [code]true[/code], if menu item is hidden.
</description>
</method>
<method name="is_native_menu" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code], if system global menu is supported and used by this [MenuBar].
</description>
</method>
<method name="set_disable_shortcuts">
<return type="void" />
<param index="0" name="disabled" type="bool" />
<description>
If [code]true[/code], shortcuts are disabled and cannot be used to trigger the button.
</description>
</method>
<method name="set_menu_disabled">
<return type="void" />
<param index="0" name="menu" type="int" />
<param index="1" name="disabled" type="bool" />
<description>
If [code]true[/code], menu item is disabled.
</description>
</method>
<method name="set_menu_hidden">
<return type="void" />
<param index="0" name="menu" type="int" />
<param index="1" name="hidden" type="bool" />
<description>
If [code]true[/code], menu item is hidden.
</description>
</method>
<method name="set_menu_title">
<return type="void" />
<param index="0" name="menu" type="int" />
<param index="1" name="title" type="String" />
<description>
Sets menu item title.
</description>
</method>
<method name="set_menu_tooltip">
<return type="void" />
<param index="0" name="menu" type="int" />
<param index="1" name="tooltip" type="String" />
<description>
Sets menu item tooltip.
</description>
</method>
</methods>
<members>
<member name="flat" type="bool" setter="set_flat" getter="is_flat" default="false">
Flat [MenuBar] don't display item decoration.
</member>
<member name="language" type="String" setter="set_language" getter="get_language" default="&quot;&quot;">
Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead.
</member>
<member name="prefer_global_menu" type="bool" setter="set_prefer_global_menu" getter="is_prefer_global_menu" default="true">
If [code]true[/code], [MenuBar] will use system global menu when supported.
</member>
<member name="shortcut_context" type="Node" setter="set_shortcut_context" getter="get_shortcut_context">
The [Node] which must be a parent of the focused GUI [Control] for the shortcut to be activated. If [code]null[/code], the shortcut can be activated when any control is focused (a global shortcut). This allows shortcuts to be accepted only when the user has a certain area of the GUI focused.
</member>
<member name="start_index" type="int" setter="set_start_index" getter="get_start_index" default="-1">
Position in the global menu to insert first [MenuBar] item at.
</member>
<member name="switch_on_hover" type="bool" setter="set_switch_on_hover" getter="is_switch_on_hover" default="true">
If [code]true[/code], when the cursor hovers above menu item, it will close the current [PopupMenu] and open the other one.
</member>
<member name="text_direction" type="int" setter="set_text_direction" getter="get_text_direction" enum="Control.TextDirection" default="0">
Base text writing direction.
</member>
</members>
<theme_items>
<theme_item name="font_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)">
Default text [Color] of the menu item.
</theme_item>
<theme_item name="font_disabled_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 0.5)">
Text [Color] used when the menu item is disabled.
</theme_item>
<theme_item name="font_focus_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)">
Text [Color] used when the menu item is focused. Only replaces the normal text color of the menu item. Disabled, hovered, and pressed states take precedence over this color.
</theme_item>
<theme_item name="font_hover_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)">
Text [Color] used when the menu item is being hovered.
</theme_item>
<theme_item name="font_hover_pressed_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)">
Text [Color] used when the menu item is being hovered and pressed.
</theme_item>
<theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)">
The tint of text outline of the menu item.
</theme_item>
<theme_item name="font_pressed_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)">
Text [Color] used when the menu item is being pressed.
</theme_item>
<theme_item name="h_separation" data_type="constant" type="int" default="4">
The horizontal space between menu items.
</theme_item>
<theme_item name="outline_size" data_type="constant" type="int" default="0">
The size of the text outline.
</theme_item>
<theme_item name="font" data_type="font" type="Font">
[Font] of the menu item's text.
</theme_item>
<theme_item name="font_size" data_type="font_size" type="int">
Font size of the menu item's text.
</theme_item>
<theme_item name="disabled" data_type="style" type="StyleBox">
[StyleBox] used when the menu item is disabled.
</theme_item>
<theme_item name="focus" data_type="style" type="StyleBox">
[StyleBox] used when the menu item is focused. The [code]focus[/code] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
</theme_item>
<theme_item name="hover" data_type="style" type="StyleBox">
[StyleBox] used when the menu item is being hovered.
</theme_item>
<theme_item name="normal" data_type="style" type="StyleBox">
Default [StyleBox] for the menu item.
</theme_item>
<theme_item name="pressed" data_type="style" type="StyleBox">
[StyleBox] used when the menu item is being pressed.
</theme_item>
</theme_items>
</class>

View File

@ -195,13 +195,6 @@
Returns the accelerator of the item at the given [param index]. Accelerators are special combinations of keys that activate the item, no matter which control is focused.
</description>
</method>
<method name="get_item_horizontal_offset" qualifiers="const">
<return type="int" />
<param index="0" name="index" type="int" />
<description>
Returns the horizontal offset of the item at the given [param index].
</description>
</method>
<method name="get_item_icon" qualifiers="const">
<return type="Texture2D" />
<param index="0" name="index" type="int" />
@ -216,6 +209,13 @@
Returns the id of the item at the given [param index]. [code]id[/code] can be manually assigned, while index can not.
</description>
</method>
<method name="get_item_indent" qualifiers="const">
<return type="int" />
<param index="0" name="index" type="int" />
<description>
Returns the horizontal offset of the item at the given [param index].
</description>
</method>
<method name="get_item_index" qualifiers="const">
<return type="int" />
<param index="0" name="id" type="int" />
@ -388,14 +388,6 @@
Enables/disables the item at the given [param index]. When it is disabled, it can't be selected and its action can't be invoked.
</description>
</method>
<method name="set_item_horizontal_offset">
<return type="void" />
<param index="0" name="index" type="int" />
<param index="1" name="offset" type="int" />
<description>
Sets the horizontal offset of the item at the given [param index].
</description>
</method>
<method name="set_item_icon">
<return type="void" />
<param index="0" name="index" type="int" />
@ -413,6 +405,14 @@
The [param id] is used in [signal id_pressed] and [signal id_focused] signals.
</description>
</method>
<method name="set_item_indent">
<return type="void" />
<param index="0" name="index" type="int" />
<param index="1" name="indent" type="int" />
<description>
Sets the horizontal offset of the item at the given [param index].
</description>
</method>
<method name="set_item_language">
<return type="void" />
<param index="0" name="index" type="int" />
@ -540,6 +540,10 @@
Emitted when an item of some [param index] is pressed or its accelerator is activated.
</description>
</signal>
<signal name="menu_changed">
<description>
</description>
</signal>
</signals>
<theme_items>
<theme_item name="font_accelerator_color" data_type="color" type="Color" default="Color(0.7, 0.7, 0.7, 0.8)">
@ -566,6 +570,9 @@
<theme_item name="h_separation" data_type="constant" type="int" default="4">
The horizontal space between the item's elements.
</theme_item>
<theme_item name="indent" data_type="constant" type="int" default="10">
Width of the single indentation level.
</theme_item>
<theme_item name="item_end_padding" data_type="constant" type="int" default="2">
</theme_item>
<theme_item name="item_start_padding" data_type="constant" type="int" default="2">

View File

@ -54,6 +54,7 @@
#include "scene/gui/dialogs.h"
#include "scene/gui/file_dialog.h"
#include "scene/gui/link_button.h"
#include "scene/gui/menu_bar.h"
#include "scene/gui/menu_button.h"
#include "scene/gui/panel.h"
#include "scene/gui/panel_container.h"
@ -751,11 +752,7 @@ void EditorNode::_notification(int p_what) {
scene_tabs->add_theme_style_override("tab_selected", gui_base->get_theme_stylebox(SNAME("SceneTabFG"), SNAME("EditorStyles")));
scene_tabs->add_theme_style_override("tab_unselected", gui_base->get_theme_stylebox(SNAME("SceneTabBG"), SNAME("EditorStyles")));
file_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles")));
project_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles")));
debug_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles")));
settings_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles")));
help_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles")));
main_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles")));
}
scene_tabs->set_max_tab_width(int(EDITOR_GET("interface/scene_tabs/maximum_width")) * EDSCALE);
@ -803,16 +800,15 @@ void EditorNode::_notification(int p_what) {
dock_tab_move_right->set_icon(theme->get_icon(SNAME("Forward"), SNAME("EditorIcons")));
}
PopupMenu *p = help_menu->get_popup();
p->set_item_icon(p->get_item_index(HELP_SEARCH), gui_base->get_theme_icon(SNAME("HelpSearch"), SNAME("EditorIcons")));
p->set_item_icon(p->get_item_index(HELP_DOCS), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")));
p->set_item_icon(p->get_item_index(HELP_QA), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")));
p->set_item_icon(p->get_item_index(HELP_REPORT_A_BUG), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")));
p->set_item_icon(p->get_item_index(HELP_SUGGEST_A_FEATURE), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")));
p->set_item_icon(p->get_item_index(HELP_SEND_DOCS_FEEDBACK), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")));
p->set_item_icon(p->get_item_index(HELP_COMMUNITY), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")));
p->set_item_icon(p->get_item_index(HELP_ABOUT), gui_base->get_theme_icon(SNAME("Godot"), SNAME("EditorIcons")));
p->set_item_icon(p->get_item_index(HELP_SUPPORT_GODOT_DEVELOPMENT), gui_base->get_theme_icon(SNAME("Heart"), SNAME("EditorIcons")));
help_menu->set_item_icon(help_menu->get_item_index(HELP_SEARCH), gui_base->get_theme_icon(SNAME("HelpSearch"), SNAME("EditorIcons")));
help_menu->set_item_icon(help_menu->get_item_index(HELP_DOCS), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")));
help_menu->set_item_icon(help_menu->get_item_index(HELP_QA), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")));
help_menu->set_item_icon(help_menu->get_item_index(HELP_REPORT_A_BUG), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")));
help_menu->set_item_icon(help_menu->get_item_index(HELP_SUGGEST_A_FEATURE), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")));
help_menu->set_item_icon(help_menu->get_item_index(HELP_SEND_DOCS_FEEDBACK), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")));
help_menu->set_item_icon(help_menu->get_item_index(HELP_COMMUNITY), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")));
help_menu->set_item_icon(help_menu->get_item_index(HELP_ABOUT), gui_base->get_theme_icon(SNAME("Godot"), SNAME("EditorIcons")));
help_menu->set_item_icon(help_menu->get_item_index(HELP_SUPPORT_GODOT_DEVELOPMENT), gui_base->get_theme_icon(SNAME("Heart"), SNAME("EditorIcons")));
for (int i = 0; i < main_editor_buttons.size(); i++) {
main_editor_buttons.write[i]->add_theme_font_override("font", gui_base->get_theme_font(SNAME("main_button_font"), SNAME("EditorFonts")));
@ -3195,17 +3191,15 @@ void EditorNode::_update_file_menu_opened() {
Ref<Shortcut> reopen_closed_scene_sc = ED_GET_SHORTCUT("editor/reopen_closed_scene");
reopen_closed_scene_sc->set_name(TTR("Reopen Closed Scene"));
PopupMenu *pop = file_menu->get_popup();
pop->set_item_disabled(pop->get_item_index(FILE_OPEN_PREV), previous_scenes.is_empty());
file_menu->set_item_disabled(file_menu->get_item_index(FILE_OPEN_PREV), previous_scenes.is_empty());
const UndoRedo &undo_redo = editor_data.get_undo_redo();
pop->set_item_disabled(pop->get_item_index(EDIT_UNDO), !undo_redo.has_undo());
pop->set_item_disabled(pop->get_item_index(EDIT_REDO), !undo_redo.has_redo());
file_menu->set_item_disabled(file_menu->get_item_index(EDIT_UNDO), !undo_redo.has_undo());
file_menu->set_item_disabled(file_menu->get_item_index(EDIT_REDO), !undo_redo.has_redo());
}
void EditorNode::_update_file_menu_closed() {
PopupMenu *pop = file_menu->get_popup();
pop->set_item_disabled(pop->get_item_index(FILE_OPEN_PREV), false);
file_menu->set_item_disabled(file_menu->get_item_index(FILE_OPEN_PREV), false);
}
Control *EditorNode::get_main_control() {
@ -6471,15 +6465,20 @@ EditorNode::EditorNode() {
main_control->add_theme_constant_override("separation", 0);
scene_root_parent->add_child(main_control);
HBoxContainer *left_menu_hb = memnew(HBoxContainer);
menu_hb->add_child(left_menu_hb);
bool global_menu = !bool(EDITOR_GET("interface/editor/use_embedded_menu")) && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU);
file_menu = memnew(MenuButton);
file_menu->set_flat(false);
file_menu->set_switch_on_hover(true);
file_menu->set_text(TTR("Scene"));
file_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles")));
left_menu_hb->add_child(file_menu);
main_menu = memnew(MenuBar);
menu_hb->add_child(main_menu);
main_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles")));
main_menu->set_flat(true);
main_menu->set_start_index(0); // Main menu, add to the start of global menu.
main_menu->set_prefer_global_menu(global_menu);
main_menu->set_switch_on_hover(true);
file_menu = memnew(PopupMenu);
file_menu->set_name(TTR("Scene"));
main_menu->add_child(file_menu);
main_menu->set_menu_tooltip(0, TTR("Operations with scene files."));
prev_scene = memnew(Button);
prev_scene->set_flat(true);
@ -6549,84 +6548,75 @@ EditorNode::EditorNode() {
command_palette->set_title(TTR("Command Palette"));
gui_base->add_child(command_palette);
PopupMenu *p;
file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/new_scene", TTR("New Scene"), KeyModifierMask::CMD + Key::N), FILE_NEW_SCENE);
file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/new_inherited_scene", TTR("New Inherited Scene..."), KeyModifierMask::CMD + KeyModifierMask::SHIFT + Key::N), FILE_NEW_INHERITED_SCENE);
file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/open_scene", TTR("Open Scene..."), KeyModifierMask::CMD + Key::O), FILE_OPEN_SCENE);
file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/reopen_closed_scene", TTR("Reopen Closed Scene"), KeyModifierMask::CMD + KeyModifierMask::SHIFT + Key::T), FILE_OPEN_PREV);
file_menu->add_submenu_item(TTR("Open Recent"), "RecentScenes", FILE_OPEN_RECENT);
file_menu->set_tooltip(TTR("Operations with scene files."));
file_menu->add_separator();
file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/save_scene", TTR("Save Scene"), KeyModifierMask::CMD + Key::S), FILE_SAVE_SCENE);
file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/save_scene_as", TTR("Save Scene As..."), KeyModifierMask::CMD + KeyModifierMask::SHIFT + Key::S), FILE_SAVE_AS_SCENE);
file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/save_all_scenes", TTR("Save All Scenes"), KeyModifierMask::CMD + KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::S), FILE_SAVE_ALL_SCENES);
p = file_menu->get_popup();
file_menu->add_separator();
p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/new_scene", TTR("New Scene"), KeyModifierMask::CMD + Key::N), FILE_NEW_SCENE);
p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/new_inherited_scene", TTR("New Inherited Scene..."), KeyModifierMask::CMD + KeyModifierMask::SHIFT + Key::N), FILE_NEW_INHERITED_SCENE);
p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/open_scene", TTR("Open Scene..."), KeyModifierMask::CMD + Key::O), FILE_OPEN_SCENE);
p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/reopen_closed_scene", TTR("Reopen Closed Scene"), KeyModifierMask::CMD + KeyModifierMask::SHIFT + Key::T), FILE_OPEN_PREV);
p->add_submenu_item(TTR("Open Recent"), "RecentScenes", FILE_OPEN_RECENT);
file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open", TTR("Quick Open..."), KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::O), FILE_QUICK_OPEN);
file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open_scene", TTR("Quick Open Scene..."), KeyModifierMask::CMD + KeyModifierMask::SHIFT + Key::O), FILE_QUICK_OPEN_SCENE);
file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open_script", TTR("Quick Open Script..."), KeyModifierMask::CMD + KeyModifierMask::ALT + Key::O), FILE_QUICK_OPEN_SCRIPT);
p->add_separator();
p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/save_scene", TTR("Save Scene"), KeyModifierMask::CMD + Key::S), FILE_SAVE_SCENE);
p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/save_scene_as", TTR("Save Scene As..."), KeyModifierMask::CMD + KeyModifierMask::SHIFT + Key::S), FILE_SAVE_AS_SCENE);
p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/save_all_scenes", TTR("Save All Scenes"), KeyModifierMask::CMD + KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::S), FILE_SAVE_ALL_SCENES);
p->add_separator();
p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open", TTR("Quick Open..."), KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::O), FILE_QUICK_OPEN);
p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open_scene", TTR("Quick Open Scene..."), KeyModifierMask::CMD + KeyModifierMask::SHIFT + Key::O), FILE_QUICK_OPEN_SCENE);
p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open_script", TTR("Quick Open Script..."), KeyModifierMask::CMD + KeyModifierMask::ALT + Key::O), FILE_QUICK_OPEN_SCRIPT);
p->add_separator();
file_menu->add_separator();
export_as_menu = memnew(PopupMenu);
export_as_menu->set_name("Export");
p->add_child(export_as_menu);
p->add_submenu_item(TTR("Export As..."), "Export");
file_menu->add_child(export_as_menu);
file_menu->add_submenu_item(TTR("Export As..."), "Export");
export_as_menu->add_shortcut(ED_SHORTCUT("editor/export_as_mesh_library", TTR("MeshLibrary...")), FILE_EXPORT_MESH_LIBRARY);
export_as_menu->connect("index_pressed", callable_mp(this, &EditorNode::_export_as_menu_option));
p->add_separator();
p->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO, true);
p->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO, true);
file_menu->add_separator();
file_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO, true);
file_menu->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO, true);
p->add_separator();
p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/reload_saved_scene", TTR("Reload Saved Scene")), EDIT_RELOAD_SAVED_SCENE);
p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/close_scene", TTR("Close Scene"), KeyModifierMask::CMD + KeyModifierMask::SHIFT + Key::W), FILE_CLOSE);
file_menu->add_separator();
file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/reload_saved_scene", TTR("Reload Saved Scene")), EDIT_RELOAD_SAVED_SCENE);
file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/close_scene", TTR("Close Scene"), KeyModifierMask::CMD + KeyModifierMask::SHIFT + Key::W), FILE_CLOSE);
recent_scenes = memnew(PopupMenu);
recent_scenes->set_name("RecentScenes");
p->add_child(recent_scenes);
file_menu->add_child(recent_scenes);
recent_scenes->connect("id_pressed", callable_mp(this, &EditorNode::_open_recent_scene));
p->add_separator();
p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/file_quit", TTR("Quit"), KeyModifierMask::CMD + Key::Q), FILE_QUIT, true);
if (!global_menu || !OS::get_singleton()->has_feature("macos")) {
// On macOS "Quit" and "About" options are in the "app" menu.
file_menu->add_separator();
file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/file_quit", TTR("Quit"), KeyModifierMask::CMD + Key::Q), FILE_QUIT, true);
}
project_menu = memnew(MenuButton);
project_menu->set_flat(false);
project_menu->set_switch_on_hover(true);
project_menu->set_tooltip(TTR("Miscellaneous project or scene-wide tools."));
project_menu->set_text(TTR("Project"));
project_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles")));
left_menu_hb->add_child(project_menu);
project_menu = memnew(PopupMenu);
project_menu->set_name(TTR("Project"));
main_menu->add_child(project_menu);
p = project_menu->get_popup();
p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/project_settings", TTR("Project Settings..."), Key::NONE, TTR("Project Settings")), RUN_SETTINGS);
p->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
project_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/project_settings", TTR("Project Settings..."), Key::NONE, TTR("Project Settings")), RUN_SETTINGS);
project_menu->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
vcs_actions_menu = VersionControlEditorPlugin::get_singleton()->get_version_control_actions_panel();
vcs_actions_menu->set_name("Version Control");
vcs_actions_menu->connect("index_pressed", callable_mp(this, &EditorNode::_version_control_menu_option));
p->add_separator();
p->add_child(vcs_actions_menu);
p->add_submenu_item(TTR("Version Control"), "Version Control");
project_menu->add_separator();
project_menu->add_child(vcs_actions_menu);
project_menu->add_submenu_item(TTR("Version Control"), "Version Control");
vcs_actions_menu->add_item(TTR("Create Version Control Metadata"), RUN_VCS_METADATA);
vcs_actions_menu->add_item(TTR("Set Up Version Control"), RUN_VCS_SETTINGS);
vcs_actions_menu->add_item(TTR("Shut Down Version Control"), RUN_VCS_SHUT_DOWN);
p->add_separator();
p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/export", TTR("Export..."), Key::NONE, TTR("Export")), FILE_EXPORT_PROJECT);
p->add_item(TTR("Install Android Build Template..."), FILE_INSTALL_ANDROID_SOURCE);
p->add_item(TTR("Open User Data Folder"), RUN_USER_DATA_FOLDER);
project_menu->add_separator();
project_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/export", TTR("Export..."), Key::NONE, TTR("Export")), FILE_EXPORT_PROJECT);
project_menu->add_item(TTR("Install Android Build Template..."), FILE_INSTALL_ANDROID_SOURCE);
project_menu->add_item(TTR("Open User Data Folder"), RUN_USER_DATA_FOLDER);
p->add_separator();
p->add_item(TTR("Customize Engine Build Configuration..."), TOOLS_BUILD_PROFILE_MANAGER);
p->add_separator();
project_menu->add_separator();
project_menu->add_item(TTR("Customize Engine Build Configuration..."), TOOLS_BUILD_PROFILE_MANAGER);
project_menu->add_separator();
plugin_config_dialog = memnew(PluginConfigDialog);
plugin_config_dialog->connect("plugin_ready", callable_mp(this, &EditorNode::_on_plugin_ready));
@ -6635,15 +6625,15 @@ EditorNode::EditorNode() {
tool_menu = memnew(PopupMenu);
tool_menu->set_name("Tools");
tool_menu->connect("index_pressed", callable_mp(this, &EditorNode::_tool_menu_option));
p->add_child(tool_menu);
p->add_submenu_item(TTR("Tools"), "Tools");
project_menu->add_child(tool_menu);
project_menu->add_submenu_item(TTR("Tools"), "Tools");
tool_menu->add_item(TTR("Orphan Resource Explorer..."), TOOLS_ORPHAN_RESOURCES);
p->add_separator();
p->add_shortcut(ED_SHORTCUT("editor/reload_current_project", TTR("Reload Current Project")), RELOAD_CURRENT_PROJECT);
project_menu->add_separator();
project_menu->add_shortcut(ED_SHORTCUT("editor/reload_current_project", TTR("Reload Current Project")), RELOAD_CURRENT_PROJECT);
ED_SHORTCUT_AND_COMMAND("editor/quit_to_project_list", TTR("Quit to Project List"), KeyModifierMask::CMD + KeyModifierMask::SHIFT + Key::Q);
ED_SHORTCUT_OVERRIDE("editor/quit_to_project_list", "macos", KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::Q);
p->add_shortcut(ED_GET_SHORTCUT("editor/quit_to_project_list"), RUN_PROJECT_MANAGER, true);
project_menu->add_shortcut(ED_GET_SHORTCUT("editor/quit_to_project_list"), RUN_PROJECT_MANAGER, true);
menu_hb->add_spacer();
@ -6651,85 +6641,79 @@ EditorNode::EditorNode() {
menu_hb->add_child(main_editor_button_vb);
// Options are added and handled by DebuggerEditorPlugin.
debug_menu = memnew(MenuButton);
debug_menu->set_flat(false);
debug_menu->set_switch_on_hover(true);
debug_menu->set_text(TTR("Debug"));
debug_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles")));
left_menu_hb->add_child(debug_menu);
debug_menu = memnew(PopupMenu);
debug_menu->set_name(TTR("Debug"));
main_menu->add_child(debug_menu);
menu_hb->add_spacer();
settings_menu = memnew(MenuButton);
settings_menu->set_flat(false);
settings_menu->set_switch_on_hover(true);
settings_menu->set_text(TTR("Editor"));
settings_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles")));
left_menu_hb->add_child(settings_menu);
p = settings_menu->get_popup();
settings_menu = memnew(PopupMenu);
settings_menu->set_name(TTR("Editor"));
main_menu->add_child(settings_menu);
ED_SHORTCUT_AND_COMMAND("editor/editor_settings", TTR("Editor Settings..."));
ED_SHORTCUT_OVERRIDE("editor/editor_settings", "macos", KeyModifierMask::CMD + Key::COMMA);
p->add_shortcut(ED_GET_SHORTCUT("editor/editor_settings"), SETTINGS_PREFERENCES);
p->add_shortcut(ED_SHORTCUT("editor/command_palette", TTR("Command Palette..."), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::P), HELP_COMMAND_PALETTE);
p->add_separator();
settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/editor_settings"), SETTINGS_PREFERENCES);
settings_menu->add_shortcut(ED_SHORTCUT("editor/command_palette", TTR("Command Palette..."), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::P), HELP_COMMAND_PALETTE);
settings_menu->add_separator();
editor_layouts = memnew(PopupMenu);
editor_layouts->set_name("Layouts");
p->add_child(editor_layouts);
settings_menu->add_child(editor_layouts);
editor_layouts->connect("id_pressed", callable_mp(this, &EditorNode::_layout_menu_option));
p->add_submenu_item(TTR("Editor Layout"), "Layouts");
p->add_separator();
settings_menu->add_submenu_item(TTR("Editor Layout"), "Layouts");
settings_menu->add_separator();
ED_SHORTCUT_AND_COMMAND("editor/take_screenshot", TTR("Take Screenshot"), KeyModifierMask::CTRL | Key::F12);
ED_SHORTCUT_OVERRIDE("editor/take_screenshot", "macos", KeyModifierMask::CMD | Key::F12);
p->add_shortcut(ED_GET_SHORTCUT("editor/take_screenshot"), EDITOR_SCREENSHOT);
settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/take_screenshot"), EDITOR_SCREENSHOT);
p->set_item_tooltip(-1, TTR("Screenshots are stored in the Editor Data/Settings Folder."));
settings_menu->set_item_tooltip(-1, TTR("Screenshots are stored in the Editor Data/Settings Folder."));
ED_SHORTCUT_AND_COMMAND("editor/fullscreen_mode", TTR("Toggle Fullscreen"), KeyModifierMask::SHIFT | Key::F11);
ED_SHORTCUT_OVERRIDE("editor/fullscreen_mode", "macos", KeyModifierMask::CMD | KeyModifierMask::CTRL | Key::F);
p->add_shortcut(ED_GET_SHORTCUT("editor/fullscreen_mode"), SETTINGS_TOGGLE_FULLSCREEN);
settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/fullscreen_mode"), SETTINGS_TOGGLE_FULLSCREEN);
p->add_separator();
settings_menu->add_separator();
if (OS::get_singleton()->get_data_path() == OS::get_singleton()->get_config_path()) {
// Configuration and data folders are located in the same place (Windows/MacOS).
p->add_item(TTR("Open Editor Data/Settings Folder"), SETTINGS_EDITOR_DATA_FOLDER);
settings_menu->add_item(TTR("Open Editor Data/Settings Folder"), SETTINGS_EDITOR_DATA_FOLDER);
} else {
// Separate configuration and data folders (Linux).
p->add_item(TTR("Open Editor Data Folder"), SETTINGS_EDITOR_DATA_FOLDER);
p->add_item(TTR("Open Editor Settings Folder"), SETTINGS_EDITOR_CONFIG_FOLDER);
settings_menu->add_item(TTR("Open Editor Data Folder"), SETTINGS_EDITOR_DATA_FOLDER);
settings_menu->add_item(TTR("Open Editor Settings Folder"), SETTINGS_EDITOR_CONFIG_FOLDER);
}
p->add_separator();
settings_menu->add_separator();
p->add_item(TTR("Manage Editor Features..."), SETTINGS_MANAGE_FEATURE_PROFILES);
p->add_item(TTR("Manage Export Templates..."), SETTINGS_MANAGE_EXPORT_TEMPLATES);
settings_menu->add_item(TTR("Manage Editor Features..."), SETTINGS_MANAGE_FEATURE_PROFILES);
settings_menu->add_item(TTR("Manage Export Templates..."), SETTINGS_MANAGE_EXPORT_TEMPLATES);
help_menu = memnew(MenuButton);
help_menu->set_flat(false);
help_menu->set_switch_on_hover(true);
help_menu->set_text(TTR("Help"));
help_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles")));
left_menu_hb->add_child(help_menu);
help_menu = memnew(PopupMenu);
help_menu->set_name(TTR("Help"));
main_menu->add_child(help_menu);
p = help_menu->get_popup();
p->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
help_menu->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
ED_SHORTCUT_AND_COMMAND("editor/editor_help", TTR("Search Help"), Key::F1);
ED_SHORTCUT_OVERRIDE("editor/editor_help", "macos", KeyModifierMask::ALT | Key::SPACE);
p->add_icon_shortcut(gui_base->get_theme_icon(SNAME("HelpSearch"), SNAME("EditorIcons")), ED_GET_SHORTCUT("editor/editor_help"), HELP_SEARCH);
p->add_separator();
p->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/online_docs", TTR("Online Documentation")), HELP_DOCS);
p->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/q&a", TTR("Questions & Answers")), HELP_QA);
p->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/report_a_bug", TTR("Report a Bug")), HELP_REPORT_A_BUG);
p->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/suggest_a_feature", TTR("Suggest a Feature")), HELP_SUGGEST_A_FEATURE);
p->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/send_docs_feedback", TTR("Send Docs Feedback")), HELP_SEND_DOCS_FEEDBACK);
p->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/community", TTR("Community")), HELP_COMMUNITY);
p->add_separator();
p->add_icon_shortcut(gui_base->get_theme_icon(SNAME("Godot"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/about", TTR("About Godot")), HELP_ABOUT);
p->add_icon_shortcut(gui_base->get_theme_icon(SNAME("Heart"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/support_development", TTR("Support Godot Development")), HELP_SUPPORT_GODOT_DEVELOPMENT);
help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("HelpSearch"), SNAME("EditorIcons")), ED_GET_SHORTCUT("editor/editor_help"), HELP_SEARCH);
help_menu->add_separator();
help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/online_docs", TTR("Online Documentation")), HELP_DOCS);
help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/q&a", TTR("Questions & Answers")), HELP_QA);
help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/report_a_bug", TTR("Report a Bug")), HELP_REPORT_A_BUG);
help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/suggest_a_feature", TTR("Suggest a Feature")), HELP_SUGGEST_A_FEATURE);
help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/send_docs_feedback", TTR("Send Docs Feedback")), HELP_SEND_DOCS_FEEDBACK);
help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/community", TTR("Community")), HELP_COMMUNITY);
help_menu->add_separator();
if (!global_menu || !OS::get_singleton()->has_feature("macos")) {
// On macOS "Quit" and "About" options are in the "app" menu.
help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("Godot"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/about", TTR("About Godot")), HELP_ABOUT);
}
help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("Heart"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/support_development", TTR("Support Godot Development")), HELP_SUPPORT_GODOT_DEVELOPMENT);
Control *right_spacer = memnew(Control);
menu_hb->add_child(right_spacer);
HBoxContainer *play_hb = memnew(HBoxContainer);
menu_hb->add_child(play_hb);
@ -6878,7 +6862,7 @@ EditorNode::EditorNode() {
right_menu_hb->add_child(update_spinner);
update_spinner->set_icon(gui_base->get_theme_icon(SNAME("Progress1"), SNAME("EditorIcons")));
update_spinner->get_popup()->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
p = update_spinner->get_popup();
PopupMenu *p = update_spinner->get_popup();
p->add_radio_check_item(TTR("Update Continuously"), SETTINGS_UPDATE_CONTINUOUSLY);
p->add_radio_check_item(TTR("Update When Changed"), SETTINGS_UPDATE_WHEN_CHANGED);
p->add_separator();
@ -7098,11 +7082,11 @@ EditorNode::EditorNode() {
gui_base->add_child(file_script);
file_script->connect("file_selected", callable_mp(this, &EditorNode::_dialog_action));
file_menu->get_popup()->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
file_menu->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
file_menu->connect("about_to_popup", callable_mp(this, &EditorNode::_update_file_menu_opened));
file_menu->get_popup()->connect("popup_hide", callable_mp(this, &EditorNode::_update_file_menu_closed));
file_menu->connect("popup_hide", callable_mp(this, &EditorNode::_update_file_menu_closed));
settings_menu->get_popup()->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
settings_menu->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
file->connect("file_selected", callable_mp(this, &EditorNode::_dialog_action));
file_templates->connect("file_selected", callable_mp(this, &EditorNode::_dialog_action));
@ -7385,11 +7369,14 @@ EditorNode::EditorNode() {
screenshot_timer = memnew(Timer);
screenshot_timer->set_one_shot(true);
screenshot_timer->set_wait_time(settings_menu->get_popup()->get_submenu_popup_delay() + 0.1f);
screenshot_timer->set_wait_time(settings_menu->get_submenu_popup_delay() + 0.1f);
screenshot_timer->connect("timeout", callable_mp(this, &EditorNode::_request_screenshot));
add_child(screenshot_timer);
screenshot_timer->set_owner(get_owner());
main_menu->set_custom_minimum_size(Size2(MAX(main_menu->get_minimum_size().x, play_hb->get_minimum_size().x + right_menu_hb->get_minimum_size().x), 0));
right_spacer->set_custom_minimum_size(Size2(MAX(0, main_menu->get_minimum_size().x - play_hb->get_minimum_size().x - right_menu_hb->get_minimum_size().x), 0));
String exec = OS::get_singleton()->get_executable_path();
// Save editor executable path for third-party tools.
EditorSettings::get_singleton()->set_project_metadata("editor_metadata", "executable_path", exec);

View File

@ -78,6 +78,7 @@ class FileSystemDock;
class HSplitContainer;
class ImportDock;
class LinkButton;
class MenuBar;
class MenuButton;
class NodeDock;
class OrphanResourcesDialog;
@ -322,11 +323,12 @@ private:
HBoxContainer *menu_hb = nullptr;
Control *main_control = nullptr;
MenuButton *file_menu = nullptr;
MenuButton *project_menu = nullptr;
MenuButton *debug_menu = nullptr;
MenuButton *settings_menu = nullptr;
MenuButton *help_menu = nullptr;
MenuBar *main_menu = nullptr;
PopupMenu *file_menu = nullptr;
PopupMenu *project_menu = nullptr;
PopupMenu *debug_menu = nullptr;
PopupMenu *settings_menu = nullptr;
PopupMenu *help_menu = nullptr;
PopupMenu *tool_menu = nullptr;
PopupMenu *export_as_menu = nullptr;
Button *export_button = nullptr;

View File

@ -72,7 +72,7 @@ void EditorPath::_add_children_to_popup(Object *p_obj, int p_depth) {
int index = sub_objects_menu->get_item_count();
sub_objects_menu->add_icon_item(icon, proper_name, objects.size());
sub_objects_menu->set_item_horizontal_offset(index, p_depth * 10 * EDSCALE);
sub_objects_menu->set_item_indent(index, p_depth);
objects.push_back(obj->get_instance_id());
_add_children_to_popup(obj, p_depth + 1);

View File

@ -406,6 +406,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
_initial_set("interface/editor/debug/enable_pseudolocalization", false);
set_restart_if_changed("interface/editor/debug/enable_pseudolocalization", true);
// Use pseudolocalization in editor.
EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/use_embedded_menu", false, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
EDITOR_SETTING_USAGE(Variant::FLOAT, PROPERTY_HINT_RANGE, "interface/editor/custom_display_scale", 1.0, "0.5,3,0.01", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "interface/editor/main_font_size", 14, "8,48,1")

View File

@ -788,6 +788,25 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
editor_log_button_pressed->set_border_color(accent_color);
theme->set_stylebox("pressed", "EditorLogFilterButton", editor_log_button_pressed);
// MenuBar
theme->set_stylebox("normal", "MenuBar", style_widget);
theme->set_stylebox("hover", "MenuBar", style_widget_hover);
theme->set_stylebox("pressed", "MenuBar", style_widget_pressed);
theme->set_stylebox("focus", "MenuBar", style_widget_focus);
theme->set_stylebox("disabled", "MenuBar", style_widget_disabled);
theme->set_color("font_color", "MenuBar", font_color);
theme->set_color("font_hover_color", "MenuBar", font_hover_color);
theme->set_color("font_focus_color", "MenuBar", font_focus_color);
theme->set_color("font_pressed_color", "MenuBar", accent_color);
theme->set_color("font_disabled_color", "MenuBar", font_disabled_color);
theme->set_color("icon_normal_color", "MenuBar", icon_normal_color);
theme->set_color("icon_hover_color", "MenuBar", icon_hover_color);
theme->set_color("icon_focus_color", "MenuBar", icon_focus_color);
theme->set_color("icon_pressed_color", "MenuBar", icon_pressed_color);
theme->set_color("icon_disabled_color", "MenuBar", icon_disabled_color);
// OptionButton
Ref<StyleBoxFlat> style_option_button_focus = style_widget_focus->duplicate();
Ref<StyleBoxFlat> style_option_button_normal = style_widget->duplicate();

1
editor/icons/MenuBar.svg Normal file
View File

@ -0,0 +1 @@
<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M2 6a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1Zm1 2h10v2H3Zm0 3h10v2H3ZM1 1v4h6V1Zm1 1h4L4 4Z" fill="#8eef97"/></svg>

After

Width:  |  Height:  |  Size: 211 B

View File

@ -40,7 +40,7 @@
#include "editor/plugins/script_editor_plugin.h"
#include "scene/gui/menu_button.h"
DebuggerEditorPlugin::DebuggerEditorPlugin(MenuButton *p_debug_menu) {
DebuggerEditorPlugin::DebuggerEditorPlugin(PopupMenu *p_debug_menu) {
EditorDebuggerServer::initialize();
ED_SHORTCUT("debugger/step_into", TTR("Step Into"), Key::F11);
@ -61,30 +61,29 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(MenuButton *p_debug_menu) {
// Main editor debug menu.
debug_menu = p_debug_menu;
PopupMenu *p = debug_menu->get_popup();
p->set_hide_on_checkable_item_selection(false);
p->add_check_shortcut(ED_SHORTCUT("editor/deploy_with_remote_debug", TTR("Deploy with Remote Debug")), RUN_DEPLOY_REMOTE_DEBUG);
p->set_item_tooltip(-1,
debug_menu->set_hide_on_checkable_item_selection(false);
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/deploy_with_remote_debug", TTR("Deploy with Remote Debug")), RUN_DEPLOY_REMOTE_DEBUG);
debug_menu->set_item_tooltip(-1,
TTR("When this option is enabled, using one-click deploy will make the executable attempt to connect to this computer's IP so the running project can be debugged.\nThis option is intended to be used for remote debugging (typically with a mobile device).\nYou don't need to enable it to use the GDScript debugger locally."));
p->add_check_shortcut(ED_SHORTCUT("editor/small_deploy_with_network_fs", TTR("Small Deploy with Network Filesystem")), RUN_FILE_SERVER);
p->set_item_tooltip(-1,
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/small_deploy_with_network_fs", TTR("Small Deploy with Network Filesystem")), RUN_FILE_SERVER);
debug_menu->set_item_tooltip(-1,
TTR("When this option is enabled, using one-click deploy for Android will only export an executable without the project data.\nThe filesystem will be provided from the project by the editor over the network.\nOn Android, deploying will use the USB cable for faster performance. This option speeds up testing for projects with large assets."));
p->add_separator();
p->add_check_shortcut(ED_SHORTCUT("editor/visible_collision_shapes", TTR("Visible Collision Shapes")), RUN_DEBUG_COLLISONS);
p->set_item_tooltip(-1,
debug_menu->add_separator();
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_collision_shapes", TTR("Visible Collision Shapes")), RUN_DEBUG_COLLISONS);
debug_menu->set_item_tooltip(-1,
TTR("When this option is enabled, collision shapes and raycast nodes (for 2D and 3D) will be visible in the running project."));
p->add_check_shortcut(ED_SHORTCUT("editor/visible_paths", TTR("Visible Paths")), RUN_DEBUG_PATHS);
p->set_item_tooltip(-1,
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_paths", TTR("Visible Paths")), RUN_DEBUG_PATHS);
debug_menu->set_item_tooltip(-1,
TTR("When this option is enabled, curve resources used by path nodes will be visible in the running project."));
p->add_check_shortcut(ED_SHORTCUT("editor/visible_navigation", TTR("Visible Navigation")), RUN_DEBUG_NAVIGATION);
p->set_item_tooltip(-1,
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_navigation", TTR("Visible Navigation")), RUN_DEBUG_NAVIGATION);
debug_menu->set_item_tooltip(-1,
TTR("When this option is enabled, navigation meshes and polygons will be visible in the running project."));
p->add_separator();
p->add_check_shortcut(ED_SHORTCUT("editor/sync_scene_changes", TTR("Synchronize Scene Changes")), RUN_LIVE_DEBUG);
p->set_item_tooltip(-1,
debug_menu->add_separator();
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/sync_scene_changes", TTR("Synchronize Scene Changes")), RUN_LIVE_DEBUG);
debug_menu->set_item_tooltip(-1,
TTR("When this option is enabled, any changes made to the scene in the editor will be replicated in the running project.\nWhen used remotely on a device, this is more efficient when the network filesystem option is enabled."));
p->add_check_shortcut(ED_SHORTCUT("editor/sync_script_changes", TTR("Synchronize Script Changes")), RUN_RELOAD_SCRIPTS);
p->set_item_tooltip(-1,
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/sync_script_changes", TTR("Synchronize Script Changes")), RUN_RELOAD_SCRIPTS);
debug_menu->set_item_tooltip(-1,
TTR("When this option is enabled, any script that is saved will be reloaded in the running project.\nWhen used remotely on a device, this is more efficient when the network filesystem option is enabled."));
// Multi-instance, start/stop
@ -92,9 +91,9 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(MenuButton *p_debug_menu) {
instances_menu->set_name("run_instances");
instances_menu->set_hide_on_checkable_item_selection(false);
p->add_child(instances_menu);
p->add_separator();
p->add_submenu_item(TTR("Run Multiple Instances"), "run_instances");
debug_menu->add_child(instances_menu);
debug_menu->add_separator();
debug_menu->add_submenu_item(TTR("Run Multiple Instances"), "run_instances");
instances_menu->add_radio_check_item(TTR("Run 1 Instance"));
instances_menu->set_item_metadata(0, 1);
@ -106,7 +105,7 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(MenuButton *p_debug_menu) {
instances_menu->set_item_metadata(3, 4);
instances_menu->set_item_checked(0, true);
instances_menu->connect("index_pressed", callable_mp(this, &DebuggerEditorPlugin::_select_run_count));
p->connect("id_pressed", callable_mp(this, &DebuggerEditorPlugin::_menu_option));
debug_menu->connect("id_pressed", callable_mp(this, &DebuggerEditorPlugin::_menu_option));
}
DebuggerEditorPlugin::~DebuggerEditorPlugin() {
@ -125,7 +124,7 @@ void DebuggerEditorPlugin::_select_run_count(int p_index) {
void DebuggerEditorPlugin::_menu_option(int p_option) {
switch (p_option) {
case RUN_FILE_SERVER: {
bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_FILE_SERVER));
bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_FILE_SERVER));
if (ischecked) {
file_server->stop();
@ -133,45 +132,45 @@ void DebuggerEditorPlugin::_menu_option(int p_option) {
file_server->start();
}
debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_FILE_SERVER), !ischecked);
debug_menu->set_item_checked(debug_menu->get_item_index(RUN_FILE_SERVER), !ischecked);
EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_file_server", !ischecked);
} break;
case RUN_LIVE_DEBUG: {
bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_LIVE_DEBUG));
bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_LIVE_DEBUG));
debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_LIVE_DEBUG), !ischecked);
debug_menu->set_item_checked(debug_menu->get_item_index(RUN_LIVE_DEBUG), !ischecked);
EditorDebuggerNode::get_singleton()->set_live_debugging(!ischecked);
EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_live_debug", !ischecked);
} break;
case RUN_DEPLOY_REMOTE_DEBUG: {
bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEPLOY_REMOTE_DEBUG));
debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEPLOY_REMOTE_DEBUG), !ischecked);
bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_DEPLOY_REMOTE_DEBUG));
debug_menu->set_item_checked(debug_menu->get_item_index(RUN_DEPLOY_REMOTE_DEBUG), !ischecked);
EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_deploy_remote_debug", !ischecked);
} break;
case RUN_DEBUG_COLLISONS: {
bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_COLLISONS));
debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_COLLISONS), !ischecked);
bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_DEBUG_COLLISONS));
debug_menu->set_item_checked(debug_menu->get_item_index(RUN_DEBUG_COLLISONS), !ischecked);
EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_debug_collisons", !ischecked);
} break;
case RUN_DEBUG_PATHS: {
bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_PATHS));
debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_PATHS), !ischecked);
bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_DEBUG_PATHS));
debug_menu->set_item_checked(debug_menu->get_item_index(RUN_DEBUG_PATHS), !ischecked);
EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_debug_paths", !ischecked);
} break;
case RUN_DEBUG_NAVIGATION: {
bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_NAVIGATION));
debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_NAVIGATION), !ischecked);
bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_DEBUG_NAVIGATION));
debug_menu->set_item_checked(debug_menu->get_item_index(RUN_DEBUG_NAVIGATION), !ischecked);
EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_debug_navigation", !ischecked);
} break;
case RUN_RELOAD_SCRIPTS: {
bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_RELOAD_SCRIPTS));
debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_RELOAD_SCRIPTS), !ischecked);
bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_RELOAD_SCRIPTS));
debug_menu->set_item_checked(debug_menu->get_item_index(RUN_RELOAD_SCRIPTS), !ischecked);
ScriptEditor::get_singleton()->set_live_auto_reload_running_scripts(!ischecked);
EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_reload_scripts", !ischecked);

View File

@ -41,7 +41,7 @@ class DebuggerEditorPlugin : public EditorPlugin {
GDCLASS(DebuggerEditorPlugin, EditorPlugin);
private:
MenuButton *debug_menu = nullptr;
PopupMenu *debug_menu = nullptr;
EditorFileServer *file_server = nullptr;
PopupMenu *instances_menu = nullptr;
@ -64,7 +64,7 @@ public:
virtual String get_name() const override { return "Debugger"; }
bool has_main_screen() const override { return false; }
DebuggerEditorPlugin(MenuButton *p_menu);
DebuggerEditorPlugin(PopupMenu *p_menu);
~DebuggerEditorPlugin();
};

View File

@ -2678,7 +2678,7 @@ void SceneTreeDock::_add_children_to_popup(Object *p_obj, int p_depth) {
}
int index = menu_subresources->get_item_count();
menu_subresources->add_icon_item(icon, E.name.capitalize(), EDIT_SUBRESOURCE_BASE + subresources.size());
menu_subresources->set_item_horizontal_offset(index, p_depth * 10 * EDSCALE);
menu_subresources->set_item_indent(index, p_depth);
subresources.push_back(obj->get_instance_id());
_add_children_to_popup(obj, p_depth + 1);

View File

@ -196,6 +196,9 @@ private:
static NSCursor *_cursor_from_selector(SEL p_selector, SEL p_fallback = nil);
bool _has_help_menu() const;
NSMenuItem *_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out);
public:
NSMenu *get_dock_menu() const;
void menu_callback(id p_sender);
@ -224,15 +227,15 @@ public:
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;
virtual void global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
virtual void global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
virtual void global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
virtual void global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
virtual void global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
virtual void global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
virtual void global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
virtual void global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override;
virtual void global_menu_add_separator(const String &p_menu_root, int p_index = -1) override;
virtual int global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
virtual int global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
virtual int global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
virtual int global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
virtual int global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
virtual int global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
virtual int global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override;
virtual int global_menu_add_separator(const String &p_menu_root, int p_index = -1) override;
virtual int global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const override;
virtual int global_menu_get_item_index_from_tag(const String &p_menu_root, const Variant &p_tag) const override;
@ -250,6 +253,7 @@ public:
virtual int global_menu_get_item_state(const String &p_menu_root, int p_idx) const override;
virtual int global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const override;
virtual Ref<Texture2D> global_menu_get_item_icon(const String &p_menu_root, int p_idx) const override;
virtual int global_menu_get_item_indentation_level(const String &p_menu_root, int p_idx) const override;
virtual void global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) override;
virtual void global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override;
@ -264,6 +268,7 @@ public:
virtual void global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state) override;
virtual void global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states) override;
virtual void global_menu_set_item_icon(const String &p_menu_root, int p_idx, const Ref<Texture2D> &p_icon) override;
virtual void global_menu_set_item_indentation_level(const String &p_menu_root, int p_idx, int p_level) override;
virtual int global_menu_get_item_count(const String &p_menu_root) const override;

View File

@ -63,7 +63,7 @@
const NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) const {
const NSMenu *menu = nullptr;
if (p_menu_root == "") {
if (p_menu_root == "" || p_menu_root.to_lower() == "_main") {
// Main menu.
menu = [NSApp mainMenu];
} else if (p_menu_root.to_lower() == "_dock") {
@ -84,7 +84,7 @@ const NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) cons
NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) {
NSMenu *menu = nullptr;
if (p_menu_root == "") {
if (p_menu_root == "" || p_menu_root.to_lower() == "_main") {
// Main menu.
menu = [NSApp mainMenu];
} else if (p_menu_root.to_lower() == "_dock") {
@ -714,18 +714,51 @@ String DisplayServerMacOS::get_name() const {
return "macOS";
}
void DisplayServerMacOS::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
_THREAD_SAFE_METHOD_
bool DisplayServerMacOS::_has_help_menu() const {
if ([NSApp helpMenu]) {
return true;
} else {
NSMenu *menu = [NSApp mainMenu];
const NSMenuItem *menu_item = [menu itemAtIndex:[menu numberOfItems] - 1];
if (menu_item) {
String menu_name = String::utf8([[menu_item title] UTF8String]);
if (menu_name == "Help" || menu_name == RTR("Help")) {
return true;
}
}
return false;
}
}
NSMenuItem *DisplayServerMacOS::_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out) {
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
NSMenuItem *menu_item;
if (p_index != -1) {
menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
int item_count = ((menu == [NSApp mainMenu]) && _has_help_menu()) ? [menu numberOfItems] - 1 : [menu numberOfItems];
if ((menu == [NSApp mainMenu]) && (p_label == "Help" || p_label == RTR("Help"))) {
p_index = [menu numberOfItems];
} else if (p_index < 0) {
p_index = item_count;
} else {
menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_index++;
}
p_index = CLAMP(p_index, 0, item_count);
}
menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
*r_out = (menu == [NSApp mainMenu]) ? p_index - 1 : p_index;
return menu_item;
}
return nullptr;
}
int DisplayServerMacOS::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
_THREAD_SAFE_METHOD_
int out = -1;
NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
if (menu_item) {
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
@ -735,20 +768,15 @@ void DisplayServerMacOS::global_menu_add_item(const String &p_menu_root, const S
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
return out;
}
void DisplayServerMacOS::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
int DisplayServerMacOS::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
NSMenuItem *menu_item;
if (p_index != -1) {
menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
} else {
menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
}
int out = -1;
NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
if (menu_item) {
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
@ -758,20 +786,15 @@ void DisplayServerMacOS::global_menu_add_check_item(const String &p_menu_root, c
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
return out;
}
void DisplayServerMacOS::global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
int DisplayServerMacOS::global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
NSMenuItem *menu_item;
if (p_index != -1) {
menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
} else {
menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
}
int out = -1;
NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
if (menu_item) {
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
@ -790,20 +813,15 @@ void DisplayServerMacOS::global_menu_add_icon_item(const String &p_menu_root, co
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
return out;
}
void DisplayServerMacOS::global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
int DisplayServerMacOS::global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
NSMenuItem *menu_item;
if (p_index != -1) {
menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
} else {
menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
}
int out = -1;
NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
if (menu_item) {
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
@ -822,20 +840,15 @@ void DisplayServerMacOS::global_menu_add_icon_check_item(const String &p_menu_ro
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
return out;
}
void DisplayServerMacOS::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
int DisplayServerMacOS::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
NSMenuItem *menu_item;
if (p_index != -1) {
menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
} else {
menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
}
int out = -1;
NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
if (menu_item) {
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
@ -845,20 +858,15 @@ void DisplayServerMacOS::global_menu_add_radio_check_item(const String &p_menu_r
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
return out;
}
void DisplayServerMacOS::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
int DisplayServerMacOS::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
NSMenuItem *menu_item;
if (p_index != -1) {
menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
} else {
menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
}
int out = -1;
NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
if (menu_item) {
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
@ -877,20 +885,31 @@ void DisplayServerMacOS::global_menu_add_icon_radio_check_item(const String &p_m
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
return out;
}
void DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
int DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
int out = -1;
if (menu) {
String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
NSMenuItem *menu_item;
if (p_index != -1) {
menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
int item_count = ((menu == [NSApp mainMenu]) && _has_help_menu()) ? [menu numberOfItems] - 1 : [menu numberOfItems];
if ((menu == [NSApp mainMenu]) && (p_label == "Help" || p_label == RTR("Help"))) {
p_index = [menu numberOfItems];
} else if (p_index < 0) {
p_index = item_count;
} else {
menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_index++;
}
p_index = CLAMP(p_index, 0, item_count);
}
menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
out = (menu == [NSApp mainMenu]) ? p_index - 1 : p_index;
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
@ -900,44 +919,69 @@ void DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_ro
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
return out;
}
void DisplayServerMacOS::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) {
int DisplayServerMacOS::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
NSMenu *sub_menu = _get_menu_root(p_submenu);
int out = -1;
if (menu && sub_menu) {
if (sub_menu == menu) {
ERR_PRINT("Can't set submenu to self!");
return;
return -1;
}
if ([sub_menu supermenu]) {
ERR_PRINT("Can't set submenu to menu that is already a submenu of some other menu!");
return;
return -1;
}
NSMenuItem *menu_item;
if (p_index != -1) {
menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index];
int item_count = ((menu == [NSApp mainMenu]) && _has_help_menu()) ? [menu numberOfItems] - 1 : [menu numberOfItems];
if ((menu == [NSApp mainMenu]) && (p_label == "Help" || p_label == RTR("Help"))) {
p_index = [menu numberOfItems];
} else if (p_index < 0) {
p_index = item_count;
} else {
menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@""];
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_index++;
}
p_index = CLAMP(p_index, 0, item_count);
}
menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index];
out = (menu == [NSApp mainMenu]) ? p_index - 1 : p_index;
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = Callable();
obj->checkable_type = CHECKABLE_TYPE_NONE;
obj->max_states = 0;
obj->state = 0;
[menu_item setRepresentedObject:obj];
[sub_menu setTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()]];
[menu setSubmenu:sub_menu forItem:menu_item];
}
return out;
}
void DisplayServerMacOS::global_menu_add_separator(const String &p_menu_root, int p_index) {
int DisplayServerMacOS::global_menu_add_separator(const String &p_menu_root, int p_index) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
if (p_index != -1) {
[menu insertItem:[NSMenuItem separatorItem] atIndex:p_index];
} else {
[menu addItem:[NSMenuItem separatorItem]];
if (menu == [NSApp mainMenu]) { // Do not add separators into main menu.
return -1;
}
if (p_index < 0) {
p_index = [menu numberOfItems];
} else {
p_index = CLAMP(p_index, 0, [menu numberOfItems]);
}
[menu insertItem:[NSMenuItem separatorItem] atIndex:p_index];
return p_index;
}
return -1;
}
int DisplayServerMacOS::global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const {
@ -945,7 +989,11 @@ int DisplayServerMacOS::global_menu_get_item_index_from_text(const String &p_men
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]] - 1;
} else {
return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
}
}
return -1;
@ -956,12 +1004,16 @@ int DisplayServerMacOS::global_menu_get_item_index_from_tag(const String &p_menu
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
for (NSInteger i = 0; i < [menu numberOfItems]; i++) {
for (NSInteger i = (menu == [NSApp mainMenu]) ? 1 : 0; i < [menu numberOfItems]; i++) {
const NSMenuItem *menu_item = [menu itemAtIndex:i];
if (menu_item) {
const GodotMenuItem *obj = [menu_item representedObject];
if (obj && obj->meta == p_tag) {
return i;
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
return i - 1;
} else {
return i;
}
}
}
}
@ -975,6 +1027,11 @@ bool DisplayServerMacOS::global_menu_is_item_checked(const String &p_menu_root,
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
ERR_FAIL_COND_V(p_idx < 0, false);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false);
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
return ([menu_item state] == NSControlStateValueOn);
@ -988,6 +1045,11 @@ bool DisplayServerMacOS::global_menu_is_item_checkable(const String &p_menu_root
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
ERR_FAIL_COND_V(p_idx < 0, false);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false);
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
@ -1004,6 +1066,11 @@ bool DisplayServerMacOS::global_menu_is_item_radio_checkable(const String &p_men
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
ERR_FAIL_COND_V(p_idx < 0, false);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false);
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
@ -1020,6 +1087,11 @@ Callable DisplayServerMacOS::global_menu_get_item_callback(const String &p_menu_
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
ERR_FAIL_COND_V(p_idx < 0, Callable());
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Callable());
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
@ -1036,6 +1108,11 @@ Variant DisplayServerMacOS::global_menu_get_item_tag(const String &p_menu_root,
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
ERR_FAIL_COND_V(p_idx < 0, Variant());
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Variant());
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
@ -1052,6 +1129,11 @@ String DisplayServerMacOS::global_menu_get_item_text(const String &p_menu_root,
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
ERR_FAIL_COND_V(p_idx < 0, String());
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String());
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
return String::utf8([[menu_item title] UTF8String]);
@ -1065,6 +1147,11 @@ String DisplayServerMacOS::global_menu_get_item_submenu(const String &p_menu_roo
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
ERR_FAIL_COND_V(p_idx < 0, String());
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String());
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
const NSMenu *sub_menu = [menu_item submenu];
@ -1085,6 +1172,11 @@ Key DisplayServerMacOS::global_menu_get_item_accelerator(const String &p_menu_ro
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
ERR_FAIL_COND_V(p_idx < 0, Key::NONE);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Key::NONE);
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
String ret = String::utf8([[menu_item keyEquivalent] UTF8String]);
@ -1116,6 +1208,11 @@ bool DisplayServerMacOS::global_menu_is_item_disabled(const String &p_menu_root,
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
ERR_FAIL_COND_V(p_idx < 0, false);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false);
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
return ![menu_item isEnabled];
@ -1129,6 +1226,11 @@ String DisplayServerMacOS::global_menu_get_item_tooltip(const String &p_menu_roo
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
ERR_FAIL_COND_V(p_idx < 0, String());
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String());
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
return String::utf8([[menu_item toolTip] UTF8String]);
@ -1142,6 +1244,11 @@ int DisplayServerMacOS::global_menu_get_item_state(const String &p_menu_root, in
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
ERR_FAIL_COND_V(p_idx < 0, 0);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], 0);
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
@ -1158,6 +1265,11 @@ int DisplayServerMacOS::global_menu_get_item_max_states(const String &p_menu_roo
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
ERR_FAIL_COND_V(p_idx < 0, 0);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], 0);
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
@ -1174,6 +1286,11 @@ Ref<Texture2D> DisplayServerMacOS::global_menu_get_item_icon(const String &p_men
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
ERR_FAIL_COND_V(p_idx < 0, Ref<Texture2D>());
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Ref<Texture2D>());
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
@ -1187,14 +1304,34 @@ Ref<Texture2D> DisplayServerMacOS::global_menu_get_item_icon(const String &p_men
return Ref<Texture2D>();
}
int DisplayServerMacOS::global_menu_get_item_indentation_level(const String &p_menu_root, int p_idx) const {
_THREAD_SAFE_METHOD_
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
ERR_FAIL_COND_V(p_idx < 0, 0);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], 0);
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
return [menu_item indentationLevel];
}
}
return 0;
}
void DisplayServerMacOS::global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
return;
ERR_FAIL_COND(p_idx < 0);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
if (p_checked) {
@ -1211,12 +1348,15 @@ void DisplayServerMacOS::global_menu_set_item_checkable(const String &p_menu_roo
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
return;
ERR_FAIL_COND(p_idx < 0);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
ERR_FAIL_COND(!obj);
obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_CHECK_BOX : CHECKABLE_TYPE_NONE;
}
}
@ -1227,12 +1367,15 @@ void DisplayServerMacOS::global_menu_set_item_radio_checkable(const String &p_me
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
return;
ERR_FAIL_COND(p_idx < 0);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
ERR_FAIL_COND(!obj);
obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_RADIO_BUTTON : CHECKABLE_TYPE_NONE;
}
}
@ -1243,12 +1386,15 @@ void DisplayServerMacOS::global_menu_set_item_callback(const String &p_menu_root
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
return;
ERR_FAIL_COND(p_idx < 0);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
ERR_FAIL_COND(!obj);
obj->callback = p_callback;
}
}
@ -1259,12 +1405,15 @@ void DisplayServerMacOS::global_menu_set_item_tag(const String &p_menu_root, int
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
return;
ERR_FAIL_COND(p_idx < 0);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
ERR_FAIL_COND(!obj);
obj->meta = p_tag;
}
}
@ -1275,9 +1424,11 @@ void DisplayServerMacOS::global_menu_set_item_text(const String &p_menu_root, in
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
return;
ERR_FAIL_COND(p_idx < 0);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
[menu_item setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
@ -1299,9 +1450,11 @@ void DisplayServerMacOS::global_menu_set_item_submenu(const String &p_menu_root,
ERR_PRINT("Can't set submenu to menu that is already a submenu of some other menu!");
return;
}
if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
return;
ERR_FAIL_COND(p_idx < 0);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
[menu setSubmenu:sub_menu forItem:menu_item];
@ -1314,9 +1467,11 @@ void DisplayServerMacOS::global_menu_set_item_accelerator(const String &p_menu_r
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
return;
ERR_FAIL_COND(p_idx < 0);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_keycode)];
@ -1331,9 +1486,11 @@ void DisplayServerMacOS::global_menu_set_item_disabled(const String &p_menu_root
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
return;
ERR_FAIL_COND(p_idx < 0);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
[menu_item setEnabled:(!p_disabled)];
@ -1346,9 +1503,11 @@ void DisplayServerMacOS::global_menu_set_item_tooltip(const String &p_menu_root,
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
return;
ERR_FAIL_COND(p_idx < 0);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
[menu_item setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]];
@ -1361,15 +1520,16 @@ void DisplayServerMacOS::global_menu_set_item_state(const String &p_menu_root, i
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
return;
ERR_FAIL_COND(p_idx < 0);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
if (obj) {
obj->state = p_state;
}
ERR_FAIL_COND(!obj);
obj->state = p_state;
}
}
}
@ -1379,15 +1539,16 @@ void DisplayServerMacOS::global_menu_set_item_max_states(const String &p_menu_ro
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
return;
ERR_FAIL_COND(p_idx < 0);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
if (obj) {
obj->max_states = p_max_states;
}
ERR_FAIL_COND(!obj);
obj->max_states = p_max_states;
}
}
}
@ -1397,12 +1558,15 @@ void DisplayServerMacOS::global_menu_set_item_icon(const String &p_menu_root, in
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
return;
ERR_FAIL_COND(p_idx < 0);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
ERR_FAIL_COND(!obj);
if (p_icon.is_valid()) {
obj->img = p_icon->get_image();
obj->img = obj->img->duplicate();
@ -1419,12 +1583,33 @@ void DisplayServerMacOS::global_menu_set_item_icon(const String &p_menu_root, in
}
}
void DisplayServerMacOS::global_menu_set_item_indentation_level(const String &p_menu_root, int p_idx, int p_level) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
ERR_FAIL_COND(p_idx < 0);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
[menu_item setIndentationLevel:p_level];
}
}
}
int DisplayServerMacOS::global_menu_get_item_count(const String &p_menu_root) const {
_THREAD_SAFE_METHOD_
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
return [menu numberOfItems];
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
return [menu numberOfItems] - 1;
} else {
return [menu numberOfItems];
}
} else {
return 0;
}
@ -1435,9 +1620,11 @@ void DisplayServerMacOS::global_menu_remove_item(const String &p_menu_root, int
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not delete Apple menu.
return;
ERR_FAIL_COND(p_idx < 0);
if (menu == [NSApp mainMenu]) { // Skip Apple menu.
p_idx++;
}
ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
[menu removeItemAtIndex:p_idx];
}
}
@ -1453,6 +1640,9 @@ void DisplayServerMacOS::global_menu_clear(const String &p_menu_root) {
NSMenuItem *menu_item = [menu addItemWithTitle:@"" action:nil keyEquivalent:@""];
[menu setSubmenu:apple_menu forItem:menu_item];
}
if (submenu.has(p_menu_root)) {
submenu.erase(p_menu_root);
}
}
}
@ -3222,7 +3412,7 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
[apple_menu addItem:[NSMenuItem separatorItem]];
title = [NSString stringWithFormat:NSLocalizedString(@"Quit %@", nil), nsappname];
title = [NSString stringWithFormat:NSLocalizedString(@"\t\tQuit %@", nil), nsappname];
[apple_menu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
// Add items to the menu bar.

View File

@ -46,7 +46,6 @@ enum GlobalMenuCheckType {
@public
Callable callback;
Variant meta;
int id;
GlobalMenuCheckType checkable_type;
int max_states;
int state;

865
scene/gui/menu_bar.cpp Normal file
View File

@ -0,0 +1,865 @@
/*************************************************************************/
/* menu_bar.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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 "menu_bar.h"
#include "core/os/keyboard.h"
#include "scene/main/window.h"
void MenuBar::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
if (is_native_menu()) {
// Handled by OS.
return;
}
MutexLock lock(mutex);
if (p_event->is_action("ui_left") && p_event->is_pressed()) {
int new_sel = selected_menu;
int old_sel = (selected_menu < 0) ? 0 : selected_menu;
do {
new_sel--;
if (new_sel < 0) {
new_sel = menu_cache.size() - 1;
}
if (old_sel == new_sel) {
return;
}
} while (menu_cache[new_sel].hidden || menu_cache[new_sel].disabled);
if (selected_menu != new_sel) {
selected_menu = new_sel;
focused_menu = selected_menu;
if (active_menu >= 0) {
get_menu_popup(active_menu)->hide();
}
_open_popup(selected_menu);
}
return;
} else if (p_event->is_action("ui_right") && p_event->is_pressed()) {
int new_sel = selected_menu;
int old_sel = (selected_menu < 0) ? menu_cache.size() - 1 : selected_menu;
do {
new_sel++;
if (new_sel >= menu_cache.size()) {
new_sel = 0;
}
if (old_sel == new_sel) {
return;
}
} while (menu_cache[new_sel].hidden || menu_cache[new_sel].disabled);
if (selected_menu != new_sel) {
selected_menu = new_sel;
focused_menu = selected_menu;
if (active_menu >= 0) {
get_menu_popup(active_menu)->hide();
}
_open_popup(selected_menu);
}
return;
}
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
int old_sel = selected_menu;
focused_menu = _get_index_at_point(mm->get_position());
if (focused_menu >= 0) {
selected_menu = focused_menu;
}
if (selected_menu != old_sel) {
update();
}
}
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid()) {
if (mb->is_pressed() && (mb->get_button_index() == MouseButton::LEFT || mb->get_button_index() == MouseButton::RIGHT)) {
int index = _get_index_at_point(mb->get_position());
if (index >= 0) {
_open_popup(index);
}
}
}
}
void MenuBar::_open_popup(int p_index) {
ERR_FAIL_INDEX(p_index, menu_cache.size());
PopupMenu *pm = get_menu_popup(p_index);
if (pm->is_visible()) {
pm->hide();
return;
}
Rect2 item_rect = _get_menu_item_rect(p_index);
Point2 screen_pos = get_screen_position() + item_rect.position * get_viewport()->get_canvas_transform().get_scale();
Size2 screen_size = item_rect.size * get_viewport()->get_canvas_transform().get_scale();
active_menu = p_index;
pm->set_size(Size2(screen_size.x, 0));
screen_pos.y += screen_size.y;
if (is_layout_rtl()) {
screen_pos.x += screen_size.x - pm->get_size().width;
}
pm->set_position(screen_pos);
pm->set_parent_rect(Rect2(Point2(screen_pos - pm->get_position()), Size2(screen_size.x, screen_pos.y)));
pm->popup();
update();
}
void MenuBar::shortcut_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
if (is_native_menu()) {
return;
}
if (!_is_focus_owner_in_shortcut_context()) {
return;
}
if (disable_shortcuts) {
return;
}
if (p_event->is_pressed() && !p_event->is_echo() && (Object::cast_to<InputEventKey>(p_event.ptr()) || Object::cast_to<InputEventJoypadButton>(p_event.ptr()) || Object::cast_to<InputEventAction>(*p_event) || Object::cast_to<InputEventShortcut>(*p_event))) {
if (!get_parent() || !is_visible_in_tree()) {
return;
}
Vector<PopupMenu *> popups = _get_popups();
for (int i = 0; i < popups.size(); i++) {
if (menu_cache[i].hidden || menu_cache[i].disabled) {
continue;
}
if (popups[i]->activate_item_by_event(p_event, false)) {
accept_event();
return;
}
}
}
}
void MenuBar::set_shortcut_context(Node *p_node) {
if (p_node != nullptr) {
shortcut_context = p_node->get_instance_id();
} else {
shortcut_context = ObjectID();
}
}
Node *MenuBar::get_shortcut_context() const {
Object *ctx_obj = ObjectDB::get_instance(shortcut_context);
Node *ctx_node = Object::cast_to<Node>(ctx_obj);
return ctx_node;
}
bool MenuBar::_is_focus_owner_in_shortcut_context() const {
if (shortcut_context == ObjectID()) {
// No context, therefore global - always "in" context.
return true;
}
Node *ctx_node = get_shortcut_context();
Control *vp_focus = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr;
// If the context is valid and the viewport focus is valid, check if the context is the focus or is a parent of it.
return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_ancestor_of(vp_focus));
}
void MenuBar::_popup_visibility_changed(bool p_visible) {
if (!p_visible) {
active_menu = -1;
focused_menu = -1;
set_process_internal(false);
update();
return;
}
if (switch_on_hover) {
Window *window = Object::cast_to<Window>(get_viewport());
if (window) {
mouse_pos_adjusted = window->get_position();
if (window->is_embedded()) {
Window *window_parent = Object::cast_to<Window>(window->get_parent()->get_viewport());
while (window_parent) {
if (!window_parent->is_embedded()) {
mouse_pos_adjusted += window_parent->get_position();
break;
}
window_parent = Object::cast_to<Window>(window_parent->get_parent()->get_viewport());
}
}
set_process_internal(true);
}
}
}
void MenuBar::_update_submenu(const String &p_menu_name, PopupMenu *p_child) {
int count = p_child->get_item_count();
global_menus.insert(p_menu_name);
for (int i = 0; i < count; i++) {
if (p_child->is_item_separator(i)) {
DisplayServer::get_singleton()->global_menu_add_separator(p_menu_name);
} else if (!p_child->get_item_submenu(i).is_empty()) {
Node *n = p_child->get_node(p_child->get_item_submenu(i));
ERR_FAIL_COND_MSG(!n, "Item subnode does not exist: " + p_child->get_item_submenu(i) + ".");
PopupMenu *pm = Object::cast_to<PopupMenu>(n);
ERR_FAIL_COND_MSG(!pm, "Item subnode is not a PopupMenu: " + p_child->get_item_submenu(i) + ".");
DisplayServer::get_singleton()->global_menu_add_submenu_item(p_menu_name, p_child->get_item_text(i), p_menu_name + "/" + itos(i));
_update_submenu(p_menu_name + "/" + itos(i), pm);
} else {
int index = DisplayServer::get_singleton()->global_menu_add_item(p_menu_name, p_child->get_item_text(i), callable_mp(p_child, &PopupMenu::activate_item), i);
if (p_child->is_item_checkable(i)) {
DisplayServer::get_singleton()->global_menu_set_item_checkable(p_menu_name, index, true);
}
if (p_child->is_item_radio_checkable(i)) {
DisplayServer::get_singleton()->global_menu_set_item_radio_checkable(p_menu_name, index, true);
}
DisplayServer::get_singleton()->global_menu_set_item_checked(p_menu_name, index, p_child->is_item_checked(i));
DisplayServer::get_singleton()->global_menu_set_item_disabled(p_menu_name, index, p_child->is_item_disabled(i));
DisplayServer::get_singleton()->global_menu_set_item_max_states(p_menu_name, index, p_child->get_item_max_states(i));
DisplayServer::get_singleton()->global_menu_set_item_icon(p_menu_name, index, p_child->get_item_icon(i));
DisplayServer::get_singleton()->global_menu_set_item_state(p_menu_name, index, p_child->get_item_state(i));
DisplayServer::get_singleton()->global_menu_set_item_indentation_level(p_menu_name, index, p_child->get_item_indent(i));
DisplayServer::get_singleton()->global_menu_set_item_tooltip(p_menu_name, index, p_child->get_item_tooltip(i));
if (!p_child->is_item_shortcut_disabled(i) && p_child->get_item_shortcut(i).is_valid() && p_child->get_item_shortcut(i)->has_valid_event()) {
Array events = p_child->get_item_shortcut(i)->get_events();
for (int j = 0; j < events.size(); j++) {
Ref<InputEventKey> ie = events[j];
if (ie.is_valid()) {
DisplayServer::get_singleton()->global_menu_set_item_accelerator(p_menu_name, index, ie->get_keycode_with_modifiers());
break;
}
}
} else if (p_child->get_item_accelerator(i) != Key::NONE) {
DisplayServer::get_singleton()->global_menu_set_item_accelerator(p_menu_name, i, p_child->get_item_accelerator(i));
}
}
}
}
bool MenuBar::is_native_menu() const {
if (!is_visible_in_tree()) {
return false;
}
if (Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root()->is_ancestor_of(this) || get_tree()->get_edited_scene_root() == this)) {
return false;
}
return (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU) && is_native);
}
void MenuBar::_clear_menu() {
if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) {
return;
}
// Remove root menu items.
int count = DisplayServer::get_singleton()->global_menu_get_item_count("_main");
for (int i = count - 1; i >= 0; i--) {
if (global_menus.has(DisplayServer::get_singleton()->global_menu_get_item_submenu("_main", i))) {
DisplayServer::get_singleton()->global_menu_remove_item("_main", i);
}
}
// Erase submenu contents.
for (const String &E : global_menus) {
DisplayServer::get_singleton()->global_menu_clear(E);
}
global_menus.clear();
}
void MenuBar::_update_menu() {
_clear_menu();
if (!is_inside_tree()) {
return;
}
int index = start_index;
if (is_native_menu()) {
Vector<PopupMenu *> popups = _get_popups();
String root_name = "MenuBar<" + String::num_int64((uint64_t)this, 16) + ">";
for (int i = 0; i < popups.size(); i++) {
if (menu_cache[i].hidden) {
continue;
}
String menu_name = String(popups[i]->get_meta("_menu_name", popups[i]->get_name()));
index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", menu_name, root_name + "/" + itos(i), index);
if (menu_cache[i].disabled) {
DisplayServer::get_singleton()->global_menu_set_item_disabled("_main", index, true);
}
_update_submenu(root_name + "/" + itos(i), popups[i]);
index++;
}
}
update_minimum_size();
update();
}
void MenuBar::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
if (get_menu_count() > 0) {
_refresh_menu_names();
}
} break;
case NOTIFICATION_EXIT_TREE: {
_clear_menu();
} break;
case NOTIFICATION_MOUSE_EXIT: {
focused_menu = -1;
update();
} break;
case NOTIFICATION_TRANSLATION_CHANGED:
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_THEME_CHANGED: {
for (int i = 0; i < menu_cache.size(); i++) {
shape(menu_cache.write[i]);
}
_update_menu();
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
_update_menu();
} break;
case NOTIFICATION_DRAW: {
if (is_native_menu()) {
return;
}
for (int i = 0; i < menu_cache.size(); i++) {
_draw_menu_item(i);
}
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
MutexLock lock(mutex);
Vector2 pos = DisplayServer::get_singleton()->mouse_get_position() - mouse_pos_adjusted - get_global_position();
int index = _get_index_at_point(pos);
if (index >= 0 && index != active_menu) {
selected_menu = index;
focused_menu = selected_menu;
get_menu_popup(active_menu)->hide();
_open_popup(index);
}
} break;
}
}
int MenuBar::_get_index_at_point(const Point2 &p_point) const {
Ref<StyleBox> style = get_theme_stylebox(SNAME("normal"));
int hsep = get_theme_constant(SNAME("h_separation"));
int offset = 0;
for (int i = 0; i < menu_cache.size(); i++) {
if (menu_cache[i].hidden) {
continue;
}
Size2 size = menu_cache[i].text_buf->get_size() + style->get_minimum_size();
if (p_point.x > offset && p_point.x < offset + size.x) {
if (p_point.y > 0 && p_point.y < size.y) {
return i;
}
}
offset += size.x + hsep;
}
return -1;
}
Rect2 MenuBar::_get_menu_item_rect(int p_index) const {
ERR_FAIL_INDEX_V(p_index, menu_cache.size(), Rect2());
Ref<StyleBox> style = get_theme_stylebox(SNAME("normal"));
int hsep = get_theme_constant(SNAME("h_separation"));
int offset = 0;
for (int i = 0; i < p_index; i++) {
if (menu_cache[i].hidden) {
continue;
}
Size2 size = menu_cache[i].text_buf->get_size() + style->get_minimum_size();
offset += size.x + hsep;
}
return Rect2(Point2(offset, 0), menu_cache[p_index].text_buf->get_size() + style->get_minimum_size());
}
void MenuBar::_draw_menu_item(int p_index) {
ERR_FAIL_INDEX(p_index, menu_cache.size());
RID ci = get_canvas_item();
bool hovered = (focused_menu == p_index);
bool pressed = (active_menu == p_index);
bool rtl = is_layout_rtl();
if (menu_cache[p_index].hidden) {
return;
}
Color color;
Ref<StyleBox> style = get_theme_stylebox(SNAME("normal"));
Rect2 item_rect = _get_menu_item_rect(p_index);
if (menu_cache[p_index].disabled) {
if (rtl && has_theme_stylebox(SNAME("disabled_mirrored"))) {
style = get_theme_stylebox(SNAME("disabled_mirrored"));
} else {
style = get_theme_stylebox(SNAME("disabled"));
}
if (!flat) {
style->draw(ci, item_rect);
}
color = get_theme_color(SNAME("font_disabled_color"));
} else if (hovered && pressed && has_theme_stylebox("hover_pressed")) {
if (rtl && has_theme_stylebox(SNAME("hover_pressed_mirrored"))) {
style = get_theme_stylebox(SNAME("hover_pressed_mirrored"));
} else {
style = get_theme_stylebox(SNAME("hover_pressed"));
}
if (!flat) {
style->draw(ci, item_rect);
}
if (has_theme_color(SNAME("font_hover_pressed_color"))) {
color = get_theme_color(SNAME("font_hover_pressed_color"));
}
} else if (pressed) {
if (rtl && has_theme_stylebox(SNAME("pressed_mirrored"))) {
style = get_theme_stylebox(SNAME("pressed_mirrored"));
} else {
style = get_theme_stylebox(SNAME("pressed"));
}
if (!flat) {
style->draw(ci, item_rect);
}
if (has_theme_color(SNAME("font_pressed_color"))) {
color = get_theme_color(SNAME("font_pressed_color"));
} else {
color = get_theme_color(SNAME("font_color"));
}
} else if (hovered) {
if (rtl && has_theme_stylebox(SNAME("hover_mirrored"))) {
style = get_theme_stylebox(SNAME("hover_mirrored"));
} else {
style = get_theme_stylebox(SNAME("hover"));
}
if (!flat) {
style->draw(ci, item_rect);
}
color = get_theme_color(SNAME("font_hover_color"));
} else {
if (rtl && has_theme_stylebox(SNAME("normal_mirrored"))) {
style = get_theme_stylebox(SNAME("normal_mirrored"));
} else {
style = get_theme_stylebox(SNAME("normal"));
}
if (!flat) {
style->draw(ci, item_rect);
}
// Focus colors only take precedence over normal state.
if (has_focus()) {
color = get_theme_color(SNAME("font_focus_color"));
} else {
color = get_theme_color(SNAME("font_color"));
}
}
Point2 text_ofs = item_rect.position + Point2(style->get_margin(SIDE_LEFT), style->get_margin(SIDE_TOP));
Color font_outline_color = get_theme_color(SNAME("font_outline_color"));
int outline_size = get_theme_constant(SNAME("outline_size"));
if (outline_size > 0 && font_outline_color.a > 0) {
menu_cache[p_index].text_buf->draw_outline(ci, text_ofs, outline_size, font_outline_color);
}
menu_cache[p_index].text_buf->draw(ci, text_ofs, color);
}
void MenuBar::shape(Menu &p_menu) {
Ref<Font> font = get_theme_font(SNAME("font"));
int font_size = get_theme_font_size(SNAME("font_size"));
p_menu.text_buf->clear();
if (text_direction == Control::TEXT_DIRECTION_INHERITED) {
p_menu.text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
} else {
p_menu.text_buf->set_direction((TextServer::Direction)text_direction);
}
p_menu.text_buf->add_string(p_menu.name, font, font_size, language);
}
void MenuBar::_refresh_menu_names() {
Vector<PopupMenu *> popups = _get_popups();
for (int i = 0; i < popups.size(); i++) {
if (!popups[i]->has_meta("_menu_name") && String(popups[i]->get_name()) != get_menu_title(i)) {
menu_cache.write[i].name = popups[i]->get_name();
shape(menu_cache.write[i]);
}
}
_update_menu();
}
Vector<PopupMenu *> MenuBar::_get_popups() const {
Vector<PopupMenu *> popups;
for (int i = 0; i < get_child_count(); i++) {
PopupMenu *pm = Object::cast_to<PopupMenu>(get_child(i));
if (!pm) {
continue;
}
popups.push_back(pm);
}
return popups;
}
int MenuBar::get_menu_idx_from_control(PopupMenu *p_child) const {
ERR_FAIL_NULL_V(p_child, -1);
ERR_FAIL_COND_V(p_child->get_parent() != this, -1);
Vector<PopupMenu *> popups = _get_popups();
for (int i = 0; i < popups.size(); i++) {
if (popups[i] == p_child) {
return i;
}
}
return -1;
}
void MenuBar::add_child_notify(Node *p_child) {
Control::add_child_notify(p_child);
PopupMenu *pm = Object::cast_to<PopupMenu>(p_child);
if (!pm) {
return;
}
Menu menu = Menu(p_child->get_name());
shape(menu);
menu_cache.push_back(menu);
p_child->connect("renamed", callable_mp(this, &MenuBar::_refresh_menu_names));
p_child->connect("menu_changed", callable_mp(this, &MenuBar::_update_menu));
p_child->connect("about_to_popup", callable_mp(this, &MenuBar::_popup_visibility_changed).bind(true));
p_child->connect("popup_hide", callable_mp(this, &MenuBar::_popup_visibility_changed).bind(false));
_update_menu();
}
void MenuBar::move_child_notify(Node *p_child) {
Control::move_child_notify(p_child);
PopupMenu *pm = Object::cast_to<PopupMenu>(p_child);
if (!pm) {
return;
}
int old_idx = -1;
String menu_name = String(pm->get_meta("_menu_name", pm->get_name()));
// Find the previous menu index of the control.
for (int i = 0; i < get_menu_count(); i++) {
if (get_menu_title(i) == menu_name) {
old_idx = i;
break;
}
}
Menu menu = menu_cache[old_idx];
menu_cache.remove_at(old_idx);
menu_cache.insert(get_menu_idx_from_control(pm), menu);
_update_menu();
}
void MenuBar::remove_child_notify(Node *p_child) {
Control::remove_child_notify(p_child);
PopupMenu *pm = Object::cast_to<PopupMenu>(p_child);
if (!pm) {
return;
}
int idx = get_menu_idx_from_control(pm);
menu_cache.remove_at(idx);
p_child->remove_meta("_menu_name");
p_child->remove_meta("_menu_tooltip");
p_child->disconnect("renamed", callable_mp(this, &MenuBar::_refresh_menu_names));
p_child->disconnect("menu_changed", callable_mp(this, &MenuBar::_update_menu));
p_child->disconnect("about_to_popup", callable_mp(this, &MenuBar::_popup_visibility_changed));
p_child->disconnect("popup_hide", callable_mp(this, &MenuBar::_popup_visibility_changed));
_update_menu();
}
void MenuBar::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_switch_on_hover", "enable"), &MenuBar::set_switch_on_hover);
ClassDB::bind_method(D_METHOD("is_switch_on_hover"), &MenuBar::is_switch_on_hover);
ClassDB::bind_method(D_METHOD("set_disable_shortcuts", "disabled"), &MenuBar::set_disable_shortcuts);
ClassDB::bind_method(D_METHOD("set_prefer_global_menu", "enabled"), &MenuBar::set_prefer_global_menu);
ClassDB::bind_method(D_METHOD("is_prefer_global_menu"), &MenuBar::is_prefer_global_menu);
ClassDB::bind_method(D_METHOD("is_native_menu"), &MenuBar::is_native_menu);
ClassDB::bind_method(D_METHOD("get_menu_count"), &MenuBar::get_menu_count);
ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &MenuBar::set_text_direction);
ClassDB::bind_method(D_METHOD("get_text_direction"), &MenuBar::get_text_direction);
ClassDB::bind_method(D_METHOD("set_language", "language"), &MenuBar::set_language);
ClassDB::bind_method(D_METHOD("get_language"), &MenuBar::get_language);
ClassDB::bind_method(D_METHOD("set_flat", "enabled"), &MenuBar::set_flat);
ClassDB::bind_method(D_METHOD("is_flat"), &MenuBar::is_flat);
ClassDB::bind_method(D_METHOD("set_start_index", "enabled"), &MenuBar::set_start_index);
ClassDB::bind_method(D_METHOD("get_start_index"), &MenuBar::get_start_index);
ClassDB::bind_method(D_METHOD("set_menu_title", "menu", "title"), &MenuBar::set_menu_title);
ClassDB::bind_method(D_METHOD("get_menu_title", "menu"), &MenuBar::get_menu_title);
ClassDB::bind_method(D_METHOD("set_menu_tooltip", "menu", "tooltip"), &MenuBar::set_menu_tooltip);
ClassDB::bind_method(D_METHOD("get_menu_tooltip", "menu"), &MenuBar::get_menu_tooltip);
ClassDB::bind_method(D_METHOD("set_menu_disabled", "menu", "disabled"), &MenuBar::set_menu_disabled);
ClassDB::bind_method(D_METHOD("is_menu_disabled", "menu"), &MenuBar::is_menu_disabled);
ClassDB::bind_method(D_METHOD("set_menu_hidden", "menu", "hidden"), &MenuBar::set_menu_hidden);
ClassDB::bind_method(D_METHOD("is_menu_hidden", "menu"), &MenuBar::is_menu_hidden);
ClassDB::bind_method(D_METHOD("set_shortcut_context", "node"), &MenuBar::set_shortcut_context);
ClassDB::bind_method(D_METHOD("get_shortcut_context"), &MenuBar::get_shortcut_context);
ClassDB::bind_method(D_METHOD("get_menu_popup", "menu"), &MenuBar::get_menu_popup);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "start_index"), "set_start_index", "get_start_index");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "switch_on_hover"), "set_switch_on_hover", "is_switch_on_hover");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "prefer_global_menu"), "set_prefer_global_menu", "is_prefer_global_menu");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut_context", PROPERTY_HINT_RESOURCE_TYPE, "Node"), "set_shortcut_context", "get_shortcut_context");
ADD_GROUP("BiDi", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language");
}
void MenuBar::set_switch_on_hover(bool p_enabled) {
switch_on_hover = p_enabled;
}
bool MenuBar::is_switch_on_hover() {
return switch_on_hover;
}
void MenuBar::set_disable_shortcuts(bool p_disabled) {
disable_shortcuts = p_disabled;
}
void MenuBar::set_text_direction(Control::TextDirection p_text_direction) {
ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
if (text_direction != p_text_direction) {
text_direction = p_text_direction;
_update_menu();
}
}
Control::TextDirection MenuBar::get_text_direction() const {
return text_direction;
}
void MenuBar::set_language(const String &p_language) {
if (language != p_language) {
language = p_language;
_update_menu();
}
}
String MenuBar::get_language() const {
return language;
}
void MenuBar::set_flat(bool p_enabled) {
if (flat != p_enabled) {
flat = p_enabled;
update();
}
}
bool MenuBar::is_flat() const {
return flat;
}
void MenuBar::set_start_index(int p_index) {
if (start_index != p_index) {
start_index = p_index;
_update_menu();
}
}
int MenuBar::get_start_index() const {
return start_index;
}
void MenuBar::set_prefer_global_menu(bool p_enabled) {
if (is_native != p_enabled) {
if (is_native) {
_clear_menu();
}
is_native = p_enabled;
_update_menu();
}
}
bool MenuBar::is_prefer_global_menu() const {
return is_native;
}
Size2 MenuBar::get_minimum_size() const {
if (is_native_menu()) {
return Size2();
}
Ref<StyleBox> style = get_theme_stylebox(SNAME("normal"));
int hsep = get_theme_constant(SNAME("h_separation"));
Vector2 size;
for (int i = 0; i < menu_cache.size(); i++) {
if (menu_cache[i].hidden) {
continue;
}
Size2 sz = menu_cache[i].text_buf->get_size() + style->get_minimum_size();
size.y = MAX(size.y, sz.y);
size.x += sz.x + hsep;
}
return size;
}
int MenuBar::get_menu_count() const {
return menu_cache.size();
}
void MenuBar::set_menu_title(int p_menu, const String &p_title) {
ERR_FAIL_INDEX(p_menu, menu_cache.size());
PopupMenu *pm = get_menu_popup(p_menu);
if (p_title == pm->get_name()) {
pm->remove_meta("_menu_name");
} else {
pm->set_meta("_menu_name", p_title);
}
menu_cache.write[p_menu].name = p_title;
shape(menu_cache.write[p_menu]);
_update_menu();
}
String MenuBar::get_menu_title(int p_menu) const {
ERR_FAIL_INDEX_V(p_menu, menu_cache.size(), String());
return menu_cache[p_menu].name;
}
void MenuBar::set_menu_tooltip(int p_menu, const String &p_tooltip) {
ERR_FAIL_INDEX(p_menu, menu_cache.size());
PopupMenu *pm = get_menu_popup(p_menu);
pm->set_meta("_menu_tooltip", p_tooltip);
menu_cache.write[p_menu].name = p_tooltip;
}
String MenuBar::get_menu_tooltip(int p_menu) const {
ERR_FAIL_INDEX_V(p_menu, menu_cache.size(), String());
return menu_cache[p_menu].tooltip;
}
void MenuBar::set_menu_disabled(int p_menu, bool p_disabled) {
ERR_FAIL_INDEX(p_menu, menu_cache.size());
menu_cache.write[p_menu].disabled = p_disabled;
_update_menu();
}
bool MenuBar::is_menu_disabled(int p_menu) const {
ERR_FAIL_INDEX_V(p_menu, menu_cache.size(), false);
return menu_cache[p_menu].disabled;
}
void MenuBar::set_menu_hidden(int p_menu, bool p_hidden) {
ERR_FAIL_INDEX(p_menu, menu_cache.size());
menu_cache.write[p_menu].hidden = p_hidden;
_update_menu();
}
bool MenuBar::is_menu_hidden(int p_menu) const {
ERR_FAIL_INDEX_V(p_menu, menu_cache.size(), false);
return menu_cache[p_menu].hidden;
}
PopupMenu *MenuBar::get_menu_popup(int p_idx) const {
Vector<PopupMenu *> controls = _get_popups();
if (p_idx >= 0 && p_idx < controls.size()) {
return controls[p_idx];
} else {
return nullptr;
}
}
String MenuBar::get_tooltip(const Point2 &p_pos) const {
int index = _get_index_at_point(p_pos);
if (index >= 0 && index < menu_cache.size()) {
return menu_cache[index].tooltip;
} else {
return String();
}
}
void MenuBar::get_translatable_strings(List<String> *p_strings) const {
Vector<PopupMenu *> popups = _get_popups();
for (int i = 0; i < popups.size(); i++) {
PopupMenu *pm = popups[i];
if (!pm->has_meta("_menu_name") && !pm->has_meta("_menu_tooltip")) {
continue;
}
String name = pm->get_meta("_menu_name");
if (!name.is_empty()) {
p_strings->push_back(name);
}
String tooltip = pm->get_meta("_menu_tooltip");
if (!tooltip.is_empty()) {
p_strings->push_back(tooltip);
}
}
}
MenuBar::MenuBar() {
}
MenuBar::~MenuBar() {
}

156
scene/gui/menu_bar.h Normal file
View File

@ -0,0 +1,156 @@
/*************************************************************************/
/* menu_bar.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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 MENU_BAR_H
#define MENU_BAR_H
#include "scene/gui/button.h"
#include "scene/gui/popup_menu.h"
class MenuBar : public Control {
GDCLASS(MenuBar, Control);
Mutex mutex;
bool switch_on_hover = true;
bool disable_shortcuts = false;
bool is_native = true;
bool flat = false;
int start_index = -1;
String language;
TextDirection text_direction = TEXT_DIRECTION_AUTO;
struct Menu {
String name;
String tooltip;
Ref<TextLine> text_buf;
bool hidden = false;
bool disabled = false;
Menu(const String &p_name) {
name = p_name;
text_buf.instantiate();
}
Menu() {
text_buf.instantiate();
}
};
Vector<Menu> menu_cache;
HashSet<String> global_menus;
int focused_menu = -1;
int selected_menu = -1;
int active_menu = -1;
Vector2i mouse_pos_adjusted;
ObjectID shortcut_context;
int _get_index_at_point(const Point2 &p_point) const;
Rect2 _get_menu_item_rect(int p_index) const;
void _draw_menu_item(int p_index);
void shape(Menu &p_menu);
void _refresh_menu_names();
Vector<PopupMenu *> _get_popups() const;
int get_menu_idx_from_control(PopupMenu *p_child) const;
void _open_popup(int p_index);
void _popup_visibility_changed(bool p_visible);
void _update_submenu(const String &p_menu_name, PopupMenu *p_child);
void _clear_menu();
void _update_menu();
bool _is_focus_owner_in_shortcut_context() const;
protected:
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
void _notification(int p_what);
virtual void add_child_notify(Node *p_child) override;
virtual void move_child_notify(Node *p_child) override;
virtual void remove_child_notify(Node *p_child) override;
static void _bind_methods();
public:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
void set_switch_on_hover(bool p_enabled);
bool is_switch_on_hover();
void set_disable_shortcuts(bool p_disabled);
void set_prefer_global_menu(bool p_enabled);
bool is_prefer_global_menu() const;
bool is_native_menu() const;
virtual Size2 get_minimum_size() const override;
int get_menu_count() const;
void set_text_direction(TextDirection p_text_direction);
TextDirection get_text_direction() const;
void set_language(const String &p_language);
String get_language() const;
void set_start_index(int p_index);
int get_start_index() const;
void set_flat(bool p_enabled);
bool is_flat() const;
void set_menu_title(int p_menu, const String &p_title);
String get_menu_title(int p_menu) const;
void set_menu_tooltip(int p_menu, const String &p_tooltip);
String get_menu_tooltip(int p_menu) const;
void set_menu_disabled(int p_menu, bool p_disabled);
bool is_menu_disabled(int p_menu) const;
void set_menu_hidden(int p_menu, bool p_hidden);
bool is_menu_hidden(int p_menu) const;
void set_shortcut_context(Node *p_node);
Node *get_shortcut_context() const;
PopupMenu *get_menu_popup(int p_menu) const;
virtual void get_translatable_strings(List<String> *p_strings) const override;
virtual String get_tooltip(const Point2 &p_pos) const override;
MenuBar();
~MenuBar();
};
#endif // MENU_BAR_H

View File

@ -105,7 +105,11 @@ void MenuButton::pressed() {
popup->set_current_index(0);
}
popup->popup();
if (popup->is_visible()) {
popup->hide();
} else {
popup->popup();
}
}
void MenuButton::gui_input(const Ref<InputEvent> &p_event) {

View File

@ -36,6 +36,7 @@
#include "core/os/os.h"
#include "core/string/print_string.h"
#include "core/string/translation.h"
#include "scene/gui/menu_bar.h"
String PopupMenu::_get_accel_text(const Item &p_item) const {
if (p_item.shortcut.is_valid()) {
@ -66,7 +67,7 @@ Size2 PopupMenu::_get_contents_minimum_size() const {
size.height = _get_item_height(i);
icon_w = MAX(icon_size.width, icon_w);
size.width += items[i].h_ofs;
size.width += items[i].indent * get_theme_constant(SNAME("indent"));
if (items[i].checkable_type && !items[i].separator) {
has_check = true;
@ -343,14 +344,27 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) {
}
} else if (p_event->is_action("ui_left") && p_event->is_pressed()) {
Node *n = get_parent();
if (n && Object::cast_to<PopupMenu>(n)) {
hide();
set_input_as_handled();
if (n) {
if (Object::cast_to<PopupMenu>(n)) {
hide();
set_input_as_handled();
} else if (Object::cast_to<MenuBar>(n)) {
Object::cast_to<MenuBar>(n)->gui_input(p_event);
set_input_as_handled();
return;
}
}
} else if (p_event->is_action("ui_right") && p_event->is_pressed()) {
if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator && !items[mouse_over].submenu.is_empty() && submenu_over != mouse_over) {
_activate_submenu(mouse_over, true);
set_input_as_handled();
} else {
Node *n = get_parent();
if (n && Object::cast_to<MenuBar>(n)) {
Object::cast_to<MenuBar>(n)->gui_input(p_event);
set_input_as_handled();
return;
}
}
} else if (p_event->is_action("ui_accept") && p_event->is_pressed()) {
if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator) {
@ -589,7 +603,7 @@ void PopupMenu::_draw_items() {
String text = items[i].xl_text;
// Separator
item_ofs.x += items[i].h_ofs;
item_ofs.x += items[i].indent * get_theme_constant(SNAME("indent"));
if (items[i].separator) {
if (!text.is_empty() || !items[i].icon.is_null()) {
int content_size = items[i].text_buf->get_size().width + hseparation * 2;
@ -774,6 +788,32 @@ void PopupMenu::_shape_item(int p_item) {
}
}
void PopupMenu::_menu_changed() {
emit_signal(SNAME("menu_changed"));
}
void PopupMenu::add_child_notify(Node *p_child) {
Window::add_child_notify(p_child);
PopupMenu *pm = Object::cast_to<PopupMenu>(p_child);
if (!pm) {
return;
}
p_child->connect("menu_changed", callable_mp(this, &PopupMenu::_menu_changed));
_menu_changed();
}
void PopupMenu::remove_child_notify(Node *p_child) {
Window::remove_child_notify(p_child);
PopupMenu *pm = Object::cast_to<PopupMenu>(p_child);
if (!pm) {
return;
}
p_child->disconnect("menu_changed", callable_mp(this, &PopupMenu::_menu_changed));
_menu_changed();
}
void PopupMenu::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
@ -795,6 +835,7 @@ void PopupMenu::_notification(int p_what) {
}
child_controls_changed();
_menu_changed();
control->update();
} break;
@ -889,6 +930,7 @@ void PopupMenu::add_item(const String &p_label, int p_id, Key p_accel) {
control->update();
child_controls_changed();
notify_property_list_changed();
_menu_changed();
}
void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) {
@ -900,6 +942,7 @@ void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_labe
control->update();
child_controls_changed();
notify_property_list_changed();
_menu_changed();
}
void PopupMenu::add_check_item(const String &p_label, int p_id, Key p_accel) {
@ -910,6 +953,7 @@ void PopupMenu::add_check_item(const String &p_label, int p_id, Key p_accel) {
_shape_item(items.size() - 1);
control->update();
child_controls_changed();
_menu_changed();
}
void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) {
@ -931,6 +975,7 @@ void PopupMenu::add_radio_check_item(const String &p_label, int p_id, Key p_acce
_shape_item(items.size() - 1);
control->update();
child_controls_changed();
_menu_changed();
}
void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) {
@ -942,6 +987,7 @@ void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const St
_shape_item(items.size() - 1);
control->update();
child_controls_changed();
_menu_changed();
}
void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_id, Key p_accel) {
@ -953,6 +999,7 @@ void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int
_shape_item(items.size() - 1);
control->update();
child_controls_changed();
_menu_changed();
}
#define ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global) \
@ -971,6 +1018,7 @@ void PopupMenu::add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_g
_shape_item(items.size() - 1);
control->update();
child_controls_changed();
_menu_changed();
}
void PopupMenu::add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
@ -981,6 +1029,7 @@ void PopupMenu::add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortc
_shape_item(items.size() - 1);
control->update();
child_controls_changed();
_menu_changed();
}
void PopupMenu::add_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
@ -991,6 +1040,7 @@ void PopupMenu::add_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bo
_shape_item(items.size() - 1);
control->update();
child_controls_changed();
_menu_changed();
}
void PopupMenu::add_icon_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
@ -1002,6 +1052,7 @@ void PopupMenu::add_icon_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<
_shape_item(items.size() - 1);
control->update();
child_controls_changed();
_menu_changed();
}
void PopupMenu::add_radio_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
@ -1012,6 +1063,7 @@ void PopupMenu::add_radio_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_
_shape_item(items.size() - 1);
control->update();
child_controls_changed();
_menu_changed();
}
void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
@ -1023,6 +1075,7 @@ void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, cons
_shape_item(items.size() - 1);
control->update();
child_controls_changed();
_menu_changed();
}
void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, int p_id) {
@ -1035,6 +1088,7 @@ void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu,
_shape_item(items.size() - 1);
control->update();
child_controls_changed();
_menu_changed();
}
#undef ITEM_SETUP_WITH_ACCEL
@ -1057,6 +1111,7 @@ void PopupMenu::set_item_text(int p_idx, const String &p_text) {
control->update();
child_controls_changed();
_menu_changed();
}
void PopupMenu::set_item_text_direction(int p_item, Control::TextDirection p_text_direction) {
@ -1093,6 +1148,7 @@ void PopupMenu::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) {
control->update();
child_controls_changed();
_menu_changed();
}
void PopupMenu::set_item_checked(int p_idx, bool p_checked) {
@ -1105,6 +1161,7 @@ void PopupMenu::set_item_checked(int p_idx, bool p_checked) {
control->update();
child_controls_changed();
_menu_changed();
}
void PopupMenu::set_item_id(int p_idx, int p_id) {
@ -1116,6 +1173,7 @@ void PopupMenu::set_item_id(int p_idx, int p_id) {
control->update();
child_controls_changed();
_menu_changed();
}
void PopupMenu::set_item_accelerator(int p_idx, Key p_accel) {
@ -1128,6 +1186,7 @@ void PopupMenu::set_item_accelerator(int p_idx, Key p_accel) {
control->update();
child_controls_changed();
_menu_changed();
}
void PopupMenu::set_item_metadata(int p_idx, const Variant &p_meta) {
@ -1138,6 +1197,7 @@ void PopupMenu::set_item_metadata(int p_idx, const Variant &p_meta) {
items.write[p_idx].metadata = p_meta;
control->update();
child_controls_changed();
_menu_changed();
}
void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) {
@ -1148,6 +1208,7 @@ void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) {
items.write[p_idx].disabled = p_disabled;
control->update();
child_controls_changed();
_menu_changed();
}
void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) {
@ -1158,6 +1219,7 @@ void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) {
items.write[p_idx].submenu = p_submenu;
control->update();
child_controls_changed();
_menu_changed();
}
void PopupMenu::toggle_item_checked(int p_idx) {
@ -1165,6 +1227,7 @@ void PopupMenu::toggle_item_checked(int p_idx) {
items.write[p_idx].checked = !items[p_idx].checked;
control->update();
child_controls_changed();
_menu_changed();
}
String PopupMenu::get_item_text(int p_idx) const {
@ -1247,9 +1310,14 @@ Ref<Shortcut> PopupMenu::get_item_shortcut(int p_idx) const {
return items[p_idx].shortcut;
}
int PopupMenu::get_item_horizontal_offset(int p_idx) const {
int PopupMenu::get_item_indent(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, items.size(), 0);
return items[p_idx].h_ofs;
return items[p_idx].indent;
}
int PopupMenu::get_item_max_states(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, items.size(), -1);
return items[p_idx].max_states;
}
int PopupMenu::get_item_state(int p_idx) const {
@ -1278,6 +1346,7 @@ void PopupMenu::set_item_as_checkable(int p_idx, bool p_checkable) {
ERR_FAIL_INDEX(p_idx, items.size());
items.write[p_idx].checkable_type = p_checkable ? Item::CHECKABLE_TYPE_CHECK_BOX : Item::CHECKABLE_TYPE_NONE;
control->update();
_menu_changed();
}
void PopupMenu::set_item_as_radio_checkable(int p_idx, bool p_radio_checkable) {
@ -1287,6 +1356,7 @@ void PopupMenu::set_item_as_radio_checkable(int p_idx, bool p_radio_checkable) {
ERR_FAIL_INDEX(p_idx, items.size());
items.write[p_idx].checkable_type = p_radio_checkable ? Item::CHECKABLE_TYPE_RADIO_BUTTON : Item::CHECKABLE_TYPE_NONE;
control->update();
_menu_changed();
}
void PopupMenu::set_item_tooltip(int p_idx, const String &p_tooltip) {
@ -1296,6 +1366,7 @@ void PopupMenu::set_item_tooltip(int p_idx, const String &p_tooltip) {
ERR_FAIL_INDEX(p_idx, items.size());
items.write[p_idx].tooltip = p_tooltip;
control->update();
_menu_changed();
}
void PopupMenu::set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bool p_global) {
@ -1315,16 +1386,18 @@ void PopupMenu::set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bo
}
control->update();
_menu_changed();
}
void PopupMenu::set_item_horizontal_offset(int p_idx, int p_offset) {
void PopupMenu::set_item_indent(int p_idx, int p_indent) {
if (p_idx < 0) {
p_idx += get_item_count();
}
ERR_FAIL_INDEX(p_idx, items.size());
items.write[p_idx].h_ofs = p_offset;
items.write[p_idx].indent = p_indent;
control->update();
child_controls_changed();
_menu_changed();
}
void PopupMenu::set_item_multistate(int p_idx, int p_state) {
@ -1334,6 +1407,7 @@ void PopupMenu::set_item_multistate(int p_idx, int p_state) {
ERR_FAIL_INDEX(p_idx, items.size());
items.write[p_idx].state = p_state;
control->update();
_menu_changed();
}
void PopupMenu::set_item_shortcut_disabled(int p_idx, bool p_disabled) {
@ -1343,6 +1417,7 @@ void PopupMenu::set_item_shortcut_disabled(int p_idx, bool p_disabled) {
ERR_FAIL_INDEX(p_idx, items.size());
items.write[p_idx].shortcut_is_disabled = p_disabled;
control->update();
_menu_changed();
}
void PopupMenu::toggle_item_multistate(int p_idx) {
@ -1357,6 +1432,7 @@ void PopupMenu::toggle_item_multistate(int p_idx) {
}
control->update();
_menu_changed();
}
bool PopupMenu::is_item_checkable(int p_idx) const {
@ -1369,6 +1445,11 @@ bool PopupMenu::is_item_radio_checkable(int p_idx) const {
return items[p_idx].checkable_type == Item::CHECKABLE_TYPE_RADIO_BUTTON;
}
bool PopupMenu::is_item_shortcut_global(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, items.size(), false);
return items[p_idx].shortcut_is_global;
}
bool PopupMenu::is_item_shortcut_disabled(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, items.size(), false);
return items[p_idx].shortcut_is_disabled;
@ -1399,6 +1480,7 @@ void PopupMenu::set_item_count(int p_count) {
control->update();
child_controls_changed();
notify_property_list_changed();
_menu_changed();
}
int PopupMenu::get_item_count() const {
@ -1540,6 +1622,7 @@ void PopupMenu::remove_item(int p_idx) {
items.remove_at(p_idx);
control->update();
child_controls_changed();
_menu_changed();
}
void PopupMenu::add_separator(const String &p_text, int p_id) {
@ -1552,6 +1635,7 @@ void PopupMenu::add_separator(const String &p_text, int p_id) {
}
items.push_back(sep);
control->update();
_menu_changed();
}
void PopupMenu::clear() {
@ -1565,6 +1649,7 @@ void PopupMenu::clear() {
control->update();
child_controls_changed();
notify_property_list_changed();
_menu_changed();
}
void PopupMenu::_ref_shortcut(Ref<Shortcut> p_sc) {
@ -1839,7 +1924,7 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_item_as_radio_checkable", "index", "enable"), &PopupMenu::set_item_as_radio_checkable);
ClassDB::bind_method(D_METHOD("set_item_tooltip", "index", "tooltip"), &PopupMenu::set_item_tooltip);
ClassDB::bind_method(D_METHOD("set_item_shortcut", "index", "shortcut", "global"), &PopupMenu::set_item_shortcut, DEFVAL(false));
ClassDB::bind_method(D_METHOD("set_item_horizontal_offset", "index", "offset"), &PopupMenu::set_item_horizontal_offset);
ClassDB::bind_method(D_METHOD("set_item_indent", "index", "indent"), &PopupMenu::set_item_indent);
ClassDB::bind_method(D_METHOD("set_item_multistate", "index", "state"), &PopupMenu::set_item_multistate);
ClassDB::bind_method(D_METHOD("set_item_shortcut_disabled", "index", "disabled"), &PopupMenu::set_item_shortcut_disabled);
@ -1863,7 +1948,7 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_item_shortcut_disabled", "index"), &PopupMenu::is_item_shortcut_disabled);
ClassDB::bind_method(D_METHOD("get_item_tooltip", "index"), &PopupMenu::get_item_tooltip);
ClassDB::bind_method(D_METHOD("get_item_shortcut", "index"), &PopupMenu::get_item_shortcut);
ClassDB::bind_method(D_METHOD("get_item_horizontal_offset", "index"), &PopupMenu::get_item_horizontal_offset);
ClassDB::bind_method(D_METHOD("get_item_indent", "index"), &PopupMenu::get_item_indent);
ClassDB::bind_method(D_METHOD("set_current_index", "index"), &PopupMenu::set_current_index);
ClassDB::bind_method(D_METHOD("get_current_index"), &PopupMenu::get_current_index);
@ -1903,6 +1988,7 @@ void PopupMenu::_bind_methods() {
ADD_SIGNAL(MethodInfo("id_pressed", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("id_focused", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("index_pressed", PropertyInfo(Variant::INT, "index")));
ADD_SIGNAL(MethodInfo("menu_changed"));
}
void PopupMenu::popup(const Rect2 &p_bounds) {

View File

@ -68,7 +68,7 @@ class PopupMenu : public Popup {
Key accel = Key::NONE;
int _ofs_cache = 0;
int _height_cache = 0;
int h_ofs = 0;
int indent = 0;
Ref<Shortcut> shortcut;
bool shortcut_is_global = false;
bool shortcut_is_disabled = false;
@ -134,8 +134,11 @@ class PopupMenu : public Popup {
void _minimum_lifetime_timeout();
void _close_pressed();
void _menu_changed();
protected:
virtual void add_child_notify(Node *p_child) override;
virtual void remove_child_notify(Node *p_child) override;
void _notification(int p_what);
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
@ -183,7 +186,7 @@ public:
void set_item_as_radio_checkable(int p_idx, bool p_radio_checkable);
void set_item_tooltip(int p_idx, const String &p_tooltip);
void set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bool p_global = false);
void set_item_horizontal_offset(int p_idx, int p_offset);
void set_item_indent(int p_idx, int p_indent);
void set_item_multistate(int p_idx, int p_state);
void toggle_item_multistate(int p_idx);
void set_item_shortcut_disabled(int p_idx, bool p_disabled);
@ -206,9 +209,11 @@ public:
bool is_item_checkable(int p_idx) const;
bool is_item_radio_checkable(int p_idx) const;
bool is_item_shortcut_disabled(int p_idx) const;
bool is_item_shortcut_global(int p_idx) const;
String get_item_tooltip(int p_idx) const;
Ref<Shortcut> get_item_shortcut(int p_idx) const;
int get_item_horizontal_offset(int p_idx) const;
int get_item_indent(int p_idx) const;
int get_item_max_states(int p_idx) const;
int get_item_state(int p_idx) const;
void set_current_index(int p_idx);

View File

@ -100,6 +100,7 @@
#include "scene/gui/line_edit.h"
#include "scene/gui/link_button.h"
#include "scene/gui/margin_container.h"
#include "scene/gui/menu_bar.h"
#include "scene/gui/menu_button.h"
#include "scene/gui/nine_patch_rect.h"
#include "scene/gui/option_button.h"
@ -351,6 +352,7 @@ void register_scene_types() {
GDREGISTER_CLASS(VSlider);
GDREGISTER_CLASS(Popup);
GDREGISTER_CLASS(PopupPanel);
GDREGISTER_CLASS(MenuBar);
GDREGISTER_CLASS(MenuButton);
GDREGISTER_CLASS(CheckBox);
GDREGISTER_CLASS(CheckButton);

View File

@ -177,6 +177,27 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_constant("h_separation", "Button", 2 * scale);
// MenuBar
theme->set_stylebox("normal", "MenuBar", button_normal);
theme->set_stylebox("hover", "MenuBar", button_hover);
theme->set_stylebox("pressed", "MenuBar", button_pressed);
theme->set_stylebox("disabled", "MenuBar", button_disabled);
theme->set_stylebox("focus", "MenuBar", focus);
theme->set_font("font", "MenuBar", Ref<Font>());
theme->set_font_size("font_size", "MenuBar", -1);
theme->set_constant("outline_size", "MenuBar", 0 * scale);
theme->set_color("font_color", "MenuBar", control_font_color);
theme->set_color("font_pressed_color", "MenuBar", control_font_pressed_color);
theme->set_color("font_hover_color", "MenuBar", control_font_hover_color);
theme->set_color("font_focus_color", "MenuBar", control_font_focus_color);
theme->set_color("font_hover_pressed_color", "MenuBar", control_font_pressed_color);
theme->set_color("font_disabled_color", "MenuBar", control_font_disabled_color);
theme->set_color("font_outline_color", "MenuBar", Color(1, 1, 1));
theme->set_constant("h_separation", "MenuBar", 4 * scale);
// LinkButton
theme->set_stylebox("focus", "LinkButton", focus);
@ -669,6 +690,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_color("font_outline_color", "PopupMenu", Color(1, 1, 1));
theme->set_color("font_separator_outline_color", "PopupMenu", Color(1, 1, 1));
theme->set_constant("indent", "PopupMenu", 10 * scale);
theme->set_constant("h_separation", "PopupMenu", 4 * scale);
theme->set_constant("v_separation", "PopupMenu", 4 * scale);
theme->set_constant("outline_size", "PopupMenu", 0);

View File

@ -44,40 +44,49 @@ DisplayServer::DisplayServerCreate DisplayServer::server_create_functions[Displa
int DisplayServer::server_create_count = 1;
void DisplayServer::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
int DisplayServer::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
WARN_PRINT("Global menus not supported by this display server.");
return -1;
}
void DisplayServer::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
int DisplayServer::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
WARN_PRINT("Global menus not supported by this display server.");
return -1;
}
void DisplayServer::global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
int DisplayServer::global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
WARN_PRINT("Global menus not supported by this display server.");
return -1;
}
void DisplayServer::global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
int DisplayServer::global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
WARN_PRINT("Global menus not supported by this display server.");
return -1;
}
void DisplayServer::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
int DisplayServer::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
WARN_PRINT("Global menus not supported by this display server.");
return -1;
}
void DisplayServer::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
int DisplayServer::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
WARN_PRINT("Global menus not supported by this display server.");
return -1;
}
void DisplayServer::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
int DisplayServer::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
WARN_PRINT("Global menus not supported by this display server.");
return -1;
}
void DisplayServer::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) {
int DisplayServer::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) {
WARN_PRINT("Global menus not supported by this display server.");
return -1;
}
void DisplayServer::global_menu_add_separator(const String &p_menu_root, int p_index) {
int DisplayServer::global_menu_add_separator(const String &p_menu_root, int p_index) {
WARN_PRINT("Global menus not supported by this display server.");
return -1;
}
int DisplayServer::global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const {
@ -159,6 +168,11 @@ Ref<Texture2D> DisplayServer::global_menu_get_item_icon(const String &p_menu_roo
return Ref<Texture2D>();
}
int DisplayServer::global_menu_get_item_indentation_level(const String &p_menu_root, int p_idx) const {
WARN_PRINT("Global menus not supported by this display server.");
return 0;
}
void DisplayServer::global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) {
WARN_PRINT("Global menus not supported by this display server.");
}
@ -207,6 +221,10 @@ void DisplayServer::global_menu_set_item_icon(const String &p_menu_root, int p_i
WARN_PRINT("Global menus not supported by this display server.");
}
void DisplayServer::global_menu_set_item_indentation_level(const String &p_menu_root, int p_idx, int p_level) {
WARN_PRINT("Global menus not supported by this display server.");
}
int DisplayServer::global_menu_get_item_count(const String &p_menu_root) const {
WARN_PRINT("Global menus not supported by this display server.");
return 0;
@ -535,6 +553,7 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("global_menu_get_item_state", "menu_root", "idx"), &DisplayServer::global_menu_get_item_state);
ClassDB::bind_method(D_METHOD("global_menu_get_item_max_states", "menu_root", "idx"), &DisplayServer::global_menu_get_item_max_states);
ClassDB::bind_method(D_METHOD("global_menu_get_item_icon", "menu_root", "idx"), &DisplayServer::global_menu_get_item_icon);
ClassDB::bind_method(D_METHOD("global_menu_get_item_indentation_level", "menu_root", "idx"), &DisplayServer::global_menu_get_item_indentation_level);
ClassDB::bind_method(D_METHOD("global_menu_set_item_checked", "menu_root", "idx", "checked"), &DisplayServer::global_menu_set_item_checked);
ClassDB::bind_method(D_METHOD("global_menu_set_item_checkable", "menu_root", "idx", "checkable"), &DisplayServer::global_menu_set_item_checkable);
@ -549,6 +568,7 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("global_menu_set_item_state", "menu_root", "idx", "state"), &DisplayServer::global_menu_set_item_state);
ClassDB::bind_method(D_METHOD("global_menu_set_item_max_states", "menu_root", "idx", "max_states"), &DisplayServer::global_menu_set_item_max_states);
ClassDB::bind_method(D_METHOD("global_menu_set_item_icon", "menu_root", "idx", "icon"), &DisplayServer::global_menu_set_item_icon);
ClassDB::bind_method(D_METHOD("global_menu_set_item_indentation_level", "menu_root", "idx", "level"), &DisplayServer::global_menu_set_item_indentation_level);
ClassDB::bind_method(D_METHOD("global_menu_remove_item", "menu_root", "idx"), &DisplayServer::global_menu_remove_item);
ClassDB::bind_method(D_METHOD("global_menu_clear", "menu_root"), &DisplayServer::global_menu_clear);

View File

@ -127,15 +127,15 @@ public:
virtual bool has_feature(Feature p_feature) const = 0;
virtual String get_name() const = 0;
virtual void global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
virtual void global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
virtual void global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
virtual void global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
virtual void global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
virtual void global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
virtual void global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
virtual void global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1);
virtual void global_menu_add_separator(const String &p_menu_root, int p_index = -1);
virtual int global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
virtual int global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
virtual int global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
virtual int global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
virtual int global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
virtual int global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
virtual int global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1);
virtual int global_menu_add_separator(const String &p_menu_root, int p_index = -1);
virtual int global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const;
virtual int global_menu_get_item_index_from_tag(const String &p_menu_root, const Variant &p_tag) const;
@ -153,6 +153,7 @@ public:
virtual int global_menu_get_item_state(const String &p_menu_root, int p_idx) const;
virtual int global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const;
virtual Ref<Texture2D> global_menu_get_item_icon(const String &p_menu_root, int p_idx) const;
virtual int global_menu_get_item_indentation_level(const String &p_menu_root, int p_idx) const;
virtual void global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked);
virtual void global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable);
@ -167,6 +168,7 @@ public:
virtual void global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state);
virtual void global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states);
virtual void global_menu_set_item_icon(const String &p_menu_root, int p_idx, const Ref<Texture2D> &p_icon);
virtual void global_menu_set_item_indentation_level(const String &p_menu_root, int p_idx, int p_level);
virtual int global_menu_get_item_count(const String &p_menu_root) const;