diff options
44 files changed, 626 insertions, 329 deletions
diff --git a/.github/workflows/godot_cpp_test.yml b/.github/workflows/godot_cpp_test.yml index 2051920f30..5384197f37 100644 --- a/.github/workflows/godot_cpp_test.yml +++ b/.github/workflows/godot_cpp_test.yml @@ -4,8 +4,10 @@ on: # Global Settings env: - # Used for the cache key, and godot-cpp checkout. Add version suffix to force clean build. + # Used for the cache key. Add version suffix to force clean build. GODOT_BASE_BRANCH: master + # Used for the godot-cpp checkout. + GODOT_CPP_BRANCH: '4.1' concurrency: group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-cpp-tests @@ -26,7 +28,7 @@ jobs: uses: actions/checkout@v3 with: repository: godotengine/godot-cpp - ref: ${{ env.GODOT_BASE_BRANCH }} + ref: ${{ env.GODOT_CPP_BRANCH }} submodules: 'recursive' path: 'godot-cpp' diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp index 24517d71e4..b6dfec33ed 100644 --- a/core/extension/gdextension_interface.cpp +++ b/core/extension/gdextension_interface.cpp @@ -1043,35 +1043,35 @@ static void gdextension_ref_set_object(GDExtensionRefPtr p_ref, GDExtensionObjec #ifndef DISABLE_DEPRECATED static GDExtensionScriptInstancePtr gdextension_script_instance_create(const GDExtensionScriptInstanceInfo *p_info, GDExtensionScriptInstanceDataPtr p_instance_data) { - const GDExtensionScriptInstanceInfo2 info_2 = { - p_info->set_func, - p_info->get_func, - p_info->get_property_list_func, - p_info->free_property_list_func, - p_info->property_can_revert_func, - p_info->property_get_revert_func, - p_info->get_owner_func, - p_info->get_property_state_func, - p_info->get_method_list_func, - p_info->free_method_list_func, - p_info->get_property_type_func, - p_info->has_method_func, - p_info->call_func, - nullptr, // notification_func. - p_info->to_string_func, - p_info->refcount_incremented_func, - p_info->refcount_decremented_func, - p_info->get_script_func, - p_info->is_placeholder_func, - p_info->set_fallback_func, - p_info->get_fallback_func, - p_info->get_language_func, - p_info->free_func, - }; + GDExtensionScriptInstanceInfo2 *info_2 = memnew(GDExtensionScriptInstanceInfo2); + info_2->set_func = p_info->set_func; + info_2->get_func = p_info->get_func; + info_2->get_property_list_func = p_info->get_property_list_func; + info_2->free_property_list_func = p_info->free_property_list_func; + info_2->property_can_revert_func = p_info->property_can_revert_func; + info_2->property_get_revert_func = p_info->property_get_revert_func; + info_2->get_owner_func = p_info->get_owner_func; + info_2->get_property_state_func = p_info->get_property_state_func; + info_2->get_method_list_func = p_info->get_method_list_func; + info_2->free_method_list_func = p_info->free_method_list_func; + info_2->get_property_type_func = p_info->get_property_type_func; + info_2->has_method_func = p_info->has_method_func; + info_2->call_func = p_info->call_func; + info_2->notification_func = nullptr; + info_2->to_string_func = p_info->to_string_func; + info_2->refcount_incremented_func = p_info->refcount_incremented_func; + info_2->refcount_decremented_func = p_info->refcount_decremented_func; + info_2->get_script_func = p_info->get_script_func; + info_2->is_placeholder_func = p_info->is_placeholder_func; + info_2->set_fallback_func = p_info->set_fallback_func; + info_2->get_fallback_func = p_info->get_fallback_func; + info_2->get_language_func = p_info->get_language_func; + info_2->free_func = p_info->free_func; ScriptInstanceExtension *script_instance_extension = memnew(ScriptInstanceExtension); script_instance_extension->instance = p_instance_data; - script_instance_extension->native_info = &info_2; + script_instance_extension->native_info = info_2; + script_instance_extension->free_native_info = true; script_instance_extension->deprecated_native_info.notification_func = p_info->notification_func; return reinterpret_cast<GDExtensionScriptInstancePtr>(script_instance_extension); } diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h index e719d7337b..102220c0fc 100644 --- a/core/extension/gdextension_interface.h +++ b/core/extension/gdextension_interface.h @@ -259,7 +259,7 @@ typedef void (*GDExtensionClassFreePropertyList)(GDExtensionClassInstancePtr p_i typedef GDExtensionBool (*GDExtensionClassPropertyCanRevert)(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name); typedef GDExtensionBool (*GDExtensionClassPropertyGetRevert)(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionVariantPtr r_ret); typedef void (*GDExtensionClassNotification)(GDExtensionClassInstancePtr p_instance, int32_t p_what); // Deprecated. Use GDExtensionClassNotification2 instead. -typedef void (*GDExtensionClassNotification2)(GDExtensionClassInstancePtr p_instance, int32_t p_what, bool p_reversed); +typedef void (*GDExtensionClassNotification2)(GDExtensionClassInstancePtr p_instance, int32_t p_what, GDExtensionBool p_reversed); typedef void (*GDExtensionClassToString)(GDExtensionClassInstancePtr p_instance, GDExtensionBool *r_is_valid, GDExtensionStringPtr p_out); typedef void (*GDExtensionClassReference)(GDExtensionClassInstancePtr p_instance); typedef void (*GDExtensionClassUnreference)(GDExtensionClassInstancePtr p_instance); @@ -388,7 +388,7 @@ typedef GDExtensionBool (*GDExtensionScriptInstanceHasMethod)(GDExtensionScriptI typedef void (*GDExtensionScriptInstanceCall)(GDExtensionScriptInstanceDataPtr p_self, GDExtensionConstStringNamePtr p_method, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error); typedef void (*GDExtensionScriptInstanceNotification)(GDExtensionScriptInstanceDataPtr p_instance, int32_t p_what); // Deprecated. Use GDExtensionScriptInstanceNotification2 instead. -typedef void (*GDExtensionScriptInstanceNotification2)(GDExtensionScriptInstanceDataPtr p_instance, int32_t p_what, bool p_reversed); +typedef void (*GDExtensionScriptInstanceNotification2)(GDExtensionScriptInstanceDataPtr p_instance, int32_t p_what, GDExtensionBool p_reversed); typedef void (*GDExtensionScriptInstanceToString)(GDExtensionScriptInstanceDataPtr p_instance, GDExtensionBool *r_is_valid, GDExtensionStringPtr r_out); typedef void (*GDExtensionScriptInstanceRefCountIncremented)(GDExtensionScriptInstanceDataPtr p_instance); diff --git a/core/object/object.cpp b/core/object/object.cpp index 39f20a212f..0a0953f7dc 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -485,7 +485,7 @@ void Object::get_property_list(List<PropertyInfo> *p_list, bool p_reversed) cons if (_extension) { const ObjectGDExtension *current_extension = _extension; while (current_extension) { - p_list->push_back(PropertyInfo(Variant::NIL, current_extension->class_name, PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY)); + p_list->push_back(PropertyInfo(Variant::NIL, current_extension->class_name, PROPERTY_HINT_NONE, current_extension->class_name, PROPERTY_USAGE_CATEGORY)); ClassDB::get_property_list(current_extension->class_name, p_list, true, this); @@ -809,7 +809,7 @@ void Object::notification(int p_notification, bool p_reversed) { if (_extension) { if (_extension->notification2) { - _extension->notification2(_extension_instance, p_notification, p_reversed); + _extension->notification2(_extension_instance, p_notification, static_cast<GDExtensionBool>(p_reversed)); #ifndef DISABLE_DEPRECATED } else if (_extension->notification) { _extension->notification(_extension_instance, p_notification); diff --git a/core/object/object.h b/core/object/object.h index 675b6cc1d8..309cd34c4b 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -489,7 +489,7 @@ protected: if (!p_reversed) { \ m_inherits::_get_property_listv(p_list, p_reversed); \ } \ - p_list->push_back(PropertyInfo(Variant::NIL, get_class_static(), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY)); \ + p_list->push_back(PropertyInfo(Variant::NIL, get_class_static(), PROPERTY_HINT_NONE, get_class_static(), PROPERTY_USAGE_CATEGORY)); \ if (!_is_gpl_reversed()) { \ ::ClassDB::get_property_list(#m_class, p_list, true, this); \ } \ diff --git a/core/object/script_language.h b/core/object/script_language.h index ad88c3a7d4..3cac360b1a 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -344,14 +344,16 @@ public: Vector<Pair<int, int>> matches; Vector<Pair<int, int>> last_matches = { { -1, -1 } }; // This value correspond to an impossible match int location = LOCATION_OTHER; + String theme_color_name; CodeCompletionOption() {} - CodeCompletionOption(const String &p_text, CodeCompletionKind p_kind, int p_location = LOCATION_OTHER) { + CodeCompletionOption(const String &p_text, CodeCompletionKind p_kind, int p_location = LOCATION_OTHER, const String &p_theme_color_name = "") { display = p_text; insert_text = p_text; kind = p_kind; location = p_location; + theme_color_name = p_theme_color_name; } TypedArray<int> get_option_characteristics(const String &p_base); diff --git a/core/object/script_language_extension.h b/core/object/script_language_extension.h index eca208a2bd..bf5ad3c107 100644 --- a/core/object/script_language_extension.h +++ b/core/object/script_language_extension.h @@ -631,6 +631,7 @@ VARIANT_ENUM_CAST(ScriptLanguageExtension::CodeCompletionLocation) class ScriptInstanceExtension : public ScriptInstance { public: const GDExtensionScriptInstanceInfo2 *native_info; + bool free_native_info = false; struct { GDExtensionClassNotification notification_func; } deprecated_native_info; @@ -831,6 +832,9 @@ public: if (native_info->free_func) { native_info->free_func(instance); } + if (free_native_info) { + memfree(const_cast<GDExtensionScriptInstanceInfo2 *>(native_info)); + } } #if defined(__GNUC__) && !defined(__clang__) diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index ccf9b82022..5a405a7080 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -561,8 +561,8 @@ static _FORCE_INLINE_ void vc_ptrcall(void (*method)(T *, P...), void *p_base, c } \ static void ptrcall(void *p_base, const void **p_args, void *r_ret, int p_argcount) { \ LocalVector<Variant> vars; \ - vars.resize(p_argcount); \ LocalVector<const Variant *> vars_ptrs; \ + vars.resize(p_argcount); \ vars_ptrs.resize(p_argcount); \ for (int i = 0; i < p_argcount; i++) { \ vars[i] = PtrToArg<Variant>::convert(p_args[i]); \ @@ -571,7 +571,7 @@ static _FORCE_INLINE_ void vc_ptrcall(void (*method)(T *, P...), void *p_base, c Variant base = PtrToArg<m_class>::convert(p_base); \ Variant ret; \ Callable::CallError ce; \ - m_method_ptr(&base, (const Variant **)&vars_ptrs[0], p_argcount, ret, ce); \ + m_method_ptr(&base, vars_ptrs.ptr(), p_argcount, ret, ce); \ if (m_has_return) { \ m_return_type r = ret; \ PtrToArg<m_return_type>::encode(ret, r_ret); \ @@ -617,8 +617,8 @@ static _FORCE_INLINE_ void vc_ptrcall(void (*method)(T *, P...), void *p_base, c } \ static void ptrcall(void *p_base, const void **p_args, void *r_ret, int p_argcount) { \ LocalVector<Variant> vars; \ - vars.resize(p_argcount); \ LocalVector<const Variant *> vars_ptrs; \ + vars.resize(p_argcount); \ vars_ptrs.resize(p_argcount); \ for (int i = 0; i < p_argcount; i++) { \ vars[i] = PtrToArg<Variant>::convert(p_args[i]); \ @@ -627,7 +627,7 @@ static _FORCE_INLINE_ void vc_ptrcall(void (*method)(T *, P...), void *p_base, c Variant base = PtrToArg<m_class>::convert(p_base); \ Variant ret; \ Callable::CallError ce; \ - m_method_ptr(&base, (const Variant **)&vars_ptrs[0], p_argcount, ret, ce); \ + m_method_ptr(&base, vars_ptrs.ptr(), p_argcount, ret, ce); \ } \ static int get_argument_count() { \ return 1; \ @@ -1132,11 +1132,7 @@ static void register_builtin_method(const Vector<String> &p_argnames, const Vect imi.call = T::call; imi.validated_call = T::validated_call; - if (T::is_vararg()) { - imi.ptrcall = nullptr; - } else { - imi.ptrcall = T::ptrcall; - } + imi.ptrcall = T::ptrcall; imi.default_arguments = p_def_args; imi.argument_names = p_argnames; diff --git a/doc/classes/AnimatableBody2D.xml b/doc/classes/AnimatableBody2D.xml index b6723a9cfb..831f7d73d0 100644 --- a/doc/classes/AnimatableBody2D.xml +++ b/doc/classes/AnimatableBody2D.xml @@ -4,7 +4,7 @@ A 2D physics body that can't be moved by external forces. When moved manually, it affects other bodies in its path. </brief_description> <description> - An animatable 2D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationPlayer]s (with [member AnimationPlayer.playback_process_mode] set to [code]ANIMATION_PROCESS_PHYSICS[/code]), and [RemoteTransform2D]. + An animatable 2D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationPlayer]s (with [member AnimationPlayer.playback_process_mode] set to [constant AnimationPlayer.ANIMATION_PROCESS_PHYSICS]), and [RemoteTransform2D]. When [AnimatableBody2D] is moved, its linear and angular velocity are estimated and used to affect other physics bodies in its path. This makes it useful for moving platforms, doors, and other moving objects. </description> <tutorials> diff --git a/doc/classes/AnimatableBody3D.xml b/doc/classes/AnimatableBody3D.xml index 1264a6ad03..f1b3eb6ad8 100644 --- a/doc/classes/AnimatableBody3D.xml +++ b/doc/classes/AnimatableBody3D.xml @@ -4,7 +4,7 @@ A 3D physics body that can't be moved by external forces. When moved manually, it affects other bodies in its path. </brief_description> <description> - An animatable 3D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationPlayer]s (with [member AnimationPlayer.playback_process_mode] set to [code]ANIMATION_PROCESS_PHYSICS[/code]), and [RemoteTransform3D]. + An animatable 3D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationPlayer]s (with [member AnimationPlayer.playback_process_mode] set to [constant AnimationPlayer.ANIMATION_PROCESS_PHYSICS]), and [RemoteTransform3D]. When [AnimatableBody3D] is moved, its linear and angular velocity are estimated and used to affect other physics bodies in its path. This makes it useful for moving platforms, doors, and other moving objects. </description> <tutorials> diff --git a/doc/classes/ConcavePolygonShape2D.xml b/doc/classes/ConcavePolygonShape2D.xml index 3d366cecc6..31e28bc714 100644 --- a/doc/classes/ConcavePolygonShape2D.xml +++ b/doc/classes/ConcavePolygonShape2D.xml @@ -4,11 +4,11 @@ A 2D polyline shape used for physics collision. </brief_description> <description> - A 2D polyline shape, intended for use in physics. Used internally in [CollisionPolygon2D] when it's in [code]BUILD_SEGMENTS[/code] mode. + A 2D polyline shape, intended for use in physics. Used internally in [CollisionPolygon2D] when it's in [constant CollisionPolygon2D.BUILD_SEGMENTS] mode. Being just a collection of interconnected line segments, [ConcavePolygonShape2D] is the most freely configurable single 2D shape. It can be used to form polygons of any nature, or even shapes that don't enclose an area. However, [ConvexPolygonShape2D] is [i]hollow[/i] even if the interconnected line segments do enclose an area, which often makes it unsuitable for physics or detection. [b]Note:[/b] When used for collision, [ConcavePolygonShape2D] is intended to work with static [CollisionShape2D] nodes like [StaticBody2D] and will likely not behave well for [CharacterBody2D]s or [RigidBody2D]s in a mode other than Static. [b]Warning:[/b] Physics bodies that are small have a chance to clip through this shape when moving fast. This happens because on one frame, the physics body may be on the "outside" of the shape, and on the next frame it may be "inside" it. [ConcavePolygonShape2D] is hollow, so it won't detect a collision. - [b]Performance:[/b] Due to its complexity, [ConcavePolygonShape2D] is the slowest 2D collision shape to check collisions against. Its use should generally be limited to level geometry. If the polyline is closed, [CollisionPolygon2D]'s [code]BUILD_SOLIDS[/code] mode can be used, which decomposes the polygon into convex ones; see [ConvexPolygonShape2D]'s documentation for instructions. + [b]Performance:[/b] Due to its complexity, [ConcavePolygonShape2D] is the slowest 2D collision shape to check collisions against. Its use should generally be limited to level geometry. If the polyline is closed, [CollisionPolygon2D]'s [constant CollisionPolygon2D.BUILD_SOLIDS] mode can be used, which decomposes the polygon into convex ones; see [ConvexPolygonShape2D]'s documentation for instructions. </description> <tutorials> </tutorials> diff --git a/doc/classes/ConvexPolygonShape2D.xml b/doc/classes/ConvexPolygonShape2D.xml index 4f86f3830b..984787d348 100644 --- a/doc/classes/ConvexPolygonShape2D.xml +++ b/doc/classes/ConvexPolygonShape2D.xml @@ -4,9 +4,9 @@ A 2D convex polygon shape used for physics collision. </brief_description> <description> - A 2D convex polygon shape, intended for use in physics. Used internally in [CollisionPolygon2D] when it's in [code]BUILD_SOLIDS[/code] mode. + A 2D convex polygon shape, intended for use in physics. Used internally in [CollisionPolygon2D] when it's in [constant CollisionPolygon2D.BUILD_SOLIDS] mode. [ConvexPolygonShape2D] is [i]solid[/i], which means it detects collisions from objects that are fully inside it, unlike [ConcavePolygonShape2D] which is hollow. This makes it more suitable for both detection and physics. - [b]Convex decomposition:[/b] A concave polygon can be split up into several convex polygons. This allows dynamic physics bodies to have complex concave collisions (at a performance cost) and can be achieved by using several [ConvexPolygonShape2D] nodes or by using the [CollisionPolygon2D] node in [code]BUILD_SOLIDS[/code] mode. To generate a collision polygon from a sprite, select the [Sprite2D] node, go to the [b]Sprite2D[/b] menu that appears above the viewport, and choose [b]Create Polygon2D Sibling[/b]. + [b]Convex decomposition:[/b] A concave polygon can be split up into several convex polygons. This allows dynamic physics bodies to have complex concave collisions (at a performance cost) and can be achieved by using several [ConvexPolygonShape2D] nodes or by using the [CollisionPolygon2D] node in [constant CollisionPolygon2D.BUILD_SOLIDS] mode. To generate a collision polygon from a sprite, select the [Sprite2D] node, go to the [b]Sprite2D[/b] menu that appears above the viewport, and choose [b]Create Polygon2D Sibling[/b]. [b]Performance:[/b] [ConvexPolygonShape2D] is faster to check collisions against compared to [ConcavePolygonShape2D], but it is slower than primitive collision shapes such as [CircleShape2D] and [RectangleShape2D]. Its use should generally be limited to medium-sized objects that cannot have their collision accurately represented by primitive shapes. </description> <tutorials> diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 83abc963bb..aa3bf3c535 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -858,6 +858,9 @@ <member name="text_editor/completion/code_complete_enabled" type="bool" setter="" getter=""> If [code]true[/code], code completion will be triggered automatically after [member text_editor/completion/code_complete_delay]. If [code]false[/code], you can still trigger completion manually by pressing [kbd]Ctrl + Space[/kbd] ([kbd]Cmd + Space[/kbd] on macOS). </member> + <member name="text_editor/completion/colorize_suggestions" type="bool" setter="" getter=""> + If [code]true[/code] enables the coloring for some items in the autocompletion suggestions, like vector components. + </member> <member name="text_editor/completion/complete_file_paths" type="bool" setter="" getter=""> If [code]true[/code], provides autocompletion suggestions for file paths in methods such as [code]load()[/code] and [code]preload()[/code]. </member> diff --git a/doc/classes/LineEdit.xml b/doc/classes/LineEdit.xml index ef620da847..e2aa13403a 100644 --- a/doc/classes/LineEdit.xml +++ b/doc/classes/LineEdit.xml @@ -223,11 +223,11 @@ If [code]true[/code], the [LineEdit] width will increase to stay longer than the [member text]. It will [b]not[/b] compress if the [member text] is shortened. </member> <member name="flat" type="bool" setter="set_flat" getter="is_flat" default="false"> - If [code]true[/code], the [LineEdit] don't display decoration. + If [code]true[/code], the [LineEdit] doesn't display decoration. </member> <member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" overrides="Control" enum="Control.FocusMode" default="2" /> <member name="language" type="String" setter="set_language" getter="get_language" default=""""> - Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead. + Language code used for line-breaking and text shaping algorithms. If left empty, current locale is used instead. </member> <member name="max_length" type="int" setter="set_max_length" getter="get_max_length" default="0"> Maximum number of characters that can be entered inside the [LineEdit]. If [code]0[/code], there is no limit. diff --git a/doc/classes/MeshDataTool.xml b/doc/classes/MeshDataTool.xml index 23bcc7f0ee..48be28be26 100644 --- a/doc/classes/MeshDataTool.xml +++ b/doc/classes/MeshDataTool.xml @@ -171,7 +171,7 @@ <return type="Vector3" /> <param index="0" name="idx" type="int" /> <description> - Returns the vertex at given index. + Returns the position of the given vertex. </description> </method> <method name="get_vertex_bones" qualifiers="const"> diff --git a/doc/classes/StaticBody2D.xml b/doc/classes/StaticBody2D.xml index 185a2cdda4..ac29754508 100644 --- a/doc/classes/StaticBody2D.xml +++ b/doc/classes/StaticBody2D.xml @@ -4,7 +4,7 @@ A 2D physics body that can't be moved by external forces. When moved manually, it doesn't affect other bodies in its path. </brief_description> <description> - A static 2D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationPlayer]s (with [member AnimationPlayer.playback_process_mode] set to [code]ANIMATION_PROCESS_PHYSICS[/code]), and [RemoteTransform2D]. + A static 2D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationPlayer]s (with [member AnimationPlayer.playback_process_mode] set to [constant AnimationPlayer.ANIMATION_PROCESS_PHYSICS]), and [RemoteTransform2D]. When [StaticBody2D] is moved, it is teleported to its new position without affecting other physics bodies in its path. If this is not desired, use [AnimatableBody2D] instead. [StaticBody2D] is useful for completely static objects like floors and walls, as well as moving surfaces like conveyor belts and circular revolving platforms (by using [member constant_linear_velocity] and [member constant_angular_velocity]). </description> diff --git a/doc/classes/StaticBody3D.xml b/doc/classes/StaticBody3D.xml index 093fe4c926..c2fd5e03bd 100644 --- a/doc/classes/StaticBody3D.xml +++ b/doc/classes/StaticBody3D.xml @@ -4,7 +4,7 @@ A 3D physics body that can't be moved by external forces. When moved manually, it doesn't affect other bodies in its path. </brief_description> <description> - A static 3D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationPlayer]s (with [member AnimationPlayer.playback_process_mode] set to [code]ANIMATION_PROCESS_PHYSICS[/code]), and [RemoteTransform3D]. + A static 3D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationPlayer]s (with [member AnimationPlayer.playback_process_mode] set to [constant AnimationPlayer.ANIMATION_PROCESS_PHYSICS]), and [RemoteTransform3D]. When [StaticBody3D] is moved, it is teleported to its new position without affecting other physics bodies in its path. If this is not desired, use [AnimatableBody3D] instead. [StaticBody3D] is useful for completely static objects like floors and walls, as well as moving surfaces like conveyor belts and circular revolving platforms (by using [member constant_linear_velocity] and [member constant_angular_velocity]). </description> diff --git a/doc/classes/int.xml b/doc/classes/int.xml index 914cf75929..9d168c60e2 100644 --- a/doc/classes/int.xml +++ b/doc/classes/int.xml @@ -17,7 +17,7 @@ [/gdscript] [csharp] int x = 1; // x is 1 - x = 4.2; // x is 4, because 4.2 gets truncated + x = (int)4.2; // x is 4, because 4.2 gets truncated // We use long below, because GDScript's int is 64-bit while C#'s int is 32-bit. long maxLong = 9223372036854775807; // Biggest value a long can store maxLong++; // maxLong is now -9223372036854775808, because it wrapped around. @@ -27,12 +27,19 @@ maxInt++; // maxInt is now -2147483648, because it wrapped around [/csharp] [/codeblocks] - In GDScript, you can use the [code]0b[/code] literal for binary representation, the [code]0x[/code] literal for hexadecimal representation, and the [code]_[/code] symbol to separate long numbers and improve readability. - [codeblock] + You can use the [code]0b[/code] literal for binary representation, the [code]0x[/code] literal for hexadecimal representation, and the [code]_[/code] symbol to separate long numbers and improve readability. + [codeblocks] + [gdscript] var x = 0b1001 # x is 9 var y = 0xF5 # y is 245 var z = 10_000_000 # z is 10000000 - [/codeblock] + [/gdscript] + [csharp] + int x = 0b1001; // x is 9 + int y = 0xF5; // y is 245 + int z = 10_000_000; // z is 10000000 + [/csharp] + [/codeblocks] </description> <tutorials> </tutorials> diff --git a/drivers/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp index 803a109863..6e6a2a791a 100644 --- a/drivers/vulkan/rendering_device_vulkan.cpp +++ b/drivers/vulkan/rendering_device_vulkan.cpp @@ -9135,12 +9135,32 @@ void RenderingDeviceVulkan::initialize(VulkanContext *p_context, bool p_local_de draw_list_split = false; compute_list = nullptr; + + pipelines_cache.file_path = "user://vulkan/pipelines"; + pipelines_cache.file_path += "." + context->get_device_name().validate_filename().replace(" ", "_").to_lower(); + if (Engine::get_singleton()->is_editor_hint()) { + pipelines_cache.file_path += ".editor"; + } + pipelines_cache.file_path += ".cache"; + + // Prepare most fields now. + VkPhysicalDeviceProperties props; + vkGetPhysicalDeviceProperties(context->get_physical_device(), &props); + pipelines_cache.header.magic = 868 + VK_PIPELINE_CACHE_HEADER_VERSION_ONE; + pipelines_cache.header.device_id = props.deviceID; + pipelines_cache.header.vendor_id = props.vendorID; + pipelines_cache.header.driver_version = props.driverVersion; + memcpy(pipelines_cache.header.uuid, props.pipelineCacheUUID, VK_UUID_SIZE); + pipelines_cache.header.driver_abi = sizeof(void *); + _load_pipeline_cache(); print_verbose(vformat("Startup PSO cache (%.1f MiB)", pipelines_cache.buffer.size() / (1024.0f * 1024.0f))); VkPipelineCacheCreateInfo cache_info = {}; cache_info.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; cache_info.pNext = nullptr; - cache_info.flags = 0; + if (context->is_device_extension_enabled(VK_EXT_PIPELINE_CREATION_CACHE_CONTROL_EXTENSION_NAME)) { + cache_info.flags = VK_PIPELINE_CACHE_CREATE_EXTERNALLY_SYNCHRONIZED_BIT; + } cache_info.initialDataSize = pipelines_cache.buffer.size(); cache_info.pInitialData = pipelines_cache.buffer.ptr(); VkResult err = vkCreatePipelineCache(device, &cache_info, nullptr, &pipelines_cache.cache_object); @@ -9151,111 +9171,106 @@ void RenderingDeviceVulkan::initialize(VulkanContext *p_context, bool p_local_de } void RenderingDeviceVulkan::_load_pipeline_cache() { - if (!DirAccess::exists("user://vulkan/")) { - Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_USERDATA); + DirAccess::make_dir_recursive_absolute(pipelines_cache.file_path.get_base_dir()); - if (da.is_valid()) { - da->make_dir_recursive("user://vulkan/"); - } - } - - if (FileAccess::exists("user://vulkan/pipelines.cache")) { + if (FileAccess::exists(pipelines_cache.file_path)) { Error file_error; - Vector<uint8_t> file_data = FileAccess::get_file_as_bytes("user://vulkan/pipelines.cache", &file_error); + Vector<uint8_t> file_data = FileAccess::get_file_as_bytes(pipelines_cache.file_path, &file_error); if (file_error != OK || file_data.size() <= (int)sizeof(PipelineCacheHeader)) { WARN_PRINT("Invalid/corrupt pipelines cache."); return; } - PipelineCacheHeader header = {}; - memcpy((char *)&header, file_data.ptr(), sizeof(PipelineCacheHeader)); - if (header.magic != 868 + VK_PIPELINE_CACHE_HEADER_VERSION_ONE) { + const PipelineCacheHeader *header = reinterpret_cast<const PipelineCacheHeader *>(file_data.ptr()); + if (header->magic != 868 + VK_PIPELINE_CACHE_HEADER_VERSION_ONE) { WARN_PRINT("Invalid pipelines cache magic number."); return; } - pipelines_cache.buffer.resize(file_data.size() - sizeof(PipelineCacheHeader)); - memcpy(pipelines_cache.buffer.ptrw(), file_data.ptr() + sizeof(PipelineCacheHeader), pipelines_cache.buffer.size()); - VkPhysicalDeviceProperties props; - vkGetPhysicalDeviceProperties(context->get_physical_device(), &props); - bool invalid_uuid = false; - for (size_t i = 0; i < VK_UUID_SIZE; i++) { - if (header.uuid[i] != props.pipelineCacheUUID[i]) { - invalid_uuid = true; - break; - } - } - if (header.data_hash != hash_murmur3_buffer(pipelines_cache.buffer.ptr(), pipelines_cache.buffer.size()) || header.data_size != (uint32_t)pipelines_cache.buffer.size() || header.vendor_id != props.vendorID || header.device_id != props.deviceID || header.driver_abi != sizeof(void *) || invalid_uuid) { + const uint8_t *loaded_buffer_start = file_data.ptr() + sizeof(PipelineCacheHeader); + uint32_t loaded_buffer_size = file_data.size() - sizeof(PipelineCacheHeader); + if (header->data_hash != hash_murmur3_buffer(loaded_buffer_start, loaded_buffer_size) || + header->data_size != loaded_buffer_size || + header->vendor_id != pipelines_cache.header.vendor_id || + header->device_id != pipelines_cache.header.device_id || + header->driver_version != pipelines_cache.header.driver_version || + memcmp(header->uuid, pipelines_cache.header.uuid, VK_UUID_SIZE) != 0 || + header->driver_abi != pipelines_cache.header.driver_abi) { WARN_PRINT("Invalid pipelines cache header."); pipelines_cache.current_size = 0; pipelines_cache.buffer.clear(); } else { - pipelines_cache.current_size = pipelines_cache.buffer.size(); + pipelines_cache.current_size = loaded_buffer_size; + pipelines_cache.buffer.resize(loaded_buffer_size); + memcpy(pipelines_cache.buffer.ptr(), loaded_buffer_start, pipelines_cache.buffer.size()); } } } void RenderingDeviceVulkan::_update_pipeline_cache(bool p_closing) { - size_t pso_blob_size = 0; - float save_interval = GLOBAL_GET("rendering/rendering_device/pipeline_cache/save_chunk_size_mb"); - VkResult vr = vkGetPipelineCacheData(device, pipelines_cache.cache_object, &pso_blob_size, nullptr); - ERR_FAIL_COND(vr); - size_t difference = (pso_blob_size - pipelines_cache.current_size) / (1024 * 1024); - if (p_closing && Engine::get_singleton()->is_editor_hint()) { - // This is mostly for the editor to check if after playing the game, game's pipeline cache size still matches with editor's cache. - _load_pipeline_cache(); - if (pipelines_cache.current_size > pso_blob_size) { - pso_blob_size = pipelines_cache.current_size; - if (pipelines_cache_save_task != WorkerThreadPool::INVALID_TASK_ID || !WorkerThreadPool::get_singleton()->is_task_completed(pipelines_cache_save_task)) { + { + bool still_saving = pipelines_cache_save_task != WorkerThreadPool::INVALID_TASK_ID && !WorkerThreadPool::get_singleton()->is_task_completed(pipelines_cache_save_task); + if (still_saving) { + if (p_closing) { WorkerThreadPool::get_singleton()->wait_for_task_completion(pipelines_cache_save_task); + pipelines_cache_save_task = WorkerThreadPool::INVALID_TASK_ID; + } else { + // We can't save until the currently running save is done. We'll retry next time; worst case, we'll save when exiting. + return; } } } - if (pso_blob_size == pipelines_cache.current_size) { - return; - } else if (difference < save_interval && !p_closing) { - return; - } - if (p_closing) { - if (pipelines_cache_save_task == WorkerThreadPool::INVALID_TASK_ID || WorkerThreadPool::get_singleton()->is_task_completed(pipelines_cache_save_task)) { - pipelines_cache_save_task = WorkerThreadPool::get_singleton()->add_template_task(this, &RenderingDeviceVulkan::_save_pipeline_cache_threaded, pso_blob_size, false, "PipelineCacheSave"); - WorkerThreadPool::get_singleton()->wait_for_task_completion(pipelines_cache_save_task); + { + // FIXME: + // We're letting the cache grow unboundedly. We may want to set at limit and see if implementations use LRU or the like. + // If we do, we won't be able to assume any longer that the cache is dirty if, and only if, it has grown. + size_t pso_blob_size = 0; + VkResult vr = vkGetPipelineCacheData(device, pipelines_cache.cache_object, &pso_blob_size, nullptr); + ERR_FAIL_COND(vr); + size_t difference = pso_blob_size - pipelines_cache.current_size; + + bool must_save = false; + + if (p_closing) { + must_save = difference > 0; } else { - WorkerThreadPool::get_singleton()->wait_for_task_completion(pipelines_cache_save_task); - pipelines_cache_save_task = WorkerThreadPool::get_singleton()->add_template_task(this, &RenderingDeviceVulkan::_save_pipeline_cache_threaded, pso_blob_size, false, "PipelineCacheSave"); - WorkerThreadPool::get_singleton()->wait_for_task_completion(pipelines_cache_save_task); + float save_interval = GLOBAL_GET("rendering/rendering_device/pipeline_cache/save_chunk_size_mb"); + must_save = difference > 0 && difference / (1024.0f * 1024.0f) >= save_interval; } - } else { - if (pipelines_cache_save_task == WorkerThreadPool::INVALID_TASK_ID || WorkerThreadPool::get_singleton()->is_task_completed(pipelines_cache_save_task)) { - pipelines_cache_save_task = WorkerThreadPool::get_singleton()->add_template_task(this, &RenderingDeviceVulkan::_save_pipeline_cache_threaded, pso_blob_size, false, "PipelineCacheSave"); + + if (must_save) { + pipelines_cache.current_size = pso_blob_size; + } else { + return; } } + + if (p_closing) { + _save_pipeline_cache(this); + } else { + pipelines_cache_save_task = WorkerThreadPool::get_singleton()->add_native_task(&_save_pipeline_cache, this, false, "PipelineCacheSave"); + } } -void RenderingDeviceVulkan::_save_pipeline_cache_threaded(size_t p_pso_blob_size) { - pipelines_cache.current_size = p_pso_blob_size; - pipelines_cache.buffer.clear(); - pipelines_cache.buffer.resize(p_pso_blob_size); - VkResult vr = vkGetPipelineCacheData(device, pipelines_cache.cache_object, &p_pso_blob_size, pipelines_cache.buffer.ptrw()); - ERR_FAIL_COND(vr); - print_verbose(vformat("Updated PSO cache (%.1f MiB)", p_pso_blob_size / (1024.0f * 1024.0f))); +void RenderingDeviceVulkan::_save_pipeline_cache(void *p_data) { + RenderingDeviceVulkan *self = static_cast<RenderingDeviceVulkan *>(p_data); - VkPhysicalDeviceProperties props; - vkGetPhysicalDeviceProperties(context->get_physical_device(), &props); - PipelineCacheHeader header = {}; - header.magic = 868 + VK_PIPELINE_CACHE_HEADER_VERSION_ONE; - header.data_size = pipelines_cache.buffer.size(); - header.data_hash = hash_murmur3_buffer(pipelines_cache.buffer.ptr(), pipelines_cache.buffer.size()); - header.device_id = props.deviceID; - header.vendor_id = props.vendorID; - header.driver_version = props.driverVersion; - for (size_t i = 0; i < VK_UUID_SIZE; i++) { - header.uuid[i] = props.pipelineCacheUUID[i]; - } - header.driver_abi = sizeof(void *); - Ref<FileAccess> f = FileAccess::open("user://vulkan/pipelines.cache", FileAccess::WRITE, nullptr); + self->pipelines_cache.buffer.resize(self->pipelines_cache.current_size); + + self->_thread_safe_.lock(); + VkResult vr = vkGetPipelineCacheData(self->device, self->pipelines_cache.cache_object, &self->pipelines_cache.current_size, self->pipelines_cache.buffer.ptr()); + self->_thread_safe_.unlock(); + ERR_FAIL_COND(vr != VK_SUCCESS && vr != VK_INCOMPLETE); // Incomplete is OK because the cache may have grown since the size was queried (unless when exiting). + print_verbose(vformat("Updated PSO cache (%.1f MiB)", self->pipelines_cache.current_size / (1024.0f * 1024.0f))); + + // The real buffer size may now be bigger than the updated current_size. + // We take into account the new size but keep the buffer resized in a worst-case fashion. + + self->pipelines_cache.header.data_size = self->pipelines_cache.current_size; + self->pipelines_cache.header.data_hash = hash_murmur3_buffer(self->pipelines_cache.buffer.ptr(), self->pipelines_cache.current_size); + Ref<FileAccess> f = FileAccess::open(self->pipelines_cache.file_path, FileAccess::WRITE, nullptr); if (f.is_valid()) { - f->store_buffer((const uint8_t *)&header, sizeof(PipelineCacheHeader)); - f->store_buffer(pipelines_cache.buffer); + f->store_buffer((const uint8_t *)&self->pipelines_cache.header, sizeof(PipelineCacheHeader)); + f->store_buffer(self->pipelines_cache.buffer.ptr(), self->pipelines_cache.current_size); } } diff --git a/drivers/vulkan/rendering_device_vulkan.h b/drivers/vulkan/rendering_device_vulkan.h index edff19a70c..5b28a67cec 100644 --- a/drivers/vulkan/rendering_device_vulkan.h +++ b/drivers/vulkan/rendering_device_vulkan.h @@ -805,8 +805,10 @@ class RenderingDeviceVulkan : public RenderingDevice { }; struct PipelineCache { + String file_path; + PipelineCacheHeader header = {}; size_t current_size = 0; - Vector<uint8_t> buffer; + LocalVector<uint8_t> buffer; VkPipelineCache cache_object = VK_NULL_HANDLE; }; @@ -816,7 +818,7 @@ class RenderingDeviceVulkan : public RenderingDevice { void _load_pipeline_cache(); void _update_pipeline_cache(bool p_closing = false); - void _save_pipeline_cache_threaded(size_t pso_blob_size); + static void _save_pipeline_cache(void *p_data); struct ComputePipeline { RID shader; diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp index b55eb586de..9f35230eaf 100644 --- a/drivers/vulkan/vulkan_context.cpp +++ b/drivers/vulkan/vulkan_context.cpp @@ -503,6 +503,7 @@ Error VulkanContext::_initialize_device_extensions() { register_requested_device_extension(VK_KHR_16BIT_STORAGE_EXTENSION_NAME, false); register_requested_device_extension(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME, false); register_requested_device_extension(VK_KHR_MAINTENANCE_2_EXTENSION_NAME, false); + register_requested_device_extension(VK_EXT_PIPELINE_CREATION_CACHE_CONTROL_EXTENSION_NAME, false); if (Engine::get_singleton()->is_generate_spirv_debug_info_enabled()) { register_requested_device_extension(VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME, true); diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 6c5c99698d..2123e79475 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -938,7 +938,9 @@ void CodeTextEditor::_complete_request() { for (const ScriptLanguage::CodeCompletionOption &e : entries) { Color font_color = completion_font_color; - if (e.insert_text.begins_with("\"") || e.insert_text.begins_with("\'")) { + if (!e.theme_color_name.is_empty() && EDITOR_GET("text_editor/completion/colorize_suggestions")) { + font_color = get_theme_color(e.theme_color_name, SNAME("Editor")); + } else if (e.insert_text.begins_with("\"") || e.insert_text.begins_with("\'")) { font_color = completion_string_color; } else if (e.insert_text.begins_with("#") || e.insert_text.begins_with("//")) { font_color = completion_comment_color; diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index bcb7c003c2..b3ca04420f 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -811,26 +811,30 @@ ConnectDialog::~ConnectDialog() { // Originally copied and adapted from EditorProperty, try to keep style in sync. Control *ConnectionsDockTree::make_custom_tooltip(const String &p_text) const { - EditorHelpBit *help_bit = memnew(EditorHelpBit); - help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1)); - - // p_text is expected to be something like this: - // "gui_input::(event: InputEvent)::<Signal description>" - // with the latter being possibly empty. - PackedStringArray slices = p_text.split("::", false); - if (slices.size() < 2) { - // Shouldn't happen here, but just in case pass the text along. - help_bit->set_text(p_text); - return help_bit; - } - - String text = TTR("Signal:") + " [u][b]" + slices[0] + "[/b][/u]"; - text += slices[1].strip_edges() + "\n"; - if (slices.size() > 2) { - text += slices[2].strip_edges(); - } else { + // `p_text` is expected to be something like this: + // - `class|Control||Control brief description.`; + // - `signal|gui_input|(event: InputEvent)|gui_input description.`; + // - `../../.. :: _on_gui_input()`. + // Note that the description can be empty or contain `|`. + PackedStringArray slices = p_text.split("|", true, 3); + if (slices.size() < 4) { + return nullptr; // Use default tooltip instead. + } + + String item_type = (slices[0] == "class") ? TTR("Class:") : TTR("Signal:"); + String item_name = slices[1].strip_edges(); + String item_params = slices[2].strip_edges(); + String item_descr = slices[3].strip_edges(); + + String text = item_type + " [u][b]" + item_name + "[/b][/u]" + item_params + "\n"; + if (item_descr.is_empty()) { text += "[i]" + TTR("No description.") + "[/i]"; + } else { + text += item_descr; } + + EditorHelpBit *help_bit = memnew(EditorHelpBit); + help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1)); help_bit->set_text(text); return help_bit; @@ -960,8 +964,7 @@ void ConnectionsDock::_disconnect(const ConnectDialog::ConnectionData &p_cd) { */ void ConnectionsDock::_disconnect_all() { TreeItem *item = tree->get_selected(); - - if (!_is_item_signal(*item)) { + if (!item || _get_item_type(*item) != TREE_ITEM_TYPE_SIGNAL) { return; } @@ -990,38 +993,44 @@ void ConnectionsDock::_disconnect_all() { void ConnectionsDock::_tree_item_selected() { TreeItem *item = tree->get_selected(); - if (!item) { // Unlikely. Disable button just in case. - connect_button->set_text(TTR("Connect...")); - connect_button->set_icon(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons"))); - connect_button->set_disabled(true); - } else if (_is_item_signal(*item)) { + if (item && _get_item_type(*item) == TREE_ITEM_TYPE_SIGNAL) { connect_button->set_text(TTR("Connect...")); connect_button->set_icon(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons"))); connect_button->set_disabled(false); - } else { + } else if (item && _get_item_type(*item) == TREE_ITEM_TYPE_CONNECTION) { connect_button->set_text(TTR("Disconnect")); connect_button->set_icon(get_theme_icon(SNAME("Unlinked"), SNAME("EditorIcons"))); connect_button->set_disabled(false); + } else { + connect_button->set_text(TTR("Connect...")); + connect_button->set_icon(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons"))); + connect_button->set_disabled(true); } } void ConnectionsDock::_tree_item_activated() { // "Activation" on double-click. - TreeItem *item = tree->get_selected(); - if (!item) { return; } - if (_is_item_signal(*item)) { + if (_get_item_type(*item) == TREE_ITEM_TYPE_SIGNAL) { _open_connection_dialog(*item); - } else { - _go_to_script(*item); + } else if (_get_item_type(*item) == TREE_ITEM_TYPE_CONNECTION) { + _go_to_method(*item); } } -bool ConnectionsDock::_is_item_signal(TreeItem &p_item) { - return (p_item.get_parent() == tree->get_root() || p_item.get_parent()->get_parent() == tree->get_root()); +ConnectionsDock::TreeItemType ConnectionsDock::_get_item_type(const TreeItem &p_item) const { + if (&p_item == tree->get_root()) { + return TREE_ITEM_TYPE_ROOT; + } else if (p_item.get_parent() == tree->get_root()) { + return TREE_ITEM_TYPE_CLASS; + } else if (p_item.get_parent()->get_parent() == tree->get_root()) { + return TREE_ITEM_TYPE_SIGNAL; + } else { + return TREE_ITEM_TYPE_CONNECTION; + } } bool ConnectionsDock::_is_connection_inherited(Connection &p_connection) { @@ -1077,8 +1086,8 @@ void ConnectionsDock::_open_edit_connection_dialog(TreeItem &p_item) { /* * Open slot method location in script editor. */ -void ConnectionsDock::_go_to_script(TreeItem &p_item) { - if (_is_item_signal(p_item)) { +void ConnectionsDock::_go_to_method(TreeItem &p_item) { + if (_get_item_type(p_item) != TREE_ITEM_TYPE_CONNECTION) { return; } @@ -1101,27 +1110,39 @@ void ConnectionsDock::_go_to_script(TreeItem &p_item) { } } +void ConnectionsDock::_handle_class_menu_option(int p_option) { + switch (p_option) { + case CLASS_MENU_OPEN_DOCS: + ScriptEditor::get_singleton()->goto_help("class:" + class_menu_doc_class_name); + EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT); + break; + } +} + +void ConnectionsDock::_class_menu_about_to_popup() { + class_menu->set_item_disabled(class_menu->get_item_index(CLASS_MENU_OPEN_DOCS), class_menu_doc_class_name.is_empty()); +} + void ConnectionsDock::_handle_signal_menu_option(int p_option) { TreeItem *item = tree->get_selected(); - - if (!item) { + if (!item || _get_item_type(*item) != TREE_ITEM_TYPE_SIGNAL) { return; } Dictionary meta = item->get_metadata(0); switch (p_option) { - case CONNECT: { + case SIGNAL_MENU_CONNECT: { _open_connection_dialog(*item); } break; - case DISCONNECT_ALL: { + case SIGNAL_MENU_DISCONNECT_ALL: { disconnect_all_dialog->set_text(vformat(TTR("Are you sure you want to remove all connections from the \"%s\" signal?"), meta["name"])); disconnect_all_dialog->popup_centered(); } break; - case COPY_NAME: { + case SIGNAL_MENU_COPY_NAME: { DisplayServer::get_singleton()->clipboard_set(meta["name"]); } break; - case OPEN_DOCUMENTATION: { + case SIGNAL_MENU_OPEN_DOCS: { ScriptEditor::get_singleton()->goto_help("class_signal:" + String(meta["class"]) + ":" + String(meta["name"])); EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT); } break; @@ -1130,8 +1151,7 @@ void ConnectionsDock::_handle_signal_menu_option(int p_option) { void ConnectionsDock::_signal_menu_about_to_popup() { TreeItem *item = tree->get_selected(); - - if (!item) { + if (!item || _get_item_type(*item) != TREE_ITEM_TYPE_SIGNAL) { return; } @@ -1144,25 +1164,24 @@ void ConnectionsDock::_signal_menu_about_to_popup() { } } - signal_menu->set_item_disabled(signal_menu->get_item_index(DISCONNECT_ALL), disable_disconnect_all); - signal_menu->set_item_disabled(signal_menu->get_item_index(OPEN_DOCUMENTATION), String(meta["class"]).is_empty()); + signal_menu->set_item_disabled(signal_menu->get_item_index(SIGNAL_MENU_DISCONNECT_ALL), disable_disconnect_all); + signal_menu->set_item_disabled(signal_menu->get_item_index(SIGNAL_MENU_OPEN_DOCS), String(meta["class"]).is_empty()); } void ConnectionsDock::_handle_slot_menu_option(int p_option) { TreeItem *item = tree->get_selected(); - - if (!item) { + if (!item || _get_item_type(*item) != TREE_ITEM_TYPE_CONNECTION) { return; } switch (p_option) { - case EDIT: { + case SLOT_MENU_EDIT: { _open_edit_connection_dialog(*item); } break; - case GO_TO_SCRIPT: { - _go_to_script(*item); + case SLOT_MENU_GO_TO_METHOD: { + _go_to_method(*item); } break; - case DISCONNECT: { + case SLOT_MENU_DISCONNECT: { Connection connection = item->get_metadata(0); _disconnect(connection); update_tree(); @@ -1171,33 +1190,50 @@ void ConnectionsDock::_handle_slot_menu_option(int p_option) { } void ConnectionsDock::_slot_menu_about_to_popup() { - bool connection_is_inherited = tree->get_selected()->has_meta("_inherited_connection"); + TreeItem *item = tree->get_selected(); + if (!item || _get_item_type(*item) != TREE_ITEM_TYPE_CONNECTION) { + return; + } + + bool connection_is_inherited = item->has_meta("_inherited_connection"); - slot_menu->set_item_disabled(slot_menu->get_item_index(EDIT), connection_is_inherited); - slot_menu->set_item_disabled(slot_menu->get_item_index(DISCONNECT), connection_is_inherited); + slot_menu->set_item_disabled(slot_menu->get_item_index(SLOT_MENU_EDIT), connection_is_inherited); + slot_menu->set_item_disabled(slot_menu->get_item_index(SLOT_MENU_DISCONNECT), connection_is_inherited); } -void ConnectionsDock::_rmb_pressed(Vector2 p_position, MouseButton p_button) { - if (p_button != MouseButton::RIGHT) { +void ConnectionsDock::_rmb_pressed(const Ref<InputEvent> &p_event) { + const Ref<InputEventMouseButton> &mb_event = p_event; + if (mb_event.is_null() || !mb_event->is_pressed() || mb_event->get_button_index() != MouseButton::RIGHT) { return; } - TreeItem *item = tree->get_selected(); - + TreeItem *item = tree->get_item_at_position(mb_event->get_position()); if (!item) { return; } - Vector2 screen_position = tree->get_screen_position() + p_position; + Vector2 screen_position = tree->get_screen_position() + mb_event->get_position(); - if (_is_item_signal(*item)) { - signal_menu->set_position(screen_position); - signal_menu->reset_size(); - signal_menu->popup(); - } else { - slot_menu->set_position(screen_position); - slot_menu->reset_size(); - slot_menu->popup(); + switch (_get_item_type(*item)) { + case TREE_ITEM_TYPE_ROOT: + break; + case TREE_ITEM_TYPE_CLASS: + class_menu_doc_class_name = item->get_metadata(0); + class_menu->set_position(screen_position); + class_menu->reset_size(); + class_menu->popup(); + accept_event(); // Don't collapse item. + break; + case TREE_ITEM_TYPE_SIGNAL: + signal_menu->set_position(screen_position); + signal_menu->reset_size(); + signal_menu->popup(); + break; + case TREE_ITEM_TYPE_CONNECTION: + slot_menu->set_position(screen_position); + slot_menu->reset_size(); + slot_menu->popup(); + break; } } @@ -1212,9 +1248,9 @@ void ConnectionsDock::_connect_pressed() { return; } - if (_is_item_signal(*item)) { + if (_get_item_type(*item) == TREE_ITEM_TYPE_SIGNAL) { _open_connection_dialog(*item); - } else { + } else if (_get_item_type(*item) == TREE_ITEM_TYPE_CONNECTION) { Connection connection = item->get_metadata(0); _disconnect(connection); update_tree(); @@ -1227,14 +1263,16 @@ void ConnectionsDock::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: { search_box->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); - signal_menu->set_item_icon(signal_menu->get_item_index(CONNECT), get_theme_icon(SNAME("Instance"), SNAME("EditorIcons"))); - signal_menu->set_item_icon(signal_menu->get_item_index(DISCONNECT_ALL), get_theme_icon(SNAME("Unlinked"), SNAME("EditorIcons"))); - signal_menu->set_item_icon(signal_menu->get_item_index(COPY_NAME), get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons"))); - signal_menu->set_item_icon(signal_menu->get_item_index(OPEN_DOCUMENTATION), get_theme_icon(SNAME("Help"), SNAME("EditorIcons"))); + class_menu->set_item_icon(class_menu->get_item_index(CLASS_MENU_OPEN_DOCS), get_theme_icon(SNAME("Help"), SNAME("EditorIcons"))); + + signal_menu->set_item_icon(signal_menu->get_item_index(SIGNAL_MENU_CONNECT), get_theme_icon(SNAME("Instance"), SNAME("EditorIcons"))); + signal_menu->set_item_icon(signal_menu->get_item_index(SIGNAL_MENU_DISCONNECT_ALL), get_theme_icon(SNAME("Unlinked"), SNAME("EditorIcons"))); + signal_menu->set_item_icon(signal_menu->get_item_index(SIGNAL_MENU_COPY_NAME), get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons"))); + signal_menu->set_item_icon(signal_menu->get_item_index(SIGNAL_MENU_OPEN_DOCS), get_theme_icon(SNAME("Help"), SNAME("EditorIcons"))); - slot_menu->set_item_icon(slot_menu->get_item_index(EDIT), get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); - slot_menu->set_item_icon(slot_menu->get_item_index(GO_TO_SCRIPT), get_theme_icon(SNAME("ArrowRight"), SNAME("EditorIcons"))); - slot_menu->set_item_icon(slot_menu->get_item_index(DISCONNECT), get_theme_icon(SNAME("Unlinked"), SNAME("EditorIcons"))); + slot_menu->set_item_icon(slot_menu->get_item_index(SLOT_MENU_EDIT), get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); + slot_menu->set_item_icon(slot_menu->get_item_index(SLOT_MENU_GO_TO_METHOD), get_theme_icon(SNAME("ArrowRight"), SNAME("EditorIcons"))); + slot_menu->set_item_icon(slot_menu->get_item_index(SLOT_MENU_DISCONNECT), get_theme_icon(SNAME("Unlinked"), SNAME("EditorIcons"))); } break; case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { @@ -1272,6 +1310,7 @@ void ConnectionsDock::update_tree() { while (native_base != StringName()) { String class_name; String doc_class_name; + String class_brief; Ref<Texture2D> class_icon; List<MethodInfo> class_signals; @@ -1293,6 +1332,7 @@ void ConnectionsDock::update_tree() { } HashMap<String, DocData::ClassDoc>::ConstIterator F = doc_data->class_list.find(doc_class_name); if (F) { + class_brief = F->value.brief_description; for (int i = 0; i < F->value.signals.size(); i++) { descr_cache[doc_class_name][F->value.signals[i].name] = F->value.signals[i].description; } @@ -1329,16 +1369,17 @@ void ConnectionsDock::update_tree() { class_name = native_base; doc_class_name = class_name; - // For a native class, the cache is filled once. - if (!descr_cache.has(doc_class_name)) { - HashMap<String, DocData::ClassDoc>::ConstIterator F = doc_data->class_list.find(doc_class_name); - if (F) { + HashMap<String, DocData::ClassDoc>::ConstIterator F = doc_data->class_list.find(doc_class_name); + if (F) { + class_brief = DTR(F->value.brief_description); + // For a native class, the cache is filled once. + if (!descr_cache.has(doc_class_name)) { for (int i = 0; i < F->value.signals.size(); i++) { descr_cache[doc_class_name][F->value.signals[i].name] = DTR(F->value.signals[i].description); } - } else { - doc_class_name = String(); } + } else { + doc_class_name = String(); } if (has_theme_icon(native_base, SNAME("EditorIcons"))) { @@ -1362,14 +1403,17 @@ void ConnectionsDock::update_tree() { section_item = tree->create_item(root); section_item->set_text(0, class_name); + // `|` separators used in `make_custom_tooltip()` for formatting. + section_item->set_tooltip_text(0, "class|" + class_name + "||" + class_brief); section_item->set_icon(0, class_icon); section_item->set_selectable(0, false); section_item->set_editable(0, false); section_item->set_custom_bg_color(0, get_theme_color(SNAME("prop_subsection"), SNAME("Editor"))); + section_item->set_metadata(0, doc_class_name); } for (MethodInfo &mi : class_signals) { - const StringName signal_name = mi.name; + const StringName &signal_name = mi.name; if (!search_box->get_text().is_subsequence_ofn(signal_name)) { continue; } @@ -1404,8 +1448,8 @@ void ConnectionsDock::update_tree() { } } - // "::" separators used in make_custom_tooltip for formatting. - signal_item->set_tooltip_text(0, String(signal_name) + "::" + signame.trim_prefix(mi.name) + "::" + descr); + // `|` separators used in `make_custom_tooltip()` for formatting. + signal_item->set_tooltip_text(0, "signal|" + String(signal_name) + "|" + signame.trim_prefix(mi.name) + "|" + descr); } // List existing connections. @@ -1500,28 +1544,34 @@ ConnectionsDock::ConnectionsDock() { disconnect_all_dialog->connect("confirmed", callable_mp(this, &ConnectionsDock::_disconnect_all)); disconnect_all_dialog->set_text(TTR("Are you sure you want to remove all connections from this signal?")); + class_menu = memnew(PopupMenu); + class_menu->connect("id_pressed", callable_mp(this, &ConnectionsDock::_handle_class_menu_option)); + class_menu->connect("about_to_popup", callable_mp(this, &ConnectionsDock::_class_menu_about_to_popup)); + class_menu->add_item(TTR("Open Documentation"), CLASS_MENU_OPEN_DOCS); + add_child(class_menu); + signal_menu = memnew(PopupMenu); - add_child(signal_menu); signal_menu->connect("id_pressed", callable_mp(this, &ConnectionsDock::_handle_signal_menu_option)); signal_menu->connect("about_to_popup", callable_mp(this, &ConnectionsDock::_signal_menu_about_to_popup)); - signal_menu->add_item(TTR("Connect..."), CONNECT); - signal_menu->add_item(TTR("Disconnect All"), DISCONNECT_ALL); - signal_menu->add_item(TTR("Copy Name"), COPY_NAME); + signal_menu->add_item(TTR("Connect..."), SIGNAL_MENU_CONNECT); + signal_menu->add_item(TTR("Disconnect All"), SIGNAL_MENU_DISCONNECT_ALL); + signal_menu->add_item(TTR("Copy Name"), SIGNAL_MENU_COPY_NAME); signal_menu->add_separator(); - signal_menu->add_item(TTR("Open Documentation"), OPEN_DOCUMENTATION); + signal_menu->add_item(TTR("Open Documentation"), SIGNAL_MENU_OPEN_DOCS); + add_child(signal_menu); slot_menu = memnew(PopupMenu); - add_child(slot_menu); slot_menu->connect("id_pressed", callable_mp(this, &ConnectionsDock::_handle_slot_menu_option)); slot_menu->connect("about_to_popup", callable_mp(this, &ConnectionsDock::_slot_menu_about_to_popup)); - slot_menu->add_item(TTR("Edit..."), EDIT); - slot_menu->add_item(TTR("Go to Method"), GO_TO_SCRIPT); - slot_menu->add_item(TTR("Disconnect"), DISCONNECT); + slot_menu->add_item(TTR("Edit..."), SLOT_MENU_EDIT); + slot_menu->add_item(TTR("Go to Method"), SLOT_MENU_GO_TO_METHOD); + slot_menu->add_item(TTR("Disconnect"), SLOT_MENU_DISCONNECT); + add_child(slot_menu); connect_dialog->connect("connected", callable_mp(this, &ConnectionsDock::_make_or_edit_connection)); tree->connect("item_selected", callable_mp(this, &ConnectionsDock::_tree_item_selected)); tree->connect("item_activated", callable_mp(this, &ConnectionsDock::_tree_item_activated)); - tree->connect("item_mouse_selected", callable_mp(this, &ConnectionsDock::_rmb_pressed)); + tree->connect("gui_input", callable_mp(this, &ConnectionsDock::_rmb_pressed)); add_theme_constant_override("separation", 3 * EDSCALE); } diff --git a/editor/connections_dialog.h b/editor/connections_dialog.h index d728072c2e..b07b08ecc7 100644 --- a/editor/connections_dialog.h +++ b/editor/connections_dialog.h @@ -196,18 +196,27 @@ class ConnectionsDockTree : public Tree { class ConnectionsDock : public VBoxContainer { GDCLASS(ConnectionsDock, VBoxContainer); - // Right-click popup menu options. - enum SignalMenuOption { - CONNECT, - DISCONNECT_ALL, - COPY_NAME, - OPEN_DOCUMENTATION, + enum TreeItemType { + TREE_ITEM_TYPE_ROOT, + TREE_ITEM_TYPE_CLASS, + TREE_ITEM_TYPE_SIGNAL, + TREE_ITEM_TYPE_CONNECTION, }; + // Right-click context menu options. + enum ClassMenuOption { + CLASS_MENU_OPEN_DOCS, + }; + enum SignalMenuOption { + SIGNAL_MENU_CONNECT, + SIGNAL_MENU_DISCONNECT_ALL, + SIGNAL_MENU_COPY_NAME, + SIGNAL_MENU_OPEN_DOCS, + }; enum SlotMenuOption { - EDIT, - GO_TO_SCRIPT, - DISCONNECT, + SLOT_MENU_EDIT, + SLOT_MENU_GO_TO_METHOD, + SLOT_MENU_DISCONNECT, }; Node *selected_node = nullptr; @@ -216,6 +225,8 @@ class ConnectionsDock : public VBoxContainer { ConfirmationDialog *disconnect_all_dialog = nullptr; ConnectDialog *connect_dialog = nullptr; Button *connect_button = nullptr; + PopupMenu *class_menu = nullptr; + String class_menu_doc_class_name; PopupMenu *signal_menu = nullptr; PopupMenu *slot_menu = nullptr; LineEdit *search_box = nullptr; @@ -231,18 +242,20 @@ class ConnectionsDock : public VBoxContainer { void _tree_item_selected(); void _tree_item_activated(); - bool _is_item_signal(TreeItem &p_item); + TreeItemType _get_item_type(const TreeItem &p_item) const; bool _is_connection_inherited(Connection &p_connection); void _open_connection_dialog(TreeItem &p_item); void _open_edit_connection_dialog(TreeItem &p_item); - void _go_to_script(TreeItem &p_item); + void _go_to_method(TreeItem &p_item); + void _handle_class_menu_option(int p_option); + void _class_menu_about_to_popup(); void _handle_signal_menu_option(int p_option); void _signal_menu_about_to_popup(); void _handle_slot_menu_option(int p_option); void _slot_menu_about_to_popup(); - void _rmb_pressed(Vector2 p_position, MouseButton p_button); + void _rmb_pressed(const Ref<InputEvent> &p_event); void _close(); protected: diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 7fc9806bd2..3c14dedb2d 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -904,36 +904,34 @@ void EditorProperty::_update_pin_flags() { } } -static Control *make_help_bit(const String &p_text, const String &p_warning, const Color &p_warn_color, bool p_property) { - EditorHelpBit *help_bit = memnew(EditorHelpBit); - help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1)); - - PackedStringArray slices = p_text.split("::", false); - if (slices.is_empty()) { - // Shouldn't happen here, but just in case pass the text along. - help_bit->set_text(p_text); - return help_bit; +static Control *make_help_bit(const String &p_item_type, const String &p_text, const String &p_warning, const Color &p_warn_color) { + // `p_text` is expected to be something like this: + // `item_name|Item description.`. + // Note that the description can be empty or contain `|`. + PackedStringArray slices = p_text.split("|", true, 1); + if (slices.size() < 2) { + return nullptr; // Use default tooltip instead. } - String property_name = slices[0].strip_edges(); + String item_name = slices[0].strip_edges(); + String item_descr = slices[1].strip_edges(); + String text; - if (p_property) { - text = TTR("Property:") + " "; + if (!p_item_type.is_empty()) { + text = p_item_type + " "; } - text += "[u][b]" + property_name + "[/b][/u]"; - - if (slices.size() > 1) { - String property_doc = slices[1].strip_edges(); - if (property_name != property_doc) { - text += "\n" + property_doc; - } + text += "[u][b]" + item_name + "[/b][/u]\n"; + if (item_descr.is_empty()) { + text += "[i]" + TTR("No description.") + "[/i]"; } else { - text += "\n[i]" + TTR("No description.") + "[/i]"; + text += item_descr; } - if (!p_warning.is_empty()) { text += "\n[b][color=" + p_warn_color.to_html(false) + "]" + p_warning + "[/color][/b]"; } + + EditorHelpBit *help_bit = memnew(EditorHelpBit); + help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1)); help_bit->set_text(text); return help_bit; @@ -946,7 +944,7 @@ Control *EditorProperty::make_custom_tooltip(const String &p_text) const { warn = object->call("_get_property_warning", property); warn_color = get_theme_color(SNAME("warning_color")); } - return make_help_bit(p_text, warn, warn_color, true); + return make_help_bit(TTR("Property:"), p_text, warn, warn_color); } void EditorProperty::menu_option(int p_option) { @@ -1142,6 +1140,10 @@ void EditorInspectorPlugin::_bind_methods() { void EditorInspectorCategory::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + menu->set_item_icon(menu->get_item_index(MENU_OPEN_DOCS), get_theme_icon(SNAME("Help"), SNAME("EditorIcons"))); + } break; case NOTIFICATION_DRAW: { Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg")); @@ -1175,7 +1177,7 @@ void EditorInspectorCategory::_notification(int p_what) { } Control *EditorInspectorCategory::make_custom_tooltip(const String &p_text) const { - return make_help_bit(p_text, String(), Color(), false); + return make_help_bit(TTR("Class:"), p_text, String(), Color()); } Size2 EditorInspectorCategory::get_minimum_size() const { @@ -1192,7 +1194,37 @@ Size2 EditorInspectorCategory::get_minimum_size() const { return ms; } +void EditorInspectorCategory::_handle_menu_option(int p_option) { + switch (p_option) { + case MENU_OPEN_DOCS: + ScriptEditor::get_singleton()->goto_help("class:" + doc_class_name); + EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT); + break; + } +} + +void EditorInspectorCategory::gui_input(const Ref<InputEvent> &p_event) { + if (doc_class_name.is_empty()) { + return; + } + + const Ref<InputEventMouseButton> &mb_event = p_event; + if (mb_event.is_null() || !mb_event->is_pressed() || mb_event->get_button_index() != MouseButton::RIGHT) { + return; + } + + menu->set_item_disabled(menu->get_item_index(MENU_OPEN_DOCS), !EditorHelp::get_doc_data()->class_list.has(doc_class_name)); + + menu->set_position(get_screen_position() + mb_event->get_position()); + menu->reset_size(); + menu->popup(); +} + EditorInspectorCategory::EditorInspectorCategory() { + menu = memnew(PopupMenu); + menu->connect("id_pressed", callable_mp(this, &EditorInspectorCategory::_handle_menu_option)); + menu->add_item(TTR("Open Documentation"), MENU_OPEN_DOCS); + add_child(menu); } //////////////////////////////////////////////// @@ -2801,6 +2833,14 @@ void EditorInspector::update_tree() { main_vbox->add_child(category); category_vbox = nullptr; //reset + // `hint_script` should contain a native class name or a script path. + // Otherwise the category was probably added via `@export_category` or `_get_property_list()`. + if (p.hint_string.is_empty()) { + category->label = p.name; + category->set_tooltip_text(p.name); + continue; // Do not add an icon, do not change the current class (`doc_name`). + } + String type = p.name; String label = p.name; doc_name = p.name; @@ -2837,6 +2877,7 @@ void EditorInspector::update_tree() { // Set the category label. category->label = label; + category->doc_class_name = doc_name; if (use_doc_hints) { String descr = ""; @@ -2845,16 +2886,18 @@ void EditorInspector::update_tree() { DocTools *dd = EditorHelp::get_doc_data(); HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(doc_name); if (E) { - descr = DTR(E->value.brief_description); + descr = E->value.brief_description; } if (ClassDB::class_exists(doc_name)) { + descr = DTR(descr); // Do not translate the class description of scripts. class_descr_cache[doc_name] = descr; // Do not cache the class description of scripts. } } else { descr = class_descr_cache[doc_name]; } - category->set_tooltip_text(p.name + "::" + descr); + // `|` separator used in `make_help_bit()` for formatting. + category->set_tooltip_text(p.name + "|" + descr); } // Add editors at the start of a category. @@ -3196,13 +3239,18 @@ void EditorInspector::update_tree() { } if (!found) { + bool is_native_class = ClassDB::class_exists(classname); + // Build the property description String and add it to the cache. DocTools *dd = EditorHelp::get_doc_data(); HashMap<String, DocData::ClassDoc>::ConstIterator F = dd->class_list.find(classname); while (F && doc_info.description.is_empty()) { for (int i = 0; i < F->value.properties.size(); i++) { if (F->value.properties[i].name == propname.operator String()) { - doc_info.description = DTR(F->value.properties[i].description); + doc_info.description = F->value.properties[i].description; + if (is_native_class) { + doc_info.description = DTR(doc_info.description); // Do not translate the property description of scripts. + } const Vector<String> class_enum = F->value.properties[i].enumeration.split("."); const String class_name = class_enum[0]; @@ -3215,7 +3263,11 @@ void EditorInspector::update_tree() { if (val.enumeration == enum_name && !val.name.ends_with("_MAX")) { const String enum_value = EditorPropertyNameProcessor::get_singleton()->process_name(val.name, EditorPropertyNameProcessor::STYLE_CAPITALIZED); // Prettify the enum value display, so that "<ENUM NAME>_<VALUE>" becomes "Value". - String desc = DTR(val.description).trim_prefix("\n"); + String desc = val.description; + if (is_native_class) { + desc = DTR(desc); // Do not translate the enum value description of scripts. + } + desc = desc.trim_prefix("\n"); doc_info.description += vformat( "\n[b]%s:[/b] %s", enum_value.trim_prefix(EditorPropertyNameProcessor::get_singleton()->process_name(enum_name, EditorPropertyNameProcessor::STYLE_CAPITALIZED) + " "), @@ -3234,7 +3286,10 @@ void EditorInspector::update_tree() { if (slices.size() == 2 && slices[0].begins_with("theme_override_")) { for (int i = 0; i < F->value.theme_properties.size(); i++) { if (F->value.theme_properties[i].name == slices[1]) { - doc_info.description = DTR(F->value.theme_properties[i].description); + doc_info.description = F->value.theme_properties[i].description; + if (is_native_class) { + doc_info.description = DTR(doc_info.description); // Do not translate the theme item description of scripts. + } doc_info.path = "class_theme_item:" + F->value.name + ":" + F->value.theme_properties[i].name; break; } @@ -3248,7 +3303,7 @@ void EditorInspector::update_tree() { } } - if (ClassDB::class_exists(classname)) { + if (is_native_class) { doc_info_cache[classname][propname] = doc_info; // Do not cache the doc information of scripts. } } @@ -3340,11 +3395,8 @@ void EditorInspector::update_tree() { ep->connect("multiple_properties_changed", callable_mp(this, &EditorInspector::_multiple_properties_changed)); ep->connect("resource_selected", callable_mp(this, &EditorInspector::_resource_selected), CONNECT_DEFERRED); ep->connect("object_id_selected", callable_mp(this, &EditorInspector::_object_id_selected), CONNECT_DEFERRED); - if (!doc_info.description.is_empty()) { - ep->set_tooltip_text(property_prefix + p.name + "::" + doc_info.description); - } else { - ep->set_tooltip_text(property_prefix + p.name); - } + // `|` separator used in `make_help_bit()` for formatting. + ep->set_tooltip_text(property_prefix + p.name + "|" + doc_info.description); ep->set_doc_path(doc_info.path); ep->update_property(); ep->_update_pin_flags(); diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 63d3db9b89..4393922f52 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -253,11 +253,22 @@ class EditorInspectorCategory : public Control { GDCLASS(EditorInspectorCategory, Control); friend class EditorInspector; + + // Right-click context menu options. + enum ClassMenuOption { + MENU_OPEN_DOCS, + }; + Ref<Texture2D> icon; String label; + String doc_class_name; + PopupMenu *menu = nullptr; + + void _handle_menu_option(int p_option); protected: void _notification(int p_what); + virtual void gui_input(const Ref<InputEvent> &p_event) override; public: virtual Size2 get_minimum_size() const override; diff --git a/editor/editor_layouts_dialog.cpp b/editor/editor_layouts_dialog.cpp index 1f7172db57..dce61f66d3 100644 --- a/editor/editor_layouts_dialog.cpp +++ b/editor/editor_layouts_dialog.cpp @@ -42,7 +42,7 @@ void EditorLayoutsDialog::_line_gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventKey> k = p_event; if (k.is_valid()) { - if (k->is_action_pressed(SNAME("ui_accept"), false, true)) { + if (k->is_action_pressed(SNAME("ui_text_submit"), false, true)) { if (get_hide_on_ok()) { hide(); } diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 8be4b94062..ea743fe470 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -3331,6 +3331,9 @@ bool EditorNode::is_addon_plugin_enabled(const String &p_addon) const { } void EditorNode::_remove_edited_scene(bool p_change_tab) { + // When scene gets closed no node is edited anymore, so make sure the editors are notified before nodes are freed. + hide_unused_editors(SceneTreeDock::get_singleton()); + int new_index = editor_data.get_edited_scene(); int old_index = new_index; diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index b923566bfa..e042d8570f 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -609,6 +609,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("text_editor/completion/complete_file_paths", true); _initial_set("text_editor/completion/add_type_hints", false); _initial_set("text_editor/completion/use_single_quotes", false); + _initial_set("text_editor/completion/colorize_suggestions", true); // Help _initial_set("text_editor/help/show_help_index", true); diff --git a/editor/gui/editor_scene_tabs.cpp b/editor/gui/editor_scene_tabs.cpp index 3732b922c7..553c45bd7f 100644 --- a/editor/gui/editor_scene_tabs.cpp +++ b/editor/gui/editor_scene_tabs.cpp @@ -142,38 +142,53 @@ void EditorSceneTabs::_update_context_menu() { scene_tabs_context_menu->reset_size(); int tab_id = scene_tabs->get_hovered_tab(); + bool no_root_node = !EditorNode::get_editor_data().get_edited_scene_root(tab_id); scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/new_scene"), EditorNode::FILE_NEW_SCENE); if (tab_id >= 0) { scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_scene"), EditorNode::FILE_SAVE_SCENE); + _disable_menu_option_if(EditorNode::FILE_SAVE_SCENE, no_root_node); scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_scene_as"), EditorNode::FILE_SAVE_AS_SCENE); + _disable_menu_option_if(EditorNode::FILE_SAVE_AS_SCENE, no_root_node); } + scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_all_scenes"), EditorNode::FILE_SAVE_ALL_SCENES); + bool can_save_all_scenes = false; + for (int i = 0; i < EditorNode::get_editor_data().get_edited_scene_count(); i++) { + if (!EditorNode::get_editor_data().get_scene_path(i).is_empty() && EditorNode::get_editor_data().get_edited_scene_root(i)) { + can_save_all_scenes = true; + break; + } + } + _disable_menu_option_if(EditorNode::FILE_SAVE_ALL_SCENES, !can_save_all_scenes); + if (tab_id >= 0) { scene_tabs_context_menu->add_separator(); scene_tabs_context_menu->add_item(TTR("Show in FileSystem"), EditorNode::FILE_SHOW_IN_FILESYSTEM); + _disable_menu_option_if(EditorNode::FILE_SHOW_IN_FILESYSTEM, !ResourceLoader::exists(EditorNode::get_editor_data().get_scene_path(tab_id))); scene_tabs_context_menu->add_item(TTR("Play This Scene"), EditorNode::FILE_RUN_SCENE); + _disable_menu_option_if(EditorNode::FILE_RUN_SCENE, no_root_node); scene_tabs_context_menu->add_separator(); scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/close_scene"), EditorNode::FILE_CLOSE); scene_tabs_context_menu->set_item_text(scene_tabs_context_menu->get_item_index(EditorNode::FILE_CLOSE), TTR("Close Tab")); scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/reopen_closed_scene"), EditorNode::FILE_OPEN_PREV); scene_tabs_context_menu->set_item_text(scene_tabs_context_menu->get_item_index(EditorNode::FILE_OPEN_PREV), TTR("Undo Close Tab")); - if (!EditorNode::get_singleton()->has_previous_scenes()) { - scene_tabs_context_menu->set_item_disabled(scene_tabs_context_menu->get_item_index(EditorNode::FILE_OPEN_PREV), true); - } + _disable_menu_option_if(EditorNode::FILE_OPEN_PREV, !EditorNode::get_singleton()->has_previous_scenes()); scene_tabs_context_menu->add_item(TTR("Close Other Tabs"), EditorNode::FILE_CLOSE_OTHERS); - if (EditorNode::get_editor_data().get_edited_scene_count() <= 1) { - scene_tabs_context_menu->set_item_disabled(scene_tabs_context_menu->get_item_index(EditorNode::FILE_CLOSE_OTHERS), true); - } + _disable_menu_option_if(EditorNode::FILE_CLOSE_OTHERS, EditorNode::get_editor_data().get_edited_scene_count() <= 1); scene_tabs_context_menu->add_item(TTR("Close Tabs to the Right"), EditorNode::FILE_CLOSE_RIGHT); - if (EditorNode::get_editor_data().get_edited_scene_count() == tab_id + 1) { - scene_tabs_context_menu->set_item_disabled(scene_tabs_context_menu->get_item_index(EditorNode::FILE_CLOSE_RIGHT), true); - } + _disable_menu_option_if(EditorNode::FILE_CLOSE_RIGHT, EditorNode::get_editor_data().get_edited_scene_count() == tab_id + 1); scene_tabs_context_menu->add_item(TTR("Close All Tabs"), EditorNode::FILE_CLOSE_ALL); } } +void EditorSceneTabs::_disable_menu_option_if(int p_option, bool p_condition) { + if (p_condition) { + scene_tabs_context_menu->set_item_disabled(scene_tabs_context_menu->get_item_index(p_option), true); + } +} + // TODO: This REALLY should be done in a better way than replacing all tabs after almost EVERY action. void EditorSceneTabs::update_scene_tabs() { tab_preview_panel->hide(); diff --git a/editor/gui/editor_scene_tabs.h b/editor/gui/editor_scene_tabs.h index c0dbfccdab..88e5b35364 100644 --- a/editor/gui/editor_scene_tabs.h +++ b/editor/gui/editor_scene_tabs.h @@ -66,6 +66,7 @@ class EditorSceneTabs : public MarginContainer { void _reposition_active_tab(int p_to_index); void _update_context_menu(); + void _disable_menu_option_if(int p_option, bool p_condition); void _tab_preview_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata); diff --git a/editor/import_dock.cpp b/editor/import_dock.cpp index fcd2d8f908..018231cda5 100644 --- a/editor/import_dock.cpp +++ b/editor/import_dock.cpp @@ -543,8 +543,11 @@ void ImportDock::_reimport_and_cleanup() { Ref<Resource> old_res = old_resources[path]; Ref<Resource> new_res = ResourceLoader::load(path); - for (int j = 0; j < EditorNode::get_editor_data().get_edited_scene_count(); j++) { - _replace_resource_in_object(EditorNode::get_editor_data().get_edited_scene_root(j), old_res, new_res); + for (int i = 0; i < EditorNode::get_editor_data().get_edited_scene_count(); i++) { + Node *edited_scene_root = EditorNode::get_editor_data().get_edited_scene_root(i); + if (likely(edited_scene_root)) { + _replace_resource_in_object(edited_scene_root, old_res, new_res); + } } for (Ref<Resource> res : external_resources) { _replace_resource_in_object(res.ptr(), old_res, new_res); diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index a1ddfc4b85..97ab6d04f4 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -1834,7 +1834,7 @@ void ThemeItemEditorDialog::_edit_theme_item_gui_input(const Ref<InputEvent> &p_ return; } - if (k->is_action_pressed(SNAME("ui_accept"), false, true)) { + if (k->is_action_pressed(SNAME("ui_text_submit"), false, true)) { _confirm_edit_theme_item(); edit_theme_item_dialog->hide(); edit_theme_item_dialog->set_input_as_handled(); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 3019354d93..6cad3b2b90 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -1207,6 +1207,9 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base for (const PropertyInfo &E : members) { if (!String(E.name).contains("/")) { ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER); + if (GDScriptParser::theme_color_names.has(E.name)) { + option.theme_color_name = GDScriptParser::theme_color_names[E.name]; + } r_result.insert(option.display, option); } } diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 039d46f678..a0d02b12b5 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -66,6 +66,10 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { return Variant::VARIANT_MAX; } +#ifdef TOOLS_ENABLED +HashMap<String, String> GDScriptParser::theme_color_names; +#endif + void GDScriptParser::cleanup() { builtin_types.clear(); } @@ -121,6 +125,15 @@ GDScriptParser::GDScriptParser() { #ifdef DEBUG_ENABLED is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable"); #endif + +#ifdef TOOLS_ENABLED + if (theme_color_names.is_empty()) { + theme_color_names.insert("x", "axis_x_color"); + theme_color_names.insert("y", "axis_y_color"); + theme_color_names.insert("z", "axis_z_color"); + theme_color_names.insert("w", "axis_w_color"); + } +#endif } GDScriptParser::~GDScriptParser() { diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 05c5bc2f11..9690784cba 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -1540,6 +1540,10 @@ public: int get_last_line_number() const { return current.end_line; } #endif +#ifdef TOOLS_ENABLED + static HashMap<String, String> theme_color_names; +#endif // TOOLS_ENABLED + GDScriptParser(); ~GDScriptParser(); diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 9e7611bc5e..74de40e901 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -563,17 +563,17 @@ Error GLTFDocument::_parse_scenes(Ref<GLTFState> p_state) { if (scenes.size()) { ERR_FAIL_COND_V(loaded_scene >= scenes.size(), ERR_FILE_CORRUPT); - const Dictionary &s = scenes[loaded_scene]; - ERR_FAIL_COND_V(!s.has("nodes"), ERR_UNAVAILABLE); - const Array &nodes = s["nodes"]; + const Dictionary &scene_dict = scenes[loaded_scene]; + ERR_FAIL_COND_V(!scene_dict.has("nodes"), ERR_UNAVAILABLE); + const Array &nodes = scene_dict["nodes"]; for (int j = 0; j < nodes.size(); j++) { p_state->root_nodes.push_back(nodes[j]); } - - if (s.has("name") && !String(s["name"]).is_empty() && !((String)s["name"]).begins_with("Scene")) { - p_state->scene_name = _gen_unique_name(p_state, s["name"]); + // Determine what to use for the scene name. + if (scene_dict.has("name") && !String(scene_dict["name"]).is_empty() && !((String)scene_dict["name"]).begins_with("Scene")) { + p_state->scene_name = scene_dict["name"]; } else { - p_state->scene_name = _gen_unique_name(p_state, p_state->filename); + p_state->scene_name = p_state->filename; } } @@ -5271,23 +5271,21 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> p_state) { void GLTFDocument::_assign_node_names(Ref<GLTFState> p_state) { for (int i = 0; i < p_state->nodes.size(); i++) { Ref<GLTFNode> gltf_node = p_state->nodes[i]; - // Any joints get unique names generated when the skeleton is made, unique to the skeleton if (gltf_node->skeleton >= 0) { continue; } - - if (gltf_node->get_name().is_empty()) { + String gltf_node_name = gltf_node->get_name(); + if (gltf_node_name.is_empty()) { if (gltf_node->mesh >= 0) { - gltf_node->set_name(_gen_unique_name(p_state, "Mesh")); + gltf_node_name = "Mesh"; } else if (gltf_node->camera >= 0) { - gltf_node->set_name(_gen_unique_name(p_state, "Camera3D")); + gltf_node_name = "Camera3D"; } else { - gltf_node->set_name(_gen_unique_name(p_state, "Node")); + gltf_node_name = "Node"; } } - - gltf_node->set_name(_gen_unique_name(p_state, gltf_node->get_name())); + gltf_node->set_name(_gen_unique_name(p_state, gltf_node_name)); } } diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index 34c53b5de5..4ac143c7ff 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -906,13 +906,14 @@ void GridMap::_notification(int p_what) { } } break; -#ifdef DEBUG_ENABLED case NOTIFICATION_ENTER_TREE: { +#ifdef DEBUG_ENABLED if (bake_navigation && NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) { _update_navigation_debug_edge_connections(); } - } break; #endif // DEBUG_ENABLED + _update_visibility(); + } break; case NOTIFICATION_TRANSFORM_CHANGED: { Transform3D new_xform = get_global_transform(); diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 32a08a3c8e..9b41f9cd1b 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -367,9 +367,19 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf } if (target_itype) { - xml_output.append("<see cref=\"" BINDINGS_NAMESPACE "."); - xml_output.append(target_itype->proxy_name); - xml_output.append("\"/>"); + if ((!p_itype || p_itype->api_type == ClassDB::API_CORE) && target_itype->api_type == ClassDB::API_EDITOR) { + // Editor references in core documentation cannot be resolved, + // handle as standard codeblock. + _log("Cannot reference editor type '%s' in documentation for core type '%s'\n", + target_itype->proxy_name.utf8().get_data(), p_itype ? p_itype->proxy_name.utf8().get_data() : "@GlobalScope"); + xml_output.append("<c>"); + xml_output.append(target_itype->proxy_name); + xml_output.append("</c>"); + } else { + xml_output.append("<see cref=\"" BINDINGS_NAMESPACE "."); + xml_output.append(target_itype->proxy_name); + xml_output.append("\"/>"); + } } else { ERR_PRINT("Cannot resolve type reference in documentation: '" + tag + "'."); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs index 5163ea5113..10aeeae995 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs @@ -52,7 +52,7 @@ namespace Godot.Collections /// </summary> /// <param name="array">The objects to put in the new array.</param> /// <returns>A new Godot Array.</returns> - public Array(Variant[] array) : this() + public Array(Variant[] array) { if (array == null) throw new ArgumentNullException(nameof(array)); @@ -68,7 +68,7 @@ namespace Godot.Collections this[i] = array[i]; } - public Array(Span<StringName> array) : this() + public Array(Span<StringName> array) { if (array == null) throw new ArgumentNullException(nameof(array)); @@ -84,7 +84,7 @@ namespace Godot.Collections this[i] = array[i]; } - public Array(Span<NodePath> array) : this() + public Array(Span<NodePath> array) { if (array == null) throw new ArgumentNullException(nameof(array)); @@ -100,7 +100,7 @@ namespace Godot.Collections this[i] = array[i]; } - public Array(Span<Rid> array) : this() + public Array(Span<Rid> array) { if (array == null) throw new ArgumentNullException(nameof(array)); @@ -121,7 +121,7 @@ namespace Godot.Collections // fine as long as the array is not mutated. However, Span does this type checking at // instantiation, so it's not possible to use it even when not mutating anything. // ReSharper disable once RedundantNameQualifier - public Array(ReadOnlySpan<GodotObject> array) : this() + public Array(ReadOnlySpan<GodotObject> array) { if (array == null) throw new ArgumentNullException(nameof(array)); @@ -1057,7 +1057,7 @@ namespace Godot.Collections /// </summary> /// <param name="array">The items to put in the new array.</param> /// <returns>A new Godot Array.</returns> - public Array(T[] array) : this() + public Array(T[] array) { if (array == null) throw new ArgumentNullException(nameof(array)); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs index ca0032df73..81b2ffef34 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs @@ -104,6 +104,36 @@ namespace Godot } /// <summary> + /// Returns the hyperbolic arc (also called inverse) cosine of <paramref name="s"/> in radians. + /// Use it to get the angle from an angle's cosine in hyperbolic space if + /// <paramref name="s"/> is larger or equal to 1. + /// </summary> + /// <param name="s">The input hyperbolic cosine value.</param> + /// <returns> + /// An angle that would result in the given hyperbolic cosine value. + /// </returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Acosh(float s) + { + return MathF.Acosh(s); + } + + /// <summary> + /// Returns the hyperbolic arc (also called inverse) cosine of <paramref name="s"/> in radians. + /// Use it to get the angle from an angle's cosine in hyperbolic space if + /// <paramref name="s"/> is larger or equal to 1. + /// </summary> + /// <param name="s">The input hyperbolic cosine value.</param> + /// <returns> + /// An angle that would result in the given hyperbolic cosine value. + /// </returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double Acosh(double s) + { + return Math.Acosh(s); + } + + /// <summary> /// Returns the arc sine of <paramref name="s"/> in radians. /// Use to get the angle of sine <paramref name="s"/>. /// </summary> @@ -132,6 +162,36 @@ namespace Godot } /// <summary> + /// Returns the hyperbolic arc (also called inverse) sine of <paramref name="s"/> in radians. + /// Use it to get the angle from an angle's sine in hyperbolic space if + /// <paramref name="s"/> is larger or equal to 1. + /// </summary> + /// <param name="s">The input hyperbolic sine value.</param> + /// <returns> + /// An angle that would result in the given hyperbolic sine value. + /// </returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Asinh(float s) + { + return MathF.Asinh(s); + } + + /// <summary> + /// Returns the hyperbolic arc (also called inverse) sine of <paramref name="s"/> in radians. + /// Use it to get the angle from an angle's sine in hyperbolic space if + /// <paramref name="s"/> is larger or equal to 1. + /// </summary> + /// <param name="s">The input hyperbolic sine value.</param> + /// <returns> + /// An angle that would result in the given hyperbolic sine value. + /// </returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double Asinh(double s) + { + return Math.Asinh(s); + } + + /// <summary> /// Returns the arc tangent of <paramref name="s"/> in radians. /// Use to get the angle of tangent <paramref name="s"/>. /// @@ -202,6 +262,36 @@ namespace Godot } /// <summary> + /// Returns the hyperbolic arc (also called inverse) tangent of <paramref name="s"/> in radians. + /// Use it to get the angle from an angle's tangent in hyperbolic space if + /// <paramref name="s"/> is between -1 and 1 (non-inclusive). + /// </summary> + /// <param name="s">The input hyperbolic tangent value.</param> + /// <returns> + /// An angle that would result in the given hyperbolic tangent value. + /// </returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Atanh(float s) + { + return MathF.Atanh(s); + } + + /// <summary> + /// Returns the hyperbolic arc (also called inverse) tangent of <paramref name="s"/> in radians. + /// Use it to get the angle from an angle's tangent in hyperbolic space if + /// <paramref name="s"/> is between -1 and 1 (non-inclusive). + /// </summary> + /// <param name="s">The input hyperbolic tangent value.</param> + /// <returns> + /// An angle that would result in the given hyperbolic tangent value. + /// </returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double Atanh(double s) + { + return Math.Atanh(s); + } + + /// <summary> /// Rounds <paramref name="s"/> upward (towards positive infinity). /// </summary> /// <param name="s">The number to ceil.</param> diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 6daa330ee0..382707fd04 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -2411,6 +2411,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (rtl) { button_ofs.x = get_size().width - button_ofs.x - button_texture->get_width(); } + p_item->cells.write[i].buttons.write[j].rect = Rect2i(button_ofs, button_size); button_texture->draw(ci, button_ofs, p_item->cells[i].buttons[j].disabled ? Color(1, 1, 1, 0.5) : p_item->cells[i].buttons[j].color); item_width_with_buttons -= button_size.width + theme_cache.button_margin; } @@ -5350,6 +5351,7 @@ String Tree::get_tooltip(const Point2 &p_pos) const { return Control::get_tooltip(p_pos); } + Point2 button_pos = pos; if (h_scroll->is_visible_in_tree()) { pos.x += h_scroll->get_value(); } @@ -5362,22 +5364,13 @@ String Tree::get_tooltip(const Point2 &p_pos) const { if (it) { const TreeItem::Cell &c = it->cells[col]; - int col_width = get_column_width(col); - - for (int i = 0; i < col; i++) { - pos.x -= get_column_width(i); - } - for (int j = c.buttons.size() - 1; j >= 0; j--) { - Ref<Texture2D> b = c.buttons[j].texture; - Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size(); - if (pos.x > col_width - size.width) { + if (c.buttons[j].rect.has_point(button_pos)) { String tooltip = c.buttons[j].tooltip; if (!tooltip.is_empty()) { return tooltip; } } - col_width -= size.width; } String ret; if (it->get_tooltip_text(col) == "") { diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 4afe94a3a0..c174d0e960 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -108,6 +108,7 @@ private: Ref<Texture2D> texture; Color color = Color(1, 1, 1, 1); String tooltip; + Rect2i rect; }; Vector<Button> buttons; diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index e9421a435e..1460a91826 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -10497,6 +10497,7 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_ const char colv[4] = { 'r', 'g', 'b', 'a' }; const char coordv[4] = { 'x', 'y', 'z', 'w' }; const char coordt[4] = { 's', 't', 'p', 'q' }; + const String theme_color_names[4] = { "axis_x_color", "axis_y_color", "axis_z_color", "axis_w_color" }; int limit = 0; @@ -10527,9 +10528,9 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_ } for (int i = 0; i < limit; i++) { - r_options->push_back(ScriptLanguage::CodeCompletionOption(String::chr(colv[i]), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT)); - r_options->push_back(ScriptLanguage::CodeCompletionOption(String::chr(coordv[i]), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT)); - r_options->push_back(ScriptLanguage::CodeCompletionOption(String::chr(coordt[i]), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT)); + r_options->push_back(ScriptLanguage::CodeCompletionOption(String::chr(colv[i]), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT, ScriptLanguage::LOCATION_OTHER, theme_color_names[i])); + r_options->push_back(ScriptLanguage::CodeCompletionOption(String::chr(coordv[i]), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT, ScriptLanguage::LOCATION_OTHER, theme_color_names[i])); + r_options->push_back(ScriptLanguage::CodeCompletionOption(String::chr(coordt[i]), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT, ScriptLanguage::LOCATION_OTHER, theme_color_names[i])); } } break; |