summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/config/project_settings.cpp11
-rw-r--r--core/config/project_settings.h5
-rw-r--r--core/extension/gdnative_interface.h10
-rw-r--r--core/extension/native_extension.cpp2
-rw-r--r--core/object/object.cpp63
-rw-r--r--core/object/object.h36
-rw-r--r--core/object/script_language.h6
-rw-r--r--core/object/script_language_extension.h13
-rw-r--r--doc/classes/Array.xml4
-rw-r--r--doc/classes/DisplayServer.xml67
-rw-r--r--doc/classes/EditorNode3DGizmo.xml2
-rw-r--r--doc/classes/EditorSettings.xml20
-rw-r--r--doc/classes/MenuBar.xml172
-rw-r--r--doc/classes/Node3D.xml14
-rw-r--r--doc/classes/Object.xml28
-rw-r--r--doc/classes/PopupMenu.xml37
-rw-r--r--doc/classes/ProjectSettings.xml14
-rw-r--r--doc/classes/ProxyTexture.xml13
-rw-r--r--doc/classes/ShaderMaterial.xml14
-rw-r--r--doc/classes/VelocityTracker3D.xml32
-rw-r--r--drivers/gles3/rasterizer_canvas_gles3.cpp4
-rw-r--r--drivers/gles3/storage/material_storage.cpp62
-rw-r--r--drivers/gles3/storage/material_storage.h2
-rw-r--r--editor/editor_inspector.cpp4
-rw-r--r--editor/editor_node.cpp267
-rw-r--r--editor/editor_node.h12
-rw-r--r--editor/editor_path.cpp2
-rw-r--r--editor/editor_sectioned_inspector.cpp4
-rw-r--r--editor/editor_settings.cpp20
-rw-r--r--editor/editor_settings.h4
-rw-r--r--editor/editor_themes.cpp33
-rw-r--r--editor/icons/ContainerLayout.svg1
-rw-r--r--editor/icons/ControlLayout.svg2
-rw-r--r--editor/icons/MenuBar.svg1
-rw-r--r--editor/plugins/control_editor_plugin.cpp848
-rw-r--r--editor/plugins/control_editor_plugin.h168
-rw-r--r--editor/plugins/debugger_editor_plugin.cpp73
-rw-r--r--editor/plugins/debugger_editor_plugin.h4
-rw-r--r--editor/plugins/font_config_plugin.cpp14
-rw-r--r--editor/plugins/font_config_plugin.h6
-rw-r--r--editor/project_converter_3_to_4.cpp1
-rw-r--r--editor/scene_tree_dock.cpp2
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp69
-rw-r--r--modules/gdscript/gdscript.cpp43
-rw-r--r--modules/gdscript/gdscript.h5
-rw-r--r--modules/mono/csharp_script.cpp70
-rw-r--r--modules/mono/csharp_script.h5
-rw-r--r--modules/visual_script/visual_script.h3
-rw-r--r--platform/macos/display_server_macos.h23
-rw-r--r--platform/macos/display_server_macos.mm410
-rw-r--r--platform/macos/godot_menu_item.h1
-rw-r--r--scene/3d/node_3d.cpp32
-rw-r--r--scene/3d/node_3d.h4
-rw-r--r--scene/3d/velocity_tracker_3d.h2
-rw-r--r--scene/gui/menu_bar.cpp866
-rw-r--r--scene/gui/menu_bar.h156
-rw-r--r--scene/gui/menu_button.cpp6
-rw-r--r--scene/gui/popup_menu.cpp108
-rw-r--r--scene/gui/popup_menu.h11
-rw-r--r--scene/register_scene_types.cpp6
-rw-r--r--scene/resources/default_theme/default_theme.cpp22
-rw-r--r--scene/resources/material.cpp12
-rw-r--r--scene/resources/material.h4
-rw-r--r--scene/resources/texture.cpp7
-rw-r--r--scene/resources/texture.h2
-rw-r--r--servers/display_server.cpp38
-rw-r--r--servers/display_server.h20
-rw-r--r--servers/rendering/renderer_rd/environment/sky.cpp14
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp14
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h1
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp12
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp29
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.h1
-rw-r--r--servers/rendering/renderer_rd/shaders/effects/bokeh_dof.glsl2
-rw-r--r--servers/rendering/renderer_rd/shaders/effects/bokeh_dof_raster.glsl2
-rw-r--r--servers/rendering/renderer_rd/storage_rd/material_storage.cpp14
-rw-r--r--servers/rendering/shader_compiler.cpp43
-rw-r--r--servers/rendering/shader_compiler.h1
-rw-r--r--servers/rendering/shader_language.cpp37
-rw-r--r--servers/rendering/shader_language.h6
-rw-r--r--servers/rendering/shader_preprocessor.cpp113
-rw-r--r--servers/rendering/shader_preprocessor.h21
-rw-r--r--tests/core/object/test_object.h6
83 files changed, 3168 insertions, 1170 deletions
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index ec74d7ea28..26b683db82 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -1070,7 +1070,7 @@ bool ProjectSettings::is_using_datapack() const {
return using_datapack;
}
-bool ProjectSettings::property_can_revert(const String &p_name) {
+bool ProjectSettings::_property_can_revert(const StringName &p_name) const {
if (!props.has(p_name)) {
return false;
}
@@ -1078,12 +1078,13 @@ bool ProjectSettings::property_can_revert(const String &p_name) {
return props[p_name].initial != props[p_name].variant;
}
-Variant ProjectSettings::property_get_revert(const String &p_name) {
+bool ProjectSettings::_property_get_revert(const StringName &p_name, Variant &r_property) const {
if (!props.has(p_name)) {
- return Variant();
+ return false;
}
- return props[p_name].initial;
+ r_property = props[p_name].initial;
+ return true;
}
void ProjectSettings::set_setting(const String &p_setting, const Variant &p_value) {
@@ -1134,8 +1135,6 @@ void ProjectSettings::_bind_methods() {
ClassDB::bind_method(D_METHOD("globalize_path", "path"), &ProjectSettings::globalize_path);
ClassDB::bind_method(D_METHOD("save"), &ProjectSettings::save);
ClassDB::bind_method(D_METHOD("load_resource_pack", "pack", "replace_files", "offset"), &ProjectSettings::_load_resource_pack, DEFVAL(true), DEFVAL(0));
- ClassDB::bind_method(D_METHOD("property_can_revert", "name"), &ProjectSettings::property_can_revert);
- ClassDB::bind_method(D_METHOD("property_get_revert", "name"), &ProjectSettings::property_get_revert);
ClassDB::bind_method(D_METHOD("save_custom", "file"), &ProjectSettings::_save_custom_bnd);
}
diff --git a/core/config/project_settings.h b/core/config/project_settings.h
index c3992a4db2..c845120a26 100644
--- a/core/config/project_settings.h
+++ b/core/config/project_settings.h
@@ -102,6 +102,8 @@ protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
+ bool _property_can_revert(const StringName &p_name) const;
+ bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
static ProjectSettings *singleton;
@@ -147,9 +149,6 @@ public:
void set_ignore_value_in_docs(const String &p_name, bool p_ignore);
bool get_ignore_value_in_docs(const String &p_name) const;
- bool property_can_revert(const String &p_name);
- Variant property_get_revert(const String &p_name);
-
String get_project_data_dir_name() const;
String get_project_data_path() const;
String get_resource_path() const;
diff --git a/core/extension/gdnative_interface.h b/core/extension/gdnative_interface.h
index 041a6e5112..cb2adcb562 100644
--- a/core/extension/gdnative_interface.h
+++ b/core/extension/gdnative_interface.h
@@ -222,6 +222,8 @@ typedef struct {
typedef const GDNativePropertyInfo *(*GDNativeExtensionClassGetPropertyList)(GDExtensionClassInstancePtr p_instance, uint32_t *r_count);
typedef void (*GDNativeExtensionClassFreePropertyList)(GDExtensionClassInstancePtr p_instance, const GDNativePropertyInfo *p_list);
+typedef GDNativeBool (*GDNativeExtensionClassPropertyCanRevert)(GDExtensionClassInstancePtr p_instance, const GDNativeStringNamePtr p_name);
+typedef GDNativeBool (*GDNativeExtensionClassPropertyGetRevert)(GDExtensionClassInstancePtr p_instance, const GDNativeStringNamePtr p_name, GDNativeVariantPtr r_ret);
typedef void (*GDNativeExtensionClassNotification)(GDExtensionClassInstancePtr p_instance, int32_t p_what);
typedef void (*GDNativeExtensionClassToString)(GDExtensionClassInstancePtr p_instance, GDNativeStringPtr p_out);
typedef void (*GDNativeExtensionClassReference)(GDExtensionClassInstancePtr p_instance);
@@ -237,6 +239,8 @@ typedef struct {
GDNativeExtensionClassGet get_func;
GDNativeExtensionClassGetPropertyList get_property_list_func;
GDNativeExtensionClassFreePropertyList free_property_list_func;
+ GDNativeExtensionClassPropertyCanRevert property_can_revert_func;
+ GDNativeExtensionClassPropertyGetRevert property_get_revert_func;
GDNativeExtensionClassNotification notification_func;
GDNativeExtensionClassToString to_string_func;
GDNativeExtensionClassReference reference_func;
@@ -309,6 +313,9 @@ typedef const GDNativePropertyInfo *(*GDNativeExtensionScriptInstanceGetProperty
typedef void (*GDNativeExtensionScriptInstanceFreePropertyList)(GDNativeExtensionScriptInstanceDataPtr p_instance, const GDNativePropertyInfo *p_list);
typedef GDNativeVariantType (*GDNativeExtensionScriptInstanceGetPropertyType)(GDNativeExtensionScriptInstanceDataPtr p_instance, const GDNativeStringNamePtr p_name, GDNativeBool *r_is_valid);
+typedef GDNativeBool (*GDNativeExtensionScriptInstancePropertyCanRevert)(GDNativeExtensionScriptInstanceDataPtr p_instance, const GDNativeStringNamePtr p_name);
+typedef GDNativeBool (*GDNativeExtensionScriptInstancePropertyGetRevert)(GDNativeExtensionScriptInstanceDataPtr p_instance, const GDNativeStringNamePtr p_name, GDNativeVariantPtr r_ret);
+
typedef GDNativeObjectPtr (*GDNativeExtensionScriptInstanceGetOwner)(GDNativeExtensionScriptInstanceDataPtr p_instance);
typedef void (*GDNativeExtensionScriptInstancePropertyStateAdd)(const GDNativeStringNamePtr p_name, const GDNativeVariantPtr p_value, void *p_userdata);
typedef void (*GDNativeExtensionScriptInstanceGetPropertyState)(GDNativeExtensionScriptInstanceDataPtr p_instance, GDNativeExtensionScriptInstancePropertyStateAdd p_add_func, void *p_userdata);
@@ -343,6 +350,9 @@ typedef struct {
GDNativeExtensionScriptInstanceFreePropertyList free_property_list_func;
GDNativeExtensionScriptInstanceGetPropertyType get_property_type_func;
+ GDNativeExtensionScriptInstancePropertyCanRevert property_can_revert_func;
+ GDNativeExtensionScriptInstancePropertyGetRevert property_get_revert_func;
+
GDNativeExtensionScriptInstanceGetOwner get_owner_func;
GDNativeExtensionScriptInstanceGetPropertyState get_property_state_func;
diff --git a/core/extension/native_extension.cpp b/core/extension/native_extension.cpp
index a085df874e..fdb4e50d90 100644
--- a/core/extension/native_extension.cpp
+++ b/core/extension/native_extension.cpp
@@ -156,6 +156,8 @@ void NativeExtension::_register_extension_class(const GDNativeExtensionClassLibr
extension->native_extension.get = p_extension_funcs->get_func;
extension->native_extension.get_property_list = p_extension_funcs->get_property_list_func;
extension->native_extension.free_property_list = p_extension_funcs->free_property_list_func;
+ extension->native_extension.property_can_revert = p_extension_funcs->property_can_revert_func;
+ extension->native_extension.property_get_revert = p_extension_funcs->property_get_revert_func;
extension->native_extension.notification = p_extension_funcs->notification_func;
extension->native_extension.to_string = p_extension_funcs->to_string_func;
extension->native_extension.reference = p_extension_funcs->reference_func;
diff --git a/core/object/object.cpp b/core/object/object.cpp
index 0fcd1c0e40..a95ba7992b 100644
--- a/core/object/object.cpp
+++ b/core/object/object.cpp
@@ -518,6 +518,59 @@ void Object::get_property_list(List<PropertyInfo> *p_list, bool p_reversed) cons
void Object::_validate_property(PropertyInfo &property) const {
}
+bool Object::property_can_revert(const String &p_name) const {
+ if (script_instance) {
+ if (script_instance->property_can_revert(p_name)) {
+ return true;
+ }
+ }
+
+// C style pointer casts should never trigger a compiler warning because the risk is assumed by the user, so GCC should keep quiet about it.
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wignored-qualifiers"
+#endif
+ if (_extension && _extension->property_can_revert) {
+ if (_extension->property_can_revert(_extension_instance, (const GDNativeStringNamePtr)&p_name)) {
+ return true;
+ }
+ }
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
+ return _property_can_revertv(p_name);
+}
+
+Variant Object::property_get_revert(const String &p_name) const {
+ Variant ret;
+
+ if (script_instance) {
+ if (script_instance->property_get_revert(p_name, ret)) {
+ return ret;
+ }
+ }
+
+// C style pointer casts should never trigger a compiler warning because the risk is assumed by the user, so GCC should keep quiet about it.
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wignored-qualifiers"
+#endif
+ if (_extension && _extension->property_get_revert) {
+ if (_extension->property_get_revert(_extension_instance, (const GDNativeStringNamePtr)&p_name, (GDNativeVariantPtr)&ret)) {
+ return ret;
+ }
+ }
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
+ if (_property_get_revertv(p_name, ret)) {
+ return ret;
+ }
+ return Variant();
+}
+
void Object::get_method_list(List<MethodInfo> *p_list) const {
ClassDB::get_method_list(get_class_name(), p_list);
if (script_instance) {
@@ -1499,10 +1552,12 @@ void Object::_bind_methods() {
miget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
BIND_OBJ_CORE_METHOD(miget);
- MethodInfo plget("_get_property_list");
-
- plget.return_val.type = Variant::ARRAY;
- BIND_OBJ_CORE_METHOD(plget);
+ BIND_OBJ_CORE_METHOD(MethodInfo(Variant::ARRAY, "_get_property_list"));
+ BIND_OBJ_CORE_METHOD(MethodInfo(Variant::BOOL, "_property_can_revert", PropertyInfo(Variant::STRING_NAME, "property")));
+ MethodInfo mipgr("_property_get_revert", PropertyInfo(Variant::STRING_NAME, "property"));
+ mipgr.return_val.name = "Variant";
+ mipgr.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ BIND_OBJ_CORE_METHOD(mipgr);
#endif
BIND_OBJ_CORE_METHOD(MethodInfo("_init"));
diff --git a/core/object/object.h b/core/object/object.h
index 35d0aaaa7d..154ef176d3 100644
--- a/core/object/object.h
+++ b/core/object/object.h
@@ -294,6 +294,8 @@ struct ObjectNativeExtension {
GDNativeExtensionClassGet get;
GDNativeExtensionClassGetPropertyList get_property_list;
GDNativeExtensionClassFreePropertyList free_property_list;
+ GDNativeExtensionClassPropertyCanRevert property_can_revert;
+ GDNativeExtensionClassPropertyGetRevert property_get_revert;
GDNativeExtensionClassNotification notification;
GDNativeExtensionClassToString to_string;
GDNativeExtensionClassReference reference;
@@ -469,6 +471,28 @@ protected:
m_inherits::_get_property_listv(p_list, p_reversed); \
} \
} \
+ _FORCE_INLINE_ bool (Object::*_get_property_can_revert() const)(const StringName &p_name) const { \
+ return (bool(Object::*)(const StringName &) const) & m_class::_property_can_revert; \
+ } \
+ virtual bool _property_can_revertv(const StringName &p_name) const override { \
+ if (m_class::_get_property_can_revert() != m_inherits::_get_property_can_revert()) { \
+ if (_property_can_revert(p_name)) { \
+ return true; \
+ } \
+ } \
+ return m_inherits::_property_can_revertv(p_name); \
+ } \
+ _FORCE_INLINE_ bool (Object::*_get_property_get_revert() const)(const StringName &p_name, Variant &) const { \
+ return (bool(Object::*)(const StringName &, Variant &) const) & m_class::_property_get_revert; \
+ } \
+ virtual bool _property_get_revertv(const StringName &p_name, Variant &r_ret) const override { \
+ if (m_class::_get_property_get_revert() != m_inherits::_get_property_get_revert()) { \
+ if (_property_get_revert(p_name, r_ret)) { \
+ return true; \
+ } \
+ } \
+ return m_inherits::_property_get_revertv(p_name, r_ret); \
+ } \
_FORCE_INLINE_ void (Object::*_get_notification() const)(int) { \
return (void(Object::*)(int)) & m_class::_notification; \
} \
@@ -613,12 +637,16 @@ protected:
virtual bool _setv(const StringName &p_name, const Variant &p_property) { return false; };
virtual bool _getv(const StringName &p_name, Variant &r_property) const { return false; };
virtual void _get_property_listv(List<PropertyInfo> *p_list, bool p_reversed) const {};
+ virtual bool _property_can_revertv(const StringName &p_name) const { return false; };
+ virtual bool _property_get_revertv(const StringName &p_name, Variant &r_property) const { return false; };
virtual void _notificationv(int p_notification, bool p_reversed) {}
static void _bind_methods();
bool _set(const StringName &p_name, const Variant &p_property) { return false; };
bool _get(const StringName &p_name, Variant &r_property) const { return false; };
void _get_property_list(List<PropertyInfo> *p_list) const {};
+ bool _property_can_revert(const StringName &p_name) const { return false; };
+ bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return false; };
void _notification(int p_notification) {}
_FORCE_INLINE_ static void (*_get_bind_methods())() {
@@ -633,6 +661,12 @@ protected:
_FORCE_INLINE_ void (Object::*_get_get_property_list() const)(List<PropertyInfo> *p_list) const {
return &Object::_get_property_list;
}
+ _FORCE_INLINE_ bool (Object::*_get_property_can_revert() const)(const StringName &p_name) const {
+ return &Object::_property_can_revert;
+ }
+ _FORCE_INLINE_ bool (Object::*_get_property_get_revert() const)(const StringName &p_name, Variant &) const {
+ return &Object::_property_get_revert;
+ }
_FORCE_INLINE_ void (Object::*_get_notification() const)(int) {
return &Object::_notification;
}
@@ -757,6 +791,8 @@ public:
Variant get_indexed(const Vector<StringName> &p_names, bool *r_valid = nullptr) const;
void get_property_list(List<PropertyInfo> *p_list, bool p_reversed = false) const;
+ bool property_can_revert(const String &p_name) const;
+ Variant property_get_revert(const String &p_name) const;
bool has_method(const StringName &p_method) const;
void get_method_list(List<MethodInfo> *p_list) const;
diff --git a/core/object/script_language.h b/core/object/script_language.h
index f5f052b600..bfdedbe4a5 100644
--- a/core/object/script_language.h
+++ b/core/object/script_language.h
@@ -171,6 +171,9 @@ public:
virtual void get_property_list(List<PropertyInfo> *p_properties) const = 0;
virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid = nullptr) const = 0;
+ virtual bool property_can_revert(const StringName &p_name) const = 0;
+ virtual bool property_get_revert(const StringName &p_name, Variant &r_ret) const = 0;
+
virtual Object *get_owner() { return nullptr; }
virtual void get_property_state(List<Pair<StringName, Variant>> &state);
@@ -447,6 +450,9 @@ public:
virtual void get_property_list(List<PropertyInfo> *p_properties) const override;
virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid = nullptr) const override;
+ virtual bool property_can_revert(const StringName &p_name) const override { return false; };
+ virtual bool property_get_revert(const StringName &p_name, Variant &r_ret) const override { return false; };
+
virtual void get_method_list(List<MethodInfo> *p_list) const override;
virtual bool has_method(const StringName &p_method) const override;
diff --git a/core/object/script_language_extension.h b/core/object/script_language_extension.h
index 2869f4ad98..7e74f6a2be 100644
--- a/core/object/script_language_extension.h
+++ b/core/object/script_language_extension.h
@@ -692,6 +692,19 @@ public:
return Variant::NIL;
}
+ virtual bool property_can_revert(const StringName &p_name) const override {
+ if (native_info->property_can_revert_func) {
+ return native_info->property_can_revert_func(instance, (const GDNativeStringNamePtr)&p_name);
+ }
+ return false;
+ }
+ virtual bool property_get_revert(const StringName &p_name, Variant &r_ret) const override {
+ if (native_info->property_get_revert_func) {
+ return native_info->property_get_revert_func(instance, (const GDNativeStringNamePtr)&p_name, (GDNativeVariantPtr)&r_ret);
+ }
+ return false;
+ }
+
virtual Object *get_owner() override {
if (native_info->get_owner_func) {
return (Object *)native_info->get_owner_func(instance);
diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml
index 35656dbd70..f6d926031d 100644
--- a/doc/classes/Array.xml
+++ b/doc/classes/Array.xml
@@ -576,14 +576,14 @@
<return type="bool" />
<param index="0" name="right" type="Array" />
<description>
- Performs a comparison for each index between the left operand [Array] and the [param right] [Array], considering the highest common index of both arrays for this comparison: Returns [code]true[/code] on the first occurrence of an element that is less, or [code]false[/code] if the element is greater. Note that depending on the type of data stored, this function may be recursive. If all elements are equal, it compares the length of both arrays and returns [code]false[/code] if the left operand [Array] has less elements, otherwise it returns [code]true[/code].
+ Performs a comparison for each index between the left operand [Array] and the [param right] [Array], considering the highest common index of both arrays for this comparison: Returns [code]true[/code] on the first occurrence of an element that is less, or [code]false[/code] if the element is greater. Note that depending on the type of data stored, this function may be recursive. If all elements are equal, it compares the length of both arrays and returns [code]false[/code] if the left operand [Array] has fewer elements, otherwise it returns [code]true[/code].
</description>
</operator>
<operator name="operator &lt;=">
<return type="bool" />
<param index="0" name="right" type="Array" />
<description>
- Performs a comparison for each index between the left operand [Array] and the [param right] [Array], considering the highest common index of both arrays for this comparison: Returns [code]true[/code] on the first occurrence of an element that is less, or [code]false[/code] if the element is greater. Note that depending on the type of data stored, this function may be recursive. If all elements are equal, it compares the length of both arrays and returns [code]true[/code] if the left operand [Array] has less or the same number of elements, otherwise it returns [code]false[/code].
+ Performs a comparison for each index between the left operand [Array] and the [param right] [Array], considering the highest common index of both arrays for this comparison: Returns [code]true[/code] on the first occurrence of an element that is less, or [code]false[/code] if the element is greater. Note that depending on the type of data stored, this function may be recursive. If all elements are equal, it compares the length of both arrays and returns [code]true[/code] if the left operand [Array] has the same number of elements or fewer, otherwise it returns [code]false[/code].
</description>
</operator>
<operator name="operator ==">
diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index cb2474384a..989d0cdb55 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -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" />
diff --git a/doc/classes/EditorNode3DGizmo.xml b/doc/classes/EditorNode3DGizmo.xml
index 74870f34db..9ee21fd63b 100644
--- a/doc/classes/EditorNode3DGizmo.xml
+++ b/doc/classes/EditorNode3DGizmo.xml
@@ -129,7 +129,7 @@
<param index="4" name="secondary" type="bool" default="false" />
<description>
Adds a list of handles (points) which can be used to edit the properties of the gizmo's Node3D. The [param ids] argument can be used to specify a custom identifier for each handle, if an empty [code]Array[/code] is passed, the ids will be assigned automatically from the [param handles] argument order.
- The [param secondary] argument marks the added handles as secondary, meaning they will normally have less selection priority than regular handles. When the user is holding the shift key secondary handles will switch to have higher priority than regular handles. This change in priority can be used to place multiple handles at the same point while still giving the user control on their selection.
+ The [param secondary] argument marks the added handles as secondary, meaning they will normally have lower selection priority than regular handles. When the user is holding the shift key secondary handles will switch to have higher priority than regular handles. This change in priority can be used to place multiple handles at the same point while still giving the user control on their selection.
There are virtual methods which will be called upon editing of these handles. Call this method during [method _redraw].
</description>
</method>
diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml
index 6079cc48c8..652b834f98 100644
--- a/doc/classes/EditorSettings.xml
+++ b/doc/classes/EditorSettings.xml
@@ -132,20 +132,6 @@
Marks the passed editor setting as being changed, see [method get_changed_settings]. Only settings which exist (see [method has_setting]) will be accepted.
</description>
</method>
- <method name="property_can_revert">
- <return type="bool" />
- <param index="0" name="name" type="String" />
- <description>
- Returns [code]true[/code] if the setting specified by [param name] can have its value reverted to the default value, [code]false[/code] otherwise. When this method returns [code]true[/code], a Revert button will display next to the setting in the Editor Settings.
- </description>
- </method>
- <method name="property_get_revert">
- <return type="Variant" />
- <param index="0" name="name" type="String" />
- <description>
- Returns the default value of the setting specified by [param name]. This is the value that would be applied when clicking the Revert button in the Editor Settings.
- </description>
- </method>
<method name="set_builtin_action_override">
<return type="void" />
<param index="0" name="name" type="String" />
@@ -553,6 +539,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>
@@ -710,7 +700,7 @@
If [code]true[/code], draws tab characters as chevrons.
</member>
<member name="text_editor/appearance/whitespace/line_spacing" type="int" setter="" getter="">
- The space to add between lines (in pixels). Greater line spacing can help improve readability at the cost of displaying less lines on screen.
+ The space to add between lines (in pixels). Greater line spacing can help improve readability at the cost of displaying fewer lines on screen.
</member>
<member name="text_editor/behavior/files/auto_reload_scripts_on_external_change" type="bool" setter="" getter="">
If [code]true[/code], automatically reloads scripts in the editor when they have been modified and saved by external editors.
diff --git a/doc/classes/MenuBar.xml b/doc/classes/MenuBar.xml
new file mode 100644
index 0000000000..3ef0572e9f
--- /dev/null
+++ b/doc/classes/MenuBar.xml
@@ -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>
diff --git a/doc/classes/Node3D.xml b/doc/classes/Node3D.xml
index 8a8a68ec35..e9f1f995a5 100644
--- a/doc/classes/Node3D.xml
+++ b/doc/classes/Node3D.xml
@@ -134,20 +134,6 @@
Resets this node's transformations (like scale, skew and taper) preserving its rotation and translation by performing Gram-Schmidt orthonormalization on this node's [Transform3D].
</description>
</method>
- <method name="property_can_revert">
- <return type="bool" />
- <param index="0" name="name" type="String" />
- <description>
- Returns [code]true[/code] if the property identified by [param name] can be reverted to a default value.
- </description>
- </method>
- <method name="property_get_revert">
- <return type="Variant" />
- <param index="0" name="name" type="String" />
- <description>
- Returns the default value of the Node3D property with given [param name].
- </description>
- </method>
<method name="rotate">
<return type="void" />
<param index="0" name="axis" type="Vector3" />
diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml
index ba219d8603..2e03ac5291 100644
--- a/doc/classes/Object.xml
+++ b/doc/classes/Object.xml
@@ -64,6 +64,22 @@
Called whenever the object receives a notification, which is identified in [param what] by a constant. The base [Object] has two constants [constant NOTIFICATION_POSTINITIALIZE] and [constant NOTIFICATION_PREDELETE], but subclasses such as [Node] define a lot more notifications which are also received by this method.
</description>
</method>
+ <method name="_property_can_revert" qualifiers="virtual">
+ <return type="bool" />
+ <param index="0" name="property" type="StringName" />
+ <description>
+ Virtual methods that can be overridden to customize the property revert behavior in the editor.
+ Returns [code]true[/code] if the property identified by [code]name[/code] can be reverted to a default value. Override [method _property_get_revert] to return the actual value.
+ </description>
+ </method>
+ <method name="_property_get_revert" qualifiers="virtual">
+ <return type="Variant" />
+ <param index="0" name="property" type="StringName" />
+ <description>
+ Virtual methods that can be overridden to customize the property revert behavior in the editor.
+ Returns the default value of the property identified by [code]name[/code]. [method _property_can_revert] must be overridden as well for this method to be called.
+ </description>
+ </method>
<method name="_set" qualifiers="virtual">
<return type="bool" />
<param index="0" name="property" type="StringName" />
@@ -367,7 +383,8 @@
<param index="1" name="default" type="Variant" default="null" />
<description>
Returns the object's metadata entry for the given [param name].
- Throws error if the entry does not exist, unless [param default] is not [code]null[/code] (in which case the default value will be returned).
+ Throws error if the entry does not exist, unless [param default] is not [code]null[/code] (in which case the default value will be returned). See also [method has_meta], [method set_meta] and [method remove_meta].
+ [b]Note:[/b] Metadata that has a [param name] starting with an underscore ([code]_[/code]) is considered editor-only. Editor-only metadata is not displayed in the inspector and should not be edited.
</description>
</method>
<method name="get_meta_list" qualifiers="const">
@@ -412,7 +429,8 @@
<return type="bool" />
<param index="0" name="name" type="StringName" />
<description>
- Returns [code]true[/code] if a metadata entry is found with the given [param name].
+ Returns [code]true[/code] if a metadata entry is found with the given [param name]. See also [method get_meta], [method set_meta] and [method remove_meta].
+ [b]Note:[/b] Metadata that has a [param name] starting with an underscore ([code]_[/code]) is considered editor-only. Editor-only metadata is not displayed in the inspector and should not be edited.
</description>
</method>
<method name="has_method" qualifiers="const">
@@ -483,7 +501,8 @@
<return type="void" />
<param index="0" name="name" type="StringName" />
<description>
- Removes a given entry from the object's metadata. See also [method set_meta].
+ Removes a given entry from the object's metadata. See also [method has_meta], [method get_meta] and [method set_meta].
+ [b]Note:[/b] Metadata that has a [param name] starting with an underscore ([code]_[/code]) is considered editor-only. Editor-only metadata is not displayed in the inspector and should not be edited.
</description>
</method>
<method name="set">
@@ -546,7 +565,8 @@
<param index="1" name="value" type="Variant" />
<description>
Adds, changes or removes a given entry in the object's metadata. Metadata are serialized and can take any [Variant] value.
- To remove a given entry from the object's metadata, use [method remove_meta]. Metadata is also removed if its value is set to [code]null[/code]. This means you can also use [code]set_meta("name", null)[/code] to remove metadata for [code]"name"[/code].
+ To remove a given entry from the object's metadata, use [method remove_meta]. Metadata is also removed if its value is set to [code]null[/code]. This means you can also use [code]set_meta("name", null)[/code] to remove metadata for [code]"name"[/code]. See also [method has_meta] and [method get_meta].
+ [b]Note:[/b] Metadata that has a [param name] starting with an underscore ([code]_[/code]) is considered editor-only. Editor-only metadata is not displayed in the inspector and should not be edited.
</description>
</method>
<method name="set_script">
diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml
index 26bc765ef4..0f47bea5df 100644
--- a/doc/classes/PopupMenu.xml
+++ b/doc/classes/PopupMenu.xml
@@ -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">
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index fcde9f6686..f72aeff469 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -129,20 +129,6 @@
Returns the localized path (starting with [code]res://[/code]) corresponding to the absolute, native OS [param path]. See also [method globalize_path].
</description>
</method>
- <method name="property_can_revert">
- <return type="bool" />
- <param index="0" name="name" type="String" />
- <description>
- Returns [code]true[/code] if the specified property exists and its initial value differs from the current value.
- </description>
- </method>
- <method name="property_get_revert">
- <return type="Variant" />
- <param index="0" name="name" type="String" />
- <description>
- Returns the specified property's initial value. Returns [code]null[/code] if the property does not exist.
- </description>
- </method>
<method name="save">
<return type="int" enum="Error" />
<description>
diff --git a/doc/classes/ProxyTexture.xml b/doc/classes/ProxyTexture.xml
deleted file mode 100644
index 778e3f3f69..0000000000
--- a/doc/classes/ProxyTexture.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<class name="ProxyTexture" inherits="Texture2D" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
- <brief_description>
- </brief_description>
- <description>
- </description>
- <tutorials>
- </tutorials>
- <members>
- <member name="base" type="Texture2D" setter="set_base" getter="get_base">
- </member>
- </members>
-</class>
diff --git a/doc/classes/ShaderMaterial.xml b/doc/classes/ShaderMaterial.xml
index 92df3255b1..8d4df87b39 100644
--- a/doc/classes/ShaderMaterial.xml
+++ b/doc/classes/ShaderMaterial.xml
@@ -17,20 +17,6 @@
Returns the current value set for this material of a uniform in the shader.
</description>
</method>
- <method name="property_can_revert">
- <return type="bool" />
- <param index="0" name="name" type="String" />
- <description>
- Returns [code]true[/code] if the property identified by [param name] can be reverted to a default value.
- </description>
- </method>
- <method name="property_get_revert">
- <return type="Variant" />
- <param index="0" name="name" type="String" />
- <description>
- Returns the default value of the material property with given [param name].
- </description>
- </method>
<method name="set_shader_uniform">
<return type="void" />
<param index="0" name="param" type="StringName" />
diff --git a/doc/classes/VelocityTracker3D.xml b/doc/classes/VelocityTracker3D.xml
deleted file mode 100644
index 56b60ba13c..0000000000
--- a/doc/classes/VelocityTracker3D.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<class name="VelocityTracker3D" inherits="RefCounted" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
- <brief_description>
- </brief_description>
- <description>
- </description>
- <tutorials>
- </tutorials>
- <methods>
- <method name="get_tracked_linear_velocity" qualifiers="const">
- <return type="Vector3" />
- <description>
- </description>
- </method>
- <method name="reset">
- <return type="void" />
- <param index="0" name="position" type="Vector3" />
- <description>
- </description>
- </method>
- <method name="update_position">
- <return type="void" />
- <param index="0" name="position" type="Vector3" />
- <description>
- </description>
- </method>
- </methods>
- <members>
- <member name="track_physics_step" type="bool" setter="set_track_physics_step" getter="is_tracking_physics_step" default="false">
- </member>
- </members>
-</class>
diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp
index 83154acd51..28802f571c 100644
--- a/drivers/gles3/rasterizer_canvas_gles3.cpp
+++ b/drivers/gles3/rasterizer_canvas_gles3.cpp
@@ -201,6 +201,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
bool material_screen_texture_found = false;
Rect2 back_buffer_rect;
bool backbuffer_copy = false;
+ bool backbuffer_gen_mipmaps = false;
Item *ci = p_item_list;
Item *canvas_group_owner = nullptr;
@@ -225,6 +226,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
if (!material_screen_texture_found) {
backbuffer_copy = true;
back_buffer_rect = Rect2();
+ backbuffer_gen_mipmaps = md->shader_data->uses_screen_texture_mipmaps;
}
}
@@ -282,7 +284,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
_render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list);
item_count = 0;
- texture_storage->render_target_copy_to_back_buffer(p_to_render_target, back_buffer_rect, true);
+ texture_storage->render_target_copy_to_back_buffer(p_to_render_target, back_buffer_rect, backbuffer_gen_mipmaps);
backbuffer_copy = false;
material_screen_texture_found = true; //after a backbuffer copy, screen texture makes no further copies
diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp
index 26441fc726..c8a35dbdad 100644
--- a/drivers/gles3/storage/material_storage.cpp
+++ b/drivers/gles3/storage/material_storage.cpp
@@ -1083,6 +1083,12 @@ void MaterialData::update_textures(const HashMap<StringName, Variant> &p_paramet
Vector<RID> textures;
+ if (p_texture_uniforms[i].hint == ShaderLanguage::ShaderNode::Uniform::HINT_SCREEN_TEXTURE ||
+ p_texture_uniforms[i].hint == ShaderLanguage::ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE ||
+ p_texture_uniforms[i].hint == ShaderLanguage::ShaderNode::Uniform::HINT_DEPTH_TEXTURE) {
+ continue;
+ }
+
if (p_texture_uniforms[i].global) {
uses_global_textures = true;
@@ -1492,9 +1498,9 @@ MaterialStorage::MaterialStorage() {
actions.renames["POINT_COORD"] = "gl_PointCoord";
actions.renames["INSTANCE_CUSTOM"] = "instance_custom";
actions.renames["SCREEN_UV"] = "screen_uv";
- actions.renames["SCREEN_TEXTURE"] = "color_buffer";
- actions.renames["DEPTH_TEXTURE"] = "depth_buffer";
- actions.renames["NORMAL_ROUGHNESS_TEXTURE"] = "normal_roughness_buffer";
+ //actions.renames["SCREEN_TEXTURE"] = "color_buffer"; //Not implemented in 3D yet.
+ //actions.renames["DEPTH_TEXTURE"] = "depth_buffer"; // Not implemented in 3D yet.
+ //actions.renames["NORMAL_ROUGHNESS_TEXTURE"] = "normal_roughness_buffer"; // Not implemented in 3D yet
actions.renames["DEPTH"] = "gl_FragDepth";
actions.renames["OUTPUT_IS_SRGB"] = "true";
actions.renames["FOG"] = "fog";
@@ -2789,6 +2795,7 @@ void CanvasShaderData::set_code(const String &p_code) {
ubo_size = 0;
uniforms.clear();
uses_screen_texture = false;
+ uses_screen_texture_mipmaps = false;
uses_sdf = false;
uses_time = false;
@@ -2799,7 +2806,6 @@ void CanvasShaderData::set_code(const String &p_code) {
ShaderCompiler::GeneratedCode gen_code;
int blend_modei = BLEND_MODE_MIX;
- uses_screen_texture = false;
ShaderCompiler::IdentifierActions actions;
actions.entry_point_stages["vertex"] = ShaderCompiler::STAGE_VERTEX;
@@ -2826,6 +2832,7 @@ void CanvasShaderData::set_code(const String &p_code) {
}
blend_mode = BlendMode(blend_modei);
+ uses_screen_texture_mipmaps = gen_code.uses_screen_texture_mipmaps;
#if 0
print_line("**compiling shader:");
@@ -2833,12 +2840,16 @@ void CanvasShaderData::set_code(const String &p_code) {
for (int i = 0; i < gen_code.defines.size(); i++) {
print_line(gen_code.defines[i]);
}
+
+ HashMap<String, String>::Iterator el = gen_code.code.begin();
+ while (el) {
+ print_line("\n**code " + el->key + ":\n" + el->value);
+ ++el;
+ }
+
print_line("\n**uniforms:\n" + gen_code.uniforms);
- print_line("\n**vertex_globals:\n" + gen_code.vertex_global);
- print_line("\n**vertex_code:\n" + gen_code.vertex);
- print_line("\n**fragment_globals:\n" + gen_code.fragment_global);
- print_line("\n**fragment_code:\n" + gen_code.fragment);
- print_line("\n**light_code:\n" + gen_code.light);
+ print_line("\n**vertex_globals:\n" + gen_code.stage_globals[ShaderCompiler::STAGE_VERTEX]);
+ print_line("\n**fragment_globals:\n" + gen_code.stage_globals[ShaderCompiler::STAGE_FRAGMENT]);
#endif
Vector<StringName> texture_uniform_names;
@@ -2877,7 +2888,10 @@ void CanvasShaderData::get_shader_uniform_list(List<PropertyInfo> *p_param_list)
HashMap<int, StringName> order;
for (const KeyValue<StringName, ShaderLanguage::ShaderNode::Uniform> &E : uniforms) {
- if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL) {
+ if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL ||
+ E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_SCREEN_TEXTURE ||
+ E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE ||
+ E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_DEPTH_TEXTURE) {
continue;
}
if (E.value.texture_order >= 0) {
@@ -3070,12 +3084,16 @@ void SkyShaderData::set_code(const String &p_code) {
for (int i = 0; i < gen_code.defines.size(); i++) {
print_line(gen_code.defines[i]);
}
+
+ HashMap<String, String>::Iterator el = gen_code.code.begin();
+ while (el) {
+ print_line("\n**code " + el->key + ":\n" + el->value);
+ ++el;
+ }
+
print_line("\n**uniforms:\n" + gen_code.uniforms);
- // print_line("\n**vertex_globals:\n" + gen_code.vertex_global);
- // print_line("\n**vertex_code:\n" + gen_code.vertex);
- print_line("\n**fragment_globals:\n" + gen_code.fragment_global);
- print_line("\n**fragment_code:\n" + gen_code.fragment);
- print_line("\n**light_code:\n" + gen_code.light);
+ print_line("\n**vertex_globals:\n" + gen_code.stage_globals[ShaderCompiler::STAGE_VERTEX]);
+ print_line("\n**fragment_globals:\n" + gen_code.stage_globals[ShaderCompiler::STAGE_FRAGMENT]);
#endif
Vector<StringName> texture_uniform_names;
@@ -3253,7 +3271,6 @@ void SceneShaderData::set_code(const String &p_code) {
valid = false;
ubo_size = 0;
uniforms.clear();
- uses_screen_texture = false;
if (code.is_empty()) {
return; //just invalid, but no error
@@ -3378,6 +3395,7 @@ void SceneShaderData::set_code(const String &p_code) {
vertex_input_mask |= uses_custom3 << 8;
vertex_input_mask |= uses_bones << 9;
vertex_input_mask |= uses_weights << 10;
+ uses_screen_texture_mipmaps = gen_code.uses_screen_texture_mipmaps;
#if 0
print_line("**compiling shader:");
@@ -3386,11 +3404,10 @@ void SceneShaderData::set_code(const String &p_code) {
print_line(gen_code.defines[i]);
}
- Map<String, String>::Element *el = gen_code.code.front();
+ HashMap<String, String>::Iterator el = gen_code.code.begin();
while (el) {
- print_line("\n**code " + el->key() + ":\n" + el->value());
-
- el = el->next();
+ print_line("\n**code " + el->key + ":\n" + el->value);
+ ++el;
}
print_line("\n**uniforms:\n" + gen_code.uniforms);
@@ -3439,7 +3456,10 @@ void SceneShaderData::get_shader_uniform_list(List<PropertyInfo> *p_param_list)
RBMap<int, StringName> order;
for (const KeyValue<StringName, ShaderLanguage::ShaderNode::Uniform> &E : uniforms) {
- if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL) {
+ if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL ||
+ E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_SCREEN_TEXTURE ||
+ E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE ||
+ E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_DEPTH_TEXTURE) {
continue;
}
diff --git a/drivers/gles3/storage/material_storage.h b/drivers/gles3/storage/material_storage.h
index 2ca47351a4..d135357f6a 100644
--- a/drivers/gles3/storage/material_storage.h
+++ b/drivers/gles3/storage/material_storage.h
@@ -159,6 +159,7 @@ struct CanvasShaderData : public ShaderData {
HashMap<StringName, HashMap<int, RID>> default_texture_params;
bool uses_screen_texture = false;
+ bool uses_screen_texture_mipmaps = false;
bool uses_sdf = false;
bool uses_time = false;
@@ -312,6 +313,7 @@ struct SceneShaderData : public ShaderData {
bool uses_sss;
bool uses_transmittance;
bool uses_screen_texture;
+ bool uses_screen_texture_mipmaps;
bool uses_depth_texture;
bool uses_normal_texture;
bool uses_time;
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index b4e36d568d..e954f06f08 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -432,11 +432,11 @@ bool EditorProperty::is_read_only() const {
}
Variant EditorPropertyRevert::get_property_revert_value(Object *p_object, const StringName &p_property, bool *r_is_valid) {
- if (p_object->has_method("property_can_revert") && p_object->call("property_can_revert", p_property)) {
+ if (p_object->property_can_revert(p_property)) {
if (r_is_valid) {
*r_is_valid = true;
}
- return p_object->call("property_get_revert", p_property);
+ return p_object->property_get_revert(p_property);
}
return PropertyUtils::get_property_default_value(p_object, p_property, r_is_valid);
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 232cda48fc..2941ae6695 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -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->set_tooltip(TTR("Operations with scene files."));
+ 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);
- p = file_menu->get_popup();
+ 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->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_separator();
- 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);
+ 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/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);
-
- 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);
+ 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);
+ }
- p = project_menu->get_popup();
+ project_menu = memnew(PopupMenu);
+ project_menu->set_name(TTR("Project"));
+ main_menu->add_child(project_menu);
- 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);
diff --git a/editor/editor_node.h b/editor/editor_node.h
index 771eaf6a68..f7a102b4c7 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -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;
diff --git a/editor/editor_path.cpp b/editor/editor_path.cpp
index dc77b5fea9..87ebd3e1c1 100644
--- a/editor/editor_path.cpp
+++ b/editor/editor_path.cpp
@@ -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);
diff --git a/editor/editor_sectioned_inspector.cpp b/editor/editor_sectioned_inspector.cpp
index 801a1a4641..cbca3e9dcd 100644
--- a/editor/editor_sectioned_inspector.cpp
+++ b/editor/editor_sectioned_inspector.cpp
@@ -114,11 +114,11 @@ class SectionedInspectorFilter : public Object {
}
bool property_can_revert(const String &p_name) {
- return edited->call("property_can_revert", section + "/" + p_name);
+ return edited->property_can_revert(section + "/" + p_name);
}
Variant property_get_revert(const String &p_name) {
- return edited->call("property_get_revert", section + "/" + p_name);
+ return edited->property_get_revert(section + "/" + p_name);
}
protected:
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index 80e77a1125..36e64cbd7a 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -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")
@@ -1073,24 +1074,25 @@ Variant _EDITOR_GET(const String &p_setting) {
return EditorSettings::get_singleton()->get(p_setting);
}
-bool EditorSettings::property_can_revert(const String &p_setting) {
- if (!props.has(p_setting)) {
+bool EditorSettings::_property_can_revert(const StringName &p_name) const {
+ if (!props.has(p_name)) {
return false;
}
- if (!props[p_setting].has_default_value) {
+ if (!props[p_name].has_default_value) {
return false;
}
- return props[p_setting].initial != props[p_setting].variant;
+ return props[p_name].initial != props[p_name].variant;
}
-Variant EditorSettings::property_get_revert(const String &p_setting) {
- if (!props.has(p_setting) || !props[p_setting].has_default_value) {
- return Variant();
+bool EditorSettings::_property_get_revert(const StringName &p_name, Variant &r_property) const {
+ if (!props.has(p_name) || !props[p_name].has_default_value) {
+ return false;
}
- return props[p_setting].initial;
+ r_property = props[p_name].initial;
+ return true;
}
void EditorSettings::add_property_hint(const PropertyInfo &p_hint) {
@@ -1621,8 +1623,6 @@ void EditorSettings::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_setting", "name"), &EditorSettings::get_setting);
ClassDB::bind_method(D_METHOD("erase", "property"), &EditorSettings::erase);
ClassDB::bind_method(D_METHOD("set_initial_value", "name", "value", "update_current"), &EditorSettings::set_initial_value);
- ClassDB::bind_method(D_METHOD("property_can_revert", "name"), &EditorSettings::property_can_revert);
- ClassDB::bind_method(D_METHOD("property_get_revert", "name"), &EditorSettings::property_get_revert);
ClassDB::bind_method(D_METHOD("add_property_info", "info"), &EditorSettings::_add_property_info_bind);
ClassDB::bind_method(D_METHOD("set_project_metadata", "section", "key", "data"), &EditorSettings::set_project_metadata);
diff --git a/editor/editor_settings.h b/editor/editor_settings.h
index 5faeec88c8..f921171c57 100644
--- a/editor/editor_settings.h
+++ b/editor/editor_settings.h
@@ -100,6 +100,8 @@ private:
void _initial_set(const StringName &p_name, const Variant &p_value);
void _get_property_list(List<PropertyInfo> *p_list) const;
void _add_property_info_bind(const Dictionary &p_info);
+ bool _property_can_revert(const StringName &p_name) const;
+ bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
void _load_defaults(Ref<ConfigFile> p_extra_config = Ref<ConfigFile>());
void _load_godot2_text_editor_theme();
@@ -138,8 +140,6 @@ public:
_set_only(p_setting, p_value);
}
}
- bool property_can_revert(const String &p_setting);
- Variant property_get_revert(const String &p_setting);
void add_property_hint(const PropertyInfo &p_hint);
Array get_changed_settings() const;
bool check_changed_settings_in_group(const String &p_setting_prefix) const;
diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp
index c58d1263f3..3da9899052 100644
--- a/editor/editor_themes.cpp
+++ b/editor/editor_themes.cpp
@@ -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();
@@ -912,6 +931,9 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
// Always display a border for PopupMenus so they can be distinguished from their background.
style_popup_menu->set_border_width_all(EDSCALE);
style_popup_menu->set_border_color(dark_color_2);
+ // Popups are separate windows by default in the editor. Windows currently don't support per-pixel transparency
+ // in 4.0, and even if it was, it may not always work in practice (e.g. running with compositing disabled).
+ style_popup_menu->set_corner_radius_all(0);
theme->set_stylebox("panel", "PopupMenu", style_popup_menu);
Ref<StyleBoxFlat> style_menu_hover = style_widget_hover->duplicate();
@@ -1460,6 +1482,17 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
// PopupPanel
theme->set_stylebox("panel", "PopupPanel", style_popup);
+ Ref<StyleBoxFlat> control_editor_popup_style = style_popup->duplicate();
+ control_editor_popup_style->set_shadow_size(0);
+ control_editor_popup_style->set_default_margin(SIDE_LEFT, default_margin_size * EDSCALE);
+ control_editor_popup_style->set_default_margin(SIDE_TOP, default_margin_size * EDSCALE);
+ control_editor_popup_style->set_default_margin(SIDE_RIGHT, default_margin_size * EDSCALE);
+ control_editor_popup_style->set_default_margin(SIDE_BOTTOM, default_margin_size * EDSCALE);
+ control_editor_popup_style->set_border_width_all(0);
+
+ theme->set_stylebox("panel", "ControlEditorPopupButton", control_editor_popup_style);
+ theme->set_type_variation("ControlEditorPopupButton", "PopupPanel");
+
// SpinBox
theme->set_icon("updown", "SpinBox", theme->get_icon(SNAME("GuiSpinboxUpdown"), SNAME("EditorIcons")));
theme->set_icon("updown_disabled", "SpinBox", theme->get_icon(SNAME("GuiSpinboxUpdownDisabled"), SNAME("EditorIcons")));
diff --git a/editor/icons/ContainerLayout.svg b/editor/icons/ContainerLayout.svg
new file mode 100644
index 0000000000..feabc2c350
--- /dev/null
+++ b/editor/icons/ContainerLayout.svg
@@ -0,0 +1 @@
+<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero"><path d="m3 1c-1.105 0-2 .895-2 2h2zm2 0v2h2v-2zm4 0v2h2v-2zm4 0v2h2c0-1.105-.895-2-2-2zm-12 4v2h2v-2zm12 0v2h2v-2zm-12 4v2h2v-2zm12 0v2h2v-2zm-12 4c0 1.105.895 2 2 2v-2zm4 0v2h2v-2zm4 0v2h2v-2zm4 0v2c1.105 0 2-.895 2-2z" fill="#8eef97"/><path d="m7 7h4v4h-4z" fill="#d6d6d6"/></g></svg>
diff --git a/editor/icons/ControlLayout.svg b/editor/icons/ControlLayout.svg
index 11dd2554be..8503e3313c 100644
--- a/editor/icons/ControlLayout.svg
+++ b/editor/icons/ControlLayout.svg
@@ -1 +1 @@
-<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m1 1v14h14v-14zm2 2h3v3h-3zm5 0h5v3h-5zm-5 5h3v5h-3zm5 0h5v5h-5z" fill="#8eef97"/></svg>
+<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero"><path d="m11.793 8v-2h-3.793v-2.113h-2v2.113h-2.142v2h2.142v3.967h2v-3.967z" fill="#d6d6d6"/><path d="m8 .345c-4.199 0-7.655 3.456-7.655 7.655s3.456 7.655 7.655 7.655 7.655-3.456 7.655-7.655-3.456-7.655-7.655-7.655zm0 1.999c3.103 0 5.656 2.553 5.656 5.656s-2.553 5.656-5.656 5.656-5.656-2.553-5.656-5.656 2.553-5.656 5.656-5.656z" fill="#8eef97"/></g></svg>
diff --git a/editor/icons/MenuBar.svg b/editor/icons/MenuBar.svg
new file mode 100644
index 0000000000..0a53f07f85
--- /dev/null
+++ b/editor/icons/MenuBar.svg
@@ -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>
diff --git a/editor/plugins/control_editor_plugin.cpp b/editor/plugins/control_editor_plugin.cpp
index 2756e45cf4..ff5d112956 100644
--- a/editor/plugins/control_editor_plugin.cpp
+++ b/editor/plugins/control_editor_plugin.cpp
@@ -31,10 +31,13 @@
#include "control_editor_plugin.h"
#include "editor/editor_node.h"
+#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "editor/plugins/canvas_item_editor_plugin.h"
#include "scene/gui/separator.h"
+// Inspector controls.
+
void ControlPositioningWarning::_update_warning() {
if (!control_node) {
title_icon->set_texture(nullptr);
@@ -49,7 +52,7 @@ void ControlPositioningWarning::_update_warning() {
title_label->set_text(TTR("This node doesn't have a control parent."));
hint_label->set_text(TTR("Use the appropriate layout properties depending on where you are going to put it."));
} else if (Object::cast_to<Container>(parent_node)) {
- title_icon->set_texture(get_theme_icon(SNAME("Container"), SNAME("EditorIcons")));
+ title_icon->set_texture(get_theme_icon(SNAME("ContainerLayout"), SNAME("EditorIcons")));
title_label->set_text(TTR("This node is a child of a container."));
hint_label->set_text(TTR("Use container properties for positioning."));
} else {
@@ -448,37 +451,280 @@ bool EditorInspectorPluginControl::parse_property(Object *p_object, const Varian
return false;
}
-void ControlEditorToolbar::_set_anchors_and_offsets_preset(Control::LayoutPreset p_preset) {
+// Toolbars controls.
+
+Size2 ControlEditorPopupButton::get_minimum_size() const {
+ Vector2 base_size = Vector2(26, 26) * EDSCALE;
+
+ if (arrow_icon.is_null()) {
+ return base_size;
+ }
+
+ Vector2 final_size;
+ final_size.x = base_size.x + arrow_icon->get_width();
+ final_size.y = MAX(base_size.y, arrow_icon->get_height());
+
+ return final_size;
+}
+
+void ControlEditorPopupButton::toggled(bool p_pressed) {
+ if (!p_pressed) {
+ return;
+ }
+
+ Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale();
+
+ popup_panel->set_size(Size2(size.width, 0));
+ Point2 gp = get_screen_position();
+ gp.y += size.y;
+ if (is_layout_rtl()) {
+ gp.x += size.width - popup_panel->get_size().width;
+ }
+ popup_panel->set_position(gp);
+
+ popup_panel->popup();
+}
+
+void ControlEditorPopupButton::_popup_visibility_changed(bool p_visible) {
+ set_pressed(p_visible);
+}
+
+void ControlEditorPopupButton::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED: {
+ arrow_icon = get_theme_icon("select_arrow", "Tree");
+ } break;
+
+ case NOTIFICATION_DRAW: {
+ if (arrow_icon.is_valid()) {
+ Vector2 arrow_pos = Point2(26, 0) * EDSCALE;
+ arrow_pos.y = get_size().y / 2 - arrow_icon->get_height() / 2;
+ draw_texture(arrow_icon, arrow_pos);
+ }
+ } break;
+
+ case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
+ popup_panel->set_layout_direction((Window::LayoutDirection)get_layout_direction());
+ } break;
+
+ case NOTIFICATION_VISIBILITY_CHANGED: {
+ if (!is_visible_in_tree()) {
+ popup_panel->hide();
+ }
+ } break;
+ }
+}
+
+ControlEditorPopupButton::ControlEditorPopupButton() {
+ set_flat(true);
+ set_toggle_mode(true);
+ set_focus_mode(FOCUS_NONE);
+
+ popup_panel = memnew(PopupPanel);
+ popup_panel->set_theme_type_variation("ControlEditorPopupButton");
+ add_child(popup_panel);
+ popup_panel->connect("about_to_popup", callable_mp(this, &ControlEditorPopupButton::_popup_visibility_changed).bind(true));
+ popup_panel->connect("popup_hide", callable_mp(this, &ControlEditorPopupButton::_popup_visibility_changed).bind(false));
+
+ popup_vbox = memnew(VBoxContainer);
+ popup_panel->add_child(popup_vbox);
+}
+
+void ControlEditorPresetPicker::_add_row_button(HBoxContainer *p_row, const int p_preset, const String &p_name) {
+ ERR_FAIL_COND(preset_buttons.has(p_preset));
+
+ Button *b = memnew(Button);
+ b->set_custom_minimum_size(Size2i(36, 36) * EDSCALE);
+ b->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER);
+ b->set_tooltip(p_name);
+ b->set_flat(true);
+ p_row->add_child(b);
+ b->connect("pressed", callable_mp(this, &ControlEditorPresetPicker::_preset_button_pressed).bind(p_preset));
+
+ preset_buttons[p_preset] = b;
+}
+
+void ControlEditorPresetPicker::_add_separator(BoxContainer *p_box, Separator *p_separator) {
+ p_separator->add_theme_constant_override("separation", grid_separation);
+ p_separator->set_custom_minimum_size(Size2i(1, 1));
+ p_box->add_child(p_separator);
+}
+
+void AnchorPresetPicker::_preset_button_pressed(const int p_preset) {
+ emit_signal("anchors_preset_selected", p_preset);
+}
+
+void AnchorPresetPicker::_notification(int p_notification) {
+ switch (p_notification) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED: {
+ preset_buttons[PRESET_TOP_LEFT]->set_icon(get_theme_icon(SNAME("ControlAlignTopLeft"), SNAME("EditorIcons")));
+ preset_buttons[PRESET_CENTER_TOP]->set_icon(get_theme_icon(SNAME("ControlAlignCenterTop"), SNAME("EditorIcons")));
+ preset_buttons[PRESET_TOP_RIGHT]->set_icon(get_theme_icon(SNAME("ControlAlignTopRight"), SNAME("EditorIcons")));
+
+ preset_buttons[PRESET_CENTER_LEFT]->set_icon(get_theme_icon(SNAME("ControlAlignCenterLeft"), SNAME("EditorIcons")));
+ preset_buttons[PRESET_CENTER]->set_icon(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")));
+ preset_buttons[PRESET_CENTER_RIGHT]->set_icon(get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons")));
+
+ preset_buttons[PRESET_BOTTOM_LEFT]->set_icon(get_theme_icon(SNAME("ControlAlignBottomLeft"), SNAME("EditorIcons")));
+ preset_buttons[PRESET_CENTER_BOTTOM]->set_icon(get_theme_icon(SNAME("ControlAlignCenterBottom"), SNAME("EditorIcons")));
+ preset_buttons[PRESET_BOTTOM_RIGHT]->set_icon(get_theme_icon(SNAME("ControlAlignBottomRight"), SNAME("EditorIcons")));
+
+ preset_buttons[PRESET_TOP_WIDE]->set_icon(get_theme_icon(SNAME("ControlAlignTopWide"), SNAME("EditorIcons")));
+ preset_buttons[PRESET_HCENTER_WIDE]->set_icon(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons")));
+ preset_buttons[PRESET_BOTTOM_WIDE]->set_icon(get_theme_icon(SNAME("ControlAlignBottomWide"), SNAME("EditorIcons")));
+
+ preset_buttons[PRESET_LEFT_WIDE]->set_icon(get_theme_icon(SNAME("ControlAlignLeftWide"), SNAME("EditorIcons")));
+ preset_buttons[PRESET_VCENTER_WIDE]->set_icon(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons")));
+ preset_buttons[PRESET_RIGHT_WIDE]->set_icon(get_theme_icon(SNAME("ControlAlignRightWide"), SNAME("EditorIcons")));
+
+ preset_buttons[PRESET_FULL_RECT]->set_icon(get_theme_icon(SNAME("ControlAlignFullRect"), SNAME("EditorIcons")));
+ } break;
+ }
+}
+
+void AnchorPresetPicker::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("anchors_preset_selected", PropertyInfo(Variant::INT, "preset")));
+}
+
+AnchorPresetPicker::AnchorPresetPicker() {
+ VBoxContainer *main_vb = memnew(VBoxContainer);
+ main_vb->add_theme_constant_override("separation", grid_separation);
+ add_child(main_vb);
+
+ HBoxContainer *top_row = memnew(HBoxContainer);
+ top_row->set_alignment(BoxContainer::ALIGNMENT_CENTER);
+ top_row->add_theme_constant_override("separation", grid_separation);
+ main_vb->add_child(top_row);
+
+ _add_row_button(top_row, PRESET_TOP_LEFT, TTR("Top Left"));
+ _add_row_button(top_row, PRESET_CENTER_TOP, TTR("Center Top"));
+ _add_row_button(top_row, PRESET_TOP_RIGHT, TTR("Top Right"));
+ _add_separator(top_row, memnew(VSeparator));
+ _add_row_button(top_row, PRESET_TOP_WIDE, TTR("Top Wide"));
+
+ HBoxContainer *mid_row = memnew(HBoxContainer);
+ mid_row->set_alignment(BoxContainer::ALIGNMENT_CENTER);
+ mid_row->add_theme_constant_override("separation", grid_separation);
+ main_vb->add_child(mid_row);
+
+ _add_row_button(mid_row, PRESET_CENTER_LEFT, TTR("Center Left"));
+ _add_row_button(mid_row, PRESET_CENTER, TTR("Center"));
+ _add_row_button(mid_row, PRESET_CENTER_RIGHT, TTR("Center Right"));
+ _add_separator(mid_row, memnew(VSeparator));
+ _add_row_button(mid_row, PRESET_HCENTER_WIDE, TTR("HCenter Wide"));
+
+ HBoxContainer *bot_row = memnew(HBoxContainer);
+ bot_row->set_alignment(BoxContainer::ALIGNMENT_CENTER);
+ bot_row->add_theme_constant_override("separation", grid_separation);
+ main_vb->add_child(bot_row);
+
+ _add_row_button(bot_row, PRESET_BOTTOM_LEFT, TTR("Bottom Left"));
+ _add_row_button(bot_row, PRESET_CENTER_BOTTOM, TTR("Center Bottom"));
+ _add_row_button(bot_row, PRESET_BOTTOM_RIGHT, TTR("Bottom Right"));
+ _add_separator(bot_row, memnew(VSeparator));
+ _add_row_button(bot_row, PRESET_BOTTOM_WIDE, TTR("Bottom Wide"));
+
+ _add_separator(main_vb, memnew(HSeparator));
+
+ HBoxContainer *extra_row = memnew(HBoxContainer);
+ extra_row->set_alignment(BoxContainer::ALIGNMENT_CENTER);
+ extra_row->add_theme_constant_override("separation", grid_separation);
+ main_vb->add_child(extra_row);
+
+ _add_row_button(extra_row, PRESET_LEFT_WIDE, TTR("Left Wide"));
+ _add_row_button(extra_row, PRESET_VCENTER_WIDE, TTR("VCenter Wide"));
+ _add_row_button(extra_row, PRESET_RIGHT_WIDE, TTR("Right Wide"));
+ _add_separator(extra_row, memnew(VSeparator));
+ _add_row_button(extra_row, PRESET_FULL_RECT, TTR("Full Rect"));
+}
+
+void SizeFlagPresetPicker::_preset_button_pressed(const int p_preset) {
+ int flags = (SizeFlags)p_preset;
+ if (expand_button->is_pressed()) {
+ flags |= SIZE_EXPAND;
+ }
+
+ emit_signal("size_flags_selected", flags);
+}
+
+void SizeFlagPresetPicker::set_allowed_flags(Vector<SizeFlags> &p_flags) {
+ preset_buttons[SIZE_SHRINK_BEGIN]->set_disabled(!p_flags.has(SIZE_SHRINK_BEGIN));
+ preset_buttons[SIZE_SHRINK_CENTER]->set_disabled(!p_flags.has(SIZE_SHRINK_CENTER));
+ preset_buttons[SIZE_SHRINK_END]->set_disabled(!p_flags.has(SIZE_SHRINK_END));
+ preset_buttons[SIZE_FILL]->set_disabled(!p_flags.has(SIZE_FILL));
+
+ expand_button->set_disabled(!p_flags.has(SIZE_EXPAND));
+ if (p_flags.has(SIZE_EXPAND)) {
+ expand_button->set_tooltip(TTR("Enable to also set the Expand flag.\nDisable to only set Shrink/Fill flags."));
+ } else {
+ expand_button->set_pressed(false);
+ expand_button->set_tooltip(TTR("Some parents of the selected nodes do not support the Expand flag."));
+ }
+}
+
+void SizeFlagPresetPicker::_notification(int p_notification) {
+ switch (p_notification) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED: {
+ if (vertical) {
+ preset_buttons[SIZE_SHRINK_BEGIN]->set_icon(get_theme_icon(SNAME("ControlAlignCenterTop"), SNAME("EditorIcons")));
+ preset_buttons[SIZE_SHRINK_CENTER]->set_icon(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")));
+ preset_buttons[SIZE_SHRINK_END]->set_icon(get_theme_icon(SNAME("ControlAlignCenterBottom"), SNAME("EditorIcons")));
+
+ preset_buttons[SIZE_FILL]->set_icon(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons")));
+ } else {
+ preset_buttons[SIZE_SHRINK_BEGIN]->set_icon(get_theme_icon(SNAME("ControlAlignCenterLeft"), SNAME("EditorIcons")));
+ preset_buttons[SIZE_SHRINK_CENTER]->set_icon(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")));
+ preset_buttons[SIZE_SHRINK_END]->set_icon(get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons")));
+
+ preset_buttons[SIZE_FILL]->set_icon(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons")));
+ }
+ } break;
+ }
+}
+
+void SizeFlagPresetPicker::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("size_flags_selected", PropertyInfo(Variant::INT, "size_flags")));
+}
+
+SizeFlagPresetPicker::SizeFlagPresetPicker(bool p_vertical) {
+ vertical = p_vertical;
+
+ VBoxContainer *main_vb = memnew(VBoxContainer);
+ add_child(main_vb);
+
+ HBoxContainer *main_row = memnew(HBoxContainer);
+ main_row->set_alignment(BoxContainer::ALIGNMENT_CENTER);
+ main_row->add_theme_constant_override("separation", grid_separation);
+ main_vb->add_child(main_row);
+
+ _add_row_button(main_row, SIZE_SHRINK_BEGIN, TTR("Shrink Begin"));
+ _add_row_button(main_row, SIZE_SHRINK_CENTER, TTR("Shrink Center"));
+ _add_row_button(main_row, SIZE_SHRINK_END, TTR("Shrink End"));
+ _add_separator(main_row, memnew(VSeparator));
+ _add_row_button(main_row, SIZE_FILL, TTR("Fill"));
+
+ expand_button = memnew(CheckBox);
+ expand_button->set_flat(true);
+ expand_button->set_text(TTR("Align with Expand"));
+ expand_button->set_tooltip(TTR("Enable to also set the Expand flag.\nDisable to only set Shrink/Fill flags."));
+ main_vb->add_child(expand_button);
+}
+
+// Toolbar.
+
+void ControlEditorToolbar::_anchors_preset_selected(int p_preset) {
+ LayoutPreset preset = (LayoutPreset)p_preset;
List<Node *> selection = editor_selection->get_selected_node_list();
- undo_redo->create_action(TTR("Change Anchors and Offsets"));
+ undo_redo->create_action(TTR("Change Anchors, Offsets, Grow Direction"));
for (Node *E : selection) {
Control *control = Object::cast_to<Control>(E);
if (control) {
- undo_redo->add_do_method(control, "set_anchors_preset", p_preset);
- switch (p_preset) {
- case PRESET_TOP_LEFT:
- case PRESET_TOP_RIGHT:
- case PRESET_BOTTOM_LEFT:
- case PRESET_BOTTOM_RIGHT:
- case PRESET_CENTER_LEFT:
- case PRESET_CENTER_TOP:
- case PRESET_CENTER_RIGHT:
- case PRESET_CENTER_BOTTOM:
- case PRESET_CENTER:
- undo_redo->add_do_method(control, "set_offsets_preset", p_preset, Control::PRESET_MODE_KEEP_SIZE);
- break;
- case PRESET_LEFT_WIDE:
- case PRESET_TOP_WIDE:
- case PRESET_RIGHT_WIDE:
- case PRESET_BOTTOM_WIDE:
- case PRESET_VCENTER_WIDE:
- case PRESET_HCENTER_WIDE:
- case PRESET_FULL_RECT:
- undo_redo->add_do_method(control, "set_offsets_preset", p_preset, Control::PRESET_MODE_MINSIZE);
- break;
- }
+ undo_redo->add_do_property(control, "anchors_preset", preset);
undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state());
}
}
@@ -489,10 +735,10 @@ void ControlEditorToolbar::_set_anchors_and_offsets_preset(Control::LayoutPreset
anchor_mode_button->set_pressed(anchors_mode);
}
-void ControlEditorToolbar::_set_anchors_and_offsets_to_keep_ratio() {
+void ControlEditorToolbar::_anchors_to_current_ratio() {
List<Node *> selection = editor_selection->get_selected_node_list();
- undo_redo->create_action(TTR("Change Anchors and Offsets"));
+ undo_redo->create_action(TTR("Change Anchors, Offsets (Keep Ratio)"));
for (Node *E : selection) {
Control *control = Object::cast_to<Control>(E);
@@ -521,44 +767,41 @@ void ControlEditorToolbar::_set_anchors_and_offsets_to_keep_ratio() {
undo_redo->commit_action();
}
-void ControlEditorToolbar::_set_anchors_preset(Control::LayoutPreset p_preset) {
- List<Node *> selection = editor_selection->get_selected_node_list();
+void ControlEditorToolbar::_anchor_mode_toggled(bool p_status) {
+ List<Control *> selection = _get_edited_controls();
+ for (Control *E : selection) {
+ if (Object::cast_to<Container>(E->get_parent())) {
+ continue;
+ }
- undo_redo->create_action(TTR("Change Anchors"));
- for (Node *E : selection) {
- Control *control = Object::cast_to<Control>(E);
- if (control) {
- undo_redo->add_do_method(control, "set_anchors_preset", p_preset);
- undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state());
+ if (p_status) {
+ E->set_meta("_edit_use_anchors_", true);
+ } else {
+ E->remove_meta("_edit_use_anchors_");
}
}
- undo_redo->commit_action();
+ anchors_mode = p_status;
+ CanvasItemEditor::get_singleton()->update_viewport();
}
-void ControlEditorToolbar::_set_container_h_preset(Control::SizeFlags p_preset) {
+void ControlEditorToolbar::_container_flags_selected(int p_flags, bool p_vertical) {
List<Node *> selection = editor_selection->get_selected_node_list();
- undo_redo->create_action(TTR("Change Horizontal Size Flags"));
- for (Node *E : selection) {
- Control *control = Object::cast_to<Control>(E);
- if (control) {
- undo_redo->add_do_method(control, "set_h_size_flags", p_preset);
- undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state());
- }
+ if (p_vertical) {
+ undo_redo->create_action(TTR("Change Vertical Size Flags"));
+ } else {
+ undo_redo->create_action(TTR("Change Horizontal Size Flags"));
}
- undo_redo->commit_action();
-}
-
-void ControlEditorToolbar::_set_container_v_preset(Control::SizeFlags p_preset) {
- List<Node *> selection = editor_selection->get_selected_node_list();
-
- undo_redo->create_action(TTR("Change Horizontal Size Flags"));
for (Node *E : selection) {
Control *control = Object::cast_to<Control>(E);
if (control) {
- undo_redo->add_do_method(control, "set_v_size_flags", p_preset);
+ if (p_vertical) {
+ undo_redo->add_do_method(control, "set_v_size_flags", p_flags);
+ } else {
+ undo_redo->add_do_method(control, "set_h_size_flags", p_flags);
+ }
undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state());
}
}
@@ -594,400 +837,205 @@ Vector2 ControlEditorToolbar::_position_to_anchor(const Control *p_control, Vect
return output;
}
-void ControlEditorToolbar::_button_toggle_anchor_mode(bool p_status) {
- List<Control *> selection = _get_edited_controls(false, false);
- for (Control *E : selection) {
- if (Object::cast_to<Container>(E->get_parent())) {
- continue;
- }
-
- if (p_status) {
- E->set_meta("_edit_use_anchors_", true);
- } else {
- E->remove_meta("_edit_use_anchors_");
- }
- }
-
- anchors_mode = p_status;
- CanvasItemEditor::get_singleton()->update_viewport();
-}
-
bool ControlEditorToolbar::_is_node_locked(const Node *p_node) {
return p_node->get_meta("_edit_lock_", false);
}
-List<Control *> ControlEditorToolbar::_get_edited_controls(bool retrieve_locked, bool remove_controls_if_parent_in_selection) {
+List<Control *> ControlEditorToolbar::_get_edited_controls() {
List<Control *> selection;
for (const KeyValue<Node *, Object *> &E : editor_selection->get_selection()) {
Control *control = Object::cast_to<Control>(E.key);
- if (control && control->is_visible_in_tree() && control->get_viewport() == EditorNode::get_singleton()->get_scene_root() && (retrieve_locked || !_is_node_locked(control))) {
+ if (control && control->is_visible_in_tree() && control->get_viewport() == EditorNode::get_singleton()->get_scene_root() && !_is_node_locked(control)) {
selection.push_back(control);
}
}
- if (remove_controls_if_parent_in_selection) {
- List<Control *> filtered_selection;
- for (Control *E : selection) {
- if (!selection.find(E->get_parent())) {
- filtered_selection.push_back(E);
- }
- }
- return filtered_selection;
- }
-
return selection;
}
-void ControlEditorToolbar::_popup_callback(int p_op) {
- switch (p_op) {
- case ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT: {
- _set_anchors_and_offsets_preset(PRESET_TOP_LEFT);
- } break;
- case ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT: {
- _set_anchors_and_offsets_preset(PRESET_TOP_RIGHT);
- } break;
- case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT: {
- _set_anchors_and_offsets_preset(PRESET_BOTTOM_LEFT);
- } break;
- case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT: {
- _set_anchors_and_offsets_preset(PRESET_BOTTOM_RIGHT);
- } break;
- case ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT: {
- _set_anchors_and_offsets_preset(PRESET_CENTER_LEFT);
- } break;
- case ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT: {
- _set_anchors_and_offsets_preset(PRESET_CENTER_RIGHT);
- } break;
- case ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP: {
- _set_anchors_and_offsets_preset(PRESET_CENTER_TOP);
- } break;
- case ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM: {
- _set_anchors_and_offsets_preset(PRESET_CENTER_BOTTOM);
- } break;
- case ANCHORS_AND_OFFSETS_PRESET_CENTER: {
- _set_anchors_and_offsets_preset(PRESET_CENTER);
- } break;
- case ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE: {
- _set_anchors_and_offsets_preset(PRESET_TOP_WIDE);
- } break;
- case ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE: {
- _set_anchors_and_offsets_preset(PRESET_LEFT_WIDE);
- } break;
- case ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE: {
- _set_anchors_and_offsets_preset(PRESET_RIGHT_WIDE);
- } break;
- case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE: {
- _set_anchors_and_offsets_preset(PRESET_BOTTOM_WIDE);
- } break;
- case ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE: {
- _set_anchors_and_offsets_preset(PRESET_VCENTER_WIDE);
- } break;
- case ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE: {
- _set_anchors_and_offsets_preset(PRESET_HCENTER_WIDE);
- } break;
- case ANCHORS_AND_OFFSETS_PRESET_FULL_RECT: {
- _set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
- } break;
- case ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO: {
- _set_anchors_and_offsets_to_keep_ratio();
- } break;
-
- case ANCHORS_PRESET_TOP_LEFT: {
- _set_anchors_preset(PRESET_TOP_LEFT);
- } break;
- case ANCHORS_PRESET_TOP_RIGHT: {
- _set_anchors_preset(PRESET_TOP_RIGHT);
- } break;
- case ANCHORS_PRESET_BOTTOM_LEFT: {
- _set_anchors_preset(PRESET_BOTTOM_LEFT);
- } break;
- case ANCHORS_PRESET_BOTTOM_RIGHT: {
- _set_anchors_preset(PRESET_BOTTOM_RIGHT);
- } break;
- case ANCHORS_PRESET_CENTER_LEFT: {
- _set_anchors_preset(PRESET_CENTER_LEFT);
- } break;
- case ANCHORS_PRESET_CENTER_RIGHT: {
- _set_anchors_preset(PRESET_CENTER_RIGHT);
- } break;
- case ANCHORS_PRESET_CENTER_TOP: {
- _set_anchors_preset(PRESET_CENTER_TOP);
- } break;
- case ANCHORS_PRESET_CENTER_BOTTOM: {
- _set_anchors_preset(PRESET_CENTER_BOTTOM);
- } break;
- case ANCHORS_PRESET_CENTER: {
- _set_anchors_preset(PRESET_CENTER);
- } break;
- case ANCHORS_PRESET_TOP_WIDE: {
- _set_anchors_preset(PRESET_TOP_WIDE);
- } break;
- case ANCHORS_PRESET_LEFT_WIDE: {
- _set_anchors_preset(PRESET_LEFT_WIDE);
- } break;
- case ANCHORS_PRESET_RIGHT_WIDE: {
- _set_anchors_preset(PRESET_RIGHT_WIDE);
- } break;
- case ANCHORS_PRESET_BOTTOM_WIDE: {
- _set_anchors_preset(PRESET_BOTTOM_WIDE);
- } break;
- case ANCHORS_PRESET_VCENTER_WIDE: {
- _set_anchors_preset(PRESET_VCENTER_WIDE);
- } break;
- case ANCHORS_PRESET_HCENTER_WIDE: {
- _set_anchors_preset(PRESET_HCENTER_WIDE);
- } break;
- case ANCHORS_PRESET_FULL_RECT: {
- _set_anchors_preset(Control::PRESET_FULL_RECT);
- } break;
-
- case CONTAINERS_H_PRESET_FILL: {
- _set_container_h_preset(Control::SIZE_FILL);
- } break;
- case CONTAINERS_H_PRESET_FILL_EXPAND: {
- _set_container_h_preset(Control::SIZE_EXPAND_FILL);
- } break;
- case CONTAINERS_H_PRESET_SHRINK_BEGIN: {
- _set_container_h_preset(Control::SIZE_SHRINK_BEGIN);
- } break;
- case CONTAINERS_H_PRESET_SHRINK_CENTER: {
- _set_container_h_preset(Control::SIZE_SHRINK_CENTER);
- } break;
- case CONTAINERS_H_PRESET_SHRINK_END: {
- _set_container_h_preset(Control::SIZE_SHRINK_END);
- } break;
-
- case CONTAINERS_V_PRESET_FILL: {
- _set_container_v_preset(Control::SIZE_FILL);
- } break;
- case CONTAINERS_V_PRESET_FILL_EXPAND: {
- _set_container_v_preset(Control::SIZE_EXPAND_FILL);
- } break;
- case CONTAINERS_V_PRESET_SHRINK_BEGIN: {
- _set_container_v_preset(Control::SIZE_SHRINK_BEGIN);
- } break;
- case CONTAINERS_V_PRESET_SHRINK_CENTER: {
- _set_container_v_preset(Control::SIZE_SHRINK_CENTER);
- } break;
- case CONTAINERS_V_PRESET_SHRINK_END: {
- _set_container_v_preset(Control::SIZE_SHRINK_END);
- } break;
- }
-}
-
void ControlEditorToolbar::_selection_changed() {
- // Update the anchors_mode.
- int nb_controls = 0;
- int nb_valid_controls = 0;
- int nb_anchors_mode = 0;
+ // Update toolbar visibility.
+ bool has_controls = false;
+ bool has_control_parents = false;
+ bool has_container_parents = false;
+
+ // Also update which size flags can be configured for the selected nodes.
+ Vector<SizeFlags> allowed_h_flags = {
+ SIZE_SHRINK_BEGIN,
+ SIZE_SHRINK_CENTER,
+ SIZE_SHRINK_END,
+ SIZE_FILL,
+ SIZE_EXPAND,
+ };
+ Vector<SizeFlags> allowed_v_flags = {
+ SIZE_SHRINK_BEGIN,
+ SIZE_SHRINK_CENTER,
+ SIZE_SHRINK_END,
+ SIZE_FILL,
+ SIZE_EXPAND,
+ };
- List<Node *> selection = editor_selection->get_selected_node_list();
- for (Node *E : selection) {
- Control *control = Object::cast_to<Control>(E);
+ for (const KeyValue<Node *, Object *> &E : editor_selection->get_selection()) {
+ Control *control = Object::cast_to<Control>(E.key);
if (!control) {
continue;
}
+ has_controls = true;
- nb_controls++;
+ if (Object::cast_to<Control>(control->get_parent())) {
+ has_control_parents = true;
+ }
if (Object::cast_to<Container>(control->get_parent())) {
- continue;
+ has_container_parents = true;
+
+ Container *parent_container = Object::cast_to<Container>(control->get_parent());
+
+ Vector<int> container_h_flags = parent_container->get_allowed_size_flags_horizontal();
+ Vector<SizeFlags> tmp_flags = allowed_h_flags.duplicate();
+ for (int i = 0; i < allowed_h_flags.size(); i++) {
+ if (!container_h_flags.has((int)allowed_h_flags[i])) {
+ tmp_flags.erase(allowed_h_flags[i]);
+ }
+ }
+ allowed_h_flags = tmp_flags;
+
+ Vector<int> container_v_flags = parent_container->get_allowed_size_flags_vertical();
+ tmp_flags = allowed_v_flags.duplicate();
+ for (int i = 0; i < allowed_v_flags.size(); i++) {
+ if (!container_v_flags.has((int)allowed_v_flags[i])) {
+ tmp_flags.erase(allowed_v_flags[i]);
+ }
+ }
+ allowed_v_flags = tmp_flags;
}
+ }
+
+ // Set general toolbar visibility.
+ set_visible(has_controls);
+
+ // Set anchor tools visibility.
+ if (has_controls && (!has_control_parents || !has_container_parents)) {
+ anchors_button->set_visible(true);
+ anchor_mode_button->set_visible(true);
+
+ // Update anchor mode.
+ int nb_valid_controls = 0;
+ int nb_anchors_mode = 0;
- nb_valid_controls++;
- if (control->get_meta("_edit_use_anchors_", false)) {
- nb_anchors_mode++;
+ List<Node *> selection = editor_selection->get_selected_node_list();
+ for (Node *E : selection) {
+ Control *control = Object::cast_to<Control>(E);
+ if (!control) {
+ continue;
+ }
+ if (Object::cast_to<Container>(control->get_parent())) {
+ continue;
+ }
+
+ nb_valid_controls++;
+ if (control->get_meta("_edit_use_anchors_", false)) {
+ nb_anchors_mode++;
+ }
}
+
+ anchors_mode = (nb_valid_controls == nb_anchors_mode);
+ anchor_mode_button->set_pressed(anchors_mode);
+ } else {
+ anchors_button->set_visible(false);
+ anchor_mode_button->set_visible(false);
+ anchor_mode_button->set_pressed(false);
}
- anchors_mode = (nb_valid_controls == nb_anchors_mode);
- anchor_mode_button->set_pressed(anchors_mode);
+ // Set container tools visibility.
+ if (has_controls && (!has_control_parents || has_container_parents)) {
+ containers_button->set_visible(true);
- if (nb_controls > 0) {
- set_physics_process(true);
+ // Update allowed size flags.
+ if (has_container_parents) {
+ container_h_picker->set_allowed_flags(allowed_h_flags);
+ container_v_picker->set_allowed_flags(allowed_v_flags);
+ } else {
+ Vector<SizeFlags> allowed_all_flags = {
+ SIZE_SHRINK_BEGIN,
+ SIZE_SHRINK_CENTER,
+ SIZE_SHRINK_END,
+ SIZE_FILL,
+ SIZE_EXPAND,
+ };
+
+ container_h_picker->set_allowed_flags(allowed_all_flags);
+ container_v_picker->set_allowed_flags(allowed_all_flags);
+ }
} else {
- set_physics_process(false);
- set_visible(false);
+ containers_button->set_visible(false);
}
}
void ControlEditorToolbar::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE:
- case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
- anchor_presets_menu->set_icon(get_theme_icon(SNAME("ControlLayout"), SNAME("EditorIcons")));
-
- PopupMenu *p = anchor_presets_menu->get_popup();
- p->clear();
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopLeft"), SNAME("EditorIcons")), TTR("Top Left"), ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT);
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopRight"), SNAME("EditorIcons")), TTR("Top Right"), ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT);
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomRight"), SNAME("EditorIcons")), TTR("Bottom Right"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT);
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomLeft"), SNAME("EditorIcons")), TTR("Bottom Left"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT);
- p->add_separator();
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterLeft"), SNAME("EditorIcons")), TTR("Center Left"), ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT);
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterTop"), SNAME("EditorIcons")), TTR("Center Top"), ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP);
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons")), TTR("Center Right"), ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT);
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterBottom"), SNAME("EditorIcons")), TTR("Center Bottom"), ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM);
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Center"), ANCHORS_AND_OFFSETS_PRESET_CENTER);
- p->add_separator();
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftWide"), SNAME("EditorIcons")), TTR("Left Wide"), ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE);
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopWide"), SNAME("EditorIcons")), TTR("Top Wide"), ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE);
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignRightWide"), SNAME("EditorIcons")), TTR("Right Wide"), ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE);
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomWide"), SNAME("EditorIcons")), TTR("Bottom Wide"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE);
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons")), TTR("VCenter Wide"), ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE);
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons")), TTR("HCenter Wide"), ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE);
- p->add_separator();
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignFullRect"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_AND_OFFSETS_PRESET_FULL_RECT);
- p->add_icon_item(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons")), TTR("Keep Current Ratio"), ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO);
- p->set_item_tooltip(19, TTR("Adjust anchors and offsets to match the current rect size."));
-
- p->add_separator();
- p->add_submenu_item(TTR("Anchors only"), "Anchors");
- p->set_item_icon(21, get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons")));
-
- anchors_popup->clear();
- anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopLeft"), SNAME("EditorIcons")), TTR("Top Left"), ANCHORS_PRESET_TOP_LEFT);
- anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopRight"), SNAME("EditorIcons")), TTR("Top Right"), ANCHORS_PRESET_TOP_RIGHT);
- anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomRight"), SNAME("EditorIcons")), TTR("Bottom Right"), ANCHORS_PRESET_BOTTOM_RIGHT);
- anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomLeft"), SNAME("EditorIcons")), TTR("Bottom Left"), ANCHORS_PRESET_BOTTOM_LEFT);
- anchors_popup->add_separator();
- anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterLeft"), SNAME("EditorIcons")), TTR("Center Left"), ANCHORS_PRESET_CENTER_LEFT);
- anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterTop"), SNAME("EditorIcons")), TTR("Center Top"), ANCHORS_PRESET_CENTER_TOP);
- anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons")), TTR("Center Right"), ANCHORS_PRESET_CENTER_RIGHT);
- anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterBottom"), SNAME("EditorIcons")), TTR("Center Bottom"), ANCHORS_PRESET_CENTER_BOTTOM);
- anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Center"), ANCHORS_PRESET_CENTER);
- anchors_popup->add_separator();
- anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftWide"), SNAME("EditorIcons")), TTR("Left Wide"), ANCHORS_PRESET_LEFT_WIDE);
- anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopWide"), SNAME("EditorIcons")), TTR("Top Wide"), ANCHORS_PRESET_TOP_WIDE);
- anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignRightWide"), SNAME("EditorIcons")), TTR("Right Wide"), ANCHORS_PRESET_RIGHT_WIDE);
- anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomWide"), SNAME("EditorIcons")), TTR("Bottom Wide"), ANCHORS_PRESET_BOTTOM_WIDE);
- anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons")), TTR("VCenter Wide"), ANCHORS_PRESET_VCENTER_WIDE);
- anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons")), TTR("HCenter Wide"), ANCHORS_PRESET_HCENTER_WIDE);
- anchors_popup->add_separator();
- anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignFullRect"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_PRESET_FULL_RECT);
-
+ case NOTIFICATION_THEME_CHANGED: {
+ anchors_button->set_icon(get_theme_icon(SNAME("ControlLayout"), SNAME("EditorIcons")));
anchor_mode_button->set_icon(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons")));
-
- container_h_presets_menu->set_icon(get_theme_icon(SNAME("Container"), SNAME("EditorIcons")));
- container_v_presets_menu->set_icon(get_theme_icon(SNAME("Container"), SNAME("EditorIcons")));
-
- p = container_h_presets_menu->get_popup();
- p->clear();
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons")), TTR("Fill"), CONTAINERS_H_PRESET_FILL);
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons")), TTR("Fill & Expand"), CONTAINERS_H_PRESET_FILL_EXPAND);
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterLeft"), SNAME("EditorIcons")), TTR("Shrink Begin"), CONTAINERS_H_PRESET_SHRINK_BEGIN);
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Shrink Center"), CONTAINERS_H_PRESET_SHRINK_CENTER);
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons")), TTR("Shrink End"), CONTAINERS_H_PRESET_SHRINK_END);
-
- p = container_v_presets_menu->get_popup();
- p->clear();
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons")), TTR("Fill"), CONTAINERS_V_PRESET_FILL);
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons")), TTR("Fill & Expand"), CONTAINERS_V_PRESET_FILL_EXPAND);
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterTop"), SNAME("EditorIcons")), TTR("Shrink Begin"), CONTAINERS_V_PRESET_SHRINK_BEGIN);
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Shrink Center"), CONTAINERS_V_PRESET_SHRINK_CENTER);
- p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterBottom"), SNAME("EditorIcons")), TTR("Shrink End"), CONTAINERS_V_PRESET_SHRINK_END);
- } break;
-
- case NOTIFICATION_PHYSICS_PROCESS: {
- bool has_control_parents = false;
- bool has_container_parents = false;
-
- // Update the viewport if the canvas_item changes
- List<Control *> selection = _get_edited_controls(true);
- for (Control *control : selection) {
- if (Object::cast_to<Control>(control->get_parent())) {
- has_control_parents = true;
- }
- if (Object::cast_to<Container>(control->get_parent())) {
- has_container_parents = true;
- }
- }
-
- // Show / Hide the control layout buttons.
- if (selection.size() > 0) {
- set_visible(true);
-
- // Toggle anchor and container layout buttons depending on parents of the selected nodes.
- // - If there are no control parents, enable everything.
- // - If there are container parents, then enable only container buttons.
- // - If there are NO container parents, then enable only anchor buttons.
- bool enable_anchors = false;
- bool enable_containers = false;
- if (!has_control_parents) {
- enable_anchors = true;
- enable_containers = true;
- } else if (has_container_parents) {
- enable_containers = true;
- } else {
- enable_anchors = true;
- }
-
- if (enable_anchors) {
- anchor_presets_menu->set_visible(true);
- anchor_mode_button->set_visible(true);
- } else {
- anchor_presets_menu->set_visible(false);
- anchor_mode_button->set_visible(false);
- }
-
- if (enable_containers) {
- container_h_presets_menu->set_visible(true);
- container_v_presets_menu->set_visible(true);
- } else {
- container_h_presets_menu->set_visible(false);
- container_v_presets_menu->set_visible(false);
- }
- } else {
- set_visible(false);
- }
+ containers_button->set_icon(get_theme_icon(SNAME("ContainerLayout"), SNAME("EditorIcons")));
} break;
}
}
ControlEditorToolbar::ControlEditorToolbar() {
- anchor_presets_menu = memnew(MenuButton);
- anchor_presets_menu->set_shortcut_context(this);
- anchor_presets_menu->set_text(TTR("Anchors"));
- anchor_presets_menu->set_tooltip(TTR("Presets for the anchor and offset values of a Control node."));
- add_child(anchor_presets_menu);
- anchor_presets_menu->set_switch_on_hover(true);
+ add_child(memnew(VSeparator));
- PopupMenu *p = anchor_presets_menu->get_popup();
- p->connect("id_pressed", callable_mp(this, &ControlEditorToolbar::_popup_callback));
+ // Anchor and offset tools.
+ anchors_button = memnew(ControlEditorPopupButton);
+ anchors_button->set_tooltip(TTR("Presets for the anchor and offset values of a Control node."));
+ add_child(anchors_button);
- anchors_popup = memnew(PopupMenu);
- p->add_child(anchors_popup);
- anchors_popup->set_name("Anchors");
- anchors_popup->connect("id_pressed", callable_mp(this, &ControlEditorToolbar::_popup_callback));
+ Label *anchors_label = memnew(Label);
+ anchors_label->set_text(TTR("Anchor preset"));
+ anchors_button->get_popup_hbox()->add_child(anchors_label);
+ AnchorPresetPicker *anchors_picker = memnew(AnchorPresetPicker);
+ anchors_picker->set_h_size_flags(SIZE_SHRINK_CENTER);
+ anchors_button->get_popup_hbox()->add_child(anchors_picker);
+ anchors_picker->connect("anchors_preset_selected", callable_mp(this, &ControlEditorToolbar::_anchors_preset_selected));
+
+ anchors_button->get_popup_hbox()->add_child(memnew(HSeparator));
+
+ Button *keep_ratio_button = memnew(Button);
+ keep_ratio_button->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);
+ keep_ratio_button->set_text(TTR("Set to Current Ratio"));
+ keep_ratio_button->set_tooltip(TTR("Adjust anchors and offsets to match the current rect size."));
+ anchors_button->get_popup_hbox()->add_child(keep_ratio_button);
+ keep_ratio_button->connect("pressed", callable_mp(this, &ControlEditorToolbar::_anchors_to_current_ratio));
anchor_mode_button = memnew(Button);
anchor_mode_button->set_flat(true);
anchor_mode_button->set_toggle_mode(true);
anchor_mode_button->set_tooltip(TTR("When active, moving Control nodes changes their anchors instead of their offsets."));
add_child(anchor_mode_button);
- anchor_mode_button->connect("toggled", callable_mp(this, &ControlEditorToolbar::_button_toggle_anchor_mode));
-
- add_child(memnew(VSeparator));
-
- container_h_presets_menu = memnew(MenuButton);
- container_h_presets_menu->set_shortcut_context(this);
- container_h_presets_menu->set_text(TTR("Horizontal"));
- container_h_presets_menu->set_tooltip(TTR("Horizontal sizing setting for children of a Container node."));
- add_child(container_h_presets_menu);
- container_h_presets_menu->set_switch_on_hover(true);
-
- p = container_h_presets_menu->get_popup();
- p->connect("id_pressed", callable_mp(this, &ControlEditorToolbar::_popup_callback));
-
- container_v_presets_menu = memnew(MenuButton);
- container_v_presets_menu->set_shortcut_context(this);
- container_v_presets_menu->set_text(TTR("Vertical"));
- container_v_presets_menu->set_tooltip(TTR("Vertical sizing setting for children of a Container node."));
- add_child(container_v_presets_menu);
- container_v_presets_menu->set_switch_on_hover(true);
-
- p = container_v_presets_menu->get_popup();
- p->connect("id_pressed", callable_mp(this, &ControlEditorToolbar::_popup_callback));
-
+ anchor_mode_button->connect("toggled", callable_mp(this, &ControlEditorToolbar::_anchor_mode_toggled));
+
+ // Container tools.
+ containers_button = memnew(ControlEditorPopupButton);
+ containers_button->set_tooltip(TTR("Sizing settings for children of a Container node."));
+ add_child(containers_button);
+
+ Label *container_h_label = memnew(Label);
+ container_h_label->set_text(TTR("Horizontal alignment"));
+ containers_button->get_popup_hbox()->add_child(container_h_label);
+ container_h_picker = memnew(SizeFlagPresetPicker(false));
+ containers_button->get_popup_hbox()->add_child(container_h_picker);
+ container_h_picker->connect("size_flags_selected", callable_mp(this, &ControlEditorToolbar::_container_flags_selected).bind(false));
+
+ containers_button->get_popup_hbox()->add_child(memnew(HSeparator));
+
+ Label *container_v_label = memnew(Label);
+ container_v_label->set_text(TTR("Vertical alignment"));
+ containers_button->get_popup_hbox()->add_child(container_v_label);
+ container_v_picker = memnew(SizeFlagPresetPicker(true));
+ containers_button->get_popup_hbox()->add_child(container_v_picker);
+ container_v_picker->connect("size_flags_selected", callable_mp(this, &ControlEditorToolbar::_container_flags_selected).bind(true));
+
+ // Editor connections.
undo_redo = EditorNode::get_singleton()->get_undo_redo();
editor_selection = EditorNode::get_singleton()->get_editor_selection();
editor_selection->add_editor_plugin(this);
@@ -998,6 +1046,8 @@ ControlEditorToolbar::ControlEditorToolbar() {
ControlEditorToolbar *ControlEditorToolbar::singleton = nullptr;
+// Editor plugin.
+
ControlEditorPlugin::ControlEditorPlugin() {
toolbar = memnew(ControlEditorToolbar);
toolbar->hide();
diff --git a/editor/plugins/control_editor_plugin.h b/editor/plugins/control_editor_plugin.h
index 11389bc095..f1b9190a0b 100644
--- a/editor/plugins/control_editor_plugin.h
+++ b/editor/plugins/control_editor_plugin.h
@@ -33,14 +33,18 @@
#include "editor/editor_plugin.h"
#include "scene/gui/box_container.h"
+#include "scene/gui/button.h"
#include "scene/gui/check_box.h"
#include "scene/gui/control.h"
#include "scene/gui/label.h"
#include "scene/gui/margin_container.h"
#include "scene/gui/option_button.h"
#include "scene/gui/panel_container.h"
+#include "scene/gui/popup.h"
+#include "scene/gui/separator.h"
#include "scene/gui/texture_rect.h"
+// Inspector controls.
class ControlPositioningWarning : public MarginContainer {
GDCLASS(ControlPositioningWarning, MarginContainer);
@@ -125,102 +129,101 @@ public:
virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide = false) override;
};
+// Toolbar controls.
+class ControlEditorPopupButton : public Button {
+ GDCLASS(ControlEditorPopupButton, Button);
+
+ Ref<Texture2D> arrow_icon;
+
+ PopupPanel *popup_panel = nullptr;
+ VBoxContainer *popup_vbox = nullptr;
+
+ void _popup_visibility_changed(bool p_visible);
+
+protected:
+ void _notification(int p_what);
+
+public:
+ virtual Size2 get_minimum_size() const override;
+ virtual void toggled(bool p_pressed) override;
+
+ VBoxContainer *get_popup_hbox() const { return popup_vbox; }
+
+ ControlEditorPopupButton();
+};
+
+class ControlEditorPresetPicker : public MarginContainer {
+ GDCLASS(ControlEditorPresetPicker, MarginContainer);
+
+ virtual void _preset_button_pressed(const int p_preset) {}
+
+protected:
+ static constexpr int grid_separation = 0;
+ HashMap<int, Button *> preset_buttons;
+
+ void _add_row_button(HBoxContainer *p_row, const int p_preset, const String &p_name);
+ void _add_separator(BoxContainer *p_box, Separator *p_separator);
+
+public:
+ ControlEditorPresetPicker() {}
+};
+
+class AnchorPresetPicker : public ControlEditorPresetPicker {
+ GDCLASS(AnchorPresetPicker, ControlEditorPresetPicker);
+
+ virtual void _preset_button_pressed(const int p_preset) override;
+
+protected:
+ void _notification(int p_notification);
+ static void _bind_methods();
+
+public:
+ AnchorPresetPicker();
+};
+
+class SizeFlagPresetPicker : public ControlEditorPresetPicker {
+ GDCLASS(SizeFlagPresetPicker, ControlEditorPresetPicker);
+
+ CheckBox *expand_button;
+
+ bool vertical = false;
+
+ virtual void _preset_button_pressed(const int p_preset) override;
+
+protected:
+ void _notification(int p_notification);
+ static void _bind_methods();
+
+public:
+ void set_allowed_flags(Vector<SizeFlags> &p_flags);
+
+ SizeFlagPresetPicker(bool p_vertical);
+};
+
class ControlEditorToolbar : public HBoxContainer {
GDCLASS(ControlEditorToolbar, HBoxContainer);
UndoRedo *undo_redo = nullptr;
EditorSelection *editor_selection = nullptr;
- enum MenuOption {
- ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT,
- ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT,
- ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT,
- ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT,
- ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT,
- ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT,
- ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP,
- ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM,
- ANCHORS_AND_OFFSETS_PRESET_CENTER,
- ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE,
- ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE,
- ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE,
- ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE,
- ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE,
- ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE,
- ANCHORS_AND_OFFSETS_PRESET_FULL_RECT,
-
- ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO,
-
- ANCHORS_PRESET_TOP_LEFT,
- ANCHORS_PRESET_TOP_RIGHT,
- ANCHORS_PRESET_BOTTOM_LEFT,
- ANCHORS_PRESET_BOTTOM_RIGHT,
- ANCHORS_PRESET_CENTER_LEFT,
- ANCHORS_PRESET_CENTER_RIGHT,
- ANCHORS_PRESET_CENTER_TOP,
- ANCHORS_PRESET_CENTER_BOTTOM,
- ANCHORS_PRESET_CENTER,
- ANCHORS_PRESET_TOP_WIDE,
- ANCHORS_PRESET_LEFT_WIDE,
- ANCHORS_PRESET_RIGHT_WIDE,
- ANCHORS_PRESET_BOTTOM_WIDE,
- ANCHORS_PRESET_VCENTER_WIDE,
- ANCHORS_PRESET_HCENTER_WIDE,
- ANCHORS_PRESET_FULL_RECT,
-
- // Offsets Presets are not currently in use.
- OFFSETS_PRESET_TOP_LEFT,
- OFFSETS_PRESET_TOP_RIGHT,
- OFFSETS_PRESET_BOTTOM_LEFT,
- OFFSETS_PRESET_BOTTOM_RIGHT,
- OFFSETS_PRESET_CENTER_LEFT,
- OFFSETS_PRESET_CENTER_RIGHT,
- OFFSETS_PRESET_CENTER_TOP,
- OFFSETS_PRESET_CENTER_BOTTOM,
- OFFSETS_PRESET_CENTER,
- OFFSETS_PRESET_TOP_WIDE,
- OFFSETS_PRESET_LEFT_WIDE,
- OFFSETS_PRESET_RIGHT_WIDE,
- OFFSETS_PRESET_BOTTOM_WIDE,
- OFFSETS_PRESET_VCENTER_WIDE,
- OFFSETS_PRESET_HCENTER_WIDE,
- OFFSETS_PRESET_FULL_RECT,
-
- CONTAINERS_H_PRESET_FILL,
- CONTAINERS_H_PRESET_FILL_EXPAND,
- CONTAINERS_H_PRESET_SHRINK_BEGIN,
- CONTAINERS_H_PRESET_SHRINK_CENTER,
- CONTAINERS_H_PRESET_SHRINK_END,
- CONTAINERS_V_PRESET_FILL,
- CONTAINERS_V_PRESET_FILL_EXPAND,
- CONTAINERS_V_PRESET_SHRINK_BEGIN,
- CONTAINERS_V_PRESET_SHRINK_CENTER,
- CONTAINERS_V_PRESET_SHRINK_END,
- };
-
- MenuButton *anchor_presets_menu = nullptr;
- PopupMenu *anchors_popup = nullptr;
- MenuButton *container_h_presets_menu = nullptr;
- MenuButton *container_v_presets_menu = nullptr;
-
+ ControlEditorPopupButton *anchors_button = nullptr;
+ ControlEditorPopupButton *containers_button = nullptr;
Button *anchor_mode_button = nullptr;
+ SizeFlagPresetPicker *container_h_picker = nullptr;
+ SizeFlagPresetPicker *container_v_picker = nullptr;
+
bool anchors_mode = false;
- void _set_anchors_preset(Control::LayoutPreset p_preset);
- void _set_anchors_and_offsets_preset(Control::LayoutPreset p_preset);
- void _set_anchors_and_offsets_to_keep_ratio();
- void _set_container_h_preset(Control::SizeFlags p_preset);
- void _set_container_v_preset(Control::SizeFlags p_preset);
+ void _anchors_preset_selected(int p_preset);
+ void _anchors_to_current_ratio();
+ void _anchor_mode_toggled(bool p_status);
+ void _container_flags_selected(int p_flags, bool p_vertical);
Vector2 _anchor_to_position(const Control *p_control, Vector2 anchor);
Vector2 _position_to_anchor(const Control *p_control, Vector2 position);
-
- void _button_toggle_anchor_mode(bool p_status);
-
bool _is_node_locked(const Node *p_node);
- List<Control *> _get_edited_controls(bool retrieve_locked = false, bool remove_controls_if_parent_in_selection = true);
- void _popup_callback(int p_op);
+ List<Control *> _get_edited_controls();
void _selection_changed();
protected:
@@ -236,6 +239,7 @@ public:
ControlEditorToolbar();
};
+// Editor plugin.
class ControlEditorPlugin : public EditorPlugin {
GDCLASS(ControlEditorPlugin, EditorPlugin);
diff --git a/editor/plugins/debugger_editor_plugin.cpp b/editor/plugins/debugger_editor_plugin.cpp
index c572b5b7fe..40e7dfead2 100644
--- a/editor/plugins/debugger_editor_plugin.cpp
+++ b/editor/plugins/debugger_editor_plugin.cpp
@@ -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);
diff --git a/editor/plugins/debugger_editor_plugin.h b/editor/plugins/debugger_editor_plugin.h
index fb963385cd..d8871128c3 100644
--- a/editor/plugins/debugger_editor_plugin.h
+++ b/editor/plugins/debugger_editor_plugin.h
@@ -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();
};
diff --git a/editor/plugins/font_config_plugin.cpp b/editor/plugins/font_config_plugin.cpp
index cadb974345..c7d3e92802 100644
--- a/editor/plugins/font_config_plugin.cpp
+++ b/editor/plugins/font_config_plugin.cpp
@@ -100,11 +100,6 @@ bool EditorPropertyFontOTObject::_get(const StringName &p_name, Variant &r_ret)
return false;
}
-void EditorPropertyFontOTObject::_bind_methods() {
- ClassDB::bind_method(D_METHOD("property_can_revert", "name"), &EditorPropertyFontOTObject::property_can_revert);
- ClassDB::bind_method(D_METHOD("property_get_revert", "name"), &EditorPropertyFontOTObject::property_get_revert);
-}
-
void EditorPropertyFontOTObject::set_dict(const Dictionary &p_dict) {
dict = p_dict;
}
@@ -121,7 +116,7 @@ Dictionary EditorPropertyFontOTObject::get_defaults() {
return defaults_dict;
}
-bool EditorPropertyFontOTObject::property_can_revert(const String &p_name) {
+bool EditorPropertyFontOTObject::_property_can_revert(const StringName &p_name) const {
String name = p_name;
if (name.begins_with("keys")) {
@@ -136,18 +131,19 @@ bool EditorPropertyFontOTObject::property_can_revert(const String &p_name) {
return false;
}
-Variant EditorPropertyFontOTObject::property_get_revert(const String &p_name) {
+bool EditorPropertyFontOTObject::_property_get_revert(const StringName &p_name, Variant &r_property) const {
String name = p_name;
if (name.begins_with("keys")) {
int key = name.get_slicec('/', 1).to_int();
if (defaults_dict.has(key)) {
Vector3i range = defaults_dict[key];
- return range.z;
+ r_property = range.z;
+ return true;
}
}
- return Variant();
+ return false;
}
/*************************************************************************/
diff --git a/editor/plugins/font_config_plugin.h b/editor/plugins/font_config_plugin.h
index 3eaa2fdc17..41dde3cc59 100644
--- a/editor/plugins/font_config_plugin.h
+++ b/editor/plugins/font_config_plugin.h
@@ -66,7 +66,8 @@ class EditorPropertyFontOTObject : public RefCounted {
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
- static void _bind_methods();
+ bool _property_can_revert(const StringName &p_name) const;
+ bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
public:
void set_dict(const Dictionary &p_dict);
@@ -75,9 +76,6 @@ public:
void set_defaults(const Dictionary &p_dict);
Dictionary get_defaults();
- bool property_can_revert(const String &p_name);
- Variant property_get_revert(const String &p_name);
-
EditorPropertyFontOTObject(){};
};
diff --git a/editor/project_converter_3_to_4.cpp b/editor/project_converter_3_to_4.cpp
index 5ce837f862..924b735012 100644
--- a/editor/project_converter_3_to_4.cpp
+++ b/editor/project_converter_3_to_4.cpp
@@ -1381,7 +1381,6 @@ static const char *class_renames[][2] = {
{ "Spatial", "Node3D" },
{ "SpatialGizmo", "Node3DGizmo" },
{ "SpatialMaterial", "StandardMaterial3D" },
- { "SpatialVelocityTracker", "VelocityTracker3D" },
{ "SphereShape", "SphereShape3D" },
{ "SpotLight", "SpotLight3D" },
{ "SpringArm", "SpringArm3D" },
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index d19a40599f..0f4f3dcfcf 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -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);
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index a23f19de85..e7299b4d3a 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -46,6 +46,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
bool prev_is_char = false;
bool prev_is_number = false;
+ bool prev_is_binary_op = false;
bool in_keyword = false;
bool in_word = false;
bool in_function_name = false;
@@ -84,16 +85,17 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
const int line_length = str.length();
Color prev_color;
- if (in_region != -1 && str.length() == 0) {
+ if (in_region != -1 && line_length == 0) {
color_region_cache[p_line] = in_region;
}
- for (int j = 0; j < str.length(); j++) {
+ for (int j = 0; j < line_length; j++) {
Dictionary highlighter_info;
color = font_color;
bool is_char = !is_symbol(str[j]);
bool is_a_symbol = is_symbol(str[j]);
bool is_number = is_digit(str[j]);
+ bool is_binary_op = false;
/* color regions */
if (is_a_symbol || in_region != -1) {
@@ -244,7 +246,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
is_hex_notation = false;
}
- // disallow anything not a 0 or 1
+ // disallow anything not a 0 or 1 in binary notation
if (is_bin_notation && (is_binary_digit(str[j]))) {
is_number = true;
} else if (is_bin_notation) {
@@ -285,7 +287,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
if (!in_keyword && is_char && !prev_is_char) {
int to = j;
- while (to < str.length() && !is_symbol(str[to])) {
+ while (to < line_length && !is_symbol(str[to])) {
to++;
}
@@ -318,12 +320,12 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
in_signal_declaration = true;
} else {
int k = j;
- while (k < str.length() && !is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') {
+ while (k < line_length && !is_symbol(str[k]) && !is_whitespace(str[k])) {
k++;
}
// check for space between name and bracket
- while (k < str.length() && (str[k] == '\t' || str[k] == ' ')) {
+ while (k < line_length && is_whitespace(str[k])) {
k++;
}
@@ -336,7 +338,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
// Check for lambda.
if (in_function_name && previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) {
k = j - 1;
- while (k > 0 && (str[k] == '\t' || str[k] == ' ')) {
+ while (k > 0 && is_whitespace(str[k])) {
k--;
}
@@ -349,7 +351,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
if (!in_function_name && !in_member_variable && !in_keyword && !is_number && in_word) {
int k = j;
- while (k > 0 && !is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') {
+ while (k > 0 && !is_symbol(str[k]) && !is_whitespace(str[k])) {
k--;
}
@@ -378,7 +380,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
if (in_variable_declaration || in_function_args) {
int k = j;
// Skip space
- while (k < str.length() && (str[k] == '\t' || str[k] == ' ')) {
+ while (k < line_length && is_whitespace(str[k])) {
k++;
}
@@ -395,13 +397,41 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
in_member_variable = false;
}
- if (!in_node_path && in_region == -1 && (str[j] == '^')) {
+ if (j > 0 && (str[j] == '&' || str[j] == '^' || str[j] == '%' || str[j] == '+' || str[j] == '-' || str[j] == '~')) {
+ int k = j - 1;
+ while (k > 0 && is_whitespace(str[k])) {
+ k--;
+ }
+ if (!is_symbol(str[k]) || str[k] == '"' || str[k] == '\'' || str[k] == ')' || str[k] == ']' || str[k] == '}') {
+ is_binary_op = true;
+ }
+ }
+
+ // Highlight '+' and '-' like numbers when unary
+ if ((str[j] == '+' || str[j] == '-' || str[j] == '~') && !is_binary_op) {
+ is_number = true;
+ is_a_symbol = false;
+ }
+
+ // Keep symbol color for binary '&&'. In the case of '&&&' use StringName color for the last ampersand
+ if (!in_string_name && in_region == -1 && str[j] == '&' && !is_binary_op) {
+ if (j >= 2 && str[j - 1] == '&' && str[j - 2] != '&' && prev_is_binary_op) {
+ is_binary_op = true;
+ } else if (j == 0 || (j > 0 && str[j - 1] != '&') || prev_is_binary_op) {
+ in_string_name = true;
+ }
+ } else if (in_region != -1 || is_a_symbol) {
+ in_string_name = false;
+ }
+
+ // '^^' has no special meaning, so unlike StringName, when binary, use NodePath color for the last caret
+ if (!in_node_path && in_region == -1 && str[j] == '^' && !is_binary_op && (j == 0 || (j > 0 && str[j - 1] != '^') || prev_is_binary_op)) {
in_node_path = true;
- } else if (in_region != -1 || (is_a_symbol && str[j] != '/' && str[j] != '%')) {
+ } else if (in_region != -1 || is_a_symbol) {
in_node_path = false;
}
- if (!in_node_ref && in_region == -1 && (str[j] == '$' || str[j] == '%')) {
+ if (!in_node_ref && in_region == -1 && (str[j] == '$' || (str[j] == '%' && !is_binary_op))) {
in_node_ref = true;
} else if (in_region != -1 || (is_a_symbol && str[j] != '/' && str[j] != '%')) {
in_node_ref = false;
@@ -413,16 +443,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
in_annotation = false;
}
- if (!in_string_name && in_region == -1 && str[j] == '&') {
- in_string_name = true;
- } else if (in_region != -1 || is_a_symbol) {
- in_string_name = false;
- }
-
- if (in_node_path) {
- next_type = NODE_PATH;
- color = node_path_color;
- } else if (in_node_ref) {
+ if (in_node_ref) {
next_type = NODE_REF;
color = node_ref_color;
} else if (in_annotation) {
@@ -431,6 +452,9 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
} else if (in_string_name) {
next_type = STRING_NAME;
color = string_name_color;
+ } else if (in_node_path) {
+ next_type = NODE_PATH;
+ color = node_path_color;
} else if (in_keyword) {
next_type = KEYWORD;
color = keyword_color;
@@ -487,6 +511,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
prev_is_char = is_char;
prev_is_number = is_number;
+ prev_is_binary_op = is_binary_op;
if (color != prev_color) {
prev_color = color;
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 8a5e93eeff..cf2d6ae9f8 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -1538,6 +1538,47 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const
}
}
+bool GDScriptInstance::property_can_revert(const StringName &p_name) const {
+ Variant name = p_name;
+ const Variant *args[1] = { &name };
+
+ const GDScript *sptr = script.ptr();
+ while (sptr) {
+ HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._property_can_revert);
+ if (E) {
+ Callable::CallError err;
+ Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err);
+ if (err.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::BOOL && ret.operator bool()) {
+ return true;
+ }
+ }
+ sptr = sptr->_base;
+ }
+
+ return false;
+}
+
+bool GDScriptInstance::property_get_revert(const StringName &p_name, Variant &r_ret) const {
+ Variant name = p_name;
+ const Variant *args[1] = { &name };
+
+ const GDScript *sptr = script.ptr();
+ while (sptr) {
+ HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._property_get_revert);
+ if (E) {
+ Callable::CallError err;
+ Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err);
+ if (err.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) {
+ r_ret = ret;
+ return true;
+ }
+ }
+ sptr = sptr->_base;
+ }
+
+ return false;
+}
+
void GDScriptInstance::get_method_list(List<MethodInfo> *p_list) const {
const GDScript *sptr = script.ptr();
while (sptr) {
@@ -2248,6 +2289,8 @@ GDScriptLanguage::GDScriptLanguage() {
strings._set = StaticCString::create("_set");
strings._get = StaticCString::create("_get");
strings._get_property_list = StaticCString::create("_get_property_list");
+ strings._property_can_revert = StaticCString::create("_property_can_revert");
+ strings._property_get_revert = StaticCString::create("_property_get_revert");
strings._script_source = StaticCString::create("script/source");
_debug_parse_err_line = -1;
_debug_parse_err_file = "";
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 5123cccddd..e4b12d4ddb 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -287,6 +287,9 @@ public:
virtual void get_property_list(List<PropertyInfo> *p_properties) const;
virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid = nullptr) const;
+ virtual bool property_can_revert(const StringName &p_name) const;
+ virtual bool property_get_revert(const StringName &p_name, Variant &r_ret) const;
+
virtual void get_method_list(List<MethodInfo> *p_list) const;
virtual bool has_method(const StringName &p_method) const;
virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error);
@@ -423,6 +426,8 @@ public:
StringName _set;
StringName _get;
StringName _get_property_list;
+ StringName _property_can_revert;
+ StringName _property_get_revert;
StringName _script_source;
} strings;
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 475b483d6c..b95b63cf1f 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -1853,6 +1853,74 @@ Variant::Type CSharpInstance::get_property_type(const StringName &p_name, bool *
return Variant::NIL;
}
+bool CSharpInstance::property_can_revert(const StringName &p_name) const {
+ ERR_FAIL_COND_V(!script.is_valid(), false);
+
+ GD_MONO_SCOPE_THREAD_ATTACH;
+
+ MonoObject *mono_object = get_mono_object();
+ ERR_FAIL_NULL_V(mono_object, false);
+
+ GDMonoClass *top = script->script_class;
+
+ while (top && top != script->native) {
+ GDMonoMethod *method = top->get_method(CACHED_STRING_NAME(_property_can_revert), 1);
+
+ if (method) {
+ Variant name = p_name;
+ const Variant *args[1] = { &name };
+
+ MonoObject *ret = method->invoke(mono_object, args);
+
+ if (ret) {
+ bool can_revert = GDMonoMarshal::mono_object_to_variant(ret);
+ if (can_revert) {
+ return true;
+ }
+ }
+
+ break;
+ }
+
+ top = top->get_parent_class();
+ }
+
+ return false;
+}
+
+bool CSharpInstance::property_get_revert(const StringName &p_name, Variant &r_ret) const {
+ ERR_FAIL_COND_V(!script.is_valid(), false);
+
+ GD_MONO_SCOPE_THREAD_ATTACH;
+
+ MonoObject *mono_object = get_mono_object();
+ ERR_FAIL_NULL_V(mono_object, false);
+
+ GDMonoClass *top = script->script_class;
+
+ while (top && top != script->native) {
+ GDMonoMethod *method = top->get_method(CACHED_STRING_NAME(_property_get_revert), 1);
+
+ if (method) {
+ Variant name = p_name;
+ const Variant *args[1] = { &name };
+
+ MonoObject *ret = method->invoke(mono_object, args);
+
+ if (ret) {
+ r_ret = GDMonoMarshal::mono_object_to_variant(ret);
+ return true;
+ }
+
+ break;
+ }
+
+ top = top->get_parent_class();
+ }
+
+ return false;
+}
+
void CSharpInstance::get_method_list(List<MethodInfo> *p_list) const {
if (!script->is_valid() || !script->script_class) {
return;
@@ -3705,6 +3773,8 @@ CSharpLanguage::StringNameCache::StringNameCache() {
_set = StaticCString::create("_set");
_get = StaticCString::create("_get");
_get_property_list = StaticCString::create("_get_property_list");
+ _property_can_revert = StaticCString::create("_property_can_revert");
+ _property_get_revert = StaticCString::create("_property_get_revert");
_notification = StaticCString::create("_notification");
_script_source = StaticCString::create("script/source");
on_before_serialize = StaticCString::create("OnBeforeSerialize");
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index 48129e69cb..823de91bf6 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -293,6 +293,9 @@ public:
void get_property_list(List<PropertyInfo> *p_properties) const override;
Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid) const override;
+ bool property_can_revert(const StringName &p_name) const override;
+ bool property_get_revert(const StringName &p_name, Variant &r_ret) const override;
+
void get_method_list(List<MethodInfo> *p_list) const override;
bool has_method(const StringName &p_method) const override;
Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
@@ -371,6 +374,8 @@ class CSharpLanguage : public ScriptLanguage {
StringName _set;
StringName _get;
StringName _get_property_list;
+ StringName _property_can_revert;
+ StringName _property_get_revert;
StringName _notification;
StringName _script_source;
StringName dotctor; // .ctor
diff --git a/modules/visual_script/visual_script.h b/modules/visual_script/visual_script.h
index 14cb14e8d9..d3a90d53fb 100644
--- a/modules/visual_script/visual_script.h
+++ b/modules/visual_script/visual_script.h
@@ -409,6 +409,9 @@ public:
virtual void get_property_list(List<PropertyInfo> *p_properties) const;
virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid = nullptr) const;
+ virtual bool property_can_revert(const StringName &p_name) const { return false; };
+ virtual bool property_get_revert(const StringName &p_name, Variant &r_ret) const { return false; };
+
virtual void get_method_list(List<MethodInfo> *p_list) const;
virtual bool has_method(const StringName &p_method) const;
virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error);
diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h
index 54c479fc81..5fda5dd974 100644
--- a/platform/macos/display_server_macos.h
+++ b/platform/macos/display_server_macos.h
@@ -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;
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index a49485154b..8142f44bb0 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -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];
+ if (menu == [NSApp mainMenu]) { // Do not add separators into main menu.
+ return -1;
+ }
+ if (p_index < 0) {
+ p_index = [menu numberOfItems];
} else {
- [menu addItem:[NSMenuItem separatorItem]];
+ 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.
diff --git a/platform/macos/godot_menu_item.h b/platform/macos/godot_menu_item.h
index 2c12897f10..e0b9f41632 100644
--- a/platform/macos/godot_menu_item.h
+++ b/platform/macos/godot_menu_item.h
@@ -46,7 +46,6 @@ enum GlobalMenuCheckType {
@public
Callable callback;
Variant meta;
- int id;
GlobalMenuCheckType checkable_type;
int max_states;
int state;
diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp
index 1de85d57a3..4ae834cf7a 100644
--- a/scene/3d/node_3d.cpp
+++ b/scene/3d/node_3d.cpp
@@ -897,7 +897,7 @@ void Node3D::_validate_property(PropertyInfo &property) const {
}
}
-bool Node3D::property_can_revert(const String &p_name) {
+bool Node3D::_property_can_revert(const StringName &p_name) const {
if (p_name == "basis") {
return true;
} else if (p_name == "scale") {
@@ -912,47 +912,48 @@ bool Node3D::property_can_revert(const String &p_name) {
return false;
}
-Variant Node3D::property_get_revert(const String &p_name) {
- Variant r_ret;
+bool Node3D::_property_get_revert(const StringName &p_name, Variant &r_property) const {
bool valid = false;
if (p_name == "basis") {
Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid);
if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) {
- r_ret = Transform3D(variant).get_basis();
+ r_property = Transform3D(variant).get_basis();
} else {
- r_ret = Basis();
+ r_property = Basis();
}
} else if (p_name == "scale") {
Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid);
if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) {
- r_ret = Transform3D(variant).get_basis().get_scale();
+ r_property = Transform3D(variant).get_basis().get_scale();
} else {
- return Vector3(1.0, 1.0, 1.0);
+ r_property = Vector3(1.0, 1.0, 1.0);
}
} else if (p_name == "quaternion") {
Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid);
if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) {
- r_ret = Quaternion(Transform3D(variant).get_basis().get_rotation_quaternion());
+ r_property = Quaternion(Transform3D(variant).get_basis().get_rotation_quaternion());
} else {
- return Quaternion();
+ r_property = Quaternion();
}
} else if (p_name == "rotation") {
Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid);
if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) {
- r_ret = Transform3D(variant).get_basis().get_euler_normalized(data.euler_rotation_order);
+ r_property = Transform3D(variant).get_basis().get_euler_normalized(data.euler_rotation_order);
} else {
- return Vector3();
+ r_property = Vector3();
}
} else if (p_name == "position") {
Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid);
if (valid) {
- r_ret = Transform3D(variant).get_origin();
+ r_property = Transform3D(variant).get_origin();
} else {
- return Vector3();
+ r_property = Vector3();
}
+ } else {
+ return false;
}
- return r_ret;
+ return true;
}
void Node3D::_bind_methods() {
@@ -1032,9 +1033,6 @@ void Node3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("to_local", "global_point"), &Node3D::to_local);
ClassDB::bind_method(D_METHOD("to_global", "local_point"), &Node3D::to_global);
- ClassDB::bind_method(D_METHOD("property_can_revert", "name"), &Node3D::property_can_revert);
- ClassDB::bind_method(D_METHOD("property_get_revert", "name"), &Node3D::property_get_revert);
-
BIND_CONSTANT(NOTIFICATION_TRANSFORM_CHANGED);
BIND_CONSTANT(NOTIFICATION_ENTER_WORLD);
BIND_CONSTANT(NOTIFICATION_EXIT_WORLD);
diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h
index b1e129798d..3d1c59445b 100644
--- a/scene/3d/node_3d.h
+++ b/scene/3d/node_3d.h
@@ -157,8 +157,8 @@ protected:
virtual void _validate_property(PropertyInfo &property) const override;
- bool property_can_revert(const String &p_name);
- Variant property_get_revert(const String &p_name);
+ bool _property_can_revert(const StringName &p_name) const;
+ bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
public:
enum {
diff --git a/scene/3d/velocity_tracker_3d.h b/scene/3d/velocity_tracker_3d.h
index 6b27cdffc2..d3b92ab766 100644
--- a/scene/3d/velocity_tracker_3d.h
+++ b/scene/3d/velocity_tracker_3d.h
@@ -34,8 +34,6 @@
#include "scene/3d/node_3d.h"
class VelocityTracker3D : public RefCounted {
- GDCLASS(VelocityTracker3D, RefCounted);
-
struct PositionHistory {
uint64_t frame = 0;
Vector3 position;
diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp
new file mode 100644
index 0000000000..ebe833c6e0
--- /dev/null
+++ b/scene/gui/menu_bar.cpp
@@ -0,0 +1,866 @@
+/*************************************************************************/
+/* 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() {
+ set_process_shortcut_input(true);
+}
+
+MenuBar::~MenuBar() {
+}
diff --git a/scene/gui/menu_bar.h b/scene/gui/menu_bar.h
new file mode 100644
index 0000000000..3c4a25fd06
--- /dev/null
+++ b/scene/gui/menu_bar.h
@@ -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
diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp
index 069a31d9d2..0252f25888 100644
--- a/scene/gui/menu_button.cpp
+++ b/scene/gui/menu_button.cpp
@@ -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) {
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index cd0d437051..6ef8158302 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -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) {
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index e203793c2e..d3ad0762e4 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -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);
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index a5842106fb..9979855232 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -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);
@@ -822,8 +824,6 @@ void register_scene_types() {
ClassDB::register_class<SkeletonModification3DStackHolder>();
OS::get_singleton()->yield(); // may take time to init
-
- GDREGISTER_CLASS(VelocityTracker3D);
#endif
GDREGISTER_CLASS(PhysicsMaterial);
@@ -843,7 +843,6 @@ void register_scene_types() {
GDREGISTER_CLASS(CurveXYZTexture);
GDREGISTER_CLASS(GradientTexture1D);
GDREGISTER_CLASS(GradientTexture2D);
- GDREGISTER_CLASS(ProxyTexture);
GDREGISTER_CLASS(AnimatedTexture);
GDREGISTER_CLASS(CameraTexture);
GDREGISTER_VIRTUAL_CLASS(TextureLayered);
@@ -1052,7 +1051,6 @@ void register_scene_types() {
ClassDB::add_compatibility_class("Spatial", "Node3D");
ClassDB::add_compatibility_class("SpatialGizmo", "Node3DGizmo");
ClassDB::add_compatibility_class("SpatialMaterial", "StandardMaterial3D");
- ClassDB::add_compatibility_class("SpatialVelocityTracker", "VelocityTracker3D");
ClassDB::add_compatibility_class("SphereShape", "SphereShape3D");
ClassDB::add_compatibility_class("SpotLight", "SpotLight3D");
ClassDB::add_compatibility_class("SpringArm", "SpringArm3D");
diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp
index f29cfec92f..b96ee5c6c4 100644
--- a/scene/resources/default_theme/default_theme.cpp
+++ b/scene/resources/default_theme/default_theme.cpp
@@ -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);
diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp
index 88bc01fb25..5d887dae17 100644
--- a/scene/resources/material.cpp
+++ b/scene/resources/material.cpp
@@ -297,7 +297,7 @@ void ShaderMaterial::_get_property_list(List<PropertyInfo> *p_list) const {
}
}
-bool ShaderMaterial::property_can_revert(const String &p_name) {
+bool ShaderMaterial::_property_can_revert(const StringName &p_name) const {
if (shader.is_valid()) {
StringName pr = shader->remap_uniform(p_name);
if (pr) {
@@ -310,15 +310,15 @@ bool ShaderMaterial::property_can_revert(const String &p_name) {
return false;
}
-Variant ShaderMaterial::property_get_revert(const String &p_name) {
- Variant r_ret;
+bool ShaderMaterial::_property_get_revert(const StringName &p_name, Variant &r_property) const {
if (shader.is_valid()) {
StringName pr = shader->remap_uniform(p_name);
if (pr) {
- r_ret = RenderingServer::get_singleton()->shader_get_param_default(shader->get_rid(), pr);
+ r_property = RenderingServer::get_singleton()->shader_get_param_default(shader->get_rid(), pr);
+ return true;
}
}
- return r_ret;
+ return false;
}
void ShaderMaterial::set_shader(const Ref<Shader> &p_shader) {
@@ -386,8 +386,6 @@ void ShaderMaterial::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_shader"), &ShaderMaterial::get_shader);
ClassDB::bind_method(D_METHOD("set_shader_uniform", "param", "value"), &ShaderMaterial::set_shader_uniform);
ClassDB::bind_method(D_METHOD("get_shader_uniform", "param"), &ShaderMaterial::get_shader_uniform);
- ClassDB::bind_method(D_METHOD("property_can_revert", "name"), &ShaderMaterial::property_can_revert);
- ClassDB::bind_method(D_METHOD("property_get_revert", "name"), &ShaderMaterial::property_get_revert);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shader", PROPERTY_HINT_RESOURCE_TYPE, "Shader"), "set_shader", "get_shader");
}
diff --git a/scene/resources/material.h b/scene/resources/material.h
index ca5b17dd07..6049671582 100644
--- a/scene/resources/material.h
+++ b/scene/resources/material.h
@@ -99,8 +99,8 @@ protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
- bool property_can_revert(const String &p_name);
- Variant property_get_revert(const String &p_name);
+ bool _property_can_revert(const StringName &p_name) const;
+ bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
static void _bind_methods();
diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp
index 05ed9238b8..e5c4974967 100644
--- a/scene/resources/texture.cpp
+++ b/scene/resources/texture.cpp
@@ -2537,13 +2537,6 @@ void GradientTexture2D::_bind_methods() {
//////////////////////////////////////
-void ProxyTexture::_bind_methods() {
- ClassDB::bind_method(D_METHOD("set_base", "base"), &ProxyTexture::set_base);
- ClassDB::bind_method(D_METHOD("get_base"), &ProxyTexture::get_base);
-
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "base", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_base", "get_base");
-}
-
void ProxyTexture::set_base(const Ref<Texture2D> &p_texture) {
ERR_FAIL_COND(p_texture == this);
diff --git a/scene/resources/texture.h b/scene/resources/texture.h
index 36b193c5d4..7c5624bd09 100644
--- a/scene/resources/texture.h
+++ b/scene/resources/texture.h
@@ -883,8 +883,6 @@ VARIANT_ENUM_CAST(GradientTexture2D::Fill);
VARIANT_ENUM_CAST(GradientTexture2D::Repeat);
class ProxyTexture : public Texture2D {
- GDCLASS(ProxyTexture, Texture2D);
-
private:
mutable RID proxy_ph;
mutable RID proxy;
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index 4e7db7d0a5..ff6d769a86 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -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);
diff --git a/servers/display_server.h b/servers/display_server.h
index 8632b53f7b..a5c42617af 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -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;
diff --git a/servers/rendering/renderer_rd/environment/sky.cpp b/servers/rendering/renderer_rd/environment/sky.cpp
index 6433a39863..d3601274b5 100644
--- a/servers/rendering/renderer_rd/environment/sky.cpp
+++ b/servers/rendering/renderer_rd/environment/sky.cpp
@@ -114,12 +114,16 @@ void SkyRD::SkyShaderData::set_code(const String &p_code) {
for (int i = 0; i < gen_code.defines.size(); i++) {
print_line(gen_code.defines[i]);
}
+
+ HashMap<String, String>::Iterator el = gen_code.code.begin();
+ while (el) {
+ print_line("\n**code " + el->key + ":\n" + el->value);
+ ++el;
+ }
+
print_line("\n**uniforms:\n" + gen_code.uniforms);
- // print_line("\n**vertex_globals:\n" + gen_code.vertex_global);
- // print_line("\n**vertex_code:\n" + gen_code.vertex);
- print_line("\n**fragment_globals:\n" + gen_code.fragment_global);
- print_line("\n**fragment_code:\n" + gen_code.fragment);
- print_line("\n**light_code:\n" + gen_code.light);
+ print_line("\n**vertex_globals:\n" + gen_code.stage_globals[ShaderCompiler::STAGE_VERTEX]);
+ print_line("\n**fragment_globals:\n" + gen_code.stage_globals[ShaderCompiler::STAGE_FRAGMENT]);
#endif
scene_singleton->sky.sky_shader.shader.version_set_code(version, gen_code.code, gen_code.uniforms, gen_code.stage_globals[ShaderCompiler::STAGE_VERTEX], gen_code.stage_globals[ShaderCompiler::STAGE_FRAGMENT], gen_code.defines);
diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
index 556db086b2..0911ee595f 100644
--- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
@@ -150,6 +150,7 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) {
depth_draw = DepthDraw(depth_drawi);
depth_test = DepthTest(depth_testi);
cull_mode = Cull(cull_modei);
+ uses_screen_texture_mipmaps = gen_code.uses_screen_texture_mipmaps;
#if 0
print_line("**compiling shader:");
@@ -158,11 +159,10 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) {
print_line(gen_code.defines[i]);
}
- RBMap<String, String>::Element *el = gen_code.code.front();
+ HashMap<String, String>::Iterator el = gen_code.code.begin();
while (el) {
- print_line("\n**code " + el->key() + ":\n" + el->value());
-
- el = el->next();
+ print_line("\n**code " + el->key + ":\n" + el->value);
+ ++el;
}
print_line("\n**uniforms:\n" + gen_code.uniforms);
@@ -396,7 +396,11 @@ void SceneShaderForwardClustered::ShaderData::get_shader_uniform_list(List<Prope
HashMap<int, StringName> order;
for (const KeyValue<StringName, ShaderLanguage::ShaderNode::Uniform> &E : uniforms) {
- if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL) {
+ if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL ||
+ E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_SCREEN_TEXTURE ||
+ E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE ||
+ E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_DEPTH_TEXTURE) {
+ // Don't expose any of these.
continue;
}
diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h
index fa9ebde1b2..d6b526fa4a 100644
--- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h
+++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h
@@ -174,6 +174,7 @@ public:
bool uses_time = false;
bool writes_modelview_or_projection = false;
bool uses_world_coordinates = false;
+ bool uses_screen_texture_mipmaps = false;
Cull cull_mode = CULL_DISABLED;
uint64_t last_pass = 0;
diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
index 01b54607bc..85c9e1db2a 100644
--- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
@@ -158,11 +158,10 @@ void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) {
print_line(gen_code.defines[i]);
}
- RBMap<String, String>::Element * el = gen_code.code.front();
+ HashMap<String, String>::Iterator el = gen_code.code.begin();
while (el) {
- print_line("\n**code " + el->key() + ":\n" + el->value());
-
- el = el->next();
+ print_line("\n**code " + el->key + ":\n" + el->value);
+ ++el;
}
print_line("\n**uniforms:\n" + gen_code.uniforms);
@@ -353,7 +352,10 @@ void SceneShaderForwardMobile::ShaderData::get_shader_uniform_list(List<Property
HashMap<int, StringName> order;
for (const KeyValue<StringName, ShaderLanguage::ShaderNode::Uniform> &E : uniforms) {
- if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL) {
+ if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL ||
+ E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_SCREEN_TEXTURE ||
+ E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE ||
+ E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_DEPTH_TEXTURE) {
continue;
}
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
index c1b08ee4c9..38a2340d40 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
@@ -1361,6 +1361,7 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
Item *ci = p_item_list;
Rect2 back_buffer_rect;
bool backbuffer_copy = false;
+ bool backbuffer_gen_mipmaps = false;
Item *canvas_group_owner = nullptr;
@@ -1389,6 +1390,7 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
if (!material_screen_texture_found) {
backbuffer_copy = true;
back_buffer_rect = Rect2();
+ backbuffer_gen_mipmaps = md->shader_data->uses_screen_texture_mipmaps;
}
}
@@ -1474,9 +1476,10 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
_render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list);
item_count = 0;
- texture_storage->render_target_copy_to_back_buffer(p_to_render_target, back_buffer_rect, true);
+ texture_storage->render_target_copy_to_back_buffer(p_to_render_target, back_buffer_rect, backbuffer_gen_mipmaps);
backbuffer_copy = false;
+ backbuffer_gen_mipmaps = false;
material_screen_texture_found = true; //after a backbuffer copy, screen texture makes no further copies
}
@@ -1980,6 +1983,7 @@ void RendererCanvasRenderRD::CanvasShaderData::set_code(const String &p_code) {
ubo_size = 0;
uniforms.clear();
uses_screen_texture = false;
+ uses_screen_texture_mipmaps = false;
uses_sdf = false;
uses_time = false;
@@ -1990,7 +1994,6 @@ void RendererCanvasRenderRD::CanvasShaderData::set_code(const String &p_code) {
ShaderCompiler::GeneratedCode gen_code;
int blend_mode = BLEND_MODE_MIX;
- uses_screen_texture = false;
ShaderCompiler::IdentifierActions actions;
actions.entry_point_stages["vertex"] = ShaderCompiler::STAGE_VERTEX;
@@ -2015,6 +2018,8 @@ void RendererCanvasRenderRD::CanvasShaderData::set_code(const String &p_code) {
Error err = canvas_singleton->shader.compiler.compile(RS::SHADER_CANVAS_ITEM, code, &actions, path, gen_code);
ERR_FAIL_COND_MSG(err != OK, "Shader compilation failed.");
+ uses_screen_texture_mipmaps = gen_code.uses_screen_texture_mipmaps;
+
if (version.is_null()) {
version = canvas_singleton->shader.canvas_shader.version_create();
}
@@ -2025,12 +2030,16 @@ void RendererCanvasRenderRD::CanvasShaderData::set_code(const String &p_code) {
for (int i = 0; i < gen_code.defines.size(); i++) {
print_line(gen_code.defines[i]);
}
+
+ HashMap<String, String>::Iterator el = gen_code.code.begin();
+ while (el) {
+ print_line("\n**code " + el->key + ":\n" + el->value);
+ ++el;
+ }
+
print_line("\n**uniforms:\n" + gen_code.uniforms);
- print_line("\n**vertex_globals:\n" + gen_code.vertex_global);
- print_line("\n**vertex_code:\n" + gen_code.vertex);
- print_line("\n**fragment_globals:\n" + gen_code.fragment_global);
- print_line("\n**fragment_code:\n" + gen_code.fragment);
- print_line("\n**light_code:\n" + gen_code.light);
+ print_line("\n**vertex_globals:\n" + gen_code.stage_globals[ShaderCompiler::STAGE_VERTEX]);
+ print_line("\n**fragment_globals:\n" + gen_code.stage_globals[ShaderCompiler::STAGE_FRAGMENT]);
#endif
canvas_singleton->shader.canvas_shader.version_set_code(version, gen_code.code, gen_code.uniforms, gen_code.stage_globals[ShaderCompiler::STAGE_VERTEX], gen_code.stage_globals[ShaderCompiler::STAGE_FRAGMENT], gen_code.defines);
ERR_FAIL_COND(!canvas_singleton->shader.canvas_shader.version_is_valid(version));
@@ -2175,7 +2184,11 @@ void RendererCanvasRenderRD::CanvasShaderData::get_shader_uniform_list(List<Prop
HashMap<int, StringName> order;
for (const KeyValue<StringName, ShaderLanguage::ShaderNode::Uniform> &E : uniforms) {
- if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL) {
+ if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL ||
+ E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_SCREEN_TEXTURE ||
+ E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE ||
+ E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_DEPTH_TEXTURE) {
+ // Don't expose any of these.
continue;
}
if (E.value.texture_order >= 0) {
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
index 5eb4cee4c6..bcbbbaa1a0 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
@@ -174,6 +174,7 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
HashMap<StringName, HashMap<int, RID>> default_texture_params;
bool uses_screen_texture = false;
+ bool uses_screen_texture_mipmaps = false;
bool uses_sdf = false;
bool uses_time = false;
diff --git a/servers/rendering/renderer_rd/shaders/effects/bokeh_dof.glsl b/servers/rendering/renderer_rd/shaders/effects/bokeh_dof.glsl
index 0438671dd2..0b43af7738 100644
--- a/servers/rendering/renderer_rd/shaders/effects/bokeh_dof.glsl
+++ b/servers/rendering/renderer_rd/shaders/effects/bokeh_dof.glsl
@@ -30,7 +30,7 @@ layout(set = 1, binding = 0) uniform sampler2D source_bokeh;
#ifdef MODE_GEN_BLUR_SIZE
float get_depth_at_pos(vec2 uv) {
- float depth = textureLod(source_depth, uv, 0.0).x;
+ float depth = textureLod(source_depth, uv, 0.0).x * 2.0 - 1.0;
if (params.orthogonal) {
depth = ((depth + (params.z_far + params.z_near) / (params.z_far - params.z_near)) * (params.z_far - params.z_near)) / 2.0;
} else {
diff --git a/servers/rendering/renderer_rd/shaders/effects/bokeh_dof_raster.glsl b/servers/rendering/renderer_rd/shaders/effects/bokeh_dof_raster.glsl
index a3b3938ee9..a06cacfabe 100644
--- a/servers/rendering/renderer_rd/shaders/effects/bokeh_dof_raster.glsl
+++ b/servers/rendering/renderer_rd/shaders/effects/bokeh_dof_raster.glsl
@@ -52,7 +52,7 @@ layout(set = 2, binding = 0) uniform sampler2D original_weight;
#ifdef MODE_GEN_BLUR_SIZE
float get_depth_at_pos(vec2 uv) {
- float depth = textureLod(source_depth, uv, 0.0).x;
+ float depth = textureLod(source_depth, uv, 0.0).x * 2.0 - 1.0;
if (params.orthogonal) {
depth = ((depth + (params.z_far + params.z_near) / (params.z_far - params.z_near)) * (params.z_far - params.z_near)) / 2.0;
} else {
diff --git a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
index 41dd1ccc40..fa8406e7a1 100644
--- a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
@@ -941,6 +941,12 @@ void MaterialStorage::MaterialData::update_uniform_buffer(const HashMap<StringNa
continue; //instance uniforms don't appear in the buffer
}
+ if (E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_SCREEN_TEXTURE ||
+ E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE ||
+ E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_DEPTH_TEXTURE) {
+ continue;
+ }
+
if (E.value.scope == ShaderLanguage::ShaderNode::Uniform::SCOPE_GLOBAL) {
//this is a global variable, get the index to it
GlobalShaderUniforms::Variable *gv = material_storage->global_shader_uniforms.variables.getptr(E.key);
@@ -1052,6 +1058,12 @@ void MaterialStorage::MaterialData::update_textures(const HashMap<StringName, Va
Vector<RID> textures;
+ if (p_texture_uniforms[i].hint == ShaderLanguage::ShaderNode::Uniform::HINT_SCREEN_TEXTURE ||
+ p_texture_uniforms[i].hint == ShaderLanguage::ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE ||
+ p_texture_uniforms[i].hint == ShaderLanguage::ShaderNode::Uniform::HINT_DEPTH_TEXTURE) {
+ continue;
+ }
+
if (p_texture_uniforms[i].global) {
uses_global_textures = true;
@@ -1307,7 +1319,7 @@ bool MaterialStorage::MaterialData::update_parameters_uniform_set(const HashMap<
update_textures(p_parameters, p_default_texture_params, p_texture_uniforms, texture_cache.ptrw(), true);
}
- if (p_ubo_size == 0 && p_texture_uniforms.size() == 0) {
+ if (p_ubo_size == 0 && (p_texture_uniforms.size() == 0)) {
// This material does not require an uniform set, so don't create it.
return false;
}
diff --git a/servers/rendering/shader_compiler.cpp b/servers/rendering/shader_compiler.cpp
index c2cf08812c..f14350305a 100644
--- a/servers/rendering/shader_compiler.cpp
+++ b/servers/rendering/shader_compiler.cpp
@@ -498,6 +498,11 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
for (const KeyValue<StringName, SL::ShaderNode::Uniform> &E : pnode->uniforms) {
if (SL::is_sampler_type(E.value.type)) {
+ if (E.value.hint == SL::ShaderNode::Uniform::HINT_SCREEN_TEXTURE ||
+ E.value.hint == SL::ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE ||
+ E.value.hint == SL::ShaderNode::Uniform::HINT_DEPTH_TEXTURE) {
+ continue; // Don't create uniforms in the generated code for these.
+ }
max_texture_uniforms++;
} else {
if (E.value.scope == SL::ShaderNode::Uniform::SCOPE_INSTANCE) {
@@ -537,6 +542,13 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
p_actions.uniforms->insert(uniform_name, uniform);
continue; // Instances are indexed directly, don't need index uniforms.
}
+
+ if (uniform.hint == SL::ShaderNode::Uniform::HINT_SCREEN_TEXTURE ||
+ uniform.hint == SL::ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE ||
+ uniform.hint == SL::ShaderNode::Uniform::HINT_DEPTH_TEXTURE) {
+ continue; // Don't create uniforms in the generated code for these.
+ }
+
if (SL::is_sampler_type(uniform.type)) {
// Texture layouts are different for OpenGL GLSL and Vulkan GLSL
if (!RS::get_singleton()->is_low_end()) {
@@ -892,12 +904,39 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
if (p_default_actions.renames.has(vnode->name)) {
code = p_default_actions.renames[vnode->name];
+ if (vnode->name == "SCREEN_TEXTURE") {
+ r_gen_code.uses_screen_texture_mipmaps = true;
+ }
} else {
if (shader->uniforms.has(vnode->name)) {
//its a uniform!
const ShaderLanguage::ShaderNode::Uniform &u = shader->uniforms[vnode->name];
if (u.texture_order >= 0) {
- code = _mkid(vnode->name); //texture, use as is
+ StringName name = vnode->name;
+ if (u.hint == ShaderLanguage::ShaderNode::Uniform::HINT_SCREEN_TEXTURE) {
+ name = "SCREEN_TEXTURE";
+ if (u.filter >= ShaderLanguage::FILTER_NEAREST_MIPMAP) {
+ r_gen_code.uses_screen_texture_mipmaps = true;
+ }
+ } else if (u.hint == ShaderLanguage::ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE) {
+ name = "NORMAL_ROUGHNESS_TEXTURE";
+ } else if (u.hint == ShaderLanguage::ShaderNode::Uniform::HINT_DEPTH_TEXTURE) {
+ name = "DEPTH_TEXTURE";
+ } else {
+ name = _mkid(vnode->name); //texture, use as is
+ }
+
+ if (p_default_actions.renames.has(name)) {
+ code = p_default_actions.renames[name];
+ } else {
+ code = name;
+ }
+
+ if (p_actions.usage_flag_pointers.has(name) && !used_flag_pointers.has(name)) {
+ *p_actions.usage_flag_pointers[name] = true;
+ used_flag_pointers.insert(name);
+ }
+
} else {
//a scalar or vector
if (u.scope == ShaderLanguage::ShaderNode::Uniform::SCOPE_GLOBAL) {
@@ -1155,6 +1194,7 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
}
if (correct_texture_uniform) {
+ //TODO Needs to detect screen_texture hint as well
is_screen_texture = (texture_uniform == "SCREEN_TEXTURE");
String sampler_name;
@@ -1404,6 +1444,7 @@ Error ShaderCompiler::compile(RS::ShaderMode p_mode, const String &p_code, Ident
r_gen_code.uses_fragment_time = false;
r_gen_code.uses_vertex_time = false;
r_gen_code.uses_global_textures = false;
+ r_gen_code.uses_screen_texture_mipmaps = false;
used_name_defines.clear();
used_rmode_defines.clear();
diff --git a/servers/rendering/shader_compiler.h b/servers/rendering/shader_compiler.h
index 06f42e9f0f..1ad43daf5f 100644
--- a/servers/rendering/shader_compiler.h
+++ b/servers/rendering/shader_compiler.h
@@ -80,6 +80,7 @@ public:
bool uses_global_textures;
bool uses_fragment_time;
bool uses_vertex_time;
+ bool uses_screen_texture_mipmaps;
};
struct DefaultIdentifierActions {
diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp
index 81e4d5e217..cab92e6e20 100644
--- a/servers/rendering/shader_language.cpp
+++ b/servers/rendering/shader_language.cpp
@@ -200,6 +200,9 @@ const char *ShaderLanguage::token_names[TK_MAX] = {
"HINT_ANISOTROPY_TEXTURE",
"HINT_RANGE",
"HINT_INSTANCE_INDEX",
+ "HINT_SCREEN_TEXTURE",
+ "HINT_NORMAL_ROUGHNESS_TEXTURE",
+ "HINT_DEPTH_TEXTURE",
"FILTER_NEAREST",
"FILTER_LINEAR",
"FILTER_NEAREST_MIPMAP",
@@ -363,6 +366,10 @@ const ShaderLanguage::KeyWord ShaderLanguage::keyword_list[] = {
{ TK_HINT_ROUGHNESS_A, "hint_roughness_a", CF_UNSPECIFIED, {}, {} },
{ TK_HINT_ROUGHNESS_NORMAL_TEXTURE, "hint_roughness_normal", CF_UNSPECIFIED, {}, {} },
{ TK_HINT_ROUGHNESS_GRAY, "hint_roughness_gray", CF_UNSPECIFIED, {}, {} },
+ { TK_HINT_SCREEN_TEXTURE, "hint_screen_texture", CF_UNSPECIFIED, {}, {} },
+ { TK_HINT_NORMAL_ROUGHNESS_TEXTURE, "hint_normal_roughness_texture", CF_UNSPECIFIED, {}, {} },
+ { TK_HINT_DEPTH_TEXTURE, "hint_depth_texture", CF_UNSPECIFIED, {}, {} },
+
{ TK_FILTER_NEAREST, "filter_nearest", CF_UNSPECIFIED, {}, {} },
{ TK_FILTER_LINEAR, "filter_linear", CF_UNSPECIFIED, {}, {} },
{ TK_FILTER_NEAREST_MIPMAP, "filter_nearest_mipmap", CF_UNSPECIFIED, {}, {} },
@@ -1096,6 +1103,15 @@ String ShaderLanguage::get_uniform_hint_name(ShaderNode::Uniform::Hint p_hint) {
case ShaderNode::Uniform::HINT_ANISOTROPY: {
result = "hint_anisotropy";
} break;
+ case ShaderNode::Uniform::HINT_SCREEN_TEXTURE: {
+ result = "hint_screen_texture";
+ } break;
+ case ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE: {
+ result = "hint_normal_roughness_texture";
+ } break;
+ case ShaderNode::Uniform::HINT_DEPTH_TEXTURE: {
+ result = "hint_depth_texture";
+ } break;
default:
break;
}
@@ -8605,6 +8621,15 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
return ERR_PARSE_ERROR;
}
} break;
+ case TK_HINT_SCREEN_TEXTURE: {
+ new_hint = ShaderNode::Uniform::HINT_SCREEN_TEXTURE;
+ } break;
+ case TK_HINT_NORMAL_ROUGHNESS_TEXTURE: {
+ new_hint = ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE;
+ } break;
+ case TK_HINT_DEPTH_TEXTURE: {
+ new_hint = ShaderNode::Uniform::HINT_DEPTH_TEXTURE;
+ } break;
case TK_FILTER_NEAREST: {
new_filter = FILTER_NEAREST;
} break;
@@ -8629,6 +8654,7 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
case TK_REPEAT_ENABLE: {
new_repeat = REPEAT_ENABLE;
} break;
+
default:
break;
}
@@ -8653,9 +8679,9 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
if (new_filter != FILTER_DEFAULT) {
if (uniform.filter != FILTER_DEFAULT) {
if (uniform.filter == new_filter) {
- _set_error(vformat(RTR("Duplicated hint: '%s'."), get_texture_filter_name(new_filter)));
+ _set_error(vformat(RTR("Duplicated filter mode: '%s'."), get_texture_filter_name(new_filter)));
} else {
- _set_error(vformat(RTR("Redefinition of hint: '%s'. The filter mode has already been set to '%s'."), get_texture_filter_name(new_filter), get_texture_filter_name(uniform.filter)));
+ _set_error(vformat(RTR("Redefinition of filter mode: '%s'. The filter mode has already been set to '%s'."), get_texture_filter_name(new_filter), get_texture_filter_name(uniform.filter)));
}
return ERR_PARSE_ERROR;
} else {
@@ -8666,9 +8692,9 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
if (new_repeat != REPEAT_DEFAULT) {
if (uniform.repeat != REPEAT_DEFAULT) {
if (uniform.repeat == new_repeat) {
- _set_error(vformat(RTR("Duplicated hint: '%s'."), get_texture_repeat_name(new_repeat)));
+ _set_error(vformat(RTR("Duplicated repeat mode: '%s'."), get_texture_repeat_name(new_repeat)));
} else {
- _set_error(vformat(RTR("Redefinition of hint: '%s'. The repeat mode has already been set to '%s'."), get_texture_repeat_name(new_repeat), get_texture_repeat_name(uniform.repeat)));
+ _set_error(vformat(RTR("Redefinition of repeat mode: '%s'. The repeat mode has already been set to '%s'."), get_texture_repeat_name(new_repeat), get_texture_repeat_name(uniform.repeat)));
}
return ERR_PARSE_ERROR;
} else {
@@ -10309,6 +10335,9 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
options.push_back("hint_roughness_gray");
options.push_back("hint_roughness_normal");
options.push_back("hint_roughness_r");
+ options.push_back("hint_screen_texture");
+ options.push_back("hint_normal_roughness_texture");
+ options.push_back("hint_depth_texture");
options.push_back("source_color");
options.push_back("repeat_enable");
options.push_back("repeat_disable");
diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h
index bfec6e1df6..75b713d167 100644
--- a/servers/rendering/shader_language.h
+++ b/servers/rendering/shader_language.h
@@ -176,6 +176,9 @@ public:
TK_HINT_SOURCE_COLOR,
TK_HINT_RANGE,
TK_HINT_INSTANCE_INDEX,
+ TK_HINT_SCREEN_TEXTURE,
+ TK_HINT_NORMAL_ROUGHNESS_TEXTURE,
+ TK_HINT_DEPTH_TEXTURE,
TK_FILTER_NEAREST,
TK_FILTER_LINEAR,
TK_FILTER_NEAREST_MIPMAP,
@@ -667,6 +670,9 @@ public:
HINT_DEFAULT_WHITE,
HINT_DEFAULT_TRANSPARENT,
HINT_ANISOTROPY,
+ HINT_SCREEN_TEXTURE,
+ HINT_NORMAL_ROUGHNESS_TEXTURE,
+ HINT_DEPTH_TEXTURE,
HINT_MAX
};
diff --git a/servers/rendering/shader_preprocessor.cpp b/servers/rendering/shader_preprocessor.cpp
index d118c73c4a..8755ee61cc 100644
--- a/servers/rendering/shader_preprocessor.cpp
+++ b/servers/rendering/shader_preprocessor.cpp
@@ -349,6 +349,8 @@ void ShaderPreprocessor::process_directive(Tokenizer *p_tokenizer) {
process_ifdef(p_tokenizer);
} else if (directive == "ifndef") {
process_ifndef(p_tokenizer);
+ } else if (directive == "elif") {
+ process_elif(p_tokenizer);
} else if (directive == "else") {
process_else(p_tokenizer);
} else if (directive == "endif") {
@@ -415,10 +417,62 @@ void ShaderPreprocessor::process_define(Tokenizer *p_tokenizer) {
}
}
+void ShaderPreprocessor::process_elif(Tokenizer *p_tokenizer) {
+ const int line = p_tokenizer->get_line();
+
+ if (state->current_branch == nullptr || state->current_branch->else_defined) {
+ set_error(RTR("Unmatched elif."), line);
+ return;
+ }
+ if (state->previous_region != nullptr) {
+ state->previous_region->to_line = line - 1;
+ }
+
+ String body = tokens_to_string(p_tokenizer->advance('\n')).strip_edges();
+ if (body.is_empty()) {
+ set_error(RTR("Missing condition."), line);
+ return;
+ }
+
+ Error error = expand_macros(body, line, body);
+ if (error != OK) {
+ return;
+ }
+
+ Expression expression;
+ Vector<String> names;
+ error = expression.parse(body, names);
+ if (error != OK) {
+ set_error(expression.get_error_text(), line);
+ return;
+ }
+
+ Variant v = expression.execute(Array(), nullptr, false);
+ if (v.get_type() == Variant::NIL) {
+ set_error(RTR("Condition evaluation error."), line);
+ return;
+ }
+
+ bool skip = false;
+ for (int i = 0; i < state->current_branch->conditions.size(); i++) {
+ if (state->current_branch->conditions[i]) {
+ skip = true;
+ break;
+ }
+ }
+
+ bool success = !skip && v.booleanize();
+ start_branch_condition(p_tokenizer, success, true);
+
+ if (state->save_regions) {
+ add_region(line + 1, success, state->previous_region->parent);
+ }
+}
+
void ShaderPreprocessor::process_else(Tokenizer *p_tokenizer) {
const int line = p_tokenizer->get_line();
- if (state->skip_stack_else.is_empty()) {
+ if (state->current_branch == nullptr || state->current_branch->else_defined) {
set_error(RTR("Unmatched else."), line);
return;
}
@@ -428,17 +482,14 @@ void ShaderPreprocessor::process_else(Tokenizer *p_tokenizer) {
p_tokenizer->advance('\n');
- bool skip = state->skip_stack_else[state->skip_stack_else.size() - 1];
- state->skip_stack_else.remove_at(state->skip_stack_else.size() - 1);
-
- Vector<SkippedCondition *> vec = state->skipped_conditions[state->current_filename];
- int index = vec.size() - 1;
- if (index >= 0) {
- SkippedCondition *cond = vec[index];
- if (cond->end_line == -1) {
- cond->end_line = p_tokenizer->get_line();
+ bool skip = false;
+ for (int i = 0; i < state->current_branch->conditions.size(); i++) {
+ if (state->current_branch->conditions[i]) {
+ skip = true;
+ break;
}
}
+ state->current_branch->else_defined = true;
if (state->save_regions) {
add_region(line + 1, !skip, state->previous_region->parent);
@@ -462,16 +513,10 @@ void ShaderPreprocessor::process_endif(Tokenizer *p_tokenizer) {
state->previous_region = state->previous_region->parent;
}
- Vector<SkippedCondition *> vec = state->skipped_conditions[state->current_filename];
- int index = vec.size() - 1;
- if (index >= 0) {
- SkippedCondition *cond = vec[index];
- if (cond->end_line == -1) {
- cond->end_line = p_tokenizer->get_line();
- }
- }
-
p_tokenizer->advance('\n');
+
+ state->current_branch = state->current_branch->parent;
+ state->branches.pop_back();
}
void ShaderPreprocessor::process_if(Tokenizer *p_tokenizer) {
@@ -703,24 +748,19 @@ void ShaderPreprocessor::add_region(int p_line, bool p_enabled, Region *p_parent
state->previous_region = &state->regions[region.file].push_back(region)->get();
}
-void ShaderPreprocessor::start_branch_condition(Tokenizer *p_tokenizer, bool p_success) {
- state->condition_depth++;
-
- if (p_success) {
- state->skip_stack_else.push_back(true);
+void ShaderPreprocessor::start_branch_condition(Tokenizer *p_tokenizer, bool p_success, bool p_continue) {
+ if (!p_continue) {
+ state->condition_depth++;
+ state->current_branch = &state->branches.push_back(Branch(p_success, state->current_branch))->get();
} else {
- SkippedCondition *cond = memnew(SkippedCondition());
- cond->start_line = p_tokenizer->get_line();
- state->skipped_conditions[state->current_filename].push_back(cond);
-
+ state->current_branch->conditions.push_back(p_success);
+ }
+ if (!p_success) {
Vector<String> ends;
+ ends.push_back("elif");
ends.push_back("else");
ends.push_back("endif");
- if (next_directive(p_tokenizer, ends) == "else") {
- state->skip_stack_else.push_back(false);
- } else {
- state->skip_stack_else.push_back(true);
- }
+ next_directive(p_tokenizer, ends);
}
}
@@ -909,12 +949,6 @@ void ShaderPreprocessor::clear() {
memdelete(E->get());
}
- for (const RBMap<String, Vector<SkippedCondition *>>::Element *E = state->skipped_conditions.front(); E; E = E->next()) {
- for (SkippedCondition *condition : E->get()) {
- memdelete(condition);
- }
- }
-
memdelete(state);
}
state_owner = false;
@@ -1064,6 +1098,7 @@ Error ShaderPreprocessor::preprocess(const String &p_code, const String &p_filen
void ShaderPreprocessor::get_keyword_list(List<String> *r_keywords, bool p_include_shader_keywords) {
r_keywords->push_back("define");
+ r_keywords->push_back("elif");
if (p_include_shader_keywords) {
r_keywords->push_back("else");
}
diff --git a/servers/rendering/shader_preprocessor.h b/servers/rendering/shader_preprocessor.h
index 41b574298d..71dd9a6ca8 100644
--- a/servers/rendering/shader_preprocessor.h
+++ b/servers/rendering/shader_preprocessor.h
@@ -130,14 +130,23 @@ private:
String body;
};
- struct SkippedCondition {
- int start_line = -1;
- int end_line = -1;
+ struct Branch {
+ Vector<bool> conditions;
+ Branch *parent = nullptr;
+ bool else_defined = false;
+
+ Branch() {}
+
+ Branch(bool p_condition, Branch *p_parent) :
+ parent(p_parent) {
+ conditions.push_back(p_condition);
+ }
};
struct State {
RBMap<String, Define *> defines;
- Vector<bool> skip_stack_else;
+ List<Branch> branches;
+ Branch *current_branch = nullptr;
int condition_depth = 0;
RBSet<String> includes;
List<uint64_t> cyclic_include_hashes; // Holds code hash of includes.
@@ -149,7 +158,6 @@ private:
bool save_regions = false;
RBMap<String, List<Region>> regions;
Region *previous_region = nullptr;
- RBMap<String, Vector<SkippedCondition *>> skipped_conditions;
bool disabled = false;
CompletionType completion_type = COMPLETION_TYPE_NONE;
HashSet<Ref<ShaderInclude>> shader_includes;
@@ -169,6 +177,7 @@ private:
void process_directive(Tokenizer *p_tokenizer);
void process_define(Tokenizer *p_tokenizer);
+ void process_elif(Tokenizer *p_tokenizer);
void process_else(Tokenizer *p_tokenizer);
void process_endif(Tokenizer *p_tokenizer);
void process_if(Tokenizer *p_tokenizer);
@@ -179,7 +188,7 @@ private:
void process_undef(Tokenizer *p_tokenizer);
void add_region(int p_line, bool p_enabled, Region *p_parent_region);
- void start_branch_condition(Tokenizer *p_tokenizer, bool p_success);
+ void start_branch_condition(Tokenizer *p_tokenizer, bool p_success, bool p_continue = false);
void expand_output_macros(int p_start, int p_line);
Error expand_macros(const String &p_string, int p_line, String &r_result);
diff --git a/tests/core/object/test_object.h b/tests/core/object/test_object.h
index 88a3e4ccad..f5c5de7fdf 100644
--- a/tests/core/object/test_object.h
+++ b/tests/core/object/test_object.h
@@ -82,6 +82,12 @@ public:
Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid) const override {
return Variant::PACKED_FLOAT32_ARRAY;
}
+ bool property_can_revert(const StringName &p_name) const override {
+ return false;
+ };
+ bool property_get_revert(const StringName &p_name, Variant &r_ret) const override {
+ return false;
+ };
void get_method_list(List<MethodInfo> *p_list) const override {
}
bool has_method(const StringName &p_method) const override {