summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--SConstruct7
-rw-r--r--core/extension/gdextension_interface.h2
-rw-r--r--core/object/script_language_extension.h2
-rw-r--r--core/variant/variant_utility.cpp6
-rw-r--r--core/variant/variant_utility.h1
-rw-r--r--doc/classes/@GlobalScope.xml26
-rw-r--r--doc/classes/AStar3D.xml10
-rw-r--r--doc/classes/AStarGrid2D.xml10
-rw-r--r--doc/classes/AcceptDialog.xml1
-rw-r--r--doc/classes/AudioStreamPlayer.xml2
-rw-r--r--doc/classes/AudioStreamPlayer2D.xml2
-rw-r--r--doc/classes/BaseMaterial3D.xml4
-rw-r--r--doc/classes/Basis.xml2
-rw-r--r--doc/classes/Bone2D.xml2
-rw-r--r--doc/classes/Button.xml2
-rw-r--r--doc/classes/Camera3D.xml2
-rw-r--r--doc/classes/CharacterBody2D.xml4
-rw-r--r--doc/classes/CharacterBody3D.xml4
-rw-r--r--doc/classes/CodeEdit.xml6
-rw-r--r--doc/classes/CollisionObject2D.xml2
-rw-r--r--doc/classes/CollisionPolygon3D.xml2
-rw-r--r--doc/classes/Control.xml2
-rw-r--r--doc/classes/Curve3D.xml8
-rw-r--r--doc/classes/DampedSpringJoint2D.xml2
-rw-r--r--doc/classes/DisplayServer.xml63
-rw-r--r--doc/classes/EditorExportPlugin.xml4
-rw-r--r--doc/classes/EditorFileDialog.xml2
-rw-r--r--doc/classes/EditorImportPlugin.xml2
-rw-r--r--doc/classes/EditorInterface.xml2
-rw-r--r--doc/classes/EditorNode3DGizmo.xml2
-rw-r--r--doc/classes/EditorNode3DGizmoPlugin.xml2
-rw-r--r--doc/classes/Environment.xml8
-rw-r--r--doc/classes/FileAccess.xml4
-rw-r--r--doc/classes/FontFile.xml2
-rw-r--r--doc/classes/FontVariation.xml2
-rw-r--r--doc/classes/GPUParticles3D.xml4
-rw-r--r--doc/classes/HTTPRequest.xml6
-rw-r--r--doc/classes/Image.xml4
-rw-r--r--doc/classes/ImageTextureLayered.xml2
-rw-r--r--doc/classes/JavaScriptBridge.xml2
-rw-r--r--doc/classes/Joint2D.xml2
-rw-r--r--doc/classes/Label.xml2
-rw-r--r--doc/classes/Line2D.xml2
-rw-r--r--doc/classes/LinkButton.xml2
-rw-r--r--doc/classes/MarginContainer.xml8
-rw-r--r--doc/classes/MenuButton.xml2
-rw-r--r--doc/classes/NavigationMeshSourceGeometryData3D.xml6
-rw-r--r--doc/classes/NavigationPathQueryParameters2D.xml2
-rw-r--r--doc/classes/NavigationPathQueryParameters3D.xml2
-rw-r--r--doc/classes/NavigationRegion2D.xml4
-rw-r--r--doc/classes/NavigationRegion3D.xml4
-rw-r--r--doc/classes/Node.xml4
-rw-r--r--doc/classes/OS.xml2
-rw-r--r--doc/classes/Object.xml4
-rw-r--r--doc/classes/PackedByteArray.xml4
-rw-r--r--doc/classes/PackedScene.xml4
-rw-r--r--doc/classes/PacketPeer.xml2
-rw-r--r--doc/classes/ParticleProcessMaterial.xml6
-rw-r--r--doc/classes/PhysicsServer2D.xml16
-rw-r--r--doc/classes/PhysicsServer3D.xml7
-rw-r--r--doc/classes/Plane.xml4
-rw-r--r--doc/classes/Polygon2D.xml12
-rw-r--r--doc/classes/ProjectSettings.xml15
-rw-r--r--doc/classes/Quaternion.xml2
-rw-r--r--doc/classes/RichTextEffect.xml4
-rw-r--r--doc/classes/RichTextLabel.xml58
-rw-r--r--doc/classes/RigidBody3D.xml2
-rw-r--r--doc/classes/SeparationRayShape2D.xml2
-rw-r--r--doc/classes/SeparationRayShape3D.xml2
-rw-r--r--doc/classes/Slider.xml8
-rw-r--r--doc/classes/SpinBox.xml6
-rw-r--r--doc/classes/SubViewportContainer.xml9
-rw-r--r--doc/classes/TLSOptions.xml2
-rw-r--r--doc/classes/TextEdit.xml4
-rw-r--r--doc/classes/TextServer.xml14
-rw-r--r--doc/classes/TextServerExtension.xml6
-rw-r--r--doc/classes/TextureProgressBar.xml6
-rw-r--r--doc/classes/TileSet.xml2
-rw-r--r--doc/classes/TileSetAtlasSource.xml2
-rw-r--r--doc/classes/Time.xml4
-rw-r--r--doc/classes/Transform2D.xml2
-rw-r--r--doc/classes/Transform3D.xml2
-rw-r--r--doc/classes/Tree.xml2
-rw-r--r--doc/classes/Variant.xml2
-rw-r--r--doc/classes/Vector2.xml2
-rw-r--r--doc/classes/Vector3.xml2
-rw-r--r--doc/classes/Vector4.xml2
-rw-r--r--doc/classes/VisualShaderNode.xml16
-rw-r--r--doc/classes/VisualShaderNodeBooleanConstant.xml2
-rw-r--r--doc/classes/VisualShaderNodeFloatConstant.xml2
-rw-r--r--doc/classes/VisualShaderNodeIntConstant.xml2
-rw-r--r--doc/classes/VisualShaderNodeSwitch.xml2
-rw-r--r--doc/classes/Window.xml19
-rw-r--r--doc/classes/WorldBoundaryShape2D.xml2
-rw-r--r--doc/classes/XRPositionalTracker.xml2
-rw-r--r--doc/classes/float.xml2
-rwxr-xr-xdoc/tools/make_rst.py410
-rw-r--r--drivers/gles3/rasterizer_canvas_gles3.cpp7
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.cpp8
-rw-r--r--drivers/unix/SCsub2
-rw-r--r--drivers/vulkan/rendering_device_vulkan.cpp1
-rw-r--r--editor/animation_track_editor.cpp16
-rw-r--r--editor/animation_track_editor.h2
-rw-r--r--editor/connections_dialog.cpp85
-rw-r--r--editor/connections_dialog.h2
-rw-r--r--editor/create_dialog.cpp5
-rw-r--r--editor/editor_build_profile.cpp29
-rw-r--r--editor/editor_feature_profile.cpp25
-rw-r--r--editor/editor_help.cpp307
-rw-r--r--editor/editor_help.h31
-rw-r--r--editor/editor_inspector.cpp182
-rw-r--r--editor/editor_inspector.h8
-rw-r--r--editor/editor_node.cpp2
-rw-r--r--editor/editor_properties.cpp137
-rw-r--r--editor/editor_properties.h20
-rw-r--r--editor/editor_properties_array_dict.cpp3
-rw-r--r--editor/export/editor_export_platform.cpp24
-rw-r--r--editor/filesystem_dock.cpp71
-rw-r--r--editor/filesystem_dock.h10
-rw-r--r--editor/icons/CodeRegionFoldDownArrow.svg2
-rw-r--r--editor/icons/CodeRegionFoldedRightArrow.svg2
-rw-r--r--editor/import_dock.cpp9
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp245
-rw-r--r--editor/plugins/animation_player_editor_plugin.h27
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp12
-rw-r--r--editor/plugins/path_3d_editor_plugin.cpp43
-rw-r--r--editor/plugins/path_3d_editor_plugin.h9
-rw-r--r--editor/project_converter_3_to_4.cpp6
-rw-r--r--editor/property_selector.cpp43
-rw-r--r--editor/renames_map_3_to_4.cpp3
-rw-r--r--editor/scene_tree_dock.cpp26
-rw-r--r--main/main.cpp21
-rw-r--r--modules/bmp/image_loader_bmp.cpp2
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml6
-rw-r--r--modules/gdscript/editor/gdscript_docgen.cpp126
-rw-r--r--modules/gdscript/editor/gdscript_docgen.h10
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp22
-rw-r--r--modules/gdscript/gdscript.cpp98
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp28
-rw-r--r--modules/gdscript/gdscript_editor.cpp6
-rw-r--r--modules/gdscript/gdscript_warning.cpp4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd17
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out40
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/stringify.gd1
-rw-r--r--modules/gdscript/tests/scripts/utils.notest.gd84
-rw-r--r--modules/gdscript/tests/test_gdscript.cpp10
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs42
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs28
-rw-r--r--modules/mono/editor/bindings_generator.cpp20
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Compat.cs102
-rw-r--r--modules/openxr/SCsub1
-rw-r--r--modules/openxr/action_map/openxr_action_map.cpp8
-rw-r--r--modules/openxr/doc_classes/OpenXRInterface.xml7
-rw-r--r--modules/openxr/extensions/openxr_eye_gaze_interaction.cpp98
-rw-r--r--modules/openxr/extensions/openxr_eye_gaze_interaction.h58
-rw-r--r--modules/openxr/extensions/openxr_hand_tracking_extension.cpp72
-rw-r--r--modules/openxr/extensions/openxr_hand_tracking_extension.h28
-rw-r--r--modules/openxr/openxr_interface.cpp69
-rw-r--r--modules/openxr/openxr_interface.h6
-rw-r--r--modules/openxr/register_types.cpp10
-rw-r--r--modules/openxr/scene/openxr_hand.cpp15
-rw-r--r--modules/text_server_adv/text_server_adv.cpp281
-rw-r--r--modules/text_server_adv/text_server_adv.h16
-rw-r--r--modules/text_server_fb/text_server_fb.cpp265
-rw-r--r--modules/text_server_fb/text_server_fb.h16
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.kt13
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt15
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java9
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java2
-rw-r--r--platform/android/java_godot_wrapper.cpp13
-rw-r--r--platform/android/java_godot_wrapper.h4
-rw-r--r--platform/android/os_android.cpp11
-rw-r--r--platform/linuxbsd/crash_handler_linuxbsd.cpp10
-rw-r--r--platform/linuxbsd/freedesktop_portal_desktop.cpp106
-rw-r--r--platform/linuxbsd/freedesktop_portal_desktop.h8
-rw-r--r--platform/linuxbsd/platform_gl.h4
-rw-r--r--platform/linuxbsd/x11/SCsub4
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp84
-rw-r--r--platform/linuxbsd/x11/display_server_x11.h2
-rw-r--r--platform/linuxbsd/x11/gl_manager_x11_egl.cpp63
-rw-r--r--platform/linuxbsd/x11/gl_manager_x11_egl.h61
-rw-r--r--platform/macos/crash_handler_macos.mm10
-rw-r--r--platform/macos/display_server_macos.h19
-rw-r--r--platform/macos/display_server_macos.mm604
-rw-r--r--platform/macos/doc_classes/EditorExportPlatformMacOS.xml12
-rw-r--r--platform/macos/export/export_plugin.cpp6
-rw-r--r--platform/macos/godot_menu_delegate.mm28
-rw-r--r--platform/macos/godot_menu_item.h1
-rw-r--r--platform/macos/godot_window_delegate.mm2
-rw-r--r--platform/web/js/libs/library_godot_javascript_singleton.js2
-rw-r--r--platform/windows/display_server_windows.cpp115
-rw-r--r--platform/windows/display_server_windows.h3
-rw-r--r--platform/windows/platform_windows_builders.py16
-rw-r--r--platform/windows/wgl_detect_version.cpp24
-rw-r--r--platform/windows/wgl_detect_version.h4
-rw-r--r--scene/2d/node_2d.cpp5
-rw-r--r--scene/2d/physics_body_2d.cpp7
-rw-r--r--scene/3d/node_3d.cpp11
-rw-r--r--scene/3d/physics_body_3d.cpp14
-rw-r--r--scene/gui/code_edit.cpp45
-rw-r--r--scene/gui/code_edit.h1
-rw-r--r--scene/gui/dialogs.cpp1
-rw-r--r--scene/gui/file_dialog.cpp16
-rw-r--r--scene/gui/file_dialog.h2
-rw-r--r--scene/gui/menu_bar.cpp228
-rw-r--r--scene/gui/menu_bar.h24
-rw-r--r--scene/gui/popup_menu.cpp461
-rw-r--r--scene/gui/popup_menu.h9
-rw-r--r--scene/gui/subviewport_container.cpp16
-rw-r--r--scene/gui/subviewport_container.h2
-rw-r--r--scene/gui/text_edit.cpp18
-rw-r--r--scene/main/canvas_item.cpp11
-rw-r--r--scene/main/window.cpp70
-rw-r--r--scene/main/window.h5
-rw-r--r--scene/resources/font.cpp85
-rw-r--r--scene/resources/font.h2
-rw-r--r--scene/theme/default_theme.cpp4
-rw-r--r--scene/theme/icons/folder_down_arrow.svg1
-rw-r--r--scene/theme/icons/folder_right_arrow.svg1
-rw-r--r--scene/theme/icons/region_folded.svg1
-rw-r--r--scene/theme/icons/region_unfolded.svg1
-rw-r--r--servers/display_server.cpp22
-rw-r--r--servers/display_server.h9
-rw-r--r--servers/rendering/renderer_rd/renderer_compositor_rd.cpp5
-rw-r--r--servers/rendering/renderer_rd/storage_rd/material_storage.cpp2
-rw-r--r--servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp14
-rw-r--r--servers/rendering/renderer_rd/storage_rd/mesh_storage.h4
-rw-r--r--servers/rendering/renderer_viewport.cpp18
-rw-r--r--servers/rendering/renderer_viewport.h2
-rw-r--r--servers/rendering/rendering_server_default.cpp4
-rw-r--r--servers/rendering/shader_compiler.cpp26
-rw-r--r--servers/rendering/shader_language.cpp103
-rw-r--r--servers/rendering/shader_language.h3
-rw-r--r--servers/text/text_server_extension.cpp7
-rw-r--r--servers/text/text_server_extension.h3
-rw-r--r--servers/text_server.cpp1
-rw-r--r--servers/text_server.h1
-rw-r--r--tests/scene/test_code_edit.h61
239 files changed, 4584 insertions, 1966 deletions
diff --git a/SConstruct b/SConstruct
index 4f411d6d0a..294ae82b7d 100644
--- a/SConstruct
+++ b/SConstruct
@@ -988,9 +988,10 @@ if selected_platform in platform_list:
# Check for the existence of headers
conf = Configure(env)
if "check_c_headers" in env:
- for header in env["check_c_headers"]:
- if conf.CheckCHeader(header[0]):
- env.AppendUnique(CPPDEFINES=[header[1]])
+ headers = env["check_c_headers"]
+ for header in headers:
+ if conf.CheckCHeader(header):
+ env.AppendUnique(CPPDEFINES=[headers[header]])
elif selected_platform != "":
if selected_platform == "list":
diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h
index 35602b76dd..fbd1480e69 100644
--- a/core/extension/gdextension_interface.h
+++ b/core/extension/gdextension_interface.h
@@ -510,7 +510,7 @@ typedef struct {
GDExtensionScriptInstanceGet get_func;
GDExtensionScriptInstanceGetPropertyList get_property_list_func;
GDExtensionScriptInstanceFreePropertyList free_property_list_func;
- GDExtensionScriptInstanceGetClassCategory get_class_category_func;
+ GDExtensionScriptInstanceGetClassCategory get_class_category_func; // Optional. Set to NULL for the default behavior.
GDExtensionScriptInstancePropertyCanRevert property_can_revert_func;
GDExtensionScriptInstancePropertyGetRevert property_get_revert_func;
diff --git a/core/object/script_language_extension.h b/core/object/script_language_extension.h
index b682efec12..beb8064a33 100644
--- a/core/object/script_language_extension.h
+++ b/core/object/script_language_extension.h
@@ -676,13 +676,11 @@ public:
if (native_info->get_class_category_func(instance, &gdext_class_category)) {
p_list->push_back(PropertyInfo(gdext_class_category));
}
-#ifndef DISABLE_DEPRECATED
} else {
Ref<Script> script = get_script();
if (script.is_valid()) {
p_list->push_back(script->get_class_category());
}
-#endif // DISABLE_DEPRECATED
}
}
#endif // TOOLS_ENABLED
diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp
index d80bc473e5..6f334d1859 100644
--- a/core/variant/variant_utility.cpp
+++ b/core/variant/variant_utility.cpp
@@ -875,6 +875,11 @@ String VariantUtilityFunctions::error_string(Error error) {
return String(error_names[error]);
}
+String VariantUtilityFunctions::type_string(Variant::Type p_type) {
+ ERR_FAIL_INDEX_V_MSG((int)p_type, (int)Variant::VARIANT_MAX, "<invalid type>", "Invalid type argument to type_string(), use the TYPE_* constants.");
+ return Variant::get_type_name(p_type);
+}
+
void VariantUtilityFunctions::print(const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
String s;
for (int i = 0; i < p_arg_count; i++) {
@@ -1713,6 +1718,7 @@ void Variant::_register_variant_utility_functions() {
FUNCBINDR(type_convert, sarray("variant", "type"), Variant::UTILITY_FUNC_TYPE_GENERAL);
FUNCBINDVARARGS(str, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL);
FUNCBINDR(error_string, sarray("error"), Variant::UTILITY_FUNC_TYPE_GENERAL);
+ FUNCBINDR(type_string, sarray("type"), Variant::UTILITY_FUNC_TYPE_GENERAL);
FUNCBINDVARARGV(print, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL);
FUNCBINDVARARGV(print_rich, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL);
FUNCBINDVARARGV(printerr, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL);
diff --git a/core/variant/variant_utility.h b/core/variant/variant_utility.h
index 8130f30881..a56c84a8e9 100644
--- a/core/variant/variant_utility.h
+++ b/core/variant/variant_utility.h
@@ -130,6 +130,7 @@ struct VariantUtilityFunctions {
static Variant type_convert(const Variant &p_variant, const Variant::Type p_type);
static String str(const Variant **p_args, int p_arg_count, Callable::CallError &r_error);
static String error_string(Error error);
+ static String type_string(Variant::Type p_type);
static void print(const Variant **p_args, int p_arg_count, Callable::CallError &r_error);
static void print_rich(const Variant **p_args, int p_arg_count, Callable::CallError &r_error);
#undef print_verbose
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index a077900a9c..c854d9b54e 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -120,7 +120,7 @@
<param index="0" name="x" type="float" />
<description>
Returns the arc tangent of [param x] in radians. Use it to get the angle from an angle's tangent in trigonometry.
- The method cannot know in which quadrant the angle should fall. See [method atan2] if you have both [code]y[/code] and [code]x[/code].
+ The method cannot know in which quadrant the angle should fall. See [method atan2] if you have both [code]y[/code] and [code skip-lint]x[/code].
[codeblock]
var a = atan(0.5) # a is 0.463648
[/codeblock]
@@ -338,7 +338,7 @@
<param index="7" name="post_t" type="float" />
<description>
Cubic interpolates between two rotation values with shortest path by the factor defined in [param weight] with [param pre] and [param post] values. See also [method lerp_angle].
- It can perform smoother interpolation than [code]cubic_interpolate()[/code] by the time values.
+ It can perform smoother interpolation than [method cubic_interpolate] by the time values.
</description>
</method>
<method name="cubic_interpolate_in_time">
@@ -879,10 +879,10 @@
URL tags only support URLs wrapped by a URL tag, not URLs with a different title.
When printing to standard output, the supported subset of BBCode is converted to ANSI escape codes for the terminal emulator to display. Support for ANSI escape codes varies across terminal emulators, especially for italic and strikethrough. In standard output, [code]code[/code] is represented with faint text but without any font change. Unsupported tags are left as-is in standard output.
[codeblocks]
- [gdscript]
+ [gdscript skip-lint]
print_rich("[color=green][b]Hello world![/b][/color]") # Prints out "Hello world!" in green with a bold font
[/gdscript]
- [csharp]
+ [csharp skip-lint]
GD.PrintRich("[color=green][b]Hello world![/b][/color]"); // Prints out "Hello world!" in green with a bold font
[/csharp]
[/codeblocks]
@@ -1392,7 +1392,7 @@
<param index="1" name="type" type="int" />
<description>
Converts the given [param variant] to the given [param type], using the [enum Variant.Type] values. This method is generous with how it handles types, it can automatically convert between array types, convert numeric [String]s to [int], and converting most things to [String].
- If the type conversion cannot be done, this method will return the default value for that type, for example converting [Rect2] to [Vector2] will always return [code]Vector2.ZERO[/code]. This method will never show error messages as long as [param type] is a valid Variant type.
+ If the type conversion cannot be done, this method will return the default value for that type, for example converting [Rect2] to [Vector2] will always return [constant Vector2.ZERO]. This method will never show error messages as long as [param type] is a valid Variant type.
The returned value is a [Variant], but the data inside and the [enum Variant.Type] will be the same as the requested type.
[codeblock]
type_convert("Hi!", TYPE_INT) # Returns 0
@@ -1403,6 +1403,19 @@
[/codeblock]
</description>
</method>
+ <method name="type_string">
+ <return type="String" />
+ <param index="0" name="type" type="int" />
+ <description>
+ Returns a human-readable name of the given [param type], using the [enum Variant.Type] values.
+ [codeblock]
+ print(TYPE_INT) # Prints 2.
+ print(type_string(TYPE_INT)) # Prints "int".
+ print(type_string(TYPE_STRING)) # Prints "String".
+ [/codeblock]
+ See also [method typeof].
+ </description>
+ </method>
<method name="typeof">
<return type="int" />
<param index="0" name="variable" type="Variant" />
@@ -1417,6 +1430,7 @@
else:
print("Unexpected result")
[/codeblock]
+ See also [method type_string].
</description>
</method>
<method name="var_to_bytes">
@@ -1507,7 +1521,7 @@
angle = wrapf(angle + 0.1, -PI, PI)
[/codeblock]
[b]Note:[/b] If [param min] is [code]0[/code], this is equivalent to [method fposmod], so prefer using that instead.
- [code]wrapf[/code] is more flexible than using the [method fposmod] approach by giving the user control over the minimum value.
+ [method wrapf] is more flexible than using the [method fposmod] approach by giving the user control over the minimum value.
</description>
</method>
<method name="wrapi">
diff --git a/doc/classes/AStar3D.xml b/doc/classes/AStar3D.xml
index d35835d73d..e2afeef377 100644
--- a/doc/classes/AStar3D.xml
+++ b/doc/classes/AStar3D.xml
@@ -6,7 +6,7 @@
<description>
A* (A star) is a computer algorithm used in pathfinding and graph traversal, the process of plotting short paths among vertices (points), passing through a given set of edges (segments). It enjoys widespread use due to its performance and accuracy. Godot's A* implementation uses points in 3D space and Euclidean distances by default.
You must add points manually with [method add_point] and create segments manually with [method connect_points]. Once done, you can test if there is a path between two points with the [method are_points_connected] function, get a path containing indices by [method get_id_path], or one containing actual coordinates with [method get_point_path].
- It is also possible to use non-Euclidean distances. To do so, create a class that extends [code]AStar3D[/code] and override methods [method _compute_cost] and [method _estimate_cost]. Both take two indices and return a length, as is shown in the following example.
+ It is also possible to use non-Euclidean distances. To do so, create a class that extends [AStar3D] and override methods [method _compute_cost] and [method _estimate_cost]. Both take two indices and return a length, as is shown in the following example.
[codeblocks]
[gdscript]
class MyAStar:
@@ -33,7 +33,7 @@
}
[/csharp]
[/codeblocks]
- [method _estimate_cost] should return a lower bound of the distance, i.e. [code]_estimate_cost(u, v) &lt;= _compute_cost(u, v)[/code]. This serves as a hint to the algorithm because the custom [code]_compute_cost[/code] might be computation-heavy. If this is not the case, make [method _estimate_cost] return the same value as [method _compute_cost] to provide the algorithm with the most accurate information.
+ [method _estimate_cost] should return a lower bound of the distance, i.e. [code]_estimate_cost(u, v) &lt;= _compute_cost(u, v)[/code]. This serves as a hint to the algorithm because the custom [method _compute_cost] might be computation-heavy. If this is not the case, make [method _estimate_cost] return the same value as [method _compute_cost] to provide the algorithm with the most accurate information.
If the default [method _estimate_cost] and [method _compute_cost] methods are used, or if the supplied [method _estimate_cost] method returns a lower bound of the cost, then the paths returned by A* will be the lowest-cost paths. Here, the cost of a path equals the sum of the [method _compute_cost] results of all segments in the path multiplied by the [code]weight_scale[/code]s of the endpoints of the respective segments. If the default methods are used and the [code]weight_scale[/code]s of all points are set to [code]1.0[/code], then this equals the sum of Euclidean distances of all segments in the path.
</description>
<tutorials>
@@ -45,7 +45,7 @@
<param index="1" name="to_id" type="int" />
<description>
Called when computing the cost between two connected points.
- Note that this function is hidden in the default [code]AStar3D[/code] class.
+ Note that this function is hidden in the default [AStar3D] class.
</description>
</method>
<method name="_estimate_cost" qualifiers="virtual const">
@@ -54,7 +54,7 @@
<param index="1" name="to_id" type="int" />
<description>
Called when estimating the cost between a point and the path's ending point.
- Note that this function is hidden in the default [code]AStar3D[/code] class.
+ Note that this function is hidden in the default [AStar3D] class.
</description>
</method>
<method name="add_point">
@@ -204,7 +204,7 @@
<method name="get_point_capacity" qualifiers="const">
<return type="int" />
<description>
- Returns the capacity of the structure backing the points, useful in conjunction with [code]reserve_space[/code].
+ Returns the capacity of the structure backing the points, useful in conjunction with [method reserve_space].
</description>
</method>
<method name="get_point_connections">
diff --git a/doc/classes/AStarGrid2D.xml b/doc/classes/AStarGrid2D.xml
index d64941cb9b..277630588a 100644
--- a/doc/classes/AStarGrid2D.xml
+++ b/doc/classes/AStarGrid2D.xml
@@ -35,7 +35,7 @@
<param index="1" name="to_id" type="Vector2i" />
<description>
Called when computing the cost between two connected points.
- Note that this function is hidden in the default [code]AStarGrid2D[/code] class.
+ Note that this function is hidden in the default [AStarGrid2D] class.
</description>
</method>
<method name="_estimate_cost" qualifiers="virtual const">
@@ -44,7 +44,7 @@
<param index="1" name="to_id" type="Vector2i" />
<description>
Called when estimating the cost between a point and the path's ending point.
- Note that this function is hidden in the default [code]AStarGrid2D[/code] class.
+ Note that this function is hidden in the default [AStarGrid2D] class.
</description>
</method>
<method name="clear">
@@ -84,7 +84,7 @@
<param index="0" name="from_id" type="Vector2i" />
<param index="1" name="to_id" type="Vector2i" />
<description>
- Returns an array with the points that are in the path found by AStarGrid2D between the given points. The array is ordered from the starting point to the ending point of the path.
+ Returns an array with the points that are in the path found by [AStarGrid2D] between the given points. The array is ordered from the starting point to the ending point of the path.
[b]Note:[/b] This method is not thread-safe. If called from a [Thread], it will return an empty [PackedVector3Array] and will print an error message.
</description>
</method>
@@ -113,14 +113,14 @@
<param index="0" name="x" type="int" />
<param index="1" name="y" type="int" />
<description>
- Returns [code]true[/code] if the [param x] and [param y] is a valid grid coordinate (id).
+ Returns [code]true[/code] if the [param x] and [param y] is a valid grid coordinate (id), i.e. if it is inside [member region]. Equivalent to [code]region.has_point(Vector2i(x, y))[/code].
</description>
</method>
<method name="is_in_boundsv" qualifiers="const">
<return type="bool" />
<param index="0" name="id" type="Vector2i" />
<description>
- Returns [code]true[/code] if the [param id] vector is a valid grid coordinate.
+ Returns [code]true[/code] if the [param id] vector is a valid grid coordinate, i.e. if it is inside [member region]. Equivalent to [code]region.has_point(id)[/code].
</description>
</method>
<method name="is_point_solid" qualifiers="const">
diff --git a/doc/classes/AcceptDialog.xml b/doc/classes/AcceptDialog.xml
index 202a84fb58..59cbb38036 100644
--- a/doc/classes/AcceptDialog.xml
+++ b/doc/classes/AcceptDialog.xml
@@ -72,6 +72,7 @@
The text displayed by the dialog.
</member>
<member name="exclusive" type="bool" setter="set_exclusive" getter="is_exclusive" overrides="Window" default="true" />
+ <member name="keep_title_visible" type="bool" setter="set_keep_title_visible" getter="get_keep_title_visible" overrides="Window" default="true" />
<member name="ok_button_text" type="String" setter="set_ok_button_text" getter="get_ok_button_text" default="&quot;OK&quot;">
The text displayed by the OK button (see [method get_ok_button]).
</member>
diff --git a/doc/classes/AudioStreamPlayer.xml b/doc/classes/AudioStreamPlayer.xml
index c48c7f4300..ac50a5ee30 100644
--- a/doc/classes/AudioStreamPlayer.xml
+++ b/doc/classes/AudioStreamPlayer.xml
@@ -79,7 +79,7 @@
The [AudioStream] object to be played.
</member>
<member name="stream_paused" type="bool" setter="set_stream_paused" getter="get_stream_paused" default="false">
- If [code]true[/code], the playback is paused. You can resume it by setting [code]stream_paused[/code] to [code]false[/code].
+ If [code]true[/code], the playback is paused. You can resume it by setting [member stream_paused] to [code]false[/code].
</member>
<member name="volume_db" type="float" setter="set_volume_db" getter="get_volume_db" default="0.0">
Volume of sound, in dB.
diff --git a/doc/classes/AudioStreamPlayer2D.xml b/doc/classes/AudioStreamPlayer2D.xml
index 7c59089708..77a038c5fa 100644
--- a/doc/classes/AudioStreamPlayer2D.xml
+++ b/doc/classes/AudioStreamPlayer2D.xml
@@ -85,7 +85,7 @@
The [AudioStream] object to be played.
</member>
<member name="stream_paused" type="bool" setter="set_stream_paused" getter="get_stream_paused" default="false">
- If [code]true[/code], the playback is paused. You can resume it by setting [code]stream_paused[/code] to [code]false[/code].
+ If [code]true[/code], the playback is paused. You can resume it by setting [member stream_paused] to [code]false[/code].
</member>
<member name="volume_db" type="float" setter="set_volume_db" getter="get_volume_db" default="0.0">
Base volume before attenuation.
diff --git a/doc/classes/BaseMaterial3D.xml b/doc/classes/BaseMaterial3D.xml
index 2246e30817..72bfc2dcc7 100644
--- a/doc/classes/BaseMaterial3D.xml
+++ b/doc/classes/BaseMaterial3D.xml
@@ -184,11 +184,11 @@
</member>
<member name="distance_fade_max_distance" type="float" setter="set_distance_fade_max_distance" getter="get_distance_fade_max_distance" default="10.0">
Distance at which the object appears fully opaque.
- [b]Note:[/b] If [code]distance_fade_max_distance[/code] is less than [code]distance_fade_min_distance[/code], the behavior will be reversed. The object will start to fade away at [code]distance_fade_max_distance[/code] and will fully disappear once it reaches [code]distance_fade_min_distance[/code].
+ [b]Note:[/b] If [member distance_fade_max_distance] is less than [member distance_fade_min_distance], the behavior will be reversed. The object will start to fade away at [member distance_fade_max_distance] and will fully disappear once it reaches [member distance_fade_min_distance].
</member>
<member name="distance_fade_min_distance" type="float" setter="set_distance_fade_min_distance" getter="get_distance_fade_min_distance" default="0.0">
Distance at which the object starts to become visible. If the object is less than this distance away, it will be invisible.
- [b]Note:[/b] If [code]distance_fade_min_distance[/code] is greater than [code]distance_fade_max_distance[/code], the behavior will be reversed. The object will start to fade away at [code]distance_fade_max_distance[/code] and will fully disappear once it reaches [code]distance_fade_min_distance[/code].
+ [b]Note:[/b] If [member distance_fade_min_distance] is greater than [member distance_fade_max_distance], the behavior will be reversed. The object will start to fade away at [member distance_fade_max_distance] and will fully disappear once it reaches [member distance_fade_min_distance].
</member>
<member name="distance_fade_mode" type="int" setter="set_distance_fade" getter="get_distance_fade" enum="BaseMaterial3D.DistanceFadeMode" default="0">
Specifies which type of fade to use. Can be any of the [enum DistanceFadeMode]s.
diff --git a/doc/classes/Basis.xml b/doc/classes/Basis.xml
index 972a8eb114..f98c207a6e 100644
--- a/doc/classes/Basis.xml
+++ b/doc/classes/Basis.xml
@@ -207,7 +207,7 @@
<constants>
<constant name="IDENTITY" value="Basis(1, 0, 0, 0, 1, 0, 0, 0, 1)">
The identity basis, with no rotation or scaling applied.
- This is identical to calling [code]Basis()[/code] without any parameters. This constant can be used to make your code clearer, and for consistency with C#.
+ This is identical to creating [constructor Basis] without any parameters. This constant can be used to make your code clearer, and for consistency with C#.
</constant>
<constant name="FLIP_X" value="Basis(-1, 0, 0, 0, 1, 0, 0, 0, 1)">
The basis that will flip something along the X axis when used in a transformation.
diff --git a/doc/classes/Bone2D.xml b/doc/classes/Bone2D.xml
index fa8112de29..a62c160080 100644
--- a/doc/classes/Bone2D.xml
+++ b/doc/classes/Bone2D.xml
@@ -46,7 +46,7 @@
<method name="get_skeleton_rest" qualifiers="const">
<return type="Transform2D" />
<description>
- Returns the node's [member rest] [code]Transform2D[/code] if it doesn't have a parent, or its rest pose relative to its parent.
+ Returns the node's [member rest] [Transform2D] if it doesn't have a parent, or its rest pose relative to its parent.
</description>
</method>
<method name="set_autocalculate_length_and_angle">
diff --git a/doc/classes/Button.xml b/doc/classes/Button.xml
index bee0cbcf4a..bf0534ab1e 100644
--- a/doc/classes/Button.xml
+++ b/doc/classes/Button.xml
@@ -140,7 +140,7 @@
[StyleBox] used when the [Button] is disabled (for right-to-left layouts).
</theme_item>
<theme_item name="focus" data_type="style" type="StyleBox">
- [StyleBox] used when the [Button] is focused. The [code]focus[/code] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
+ [StyleBox] used when the [Button] is focused. The [theme_item focus] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
</theme_item>
<theme_item name="hover" data_type="style" type="StyleBox">
[StyleBox] used when the [Button] is being hovered.
diff --git a/doc/classes/Camera3D.xml b/doc/classes/Camera3D.xml
index 83f1ffa08e..07b23e0252 100644
--- a/doc/classes/Camera3D.xml
+++ b/doc/classes/Camera3D.xml
@@ -179,7 +179,7 @@
The distance to the far culling boundary for this camera relative to its local Z axis. Higher values allow the camera to see further away, while decreasing [member far] can improve performance if it results in objects being partially or fully culled.
</member>
<member name="fov" type="float" setter="set_fov" getter="get_fov" default="75.0">
- The camera's field of view angle (in degrees). Only applicable in perspective mode. Since [member keep_aspect] locks one axis, [code]fov[/code] sets the other axis' field of view angle.
+ The camera's field of view angle (in degrees). Only applicable in perspective mode. Since [member keep_aspect] locks one axis, [member fov] sets the other axis' field of view angle.
For reference, the default vertical field of view value ([code]75.0[/code]) is equivalent to a horizontal FOV of:
- ~91.31 degrees in a 4:3 viewport
- ~101.67 degrees in a 16:10 viewport
diff --git a/doc/classes/CharacterBody2D.xml b/doc/classes/CharacterBody2D.xml
index 7b22fe2e93..b66a01a282 100644
--- a/doc/classes/CharacterBody2D.xml
+++ b/doc/classes/CharacterBody2D.xml
@@ -24,7 +24,7 @@
<return type="float" />
<param index="0" name="up_direction" type="Vector2" default="Vector2(0, -1)" />
<description>
- Returns the floor's collision angle at the last collision point according to [param up_direction], which is [code]Vector2.UP[/code] by default. This value is always positive and only valid after calling [method move_and_slide] and when [method is_on_floor] returns [code]true[/code].
+ Returns the floor's collision angle at the last collision point according to [param up_direction], which is [constant Vector2.UP] by default. This value is always positive and only valid after calling [method move_and_slide] and when [method is_on_floor] returns [code]true[/code].
</description>
</method>
<method name="get_floor_normal" qualifiers="const">
@@ -188,7 +188,7 @@
If [code]true[/code], during a jump against the ceiling, the body will slide, if [code]false[/code] it will be stopped and will fall vertically.
</member>
<member name="up_direction" type="Vector2" setter="set_up_direction" getter="get_up_direction" default="Vector2(0, -1)">
- Vector pointing upwards, used to determine what is a wall and what is a floor (or a ceiling) when calling [method move_and_slide]. Defaults to [code]Vector2.UP[/code]. As the vector will be normalized it can't be equal to [constant Vector2.ZERO], if you want all collisions to be reported as walls, consider using [constant MOTION_MODE_FLOATING] as [member motion_mode].
+ Vector pointing upwards, used to determine what is a wall and what is a floor (or a ceiling) when calling [method move_and_slide]. Defaults to [constant Vector2.UP]. As the vector will be normalized it can't be equal to [constant Vector2.ZERO], if you want all collisions to be reported as walls, consider using [constant MOTION_MODE_FLOATING] as [member motion_mode].
</member>
<member name="velocity" type="Vector2" setter="set_velocity" getter="get_velocity" default="Vector2(0, 0)">
Current velocity vector in pixels per second, used and modified during calls to [method move_and_slide].
diff --git a/doc/classes/CharacterBody3D.xml b/doc/classes/CharacterBody3D.xml
index b4f68cb1aa..2382c77a12 100644
--- a/doc/classes/CharacterBody3D.xml
+++ b/doc/classes/CharacterBody3D.xml
@@ -25,7 +25,7 @@
<return type="float" />
<param index="0" name="up_direction" type="Vector3" default="Vector3(0, 1, 0)" />
<description>
- Returns the floor's collision angle at the last collision point according to [param up_direction], which is [code]Vector3.UP[/code] by default. This value is always positive and only valid after calling [method move_and_slide] and when [method is_on_floor] returns [code]true[/code].
+ Returns the floor's collision angle at the last collision point according to [param up_direction], which is [constant Vector3.UP] by default. This value is always positive and only valid after calling [method move_and_slide] and when [method is_on_floor] returns [code]true[/code].
</description>
</method>
<method name="get_floor_normal" qualifiers="const">
@@ -179,7 +179,7 @@
If [code]true[/code], during a jump against the ceiling, the body will slide, if [code]false[/code] it will be stopped and will fall vertically.
</member>
<member name="up_direction" type="Vector3" setter="set_up_direction" getter="get_up_direction" default="Vector3(0, 1, 0)">
- Vector pointing upwards, used to determine what is a wall and what is a floor (or a ceiling) when calling [method move_and_slide]. Defaults to [code]Vector3.UP[/code]. As the vector will be normalized it can't be equal to [constant Vector3.ZERO], if you want all collisions to be reported as walls, consider using [constant MOTION_MODE_FLOATING] as [member motion_mode].
+ Vector pointing upwards, used to determine what is a wall and what is a floor (or a ceiling) when calling [method move_and_slide]. Defaults to [constant Vector3.UP]. As the vector will be normalized it can't be equal to [constant Vector3.ZERO], if you want all collisions to be reported as walls, consider using [constant MOTION_MODE_FLOATING] as [member motion_mode].
</member>
<member name="velocity" type="Vector3" setter="set_velocity" getter="get_velocity" default="Vector3(0, 0, 0)">
Current velocity vector (typically meters per second), used and modified during calls to [method move_and_slide].
diff --git a/doc/classes/CodeEdit.xml b/doc/classes/CodeEdit.xml
index e5c255706f..13f39d4fae 100644
--- a/doc/classes/CodeEdit.xml
+++ b/doc/classes/CodeEdit.xml
@@ -125,7 +125,7 @@
<return type="void" />
<param index="0" name="replace" type="bool" default="false" />
<description>
- Inserts the selected entry into the text. If [param replace] is true, any existing text is replaced rather then merged.
+ Inserts the selected entry into the text. If [param replace] is true, any existing text is replaced rather than merged.
</description>
</method>
<method name="convert_indent">
@@ -544,7 +544,7 @@
Prefixes to trigger an automatic indent.
</member>
<member name="indent_size" type="int" setter="set_indent_size" getter="get_indent_size" default="4">
- Size of tabs, if [code]indent_use_spaces[/code] is enabled the number of spaces to use.
+ Size of the tabulation indent (one [kbd]Tab[/kbd] press) in characters. If [member indent_use_spaces] is enabled the number of spaces to use.
</member>
<member name="indent_use_spaces" type="bool" setter="set_indent_using_spaces" getter="is_indent_using_spaces" default="false">
Use spaces instead of tabs for indentation.
@@ -676,7 +676,7 @@
Max number of options to display in the code completion popup at any one time.
</theme_item>
<theme_item name="completion_max_width" data_type="constant" type="int" default="50">
- Max width of options in the code completion popup. Options longer then this will be cut off.
+ Max width of options in the code completion popup. Options longer than this will be cut off.
</theme_item>
<theme_item name="completion_scroll_width" data_type="constant" type="int" default="6">
Width of the scrollbar in the code completion popup.
diff --git a/doc/classes/CollisionObject2D.xml b/doc/classes/CollisionObject2D.xml
index 07bc53575c..137e712913 100644
--- a/doc/classes/CollisionObject2D.xml
+++ b/doc/classes/CollisionObject2D.xml
@@ -16,7 +16,7 @@
<param index="1" name="event" type="InputEvent" />
<param index="2" name="shape_idx" type="int" />
<description>
- Accepts unhandled [InputEvent]s. [param shape_idx] is the child index of the clicked [Shape2D]. Connect to the [code]input_event[/code] signal to easily pick up these events.
+ Accepts unhandled [InputEvent]s. [param shape_idx] is the child index of the clicked [Shape2D]. Connect to [signal input_event] to easily pick up these events.
[b]Note:[/b] [method _input_event] requires [member input_pickable] to be [code]true[/code] and at least one [member collision_layer] bit to be set.
</description>
</method>
diff --git a/doc/classes/CollisionPolygon3D.xml b/doc/classes/CollisionPolygon3D.xml
index 0a3a3cfd26..ed6fa0ba3c 100644
--- a/doc/classes/CollisionPolygon3D.xml
+++ b/doc/classes/CollisionPolygon3D.xml
@@ -21,7 +21,7 @@
</member>
<member name="polygon" type="PackedVector2Array" setter="set_polygon" getter="get_polygon" default="PackedVector2Array()">
Array of vertices which define the 2D polygon in the local XY plane.
- [b]Note:[/b] The returned value is a copy of the original. Methods which mutate the size or properties of the return value will not impact the original polygon. To change properties of the polygon, assign it to a temporary variable and make changes before reassigning the [code]polygon[/code] member.
+ [b]Note:[/b] The returned value is a copy of the original. Methods which mutate the size or properties of the return value will not impact the original polygon. To change properties of the polygon, assign it to a temporary variable and make changes before reassigning the class property.
</member>
</members>
</class>
diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml
index d665084469..21d6cb2756 100644
--- a/doc/classes/Control.xml
+++ b/doc/classes/Control.xml
@@ -1052,7 +1052,7 @@
[b]Note:[/b] [Window] styles will have no effect unless the window is embedded.
</member>
<member name="theme_type_variation" type="StringName" setter="set_theme_type_variation" getter="get_theme_type_variation" default="&amp;&quot;&quot;">
- The name of a theme type variation used by this [Control] to look up its own theme items. When empty, the class name of the node is used (e.g. [code]Button[/code] for the [Button] control), as well as the class names of all parent classes (in order of inheritance).
+ The name of a theme type variation used by this [Control] to look up its own theme items. When empty, the class name of the node is used (e.g. [code skip-lint]Button[/code] for the [Button] control), as well as the class names of all parent classes (in order of inheritance).
When set, this property gives the highest priority to the type of the specified name. This type can in turn extend another type, forming a dependency chain. See [method Theme.set_type_variation]. If the theme item cannot be found using this type or its base types, lookup falls back on the class names.
[b]Note:[/b] To look up [Control]'s own items use various [code]get_theme_*[/code] methods without specifying [code]theme_type[/code].
[b]Note:[/b] Theme items are looked for in the tree order, from branch to root, where each [Control] node is checked for its [member theme] property. The earliest match against any type/class name is returned. The project-level Theme and the default Theme are checked last.
diff --git a/doc/classes/Curve3D.xml b/doc/classes/Curve3D.xml
index 06b6409e09..9157649af2 100644
--- a/doc/classes/Curve3D.xml
+++ b/doc/classes/Curve3D.xml
@@ -117,8 +117,7 @@
<param index="0" name="offset" type="float" default="0.0" />
<param index="1" name="cubic" type="bool" default="false" />
<description>
- Returns a point within the curve at position [param offset], where [param offset] is measured as a distance in 3D units along the curve.
- To do that, it finds the two cached points where the [param offset] lies between, then interpolates the values. This interpolation is cubic if [param cubic] is set to [code]true[/code], or linear if set to [code]false[/code].
+ Returns a point within the curve at position [param offset], where [param offset] is measured as a distance in 3D units along the curve. To do that, it finds the two cached points where the [param offset] lies between, then interpolates the values. This interpolation is cubic if [param cubic] is set to [code]true[/code], or linear if set to [code]false[/code].
Cubic interpolation tends to follow the curves better, but linear is faster (and often, precise enough).
</description>
</method>
@@ -127,8 +126,7 @@
<param index="0" name="offset" type="float" />
<param index="1" name="apply_tilt" type="bool" default="false" />
<description>
- Returns an up vector within the curve at position [param offset], where [param offset] is measured as a distance in 3D units along the curve.
- To do that, it finds the two cached up vectors where the [param offset] lies between, then interpolates the values. If [param apply_tilt] is [code]true[/code], an interpolated tilt is applied to the interpolated up vector.
+ Returns an up vector within the curve at position [param offset], where [param offset] is measured as a distance in 3D units along the curve. To do that, it finds the two cached up vectors where the [param offset] lies between, then interpolates the values. If [param apply_tilt] is [code]true[/code], an interpolated tilt is applied to the interpolated up vector.
If the curve has no up vectors, the function sends an error to the console, and returns [code](0, 1, 0)[/code].
</description>
</method>
@@ -138,7 +136,7 @@
<param index="1" name="cubic" type="bool" default="false" />
<param index="2" name="apply_tilt" type="bool" default="false" />
<description>
- Similar with [code]interpolate_baked()[/code]. The return value is [code]Transform3D[/code], with [code]origin[/code] as point position, [code]basis.x[/code] as sideway vector, [code]basis.y[/code] as up vector, [code]basis.z[/code] as forward vector. When the curve length is 0, there is no reasonable way to calculate the rotation, all vectors aligned with global space axes.
+ Returns a [Transform3D] with [code]origin[/code] as point position, [code]basis.x[/code] as sideway vector, [code]basis.y[/code] as up vector, [code]basis.z[/code] as forward vector. When the curve length is 0, there is no reasonable way to calculate the rotation, all vectors aligned with global space axes. See also [method sample_baked].
</description>
</method>
<method name="samplef" qualifiers="const">
diff --git a/doc/classes/DampedSpringJoint2D.xml b/doc/classes/DampedSpringJoint2D.xml
index c47a7d9303..4bb80c84e5 100644
--- a/doc/classes/DampedSpringJoint2D.xml
+++ b/doc/classes/DampedSpringJoint2D.xml
@@ -10,7 +10,7 @@
</tutorials>
<members>
<member name="damping" type="float" setter="set_damping" getter="get_damping" default="1.0">
- The spring joint's damping ratio. A value between [code]0[/code] and [code]1[/code]. When the two bodies move into different directions the system tries to align them to the spring axis again. A high [code]damping[/code] value forces the attached bodies to align faster.
+ The spring joint's damping ratio. A value between [code]0[/code] and [code]1[/code]. When the two bodies move into different directions the system tries to align them to the spring axis again. A high [member damping] value forces the attached bodies to align faster.
</member>
<member name="length" type="float" setter="set_length" getter="get_length" default="50.0">
The spring joint's maximum length. The two attached bodies cannot stretch it past this value.
diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index 6cb049e0e4..3c7d66a67b 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -119,8 +119,8 @@
<param index="6" name="callback" type="Callable" />
<description>
Displays OS native dialog for selecting files or directories in the file system.
- Callbacks have the following arguments: [code]bool status, PackedStringArray selected_paths[/code].
- [b]Note:[/b] This method is implemented if the display server has the [code]FEATURE_NATIVE_DIALOG[/code] feature.
+ Callbacks have the following arguments: [code]bool status, PackedStringArray selected_paths, int selected_filter_index[/code].
+ [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature.
[b]Note:[/b] This method is implemented on Linux, Windows and macOS.
[b]Note:[/b] [param current_directory] might be ignored.
[b]Note:[/b] On Linux, [param show_hidden] is ignored.
@@ -577,6 +577,16 @@
[b]Note:[/b] This method is implemented only on macOS.
</description>
</method>
+ <method name="global_menu_is_item_hidden" qualifiers="const">
+ <return type="bool" />
+ <param index="0" name="menu_root" type="String" />
+ <param index="1" name="idx" type="int" />
+ <description>
+ Returns [code]true[/code] if the item at index [param idx] is hidden.
+ See [method global_menu_set_item_hidden] for more info on how to hide an item.
+ [b]Note:[/b] This method is implemented only on macOS.
+ </description>
+ </method>
<method name="global_menu_is_item_radio_checkable" qualifiers="const">
<return type="bool" />
<param index="0" name="menu_root" type="String" />
@@ -648,6 +658,27 @@
[b]Note:[/b] This method is implemented only on macOS.
</description>
</method>
+ <method name="global_menu_set_item_hidden">
+ <return type="void" />
+ <param index="0" name="menu_root" type="String" />
+ <param index="1" name="idx" type="int" />
+ <param index="2" name="hidden" type="bool" />
+ <description>
+ Hides/shows the item at index [param idx]. When it is hidden, an item does not appear in a menu and its action cannot be invoked.
+ [b]Note:[/b] This method is implemented only on macOS.
+ </description>
+ </method>
+ <method name="global_menu_set_item_hover_callbacks">
+ <return type="void" />
+ <param index="0" name="menu_root" type="String" />
+ <param index="1" name="idx" type="int" />
+ <param index="2" name="callback" type="Callable" />
+ <description>
+ Sets the callback of the item at index [param idx]. The callback is emitted when an item is hovered.
+ [b]Note:[/b] The [param callback] Callable needs to accept exactly one Variant parameter, the parameter passed to the Callable will be the value passed to the [code]tag[/code] parameter when the menu item was created.
+ [b]Note:[/b] This method is implemented only on macOS.
+ </description>
+ </method>
<method name="global_menu_set_item_icon">
<return type="void" />
<param index="0" name="menu_root" type="String" />
@@ -751,6 +782,15 @@
[b]Note:[/b] This method is implemented only on macOS.
</description>
</method>
+ <method name="global_menu_set_popup_callbacks">
+ <return type="void" />
+ <param index="0" name="menu_root" type="String" />
+ <param index="1" name="open_callback" type="Callable" />
+ <param index="2" name="close_callback" type="Callable" />
+ <description>
+ Registers callables to emit when the menu is respectively about to show or closed.
+ </description>
+ </method>
<method name="has_feature" qualifiers="const">
<return type="bool" />
<param index="0" name="feature" type="int" enum="DisplayServer.Feature" />
@@ -1055,7 +1095,7 @@
Each [Dictionary] contains two [String] entries:
- [code]name[/code] is voice name.
- [code]id[/code] is voice identifier.
- - [code]language[/code] is language code in [code]lang_Variant[/code] format. [code]lang[/code] part is a 2 or 3-letter code based on the ISO-639 standard, in lowercase. And [code]Variant[/code] part is an engine dependent string describing country, region or/and dialect.
+ - [code]language[/code] is language code in [code]lang_Variant[/code] format. The [code]lang[/code] part is a 2 or 3-letter code based on the ISO-639 standard, in lowercase. The [code skip-lint]Variant[/code] part is an engine-dependent string describing country, region or/and dialect.
Note that Godot depends on system libraries for text-to-speech functionality. These libraries are installed by default on Windows and macOS, but not on all Linux distributions. If they are not present, this method will return an empty list. This applies to both Godot users on Linux, as well as end-users on Linux running Godot games that use text-to-speech.
[b]Note:[/b] This method is implemented on Android, iOS, Web, Linux (X11), macOS, and Windows.
[b]Note:[/b] [member ProjectSettings.audio/general/text_to_speech] should be [code]true[/code] to use text-to-speech.
@@ -1291,6 +1331,15 @@
Returns the size of the window specified by [param window_id] (in pixels), including the borders drawn by the operating system. See also [method window_get_size].
</description>
</method>
+ <method name="window_get_title_size" qualifiers="const">
+ <return type="Vector2i" />
+ <param index="0" name="title" type="String" />
+ <param index="1" name="window_id" type="int" default="0" />
+ <description>
+ Returns the estimated window title bar size (including text and window buttons) for the window specified by [param window_id] (in pixels). This method does not change the window title.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
+ </description>
+ </method>
<method name="window_get_vsync_mode" qualifiers="const">
<return type="int" enum="DisplayServer.VSyncMode" />
<param index="0" name="window_id" type="int" default="0" />
@@ -1787,14 +1836,16 @@
</constant>
<constant name="WINDOW_MODE_FULLSCREEN" value="3" enum="WindowMode">
Full screen mode with full multi-window support.
- Full screen window cover the entire display area of a screen, have no border or decorations. Display video mode is not changed.
+ Full screen window covers the entire display area of a screen and has no decorations. The display's video mode is not changed.
+ [b]On Windows:[/b] Multi-window full-screen mode has a 1px border of the [member ProjectSettings.rendering/environment/defaults/default_clear_color] color.
+ [b]On macOS:[/b] A new desktop is used to display the running project.
[b]Note:[/b] Regardless of the platform, enabling full screen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling full screen mode.
</constant>
<constant name="WINDOW_MODE_EXCLUSIVE_FULLSCREEN" value="4" enum="WindowMode">
A single window full screen mode. This mode has less overhead, but only one window can be open on a given screen at a time (opening a child window or application switching will trigger a full screen transition).
- Full screen window cover the entire display area of a screen, have no border or decorations. Display video mode is not changed.
+ Full screen window covers the entire display area of a screen and has no border or decorations. The display's video mode is not changed.
[b]On Windows:[/b] Depending on video driver, full screen transition might cause screens to go black for a moment.
- [b]On macOS:[/b] Exclusive full screen mode prevents Dock and Menu from showing up when the mouse pointer is hovering the edge of the screen.
+ [b]On macOS:[/b] A new desktop is used to display the running project. Exclusive full screen mode prevents Dock and Menu from showing up when the mouse pointer is hovering the edge of the screen.
[b]On Linux (X11):[/b] Exclusive full screen mode bypasses compositor.
[b]Note:[/b] Regardless of the platform, enabling full screen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling full screen mode.
</constant>
diff --git a/doc/classes/EditorExportPlugin.xml b/doc/classes/EditorExportPlugin.xml
index fde6307a13..853348f64c 100644
--- a/doc/classes/EditorExportPlugin.xml
+++ b/doc/classes/EditorExportPlugin.xml
@@ -228,8 +228,8 @@
<param index="0" name="path" type="String" />
<description>
Adds a dynamic library (*.dylib, *.framework) to Linking Phase in iOS's Xcode project and embeds it into resulting binary.
- [b]Note:[/b] For static libraries (*.a) works in same way as [code]add_ios_framework[/code].
- This method should not be used for System libraries as they are already present on the device.
+ [b]Note:[/b] For static libraries (*.a) works in same way as [method add_ios_framework].
+ [b]Note:[/b] This method should not be used for System libraries as they are already present on the device.
</description>
</method>
<method name="add_ios_framework">
diff --git a/doc/classes/EditorFileDialog.xml b/doc/classes/EditorFileDialog.xml
index 59c0b7b912..b51341dc24 100644
--- a/doc/classes/EditorFileDialog.xml
+++ b/doc/classes/EditorFileDialog.xml
@@ -43,7 +43,7 @@
<method name="get_vbox">
<return type="VBoxContainer" />
<description>
- Returns the [code]VBoxContainer[/code] used to display the file system.
+ Returns the [VBoxContainer] used to display the file system.
[b]Warning:[/b] This is a required internal node, removing and freeing it may cause a crash. If you wish to hide it or any of its children, use their [member CanvasItem.visible] property.
</description>
</method>
diff --git a/doc/classes/EditorImportPlugin.xml b/doc/classes/EditorImportPlugin.xml
index d1361c4f0d..82ba956151 100644
--- a/doc/classes/EditorImportPlugin.xml
+++ b/doc/classes/EditorImportPlugin.xml
@@ -234,7 +234,7 @@
<param index="2" name="custom_importer" type="String" default="&quot;&quot;" />
<param index="3" name="generator_parameters" type="Variant" default="null" />
<description>
- This function can only be called during the [method _import] callback and it allows manually importing resources from it. This is useful when the imported file generates external resources that require importing (as example, images). Custom parameters for the ".import" file can be passed via the [param custom_options]. Additionally, in cases where multiple importers can handle a file, the [param custom_importer] ca be specified to force a specific one. This function performs a resource import and returns immediately with a success or error code. [param generator_parameters] defines optional extra metadata which will be stored as [code]generator_parameters[/code] in the [code]remap[/code] section of the [code].import[/code] file, for example to store a md5 hash of the source data.
+ This function can only be called during the [method _import] callback and it allows manually importing resources from it. This is useful when the imported file generates external resources that require importing (as example, images). Custom parameters for the ".import" file can be passed via the [param custom_options]. Additionally, in cases where multiple importers can handle a file, the [param custom_importer] ca be specified to force a specific one. This function performs a resource import and returns immediately with a success or error code. [param generator_parameters] defines optional extra metadata which will be stored as [code skip-lint]generator_parameters[/code] in the [code]remap[/code] section of the [code].import[/code] file, for example to store a md5 hash of the source data.
</description>
</method>
</methods>
diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml
index f8a73141a9..d0de09e451 100644
--- a/doc/classes/EditorInterface.xml
+++ b/doc/classes/EditorInterface.xml
@@ -339,7 +339,7 @@
<return type="void" />
<param index="0" name="name" type="String" />
<description>
- Sets the editor's current main screen to the one specified in [param name]. [param name] must match the text of the tab in question exactly ([code]2D[/code], [code]3D[/code], [code]Script[/code], [code]AssetLib[/code]).
+ Sets the editor's current main screen to the one specified in [param name]. [param name] must match the title of the tab in question exactly (e.g. [code]2D[/code], [code]3D[/code], [code skip-lint]Script[/code], or [code]AssetLib[/code] for default tabs).
</description>
</method>
<method name="set_plugin_enabled">
diff --git a/doc/classes/EditorNode3DGizmo.xml b/doc/classes/EditorNode3DGizmo.xml
index 5ac9840f81..1680400384 100644
--- a/doc/classes/EditorNode3DGizmo.xml
+++ b/doc/classes/EditorNode3DGizmo.xml
@@ -95,7 +95,7 @@
<param index="0" name="camera" type="Camera3D" />
<param index="1" name="frustum" type="Plane[]" />
<description>
- Override this method to allow selecting subgizmos using mouse drag box selection. Given a [param camera] and a [param frustum], this method should return which subgizmos are contained within the frustum. The [param frustum] argument consists of an array with all the [code]Plane[/code]s that make up the selection frustum. The returned value should contain a list of unique subgizmo identifiers, which can have any non-negative value and will be used in other virtual methods like [method _get_subgizmo_transform] or [method _commit_subgizmos].
+ Override this method to allow selecting subgizmos using mouse drag box selection. Given a [param camera] and a [param frustum], this method should return which subgizmos are contained within the frustum. The [param frustum] argument consists of an array with all the [Plane]s that make up the selection frustum. The returned value should contain a list of unique subgizmo identifiers, which can have any non-negative value and will be used in other virtual methods like [method _get_subgizmo_transform] or [method _commit_subgizmos].
</description>
</method>
<method name="_subgizmos_intersect_ray" qualifiers="virtual const">
diff --git a/doc/classes/EditorNode3DGizmoPlugin.xml b/doc/classes/EditorNode3DGizmoPlugin.xml
index 3e90c54647..da7ee17335 100644
--- a/doc/classes/EditorNode3DGizmoPlugin.xml
+++ b/doc/classes/EditorNode3DGizmoPlugin.xml
@@ -147,7 +147,7 @@
<param index="1" name="camera" type="Camera3D" />
<param index="2" name="frustum_planes" type="Plane[]" />
<description>
- Override this method to allow selecting subgizmos using mouse drag box selection. Given a [param camera] and [param frustum_planes], this method should return which subgizmos are contained within the frustums. The [param frustum_planes] argument consists of an [code]Array[/code] with all the [code]Plane[/code]s that make up the selection frustum. The returned value should contain a list of unique subgizmo identifiers, these identifiers can have any non-negative value and will be used in other virtual methods like [method _get_subgizmo_transform] or [method _commit_subgizmos]. Called for this plugin's active gizmos.
+ Override this method to allow selecting subgizmos using mouse drag box selection. Given a [param camera] and [param frustum_planes], this method should return which subgizmos are contained within the frustums. The [param frustum_planes] argument consists of an array with all the [Plane]s that make up the selection frustum. The returned value should contain a list of unique subgizmo identifiers, these identifiers can have any non-negative value and will be used in other virtual methods like [method _get_subgizmo_transform] or [method _commit_subgizmos]. Called for this plugin's active gizmos.
</description>
</method>
<method name="_subgizmos_intersect_ray" qualifiers="virtual const">
diff --git a/doc/classes/Environment.xml b/doc/classes/Environment.xml
index 3b6263b67b..acd959d7f7 100644
--- a/doc/classes/Environment.xml
+++ b/doc/classes/Environment.xml
@@ -36,20 +36,20 @@
</methods>
<members>
<member name="adjustment_brightness" type="float" setter="set_adjustment_brightness" getter="get_adjustment_brightness" default="1.0">
- The global brightness value of the rendered scene. Effective only if [code]adjustment_enabled[/code] is [code]true[/code].
+ The global brightness value of the rendered scene. Effective only if [member adjustment_enabled] is [code]true[/code].
</member>
<member name="adjustment_color_correction" type="Texture" setter="set_adjustment_color_correction" getter="get_adjustment_color_correction">
- The [Texture2D] or [Texture3D] lookup table (LUT) to use for the built-in post-process color grading. Can use a [GradientTexture1D] for a 1-dimensional LUT, or a [Texture3D] for a more complex LUT. Effective only if [code]adjustment_enabled[/code] is [code]true[/code].
+ The [Texture2D] or [Texture3D] lookup table (LUT) to use for the built-in post-process color grading. Can use a [GradientTexture1D] for a 1-dimensional LUT, or a [Texture3D] for a more complex LUT. Effective only if [member adjustment_enabled] is [code]true[/code].
</member>
<member name="adjustment_contrast" type="float" setter="set_adjustment_contrast" getter="get_adjustment_contrast" default="1.0">
- The global contrast value of the rendered scene (default value is 1). Effective only if [code]adjustment_enabled[/code] is [code]true[/code].
+ The global contrast value of the rendered scene (default value is 1). Effective only if [member adjustment_enabled] is [code]true[/code].
</member>
<member name="adjustment_enabled" type="bool" setter="set_adjustment_enabled" getter="is_adjustment_enabled" default="false">
If [code]true[/code], enables the [code]adjustment_*[/code] properties provided by this resource. If [code]false[/code], modifications to the [code]adjustment_*[/code] properties will have no effect on the rendered scene.
[b]Note:[/b] Adjustments are only supported in the Forward+ and Mobile rendering methods, not Compatibility.
</member>
<member name="adjustment_saturation" type="float" setter="set_adjustment_saturation" getter="get_adjustment_saturation" default="1.0">
- The global color saturation value of the rendered scene (default value is 1). Effective only if [code]adjustment_enabled[/code] is [code]true[/code].
+ The global color saturation value of the rendered scene (default value is 1). Effective only if [member adjustment_enabled] is [code]true[/code].
</member>
<member name="ambient_light_color" type="Color" setter="set_ambient_light_color" getter="get_ambient_light_color" default="Color(0, 0, 0, 1)">
The ambient light's [Color]. Only effective if [member ambient_light_sky_contribution] is lower than [code]1.0[/code] (exclusive).
diff --git a/doc/classes/FileAccess.xml b/doc/classes/FileAccess.xml
index e50e610e71..42528dc287 100644
--- a/doc/classes/FileAccess.xml
+++ b/doc/classes/FileAccess.xml
@@ -341,7 +341,7 @@
<param index="0" name="file" type="String" />
<param index="1" name="hidden" type="bool" />
<description>
- Sets file [code]hidden[/code] attribute.
+ Sets file [b]hidden[/b] attribute.
[b]Note:[/b] This method is implemented on iOS, BSD, macOS, and Windows.
</description>
</method>
@@ -350,7 +350,7 @@
<param index="0" name="file" type="String" />
<param index="1" name="ro" type="bool" />
<description>
- Sets file [code]read only[/code] attribute.
+ Sets file [b]read only[/b] attribute.
[b]Note:[/b] This method is implemented on iOS, BSD, macOS, and Windows.
</description>
</method>
diff --git a/doc/classes/FontFile.xml b/doc/classes/FontFile.xml
index 1628f8819f..e6557343d3 100644
--- a/doc/classes/FontFile.xml
+++ b/doc/classes/FontFile.xml
@@ -255,7 +255,7 @@
<return type="Vector2i[]" />
<param index="0" name="cache_index" type="int" />
<description>
- Returns list of the font sizes in the cache. Each size is [code]Vector2i[/code] with font size and outline size.
+ Returns list of the font sizes in the cache. Each size is [Vector2i] with font size and outline size.
</description>
</method>
<method name="get_texture_count" qualifiers="const">
diff --git a/doc/classes/FontVariation.xml b/doc/classes/FontVariation.xml
index 128f3b5a85..e21881fc3e 100644
--- a/doc/classes/FontVariation.xml
+++ b/doc/classes/FontVariation.xml
@@ -70,7 +70,7 @@
</member>
<member name="variation_opentype" type="Dictionary" setter="set_variation_opentype" getter="get_variation_opentype" default="{}">
Font OpenType variation coordinates. More info: [url=https://docs.microsoft.com/en-us/typography/opentype/spec/dvaraxisreg]OpenType variation tags[/url].
- [b]Note:[/b] This [Dictionary] uses OpenType tags as keys. Variation axes can be identified both by tags([code]int[/code]) and names ([code]string[/code]). Some axes might be accessible by multiple names. For example, [code]wght[/code] refers to the same axis as [code]weight[/code]. Tags on the other hand are unique. To convert between names and tags, use [method TextServer.name_to_tag] and [method TextServer.tag_to_name].
+ [b]Note:[/b] This [Dictionary] uses OpenType tags as keys. Variation axes can be identified both by tags ([int], e.g. [code]0x77678674[/code]) and names ([String], e.g. [code]wght[/code]). Some axes might be accessible by multiple names. For example, [code]wght[/code] refers to the same axis as [code]weight[/code]. Tags on the other hand are unique. To convert between names and tags, use [method TextServer.name_to_tag] and [method TextServer.tag_to_name].
[b]Note:[/b] To get available variation axes of a font, use [method Font.get_supported_variation_list].
</member>
<member name="variation_transform" type="Transform2D" setter="set_variation_transform" getter="get_variation_transform" default="Transform2D(1, 0, 0, 1, 0, 0)">
diff --git a/doc/classes/GPUParticles3D.xml b/doc/classes/GPUParticles3D.xml
index e42aca1ada..3d7243fcbd 100644
--- a/doc/classes/GPUParticles3D.xml
+++ b/doc/classes/GPUParticles3D.xml
@@ -5,7 +5,7 @@
</brief_description>
<description>
3D particle node used to create a variety of particle systems and effects. [GPUParticles3D] features an emitter that generates some number of particles at a given rate.
- Use the [code]process_material[/code] property to add a [ParticleProcessMaterial] to configure particle appearance and behavior. Alternatively, you can add a [ShaderMaterial] which will be applied to all particles.
+ Use [member process_material] to add a [ParticleProcessMaterial] to configure particle appearance and behavior. Alternatively, you can add a [ShaderMaterial] which will be applied to all particles.
</description>
<tutorials>
<link title="Particle systems (3D)">$DOCS_URL/tutorials/3d/particles/index.html</link>
@@ -108,7 +108,7 @@
If [code]true[/code], particles use the parent node's coordinate space (known as local coordinates). This will cause particles to move and rotate along the [GPUParticles3D] node (and its parents) when it is moved or rotated. If [code]false[/code], particles use global coordinates; they will not move or rotate along the [GPUParticles3D] node (and its parents) when it is moved or rotated.
</member>
<member name="one_shot" type="bool" setter="set_one_shot" getter="get_one_shot" default="false">
- If [code]true[/code], only [code]amount[/code] particles will be emitted.
+ If [code]true[/code], only the number of particles equal to [member amount] will be emitted.
</member>
<member name="preprocess" type="float" setter="set_pre_process_time" getter="get_pre_process_time" default="0.0">
Amount of time to preprocess the particles before animation starts. Lets you start the animation some time after particles have started emitting.
diff --git a/doc/classes/HTTPRequest.xml b/doc/classes/HTTPRequest.xml
index 961e9f216f..03c1ce2e00 100644
--- a/doc/classes/HTTPRequest.xml
+++ b/doc/classes/HTTPRequest.xml
@@ -235,9 +235,9 @@
<members>
<member name="accept_gzip" type="bool" setter="set_accept_gzip" getter="is_accepting_gzip" default="true">
If [code]true[/code], this header will be added to each request: [code]Accept-Encoding: gzip, deflate[/code] telling servers that it's okay to compress response bodies.
- Any Response body declaring a [code]Content-Encoding[/code] of either [code]gzip[/code] or [code]deflate[/code] will then be automatically decompressed, and the uncompressed bytes will be delivered via [code]request_completed[/code].
- If the user has specified their own [code]Accept-Encoding[/code] header, then no header will be added regardless of [code]accept_gzip[/code].
- If [code]false[/code] no header will be added, and no decompression will be performed on response bodies. The raw bytes of the response body will be returned via [code]request_completed[/code].
+ Any Response body declaring a [code]Content-Encoding[/code] of either [code]gzip[/code] or [code]deflate[/code] will then be automatically decompressed, and the uncompressed bytes will be delivered via [signal request_completed].
+ If the user has specified their own [code]Accept-Encoding[/code] header, then no header will be added regardless of [member accept_gzip].
+ If [code]false[/code] no header will be added, and no decompression will be performed on response bodies. The raw bytes of the response body will be returned via [signal request_completed].
</member>
<member name="body_size_limit" type="int" setter="set_body_size_limit" getter="get_body_size_limit" default="-1">
Maximum allowed size for response bodies. If the response body is compressed, this will be used as the maximum allowed size for the decompressed body.
diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml
index fabe6fe4bd..310dae6c65 100644
--- a/doc/classes/Image.xml
+++ b/doc/classes/Image.xml
@@ -5,7 +5,7 @@
</brief_description>
<description>
Native image datatype. Contains image data which can be converted to an [ImageTexture] and provides commonly used [i]image processing[/i] methods. The maximum width and height for an [Image] are [constant MAX_WIDTH] and [constant MAX_HEIGHT].
- An [Image] cannot be assigned to a [code]texture[/code] property of an object directly (such as [Sprite2D]), and has to be converted manually to an [ImageTexture] first.
+ An [Image] cannot be assigned to a texture property of an object directly (such as [member Sprite2D.texture]), and has to be converted manually to an [ImageTexture] first.
[b]Note:[/b] The maximum image size is 16384×16384 pixels due to graphics hardware limitations. Larger images may fail to import.
</description>
<tutorials>
@@ -233,7 +233,7 @@
<return type="int" />
<param index="0" name="mipmap" type="int" />
<description>
- Returns the offset where the image's mipmap with index [param mipmap] is stored in the [code]data[/code] dictionary.
+ Returns the offset where the image's mipmap with index [param mipmap] is stored in the [member data] dictionary.
</description>
</method>
<method name="get_pixel" qualifiers="const">
diff --git a/doc/classes/ImageTextureLayered.xml b/doc/classes/ImageTextureLayered.xml
index 545c9289d1..e8d054bdd7 100644
--- a/doc/classes/ImageTextureLayered.xml
+++ b/doc/classes/ImageTextureLayered.xml
@@ -23,7 +23,7 @@
<param index="1" name="layer" type="int" />
<description>
Replaces the existing [Image] data at the given [param layer] with this new image.
- The given [Image] must have the same width, height, image format and mipmapping setting (a [code]bool[/code] value) as the rest of the referenced images.
+ The given [Image] must have the same width, height, image format, and mipmapping flag as the rest of the referenced images.
If the image format is unsupported, it will be decompressed and converted to a similar and supported [enum Image.Format].
The update is immediate: it's synchronized with drawing.
</description>
diff --git a/doc/classes/JavaScriptBridge.xml b/doc/classes/JavaScriptBridge.xml
index 6ce5801864..faf5424c47 100644
--- a/doc/classes/JavaScriptBridge.xml
+++ b/doc/classes/JavaScriptBridge.xml
@@ -42,7 +42,7 @@
<param index="0" name="code" type="String" />
<param index="1" name="use_global_execution_context" type="bool" default="false" />
<description>
- Execute the string [param code] as JavaScript code within the browser window. This is a call to the actual global JavaScript function [code]eval()[/code].
+ Execute the string [param code] as JavaScript code within the browser window. This is a call to the actual global JavaScript function [code skip-lint]eval()[/code].
If [param use_global_execution_context] is [code]true[/code], the code will be evaluated in the global execution context. Otherwise, it is evaluated in the execution context of a function within the engine's runtime environment.
</description>
</method>
diff --git a/doc/classes/Joint2D.xml b/doc/classes/Joint2D.xml
index 9fb9154827..af0a54815f 100644
--- a/doc/classes/Joint2D.xml
+++ b/doc/classes/Joint2D.xml
@@ -18,7 +18,7 @@
</methods>
<members>
<member name="bias" type="float" setter="set_bias" getter="get_bias" default="0.0">
- When [member node_a] and [member node_b] move in different directions the [code]bias[/code] controls how fast the joint pulls them back to their original position. The lower the [code]bias[/code] the more the two bodies can pull on the joint.
+ When [member node_a] and [member node_b] move in different directions the [member bias] controls how fast the joint pulls them back to their original position. The lower the [member bias] the more the two bodies can pull on the joint.
When set to [code]0[/code], the default value from [member ProjectSettings.physics/2d/solver/default_constraint_bias] is used.
</member>
<member name="disable_collision" type="bool" setter="set_exclude_nodes_from_collision" getter="get_exclude_nodes_from_collision" default="true">
diff --git a/doc/classes/Label.xml b/doc/classes/Label.xml
index d0aa89b2e7..f13f1bdcf4 100644
--- a/doc/classes/Label.xml
+++ b/doc/classes/Label.xml
@@ -58,7 +58,7 @@
Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead.
</member>
<member name="lines_skipped" type="int" setter="set_lines_skipped" getter="get_lines_skipped" default="0">
- The node ignores the first [code]lines_skipped[/code] lines before it starts to display text.
+ The number of the lines ignored and not displayed from the start of the [member text] value.
</member>
<member name="max_lines_visible" type="int" setter="set_max_lines_visible" getter="get_max_lines_visible" default="-1">
Limits the lines of text the node shows on screen.
diff --git a/doc/classes/Line2D.xml b/doc/classes/Line2D.xml
index 4c444721f4..283d847a93 100644
--- a/doc/classes/Line2D.xml
+++ b/doc/classes/Line2D.xml
@@ -92,7 +92,7 @@
Determines the miter limit of the polyline. Normally, when [member joint_mode] is set to [constant LINE_JOINT_SHARP], sharp angles fall back to using the logic of [constant LINE_JOINT_BEVEL] joints to prevent very long miters. Higher values of this property mean that the fallback to a bevel joint will happen at sharper angles.
</member>
<member name="texture" type="Texture2D" setter="set_texture" getter="get_texture">
- The texture used for the polyline. Uses [code]texture_mode[/code] for drawing style.
+ The texture used for the polyline. Uses [member texture_mode] for drawing style.
</member>
<member name="texture_mode" type="int" setter="set_texture_mode" getter="get_texture_mode" enum="Line2D.LineTextureMode" default="0">
The style to render the [member texture] of the polyline. Use [enum LineTextureMode] constants.
diff --git a/doc/classes/LinkButton.xml b/doc/classes/LinkButton.xml
index 4f38b4d336..aa81b6fde0 100644
--- a/doc/classes/LinkButton.xml
+++ b/doc/classes/LinkButton.xml
@@ -94,7 +94,7 @@
Font size of the [LinkButton]'s text.
</theme_item>
<theme_item name="focus" data_type="style" type="StyleBox">
- [StyleBox] used when the [LinkButton] is focused. The [code]focus[/code] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
+ [StyleBox] used when the [LinkButton] is focused. The [theme_item focus] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
</theme_item>
</theme_items>
</class>
diff --git a/doc/classes/MarginContainer.xml b/doc/classes/MarginContainer.xml
index 1a6621d354..b83ee7ba00 100644
--- a/doc/classes/MarginContainer.xml
+++ b/doc/classes/MarginContainer.xml
@@ -30,16 +30,16 @@
</tutorials>
<theme_items>
<theme_item name="margin_bottom" data_type="constant" type="int" default="0">
- All direct children of [MarginContainer] will have a bottom margin of [code]margin_bottom[/code] pixels.
+ Offsets towards the inside direct children of the container by this amount of pixels from the bottom.
</theme_item>
<theme_item name="margin_left" data_type="constant" type="int" default="0">
- All direct children of [MarginContainer] will have a left margin of [code]margin_left[/code] pixels.
+ Offsets towards the inside direct children of the container by this amount of pixels from the left.
</theme_item>
<theme_item name="margin_right" data_type="constant" type="int" default="0">
- All direct children of [MarginContainer] will have a right margin of [code]margin_right[/code] pixels.
+ Offsets towards the inside direct children of the container by this amount of pixels from the right.
</theme_item>
<theme_item name="margin_top" data_type="constant" type="int" default="0">
- All direct children of [MarginContainer] will have a top margin of [code]margin_top[/code] pixels.
+ Offsets towards the inside direct children of the container by this amount of pixels from the top.
</theme_item>
</theme_items>
</class>
diff --git a/doc/classes/MenuButton.xml b/doc/classes/MenuButton.xml
index 6aa17c1e16..16b3772fa9 100644
--- a/doc/classes/MenuButton.xml
+++ b/doc/classes/MenuButton.xml
@@ -39,7 +39,7 @@
The number of items currently in the list.
</member>
<member name="switch_on_hover" type="bool" setter="set_switch_on_hover" getter="is_switch_on_hover" default="false">
- If [code]true[/code], when the cursor hovers above another [MenuButton] within the same parent which also has [code]switch_on_hover[/code] enabled, it will close the current [MenuButton] and open the other one.
+ If [code]true[/code], when the cursor hovers above another [MenuButton] within the same parent which also has [member switch_on_hover] enabled, it will close the current [MenuButton] and open the other one.
</member>
<member name="toggle_mode" type="bool" setter="set_toggle_mode" getter="is_toggle_mode" overrides="BaseButton" default="true" />
</members>
diff --git a/doc/classes/NavigationMeshSourceGeometryData3D.xml b/doc/classes/NavigationMeshSourceGeometryData3D.xml
index dc446dcaaa..dad3623416 100644
--- a/doc/classes/NavigationMeshSourceGeometryData3D.xml
+++ b/doc/classes/NavigationMeshSourceGeometryData3D.xml
@@ -14,7 +14,7 @@
<param index="0" name="faces" type="PackedVector3Array" />
<param index="1" name="xform" type="Transform3D" />
<description>
- Adds an array of vertex positions to the geometry data for navigation mesh baking to form triangulated faces. For each face the array must have three vertex positions in clockwise winding order. Since [NavigationMesh] resource have no transform all vertex positions need to be offset by the node's transform using the [code]xform[/code] parameter.
+ Adds an array of vertex positions to the geometry data for navigation mesh baking to form triangulated faces. For each face the array must have three vertex positions in clockwise winding order. Since [NavigationMesh] resources have no transform, all vertex positions need to be offset by the node's transform using [param xform].
</description>
</method>
<method name="add_mesh">
@@ -22,7 +22,7 @@
<param index="0" name="mesh" type="Mesh" />
<param index="1" name="xform" type="Transform3D" />
<description>
- Adds the geometry data of a [Mesh] resource to the navigation mesh baking data. The mesh must have valid triangulated mesh data to be considered. Since [NavigationMesh] resource have no transform all vertex positions need to be offset by the node's transform using the [code]xform[/code] parameter.
+ Adds the geometry data of a [Mesh] resource to the navigation mesh baking data. The mesh must have valid triangulated mesh data to be considered. Since [NavigationMesh] resources have no transform, all vertex positions need to be offset by the node's transform using [param xform].
</description>
</method>
<method name="add_mesh_array">
@@ -30,7 +30,7 @@
<param index="0" name="mesh_array" type="Array" />
<param index="1" name="xform" type="Transform3D" />
<description>
- Adds an [Array] the size of [constant Mesh.ARRAY_MAX] and with vertices at index [constant Mesh.ARRAY_VERTEX] and indices at index [constant Mesh.ARRAY_INDEX] to the navigation mesh baking data. The array must have valid triangulated mesh data to be considered. Since [NavigationMesh] resource have no transform all vertex positions need to be offset by the node's transform using the [code]xform[/code] parameter.
+ Adds an [Array] the size of [constant Mesh.ARRAY_MAX] and with vertices at index [constant Mesh.ARRAY_VERTEX] and indices at index [constant Mesh.ARRAY_INDEX] to the navigation mesh baking data. The array must have valid triangulated mesh data to be considered. Since [NavigationMesh] resources have no transform, all vertex positions need to be offset by the node's transform using [param xform].
</description>
</method>
<method name="clear">
diff --git a/doc/classes/NavigationPathQueryParameters2D.xml b/doc/classes/NavigationPathQueryParameters2D.xml
index 4acb6b697f..74cf4bdb75 100644
--- a/doc/classes/NavigationPathQueryParameters2D.xml
+++ b/doc/classes/NavigationPathQueryParameters2D.xml
@@ -11,7 +11,7 @@
</tutorials>
<members>
<member name="map" type="RID" setter="set_map" getter="get_map" default="RID()">
- The navigation [code]map[/code] [RID] used in the path query.
+ The navigation map [RID] used in the path query.
</member>
<member name="metadata_flags" type="int" setter="set_metadata_flags" getter="get_metadata_flags" enum="NavigationPathQueryParameters2D.PathMetadataFlags" is_bitfield="true" default="7">
Additional information to include with the navigation path.
diff --git a/doc/classes/NavigationPathQueryParameters3D.xml b/doc/classes/NavigationPathQueryParameters3D.xml
index 204e915efd..2a366c0498 100644
--- a/doc/classes/NavigationPathQueryParameters3D.xml
+++ b/doc/classes/NavigationPathQueryParameters3D.xml
@@ -11,7 +11,7 @@
</tutorials>
<members>
<member name="map" type="RID" setter="set_map" getter="get_map" default="RID()">
- The navigation [code]map[/code] [RID] used in the path query.
+ The navigation map [RID] used in the path query.
</member>
<member name="metadata_flags" type="int" setter="set_metadata_flags" getter="get_metadata_flags" enum="NavigationPathQueryParameters3D.PathMetadataFlags" is_bitfield="true" default="7">
Additional information to include with the navigation path.
diff --git a/doc/classes/NavigationRegion2D.xml b/doc/classes/NavigationRegion2D.xml
index e023d8dd7f..ef660305f4 100644
--- a/doc/classes/NavigationRegion2D.xml
+++ b/doc/classes/NavigationRegion2D.xml
@@ -85,7 +85,7 @@
Determines if the [NavigationRegion2D] is enabled or disabled.
</member>
<member name="enter_cost" type="float" setter="set_enter_cost" getter="get_enter_cost" default="0.0">
- When pathfinding enters this region's navigation mesh from another regions navigation mesh the [code]enter_cost[/code] value is added to the path distance for determining the shortest path.
+ When pathfinding enters this region's navigation mesh from another regions navigation mesh the [member enter_cost] value is added to the path distance for determining the shortest path.
</member>
<member name="navigation_layers" type="int" setter="set_navigation_layers" getter="get_navigation_layers" default="1">
A bitfield determining all navigation layers the region belongs to. These navigation layers can be checked upon when requesting a path with [method NavigationServer2D.map_get_path].
@@ -94,7 +94,7 @@
The [NavigationPolygon] resource to use.
</member>
<member name="travel_cost" type="float" setter="set_travel_cost" getter="get_travel_cost" default="1.0">
- When pathfinding moves inside this region's navigation mesh the traveled distances are multiplied with [code]travel_cost[/code] for determining the shortest path.
+ When pathfinding moves inside this region's navigation mesh the traveled distances are multiplied with [member travel_cost] for determining the shortest path.
</member>
<member name="use_edge_connections" type="bool" setter="set_use_edge_connections" getter="get_use_edge_connections" default="true">
If enabled the navigation region will use edge connections to connect with other navigation regions within proximity of the navigation map edge connection margin.
diff --git a/doc/classes/NavigationRegion3D.xml b/doc/classes/NavigationRegion3D.xml
index 7e8ead2032..3257160485 100644
--- a/doc/classes/NavigationRegion3D.xml
+++ b/doc/classes/NavigationRegion3D.xml
@@ -63,7 +63,7 @@
Determines if the [NavigationRegion3D] is enabled or disabled.
</member>
<member name="enter_cost" type="float" setter="set_enter_cost" getter="get_enter_cost" default="0.0">
- When pathfinding enters this region's navigation mesh from another regions navigation mesh the [code]enter_cost[/code] value is added to the path distance for determining the shortest path.
+ When pathfinding enters this region's navigation mesh from another regions navigation mesh the [member enter_cost] value is added to the path distance for determining the shortest path.
</member>
<member name="navigation_layers" type="int" setter="set_navigation_layers" getter="get_navigation_layers" default="1">
A bitfield determining all navigation layers the region belongs to. These navigation layers can be checked upon when requesting a path with [method NavigationServer3D.map_get_path].
@@ -72,7 +72,7 @@
The [NavigationMesh] resource to use.
</member>
<member name="travel_cost" type="float" setter="set_travel_cost" getter="get_travel_cost" default="1.0">
- When pathfinding moves inside this region's navigation mesh the traveled distances are multiplied with [code]travel_cost[/code] for determining the shortest path.
+ When pathfinding moves inside this region's navigation mesh the traveled distances are multiplied with [member travel_cost] for determining the shortest path.
</member>
<member name="use_edge_connections" type="bool" setter="set_use_edge_connections" getter="get_use_edge_connections" default="true">
If enabled the navigation region will use edge connections to connect with other navigation regions within proximity of the navigation map edge connection margin.
diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml
index 8da2f0b86b..7eff3fc950 100644
--- a/doc/classes/Node.xml
+++ b/doc/classes/Node.xml
@@ -93,7 +93,7 @@
Called when the node is "ready", i.e. when both the node and its children have entered the scene tree. If the node has children, their [method _ready] callbacks get triggered first, and the parent node will receive the ready notification afterwards.
Corresponds to the [constant NOTIFICATION_READY] notification in [method Object._notification]. See also the [code]@onready[/code] annotation for variables.
Usually used for initialization. For even earlier initialization, [method Object._init] may be used. See also [method _enter_tree].
- [b]Note:[/b] [method _ready] may be called only once for each node. After removing a node from the scene tree and adding it again, [code]_ready[/code] will not be called a second time. This can be bypassed by requesting another call with [method request_ready], which may be called anywhere before adding the node again.
+ [b]Note:[/b] [method _ready] may be called only once for each node. After removing a node from the scene tree and adding it again, [method _ready] will not be called a second time. This can be bypassed by requesting another call with [method request_ready], which may be called anywhere before adding the node again.
</description>
</method>
<method name="_shortcut_input" qualifiers="virtual">
@@ -696,7 +696,7 @@
<method name="request_ready">
<return type="void" />
<description>
- Requests that [code]_ready[/code] be called again. Note that the method won't be called immediately, but is scheduled for when the node is added to the scene tree again (see [method _ready]). [code]_ready[/code] is called only for the node which requested it, which means that you need to request ready for each child if you want them to call [code]_ready[/code] too (in which case, [code]_ready[/code] will be called in the same order as it would normally).
+ Requests that [method _ready] be called again. Note that the method won't be called immediately, but is scheduled for when the node is added to the scene tree again. [method _ready] is called only for the node which requested it, which means that you need to request ready for each child if you want them to call [method _ready] too (in which case, [method _ready] will be called in the same order as it would normally).
</description>
</method>
<method name="rpc" qualifiers="vararg">
diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml
index 7cfbca1178..ae2ca324e3 100644
--- a/doc/classes/OS.xml
+++ b/doc/classes/OS.xml
@@ -257,7 +257,7 @@
<description>
Returns the host OS locale as a string of the form [code]language_Script_COUNTRY_VARIANT@extra[/code]. If you want only the language code and not the fully specified locale from the OS, you can use [method get_locale_language].
[code]language[/code] - 2 or 3-letter [url=https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes]language code[/url], in lower case.
- [code]Script[/code] - optional, 4-letter [url=https://en.wikipedia.org/wiki/ISO_15924]script code[/url], in title case.
+ [code skip-lint]Script[/code] - optional, 4-letter [url=https://en.wikipedia.org/wiki/ISO_15924]script code[/url], in title case.
[code]COUNTRY[/code] - optional, 2 or 3-letter [url=https://en.wikipedia.org/wiki/ISO_3166-1]country code[/url], in upper case.
[code]VARIANT[/code] - optional, language variant, region and sort order. Variant can have any number of underscored keywords.
[code]extra[/code] - optional, semicolon separated list of additional key words. Currency, calendar, sort order and numbering system information.
diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml
index c530569786..bb7289cc99 100644
--- a/doc/classes/Object.xml
+++ b/doc/classes/Object.xml
@@ -487,7 +487,7 @@
}
[/csharp]
[/codeblocks]
- [b][code]Object.connect()[/code] or [code]Signal.connect()[/code]?[/b]
+ [b][code skip-lint]Object.connect()[/code] or [code skip-lint]Signal.connect()[/code]?[/b]
As seen above, the recommended method to connect signals is not [method Object.connect]. The code block below shows the four options for connecting signals, using either this legacy method or the recommended [method Signal.connect], and using either an implicit [Callable] or a manually defined one.
[codeblocks]
[gdscript]
@@ -718,7 +718,7 @@
<param index="0" name="signal" type="StringName" />
<description>
Returns an [Array] of connections for the given [param signal] name. Each connection is represented as a [Dictionary] that contains three entries:
- - [code]signal[/code] is a reference to the [Signal];
+ - [code skip-lint]signal[/code] is a reference to the [Signal];
- [code]callable[/code] is a reference to the connected [Callable];
- [code]flags[/code] is a combination of [enum ConnectFlags].
</description>
diff --git a/doc/classes/PackedByteArray.xml b/doc/classes/PackedByteArray.xml
index b706e2782f..516cc9f020 100644
--- a/doc/classes/PackedByteArray.xml
+++ b/doc/classes/PackedByteArray.xml
@@ -182,7 +182,7 @@
<param index="1" name="compression_mode" type="int" default="0" />
<description>
Returns a new [PackedByteArray] with the data decompressed. Set the compression mode using one of [enum FileAccess.CompressionMode]'s constants. [b]This method only accepts brotli, gzip, and deflate compression modes.[/b]
- This method is potentially slower than [code]decompress[/code], as it may have to re-allocate its output buffer multiple times while decompressing, whereas [code]decompress[/code] knows it's output buffer size from the beginning.
+ This method is potentially slower than [method decompress], as it may have to re-allocate its output buffer multiple times while decompressing, whereas [method decompress] knows it's output buffer size from the beginning.
GZIP has a maximal compression ratio of 1032:1, meaning it's very possible for a small compressed payload to decompress to a potentially very large output. To guard against this, you may provide a maximum size this function is allowed to allocate in bytes via [param max_output_size]. Passing -1 will allow for unbounded output. If any positive value is passed, and the decompression exceeds that amount in bytes, then an error will be returned.
</description>
</method>
@@ -447,7 +447,7 @@
<method name="to_float32_array" qualifiers="const">
<return type="PackedFloat32Array" />
<description>
- Returns a copy of the data converted to a [PackedFloat32Array], where each block of 4 bytes has been converted to a 32-bit float (C++ [code]float[/code]).
+ Returns a copy of the data converted to a [PackedFloat32Array], where each block of 4 bytes has been converted to a 32-bit float (C++ [code skip-lint]float[/code]).
The size of the input array must be a multiple of 4 (size of 32-bit float). The size of the new array will be [code]byte_array.size() / 4[/code].
If the original data can't be converted to 32-bit floats, the resulting data is undefined.
</description>
diff --git a/doc/classes/PackedScene.xml b/doc/classes/PackedScene.xml
index f945ce30ad..a8cfc288f3 100644
--- a/doc/classes/PackedScene.xml
+++ b/doc/classes/PackedScene.xml
@@ -22,7 +22,7 @@
AddChild(scene);
[/csharp]
[/codeblocks]
- [b]Example of saving a node with different owners:[/b] The following example creates 3 objects: [Node2D] ([code]node[/code]), [RigidBody2D] ([code]body[/code]) and [CollisionObject2D] ([code]collision[/code]). [code]collision[/code] is a child of [code]body[/code] which is a child of [code]node[/code]. Only [code]body[/code] is owned by [code]node[/code] and [code]pack[/code] will therefore only save those two nodes, but not [code]collision[/code].
+ [b]Example of saving a node with different owners:[/b] The following example creates 3 objects: [Node2D] ([code]node[/code]), [RigidBody2D] ([code]body[/code]) and [CollisionObject2D] ([code]collision[/code]). [code]collision[/code] is a child of [code]body[/code] which is a child of [code]node[/code]. Only [code]body[/code] is owned by [code]node[/code] and [method pack] will therefore only save those two nodes, but not [code]collision[/code].
[codeblocks]
[gdscript]
# Create the objects.
@@ -85,7 +85,7 @@
<method name="get_state" qualifiers="const">
<return type="SceneState" />
<description>
- Returns the [code]SceneState[/code] representing the scene file contents.
+ Returns the [SceneState] representing the scene file contents.
</description>
</method>
<method name="instantiate" qualifiers="const">
diff --git a/doc/classes/PacketPeer.xml b/doc/classes/PacketPeer.xml
index 85917efe25..9a2a23c9b7 100644
--- a/doc/classes/PacketPeer.xml
+++ b/doc/classes/PacketPeer.xml
@@ -57,7 +57,7 @@
<members>
<member name="encode_buffer_max_size" type="int" setter="set_encode_buffer_max_size" getter="get_encode_buffer_max_size" default="8388608">
Maximum buffer size allowed when encoding [Variant]s. Raise this value to support heavier memory allocations.
- The [method put_var] method allocates memory on the stack, and the buffer used will grow automatically to the closest power of two to match the size of the [Variant]. If the [Variant] is bigger than [code]encode_buffer_max_size[/code], the method will error out with [constant ERR_OUT_OF_MEMORY].
+ The [method put_var] method allocates memory on the stack, and the buffer used will grow automatically to the closest power of two to match the size of the [Variant]. If the [Variant] is bigger than [member encode_buffer_max_size], the method will error out with [constant ERR_OUT_OF_MEMORY].
</member>
</members>
</class>
diff --git a/doc/classes/ParticleProcessMaterial.xml b/doc/classes/ParticleProcessMaterial.xml
index 355fec3713..0f6cc3b29e 100644
--- a/doc/classes/ParticleProcessMaterial.xml
+++ b/doc/classes/ParticleProcessMaterial.xml
@@ -152,7 +152,7 @@
Unit vector specifying the particles' emission direction.
</member>
<member name="emission_box_extents" type="Vector3" setter="set_emission_box_extents" getter="get_emission_box_extents">
- The box's extents if [code]emission_shape[/code] is set to [constant EMISSION_SHAPE_BOX].
+ The box's extents if [member emission_shape] is set to [constant EMISSION_SHAPE_BOX].
</member>
<member name="emission_color_texture" type="Texture2D" setter="set_emission_color_texture" getter="get_emission_color_texture">
Particle color will be modulated by color determined by sampling this texture at the same point as the [member emission_point_texture].
@@ -162,7 +162,7 @@
Particle velocity and rotation will be set by sampling this texture at the same point as the [member emission_point_texture]. Used only in [constant EMISSION_SHAPE_DIRECTED_POINTS]. Can be created automatically from mesh or node by selecting "Create Emission Points from Mesh/Node" under the "Particles" tool in the toolbar.
</member>
<member name="emission_point_count" type="int" setter="set_emission_point_count" getter="get_emission_point_count">
- The number of emission points if [code]emission_shape[/code] is set to [constant EMISSION_SHAPE_POINTS] or [constant EMISSION_SHAPE_DIRECTED_POINTS].
+ The number of emission points if [member emission_shape] is set to [constant EMISSION_SHAPE_POINTS] or [constant EMISSION_SHAPE_DIRECTED_POINTS].
</member>
<member name="emission_point_texture" type="Texture2D" setter="set_emission_point_texture" getter="get_emission_point_texture">
Particles will be emitted at positions determined by sampling this texture at a random position. Used with [constant EMISSION_SHAPE_POINTS] and [constant EMISSION_SHAPE_DIRECTED_POINTS]. Can be created automatically from mesh or node by selecting "Create Emission Points from Mesh/Node" under the "Particles" tool in the toolbar.
@@ -183,7 +183,7 @@
Particles will be emitted inside this region. Use [enum EmissionShape] constants for values.
</member>
<member name="emission_sphere_radius" type="float" setter="set_emission_sphere_radius" getter="get_emission_sphere_radius">
- The sphere's radius if [code]emission_shape[/code] is set to [constant EMISSION_SHAPE_SPHERE].
+ The sphere's radius if [member emission_shape] is set to [constant EMISSION_SHAPE_SPHERE].
</member>
<member name="flatness" type="float" setter="set_flatness" getter="get_flatness" default="0.0">
Amount of [member spread] along the Y axis.
diff --git a/doc/classes/PhysicsServer2D.xml b/doc/classes/PhysicsServer2D.xml
index ae25af1dd9..8be92edbad 100644
--- a/doc/classes/PhysicsServer2D.xml
+++ b/doc/classes/PhysicsServer2D.xml
@@ -118,7 +118,7 @@
<return type="RID" />
<param index="0" name="area" type="RID" />
<description>
- Returns the [RID] of the space assigned to the area. Returns [code]RID()[/code] if no space is assigned.
+ Returns the [RID] of the space assigned to the area. Returns an empty [RID] if no space is assigned.
</description>
</method>
<method name="area_get_transform" qualifiers="const">
@@ -486,7 +486,7 @@
<return type="RID" />
<param index="0" name="body" type="RID" />
<description>
- Returns the [RID] of the space assigned to the body. Returns [code]RID()[/code] if no space is assigned.
+ Returns the [RID] of the space assigned to the body. Returns an empty [RID] if no space is assigned.
</description>
</method>
<method name="body_get_state" qualifiers="const">
@@ -827,7 +827,7 @@
<param index="2" name="body_a" type="RID" />
<param index="3" name="body_b" type="RID" default="RID()" />
<description>
- Makes the joint a pin joint. If [param body_b] is [code]RID()[/code], then [param body_a] is pinned to the point [param anchor] (given in global coordinates); otherwise, [param body_a] is pinned to [param body_b] at the point [param anchor] (given in global coordinates). To set the parameters which are specific to the pin joint, see [method pin_joint_set_param].
+ Makes the joint a pin joint. If [param body_b] is an empty [RID], then [param body_a] is pinned to the point [param anchor] (given in global coordinates); otherwise, [param body_a] is pinned to [param body_b] at the point [param anchor] (given in global coordinates). To set the parameters which are specific to the pin joint, see [method pin_joint_set_param].
</description>
</method>
<method name="joint_set_param">
@@ -918,13 +918,13 @@
<param index="1" name="data" type="Variant" />
<description>
Sets the shape data that defines the configuration of the shape. The [param data] to be passed depends on the shape's type (see [method shape_get_type]):
- - [constant SHAPE_WORLD_BOUNDARY]: an array of length two containing a [Vector2] [code]normal[/code] direction and a [code]float[/code] distance [code]d[/code],
- - [constant SHAPE_SEPARATION_RAY]: a dictionary containing the key [code]length[/code] with a [code]float[/code] value and the key [code]slide_on_slope[/code] with a [code]bool[/code] value,
+ - [constant SHAPE_WORLD_BOUNDARY]: an array of length two containing a [Vector2] [code]normal[/code] direction and a [float] distance [code]d[/code],
+ - [constant SHAPE_SEPARATION_RAY]: a dictionary containing the key [code]length[/code] with a [float] value and the key [code]slide_on_slope[/code] with a [bool] value,
- [constant SHAPE_SEGMENT]: a [Rect2] [code]rect[/code] containing the first point of the segment in [code]rect.position[/code] and the second point of the segment in [code]rect.size[/code],
- - [constant SHAPE_CIRCLE]: a [code]float[/code] [code]radius[/code],
+ - [constant SHAPE_CIRCLE]: a [float] [code]radius[/code],
- [constant SHAPE_RECTANGLE]: a [Vector2] [code]half_extents[/code],
- - [constant SHAPE_CAPSULE]: an array of length two (or a [Vector2]) containing a [code]float[/code] [code]height[/code] and a [code]float[/code] [code]radius[/code],
- - [constant SHAPE_CONVEX_POLYGON]: either a [PackedVector2Array] of points defining a convex polygon in counterclockwise order (the clockwise outward normal of each segment formed by consecutive points is calculated internally), or a [PackedFloat32Array] of length divisible by four so that every 4-tuple of [code]float[/code]s contains the coordinates of a point followed by the coordinates of the clockwise outward normal vector to the segment between the current point and the next point,
+ - [constant SHAPE_CAPSULE]: an array of length two (or a [Vector2]) containing a [float] [code]height[/code] and a [float] [code]radius[/code],
+ - [constant SHAPE_CONVEX_POLYGON]: either a [PackedVector2Array] of points defining a convex polygon in counterclockwise order (the clockwise outward normal of each segment formed by consecutive points is calculated internally), or a [PackedFloat32Array] of length divisible by four so that every 4-tuple of [float]s contains the coordinates of a point followed by the coordinates of the clockwise outward normal vector to the segment between the current point and the next point,
- [constant SHAPE_CONCAVE_POLYGON]: a [PackedVector2Array] of length divisible by two (each pair of points forms one segment).
[b]Warning:[/b] In the case of [constant SHAPE_CONVEX_POLYGON], this method does not check if the points supplied actually form a convex polygon (unlike the [member CollisionPolygon2D.polygon] property).
</description>
diff --git a/doc/classes/PhysicsServer3D.xml b/doc/classes/PhysicsServer3D.xml
index f3441f45ed..32810b0daf 100644
--- a/doc/classes/PhysicsServer3D.xml
+++ b/doc/classes/PhysicsServer3D.xml
@@ -582,10 +582,9 @@
<param index="1" name="callable" type="Callable" />
<param index="2" name="userdata" type="Variant" default="null" />
<description>
- Sets the function used to calculate physics for an object, if that object allows it (see [method body_set_omit_force_integration]).
- The force integration function takes 2 arguments:
- [code]state:[/code] [PhysicsDirectBodyState3D] used to retrieve and modify the body's state.
- [code]userdata:[/code] Optional user data, if it was passed when calling [code]body_set_force_integration_callback[/code].
+ Sets the function used to calculate physics for an object, if that object allows it (see [method body_set_omit_force_integration]). The force integration function takes 2 arguments:
+ - [code]state[/code] — [PhysicsDirectBodyState3D] used to retrieve and modify the body's state.
+ - [code skip-lint]userdata[/code] — optional user data passed to [method body_set_force_integration_callback].
</description>
</method>
<method name="body_set_max_contacts_reported">
diff --git a/doc/classes/Plane.xml b/doc/classes/Plane.xml
index 6faf83c0d6..a0da82d1d6 100644
--- a/doc/classes/Plane.xml
+++ b/doc/classes/Plane.xml
@@ -152,11 +152,11 @@
<members>
<member name="d" type="float" setter="" getter="" default="0.0">
The distance from the origin to the plane, expressed in terms of [member normal] (according to its direction and magnitude). Actual absolute distance from the origin to the plane can be calculated as [code]abs(d) / normal.length()[/code] (if [member normal] has zero length then this [Plane] does not represent a valid plane).
- In the scalar equation of the plane [code]ax + by + cz = d[/code], this is [code]d[/code], while the [code](a, b, c)[/code] coordinates are represented by the [member normal] property.
+ In the scalar equation of the plane [code]ax + by + cz = d[/code], this is [code skip-lint]d[/code], while the [code](a, b, c)[/code] coordinates are represented by the [member normal] property.
</member>
<member name="normal" type="Vector3" setter="" getter="" default="Vector3(0, 0, 0)">
The normal of the plane, typically a unit vector. Shouldn't be a zero vector as [Plane] with such [member normal] does not represent a valid plane.
- In the scalar equation of the plane [code]ax + by + cz = d[/code], this is the vector [code](a, b, c)[/code], where [code]d[/code] is the [member d] property.
+ In the scalar equation of the plane [code]ax + by + cz = d[/code], this is the vector [code](a, b, c)[/code], where [code skip-lint]d[/code] is the [member d] property.
</member>
<member name="x" type="float" setter="" getter="" default="0.0">
The X component of the plane's [member normal] vector.
diff --git a/doc/classes/Polygon2D.xml b/doc/classes/Polygon2D.xml
index 73c3c022b7..25adc57990 100644
--- a/doc/classes/Polygon2D.xml
+++ b/doc/classes/Polygon2D.xml
@@ -75,7 +75,7 @@
Internal list of [Bone2D] nodes used by the assigned [member skeleton]. Edited using the Polygon2D editor ("UV" button on the top toolbar).
</member>
<member name="color" type="Color" setter="set_color" getter="get_color" default="Color(1, 1, 1, 1)">
- The polygon's fill color. If [code]texture[/code] is defined, it will be multiplied by this color. It will also be the default color for vertices not set in [code]vertex_colors[/code].
+ The polygon's fill color. If [member texture] is set, it will be multiplied by this color. It will also be the default color for vertices not set in [member vertex_colors].
</member>
<member name="internal_vertex_count" type="int" setter="set_internal_vertex_count" getter="get_internal_vertex_count" default="0">
Number of internal vertices, used for UV mapping.
@@ -100,22 +100,22 @@
Path to a [Skeleton2D] node used for skeleton-based deformations of this polygon. If empty or invalid, skeletal deformations will not be used.
</member>
<member name="texture" type="Texture2D" setter="set_texture" getter="get_texture">
- The polygon's fill texture. Use [code]uv[/code] to set texture coordinates.
+ The polygon's fill texture. Use [member uv] to set texture coordinates.
</member>
<member name="texture_offset" type="Vector2" setter="set_texture_offset" getter="get_texture_offset" default="Vector2(0, 0)">
- Amount to offset the polygon's [code]texture[/code]. If [code](0, 0)[/code] the texture's origin (its top-left corner) will be placed at the polygon's [code]position[/code].
+ Amount to offset the polygon's [member texture]. If set to [code]Vector2(0, 0)[/code], the texture's origin (its top-left corner) will be placed at the polygon's position.
</member>
<member name="texture_rotation" type="float" setter="set_texture_rotation" getter="get_texture_rotation" default="0.0">
The texture's rotation in radians.
</member>
<member name="texture_scale" type="Vector2" setter="set_texture_scale" getter="get_texture_scale" default="Vector2(1, 1)">
- Amount to multiply the [code]uv[/code] coordinates when using a [code]texture[/code]. Larger values make the texture smaller, and vice versa.
+ Amount to multiply the [member uv] coordinates when using [member texture]. Larger values make the texture smaller, and vice versa.
</member>
<member name="uv" type="PackedVector2Array" setter="set_uv" getter="get_uv" default="PackedVector2Array()">
- Texture coordinates for each vertex of the polygon. There should be one [code]uv[/code] per polygon vertex. If there are fewer, undefined vertices will use [code](0, 0)[/code].
+ Texture coordinates for each vertex of the polygon. There should be one UV value per polygon vertex. If there are fewer, undefined vertices will use [code]Vector2(0, 0)[/code].
</member>
<member name="vertex_colors" type="PackedColorArray" setter="set_vertex_colors" getter="get_vertex_colors" default="PackedColorArray()">
- Color for each vertex. Colors are interpolated between vertices, resulting in smooth gradients. There should be one per polygon vertex. If there are fewer, undefined vertices will use [code]color[/code].
+ Color for each vertex. Colors are interpolated between vertices, resulting in smooth gradients. There should be one per polygon vertex. If there are fewer, undefined vertices will use [member color].
</member>
</members>
</class>
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 1157836779..b49de068fd 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -2366,6 +2366,13 @@
<member name="rendering/gl_compatibility/driver.windows" type="String" setter="" getter="" default="&quot;opengl3&quot;">
Windows override for [member rendering/gl_compatibility/driver].
</member>
+ <member name="rendering/gl_compatibility/fallback_to_angle" type="bool" setter="" getter="" default="true">
+ If [code]true[/code], the compatibility renderer will fall back to ANGLE if native OpenGL is not supported or the device is listed in [member rendering/gl_compatibility/force_angle_on_devices].
+ </member>
+ <member name="rendering/gl_compatibility/force_angle_on_devices" type="Array" setter="" getter="" default="[]">
+ An [Array] of devices which should always use the ANGLE renderer.
+ Each entry is a [Dictionary] with the following keys: [code]vendor[/code] and [code]name[/code]. [code]name[/code] can be set to [code]*[/code] to add all devices with the specified [code]vendor[/code].
+ </member>
<member name="rendering/gl_compatibility/item_buffer_size" type="int" setter="" getter="" default="16384">
Maximum number of canvas items commands that can be drawn in a single viewport update. If more render commands are issued they will be ignored. Decreasing this limit may improve performance on bandwidth limited devices. Increase this limit if you find that not all objects are being drawn in a frame.
</member>
@@ -2718,11 +2725,17 @@
<member name="xr/openxr/environment_blend_mode" type="int" setter="" getter="" default="&quot;0&quot;">
Specify how OpenXR should blend in the environment. This is specific to certain AR and passthrough devices where camera images are blended in by the XR compositor.
</member>
+ <member name="xr/openxr/extensions/eye_gaze_interaction" type="bool" setter="" getter="" default="false">
+ Specify whether to enable eye tracking for this project. Depending on the platform, additional export configuration may be needed.
+ </member>
+ <member name="xr/openxr/extensions/hand_tracking" type="bool" setter="" getter="" default="true">
+ If true we enable the hand tracking extension if available.
+ </member>
<member name="xr/openxr/form_factor" type="int" setter="" getter="" default="&quot;0&quot;">
Specify whether OpenXR should be configured for an HMD or a hand held device.
</member>
<member name="xr/openxr/foveation_dynamic" type="bool" setter="" getter="" default="false">
- If true and foveation is supported, will automatically adjust foveation level based on framerate up to the level set on [code]xr/openxr/foveation_level[/code].
+ If true and foveation is supported, will automatically adjust foveation level based on framerate up to the level set on [member xr/openxr/foveation_level].
</member>
<member name="xr/openxr/foveation_level" type="int" setter="" getter="" default="&quot;0&quot;">
Applied foveation level if supported: 0 = off, 1 = low, 2 = medium, 3 = high.
diff --git a/doc/classes/Quaternion.xml b/doc/classes/Quaternion.xml
index 0499d51d79..74dfae77fc 100644
--- a/doc/classes/Quaternion.xml
+++ b/doc/classes/Quaternion.xml
@@ -191,7 +191,7 @@
<param index="6" name="post_b_t" type="float" />
<description>
Performs a spherical cubic interpolation between quaternions [param pre_a], this vector, [param b], and [param post_b], by the given amount [param weight].
- It can perform smoother interpolation than [code]spherical_cubic_interpolate()[/code] by the time values.
+ It can perform smoother interpolation than [method spherical_cubic_interpolate] by the time values.
</description>
</method>
</methods>
diff --git a/doc/classes/RichTextEffect.xml b/doc/classes/RichTextEffect.xml
index 0f429c31fa..ca95557f1b 100644
--- a/doc/classes/RichTextEffect.xml
+++ b/doc/classes/RichTextEffect.xml
@@ -7,11 +7,11 @@
A custom effect for a [RichTextLabel].
[b]Note:[/b] For a [RichTextEffect] to be usable, a BBCode tag must be defined as a member variable called [code]bbcode[/code] in the script.
[codeblocks]
- [gdscript]
+ [gdscript skip-lint]
# The RichTextEffect will be usable like this: `[example]Some text[/example]`
var bbcode = "example"
[/gdscript]
- [csharp]
+ [csharp skip-lint]
// The RichTextEffect will be usable like this: `[example]Some text[/example]`
string bbcode = "example";
[/csharp]
diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml
index 1e5b09516f..405d189c54 100644
--- a/doc/classes/RichTextLabel.xml
+++ b/doc/classes/RichTextLabel.xml
@@ -6,9 +6,9 @@
<description>
A control for displaying text that can contain custom fonts, images, and basic formatting. [RichTextLabel] manages these as an internal tag stack. It also adapts itself to given width/heights.
[b]Note:[/b] Assignments to [member text] clear the tag stack and reconstruct it from the property's contents. Any edits made to [member text] will erase previous edits made from other manual sources such as [method append_text] and the [code]push_*[/code] / [method pop] methods.
- [b]Note:[/b] RichTextLabel doesn't support entangled BBCode tags. For example, instead of using [code][b]bold[i]bold italic[/b]italic[/i][/code], use [code][b]bold[i]bold italic[/i][/b][i]italic[/i][/code].
+ [b]Note:[/b] RichTextLabel doesn't support entangled BBCode tags. For example, instead of using [code skip-lint][b]bold[i]bold italic[/b]italic[/i][/code], use [code skip-lint][b]bold[i]bold italic[/i][/b][i]italic[/i][/code].
[b]Note:[/b] [code]push_*/pop_*[/code] functions won't affect BBCode.
- [b]Note:[/b] Unlike [Label], [RichTextLabel] doesn't have a [i]property[/i] to horizontally align text to the center. Instead, enable [member bbcode_enabled] and surround the text in a [code][center][/code] tag as follows: [code][center]Example[/center][/code]. There is currently no built-in way to vertically align text either, but this can be emulated by relying on anchors/containers and the [member fit_content] property.
+ [b]Note:[/b] Unlike [Label], [RichTextLabel] doesn't have a [i]property[/i] to horizontally align text to the center. Instead, enable [member bbcode_enabled] and surround the text in a [code skip-lint][center][/code] tag as follows: [code skip-lint][center]Example[/center][/code]. There is currently no built-in way to vertically align text either, but this can be emulated by relying on anchors/containers and the [member fit_content] property.
</description>
<tutorials>
<link title="BBCode in RichTextLabel">$DOCS_URL/tutorials/ui/bbcode_in_richtextlabel.html</link>
@@ -289,32 +289,32 @@
<return type="void" />
<param index="0" name="bgcolor" type="Color" />
<description>
- Adds a [code][bgcolor][/code] tag to the tag stack.
+ Adds a [code skip-lint][bgcolor][/code] tag to the tag stack.
</description>
</method>
<method name="push_bold">
<return type="void" />
<description>
- Adds a [code][font][/code] tag with a bold font to the tag stack. This is the same as adding a [code][b][/code] tag if not currently in a [code][i][/code] tag.
+ Adds a [code skip-lint][font][/code] tag with a bold font to the tag stack. This is the same as adding a [code skip-lint][b][/code] tag if not currently in a [code skip-lint][i][/code] tag.
</description>
</method>
<method name="push_bold_italics">
<return type="void" />
<description>
- Adds a [code][font][/code] tag with a bold italics font to the tag stack.
+ Adds a [code skip-lint][font][/code] tag with a bold italics font to the tag stack.
</description>
</method>
<method name="push_cell">
<return type="void" />
<description>
- Adds a [code][cell][/code] tag to the tag stack. Must be inside a [code][table][/code] tag. See [method push_table] for details.
+ Adds a [code skip-lint][cell][/code] tag to the tag stack. Must be inside a [code skip-lint][table][/code] tag. See [method push_table] for details.
</description>
</method>
<method name="push_color">
<return type="void" />
<param index="0" name="color" type="Color" />
<description>
- Adds a [code][color][/code] tag to the tag stack.
+ Adds a [code skip-lint][color][/code] tag to the tag stack.
</description>
</method>
<method name="push_context">
@@ -341,14 +341,14 @@
<param index="5" name="outline_size" type="int" default="0" />
<param index="6" name="outline_color" type="Color" default="Color(0, 0, 0, 0)" />
<description>
- Adds a [code][dropcap][/code] tag to the tag stack. Drop cap (dropped capital) is a decorative element at the beginning of a paragraph that is larger than the rest of the text.
+ Adds a [code skip-lint][dropcap][/code] tag to the tag stack. Drop cap (dropped capital) is a decorative element at the beginning of a paragraph that is larger than the rest of the text.
</description>
</method>
<method name="push_fgcolor">
<return type="void" />
<param index="0" name="fgcolor" type="Color" />
<description>
- Adds a [code][fgcolor][/code] tag to the tag stack.
+ Adds a [code skip-lint][fgcolor][/code] tag to the tag stack.
</description>
</method>
<method name="push_font">
@@ -356,7 +356,7 @@
<param index="0" name="font" type="Font" />
<param index="1" name="font_size" type="int" default="0" />
<description>
- Adds a [code][font][/code] tag to the tag stack. Overrides default fonts for its duration.
+ Adds a [code skip-lint][font][/code] tag to the tag stack. Overrides default fonts for its duration.
Passing [code]0[/code] to [param font_size] will use the existing default font size.
</description>
</method>
@@ -364,27 +364,27 @@
<return type="void" />
<param index="0" name="font_size" type="int" />
<description>
- Adds a [code][font_size][/code] tag to the tag stack. Overrides default font size for its duration.
+ Adds a [code skip-lint][font_size][/code] tag to the tag stack. Overrides default font size for its duration.
</description>
</method>
<method name="push_hint">
<return type="void" />
<param index="0" name="description" type="String" />
<description>
- Adds a [code][hint][/code] tag to the tag stack. Same as BBCode [code][hint=something]{text}[/hint][/code].
+ Adds a [code skip-lint][hint][/code] tag to the tag stack. Same as BBCode [code skip-lint][hint=something]{text}[/hint][/code].
</description>
</method>
<method name="push_indent">
<return type="void" />
<param index="0" name="level" type="int" />
<description>
- Adds an [code][indent][/code] tag to the tag stack. Multiplies [param level] by current [member tab_size] to determine new margin length.
+ Adds an [code skip-lint][indent][/code] tag to the tag stack. Multiplies [param level] by current [member tab_size] to determine new margin length.
</description>
</method>
<method name="push_italics">
<return type="void" />
<description>
- Adds a [code][font][/code] tag with an italics font to the tag stack. This is the same as adding an [code][i][/code] tag if not currently in a [code][b][/code] tag.
+ Adds a [code skip-lint][font][/code] tag with an italics font to the tag stack. This is the same as adding an [code skip-lint][i][/code] tag if not currently in a [code skip-lint][b][/code] tag.
</description>
</method>
<method name="push_language">
@@ -401,40 +401,40 @@
<param index="2" name="capitalize" type="bool" />
<param index="3" name="bullet" type="String" default="&quot;•&quot;" />
<description>
- Adds [code][ol][/code] or [code][ul][/code] tag to the tag stack. Multiplies [param level] by current [member tab_size] to determine new margin length.
+ Adds [code skip-lint][ol][/code] or [code skip-lint][ul][/code] tag to the tag stack. Multiplies [param level] by current [member tab_size] to determine new margin length.
</description>
</method>
<method name="push_meta">
<return type="void" />
<param index="0" name="data" type="Variant" />
<description>
- Adds a meta tag to the tag stack. Similar to the BBCode [code][url=something]{text}[/url][/code], but supports non-[String] metadata types.
+ Adds a meta tag to the tag stack. Similar to the BBCode [code skip-lint][url=something]{text}[/url][/code], but supports non-[String] metadata types.
</description>
</method>
<method name="push_mono">
<return type="void" />
<description>
- Adds a [code][font][/code] tag with a monospace font to the tag stack.
+ Adds a [code skip-lint][font][/code] tag with a monospace font to the tag stack.
</description>
</method>
<method name="push_normal">
<return type="void" />
<description>
- Adds a [code][font][/code] tag with a normal font to the tag stack.
+ Adds a [code skip-lint][font][/code] tag with a normal font to the tag stack.
</description>
</method>
<method name="push_outline_color">
<return type="void" />
<param index="0" name="color" type="Color" />
<description>
- Adds a [code][outline_color][/code] tag to the tag stack. Adds text outline for its duration.
+ Adds a [code skip-lint][outline_color][/code] tag to the tag stack. Adds text outline for its duration.
</description>
</method>
<method name="push_outline_size">
<return type="void" />
<param index="0" name="outline_size" type="int" />
<description>
- Adds a [code][outline_size][/code] tag to the tag stack. Overrides default text outline size for its duration.
+ Adds a [code skip-lint][outline_size][/code] tag to the tag stack. Overrides default text outline size for its duration.
</description>
</method>
<method name="push_paragraph">
@@ -446,13 +446,13 @@
<param index="4" name="justification_flags" type="int" enum="TextServer.JustificationFlag" is_bitfield="true" default="163" />
<param index="5" name="tab_stops" type="PackedFloat32Array" default="PackedFloat32Array()" />
<description>
- Adds a [code][p][/code] tag to the tag stack.
+ Adds a [code skip-lint][p][/code] tag to the tag stack.
</description>
</method>
<method name="push_strikethrough">
<return type="void" />
<description>
- Adds a [code][s][/code] tag to the tag stack.
+ Adds a [code skip-lint][s][/code] tag to the tag stack.
</description>
</method>
<method name="push_table">
@@ -461,13 +461,13 @@
<param index="1" name="inline_align" type="int" enum="InlineAlignment" default="0" />
<param index="2" name="align_to_row" type="int" default="-1" />
<description>
- Adds a [code][table=columns,inline_align][/code] tag to the tag stack.
+ Adds a [code skip-lint][table=columns,inline_align][/code] tag to the tag stack.
</description>
</method>
<method name="push_underline">
<return type="void" />
<description>
- Adds a [code][u][/code] tag to the tag stack.
+ Adds a [code skip-lint][u][/code] tag to the tag stack.
</description>
</method>
<method name="remove_paragraph">
@@ -589,13 +589,13 @@
If [code]true[/code], the label's minimum size will be automatically updated to fit its content, matching the behavior of [Label].
</member>
<member name="hint_underlined" type="bool" setter="set_hint_underline" getter="is_hint_underlined" default="true">
- If [code]true[/code], the label underlines hint tags such as [code][hint=description]{text}[/hint][/code].
+ If [code]true[/code], the label underlines hint tags such as [code skip-lint][hint=description]{text}[/hint][/code].
</member>
<member name="language" type="String" setter="set_language" getter="get_language" default="&quot;&quot;">
Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead.
</member>
<member name="meta_underlined" type="bool" setter="set_meta_underline" getter="is_meta_underlined" default="true">
- If [code]true[/code], the label underlines meta tags such as [code][url]{text}[/url][/code].
+ If [code]true[/code], the label underlines meta tags such as [code skip-lint][url]{text}[/url][/code].
</member>
<member name="progress_bar_delay" type="int" setter="set_progress_bar_delay" getter="get_progress_bar_delay" default="1000">
The delay after which the loading progress bar is displayed, in milliseconds. Set to [code]-1[/code] to disable progress bar entirely.
@@ -624,7 +624,7 @@
</member>
<member name="text" type="String" setter="set_text" getter="get_text" default="&quot;&quot;">
The label's text in BBCode format. Is not representative of manual modifications to the internal tag stack. Erases changes made by other methods when edited.
- [b]Note:[/b] If [member bbcode_enabled] is [code]true[/code], it is unadvised to use the [code]+=[/code] operator with [code]text[/code] (e.g. [code]text += "some string"[/code]) as it replaces the whole text and can cause slowdowns. It will also erase all BBCode that was added to stack using [code]push_*[/code] methods. Use [method append_text] for adding text instead, unless you absolutely need to close a tag that was opened in an earlier method call.
+ [b]Note:[/b] If [member bbcode_enabled] is [code]true[/code], it is unadvised to use the [code]+=[/code] operator with [member text] (e.g. [code]text += "some string"[/code]) as it replaces the whole text and can cause slowdowns. It will also erase all BBCode that was added to stack using [code]push_*[/code] methods. Use [method append_text] for adding text instead, unless you absolutely need to close a tag that was opened in an earlier method call.
</member>
<member name="text_direction" type="int" setter="set_text_direction" getter="get_text_direction" enum="Control.TextDirection" default="0">
Base text writing direction.
@@ -653,7 +653,7 @@
<signal name="meta_clicked">
<param index="0" name="meta" type="Variant" />
<description>
- Triggered when the user clicks on content between meta tags. If the meta is defined in text, e.g. [code][url={"data"="hi"}]hi[/url][/code], then the parameter for this signal will be a [String] type. If a particular type or an object is desired, the [method push_meta] method must be used to manually insert the data into the tag stack.
+ Triggered when the user clicks on content between meta tags. If the meta is defined in text, e.g. [code skip-lint][url={"data"="hi"}]hi[/url][/code], then the parameter for this signal will be a [String] type. If a particular type or an object is desired, the [method push_meta] method must be used to manually insert the data into the tag stack.
</description>
</signal>
<signal name="meta_hover_ended">
@@ -800,7 +800,7 @@
The default text font size.
</theme_item>
<theme_item name="focus" data_type="style" type="StyleBox">
- The background used when the [RichTextLabel] is focused. The [code]focus[/code] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
+ The background used when the [RichTextLabel] is focused. The [theme_item focus] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
</theme_item>
<theme_item name="normal" data_type="style" type="StyleBox">
The normal background for the [RichTextLabel].
diff --git a/doc/classes/RigidBody3D.xml b/doc/classes/RigidBody3D.xml
index 69cfd2bb07..c507a7c39a 100644
--- a/doc/classes/RigidBody3D.xml
+++ b/doc/classes/RigidBody3D.xml
@@ -182,7 +182,7 @@
</member>
<member name="inertia" type="Vector3" setter="set_inertia" getter="get_inertia" default="Vector3(0, 0, 0)">
The body's moment of inertia. This is like mass, but for rotation: it determines how much torque it takes to rotate the body on each axis. The moment of inertia is usually computed automatically from the mass and the shapes, but this property allows you to set a custom value.
- If set to [code]Vector3.ZERO[/code], inertia is automatically computed (default value).
+ If set to [constant Vector3.ZERO], inertia is automatically computed (default value).
[b]Note:[/b] This value does not change when inertia is automatically computed. Use [PhysicsServer3D] to get the computed inertia.
[codeblocks]
[gdscript]
diff --git a/doc/classes/SeparationRayShape2D.xml b/doc/classes/SeparationRayShape2D.xml
index 6237584056..9c805d4ede 100644
--- a/doc/classes/SeparationRayShape2D.xml
+++ b/doc/classes/SeparationRayShape2D.xml
@@ -4,7 +4,7 @@
A 2D ray shape used for physics collision that tries to separate itself from any collider.
</brief_description>
<description>
- A 2D ray shape, intended for use in physics. Usually used to provide a shape for a [CollisionShape2D]. When a [SeparationRayShape2D] collides with an object, it tries to separate itself from it by moving its endpoint to the collision point. It can for example be used for spears falling from the sky.
+ A 2D ray shape, intended for use in physics. Usually used to provide a shape for a [CollisionShape2D]. When a [SeparationRayShape2D] collides with an object, it tries to separate itself from it by moving its endpoint to the collision point. For example, a [SeparationRayShape2D] next to a character can allow it to instantly move up when touching stairs.
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/SeparationRayShape3D.xml b/doc/classes/SeparationRayShape3D.xml
index aaf8791224..afb030ad0e 100644
--- a/doc/classes/SeparationRayShape3D.xml
+++ b/doc/classes/SeparationRayShape3D.xml
@@ -4,7 +4,7 @@
A 3D ray shape used for physics collision that tries to separate itself from any collider.
</brief_description>
<description>
- A 3D ray shape, intended for use in physics. Usually used to provide a shape for a [CollisionShape3D]. When a [SeparationRayShape3D] collides with an object, it tries to separate itself from it by moving its endpoint to the collision point. It can for example be used for spears falling from the sky.
+ A 3D ray shape, intended for use in physics. Usually used to provide a shape for a [CollisionShape3D]. When a [SeparationRayShape3D] collides with an object, it tries to separate itself from it by moving its endpoint to the collision point. For example, a [SeparationRayShape3D] next to a character can allow it to instantly move up when touching stairs.
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/Slider.xml b/doc/classes/Slider.xml
index efb646b7ae..0471b8c275 100644
--- a/doc/classes/Slider.xml
+++ b/doc/classes/Slider.xml
@@ -42,7 +42,7 @@
Boolean constant. If [code]1[/code], the grabber texture size will be ignored and it will fit within slider's bounds based only on its center position.
</theme_item>
<theme_item name="grabber_offset" data_type="constant" type="int" default="0">
- Vertical/horizontal offset of the grabber.
+ Vertical or horizontal offset of the grabber.
</theme_item>
<theme_item name="grabber" data_type="icon" type="Texture2D">
The texture for the grabber (the draggable element).
@@ -57,13 +57,13 @@
The texture for the ticks, visible when [member Slider.tick_count] is greater than 0.
</theme_item>
<theme_item name="grabber_area" data_type="style" type="StyleBox">
- The background of the area to the left/bottom of the grabber.
+ The background of the area to the left or bottom of the grabber.
</theme_item>
<theme_item name="grabber_area_highlight" data_type="style" type="StyleBox">
- The background of the area to the left/bottom of the grabber that displays when it's being hovered or focused.
+ The background of the area to the left or bottom of the grabber that displays when it's being hovered or focused.
</theme_item>
<theme_item name="slider" data_type="style" type="StyleBox">
- The background for the whole slider. Determines the height/width of the [code]grabber_area[/code].
+ The background for the whole slider. Affects the height or width of the [theme_item grabber_area].
</theme_item>
</theme_items>
</class>
diff --git a/doc/classes/SpinBox.xml b/doc/classes/SpinBox.xml
index 217d0947db..0671bd5967 100644
--- a/doc/classes/SpinBox.xml
+++ b/doc/classes/SpinBox.xml
@@ -49,13 +49,13 @@
Changes the alignment of the underlying [LineEdit].
</member>
<member name="custom_arrow_step" type="float" setter="set_custom_arrow_step" getter="get_custom_arrow_step" default="0.0">
- If not [code]0[/code], [code]value[/code] will always be rounded to a multiple of [code]custom_arrow_step[/code] when interacting with the arrow buttons of the [SpinBox].
+ If not [code]0[/code], [member Range.value] will always be rounded to a multiple of [member custom_arrow_step] when interacting with the arrow buttons of the [SpinBox].
</member>
<member name="editable" type="bool" setter="set_editable" getter="is_editable" default="true">
If [code]true[/code], the [SpinBox] will be editable. Otherwise, it will be read only.
</member>
<member name="prefix" type="String" setter="set_prefix" getter="get_prefix" default="&quot;&quot;">
- Adds the specified [code]prefix[/code] string before the numerical value of the [SpinBox].
+ Adds the specified prefix string before the numerical value of the [SpinBox].
</member>
<member name="select_all_on_focus" type="bool" setter="set_select_all_on_focus" getter="is_select_all_on_focus" default="false">
If [code]true[/code], the [SpinBox] will select the whole text when the [LineEdit] gains focus. Clicking the up and down arrows won't trigger this behavior.
@@ -63,7 +63,7 @@
<member name="size_flags_vertical" type="int" setter="set_v_size_flags" getter="get_v_size_flags" overrides="Control" enum="Control.SizeFlags" is_bitfield="true" default="1" />
<member name="step" type="float" setter="set_step" getter="get_step" overrides="Range" default="1.0" />
<member name="suffix" type="String" setter="set_suffix" getter="get_suffix" default="&quot;&quot;">
- Adds the specified [code]suffix[/code] string after the numerical value of the [SpinBox].
+ Adds the specified suffix string after the numerical value of the [SpinBox].
</member>
<member name="update_on_text_changed" type="bool" setter="set_update_on_text_changed" getter="get_update_on_text_changed" default="false">
Sets the value of the [Range] for this [SpinBox] when the [LineEdit] text is [i]changed[/i] instead of [i]submitted[/i]. See [signal LineEdit.text_changed] and [signal LineEdit.text_submitted].
diff --git a/doc/classes/SubViewportContainer.xml b/doc/classes/SubViewportContainer.xml
index 08e7ca23f7..e7d7883c78 100644
--- a/doc/classes/SubViewportContainer.xml
+++ b/doc/classes/SubViewportContainer.xml
@@ -10,6 +10,15 @@
</description>
<tutorials>
</tutorials>
+ <methods>
+ <method name="_propagate_input_event" qualifiers="virtual const">
+ <return type="bool" />
+ <param index="0" name="event" type="InputEvent" />
+ <description>
+ Virtual method to be implemented by the user. If it returns [code]true[/code], the [param event] is propagated to [SubViewport] children. Propagation doesn't happen if it returns [code]false[/code]. If the function is not implemented, all events are propagated to SubViewports.
+ </description>
+ </method>
+ </methods>
<members>
<member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" overrides="Control" enum="Control.FocusMode" default="1" />
<member name="stretch" type="bool" setter="set_stretch" getter="is_stretch_enabled" default="false">
diff --git a/doc/classes/TLSOptions.xml b/doc/classes/TLSOptions.xml
index 125c730e27..aed100ec07 100644
--- a/doc/classes/TLSOptions.xml
+++ b/doc/classes/TLSOptions.xml
@@ -28,7 +28,7 @@
<param index="1" name="common_name_override" type="String" default="&quot;&quot;" />
<description>
Creates a TLS client configuration which validates certificates and their common names (fully qualified domain names).
- You can specify a custom [param trusted_chain] of certification authorities (the default CA list will be used if [code]null[/code]), and optionally provide a [param common_name_override] if you expect the certificate to have a common name other then the server FQDN.
+ You can specify a custom [param trusted_chain] of certification authorities (the default CA list will be used if [code]null[/code]), and optionally provide a [param common_name_override] if you expect the certificate to have a common name other than the server FQDN.
[b]Note:[/b] On the Web platform, TLS verification is always enforced against the CA list of the web browser. This is considered a security feature.
</description>
</method>
diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml
index d22e646dd0..4148c4eb09 100644
--- a/doc/classes/TextEdit.xml
+++ b/doc/classes/TextEdit.xml
@@ -1169,7 +1169,7 @@
Allow scrolling past the last line into "virtual" space.
</member>
<member name="scroll_smooth" type="bool" setter="set_smooth_scroll_enabled" getter="is_smooth_scroll_enabled" default="false">
- Scroll smoothly over the text rather then jumping to the next location.
+ Scroll smoothly over the text rather than jumping to the next location.
</member>
<member name="scroll_v_scroll_speed" type="float" setter="set_v_scroll_speed" getter="get_v_scroll_speed" default="80.0">
Sets the scroll speed with the minimap or when [member scroll_smooth] is enabled.
@@ -1463,7 +1463,7 @@
Sets a custom [Texture2D] for tab text characters.
</theme_item>
<theme_item name="focus" data_type="style" type="StyleBox">
- Sets the [StyleBox] when in focus. The [code]focus[/code] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
+ Sets the [StyleBox] when in focus. The [theme_item focus] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
</theme_item>
<theme_item name="normal" data_type="style" type="StyleBox">
Sets the [StyleBox] of this [TextEdit].
diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml
index 3893ef8cbc..cfd6d3b281 100644
--- a/doc/classes/TextServer.xml
+++ b/doc/classes/TextServer.xml
@@ -15,6 +15,13 @@
Creates a new, empty font cache entry resource. To free the resulting resource, use the [method free_rid] method.
</description>
</method>
+ <method name="create_font_linked_variation">
+ <return type="RID" />
+ <param index="0" name="font_rid" type="RID" />
+ <description>
+ Creates a new variation existing font which is reusing the same glyph cache and font data. To free the resulting resource, use the [method free_rid] method.
+ </description>
+ </method>
<method name="create_shaped_text">
<return type="RID" />
<param index="0" name="direction" type="int" enum="TextServer.Direction" default="0" />
@@ -187,7 +194,7 @@
<param index="1" name="size" type="int" />
<param index="2" name="index" type="int" />
<description>
- Returns outline contours of the glyph as a [code]Dictionary[/code] with the following contents:
+ Returns outline contours of the glyph as a [Dictionary] with the following contents:
[code]points[/code] - [PackedVector3Array], containing outline points. [code]x[/code] and [code]y[/code] are point coordinates. [code]z[/code] is the type of the point, using the [enum ContourPointTag] values.
[code]contours[/code] - [PackedInt32Array], containing indices the end points of each contour.
[code]orientation[/code] - [bool], contour orientation. If [code]true[/code], clockwise contours must be filled.
@@ -375,7 +382,7 @@
<return type="Vector2i[]" />
<param index="0" name="font_rid" type="RID" />
<description>
- Returns list of the font sizes in the cache. Each size is [code]Vector2i[/code] with font size and outline size.
+ Returns list of the font sizes in the cache. Each size is [Vector2i] with font size and outline size.
</description>
</method>
<method name="font_get_spacing" qualifiers="const">
@@ -1991,8 +1998,7 @@
BiDi override for email.
</constant>
<constant name="STRUCTURED_TEXT_LIST" value="4" enum="StructuredTextParser">
- BiDi override for lists.
- Structured text options: list separator [code]String[/code].
+ BiDi override for lists. Structured text options: list separator [String].
</constant>
<constant name="STRUCTURED_TEXT_GDSCRIPT" value="5" enum="StructuredTextParser">
BiDi override for GDScript.
diff --git a/doc/classes/TextServerExtension.xml b/doc/classes/TextServerExtension.xml
index 82162e77be..f8e21ece35 100644
--- a/doc/classes/TextServerExtension.xml
+++ b/doc/classes/TextServerExtension.xml
@@ -19,6 +19,12 @@
<description>
</description>
</method>
+ <method name="_create_font_linked_variation" qualifiers="virtual">
+ <return type="RID" />
+ <param index="0" name="font_rid" type="RID" />
+ <description>
+ </description>
+ </method>
<method name="_create_shaped_text" qualifiers="virtual">
<return type="RID" />
<param index="0" name="direction" type="int" enum="TextServer.Direction" />
diff --git a/doc/classes/TextureProgressBar.xml b/doc/classes/TextureProgressBar.xml
index 23cacf481f..0c1e9adf71 100644
--- a/doc/classes/TextureProgressBar.xml
+++ b/doc/classes/TextureProgressBar.xml
@@ -71,13 +71,13 @@
[Texture2D] that draws under the progress bar. The bar's background.
</member>
<member name="tint_over" type="Color" setter="set_tint_over" getter="get_tint_over" default="Color(1, 1, 1, 1)">
- Multiplies the color of the bar's [code]texture_over[/code] texture. The effect is similar to [member CanvasItem.modulate], except it only affects this specific texture instead of the entire node.
+ Multiplies the color of the bar's [member texture_over] texture. The effect is similar to [member CanvasItem.modulate], except it only affects this specific texture instead of the entire node.
</member>
<member name="tint_progress" type="Color" setter="set_tint_progress" getter="get_tint_progress" default="Color(1, 1, 1, 1)">
- Multiplies the color of the bar's [code]texture_progress[/code] texture.
+ Multiplies the color of the bar's [member texture_progress] texture.
</member>
<member name="tint_under" type="Color" setter="set_tint_under" getter="get_tint_under" default="Color(1, 1, 1, 1)">
- Multiplies the color of the bar's [code]texture_under[/code] texture.
+ Multiplies the color of the bar's [member texture_under] texture.
</member>
</members>
<constants>
diff --git a/doc/classes/TileSet.xml b/doc/classes/TileSet.xml
index 8c2a690ec9..37f4cc5c0b 100644
--- a/doc/classes/TileSet.xml
+++ b/doc/classes/TileSet.xml
@@ -168,7 +168,7 @@
<method name="get_next_source_id" qualifiers="const">
<return type="int" />
<description>
- Returns a new unused source ID. This generated ID is the same that a call to [code]add_source[/code] would return.
+ Returns a new unused source ID. This generated ID is the same that a call to [method add_source] would return.
</description>
</method>
<method name="get_occlusion_layer_light_mask" qualifiers="const">
diff --git a/doc/classes/TileSetAtlasSource.xml b/doc/classes/TileSetAtlasSource.xml
index f478f37500..755c266cd9 100644
--- a/doc/classes/TileSetAtlasSource.xml
+++ b/doc/classes/TileSetAtlasSource.xml
@@ -39,7 +39,7 @@
<method name="get_atlas_grid_size" qualifiers="const">
<return type="Vector2i" />
<description>
- Returns the atlas grid size, which depends on how many tiles can fit in the texture. It thus depends on the Texture's size, the atlas [code]margins[/code] the tiles' [code]texture_region_size[/code].
+ Returns the atlas grid size, which depends on how many tiles can fit in the texture. It thus depends on the [member texture]'s size, the atlas [member margins], and the tiles' [member texture_region_size].
</description>
</method>
<method name="get_next_alternative_tile_id" qualifiers="const">
diff --git a/doc/classes/Time.xml b/doc/classes/Time.xml
index 8ed17f6d8a..9dc567562a 100644
--- a/doc/classes/Time.xml
+++ b/doc/classes/Time.xml
@@ -48,8 +48,8 @@
<param index="0" name="datetime" type="String" />
<param index="1" name="weekday" type="bool" />
<description>
- Converts the given ISO 8601 date and time string (YYYY-MM-DDTHH:MM:SS) to a dictionary of keys: [code]year[/code], [code]month[/code], [code]day[/code], [code]weekday[/code], [code]hour[/code], [code]minute[/code], and [code]second[/code].
- If [param weekday] is [code]false[/code], then the [code]weekday[/code] entry is excluded (the calculation is relatively expensive).
+ Converts the given ISO 8601 date and time string (YYYY-MM-DDTHH:MM:SS) to a dictionary of keys: [code]year[/code], [code]month[/code], [code]day[/code], [code skip-lint]weekday[/code], [code]hour[/code], [code]minute[/code], and [code]second[/code].
+ If [param weekday] is [code]false[/code], then the [code skip-lint]weekday[/code] entry is excluded (the calculation is relatively expensive).
[b]Note:[/b] Any decimal fraction in the time string will be ignored silently.
</description>
</method>
diff --git a/doc/classes/Transform2D.xml b/doc/classes/Transform2D.xml
index 6295412692..abb81b95cb 100644
--- a/doc/classes/Transform2D.xml
+++ b/doc/classes/Transform2D.xml
@@ -133,7 +133,7 @@
<return type="bool" />
<param index="0" name="xform" type="Transform2D" />
<description>
- Returns [code]true[/code] if this transform and [param xform] are approximately equal, by calling [code]is_equal_approx[/code] on each component.
+ Returns [code]true[/code] if this transform and [param xform] are approximately equal, by running [method @GlobalScope.is_equal_approx] on each component.
</description>
</method>
<method name="is_finite" qualifiers="const">
diff --git a/doc/classes/Transform3D.xml b/doc/classes/Transform3D.xml
index 7bca99d697..c96be5124e 100644
--- a/doc/classes/Transform3D.xml
+++ b/doc/classes/Transform3D.xml
@@ -80,7 +80,7 @@
<return type="bool" />
<param index="0" name="xform" type="Transform3D" />
<description>
- Returns [code]true[/code] if this transform and [param xform] are approximately equal, by calling [code]is_equal_approx[/code] on each component.
+ Returns [code]true[/code] if this transform and [param xform] are approximately equal, by running [method @GlobalScope.is_equal_approx] on each component.
</description>
</method>
<method name="is_finite" qualifiers="const">
diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml
index d85678cc81..e322e3adc0 100644
--- a/doc/classes/Tree.xml
+++ b/doc/classes/Tree.xml
@@ -458,7 +458,7 @@
<param index="1" name="column" type="int" />
<param index="2" name="selected" type="bool" />
<description>
- Emitted instead of [code]item_selected[/code] if [code]select_mode[/code] is [constant SELECT_MULTI].
+ Emitted instead of [signal item_selected] if [member select_mode] is set to [constant SELECT_MULTI].
</description>
</signal>
<signal name="nothing_selected">
diff --git a/doc/classes/Variant.xml b/doc/classes/Variant.xml
index dc3e25fa9b..2734800642 100644
--- a/doc/classes/Variant.xml
+++ b/doc/classes/Variant.xml
@@ -28,7 +28,7 @@
[/codeblocks]
Godot tracks all scripting API variables within Variants. Without even realizing it, you use Variants all the time. When a particular language enforces its own rules for keeping data typed, then that language is applying its own custom logic over the base Variant scripting API.
- GDScript automatically wrap values in them. It keeps all data in plain Variants by default and then optionally enforces custom static typing rules on variable types.
- - C# is statically typed, but uses its own implementation of the [code]Variant[/code] type in place of Godot's Variant class when it needs to represent a dynamic value. A [code]Variant[/code] can be assigned any compatible type implicitly but converting requires an explicit cast.
+ - C# is statically typed, but uses its own implementation of the Variant type in place of Godot's [Variant] class when it needs to represent a dynamic value. C# Variant can be assigned any compatible type implicitly but converting requires an explicit cast.
The global [method @GlobalScope.typeof] function returns the enumerated value of the Variant type stored in the current variable (see [enum Variant.Type]).
[codeblocks]
[gdscript]
diff --git a/doc/classes/Vector2.xml b/doc/classes/Vector2.xml
index b4718d96a3..fdaaf239f8 100644
--- a/doc/classes/Vector2.xml
+++ b/doc/classes/Vector2.xml
@@ -157,7 +157,7 @@
<param index="6" name="post_b_t" type="float" />
<description>
Performs a cubic interpolation between this vector and [param b] using [param pre_a] and [param post_b] as handles, and returns the result at position [param weight]. [param weight] is on the range of 0.0 to 1.0, representing the amount of interpolation.
- It can perform smoother interpolation than [code]cubic_interpolate()[/code] by the time values.
+ It can perform smoother interpolation than [method cubic_interpolate] by the time values.
</description>
</method>
<method name="direction_to" qualifiers="const">
diff --git a/doc/classes/Vector3.xml b/doc/classes/Vector3.xml
index 840e2ba61a..92fbdf6850 100644
--- a/doc/classes/Vector3.xml
+++ b/doc/classes/Vector3.xml
@@ -131,7 +131,7 @@
<param index="6" name="post_b_t" type="float" />
<description>
Performs a cubic interpolation between this vector and [param b] using [param pre_a] and [param post_b] as handles, and returns the result at position [param weight]. [param weight] is on the range of 0.0 to 1.0, representing the amount of interpolation.
- It can perform smoother interpolation than [code]cubic_interpolate()[/code] by the time values.
+ It can perform smoother interpolation than [method cubic_interpolate] by the time values.
</description>
</method>
<method name="direction_to" qualifiers="const">
diff --git a/doc/classes/Vector4.xml b/doc/classes/Vector4.xml
index 6f99234991..e5ad5631e8 100644
--- a/doc/classes/Vector4.xml
+++ b/doc/classes/Vector4.xml
@@ -85,7 +85,7 @@
<param index="6" name="post_b_t" type="float" />
<description>
Performs a cubic interpolation between this vector and [param b] using [param pre_a] and [param post_b] as handles, and returns the result at position [param weight]. [param weight] is on the range of 0.0 to 1.0, representing the amount of interpolation.
- It can perform smoother interpolation than [code]cubic_interpolate()[/code] by the time values.
+ It can perform smoother interpolation than [method cubic_interpolate] by the time values.
</description>
</method>
<method name="direction_to" qualifiers="const">
diff --git a/doc/classes/VisualShaderNode.xml b/doc/classes/VisualShaderNode.xml
index baf9eded56..cc6394e2da 100644
--- a/doc/classes/VisualShaderNode.xml
+++ b/doc/classes/VisualShaderNode.xml
@@ -67,28 +67,28 @@
</members>
<constants>
<constant name="PORT_TYPE_SCALAR" value="0" enum="PortType">
- Floating-point scalar. Translated to [code]float[/code] type in shader code.
+ Floating-point scalar. Translated to [code skip-lint]float[/code] type in shader code.
</constant>
<constant name="PORT_TYPE_SCALAR_INT" value="1" enum="PortType">
- Integer scalar. Translated to [code]int[/code] type in shader code.
+ Integer scalar. Translated to [code skip-lint]int[/code] type in shader code.
</constant>
<constant name="PORT_TYPE_SCALAR_UINT" value="2" enum="PortType">
- Unsigned integer scalar. Translated to [code]uint[/code] type in shader code.
+ Unsigned integer scalar. Translated to [code skip-lint]uint[/code] type in shader code.
</constant>
<constant name="PORT_TYPE_VECTOR_2D" value="3" enum="PortType">
- 2D vector of floating-point values. Translated to [code]vec2[/code] type in shader code.
+ 2D vector of floating-point values. Translated to [code skip-lint]vec2[/code] type in shader code.
</constant>
<constant name="PORT_TYPE_VECTOR_3D" value="4" enum="PortType">
- 3D vector of floating-point values. Translated to [code]vec3[/code] type in shader code.
+ 3D vector of floating-point values. Translated to [code skip-lint]vec3[/code] type in shader code.
</constant>
<constant name="PORT_TYPE_VECTOR_4D" value="5" enum="PortType">
- 4D vector of floating-point values. Translated to [code]vec4[/code] type in shader code.
+ 4D vector of floating-point values. Translated to [code skip-lint]vec4[/code] type in shader code.
</constant>
<constant name="PORT_TYPE_BOOLEAN" value="6" enum="PortType">
- Boolean type. Translated to [code]bool[/code] type in shader code.
+ Boolean type. Translated to [code skip-lint]bool[/code] type in shader code.
</constant>
<constant name="PORT_TYPE_TRANSFORM" value="7" enum="PortType">
- Transform type. Translated to [code]mat4[/code] type in shader code.
+ Transform type. Translated to [code skip-lint]mat4[/code] type in shader code.
</constant>
<constant name="PORT_TYPE_SAMPLER" value="8" enum="PortType">
Sampler type. Translated to reference of sampler uniform in shader code. Can only be used for input ports in non-uniform nodes.
diff --git a/doc/classes/VisualShaderNodeBooleanConstant.xml b/doc/classes/VisualShaderNodeBooleanConstant.xml
index d32af17940..d2a7ff3d45 100644
--- a/doc/classes/VisualShaderNodeBooleanConstant.xml
+++ b/doc/classes/VisualShaderNodeBooleanConstant.xml
@@ -5,7 +5,7 @@
</brief_description>
<description>
Has only one output port and no inputs.
- Translated to [code]bool[/code] in the shader language.
+ Translated to [code skip-lint]bool[/code] in the shader language.
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/VisualShaderNodeFloatConstant.xml b/doc/classes/VisualShaderNodeFloatConstant.xml
index 9b57a9a240..2436663f1f 100644
--- a/doc/classes/VisualShaderNodeFloatConstant.xml
+++ b/doc/classes/VisualShaderNodeFloatConstant.xml
@@ -4,7 +4,7 @@
A scalar floating-point constant to be used within the visual shader graph.
</brief_description>
<description>
- Translated to [code]float[/code] in the shader language.
+ Translated to [code skip-lint]float[/code] in the shader language.
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/VisualShaderNodeIntConstant.xml b/doc/classes/VisualShaderNodeIntConstant.xml
index bde1aaab5d..f942bf299f 100644
--- a/doc/classes/VisualShaderNodeIntConstant.xml
+++ b/doc/classes/VisualShaderNodeIntConstant.xml
@@ -4,7 +4,7 @@
A scalar integer constant to be used within the visual shader graph.
</brief_description>
<description>
- Translated to [code]int[/code] in the shader language.
+ Translated to [code skip-lint]int[/code] in the shader language.
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/VisualShaderNodeSwitch.xml b/doc/classes/VisualShaderNodeSwitch.xml
index 9b9266c478..a44ca3f0b3 100644
--- a/doc/classes/VisualShaderNodeSwitch.xml
+++ b/doc/classes/VisualShaderNodeSwitch.xml
@@ -4,7 +4,7 @@
A selector function for use within the visual shader graph.
</brief_description>
<description>
- Returns an associated value of the [code]op_type[/code] type if the provided boolean value is [code]true[/code] or [code]false[/code].
+ Returns an associated value of the [member op_type] type if the provided boolean value is [code]true[/code] or [code]false[/code].
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml
index b03ef3ab4e..5f6b1960b7 100644
--- a/doc/classes/Window.xml
+++ b/doc/classes/Window.xml
@@ -593,6 +593,9 @@
<member name="initial_position" type="int" setter="set_initial_position" getter="get_initial_position" enum="Window.WindowInitialPosition" default="0">
Specifies the initial type of position for the [Window]. See [enum WindowInitialPosition] constants.
</member>
+ <member name="keep_title_visible" type="bool" setter="set_keep_title_visible" getter="get_keep_title_visible" default="false">
+ If [code]true[/code], the [Window] width is expanded to keep the title bar text fully visible.
+ </member>
<member name="max_size" type="Vector2i" setter="set_max_size" getter="get_max_size" default="Vector2i(0, 0)">
If non-zero, the [Window] can't be resized to be bigger than this size.
[b]Note:[/b] This property will be ignored if the value is lower than [member min_size].
@@ -785,13 +788,19 @@
Maximized window mode, i.e. [Window] will occupy whole screen area except task bar and still display its borders. Normally happens when the maximize button is pressed.
</constant>
<constant name="MODE_FULLSCREEN" value="3" enum="Mode">
- Full screen window mode. Note that this is not [i]exclusive[/i] full screen. On Windows and Linux, a borderless window is used to emulate full screen. On macOS, a new desktop is used to display the running project.
- Regardless of the platform, enabling full screen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling full screen mode.
+ Full screen mode with full multi-window support.
+ Full screen window covers the entire display area of a screen and has no decorations. The display's video mode is not changed.
+ [b]On Windows:[/b] Multi-window full-screen mode has a 1px border of the [member ProjectSettings.rendering/environment/defaults/default_clear_color] color.
+ [b]On macOS:[/b] A new desktop is used to display the running project.
+ [b]Note:[/b] Regardless of the platform, enabling full screen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling full screen mode.
</constant>
<constant name="MODE_EXCLUSIVE_FULLSCREEN" value="4" enum="Mode">
- Exclusive full screen window mode. This mode is implemented on Windows only. On other platforms, it is equivalent to [constant MODE_FULLSCREEN].
- Only one window in exclusive full screen mode can be visible on a given screen at a time. If multiple windows are in exclusive full screen mode for the same screen, the last one being set to this mode takes precedence.
- Regardless of the platform, enabling full screen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling full screen mode.
+ A single window full screen mode. This mode has less overhead, but only one window can be open on a given screen at a time (opening a child window or application switching will trigger a full screen transition).
+ Full screen window covers the entire display area of a screen and has no border or decorations. The display's video mode is not changed.
+ [b]On Windows:[/b] Depending on video driver, full screen transition might cause screens to go black for a moment.
+ [b]On macOS:[/b] A new desktop is used to display the running project. Exclusive full screen mode prevents Dock and Menu from showing up when the mouse pointer is hovering the edge of the screen.
+ [b]On Linux (X11):[/b] Exclusive full screen mode bypasses compositor.
+ [b]Note:[/b] Regardless of the platform, enabling full screen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling full screen mode.
</constant>
<constant name="FLAG_RESIZE_DISABLED" value="0" enum="Flags">
The window can't be resized by dragging its resize grip. It's still possible to resize the window using [member size]. This flag is ignored for full screen windows. Set with [member unresizable].
diff --git a/doc/classes/WorldBoundaryShape2D.xml b/doc/classes/WorldBoundaryShape2D.xml
index 018e6289a3..ee1aa8d224 100644
--- a/doc/classes/WorldBoundaryShape2D.xml
+++ b/doc/classes/WorldBoundaryShape2D.xml
@@ -14,7 +14,7 @@
In the scalar equation of the line [code]ax + by = d[/code], this is [code]d[/code], while the [code](a, b)[/code] coordinates are represented by the [member normal] property.
</member>
<member name="normal" type="Vector2" setter="set_normal" getter="get_normal" default="Vector2(0, -1)">
- The line's normal, typically a unit vector. Its direction indicates the non-colliding half-plane. Can be of any length but zero. Defaults to [code]Vector2.UP[/code].
+ The line's normal, typically a unit vector. Its direction indicates the non-colliding half-plane. Can be of any length but zero. Defaults to [constant Vector2.UP].
</member>
</members>
</class>
diff --git a/doc/classes/XRPositionalTracker.xml b/doc/classes/XRPositionalTracker.xml
index d43daa3028..bd2432af50 100644
--- a/doc/classes/XRPositionalTracker.xml
+++ b/doc/classes/XRPositionalTracker.xml
@@ -37,7 +37,7 @@
<return type="void" />
<param index="0" name="name" type="StringName" />
<description>
- Marks this pose as invalid, we don't clear the last reported state but it allows users to decide if trackers need to be hidden if we loose tracking or just remain at their last known position.
+ Marks this pose as invalid, we don't clear the last reported state but it allows users to decide if trackers need to be hidden if we lose tracking or just remain at their last known position.
</description>
</method>
<method name="set_input">
diff --git a/doc/classes/float.xml b/doc/classes/float.xml
index 7d74e3d832..3ffaa60c32 100644
--- a/doc/classes/float.xml
+++ b/doc/classes/float.xml
@@ -5,7 +5,7 @@
</brief_description>
<description>
The [float] built-in type is a 64-bit double-precision floating-point number, equivalent to [code]double[/code] in C++. This type has 14 reliable decimal digits of precision. The maximum value of [float] is approximately [code]1.79769e308[/code], and the minimum is approximately [code]-1.79769e308[/code].
- Many methods and properties in the engine use 32-bit single-precision floating-point numbers instead, equivalent to [code]float[/code] in C++, which have 6 reliable decimal digits of precision. For data structures such as [Vector2] and [Vector3], Godot uses 32-bit floating-point numbers by default, but it can be changed to use 64-bit doubles if Godot is compiled with the [code]precision=double[/code] option.
+ Many methods and properties in the engine use 32-bit single-precision floating-point numbers instead, equivalent to [code skip-lint]float[/code] in C++, which have 6 reliable decimal digits of precision. For data structures such as [Vector2] and [Vector3], Godot uses 32-bit floating-point numbers by default, but it can be changed to use 64-bit doubles if Godot is compiled with the [code]precision=double[/code] option.
Math done using the [float] type is not guaranteed to be exact and will often result in small errors. You should usually use the [method @GlobalScope.is_equal_approx] and [method @GlobalScope.is_zero_approx] methods instead of [code]==[/code] to compare [float] values for equality.
</description>
<tutorials>
diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py
index 54bad7cf05..e62b3d56d7 100755
--- a/doc/tools/make_rst.py
+++ b/doc/tools/make_rst.py
@@ -401,6 +401,15 @@ class State:
self.classes = OrderedDict(sorted(self.classes.items(), key=lambda t: t[0].lower()))
+class TagState:
+ def __init__(self, raw: str, name: str, arguments: List[str], closing: bool) -> None:
+ self.raw = raw
+
+ self.name = name
+ self.arguments = arguments
+ self.closing = closing
+
+
class TypeName:
def __init__(self, type_name: str, enum: Optional[str] = None, is_bitfield: bool = False) -> None:
self.type_name = type_name
@@ -694,6 +703,8 @@ def main() -> None:
print("")
+ # Print out warnings and errors, or lack thereof, and exit with an appropriate code.
+
if state.num_warnings >= 2:
print(
f'{STYLES["yellow"]}{state.num_warnings} warnings were found in the class reference XML. Please check the messages above.{STYLES["reset"]}'
@@ -703,19 +714,20 @@ def main() -> None:
f'{STYLES["yellow"]}1 warning was found in the class reference XML. Please check the messages above.{STYLES["reset"]}'
)
- if state.num_errors == 0:
- print(f'{STYLES["green"]}No errors found in the class reference XML.{STYLES["reset"]}')
+ if state.num_errors >= 2:
+ print(
+ f'{STYLES["red"]}{state.num_errors} errors were found in the class reference XML. Please check the messages above.{STYLES["reset"]}'
+ )
+ elif state.num_errors == 1:
+ print(
+ f'{STYLES["red"]}1 error was found in the class reference XML. Please check the messages above.{STYLES["reset"]}'
+ )
+
+ if state.num_warnings == 0 and state.num_errors == 0:
+ print(f'{STYLES["green"]}No warnings or errors found in the class reference XML.{STYLES["reset"]}')
if not args.dry_run:
print(f"Wrote reStructuredText files for each class to: {args.output}")
else:
- if state.num_errors >= 2:
- print(
- f'{STYLES["red"]}{state.num_errors} errors were found in the class reference XML. Please check the messages above.{STYLES["reset"]}'
- )
- else:
- print(
- f'{STYLES["red"]}1 error was found in the class reference XML. Please check the messages above.{STYLES["reset"]}'
- )
exit(1)
@@ -1562,8 +1574,20 @@ def make_rst_index(grouped_classes: Dict[str, List[str]], dry_run: bool, output_
RESERVED_FORMATTING_TAGS = ["i", "b", "u", "code", "kbd", "center", "url", "br"]
-RESERVED_CODEBLOCK_TAGS = ["codeblocks", "codeblock", "gdscript", "csharp"]
-RESERVED_CROSSLINK_TAGS = ["method", "member", "signal", "constant", "enum", "annotation", "theme_item", "param"]
+RESERVED_LAYOUT_TAGS = ["codeblocks"]
+RESERVED_CODEBLOCK_TAGS = ["codeblock", "gdscript", "csharp"]
+RESERVED_CROSSLINK_TAGS = [
+ "method",
+ "constructor",
+ "operator",
+ "member",
+ "signal",
+ "constant",
+ "enum",
+ "annotation",
+ "theme_item",
+ "param",
+]
def is_in_tagset(tag_text: str, tagset: List[str]) -> bool:
@@ -1581,6 +1605,35 @@ def is_in_tagset(tag_text: str, tagset: List[str]) -> bool:
return False
+def get_tag_and_args(tag_text: str) -> TagState:
+ tag_name = tag_text
+ arguments: List[str] = []
+
+ assign_pos = tag_text.find("=")
+ if assign_pos >= 0:
+ tag_name = tag_text[:assign_pos]
+ arguments = [tag_text[assign_pos + 1 :].strip()]
+ else:
+ space_pos = tag_text.find(" ")
+ if space_pos >= 0:
+ tag_name = tag_text[:space_pos]
+ arguments = [tag_text[space_pos + 1 :].strip()]
+
+ closing = False
+ if tag_name.startswith("/"):
+ tag_name = tag_name[1:]
+ closing = True
+
+ return TagState(tag_text, tag_name, arguments, closing)
+
+
+def parse_link_target(link_target: str, state: State, context_name: str) -> List[str]:
+ if link_target.find(".") != -1:
+ return link_target.split(".")
+ else:
+ return [state.current_class, link_target]
+
+
def format_text_block(
text: str,
context: Union[DefinitionBase, None],
@@ -1603,11 +1656,15 @@ def format_text_block(
# Handle codeblocks
if (
post_text.startswith("[codeblock]")
+ or post_text.startswith("[codeblock ")
or post_text.startswith("[gdscript]")
+ or post_text.startswith("[gdscript ")
or post_text.startswith("[csharp]")
+ or post_text.startswith("[csharp ")
):
- block_type = post_text[1:].split("]")[0]
- result = format_codeblock(block_type, post_text, indent_level, state)
+ tag_text = post_text[1:].split("]", 1)[0]
+ tag_state = get_tag_and_args(tag_text)
+ result = format_codeblock(tag_state, post_text, indent_level, state)
if result is None:
return ""
text = f"{pre_text}{result[0]}"
@@ -1627,6 +1684,8 @@ def format_text_block(
inside_code = False
inside_code_tag = ""
inside_code_tabs = False
+ ignore_code_warnings = False
+
pos = 0
tag_depth = 0
while True:
@@ -1657,33 +1716,34 @@ def format_text_block(
# Tag is a cross-reference or a formatting directive.
else:
- cmd = tag_text
- space_pos = tag_text.find(" ")
+ tag_state = get_tag_and_args(tag_text)
# Anything identified as a tag inside of a code block is valid,
# unless it's a matching closing tag.
if inside_code:
# Exiting codeblocks and inline code tags.
- if inside_code_tag == cmd[1:]:
- if cmd == "/codeblock" or cmd == "/gdscript" or cmd == "/csharp":
+ if tag_state.closing and tag_state.name == inside_code_tag:
+ if is_in_tagset(tag_state.name, RESERVED_CODEBLOCK_TAGS):
tag_text = ""
tag_depth -= 1
inside_code = False
+ ignore_code_warnings = False
# Strip newline if the tag was alone on one
if pre_text[-1] == "\n":
pre_text = pre_text[:-1]
- elif cmd == "/code":
+ elif is_in_tagset(tag_state.name, ["code"]):
tag_text = "``"
tag_depth -= 1
inside_code = False
+ ignore_code_warnings = False
escape_post = True
else:
- if cmd.startswith("/"):
+ if not ignore_code_warnings and tag_state.closing:
print_warning(
- f'{state.current_class}.xml: Potential error inside of a code tag, found a string that looks like a closing tag "[{cmd}]" in {context_name}.',
+ f'{state.current_class}.xml: Found a code string that looks like a closing tag "[{tag_state.raw}]" in {context_name}.',
state,
)
@@ -1691,26 +1751,27 @@ def format_text_block(
# Entering codeblocks and inline code tags.
- elif cmd == "codeblocks":
- tag_depth += 1
- tag_text = "\n.. tabs::"
- inside_code_tabs = True
- elif cmd == "/codeblocks":
- tag_depth -= 1
- tag_text = ""
- inside_code_tabs = False
+ elif tag_state.name == "codeblocks":
+ if tag_state.closing:
+ tag_depth -= 1
+ tag_text = ""
+ inside_code_tabs = False
+ else:
+ tag_depth += 1
+ tag_text = "\n.. tabs::"
+ inside_code_tabs = True
- elif cmd == "codeblock" or cmd == "gdscript" or cmd == "csharp":
+ elif is_in_tagset(tag_state.name, RESERVED_CODEBLOCK_TAGS):
tag_depth += 1
- if cmd == "gdscript":
+ if tag_state.name == "gdscript":
if not inside_code_tabs:
print_error(
f"{state.current_class}.xml: GDScript code block is used outside of [codeblocks] in {context_name}.",
state,
)
tag_text = "\n .. code-tab:: gdscript\n"
- elif cmd == "csharp":
+ elif tag_state.name == "csharp":
if not inside_code_tabs:
print_error(
f"{state.current_class}.xml: C# code block is used outside of [codeblocks] in {context_name}.",
@@ -1721,17 +1782,19 @@ def format_text_block(
tag_text = "\n::\n"
inside_code = True
- inside_code_tag = cmd
+ inside_code_tag = tag_state.name
+ ignore_code_warnings = "skip-lint" in tag_state.arguments
- elif cmd == "code":
+ elif is_in_tagset(tag_state.name, ["code"]):
tag_text = "``"
tag_depth += 1
+
inside_code = True
- inside_code_tag = cmd
+ inside_code_tag = "code"
+ ignore_code_warnings = "skip-lint" in tag_state.arguments
escape_pre = True
- valid_context = isinstance(context, (MethodDef, SignalDef, AnnotationDef))
- if valid_context:
+ if not ignore_code_warnings:
endcode_pos = text.find("[/code]", endq_pos + 1)
if endcode_pos == -1:
print_error(
@@ -1741,110 +1804,173 @@ def format_text_block(
break
inside_code_text = text[endq_pos + 1 : endcode_pos]
- context_params: List[ParameterDef] = context.parameters # type: ignore
- for param_def in context_params:
- if param_def.name == inside_code_text:
+ if inside_code_text.endswith("()"):
+ # It's formatted like a call for some reason, may still be a mistake.
+ inside_code_text = inside_code_text[:-2]
+
+ if inside_code_text in state.classes:
+ print_warning(
+ f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches one of the known classes in {context_name}.',
+ state,
+ )
+
+ target_class_name, target_name, *rest = parse_link_target(inside_code_text, state, context_name)
+ if len(rest) == 0 and target_class_name in state.classes:
+ class_def = state.classes[target_class_name]
+
+ if target_name in class_def.methods:
+ print_warning(
+ f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} method in {context_name}.',
+ state,
+ )
+
+ elif target_name in class_def.constructors:
+ print_warning(
+ f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} constructor in {context_name}.',
+ state,
+ )
+
+ elif target_name in class_def.operators:
+ print_warning(
+ f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} operator in {context_name}.',
+ state,
+ )
+
+ elif target_name in class_def.properties:
+ print_warning(
+ f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} member in {context_name}.',
+ state,
+ )
+
+ elif target_name in class_def.signals:
+ print_warning(
+ f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} signal in {context_name}.',
+ state,
+ )
+
+ elif target_name in class_def.annotations:
+ print_warning(
+ f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} annotation in {context_name}.',
+ state,
+ )
+
+ elif target_name in class_def.theme_items:
print_warning(
- f'{state.current_class}.xml: Potential error inside of a code tag, found a string "{inside_code_text}" that matches one of the parameters in {context_name}.',
+ f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} theme item in {context_name}.',
state,
)
- break
+
+ elif target_name in class_def.constants:
+ print_warning(
+ f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} constant in {context_name}.',
+ state,
+ )
+
+ else:
+ for enum in class_def.enums.values():
+ if target_name in enum.values:
+ print_warning(
+ f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} enum value in {context_name}.',
+ state,
+ )
+ break
+
+ valid_param_context = isinstance(context, (MethodDef, SignalDef, AnnotationDef))
+ if valid_param_context:
+ context_params: List[ParameterDef] = context.parameters # type: ignore
+ for param_def in context_params:
+ if param_def.name == inside_code_text:
+ print_warning(
+ f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches one of the parameters in {context_name}.',
+ state,
+ )
+ break
# Cross-references to items in this or other class documentation pages.
- elif is_in_tagset(cmd, RESERVED_CROSSLINK_TAGS):
- link_type: str = ""
- link_target: str = ""
- if space_pos >= 0:
- link_type = tag_text[:space_pos]
- link_target = tag_text[space_pos + 1 :].strip()
+ elif is_in_tagset(tag_state.name, RESERVED_CROSSLINK_TAGS):
+ link_target: str = tag_state.arguments[0] if len(tag_state.arguments) > 0 else ""
if link_target == "":
print_error(
- f'{state.current_class}.xml: Empty cross-reference link "{cmd}" in {context_name}.',
+ f'{state.current_class}.xml: Empty cross-reference link "[{tag_state.raw}]" in {context_name}.',
state,
)
tag_text = ""
else:
if (
- cmd.startswith("method")
- or cmd.startswith("constructor")
- or cmd.startswith("operator")
- or cmd.startswith("member")
- or cmd.startswith("signal")
- or cmd.startswith("annotation")
- or cmd.startswith("theme_item")
- or cmd.startswith("constant")
+ tag_state.name == "method"
+ or tag_state.name == "constructor"
+ or tag_state.name == "operator"
+ or tag_state.name == "member"
+ or tag_state.name == "signal"
+ or tag_state.name == "annotation"
+ or tag_state.name == "theme_item"
+ or tag_state.name == "constant"
):
- if link_target.find(".") != -1:
- ss = link_target.split(".")
- if len(ss) > 2:
- print_error(
- f'{state.current_class}.xml: Bad reference "{link_target}" in {context_name}.',
- state,
- )
- class_param, method_param = ss
-
- else:
- class_param = state.current_class
- method_param = link_target
+ target_class_name, target_name, *rest = parse_link_target(link_target, state, context_name)
+ if len(rest) > 0:
+ print_error(
+ f'{state.current_class}.xml: Bad reference "{link_target}" in {context_name}.',
+ state,
+ )
# Default to the tag command name. This works by default for most tags,
# but member and theme_item have special cases.
- ref_type = "_{}".format(link_type)
- if link_type == "member":
+ ref_type = "_{}".format(tag_state.name)
+ if tag_state.name == "member":
ref_type = "_property"
- if class_param in state.classes:
- class_def = state.classes[class_param]
+ if target_class_name in state.classes:
+ class_def = state.classes[target_class_name]
- if cmd.startswith("method") and method_param not in class_def.methods:
+ if tag_state.name == "method" and target_name not in class_def.methods:
print_error(
f'{state.current_class}.xml: Unresolved method reference "{link_target}" in {context_name}.',
state,
)
- elif cmd.startswith("constructor") and method_param not in class_def.constructors:
+ elif tag_state.name == "constructor" and target_name not in class_def.constructors:
print_error(
f'{state.current_class}.xml: Unresolved constructor reference "{link_target}" in {context_name}.',
state,
)
- elif cmd.startswith("operator") and method_param not in class_def.operators:
+ elif tag_state.name == "operator" and target_name not in class_def.operators:
print_error(
f'{state.current_class}.xml: Unresolved operator reference "{link_target}" in {context_name}.',
state,
)
- elif cmd.startswith("member") and method_param not in class_def.properties:
+ elif tag_state.name == "member" and target_name not in class_def.properties:
print_error(
f'{state.current_class}.xml: Unresolved member reference "{link_target}" in {context_name}.',
state,
)
- elif cmd.startswith("signal") and method_param not in class_def.signals:
+ elif tag_state.name == "signal" and target_name not in class_def.signals:
print_error(
f'{state.current_class}.xml: Unresolved signal reference "{link_target}" in {context_name}.',
state,
)
- elif cmd.startswith("annotation") and method_param not in class_def.annotations:
+ elif tag_state.name == "annotation" and target_name not in class_def.annotations:
print_error(
f'{state.current_class}.xml: Unresolved annotation reference "{link_target}" in {context_name}.',
state,
)
- elif cmd.startswith("theme_item"):
- if method_param not in class_def.theme_items:
+ elif tag_state.name == "theme_item":
+ if target_name not in class_def.theme_items:
print_error(
f'{state.current_class}.xml: Unresolved theme item reference "{link_target}" in {context_name}.',
state,
)
else:
# Needs theme data type to be properly linked, which we cannot get without a class.
- name = class_def.theme_items[method_param].data_name
+ name = class_def.theme_items[target_name].data_name
ref_type = f"_theme_{name}"
- elif cmd.startswith("constant"):
+ elif tag_state.name == "constant":
found = False
# Search in the current class
@@ -1855,14 +1981,14 @@ def format_text_block(
search_class_defs.append(state.classes["@GlobalScope"])
for search_class_def in search_class_defs:
- if method_param in search_class_def.constants:
- class_param = search_class_def.name
+ if target_name in search_class_def.constants:
+ target_class_name = search_class_def.name
found = True
else:
for enum in search_class_def.enums.values():
- if method_param in enum.values:
- class_param = search_class_def.name
+ if target_name in enum.values:
+ target_class_name = search_class_def.name
found = True
break
@@ -1874,25 +2000,25 @@ def format_text_block(
else:
print_error(
- f'{state.current_class}.xml: Unresolved type reference "{class_param}" in method reference "{link_target}" in {context_name}.',
+ f'{state.current_class}.xml: Unresolved type reference "{target_class_name}" in method reference "{link_target}" in {context_name}.',
state,
)
- repl_text = method_param
- if class_param != state.current_class:
- repl_text = f"{class_param}.{method_param}"
- tag_text = f":ref:`{repl_text}<class_{class_param}{ref_type}_{method_param}>`"
+ repl_text = target_name
+ if target_class_name != state.current_class:
+ repl_text = f"{target_class_name}.{target_name}"
+ tag_text = f":ref:`{repl_text}<class_{target_class_name}{ref_type}_{target_name}>`"
escape_pre = True
escape_post = True
- elif cmd.startswith("enum"):
+ elif tag_state.name == "enum":
tag_text = make_enum(link_target, False, state)
escape_pre = True
escape_post = True
- elif cmd.startswith("param"):
- valid_context = isinstance(context, (MethodDef, SignalDef, AnnotationDef))
- if not valid_context:
+ elif tag_state.name == "param":
+ valid_param_context = isinstance(context, (MethodDef, SignalDef, AnnotationDef))
+ if not valid_param_context:
print_error(
f'{state.current_class}.xml: Argument reference "{link_target}" used outside of method, signal, or annotation context in {context_name}.',
state,
@@ -1916,11 +2042,17 @@ def format_text_block(
# Formatting directives.
- elif is_in_tagset(cmd, ["url"]):
- if cmd.startswith("url="):
- # URLs are handled in full here as we need to extract the optional link
- # title to use `make_link`.
- link_url = cmd[4:]
+ elif is_in_tagset(tag_state.name, ["url"]):
+ url_target = tag_state.arguments[0] if len(tag_state.arguments) > 0 else ""
+
+ if url_target == "":
+ print_error(
+ f'{state.current_class}.xml: Misformatted [url] tag "[{tag_state.raw}]" in {context_name}.',
+ state,
+ )
+ else:
+ # Unlike other tags, URLs are handled in full here, as we need to extract
+ # the optional link title to use `make_link`.
endurl_pos = text.find("[/url]", endq_pos + 1)
if endurl_pos == -1:
print_error(
@@ -1929,7 +2061,7 @@ def format_text_block(
)
break
link_title = text[endq_pos + 1 : endurl_pos]
- tag_text = make_link(link_url, link_title)
+ tag_text = make_link(url_target, link_title)
pre_text = text[:pos]
post_text = text[endurl_pos + 6 :]
@@ -1942,28 +2074,23 @@ def format_text_block(
text = pre_text + tag_text + post_text
pos = len(pre_text) + len(tag_text)
continue
- else:
- print_error(
- f'{state.current_class}.xml: Misformatted [url] tag "{cmd}" in {context_name}.',
- state,
- )
- elif cmd == "br":
+ elif tag_state.name == "br":
# Make a new paragraph instead of a linebreak, rst is not so linebreak friendly
tag_text = "\n\n"
# Strip potential leading spaces
while post_text[0] == " ":
post_text = post_text[1:]
- elif cmd == "center" or cmd == "/center":
- if cmd == "/center":
+ elif tag_state.name == "center":
+ if tag_state.closing:
tag_depth -= 1
else:
tag_depth += 1
tag_text = ""
- elif cmd == "i" or cmd == "/i":
- if cmd == "/i":
+ elif tag_state.name == "i":
+ if tag_state.closing:
tag_depth -= 1
escape_post = True
else:
@@ -1971,8 +2098,8 @@ def format_text_block(
escape_pre = True
tag_text = "*"
- elif cmd == "b" or cmd == "/b":
- if cmd == "/b":
+ elif tag_state.name == "b":
+ if tag_state.closing:
tag_depth -= 1
escape_post = True
else:
@@ -1980,8 +2107,8 @@ def format_text_block(
escape_pre = True
tag_text = "**"
- elif cmd == "u" or cmd == "/u":
- if cmd == "/u":
+ elif tag_state.name == "u":
+ if tag_state.closing:
tag_depth -= 1
escape_post = True
else:
@@ -1989,9 +2116,9 @@ def format_text_block(
escape_pre = True
tag_text = ""
- elif cmd == "kbd" or cmd == "/kbd":
+ elif tag_state.name == "kbd":
tag_text = "`"
- if cmd == "/kbd":
+ if tag_state.closing:
tag_depth -= 1
escape_post = True
else:
@@ -1999,18 +2126,24 @@ def format_text_block(
tag_depth += 1
escape_pre = True
- # Invalid syntax checks.
- elif cmd.startswith("/"):
- print_error(f'{state.current_class}.xml: Unrecognized closing tag "{cmd}" in {context_name}.', state)
-
- tag_text = f"[{tag_text}]"
-
+ # Invalid syntax.
else:
- print_error(f'{state.current_class}.xml: Unrecognized opening tag "{cmd}" in {context_name}.', state)
+ if tag_state.closing:
+ print_error(
+ f'{state.current_class}.xml: Unrecognized closing tag "[{tag_state.raw}]" in {context_name}.',
+ state,
+ )
- tag_text = f"``{tag_text}``"
- escape_pre = True
- escape_post = True
+ tag_text = f"[{tag_text}]"
+ else:
+ print_error(
+ f'{state.current_class}.xml: Unrecognized opening tag "[{tag_state.raw}]" in {context_name}.',
+ state,
+ )
+
+ tag_text = f"``{tag_text}``"
+ escape_pre = True
+ escape_post = True
# Properly escape things like `[Node]s`
if escape_pre and pre_text and pre_text[-1] not in MARKUP_ALLOWED_PRECEDENT:
@@ -2092,13 +2225,22 @@ def escape_rst(text: str, until_pos: int = -1) -> str:
return text
-def format_codeblock(code_type: str, post_text: str, indent_level: int, state: State) -> Union[Tuple[str, int], None]:
- end_pos = post_text.find("[/" + code_type + "]")
+def format_codeblock(
+ tag_state: TagState, post_text: str, indent_level: int, state: State
+) -> Union[Tuple[str, int], None]:
+ end_pos = post_text.find("[/" + tag_state.name + "]")
if end_pos == -1:
- print_error(f"{state.current_class}.xml: [{code_type}] without a closing tag.", state)
+ print_error(
+ f"{state.current_class}.xml: Tag depth mismatch for [{tag_state.name}]: no closing [/{tag_state.name}].",
+ state,
+ )
return None
- code_text = post_text[len(f"[{code_type}]") : end_pos]
+ opening_formatted = tag_state.name
+ if len(tag_state.arguments) > 0:
+ opening_formatted += " " + " ".join(tag_state.arguments)
+
+ code_text = post_text[len(f"[{opening_formatted}]") : end_pos]
post_text = post_text[end_pos:]
# Remove extraneous tabs
@@ -2114,7 +2256,7 @@ def format_codeblock(code_type: str, post_text: str, indent_level: int, state: S
if to_skip > indent_level:
print_error(
- f"{state.current_class}.xml: Four spaces should be used for indentation within [{code_type}].",
+ f"{state.current_class}.xml: Four spaces should be used for indentation within [{tag_state.name}].",
state,
)
@@ -2124,7 +2266,7 @@ def format_codeblock(code_type: str, post_text: str, indent_level: int, state: S
else:
code_text = f"{code_text[:code_pos]}\n {code_text[code_pos + to_skip + 1 :]}"
code_pos += 5 - to_skip
- return (f"\n[{code_type}]{code_text}{post_text}", len(f"\n[{code_type}]{code_text}"))
+ return (f"\n[{opening_formatted}]{code_text}{post_text}", len(f"\n[{opening_formatted}]{code_text}"))
def format_table(f: TextIO, data: List[Tuple[Optional[str], ...]], remove_empty_columns: bool = False) -> None:
diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp
index 2894f4164f..fcd3af8d62 100644
--- a/drivers/gles3/rasterizer_canvas_gles3.cpp
+++ b/drivers/gles3/rasterizer_canvas_gles3.cpp
@@ -1420,6 +1420,13 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) {
glEnableVertexAttribArray(5);
glVertexAttribIPointer(5, 4, GL_UNSIGNED_INT, instance_stride * sizeof(float), CAST_INT_TO_UCHAR_PTR(instance_color_offset * sizeof(float)));
glVertexAttribDivisor(5, 1);
+ } else {
+ // Set all default instance color and custom data values to 1.0 or 0.0 using a compressed format.
+ uint16_t zero = Math::make_half_float(0.0f);
+ uint16_t one = Math::make_half_float(1.0f);
+ GLuint default_color = (uint32_t(one) << 16) | one;
+ GLuint default_custom = (uint32_t(zero) << 16) | zero;
+ glVertexAttribI4ui(5, default_color, default_color, default_custom, default_custom);
}
}
diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp
index 1f8e9180e3..22bb772e4f 100644
--- a/drivers/gles3/rasterizer_scene_gles3.cpp
+++ b/drivers/gles3/rasterizer_scene_gles3.cpp
@@ -2962,7 +2962,15 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params,
glEnableVertexAttribArray(15);
glVertexAttribIPointer(15, 4, GL_UNSIGNED_INT, stride * sizeof(float), CAST_INT_TO_UCHAR_PTR(color_custom_offset * sizeof(float)));
glVertexAttribDivisor(15, 1);
+ } else {
+ // Set all default instance color and custom data values to 1.0 or 0.0 using a compressed format.
+ uint16_t zero = Math::make_half_float(0.0f);
+ uint16_t one = Math::make_half_float(1.0f);
+ GLuint default_color = (uint32_t(one) << 16) | one;
+ GLuint default_custom = (uint32_t(zero) << 16) | zero;
+ glVertexAttribI4ui(15, default_color, default_color, default_custom, default_custom);
}
+
if (use_index_buffer) {
glDrawElementsInstanced(primitive_gl, count, mesh_storage->mesh_surface_get_index_type(mesh_surface), 0, inst->instance_count);
} else {
diff --git a/drivers/unix/SCsub b/drivers/unix/SCsub
index 91ef613546..146563a3b6 100644
--- a/drivers/unix/SCsub
+++ b/drivers/unix/SCsub
@@ -4,4 +4,4 @@ Import("env")
env.add_source_files(env.drivers_sources, "*.cpp")
-env["check_c_headers"] = [["mntent.h", "HAVE_MNTENT"]]
+env["check_c_headers"] = {"mntent.h": "HAVE_MNTENT"}
diff --git a/drivers/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp
index 11321b2121..e553b2b522 100644
--- a/drivers/vulkan/rendering_device_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_vulkan.cpp
@@ -5054,6 +5054,7 @@ RID RenderingDeviceVulkan::shader_create_from_bytecode(const Vector<uint8_t> &p_
}
Shader *shader = shader_owner.get_or_null(id);
+ ERR_FAIL_NULL_V(shader, RID());
shader->vertex_input_mask = vertex_input_mask;
shader->fragment_output_mask = fragment_output_mask;
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index 7105ff280a..e2852d5a31 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -3249,6 +3249,22 @@ void AnimationTrackEditGroup::_notification(int p_what) {
}
}
+void AnimationTrackEditGroup::gui_input(const Ref<InputEvent> &p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
+ Point2 pos = mb->get_position();
+ Rect2 node_name_rect = Rect2(0, 0, timeline->get_name_limit(), get_size().height);
+
+ if (node_name_rect.has_point(pos)) {
+ EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();
+ editor_selection->clear();
+ editor_selection->add_node(root->get_node(node));
+ }
+ }
+}
+
void AnimationTrackEditGroup::set_type_and_name(const Ref<Texture2D> &p_type, const String &p_name, const NodePath &p_node) {
icon = p_type;
node_name = p_name;
diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h
index 5327099517..5592d43ffe 100644
--- a/editor/animation_track_editor.h
+++ b/editor/animation_track_editor.h
@@ -349,6 +349,8 @@ class AnimationTrackEditGroup : public Control {
protected:
void _notification(int p_what);
+ virtual void gui_input(const Ref<InputEvent> &p_event) override;
+
public:
void set_type_and_name(const Ref<Texture2D> &p_type, const String &p_name, const NodePath &p_node);
virtual Size2 get_minimum_size() const override;
diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp
index 208253d617..31659d4d4e 100644
--- a/editor/connections_dialog.cpp
+++ b/editor/connections_dialog.cpp
@@ -835,35 +835,9 @@ ConnectDialog::~ConnectDialog() {
//////////////////////////////////////////
-// Originally copied and adapted from EditorProperty, try to keep style in sync.
Control *ConnectionsDockTree::make_custom_tooltip(const String &p_text) const {
- // `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;
+ // If it's not a doc tooltip, fallback to the default one.
+ return p_text.contains("::") ? nullptr : memnew(EditorHelpTooltip(p_text));
}
struct _ConnectionsDockMethodInfoSort {
@@ -1341,7 +1315,6 @@ 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;
@@ -1355,21 +1328,8 @@ void ConnectionsDock::update_tree() {
if (doc_class_name.is_empty()) {
doc_class_name = script_base->get_path().trim_prefix("res://").quote();
}
-
- // For a script class, the cache is filled each time.
- if (!doc_class_name.is_empty()) {
- if (descr_cache.has(doc_class_name)) {
- descr_cache[doc_class_name].clear();
- }
- 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;
- }
- } else {
- doc_class_name = String();
- }
+ if (!doc_class_name.is_empty() && !doc_data->class_list.find(doc_class_name)) {
+ doc_class_name = String();
}
class_icon = editor_data.get_script_icon(script_base);
@@ -1398,18 +1358,9 @@ void ConnectionsDock::update_tree() {
script_base = base;
} else {
class_name = native_base;
- doc_class_name = class_name;
-
- 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 = native_base;
+
+ if (!doc_data->class_list.find(doc_class_name)) {
doc_class_name = String();
}
@@ -1434,8 +1385,8 @@ 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);
+ // `|` separators used in `EditorHelpTooltip` for formatting.
+ section_item->set_tooltip_text(0, "class|" + doc_class_name + "||");
section_item->set_icon(0, class_icon);
section_item->set_selectable(0, false);
section_item->set_editable(0, false);
@@ -1466,22 +1417,8 @@ void ConnectionsDock::update_tree() {
sinfo["args"] = argnames;
signal_item->set_metadata(0, sinfo);
signal_item->set_icon(0, get_editor_theme_icon(SNAME("Signal")));
-
- // Set tooltip with the signal's documentation.
- {
- String descr;
-
- HashMap<StringName, HashMap<StringName, String>>::ConstIterator G = descr_cache.find(doc_class_name);
- if (G) {
- HashMap<StringName, String>::ConstIterator F = G->value.find(signal_name);
- if (F) {
- descr = F->value;
- }
- }
-
- // `|` separators used in `make_custom_tooltip()` for formatting.
- signal_item->set_tooltip_text(0, "signal|" + String(signal_name) + "|" + signame.trim_prefix(mi.name) + "|" + descr);
- }
+ // `|` separators used in `EditorHelpTooltip` for formatting.
+ signal_item->set_tooltip_text(0, "signal|" + doc_class_name + "|" + String(signal_name) + "|" + signame.trim_prefix(mi.name));
// List existing connections.
List<Object::Connection> existing_connections;
diff --git a/editor/connections_dialog.h b/editor/connections_dialog.h
index b07b08ecc7..7316a770ec 100644
--- a/editor/connections_dialog.h
+++ b/editor/connections_dialog.h
@@ -231,8 +231,6 @@ class ConnectionsDock : public VBoxContainer {
PopupMenu *slot_menu = nullptr;
LineEdit *search_box = nullptr;
- HashMap<StringName, HashMap<StringName, String>> descr_cache;
-
void _filter_changed(const String &p_text);
void _make_or_edit_connection();
diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp
index 1c8226e5e1..0e025b4430 100644
--- a/editor/create_dialog.cpp
+++ b/editor/create_dialog.cpp
@@ -500,10 +500,11 @@ void CreateDialog::select_type(const String &p_type, bool p_center_on_item) {
to_select->select(0);
search_options->scroll_to_item(to_select, p_center_on_item);
- if (EditorHelp::get_doc_data()->class_list.has(p_type) && !DTR(EditorHelp::get_doc_data()->class_list[p_type].brief_description).is_empty()) {
+ String text = help_bit->get_class_description(p_type);
+ if (!text.is_empty()) {
// Display both class name and description, since the help bit may be displayed
// far away from the location (especially if the dialog was resized to be taller).
- help_bit->set_text(vformat("[b]%s[/b]: %s", p_type, DTR(EditorHelp::get_doc_data()->class_list[p_type].brief_description)));
+ help_bit->set_text(vformat("[b]%s[/b]: %s", p_type, text));
help_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1));
} else {
// Use nested `vformat()` as translators shouldn't interfere with BBCode tags.
diff --git a/editor/editor_build_profile.cpp b/editor/editor_build_profile.cpp
index bca30b2d2d..c3087d797a 100644
--- a/editor/editor_build_profile.cpp
+++ b/editor/editor_build_profile.cpp
@@ -646,24 +646,21 @@ void EditorBuildProfileManager::_class_list_item_selected() {
Variant md = item->get_metadata(0);
if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
- String class_name = md;
- String class_description;
-
- DocTools *dd = EditorHelp::get_doc_data();
- HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(class_name);
- if (E) {
- class_description = DTR(E->value.brief_description);
+ String text = description_bit->get_class_description(md);
+ if (!text.is_empty()) {
+ // Display both class name and description, since the help bit may be displayed
+ // far away from the location (especially if the dialog was resized to be taller).
+ description_bit->set_text(vformat("[b]%s[/b]: %s", md, text));
+ description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1));
+ } else {
+ // Use nested `vformat()` as translators shouldn't interfere with BBCode tags.
+ description_bit->set_text(vformat(TTR("No description available for %s."), vformat("[b]%s[/b]", md)));
+ description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 0.5));
}
-
- description_bit->set_text(class_description);
} else if (md.get_type() == Variant::INT) {
- int build_option_id = md;
- String build_option_description = EditorBuildProfile::get_build_option_description(EditorBuildProfile::BuildOption(build_option_id));
-
- description_bit->set_text(TTRGET(build_option_description));
- return;
- } else {
- return;
+ String build_option_description = EditorBuildProfile::get_build_option_description(EditorBuildProfile::BuildOption((int)md));
+ description_bit->set_text(vformat("[b]%s[/b]: %s", TTR(item->get_text(0)), TTRGET(build_option_description)));
+ description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1));
}
}
diff --git a/editor/editor_feature_profile.cpp b/editor/editor_feature_profile.cpp
index 50406bea6a..5a44ae1aba 100644
--- a/editor/editor_feature_profile.cpp
+++ b/editor/editor_feature_profile.cpp
@@ -555,21 +555,22 @@ void EditorFeatureProfileManager::_class_list_item_selected() {
Variant md = item->get_metadata(0);
if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
- String class_name = md;
- String class_description;
-
- DocTools *dd = EditorHelp::get_doc_data();
- HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(class_name);
- if (E) {
- class_description = DTR(E->value.brief_description);
+ String text = description_bit->get_class_description(md);
+ if (!text.is_empty()) {
+ // Display both class name and description, since the help bit may be displayed
+ // far away from the location (especially if the dialog was resized to be taller).
+ description_bit->set_text(vformat("[b]%s[/b]: %s", md, text));
+ description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1));
+ } else {
+ // Use nested `vformat()` as translators shouldn't interfere with BBCode tags.
+ description_bit->set_text(vformat(TTR("No description available for %s."), vformat("[b]%s[/b]", md)));
+ description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 0.5));
}
-
- description_bit->set_text(class_description);
} else if (md.get_type() == Variant::INT) {
- int feature_id = md;
- String feature_description = EditorFeatureProfile::get_feature_description(EditorFeatureProfile::Feature(feature_id));
+ String feature_description = EditorFeatureProfile::get_feature_description(EditorFeatureProfile::Feature((int)md));
+ description_bit->set_text(vformat("[b]%s[/b]: %s", TTR(item->get_text(0)), TTRGET(feature_description)));
+ description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1));
- description_bit->set_text(TTRGET(feature_description));
return;
} else {
return;
diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp
index b3bcb9f014..6004591bb2 100644
--- a/editor/editor_help.cpp
+++ b/editor/editor_help.cpp
@@ -38,6 +38,7 @@
#include "doc_data_compressed.gen.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
+#include "editor/editor_property_name_processor.h"
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
@@ -216,6 +217,12 @@ void EditorHelp::_class_desc_select(const String &p_select) {
if (tag == "method") {
topic = "class_method";
table = &this->method_line;
+ } else if (tag == "constructor") {
+ topic = "class_method";
+ table = &this->method_line;
+ } else if (tag == "operator") {
+ topic = "class_method";
+ table = &this->method_line;
} else if (tag == "member") {
topic = "class_property";
table = &this->property_line;
@@ -410,8 +417,10 @@ String EditorHelp::_fix_constant(const String &p_constant) const {
class_desc->add_text(" (" + TTR("Experimental") + ")"); \
class_desc->pop();
-void EditorHelp::_add_method(const DocData::MethodDoc &p_method, bool p_overview) {
- method_line[p_method.name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description
+void EditorHelp::_add_method(const DocData::MethodDoc &p_method, bool p_overview, bool p_override) {
+ if (p_override) {
+ method_line[p_method.name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description.
+ }
const bool is_vararg = p_method.qualifiers.contains("vararg");
@@ -462,7 +471,7 @@ void EditorHelp::_add_method(const DocData::MethodDoc &p_method, bool p_overview
class_desc->add_text(" = ");
class_desc->pop();
class_desc->push_color(theme_cache.value_color);
- _add_text(_fix_constant(p_method.arguments[j].default_value));
+ class_desc->add_text(_fix_constant(p_method.arguments[j].default_value));
class_desc->pop();
}
@@ -582,7 +591,7 @@ Error EditorHelp::_goto_desc(const String &p_class) {
return OK;
}
-void EditorHelp::_update_method_list(const Vector<DocData::MethodDoc> p_methods) {
+void EditorHelp::_update_method_list(const Vector<DocData::MethodDoc> p_methods, MethodType p_method_type) {
class_desc->add_newline();
_push_code_font();
@@ -628,7 +637,8 @@ void EditorHelp::_update_method_list(const Vector<DocData::MethodDoc> p_methods)
class_desc->pop(); // cell
}
- _add_method(m[i], true);
+ // For constructors always point to the first one.
+ _add_method(m[i], true, (p_method_type != METHOD_TYPE_CONSTRUCTOR || i == 0));
}
any_previous = !m.is_empty();
@@ -660,7 +670,8 @@ void EditorHelp::_update_method_descriptions(const DocData::ClassDoc p_classdoc,
for (int i = 0; i < methods_filtered.size(); i++) {
_push_code_font();
- _add_method(methods_filtered[i], false);
+ // For constructors always point to the first one.
+ _add_method(methods_filtered[i], false, (p_method_type != METHOD_TYPE_CONSTRUCTOR || i == 0));
_pop_code_font();
class_desc->add_newline();
@@ -1062,7 +1073,7 @@ void EditorHelp::_update_doc() {
class_desc->pop();
class_desc->push_color(theme_cache.value_color);
- _add_text(_fix_constant(cd.properties[i].default_value));
+ class_desc->add_text(_fix_constant(cd.properties[i].default_value));
class_desc->pop();
class_desc->push_color(theme_cache.symbol_color);
@@ -1151,7 +1162,7 @@ void EditorHelp::_update_doc() {
class_desc->add_text(TTR("Constructors"));
_pop_title_font();
- _update_method_list(cd.constructors);
+ _update_method_list(cd.constructors, METHOD_TYPE_CONSTRUCTOR);
}
if (!methods.is_empty()) {
@@ -1164,7 +1175,7 @@ void EditorHelp::_update_doc() {
class_desc->add_text(TTR("Methods"));
_pop_title_font();
- _update_method_list(methods);
+ _update_method_list(methods, METHOD_TYPE_METHOD);
}
if (!cd.operators.is_empty()) {
@@ -1177,7 +1188,7 @@ void EditorHelp::_update_doc() {
class_desc->add_text(TTR("Operators"));
_pop_title_font();
- _update_method_list(cd.operators);
+ _update_method_list(cd.operators, METHOD_TYPE_OPERATOR);
}
// Theme properties
@@ -1238,7 +1249,7 @@ void EditorHelp::_update_doc() {
class_desc->add_text(" [" + TTR("default:") + " ");
class_desc->pop();
class_desc->push_color(theme_cache.value_color);
- _add_text(_fix_constant(cd.theme_properties[i].default_value));
+ class_desc->add_text(_fix_constant(cd.theme_properties[i].default_value));
class_desc->pop();
class_desc->push_color(theme_cache.symbol_color);
class_desc->add_text("]");
@@ -1454,7 +1465,7 @@ void EditorHelp::_update_doc() {
class_desc->add_text(" = ");
class_desc->pop();
class_desc->push_color(theme_cache.value_color);
- _add_text(_fix_constant(enum_list[i].value));
+ class_desc->add_text(_fix_constant(enum_list[i].value));
class_desc->pop();
if (enum_list[i].is_deprecated) {
@@ -1530,7 +1541,7 @@ void EditorHelp::_update_doc() {
class_desc->add_text(" = ");
class_desc->pop();
class_desc->push_color(theme_cache.value_color);
- _add_text(_fix_constant(constants[i].value));
+ class_desc->add_text(_fix_constant(constants[i].value));
class_desc->pop();
if (constants[i].is_deprecated) {
@@ -1711,7 +1722,7 @@ void EditorHelp::_update_doc() {
class_desc->pop(); // color
class_desc->push_color(theme_cache.value_color);
- _add_text(_fix_constant(cd.properties[i].default_value));
+ class_desc->add_text(_fix_constant(cd.properties[i].default_value));
class_desc->pop(); // color
class_desc->push_color(theme_cache.symbol_color);
@@ -1991,10 +2002,10 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
// Select the correct code examples.
switch ((int)EDITOR_GET("text_editor/help/class_reference_examples")) {
case 0: // GDScript
- bbcode = bbcode.replace("[gdscript]", "[codeblock]");
+ bbcode = bbcode.replace("[gdscript", "[codeblock"); // Tag can have extra arguments.
bbcode = bbcode.replace("[/gdscript]", "[/codeblock]");
- for (int pos = bbcode.find("[csharp]"); pos != -1; pos = bbcode.find("[csharp]")) {
+ for (int pos = bbcode.find("[csharp"); pos != -1; pos = bbcode.find("[csharp")) {
int end_pos = bbcode.find("[/csharp]");
if (end_pos == -1) {
WARN_PRINT("Unclosed [csharp] block or parse fail in code (search for tag errors)");
@@ -2008,10 +2019,10 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
}
break;
case 1: // C#
- bbcode = bbcode.replace("[csharp]", "[codeblock]");
+ bbcode = bbcode.replace("[csharp", "[codeblock"); // Tag can have extra arguments.
bbcode = bbcode.replace("[/csharp]", "[/codeblock]");
- for (int pos = bbcode.find("[gdscript]"); pos != -1; pos = bbcode.find("[gdscript]")) {
+ for (int pos = bbcode.find("[gdscript"); pos != -1; pos = bbcode.find("[gdscript")) {
int end_pos = bbcode.find("[/gdscript]");
if (end_pos == -1) {
WARN_PRINT("Unclosed [gdscript] block or parse fail in code (search for tag errors)");
@@ -2025,8 +2036,8 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
}
break;
case 2: // GDScript and C#
- bbcode = bbcode.replace("[csharp]", "[b]C#:[/b]\n[codeblock]");
- bbcode = bbcode.replace("[gdscript]", "[b]GDScript:[/b]\n[codeblock]");
+ bbcode = bbcode.replace("[csharp", "[b]C#:[/b]\n[codeblock"); // Tag can have extra arguments.
+ bbcode = bbcode.replace("[gdscript", "[b]GDScript:[/b]\n[codeblock"); // Tag can have extra arguments.
bbcode = bbcode.replace("[/csharp]", "[/codeblock]");
bbcode = bbcode.replace("[/gdscript]", "[/codeblock]");
@@ -2041,6 +2052,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
// Remove extra new lines around code blocks.
bbcode = bbcode.replace("[codeblock]\n", "[codeblock]");
+ bbcode = bbcode.replace("[codeblock skip-lint]\n", "[codeblock skip-lint]"); // Extra argument to silence validation warnings.
bbcode = bbcode.replace("\n[/codeblock]", "[/codeblock]");
List<String> tag_stack;
@@ -2114,7 +2126,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
p_rt->add_text("[");
pos = brk_pos + 1;
- } else if (tag.begins_with("method ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("annotation ") || tag.begins_with("theme_item ")) {
+ } else if (tag.begins_with("method ") || tag.begins_with("constructor ") || tag.begins_with("operator ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("annotation ") || tag.begins_with("theme_item ")) {
const int tag_end = tag.find(" ");
const String link_tag = tag.substr(0, tag_end);
const String link_target = tag.substr(tag_end + 1, tag.length()).lstrip(" ");
@@ -2125,7 +2137,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
p_rt->push_font_size(doc_code_font_size);
Color target_color = link_color;
- if (link_tag == "method") {
+ if (link_tag == "method" || link_tag == "constructor" || link_tag == "operator") {
target_color = link_method_color;
} else if (link_tag == "member" || link_tag == "signal" || link_tag == "theme property") {
target_color = link_property_color;
@@ -2196,7 +2208,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
pos = brk_end + 1;
tag_stack.push_front(tag);
- } else if (tag == "code") {
+ } else if (tag == "code" || tag.begins_with("code ")) {
// Use monospace font with darkened background color to make code easier to distinguish from other text.
p_rt->push_font(doc_code_font);
p_rt->push_font_size(doc_code_font_size);
@@ -2205,8 +2217,8 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
code_tag = true;
pos = brk_end + 1;
- tag_stack.push_front(tag);
- } else if (tag == "codeblock") {
+ tag_stack.push_front("code");
+ } else if (tag == "codeblock" || tag.begins_with("codeblock ")) {
// Use monospace font with darkened background color to make code easier to distinguish from other text.
// Use a single-column table with cell row background color instead of `[bgcolor]`.
// This makes the background color highlight cover the entire block, rather than individual lines.
@@ -2221,7 +2233,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
codeblock_tag = true;
pos = brk_end + 1;
- tag_stack.push_front(tag);
+ tag_stack.push_front("codeblock");
} else if (tag == "kbd") {
// Use keyboard font with custom color and background color.
p_rt->push_font(doc_kbd_font);
@@ -2587,7 +2599,7 @@ DocTools *EditorHelp::get_doc_data() {
return doc;
}
-//// EditorHelpBit ///
+/// EditorHelpBit ///
void EditorHelpBit::_go_to_help(String p_what) {
EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
@@ -2620,6 +2632,179 @@ void EditorHelpBit::_meta_clicked(String p_select) {
}
}
+String EditorHelpBit::get_class_description(const StringName &p_class_name) const {
+ if (doc_class_cache.has(p_class_name)) {
+ return doc_class_cache[p_class_name];
+ }
+
+ String description;
+ HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name);
+ if (E) {
+ // Non-native class shouldn't be cached, nor translated.
+ bool is_native = ClassDB::class_exists(p_class_name);
+ description = is_native ? DTR(E->value.brief_description) : E->value.brief_description;
+
+ if (is_native) {
+ doc_class_cache[p_class_name] = description;
+ }
+ }
+
+ return description;
+}
+
+String EditorHelpBit::get_property_description(const StringName &p_class_name, const StringName &p_property_name) const {
+ if (doc_property_cache.has(p_class_name) && doc_property_cache[p_class_name].has(p_property_name)) {
+ return doc_property_cache[p_class_name][p_property_name];
+ }
+
+ String description;
+ // Non-native properties shouldn't be cached, nor translated.
+ bool is_native = ClassDB::class_exists(p_class_name);
+ DocTools *dd = EditorHelp::get_doc_data();
+ HashMap<String, DocData::ClassDoc>::ConstIterator E = dd->class_list.find(p_class_name);
+ if (E) {
+ for (int i = 0; i < E->value.properties.size(); i++) {
+ String description_current = is_native ? DTR(E->value.properties[i].description) : E->value.properties[i].description;
+
+ const Vector<String> class_enum = E->value.properties[i].enumeration.split(".");
+ const String enum_name = class_enum.size() >= 2 ? class_enum[1] : "";
+ if (!enum_name.is_empty()) {
+ // Classes can use enums from other classes, so check from which it came.
+ HashMap<String, DocData::ClassDoc>::ConstIterator enum_class = dd->class_list.find(class_enum[0]);
+ if (enum_class) {
+ for (DocData::ConstantDoc val : enum_class->value.constants) {
+ // Don't display `_MAX` enum value descriptions, as these are never exposed in the inspector.
+ if (val.enumeration == enum_name && !val.name.ends_with("_MAX")) {
+ const String enum_value = EditorPropertyNameProcessor::get_singleton()->process_name(val.name, EditorPropertyNameProcessor::STYLE_CAPITALIZED);
+ const String enum_prefix = EditorPropertyNameProcessor::get_singleton()->process_name(enum_name, EditorPropertyNameProcessor::STYLE_CAPITALIZED) + " ";
+ const String enum_description = is_native ? DTR(val.description) : val.description;
+
+ // Prettify the enum value display, so that "<ENUM NAME>_<VALUE>" becomes "Value".
+ description_current = description_current.trim_prefix("\n") + vformat("\n[b]%s:[/b] %s", enum_value.trim_prefix(enum_prefix), enum_description.is_empty() ? ("[i]" + DTR("No description available.") + "[/i]") : enum_description);
+ }
+ }
+ }
+ }
+
+ if (E->value.properties[i].name == p_property_name) {
+ description = description_current;
+
+ if (!is_native) {
+ break;
+ }
+ }
+
+ if (is_native) {
+ doc_property_cache[p_class_name][E->value.properties[i].name] = description_current;
+ }
+ }
+ }
+
+ return description;
+}
+
+String EditorHelpBit::get_method_description(const StringName &p_class_name, const StringName &p_method_name) const {
+ if (doc_method_cache.has(p_class_name) && doc_method_cache[p_class_name].has(p_method_name)) {
+ return doc_method_cache[p_class_name][p_method_name];
+ }
+
+ String description;
+ HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name);
+ if (E) {
+ // Non-native methods shouldn't be cached, nor translated.
+ bool is_native = ClassDB::class_exists(p_class_name);
+
+ for (int i = 0; i < E->value.methods.size(); i++) {
+ String description_current = is_native ? DTR(E->value.methods[i].description) : E->value.methods[i].description;
+
+ if (E->value.methods[i].name == p_method_name) {
+ description = description_current;
+
+ if (!is_native) {
+ break;
+ }
+ }
+
+ if (is_native) {
+ doc_method_cache[p_class_name][E->value.methods[i].name] = description_current;
+ }
+ }
+ }
+
+ return description;
+}
+
+String EditorHelpBit::get_signal_description(const StringName &p_class_name, const StringName &p_signal_name) const {
+ if (doc_signal_cache.has(p_class_name) && doc_signal_cache[p_class_name].has(p_signal_name)) {
+ return doc_signal_cache[p_class_name][p_signal_name];
+ }
+
+ String description;
+ HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name);
+ if (E) {
+ // Non-native signals shouldn't be cached, nor translated.
+ bool is_native = ClassDB::class_exists(p_class_name);
+
+ for (int i = 0; i < E->value.signals.size(); i++) {
+ String description_current = is_native ? DTR(E->value.signals[i].description) : E->value.signals[i].description;
+
+ if (E->value.signals[i].name == p_signal_name) {
+ description = description_current;
+
+ if (!is_native) {
+ break;
+ }
+ }
+
+ if (is_native) {
+ doc_signal_cache[p_class_name][E->value.signals[i].name] = description_current;
+ }
+ }
+ }
+
+ return description;
+}
+
+String EditorHelpBit::get_theme_item_description(const StringName &p_class_name, const StringName &p_theme_item_name) const {
+ if (doc_theme_item_cache.has(p_class_name) && doc_theme_item_cache[p_class_name].has(p_theme_item_name)) {
+ return doc_theme_item_cache[p_class_name][p_theme_item_name];
+ }
+
+ String description;
+ bool found = false;
+ DocTools *dd = EditorHelp::get_doc_data();
+ HashMap<String, DocData::ClassDoc>::ConstIterator E = dd->class_list.find(p_class_name);
+ while (E) {
+ // Non-native theme items shouldn't be cached, nor translated.
+ bool is_native = ClassDB::class_exists(p_class_name);
+
+ for (int i = 0; i < E->value.theme_properties.size(); i++) {
+ String description_current = is_native ? DTR(E->value.theme_properties[i].description) : E->value.theme_properties[i].description;
+
+ if (E->value.theme_properties[i].name == p_theme_item_name) {
+ description = description_current;
+ found = true;
+
+ if (!is_native) {
+ break;
+ }
+ }
+
+ if (is_native) {
+ doc_theme_item_cache[p_class_name][E->value.theme_properties[i].name] = description_current;
+ }
+ }
+
+ if (found || E->value.inherits.is_empty()) {
+ break;
+ }
+ // Check for inherited theme items.
+ E = dd->class_list.find(E->value.inherits);
+ }
+
+ return description;
+}
+
void EditorHelpBit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_text", "text"), &EditorHelpBit::set_text);
ADD_SIGNAL(MethodInfo("request_hide"));
@@ -2650,7 +2835,73 @@ EditorHelpBit::EditorHelpBit() {
set_custom_minimum_size(Size2(0, 50 * EDSCALE));
}
-//// FindBar ///
+/// EditorHelpTooltip ///
+
+void EditorHelpTooltip::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_POSTINITIALIZE: {
+ if (!tooltip_text.is_empty()) {
+ parse_tooltip(tooltip_text);
+ }
+ } break;
+ }
+}
+
+// `p_text` is expected to be something like these:
+// - `class|Control||`;
+// - `property|Control|size|`;
+// - `signal|Control|gui_input|(event: InputEvent)`
+void EditorHelpTooltip::parse_tooltip(const String &p_text) {
+ tooltip_text = p_text;
+
+ PackedStringArray slices = p_text.split("|", true, 3);
+ ERR_FAIL_COND_MSG(slices.size() < 4, "Invalid tooltip formatting. The expect string should be formatted as 'type|class|property|args'.");
+
+ String type = slices[0];
+ String class_name = slices[1];
+ String property_name = slices[2];
+ String property_args = slices[3];
+
+ String title;
+ String description;
+ String formatted_text;
+
+ if (type == "class") {
+ title = class_name;
+ description = get_class_description(class_name);
+ formatted_text = TTR("Class:");
+ } else {
+ title = property_name;
+
+ if (type == "property") {
+ description = get_property_description(class_name, property_name);
+ formatted_text = TTR("Property:");
+ } else if (type == "method") {
+ description = get_method_description(class_name, property_name);
+ formatted_text = TTR("Method:");
+ } else if (type == "signal") {
+ description = get_signal_description(class_name, property_name);
+ formatted_text = TTR("Signal:");
+ } else if (type == "theme_item") {
+ description = get_theme_item_description(class_name, property_name);
+ formatted_text = TTR("Theme Item:");
+ } else {
+ ERR_FAIL_MSG("Invalid tooltip type '" + type + "'. Valid types are 'class', 'property', 'method', 'signal', and 'theme_item'.");
+ }
+ }
+
+ formatted_text += " [u][b]" + title + "[/b][/u]" + property_args + "\n";
+ formatted_text += description.is_empty() ? "[i]" + TTR("No description available.") + "[/i]" : description;
+ set_text(formatted_text);
+}
+
+EditorHelpTooltip::EditorHelpTooltip(const String &p_text) {
+ tooltip_text = p_text;
+
+ get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 0));
+}
+
+/// FindBar ///
FindBar::FindBar() {
search_text = memnew(LineEdit);
diff --git a/editor/editor_help.h b/editor/editor_help.h
index 1f1528945b..0ca3942e0b 100644
--- a/editor/editor_help.h
+++ b/editor/editor_help.h
@@ -157,7 +157,7 @@ class EditorHelp : public VBoxContainer {
//void _button_pressed(int p_idx);
void _add_type(const String &p_type, const String &p_enum = String(), bool p_is_bitfield = false);
void _add_type_icon(const String &p_type, int p_size = 0, const String &p_fallback = "");
- void _add_method(const DocData::MethodDoc &p_method, bool p_overview = true);
+ void _add_method(const DocData::MethodDoc &p_method, bool p_overview, bool p_override = true);
void _add_bulletpoint();
@@ -177,7 +177,7 @@ class EditorHelp : public VBoxContainer {
Error _goto_desc(const String &p_class);
//void _update_history_buttons();
- void _update_method_list(const Vector<DocData::MethodDoc> p_methods);
+ void _update_method_list(const Vector<DocData::MethodDoc> p_methods, MethodType p_method_type);
void _update_method_descriptions(const DocData::ClassDoc p_classdoc, const Vector<DocData::MethodDoc> p_methods, MethodType p_method_type);
void _update_doc();
@@ -232,6 +232,12 @@ public:
class EditorHelpBit : public MarginContainer {
GDCLASS(EditorHelpBit, MarginContainer);
+ inline static HashMap<StringName, String> doc_class_cache;
+ inline static HashMap<StringName, HashMap<StringName, String>> doc_property_cache;
+ inline static HashMap<StringName, HashMap<StringName, String>> doc_method_cache;
+ inline static HashMap<StringName, HashMap<StringName, String>> doc_signal_cache;
+ inline static HashMap<StringName, HashMap<StringName, String>> doc_theme_item_cache;
+
RichTextLabel *rich_text = nullptr;
void _go_to_help(String p_what);
void _meta_clicked(String p_select);
@@ -243,9 +249,30 @@ protected:
void _notification(int p_what);
public:
+ String get_class_description(const StringName &p_class_name) const;
+ String get_property_description(const StringName &p_class_name, const StringName &p_property_name) const;
+ String get_method_description(const StringName &p_class_name, const StringName &p_method_name) const;
+ String get_signal_description(const StringName &p_class_name, const StringName &p_signal_name) const;
+ String get_theme_item_description(const StringName &p_class_name, const StringName &p_theme_item_name) const;
+
RichTextLabel *get_rich_text() { return rich_text; }
void set_text(const String &p_text);
+
EditorHelpBit();
};
+class EditorHelpTooltip : public EditorHelpBit {
+ GDCLASS(EditorHelpTooltip, EditorHelpBit);
+
+ String tooltip_text;
+
+protected:
+ void _notification(int p_what);
+
+public:
+ void parse_tooltip(const String &p_text);
+
+ EditorHelpTooltip(const String &p_text = String());
+};
+
#endif // EDITOR_HELP_H
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index 382b182e0e..91a3181747 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -905,47 +905,17 @@ void EditorProperty::_update_pin_flags() {
}
}
-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 item_name = slices[0].strip_edges();
- String item_descr = slices[1].strip_edges();
-
- String text;
- if (!p_item_type.is_empty()) {
- text = p_item_type + " ";
- }
- text += "[u][b]" + item_name + "[/b][/u]\n";
- if (item_descr.is_empty()) {
- text += "[i]" + TTR("No description.") + "[/i]";
- } else {
- 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;
-}
-
Control *EditorProperty::make_custom_tooltip(const String &p_text) const {
- String warn;
- Color warn_color;
+ EditorHelpTooltip *tooltip = memnew(EditorHelpTooltip(p_text));
+
if (object->has_method("_get_property_warning")) {
- warn = object->call("_get_property_warning", property);
- warn_color = get_theme_color(SNAME("warning_color"));
+ String warn = object->call("_get_property_warning", property);
+ if (!warn.is_empty()) {
+ tooltip->set_text(tooltip->get_rich_text()->get_text() + "\n[b][color=" + get_theme_color(SNAME("warning_color")).to_html(false) + "]" + warn + "[/color][/b]");
+ }
}
- return make_help_bit(TTR("Property:"), p_text, warn, warn_color);
+
+ return tooltip;
}
void EditorProperty::menu_option(int p_option) {
@@ -1178,7 +1148,8 @@ void EditorInspectorCategory::_notification(int p_what) {
}
Control *EditorInspectorCategory::make_custom_tooltip(const String &p_text) const {
- return make_help_bit(TTR("Class:"), p_text, String(), Color());
+ // Far from perfect solution, as there's nothing that prevents a category from having a name that starts with that.
+ return p_text.begins_with("class|") ? memnew(EditorHelpTooltip(p_text)) : nullptr;
}
Size2 EditorInspectorCategory::get_minimum_size() const {
@@ -2883,24 +2854,8 @@ void EditorInspector::update_tree() {
category->doc_class_name = doc_name;
if (use_doc_hints) {
- String descr = "";
- // Sets the category tooltip to show documentation.
- if (!class_descr_cache.has(doc_name)) {
- DocTools *dd = EditorHelp::get_doc_data();
- HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(doc_name);
- if (E) {
- 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];
- }
-
- // `|` separator used in `make_help_bit()` for formatting.
- category->set_tooltip_text(p.name + "|" + descr);
+ // `|` separator used in `EditorHelpTooltip` for formatting.
+ category->set_tooltip_text("class|" + doc_name + "||");
}
// Add editors at the start of a category.
@@ -3195,13 +3150,12 @@ void EditorInspector::update_tree() {
restart_request_props.insert(p.name);
}
- PropertyDocInfo doc_info;
+ String doc_path;
+ String theme_item_name;
+ StringName classname = doc_name;
+ // Build the doc hint, to use as tooltip.
if (use_doc_hints) {
- // Build the doc hint, to use as tooltip.
-
- // Get the class name.
- StringName classname = doc_name;
if (!object_class.is_empty()) {
classname = object_class;
} else if (Object::cast_to<MultiNodeEdit>(object)) {
@@ -3231,83 +3185,55 @@ void EditorInspector::update_tree() {
classname = get_edited_object()->get_class();
}
- // Search for the property description in the cache.
- HashMap<StringName, HashMap<StringName, PropertyDocInfo>>::Iterator E = doc_info_cache.find(classname);
+ // Search for the doc path in the cache.
+ HashMap<StringName, HashMap<StringName, String>>::Iterator E = doc_path_cache.find(classname);
if (E) {
- HashMap<StringName, PropertyDocInfo>::Iterator F = E->value.find(propname);
+ HashMap<StringName, String>::Iterator F = E->value.find(propname);
if (F) {
found = true;
- doc_info = F->value;
+ doc_path = F->value;
}
}
if (!found) {
+ DocTools *dd = EditorHelp::get_doc_data();
+ // Do not cache the doc path information of scripts.
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 = 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];
- const String enum_name = class_enum.size() >= 2 ? class_enum[1] : "";
- if (!enum_name.is_empty()) {
- HashMap<String, DocData::ClassDoc>::ConstIterator enum_class = dd->class_list.find(class_name);
- if (enum_class) {
- for (DocData::ConstantDoc val : enum_class->value.constants) {
- // Don't display `_MAX` enum value descriptions, as these are never exposed in the inspector.
- 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 = 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) + " "),
- desc.is_empty() ? ("[i]" + TTR("No description.") + "[/i]") : desc);
- }
- }
- }
- }
-
- doc_info.path = "class_property:" + F->value.name + ":" + F->value.properties[i].name;
- break;
- }
- }
-
+ while (F) {
Vector<String> slices = propname.operator String().split("/");
+ // Check if it's a theme item first.
if (slices.size() == 2 && slices[0].begins_with("theme_override_")) {
for (int i = 0; i < F->value.theme_properties.size(); i++) {
+ String doc_path_current = "class_theme_item:" + F->value.name + ":" + F->value.theme_properties[i].name;
if (F->value.theme_properties[i].name == slices[1]) {
- 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;
+ doc_path = doc_path_current;
+ theme_item_name = F->value.theme_properties[i].name;
}
}
- }
- if (!F->value.inherits.is_empty()) {
- F = dd->class_list.find(F->value.inherits);
+ if (is_native_class) {
+ doc_path_cache[classname][propname] = doc_path;
+ }
} else {
- break;
+ for (int i = 0; i < F->value.properties.size(); i++) {
+ String doc_path_current = "class_property:" + F->value.name + ":" + F->value.properties[i].name;
+ if (F->value.properties[i].name == propname.operator String()) {
+ doc_path = doc_path_current;
+ }
+
+ if (is_native_class) {
+ doc_path_cache[classname][propname] = doc_path;
+ }
+ }
}
- }
- if (is_native_class) {
- doc_info_cache[classname][propname] = doc_info; // Do not cache the doc information of scripts.
+ if (!doc_path.is_empty() || F->value.inherits.is_empty()) {
+ break;
+ }
+ // Couldn't find the doc path in the class itself, try its super class.
+ F = dd->class_list.find(F->value.inherits);
}
}
}
@@ -3346,11 +3272,11 @@ void EditorInspector::update_tree() {
if (properties.size()) {
if (properties.size() == 1) {
- //since it's one, associate:
+ // Since it's one, associate:
ep->property = properties[0];
ep->property_path = property_prefix + properties[0];
ep->property_usage = p.usage;
- //and set label?
+ // And set label?
}
if (!editors[i].label.is_empty()) {
ep->set_label(editors[i].label);
@@ -3398,9 +3324,17 @@ 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);
- // `|` 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);
+
+ if (use_doc_hints) {
+ // `|` separator used in `EditorHelpTooltip` for formatting.
+ if (theme_item_name.is_empty()) {
+ ep->set_tooltip_text("property|" + classname + "|" + property_prefix + p.name + "|");
+ } else {
+ ep->set_tooltip_text("theme_item|" + classname + "|" + theme_item_name + "|");
+ }
+ }
+
+ ep->set_doc_path(doc_path);
ep->update_property();
ep->_update_pin_flags();
ep->update_editor_property_status();
diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h
index 4393922f52..b5f0cec80b 100644
--- a/editor/editor_inspector.h
+++ b/editor/editor_inspector.h
@@ -501,13 +501,7 @@ class EditorInspector : public ScrollContainer {
int property_focusable;
int update_scroll_request;
- struct PropertyDocInfo {
- String description;
- String path;
- };
-
- HashMap<StringName, HashMap<StringName, PropertyDocInfo>> doc_info_cache;
- HashMap<StringName, String> class_descr_cache;
+ HashMap<StringName, HashMap<StringName, String>> doc_path_cache;
HashSet<StringName> restart_request_props;
HashMap<ObjectID, int> scroll_cache;
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index e69dcb2278..0037b356d0 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -4473,7 +4473,7 @@ String EditorNode::_get_system_info() const {
}
if (driver_name == "vulkan") {
driver_name = "Vulkan";
- } else if (driver_name == "opengl3") {
+ } else if (driver_name.begins_with("opengl3")) {
driver_name = "GLES3";
}
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index a197eb9dce..0be23fa3cc 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -47,6 +47,7 @@
#include "editor/plugins/script_editor_plugin.h"
#include "editor/project_settings_editor.h"
#include "editor/property_selector.h"
+#include "editor/scene_tree_dock.h"
#include "scene/2d/gpu_particles_2d.h"
#include "scene/3d/fog_volume.h"
#include "scene/3d/gpu_particles_3d.h"
@@ -2769,7 +2770,7 @@ EditorPropertyColor::EditorPropertyColor() {
void EditorPropertyNodePath::_set_read_only(bool p_read_only) {
assign->set_disabled(p_read_only);
- clear->set_disabled(p_read_only);
+ menu->set_disabled(p_read_only);
};
Variant EditorPropertyNodePath::_get_cache_value(const StringName &p_prop, bool &r_valid) const {
@@ -2817,9 +2818,79 @@ void EditorPropertyNodePath::_node_assign() {
scene_tree->popup_scenetree_dialog();
}
-void EditorPropertyNodePath::_node_clear() {
- emit_changed(get_edited_property(), Variant());
- update_property();
+void EditorPropertyNodePath::_update_menu() {
+ const NodePath &np = _get_node_path();
+
+ menu->get_popup()->set_item_disabled(ACTION_CLEAR, np.is_empty());
+ menu->get_popup()->set_item_disabled(ACTION_COPY, np.is_empty());
+
+ Node *edited_node = Object::cast_to<Node>(get_edited_object());
+ menu->get_popup()->set_item_disabled(ACTION_SELECT, !edited_node || !edited_node->has_node(np));
+}
+
+void EditorPropertyNodePath::_menu_option(int p_idx) {
+ switch (p_idx) {
+ case ACTION_CLEAR: {
+ emit_changed(get_edited_property(), NodePath());
+ update_property();
+ } break;
+
+ case ACTION_COPY: {
+ DisplayServer::get_singleton()->clipboard_set(_get_node_path());
+ } break;
+
+ case ACTION_EDIT: {
+ assign->hide();
+ menu->hide();
+
+ const NodePath &np = _get_node_path();
+ edit->set_text(np);
+ edit->show();
+ callable_mp((Control *)edit, &Control::grab_focus).call_deferred();
+ } break;
+
+ case ACTION_SELECT: {
+ const Node *edited_node = get_base_node();
+ ERR_FAIL_NULL(edited_node);
+
+ const NodePath &np = _get_node_path();
+ Node *target_node = edited_node->get_node_or_null(np);
+ ERR_FAIL_NULL(target_node);
+
+ SceneTreeDock::get_singleton()->set_selected(target_node);
+ } break;
+ }
+}
+
+void EditorPropertyNodePath::_accept_text() {
+ _text_submitted(edit->get_text());
+}
+
+void EditorPropertyNodePath::_text_submitted(const String &p_text) {
+ NodePath np = p_text;
+ emit_changed(get_edited_property(), np);
+ edit->hide();
+ assign->show();
+ menu->show();
+}
+
+const NodePath EditorPropertyNodePath::_get_node_path() const {
+ const Node *base_node = const_cast<EditorPropertyNodePath *>(this)->get_base_node();
+
+ Variant val = get_edited_property_value();
+ Node *n = Object::cast_to<Node>(val);
+ if (n) {
+ if (!n->is_inside_tree()) {
+ return NodePath();
+ }
+ if (base_node) {
+ return base_node->get_path_to(n);
+ } else {
+ return get_tree()->get_edited_scene_root()->get_path_to(n);
+ }
+ } else {
+ return val;
+ }
}
bool EditorPropertyNodePath::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
@@ -2865,26 +2936,11 @@ bool EditorPropertyNodePath::is_drop_valid(const Dictionary &p_drag_data) const
}
void EditorPropertyNodePath::update_property() {
- Node *base_node = get_base_node();
-
- NodePath p;
- Variant val = get_edited_object()->get(get_edited_property());
- Node *n = Object::cast_to<Node>(val);
- if (n) {
- if (!n->is_inside_tree()) {
- return;
- }
- if (base_node) {
- p = base_node->get_path_to(n);
- } else {
- p = get_tree()->get_edited_scene_root()->get_path_to(n);
- }
- } else {
- p = get_edited_property_value();
- }
-
+ const Node *base_node = get_base_node();
+ const NodePath &p = _get_node_path();
assign->set_tooltip_text(p);
- if (p == NodePath()) {
+
+ if (p.is_empty()) {
assign->set_icon(Ref<Texture2D>());
assign->set_text(TTR("Assign..."));
assign->set_flat(false);
@@ -2898,7 +2954,7 @@ void EditorPropertyNodePath::update_property() {
return;
}
- Node *target_node = base_node->get_node(p);
+ const Node *target_node = base_node->get_node(p);
ERR_FAIL_NULL(target_node);
if (String(target_node->get_name()).contains("@")) {
@@ -2922,14 +2978,15 @@ void EditorPropertyNodePath::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_THEME_CHANGED: {
- Ref<Texture2D> t = get_editor_theme_icon(SNAME("Clear"));
- clear->set_icon(t);
+ menu->set_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
+ menu->get_popup()->set_item_icon(ACTION_CLEAR, get_editor_theme_icon(SNAME("Clear")));
+ menu->get_popup()->set_item_icon(ACTION_COPY, get_editor_theme_icon(SNAME("ActionCopy")));
+ menu->get_popup()->set_item_icon(ACTION_EDIT, get_editor_theme_icon(SNAME("Edit")));
+ menu->get_popup()->set_item_icon(ACTION_SELECT, get_editor_theme_icon(SNAME("ExternalLink")));
} break;
}
}
-void EditorPropertyNodePath::_bind_methods() {
-}
Node *EditorPropertyNodePath::get_base_node() {
if (!base_hint.is_empty() && get_tree()->get_root()->has_node(base_hint)) {
return get_tree()->get_root()->get_node(base_hint);
@@ -2969,16 +3026,28 @@ EditorPropertyNodePath::EditorPropertyNodePath() {
assign->set_h_size_flags(SIZE_EXPAND_FILL);
assign->set_clip_text(true);
assign->set_auto_translate(false);
+ assign->set_expand_icon(true);
assign->connect("pressed", callable_mp(this, &EditorPropertyNodePath::_node_assign));
SET_DRAG_FORWARDING_CD(assign, EditorPropertyNodePath);
hbc->add_child(assign);
- clear = memnew(Button);
- clear->set_flat(true);
- clear->connect("pressed", callable_mp(this, &EditorPropertyNodePath::_node_clear));
- hbc->add_child(clear);
-
- scene_tree = nullptr; //do not allocate unnecessarily
+ menu = memnew(MenuButton);
+ menu->set_flat(true);
+ menu->connect(SNAME("about_to_popup"), callable_mp(this, &EditorPropertyNodePath::_update_menu));
+ hbc->add_child(menu);
+
+ menu->get_popup()->add_item(TTR("Clear"), ACTION_CLEAR);
+ menu->get_popup()->add_item(TTR("Copy as Text"), ACTION_COPY);
+ menu->get_popup()->add_item(TTR("Edit"), ACTION_EDIT);
+ menu->get_popup()->add_item(TTR("Show Node in Tree"), ACTION_SELECT);
+ menu->get_popup()->connect(SNAME("id_pressed"), callable_mp(this, &EditorPropertyNodePath::_menu_option));
+
+ edit = memnew(LineEdit);
+ edit->set_h_size_flags(SIZE_EXPAND_FILL);
+ edit->hide();
+ edit->connect(SNAME("focus_exited"), callable_mp(this, &EditorPropertyNodePath::_accept_text));
+ edit->connect(SNAME("text_submitted"), callable_mp(this, &EditorPropertyNodePath::_text_submitted));
+ hbc->add_child(edit);
}
///////////////////// RID /////////////////////////
diff --git a/editor/editor_properties.h b/editor/editor_properties.h
index 5feb40b3d7..ff9d47627a 100644
--- a/editor/editor_properties.h
+++ b/editor/editor_properties.h
@@ -40,6 +40,7 @@ class EditorFileDialog;
class EditorLocaleDialog;
class EditorResourcePicker;
class EditorSpinSlider;
+class MenuButton;
class PropertySelector;
class SceneTreeDialog;
class TextEdit;
@@ -649,8 +650,18 @@ public:
class EditorPropertyNodePath : public EditorProperty {
GDCLASS(EditorPropertyNodePath, EditorProperty);
+
+ enum {
+ ACTION_CLEAR,
+ ACTION_COPY,
+ ACTION_EDIT,
+ ACTION_SELECT,
+ };
+
Button *assign = nullptr;
- Button *clear = nullptr;
+ MenuButton *menu = nullptr;
+ LineEdit *edit = nullptr;
+
SceneTreeDialog *scene_tree = nullptr;
NodePath base_hint;
bool use_path_from_scene_root = false;
@@ -659,8 +670,12 @@ class EditorPropertyNodePath : public EditorProperty {
Vector<StringName> valid_types;
void _node_selected(const NodePath &p_path);
void _node_assign();
- void _node_clear();
Node *get_base_node();
+ void _update_menu();
+ void _menu_option(int p_idx);
+ void _accept_text();
+ void _text_submitted(const String &p_text);
+ const NodePath _get_node_path() const;
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
@@ -670,7 +685,6 @@ class EditorPropertyNodePath : public EditorProperty {
protected:
virtual void _set_read_only(bool p_read_only) override;
- static void _bind_methods();
void _notification(int p_what);
public:
diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp
index 950a1e1c4d..3fad85c95c 100644
--- a/editor/editor_properties_array_dict.cpp
+++ b/editor/editor_properties_array_dict.cpp
@@ -255,7 +255,7 @@ void EditorPropertyArray::update_property() {
array_type_name = vformat("%s[%s]", array_type_name, type_name);
}
- if (array.get_type() == Variant::NIL) {
+ if (!array.is_array()) {
edit->set_text(vformat(TTR("(Nil) %s"), array_type_name));
edit->set_pressed(false);
if (container) {
@@ -287,6 +287,7 @@ void EditorPropertyArray::update_property() {
if (!container) {
container = memnew(MarginContainer);
container->set_theme_type_variation("MarginContainer4px");
+ container->set_mouse_filter(MOUSE_FILTER_STOP);
add_child(container);
set_bottom_editor(container);
diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp
index 6c41e34e51..c492589e63 100644
--- a/editor/export/editor_export_platform.cpp
+++ b/editor/export/editor_export_platform.cpp
@@ -859,16 +859,20 @@ Vector<String> EditorExportPlatform::get_forced_export_files() {
bool use_data = GLOBAL_GET("internationalization/locale/include_text_server_data");
if (use_data) {
// Try using user provided data file.
- String ts_data = "res://" + TS->get_support_data_filename();
- if (FileAccess::exists(ts_data)) {
- files.push_back(ts_data);
- } else {
- // Use default text server data.
- String icu_data_file = EditorPaths::get_singleton()->get_cache_dir().path_join("tmp_icu_data");
- ERR_FAIL_COND_V(!TS->save_support_data(icu_data_file), files);
- files.push_back(icu_data_file);
- // Remove the file later.
- MessageQueue::get_singleton()->push_callable(callable_mp_static(DirAccess::remove_absolute), icu_data_file);
+ if (!TS->get_support_data_filename().is_empty()) {
+ String ts_data = "res://" + TS->get_support_data_filename();
+ if (FileAccess::exists(ts_data)) {
+ files.push_back(ts_data);
+ } else {
+ // Use default text server data.
+ String abs_path = ProjectSettings::get_singleton()->globalize_path(ts_data);
+ ERR_FAIL_COND_V(!TS->save_support_data(abs_path), files);
+ if (FileAccess::exists(abs_path)) {
+ files.push_back(ts_data);
+ // Remove the file later.
+ MessageQueue::get_singleton()->push_callable(callable_mp_static(DirAccess::remove_absolute), abs_path);
+ }
+ }
}
}
}
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index 36f712dde8..be06a3932c 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -216,7 +216,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory
subdirectory_item->set_icon(0, get_editor_theme_icon(SNAME("Folder")));
subdirectory_item->set_selectable(0, true);
subdirectory_item->set_metadata(0, lpath);
- if (!p_select_in_favorites && (current_path == lpath || ((display_mode == DISPLAY_MODE_SPLIT) && current_path.get_base_dir() == lpath))) {
+ if (!p_select_in_favorites && (current_path == lpath || ((display_mode != DISPLAY_MODE_TREE_ONLY) && current_path.get_base_dir() == lpath))) {
subdirectory_item->select(0);
// Keep select an item when re-created a tree
// To prevent crashing when nothing is selected.
@@ -302,7 +302,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory
udata.push_back(file_item);
EditorResourcePreview::get_singleton()->queue_resource_preview(file_metadata, this, "_tree_thumbnail_done", udata);
}
- } else if (display_mode == DISPLAY_MODE_SPLIT) {
+ } else {
if (lpath.get_base_dir() == current_path.get_base_dir()) {
subdirectory_item->select(0);
subdirectory_item->set_as_cursor(0);
@@ -455,22 +455,29 @@ void FileSystemDock::set_display_mode(DisplayMode p_display_mode) {
void FileSystemDock::_update_display_mode(bool p_force) {
// Compute the new display mode.
if (p_force || old_display_mode != display_mode) {
- button_toggle_display_mode->set_pressed(display_mode == DISPLAY_MODE_SPLIT);
switch (display_mode) {
case DISPLAY_MODE_TREE_ONLY:
+ button_toggle_display_mode->set_icon(get_editor_theme_icon(SNAME("Panels1")));
tree->show();
tree->set_v_size_flags(SIZE_EXPAND_FILL);
- if (display_mode == DISPLAY_MODE_TREE_ONLY) {
- toolbar2_hbc->show();
- } else {
- toolbar2_hbc->hide();
- }
+ toolbar2_hbc->show();
_update_tree(get_uncollapsed_paths());
file_list_vb->hide();
break;
- case DISPLAY_MODE_SPLIT:
+ case DISPLAY_MODE_HSPLIT:
+ case DISPLAY_MODE_VSPLIT:
+ const bool is_vertical = display_mode == DISPLAY_MODE_VSPLIT;
+ const int split_offset = split_box->get_split_offset();
+ is_vertical ? split_box_offset_h = split_offset : split_box_offset_v = split_offset;
+ split_box->set_vertical(is_vertical);
+
+ const int actual_offset = is_vertical ? split_box_offset_v : split_box_offset_h;
+ split_box->set_split_offset(actual_offset);
+ const StringName icon = is_vertical ? SNAME("Panels2") : SNAME("Panels2Alt");
+ button_toggle_display_mode->set_icon(get_editor_theme_icon(icon));
+
tree->show();
tree->set_v_size_flags(SIZE_EXPAND_FILL);
tree->ensure_cursor_is_visible();
@@ -500,7 +507,6 @@ void FileSystemDock::_notification(int p_what) {
EditorResourcePreview::get_singleton()->connect("preview_invalidated", callable_mp(this, &FileSystemDock::_preview_invalidated));
button_reload->set_icon(get_editor_theme_icon(SNAME("Reload")));
- button_toggle_display_mode->set_icon(get_editor_theme_icon(SNAME("Panels2")));
button_file_list_display_mode->connect("pressed", callable_mp(this, &FileSystemDock::_toggle_file_display));
files->connect("item_activated", callable_mp(this, &FileSystemDock::_file_list_activate_file));
@@ -587,7 +593,15 @@ void FileSystemDock::_notification(int p_what) {
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
// Update icons.
button_reload->set_icon(get_editor_theme_icon(SNAME("Reload")));
- button_toggle_display_mode->set_icon(get_editor_theme_icon(SNAME("Panels2")));
+
+ StringName mode_icon = "Panels1";
+ if (display_mode == DISPLAY_MODE_VSPLIT) {
+ mode_icon = "Panels2";
+ } else if (display_mode == DISPLAY_MODE_HSPLIT) {
+ mode_icon = "Panels2Alt";
+ }
+ button_toggle_display_mode->set_icon(get_editor_theme_icon(mode_icon));
+
if (is_layout_rtl()) {
button_hist_next->set_icon(get_editor_theme_icon(SNAME("Back")));
button_hist_prev->set_icon(get_editor_theme_icon(SNAME("Forward")));
@@ -665,7 +679,7 @@ void FileSystemDock::_tree_multi_selected(Object *p_item, int p_column, bool p_s
_push_to_history();
// Update the file list.
- if (!updating_tree && display_mode == DISPLAY_MODE_SPLIT) {
+ if (!updating_tree && display_mode != DISPLAY_MODE_TREE_ONLY) {
_update_file_list(false);
}
}
@@ -717,7 +731,7 @@ void FileSystemDock::_navigate_to_path(const String &p_path, bool p_select_in_fa
_push_to_history();
_update_tree(get_uncollapsed_paths(), false, p_select_in_favorites, true);
- if (display_mode == DISPLAY_MODE_SPLIT) {
+ if (display_mode != DISPLAY_MODE_TREE_ONLY) {
_update_file_list(false);
files->get_v_scroll_bar()->set_value(0);
}
@@ -2411,7 +2425,8 @@ void FileSystemDock::_search_changed(const String &p_text, const Control *p_from
case DISPLAY_MODE_TREE_ONLY: {
_update_tree(searched_string.length() == 0 ? uncollapsed_paths_before_search : Vector<String>(), false, false, unfold_path);
} break;
- case DISPLAY_MODE_SPLIT: {
+ case DISPLAY_MODE_HSPLIT:
+ case DISPLAY_MODE_VSPLIT: {
_update_file_list(false);
_update_tree(searched_string.length() == 0 ? uncollapsed_paths_before_search : Vector<String>(), false, false, unfold_path);
} break;
@@ -2423,8 +2438,15 @@ void FileSystemDock::_rescan() {
EditorFileSystem::get_singleton()->scan();
}
-void FileSystemDock::_toggle_split_mode(bool p_active) {
- set_display_mode(p_active ? DISPLAY_MODE_SPLIT : DISPLAY_MODE_TREE_ONLY);
+void FileSystemDock::_change_split_mode() {
+ DisplayMode next_mode = DISPLAY_MODE_TREE_ONLY;
+ if (display_mode == DISPLAY_MODE_VSPLIT) {
+ next_mode = DISPLAY_MODE_HSPLIT;
+ } else if (display_mode == DISPLAY_MODE_TREE_ONLY) {
+ next_mode = DISPLAY_MODE_VSPLIT;
+ }
+
+ set_display_mode(next_mode);
emit_signal(SNAME("display_mode_changed"));
}
@@ -2436,7 +2458,7 @@ void FileSystemDock::focus_on_filter() {
LineEdit *current_search_box = nullptr;
if (display_mode == DISPLAY_MODE_TREE_ONLY) {
current_search_box = tree_search_box;
- } else if (display_mode == DISPLAY_MODE_SPLIT) {
+ } else {
current_search_box = file_list_search_box;
}
@@ -2677,7 +2699,7 @@ void FileSystemDock::drop_data_fw(const Point2 &p_point, const Variant &p_data,
EditorSettings::get_singleton()->set_favorites(dirs);
_update_tree(get_uncollapsed_paths());
- if (display_mode == DISPLAY_MODE_SPLIT && current_path == "Favorites") {
+ if (display_mode != DISPLAY_MODE_TREE_ONLY && current_path == "Favorites") {
_update_file_list(true);
}
return;
@@ -3152,7 +3174,7 @@ void FileSystemDock::_file_multi_selected(int p_index, bool p_selected) {
String fpath = files->get_item_metadata(current);
if (!fpath.ends_with("/")) {
current_path = fpath;
- if (display_mode == DISPLAY_MODE_SPLIT) {
+ if (display_mode != DISPLAY_MODE_TREE_ONLY) {
_update_tree(get_uncollapsed_paths());
}
}
@@ -3225,7 +3247,7 @@ void FileSystemDock::_tree_gui_input(Ref<InputEvent> p_event) {
}
item->select(0);
- if (display_mode == DisplayMode::DISPLAY_MODE_SPLIT) {
+ if (display_mode != DisplayMode::DISPLAY_MODE_TREE_ONLY) {
files->deselect_all();
// Try to select the corresponding file list item.
const int files_item_idx = files->find_metadata(fpath);
@@ -3559,10 +3581,9 @@ FileSystemDock::FileSystemDock() {
toolbar_hbc->add_child(button_reload);
button_toggle_display_mode = memnew(Button);
- button_toggle_display_mode->set_toggle_mode(true);
- button_toggle_display_mode->connect("toggled", callable_mp(this, &FileSystemDock::_toggle_split_mode));
+ button_toggle_display_mode->connect("pressed", callable_mp(this, &FileSystemDock::_change_split_mode));
button_toggle_display_mode->set_focus_mode(FOCUS_NONE);
- button_toggle_display_mode->set_tooltip_text(TTR("Toggle Split Mode"));
+ button_toggle_display_mode->set_tooltip_text(TTR("Change Split Mode"));
button_toggle_display_mode->set_flat(true);
toolbar_hbc->add_child(button_toggle_display_mode);
@@ -3587,7 +3608,7 @@ FileSystemDock::FileSystemDock() {
add_child(tree_popup);
- split_box = memnew(VSplitContainer);
+ split_box = memnew(SplitContainer);
split_box->set_v_size_flags(SIZE_EXPAND_FILL);
add_child(split_box);
@@ -3597,7 +3618,7 @@ FileSystemDock::FileSystemDock() {
SET_DRAG_FORWARDING_GCD(tree, FileSystemDock);
tree->set_allow_rmb_select(true);
tree->set_select_mode(Tree::SELECT_MULTI);
- tree->set_custom_minimum_size(Size2(0, 15 * EDSCALE));
+ tree->set_custom_minimum_size(Size2(40 * EDSCALE, 15 * EDSCALE));
tree->set_column_clip_content(0, true);
split_box->add_child(tree);
diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h
index 818b91bdb9..1e04b6a4ff 100644
--- a/editor/filesystem_dock.h
+++ b/editor/filesystem_dock.h
@@ -88,7 +88,8 @@ public:
enum DisplayMode {
DISPLAY_MODE_TREE_ONLY,
- DISPLAY_MODE_SPLIT,
+ DISPLAY_MODE_VSPLIT,
+ DISPLAY_MODE_HSPLIT,
};
enum FileSortOption {
@@ -144,9 +145,12 @@ private:
VBoxContainer *scanning_vb = nullptr;
ProgressBar *scanning_progress = nullptr;
- VSplitContainer *split_box = nullptr;
+ SplitContainer *split_box = nullptr;
VBoxContainer *file_list_vb = nullptr;
+ int split_box_offset_h = 0;
+ int split_box_offset_v = 0;
+
HashSet<String> favorites;
Button *button_toggle_display_mode = nullptr;
@@ -299,7 +303,7 @@ private:
void _set_scanning_mode();
void _rescan();
- void _toggle_split_mode(bool p_active);
+ void _change_split_mode();
void _search_changed(const String &p_text, const Control *p_from);
diff --git a/editor/icons/CodeRegionFoldDownArrow.svg b/editor/icons/CodeRegionFoldDownArrow.svg
index 3bc4f3f73b..744ea7197d 100644
--- a/editor/icons/CodeRegionFoldDownArrow.svg
+++ b/editor/icons/CodeRegionFoldDownArrow.svg
@@ -1 +1 @@
-<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M2 1a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H6V2a1 1 0 0 0-1-1zm1 5a1 1 0 0 1 1.414-1.414L6 6.172l1.586-1.586A1 1 0 0 1 9 6L6.707 8.293a1 1 0 0 1-1.414 0Z" fill="#fff"/></svg> \ No newline at end of file
+<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M10 3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1zM3 5.75a1 1 0 0 1 1.414-1.414L6 5.922l1.586-1.586A1 1 0 0 1 9 5.75L6.707 8.043a1 1 0 0 1-1.414 0z" fill="#fff"/></svg>
diff --git a/editor/icons/CodeRegionFoldedRightArrow.svg b/editor/icons/CodeRegionFoldedRightArrow.svg
index a9b81d54f3..245371b5a1 100644
--- a/editor/icons/CodeRegionFoldedRightArrow.svg
+++ b/editor/icons/CodeRegionFoldedRightArrow.svg
@@ -1 +1 @@
-<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M2 1a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H6V2a1 1 0 0 0-1-1zm3.5 8a1 1 0 0 1-1.414-1.414L5.672 6 4.086 4.414A1 1 0 0 1 5.5 3l2.293 2.293a1 1 0 0 1 0 1.414Z" fill="#fff"/></svg> \ No newline at end of file
+<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M3 2a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1zm2.75 7a1 1 0 0 1-1.414-1.414L5.922 6 4.336 4.414A1 1 0 0 1 5.75 3l2.293 2.293a1 1 0 0 1 0 1.414z" fill="#fff"/></svg>
diff --git a/editor/import_dock.cpp b/editor/import_dock.cpp
index d13c0e7f86..5996b459a5 100644
--- a/editor/import_dock.cpp
+++ b/editor/import_dock.cpp
@@ -499,9 +499,12 @@ void ImportDock::_reimport_attempt() {
String imported_with = config->get_value("remap", "importer");
if (imported_with != importer_name) {
- need_cleanup.push_back(params->paths[i]);
- if (_find_owners(EditorFileSystem::get_singleton()->get_filesystem(), params->paths[i])) {
- used_in_resources = true;
+ Ref<Resource> resource = ResourceLoader::load(params->paths[i]);
+ if (resource.is_valid()) {
+ need_cleanup.push_back(params->paths[i]);
+ if (_find_owners(EditorFileSystem::get_singleton()->get_filesystem(), params->paths[i])) {
+ used_in_resources = true;
+ }
}
}
}
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index 7b48e6fbe9..75c8ac11d0 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -112,6 +112,7 @@ void AnimationPlayerEditor::_notification(int p_what) {
}
last_active = player->is_playing();
+
updating = false;
} break;
@@ -942,11 +943,6 @@ void AnimationPlayerEditor::_update_player() {
onion_toggle->set_disabled(no_anims_found);
onion_skinning->set_disabled(no_anims_found);
- if (hack_disable_onion_skinning) {
- onion_toggle->set_disabled(true);
- onion_skinning->set_disabled(true);
- }
-
_update_animation_list_icons();
updating = false;
@@ -1150,33 +1146,33 @@ void AnimationPlayerEditor::forward_force_draw_over_viewport(Control *p_overlay)
float alpha_step = 1.0 / (onion.steps + 1);
- int cidx = 0;
+ uint32_t capture_idx = 0;
if (onion.past) {
- float alpha = 0;
+ float alpha = 0.0f;
do {
alpha += alpha_step;
- if (onion.captures_valid[cidx]) {
+ if (onion.captures_valid[capture_idx]) {
RS::get_singleton()->canvas_item_add_texture_rect_region(
- ci, dst_rect, RS::get_singleton()->viewport_get_texture(onion.captures[cidx]), src_rect, Color(1, 1, 1, alpha));
+ ci, dst_rect, RS::get_singleton()->viewport_get_texture(onion.captures[capture_idx]), src_rect, Color(1, 1, 1, alpha));
}
- cidx++;
- } while (cidx < onion.steps);
+ capture_idx++;
+ } while (capture_idx < onion.steps);
}
if (onion.future) {
- float alpha = 1;
- int base_cidx = cidx;
+ float alpha = 1.0f;
+ uint32_t base_cidx = capture_idx;
do {
alpha -= alpha_step;
- if (onion.captures_valid[cidx]) {
+ if (onion.captures_valid[capture_idx]) {
RS::get_singleton()->canvas_item_add_texture_rect_region(
- ci, dst_rect, RS::get_singleton()->viewport_get_texture(onion.captures[cidx]), src_rect, Color(1, 1, 1, alpha));
+ ci, dst_rect, RS::get_singleton()->viewport_get_texture(onion.captures[capture_idx]), src_rect, Color(1, 1, 1, alpha));
}
- cidx++;
- } while (cidx < base_cidx + onion.steps); // In case there's the present capture at the end, skip it.
+ capture_idx++;
+ } while (capture_idx < base_cidx + onion.steps); // In case there's the present capture at the end, skip it.
}
}
@@ -1266,7 +1262,7 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set, bool
if (!p_timeline_only) {
if (player->is_valid() && !p_set) {
- double delta = pos - player->get_current_animation_position();
+ double delta = player->get_current_animation_position();
player->seek(pos, true, true);
player->seek(pos + delta, true, true);
} else {
@@ -1394,7 +1390,10 @@ void AnimationPlayerEditor::_onion_skinning_menu(int p_option) {
onion.enabled = !onion.enabled;
if (onion.enabled) {
- _start_onion_skinning();
+ if (get_player() && !get_player()->has_animation(SceneStringNames::get_singleton()->RESET)) {
+ EditorNode::get_singleton()->show_warning(TTR("Onion skinning requires a RESET animation."));
+ }
+ _start_onion_skinning(); // It will check for RESET animation anyway.
} else {
_stop_onion_skinning();
}
@@ -1416,7 +1415,7 @@ void AnimationPlayerEditor::_onion_skinning_menu(int p_option) {
onion.steps = (p_option - ONION_SKINNING_1_STEP) + 1;
int one_frame_idx = menu->get_item_index(ONION_SKINNING_1_STEP);
for (int i = 0; i <= ONION_SKINNING_LAST_STEPS_OPTION - ONION_SKINNING_1_STEP; i++) {
- menu->set_item_checked(one_frame_idx + i, onion.steps == i + 1);
+ menu->set_item_checked(one_frame_idx + i, (int)onion.steps == i + 1);
}
} break;
case ONION_SKINNING_DIFFERENCES_ONLY: {
@@ -1475,15 +1474,15 @@ void AnimationPlayerEditor::_editor_visibility_changed() {
bool AnimationPlayerEditor::_are_onion_layers_valid() {
ERR_FAIL_COND_V(!onion.past && !onion.future, false);
- Point2 capture_size = get_tree()->get_root()->get_size();
- return onion.captures.size() == onion.get_needed_capture_count() && onion.capture_size == capture_size;
+ Size2 capture_size = DisplayServer::get_singleton()->window_get_size(DisplayServer::MAIN_WINDOW_ID);
+ return onion.captures.size() == onion.get_capture_count() && onion.capture_size == capture_size;
}
void AnimationPlayerEditor::_allocate_onion_layers() {
_free_onion_layers();
- int captures = onion.get_needed_capture_count();
- Point2 capture_size = get_tree()->get_root()->get_size();
+ int captures = onion.get_capture_count();
+ Size2 capture_size = DisplayServer::get_singleton()->window_get_size(DisplayServer::MAIN_WINDOW_ID);
onion.captures.resize(captures);
onion.captures_valid.resize(captures);
@@ -1492,7 +1491,7 @@ void AnimationPlayerEditor::_allocate_onion_layers() {
bool is_present = onion.differences_only && i == captures - 1;
// Each capture is a viewport with a canvas item attached that renders a full-size rect with the contents of the main viewport.
- onion.captures.write[i] = RS::get_singleton()->viewport_create();
+ onion.captures[i] = RS::get_singleton()->viewport_create();
RS::get_singleton()->viewport_set_size(onion.captures[i], capture_size.width, capture_size.height);
RS::get_singleton()->viewport_set_update_mode(onion.captures[i], RS::VIEWPORT_UPDATE_ALWAYS);
@@ -1502,13 +1501,13 @@ void AnimationPlayerEditor::_allocate_onion_layers() {
// Reset the capture canvas item to the current root viewport texture (defensive).
RS::get_singleton()->canvas_item_clear(onion.capture.canvas_item);
- RS::get_singleton()->canvas_item_add_texture_rect(onion.capture.canvas_item, Rect2(Point2(), capture_size), get_tree()->get_root()->get_texture()->get_rid());
+ RS::get_singleton()->canvas_item_add_texture_rect(onion.capture.canvas_item, Rect2(Point2(), Point2(capture_size.x, -capture_size.y)), get_tree()->get_root()->get_texture()->get_rid());
onion.capture_size = capture_size;
}
void AnimationPlayerEditor::_free_onion_layers() {
- for (int i = 0; i < onion.captures.size(); i++) {
+ for (uint32_t i = 0; i < onion.captures.size(); i++) {
if (onion.captures[i].is_valid()) {
RS::get_singleton()->free(onion.captures[i]);
}
@@ -1524,7 +1523,7 @@ void AnimationPlayerEditor::_prepare_onion_layers_1() {
return;
}
- if (!onion.enabled || !is_processing() || !is_visible() || !get_player()) {
+ if (!onion.enabled || !is_visible() || !get_player() || !get_player()->has_animation(SceneStringNames::get_singleton()->RESET)) {
_stop_onion_skinning();
return;
}
@@ -1540,14 +1539,10 @@ void AnimationPlayerEditor::_prepare_onion_layers_1() {
}
// And go to next step afterwards.
- call_deferred(SNAME("_prepare_onion_layers_2"));
+ callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_2_prolog).call_deferred();
}
-void AnimationPlayerEditor::_prepare_onion_layers_1_deferred() {
- call_deferred(SNAME("_prepare_onion_layers_1"));
-}
-
-void AnimationPlayerEditor::_prepare_onion_layers_2() {
+void AnimationPlayerEditor::_prepare_onion_layers_2_prolog() {
Ref<Animation> anim = player->get_animation(player->get_assigned_animation());
if (!anim.is_valid()) {
return;
@@ -1558,21 +1553,20 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
}
// Hide superfluous elements that would make the overlay unnecessary cluttered.
- Dictionary canvas_edit_state;
- Dictionary spatial_edit_state;
if (Node3DEditor::get_singleton()->is_visible()) {
// 3D
- spatial_edit_state = Node3DEditor::get_singleton()->get_state();
- Dictionary new_state = spatial_edit_state.duplicate();
+ onion.temp.spatial_edit_state = Node3DEditor::get_singleton()->get_state();
+ Dictionary new_state = onion.temp.spatial_edit_state.duplicate();
new_state["show_grid"] = false;
new_state["show_origin"] = false;
- Array orig_vp = spatial_edit_state["viewports"];
+ Array orig_vp = onion.temp.spatial_edit_state["viewports"];
Array vp;
vp.resize(4);
for (int i = 0; i < vp.size(); i++) {
Dictionary d = ((Dictionary)orig_vp[i]).duplicate();
d["use_environment"] = false;
d["doppler"] = false;
+ d["listener"] = false;
d["gizmos"] = onion.include_gizmos ? d["gizmos"] : Variant(false);
d["information"] = false;
vp[i] = d;
@@ -1580,23 +1574,27 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
new_state["viewports"] = vp;
// TODO: Save/restore only affected entries.
Node3DEditor::get_singleton()->set_state(new_state);
- } else { // CanvasItemEditor
- // 2D
- canvas_edit_state = CanvasItemEditor::get_singleton()->get_state();
- Dictionary new_state = canvas_edit_state.duplicate();
+ } else {
+ // CanvasItemEditor.
+ onion.temp.canvas_edit_state = CanvasItemEditor::get_singleton()->get_state();
+ Dictionary new_state = onion.temp.canvas_edit_state.duplicate();
+ new_state["show_origin"] = false;
new_state["show_grid"] = false;
new_state["show_rulers"] = false;
new_state["show_guides"] = false;
new_state["show_helpers"] = false;
new_state["show_zoom_control"] = false;
+ new_state["show_edit_locks"] = false;
+ new_state["grid_visibility"] = 2; // TODO: Expose CanvasItemEditor::GRID_VISIBILITY_HIDE somehow and use it.
+ new_state["show_transformation_gizmos"] = onion.include_gizmos ? new_state["gizmos"] : Variant(false);
// TODO: Save/restore only affected entries.
CanvasItemEditor::get_singleton()->set_state(new_state);
}
// Tweak the root viewport to ensure it's rendered before our target.
RID root_vp = get_tree()->get_root()->get_viewport_rid();
- Rect2 root_vp_screen_rect = Rect2(Vector2(), get_tree()->get_root()->get_size());
- RS::get_singleton()->viewport_attach_to_screen(root_vp, Rect2());
+ onion.temp.screen_rect = Rect2(Vector2(), DisplayServer::get_singleton()->window_get_size(DisplayServer::MAIN_WINDOW_ID));
+ RS::get_singleton()->viewport_attach_to_screen(root_vp, Rect2(), DisplayServer::INVALID_WINDOW_ID);
RS::get_singleton()->viewport_set_update_mode(root_vp, RS::VIEWPORT_UPDATE_ALWAYS);
RID present_rid;
@@ -1611,8 +1609,8 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
}
// Backup current animation state.
- Ref<AnimatedValuesBackup> backup_current = player->make_backup();
- float cpos = player->get_current_animation_position();
+ onion.temp.anim_values_backup = player->make_backup();
+ onion.temp.anim_player_position = player->get_current_animation_position();
// Render every past/future step with the capture shader.
@@ -1620,55 +1618,94 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
onion.capture.material->set_shader_parameter("bkg_color", GLOBAL_GET("rendering/environment/defaults/default_clear_color"));
onion.capture.material->set_shader_parameter("differences_only", onion.differences_only);
onion.capture.material->set_shader_parameter("present", onion.differences_only ? RS::get_singleton()->viewport_get_texture(present_rid) : RID());
-
- int step_off_a = onion.past ? -onion.steps : 0;
- int step_off_b = onion.future ? onion.steps : 0;
- int cidx = 0;
onion.capture.material->set_shader_parameter("dir_color", onion.force_white_modulate ? Color(1, 1, 1) : Color(EDITOR_GET("editors/animation/onion_layers_past_color")));
- for (int step_off = step_off_a; step_off <= step_off_b; step_off++) {
- if (step_off == 0) {
- // Skip present step and switch to the color of future.
- if (!onion.force_white_modulate) {
- onion.capture.material->set_shader_parameter("dir_color", EDITOR_GET("editors/animation/onion_layers_future_color"));
- }
- continue;
- }
- float pos = cpos + step_off * anim->get_step();
+ uint32_t p_capture_idx = 0;
+ int first_step_offset = onion.past ? -(int)onion.steps : 0;
+ _prepare_onion_layers_2_step_prepare(first_step_offset, p_capture_idx);
+}
+
+void AnimationPlayerEditor::_prepare_onion_layers_2_step_prepare(int p_step_offset, uint32_t p_capture_idx) {
+ uint32_t next_capture_idx = p_capture_idx;
+ if (p_step_offset == 0) {
+ // Skip present step and switch to the color of future.
+ if (!onion.force_white_modulate) {
+ onion.capture.material->set_shader_parameter("dir_color", EDITOR_GET("editors/animation/onion_layers_future_color"));
+ }
+ } else {
+ Ref<Animation> anim = player->get_animation(player->get_assigned_animation());
+ double pos = onion.temp.anim_player_position + p_step_offset * anim->get_step();
bool valid = anim->get_loop_mode() != Animation::LOOP_NONE || (pos >= 0 && pos <= anim->get_length());
- onion.captures_valid.write[cidx] = valid;
+ onion.captures_valid[p_capture_idx] = valid;
if (valid) {
player->seek(pos, true);
- get_tree()->flush_transform_notifications(); // Needed for transforms of Node3Ds.
-
- RS::get_singleton()->viewport_set_active(onion.captures[cidx], true);
- RS::get_singleton()->viewport_set_parent_viewport(root_vp, onion.captures[cidx]);
- RS::get_singleton()->draw(false);
- RS::get_singleton()->viewport_set_active(onion.captures[cidx], false);
+ OS::get_singleton()->get_main_loop()->process(0);
+ // This is the key: process the frame and let all callbacks/updates/notifications happen
+ // so everything (transforms, skeletons, etc.) is up-to-date visually.
+ callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_2_step_capture).bind(p_step_offset, p_capture_idx).call_deferred();
+ return;
+ } else {
+ next_capture_idx++;
}
+ }
+
+ int last_step_offset = onion.future ? onion.steps : 0;
+ if (p_step_offset < last_step_offset) {
+ _prepare_onion_layers_2_step_prepare(p_step_offset + 1, next_capture_idx);
+ } else {
+ _prepare_onion_layers_2_epilog();
+ }
+}
+
+void AnimationPlayerEditor::_prepare_onion_layers_2_step_capture(int p_step_offset, uint32_t p_capture_idx) {
+ DEV_ASSERT(p_step_offset != 0);
+ DEV_ASSERT(onion.captures_valid[p_capture_idx]);
- cidx++;
+ RID root_vp = get_tree()->get_root()->get_viewport_rid();
+ RS::get_singleton()->viewport_set_active(onion.captures[p_capture_idx], true);
+ RS::get_singleton()->viewport_set_parent_viewport(root_vp, onion.captures[p_capture_idx]);
+ RS::get_singleton()->draw(false);
+ RS::get_singleton()->viewport_set_active(onion.captures[p_capture_idx], false);
+
+ int last_step_offset = onion.future ? onion.steps : 0;
+ if (p_step_offset < last_step_offset) {
+ _prepare_onion_layers_2_step_prepare(p_step_offset + 1, p_capture_idx + 1);
+ } else {
+ _prepare_onion_layers_2_epilog();
}
+}
+void AnimationPlayerEditor::_prepare_onion_layers_2_epilog() {
// Restore root viewport.
+ RID root_vp = get_tree()->get_root()->get_viewport_rid();
RS::get_singleton()->viewport_set_parent_viewport(root_vp, RID());
- RS::get_singleton()->viewport_attach_to_screen(root_vp, root_vp_screen_rect);
+ RS::get_singleton()->viewport_attach_to_screen(root_vp, onion.temp.screen_rect, DisplayServer::MAIN_WINDOW_ID);
RS::get_singleton()->viewport_set_update_mode(root_vp, RS::VIEWPORT_UPDATE_WHEN_VISIBLE);
- // Restore animation state
- // (Seeking with update=true wouldn't do the trick because the current value of the properties
- // may not match their value for the current point in the animation).
- player->seek(cpos, false);
- player->restore(backup_current);
+ // Restore animation state.
+ // Here we're combine the power of seeking back to the original position and
+ // restoring the values backup. In most cases they will bring the same value back,
+ // but there are cases handled by one that the other can't.
+ // Namely:
+ // - Seeking won't restore any values that may have been modified by the user
+ // in the node after the last time the AnimationPlayer updated it.
+ // - Restoring the backup won't account for values that are not directly involved
+ // in the animation but a consequence of them (e.g., SkeletonModification2DLookAt).
+ // FIXME: Since backup of values is based on the reset animation, only values
+ // backed by a proper reset animation will work correctly with onion
+ // skinning and the possibility to restore the values mentioned in the
+ // first point above is gone. Still good enough.
+ player->seek(onion.temp.anim_player_position, true, true);
+ player->restore(onion.temp.anim_values_backup);
// Restore state of main editors.
if (Node3DEditor::get_singleton()->is_visible()) {
// 3D
- Node3DEditor::get_singleton()->set_state(spatial_edit_state);
+ Node3DEditor::get_singleton()->set_state(onion.temp.spatial_edit_state);
} else { // CanvasItemEditor
// 2D
- CanvasItemEditor::get_singleton()->set_state(canvas_edit_state);
+ CanvasItemEditor::get_singleton()->set_state(onion.temp.canvas_edit_state);
}
// Update viewports with skin layers overlaid for the actual engine loop render.
@@ -1677,21 +1714,26 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
}
void AnimationPlayerEditor::_start_onion_skinning() {
- // FIXME: Using "process_frame" makes onion layers update one frame behind the current.
- if (!get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) {
- get_tree()->connect(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred));
+ if (get_player() && !get_player()->has_animation(SceneStringNames::get_singleton()->RESET)) {
+ onion.enabled = false;
+ onion_toggle->set_pressed_no_signal(false);
+ return;
+ }
+ if (!get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1))) {
+ get_tree()->connect(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1));
}
}
void AnimationPlayerEditor::_stop_onion_skinning() {
- if (get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) {
- get_tree()->disconnect(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred));
+ if (get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1))) {
+ get_tree()->disconnect(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1));
_free_onion_layers();
- // Clean up the overlay.
+ // Clean up.
onion.can_overlay = false;
plugin->update_overlays();
+ onion.temp = {};
}
}
@@ -1773,8 +1815,6 @@ void AnimationPlayerEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_list_changed"), &AnimationPlayerEditor::_list_changed);
ClassDB::bind_method(D_METHOD("_animation_duplicate"), &AnimationPlayerEditor::_animation_duplicate);
- ClassDB::bind_method(D_METHOD("_prepare_onion_layers_1"), &AnimationPlayerEditor::_prepare_onion_layers_1);
- ClassDB::bind_method(D_METHOD("_prepare_onion_layers_2"), &AnimationPlayerEditor::_prepare_onion_layers_2);
ClassDB::bind_method(D_METHOD("_start_onion_skinning"), &AnimationPlayerEditor::_start_onion_skinning);
ClassDB::bind_method(D_METHOD("_stop_onion_skinning"), &AnimationPlayerEditor::_stop_onion_skinning);
@@ -1914,16 +1954,6 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
onion_skinning->get_popup()->add_check_item(TTR("Include Gizmos (3D)"), ONION_SKINNING_INCLUDE_GIZMOS);
hb->add_child(onion_skinning);
- // FIXME: Onion skinning disabled for now as it's broken and triggers fast
- // flickering red/blue modulation (GH-53870).
- if (hack_disable_onion_skinning) {
- onion_toggle->set_disabled(true);
- onion_toggle->set_tooltip_text(TTR("Onion Skinning temporarily disabled due to rendering bug."));
-
- onion_skinning->set_disabled(true);
- onion_skinning->set_tooltip_text(TTR("Onion Skinning temporarily disabled due to rendering bug."));
- }
-
hb->add_child(memnew(VSeparator));
pin = memnew(Button);
@@ -2013,24 +2043,13 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
track_editor->connect(SNAME("visibility_changed"), callable_mp(this, &AnimationPlayerEditor::_editor_visibility_changed));
- onion.enabled = false;
- onion.past = true;
- onion.future = false;
- onion.steps = 1;
- onion.differences_only = false;
- onion.force_white_modulate = false;
- onion.include_gizmos = false;
-
- onion.last_frame = 0;
- onion.can_overlay = false;
- onion.capture_size = Size2();
onion.capture.canvas = RS::get_singleton()->canvas_create();
onion.capture.canvas_item = RS::get_singleton()->canvas_item_create();
RS::get_singleton()->canvas_item_set_parent(onion.capture.canvas_item, onion.capture.canvas);
- onion.capture.material = Ref<ShaderMaterial>(memnew(ShaderMaterial));
+ onion.capture.material.instantiate();
- onion.capture.shader = Ref<Shader>(memnew(Shader));
+ onion.capture.shader.instantiate();
onion.capture.shader->set_code(R"(
// Animation editor onion skinning shader.
@@ -2047,10 +2066,15 @@ float zero_if_equal(vec4 a, vec4 b) {
void fragment() {
vec4 capture_samp = texture(TEXTURE, UV);
- vec4 present_samp = texture(present, UV);
float bkg_mask = zero_if_equal(capture_samp, bkg_color);
- float diff_mask = 1.0 - zero_if_equal(present_samp, bkg_color);
- diff_mask = min(1.0, diff_mask + float(!differences_only));
+ float diff_mask = 1.0;
+ if (differences_only) {
+ // FIXME: If Y-flips across render target, canvas item, etc. was handled correctly,
+ // this would not be as convoluted in the shader.
+ vec4 capture_samp2 = texture(TEXTURE, vec2(UV.x, 1.0 - UV.y));
+ vec4 present_samp = texture(present, vec2(UV.x, 1.0 - UV.y));
+ diff_mask = 1.0 - zero_if_equal(present_samp, bkg_color);
+ }
COLOR = vec4(capture_samp.rgb * dir_color.rgb, bkg_mask * diff_mask);
}
)");
@@ -2061,6 +2085,7 @@ AnimationPlayerEditor::~AnimationPlayerEditor() {
_free_onion_layers();
RS::get_singleton()->free(onion.capture.canvas);
RS::get_singleton()->free(onion.capture.canvas_item);
+ onion.capture = {};
}
void AnimationPlayerEditorPlugin::_notification(int p_what) {
diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h
index 4763a008fe..6751933839 100644
--- a/editor/plugins/animation_player_editor_plugin.h
+++ b/editor/plugins/animation_player_editor_plugin.h
@@ -136,20 +136,18 @@ class AnimationPlayerEditor : public VBoxContainer {
AnimationTrackEditor *track_editor = nullptr;
static AnimationPlayerEditor *singleton;
- bool hack_disable_onion_skinning = true; // Temporary hack for GH-53870.
-
// Onion skinning.
struct {
// Settings.
bool enabled = false;
- bool past = false;
+ bool past = true;
bool future = false;
- int steps = 0;
+ uint32_t steps = 1;
bool differences_only = false;
bool force_white_modulate = false;
bool include_gizmos = false;
- int get_needed_capture_count() const {
+ uint32_t get_capture_count() const {
// 'Differences only' needs a capture of the present.
return (past && future ? 2 * steps : steps) + (differences_only ? 1 : 0);
}
@@ -158,14 +156,23 @@ class AnimationPlayerEditor : public VBoxContainer {
int64_t last_frame = 0;
int can_overlay = 0;
Size2 capture_size;
- Vector<RID> captures;
- Vector<bool> captures_valid;
+ LocalVector<RID> captures;
+ LocalVector<bool> captures_valid;
struct {
RID canvas;
RID canvas_item;
Ref<ShaderMaterial> material;
Ref<Shader> shader;
} capture;
+
+ // Cross-call state.
+ struct {
+ double anim_player_position = 0.0;
+ Ref<AnimatedValuesBackup> anim_values_backup;
+ Rect2 screen_rect;
+ Dictionary canvas_edit_state;
+ Dictionary spatial_edit_state;
+ } temp;
} onion;
void _select_anim_by_name(const String &p_anim);
@@ -215,8 +222,10 @@ class AnimationPlayerEditor : public VBoxContainer {
void _allocate_onion_layers();
void _free_onion_layers();
void _prepare_onion_layers_1();
- void _prepare_onion_layers_1_deferred();
- void _prepare_onion_layers_2();
+ void _prepare_onion_layers_2_prolog();
+ void _prepare_onion_layers_2_step_prepare(int p_step_offset, uint32_t p_capture_idx);
+ void _prepare_onion_layers_2_step_capture(int p_step_offset, uint32_t p_capture_idx);
+ void _prepare_onion_layers_2_epilog();
void _start_onion_skinning();
void _stop_onion_skinning();
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 6806deff67..4bfa6adaae 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -2276,6 +2276,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {
NodePath root_path = get_tree()->get_edited_scene_root()->get_path();
StringName root_name = root_path.get_name(root_path.get_name_count() - 1);
+ int icon_max_width = EditorNode::get_singleton()->get_editor_theme()->get_constant(SNAME("class_icon_size"), EditorStringName(Editor));
for (int i = 0; i < selection_results.size(); i++) {
CanvasItem *item = selection_results[i].item;
@@ -2307,6 +2308,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {
}
selection_menu->add_item((String)item->get_name() + suffix);
selection_menu->set_item_icon(i, icon);
+ selection_menu->set_item_icon_max_width(i, icon_max_width);
selection_menu->set_item_metadata(i, node_path);
selection_menu->set_item_tooltip(i, String(item->get_name()) + "\nType: " + item->get_class() + "\nPath: " + node_path);
}
@@ -3653,6 +3655,7 @@ void CanvasItemEditor::_draw_invisible_nodes_positions(Node *p_node, const Trans
void CanvasItemEditor::_draw_hover() {
List<Rect2> previous_rects;
+ Vector2 icon_size = Vector2(1, 1) * get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
for (int i = 0; i < hovering_results.size(); i++) {
Ref<Texture2D> node_icon = hovering_results[i].icon;
@@ -3661,9 +3664,9 @@ void CanvasItemEditor::_draw_hover() {
Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
Size2 node_name_size = font->get_string_size(node_name, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);
- Size2 item_size = Size2(node_icon->get_size().x + 4 + node_name_size.x, MAX(node_icon->get_size().y, node_name_size.y - 3));
+ Size2 item_size = Size2(icon_size.x + 4 + node_name_size.x, MAX(icon_size.y, node_name_size.y - 3));
- Point2 pos = transform.xform(hovering_results[i].position) - Point2(0, item_size.y) + (Point2(node_icon->get_size().x, -node_icon->get_size().y) / 4);
+ Point2 pos = transform.xform(hovering_results[i].position) - Point2(0, item_size.y) + (Point2(icon_size.x, -icon_size.y) / 4);
// Rectify the position to avoid overlapping items
for (const Rect2 &E : previous_rects) {
if (E.intersects(Rect2(pos, item_size))) {
@@ -3674,10 +3677,10 @@ void CanvasItemEditor::_draw_hover() {
previous_rects.push_back(Rect2(pos, item_size));
// Draw icon
- viewport->draw_texture(node_icon, pos, Color(1.0, 1.0, 1.0, 0.5));
+ viewport->draw_texture_rect(node_icon, Rect2(pos, icon_size), false, Color(1.0, 1.0, 1.0, 0.5));
// Draw name
- viewport->draw_string(font, pos + Point2(node_icon->get_size().x + 4, item_size.y - 3), node_name, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1.0, 1.0, 1.0, 0.5));
+ viewport->draw_string(font, pos + Point2(icon_size.x + 4, item_size.y - 3), node_name, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1.0, 1.0, 1.0, 0.5));
}
}
@@ -5502,6 +5505,7 @@ CanvasItemEditor::CanvasItemEditor() {
selection_menu = memnew(PopupMenu);
add_child(selection_menu);
selection_menu->set_min_size(Vector2(100, 0));
+ selection_menu->set_auto_translate(false);
selection_menu->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_selection_result_pressed));
selection_menu->connect("popup_hide", callable_mp(this, &CanvasItemEditor::_selection_menu_hide), CONNECT_DEFERRED);
diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp
index 4185460126..9d66e606b0 100644
--- a/editor/plugins/path_3d_editor_plugin.cpp
+++ b/editor/plugins/path_3d_editor_plugin.cpp
@@ -274,6 +274,7 @@ void Path3DGizmo::redraw() {
Ref<StandardMaterial3D> path_material = gizmo_plugin->get_material("path_material", this);
Ref<StandardMaterial3D> path_thin_material = gizmo_plugin->get_material("path_thin_material", this);
+ Ref<StandardMaterial3D> path_tilt_material = gizmo_plugin->get_material("path_tilt_material", this);
Ref<StandardMaterial3D> handles_material = gizmo_plugin->get_material("handles");
Ref<StandardMaterial3D> sec_handles_material = gizmo_plugin->get_material("sec_handles");
@@ -339,6 +340,7 @@ void Path3DGizmo::redraw() {
// 2. Draw handles when selected.
if (Path3DEditorPlugin::singleton->get_edited_path() == path) {
PackedVector3Array handle_lines;
+ PackedVector3Array tilt_handle_lines;
PackedVector3Array primary_handle_points;
PackedVector3Array secondary_handle_points;
PackedInt32Array collected_secondary_handle_ids; // Avoid shadowing member on Node3DEditorGizmo.
@@ -367,7 +369,7 @@ void Path3DGizmo::redraw() {
}
// Collect out-handles except for the last point.
- if (idx < c->get_point_count()) {
+ if (idx < c->get_point_count() - 1) {
info.type = HandleType::HANDLE_TYPE_OUT;
const int handle_idx = idx * 3 + 1;
collected_secondary_handle_ids.append(handle_idx);
@@ -389,9 +391,9 @@ void Path3DGizmo::redraw() {
const Basis posture = c->get_point_baked_posture(idx, true);
const Vector3 up = posture.get_column(1);
- secondary_handle_points.append(pos + up);
- handle_lines.append(pos);
- handle_lines.append(pos + up);
+ secondary_handle_points.append(pos + up * disk_size);
+ tilt_handle_lines.append(pos);
+ tilt_handle_lines.append(pos + up * disk_size);
}
// Tilt disk.
@@ -403,13 +405,13 @@ void Path3DGizmo::redraw() {
PackedVector3Array disk;
disk.append(pos);
- const int n = 24;
+ const int n = 36;
for (int i = 0; i <= n; i++) {
const float a = Math_TAU * i / n;
const Vector3 edge = sin(a) * side + cos(a) * up;
- disk.append(pos + edge);
+ disk.append(pos + edge * disk_size);
}
- add_vertices(disk, path_material, Mesh::PRIMITIVE_LINE_STRIP);
+ add_vertices(disk, path_tilt_material, Mesh::PRIMITIVE_LINE_STRIP);
}
}
}
@@ -417,6 +419,11 @@ void Path3DGizmo::redraw() {
if (handle_lines.size() > 1) {
add_lines(handle_lines, path_thin_material);
}
+
+ if (tilt_handle_lines.size() > 1) {
+ add_lines(tilt_handle_lines, path_tilt_material);
+ }
+
if (primary_handle_points.size()) {
add_handles(primary_handle_points, handles_material);
}
@@ -426,8 +433,9 @@ void Path3DGizmo::redraw() {
}
}
-Path3DGizmo::Path3DGizmo(Path3D *p_path) {
+Path3DGizmo::Path3DGizmo(Path3D *p_path, float p_disk_size) {
path = p_path;
+ disk_size = p_disk_size;
set_node_3d(p_path);
orig_in_length = 0;
orig_out_length = 0;
@@ -555,7 +563,7 @@ EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_3d_gui_input(Camera3D *p
real_t dist_to_p = p_camera->unproject_position(gt.xform(c->get_point_position(i))).distance_to(mbpos);
real_t dist_to_p_out = p_camera->unproject_position(gt.xform(c->get_point_position(i) + c->get_point_out(i))).distance_to(mbpos);
real_t dist_to_p_in = p_camera->unproject_position(gt.xform(c->get_point_position(i) + c->get_point_in(i))).distance_to(mbpos);
- real_t dist_to_p_up = p_camera->unproject_position(gt.xform(c->get_point_position(i) + c->get_point_baked_posture(i, true).get_column(1))).distance_to(mbpos);
+ real_t dist_to_p_up = p_camera->unproject_position(gt.xform(c->get_point_position(i) + c->get_point_baked_posture(i, true).get_column(1) * disk_size)).distance_to(mbpos);
// Find the offset and point index of the place to break up.
// Also check for the control points.
@@ -721,8 +729,9 @@ Path3DEditorPlugin::Path3DEditorPlugin() {
mirror_handle_angle = true;
mirror_handle_length = true;
- Ref<Path3DGizmoPlugin> gizmo_plugin;
- gizmo_plugin.instantiate();
+ disk_size = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_settings/path3d_tilt_disk_size", 0.8);
+
+ Ref<Path3DGizmoPlugin> gizmo_plugin = memnew(Path3DGizmoPlugin(disk_size));
Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin);
topmenu_bar = memnew(HBoxContainer);
@@ -789,7 +798,7 @@ Ref<EditorNode3DGizmo> Path3DGizmoPlugin::create_gizmo(Node3D *p_spatial) {
Path3D *path = Object::cast_to<Path3D>(p_spatial);
if (path) {
- ref = Ref<Path3DGizmo>(memnew(Path3DGizmo(path)));
+ ref = Ref<Path3DGizmo>(memnew(Path3DGizmo(path, disk_size)));
}
return ref;
@@ -803,10 +812,14 @@ int Path3DGizmoPlugin::get_priority() const {
return -1;
}
-Path3DGizmoPlugin::Path3DGizmoPlugin() {
- Color path_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/path", Color(0.5, 0.5, 1.0, 0.8));
+Path3DGizmoPlugin::Path3DGizmoPlugin(float p_disk_size) {
+ Color path_color = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/path", Color(0.5, 0.5, 1.0, 0.9));
+ Color path_tilt_color = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/path_tilt", Color(1.0, 1.0, 0.4, 0.9));
+ disk_size = p_disk_size;
+
create_material("path_material", path_color);
- create_material("path_thin_material", Color(0.5, 0.5, 0.5));
+ create_material("path_thin_material", Color(0.6, 0.6, 0.6));
+ create_material("path_tilt_material", path_tilt_color);
create_handle_material("handles", false, EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("EditorPathSmoothHandle"), EditorStringName(EditorIcons)));
create_handle_material("sec_handles", false, EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("EditorCurveHandle"), EditorStringName(EditorIcons)));
}
diff --git a/editor/plugins/path_3d_editor_plugin.h b/editor/plugins/path_3d_editor_plugin.h
index 871e6a1563..5a5f76b015 100644
--- a/editor/plugins/path_3d_editor_plugin.h
+++ b/editor/plugins/path_3d_editor_plugin.h
@@ -58,6 +58,7 @@ class Path3DGizmo : public EditorNode3DGizmo {
mutable Vector3 original;
mutable float orig_in_length;
mutable float orig_out_length;
+ mutable float disk_size = 0.8;
// Cache information of secondary handles.
Vector<HandleInfo> _secondary_handles_info;
@@ -69,19 +70,21 @@ public:
virtual void commit_handle(int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override;
virtual void redraw() override;
- Path3DGizmo(Path3D *p_path = nullptr);
+ Path3DGizmo(Path3D *p_path = nullptr, float p_disk_size = 0.8);
};
class Path3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(Path3DGizmoPlugin, EditorNode3DGizmoPlugin);
+ float disk_size = 0.8;
+
protected:
Ref<EditorNode3DGizmo> create_gizmo(Node3D *p_spatial) override;
public:
String get_gizmo_name() const override;
int get_priority() const override;
- Path3DGizmoPlugin();
+ Path3DGizmoPlugin(float p_disk_size);
};
class Path3DEditorPlugin : public EditorPlugin {
@@ -95,6 +98,8 @@ class Path3DEditorPlugin : public EditorPlugin {
Button *curve_close = nullptr;
MenuButton *handle_menu = nullptr;
+ float disk_size = 0.8;
+
enum Mode {
MODE_CREATE,
MODE_EDIT,
diff --git a/editor/project_converter_3_to_4.cpp b/editor/project_converter_3_to_4.cpp
index d6ed5eb995..bea865af13 100644
--- a/editor/project_converter_3_to_4.cpp
+++ b/editor/project_converter_3_to_4.cpp
@@ -113,7 +113,7 @@ public:
// GDScript keywords.
RegEx keyword_gdscript_tool = RegEx("^tool");
RegEx keyword_gdscript_export_single = RegEx("^export");
- RegEx keyword_gdscript_export_mutli = RegEx("([\t]+)export\\b");
+ RegEx keyword_gdscript_export_multi = RegEx("([\t]+)export\\b");
RegEx keyword_gdscript_onready = RegEx("^onready");
RegEx keyword_gdscript_remote = RegEx("^remote func");
RegEx keyword_gdscript_remotesync = RegEx("^remotesync func");
@@ -2548,7 +2548,7 @@ void ProjectConverter3To4::rename_gdscript_keywords(Vector<SourceLine> &source_l
line = reg_container.keyword_gdscript_export_single.sub(line, "@export", true);
}
if (line.contains("export")) {
- line = reg_container.keyword_gdscript_export_mutli.sub(line, "$1@export", true);
+ line = reg_container.keyword_gdscript_export_multi.sub(line, "$1@export", true);
}
if (line.contains("onready")) {
line = reg_container.keyword_gdscript_onready.sub(line, "@onready", true);
@@ -2607,7 +2607,7 @@ Vector<String> ProjectConverter3To4::check_for_rename_gdscript_keywords(Vector<S
if (line.contains("export")) {
old = line;
- line = reg_container.keyword_gdscript_export_mutli.sub(line, "@export", true);
+ line = reg_container.keyword_gdscript_export_multi.sub(line, "@export", true);
if (old != line) {
found_renames.append(line_formatter(current_line, "export", "@export", line));
}
diff --git a/editor/property_selector.cpp b/editor/property_selector.cpp
index 5228db03b9..8c0a5b999a 100644
--- a/editor/property_selector.cpp
+++ b/editor/property_selector.cpp
@@ -370,46 +370,15 @@ void PropertySelector::_item_selected() {
class_type = instance->get_class();
}
- DocTools *dd = EditorHelp::get_doc_data();
String text;
- if (properties) {
- while (!class_type.is_empty()) {
- HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(class_type);
- if (E) {
- for (int i = 0; i < E->value.properties.size(); i++) {
- if (E->value.properties[i].name == name) {
- text = DTR(E->value.properties[i].description);
- break;
- }
- }
- }
-
- if (!text.is_empty()) {
- break;
- }
-
- // The property may be from a parent class, keep looking.
- class_type = ClassDB::get_parent_class(class_type);
+ while (!class_type.is_empty()) {
+ text = properties ? help_bit->get_property_description(class_type, name) : help_bit->get_method_description(class_type, name);
+ if (!text.is_empty()) {
+ break;
}
- } else {
- while (!class_type.is_empty()) {
- HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(class_type);
- if (E) {
- for (int i = 0; i < E->value.methods.size(); i++) {
- if (E->value.methods[i].name == name) {
- text = DTR(E->value.methods[i].description);
- break;
- }
- }
- }
- if (!text.is_empty()) {
- break;
- }
-
- // The method may be from a parent class, keep looking.
- class_type = ClassDB::get_parent_class(class_type);
- }
+ // It may be from a parent class, keep looking.
+ class_type = ClassDB::get_parent_class(class_type);
}
if (!text.is_empty()) {
diff --git a/editor/renames_map_3_to_4.cpp b/editor/renames_map_3_to_4.cpp
index 1908e877e7..c44be7e685 100644
--- a/editor/renames_map_3_to_4.cpp
+++ b/editor/renames_map_3_to_4.cpp
@@ -1071,6 +1071,7 @@ const char *RenamesMap3To4::gdscript_properties_renames[][2] = {
// Would need bespoke solution.
// { "autowrap", "autowrap_mode" }, // Label -- Changed from bool to enum.
+ // { "extents", "size" }, // BoxShape3D, LightmapGI, ReflectionProbe
// { "frames", "sprite_frames" }, // AnimatedSprite2D, AnimatedSprite3D -- GH-73696
// { "percent_visible, "show_percentage }, // ProgressBar -- Breaks Label and RichTextLabel.
// { "pressed", "button_pressed" }, // BaseButton -- Would also rename the signal.
@@ -1098,7 +1099,6 @@ const char *RenamesMap3To4::gdscript_properties_renames[][2] = {
{ "drag_margin_top", "drag_top_margin" }, // Camera2D
{ "drag_margin_v_enabled", "drag_vertical_enabled" }, // Camera2D
{ "enabled_focus_mode", "focus_mode" }, // BaseButton - Removed
- { "extents", "size" }, // BoxShape3D, LightmapGI, ReflectionProbe
{ "extra_spacing_bottom", "spacing_bottom" }, // Font
{ "extra_spacing_top", "spacing_top" }, // Font
{ "focus_neighbour_bottom", "focus_neighbor_bottom" }, // Control
@@ -1198,7 +1198,6 @@ const char *RenamesMap3To4::csharp_properties_renames[][2] = {
{ "DragMarginTop", "DragTopMargin" }, // Camera2D
{ "DragMarginVEnabled", "DragVerticalEnabled" }, // Camera2D
{ "EnabledFocusMode", "FocusMode" }, // BaseButton - Removed
- { "Extents", "Size" }, // BoxShape3D, LightmapGI, ReflectionProbe
{ "ExtraSpacingBottom", "SpacingBottom" }, // Font
{ "ExtraSpacingTop", "SpacingTop" }, // Font
{ "FocusNeighbourBottom", "FocusNeighborBottom" }, // Control
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index a83dedad6d..e7727aec4b 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -1683,6 +1683,32 @@ bool SceneTreeDock::_check_node_path_recursive(Node *p_root_node, Variant &r_var
}
} break;
+ case Variant::OBJECT: {
+ Resource *resource = Object::cast_to<Resource>(r_variant);
+ if (!resource) {
+ break;
+ }
+
+ List<PropertyInfo> properties;
+ resource->get_property_list(&properties);
+
+ for (const PropertyInfo &E : properties) {
+ if (!(E.usage & (PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR))) {
+ continue;
+ }
+ String propertyname = E.name;
+ Variant old_variant = resource->get(propertyname);
+ Variant updated_variant = old_variant;
+ if (_check_node_path_recursive(p_root_node, updated_variant, p_renames)) {
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->add_do_property(resource, propertyname, updated_variant);
+ undo_redo->add_undo_property(resource, propertyname, old_variant);
+ resource->set(propertyname, updated_variant);
+ }
+ }
+ break;
+ };
+
default: {
}
}
diff --git a/main/main.cpp b/main/main.cpp
index f0a05fcd63..8e217575c1 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -1746,21 +1746,31 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
{
String driver_hints = "";
String driver_hints_angle = "";
+ String driver_hints_egl = "";
#ifdef GLES3_ENABLED
driver_hints = "opengl3";
driver_hints_angle = "opengl3,opengl3_angle";
+ driver_hints_egl = "opengl3,opengl3_es";
#endif
String default_driver = driver_hints.get_slice(",", 0);
+ String default_driver_macos = default_driver;
+#if defined(GLES3_ENABLED) && defined(EGL_STATIC) && defined(MACOS_ENABLED)
+ default_driver_macos = "opengl3_angle"; // Default to ANGLE if it's built-in.
+#endif
GLOBAL_DEF_RST("rendering/gl_compatibility/driver", default_driver);
GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.windows", PROPERTY_HINT_ENUM, driver_hints_angle), default_driver);
- GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.linuxbsd", PROPERTY_HINT_ENUM, driver_hints), default_driver);
+ GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.linuxbsd", PROPERTY_HINT_ENUM, driver_hints_egl), default_driver);
GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.web", PROPERTY_HINT_ENUM, driver_hints), default_driver);
GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.android", PROPERTY_HINT_ENUM, driver_hints), default_driver);
GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.ios", PROPERTY_HINT_ENUM, driver_hints), default_driver);
- GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.macos", PROPERTY_HINT_ENUM, driver_hints_angle), default_driver);
+ GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.macos", PROPERTY_HINT_ENUM, driver_hints_angle), default_driver_macos);
+
GLOBAL_DEF_RST("rendering/gl_compatibility/nvidia_disable_threaded_optimization", true);
+ GLOBAL_DEF_RST("rendering/gl_compatibility/fallback_to_angle", true);
+
+ GLOBAL_DEF_RST(PropertyInfo(Variant::ARRAY, "rendering/gl_compatibility/force_angle_on_devices", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::DICTIONARY, PROPERTY_HINT_NONE, String())), Array());
}
// Start with RenderingDevice-based backends. Should be included if any RD driver present.
@@ -1834,7 +1844,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
// Set a default renderer if none selected. Try to choose one that matches the driver.
if (rendering_method.is_empty()) {
- if (rendering_driver == "opengl3" || rendering_driver == "opengl3_angle") {
+ if (rendering_driver == "opengl3" || rendering_driver == "opengl3_angle" || rendering_driver == "opengl3_es") {
rendering_method = "gl_compatibility";
} else {
rendering_method = "forward_plus";
@@ -1853,6 +1863,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
if (rendering_method == "gl_compatibility") {
available_drivers.push_back("opengl3");
available_drivers.push_back("opengl3_angle");
+ available_drivers.push_back("opengl3_es");
}
#endif
if (available_drivers.is_empty()) {
@@ -2113,6 +2124,10 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
GLOBAL_DEF_BASIC("xr/openxr/submit_depth_buffer", false);
GLOBAL_DEF_BASIC("xr/openxr/startup_alert", true);
+ // OpenXR project extensions settings.
+ GLOBAL_DEF_BASIC("xr/openxr/extensions/hand_tracking", true);
+ GLOBAL_DEF_BASIC("xr/openxr/extensions/eye_gaze_interaction", false);
+
#ifdef TOOLS_ENABLED
// Disabled for now, using XR inside of the editor we'll be working on during the coming months.
diff --git a/modules/bmp/image_loader_bmp.cpp b/modules/bmp/image_loader_bmp.cpp
index 5605255367..72b540496d 100644
--- a/modules/bmp/image_loader_bmp.cpp
+++ b/modules/bmp/image_loader_bmp.cpp
@@ -167,7 +167,7 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
// Next we apply some color scaling going from a variable value space to a 256 value space.
// This may be simplified some but left as is for legibility.
- // float scaled_value = unscaled_value * byte_max_value / color_channel_maxium_value + rounding_offset;
+ // float scaled_value = unscaled_value * byte_max_value / color_channel_maximum_value + rounding_offset;
float f0 = b0 * 255.0f / static_cast<float>(p_header.bmp_bitfield.red_max) + 0.5f;
float f1 = b1 * 255.0f / static_cast<float>(p_header.bmp_bitfield.green_max) + 0.5f;
float f2 = b2 * 255.0f / static_cast<float>(p_header.bmp_bitfield.blue_max) + 0.5f;
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 806ab1e3df..4fec745999 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -601,8 +601,8 @@
@icon("res://path/to/class/icon.svg")
[/codeblock]
[b]Note:[/b] Only the script can have a custom icon. Inner classes are not supported.
- [b]Note:[/b] As annotations describe their subject, the [code]@icon[/code] annotation must be placed before the class definition and inheritance.
- [b]Note:[/b] Unlike other annotations, the argument of the [code]@icon[/code] annotation must be a string literal (constant expressions are not supported).
+ [b]Note:[/b] As annotations describe their subject, the [annotation @icon] annotation must be placed before the class definition and inheritance.
+ [b]Note:[/b] Unlike other annotations, the argument of the [annotation @icon] annotation must be a string literal (constant expressions are not supported).
</description>
</annotation>
<annotation name="@onready">
@@ -653,7 +653,7 @@
@tool
extends Node
[/codeblock]
- [b]Note:[/b] As annotations describe their subject, the [code]@tool[/code] annotation must be placed before the class definition and inheritance.
+ [b]Note:[/b] As annotations describe their subject, the [annotation @tool] annotation must be placed before the class definition and inheritance.
</description>
</annotation>
<annotation name="@warning_ignore" qualifiers="vararg">
diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp
index 0b440274c0..cffd661261 100644
--- a/modules/gdscript/editor/gdscript_docgen.cpp
+++ b/modules/gdscript/editor/gdscript_docgen.cpp
@@ -32,14 +32,11 @@
#include "../gdscript.h"
-using GDP = GDScriptParser;
-using GDType = GDP::DataType;
-
-static String _get_script_path(const String &p_path) {
+String GDScriptDocGen::_get_script_path(const String &p_path) {
return p_path.trim_prefix("res://").quote();
}
-static String _get_class_name(const GDP::ClassNode &p_class) {
+String GDScriptDocGen::_get_class_name(const GDP::ClassNode &p_class) {
const GDP::ClassNode *curr_class = &p_class;
if (!curr_class->identifier) { // All inner classes have an identifier, so this is the outer class.
return _get_script_path(curr_class->fqcn);
@@ -56,7 +53,7 @@ static String _get_class_name(const GDP::ClassNode &p_class) {
return full_name;
}
-static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return = false) {
+void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return) {
if (!p_gdtype.is_hard_type()) {
r_type = "Variant";
return;
@@ -82,9 +79,18 @@ static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String
r_type = Variant::get_type_name(p_gdtype.builtin_type);
return;
case GDType::NATIVE:
+ if (p_gdtype.is_meta_type) {
+ //r_type = GDScriptNativeClass::get_class_static();
+ r_type = "Object"; // "GDScriptNativeClass" refers to a blank page.
+ return;
+ }
r_type = p_gdtype.native_type;
return;
case GDType::SCRIPT:
+ if (p_gdtype.is_meta_type) {
+ r_type = p_gdtype.script_type.is_valid() ? p_gdtype.script_type->get_class() : Script::get_class_static();
+ return;
+ }
if (p_gdtype.script_type.is_valid()) {
if (p_gdtype.script_type->get_global_name() != StringName()) {
r_type = p_gdtype.script_type->get_global_name();
@@ -102,9 +108,17 @@ static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String
r_type = "Object";
return;
case GDType::CLASS:
+ if (p_gdtype.is_meta_type) {
+ r_type = GDScript::get_class_static();
+ return;
+ }
r_type = _get_class_name(*p_gdtype.class_type);
return;
case GDType::ENUM:
+ if (p_gdtype.is_meta_type) {
+ r_type = "Dictionary";
+ return;
+ }
r_type = "int";
r_enum = String(p_gdtype.native_type).replace("::", ".");
if (r_enum.begins_with("res://")) {
@@ -123,6 +137,90 @@ static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String
}
}
+String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_recursion_level) {
+ constexpr int MAX_RECURSION_LEVEL = 2;
+
+ switch (p_variant.get_type()) {
+ case Variant::STRING:
+ return String(p_variant).c_escape().quote();
+ case Variant::OBJECT:
+ return "<Object>";
+ case Variant::DICTIONARY: {
+ const Dictionary dict = p_variant;
+
+ if (dict.is_empty()) {
+ return "{}";
+ }
+
+ if (p_recursion_level > MAX_RECURSION_LEVEL) {
+ return "{...}";
+ }
+
+ List<Variant> keys;
+ dict.get_key_list(&keys);
+ keys.sort();
+
+ String data;
+ for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
+ if (E->prev()) {
+ data += ", ";
+ }
+ data += _docvalue_from_variant(E->get(), p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[E->get()], p_recursion_level + 1);
+ }
+
+ return "{" + data + "}";
+ } break;
+ case Variant::ARRAY: {
+ const Array array = p_variant;
+ String result;
+
+ if (array.get_typed_builtin() != Variant::NIL) {
+ result += "Array[";
+
+ Ref<Script> script = array.get_typed_script();
+ if (script.is_valid()) {
+ if (script->get_global_name() != StringName()) {
+ result += script->get_global_name();
+ } else if (!script->get_path().get_file().is_empty()) {
+ result += script->get_path().get_file();
+ } else {
+ result += array.get_typed_class_name();
+ }
+ } else if (array.get_typed_class_name() != StringName()) {
+ result += array.get_typed_class_name();
+ } else {
+ result += Variant::get_type_name((Variant::Type)array.get_typed_builtin());
+ }
+
+ result += "](";
+ }
+
+ if (array.is_empty()) {
+ result += "[]";
+ } else if (p_recursion_level > MAX_RECURSION_LEVEL) {
+ result += "[...]";
+ } else {
+ result += "[";
+ for (int i = 0; i < array.size(); i++) {
+ if (i > 0) {
+ result += ", ";
+ }
+ result += _docvalue_from_variant(array[i], p_recursion_level + 1);
+ }
+ result += "]";
+ }
+
+ if (array.get_typed_builtin() != Variant::NIL) {
+ result += ")";
+ }
+
+ return result;
+ } break;
+ default:
+ return p_variant.get_construct_string();
+ }
+}
+
void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_class) {
p_script->_clear_doc();
@@ -183,7 +281,10 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
p_script->member_lines[const_name] = m_const->start_line;
DocData::ConstantDoc const_doc;
- DocData::constant_doc_from_variant(const_doc, const_name, m_const->initializer->reduced_value, m_const->doc_data.description);
+ const_doc.name = const_name;
+ const_doc.value = _docvalue_from_variant(m_const->initializer->reduced_value);
+ const_doc.is_value_valid = true;
+ const_doc.description = m_const->doc_data.description;
const_doc.is_deprecated = m_const->doc_data.is_deprecated;
const_doc.is_experimental = m_const->doc_data.is_experimental;
doc.constants.push_back(const_doc);
@@ -217,7 +318,7 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
_doctype_from_gdtype(p->get_datatype(), arg_doc.type, arg_doc.enumeration);
if (p->initializer != nullptr) {
if (p->initializer->is_constant) {
- arg_doc.default_value = p->initializer->reduced_value.get_construct_string().replace("\n", "\\n");
+ arg_doc.default_value = _docvalue_from_variant(p->initializer->reduced_value);
} else {
arg_doc.default_value = "<unknown>";
}
@@ -286,7 +387,7 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
if (m_var->initializer) {
if (m_var->initializer->is_constant) {
- prop_doc.default_value = m_var->initializer->reduced_value.get_construct_string().replace("\n", "\\n");
+ prop_doc.default_value = _docvalue_from_variant(m_var->initializer->reduced_value);
} else {
prop_doc.default_value = "<unknown>";
}
@@ -312,7 +413,7 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
for (const GDP::EnumNode::Value &val : m_enum->values) {
DocData::ConstantDoc const_doc;
const_doc.name = val.identifier->name;
- const_doc.value = String(Variant(val.value));
+ const_doc.value = _docvalue_from_variant(val.value);
const_doc.is_value_valid = true;
const_doc.enumeration = name;
const_doc.description = val.doc_data.description;
@@ -331,8 +432,11 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
p_script->member_lines[name] = m_enum_val.identifier->start_line;
DocData::ConstantDoc const_doc;
- DocData::constant_doc_from_variant(const_doc, name, m_enum_val.value, m_enum_val.doc_data.description);
+ const_doc.name = name;
+ const_doc.value = _docvalue_from_variant(m_enum_val.value);
+ const_doc.is_value_valid = true;
const_doc.enumeration = "@unnamed_enums";
+ const_doc.description = m_enum_val.doc_data.description;
const_doc.is_deprecated = m_enum_val.doc_data.is_deprecated;
const_doc.is_experimental = m_enum_val.doc_data.is_experimental;
doc.constants.push_back(const_doc);
diff --git a/modules/gdscript/editor/gdscript_docgen.h b/modules/gdscript/editor/gdscript_docgen.h
index 3357fb680c..a326c02c5f 100644
--- a/modules/gdscript/editor/gdscript_docgen.h
+++ b/modules/gdscript/editor/gdscript_docgen.h
@@ -36,8 +36,16 @@
#include "core/doc_data.h"
class GDScriptDocGen {
+ using GDP = GDScriptParser;
+ using GDType = GDP::DataType;
+
+ static String _get_script_path(const String &p_path);
+ static String _get_class_name(const GDP::ClassNode &p_class);
+ static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return = false);
+ static String _docvalue_from_variant(const Variant &p_variant, int p_recursion_level = 1);
+
public:
- static void generate_docs(GDScript *p_script, const GDScriptParser::ClassNode *p_class);
+ static void generate_docs(GDScript *p_script, const GDP::ClassNode *p_class);
};
#endif // GDSCRIPT_DOCGEN_H
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index 1be690d894..2bfa191ff1 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -442,7 +442,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
if (str[k] == '(') {
in_function_name = true;
- } else if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::VAR)) {
+ } else if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::VAR) || prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FOR)) {
in_variable_declaration = true;
}
@@ -480,7 +480,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
in_function_args = false;
}
- if (expect_type && (prev_is_char || str[j] == '=') && str[j] != '[') {
+ if (expect_type && (prev_is_char || str[j] == '=') && str[j] != '[' && str[j] != '.') {
expect_type = false;
}
@@ -562,16 +562,11 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
} else if (in_keyword) {
next_type = KEYWORD;
color = keyword_color;
- } else if (in_member_variable) {
- next_type = MEMBER;
- color = member_color;
} else if (in_signal_declaration) {
next_type = SIGNAL;
-
color = member_color;
} else if (in_function_name) {
next_type = FUNCTION;
-
if (!in_lambda && prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) {
color = function_definition_color;
} else {
@@ -586,6 +581,9 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
} else if (expect_type) {
next_type = TYPE;
color = type_color;
+ } else if (in_member_variable) {
+ next_type = MEMBER;
+ color = member_color;
} else {
next_type = IDENTIFIER;
}
@@ -683,6 +681,12 @@ void GDScriptSyntaxHighlighter::_update_cache() {
for (const String &E : core_types) {
class_names[StringName(E)] = basetype_color;
}
+ class_names[SNAME("Variant")] = basetype_color;
+ class_names[SNAME("void")] = basetype_color;
+ // `get_core_type_words()` doesn't return primitive types.
+ class_names[SNAME("bool")] = basetype_color;
+ class_names[SNAME("int")] = basetype_color;
+ class_names[SNAME("float")] = basetype_color;
/* Reserved words. */
const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color");
@@ -697,6 +701,10 @@ void GDScriptSyntaxHighlighter::_update_cache() {
}
}
+ // Highlight `set` and `get` as "keywords" with the function color to avoid conflicts with method calls.
+ reserved_keywords[SNAME("set")] = function_color;
+ reserved_keywords[SNAME("get")] = function_color;
+
/* Global functions. */
List<StringName> global_function_list;
GDScriptUtilityFunctions::get_function_list(&global_function_list);
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index b5c80d9e2d..f10ed0df29 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -2368,62 +2368,60 @@ void GDScriptLanguage::frame() {
/* EDITOR FUNCTIONS */
void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
- // TODO: Add annotations here?
+ // Please keep alphabetical order within categories.
static const char *_reserved_words[] = {
- // operators
+ // Control flow.
+ "break",
+ "continue",
+ "elif",
+ "else",
+ "for",
+ "if",
+ "match",
+ "pass",
+ "return",
+ "when",
+ "while",
+ // Declarations.
+ "class",
+ "class_name",
+ "const",
+ "enum",
+ "extends",
+ "func",
+ "namespace", // Reserved for potential future use.
+ "signal",
+ "static",
+ "trait", // Reserved for potential future use.
+ "var",
+ // Other keywords.
+ "await",
+ "breakpoint",
+ "self",
+ "super",
+ "yield", // Reserved for potential future use.
+ // Operators.
"and",
+ "as",
"in",
+ "is",
"not",
"or",
- // types and values
+ // Special values (tokenizer treats them as literals, not as tokens).
"false",
- "float",
- "int",
- "bool",
"null",
- "PI",
- "TAU",
+ "true",
+ // Constants.
"INF",
"NAN",
- "self",
- "true",
- "void",
- // functions
- "as",
+ "PI",
+ "TAU",
+ // Functions (highlighter uses global function color instead).
"assert",
- "await",
- "breakpoint",
- "class",
- "class_name",
- "extends",
- "is",
- "func",
"preload",
- "signal",
- "super",
- // var
- "const",
- "enum",
- "static",
- "var",
- // control flow
- "break",
- "continue",
- "if",
- "elif",
- "else",
- "for",
- "pass",
- "return",
- "match",
- "while",
- "when",
- // These keywords are not implemented currently, but reserved for (potential) future use.
- // We highlight them as keywords to make errors easier to understand.
- "trait",
- "namespace",
- "yield",
- nullptr
+ // Types (highlighter uses type color instead).
+ "void",
+ nullptr,
};
const char **w = _reserved_words;
@@ -2432,22 +2430,16 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
p_words->push_back(*w);
w++;
}
-
- List<StringName> functions;
- GDScriptUtilityFunctions::get_function_list(&functions);
-
- for (const StringName &E : functions) {
- p_words->push_back(String(E));
- }
}
bool GDScriptLanguage::is_control_flow_keyword(String p_keyword) const {
+ // Please keep alphabetical order.
return p_keyword == "break" ||
p_keyword == "continue" ||
p_keyword == "elif" ||
p_keyword == "else" ||
- p_keyword == "if" ||
p_keyword == "for" ||
+ p_keyword == "if" ||
p_keyword == "match" ||
p_keyword == "pass" ||
p_keyword == "return" ||
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 882c246706..0d06597bc0 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -3022,9 +3022,23 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
} else {
#ifdef DEBUG_ENABLED
mark_node_unsafe(p_call);
- // We don't know what type was expected since constructors support overloads.
- // TODO: Improve this by checking for matching candidates?
- parser->push_warning(p_call->arguments[0], GDScriptWarning::UNSAFE_CALL_ARGUMENT, "1", function_name, "<unknown type>", "Variant");
+ // Constructors support overloads.
+ Vector<String> types;
+ for (int i = 0; i < Variant::VARIANT_MAX; i++) {
+ if (i != builtin_type && Variant::can_convert_strict((Variant::Type)i, builtin_type)) {
+ types.push_back(Variant::get_type_name((Variant::Type)i));
+ }
+ }
+ String expected_types = function_name;
+ if (types.size() == 1) {
+ expected_types += "\" or \"" + types[0];
+ } else if (types.size() >= 2) {
+ for (int i = 0; i < types.size() - 1; i++) {
+ expected_types += "\", \"" + types[i];
+ }
+ expected_types += "\", or \"" + types[types.size() - 1];
+ }
+ parser->push_warning(p_call->arguments[0], GDScriptWarning::UNSAFE_CALL_ARGUMENT, "1", "constructor", function_name, expected_types, "Variant");
#endif
p_call->set_datatype(call_type);
return;
@@ -3069,9 +3083,9 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
#ifdef DEBUG_ENABLED
if (!(par_type.is_variant() && par_type.is_hard_type())) {
GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype();
- if (arg_type.is_variant() || !arg_type.is_hard_type() || !is_type_compatible(arg_type, par_type, true)) {
+ if (arg_type.is_variant() || !arg_type.is_hard_type()) {
mark_node_unsafe(p_call);
- parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), function_name, par_type.to_string(), arg_type.to_string_strict());
+ parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), "constructor", function_name, par_type.to_string(), arg_type.to_string_strict());
}
}
#endif
@@ -5057,7 +5071,7 @@ void GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p
// Argument can be anything, so this is unsafe (unless the parameter is a hard variant).
if (!(par_type.is_hard_type() && par_type.is_variant())) {
mark_node_unsafe(p_call->arguments[i]);
- parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), p_call->function_name, par_type.to_string(), arg_type.to_string_strict());
+ parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), "function", p_call->function_name, par_type.to_string(), arg_type.to_string_strict());
}
#endif
} else if (par_type.is_hard_type() && !is_type_compatible(par_type, arg_type, true)) {
@@ -5069,7 +5083,7 @@ void GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p
} else {
// Supertypes are acceptable for dynamic compliance, but it's unsafe.
mark_node_unsafe(p_call);
- parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), p_call->function_name, par_type.to_string(), arg_type.to_string_strict());
+ parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), "function", p_call->function_name, par_type.to_string(), arg_type.to_string_strict());
#endif
}
#ifdef DEBUG_ENABLED
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index adfe4a3290..2f26069281 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -3396,6 +3396,12 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
}
}
+ if ("Variant" == p_symbol) {
+ r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS;
+ r_result.class_name = "Variant";
+ return OK;
+ }
+
if ("PI" == p_symbol || "TAU" == p_symbol || "INF" == p_symbol || "NAN" == p_symbol) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
r_result.class_name = "@GDScript";
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index cabac07ef9..1fe9b0425c 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -107,8 +107,8 @@ String GDScriptWarning::get_message() const {
CHECK_SYMBOLS(1);
return vformat(R"(The value is cast to "%s" but has an unknown type.)", symbols[0]);
case UNSAFE_CALL_ARGUMENT:
- CHECK_SYMBOLS(4);
- return vformat(R"*(The argument %s of the function "%s()" requires the subtype "%s" but the supertype "%s" was provided.)*", symbols[0], symbols[1], symbols[2], symbols[3]);
+ CHECK_SYMBOLS(5);
+ return vformat(R"*(The argument %s of the %s "%s()" requires the subtype "%s" but the supertype "%s" was provided.)*", symbols[0], symbols[1], symbols[2], symbols[3], symbols[4]);
case UNSAFE_VOID_RETURN:
CHECK_SYMBOLS(2);
return vformat(R"*(The method "%s()" returns "void" but it's trying to return a call to "%s()" that can't be ensured to also be "void".)*", symbols[0], symbols[1]);
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd
index 573060ae0f..c6d9b37485 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd
@@ -7,8 +7,12 @@ func int_func(x: int) -> void:
func float_func(x: float) -> void:
print(x)
+func node_func(x: Node) -> void:
+ print(x)
+
# We don't want to execute it because of errors, just analyze.
func no_exec_test():
+ var variant: Variant = null
var untyped_int = 42
var untyped_string = "abc"
var variant_int: Variant = 42
@@ -33,5 +37,18 @@ func no_exec_test():
float_func(variant_string)
float_func(typed_int) # No warning.
+ node_func(variant)
+ node_func(Object.new())
+ node_func(Node.new()) # No warning.
+ node_func(Node2D.new()) # No warning.
+
+ # GH-82529
+ print(Callable(self, "test")) # No warning.
+ print(Callable(variant, "test"))
+
+ print(Dictionary(variant))
+ print(Vector2(variant))
+ print(int(variant))
+
func test():
pass
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out
index b8fcb67158..3084515233 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out
@@ -1,33 +1,57 @@
GDTEST_OK
>> WARNING
->> Line: 24
+>> Line: 28
>> UNSAFE_CALL_ARGUMENT
>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided.
>> WARNING
->> Line: 25
+>> Line: 29
>> UNSAFE_CALL_ARGUMENT
>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided.
>> WARNING
->> Line: 26
+>> Line: 30
>> UNSAFE_CALL_ARGUMENT
>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided.
>> WARNING
->> Line: 27
+>> Line: 31
>> UNSAFE_CALL_ARGUMENT
>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided.
>> WARNING
->> Line: 30
+>> Line: 34
>> UNSAFE_CALL_ARGUMENT
>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided.
>> WARNING
->> Line: 31
+>> Line: 35
>> UNSAFE_CALL_ARGUMENT
>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided.
>> WARNING
->> Line: 32
+>> Line: 36
>> UNSAFE_CALL_ARGUMENT
>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided.
>> WARNING
->> Line: 33
+>> Line: 37
>> UNSAFE_CALL_ARGUMENT
>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided.
+>> WARNING
+>> Line: 40
+>> UNSAFE_CALL_ARGUMENT
+>> The argument 1 of the function "node_func()" requires the subtype "Node" but the supertype "Variant" was provided.
+>> WARNING
+>> Line: 41
+>> UNSAFE_CALL_ARGUMENT
+>> The argument 1 of the function "node_func()" requires the subtype "Node" but the supertype "Object" was provided.
+>> WARNING
+>> Line: 47
+>> UNSAFE_CALL_ARGUMENT
+>> The argument 1 of the constructor "Callable()" requires the subtype "Object" but the supertype "Variant" was provided.
+>> WARNING
+>> Line: 49
+>> UNSAFE_CALL_ARGUMENT
+>> The argument 1 of the constructor "Dictionary()" requires the subtype "Dictionary" but the supertype "Variant" was provided.
+>> WARNING
+>> Line: 50
+>> UNSAFE_CALL_ARGUMENT
+>> The argument 1 of the constructor "Vector2()" requires the subtype "Vector2" or "Vector2i" but the supertype "Variant" was provided.
+>> WARNING
+>> Line: 51
+>> UNSAFE_CALL_ARGUMENT
+>> The argument 1 of the constructor "int()" requires the subtype "int", "bool", or "float" but the supertype "Variant" was provided.
diff --git a/modules/gdscript/tests/scripts/runtime/features/stringify.gd b/modules/gdscript/tests/scripts/runtime/features/stringify.gd
index 1e66d8f34a..0dbb252b0e 100644
--- a/modules/gdscript/tests/scripts/runtime/features/stringify.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/stringify.gd
@@ -24,7 +24,6 @@ func test():
print(StringName("hello"))
print(NodePath("hello/world"))
var node := Node.new()
- @warning_ignore("unsafe_call_argument")
print(RID(node)) # TODO: Why is the constructor (or implicit cast) not documented?
print(node.get_name)
print(node.property_list_changed)
diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd
index fb20817117..781843b8e2 100644
--- a/modules/gdscript/tests/scripts/utils.notest.gd
+++ b/modules/gdscript/tests/scripts/utils.notest.gd
@@ -17,7 +17,7 @@ static func get_type(property: Dictionary, is_return: bool = false) -> String:
TYPE_OBJECT:
if not str(property.class_name).is_empty():
return property.class_name
- return variant_get_type_name(property.type)
+ return type_string(property.type)
static func get_property_signature(property: Dictionary, is_static: bool = false) -> String:
@@ -66,88 +66,6 @@ static func get_method_signature(method: Dictionary, is_signal: bool = false) ->
return result
-static func variant_get_type_name(type: Variant.Type) -> String:
- match type:
- TYPE_NIL:
- return "Nil" # `Nil` in core, `null` in GDScript.
- TYPE_BOOL:
- return "bool"
- TYPE_INT:
- return "int"
- TYPE_FLOAT:
- return "float"
- TYPE_STRING:
- return "String"
- TYPE_VECTOR2:
- return "Vector2"
- TYPE_VECTOR2I:
- return "Vector2i"
- TYPE_RECT2:
- return "Rect2"
- TYPE_RECT2I:
- return "Rect2i"
- TYPE_VECTOR3:
- return "Vector3"
- TYPE_VECTOR3I:
- return "Vector3i"
- TYPE_TRANSFORM2D:
- return "Transform2D"
- TYPE_VECTOR4:
- return "Vector4"
- TYPE_VECTOR4I:
- return "Vector4i"
- TYPE_PLANE:
- return "Plane"
- TYPE_QUATERNION:
- return "Quaternion"
- TYPE_AABB:
- return "AABB"
- TYPE_BASIS:
- return "Basis"
- TYPE_TRANSFORM3D:
- return "Transform3D"
- TYPE_PROJECTION:
- return "Projection"
- TYPE_COLOR:
- return "Color"
- TYPE_STRING_NAME:
- return "StringName"
- TYPE_NODE_PATH:
- return "NodePath"
- TYPE_RID:
- return "RID"
- TYPE_OBJECT:
- return "Object"
- TYPE_CALLABLE:
- return "Callable"
- TYPE_SIGNAL:
- return "Signal"
- TYPE_DICTIONARY:
- return "Dictionary"
- TYPE_ARRAY:
- return "Array"
- TYPE_PACKED_BYTE_ARRAY:
- return "PackedByteArray"
- TYPE_PACKED_INT32_ARRAY:
- return "PackedInt32Array"
- TYPE_PACKED_INT64_ARRAY:
- return "PackedInt64Array"
- TYPE_PACKED_FLOAT32_ARRAY:
- return "PackedFloat32Array"
- TYPE_PACKED_FLOAT64_ARRAY:
- return "PackedFloat64Array"
- TYPE_PACKED_STRING_ARRAY:
- return "PackedStringArray"
- TYPE_PACKED_VECTOR2_ARRAY:
- return "PackedVector2Array"
- TYPE_PACKED_VECTOR3_ARRAY:
- return "PackedVector3Array"
- TYPE_PACKED_COLOR_ARRAY:
- return "PackedColorArray"
- push_error("Argument `type` is invalid. Use `TYPE_*` constants.")
- return "<invalid type>"
-
-
static func get_property_hint_name(hint: PropertyHint) -> String:
match hint:
PROPERTY_HINT_NONE:
diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp
index b86a8b3cb1..467bedc4b2 100644
--- a/modules/gdscript/tests/test_gdscript.cpp
+++ b/modules/gdscript/tests/test_gdscript.cpp
@@ -223,6 +223,16 @@ void test(TestType p_type) {
// Initialize the language for the test routine.
init_language(fa->get_path_absolute().get_base_dir());
+ // Load global classes.
+ TypedArray<Dictionary> script_classes = ProjectSettings::get_singleton()->get_global_class_list();
+ for (int i = 0; i < script_classes.size(); i++) {
+ Dictionary c = script_classes[i];
+ if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base")) {
+ continue;
+ }
+ ScriptServer::add_global_class(c["class"], c["base"], c["language"], c["path"]);
+ }
+
Vector<uint8_t> buf;
uint64_t flen = fa->get_length();
buf.resize(flen + 1);
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
index 9bb4fd153b..907511d140 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
@@ -206,17 +206,19 @@ namespace GodotTools.Build
if (!File.Exists(buildInfo.Project))
return true; // No project to build.
- using var pr = new EditorProgress("dotnet_build_project", "Building .NET project...", 1);
-
- pr.Step("Building project", 0);
+ bool success;
+ using (var pr = new EditorProgress("dotnet_build_project", "Building .NET project...", 1))
+ {
+ pr.Step("Building project", 0);
+ success = Build(buildInfo);
+ }
- if (!Build(buildInfo))
+ if (!success)
{
ShowBuildErrorDialog("Failed to build project");
- return false;
}
- return true;
+ return success;
}
private static bool CleanProjectBlocking(BuildInfo buildInfo)
@@ -224,32 +226,36 @@ namespace GodotTools.Build
if (!File.Exists(buildInfo.Project))
return true; // No project to clean.
- using var pr = new EditorProgress("dotnet_clean_project", "Cleaning .NET project...", 1);
-
- pr.Step("Cleaning project", 0);
+ bool success;
+ using (var pr = new EditorProgress("dotnet_clean_project", "Cleaning .NET project...", 1))
+ {
+ pr.Step("Cleaning project", 0);
+ success = Build(buildInfo);
+ }
- if (!Build(buildInfo))
+ if (!success)
{
ShowBuildErrorDialog("Failed to clean project");
- return false;
}
- return true;
+ return success;
}
private static bool PublishProjectBlocking(BuildInfo buildInfo)
{
- using var pr = new EditorProgress("dotnet_publish_project", "Publishing .NET project...", 1);
-
- pr.Step("Running dotnet publish", 0);
+ bool success;
+ using (var pr = new EditorProgress("dotnet_publish_project", "Publishing .NET project...", 1))
+ {
+ pr.Step("Running dotnet publish", 0);
+ success = Publish(buildInfo);
+ }
- if (!Publish(buildInfo))
+ if (!success)
{
ShowBuildErrorDialog("Failed to publish .NET project");
- return false;
}
- return true;
+ return success;
}
private static BuildInfo CreateBuildInfo(
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index 48e654c286..a00c812c79 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -65,6 +65,7 @@ namespace GodotTools
private bool CreateProjectSolution()
{
+ string errorMessage = null;
using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 2))
{
pr.Step("Generating C# project...".TTR());
@@ -96,22 +97,23 @@ namespace GodotTools
}
catch (IOException e)
{
- ShowErrorDialog("Failed to save solution. Exception message: ".TTR() + e.Message);
- return false;
+ errorMessage = "Failed to save solution. Exception message: ".TTR() + e.Message;
}
-
- pr.Step("Done".TTR());
-
- // Here, after all calls to progress_task_step
- CallDeferred(nameof(_ShowDotnetFeatures));
}
else
{
- ShowErrorDialog("Failed to create C# project.".TTR());
+ errorMessage = "Failed to create C# project.".TTR();
}
+ }
- return true;
+ if (!string.IsNullOrEmpty(errorMessage))
+ {
+ ShowErrorDialog(errorMessage);
+ return false;
}
+
+ _ShowDotnetFeatures();
+ return true;
}
private void _ShowDotnetFeatures()
@@ -161,14 +163,14 @@ namespace GodotTools
{
_errorDialog.Title = title;
_errorDialog.DialogText = message;
- _errorDialog.PopupCentered();
+ EditorInterface.Singleton.PopupDialogCentered(_errorDialog);
}
public void ShowConfirmCreateSlnDialog()
{
_confirmCreateSlnDialog.Title = "C# solution already exists. This will override the existing C# project file, any manual changes will be lost.".TTR();
_confirmCreateSlnDialog.DialogText = "Create C# solution".TTR();
- _confirmCreateSlnDialog.PopupCentered();
+ EditorInterface.Singleton.PopupDialogCentered(_confirmCreateSlnDialog);
}
private static string _vsCodePath = string.Empty;
@@ -483,11 +485,11 @@ namespace GodotTools
_editorSettings = EditorInterface.Singleton.GetEditorSettings();
_errorDialog = new AcceptDialog();
- editorBaseControl.AddChild(_errorDialog);
+ _errorDialog.SetUnparentWhenInvisible(true);
_confirmCreateSlnDialog = new ConfirmationDialog();
+ _confirmCreateSlnDialog.SetUnparentWhenInvisible(true);
_confirmCreateSlnDialog.Confirmed += () => CreateProjectSolution();
- editorBaseControl.AddChild(_confirmCreateSlnDialog);
MSBuildPanel = new MSBuildPanel();
MSBuildPanel.BuildStateChanged += BuildStateChanged;
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index ccfcf2a87c..36fdda4625 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -264,7 +264,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
} else if (code_tag) {
xml_output.append("[");
pos = brk_pos + 1;
- } else if (tag.begins_with("method ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("theme_item ") || tag.begins_with("param ")) {
+ } else if (tag.begins_with("method ") || tag.begins_with("constructor ") || tag.begins_with("operator ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("theme_item ") || tag.begins_with("param ")) {
const int tag_end = tag.find(" ");
const String link_tag = tag.substr(0, tag_end);
const String link_target = tag.substr(tag_end + 1, tag.length()).lstrip(" ");
@@ -298,6 +298,12 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
if (link_tag == "method") {
_append_xml_method(xml_output, target_itype, target_cname, link_target, link_target_parts);
+ } else if (link_tag == "constructor") {
+ // TODO: Support constructors?
+ _append_xml_undeclared(xml_output, link_target);
+ } else if (link_tag == "operator") {
+ // TODO: Support operators?
+ _append_xml_undeclared(xml_output, link_target);
} else if (link_tag == "member") {
_append_xml_member(xml_output, target_itype, target_cname, link_target, link_target_parts);
} else if (link_tag == "signal") {
@@ -401,29 +407,29 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
pos = brk_end + 1;
tag_stack.push_front(tag);
- } else if (tag == "code") {
+ } else if (tag == "code" || tag.begins_with("code ")) {
xml_output.append("<c>");
code_tag = true;
pos = brk_end + 1;
- tag_stack.push_front(tag);
- } else if (tag == "codeblock") {
+ tag_stack.push_front("code");
+ } else if (tag == "codeblock" || tag.begins_with("codeblock ")) {
xml_output.append("<code>");
code_tag = true;
pos = brk_end + 1;
- tag_stack.push_front(tag);
+ tag_stack.push_front("codeblock");
} else if (tag == "codeblocks") {
line_del = true;
pos = brk_end + 1;
tag_stack.push_front(tag);
- } else if (tag == "csharp") {
+ } else if (tag == "csharp" || tag.begins_with("csharp ")) {
xml_output.append("<code>");
line_del = false;
code_tag = true;
pos = brk_end + 1;
- tag_stack.push_front(tag);
+ tag_stack.push_front("csharp");
} else if (tag == "kbd") {
// keyboard combinations are not supported in xml comments
pos = brk_end + 1;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs b/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs
index 48b47b166a..bf8b2f10dc 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs
@@ -7,6 +7,8 @@ using System.ComponentModel;
namespace Godot;
+#pragma warning disable CS1734 // XML comment on 'X' has a paramref tag for 'Y', but there is no parameter by that name.
+
partial class AnimationNode
{
/// <inheritdoc cref="BlendInput(int, double, bool, bool, float, FilterAction, bool, bool)"/>
@@ -24,6 +26,44 @@ partial class AnimationNode
}
}
+partial class AnimationPlayer
+{
+ /// <inheritdoc cref="CallbackModeMethod"/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public AnimationMethodCallMode MethodCallMode
+ {
+ get => (AnimationMethodCallMode)CallbackModeMethod;
+ set => CallbackModeMethod = (AnimationCallbackModeMethod)value;
+ }
+
+ /// <inheritdoc cref="Active"/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool PlaybackActive
+ {
+ get => Active;
+ set => Active = value;
+ }
+
+ /// <inheritdoc cref="CallbackModeProcess"/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public AnimationProcessCallback PlaybackProcessMode
+ {
+ get => (AnimationProcessCallback)CallbackModeProcess;
+ set => CallbackModeProcess = (AnimationCallbackModeProcess)value;
+ }
+}
+
+partial class AnimationTree
+{
+ /// <inheritdoc cref="CallbackModeProcess"/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public AnimationProcessCallback ProcessCallback
+ {
+ get => (AnimationProcessCallback)CallbackModeProcess;
+ set => CallbackModeProcess = (AnimationCallbackModeProcess)value;
+ }
+}
+
partial class CodeEdit
{
/// <inheritdoc cref="AddCodeCompletionOption(CodeCompletionKind, string, string, Nullable{Color}, Resource, Nullable{Variant}, int)"/>
@@ -36,7 +76,7 @@ partial class CodeEdit
partial class Geometry3D
{
- /// <inheritdoc cref="SegmentIntersectsConvex(Vector3, Vector3, Collections.Array{Plane})"/>
+ /// <inheritdoc cref="SegmentIntersectsConvex(Vector3, Vector3, Godot.Collections.Array{Plane})"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public static Vector3[] SegmentIntersectsConvex(Vector3 from, Vector3 to, Godot.Collections.Array planes)
{
@@ -44,6 +84,51 @@ partial class Geometry3D
}
}
+partial class GraphEdit
+{
+ /// <inheritdoc cref="ShowArrangeButton"/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool ArrangeNodesButtonHidden
+ {
+ get => !ShowArrangeButton;
+ set => ShowArrangeButton = !value;
+ }
+
+ /// <inheritdoc cref="GetMenuHBox()"/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public HBoxContainer GetZoomHBox()
+ {
+ return GetMenuHBox();
+ }
+
+ /// <inheritdoc cref="SnappingDistance"/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int SnapDistance
+ {
+ get => SnappingDistance;
+ set => SnappingDistance = value;
+ }
+
+ /// <inheritdoc cref="SnappingEnabled"/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool UseSnap
+ {
+ get => SnappingEnabled;
+ set => SnappingEnabled = value;
+ }
+}
+
+partial class GraphNode
+{
+ /// <inheritdoc cref="DeleteRequest"/>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public event Action CloseRequest
+ {
+ add => DeleteRequest += value;
+ remove => DeleteRequest -= value;
+ }
+}
+
partial class MeshInstance3D
{
/// <inheritdoc cref="CreateMultipleConvexCollisions(MeshConvexDecompositionSettings)"/>
@@ -108,6 +193,19 @@ partial class SurfaceTool
}
}
+partial class TileMap
+{
+ /// <summary>
+ /// The TileMap's quadrant size. Optimizes drawing by batching, using chunks of this size.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int CellQuadrantSize
+ {
+ get => RenderingQuadrantSize;
+ set => RenderingQuadrantSize = value;
+ }
+}
+
partial class Tree
{
/// <inheritdoc cref="EditSelected(bool)"/>
@@ -127,3 +225,5 @@ partial class UndoRedo
CreateAction(name, mergeMode, backwardUndoOps: false);
}
}
+
+#pragma warning restore CS1734
diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub
index c3a5d82fc4..73a3723ea4 100644
--- a/modules/openxr/SCsub
+++ b/modules/openxr/SCsub
@@ -106,6 +106,7 @@ if env["opengl3"] and env["platform"] != "macos":
env_openxr.add_source_files(module_obj, "extensions/openxr_palm_pose_extension.cpp")
env_openxr.add_source_files(module_obj, "extensions/openxr_composition_layer_depth_extension.cpp")
+env_openxr.add_source_files(module_obj, "extensions/openxr_eye_gaze_interaction.cpp")
env_openxr.add_source_files(module_obj, "extensions/openxr_htc_controller_extension.cpp")
env_openxr.add_source_files(module_obj, "extensions/openxr_htc_vive_tracker_extension.cpp")
env_openxr.add_source_files(module_obj, "extensions/openxr_huawei_controller_extension.cpp")
diff --git a/modules/openxr/action_map/openxr_action_map.cpp b/modules/openxr/action_map/openxr_action_map.cpp
index 6d79e33de8..72866f1cf7 100644
--- a/modules/openxr/action_map/openxr_action_map.cpp
+++ b/modules/openxr/action_map/openxr_action_map.cpp
@@ -206,7 +206,8 @@ void OpenXRActionMap::create_default_action_sets() {
"/user/vive_tracker_htcx/role/waist,"
"/user/vive_tracker_htcx/role/chest,"
"/user/vive_tracker_htcx/role/camera,"
- "/user/vive_tracker_htcx/role/keyboard");
+ "/user/vive_tracker_htcx/role/keyboard,"
+ "/user/eyes_ext");
Ref<OpenXRAction> aim_pose = action_set->add_new_action("aim_pose", "Aim pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right");
Ref<OpenXRAction> grip_pose = action_set->add_new_action("grip_pose", "Grip pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right");
Ref<OpenXRAction> palm_pose = action_set->add_new_action("palm_pose", "Palm pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right");
@@ -503,6 +504,11 @@ void OpenXRActionMap::create_default_action_sets() {
"/user/vive_tracker_htcx/role/camera/output/haptic,"
"/user/vive_tracker_htcx/role/keyboard/output/haptic");
add_interaction_profile(profile);
+
+ // Create our eye gaze interaction profile
+ profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/ext/eye_gaze_interaction");
+ profile->add_new_binding(default_pose, "/user/eyes_ext/input/gaze_ext/pose");
+ add_interaction_profile(profile);
}
void OpenXRActionMap::create_editor_action_sets() {
diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml
index d0630626e6..c7c666dc2f 100644
--- a/modules/openxr/doc_classes/OpenXRInterface.xml
+++ b/modules/openxr/doc_classes/OpenXRInterface.xml
@@ -77,6 +77,13 @@
Returns [code]true[/code] if the given action set is active.
</description>
</method>
+ <method name="is_eye_gaze_interaction_supported">
+ <return type="bool" />
+ <description>
+ Returns the capabilities of the eye gaze interaction extension.
+ [b]Note:[/b] This only returns a valid value after OpenXR has been initialized.
+ </description>
+ </method>
<method name="is_foveation_supported" qualifiers="const">
<return type="bool" />
<description>
diff --git a/modules/openxr/extensions/openxr_eye_gaze_interaction.cpp b/modules/openxr/extensions/openxr_eye_gaze_interaction.cpp
new file mode 100644
index 0000000000..59bdec5c8e
--- /dev/null
+++ b/modules/openxr/extensions/openxr_eye_gaze_interaction.cpp
@@ -0,0 +1,98 @@
+/**************************************************************************/
+/* openxr_eye_gaze_interaction.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "openxr_eye_gaze_interaction.h"
+
+#include "core/os/os.h"
+
+#include "../action_map/openxr_interaction_profile_metadata.h"
+
+OpenXREyeGazeInteractionExtension *OpenXREyeGazeInteractionExtension::singleton = nullptr;
+
+OpenXREyeGazeInteractionExtension *OpenXREyeGazeInteractionExtension::get_singleton() {
+ ERR_FAIL_NULL_V(singleton, nullptr);
+ return singleton;
+}
+
+OpenXREyeGazeInteractionExtension::OpenXREyeGazeInteractionExtension() {
+ singleton = this;
+}
+
+OpenXREyeGazeInteractionExtension::~OpenXREyeGazeInteractionExtension() {
+ singleton = nullptr;
+}
+
+HashMap<String, bool *> OpenXREyeGazeInteractionExtension::get_requested_extensions() {
+ HashMap<String, bool *> request_extensions;
+
+ request_extensions[XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME] = &available;
+
+ return request_extensions;
+}
+
+void *OpenXREyeGazeInteractionExtension::set_system_properties_and_get_next_pointer(void *p_next_pointer) {
+ if (!available) {
+ return p_next_pointer;
+ }
+
+ properties.type = XR_TYPE_SYSTEM_EYE_GAZE_INTERACTION_PROPERTIES_EXT;
+ properties.next = p_next_pointer;
+ properties.supportsEyeGazeInteraction = false;
+
+ return &properties;
+}
+
+bool OpenXREyeGazeInteractionExtension::is_available() {
+ return available;
+}
+
+bool OpenXREyeGazeInteractionExtension::supports_eye_gaze_interaction() {
+ // The extension being available only means that the OpenXR Runtime supports the extension.
+ // The `supportsEyeGazeInteraction` is set to true if the device also supports this.
+ // Thus both need to be true.
+ // In addition, on mobile runtimes, the proper permission needs to be granted.
+ if (available && properties.supportsEyeGazeInteraction) {
+ return !OS::get_singleton()->has_feature("mobile") || OS::get_singleton()->has_feature("PERMISSION_XR_EXT_eye_gaze_interaction");
+ }
+
+ return false;
+}
+
+void OpenXREyeGazeInteractionExtension::on_register_metadata() {
+ OpenXRInteractionProfileMetadata *metadata = OpenXRInteractionProfileMetadata::get_singleton();
+ ERR_FAIL_NULL(metadata);
+
+ // Eyes top path
+ metadata->register_top_level_path("Eye gaze tracker", "/user/eyes_ext", XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME);
+
+ // Eye gaze interaction
+ metadata->register_interaction_profile("Eye gaze", "/interaction_profiles/ext/eye_gaze_interaction", XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME);
+ metadata->register_io_path("/interaction_profiles/ext/eye_gaze_interaction", "Gaze pose", "/user/eyes_ext", "/user/eyes_ext/input/gaze_ext/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+}
diff --git a/modules/openxr/extensions/openxr_eye_gaze_interaction.h b/modules/openxr/extensions/openxr_eye_gaze_interaction.h
new file mode 100644
index 0000000000..704940ad26
--- /dev/null
+++ b/modules/openxr/extensions/openxr_eye_gaze_interaction.h
@@ -0,0 +1,58 @@
+/**************************************************************************/
+/* openxr_eye_gaze_interaction.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef OPENXR_EYE_GAZE_INTERACTION_H
+#define OPENXR_EYE_GAZE_INTERACTION_H
+
+#include "openxr_extension_wrapper.h"
+
+class OpenXREyeGazeInteractionExtension : public OpenXRExtensionWrapper {
+public:
+ static OpenXREyeGazeInteractionExtension *get_singleton();
+
+ OpenXREyeGazeInteractionExtension();
+ ~OpenXREyeGazeInteractionExtension();
+
+ virtual HashMap<String, bool *> get_requested_extensions() override;
+ virtual void *set_system_properties_and_get_next_pointer(void *p_next_pointer) override;
+
+ bool is_available();
+ bool supports_eye_gaze_interaction();
+
+ virtual void on_register_metadata() override;
+
+private:
+ static OpenXREyeGazeInteractionExtension *singleton;
+
+ bool available = false;
+ XrSystemEyeGazeInteractionPropertiesEXT properties;
+};
+
+#endif // OPENXR_EYE_GAZE_INTERACTION_H
diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
index c92b2b08d0..caf97ca2e0 100644
--- a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
+++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
@@ -32,6 +32,7 @@
#include "../openxr_api.h"
+#include "core/config/project_settings.h"
#include "core/string/print_string.h"
#include "servers/xr_server.h"
@@ -59,7 +60,6 @@ HashMap<String, bool *> OpenXRHandTrackingExtension::get_requested_extensions()
request_extensions[XR_EXT_HAND_TRACKING_EXTENSION_NAME] = &hand_tracking_ext;
request_extensions[XR_EXT_HAND_JOINTS_MOTION_RANGE_EXTENSION_NAME] = &hand_motion_range_ext;
- request_extensions[XR_FB_HAND_TRACKING_AIM_EXTENSION_NAME] = &hand_tracking_aim_state_ext;
return request_extensions;
}
@@ -106,17 +106,11 @@ void OpenXRHandTrackingExtension::on_state_ready() {
}
// Setup our hands and reset data
- for (int i = 0; i < MAX_OPENXR_TRACKED_HANDS; i++) {
+ for (int i = 0; i < OPENXR_MAX_TRACKED_HANDS; i++) {
// we'll do this later
hand_trackers[i].is_initialized = false;
hand_trackers[i].hand_tracker = XR_NULL_HANDLE;
- hand_trackers[i].aimState.aimPose = { { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0 } };
- hand_trackers[i].aimState.pinchStrengthIndex = 0.0;
- hand_trackers[i].aimState.pinchStrengthMiddle = 0.0;
- hand_trackers[i].aimState.pinchStrengthRing = 0.0;
- hand_trackers[i].aimState.pinchStrengthLittle = 0.0;
-
hand_trackers[i].locations.isActive = false;
for (int j = 0; j < XR_HAND_JOINT_COUNT_EXT; j++) {
@@ -141,7 +135,7 @@ void OpenXRHandTrackingExtension::on_process() {
XrResult result;
- for (int i = 0; i < MAX_OPENXR_TRACKED_HANDS; i++) {
+ for (int i = 0; i < OPENXR_MAX_TRACKED_HANDS; i++) {
if (hand_trackers[i].hand_tracker == XR_NULL_HANDLE) {
XrHandTrackerCreateInfoEXT createInfo = {
XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT, // type
@@ -157,18 +151,6 @@ void OpenXRHandTrackingExtension::on_process() {
hand_trackers[i].is_initialized = false;
} else {
void *next_pointer = nullptr;
- if (hand_tracking_aim_state_ext) {
- hand_trackers[i].aimState.type = XR_TYPE_HAND_TRACKING_AIM_STATE_FB;
- hand_trackers[i].aimState.next = next_pointer;
- hand_trackers[i].aimState.status = 0;
- hand_trackers[i].aimState.aimPose = { { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0 } };
- hand_trackers[i].aimState.pinchStrengthIndex = 0.0;
- hand_trackers[i].aimState.pinchStrengthMiddle = 0.0;
- hand_trackers[i].aimState.pinchStrengthRing = 0.0;
- hand_trackers[i].aimState.pinchStrengthLittle = 0.0;
-
- next_pointer = &hand_trackers[i].aimState;
- }
hand_trackers[i].velocities.type = XR_TYPE_HAND_JOINT_VELOCITIES_EXT;
hand_trackers[i].velocities.next = next_pointer;
@@ -219,20 +201,6 @@ void OpenXRHandTrackingExtension::on_process() {
!hand_trackers[i].locations.isActive || isnan(palm.position.x) || palm.position.x < -1000000.00 || palm.position.x > 1000000.00) {
hand_trackers[i].locations.isActive = false; // workaround, make sure its inactive
}
-
- /* TODO change this to managing the controller from openxr_interface
- if (hand_tracking_aim_state_ext && hand_trackers[i].locations.isActive && check_bit(XR_HAND_TRACKING_AIM_VALID_BIT_FB, hand_trackers[i].aimState.status)) {
- // Controllers are updated based on the aim state's pose and pinches' strength
- if (hand_trackers[i].aim_state_godot_controller == -1) {
- hand_trackers[i].aim_state_godot_controller =
- arvr_api->godot_arvr_add_controller(
- const_cast<char *>(hand_controller_names[i]),
- i + HAND_CONTROLLER_ID_OFFSET,
- true,
- true);
- }
- }
- */
}
}
}
@@ -246,7 +214,7 @@ void OpenXRHandTrackingExtension::cleanup_hand_tracking() {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
- for (int i = 0; i < MAX_OPENXR_TRACKED_HANDS; i++) {
+ for (int i = 0; i < OPENXR_MAX_TRACKED_HANDS; i++) {
if (hand_trackers[i].hand_tracker != XR_NULL_HANDLE) {
xrDestroyHandTrackerEXT(hand_trackers[i].hand_tracker);
@@ -260,25 +228,25 @@ bool OpenXRHandTrackingExtension::get_active() {
return handTrackingSystemProperties.supportsHandTracking;
}
-const OpenXRHandTrackingExtension::HandTracker *OpenXRHandTrackingExtension::get_hand_tracker(uint32_t p_hand) const {
- ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, nullptr);
+const OpenXRHandTrackingExtension::HandTracker *OpenXRHandTrackingExtension::get_hand_tracker(HandTrackedHands p_hand) const {
+ ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, nullptr);
return &hand_trackers[p_hand];
}
-XrHandJointsMotionRangeEXT OpenXRHandTrackingExtension::get_motion_range(uint32_t p_hand) const {
- ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, XR_HAND_JOINTS_MOTION_RANGE_MAX_ENUM_EXT);
+XrHandJointsMotionRangeEXT OpenXRHandTrackingExtension::get_motion_range(HandTrackedHands p_hand) const {
+ ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, XR_HAND_JOINTS_MOTION_RANGE_MAX_ENUM_EXT);
return hand_trackers[p_hand].motion_range;
}
-void OpenXRHandTrackingExtension::set_motion_range(uint32_t p_hand, XrHandJointsMotionRangeEXT p_motion_range) {
- ERR_FAIL_UNSIGNED_INDEX(p_hand, MAX_OPENXR_TRACKED_HANDS);
+void OpenXRHandTrackingExtension::set_motion_range(HandTrackedHands p_hand, XrHandJointsMotionRangeEXT p_motion_range) {
+ ERR_FAIL_UNSIGNED_INDEX(p_hand, OPENXR_MAX_TRACKED_HANDS);
hand_trackers[p_hand].motion_range = p_motion_range;
}
-Quaternion OpenXRHandTrackingExtension::get_hand_joint_rotation(uint32_t p_hand, XrHandJointEXT p_joint) const {
- ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, Quaternion());
+Quaternion OpenXRHandTrackingExtension::get_hand_joint_rotation(HandTrackedHands p_hand, XrHandJointEXT p_joint) const {
+ ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, Quaternion());
ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Quaternion());
if (!hand_trackers[p_hand].is_initialized) {
@@ -289,8 +257,8 @@ Quaternion OpenXRHandTrackingExtension::get_hand_joint_rotation(uint32_t p_hand,
return Quaternion(location.pose.orientation.x, location.pose.orientation.y, location.pose.orientation.z, location.pose.orientation.w);
}
-Vector3 OpenXRHandTrackingExtension::get_hand_joint_position(uint32_t p_hand, XrHandJointEXT p_joint) const {
- ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, Vector3());
+Vector3 OpenXRHandTrackingExtension::get_hand_joint_position(HandTrackedHands p_hand, XrHandJointEXT p_joint) const {
+ ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, Vector3());
ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Vector3());
if (!hand_trackers[p_hand].is_initialized) {
@@ -301,8 +269,8 @@ Vector3 OpenXRHandTrackingExtension::get_hand_joint_position(uint32_t p_hand, Xr
return Vector3(location.pose.position.x, location.pose.position.y, location.pose.position.z);
}
-float OpenXRHandTrackingExtension::get_hand_joint_radius(uint32_t p_hand, XrHandJointEXT p_joint) const {
- ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, 0.0);
+float OpenXRHandTrackingExtension::get_hand_joint_radius(HandTrackedHands p_hand, XrHandJointEXT p_joint) const {
+ ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, 0.0);
ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, 0.0);
if (!hand_trackers[p_hand].is_initialized) {
@@ -312,8 +280,8 @@ float OpenXRHandTrackingExtension::get_hand_joint_radius(uint32_t p_hand, XrHand
return hand_trackers[p_hand].joint_locations[p_joint].radius;
}
-Vector3 OpenXRHandTrackingExtension::get_hand_joint_linear_velocity(uint32_t p_hand, XrHandJointEXT p_joint) const {
- ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, Vector3());
+Vector3 OpenXRHandTrackingExtension::get_hand_joint_linear_velocity(HandTrackedHands p_hand, XrHandJointEXT p_joint) const {
+ ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, Vector3());
ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Vector3());
if (!hand_trackers[p_hand].is_initialized) {
@@ -324,8 +292,8 @@ Vector3 OpenXRHandTrackingExtension::get_hand_joint_linear_velocity(uint32_t p_h
return Vector3(velocity.linearVelocity.x, velocity.linearVelocity.y, velocity.linearVelocity.z);
}
-Vector3 OpenXRHandTrackingExtension::get_hand_joint_angular_velocity(uint32_t p_hand, XrHandJointEXT p_joint) const {
- ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, Vector3());
+Vector3 OpenXRHandTrackingExtension::get_hand_joint_angular_velocity(HandTrackedHands p_hand, XrHandJointEXT p_joint) const {
+ ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, Vector3());
ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Vector3());
if (!hand_trackers[p_hand].is_initialized) {
diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.h b/modules/openxr/extensions/openxr_hand_tracking_extension.h
index 99d315c525..5ca0ff60d3 100644
--- a/modules/openxr/extensions/openxr_hand_tracking_extension.h
+++ b/modules/openxr/extensions/openxr_hand_tracking_extension.h
@@ -35,10 +35,14 @@
#include "core/math/quaternion.h"
#include "openxr_extension_wrapper.h"
-#define MAX_OPENXR_TRACKED_HANDS 2
-
class OpenXRHandTrackingExtension : public OpenXRExtensionWrapper {
public:
+ enum HandTrackedHands {
+ OPENXR_TRACKED_LEFT_HAND,
+ OPENXR_TRACKED_RIGHT_HAND,
+ OPENXR_MAX_TRACKED_HANDS
+ };
+
struct HandTracker {
bool is_initialized = false;
XrHandJointsMotionRangeEXT motion_range = XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT;
@@ -47,7 +51,6 @@ public:
XrHandJointLocationEXT joint_locations[XR_HAND_JOINT_COUNT_EXT];
XrHandJointVelocityEXT joint_velocities[XR_HAND_JOINT_COUNT_EXT];
- XrHandTrackingAimStateFB aimState;
XrHandJointVelocitiesEXT velocities;
XrHandJointLocationsEXT locations;
};
@@ -69,29 +72,28 @@ public:
virtual void on_state_stopping() override;
bool get_active();
- const HandTracker *get_hand_tracker(uint32_t p_hand) const;
+ const HandTracker *get_hand_tracker(HandTrackedHands p_hand) const;
- XrHandJointsMotionRangeEXT get_motion_range(uint32_t p_hand) const;
- void set_motion_range(uint32_t p_hand, XrHandJointsMotionRangeEXT p_motion_range);
+ XrHandJointsMotionRangeEXT get_motion_range(HandTrackedHands p_hand) const;
+ void set_motion_range(HandTrackedHands p_hand, XrHandJointsMotionRangeEXT p_motion_range);
- Quaternion get_hand_joint_rotation(uint32_t p_hand, XrHandJointEXT p_joint) const;
- Vector3 get_hand_joint_position(uint32_t p_hand, XrHandJointEXT p_joint) const;
- float get_hand_joint_radius(uint32_t p_hand, XrHandJointEXT p_joint) const;
+ Quaternion get_hand_joint_rotation(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
+ Vector3 get_hand_joint_position(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
+ float get_hand_joint_radius(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
- Vector3 get_hand_joint_linear_velocity(uint32_t p_hand, XrHandJointEXT p_joint) const;
- Vector3 get_hand_joint_angular_velocity(uint32_t p_hand, XrHandJointEXT p_joint) const;
+ Vector3 get_hand_joint_linear_velocity(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
+ Vector3 get_hand_joint_angular_velocity(HandTrackedHands p_hand, XrHandJointEXT p_joint) const;
private:
static OpenXRHandTrackingExtension *singleton;
// state
XrSystemHandTrackingPropertiesEXT handTrackingSystemProperties;
- HandTracker hand_trackers[MAX_OPENXR_TRACKED_HANDS]; // Fixed for left and right hand
+ HandTracker hand_trackers[OPENXR_MAX_TRACKED_HANDS]; // Fixed for left and right hand
// related extensions
bool hand_tracking_ext = false;
bool hand_motion_range_ext = false;
- bool hand_tracking_aim_state_ext = false;
// functions
void cleanup_hand_tracking();
diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp
index 7b1530677f..d0b01c5771 100644
--- a/modules/openxr/openxr_interface.cpp
+++ b/modules/openxr/openxr_interface.cpp
@@ -34,7 +34,7 @@
#include "core/io/resource_saver.h"
#include "servers/rendering/rendering_server_globals.h"
-#include "extensions/openxr_hand_tracking_extension.h"
+#include "extensions/openxr_eye_gaze_interaction.h"
void OpenXRInterface::_bind_methods() {
// lifecycle signals
@@ -119,6 +119,8 @@ void OpenXRInterface::_bind_methods() {
BIND_ENUM_CONSTANT(HAND_JOINT_LITTLE_DISTAL);
BIND_ENUM_CONSTANT(HAND_JOINT_LITTLE_TIP);
BIND_ENUM_CONSTANT(HAND_JOINT_MAX);
+
+ ClassDB::bind_method(D_METHOD("is_eye_gaze_interaction_supported"), &OpenXRInterface::is_eye_gaze_interaction_supported);
}
StringName OpenXRInterface::get_name() const {
@@ -152,7 +154,9 @@ PackedStringArray OpenXRInterface::get_suggested_tracker_names() const {
"/user/vive_tracker_htcx/role/waist",
"/user/vive_tracker_htcx/role/chest",
"/user/vive_tracker_htcx/role/camera",
- "/user/vive_tracker_htcx/role/keyboard"
+ "/user/vive_tracker_htcx/role/keyboard",
+
+ "/user/eyes_ext",
};
return arr;
@@ -705,6 +709,21 @@ Array OpenXRInterface::get_available_display_refresh_rates() const {
}
}
+bool OpenXRInterface::is_eye_gaze_interaction_supported() {
+ if (openxr_api == nullptr) {
+ return false;
+ } else if (!openxr_api->is_initialized()) {
+ return false;
+ } else {
+ OpenXREyeGazeInteractionExtension *eye_gaze_ext = OpenXREyeGazeInteractionExtension::get_singleton();
+ if (eye_gaze_ext == nullptr) {
+ return false;
+ } else {
+ return eye_gaze_ext->supports_eye_gaze_interaction();
+ }
+ }
+}
+
bool OpenXRInterface::is_action_set_active(const String &p_action_set) const {
for (ActionSet *action_set : action_sets) {
if (action_set->action_set_name == p_action_set) {
@@ -888,6 +907,24 @@ RID OpenXRInterface::get_depth_texture() {
}
}
+void OpenXRInterface::handle_hand_tracking(const String &p_path, OpenXRHandTrackingExtension::HandTrackedHands p_hand) {
+ OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
+ if (hand_tracking_ext && hand_tracking_ext->get_active()) {
+ OpenXRInterface::Tracker *tracker = find_tracker(p_path);
+ if (tracker && tracker->positional_tracker.is_valid()) {
+ // TODO add in confidence! Requires PR #82715
+
+ Transform3D transform;
+ transform.basis = Basis(hand_tracking_ext->get_hand_joint_rotation(p_hand, XR_HAND_JOINT_PALM_EXT));
+ transform.origin = hand_tracking_ext->get_hand_joint_position(p_hand, XR_HAND_JOINT_PALM_EXT);
+ Vector3 linear_velocity = hand_tracking_ext->get_hand_joint_linear_velocity(p_hand, XR_HAND_JOINT_PALM_EXT);
+ Vector3 angular_velocity = hand_tracking_ext->get_hand_joint_angular_velocity(p_hand, XR_HAND_JOINT_PALM_EXT);
+
+ tracker->positional_tracker->set_pose("skeleton", transform, linear_velocity, angular_velocity, XRPose::XR_TRACKING_CONFIDENCE_HIGH);
+ }
+ }
+}
+
void OpenXRInterface::process() {
if (openxr_api) {
// do our normal process
@@ -895,8 +932,8 @@ void OpenXRInterface::process() {
Transform3D t;
Vector3 linear_velocity;
Vector3 angular_velocity;
- XRPose::TrackingConfidence confidence = openxr_api->get_head_center(t, linear_velocity, angular_velocity);
- if (confidence != XRPose::XR_TRACKING_CONFIDENCE_NONE) {
+ head_confidence = openxr_api->get_head_center(t, linear_velocity, angular_velocity);
+ if (head_confidence != XRPose::XR_TRACKING_CONFIDENCE_NONE) {
// Only update our transform if we have one to update it with
// note that poses are stored without world scale and reference frame applied!
head_transform = t;
@@ -918,14 +955,14 @@ void OpenXRInterface::process() {
handle_tracker(trackers[i]);
}
}
+
+ // Handle hand tracking
+ handle_hand_tracking("/user/hand/left", OpenXRHandTrackingExtension::OPENXR_TRACKED_LEFT_HAND);
+ handle_hand_tracking("/user/hand/right", OpenXRHandTrackingExtension::OPENXR_TRACKED_RIGHT_HAND);
}
if (head.is_valid()) {
- // TODO figure out how to get our velocities
-
- head->set_pose("default", head_transform, head_linear_velocity, head_angular_velocity);
-
- // TODO set confidence on pose once we support tracking this..
+ head->set_pose("default", head_transform, head_linear_velocity, head_angular_velocity, head_confidence);
}
}
@@ -1122,7 +1159,7 @@ void OpenXRInterface::set_motion_range(const Hand p_hand, const HandMotionRange
break;
}
- hand_tracking_ext->set_motion_range(uint32_t(p_hand), xr_motion_range);
+ hand_tracking_ext->set_motion_range(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), xr_motion_range);
}
}
@@ -1131,7 +1168,7 @@ OpenXRInterface::HandMotionRange OpenXRInterface::get_motion_range(const Hand p_
OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
if (hand_tracking_ext && hand_tracking_ext->get_active()) {
- XrHandJointsMotionRangeEXT xr_motion_range = hand_tracking_ext->get_motion_range(uint32_t(p_hand));
+ XrHandJointsMotionRangeEXT xr_motion_range = hand_tracking_ext->get_motion_range(OpenXRHandTrackingExtension::HandTrackedHands(p_hand));
switch (xr_motion_range) {
case XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT:
@@ -1149,7 +1186,7 @@ OpenXRInterface::HandMotionRange OpenXRInterface::get_motion_range(const Hand p_
Quaternion OpenXRInterface::get_hand_joint_rotation(Hand p_hand, HandJoints p_joint) const {
OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
if (hand_tracking_ext && hand_tracking_ext->get_active()) {
- return hand_tracking_ext->get_hand_joint_rotation(uint32_t(p_hand), XrHandJointEXT(p_joint));
+ return hand_tracking_ext->get_hand_joint_rotation(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint));
}
return Quaternion();
@@ -1158,7 +1195,7 @@ Quaternion OpenXRInterface::get_hand_joint_rotation(Hand p_hand, HandJoints p_jo
Vector3 OpenXRInterface::get_hand_joint_position(Hand p_hand, HandJoints p_joint) const {
OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
if (hand_tracking_ext && hand_tracking_ext->get_active()) {
- return hand_tracking_ext->get_hand_joint_position(uint32_t(p_hand), XrHandJointEXT(p_joint));
+ return hand_tracking_ext->get_hand_joint_position(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint));
}
return Vector3();
@@ -1167,7 +1204,7 @@ Vector3 OpenXRInterface::get_hand_joint_position(Hand p_hand, HandJoints p_joint
float OpenXRInterface::get_hand_joint_radius(Hand p_hand, HandJoints p_joint) const {
OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
if (hand_tracking_ext && hand_tracking_ext->get_active()) {
- return hand_tracking_ext->get_hand_joint_radius(uint32_t(p_hand), XrHandJointEXT(p_joint));
+ return hand_tracking_ext->get_hand_joint_radius(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint));
}
return 0.0;
@@ -1176,7 +1213,7 @@ float OpenXRInterface::get_hand_joint_radius(Hand p_hand, HandJoints p_joint) co
Vector3 OpenXRInterface::get_hand_joint_linear_velocity(Hand p_hand, HandJoints p_joint) const {
OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
if (hand_tracking_ext && hand_tracking_ext->get_active()) {
- return hand_tracking_ext->get_hand_joint_linear_velocity(uint32_t(p_hand), XrHandJointEXT(p_joint));
+ return hand_tracking_ext->get_hand_joint_linear_velocity(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint));
}
return Vector3();
@@ -1185,7 +1222,7 @@ Vector3 OpenXRInterface::get_hand_joint_linear_velocity(Hand p_hand, HandJoints
Vector3 OpenXRInterface::get_hand_joint_angular_velocity(Hand p_hand, HandJoints p_joint) const {
OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton();
if (hand_tracking_ext && hand_tracking_ext->get_active()) {
- return hand_tracking_ext->get_hand_joint_angular_velocity(uint32_t(p_hand), XrHandJointEXT(p_joint));
+ return hand_tracking_ext->get_hand_joint_angular_velocity(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint));
}
return Vector3();
diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h
index 38cf4bdbe8..8e24c8dce9 100644
--- a/modules/openxr/openxr_interface.h
+++ b/modules/openxr/openxr_interface.h
@@ -33,6 +33,7 @@
#include "action_map/openxr_action_map.h"
#include "extensions/openxr_fb_passthrough_extension_wrapper.h"
+#include "extensions/openxr_hand_tracking_extension.h"
#include "openxr_api.h"
#include "servers/xr/xr_interface.h"
@@ -55,6 +56,7 @@ private:
Transform3D head_transform;
Vector3 head_linear_velocity;
Vector3 head_angular_velocity;
+ XRPose::TrackingConfidence head_confidence;
Transform3D transform_for_view[2]; // We currently assume 2, but could be 4 for VARJO which we do not support yet
void _load_action_map();
@@ -97,6 +99,8 @@ private:
void _set_default_pos(Transform3D &p_transform, double p_world_scale, uint64_t p_eye);
+ void handle_hand_tracking(const String &p_path, OpenXRHandTrackingExtension::HandTrackedHands p_hand);
+
protected:
static void _bind_methods();
@@ -107,6 +111,8 @@ public:
virtual PackedStringArray get_suggested_tracker_names() const override;
virtual TrackingStatus get_tracking_status() const override;
+ bool is_eye_gaze_interaction_supported();
+
bool initialize_on_startup() const;
virtual bool is_initialized() const override;
virtual bool initialize() override;
diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp
index d69c803502..544932bdeb 100644
--- a/modules/openxr/register_types.cpp
+++ b/modules/openxr/register_types.cpp
@@ -42,6 +42,7 @@
#include "scene/openxr_hand.h"
#include "extensions/openxr_composition_layer_depth_extension.h"
+#include "extensions/openxr_eye_gaze_interaction.h"
#include "extensions/openxr_fb_display_refresh_rate_extension.h"
#include "extensions/openxr_fb_passthrough_extension_wrapper.h"
#include "extensions/openxr_hand_tracking_extension.h"
@@ -110,11 +111,18 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) {
OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCControllerExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCViveTrackerExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRHuaweiControllerExtension));
- OpenXRAPI::register_extension_wrapper(memnew(OpenXRHandTrackingExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRFbPassthroughExtensionWrapper));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRDisplayRefreshRateExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRWMRControllerExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRML2ControllerExtension));
+
+ // register gated extensions
+ if (GLOBAL_GET("xr/openxr/extensions/eye_gaze_interaction") && (!OS::get_singleton()->has_feature("mobile") || OS::get_singleton()->has_feature(XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME))) {
+ OpenXRAPI::register_extension_wrapper(memnew(OpenXREyeGazeInteractionExtension));
+ }
+ if (GLOBAL_GET("xr/openxr/extensions/hand_tracking")) {
+ OpenXRAPI::register_extension_wrapper(memnew(OpenXRHandTrackingExtension));
+ }
}
if (OpenXRAPI::openxr_is_enabled()) {
diff --git a/modules/openxr/scene/openxr_hand.cpp b/modules/openxr/scene/openxr_hand.cpp
index bedc8874d6..c48fac8055 100644
--- a/modules/openxr/scene/openxr_hand.cpp
+++ b/modules/openxr/scene/openxr_hand.cpp
@@ -113,7 +113,7 @@ void OpenXRHand::_set_motion_range() {
break;
}
- hand_tracking_ext->set_motion_range(hand, xr_motion_range);
+ hand_tracking_ext->set_motion_range(OpenXRHandTrackingExtension::HandTrackedHands(hand), xr_motion_range);
}
Skeleton3D *OpenXRHand::get_skeleton() {
@@ -204,7 +204,7 @@ void OpenXRHand::_update_skeleton() {
Quaternion inv_quaternions[XR_HAND_JOINT_COUNT_EXT];
Vector3 positions[XR_HAND_JOINT_COUNT_EXT];
- const OpenXRHandTrackingExtension::HandTracker *hand_tracker = hand_tracking_ext->get_hand_tracker(hand);
+ const OpenXRHandTrackingExtension::HandTracker *hand_tracker = hand_tracking_ext->get_hand_tracker(OpenXRHandTrackingExtension::HandTrackedHands(hand));
const float ws = XRServer::get_singleton()->get_world_scale();
if (hand_tracker->is_initialized && hand_tracker->locations.isActive) {
@@ -243,26 +243,27 @@ void OpenXRHand::_update_skeleton() {
// Get our target quaternion
Quaternion q = quaternions[i];
+ // Get our target position
+ Vector3 p = positions[i];
+
// get local translation, parent should already be processed
if (parent == -1) {
// use our palm location here, that is what we are tracking
q = inv_quaternions[XR_HAND_JOINT_PALM_EXT] * q;
+ p = inv_quaternions[XR_HAND_JOINT_PALM_EXT].xform(p - positions[XR_HAND_JOINT_PALM_EXT]);
} else {
int found = false;
for (int b = 0; b < XR_HAND_JOINT_COUNT_EXT && !found; b++) {
if (bones[b] == parent) {
q = inv_quaternions[b] * q;
+ p = inv_quaternions[b].xform(p - positions[b]);
found = true;
}
}
}
- // And get the movement from our rest position
- // Transform3D rest = skeleton->get_bone_rest(bones[i]);
- // q = rest.basis.get_quaternion().inverse() * q;
-
// and set our pose
- // skeleton->set_bone_pose_position(bones[i], v);
+ skeleton->set_bone_pose_position(bones[i], p);
skeleton->set_bone_pose_rotation(bones[i], q);
}
}
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index 9adb10236e..b605b29f84 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -397,6 +397,14 @@ void TextServerAdvanced::_free_rid(const RID &p_rid) {
font_owner.free(p_rid);
}
memdelete(fd);
+ } else if (font_var_owner.owns(p_rid)) {
+ MutexLock ftlock(ft_mutex);
+
+ FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(p_rid);
+ {
+ font_var_owner.free(p_rid);
+ }
+ memdelete(fdv);
} else if (shaped_owner.owns(p_rid)) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_rid);
{
@@ -409,7 +417,7 @@ void TextServerAdvanced::_free_rid(const RID &p_rid) {
bool TextServerAdvanced::_has(const RID &p_rid) {
_THREAD_SAFE_METHOD_
- return font_owner.owns(p_rid) || shaped_owner.owns(p_rid);
+ return font_owner.owns(p_rid) || font_var_owner.owns(p_rid) || shaped_owner.owns(p_rid);
}
bool TextServerAdvanced::_load_support_data(const String &p_filename) {
@@ -1809,7 +1817,7 @@ _FORCE_INLINE_ void TextServerAdvanced::_font_clear_cache(FontAdvanced *p_font_d
}
hb_font_t *TextServerAdvanced::_font_get_hb_handle(const RID &p_font_rid, int64_t p_size) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, nullptr);
MutexLock lock(fd->mutex);
@@ -1828,8 +1836,24 @@ RID TextServerAdvanced::_create_font() {
return font_owner.make_rid(fd);
}
+RID TextServerAdvanced::_create_font_linked_variation(const RID &p_font_rid) {
+ _THREAD_SAFE_METHOD_
+
+ RID rid = p_font_rid;
+ FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(rid);
+ if (unlikely(fdv)) {
+ rid = fdv->base_font;
+ }
+ ERR_FAIL_COND_V(!font_owner.owns(rid), RID());
+
+ FontAdvancedLinkedVariation *new_fdv = memnew(FontAdvancedLinkedVariation);
+ new_fdv->base_font = rid;
+
+ return font_var_owner.make_rid(new_fdv);
+}
+
void TextServerAdvanced::_font_set_data(const RID &p_font_rid, const PackedByteArray &p_data) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1840,7 +1864,7 @@ void TextServerAdvanced::_font_set_data(const RID &p_font_rid, const PackedByteA
}
void TextServerAdvanced::_font_set_data_ptr(const RID &p_font_rid, const uint8_t *p_data_ptr, int64_t p_data_size) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1854,7 +1878,7 @@ void TextServerAdvanced::_font_set_face_index(const RID &p_font_rid, int64_t p_f
ERR_FAIL_COND(p_face_index < 0);
ERR_FAIL_COND(p_face_index >= 0x7FFF);
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1865,7 +1889,7 @@ void TextServerAdvanced::_font_set_face_index(const RID &p_font_rid, int64_t p_f
}
int64_t TextServerAdvanced::_font_get_face_index(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
@@ -1873,7 +1897,7 @@ int64_t TextServerAdvanced::_font_get_face_index(const RID &p_font_rid) const {
}
int64_t TextServerAdvanced::_font_get_face_count(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
@@ -1919,7 +1943,7 @@ int64_t TextServerAdvanced::_font_get_face_count(const RID &p_font_rid) const {
}
void TextServerAdvanced::_font_set_style(const RID &p_font_rid, BitField<FontStyle> p_style) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1929,7 +1953,7 @@ void TextServerAdvanced::_font_set_style(const RID &p_font_rid, BitField<FontSty
}
BitField<TextServer::FontStyle> TextServerAdvanced::_font_get_style(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
@@ -1939,7 +1963,7 @@ BitField<TextServer::FontStyle> TextServerAdvanced::_font_get_style(const RID &p
}
void TextServerAdvanced::_font_set_style_name(const RID &p_font_rid, const String &p_name) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1949,7 +1973,7 @@ void TextServerAdvanced::_font_set_style_name(const RID &p_font_rid, const Strin
}
String TextServerAdvanced::_font_get_style_name(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
@@ -1959,7 +1983,7 @@ String TextServerAdvanced::_font_get_style_name(const RID &p_font_rid) const {
}
void TextServerAdvanced::_font_set_weight(const RID &p_font_rid, int64_t p_weight) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1969,7 +1993,7 @@ void TextServerAdvanced::_font_set_weight(const RID &p_font_rid, int64_t p_weigh
}
int64_t TextServerAdvanced::_font_get_weight(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 400);
MutexLock lock(fd->mutex);
@@ -1979,7 +2003,7 @@ int64_t TextServerAdvanced::_font_get_weight(const RID &p_font_rid) const {
}
void TextServerAdvanced::_font_set_stretch(const RID &p_font_rid, int64_t p_stretch) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1989,7 +2013,7 @@ void TextServerAdvanced::_font_set_stretch(const RID &p_font_rid, int64_t p_stre
}
int64_t TextServerAdvanced::_font_get_stretch(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 100);
MutexLock lock(fd->mutex);
@@ -1999,7 +2023,7 @@ int64_t TextServerAdvanced::_font_get_stretch(const RID &p_font_rid) const {
}
void TextServerAdvanced::_font_set_name(const RID &p_font_rid, const String &p_name) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2009,7 +2033,7 @@ void TextServerAdvanced::_font_set_name(const RID &p_font_rid, const String &p_n
}
String TextServerAdvanced::_font_get_name(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
@@ -2019,7 +2043,7 @@ String TextServerAdvanced::_font_get_name(const RID &p_font_rid) const {
}
Dictionary TextServerAdvanced::_font_get_ot_name_strings(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
@@ -2132,7 +2156,7 @@ Dictionary TextServerAdvanced::_font_get_ot_name_strings(const RID &p_font_rid)
}
void TextServerAdvanced::_font_set_antialiasing(const RID &p_font_rid, TextServer::FontAntialiasing p_antialiasing) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2143,7 +2167,7 @@ void TextServerAdvanced::_font_set_antialiasing(const RID &p_font_rid, TextServe
}
TextServer::FontAntialiasing TextServerAdvanced::_font_get_antialiasing(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, TextServer::FONT_ANTIALIASING_NONE);
MutexLock lock(fd->mutex);
@@ -2151,7 +2175,7 @@ TextServer::FontAntialiasing TextServerAdvanced::_font_get_antialiasing(const RI
}
void TextServerAdvanced::_font_set_generate_mipmaps(const RID &p_font_rid, bool p_generate_mipmaps) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2167,7 +2191,7 @@ void TextServerAdvanced::_font_set_generate_mipmaps(const RID &p_font_rid, bool
}
bool TextServerAdvanced::_font_get_generate_mipmaps(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -2175,7 +2199,7 @@ bool TextServerAdvanced::_font_get_generate_mipmaps(const RID &p_font_rid) const
}
void TextServerAdvanced::_font_set_multichannel_signed_distance_field(const RID &p_font_rid, bool p_msdf) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2186,7 +2210,7 @@ void TextServerAdvanced::_font_set_multichannel_signed_distance_field(const RID
}
bool TextServerAdvanced::_font_is_multichannel_signed_distance_field(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -2194,7 +2218,7 @@ bool TextServerAdvanced::_font_is_multichannel_signed_distance_field(const RID &
}
void TextServerAdvanced::_font_set_msdf_pixel_range(const RID &p_font_rid, int64_t p_msdf_pixel_range) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2205,7 +2229,7 @@ void TextServerAdvanced::_font_set_msdf_pixel_range(const RID &p_font_rid, int64
}
int64_t TextServerAdvanced::_font_get_msdf_pixel_range(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -2213,7 +2237,7 @@ int64_t TextServerAdvanced::_font_get_msdf_pixel_range(const RID &p_font_rid) co
}
void TextServerAdvanced::_font_set_msdf_size(const RID &p_font_rid, int64_t p_msdf_size) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2224,7 +2248,7 @@ void TextServerAdvanced::_font_set_msdf_size(const RID &p_font_rid, int64_t p_ms
}
int64_t TextServerAdvanced::_font_get_msdf_size(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -2232,7 +2256,7 @@ int64_t TextServerAdvanced::_font_get_msdf_size(const RID &p_font_rid) const {
}
void TextServerAdvanced::_font_set_fixed_size(const RID &p_font_rid, int64_t p_fixed_size) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2240,7 +2264,7 @@ void TextServerAdvanced::_font_set_fixed_size(const RID &p_font_rid, int64_t p_f
}
int64_t TextServerAdvanced::_font_get_fixed_size(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -2248,7 +2272,7 @@ int64_t TextServerAdvanced::_font_get_fixed_size(const RID &p_font_rid) const {
}
void TextServerAdvanced::_font_set_allow_system_fallback(const RID &p_font_rid, bool p_allow_system_fallback) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2256,7 +2280,7 @@ void TextServerAdvanced::_font_set_allow_system_fallback(const RID &p_font_rid,
}
bool TextServerAdvanced::_font_is_allow_system_fallback(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -2264,7 +2288,7 @@ bool TextServerAdvanced::_font_is_allow_system_fallback(const RID &p_font_rid) c
}
void TextServerAdvanced::_font_set_force_autohinter(const RID &p_font_rid, bool p_force_autohinter) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2275,7 +2299,7 @@ void TextServerAdvanced::_font_set_force_autohinter(const RID &p_font_rid, bool
}
bool TextServerAdvanced::_font_is_force_autohinter(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -2283,7 +2307,7 @@ bool TextServerAdvanced::_font_is_force_autohinter(const RID &p_font_rid) const
}
void TextServerAdvanced::_font_set_hinting(const RID &p_font_rid, TextServer::Hinting p_hinting) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2294,7 +2318,7 @@ void TextServerAdvanced::_font_set_hinting(const RID &p_font_rid, TextServer::Hi
}
TextServer::Hinting TextServerAdvanced::_font_get_hinting(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, HINTING_NONE);
MutexLock lock(fd->mutex);
@@ -2302,7 +2326,7 @@ TextServer::Hinting TextServerAdvanced::_font_get_hinting(const RID &p_font_rid)
}
void TextServerAdvanced::_font_set_subpixel_positioning(const RID &p_font_rid, TextServer::SubpixelPositioning p_subpixel) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2310,7 +2334,7 @@ void TextServerAdvanced::_font_set_subpixel_positioning(const RID &p_font_rid, T
}
TextServer::SubpixelPositioning TextServerAdvanced::_font_get_subpixel_positioning(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, SUBPIXEL_POSITIONING_DISABLED);
MutexLock lock(fd->mutex);
@@ -2318,7 +2342,7 @@ TextServer::SubpixelPositioning TextServerAdvanced::_font_get_subpixel_positioni
}
void TextServerAdvanced::_font_set_embolden(const RID &p_font_rid, double p_strength) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2329,7 +2353,7 @@ void TextServerAdvanced::_font_set_embolden(const RID &p_font_rid, double p_stre
}
double TextServerAdvanced::_font_get_embolden(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -2338,29 +2362,38 @@ double TextServerAdvanced::_font_get_embolden(const RID &p_font_rid) const {
void TextServerAdvanced::_font_set_spacing(const RID &p_font_rid, SpacingType p_spacing, int64_t p_value) {
ERR_FAIL_INDEX((int)p_spacing, 4);
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_NULL(fd);
+ FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid);
+ if (fdv) {
+ if (fdv->extra_spacing[p_spacing] != p_value) {
+ fdv->extra_spacing[p_spacing] = p_value;
+ }
+ } else {
+ FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ ERR_FAIL_NULL(fd);
- MutexLock lock(fd->mutex);
- if (fd->extra_spacing[p_spacing] != p_value) {
- _font_clear_cache(fd);
- fd->extra_spacing[p_spacing] = p_value;
+ MutexLock lock(fd->mutex);
+ if (fd->extra_spacing[p_spacing] != p_value) {
+ fd->extra_spacing[p_spacing] = p_value;
+ }
}
}
int64_t TextServerAdvanced::_font_get_spacing(const RID &p_font_rid, SpacingType p_spacing) const {
ERR_FAIL_INDEX_V((int)p_spacing, 4, 0);
+ FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid);
+ if (fdv) {
+ return fdv->extra_spacing[p_spacing];
+ } else {
+ FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ ERR_FAIL_NULL_V(fd, 0);
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_NULL_V(fd, 0);
-
- MutexLock lock(fd->mutex);
-
- return fd->extra_spacing[p_spacing];
+ MutexLock lock(fd->mutex);
+ return fd->extra_spacing[p_spacing];
+ }
}
void TextServerAdvanced::_font_set_transform(const RID &p_font_rid, const Transform2D &p_transform) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2371,7 +2404,7 @@ void TextServerAdvanced::_font_set_transform(const RID &p_font_rid, const Transf
}
Transform2D TextServerAdvanced::_font_get_transform(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Transform2D());
MutexLock lock(fd->mutex);
@@ -2379,7 +2412,7 @@ Transform2D TextServerAdvanced::_font_get_transform(const RID &p_font_rid) const
}
void TextServerAdvanced::_font_set_variation_coordinates(const RID &p_font_rid, const Dictionary &p_variation_coordinates) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2390,7 +2423,7 @@ void TextServerAdvanced::_font_set_variation_coordinates(const RID &p_font_rid,
}
Dictionary TextServerAdvanced::_font_get_variation_coordinates(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
@@ -2398,7 +2431,7 @@ Dictionary TextServerAdvanced::_font_get_variation_coordinates(const RID &p_font
}
void TextServerAdvanced::_font_set_oversampling(const RID &p_font_rid, double p_oversampling) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2409,7 +2442,7 @@ void TextServerAdvanced::_font_set_oversampling(const RID &p_font_rid, double p_
}
double TextServerAdvanced::_font_get_oversampling(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -2417,7 +2450,7 @@ double TextServerAdvanced::_font_get_oversampling(const RID &p_font_rid) const {
}
TypedArray<Vector2i> TextServerAdvanced::_font_get_size_cache_list(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, TypedArray<Vector2i>());
MutexLock lock(fd->mutex);
@@ -2429,7 +2462,7 @@ TypedArray<Vector2i> TextServerAdvanced::_font_get_size_cache_list(const RID &p_
}
void TextServerAdvanced::_font_clear_size_cache(const RID &p_font_rid) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2441,7 +2474,7 @@ void TextServerAdvanced::_font_clear_size_cache(const RID &p_font_rid) {
}
void TextServerAdvanced::_font_remove_size_cache(const RID &p_font_rid, const Vector2i &p_size) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2453,7 +2486,7 @@ void TextServerAdvanced::_font_remove_size_cache(const RID &p_font_rid, const Ve
}
void TextServerAdvanced::_font_set_ascent(const RID &p_font_rid, int64_t p_size, double p_ascent) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2464,7 +2497,7 @@ void TextServerAdvanced::_font_set_ascent(const RID &p_font_rid, int64_t p_size,
}
double TextServerAdvanced::_font_get_ascent(const RID &p_font_rid, int64_t p_size) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -2480,7 +2513,7 @@ double TextServerAdvanced::_font_get_ascent(const RID &p_font_rid, int64_t p_siz
}
void TextServerAdvanced::_font_set_descent(const RID &p_font_rid, int64_t p_size, double p_descent) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
Vector2i size = _get_size(fd, p_size);
@@ -2490,7 +2523,7 @@ void TextServerAdvanced::_font_set_descent(const RID &p_font_rid, int64_t p_size
}
double TextServerAdvanced::_font_get_descent(const RID &p_font_rid, int64_t p_size) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -2506,7 +2539,7 @@ double TextServerAdvanced::_font_get_descent(const RID &p_font_rid, int64_t p_si
}
void TextServerAdvanced::_font_set_underline_position(const RID &p_font_rid, int64_t p_size, double p_underline_position) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2517,7 +2550,7 @@ void TextServerAdvanced::_font_set_underline_position(const RID &p_font_rid, int
}
double TextServerAdvanced::_font_get_underline_position(const RID &p_font_rid, int64_t p_size) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -2533,7 +2566,7 @@ double TextServerAdvanced::_font_get_underline_position(const RID &p_font_rid, i
}
void TextServerAdvanced::_font_set_underline_thickness(const RID &p_font_rid, int64_t p_size, double p_underline_thickness) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2544,7 +2577,7 @@ void TextServerAdvanced::_font_set_underline_thickness(const RID &p_font_rid, in
}
double TextServerAdvanced::_font_get_underline_thickness(const RID &p_font_rid, int64_t p_size) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -2560,7 +2593,7 @@ double TextServerAdvanced::_font_get_underline_thickness(const RID &p_font_rid,
}
void TextServerAdvanced::_font_set_scale(const RID &p_font_rid, int64_t p_size, double p_scale) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2576,7 +2609,7 @@ void TextServerAdvanced::_font_set_scale(const RID &p_font_rid, int64_t p_size,
}
double TextServerAdvanced::_font_get_scale(const RID &p_font_rid, int64_t p_size) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -2592,7 +2625,7 @@ double TextServerAdvanced::_font_get_scale(const RID &p_font_rid, int64_t p_size
}
int64_t TextServerAdvanced::_font_get_texture_count(const RID &p_font_rid, const Vector2i &p_size) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
@@ -2604,7 +2637,7 @@ int64_t TextServerAdvanced::_font_get_texture_count(const RID &p_font_rid, const
}
void TextServerAdvanced::_font_clear_textures(const RID &p_font_rid, const Vector2i &p_size) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -2614,7 +2647,7 @@ void TextServerAdvanced::_font_clear_textures(const RID &p_font_rid, const Vecto
}
void TextServerAdvanced::_font_remove_texture(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2626,7 +2659,7 @@ void TextServerAdvanced::_font_remove_texture(const RID &p_font_rid, const Vecto
}
void TextServerAdvanced::_font_set_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const Ref<Image> &p_image) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
ERR_FAIL_COND(p_image.is_null());
@@ -2655,7 +2688,7 @@ void TextServerAdvanced::_font_set_texture_image(const RID &p_font_rid, const Ve
}
Ref<Image> TextServerAdvanced::_font_get_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Ref<Image>());
MutexLock lock(fd->mutex);
@@ -2669,7 +2702,7 @@ Ref<Image> TextServerAdvanced::_font_get_texture_image(const RID &p_font_rid, co
void TextServerAdvanced::_font_set_texture_offsets(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const PackedInt32Array &p_offsets) {
ERR_FAIL_COND(p_offsets.size() % 4 != 0);
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2688,7 +2721,7 @@ void TextServerAdvanced::_font_set_texture_offsets(const RID &p_font_rid, const
}
PackedInt32Array TextServerAdvanced::_font_get_texture_offsets(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, PackedInt32Array());
MutexLock lock(fd->mutex);
@@ -2713,7 +2746,7 @@ PackedInt32Array TextServerAdvanced::_font_get_texture_offsets(const RID &p_font
}
PackedInt32Array TextServerAdvanced::_font_get_glyph_list(const RID &p_font_rid, const Vector2i &p_size) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, PackedInt32Array());
MutexLock lock(fd->mutex);
@@ -2729,7 +2762,7 @@ PackedInt32Array TextServerAdvanced::_font_get_glyph_list(const RID &p_font_rid,
}
void TextServerAdvanced::_font_clear_glyphs(const RID &p_font_rid, const Vector2i &p_size) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2740,7 +2773,7 @@ void TextServerAdvanced::_font_clear_glyphs(const RID &p_font_rid, const Vector2
}
void TextServerAdvanced::_font_remove_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2751,7 +2784,7 @@ void TextServerAdvanced::_font_remove_glyph(const RID &p_font_rid, const Vector2
}
double TextServerAdvanced::_get_extra_advance(RID p_font_rid, int p_font_size) const {
- const FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ const FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -2765,7 +2798,7 @@ double TextServerAdvanced::_get_extra_advance(RID p_font_rid, int p_font_size) c
}
Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
@@ -2803,7 +2836,7 @@ Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64
}
void TextServerAdvanced::_font_set_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph, const Vector2 &p_advance) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2818,7 +2851,7 @@ void TextServerAdvanced::_font_set_glyph_advance(const RID &p_font_rid, int64_t
}
Vector2 TextServerAdvanced::_font_get_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
@@ -2848,7 +2881,7 @@ Vector2 TextServerAdvanced::_font_get_glyph_offset(const RID &p_font_rid, const
}
void TextServerAdvanced::_font_set_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Vector2 &p_offset) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2863,7 +2896,7 @@ void TextServerAdvanced::_font_set_glyph_offset(const RID &p_font_rid, const Vec
}
Vector2 TextServerAdvanced::_font_get_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
@@ -2893,7 +2926,7 @@ Vector2 TextServerAdvanced::_font_get_glyph_size(const RID &p_font_rid, const Ve
}
void TextServerAdvanced::_font_set_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Vector2 &p_gl_size) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2908,7 +2941,7 @@ void TextServerAdvanced::_font_set_glyph_size(const RID &p_font_rid, const Vecto
}
Rect2 TextServerAdvanced::_font_get_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Rect2());
MutexLock lock(fd->mutex);
@@ -2933,7 +2966,7 @@ Rect2 TextServerAdvanced::_font_get_glyph_uv_rect(const RID &p_font_rid, const V
}
void TextServerAdvanced::_font_set_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Rect2 &p_uv_rect) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2948,7 +2981,7 @@ void TextServerAdvanced::_font_set_glyph_uv_rect(const RID &p_font_rid, const Ve
}
int64_t TextServerAdvanced::_font_get_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, -1);
MutexLock lock(fd->mutex);
@@ -2973,7 +3006,7 @@ int64_t TextServerAdvanced::_font_get_glyph_texture_idx(const RID &p_font_rid, c
}
void TextServerAdvanced::_font_set_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, int64_t p_texture_idx) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2988,7 +3021,7 @@ void TextServerAdvanced::_font_set_glyph_texture_idx(const RID &p_font_rid, cons
}
RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, RID());
MutexLock lock(fd->mutex);
@@ -3034,7 +3067,7 @@ RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const
}
Size2 TextServerAdvanced::_font_get_glyph_texture_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Size2());
MutexLock lock(fd->mutex);
@@ -3080,7 +3113,7 @@ Size2 TextServerAdvanced::_font_get_glyph_texture_size(const RID &p_font_rid, co
}
Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, int64_t p_size, int64_t p_index) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
@@ -3130,7 +3163,7 @@ Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, i
}
TypedArray<Vector2i> TextServerAdvanced::_font_get_kerning_list(const RID &p_font_rid, int64_t p_size) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, TypedArray<Vector2i>());
MutexLock lock(fd->mutex);
@@ -3146,7 +3179,7 @@ TypedArray<Vector2i> TextServerAdvanced::_font_get_kerning_list(const RID &p_fon
}
void TextServerAdvanced::_font_clear_kerning_map(const RID &p_font_rid, int64_t p_size) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -3157,7 +3190,7 @@ void TextServerAdvanced::_font_clear_kerning_map(const RID &p_font_rid, int64_t
}
void TextServerAdvanced::_font_remove_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -3168,7 +3201,7 @@ void TextServerAdvanced::_font_remove_kerning(const RID &p_font_rid, int64_t p_s
}
void TextServerAdvanced::_font_set_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -3179,7 +3212,7 @@ void TextServerAdvanced::_font_set_kerning(const RID &p_font_rid, int64_t p_size
}
Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
@@ -3212,7 +3245,7 @@ Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_s
}
int64_t TextServerAdvanced::_font_get_glyph_index(const RID &p_font_rid, int64_t p_size, int64_t p_char, int64_t p_variation_selector) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0);
ERR_FAIL_COND_V_MSG((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff), 0, "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_char, 16) + ".");
ERR_FAIL_COND_V_MSG((p_variation_selector >= 0xd800 && p_variation_selector <= 0xdfff) || (p_variation_selector > 0x10ffff), 0, "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_variation_selector, 16) + ".");
@@ -3237,7 +3270,7 @@ int64_t TextServerAdvanced::_font_get_glyph_index(const RID &p_font_rid, int64_t
}
int64_t TextServerAdvanced::_font_get_char_from_glyph_index(const RID &p_font_rid, int64_t p_size, int64_t p_glyph_index) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
@@ -3268,7 +3301,7 @@ int64_t TextServerAdvanced::_font_get_char_from_glyph_index(const RID &p_font_ri
}
bool TextServerAdvanced::_font_has_char(const RID &p_font_rid, int64_t p_char) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_COND_V_MSG((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff), false, "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_char, 16) + ".");
if (!fd) {
return false;
@@ -3289,7 +3322,7 @@ bool TextServerAdvanced::_font_has_char(const RID &p_font_rid, int64_t p_char) c
}
String TextServerAdvanced::_font_get_supported_chars(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
@@ -3322,7 +3355,7 @@ String TextServerAdvanced::_font_get_supported_chars(const RID &p_font_rid) cons
}
void TextServerAdvanced::_font_render_range(const RID &p_font_rid, const Vector2i &p_size, int64_t p_start, int64_t p_end) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
ERR_FAIL_COND_MSG((p_start >= 0xd800 && p_start <= 0xdfff) || (p_start > 0x10ffff), "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_start, 16) + ".");
ERR_FAIL_COND_MSG((p_end >= 0xd800 && p_end <= 0xdfff) || (p_end > 0x10ffff), "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_end, 16) + ".");
@@ -3357,7 +3390,7 @@ void TextServerAdvanced::_font_render_range(const RID &p_font_rid, const Vector2
}
void TextServerAdvanced::_font_render_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_index) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -3388,7 +3421,7 @@ void TextServerAdvanced::_font_render_glyph(const RID &p_font_rid, const Vector2
}
void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_canvas, int64_t p_size, const Vector2 &p_pos, int64_t p_index, const Color &p_color) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -3480,7 +3513,7 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const RID &p_canvas, int64_t p_size, int64_t p_outline_size, const Vector2 &p_pos, int64_t p_index, const Color &p_color) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -3572,7 +3605,7 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R
}
bool TextServerAdvanced::_font_is_language_supported(const RID &p_font_rid, const String &p_language) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -3584,7 +3617,7 @@ bool TextServerAdvanced::_font_is_language_supported(const RID &p_font_rid, cons
}
void TextServerAdvanced::_font_set_language_support_override(const RID &p_font_rid, const String &p_language, bool p_supported) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -3592,7 +3625,7 @@ void TextServerAdvanced::_font_set_language_support_override(const RID &p_font_r
}
bool TextServerAdvanced::_font_get_language_support_override(const RID &p_font_rid, const String &p_language) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -3600,7 +3633,7 @@ bool TextServerAdvanced::_font_get_language_support_override(const RID &p_font_r
}
void TextServerAdvanced::_font_remove_language_support_override(const RID &p_font_rid, const String &p_language) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -3608,7 +3641,7 @@ void TextServerAdvanced::_font_remove_language_support_override(const RID &p_fon
}
PackedStringArray TextServerAdvanced::_font_get_language_support_overrides(const RID &p_font_rid) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, PackedStringArray());
MutexLock lock(fd->mutex);
@@ -3620,7 +3653,7 @@ PackedStringArray TextServerAdvanced::_font_get_language_support_overrides(const
}
bool TextServerAdvanced::_font_is_script_supported(const RID &p_font_rid, const String &p_script) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -3634,7 +3667,7 @@ bool TextServerAdvanced::_font_is_script_supported(const RID &p_font_rid, const
}
void TextServerAdvanced::_font_set_script_support_override(const RID &p_font_rid, const String &p_script, bool p_supported) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -3642,7 +3675,7 @@ void TextServerAdvanced::_font_set_script_support_override(const RID &p_font_rid
}
bool TextServerAdvanced::_font_get_script_support_override(const RID &p_font_rid, const String &p_script) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -3650,7 +3683,7 @@ bool TextServerAdvanced::_font_get_script_support_override(const RID &p_font_rid
}
void TextServerAdvanced::_font_remove_script_support_override(const RID &p_font_rid, const String &p_script) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -3658,7 +3691,7 @@ void TextServerAdvanced::_font_remove_script_support_override(const RID &p_font_
}
PackedStringArray TextServerAdvanced::_font_get_script_support_overrides(const RID &p_font_rid) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, PackedStringArray());
MutexLock lock(fd->mutex);
@@ -3670,7 +3703,7 @@ PackedStringArray TextServerAdvanced::_font_get_script_support_overrides(const R
}
void TextServerAdvanced::_font_set_opentype_feature_overrides(const RID &p_font_rid, const Dictionary &p_overrides) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -3680,7 +3713,7 @@ void TextServerAdvanced::_font_set_opentype_feature_overrides(const RID &p_font_
}
Dictionary TextServerAdvanced::_font_get_opentype_feature_overrides(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
@@ -3688,7 +3721,7 @@ Dictionary TextServerAdvanced::_font_get_opentype_feature_overrides(const RID &p
}
Dictionary TextServerAdvanced::_font_supported_feature_list(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
@@ -3698,7 +3731,7 @@ Dictionary TextServerAdvanced::_font_supported_feature_list(const RID &p_font_ri
}
Dictionary TextServerAdvanced::_font_supported_variation_list(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
@@ -4051,7 +4084,7 @@ bool TextServerAdvanced::_shaped_text_add_string(const RID &p_shaped, const Stri
MutexLock lock(sd->mutex);
for (int i = 0; i < p_fonts.size(); i++) {
- ERR_FAIL_COND_V(!font_owner.get_or_null(p_fonts[i]), false);
+ ERR_FAIL_COND_V(!_get_font_data(p_fonts[i]), false);
}
if (p_text.is_empty()) {
@@ -5633,7 +5666,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
return;
}
- FontAdvanced *fd = font_owner.get_or_null(f);
+ FontAdvanced *fd = _get_font_data(f);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h
index 7445becfae..57cca819be 100644
--- a/modules/text_server_adv/text_server_adv.h
+++ b/modules/text_server_adv/text_server_adv.h
@@ -293,6 +293,11 @@ class TextServerAdvanced : public TextServerExtension {
}
};
+ struct FontAdvancedLinkedVariation {
+ RID base_font;
+ int extra_spacing[4] = { 0, 0, 0, 0 };
+ };
+
struct FontAdvanced {
Mutex mutex;
@@ -534,9 +539,19 @@ class TextServerAdvanced : public TextServerExtension {
// Common data.
double oversampling = 1.0;
+ mutable RID_PtrOwner<FontAdvancedLinkedVariation> font_var_owner;
mutable RID_PtrOwner<FontAdvanced> font_owner;
mutable RID_PtrOwner<ShapedTextDataAdvanced> shaped_owner;
+ _FORCE_INLINE_ FontAdvanced *_get_font_data(const RID &p_font_rid) const {
+ RID rid = p_font_rid;
+ FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(rid);
+ if (unlikely(fdv)) {
+ rid = fdv->base_font;
+ }
+ return font_owner.get_or_null(rid);
+ }
+
struct SystemFontKey {
String font_name;
TextServer::FontAntialiasing antialiasing = TextServer::FONT_ANTIALIASING_GRAY;
@@ -704,6 +719,7 @@ public:
/* Font interface */
MODBIND0R(RID, create_font);
+ MODBIND1R(RID, create_font_linked_variation, const RID &);
MODBIND2(font_set_data, const RID &, const PackedByteArray &);
MODBIND3(font_set_data_ptr, const RID &, const uint8_t *, int64_t);
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index c3b64929a9..0cfbf7f530 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -123,6 +123,14 @@ void TextServerFallback::_free_rid(const RID &p_rid) {
font_owner.free(p_rid);
}
memdelete(fd);
+ } else if (font_var_owner.owns(p_rid)) {
+ MutexLock ftlock(ft_mutex);
+
+ FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(p_rid);
+ {
+ font_var_owner.free(p_rid);
+ }
+ memdelete(fdv);
} else if (shaped_owner.owns(p_rid)) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_rid);
{
@@ -935,8 +943,24 @@ RID TextServerFallback::_create_font() {
return font_owner.make_rid(fd);
}
+RID TextServerFallback::_create_font_linked_variation(const RID &p_font_rid) {
+ _THREAD_SAFE_METHOD_
+
+ RID rid = p_font_rid;
+ FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(rid);
+ if (unlikely(fdv)) {
+ rid = fdv->base_font;
+ }
+ ERR_FAIL_COND_V(!font_owner.owns(rid), RID());
+
+ FontFallbackLinkedVariation *new_fdv = memnew(FontFallbackLinkedVariation);
+ new_fdv->base_font = rid;
+
+ return font_var_owner.make_rid(new_fdv);
+}
+
void TextServerFallback::_font_set_data(const RID &p_font_rid, const PackedByteArray &p_data) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -947,7 +971,7 @@ void TextServerFallback::_font_set_data(const RID &p_font_rid, const PackedByteA
}
void TextServerFallback::_font_set_data_ptr(const RID &p_font_rid, const uint8_t *p_data_ptr, int64_t p_data_size) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -958,7 +982,7 @@ void TextServerFallback::_font_set_data_ptr(const RID &p_font_rid, const uint8_t
}
void TextServerFallback::_font_set_style(const RID &p_font_rid, BitField<FontStyle> p_style) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -971,7 +995,7 @@ void TextServerFallback::_font_set_face_index(const RID &p_font_rid, int64_t p_f
ERR_FAIL_COND(p_face_index < 0);
ERR_FAIL_COND(p_face_index >= 0x7FFF);
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -982,7 +1006,7 @@ void TextServerFallback::_font_set_face_index(const RID &p_font_rid, int64_t p_f
}
int64_t TextServerFallback::_font_get_face_index(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
@@ -990,7 +1014,7 @@ int64_t TextServerFallback::_font_get_face_index(const RID &p_font_rid) const {
}
int64_t TextServerFallback::_font_get_face_count(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
@@ -1036,7 +1060,7 @@ int64_t TextServerFallback::_font_get_face_count(const RID &p_font_rid) const {
}
BitField<TextServer::FontStyle> TextServerFallback::_font_get_style(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
@@ -1046,7 +1070,7 @@ BitField<TextServer::FontStyle> TextServerFallback::_font_get_style(const RID &p
}
void TextServerFallback::_font_set_style_name(const RID &p_font_rid, const String &p_name) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1056,7 +1080,7 @@ void TextServerFallback::_font_set_style_name(const RID &p_font_rid, const Strin
}
String TextServerFallback::_font_get_style_name(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
@@ -1066,7 +1090,7 @@ String TextServerFallback::_font_get_style_name(const RID &p_font_rid) const {
}
void TextServerFallback::_font_set_weight(const RID &p_font_rid, int64_t p_weight) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1076,7 +1100,7 @@ void TextServerFallback::_font_set_weight(const RID &p_font_rid, int64_t p_weigh
}
int64_t TextServerFallback::_font_get_weight(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 400);
MutexLock lock(fd->mutex);
@@ -1086,7 +1110,7 @@ int64_t TextServerFallback::_font_get_weight(const RID &p_font_rid) const {
}
void TextServerFallback::_font_set_stretch(const RID &p_font_rid, int64_t p_stretch) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1096,7 +1120,7 @@ void TextServerFallback::_font_set_stretch(const RID &p_font_rid, int64_t p_stre
}
int64_t TextServerFallback::_font_get_stretch(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 100);
MutexLock lock(fd->mutex);
@@ -1106,7 +1130,7 @@ int64_t TextServerFallback::_font_get_stretch(const RID &p_font_rid) const {
}
void TextServerFallback::_font_set_name(const RID &p_font_rid, const String &p_name) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1116,7 +1140,7 @@ void TextServerFallback::_font_set_name(const RID &p_font_rid, const String &p_n
}
String TextServerFallback::_font_get_name(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
@@ -1126,7 +1150,7 @@ String TextServerFallback::_font_get_name(const RID &p_font_rid) const {
}
void TextServerFallback::_font_set_antialiasing(const RID &p_font_rid, TextServer::FontAntialiasing p_antialiasing) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1137,7 +1161,7 @@ void TextServerFallback::_font_set_antialiasing(const RID &p_font_rid, TextServe
}
TextServer::FontAntialiasing TextServerFallback::_font_get_antialiasing(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, TextServer::FONT_ANTIALIASING_NONE);
MutexLock lock(fd->mutex);
@@ -1145,7 +1169,7 @@ TextServer::FontAntialiasing TextServerFallback::_font_get_antialiasing(const RI
}
void TextServerFallback::_font_set_generate_mipmaps(const RID &p_font_rid, bool p_generate_mipmaps) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1161,7 +1185,7 @@ void TextServerFallback::_font_set_generate_mipmaps(const RID &p_font_rid, bool
}
bool TextServerFallback::_font_get_generate_mipmaps(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -1169,7 +1193,7 @@ bool TextServerFallback::_font_get_generate_mipmaps(const RID &p_font_rid) const
}
void TextServerFallback::_font_set_multichannel_signed_distance_field(const RID &p_font_rid, bool p_msdf) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1180,7 +1204,7 @@ void TextServerFallback::_font_set_multichannel_signed_distance_field(const RID
}
bool TextServerFallback::_font_is_multichannel_signed_distance_field(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -1188,7 +1212,7 @@ bool TextServerFallback::_font_is_multichannel_signed_distance_field(const RID &
}
void TextServerFallback::_font_set_msdf_pixel_range(const RID &p_font_rid, int64_t p_msdf_pixel_range) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1199,7 +1223,7 @@ void TextServerFallback::_font_set_msdf_pixel_range(const RID &p_font_rid, int64
}
int64_t TextServerFallback::_font_get_msdf_pixel_range(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -1207,7 +1231,7 @@ int64_t TextServerFallback::_font_get_msdf_pixel_range(const RID &p_font_rid) co
}
void TextServerFallback::_font_set_msdf_size(const RID &p_font_rid, int64_t p_msdf_size) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1218,7 +1242,7 @@ void TextServerFallback::_font_set_msdf_size(const RID &p_font_rid, int64_t p_ms
}
int64_t TextServerFallback::_font_get_msdf_size(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -1226,7 +1250,7 @@ int64_t TextServerFallback::_font_get_msdf_size(const RID &p_font_rid) const {
}
void TextServerFallback::_font_set_fixed_size(const RID &p_font_rid, int64_t p_fixed_size) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1234,7 +1258,7 @@ void TextServerFallback::_font_set_fixed_size(const RID &p_font_rid, int64_t p_f
}
int64_t TextServerFallback::_font_get_fixed_size(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -1242,7 +1266,7 @@ int64_t TextServerFallback::_font_get_fixed_size(const RID &p_font_rid) const {
}
void TextServerFallback::_font_set_allow_system_fallback(const RID &p_font_rid, bool p_allow_system_fallback) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1250,7 +1274,7 @@ void TextServerFallback::_font_set_allow_system_fallback(const RID &p_font_rid,
}
bool TextServerFallback::_font_is_allow_system_fallback(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -1258,7 +1282,7 @@ bool TextServerFallback::_font_is_allow_system_fallback(const RID &p_font_rid) c
}
void TextServerFallback::_font_set_force_autohinter(const RID &p_font_rid, bool p_force_autohinter) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1269,7 +1293,7 @@ void TextServerFallback::_font_set_force_autohinter(const RID &p_font_rid, bool
}
bool TextServerFallback::_font_is_force_autohinter(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -1277,7 +1301,7 @@ bool TextServerFallback::_font_is_force_autohinter(const RID &p_font_rid) const
}
void TextServerFallback::_font_set_hinting(const RID &p_font_rid, TextServer::Hinting p_hinting) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1288,7 +1312,7 @@ void TextServerFallback::_font_set_hinting(const RID &p_font_rid, TextServer::Hi
}
TextServer::Hinting TextServerFallback::_font_get_hinting(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, HINTING_NONE);
MutexLock lock(fd->mutex);
@@ -1296,7 +1320,7 @@ TextServer::Hinting TextServerFallback::_font_get_hinting(const RID &p_font_rid)
}
void TextServerFallback::_font_set_subpixel_positioning(const RID &p_font_rid, TextServer::SubpixelPositioning p_subpixel) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1304,7 +1328,7 @@ void TextServerFallback::_font_set_subpixel_positioning(const RID &p_font_rid, T
}
TextServer::SubpixelPositioning TextServerFallback::_font_get_subpixel_positioning(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, SUBPIXEL_POSITIONING_DISABLED);
MutexLock lock(fd->mutex);
@@ -1312,7 +1336,7 @@ TextServer::SubpixelPositioning TextServerFallback::_font_get_subpixel_positioni
}
void TextServerFallback::_font_set_embolden(const RID &p_font_rid, double p_strength) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1323,7 +1347,7 @@ void TextServerFallback::_font_set_embolden(const RID &p_font_rid, double p_stre
}
double TextServerFallback::_font_get_embolden(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -1332,28 +1356,39 @@ double TextServerFallback::_font_get_embolden(const RID &p_font_rid) const {
void TextServerFallback::_font_set_spacing(const RID &p_font_rid, SpacingType p_spacing, int64_t p_value) {
ERR_FAIL_INDEX((int)p_spacing, 4);
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_NULL(fd);
+ FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid);
+ if (fdv) {
+ if (fdv->extra_spacing[p_spacing] != p_value) {
+ fdv->extra_spacing[p_spacing] = p_value;
+ }
+ } else {
+ FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ ERR_FAIL_NULL(fd);
- MutexLock lock(fd->mutex);
- if (fd->extra_spacing[p_spacing] != p_value) {
- _font_clear_cache(fd);
- fd->extra_spacing[p_spacing] = p_value;
+ MutexLock lock(fd->mutex);
+ if (fd->extra_spacing[p_spacing] != p_value) {
+ _font_clear_cache(fd);
+ fd->extra_spacing[p_spacing] = p_value;
+ }
}
}
int64_t TextServerFallback::_font_get_spacing(const RID &p_font_rid, SpacingType p_spacing) const {
ERR_FAIL_INDEX_V((int)p_spacing, 4, 0);
+ FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid);
+ if (fdv) {
+ return fdv->extra_spacing[p_spacing];
+ } else {
+ FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ ERR_FAIL_NULL_V(fd, 0);
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_NULL_V(fd, 0);
-
- MutexLock lock(fd->mutex);
- return fd->extra_spacing[p_spacing];
+ MutexLock lock(fd->mutex);
+ return fd->extra_spacing[p_spacing];
+ }
}
void TextServerFallback::_font_set_transform(const RID &p_font_rid, const Transform2D &p_transform) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1364,7 +1399,7 @@ void TextServerFallback::_font_set_transform(const RID &p_font_rid, const Transf
}
Transform2D TextServerFallback::_font_get_transform(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Transform2D());
MutexLock lock(fd->mutex);
@@ -1372,7 +1407,7 @@ Transform2D TextServerFallback::_font_get_transform(const RID &p_font_rid) const
}
void TextServerFallback::_font_set_variation_coordinates(const RID &p_font_rid, const Dictionary &p_variation_coordinates) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1383,7 +1418,7 @@ void TextServerFallback::_font_set_variation_coordinates(const RID &p_font_rid,
}
Dictionary TextServerFallback::_font_get_variation_coordinates(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
@@ -1391,7 +1426,7 @@ Dictionary TextServerFallback::_font_get_variation_coordinates(const RID &p_font
}
void TextServerFallback::_font_set_oversampling(const RID &p_font_rid, double p_oversampling) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1402,7 +1437,7 @@ void TextServerFallback::_font_set_oversampling(const RID &p_font_rid, double p_
}
double TextServerFallback::_font_get_oversampling(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -1410,7 +1445,7 @@ double TextServerFallback::_font_get_oversampling(const RID &p_font_rid) const {
}
TypedArray<Vector2i> TextServerFallback::_font_get_size_cache_list(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, TypedArray<Vector2i>());
MutexLock lock(fd->mutex);
@@ -1422,7 +1457,7 @@ TypedArray<Vector2i> TextServerFallback::_font_get_size_cache_list(const RID &p_
}
void TextServerFallback::_font_clear_size_cache(const RID &p_font_rid) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1434,7 +1469,7 @@ void TextServerFallback::_font_clear_size_cache(const RID &p_font_rid) {
}
void TextServerFallback::_font_remove_size_cache(const RID &p_font_rid, const Vector2i &p_size) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1446,7 +1481,7 @@ void TextServerFallback::_font_remove_size_cache(const RID &p_font_rid, const Ve
}
void TextServerFallback::_font_set_ascent(const RID &p_font_rid, int64_t p_size, double p_ascent) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1457,7 +1492,7 @@ void TextServerFallback::_font_set_ascent(const RID &p_font_rid, int64_t p_size,
}
double TextServerFallback::_font_get_ascent(const RID &p_font_rid, int64_t p_size) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -1473,7 +1508,7 @@ double TextServerFallback::_font_get_ascent(const RID &p_font_rid, int64_t p_siz
}
void TextServerFallback::_font_set_descent(const RID &p_font_rid, int64_t p_size, double p_descent) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
Vector2i size = _get_size(fd, p_size);
@@ -1483,7 +1518,7 @@ void TextServerFallback::_font_set_descent(const RID &p_font_rid, int64_t p_size
}
double TextServerFallback::_font_get_descent(const RID &p_font_rid, int64_t p_size) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -1499,7 +1534,7 @@ double TextServerFallback::_font_get_descent(const RID &p_font_rid, int64_t p_si
}
void TextServerFallback::_font_set_underline_position(const RID &p_font_rid, int64_t p_size, double p_underline_position) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1510,7 +1545,7 @@ void TextServerFallback::_font_set_underline_position(const RID &p_font_rid, int
}
double TextServerFallback::_font_get_underline_position(const RID &p_font_rid, int64_t p_size) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -1526,7 +1561,7 @@ double TextServerFallback::_font_get_underline_position(const RID &p_font_rid, i
}
void TextServerFallback::_font_set_underline_thickness(const RID &p_font_rid, int64_t p_size, double p_underline_thickness) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1537,7 +1572,7 @@ void TextServerFallback::_font_set_underline_thickness(const RID &p_font_rid, in
}
double TextServerFallback::_font_get_underline_thickness(const RID &p_font_rid, int64_t p_size) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -1553,7 +1588,7 @@ double TextServerFallback::_font_get_underline_thickness(const RID &p_font_rid,
}
void TextServerFallback::_font_set_scale(const RID &p_font_rid, int64_t p_size, double p_scale) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1569,7 +1604,7 @@ void TextServerFallback::_font_set_scale(const RID &p_font_rid, int64_t p_size,
}
double TextServerFallback::_font_get_scale(const RID &p_font_rid, int64_t p_size) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -1585,7 +1620,7 @@ double TextServerFallback::_font_get_scale(const RID &p_font_rid, int64_t p_size
}
int64_t TextServerFallback::_font_get_texture_count(const RID &p_font_rid, const Vector2i &p_size) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
@@ -1597,7 +1632,7 @@ int64_t TextServerFallback::_font_get_texture_count(const RID &p_font_rid, const
}
void TextServerFallback::_font_clear_textures(const RID &p_font_rid, const Vector2i &p_size) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -1607,7 +1642,7 @@ void TextServerFallback::_font_clear_textures(const RID &p_font_rid, const Vecto
}
void TextServerFallback::_font_remove_texture(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1619,7 +1654,7 @@ void TextServerFallback::_font_remove_texture(const RID &p_font_rid, const Vecto
}
void TextServerFallback::_font_set_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const Ref<Image> &p_image) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
ERR_FAIL_COND(p_image.is_null());
@@ -1648,7 +1683,7 @@ void TextServerFallback::_font_set_texture_image(const RID &p_font_rid, const Ve
}
Ref<Image> TextServerFallback::_font_get_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Ref<Image>());
MutexLock lock(fd->mutex);
@@ -1662,7 +1697,7 @@ Ref<Image> TextServerFallback::_font_get_texture_image(const RID &p_font_rid, co
void TextServerFallback::_font_set_texture_offsets(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const PackedInt32Array &p_offsets) {
ERR_FAIL_COND(p_offsets.size() % 4 != 0);
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1681,7 +1716,7 @@ void TextServerFallback::_font_set_texture_offsets(const RID &p_font_rid, const
}
PackedInt32Array TextServerFallback::_font_get_texture_offsets(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, PackedInt32Array());
MutexLock lock(fd->mutex);
@@ -1706,7 +1741,7 @@ PackedInt32Array TextServerFallback::_font_get_texture_offsets(const RID &p_font
}
PackedInt32Array TextServerFallback::_font_get_glyph_list(const RID &p_font_rid, const Vector2i &p_size) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, PackedInt32Array());
MutexLock lock(fd->mutex);
@@ -1722,7 +1757,7 @@ PackedInt32Array TextServerFallback::_font_get_glyph_list(const RID &p_font_rid,
}
void TextServerFallback::_font_clear_glyphs(const RID &p_font_rid, const Vector2i &p_size) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1733,7 +1768,7 @@ void TextServerFallback::_font_clear_glyphs(const RID &p_font_rid, const Vector2
}
void TextServerFallback::_font_remove_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1744,7 +1779,7 @@ void TextServerFallback::_font_remove_glyph(const RID &p_font_rid, const Vector2
}
Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
@@ -1782,7 +1817,7 @@ Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64
}
void TextServerFallback::_font_set_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph, const Vector2 &p_advance) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1797,7 +1832,7 @@ void TextServerFallback::_font_set_glyph_advance(const RID &p_font_rid, int64_t
}
Vector2 TextServerFallback::_font_get_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
@@ -1827,7 +1862,7 @@ Vector2 TextServerFallback::_font_get_glyph_offset(const RID &p_font_rid, const
}
void TextServerFallback::_font_set_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Vector2 &p_offset) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1842,7 +1877,7 @@ void TextServerFallback::_font_set_glyph_offset(const RID &p_font_rid, const Vec
}
Vector2 TextServerFallback::_font_get_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
@@ -1872,7 +1907,7 @@ Vector2 TextServerFallback::_font_get_glyph_size(const RID &p_font_rid, const Ve
}
void TextServerFallback::_font_set_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Vector2 &p_gl_size) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1887,7 +1922,7 @@ void TextServerFallback::_font_set_glyph_size(const RID &p_font_rid, const Vecto
}
Rect2 TextServerFallback::_font_get_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Rect2());
MutexLock lock(fd->mutex);
@@ -1912,7 +1947,7 @@ Rect2 TextServerFallback::_font_get_glyph_uv_rect(const RID &p_font_rid, const V
}
void TextServerFallback::_font_set_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Rect2 &p_uv_rect) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1927,7 +1962,7 @@ void TextServerFallback::_font_set_glyph_uv_rect(const RID &p_font_rid, const Ve
}
int64_t TextServerFallback::_font_get_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, -1);
MutexLock lock(fd->mutex);
@@ -1952,7 +1987,7 @@ int64_t TextServerFallback::_font_get_glyph_texture_idx(const RID &p_font_rid, c
}
void TextServerFallback::_font_set_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, int64_t p_texture_idx) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1967,7 +2002,7 @@ void TextServerFallback::_font_set_glyph_texture_idx(const RID &p_font_rid, cons
}
RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, RID());
MutexLock lock(fd->mutex);
@@ -2013,7 +2048,7 @@ RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const
}
Size2 TextServerFallback::_font_get_glyph_texture_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Size2());
MutexLock lock(fd->mutex);
@@ -2059,7 +2094,7 @@ Size2 TextServerFallback::_font_get_glyph_texture_size(const RID &p_font_rid, co
}
Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, int64_t p_size, int64_t p_index) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
@@ -2109,7 +2144,7 @@ Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, i
}
TypedArray<Vector2i> TextServerFallback::_font_get_kerning_list(const RID &p_font_rid, int64_t p_size) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, TypedArray<Vector2i>());
MutexLock lock(fd->mutex);
@@ -2125,7 +2160,7 @@ TypedArray<Vector2i> TextServerFallback::_font_get_kerning_list(const RID &p_fon
}
void TextServerFallback::_font_clear_kerning_map(const RID &p_font_rid, int64_t p_size) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2136,7 +2171,7 @@ void TextServerFallback::_font_clear_kerning_map(const RID &p_font_rid, int64_t
}
void TextServerFallback::_font_remove_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2147,7 +2182,7 @@ void TextServerFallback::_font_remove_kerning(const RID &p_font_rid, int64_t p_s
}
void TextServerFallback::_font_set_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2158,7 +2193,7 @@ void TextServerFallback::_font_set_kerning(const RID &p_font_rid, int64_t p_size
}
Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
@@ -2202,7 +2237,7 @@ int64_t TextServerFallback::_font_get_char_from_glyph_index(const RID &p_font_ri
}
bool TextServerFallback::_font_has_char(const RID &p_font_rid, int64_t p_char) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_COND_V_MSG((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff), false, "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_char, 16) + ".");
if (!fd) {
return false;
@@ -2223,7 +2258,7 @@ bool TextServerFallback::_font_has_char(const RID &p_font_rid, int64_t p_char) c
}
String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
@@ -2256,7 +2291,7 @@ String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) cons
}
void TextServerFallback::_font_render_range(const RID &p_font_rid, const Vector2i &p_size, int64_t p_start, int64_t p_end) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
ERR_FAIL_COND_MSG((p_start >= 0xd800 && p_start <= 0xdfff) || (p_start > 0x10ffff), "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_start, 16) + ".");
ERR_FAIL_COND_MSG((p_end >= 0xd800 && p_end <= 0xdfff) || (p_end > 0x10ffff), "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_end, 16) + ".");
@@ -2291,7 +2326,7 @@ void TextServerFallback::_font_render_range(const RID &p_font_rid, const Vector2
}
void TextServerFallback::_font_render_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_index) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2322,7 +2357,7 @@ void TextServerFallback::_font_render_glyph(const RID &p_font_rid, const Vector2
}
void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_canvas, int64_t p_size, const Vector2 &p_pos, int64_t p_index, const Color &p_color) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2414,7 +2449,7 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const RID &p_canvas, int64_t p_size, int64_t p_outline_size, const Vector2 &p_pos, int64_t p_index, const Color &p_color) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2506,7 +2541,7 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R
}
bool TextServerFallback::_font_is_language_supported(const RID &p_font_rid, const String &p_language) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -2518,7 +2553,7 @@ bool TextServerFallback::_font_is_language_supported(const RID &p_font_rid, cons
}
void TextServerFallback::_font_set_language_support_override(const RID &p_font_rid, const String &p_language, bool p_supported) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2526,7 +2561,7 @@ void TextServerFallback::_font_set_language_support_override(const RID &p_font_r
}
bool TextServerFallback::_font_get_language_support_override(const RID &p_font_rid, const String &p_language) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -2534,7 +2569,7 @@ bool TextServerFallback::_font_get_language_support_override(const RID &p_font_r
}
void TextServerFallback::_font_remove_language_support_override(const RID &p_font_rid, const String &p_language) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2542,7 +2577,7 @@ void TextServerFallback::_font_remove_language_support_override(const RID &p_fon
}
PackedStringArray TextServerFallback::_font_get_language_support_overrides(const RID &p_font_rid) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, PackedStringArray());
MutexLock lock(fd->mutex);
@@ -2554,7 +2589,7 @@ PackedStringArray TextServerFallback::_font_get_language_support_overrides(const
}
bool TextServerFallback::_font_is_script_supported(const RID &p_font_rid, const String &p_script) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -2566,7 +2601,7 @@ bool TextServerFallback::_font_is_script_supported(const RID &p_font_rid, const
}
void TextServerFallback::_font_set_script_support_override(const RID &p_font_rid, const String &p_script, bool p_supported) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2574,7 +2609,7 @@ void TextServerFallback::_font_set_script_support_override(const RID &p_font_rid
}
bool TextServerFallback::_font_get_script_support_override(const RID &p_font_rid, const String &p_script) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -2582,7 +2617,7 @@ bool TextServerFallback::_font_get_script_support_override(const RID &p_font_rid
}
void TextServerFallback::_font_remove_script_support_override(const RID &p_font_rid, const String &p_script) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2592,7 +2627,7 @@ void TextServerFallback::_font_remove_script_support_override(const RID &p_font_
}
PackedStringArray TextServerFallback::_font_get_script_support_overrides(const RID &p_font_rid) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, PackedStringArray());
MutexLock lock(fd->mutex);
@@ -2604,7 +2639,7 @@ PackedStringArray TextServerFallback::_font_get_script_support_overrides(const R
}
void TextServerFallback::_font_set_opentype_feature_overrides(const RID &p_font_rid, const Dictionary &p_overrides) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2614,7 +2649,7 @@ void TextServerFallback::_font_set_opentype_feature_overrides(const RID &p_font_
}
Dictionary TextServerFallback::_font_get_opentype_feature_overrides(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
@@ -2626,7 +2661,7 @@ Dictionary TextServerFallback::_font_supported_feature_list(const RID &p_font_ri
}
Dictionary TextServerFallback::_font_supported_variation_list(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
@@ -2904,7 +2939,7 @@ bool TextServerFallback::_shaped_text_add_string(const RID &p_shaped, const Stri
ERR_FAIL_COND_V(p_size <= 0, false);
for (int i = 0; i < p_fonts.size(); i++) {
- ERR_FAIL_COND_V(!font_owner.get_or_null(p_fonts[i]), false);
+ ERR_FAIL_COND_V(!_get_font_data(p_fonts[i]), false);
}
if (p_text.is_empty()) {
diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h
index c44b45fc27..3b0b10ec35 100644
--- a/modules/text_server_fb/text_server_fb.h
+++ b/modules/text_server_fb/text_server_fb.h
@@ -245,6 +245,11 @@ class TextServerFallback : public TextServerExtension {
}
};
+ struct FontFallbackLinkedVariation {
+ RID base_font;
+ int extra_spacing[4] = { 0, 0, 0, 0 };
+ };
+
struct FontFallback {
Mutex mutex;
@@ -451,9 +456,19 @@ class TextServerFallback : public TextServerExtension {
// Common data.
double oversampling = 1.0;
+ mutable RID_PtrOwner<FontFallbackLinkedVariation> font_var_owner;
mutable RID_PtrOwner<FontFallback> font_owner;
mutable RID_PtrOwner<ShapedTextDataFallback> shaped_owner;
+ _FORCE_INLINE_ FontFallback *_get_font_data(const RID &p_font_rid) const {
+ RID rid = p_font_rid;
+ FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(rid);
+ if (unlikely(fdv)) {
+ rid = fdv->base_font;
+ }
+ return font_owner.get_or_null(rid);
+ }
+
struct SystemFontKey {
String font_name;
TextServer::FontAntialiasing antialiasing = TextServer::FONT_ANTIALIASING_GRAY;
@@ -569,6 +584,7 @@ public:
/* Font interface */
MODBIND0R(RID, create_font);
+ MODBIND1R(RID, create_font_linked_variation, const RID &);
MODBIND2(font_set_data, const RID &, const PackedByteArray &);
MODBIND3(font_set_data_ptr, const RID &, const uint8_t *, int64_t);
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
index 7cedfa6888..02709d4dc5 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
@@ -91,10 +91,6 @@ open class GodotEditor : GodotActivity() {
private val commandLineParams = ArrayList<String>()
override fun onCreate(savedInstanceState: Bundle?) {
- // We exclude certain permissions from the set we request at startup, as they'll be
- // requested on demand based on use-cases.
- PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO))
-
val params = intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
Log.d(TAG, "Received parameters ${params.contentToString()}")
updateCommandLineParams(params)
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
index 0e111d5247..f819063a22 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -928,6 +928,19 @@ class Godot(private val context: Context) : SensorEventListener {
}
/**
+ * Return true if the given feature is supported.
+ */
+ @Keep
+ private fun hasFeature(feature: String): Boolean {
+ for (plugin in pluginRegistry.allPlugins) {
+ if (plugin.supportsFeature(feature)) {
+ return true
+ }
+ }
+ return false
+ }
+
+ /**
* Get the list of gdextension modules to register.
*/
@Keep
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
index 4636f753af..417be7cef4 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
@@ -30,12 +30,15 @@
package org.godotengine.godot
+import android.Manifest
import android.app.Activity
import android.content.Intent
+import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import androidx.annotation.CallSuper
import androidx.fragment.app.FragmentActivity
+import org.godotengine.godot.utils.PermissionsUtil
import org.godotengine.godot.utils.ProcessPhoenix
/**
@@ -62,6 +65,10 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
private set
override fun onCreate(savedInstanceState: Bundle?) {
+ // We exclude certain permissions from the set we request at startup, as they'll be
+ // requested on demand based on use-cases.
+ PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO))
+
super.onCreate(savedInstanceState)
setContentView(R.layout.godot_app_layout)
@@ -148,6 +155,14 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
godotFragment?.onRequestPermissionsResult(requestCode, permissions, grantResults)
+
+ if (requestCode == PermissionsUtil.REQUEST_ALL_PERMISSION_REQ_CODE) {
+ Log.d(TAG, "Received permissions request result..")
+ for (i in permissions.indices) {
+ val permissionGranted = grantResults[i] == PackageManager.PERMISSION_GRANTED
+ Log.d(TAG, "Permission ${permissions[i]} ${if (permissionGranted) { "granted"} else { "denied" }}")
+ }
+ }
}
override fun onBackPressed() {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
index 7f3a3ac7a3..c0912ca4dc 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
@@ -316,6 +316,15 @@ public abstract class GodotPlugin {
}
/**
+ * Returns whether the plugin supports the given feature tag.
+ *
+ * @see <a href="https://docs.godotengine.org/en/stable/tutorials/export/feature_tags.html">Feature tags</a>
+ */
+ public boolean supportsFeature(String featureTag) {
+ return false;
+ }
+
+ /**
* Runs the specified action on the UI thread. If the current thread is the UI
* thread, then the action is executed immediately. If the current thread is
* not the UI thread, the action is posted to the event queue of the UI thread.
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
index 8353fc8dc6..9a82204467 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
@@ -160,6 +160,7 @@ public final class PermissionsUtil {
try {
if (manifestPermission.equals(Manifest.permission.MANAGE_EXTERNAL_STORAGE)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
+ Log.d(TAG, "Requesting permission " + manifestPermission);
try {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.setData(Uri.parse(String.format("package:%s", activity.getPackageName())));
@@ -173,6 +174,7 @@ public final class PermissionsUtil {
PermissionInfo permissionInfo = getPermissionInfo(activity, manifestPermission);
int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel;
if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, manifestPermission) != PackageManager.PERMISSION_GRANTED) {
+ Log.d(TAG, "Requesting permission " + manifestPermission);
requestedPermissions.add(manifestPermission);
}
}
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index 1703179b8e..cb6ebf14a8 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -82,6 +82,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
_end_benchmark_measure = p_env->GetMethodID(godot_class, "nativeEndBenchmarkMeasure", "(Ljava/lang/String;)V");
_dump_benchmark = p_env->GetMethodID(godot_class, "nativeDumpBenchmark", "(Ljava/lang/String;)V");
_get_gdextension_list_config_file = p_env->GetMethodID(godot_class, "getGDExtensionConfigFiles", "()[Ljava/lang/String;");
+ _has_feature = p_env->GetMethodID(godot_class, "hasFeature", "(Ljava/lang/String;)Z");
}
GodotJavaWrapper::~GodotJavaWrapper() {
@@ -373,3 +374,15 @@ void GodotJavaWrapper::dump_benchmark(const String &benchmark_file) {
env->CallVoidMethod(godot_instance, _dump_benchmark, j_benchmark_file);
}
}
+
+bool GodotJavaWrapper::has_feature(const String &p_feature) const {
+ if (_has_feature) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, false);
+
+ jstring j_feature = env->NewStringUTF(p_feature.utf8().get_data());
+ return env->CallBooleanMethod(godot_instance, _has_feature, j_feature);
+ } else {
+ return false;
+ }
+}
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index f427a2937c..7c6327c9e1 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -73,6 +73,7 @@ private:
jmethodID _begin_benchmark_measure = nullptr;
jmethodID _end_benchmark_measure = nullptr;
jmethodID _dump_benchmark = nullptr;
+ jmethodID _has_feature = nullptr;
public:
GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance);
@@ -110,6 +111,9 @@ public:
// Return the list of gdextensions config file.
Vector<String> get_gdextension_list_config_file() const;
+
+ // Return true if the given feature is supported.
+ bool has_feature(const String &p_feature) const;
};
#endif // JAVA_GODOT_WRAPPER_H
diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp
index 8f80516a9f..92dc5f909f 100644
--- a/platform/android/os_android.cpp
+++ b/platform/android/os_android.cpp
@@ -60,10 +60,10 @@ String _remove_symlink(const String &dir) {
// Change directory to the external data directory.
chdir(dir.utf8().get_data());
// Get the actual directory without the potential symlink.
- char dir_name_wihout_symlink[2048];
- getcwd(dir_name_wihout_symlink, 2048);
+ char dir_name_without_symlink[2048];
+ getcwd(dir_name_without_symlink, 2048);
// Convert back to a String.
- String dir_without_symlink(dir_name_wihout_symlink);
+ String dir_without_symlink(dir_name_without_symlink);
// Restore original current directory.
chdir(current_dir_name);
return dir_without_symlink;
@@ -749,6 +749,11 @@ bool OS_Android::_check_internal_feature_support(const String &p_feature) {
return true;
}
#endif
+
+ if (godot_java->has_feature(p_feature)) {
+ return true;
+ }
+
return false;
}
diff --git a/platform/linuxbsd/crash_handler_linuxbsd.cpp b/platform/linuxbsd/crash_handler_linuxbsd.cpp
index 3a245460b4..fd4bcf92be 100644
--- a/platform/linuxbsd/crash_handler_linuxbsd.cpp
+++ b/platform/linuxbsd/crash_handler_linuxbsd.cpp
@@ -49,6 +49,10 @@
#include <stdlib.h>
static void handle_crash(int sig) {
+ signal(SIGSEGV, SIG_DFL);
+ signal(SIGFPE, SIG_DFL);
+ signal(SIGILL, SIG_DFL);
+
if (OS::get_singleton() == nullptr) {
abort();
}
@@ -156,9 +160,9 @@ void CrashHandler::disable() {
}
#ifdef CRASH_HANDLER_ENABLED
- signal(SIGSEGV, nullptr);
- signal(SIGFPE, nullptr);
- signal(SIGILL, nullptr);
+ signal(SIGSEGV, SIG_DFL);
+ signal(SIGFPE, SIG_DFL);
+ signal(SIGILL, SIG_DFL);
#endif
disabled = true;
diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp
index 91c14e0e91..cf4354139a 100644
--- a/platform/linuxbsd/freedesktop_portal_desktop.cpp
+++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp
@@ -142,36 +142,40 @@ void FreeDesktopPortalDesktop::append_dbus_string(DBusMessageIter *p_iter, const
}
}
-void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filters) {
+void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filter_names, const Vector<String> &p_filter_exts) {
DBusMessageIter dict_iter;
DBusMessageIter var_iter;
DBusMessageIter arr_iter;
const char *filters_key = "filters";
+ ERR_FAIL_COND(p_filter_names.size() != p_filter_exts.size());
+
dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter);
dbus_message_iter_append_basic(&dict_iter, DBUS_TYPE_STRING, &filters_key);
dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "a(sa(us))", &var_iter);
dbus_message_iter_open_container(&var_iter, DBUS_TYPE_ARRAY, "(sa(us))", &arr_iter);
- for (int i = 0; i < p_filters.size(); i++) {
- Vector<String> tokens = p_filters[i].split(";");
- if (tokens.size() == 2) {
- DBusMessageIter struct_iter;
- DBusMessageIter array_iter;
- DBusMessageIter array_struct_iter;
- dbus_message_iter_open_container(&arr_iter, DBUS_TYPE_STRUCT, nullptr, &struct_iter);
- append_dbus_string(&struct_iter, tokens[0]);
-
- dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(us)", &array_iter);
+ for (int i = 0; i < p_filter_names.size(); i++) {
+ DBusMessageIter struct_iter;
+ DBusMessageIter array_iter;
+ DBusMessageIter array_struct_iter;
+ dbus_message_iter_open_container(&arr_iter, DBUS_TYPE_STRUCT, nullptr, &struct_iter);
+ append_dbus_string(&struct_iter, p_filter_names[i]);
+
+ dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(us)", &array_iter);
+ String flt = p_filter_exts[i];
+ int filter_slice_count = flt.get_slice_count(",");
+ for (int j = 0; j < filter_slice_count; j++) {
dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter);
+ String str = (flt.get_slice(",", j).strip_edges());
{
const unsigned nil = 0;
dbus_message_iter_append_basic(&array_struct_iter, DBUS_TYPE_UINT32, &nil);
}
- append_dbus_string(&array_struct_iter, tokens[1]);
+ append_dbus_string(&array_struct_iter, str);
dbus_message_iter_close_container(&array_iter, &array_struct_iter);
- dbus_message_iter_close_container(&struct_iter, &array_iter);
- dbus_message_iter_close_container(&arr_iter, &struct_iter);
}
+ dbus_message_iter_close_container(&struct_iter, &array_iter);
+ dbus_message_iter_close_container(&arr_iter, &struct_iter);
}
dbus_message_iter_close_container(&var_iter, &arr_iter);
dbus_message_iter_close_container(&dict_iter, &var_iter);
@@ -219,7 +223,7 @@ void FreeDesktopPortalDesktop::append_dbus_dict_bool(DBusMessageIter *p_iter, co
dbus_message_iter_close_container(p_iter, &dict_iter);
}
-bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_iter, bool &r_cancel, Vector<String> &r_urls) {
+bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index) {
ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_UINT32, false);
dbus_uint32_t resp_code;
@@ -243,7 +247,22 @@ bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_it
DBusMessageIter var_iter;
dbus_message_iter_recurse(&iter, &var_iter);
- if (strcmp(key, "uris") == 0) {
+ if (strcmp(key, "current_filter") == 0) { // (sa(us))
+ if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_STRUCT) {
+ DBusMessageIter struct_iter;
+ dbus_message_iter_recurse(&var_iter, &struct_iter);
+ while (dbus_message_iter_get_arg_type(&struct_iter) == DBUS_TYPE_STRING) {
+ const char *value;
+ dbus_message_iter_get_basic(&struct_iter, &value);
+ String name = String::utf8(value);
+
+ r_index = p_names.find(name);
+ if (!dbus_message_iter_next(&struct_iter)) {
+ break;
+ }
+ }
+ }
+ } else if (strcmp(key, "uris") == 0) { // as
if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_ARRAY) {
DBusMessageIter uri_iter;
dbus_message_iter_recurse(&var_iter, &uri_iter);
@@ -266,17 +285,43 @@ bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_it
return true;
}
-Error FreeDesktopPortalDesktop::file_dialog_show(const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
+Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
if (unsupported) {
return FAILED;
}
+ ERR_FAIL_INDEX_V(int(p_mode), DisplayServer::FILE_DIALOG_MODE_SAVE_MAX, FAILED);
+
+ Vector<String> filter_names;
+ Vector<String> filter_exts;
+ for (int i = 0; i < p_filters.size(); i++) {
+ Vector<String> tokens = p_filters[i].split(";");
+ if (tokens.size() >= 1) {
+ String flt = tokens[0].strip_edges();
+ if (!flt.is_empty()) {
+ if (tokens.size() == 2) {
+ filter_exts.push_back(flt);
+ filter_names.push_back(tokens[1]);
+ } else {
+ filter_exts.push_back(flt);
+ filter_names.push_back(flt);
+ }
+ }
+ }
+ }
+ if (filter_names.is_empty()) {
+ filter_exts.push_back("*.*");
+ filter_names.push_back(RTR("All Files"));
+ }
+
DBusError err;
dbus_error_init(&err);
// Open connection and add signal handler.
FileDialogData fd;
fd.callback = p_callback;
+ fd.prev_focus = p_window_id;
+ fd.filter_names = filter_names;
CryptoCore::RandomGenerator rng;
ERR_FAIL_COND_V_MSG(rng.init(), FAILED, "Failed to initialize random number generator.");
@@ -307,16 +352,10 @@ Error FreeDesktopPortalDesktop::file_dialog_show(const String &p_xid, const Stri
// Generate FileChooser message.
const char *method = nullptr;
- switch (p_mode) {
- case DisplayServer::FILE_DIALOG_MODE_SAVE_FILE: {
- method = "SaveFile";
- } break;
- case DisplayServer::FILE_DIALOG_MODE_OPEN_ANY:
- case DisplayServer::FILE_DIALOG_MODE_OPEN_FILE:
- case DisplayServer::FILE_DIALOG_MODE_OPEN_DIR:
- case DisplayServer::FILE_DIALOG_MODE_OPEN_FILES: {
- method = "OpenFile";
- } break;
+ if (p_mode == DisplayServer::FILE_DIALOG_MODE_SAVE_FILE) {
+ method = "SaveFile";
+ } else {
+ method = "OpenFile";
}
DBusMessage *message = dbus_message_new_method_call(BUS_OBJECT_NAME, BUS_OBJECT_PATH, BUS_INTERFACE_FILE_CHOOSER, method);
@@ -333,7 +372,7 @@ Error FreeDesktopPortalDesktop::file_dialog_show(const String &p_xid, const Stri
append_dbus_dict_string(&arr_iter, "handle_token", token);
append_dbus_dict_bool(&arr_iter, "multiple", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_FILES);
append_dbus_dict_bool(&arr_iter, "directory", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_DIR);
- append_dbus_dict_filters(&arr_iter, p_filters);
+ append_dbus_dict_filters(&arr_iter, filter_names, filter_exts);
append_dbus_dict_string(&arr_iter, "current_folder", p_current_directory, true);
if (p_mode == DisplayServer::FILE_DIALOG_MODE_SAVE_FILE) {
append_dbus_dict_string(&arr_iter, "current_name", p_filename);
@@ -408,13 +447,18 @@ void FreeDesktopPortalDesktop::_thread_file_dialog_monitor(void *p_ud) {
if (dbus_message_iter_init(msg, &iter)) {
bool cancel = false;
Vector<String> uris;
- file_chooser_parse_response(&iter, cancel, uris);
+ int index = 0;
+ file_chooser_parse_response(&iter, fd.filter_names, cancel, uris, index);
if (fd.callback.is_valid()) {
Variant v_status = !cancel;
Variant v_files = uris;
- Variant *v_args[2] = { &v_status, &v_files };
- fd.callback.call_deferredp((const Variant **)&v_args, 2);
+ Variant v_index = index;
+ Variant *v_args[3] = { &v_status, &v_files, &v_index };
+ fd.callback.call_deferredp((const Variant **)&v_args, 3);
+ }
+ if (fd.prev_focus != DisplayServer::INVALID_WINDOW_ID) {
+ callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(fd.prev_focus);
}
}
dbus_message_unref(msg);
diff --git a/platform/linuxbsd/freedesktop_portal_desktop.h b/platform/linuxbsd/freedesktop_portal_desktop.h
index a9b83b3844..503c382207 100644
--- a/platform/linuxbsd/freedesktop_portal_desktop.h
+++ b/platform/linuxbsd/freedesktop_portal_desktop.h
@@ -49,13 +49,15 @@ private:
bool read_setting(const char *p_namespace, const char *p_key, int p_type, void *r_value);
static void append_dbus_string(DBusMessageIter *p_iter, const String &p_string);
- static void append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filters);
+ static void append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filter_names, const Vector<String> &p_filter_exts);
static void append_dbus_dict_string(DBusMessageIter *p_iter, const String &p_key, const String &p_value, bool p_as_byte_array = false);
static void append_dbus_dict_bool(DBusMessageIter *p_iter, const String &p_key, bool p_value);
- static bool file_chooser_parse_response(DBusMessageIter *p_iter, bool &r_cancel, Vector<String> &r_urls);
+ static bool file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index);
struct FileDialogData {
+ Vector<String> filter_names;
DBusConnection *connection = nullptr;
+ DisplayServer::WindowID prev_focus = DisplayServer::INVALID_WINDOW_ID;
Callable callback;
String path;
};
@@ -73,7 +75,7 @@ public:
bool is_supported() { return !unsupported; }
- Error file_dialog_show(const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback);
+ Error file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback);
// Retrieve the system's preferred color scheme.
// 0: No preference or unknown.
diff --git a/platform/linuxbsd/platform_gl.h b/platform/linuxbsd/platform_gl.h
index 1c19c4518a..5d4c2d8978 100644
--- a/platform/linuxbsd/platform_gl.h
+++ b/platform/linuxbsd/platform_gl.h
@@ -35,6 +35,10 @@
#define GL_API_ENABLED // Allow using desktop GL.
#endif
+#ifndef GLES_API_ENABLED
+#define GLES_API_ENABLED // Allow using GLES.
+#endif
+
#include "thirdparty/glad/glad/egl.h"
#include "thirdparty/glad/glad/gl.h"
diff --git a/platform/linuxbsd/x11/SCsub b/platform/linuxbsd/x11/SCsub
index a4890391ce..bbfaaf10d1 100644
--- a/platform/linuxbsd/x11/SCsub
+++ b/platform/linuxbsd/x11/SCsub
@@ -25,7 +25,9 @@ if env["vulkan"]:
if env["opengl3"]:
env.Append(CPPDEFINES=["GLAD_GLX_NO_X11"])
- source_files.append(["gl_manager_x11.cpp", "detect_prime_x11.cpp", "#thirdparty/glad/glx.c"])
+ source_files.append(
+ ["gl_manager_x11_egl.cpp", "gl_manager_x11.cpp", "detect_prime_x11.cpp", "#thirdparty/glad/glx.c"]
+ )
objects = []
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index 897a6438d2..31846c80a2 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -364,14 +364,14 @@ bool DisplayServerX11::is_dark_mode() const {
}
Error DisplayServerX11::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
- WindowID window_id = _get_focused_window_or_popup();
+ WindowID window_id = last_focused_window;
if (!windows.has(window_id)) {
window_id = MAIN_WINDOW_ID;
}
String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window);
- return portal_desktop->file_dialog_show(xid, p_title, p_current_directory, p_filename, p_mode, p_filters, p_callback);
+ return portal_desktop->file_dialog_show(last_focused_window, xid, p_title, p_current_directory, p_filename, p_mode, p_filters, p_callback);
}
#endif
@@ -1495,6 +1495,9 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) {
if (gl_manager) {
gl_manager->window_destroy(p_id);
}
+ if (gl_manager_egl) {
+ gl_manager_egl->window_destroy(p_id);
+ }
#endif
if (wd.xic) {
@@ -1534,6 +1537,9 @@ int64_t DisplayServerX11::window_get_native_handle(HandleType p_handle_type, Win
if (gl_manager) {
return (int64_t)gl_manager->get_glx_context(p_window);
}
+ if (gl_manager_egl) {
+ return (int64_t)gl_manager_egl->get_context(p_window);
+ }
return 0;
}
#endif
@@ -1711,6 +1717,9 @@ void DisplayServerX11::gl_window_make_current(DisplayServer::WindowID p_window_i
if (gl_manager) {
gl_manager->window_make_current(p_window_id);
}
+ if (gl_manager_egl) {
+ gl_manager_egl->window_make_current(p_window_id);
+ }
#endif
}
@@ -2012,6 +2021,9 @@ void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) {
if (gl_manager) {
gl_manager->window_resize(p_window, xwa.width, xwa.height);
}
+ if (gl_manager_egl) {
+ gl_manager_egl->window_resize(p_window, xwa.width, xwa.height);
+ }
#endif
}
@@ -3721,6 +3733,9 @@ void DisplayServerX11::_window_changed(XEvent *event) {
if (gl_manager) {
gl_manager->window_resize(window_id, wd.size.width, wd.size.height);
}
+ if (gl_manager_egl) {
+ gl_manager_egl->window_resize(window_id, wd.size.width, wd.size.height);
+ }
#endif
if (!wd.rect_changed_callback.is_null()) {
@@ -4855,6 +4870,9 @@ void DisplayServerX11::release_rendering_thread() {
if (gl_manager) {
gl_manager->release_current();
}
+ if (gl_manager_egl) {
+ gl_manager_egl->release_current();
+ }
#endif
}
@@ -4863,6 +4881,9 @@ void DisplayServerX11::make_rendering_thread() {
if (gl_manager) {
gl_manager->make_current();
}
+ if (gl_manager_egl) {
+ gl_manager_egl->make_current();
+ }
#endif
}
@@ -4871,6 +4892,9 @@ void DisplayServerX11::swap_buffers() {
if (gl_manager) {
gl_manager->swap_buffers();
}
+ if (gl_manager_egl) {
+ gl_manager_egl->swap_buffers();
+ }
#endif
}
@@ -5024,6 +5048,9 @@ void DisplayServerX11::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mo
if (gl_manager) {
gl_manager->set_use_vsync(p_vsync_mode != DisplayServer::VSYNC_DISABLED);
}
+ if (gl_manager_egl) {
+ gl_manager_egl->set_use_vsync(p_vsync_mode != DisplayServer::VSYNC_DISABLED);
+ }
#endif
}
@@ -5038,6 +5065,9 @@ DisplayServer::VSyncMode DisplayServerX11::window_get_vsync_mode(WindowID p_wind
if (gl_manager) {
return gl_manager->is_using_vsync() ? DisplayServer::VSYNC_ENABLED : DisplayServer::VSYNC_DISABLED;
}
+ if (gl_manager_egl) {
+ return gl_manager_egl->is_using_vsync() ? DisplayServer::VSYNC_ENABLED : DisplayServer::VSYNC_DISABLED;
+ }
#endif
return DisplayServer::VSYNC_ENABLED;
}
@@ -5050,6 +5080,7 @@ Vector<String> DisplayServerX11::get_rendering_drivers_func() {
#endif
#ifdef GLES3_ENABLED
drivers.push_back("opengl3");
+ drivers.push_back("opengl3_es");
#endif
return drivers;
@@ -5092,6 +5123,21 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V
ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't acquire visual info from display.");
vi_selected = true;
}
+ if (gl_manager_egl) {
+ XVisualInfo visual_info_template;
+ int visual_id = gl_manager_egl->display_get_native_visual_id(x11_display);
+ ERR_FAIL_COND_V_MSG(visual_id < 0, INVALID_WINDOW_ID, "Unable to get a visual id.");
+
+ visual_info_template.visualid = (VisualID)visual_id;
+
+ int number_of_visuals = 0;
+ XVisualInfo *vi_list = XGetVisualInfo(x11_display, VisualIDMask, &visual_info_template, &number_of_visuals);
+ ERR_FAIL_COND_V(number_of_visuals <= 0, INVALID_WINDOW_ID);
+
+ visualInfo = vi_list[0];
+
+ XFree(vi_list);
+ }
#endif
if (!vi_selected) {
@@ -5364,8 +5410,12 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V
if (gl_manager) {
Error err = gl_manager->window_create(id, wd.x11_window, x11_display, win_rect.size.width, win_rect.size.height);
ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create an OpenGL window");
- window_set_vsync_mode(p_vsync_mode, id);
}
+ if (gl_manager_egl) {
+ Error err = gl_manager_egl->window_create(id, x11_display, &wd.x11_window, win_rect.size.width, win_rect.size.height);
+ ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Failed to create an OpenGLES window.");
+ }
+ window_set_vsync_mode(p_vsync_mode, id);
#endif
//set_class_hint(x11_display, wd.x11_window);
@@ -5766,7 +5816,7 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
#endif
// Initialize context and rendering device.
#if defined(GLES3_ENABLED)
- if (rendering_driver == "opengl3") {
+ if (rendering_driver == "opengl3" || rendering_driver == "opengl3_es") {
if (getenv("DRI_PRIME") == nullptr) {
int use_prime = -1;
@@ -5807,7 +5857,8 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
setenv("DRI_PRIME", "1", 1);
}
}
-
+ }
+ if (rendering_driver == "opengl3") {
GLManager_X11::ContextType opengl_api_type = GLManager_X11::GLES_3_0_COMPATIBLE;
gl_manager = memnew(GLManager_X11(p_resolution, opengl_api_type));
@@ -5820,14 +5871,20 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
}
driver_found = true;
- if (true) {
- RasterizerGLES3::make_current(true);
- } else {
- memdelete(gl_manager);
- gl_manager = nullptr;
+ RasterizerGLES3::make_current(true);
+ }
+ if (rendering_driver == "opengl3_es") {
+ gl_manager_egl = memnew(GLManagerEGL_X11);
+
+ if (gl_manager_egl->initialize() != OK) {
+ memdelete(gl_manager_egl);
+ gl_manager_egl = nullptr;
r_error = ERR_UNAVAILABLE;
return;
}
+ driver_found = true;
+
+ RasterizerGLES3::make_current(false);
}
#endif
if (!driver_found) {
@@ -6044,6 +6101,9 @@ DisplayServerX11::~DisplayServerX11() {
if (gl_manager) {
gl_manager->window_destroy(E.key);
}
+ if (gl_manager_egl) {
+ gl_manager_egl->window_destroy(E.key);
+ }
#endif
WindowData &wd = E.value;
@@ -6094,6 +6154,10 @@ DisplayServerX11::~DisplayServerX11() {
memdelete(gl_manager);
gl_manager = nullptr;
}
+ if (gl_manager_egl) {
+ memdelete(gl_manager_egl);
+ gl_manager_egl = nullptr;
+ }
#endif
if (xrandr_handle) {
diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h
index 71beddce76..9706a4aa11 100644
--- a/platform/linuxbsd/x11/display_server_x11.h
+++ b/platform/linuxbsd/x11/display_server_x11.h
@@ -54,6 +54,7 @@
#if defined(GLES3_ENABLED)
#include "x11/gl_manager_x11.h"
+#include "x11/gl_manager_x11_egl.h"
#endif
#if defined(VULKAN_ENABLED)
@@ -138,6 +139,7 @@ class DisplayServerX11 : public DisplayServer {
#if defined(GLES3_ENABLED)
GLManager_X11 *gl_manager = nullptr;
+ GLManagerEGL_X11 *gl_manager_egl = nullptr;
#endif
#if defined(VULKAN_ENABLED)
VulkanContextX11 *context_vulkan = nullptr;
diff --git a/platform/linuxbsd/x11/gl_manager_x11_egl.cpp b/platform/linuxbsd/x11/gl_manager_x11_egl.cpp
new file mode 100644
index 0000000000..544684ebf1
--- /dev/null
+++ b/platform/linuxbsd/x11/gl_manager_x11_egl.cpp
@@ -0,0 +1,63 @@
+/**************************************************************************/
+/* gl_manager_x11_egl.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "gl_manager_x11_egl.h"
+
+#if defined(X11_ENABLED) && defined(GLES3_ENABLED)
+
+#include <stdio.h>
+#include <stdlib.h>
+
+const char *GLManagerEGL_X11::_get_platform_extension_name() const {
+ return "EGL_KHR_platform_x11";
+}
+
+EGLenum GLManagerEGL_X11::_get_platform_extension_enum() const {
+ return EGL_PLATFORM_X11_KHR;
+}
+
+Vector<EGLAttrib> GLManagerEGL_X11::_get_platform_display_attributes() const {
+ return Vector<EGLAttrib>();
+}
+
+EGLenum GLManagerEGL_X11::_get_platform_api_enum() const {
+ return EGL_OPENGL_ES_API;
+}
+
+Vector<EGLint> GLManagerEGL_X11::_get_platform_context_attribs() const {
+ Vector<EGLint> ret;
+ ret.push_back(EGL_CONTEXT_CLIENT_VERSION);
+ ret.push_back(3);
+ ret.push_back(EGL_NONE);
+
+ return ret;
+}
+
+#endif // WINDOWS_ENABLED && GLES3_ENABLED
diff --git a/platform/linuxbsd/x11/gl_manager_x11_egl.h b/platform/linuxbsd/x11/gl_manager_x11_egl.h
new file mode 100644
index 0000000000..b9c96619c4
--- /dev/null
+++ b/platform/linuxbsd/x11/gl_manager_x11_egl.h
@@ -0,0 +1,61 @@
+/**************************************************************************/
+/* gl_manager_x11_egl.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef GL_MANAGER_X11_EGL_H
+#define GL_MANAGER_X11_EGL_H
+
+#if defined(X11_ENABLED) && defined(GLES3_ENABLED)
+
+#include "core/error/error_list.h"
+#include "core/os/os.h"
+#include "core/templates/local_vector.h"
+#include "drivers/egl/egl_manager.h"
+#include "servers/display_server.h"
+
+#include <X11/Xlib.h>
+
+class GLManagerEGL_X11 : public EGLManager {
+private:
+ virtual const char *_get_platform_extension_name() const override;
+ virtual EGLenum _get_platform_extension_enum() const override;
+ virtual EGLenum _get_platform_api_enum() const override;
+ virtual Vector<EGLAttrib> _get_platform_display_attributes() const override;
+ virtual Vector<EGLint> _get_platform_context_attribs() const override;
+
+public:
+ void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height) {}
+
+ GLManagerEGL_X11(){};
+ ~GLManagerEGL_X11(){};
+};
+
+#endif // X11_ENABLED && GLES3_ENABLED
+
+#endif // GL_MANAGER_X11_EGL_H
diff --git a/platform/macos/crash_handler_macos.mm b/platform/macos/crash_handler_macos.mm
index 7f9a88121e..7c0cab0210 100644
--- a/platform/macos/crash_handler_macos.mm
+++ b/platform/macos/crash_handler_macos.mm
@@ -72,6 +72,10 @@ static uint64_t load_address() {
}
static void handle_crash(int sig) {
+ signal(SIGSEGV, SIG_DFL);
+ signal(SIGFPE, SIG_DFL);
+ signal(SIGILL, SIG_DFL);
+
if (OS::get_singleton() == nullptr) {
abort();
}
@@ -186,9 +190,9 @@ void CrashHandler::disable() {
}
#ifdef CRASH_HANDLER_ENABLED
- signal(SIGSEGV, nullptr);
- signal(SIGFPE, nullptr);
- signal(SIGILL, nullptr);
+ signal(SIGSEGV, SIG_DFL);
+ signal(SIGFPE, SIG_DFL);
+ signal(SIGILL, SIG_DFL);
#endif
disabled = true;
diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h
index c03b4765f8..2ca9e493b7 100644
--- a/platform/macos/display_server_macos.h
+++ b/platform/macos/display_server_macos.h
@@ -139,7 +139,14 @@ private:
NSMenu *apple_menu = nullptr;
NSMenu *dock_menu = nullptr;
- HashMap<String, NSMenu *> submenu;
+ struct MenuData {
+ Callable open;
+ Callable close;
+ NSMenu *menu = nullptr;
+ bool is_open = false;
+ };
+ HashMap<String, MenuData> submenu;
+ HashMap<NSMenu *, String> submenu_inv;
struct WarpEvent {
NSTimeInterval timestamp;
@@ -197,6 +204,7 @@ private:
const NSMenu *_get_menu_root(const String &p_menu_root) const;
NSMenu *_get_menu_root(const String &p_menu_root);
+ bool _is_menu_opened(NSMenu *p_menu) const;
WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect);
void _update_window_style(WindowData p_wd);
@@ -223,6 +231,8 @@ private:
public:
NSMenu *get_dock_menu() const;
void menu_callback(id p_sender);
+ void menu_open(NSMenu *p_menu);
+ void menu_close(NSMenu *p_menu);
bool has_window(WindowID p_window) const;
WindowData &get_window(WindowID p_window);
@@ -245,6 +255,7 @@ public:
WindowID _get_focused_window_or_popup() const;
void mouse_enter_window(WindowID p_window);
void mouse_exit_window(WindowID p_window);
+ void update_presentation_mode();
void window_destroy(WindowID p_window);
void window_resize(WindowID p_window, int p_width, int p_height);
@@ -253,6 +264,8 @@ public:
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;
+ virtual void global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callback = Callable(), const Callable &p_close_callback = Callable()) override;
+
virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override;
virtual int global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
virtual int global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
@@ -276,6 +289,7 @@ public:
virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) const override;
virtual Key global_menu_get_item_accelerator(const String &p_menu_root, int p_idx) const override;
virtual bool global_menu_is_item_disabled(const String &p_menu_root, int p_idx) const override;
+ virtual bool global_menu_is_item_hidden(const String &p_menu_root, int p_idx) const override;
virtual String global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const override;
virtual int global_menu_get_item_state(const String &p_menu_root, int p_idx) const override;
virtual int global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const override;
@@ -287,11 +301,13 @@ public:
virtual void global_menu_set_item_radio_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override;
virtual void global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback) override;
virtual void global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback) override;
+ virtual void global_menu_set_item_hover_callbacks(const String &p_menu_root, int p_idx, const Callable &p_callback) override;
virtual void global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag) override;
virtual void global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text) override;
virtual void global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu) override;
virtual void global_menu_set_item_accelerator(const String &p_menu_root, int p_idx, Key p_keycode) override;
virtual void global_menu_set_item_disabled(const String &p_menu_root, int p_idx, bool p_disabled) override;
+ virtual void global_menu_set_item_hidden(const String &p_menu_root, int p_idx, bool p_hidden) override;
virtual void global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) override;
virtual void global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state) override;
virtual void global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states) override;
@@ -368,6 +384,7 @@ public:
virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_title_size(const String &p_title, WindowID p_window) const override;
virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window = MAIN_WINDOW_ID) override;
virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index 56cb4938ec..67d6f4214f 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -75,7 +75,7 @@ const NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) cons
} else {
// Submenu.
if (submenu.has(p_menu_root)) {
- menu = submenu[p_menu_root];
+ menu = submenu[p_menu_root].menu;
}
}
if (menu == apple_menu) {
@@ -99,9 +99,10 @@ NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) {
NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]];
[n_menu setAutoenablesItems:NO];
[n_menu setDelegate:menu_delegate];
- submenu[p_menu_root] = n_menu;
+ submenu[p_menu_root].menu = n_menu;
+ submenu_inv[n_menu] = p_menu_root;
}
- menu = submenu[p_menu_root];
+ menu = submenu[p_menu_root].menu;
}
if (menu == apple_menu) {
// Do not allow to change Apple menu.
@@ -609,6 +610,30 @@ NSMenu *DisplayServerMacOS::get_dock_menu() const {
return dock_menu;
}
+void DisplayServerMacOS::menu_open(NSMenu *p_menu) {
+ if (submenu_inv.has(p_menu)) {
+ MenuData &md = submenu[submenu_inv[p_menu]];
+ md.is_open = true;
+ if (md.open.is_valid()) {
+ Variant ret;
+ Callable::CallError ce;
+ md.open.callp(nullptr, 0, ret, ce);
+ }
+ }
+}
+
+void DisplayServerMacOS::menu_close(NSMenu *p_menu) {
+ if (submenu_inv.has(p_menu)) {
+ MenuData &md = submenu[submenu_inv[p_menu]];
+ md.is_open = false;
+ if (md.close.is_valid()) {
+ Variant ret;
+ Callable::CallError ce;
+ md.close.callp(nullptr, 0, ret, ce);
+ }
+ }
+}
+
void DisplayServerMacOS::menu_callback(id p_sender) {
if (![p_sender representedObject]) {
return;
@@ -749,6 +774,7 @@ void DisplayServerMacOS::window_destroy(WindowID p_window) {
}
#endif
windows.erase(p_window);
+ update_presentation_mode();
}
void DisplayServerMacOS::window_resize(WindowID p_window, int p_width, int p_height) {
@@ -815,6 +841,24 @@ bool DisplayServerMacOS::_has_help_menu() const {
}
}
+bool DisplayServerMacOS::_is_menu_opened(NSMenu *p_menu) const {
+ if (submenu_inv.has(p_menu)) {
+ const MenuData &md = submenu[submenu_inv[p_menu]];
+ if (md.is_open) {
+ return true;
+ }
+ }
+ for (NSInteger i = (p_menu == [NSApp mainMenu]) ? 1 : 0; i < [p_menu numberOfItems]; i++) {
+ const NSMenuItem *menu_item = [p_menu itemAtIndex:i];
+ if ([menu_item submenu]) {
+ if (_is_menu_opened([menu_item submenu])) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
NSMenuItem *DisplayServerMacOS::_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out) {
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
@@ -998,6 +1042,23 @@ int DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_roo
return out;
}
+void DisplayServerMacOS::global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callback, const Callable &p_close_callback) {
+ _THREAD_SAFE_METHOD_
+
+ if (p_menu_root != "" && p_menu_root.to_lower() != "_main" && p_menu_root.to_lower() != "_dock") {
+ // Submenu.
+ if (!submenu.has(p_menu_root)) {
+ NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]];
+ [n_menu setAutoenablesItems:NO];
+ [n_menu setDelegate:menu_delegate];
+ submenu[p_menu_root].menu = n_menu;
+ submenu_inv[n_menu] = p_menu_root;
+ }
+ submenu[p_menu_root].open = p_open_callback;
+ submenu[p_menu_root].close = p_close_callback;
+ }
+}
+
int DisplayServerMacOS::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) {
_THREAD_SAFE_METHOD_
@@ -1251,13 +1312,9 @@ String DisplayServerMacOS::global_menu_get_item_submenu(const String &p_menu_roo
ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String());
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
- const NSMenu *sub_menu = [menu_item submenu];
- if (sub_menu) {
- for (const KeyValue<String, NSMenu *> &E : submenu) {
- if (E.value == sub_menu) {
- return E.key;
- }
- }
+ NSMenu *sub_menu = [menu_item submenu];
+ if (sub_menu && submenu_inv.has(sub_menu)) {
+ return submenu_inv[sub_menu];
}
}
}
@@ -1318,6 +1375,24 @@ bool DisplayServerMacOS::global_menu_is_item_disabled(const String &p_menu_root,
return false;
}
+bool DisplayServerMacOS::global_menu_is_item_hidden(const String &p_menu_root, int p_idx) const {
+ _THREAD_SAFE_METHOD_
+
+ const NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false);
+ const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ return [menu_item isHidden];
+ }
+ }
+ return false;
+}
+
String DisplayServerMacOS::global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const {
_THREAD_SAFE_METHOD_
@@ -1497,6 +1572,25 @@ void DisplayServerMacOS::global_menu_set_item_callback(const String &p_menu_root
}
}
+void DisplayServerMacOS::global_menu_set_item_hover_callbacks(const String &p_menu_root, int p_idx, const Callable &p_callback) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ ERR_FAIL_COND(!obj);
+ obj->hover_callback = p_callback;
+ }
+ }
+}
+
void DisplayServerMacOS::global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback) {
_THREAD_SAFE_METHOD_
@@ -1556,6 +1650,23 @@ void DisplayServerMacOS::global_menu_set_item_submenu(const String &p_menu_root,
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu && p_submenu.is_empty()) {
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) {
+ ERR_PRINT("Can't remove open menu!");
+ return;
+ }
+ [menu setSubmenu:nil forItem:menu_item];
+ }
+ return;
+ }
+
NSMenu *sub_menu = _get_menu_root(p_submenu);
if (menu && sub_menu) {
if (sub_menu == menu) {
@@ -1590,9 +1701,13 @@ void DisplayServerMacOS::global_menu_set_item_accelerator(const String &p_menu_r
ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
- [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_keycode)];
- String keycode = KeyMappingMacOS::keycode_get_native_string(p_keycode & KeyModifierMask::CODE_MASK);
- [menu_item setKeyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
+ if (p_keycode == Key::NONE) {
+ [menu_item setKeyEquivalent:@""];
+ } else {
+ [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_keycode)];
+ String keycode = KeyMappingMacOS::keycode_get_native_string(p_keycode & KeyModifierMask::CODE_MASK);
+ [menu_item setKeyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
+ }
}
}
}
@@ -1614,6 +1729,23 @@ void DisplayServerMacOS::global_menu_set_item_disabled(const String &p_menu_root
}
}
+void DisplayServerMacOS::global_menu_set_item_hidden(const String &p_menu_root, int p_idx, bool p_hidden) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ [menu_item setHidden:p_hidden];
+ }
+ }
+}
+
void DisplayServerMacOS::global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) {
_THREAD_SAFE_METHOD_
@@ -1741,6 +1873,11 @@ void DisplayServerMacOS::global_menu_remove_item(const String &p_menu_root, int
p_idx++;
}
ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) {
+ ERR_PRINT("Can't remove open menu!");
+ return;
+ }
[menu removeItemAtIndex:p_idx];
}
}
@@ -1750,6 +1887,10 @@ void DisplayServerMacOS::global_menu_clear(const String &p_menu_root) {
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ if (_is_menu_opened(menu)) {
+ ERR_PRINT("Can't remove open menu!");
+ return;
+ }
[menu removeAllItems];
// Restore Apple menu.
if (menu == [NSApp mainMenu]) {
@@ -1757,6 +1898,7 @@ void DisplayServerMacOS::global_menu_clear(const String &p_menu_root) {
[menu setSubmenu:apple_menu forItem:menu_item];
}
if (submenu.has(p_menu_root)) {
+ submenu_inv.erase(submenu[p_menu_root].menu);
submenu.erase(p_menu_root);
}
}
@@ -1870,171 +2012,275 @@ Error DisplayServerMacOS::dialog_show(String p_title, String p_description, Vect
return OK;
}
-Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
- _THREAD_SAFE_METHOD_
+@interface FileDialogDropdown : NSObject {
+ NSSavePanel *dialog;
+ NSMutableArray *allowed_types;
+ int cur_index;
+}
+
+- (instancetype)initWithDialog:(NSSavePanel *)p_dialog fileTypes:(NSMutableArray *)p_allowed_types;
+- (void)popupAction:(id)sender;
+- (int)getIndex;
+
+@end
+
+@implementation FileDialogDropdown
+
+- (int)getIndex {
+ return cur_index;
+}
+
+- (instancetype)initWithDialog:(NSSavePanel *)p_dialog fileTypes:(NSMutableArray *)p_allowed_types {
+ if ((self = [super init])) {
+ dialog = p_dialog;
+ allowed_types = p_allowed_types;
+ cur_index = 0;
+ }
+ return self;
+}
+
+- (void)popupAction:(id)sender {
+ NSUInteger index = [sender indexOfSelectedItem];
+ if (index < [allowed_types count]) {
+ [dialog setAllowedFileTypes:[allowed_types objectAtIndex:index]];
+ cur_index = index;
+ } else {
+ [dialog setAllowedFileTypes:@[]];
+ cur_index = -1;
+ }
+}
+
+@end
+
+FileDialogDropdown *_make_accessory_view(NSSavePanel *p_panel, const Vector<String> &p_filters) {
+ NSView *group = [[NSView alloc] initWithFrame:NSZeroRect];
+ group.translatesAutoresizingMaskIntoConstraints = NO;
+
+ NSTextField *label = [NSTextField labelWithString:[NSString stringWithUTF8String:RTR("Format").utf8().get_data()]];
+ label.translatesAutoresizingMaskIntoConstraints = NO;
+ if (@available(macOS 10.14, *)) {
+ label.textColor = NSColor.secondaryLabelColor;
+ }
+ if (@available(macOS 11.10, *)) {
+ label.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
+ }
+ [group addSubview:label];
+
+ NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO];
+ popup.translatesAutoresizingMaskIntoConstraints = NO;
- NSString *url = [NSString stringWithUTF8String:p_current_directory.utf8().get_data()];
NSMutableArray *allowed_types = [[NSMutableArray alloc] init];
bool allow_other = false;
for (int i = 0; i < p_filters.size(); i++) {
Vector<String> tokens = p_filters[i].split(";");
- if (tokens.size() > 0) {
- if (tokens[0].strip_edges() == "*.*") {
- allow_other = true;
- } else {
- [allowed_types addObject:[NSString stringWithUTF8String:tokens[0].replace("*.", "").strip_edges().utf8().get_data()]];
+ if (tokens.size() >= 1) {
+ String flt = tokens[0].strip_edges();
+ int filter_slice_count = flt.get_slice_count(",");
+
+ NSMutableArray *type_filters = [[NSMutableArray alloc] init];
+ for (int j = 0; j < filter_slice_count; j++) {
+ String str = (flt.get_slice(",", j).strip_edges());
+ if (str.strip_edges() == "*.*" || str.strip_edges() == "*") {
+ allow_other = true;
+ } else if (!str.is_empty()) {
+ [type_filters addObject:[NSString stringWithUTF8String:str.replace("*.", "").strip_edges().utf8().get_data()]];
+ }
+ }
+
+ if ([type_filters count] > 0) {
+ NSString *name_str = [NSString stringWithUTF8String:((tokens.size() == 1) ? tokens[0] : vformat("%s (%s)", tokens[1], tokens[0])).strip_edges().utf8().get_data()];
+ [allowed_types addObject:type_filters];
+ [popup addItemWithTitle:name_str];
}
}
}
+ FileDialogDropdown *handler = [[FileDialogDropdown alloc] initWithDialog:p_panel fileTypes:allowed_types];
+ popup.target = handler;
+ popup.action = @selector(popupAction:);
- Callable callback = p_callback; // Make a copy for async completion handler.
- switch (p_mode) {
- case FILE_DIALOG_MODE_SAVE_FILE: {
- NSSavePanel *panel = [NSSavePanel savePanel];
+ [group addSubview:popup];
- [panel setDirectoryURL:[NSURL fileURLWithPath:url]];
- if ([allowed_types count]) {
- [panel setAllowedFileTypes:allowed_types];
- }
- [panel setAllowsOtherFileTypes:allow_other];
- [panel setExtensionHidden:YES];
- [panel setCanSelectHiddenExtension:YES];
- [panel setCanCreateDirectories:YES];
- [panel setShowsHiddenFiles:p_show_hidden];
- if (p_filename != "") {
- NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()];
- [panel setNameFieldStringValue:fileurl];
- }
+ NSView *view = [[NSView alloc] initWithFrame:NSZeroRect];
+ view.translatesAutoresizingMaskIntoConstraints = NO;
+ [view addSubview:group];
+
+ NSMutableArray *constraints = [NSMutableArray array];
+ [constraints addObject:[popup.topAnchor constraintEqualToAnchor:group.topAnchor constant:10]];
+ [constraints addObject:[label.leadingAnchor constraintEqualToAnchor:group.leadingAnchor constant:10]];
+ [constraints addObject:[popup.leadingAnchor constraintEqualToAnchor:label.trailingAnchor constant:10]];
+ [constraints addObject:[popup.firstBaselineAnchor constraintEqualToAnchor:label.firstBaselineAnchor]];
+ [constraints addObject:[group.trailingAnchor constraintEqualToAnchor:popup.trailingAnchor constant:10]];
+ [constraints addObject:[group.bottomAnchor constraintEqualToAnchor:popup.bottomAnchor constant:10]];
+ [constraints addObject:[group.topAnchor constraintEqualToAnchor:view.topAnchor]];
+ [constraints addObject:[group.centerXAnchor constraintEqualToAnchor:view.centerXAnchor]];
+ [constraints addObject:[view.bottomAnchor constraintEqualToAnchor:group.bottomAnchor]];
+ [NSLayoutConstraint activateConstraints:constraints];
+
+ [p_panel setAllowsOtherFileTypes:allow_other];
+ if ([allowed_types count] > 0) {
+ [p_panel setAccessoryView:view];
+ [p_panel setAllowedFileTypes:[allowed_types objectAtIndex:0]];
+ }
+
+ return handler;
+}
+
+Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
+ _THREAD_SAFE_METHOD_
- [panel beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow]
- completionHandler:^(NSInteger ret) {
- if (ret == NSModalResponseOK) {
- // Save bookmark for folder.
- if (OS::get_singleton()->is_sandboxed()) {
- NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
+ ERR_FAIL_INDEX_V(int(p_mode), FILE_DIALOG_MODE_SAVE_MAX, FAILED);
+
+ NSString *url = [NSString stringWithUTF8String:p_current_directory.utf8().get_data()];
+ FileDialogDropdown *handler = nullptr;
+
+ WindowID prev_focus = last_focused_window;
+
+ Callable callback = p_callback; // Make a copy for async completion handler.
+ if (p_mode == FILE_DIALOG_MODE_SAVE_FILE) {
+ NSSavePanel *panel = [NSSavePanel savePanel];
+
+ [panel setDirectoryURL:[NSURL fileURLWithPath:url]];
+ handler = _make_accessory_view(panel, p_filters);
+ [panel setExtensionHidden:YES];
+ [panel setCanSelectHiddenExtension:YES];
+ [panel setCanCreateDirectories:YES];
+ [panel setShowsHiddenFiles:p_show_hidden];
+ if (p_filename != "") {
+ NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()];
+ [panel setNameFieldStringValue:fileurl];
+ }
+
+ [panel beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow]
+ completionHandler:^(NSInteger ret) {
+ if (ret == NSModalResponseOK) {
+ // Save bookmark for folder.
+ if (OS::get_singleton()->is_sandboxed()) {
+ NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
+ bool skip = false;
+ for (id bookmark in bookmarks) {
+ NSError *error = nil;
+ BOOL isStale = NO;
+ NSURL *exurl = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error];
+ if (!error && !isStale && ([[exurl path] compare:[[panel directoryURL] path]] == NSOrderedSame)) {
+ skip = true;
+ break;
+ }
+ }
+ if (!skip) {
+ NSError *error = nil;
+ NSData *bookmark = [[panel directoryURL] bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error];
+ if (!error) {
+ NSArray *new_bookmarks = [bookmarks arrayByAddingObject:bookmark];
+ [[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"];
+ }
+ }
+ }
+ // Callback.
+ Vector<String> files;
+ String url;
+ url.parse_utf8([[[panel URL] path] UTF8String]);
+ files.push_back(url);
+ if (!callback.is_null()) {
+ Variant v_status = true;
+ Variant v_files = files;
+ Variant v_index = [handler getIndex];
+ Variant *v_args[3] = { &v_status, &v_files, &v_index };
+ Variant ret;
+ Callable::CallError ce;
+ callback.callp((const Variant **)&v_args, 3, ret, ce);
+ }
+ } else {
+ if (!callback.is_null()) {
+ Variant v_status = false;
+ Variant v_files = Vector<String>();
+ Variant v_index = [handler getIndex];
+ Variant *v_args[3] = { &v_status, &v_files, &v_index };
+ Variant ret;
+ Callable::CallError ce;
+ callback.callp((const Variant **)&v_args, 3, ret, ce);
+ }
+ }
+ if (prev_focus != INVALID_WINDOW_ID) {
+ callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(prev_focus);
+ }
+ }];
+ } else {
+ NSOpenPanel *panel = [NSOpenPanel openPanel];
+
+ [panel setDirectoryURL:[NSURL fileURLWithPath:url]];
+ handler = _make_accessory_view(panel, p_filters);
+ [panel setExtensionHidden:YES];
+ [panel setCanSelectHiddenExtension:YES];
+ [panel setCanCreateDirectories:YES];
+ [panel setCanChooseFiles:(p_mode != FILE_DIALOG_MODE_OPEN_DIR)];
+ [panel setCanChooseDirectories:(p_mode == FILE_DIALOG_MODE_OPEN_DIR || p_mode == FILE_DIALOG_MODE_OPEN_ANY)];
+ [panel setShowsHiddenFiles:p_show_hidden];
+ if (p_filename != "") {
+ NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()];
+ [panel setNameFieldStringValue:fileurl];
+ }
+ [panel setAllowsMultipleSelection:(p_mode == FILE_DIALOG_MODE_OPEN_FILES)];
+
+ [panel beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow]
+ completionHandler:^(NSInteger ret) {
+ if (ret == NSModalResponseOK) {
+ // Save bookmark for folder.
+ NSArray *urls = [(NSOpenPanel *)panel URLs];
+ if (OS::get_singleton()->is_sandboxed()) {
+ NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
+ NSMutableArray *new_bookmarks = [bookmarks mutableCopy];
+ for (NSUInteger i = 0; i != [urls count]; ++i) {
bool skip = false;
for (id bookmark in bookmarks) {
NSError *error = nil;
BOOL isStale = NO;
NSURL *exurl = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error];
- if (!error && !isStale && ([[exurl path] compare:[[panel directoryURL] path]] == NSOrderedSame)) {
+ if (!error && !isStale && ([[exurl path] compare:[[urls objectAtIndex:i] path]] == NSOrderedSame)) {
skip = true;
break;
}
}
if (!skip) {
NSError *error = nil;
- NSData *bookmark = [[panel directoryURL] bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error];
+ NSData *bookmark = [[urls objectAtIndex:i] bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error];
if (!error) {
- NSArray *new_bookmarks = [bookmarks arrayByAddingObject:bookmark];
- [[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"];
+ [new_bookmarks addObject:bookmark];
}
}
}
- // Callback.
- Vector<String> files;
+ [[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"];
+ }
+ // Callback.
+ Vector<String> files;
+ for (NSUInteger i = 0; i != [urls count]; ++i) {
String url;
- url.parse_utf8([[[panel URL] path] UTF8String]);
+ url.parse_utf8([[[urls objectAtIndex:i] path] UTF8String]);
files.push_back(url);
- if (!callback.is_null()) {
- Variant v_status = true;
- Variant v_files = files;
- Variant *v_args[2] = { &v_status, &v_files };
- Variant ret;
- Callable::CallError ce;
- callback.callp((const Variant **)&v_args, 2, ret, ce);
- }
- } else {
- if (!callback.is_null()) {
- Variant v_status = false;
- Variant v_files = Vector<String>();
- Variant *v_args[2] = { &v_status, &v_files };
- Variant ret;
- Callable::CallError ce;
- callback.callp((const Variant **)&v_args, 2, ret, ce);
- }
}
- }];
- } break;
- case FILE_DIALOG_MODE_OPEN_ANY:
- case FILE_DIALOG_MODE_OPEN_FILE:
- case FILE_DIALOG_MODE_OPEN_FILES:
- case FILE_DIALOG_MODE_OPEN_DIR: {
- NSOpenPanel *panel = [NSOpenPanel openPanel];
-
- [panel setDirectoryURL:[NSURL fileURLWithPath:url]];
- if ([allowed_types count]) {
- [panel setAllowedFileTypes:allowed_types];
- }
- [panel setAllowsOtherFileTypes:allow_other];
- [panel setExtensionHidden:YES];
- [panel setCanSelectHiddenExtension:YES];
- [panel setCanCreateDirectories:YES];
- [panel setCanChooseFiles:(p_mode != FILE_DIALOG_MODE_OPEN_DIR)];
- [panel setCanChooseDirectories:(p_mode == FILE_DIALOG_MODE_OPEN_DIR || p_mode == FILE_DIALOG_MODE_OPEN_ANY)];
- [panel setShowsHiddenFiles:p_show_hidden];
- if (p_filename != "") {
- NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()];
- [panel setNameFieldStringValue:fileurl];
- }
- [panel setAllowsMultipleSelection:(p_mode == FILE_DIALOG_MODE_OPEN_FILES)];
-
- [panel beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow]
- completionHandler:^(NSInteger ret) {
- if (ret == NSModalResponseOK) {
- // Save bookmark for folder.
- NSArray *urls = [(NSOpenPanel *)panel URLs];
- if (OS::get_singleton()->is_sandboxed()) {
- NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
- NSMutableArray *new_bookmarks = [bookmarks mutableCopy];
- for (NSUInteger i = 0; i != [urls count]; ++i) {
- bool skip = false;
- for (id bookmark in bookmarks) {
- NSError *error = nil;
- BOOL isStale = NO;
- NSURL *exurl = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error];
- if (!error && !isStale && ([[exurl path] compare:[[urls objectAtIndex:i] path]] == NSOrderedSame)) {
- skip = true;
- break;
- }
- }
- if (!skip) {
- NSError *error = nil;
- NSData *bookmark = [[urls objectAtIndex:i] bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error];
- if (!error) {
- [new_bookmarks addObject:bookmark];
- }
- }
- }
- [[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"];
- }
- // Callback.
- Vector<String> files;
- for (NSUInteger i = 0; i != [urls count]; ++i) {
- String url;
- url.parse_utf8([[[urls objectAtIndex:i] path] UTF8String]);
- files.push_back(url);
- }
- if (!callback.is_null()) {
- Variant v_status = true;
- Variant v_files = files;
- Variant *v_args[2] = { &v_status, &v_files };
- Variant ret;
- Callable::CallError ce;
- callback.callp((const Variant **)&v_args, 2, ret, ce);
- }
- } else {
- if (!callback.is_null()) {
- Variant v_status = false;
- Variant v_files = Vector<String>();
- Variant *v_args[2] = { &v_status, &v_files };
- Variant ret;
- Callable::CallError ce;
- callback.callp((const Variant **)&v_args, 2, ret, ce);
- }
+ if (!callback.is_null()) {
+ Variant v_status = true;
+ Variant v_files = files;
+ Variant v_index = [handler getIndex];
+ Variant *v_args[3] = { &v_status, &v_files, &v_index };
+ Variant ret;
+ Callable::CallError ce;
+ callback.callp((const Variant **)&v_args, 3, ret, ce);
}
- }];
- } break;
+ } else {
+ if (!callback.is_null()) {
+ Variant v_status = false;
+ Variant v_files = Vector<String>();
+ Variant v_index = [handler getIndex];
+ Variant *v_args[3] = { &v_status, &v_files, &v_index };
+ Variant ret;
+ Callable::CallError ce;
+ callback.callp((const Variant **)&v_args, 3, ret, ce);
+ }
+ }
+ if (prev_focus != INVALID_WINDOW_ID) {
+ callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(prev_focus);
+ }
+ }];
}
return OK;
@@ -2626,6 +2872,47 @@ void DisplayServerMacOS::window_set_title(const String &p_title, WindowID p_wind
[wd.window_object setTitle:[NSString stringWithUTF8String:p_title.utf8().get_data()]];
}
+Size2i DisplayServerMacOS::window_get_title_size(const String &p_title, WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ Size2i size;
+ ERR_FAIL_COND_V(!windows.has(p_window), size);
+
+ const WindowData &wd = windows[p_window];
+ if (wd.fullscreen || wd.borderless) {
+ return size;
+ }
+ if ([wd.window_object respondsToSelector:@selector(isMiniaturized)]) {
+ if ([wd.window_object isMiniaturized]) {
+ return size;
+ }
+ }
+
+ float scale = screen_get_max_scale();
+
+ if (wd.window_button_view) {
+ size.x = ([wd.window_button_view getOffset].x + [wd.window_button_view frame].size.width);
+ size.y = ([wd.window_button_view getOffset].y + [wd.window_button_view frame].size.height);
+ } else {
+ NSButton *cb = [wd.window_object standardWindowButton:NSWindowCloseButton];
+ NSButton *mb = [wd.window_object standardWindowButton:NSWindowMiniaturizeButton];
+ float cb_frame = NSMinX([cb frame]);
+ float mb_frame = NSMinX([mb frame]);
+ bool is_rtl = ([wd.window_object windowTitlebarLayoutDirection] == NSUserInterfaceLayoutDirectionRightToLeft);
+
+ float window_buttons_spacing = (is_rtl) ? (cb_frame - mb_frame) : (mb_frame - cb_frame);
+ size.x = window_buttons_spacing * 4;
+ size.y = [cb frame].origin.y + [cb frame].size.height;
+ }
+
+ NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:[NSFont titleBarFontOfSize:0], NSFontAttributeName, nil];
+ NSSize text_size = [[[NSAttributedString alloc] initWithString:[NSString stringWithUTF8String:p_title.utf8().get_data()] attributes:attributes] size];
+ size.x += text_size.width;
+ size.y = MAX(size.y, text_size.height);
+
+ return size * scale;
+}
+
void DisplayServerMacOS::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) {
_THREAD_SAFE_METHOD_
@@ -2868,6 +3155,15 @@ Size2i DisplayServerMacOS::window_get_max_size(WindowID p_window) const {
return wd.max_size;
}
+void DisplayServerMacOS::update_presentation_mode() {
+ for (const KeyValue<WindowID, WindowData> &wd : windows) {
+ if (wd.value.fullscreen && wd.value.exclusive_fullscreen) {
+ return;
+ }
+ }
+ [NSApp setPresentationOptions:NSApplicationPresentationDefault];
+}
+
void DisplayServerMacOS::window_set_min_size(const Size2i p_size, WindowID p_window) {
_THREAD_SAFE_METHOD_
@@ -2978,7 +3274,7 @@ void DisplayServerMacOS::window_set_mode(WindowMode p_mode, WindowID p_window) {
[wd.window_object toggleFullScreen:nil];
if (old_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
- [NSApp setPresentationOptions:NSApplicationPresentationDefault];
+ update_presentation_mode();
}
wd.fullscreen = false;
@@ -3176,7 +3472,9 @@ void DisplayServerMacOS::window_set_flag(WindowFlags p_flag, bool p_enabled, Win
} break;
case WINDOW_FLAG_BORDERLESS: {
// OrderOut prevents a lose focus bug with the window.
+ bool was_visible = false;
if ([wd.window_object isVisible]) {
+ was_visible = true;
[wd.window_object orderOut:nil];
}
wd.borderless = p_enabled;
@@ -3191,7 +3489,7 @@ void DisplayServerMacOS::window_set_flag(WindowFlags p_flag, bool p_enabled, Win
[wd.window_object setFrame:frameRect display:NO];
}
_update_window_style(wd);
- if ([wd.window_object isVisible]) {
+ if (was_visible || [wd.window_object isVisible]) {
if ([wd.window_object isMiniaturized]) {
return;
} else if (wd.no_focus) {
@@ -4127,15 +4425,19 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
nsappname = [[NSProcessInfo processInfo] processName];
}
+ menu_delegate = [[GodotMenuDelegate alloc] init];
+
// Setup Dock menu.
dock_menu = [[NSMenu alloc] initWithTitle:@"_dock"];
[dock_menu setAutoenablesItems:NO];
+ [dock_menu setDelegate:menu_delegate];
// Setup Apple menu.
apple_menu = [[NSMenu alloc] initWithTitle:@""];
title = [NSString stringWithFormat:NSLocalizedString(@"About %@", nil), nsappname];
[apple_menu addItemWithTitle:title action:@selector(showAbout:) keyEquivalent:@""];
[apple_menu setAutoenablesItems:NO];
+ [apple_menu setDelegate:menu_delegate];
[apple_menu addItem:[NSMenuItem separatorItem]];
@@ -4165,8 +4467,6 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
[main_menu setSubmenu:apple_menu forItem:menu_item];
[main_menu setAutoenablesItems:NO];
- menu_delegate = [[GodotMenuDelegate alloc] init];
-
//!!!!!!!!!!!!!!!!!!!!!!!!!!
//TODO - do Vulkan and OpenGL support checks, driver selection and fallback
rendering_driver = p_rendering_driver;
diff --git a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
index 99187fe60e..85fefe65c0 100644
--- a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
+++ b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
@@ -66,7 +66,7 @@
Array of the additional command line arguments passed to the code signing tool.
</member>
<member name="codesign/entitlements/address_book" type="bool" setter="" getter="">
- Enable to allow access to contacts in the user's address book, if it's enabled you should also provide usage message in the [code]privacy/address_book_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_addressbook]com.apple.security.personal-information.addressbook[/url].
+ Enable to allow access to contacts in the user's address book, if it's enabled you should also provide usage message in the [member privacy/address_book_usage_description] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_addressbook]com.apple.security.personal-information.addressbook[/url].
</member>
<member name="codesign/entitlements/allow_dyld_environment_variables" type="bool" setter="" getter="">
Allows app to use dynamic linker environment variables to inject code. If you are using add-ons with dynamic or self-modifying native code, enable them according to the add-on documentation. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_allow-dyld-environment-variables]com.apple.security.cs.allow-dyld-environment-variables[/url].
@@ -115,13 +115,13 @@
Enable to allow app to send Apple events to other apps. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_automation_apple-events]com.apple.security.automation.apple-events[/url].
</member>
<member name="codesign/entitlements/audio_input" type="bool" setter="" getter="">
- Enable if you need to use the microphone or other audio input sources, if it's enabled you should also provide usage message in the [code]privacy/microphone_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_device_audio-input]com.apple.security.device.audio-input[/url].
+ Enable if you need to use the microphone or other audio input sources, if it's enabled you should also provide usage message in the [member privacy/microphone_usage_description] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_device_audio-input]com.apple.security.device.audio-input[/url].
</member>
<member name="codesign/entitlements/calendars" type="bool" setter="" getter="">
- Enable to allow access to the user's calendar, if it's enabled you should also provide usage message in the [code]privacy/calendar_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_calendars]com.apple.security.personal-information.calendars[/url].
+ Enable to allow access to the user's calendar, if it's enabled you should also provide usage message in the [member privacy/calendar_usage_description] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_calendars]com.apple.security.personal-information.calendars[/url].
</member>
<member name="codesign/entitlements/camera" type="bool" setter="" getter="">
- Enable if you need to use the camera, if it's enabled you should also provide usage message in the [code]privacy/camera_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_device_camera]com.apple.security.device.camera[/url].
+ Enable if you need to use the camera, if it's enabled you should also provide usage message in the [member privacy/camera_usage_description] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_device_camera]com.apple.security.device.camera[/url].
</member>
<member name="codesign/entitlements/custom_file" type="String" setter="" getter="">
Custom entitlements [code].plist[/code] file, if specified the rest of entitlements in the export config are ignored.
@@ -133,10 +133,10 @@
Allows app to load arbitrary libraries and frameworks (not signed with the same Team ID as the main executable or by Apple). Enable it if you are using GDExtension add-ons or ad-hoc signing, or want to support user-provided external add-ons. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_disable-library-validation]com.apple.security.cs.disable-library-validation[/url].
</member>
<member name="codesign/entitlements/location" type="bool" setter="" getter="">
- Enable if you need to use location information from Location Services, if it's enabled you should also provide usage message in the [code]privacy/location_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_location]com.apple.security.personal-information.location[/url].
+ Enable if you need to use location information from Location Services, if it's enabled you should also provide usage message in the [member privacy/location_usage_description] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_location]com.apple.security.personal-information.location[/url].
</member>
<member name="codesign/entitlements/photos_library" type="bool" setter="" getter="">
- Enable to allow access to the user's Photos library, if it's enabled you should also provide usage message in the [code]privacy/photos_library_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_photos-library]com.apple.security.personal-information.photos-library[/url].
+ Enable to allow access to the user's Photos library, if it's enabled you should also provide usage message in the [member privacy/photos_library_usage_description] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_photos-library]com.apple.security.personal-information.photos-library[/url].
</member>
<member name="codesign/identity" type="String" setter="" getter="">
The "Full Name", "Common Name" or SHA-1 hash of the signing identity used to sign [code].app[/code] bundle.
diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp
index 639a7699ee..804028053d 100644
--- a/platform/macos/export/export_plugin.cpp
+++ b/platform/macos/export/export_plugin.cpp
@@ -1047,6 +1047,8 @@ Error EditorExportPlatformMacOS::_code_sign(const Ref<EditorExportPreset> &p_pre
args.push_back("--p12-password");
args.push_back(certificate_pass);
}
+ args.push_back("--code-signature-flags");
+ args.push_back("runtime");
args.push_back("-v"); /* provide some more feedback */
@@ -1139,7 +1141,6 @@ Error EditorExportPlatformMacOS::_code_sign(const Ref<EditorExportPreset> &p_pre
Error EditorExportPlatformMacOS::_code_sign_directory(const Ref<EditorExportPreset> &p_preset, const String &p_path,
const String &p_ent_path, bool p_should_error_on_non_code) {
-#ifdef MACOS_ENABLED
static Vector<String> extensions_to_sign;
if (extensions_to_sign.is_empty()) {
@@ -1186,7 +1187,6 @@ Error EditorExportPlatformMacOS::_code_sign_directory(const Ref<EditorExportPres
current_file = dir_access->get_next();
}
-#endif
return OK;
}
@@ -1225,7 +1225,7 @@ Error EditorExportPlatformMacOS::_copy_and_sign_files(Ref<DirAccess> &dir_access
if (extensions_to_sign.find(p_in_app_path.get_extension()) > -1) {
err = _code_sign(p_preset, p_in_app_path, p_ent_path, false);
}
- if (is_executable(p_in_app_path)) {
+ if (dir_access->file_exists(p_in_app_path) && is_executable(p_in_app_path)) {
// chmod with 0755 if the file is executable.
FileAccess::set_unix_permissions(p_in_app_path, 0755);
}
diff --git a/platform/macos/godot_menu_delegate.mm b/platform/macos/godot_menu_delegate.mm
index ebfe8b1f6d..1bfcfa7d9e 100644
--- a/platform/macos/godot_menu_delegate.mm
+++ b/platform/macos/godot_menu_delegate.mm
@@ -39,6 +39,34 @@
- (void)doNothing:(id)sender {
}
+- (void)menuNeedsUpdate:(NSMenu *)menu {
+ if (DisplayServer::get_singleton()) {
+ DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+ ds->menu_open(menu);
+ }
+}
+
+- (void)menuDidClose:(NSMenu *)menu {
+ if (DisplayServer::get_singleton()) {
+ DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+ ds->menu_close(menu);
+ }
+}
+
+- (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item {
+ if (item) {
+ GodotMenuItem *value = [item representedObject];
+ if (value && value->hover_callback != Callable()) {
+ // If custom callback is set, use it.
+ Variant tag = value->meta;
+ Variant *tagp = &tag;
+ Variant ret;
+ Callable::CallError ce;
+ value->hover_callback.callp((const Variant **)&tagp, 1, ret, ce);
+ }
+ }
+}
+
- (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action {
NSString *ev_key = [[event charactersIgnoringModifiers] lowercaseString];
NSUInteger ev_modifiers = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
diff --git a/platform/macos/godot_menu_item.h b/platform/macos/godot_menu_item.h
index 8876ceae6a..b816ea1b3a 100644
--- a/platform/macos/godot_menu_item.h
+++ b/platform/macos/godot_menu_item.h
@@ -46,6 +46,7 @@ enum GlobalMenuCheckType {
@public
Callable callback;
Callable key_callback;
+ Callable hover_callback;
Variant meta;
GlobalMenuCheckType checkable_type;
int max_states;
diff --git a/platform/macos/godot_window_delegate.mm b/platform/macos/godot_window_delegate.mm
index 46355b4ae8..002ab0155f 100644
--- a/platform/macos/godot_window_delegate.mm
+++ b/platform/macos/godot_window_delegate.mm
@@ -157,7 +157,7 @@
DisplayServerMacOS::WindowData &wd = ds->get_window(window_id);
if (wd.exclusive_fullscreen) {
- [NSApp setPresentationOptions:NSApplicationPresentationDefault];
+ ds->update_presentation_mode();
}
wd.fullscreen = false;
diff --git a/platform/web/js/libs/library_godot_javascript_singleton.js b/platform/web/js/libs/library_godot_javascript_singleton.js
index cbe59230ee..1764c9a026 100644
--- a/platform/web/js/libs/library_godot_javascript_singleton.js
+++ b/platform/web/js/libs/library_godot_javascript_singleton.js
@@ -210,7 +210,7 @@ const GodotJSWrapper = {
// This is safe! JavaScript is single threaded (and using it in threads is not a good idea anyway).
GodotJSWrapper.cb_ret = null;
const args = Array.from(arguments);
- const argsProxy = GodotJSWrapper.MyProxy(args);
+ const argsProxy = new GodotJSWrapper.MyProxy(args);
func(p_ref, argsProxy.get_id(), args.length);
argsProxy.unref();
const ret = GodotJSWrapper.cb_ret;
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 1a1bba833d..ded80ba5f1 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -222,24 +222,45 @@ void DisplayServerWindows::tts_stop() {
Error DisplayServerWindows::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
_THREAD_SAFE_METHOD_
+ ERR_FAIL_INDEX_V(int(p_mode), FILE_DIALOG_MODE_SAVE_MAX, FAILED);
+
Vector<Char16String> filter_names;
Vector<Char16String> filter_exts;
for (const String &E : p_filters) {
Vector<String> tokens = E.split(";");
- if (tokens.size() == 2) {
- filter_exts.push_back(tokens[0].strip_edges().utf16());
- filter_names.push_back(tokens[1].strip_edges().utf16());
- } else if (tokens.size() == 1) {
- filter_exts.push_back(tokens[0].strip_edges().utf16());
- filter_names.push_back(tokens[0].strip_edges().utf16());
+ if (tokens.size() >= 1) {
+ String flt = tokens[0].strip_edges();
+ int filter_slice_count = flt.get_slice_count(",");
+ Vector<String> exts;
+ for (int j = 0; j < filter_slice_count; j++) {
+ String str = (flt.get_slice(",", j).strip_edges());
+ if (!str.is_empty()) {
+ exts.push_back(str);
+ }
+ }
+ if (!exts.is_empty()) {
+ String str = String(";").join(exts);
+ filter_exts.push_back(str.utf16());
+ if (tokens.size() == 2) {
+ filter_names.push_back(tokens[1].strip_edges().utf16());
+ } else {
+ filter_names.push_back(str.utf16());
+ }
+ }
}
}
+ if (filter_names.is_empty()) {
+ filter_exts.push_back(String("*.*").utf16());
+ filter_names.push_back(RTR("All Files").utf16());
+ }
Vector<COMDLG_FILTERSPEC> filters;
for (int i = 0; i < filter_names.size(); i++) {
filters.push_back({ (LPCWSTR)filter_names[i].ptr(), (LPCWSTR)filter_exts[i].ptr() });
}
+ WindowID prev_focus = last_focused_window;
+
HRESULT hr = S_OK;
IFileDialog *pfd = nullptr;
if (p_mode == FILE_DIALOG_MODE_SAVE_FILE) {
@@ -285,6 +306,9 @@ Error DisplayServerWindows::file_dialog_show(const String &p_title, const String
}
hr = pfd->Show(windows[window_id].hWnd);
+ UINT index = 0;
+ pfd->GetFileTypeIndex(&index);
+
if (SUCCEEDED(hr)) {
Vector<String> file_names;
@@ -324,22 +348,27 @@ Error DisplayServerWindows::file_dialog_show(const String &p_title, const String
if (!p_callback.is_null()) {
Variant v_status = true;
Variant v_files = file_names;
- Variant *v_args[2] = { &v_status, &v_files };
+ Variant v_index = index;
+ Variant *v_args[3] = { &v_status, &v_files, &v_index };
Variant ret;
Callable::CallError ce;
- p_callback.callp((const Variant **)&v_args, 2, ret, ce);
+ p_callback.callp((const Variant **)&v_args, 3, ret, ce);
}
} else {
if (!p_callback.is_null()) {
Variant v_status = false;
Variant v_files = Vector<String>();
- Variant *v_args[2] = { &v_status, &v_files };
+ Variant v_index = index;
+ Variant *v_args[3] = { &v_status, &v_files, &v_index };
Variant ret;
Callable::CallError ce;
- p_callback.callp((const Variant **)&v_args, 2, ret, ce);
+ p_callback.callp((const Variant **)&v_args, 3, ret, ce);
}
}
pfd->Release();
+ if (prev_focus != INVALID_WINDOW_ID) {
+ callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(prev_focus);
+ }
return OK;
} else {
@@ -1191,6 +1220,51 @@ void DisplayServerWindows::window_set_title(const String &p_title, WindowID p_wi
SetWindowTextW(windows[p_window].hWnd, (LPCWSTR)(p_title.utf16().get_data()));
}
+Size2i DisplayServerWindows::window_get_title_size(const String &p_title, WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ Size2i size;
+ ERR_FAIL_COND_V(!windows.has(p_window), size);
+
+ const WindowData &wd = windows[p_window];
+ if (wd.fullscreen || wd.minimized || wd.borderless) {
+ return size;
+ }
+
+ HDC hdc = GetDCEx(wd.hWnd, NULL, DCX_WINDOW);
+ if (hdc) {
+ Char16String s = p_title.utf16();
+ SIZE text_size;
+ if (GetTextExtentPoint32W(hdc, (LPCWSTR)(s.get_data()), s.length(), &text_size)) {
+ size.x = text_size.cx;
+ size.y = text_size.cy;
+ }
+
+ ReleaseDC(wd.hWnd, hdc);
+ }
+ RECT rect;
+ if (DwmGetWindowAttribute(wd.hWnd, DWMWA_CAPTION_BUTTON_BOUNDS, &rect, sizeof(RECT)) == S_OK) {
+ if (rect.right - rect.left > 0) {
+ ClientToScreen(wd.hWnd, (POINT *)&rect.left);
+ ClientToScreen(wd.hWnd, (POINT *)&rect.right);
+
+ if (win81p_PhysicalToLogicalPointForPerMonitorDPI) {
+ win81p_PhysicalToLogicalPointForPerMonitorDPI(0, (POINT *)&rect.left);
+ win81p_PhysicalToLogicalPointForPerMonitorDPI(0, (POINT *)&rect.right);
+ }
+
+ size.x += (rect.right - rect.left);
+ size.y = MAX(size.y, rect.bottom - rect.top);
+ }
+ }
+ if (icon.is_valid()) {
+ size.x += 32;
+ } else {
+ size.x += 16;
+ }
+ return size;
+}
+
void DisplayServerWindows::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) {
_THREAD_SAFE_METHOD_
@@ -4385,6 +4459,7 @@ bool DisplayServerWindows::winink_available = false;
GetPointerTypePtr DisplayServerWindows::win8p_GetPointerType = nullptr;
GetPointerPenInfoPtr DisplayServerWindows::win8p_GetPointerPenInfo = nullptr;
LogicalToPhysicalPointForPerMonitorDPIPtr DisplayServerWindows::win81p_LogicalToPhysicalPointForPerMonitorDPI = nullptr;
+PhysicalToLogicalPointForPerMonitorDPIPtr DisplayServerWindows::win81p_PhysicalToLogicalPointForPerMonitorDPI = nullptr;
typedef enum _SHC_PROCESS_DPI_AWARENESS {
SHC_PROCESS_DPI_UNAWARE = 0,
@@ -4522,6 +4597,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType");
win8p_GetPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress(user32_lib, "GetPointerPenInfo");
win81p_LogicalToPhysicalPointForPerMonitorDPI = (LogicalToPhysicalPointForPerMonitorDPIPtr)GetProcAddress(user32_lib, "LogicalToPhysicalPointForPerMonitorDPI");
+ win81p_PhysicalToLogicalPointForPerMonitorDPI = (PhysicalToLogicalPointForPerMonitorDPIPtr)GetProcAddress(user32_lib, "PhysicalToLogicalPointForPerMonitorDPI");
winink_available = win8p_GetPointerType && win8p_GetPointerPenInfo;
}
@@ -4579,9 +4655,22 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
// Init context and rendering device
#if defined(GLES3_ENABLED)
- if (rendering_driver == "opengl3") {
- int gl_version = detect_wgl_version();
- if (gl_version < 30003) {
+ bool fallback = GLOBAL_GET("rendering/gl_compatibility/fallback_to_angle");
+ if (fallback && (rendering_driver == "opengl3")) {
+ Dictionary gl_info = detect_wgl();
+
+ bool force_angle = false;
+
+ Array device_list = GLOBAL_GET("rendering/gl_compatibility/force_angle_on_devices");
+ for (int i = 0; i < device_list.size(); i++) {
+ const Dictionary &device = device_list[i];
+ if (device.has("vendor") && device.has("name") && device["vendor"].operator String().to_upper() == gl_info["vendor"].operator String().to_upper() && (device["name"] == "*" || device["name"].operator String().to_upper() == gl_info["name"].operator String().to_upper())) {
+ force_angle = true;
+ break;
+ }
+ }
+
+ if (force_angle || (gl_info["version"].operator int() < 30003)) {
WARN_PRINT("Your video card drivers seem not to support the required OpenGL 3.3 version, switching to ANGLE.");
rendering_driver = "opengl3_angle";
}
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index 28f2f7e6ff..48c8c20280 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -262,6 +262,7 @@ typedef struct tagPOINTER_PEN_INFO {
typedef BOOL(WINAPI *GetPointerTypePtr)(uint32_t p_id, POINTER_INPUT_TYPE *p_type);
typedef BOOL(WINAPI *GetPointerPenInfoPtr)(uint32_t p_id, POINTER_PEN_INFO *p_pen_info);
typedef BOOL(WINAPI *LogicalToPhysicalPointForPerMonitorDPIPtr)(HWND hwnd, LPPOINT lpPoint);
+typedef BOOL(WINAPI *PhysicalToLogicalPointForPerMonitorDPIPtr)(HWND hwnd, LPPOINT lpPoint);
typedef struct {
BYTE bWidth; // Width, in pixels, of the image
@@ -309,6 +310,7 @@ class DisplayServerWindows : public DisplayServer {
// DPI conversion API
static LogicalToPhysicalPointForPerMonitorDPIPtr win81p_LogicalToPhysicalPointForPerMonitorDPI;
+ static PhysicalToLogicalPointForPerMonitorDPIPtr win81p_PhysicalToLogicalPointForPerMonitorDPI;
void _update_tablet_ctx(const String &p_old_driver, const String &p_new_driver);
String tablet_driver;
@@ -569,6 +571,7 @@ public:
virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_title_size(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window = MAIN_WINDOW_ID) override;
virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
diff --git a/platform/windows/platform_windows_builders.py b/platform/windows/platform_windows_builders.py
index b522a75a9c..51652fa814 100644
--- a/platform/windows/platform_windows_builders.py
+++ b/platform/windows/platform_windows_builders.py
@@ -5,14 +5,24 @@ All such functions are invoked in a subprocess on Windows to prevent build flaki
"""
import os
from detect import get_mingw_bin_prefix
+from detect import try_cmd
from platform_methods import subprocess_main
def make_debug_mingw(target, source, env):
mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"])
- os.system(mingw_bin_prefix + "objcopy --only-keep-debug {0} {0}.debugsymbols".format(target[0]))
- os.system(mingw_bin_prefix + "strip --strip-debug --strip-unneeded {0}".format(target[0]))
- os.system(mingw_bin_prefix + "objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(target[0]))
+ if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]):
+ os.system(mingw_bin_prefix + "objcopy --only-keep-debug {0} {0}.debugsymbols".format(target[0]))
+ else:
+ os.system("objcopy --only-keep-debug {0} {0}.debugsymbols".format(target[0]))
+ if try_cmd("strip --version", env["mingw_prefix"], env["arch"]):
+ os.system(mingw_bin_prefix + "strip --strip-debug --strip-unneeded {0}".format(target[0]))
+ else:
+ os.system("strip --strip-debug --strip-unneeded {0}".format(target[0]))
+ if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]):
+ os.system(mingw_bin_prefix + "objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(target[0]))
+ else:
+ os.system("objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(target[0]))
if __name__ == "__main__":
diff --git a/platform/windows/wgl_detect_version.cpp b/platform/windows/wgl_detect_version.cpp
index 264cd525c5..49da4b58c7 100644
--- a/platform/windows/wgl_detect_version.cpp
+++ b/platform/windows/wgl_detect_version.cpp
@@ -35,6 +35,7 @@
#include "core/string/print_string.h"
#include "core/string/ustring.h"
+#include "core/variant/dictionary.h"
#include <windows.h>
@@ -64,9 +65,11 @@ typedef HGLRC(APIENTRY *PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC, HGLRC, const int
typedef void *(APIENTRY *PFNWGLGETPROCADDRESS)(LPCSTR);
typedef const char *(APIENTRY *PFNWGLGETSTRINGPROC)(unsigned int);
-int detect_wgl_version() {
- int major = 0;
- int minor = 0;
+Dictionary detect_wgl() {
+ Dictionary gl_info;
+ gl_info["version"] = 0;
+ gl_info["vendor"] = String();
+ gl_info["name"] = String();
PFNWGLCREATECONTEXT gd_wglCreateContext;
PFNWGLMAKECURRENT gd_wglMakeCurrent;
@@ -75,14 +78,14 @@ int detect_wgl_version() {
HMODULE module = LoadLibraryW(L"opengl32.dll");
if (!module) {
- return 0;
+ return gl_info;
}
gd_wglCreateContext = (PFNWGLCREATECONTEXT)GetProcAddress(module, "wglCreateContext");
gd_wglMakeCurrent = (PFNWGLMAKECURRENT)GetProcAddress(module, "wglMakeCurrent");
gd_wglDeleteContext = (PFNWGLDELETECONTEXT)GetProcAddress(module, "wglDeleteContext");
gd_wglGetProcAddress = (PFNWGLGETPROCADDRESS)GetProcAddress(module, "wglGetProcAddress");
if (!gd_wglCreateContext || !gd_wglMakeCurrent || !gd_wglDeleteContext || !gd_wglGetProcAddress) {
- return 0;
+ return gl_info;
}
LPCWSTR class_name = L"EngineWGLDetect";
@@ -151,8 +154,8 @@ int detect_wgl_version() {
};
const char *version = (const char *)gd_wglGetString(WGL_VERSION);
if (version) {
- const String device_vendor = String::utf8((const char *)gd_wglGetString(WGL_VENDOR)).strip_edges();
- const String device_name = String::utf8((const char *)gd_wglGetString(WGL_RENDERER)).strip_edges();
+ const String device_vendor = String::utf8((const char *)gd_wglGetString(WGL_VENDOR)).strip_edges().trim_suffix(" Corporation");
+ const String device_name = String::utf8((const char *)gd_wglGetString(WGL_RENDERER)).strip_edges().trim_suffix("/PCIe/SSE2");
for (int i = 0; prefixes[i]; i++) {
size_t length = strlen(prefixes[i]);
if (strncmp(version, prefixes[i], length) == 0) {
@@ -160,12 +163,17 @@ int detect_wgl_version() {
break;
}
}
+ int major = 0;
+ int minor = 0;
#ifdef _MSC_VER
sscanf_s(version, "%d.%d", &major, &minor);
#else
sscanf(version, "%d.%d", &major, &minor);
#endif
print_verbose(vformat("Native OpenGL API detected: %d.%d: %s - %s", major, minor, device_vendor, device_name));
+ gl_info["vendor"] = device_vendor;
+ gl_info["name"] = device_name;
+ gl_info["version"] = major * 10000 + minor;
}
}
}
@@ -183,7 +191,7 @@ int detect_wgl_version() {
}
UnregisterClassW(class_name, hInstance);
- return major * 10000 + minor;
+ return gl_info;
}
#endif // WINDOWS_ENABLED && GLES3_ENABLED
diff --git a/platform/windows/wgl_detect_version.h b/platform/windows/wgl_detect_version.h
index 0be2923ba3..c110b1219e 100644
--- a/platform/windows/wgl_detect_version.h
+++ b/platform/windows/wgl_detect_version.h
@@ -33,7 +33,9 @@
#if defined(WINDOWS_ENABLED) && defined(GLES3_ENABLED)
-int detect_wgl_version();
+class Dictionary;
+
+Dictionary detect_wgl();
#endif // WINDOWS_ENABLED && GLES3_ENABLED
diff --git a/scene/2d/node_2d.cpp b/scene/2d/node_2d.cpp
index 3ba81dba88..0a5f696992 100644
--- a/scene/2d/node_2d.cpp
+++ b/scene/2d/node_2d.cpp
@@ -424,14 +424,17 @@ Point2 Node2D::to_global(Point2 p_local) const {
}
void Node2D::_notification(int p_notification) {
- ERR_THREAD_GUARD;
switch (p_notification) {
case NOTIFICATION_ENTER_TREE: {
+ ERR_MAIN_THREAD_GUARD;
+
if (get_viewport()) {
get_parent()->connect(SNAME("child_order_changed"), callable_mp(get_viewport(), &Viewport::gui_set_root_order_dirty), CONNECT_REFERENCE_COUNTED);
}
} break;
case NOTIFICATION_EXIT_TREE: {
+ ERR_MAIN_THREAD_GUARD;
+
if (get_viewport()) {
get_parent()->disconnect(SNAME("child_order_changed"), callable_mp(get_viewport(), &Viewport::gui_set_root_order_dirty));
}
diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp
index 19063981bf..fa94712396 100644
--- a/scene/2d/physics_body_2d.cpp
+++ b/scene/2d/physics_body_2d.cpp
@@ -447,9 +447,12 @@ void RigidBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) {
lock_callback();
set_block_transform_notify(true); // don't want notify (would feedback loop)
- _sync_body_state(p_state);
- GDVIRTUAL_CALL(_integrate_forces, p_state);
+ if (GDVIRTUAL_IS_OVERRIDDEN(_integrate_forces)) {
+ _sync_body_state(p_state);
+
+ GDVIRTUAL_CALL(_integrate_forces, p_state);
+ }
_sync_body_state(p_state);
set_block_transform_notify(false); // want it back
diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp
index 07d84eebde..cb8279d534 100644
--- a/scene/3d/node_3d.cpp
+++ b/scene/3d/node_3d.cpp
@@ -131,10 +131,9 @@ void Node3D::_propagate_transform_changed(Node3D *p_origin) {
}
void Node3D::_notification(int p_what) {
- ERR_THREAD_GUARD;
-
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
+ ERR_MAIN_THREAD_GUARD;
ERR_FAIL_NULL(get_tree());
Node *p = get_parent();
@@ -163,6 +162,8 @@ void Node3D::_notification(int p_what) {
} break;
case NOTIFICATION_EXIT_TREE: {
+ ERR_MAIN_THREAD_GUARD;
+
notification(NOTIFICATION_EXIT_WORLD, true);
if (xform_change.in_list()) {
get_tree()->xform_change_list.remove(&xform_change);
@@ -176,6 +177,8 @@ void Node3D::_notification(int p_what) {
} break;
case NOTIFICATION_ENTER_WORLD: {
+ ERR_MAIN_THREAD_GUARD;
+
data.inside_world = true;
data.viewport = nullptr;
Node *parent = get_parent();
@@ -198,6 +201,8 @@ void Node3D::_notification(int p_what) {
} break;
case NOTIFICATION_EXIT_WORLD: {
+ ERR_MAIN_THREAD_GUARD;
+
#ifdef TOOLS_ENABLED
clear_gizmos();
#endif
@@ -211,6 +216,8 @@ void Node3D::_notification(int p_what) {
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
+ ERR_THREAD_GUARD;
+
#ifdef TOOLS_ENABLED
for (int i = 0; i < data.gizmos.size(); i++) {
data.gizmos.write[i]->transform();
diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp
index 973d1cde58..a5f5ae6e61 100644
--- a/scene/3d/physics_body_3d.cpp
+++ b/scene/3d/physics_body_3d.cpp
@@ -502,9 +502,12 @@ void RigidBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) {
lock_callback();
set_ignore_transform_notification(true);
- _sync_body_state(p_state);
- GDVIRTUAL_CALL(_integrate_forces, p_state);
+ if (GDVIRTUAL_IS_OVERRIDDEN(_integrate_forces)) {
+ _sync_body_state(p_state);
+
+ GDVIRTUAL_CALL(_integrate_forces, p_state);
+ }
_sync_body_state(p_state);
set_ignore_transform_notification(false);
@@ -2935,9 +2938,12 @@ void PhysicalBone3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) {
}
set_ignore_transform_notification(true);
- _sync_body_state(p_state);
- GDVIRTUAL_CALL(_integrate_forces, p_state);
+ if (GDVIRTUAL_IS_OVERRIDDEN(_integrate_forces)) {
+ _sync_body_state(p_state);
+
+ GDVIRTUAL_CALL(_integrate_forces, p_state);
+ }
_sync_body_state(p_state);
set_ignore_transform_notification(false);
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index e7a2a26a29..20fcf9cba7 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -3223,7 +3223,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
int line_height = get_line_height();
if (GDVIRTUAL_IS_OVERRIDDEN(_filter_code_completion_candidates)) {
- code_completion_options.clear();
+ Vector<ScriptLanguage::CodeCompletionOption> code_completion_options_new;
code_completion_base = "";
/* Build options argument. */
@@ -3273,11 +3273,15 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
if (theme_cache.font.is_valid()) {
max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset);
}
- code_completion_options.push_back(option);
+ code_completion_options_new.push_back(option);
}
+ if (_should_reset_selected_option_for_new_options(code_completion_options_new)) {
+ code_completion_current_selected = 0;
+ }
+ code_completion_options = code_completion_options_new;
+
code_completion_longest_line = MIN(max_width, theme_cache.code_completion_max_width * theme_cache.font_size);
- code_completion_current_selected = 0;
code_completion_force_item_center = -1;
code_completion_active = true;
queue_redraw();
@@ -3352,7 +3356,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
/* For now handle only traditional quoted strings. */
bool single_quote = in_string != -1 && first_quote_col > 0 && delimiters[in_string].start_key == "'";
- code_completion_options.clear();
+ Vector<ScriptLanguage::CodeCompletionOption> code_completion_options_new;
code_completion_base = string_to_complete;
/* Don't autocomplete setting numerical values. */
@@ -3384,7 +3388,8 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
if (string_to_complete.length() == 0) {
option.get_option_characteristics(string_to_complete);
- code_completion_options.push_back(option);
+ code_completion_options_new.push_back(option);
+
if (theme_cache.font.is_valid()) {
max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset);
}
@@ -3459,7 +3464,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
}
}
- code_completion_options.push_back(option);
+ code_completion_options_new.push_back(option);
if (theme_cache.font.is_valid()) {
max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset);
}
@@ -3467,26 +3472,46 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
}
/* No options to complete, cancel. */
- if (code_completion_options.size() == 0) {
+ if (code_completion_options_new.size() == 0) {
cancel_code_completion();
return;
}
/* A perfect match, stop completion. */
- if (code_completion_options.size() == 1 && string_to_complete == code_completion_options[0].display) {
+ if (code_completion_options_new.size() == 1 && string_to_complete == code_completion_options_new[0].display) {
cancel_code_completion();
return;
}
- code_completion_options.sort_custom<CodeCompletionOptionCompare>();
+ code_completion_options_new.sort_custom<CodeCompletionOptionCompare>();
+ if (_should_reset_selected_option_for_new_options(code_completion_options_new)) {
+ code_completion_current_selected = 0;
+ }
+ code_completion_options = code_completion_options_new;
code_completion_longest_line = MIN(max_width, theme_cache.code_completion_max_width * theme_cache.font_size);
- code_completion_current_selected = 0;
code_completion_force_item_center = -1;
code_completion_active = true;
queue_redraw();
}
+// Assumes both the new_options and the code_completion_options are sorted.
+bool CodeEdit::_should_reset_selected_option_for_new_options(const Vector<ScriptLanguage::CodeCompletionOption> &p_new_options) {
+ if (code_completion_current_selected >= p_new_options.size()) {
+ return true;
+ }
+
+ for (int i = 0; i < code_completion_options.size() && i < p_new_options.size(); i++) {
+ if (i > code_completion_current_selected) {
+ return false;
+ }
+ if (code_completion_options[i].display != p_new_options[i].display) {
+ return true;
+ }
+ }
+ return false;
+}
+
void CodeEdit::_lines_edited_from(int p_from_line, int p_to_line) {
_update_delimiter_cache(p_from_line, p_to_line);
diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h
index 97c435b52d..4b0629d29d 100644
--- a/scene/gui/code_edit.h
+++ b/scene/gui/code_edit.h
@@ -222,6 +222,7 @@ private:
void _update_scroll_selected_line(float p_mouse_y);
void _filter_code_completion_candidates_impl();
+ bool _should_reset_selected_option_for_new_options(const Vector<ScriptLanguage::CodeCompletionOption> &p_new_options);
/* Line length guidelines */
TypedArray<int> line_length_guideline_columns;
diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp
index aff0ed6f06..957a8f276e 100644
--- a/scene/gui/dialogs.cpp
+++ b/scene/gui/dialogs.cpp
@@ -405,6 +405,7 @@ AcceptDialog::AcceptDialog() {
set_transient(true);
set_exclusive(true);
set_clamp_to_embedder(true);
+ set_keep_title_visible(true);
bg_panel = memnew(Panel);
add_child(bg_panel, false, INTERNAL_MODE_FRONT);
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 3857281a66..be05273a09 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -56,6 +56,12 @@ void FileDialog::_focus_file_text() {
}
void FileDialog::popup(const Rect2i &p_rect) {
+#ifdef TOOLS_ENABLED
+ if (is_part_of_edited_scene()) {
+ ConfirmationDialog::popup(p_rect);
+ }
+#endif
+
if (access == ACCESS_FILESYSTEM && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
DisplayServer::get_singleton()->file_dialog_show(get_title(), dir->get_text(), file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, callable_mp(this, &FileDialog::_native_dialog_cb));
} else {
@@ -64,6 +70,13 @@ void FileDialog::popup(const Rect2i &p_rect) {
}
void FileDialog::set_visible(bool p_visible) {
+#ifdef TOOLS_ENABLED
+ if (is_part_of_edited_scene()) {
+ ConfirmationDialog::set_visible(p_visible);
+ return;
+ }
+#endif
+
if (access == ACCESS_FILESYSTEM && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
if (p_visible) {
DisplayServer::get_singleton()->file_dialog_show(get_title(), dir->get_text(), file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, callable_mp(this, &FileDialog::_native_dialog_cb));
@@ -73,7 +86,7 @@ void FileDialog::set_visible(bool p_visible) {
}
}
-void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files) {
+void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter) {
if (p_ok) {
if (p_files.size() > 0) {
String f = p_files[0];
@@ -90,6 +103,7 @@ void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files) {
}
file->set_text(f);
dir->set_text(f.get_base_dir());
+ _filter_selected(p_filter);
}
} else {
file->set_text("");
diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h
index 1a87b79fdd..8ae84fc9dc 100644
--- a/scene/gui/file_dialog.h
+++ b/scene/gui/file_dialog.h
@@ -159,7 +159,7 @@ private:
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
- void _native_dialog_cb(bool p_ok, const Vector<String> &p_files);
+ void _native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter);
bool _is_open_should_be_disabled();
diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp
index 13a42d0407..371d6c69af 100644
--- a/scene/gui/menu_bar.cpp
+++ b/scene/gui/menu_bar.cpp
@@ -202,52 +202,6 @@ void MenuBar::_popup_visibility_changed(bool p_visible) {
}
}
-void MenuBar::_update_submenu(const String &p_menu_name, PopupMenu *p_child) {
- int count = p_child->get_item_count();
- global_menus.insert(p_menu_name);
- for (int i = 0; i < count; i++) {
- if (p_child->is_item_separator(i)) {
- DisplayServer::get_singleton()->global_menu_add_separator(p_menu_name);
- } else if (!p_child->get_item_submenu(i).is_empty()) {
- Node *n = p_child->get_node_or_null(p_child->get_item_submenu(i));
- ERR_FAIL_NULL_MSG(n, "Item subnode does not exist: '" + p_child->get_item_submenu(i) + "'.");
- PopupMenu *pm = Object::cast_to<PopupMenu>(n);
- ERR_FAIL_NULL_MSG(pm, "Item subnode is not a PopupMenu: '" + p_child->get_item_submenu(i) + "'.");
-
- DisplayServer::get_singleton()->global_menu_add_submenu_item(p_menu_name, atr(p_child->get_item_text(i)), p_menu_name + "/" + itos(i));
- _update_submenu(p_menu_name + "/" + itos(i), pm);
- } else {
- int index = DisplayServer::get_singleton()->global_menu_add_item(p_menu_name, atr(p_child->get_item_text(i)), callable_mp(p_child, &PopupMenu::activate_item), Callable(), i);
-
- if (p_child->is_item_checkable(i)) {
- DisplayServer::get_singleton()->global_menu_set_item_checkable(p_menu_name, index, true);
- }
- if (p_child->is_item_radio_checkable(i)) {
- DisplayServer::get_singleton()->global_menu_set_item_radio_checkable(p_menu_name, index, true);
- }
- DisplayServer::get_singleton()->global_menu_set_item_checked(p_menu_name, index, p_child->is_item_checked(i));
- DisplayServer::get_singleton()->global_menu_set_item_disabled(p_menu_name, index, p_child->is_item_disabled(i));
- DisplayServer::get_singleton()->global_menu_set_item_max_states(p_menu_name, index, p_child->get_item_max_states(i));
- DisplayServer::get_singleton()->global_menu_set_item_icon(p_menu_name, index, p_child->get_item_icon(i));
- DisplayServer::get_singleton()->global_menu_set_item_state(p_menu_name, index, p_child->get_item_state(i));
- DisplayServer::get_singleton()->global_menu_set_item_indentation_level(p_menu_name, index, p_child->get_item_indent(i));
- DisplayServer::get_singleton()->global_menu_set_item_tooltip(p_menu_name, index, p_child->get_item_tooltip(i));
- if (!p_child->is_item_shortcut_disabled(i) && p_child->get_item_shortcut(i).is_valid() && p_child->get_item_shortcut(i)->has_valid_event()) {
- Array events = p_child->get_item_shortcut(i)->get_events();
- for (int j = 0; j < events.size(); j++) {
- Ref<InputEventKey> ie = events[j];
- if (ie.is_valid()) {
- DisplayServer::get_singleton()->global_menu_set_item_accelerator(p_menu_name, index, ie->get_keycode_with_modifiers());
- break;
- }
- }
- } else if (p_child->get_item_accelerator(i) != Key::NONE) {
- DisplayServer::get_singleton()->global_menu_set_item_accelerator(p_menu_name, i, p_child->get_item_accelerator(i));
- }
- }
- }
-}
-
bool MenuBar::is_native_menu() const {
#ifdef TOOLS_ENABLED
if (is_part_of_edited_scene()) {
@@ -258,52 +212,67 @@ bool MenuBar::is_native_menu() const {
return (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU) && is_native);
}
-void MenuBar::_clear_menu() {
+String MenuBar::bind_global_menu() {
+#ifdef TOOLS_ENABLED
+ if (is_part_of_edited_scene()) {
+ return String();
+ }
+#endif
if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) {
- return;
+ return String();
}
- // Remove root menu items.
- int count = DisplayServer::get_singleton()->global_menu_get_item_count("_main");
- for (int i = count - 1; i >= 0; i--) {
- if (global_menus.has(DisplayServer::get_singleton()->global_menu_get_item_submenu("_main", i))) {
- DisplayServer::get_singleton()->global_menu_remove_item("_main", i);
+ if (!global_menu_name.is_empty()) {
+ return global_menu_name; // Already bound.
+ }
+
+ DisplayServer *ds = DisplayServer::get_singleton();
+ global_menu_name = "__MenuBar#" + itos(get_instance_id());
+
+ int global_start_idx = -1;
+ int count = ds->global_menu_get_item_count("_main");
+ String prev_tag;
+ for (int i = 0; i < count; i++) {
+ String tag = ds->global_menu_get_item_tag("_main", i).operator String().get_slice("#", 1);
+ if (!tag.is_empty() && tag != prev_tag) {
+ if (i >= start_index) {
+ global_start_idx = i;
+ break;
+ }
}
+ prev_tag = tag;
}
- // Erase submenu contents.
- for (const String &E : global_menus) {
- DisplayServer::get_singleton()->global_menu_clear(E);
+ if (global_start_idx == -1) {
+ global_start_idx = count;
}
- global_menus.clear();
-}
-void MenuBar::_update_menu() {
- _clear_menu();
+ Vector<PopupMenu *> popups = _get_popups();
+ for (int i = 0; i < menu_cache.size(); i++) {
+ String submenu_name = popups[i]->bind_global_menu();
+ int index = ds->global_menu_add_submenu_item("_main", menu_cache[i].name, submenu_name, global_start_idx + i);
+ ds->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(i));
+ ds->global_menu_set_item_hidden("_main", index, menu_cache[i].hidden);
+ ds->global_menu_set_item_disabled("_main", index, menu_cache[i].disabled);
+ ds->global_menu_set_item_tooltip("_main", index, menu_cache[i].tooltip);
+ }
+
+ return global_menu_name;
+}
- if (!is_visible_in_tree()) {
+void MenuBar::unbind_global_menu() {
+ if (global_menu_name.is_empty()) {
return;
}
- int index = start_index;
- if (is_native_menu()) {
- Vector<PopupMenu *> popups = _get_popups();
- String root_name = "MenuBar<" + String::num_int64((uint64_t)this, 16) + ">";
- for (int i = 0; i < popups.size(); i++) {
- if (menu_cache[i].hidden) {
- continue;
- }
- String menu_name = atr(String(popups[i]->get_meta("_menu_name", popups[i]->get_name())));
-
- index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", menu_name, root_name + "/" + itos(i), index);
- if (menu_cache[i].disabled) {
- DisplayServer::get_singleton()->global_menu_set_item_disabled("_main", index, true);
- }
- _update_submenu(root_name + "/" + itos(i), popups[i]);
- index++;
- }
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int global_start = _find_global_start_index();
+ Vector<PopupMenu *> popups = _get_popups();
+ for (int i = menu_cache.size() - 1; i >= 0; i--) {
+ popups[i]->unbind_global_menu();
+ ds->global_menu_remove_item("_main", global_start + i);
}
- update_minimum_size();
- queue_redraw();
+
+ global_menu_name = String();
}
void MenuBar::_notification(int p_what) {
@@ -312,25 +281,43 @@ void MenuBar::_notification(int p_what) {
if (get_menu_count() > 0) {
_refresh_menu_names();
}
+ if (is_native_menu()) {
+ bind_global_menu();
+ }
} break;
case NOTIFICATION_EXIT_TREE: {
- _clear_menu();
+ unbind_global_menu();
} break;
case NOTIFICATION_MOUSE_EXIT: {
focused_menu = -1;
selected_menu = -1;
queue_redraw();
} break;
- case NOTIFICATION_TRANSLATION_CHANGED:
+ case NOTIFICATION_TRANSLATION_CHANGED: {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ bool is_global = !global_menu_name.is_empty();
+ int global_start = _find_global_start_index();
+ for (int i = 0; i < menu_cache.size(); i++) {
+ shape(menu_cache.write[i]);
+ if (is_global) {
+ ds->global_menu_set_item_text("_main", global_start + i, atr(menu_cache[i].name));
+ }
+ }
+ } break;
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_THEME_CHANGED: {
for (int i = 0; i < menu_cache.size(); i++) {
shape(menu_cache.write[i]);
}
- _update_menu();
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
- _update_menu();
+ if (is_native_menu()) {
+ if (is_visible_in_tree()) {
+ bind_global_menu();
+ } else {
+ unbind_global_menu();
+ }
+ }
} break;
case NOTIFICATION_DRAW: {
if (is_native_menu()) {
@@ -512,14 +499,20 @@ void MenuBar::shape(Menu &p_menu) {
}
void MenuBar::_refresh_menu_names() {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ bool is_global = !global_menu_name.is_empty();
+ int global_start = _find_global_start_index();
+
Vector<PopupMenu *> popups = _get_popups();
for (int i = 0; i < popups.size(); i++) {
if (!popups[i]->has_meta("_menu_name") && String(popups[i]->get_name()) != get_menu_title(i)) {
menu_cache.write[i].name = popups[i]->get_name();
shape(menu_cache.write[i]);
+ if (is_global) {
+ ds->global_menu_set_item_text("_main", global_start + i, atr(menu_cache[i].name));
+ }
}
}
- _update_menu();
}
Vector<PopupMenu *> MenuBar::_get_popups() const {
@@ -560,11 +553,14 @@ void MenuBar::add_child_notify(Node *p_child) {
menu_cache.push_back(menu);
p_child->connect("renamed", callable_mp(this, &MenuBar::_refresh_menu_names));
- p_child->connect("menu_changed", callable_mp(this, &MenuBar::_update_menu));
p_child->connect("about_to_popup", callable_mp(this, &MenuBar::_popup_visibility_changed).bind(true));
p_child->connect("popup_hide", callable_mp(this, &MenuBar::_popup_visibility_changed).bind(false));
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ String submenu_name = pm->bind_global_menu();
+ int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, _find_global_start_index() + menu_cache.size() - 1);
+ DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(menu_cache.size() - 1));
+ }
}
void MenuBar::move_child_notify(Node *p_child) {
@@ -586,9 +582,20 @@ void MenuBar::move_child_notify(Node *p_child) {
}
Menu menu = menu_cache[old_idx];
menu_cache.remove_at(old_idx);
- menu_cache.insert(get_menu_idx_from_control(pm), menu);
+ int new_idx = get_menu_idx_from_control(pm);
+ menu_cache.insert(new_idx, menu);
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ int global_start = _find_global_start_index();
+ if (old_idx != -1) {
+ DisplayServer::get_singleton()->global_menu_remove_item("_main", global_start + old_idx);
+ }
+ if (new_idx != -1) {
+ String submenu_name = pm->bind_global_menu();
+ int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, global_start + new_idx);
+ DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(new_idx));
+ }
+ }
}
void MenuBar::remove_child_notify(Node *p_child) {
@@ -603,15 +610,17 @@ void MenuBar::remove_child_notify(Node *p_child) {
menu_cache.remove_at(idx);
+ if (!global_menu_name.is_empty()) {
+ pm->unbind_global_menu();
+ DisplayServer::get_singleton()->global_menu_remove_item("_main", _find_global_start_index() + idx);
+ }
+
p_child->remove_meta("_menu_name");
p_child->remove_meta("_menu_tooltip");
p_child->disconnect("renamed", callable_mp(this, &MenuBar::_refresh_menu_names));
- p_child->disconnect("menu_changed", callable_mp(this, &MenuBar::_update_menu));
p_child->disconnect("about_to_popup", callable_mp(this, &MenuBar::_popup_visibility_changed));
p_child->disconnect("popup_hide", callable_mp(this, &MenuBar::_popup_visibility_changed));
-
- _update_menu();
}
void MenuBar::_bind_methods() {
@@ -699,7 +708,8 @@ void MenuBar::set_text_direction(Control::TextDirection p_text_direction) {
ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
if (text_direction != p_text_direction) {
text_direction = p_text_direction;
- _update_menu();
+ update_minimum_size();
+ queue_redraw();
}
}
@@ -710,7 +720,8 @@ Control::TextDirection MenuBar::get_text_direction() const {
void MenuBar::set_language(const String &p_language) {
if (language != p_language) {
language = p_language;
- _update_menu();
+ update_minimum_size();
+ queue_redraw();
}
}
@@ -732,7 +743,10 @@ bool MenuBar::is_flat() const {
void MenuBar::set_start_index(int p_index) {
if (start_index != p_index) {
start_index = p_index;
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ unbind_global_menu();
+ bind_global_menu();
+ }
}
}
@@ -742,11 +756,12 @@ int MenuBar::get_start_index() const {
void MenuBar::set_prefer_global_menu(bool p_enabled) {
if (is_native != p_enabled) {
+ is_native = p_enabled;
if (is_native) {
- _clear_menu();
+ bind_global_menu();
+ } else {
+ unbind_global_menu();
}
- is_native = p_enabled;
- _update_menu();
}
}
@@ -790,7 +805,9 @@ void MenuBar::set_menu_title(int p_menu, const String &p_title) {
}
menu_cache.write[p_menu].name = p_title;
shape(menu_cache.write[p_menu]);
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_text("_main", _find_global_start_index() + p_menu, atr(menu_cache[p_menu].name));
+ }
}
String MenuBar::get_menu_title(int p_menu) const {
@@ -802,7 +819,10 @@ void MenuBar::set_menu_tooltip(int p_menu, const String &p_tooltip) {
ERR_FAIL_INDEX(p_menu, menu_cache.size());
PopupMenu *pm = get_menu_popup(p_menu);
pm->set_meta("_menu_tooltip", p_tooltip);
- menu_cache.write[p_menu].name = p_tooltip;
+ menu_cache.write[p_menu].tooltip = p_tooltip;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_tooltip("_main", _find_global_start_index() + p_menu, p_tooltip);
+ }
}
String MenuBar::get_menu_tooltip(int p_menu) const {
@@ -813,7 +833,9 @@ String MenuBar::get_menu_tooltip(int p_menu) const {
void MenuBar::set_menu_disabled(int p_menu, bool p_disabled) {
ERR_FAIL_INDEX(p_menu, menu_cache.size());
menu_cache.write[p_menu].disabled = p_disabled;
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_disabled("_main", _find_global_start_index() + p_menu, p_disabled);
+ }
}
bool MenuBar::is_menu_disabled(int p_menu) const {
@@ -824,7 +846,9 @@ bool MenuBar::is_menu_disabled(int p_menu) const {
void MenuBar::set_menu_hidden(int p_menu, bool p_hidden) {
ERR_FAIL_INDEX(p_menu, menu_cache.size());
menu_cache.write[p_menu].hidden = p_hidden;
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_hidden("_main", _find_global_start_index() + p_menu, p_hidden);
+ }
}
bool MenuBar::is_menu_hidden(int p_menu) const {
diff --git a/scene/gui/menu_bar.h b/scene/gui/menu_bar.h
index 4d6e76d9b6..ba4df5f229 100644
--- a/scene/gui/menu_bar.h
+++ b/scene/gui/menu_bar.h
@@ -66,7 +66,6 @@ class MenuBar : public Control {
}
};
Vector<Menu> menu_cache;
- HashSet<String> global_menus;
int focused_menu = -1;
int selected_menu = -1;
@@ -114,9 +113,23 @@ class MenuBar : public Control {
void _open_popup(int p_index, bool p_focus_item = false);
void _popup_visibility_changed(bool p_visible);
- void _update_submenu(const String &p_menu_name, PopupMenu *p_child);
- void _clear_menu();
- void _update_menu();
+
+ String global_menu_name;
+
+ int _find_global_start_index() {
+ if (global_menu_name.is_empty()) {
+ return -1;
+ }
+
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int count = ds->global_menu_get_item_count("_main");
+ for (int i = 0; i < count; i++) {
+ if (ds->global_menu_get_item_tag("_main", i).operator String().begins_with(global_menu_name)) {
+ return i;
+ }
+ }
+ return -1;
+ }
protected:
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
@@ -130,6 +143,9 @@ protected:
public:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
+ String bind_global_menu();
+ void unbind_global_menu();
+
void set_switch_on_hover(bool p_enabled);
bool is_switch_on_hover();
void set_disable_shortcuts(bool p_disabled);
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 54fd8b8745..e3b0a18325 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -40,6 +40,86 @@
#include "scene/gui/menu_bar.h"
#include "scene/theme/theme_db.h"
+String PopupMenu::bind_global_menu() {
+#ifdef TOOLS_ENABLED
+ if (is_part_of_edited_scene()) {
+ return String();
+ }
+#endif
+ if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) {
+ return String();
+ }
+
+ if (!global_menu_name.is_empty()) {
+ return global_menu_name; // Already bound;
+ }
+
+ DisplayServer *ds = DisplayServer::get_singleton();
+ global_menu_name = "__PopupMenu#" + itos(get_instance_id());
+ ds->global_menu_set_popup_callbacks(global_menu_name, callable_mp(this, &PopupMenu::_about_to_popup), callable_mp(this, &PopupMenu::_about_to_close));
+ for (int i = 0; i < items.size(); i++) {
+ Item &item = items.write[i];
+ if (item.separator) {
+ ds->global_menu_add_separator(global_menu_name);
+ } else {
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), i);
+ if (!item.submenu.is_empty()) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(item.submenu));
+ if (pm) {
+ String submenu_name = pm->bind_global_menu();
+ ds->global_menu_set_item_submenu(global_menu_name, index, submenu_name);
+ item.submenu_bound = true;
+ }
+ }
+ if (item.checkable_type == Item::CHECKABLE_TYPE_CHECK_BOX) {
+ ds->global_menu_set_item_checkable(global_menu_name, index, true);
+ } else if (item.checkable_type == Item::CHECKABLE_TYPE_RADIO_BUTTON) {
+ ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
+ }
+ ds->global_menu_set_item_checked(global_menu_name, index, item.checked);
+ ds->global_menu_set_item_disabled(global_menu_name, index, item.disabled);
+ ds->global_menu_set_item_max_states(global_menu_name, index, item.max_states);
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ ds->global_menu_set_item_state(global_menu_name, index, item.state);
+ ds->global_menu_set_item_indentation_level(global_menu_name, index, item.indent);
+ ds->global_menu_set_item_tooltip(global_menu_name, index, item.tooltip);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ } else if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ }
+ }
+ return global_menu_name;
+}
+
+void PopupMenu::unbind_global_menu() {
+ if (global_menu_name.is_empty()) {
+ return;
+ }
+
+ for (int i = 0; i < items.size(); i++) {
+ Item &item = items.write[i];
+ if (!item.submenu.is_empty()) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(item.submenu));
+ if (pm) {
+ pm->unbind_global_menu();
+ }
+ item.submenu_bound = false;
+ }
+ }
+ DisplayServer::get_singleton()->global_menu_clear(global_menu_name);
+
+ global_menu_name = String();
+}
+
String PopupMenu::_get_accel_text(const Item &p_item) const {
if (p_item.shortcut.is_valid()) {
return p_item.shortcut->get_as_text();
@@ -821,11 +901,17 @@ void PopupMenu::_menu_changed() {
void PopupMenu::add_child_notify(Node *p_child) {
Window::add_child_notify(p_child);
- PopupMenu *pm = Object::cast_to<PopupMenu>(p_child);
- if (!pm) {
- return;
+ if (Object::cast_to<PopupMenu>(p_child) && !global_menu_name.is_empty()) {
+ String node_name = p_child->get_name();
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(node_name));
+ for (int i = 0; i < items.size(); i++) {
+ if (items[i].submenu == node_name) {
+ String submenu_name = pm->bind_global_menu();
+ DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, i, submenu_name);
+ items.write[i].submenu_bound = true;
+ }
+ }
}
- p_child->connect("menu_changed", callable_mp(this, &PopupMenu::_menu_changed));
_menu_changed();
}
@@ -836,7 +922,16 @@ void PopupMenu::remove_child_notify(Node *p_child) {
if (!pm) {
return;
}
- p_child->disconnect("menu_changed", callable_mp(this, &PopupMenu::_menu_changed));
+ if (Object::cast_to<PopupMenu>(p_child) && !global_menu_name.is_empty()) {
+ String node_name = p_child->get_name();
+ for (int i = 0; i < items.size(); i++) {
+ if (items[i].submenu == node_name) {
+ DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, i, String());
+ items.write[i].submenu_bound = false;
+ }
+ }
+ pm->unbind_global_menu();
+ }
_menu_changed();
}
@@ -857,9 +952,15 @@ void PopupMenu::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED:
case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED: {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ bool is_global = !global_menu_name.is_empty();
for (int i = 0; i < items.size(); i++) {
- items.write[i].xl_text = atr(items[i].text);
- items.write[i].dirty = true;
+ Item &item = items.write[i];
+ item.xl_text = atr(item.text);
+ item.dirty = true;
+ if (is_global) {
+ ds->global_menu_set_item_text(global_menu_name, i, item.xl_text);
+ }
_shape_item(i);
}
@@ -1031,6 +1132,14 @@ void PopupMenu::add_item(const String &p_label, int p_id, Key p_accel) {
ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
@@ -1045,6 +1154,15 @@ void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_labe
item.icon = p_icon;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
@@ -1059,10 +1177,20 @@ void PopupMenu::add_check_item(const String &p_label, int p_id, Key p_accel) {
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1073,10 +1201,22 @@ void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String &
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ ds->global_menu_set_item_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
+ _menu_changed();
}
void PopupMenu::add_radio_check_item(const String &p_label, int p_id, Key p_accel) {
@@ -1085,10 +1225,20 @@ void PopupMenu::add_radio_check_item(const String &p_label, int p_id, Key p_acce
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1099,10 +1249,21 @@ void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const St
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1113,11 +1274,22 @@ void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int
item.state = p_default_state;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_max_states(global_menu_name, index, item.max_states);
+ ds->global_menu_set_item_state(global_menu_name, index, item.state);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
_menu_changed();
+ notify_property_list_changed();
}
#define ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, p_allow_echo) \
@@ -1135,10 +1307,26 @@ void PopupMenu::add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_g
ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, p_allow_echo);
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1148,10 +1336,27 @@ void PopupMenu::add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortc
item.icon = p_icon;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1161,10 +1366,27 @@ void PopupMenu::add_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bo
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ ds->global_menu_set_item_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1175,10 +1397,28 @@ void PopupMenu::add_icon_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ ds->global_menu_set_item_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1188,10 +1428,27 @@ void PopupMenu::add_radio_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1202,10 +1459,28 @@ void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, cons
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1217,10 +1492,22 @@ void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu,
item.submenu = p_submenu;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(item.submenu)); // Find first menu with this name.
+ if (pm) {
+ String submenu_name = pm->bind_global_menu();
+ ds->global_menu_set_item_submenu(global_menu_name, index, submenu_name);
+ items.write[index].submenu_bound = true;
+ }
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1240,6 +1527,10 @@ void PopupMenu::set_item_text(int p_idx, const String &p_text) {
items.write[p_idx].text = p_text;
items.write[p_idx].xl_text = atr(p_text);
items.write[p_idx].dirty = true;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_text(global_menu_name, p_idx, items[p_idx].xl_text);
+ }
_shape_item(p_idx);
control->queue_redraw();
@@ -1284,6 +1575,10 @@ void PopupMenu::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) {
items.write[p_idx].icon = p_icon;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_icon(global_menu_name, p_idx, items[p_idx].icon);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1332,6 +1627,10 @@ void PopupMenu::set_item_checked(int p_idx, bool p_checked) {
items.write[p_idx].checked = p_checked;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_checked(global_menu_name, p_idx, p_checked);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1349,6 +1648,10 @@ void PopupMenu::set_item_id(int p_idx, int p_id) {
items.write[p_idx].id = p_id;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_tag(global_menu_name, p_idx, p_id);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1367,6 +1670,10 @@ void PopupMenu::set_item_accelerator(int p_idx, Key p_accel) {
items.write[p_idx].accel = p_accel;
items.write[p_idx].dirty = true;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_accelerator(global_menu_name, p_idx, p_accel);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1383,7 +1690,6 @@ void PopupMenu::set_item_metadata(int p_idx, const Variant &p_meta) {
}
items.write[p_idx].metadata = p_meta;
- control->queue_redraw();
child_controls_changed();
_menu_changed();
}
@@ -1399,6 +1705,11 @@ void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) {
}
items.write[p_idx].disabled = p_disabled;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_disabled(global_menu_name, p_idx, p_disabled);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1414,7 +1725,30 @@ void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) {
return;
}
+ if (!global_menu_name.is_empty()) {
+ if (items[p_idx].submenu_bound) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(items[p_idx].submenu));
+ if (pm) {
+ DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, p_idx, String());
+ pm->unbind_global_menu();
+ }
+ items.write[p_idx].submenu_bound = false;
+ }
+ }
+
items.write[p_idx].submenu = p_submenu;
+
+ if (!global_menu_name.is_empty()) {
+ if (!items[p_idx].submenu.is_empty()) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(items[p_idx].submenu));
+ if (pm) {
+ String submenu_name = pm->bind_global_menu();
+ DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, p_idx, submenu_name);
+ items.write[p_idx].submenu_bound = true;
+ }
+ }
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1423,6 +1757,11 @@ void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) {
void PopupMenu::toggle_item_checked(int p_idx) {
ERR_FAIL_INDEX(p_idx, items.size());
items.write[p_idx].checked = !items[p_idx].checked;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_checked(global_menu_name, p_idx, items[p_idx].checked);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1569,6 +1908,11 @@ void PopupMenu::set_item_as_checkable(int p_idx, bool p_checkable) {
}
items.write[p_idx].checkable_type = p_checkable ? Item::CHECKABLE_TYPE_CHECK_BOX : Item::CHECKABLE_TYPE_NONE;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_checkable(global_menu_name, p_idx, p_checkable);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1585,6 +1929,11 @@ void PopupMenu::set_item_as_radio_checkable(int p_idx, bool p_radio_checkable) {
}
items.write[p_idx].checkable_type = p_radio_checkable ? Item::CHECKABLE_TYPE_RADIO_BUTTON : Item::CHECKABLE_TYPE_NONE;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_radio_checkable(global_menu_name, p_idx, p_radio_checkable);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1600,6 +1949,11 @@ void PopupMenu::set_item_tooltip(int p_idx, const String &p_tooltip) {
}
items.write[p_idx].tooltip = p_tooltip;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_tooltip(global_menu_name, p_idx, p_tooltip);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1625,6 +1979,21 @@ void PopupMenu::set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bo
_ref_shortcut(items[p_idx].shortcut);
}
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ ds->global_menu_set_item_accelerator(global_menu_name, p_idx, Key::NONE);
+ if (!items[p_idx].shortcut_is_disabled && items[p_idx].shortcut.is_valid() && items[p_idx].shortcut->has_valid_event()) {
+ Array events = items[p_idx].shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, p_idx, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1640,6 +2009,10 @@ void PopupMenu::set_item_indent(int p_idx, int p_indent) {
}
items.write[p_idx].indent = p_indent;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_indentation_level(global_menu_name, p_idx, p_indent);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1656,6 +2029,11 @@ void PopupMenu::set_item_multistate(int p_idx, int p_state) {
}
items.write[p_idx].state = p_state;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_state(global_menu_name, p_idx, p_state);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1671,6 +2049,22 @@ void PopupMenu::set_item_shortcut_disabled(int p_idx, bool p_disabled) {
}
items.write[p_idx].shortcut_is_disabled = p_disabled;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ ds->global_menu_set_item_accelerator(global_menu_name, p_idx, Key::NONE);
+ if (!items[p_idx].shortcut_is_disabled && items[p_idx].shortcut.is_valid() && items[p_idx].shortcut->has_valid_event()) {
+ Array events = items[p_idx].shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, p_idx, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1686,6 +2080,10 @@ void PopupMenu::toggle_item_multistate(int p_idx) {
items.write[p_idx].state = 0;
}
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_state(global_menu_name, p_idx, items[p_idx].state);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1739,11 +2137,23 @@ void PopupMenu::set_item_count(int p_count) {
return;
}
+ DisplayServer *ds = DisplayServer::get_singleton();
+ bool is_global = !global_menu_name.is_empty();
+
+ if (is_global && prev_size > p_count) {
+ for (int i = prev_size - 1; i >= p_count; i--) {
+ ds->global_menu_remove_item(global_menu_name, i);
+ }
+ }
+
items.resize(p_count);
if (prev_size < p_count) {
for (int i = prev_size; i < p_count; i++) {
items.write[i].id = i;
+ if (is_global) {
+ ds->global_menu_add_item(global_menu_name, String(), callable_mp(this, &PopupMenu::activate_item), Callable(), i);
+ }
}
}
@@ -1828,6 +2238,16 @@ bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_fo
return false;
}
+void PopupMenu::_about_to_popup() {
+ ERR_MAIN_THREAD_GUARD;
+ emit_signal(SNAME("about_to_popup"));
+}
+
+void PopupMenu::_about_to_close() {
+ ERR_MAIN_THREAD_GUARD;
+ emit_signal(SNAME("popup_hide"));
+}
+
void PopupMenu::activate_item(int p_idx) {
ERR_FAIL_INDEX(p_idx, items.size());
ERR_FAIL_COND(items[p_idx].separator);
@@ -1890,6 +2310,11 @@ void PopupMenu::remove_item(int p_idx) {
}
items.remove_at(p_idx);
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_remove_item(global_menu_name, p_idx);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1904,6 +2329,11 @@ void PopupMenu::add_separator(const String &p_text, int p_id) {
sep.xl_text = atr(p_text);
}
items.push_back(sep);
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_add_separator(global_menu_name);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1922,7 +2352,22 @@ void PopupMenu::clear(bool p_free_submenus) {
}
}
}
+
+ if (!global_menu_name.is_empty()) {
+ for (int i = 0; i < items.size(); i++) {
+ Item &item = items.write[i];
+ if (!item.submenu.is_empty()) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(item.submenu));
+ if (pm) {
+ pm->unbind_global_menu();
+ }
+ item.submenu_bound = false;
+ }
+ }
+ DisplayServer::get_singleton()->global_menu_clear(global_menu_name);
+ }
items.clear();
+
mouse_over = -1;
control->queue_redraw();
child_controls_changed();
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index f123d08400..5d5f4a8322 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -75,6 +75,7 @@ class PopupMenu : public Popup {
bool shortcut_is_global = false;
bool shortcut_is_disabled = false;
bool allow_echo = false;
+ bool submenu_bound = false;
// Returns (0,0) if icon is null.
Size2 get_icon_size() const {
@@ -88,6 +89,8 @@ class PopupMenu : public Popup {
}
};
+ String global_menu_name;
+
bool close_allowed = false;
bool activated_by_keyboard = false;
@@ -213,6 +216,9 @@ public:
virtual void _parent_focused() override;
+ String bind_global_menu();
+ void unbind_global_menu();
+
void add_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
void add_check_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
@@ -293,6 +299,9 @@ public:
bool activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only = false);
void activate_item(int p_idx);
+ void _about_to_popup();
+ void _about_to_close();
+
void remove_item(int p_idx);
void add_separator(const String &p_text = String(), int p_id = -1);
diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp
index 851a94b32f..0d33774e20 100644
--- a/scene/gui/subviewport_container.cpp
+++ b/scene/gui/subviewport_container.cpp
@@ -190,6 +190,13 @@ void SubViewportContainer::_propagate_nonpositional_event(const Ref<InputEvent>
return;
}
+ bool send;
+ if (GDVIRTUAL_CALL(_propagate_input_event, p_event, send)) {
+ if (!send) {
+ return;
+ }
+ }
+
_send_event_to_viewports(p_event);
}
@@ -204,6 +211,13 @@ void SubViewportContainer::gui_input(const Ref<InputEvent> &p_event) {
return;
}
+ bool send;
+ if (GDVIRTUAL_CALL(_propagate_input_event, p_event, send)) {
+ if (!send) {
+ return;
+ }
+ }
+
if (stretch && shrink > 1) {
Transform2D xform;
xform.scale(Vector2(1, 1) / shrink);
@@ -275,6 +289,8 @@ void SubViewportContainer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stretch"), "set_stretch", "is_stretch_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_shrink"), "set_stretch_shrink", "get_stretch_shrink");
+
+ GDVIRTUAL_BIND(_propagate_input_event, "event");
}
SubViewportContainer::SubViewportContainer() {
diff --git a/scene/gui/subviewport_container.h b/scene/gui/subviewport_container.h
index 3c6cd09d66..06420de730 100644
--- a/scene/gui/subviewport_container.h
+++ b/scene/gui/subviewport_container.h
@@ -50,6 +50,8 @@ protected:
virtual void add_child_notify(Node *p_child) override;
virtual void remove_child_notify(Node *p_child) override;
+ GDVIRTUAL1RC(bool, _propagate_input_event, Ref<InputEvent>);
+
public:
void set_stretch(bool p_enable);
bool is_stretch_enabled() const;
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index f00fdb6896..3d426b8bf3 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -6436,14 +6436,6 @@ void TextEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "wrap_mode", PROPERTY_HINT_ENUM, "None,Boundary"), "set_line_wrapping_mode", "get_line_wrapping_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Arbitrary:1,Word:2,Word (Smart):3"), "set_autowrap_mode", "get_autowrap_mode");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_all_occurrences"), "set_highlight_all_occurrences", "is_highlight_all_occurrences_enabled");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_current_line"), "set_highlight_current_line", "is_highlight_current_line_enabled");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_tabs"), "set_draw_tabs", "is_drawing_tabs");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_spaces"), "set_draw_spaces", "is_drawing_spaces");
-
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "syntax_highlighter", PROPERTY_HINT_RESOURCE_TYPE, "SyntaxHighlighter", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE), "set_syntax_highlighter", "get_syntax_highlighter");
-
ADD_GROUP("Scroll", "scroll_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_smooth"), "set_smooth_scroll_enabled", "is_smooth_scroll_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_v_scroll_speed", PROPERTY_HINT_NONE, "suffix:px/s"), "set_v_scroll_speed", "get_v_scroll_speed");
@@ -6465,6 +6457,16 @@ void TextEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_caret_mid_grapheme_enabled", "is_caret_mid_grapheme_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_multiple"), "set_multiple_carets_enabled", "is_multiple_carets_enabled");
+ ADD_GROUP("Highlighting", "");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "syntax_highlighter", PROPERTY_HINT_RESOURCE_TYPE, "SyntaxHighlighter", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE), "set_syntax_highlighter", "get_syntax_highlighter");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_all_occurrences"), "set_highlight_all_occurrences", "is_highlight_all_occurrences_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_current_line"), "set_highlight_current_line", "is_highlight_current_line_enabled");
+
+ ADD_GROUP("Visual Whitespace", "draw_");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_tabs"), "set_draw_tabs", "is_drawing_tabs");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_spaces"), "set_draw_spaces", "is_drawing_spaces");
+
ADD_GROUP("BiDi", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language");
diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp
index 06acdd0237..8f38a6f6c8 100644
--- a/scene/main/canvas_item.cpp
+++ b/scene/main/canvas_item.cpp
@@ -273,10 +273,9 @@ void CanvasItem::_exit_canvas() {
}
void CanvasItem::_notification(int p_what) {
- ERR_MAIN_THREAD_GUARD;
-
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
+ ERR_MAIN_THREAD_GUARD;
ERR_FAIL_COND(!is_inside_tree());
Node *parent = get_parent();
@@ -336,6 +335,8 @@ void CanvasItem::_notification(int p_what) {
} break;
case NOTIFICATION_EXIT_TREE: {
+ ERR_MAIN_THREAD_GUARD;
+
if (xform_change.in_list()) {
get_tree()->xform_change_list.remove(&xform_change);
}
@@ -357,14 +358,20 @@ void CanvasItem::_notification(int p_what) {
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
+ ERR_MAIN_THREAD_GUARD;
+
emit_signal(SceneStringNames::get_singleton()->visibility_changed);
} break;
case NOTIFICATION_WORLD_2D_CHANGED: {
+ ERR_MAIN_THREAD_GUARD;
+
_exit_canvas();
_enter_canvas();
} break;
case NOTIFICATION_PARENTED: {
// The node is not inside the tree during this notification.
+ ERR_MAIN_THREAD_GUARD;
+
_notify_transform();
} break;
}
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index f5468b29c5..71ec9b29c5 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -270,20 +270,21 @@ void Window::set_title(const String &p_title) {
ERR_MAIN_THREAD_GUARD;
title = p_title;
+ tr_title = atr(p_title);
+#ifdef DEBUG_ENABLED
+ if (window_id == DisplayServer::MAIN_WINDOW_ID) {
+ // Append a suffix to the window title to denote that the project is running
+ // from a debug build (including the editor). Since this results in lower performance,
+ // this should be clearly presented to the user.
+ tr_title = vformat("%s (DEBUG)", tr_title);
+ }
+#endif
if (embedder) {
embedder->_sub_window_update(this);
} else if (window_id != DisplayServer::INVALID_WINDOW_ID) {
- String tr_title = atr(p_title);
-#ifdef DEBUG_ENABLED
- if (window_id == DisplayServer::MAIN_WINDOW_ID) {
- // Append a suffix to the window title to denote that the project is running
- // from a debug build (including the editor). Since this results in lower performance,
- // this should be clearly presented to the user.
- tr_title = vformat("%s (DEBUG)", tr_title);
- }
-#endif
DisplayServer::get_singleton()->window_set_title(tr_title, window_id);
+ _update_window_size();
}
}
@@ -586,15 +587,6 @@ void Window::_make_window() {
DisplayServer::get_singleton()->window_set_max_size(Size2i(), window_id);
DisplayServer::get_singleton()->window_set_min_size(Size2i(), window_id);
DisplayServer::get_singleton()->window_set_mouse_passthrough(mpath, window_id);
- String tr_title = atr(title);
-#ifdef DEBUG_ENABLED
- if (window_id == DisplayServer::MAIN_WINDOW_ID) {
- // Append a suffix to the window title to denote that the project is running
- // from a debug build (including the editor). Since this results in lower performance,
- // this should be clearly presented to the user.
- tr_title = vformat("%s (DEBUG)", tr_title);
- }
-#endif
DisplayServer::get_singleton()->window_set_title(tr_title, window_id);
DisplayServer::get_singleton()->window_attach_instance_id(get_instance_id(), window_id);
@@ -994,6 +986,12 @@ void Window::_update_window_size() {
}
DisplayServer::get_singleton()->window_set_max_size(max_size_used, window_id);
+
+ if (keep_title_visible) {
+ Size2i title_size = DisplayServer::get_singleton()->window_get_title_size(tr_title, window_id);
+ size_limit = size_limit.max(title_size);
+ }
+
DisplayServer::get_singleton()->window_set_min_size(size_limit, window_id);
DisplayServer::get_singleton()->window_set_size(size, window_id);
}
@@ -1281,17 +1279,19 @@ void Window::_notification(int p_what) {
_invalidate_theme_cache();
_update_theme_item_cache();
- if (!embedder && window_id != DisplayServer::INVALID_WINDOW_ID) {
- String tr_title = atr(title);
+ tr_title = atr(title);
#ifdef DEBUG_ENABLED
- if (window_id == DisplayServer::MAIN_WINDOW_ID) {
- // Append a suffix to the window title to denote that the project is running
- // from a debug build (including the editor). Since this results in lower performance,
- // this should be clearly presented to the user.
- tr_title = vformat("%s (DEBUG)", tr_title);
- }
+ if (window_id == DisplayServer::MAIN_WINDOW_ID) {
+ // Append a suffix to the window title to denote that the project is running
+ // from a debug build (including the editor). Since this results in lower performance,
+ // this should be clearly presented to the user.
+ tr_title = vformat("%s (DEBUG)", tr_title);
+ }
#endif
+
+ if (!embedder && window_id != DisplayServer::INVALID_WINDOW_ID) {
DisplayServer::get_singleton()->window_set_title(tr_title, window_id);
+ _update_window_size();
}
} break;
@@ -1384,6 +1384,20 @@ Window::ContentScaleStretch Window::get_content_scale_stretch() const {
return content_scale_stretch;
}
+void Window::set_keep_title_visible(bool p_title_visible) {
+ if (keep_title_visible == p_title_visible) {
+ return;
+ }
+ keep_title_visible = p_title_visible;
+ if (window_id != DisplayServer::INVALID_WINDOW_ID) {
+ _update_window_size();
+ }
+}
+
+bool Window::get_keep_title_visible() const {
+ return keep_title_visible;
+}
+
void Window::set_content_scale_factor(real_t p_factor) {
ERR_MAIN_THREAD_GUARD;
ERR_FAIL_COND(p_factor <= 0);
@@ -2713,6 +2727,9 @@ void Window::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_content_scale_stretch", "stretch"), &Window::set_content_scale_stretch);
ClassDB::bind_method(D_METHOD("get_content_scale_stretch"), &Window::get_content_scale_stretch);
+ ClassDB::bind_method(D_METHOD("set_keep_title_visible", "title_visible"), &Window::set_keep_title_visible);
+ ClassDB::bind_method(D_METHOD("get_keep_title_visible"), &Window::get_keep_title_visible);
+
ClassDB::bind_method(D_METHOD("set_content_scale_factor", "factor"), &Window::set_content_scale_factor);
ClassDB::bind_method(D_METHOD("get_content_scale_factor"), &Window::get_content_scale_factor);
@@ -2826,6 +2843,7 @@ void Window::_bind_methods() {
ADD_GROUP("Limits", "");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "min_size", PROPERTY_HINT_NONE, "suffix:px"), "set_min_size", "get_min_size");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "max_size", PROPERTY_HINT_NONE, "suffix:px"), "set_max_size", "get_max_size");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_title_visible"), "set_keep_title_visible", "get_keep_title_visible");
ADD_GROUP("Content Scale", "content_scale_");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "content_scale_size"), "set_content_scale_size", "get_content_scale_size");
diff --git a/scene/main/window.h b/scene/main/window.h
index db739cc304..8a54b6c7d3 100644
--- a/scene/main/window.h
+++ b/scene/main/window.h
@@ -111,6 +111,7 @@ private:
bool initialized = false;
String title;
+ String tr_title;
mutable int current_screen = 0;
mutable Vector2i position;
mutable Size2i size = Size2i(DEFAULT_WINDOW_SIZE, DEFAULT_WINDOW_SIZE);
@@ -131,6 +132,7 @@ private:
bool updating_embedded_window = false;
bool clamp_to_embedder = false;
bool unparent_when_invisible = false;
+ bool keep_title_visible = false;
LayoutDirection layout_dir = LAYOUT_DIRECTION_INHERITED;
@@ -336,6 +338,9 @@ public:
void set_content_scale_stretch(ContentScaleStretch p_stretch);
ContentScaleStretch get_content_scale_stretch() const;
+ void set_keep_title_visible(bool p_title_visible);
+ bool get_keep_title_visible() const;
+
void set_content_scale_factor(real_t p_factor);
real_t get_content_scale_factor() const;
diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp
index 7aa2f8e178..cb9cda59b2 100644
--- a/scene/resources/font.cpp
+++ b/scene/resources/font.cpp
@@ -558,24 +558,28 @@ _FORCE_INLINE_ void FontFile::_clear_cache() {
}
}
-_FORCE_INLINE_ void FontFile::_ensure_rid(int p_cache_index) const {
+_FORCE_INLINE_ void FontFile::_ensure_rid(int p_cache_index, int p_make_linked_from) const {
if (unlikely(p_cache_index >= cache.size())) {
cache.resize(p_cache_index + 1);
}
if (unlikely(!cache[p_cache_index].is_valid())) {
- cache.write[p_cache_index] = TS->create_font();
- TS->font_set_data_ptr(cache[p_cache_index], data_ptr, data_size);
- TS->font_set_antialiasing(cache[p_cache_index], antialiasing);
- TS->font_set_generate_mipmaps(cache[p_cache_index], mipmaps);
- TS->font_set_multichannel_signed_distance_field(cache[p_cache_index], msdf);
- TS->font_set_msdf_pixel_range(cache[p_cache_index], msdf_pixel_range);
- TS->font_set_msdf_size(cache[p_cache_index], msdf_size);
- TS->font_set_fixed_size(cache[p_cache_index], fixed_size);
- TS->font_set_force_autohinter(cache[p_cache_index], force_autohinter);
- TS->font_set_allow_system_fallback(cache[p_cache_index], allow_system_fallback);
- TS->font_set_hinting(cache[p_cache_index], hinting);
- TS->font_set_subpixel_positioning(cache[p_cache_index], subpixel_positioning);
- TS->font_set_oversampling(cache[p_cache_index], oversampling);
+ if (p_make_linked_from >= 0 && p_make_linked_from != p_cache_index && p_make_linked_from < cache.size()) {
+ cache.write[p_cache_index] = TS->create_font_linked_variation(cache[p_make_linked_from]);
+ } else {
+ cache.write[p_cache_index] = TS->create_font();
+ TS->font_set_data_ptr(cache[p_cache_index], data_ptr, data_size);
+ TS->font_set_antialiasing(cache[p_cache_index], antialiasing);
+ TS->font_set_generate_mipmaps(cache[p_cache_index], mipmaps);
+ TS->font_set_multichannel_signed_distance_field(cache[p_cache_index], msdf);
+ TS->font_set_msdf_pixel_range(cache[p_cache_index], msdf_pixel_range);
+ TS->font_set_msdf_size(cache[p_cache_index], msdf_size);
+ TS->font_set_fixed_size(cache[p_cache_index], fixed_size);
+ TS->font_set_force_autohinter(cache[p_cache_index], force_autohinter);
+ TS->font_set_allow_system_fallback(cache[p_cache_index], allow_system_fallback);
+ TS->font_set_hinting(cache[p_cache_index], hinting);
+ TS->font_set_subpixel_positioning(cache[p_cache_index], subpixel_positioning);
+ TS->font_set_oversampling(cache[p_cache_index], oversampling);
+ }
}
}
@@ -2218,17 +2222,19 @@ real_t FontFile::get_oversampling() const {
RID FontFile::find_variation(const Dictionary &p_variation_coordinates, int p_face_index, float p_strength, Transform2D p_transform, int p_spacing_top, int p_spacing_bottom, int p_spacing_space, int p_spacing_glyph) const {
// Find existing variation cache.
const Dictionary &supported_coords = get_supported_variation_list();
+ int make_linked_from = -1;
for (int i = 0; i < cache.size(); i++) {
if (cache[i].is_valid()) {
const Dictionary &cache_var = TS->font_get_variation_coordinates(cache[i]);
bool match = true;
+ bool match_linked = true;
match = match && (TS->font_get_face_index(cache[i]) == p_face_index);
match = match && (TS->font_get_embolden(cache[i]) == p_strength);
match = match && (TS->font_get_transform(cache[i]) == p_transform);
- match = match && (TS->font_get_spacing(cache[i], TextServer::SPACING_TOP) == p_spacing_top);
- match = match && (TS->font_get_spacing(cache[i], TextServer::SPACING_BOTTOM) == p_spacing_bottom);
- match = match && (TS->font_get_spacing(cache[i], TextServer::SPACING_SPACE) == p_spacing_space);
- match = match && (TS->font_get_spacing(cache[i], TextServer::SPACING_GLYPH) == p_spacing_glyph);
+ match_linked = match_linked && (TS->font_get_spacing(cache[i], TextServer::SPACING_TOP) == p_spacing_top);
+ match_linked = match_linked && (TS->font_get_spacing(cache[i], TextServer::SPACING_BOTTOM) == p_spacing_bottom);
+ match_linked = match_linked && (TS->font_get_spacing(cache[i], TextServer::SPACING_SPACE) == p_spacing_space);
+ match_linked = match_linked && (TS->font_get_spacing(cache[i], TextServer::SPACING_GLYPH) == p_spacing_glyph);
for (const Variant *V = supported_coords.next(nullptr); V && match; V = supported_coords.next(V)) {
const Vector3 &def = supported_coords[*V];
@@ -2255,22 +2261,34 @@ RID FontFile::find_variation(const Dictionary &p_variation_coordinates, int p_fa
match = match && (c_v == s_v);
}
if (match) {
- return cache[i];
+ if (match_linked) {
+ return cache[i];
+ } else {
+ make_linked_from = i;
+ }
}
}
}
// Create new variation cache.
int idx = cache.size();
- _ensure_rid(idx);
- TS->font_set_variation_coordinates(cache[idx], p_variation_coordinates);
- TS->font_set_face_index(cache[idx], p_face_index);
- TS->font_set_embolden(cache[idx], p_strength);
- TS->font_set_transform(cache[idx], p_transform);
- TS->font_set_spacing(cache[idx], TextServer::SPACING_TOP, p_spacing_top);
- TS->font_set_spacing(cache[idx], TextServer::SPACING_BOTTOM, p_spacing_bottom);
- TS->font_set_spacing(cache[idx], TextServer::SPACING_SPACE, p_spacing_space);
- TS->font_set_spacing(cache[idx], TextServer::SPACING_GLYPH, p_spacing_glyph);
+ if (make_linked_from >= 0) {
+ _ensure_rid(idx, make_linked_from);
+ TS->font_set_spacing(cache[idx], TextServer::SPACING_TOP, p_spacing_top);
+ TS->font_set_spacing(cache[idx], TextServer::SPACING_BOTTOM, p_spacing_bottom);
+ TS->font_set_spacing(cache[idx], TextServer::SPACING_SPACE, p_spacing_space);
+ TS->font_set_spacing(cache[idx], TextServer::SPACING_GLYPH, p_spacing_glyph);
+ } else {
+ _ensure_rid(idx);
+ TS->font_set_variation_coordinates(cache[idx], p_variation_coordinates);
+ TS->font_set_face_index(cache[idx], p_face_index);
+ TS->font_set_embolden(cache[idx], p_strength);
+ TS->font_set_transform(cache[idx], p_transform);
+ TS->font_set_spacing(cache[idx], TextServer::SPACING_TOP, p_spacing_top);
+ TS->font_set_spacing(cache[idx], TextServer::SPACING_BOTTOM, p_spacing_bottom);
+ TS->font_set_spacing(cache[idx], TextServer::SPACING_SPACE, p_spacing_space);
+ TS->font_set_spacing(cache[idx], TextServer::SPACING_GLYPH, p_spacing_glyph);
+ }
return cache[idx];
}
@@ -3016,14 +3034,19 @@ void SystemFont::_update_base_font() {
continue;
}
- // If it's a font collection check all faces to match requested style.
+ // If it's a font collection check all faces to match requested style and name.
int best_score = 0;
for (int i = 0; i < file->get_face_count(); i++) {
+ int score = 0;
file->set_face_index(0, i);
+ const String n = file->get_font_name();
+ if (n.to_upper() == E.to_upper()) {
+ score += 80;
+ }
BitField<TextServer::FontStyle> style = file->get_font_style();
int font_weight = file->get_font_weight();
int font_stretch = file->get_font_stretch();
- int score = (20 - Math::abs(font_weight - weight) / 50);
+ score += (20 - Math::abs(font_weight - weight) / 50);
score += (20 - Math::abs(font_stretch - stretch) / 10);
if (bool(style & TextServer::FONT_ITALIC) == italic) {
score += 30;
@@ -3042,7 +3065,7 @@ void SystemFont::_update_base_font() {
file->set_face_index(0, face_indeces[0]);
// If it's a variable font, apply weight, stretch and italic coordinates to match requested style.
- if (best_score != 50) {
+ if (best_score != 150) {
Dictionary ftr = file->get_supported_variation_list();
if (ftr.has(TS->name_to_tag("width"))) {
ftr_stretch = stretch;
diff --git a/scene/resources/font.h b/scene/resources/font.h
index 30ae11a235..c61d8ed9d4 100644
--- a/scene/resources/font.h
+++ b/scene/resources/font.h
@@ -205,7 +205,7 @@ class FontFile : public Font {
mutable Vector<RID> cache;
_FORCE_INLINE_ void _clear_cache();
- _FORCE_INLINE_ void _ensure_rid(int p_cache_index) const;
+ _FORCE_INLINE_ void _ensure_rid(int p_cache_index, int p_make_linked_from = -1) const;
void _convert_packed_8bit(Ref<Image> &p_source, int p_page, int p_sz);
void _convert_packed_4bit(Ref<Image> &p_source, int p_page, int p_sz);
diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp
index 1483d29384..ddec5e826b 100644
--- a/scene/theme/default_theme.cpp
+++ b/scene/theme/default_theme.cpp
@@ -498,8 +498,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_icon("executing_line", "CodeEdit", icons["arrow_right"]);
theme->set_icon("can_fold", "CodeEdit", icons["arrow_down"]);
theme->set_icon("folded", "CodeEdit", icons["arrow_right"]);
- theme->set_icon("can_fold_code_region", "CodeEdit", icons["folder_down_arrow"]);
- theme->set_icon("folded_code_region", "CodeEdit", icons["folder_right_arrow"]);
+ theme->set_icon("can_fold_code_region", "CodeEdit", icons["region_unfolded"]);
+ theme->set_icon("folded_code_region", "CodeEdit", icons["region_folded"]);
theme->set_icon("folded_eol_icon", "CodeEdit", icons["text_edit_ellipsis"]);
theme->set_font("font", "CodeEdit", Ref<Font>());
diff --git a/scene/theme/icons/folder_down_arrow.svg b/scene/theme/icons/folder_down_arrow.svg
deleted file mode 100644
index 3bc4f3f73b..0000000000
--- a/scene/theme/icons/folder_down_arrow.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M2 1a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H6V2a1 1 0 0 0-1-1zm1 5a1 1 0 0 1 1.414-1.414L6 6.172l1.586-1.586A1 1 0 0 1 9 6L6.707 8.293a1 1 0 0 1-1.414 0Z" fill="#fff"/></svg> \ No newline at end of file
diff --git a/scene/theme/icons/folder_right_arrow.svg b/scene/theme/icons/folder_right_arrow.svg
deleted file mode 100644
index a9b81d54f3..0000000000
--- a/scene/theme/icons/folder_right_arrow.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M2 1a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H6V2a1 1 0 0 0-1-1zm3.5 8a1 1 0 0 1-1.414-1.414L5.672 6 4.086 4.414A1 1 0 0 1 5.5 3l2.293 2.293a1 1 0 0 1 0 1.414Z" fill="#fff"/></svg> \ No newline at end of file
diff --git a/scene/theme/icons/region_folded.svg b/scene/theme/icons/region_folded.svg
new file mode 100644
index 0000000000..245371b5a1
--- /dev/null
+++ b/scene/theme/icons/region_folded.svg
@@ -0,0 +1 @@
+<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M3 2a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1zm2.75 7a1 1 0 0 1-1.414-1.414L5.922 6 4.336 4.414A1 1 0 0 1 5.75 3l2.293 2.293a1 1 0 0 1 0 1.414z" fill="#fff"/></svg>
diff --git a/scene/theme/icons/region_unfolded.svg b/scene/theme/icons/region_unfolded.svg
new file mode 100644
index 0000000000..744ea7197d
--- /dev/null
+++ b/scene/theme/icons/region_unfolded.svg
@@ -0,0 +1 @@
+<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M10 3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1zM3 5.75a1 1 0 0 1 1.414-1.414L6 5.922l1.586-1.586A1 1 0 0 1 9 5.75L6.707 8.043a1 1 0 0 1-1.414 0z" fill="#fff"/></svg>
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index f41238b075..6459cc7462 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -82,6 +82,10 @@ int DisplayServer::global_menu_add_multistate_item(const String &p_menu_root, co
return -1;
}
+void DisplayServer::global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callbacs, const Callable &p_close_callback) {
+ WARN_PRINT("Global menus not supported by this display server.");
+}
+
int DisplayServer::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) {
WARN_PRINT("Global menus not supported by this display server.");
return -1;
@@ -106,6 +110,10 @@ void DisplayServer::global_menu_set_item_callback(const String &p_menu_root, int
WARN_PRINT("Global menus not supported by this display server.");
}
+void DisplayServer::global_menu_set_item_hover_callbacks(const String &p_menu_root, int p_idx, const Callable &p_callback) {
+ WARN_PRINT("Global menus not supported by this display server.");
+}
+
void DisplayServer::global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback) {
WARN_PRINT("Global menus not supported by this display server.");
}
@@ -160,6 +168,11 @@ bool DisplayServer::global_menu_is_item_disabled(const String &p_menu_root, int
return false;
}
+bool DisplayServer::global_menu_is_item_hidden(const String &p_menu_root, int p_idx) const {
+ WARN_PRINT("Global menus not supported by this display server.");
+ return false;
+}
+
String DisplayServer::global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const {
WARN_PRINT("Global menus not supported by this display server.");
return String();
@@ -217,6 +230,10 @@ void DisplayServer::global_menu_set_item_disabled(const String &p_menu_root, int
WARN_PRINT("Global menus not supported by this display server.");
}
+void DisplayServer::global_menu_set_item_hidden(const String &p_menu_root, int p_idx, bool p_hidden) {
+ WARN_PRINT("Global menus not supported by this display server.");
+}
+
void DisplayServer::global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) {
WARN_PRINT("Global menus not supported by this display server.");
}
@@ -581,6 +598,7 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("has_feature", "feature"), &DisplayServer::has_feature);
ClassDB::bind_method(D_METHOD("get_name"), &DisplayServer::get_name);
+ ClassDB::bind_method(D_METHOD("global_menu_set_popup_callbacks", "menu_root", "open_callback", "close_callback"), &DisplayServer::global_menu_set_popup_callbacks);
ClassDB::bind_method(D_METHOD("global_menu_add_submenu_item", "menu_root", "label", "submenu", "index"), &DisplayServer::global_menu_add_submenu_item, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("global_menu_add_item", "menu_root", "label", "callback", "key_callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_item, DEFVAL(Callable()), DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("global_menu_add_check_item", "menu_root", "label", "callback", "key_callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_check_item, DEFVAL(Callable()), DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1));
@@ -604,6 +622,7 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("global_menu_get_item_submenu", "menu_root", "idx"), &DisplayServer::global_menu_get_item_submenu);
ClassDB::bind_method(D_METHOD("global_menu_get_item_accelerator", "menu_root", "idx"), &DisplayServer::global_menu_get_item_accelerator);
ClassDB::bind_method(D_METHOD("global_menu_is_item_disabled", "menu_root", "idx"), &DisplayServer::global_menu_is_item_disabled);
+ ClassDB::bind_method(D_METHOD("global_menu_is_item_hidden", "menu_root", "idx"), &DisplayServer::global_menu_is_item_hidden);
ClassDB::bind_method(D_METHOD("global_menu_get_item_tooltip", "menu_root", "idx"), &DisplayServer::global_menu_get_item_tooltip);
ClassDB::bind_method(D_METHOD("global_menu_get_item_state", "menu_root", "idx"), &DisplayServer::global_menu_get_item_state);
ClassDB::bind_method(D_METHOD("global_menu_get_item_max_states", "menu_root", "idx"), &DisplayServer::global_menu_get_item_max_states);
@@ -614,12 +633,14 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("global_menu_set_item_checkable", "menu_root", "idx", "checkable"), &DisplayServer::global_menu_set_item_checkable);
ClassDB::bind_method(D_METHOD("global_menu_set_item_radio_checkable", "menu_root", "idx", "checkable"), &DisplayServer::global_menu_set_item_radio_checkable);
ClassDB::bind_method(D_METHOD("global_menu_set_item_callback", "menu_root", "idx", "callback"), &DisplayServer::global_menu_set_item_callback);
+ ClassDB::bind_method(D_METHOD("global_menu_set_item_hover_callbacks", "menu_root", "idx", "callback"), &DisplayServer::global_menu_set_item_hover_callbacks);
ClassDB::bind_method(D_METHOD("global_menu_set_item_key_callback", "menu_root", "idx", "key_callback"), &DisplayServer::global_menu_set_item_key_callback);
ClassDB::bind_method(D_METHOD("global_menu_set_item_tag", "menu_root", "idx", "tag"), &DisplayServer::global_menu_set_item_tag);
ClassDB::bind_method(D_METHOD("global_menu_set_item_text", "menu_root", "idx", "text"), &DisplayServer::global_menu_set_item_text);
ClassDB::bind_method(D_METHOD("global_menu_set_item_submenu", "menu_root", "idx", "submenu"), &DisplayServer::global_menu_set_item_submenu);
ClassDB::bind_method(D_METHOD("global_menu_set_item_accelerator", "menu_root", "idx", "keycode"), &DisplayServer::global_menu_set_item_accelerator);
ClassDB::bind_method(D_METHOD("global_menu_set_item_disabled", "menu_root", "idx", "disabled"), &DisplayServer::global_menu_set_item_disabled);
+ ClassDB::bind_method(D_METHOD("global_menu_set_item_hidden", "menu_root", "idx", "hidden"), &DisplayServer::global_menu_set_item_hidden);
ClassDB::bind_method(D_METHOD("global_menu_set_item_tooltip", "menu_root", "idx", "tooltip"), &DisplayServer::global_menu_set_item_tooltip);
ClassDB::bind_method(D_METHOD("global_menu_set_item_state", "menu_root", "idx", "state"), &DisplayServer::global_menu_set_item_state);
ClassDB::bind_method(D_METHOD("global_menu_set_item_max_states", "menu_root", "idx", "max_states"), &DisplayServer::global_menu_set_item_max_states);
@@ -696,6 +717,7 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("window_get_popup_safe_rect", "window"), &DisplayServer::window_get_popup_safe_rect);
ClassDB::bind_method(D_METHOD("window_set_title", "title", "window_id"), &DisplayServer::window_set_title, DEFVAL(MAIN_WINDOW_ID));
+ ClassDB::bind_method(D_METHOD("window_get_title_size", "title", "window_id"), &DisplayServer::window_get_title_size, DEFVAL(MAIN_WINDOW_ID));
ClassDB::bind_method(D_METHOD("window_set_mouse_passthrough", "region", "window_id"), &DisplayServer::window_set_mouse_passthrough, DEFVAL(MAIN_WINDOW_ID));
ClassDB::bind_method(D_METHOD("window_get_current_screen", "window_id"), &DisplayServer::window_get_current_screen, DEFVAL(MAIN_WINDOW_ID));
diff --git a/servers/display_server.h b/servers/display_server.h
index 85f9270696..d2e112d224 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -130,6 +130,8 @@ public:
virtual bool has_feature(Feature p_feature) const = 0;
virtual String get_name() const = 0;
+ virtual void global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callback = Callable(), const Callable &p_close_callback = Callable());
+
virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1);
virtual int global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
virtual int global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
@@ -153,6 +155,7 @@ public:
virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) const;
virtual Key global_menu_get_item_accelerator(const String &p_menu_root, int p_idx) const;
virtual bool global_menu_is_item_disabled(const String &p_menu_root, int p_idx) const;
+ virtual bool global_menu_is_item_hidden(const String &p_menu_root, int p_idx) const;
virtual String global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const;
virtual int global_menu_get_item_state(const String &p_menu_root, int p_idx) const;
virtual int global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const;
@@ -164,11 +167,13 @@ public:
virtual void global_menu_set_item_radio_checkable(const String &p_menu_root, int p_idx, bool p_checkable);
virtual void global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback);
virtual void global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback);
+ virtual void global_menu_set_item_hover_callbacks(const String &p_menu_root, int p_idx, const Callable &p_callback);
virtual void global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag);
virtual void global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text);
virtual void global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu);
virtual void global_menu_set_item_accelerator(const String &p_menu_root, int p_idx, Key p_keycode);
virtual void global_menu_set_item_disabled(const String &p_menu_root, int p_idx, bool p_disabled);
+ virtual void global_menu_set_item_hidden(const String &p_menu_root, int p_idx, bool p_hidden);
virtual void global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip);
virtual void global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state);
virtual void global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states);
@@ -390,6 +395,7 @@ public:
virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) = 0;
virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) = 0;
+ virtual Size2i window_get_title_size(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) const { return Size2i(); }
virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window = MAIN_WINDOW_ID);
@@ -500,7 +506,8 @@ public:
FILE_DIALOG_MODE_OPEN_FILES,
FILE_DIALOG_MODE_OPEN_DIR,
FILE_DIALOG_MODE_OPEN_ANY,
- FILE_DIALOG_MODE_SAVE_FILE
+ FILE_DIALOG_MODE_SAVE_FILE,
+ FILE_DIALOG_MODE_SAVE_MAX
};
virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback);
diff --git a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp
index 7eb8cbd02f..b9bda9329e 100644
--- a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp
@@ -103,8 +103,9 @@ void RendererCompositorRD::begin_frame(double frame_step) {
}
void RendererCompositorRD::end_frame(bool p_swap_buffers) {
- // TODO: Likely pass a bool to swap buffers to avoid display?
- RD::get_singleton()->swap_buffers();
+ if (p_swap_buffers) {
+ RD::get_singleton()->swap_buffers();
+ }
}
void RendererCompositorRD::initialize() {
diff --git a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
index fd65b739ab..3bd35c53cc 100644
--- a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
@@ -931,11 +931,11 @@ void MaterialStorage::MaterialData::update_textures(const HashMap<StringName, Va
roughness_detect_texture = tex;
roughness_channel = RS::TextureDetectRoughnessChannel(p_texture_uniforms[i].hint - ShaderLanguage::ShaderNode::Uniform::HINT_ROUGHNESS_R);
}
+#endif // TOOLS_ENABLED
if (tex->render_target) {
tex->render_target->was_used = true;
render_target_cache.push_back(tex->render_target);
}
-#endif
}
if (rd_texture.is_null()) {
rd_texture = texture_storage->texture_rd_get_default(TextureStorage::DEFAULT_RD_TEXTURE_WHITE);
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
index 9c7bcbb54f..cb7eb3cd59 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
@@ -1061,7 +1061,7 @@ void MeshStorage::update_mesh_instances() {
RD::get_singleton()->compute_list_end();
}
-void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::Version &v, Mesh::Surface *s, uint32_t p_input_mask, bool p_input_motion_vectors, MeshInstance::Surface *mis) {
+void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::Version &v, Mesh::Surface *s, uint32_t p_input_mask, bool p_input_motion_vectors, MeshInstance::Surface *mis, uint32_t p_current_buffer, uint32_t p_previous_buffer) {
Vector<RD::VertexAttribute> attributes;
Vector<RID> buffers;
@@ -1134,7 +1134,7 @@ void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::V
}
if (mis) {
- buffer = mis->vertex_buffer[mis->current_buffer];
+ buffer = mis->vertex_buffer[p_current_buffer];
} else {
buffer = s->vertex_buffer;
}
@@ -1146,7 +1146,7 @@ void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::V
stride += sizeof(uint16_t) * 2;
if (mis) {
- buffer = mis->vertex_buffer[mis->current_buffer];
+ buffer = mis->vertex_buffer[p_current_buffer];
} else {
buffer = s->vertex_buffer;
}
@@ -1157,7 +1157,7 @@ void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::V
stride += sizeof(uint16_t) * 2;
if (mis) {
- buffer = mis->vertex_buffer[mis->current_buffer];
+ buffer = mis->vertex_buffer[p_current_buffer];
} else {
buffer = s->vertex_buffer;
}
@@ -1241,7 +1241,7 @@ void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::V
if (int(vd.location) != i) {
if (mis && buffer != mesh_default_rd_buffers[i]) {
- buffer = mis->vertex_buffer[mis->previous_buffer];
+ buffer = mis->vertex_buffer[p_previous_buffer];
}
attributes.push_back(vd);
@@ -1267,8 +1267,8 @@ void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::V
}
v.input_mask = p_input_mask;
- v.current_buffer = mis ? mis->current_buffer : 0;
- v.previous_buffer = mis ? mis->previous_buffer : 0;
+ v.current_buffer = p_current_buffer;
+ v.previous_buffer = p_previous_buffer;
v.input_motion_vectors = p_input_motion_vectors;
v.vertex_format = RD::get_singleton()->vertex_format_create(attributes);
v.vertex_array = RD::get_singleton()->vertex_array_create(s->vertex_count, v.vertex_format, buffers);
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
index 86e81d56cc..1e1db9c47d 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
@@ -190,7 +190,7 @@ private:
weight_update_list(this), array_update_list(this) {}
};
- void _mesh_surface_generate_version_for_input_mask(Mesh::Surface::Version &v, Mesh::Surface *s, uint32_t p_input_mask, bool p_input_motion_vectors, MeshInstance::Surface *mis = nullptr);
+ void _mesh_surface_generate_version_for_input_mask(Mesh::Surface::Version &v, Mesh::Surface *s, uint32_t p_input_mask, bool p_input_motion_vectors, MeshInstance::Surface *mis = nullptr, uint32_t p_current_buffer = 0, uint32_t p_previous_buffer = 0);
void _mesh_instance_clear(MeshInstance *mi);
void _mesh_instance_add_surface(MeshInstance *mi, Mesh *mesh, uint32_t p_surface);
@@ -523,7 +523,7 @@ public:
mis->version_count++;
mis->versions = (Mesh::Surface::Version *)memrealloc(mis->versions, sizeof(Mesh::Surface::Version) * mis->version_count);
- _mesh_surface_generate_version_for_input_mask(mis->versions[version], s, p_input_mask, p_input_motion_vectors, mis);
+ _mesh_surface_generate_version_for_input_mask(mis->versions[version], s, p_input_mask, p_input_motion_vectors, mis, current_buffer, previous_buffer);
r_vertex_format = mis->versions[version].vertex_format;
r_vertex_array_rd = mis->versions[version].vertex_array;
diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp
index 86631ae9d9..34d6a93e36 100644
--- a/servers/rendering/renderer_viewport.cpp
+++ b/servers/rendering/renderer_viewport.cpp
@@ -209,6 +209,7 @@ void RendererViewport::_configure_3d_render_buffers(Viewport *p_viewport) {
rb_config.set_fsr_sharpness(p_viewport->fsr_sharpness);
rb_config.set_texture_mipmap_bias(texture_mipmap_bias);
rb_config.set_use_taa(use_taa);
+ rb_config.set_use_debanding(p_viewport->use_debanding);
p_viewport->render_buffers->configure(&rb_config);
}
@@ -616,7 +617,7 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
}
}
-void RendererViewport::draw_viewports() {
+void RendererViewport::draw_viewports(bool p_swap_buffers) {
timestamp_vp_map.clear();
// get our xr interface in case we need it
@@ -732,7 +733,7 @@ void RendererViewport::draw_viewports() {
// commit our eyes
Vector<BlitToScreen> blits = xr_interface->post_draw_viewport(vp->render_target, vp->viewport_to_screen_rect);
if (vp->viewport_to_screen != DisplayServer::INVALID_WINDOW_ID) {
- if (OS::get_singleton()->get_current_rendering_driver_name() == "opengl3" || OS::get_singleton()->get_current_rendering_driver_name() == "opengl3_angle") {
+ if (OS::get_singleton()->get_current_rendering_driver_name().begins_with("opengl3")) {
if (blits.size() > 0) {
RSG::rasterizer->blit_render_targets_to_screen(vp->viewport_to_screen, blits.ptr(), blits.size());
}
@@ -771,7 +772,7 @@ void RendererViewport::draw_viewports() {
blit_to_screen_list[vp->viewport_to_screen] = Vector<BlitToScreen>();
}
- if (OS::get_singleton()->get_current_rendering_driver_name() == "opengl3" || OS::get_singleton()->get_current_rendering_driver_name() == "opengl3_angle") {
+ if (OS::get_singleton()->get_current_rendering_driver_name().begins_with("opengl3")) {
Vector<BlitToScreen> blit_to_screen_vec;
blit_to_screen_vec.push_back(blit);
RSG::rasterizer->blit_render_targets_to_screen(vp->viewport_to_screen, blit_to_screen_vec.ptr(), 1);
@@ -799,11 +800,14 @@ void RendererViewport::draw_viewports() {
total_draw_calls_used = draw_calls_used;
RENDER_TIMESTAMP("< Render Viewports");
- //this needs to be called to make screen swapping more efficient
- RSG::rasterizer->prepare_for_blitting_render_targets();
- for (const KeyValue<int, Vector<BlitToScreen>> &E : blit_to_screen_list) {
- RSG::rasterizer->blit_render_targets_to_screen(E.key, E.value.ptr(), E.value.size());
+ if (p_swap_buffers) {
+ //this needs to be called to make screen swapping more efficient
+ RSG::rasterizer->prepare_for_blitting_render_targets();
+
+ for (const KeyValue<int, Vector<BlitToScreen>> &E : blit_to_screen_list) {
+ RSG::rasterizer->blit_render_targets_to_screen(E.key, E.value.ptr(), E.value.size());
+ }
}
}
diff --git a/servers/rendering/renderer_viewport.h b/servers/rendering/renderer_viewport.h
index 44de6d8804..a0ec9e6318 100644
--- a/servers/rendering/renderer_viewport.h
+++ b/servers/rendering/renderer_viewport.h
@@ -299,7 +299,7 @@ public:
void handle_timestamp(String p_timestamp, uint64_t p_cpu_time, uint64_t p_gpu_time);
void set_default_clear_color(const Color &p_color);
- void draw_viewports();
+ void draw_viewports(bool p_swap_buffers);
bool free(RID p_rid);
diff --git a/servers/rendering/rendering_server_default.cpp b/servers/rendering/rendering_server_default.cpp
index b13d33de9e..2c8265b7d7 100644
--- a/servers/rendering/rendering_server_default.cpp
+++ b/servers/rendering/rendering_server_default.cpp
@@ -88,10 +88,10 @@ void RenderingServerDefault::_draw(bool p_swap_buffers, double frame_step) {
RSG::scene->render_probes();
- RSG::viewport->draw_viewports();
+ RSG::viewport->draw_viewports(p_swap_buffers);
RSG::canvas_render->update();
- if (OS::get_singleton()->get_current_rendering_driver_name() != "opengl3" && OS::get_singleton()->get_current_rendering_driver_name() != "opengl3_angle") {
+ if (!OS::get_singleton()->get_current_rendering_driver_name().begins_with("opengl3")) {
// Already called for gl_compatibility renderer.
RSG::rasterizer->end_frame(p_swap_buffers);
}
diff --git a/servers/rendering/shader_compiler.cpp b/servers/rendering/shader_compiler.cpp
index 671134fb06..71f821205e 100644
--- a/servers/rendering/shader_compiler.cpp
+++ b/servers/rendering/shader_compiler.cpp
@@ -303,8 +303,8 @@ String ShaderCompiler::_get_sampler_name(ShaderLanguage::TextureFilter p_filter,
void ShaderCompiler::_dump_function_deps(const SL::ShaderNode *p_node, const StringName &p_for_func, const HashMap<StringName, String> &p_func_code, String &r_to_add, HashSet<StringName> &added) {
int fidx = -1;
- for (int i = 0; i < p_node->functions.size(); i++) {
- if (p_node->functions[i].name == p_for_func) {
+ for (int i = 0; i < p_node->vfunctions.size(); i++) {
+ if (p_node->vfunctions[i].name == p_for_func) {
fidx = i;
break;
}
@@ -314,7 +314,7 @@ void ShaderCompiler::_dump_function_deps(const SL::ShaderNode *p_node, const Str
Vector<StringName> uses_functions;
- for (const StringName &E : p_node->functions[fidx].uses_function) {
+ for (const StringName &E : p_node->vfunctions[fidx].uses_function) {
uses_functions.push_back(E);
}
uses_functions.sort_custom<StringName::AlphCompare>(); //ensure order is deterministic so the same shader is always produced
@@ -328,9 +328,9 @@ void ShaderCompiler::_dump_function_deps(const SL::ShaderNode *p_node, const Str
SL::FunctionNode *fnode = nullptr;
- for (int i = 0; i < p_node->functions.size(); i++) {
- if (p_node->functions[i].name == uses_functions[k]) {
- fnode = p_node->functions[i].function;
+ for (int i = 0; i < p_node->vfunctions.size(); i++) {
+ if (p_node->vfunctions[i].name == uses_functions[k]) {
+ fnode = p_node->vfunctions[i].function;
break;
}
}
@@ -765,8 +765,8 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
HashMap<StringName, String> function_code;
//code for functions
- for (int i = 0; i < pnode->functions.size(); i++) {
- SL::FunctionNode *fnode = pnode->functions[i].function;
+ for (int i = 0; i < pnode->vfunctions.size(); i++) {
+ SL::FunctionNode *fnode = pnode->vfunctions[i].function;
function = fnode;
current_func_name = fnode->name;
function_code[fnode->name] = _dump_node_code(fnode->body, p_level + 1, r_gen_code, p_actions, p_default_actions, p_assigning);
@@ -777,8 +777,8 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
HashSet<StringName> added_funcs_per_stage[STAGE_MAX];
- for (int i = 0; i < pnode->functions.size(); i++) {
- SL::FunctionNode *fnode = pnode->functions[i].function;
+ for (int i = 0; i < pnode->vfunctions.size(); i++) {
+ SL::FunctionNode *fnode = pnode->vfunctions[i].function;
function = fnode;
@@ -1150,9 +1150,9 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
const bool is_internal_func = internal_functions.has(vnode->name);
if (!is_internal_func) {
- for (int i = 0; i < shader->functions.size(); i++) {
- if (shader->functions[i].name == vnode->name) {
- func = shader->functions[i].function;
+ for (int i = 0; i < shader->vfunctions.size(); i++) {
+ if (shader->vfunctions[i].name == vnode->name) {
+ func = shader->vfunctions[i].function;
break;
}
}
diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp
index 6f3280e6a1..34ffc1e90f 100644
--- a/servers/rendering/shader_language.cpp
+++ b/servers/rendering/shader_language.cpp
@@ -1455,17 +1455,17 @@ bool ShaderLanguage::_find_identifier(const BlockNode *p_block, bool p_allow_rea
return true;
}
- for (int i = 0; i < shader->functions.size(); i++) {
- if (!shader->functions[i].callable) {
+ for (int i = 0; i < shader->vfunctions.size(); i++) {
+ if (!shader->vfunctions[i].callable) {
continue;
}
- if (shader->functions[i].name == p_identifier) {
+ if (shader->vfunctions[i].name == p_identifier) {
if (r_data_type) {
- *r_data_type = shader->functions[i].function->return_type;
+ *r_data_type = shader->vfunctions[i].function->return_type;
}
if (r_array_size) {
- *r_array_size = shader->functions[i].function->return_array_size;
+ *r_array_size = shader->vfunctions[i].function->return_array_size;
}
if (r_type) {
*r_type = IDENTIFIER_FUNCTION;
@@ -3413,18 +3413,18 @@ bool ShaderLanguage::_validate_function_call(BlockNode *p_block, const FunctionI
bool exists = false;
String arg_list = "";
- for (int i = 0; i < shader->functions.size(); i++) {
- if (name != shader->functions[i].name) {
+ for (int i = 0; i < shader->vfunctions.size(); i++) {
+ if (name != shader->vfunctions[i].name) {
continue;
}
exists = true;
- if (!shader->functions[i].callable) {
+ if (!shader->vfunctions[i].callable) {
_set_error(vformat(RTR("Function '%s' can't be called from source code."), String(name)));
return false;
}
- FunctionNode *pfunc = shader->functions[i].function;
+ FunctionNode *pfunc = shader->vfunctions[i].function;
if (arg_list.is_empty()) {
for (int j = 0; j < pfunc->arguments.size(); j++) {
if (j > 0) {
@@ -4662,10 +4662,10 @@ bool ShaderLanguage::_validate_assign(Node *p_node, const FunctionInfo &p_functi
}
bool ShaderLanguage::_propagate_function_call_sampler_uniform_settings(StringName p_name, int p_argument, TextureFilter p_filter, TextureRepeat p_repeat) {
- for (int i = 0; i < shader->functions.size(); i++) {
- if (shader->functions[i].name == p_name) {
- ERR_FAIL_INDEX_V(p_argument, shader->functions[i].function->arguments.size(), false);
- FunctionNode::Argument *arg = &shader->functions[i].function->arguments.write[p_argument];
+ for (int i = 0; i < shader->vfunctions.size(); i++) {
+ if (shader->vfunctions[i].name == p_name) {
+ ERR_FAIL_INDEX_V(p_argument, shader->vfunctions[i].function->arguments.size(), false);
+ FunctionNode::Argument *arg = &shader->vfunctions[i].function->arguments.write[p_argument];
if (arg->tex_builtin_check) {
_set_error(vformat(RTR("Sampler argument %d of function '%s' called more than once using both built-ins and uniform textures, this is not supported (use either one or the other)."), p_argument, String(p_name)));
return false;
@@ -4696,10 +4696,10 @@ bool ShaderLanguage::_propagate_function_call_sampler_uniform_settings(StringNam
}
bool ShaderLanguage::_propagate_function_call_sampler_builtin_reference(StringName p_name, int p_argument, const StringName &p_builtin) {
- for (int i = 0; i < shader->functions.size(); i++) {
- if (shader->functions[i].name == p_name) {
- ERR_FAIL_INDEX_V(p_argument, shader->functions[i].function->arguments.size(), false);
- FunctionNode::Argument *arg = &shader->functions[i].function->arguments.write[p_argument];
+ for (int i = 0; i < shader->vfunctions.size(); i++) {
+ if (shader->vfunctions[i].name == p_name) {
+ ERR_FAIL_INDEX_V(p_argument, shader->vfunctions[i].function->arguments.size(), false);
+ FunctionNode::Argument *arg = &shader->vfunctions[i].function->arguments.write[p_argument];
if (arg->tex_argument_check) {
_set_error(vformat(RTR("Sampler argument %d of function '%s' called more than once using both built-ins and uniform textures, this is not supported (use either one or the other)."), p_argument, String(p_name)));
return false;
@@ -5229,11 +5229,13 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
expr = func;
} else { //a function call
- if (p_block == nullptr) { // Non-constructor function call in global space is forbidden.
- if (is_const_decl) {
+
+ // Non-builtin function call is forbidden for constant declaration.
+ if (is_const_decl) {
+ if (shader->functions.has(identifier)) {
_set_error(RTR("Expected constant expression."));
+ return nullptr;
}
- return nullptr;
}
const StringName &name = identifier;
@@ -5260,12 +5262,12 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
//test if function was parsed first
int function_index = -1;
- for (int i = 0; i < shader->functions.size(); i++) {
- if (shader->functions[i].name == name) {
+ for (int i = 0; i < shader->vfunctions.size(); i++) {
+ if (shader->vfunctions[i].name == name) {
//add to current function as dependency
- for (int j = 0; j < shader->functions.size(); j++) {
- if (shader->functions[j].name == current_function) {
- shader->functions.write[j].uses_function.insert(name);
+ for (int j = 0; j < shader->vfunctions.size(); j++) {
+ if (shader->vfunctions[j].name == current_function) {
+ shader->vfunctions.write[j].uses_function.insert(name);
break;
}
}
@@ -5298,7 +5300,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
//connect texture arguments, so we can cache in the
//argument what type of filter and repeat to use
- FunctionNode *call_function = shader->functions[function_index].function;
+ FunctionNode *call_function = shader->vfunctions[function_index].function;
if (call_function) {
func->return_cache = call_function->get_datatype();
func->struct_name = call_function->get_datatype_name();
@@ -9476,8 +9478,8 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
}
}
- for (int i = 0; i < shader->functions.size(); i++) {
- if (!shader->functions[i].callable && shader->functions[i].name == name) {
+ for (int i = 0; i < shader->vfunctions.size(); i++) {
+ if (!shader->vfunctions[i].callable && shader->vfunctions[i].name == name) {
_set_redefinition_error(String(name));
return ERR_PARSE_ERROR;
}
@@ -9492,7 +9494,8 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
function.function = func_node;
- shader->functions.push_back(function);
+ shader->functions.insert(name, function);
+ shader->vfunctions.push_back(function);
func_node->name = name;
func_node->return_type = type;
@@ -10136,8 +10139,8 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
continue;
}
bool found = false;
- for (int i = 0; i < shader->functions.size(); i++) {
- if (shader->functions[i].name == E.key) {
+ for (int i = 0; i < shader->vfunctions.size(); i++) {
+ if (shader->vfunctions[i].name == E.key) {
found = true;
break;
}
@@ -10257,11 +10260,11 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
}
}
- for (int i = 0; i < shader->functions.size(); i++) {
- if (!shader->functions[i].callable || shader->functions[i].name == skip_function) {
+ for (int i = 0; i < shader->vfunctions.size(); i++) {
+ if (!shader->vfunctions[i].callable || shader->vfunctions[i].name == skip_function) {
continue;
}
- matches.insert(String(shader->functions[i].name), ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION);
+ matches.insert(String(shader->vfunctions[i].name), ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION);
}
int idx = 0;
@@ -10319,26 +10322,26 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
block = block->parent_block;
}
- for (int i = 0; i < shader->functions.size(); i++) {
- if (!shader->functions[i].callable) {
+ for (int i = 0; i < shader->vfunctions.size(); i++) {
+ if (!shader->vfunctions[i].callable) {
continue;
}
- if (shader->functions[i].name == completion_function) {
+ if (shader->vfunctions[i].name == completion_function) {
String calltip;
- calltip += get_datatype_name(shader->functions[i].function->return_type);
+ calltip += get_datatype_name(shader->vfunctions[i].function->return_type);
- if (shader->functions[i].function->return_array_size > 0) {
+ if (shader->vfunctions[i].function->return_array_size > 0) {
calltip += "[";
- calltip += itos(shader->functions[i].function->return_array_size);
+ calltip += itos(shader->vfunctions[i].function->return_array_size);
calltip += "]";
}
calltip += " ";
- calltip += shader->functions[i].name;
+ calltip += shader->vfunctions[i].name;
calltip += "(";
- for (int j = 0; j < shader->functions[i].function->arguments.size(); j++) {
+ for (int j = 0; j < shader->vfunctions[i].function->arguments.size(); j++) {
if (j > 0) {
calltip += ", ";
} else {
@@ -10349,25 +10352,25 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
calltip += char32_t(0xFFFF);
}
- if (shader->functions[i].function->arguments[j].is_const) {
+ if (shader->vfunctions[i].function->arguments[j].is_const) {
calltip += "const ";
}
- if (shader->functions[i].function->arguments[j].qualifier != ArgumentQualifier::ARGUMENT_QUALIFIER_IN) {
- if (shader->functions[i].function->arguments[j].qualifier == ArgumentQualifier::ARGUMENT_QUALIFIER_OUT) {
+ if (shader->vfunctions[i].function->arguments[j].qualifier != ArgumentQualifier::ARGUMENT_QUALIFIER_IN) {
+ if (shader->vfunctions[i].function->arguments[j].qualifier == ArgumentQualifier::ARGUMENT_QUALIFIER_OUT) {
calltip += "out ";
} else { // ArgumentQualifier::ARGUMENT_QUALIFIER_INOUT
calltip += "inout ";
}
}
- calltip += get_datatype_name(shader->functions[i].function->arguments[j].type);
+ calltip += get_datatype_name(shader->vfunctions[i].function->arguments[j].type);
calltip += " ";
- calltip += shader->functions[i].function->arguments[j].name;
+ calltip += shader->vfunctions[i].function->arguments[j].name;
- if (shader->functions[i].function->arguments[j].array_size > 0) {
+ if (shader->vfunctions[i].function->arguments[j].array_size > 0) {
calltip += "[";
- calltip += itos(shader->functions[i].function->arguments[j].array_size);
+ calltip += itos(shader->vfunctions[i].function->arguments[j].array_size);
calltip += "]";
}
@@ -10376,7 +10379,7 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
}
}
- if (shader->functions[i].function->arguments.size()) {
+ if (shader->vfunctions[i].function->arguments.size()) {
calltip += " ";
}
calltip += ")";
diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h
index 90fde72152..c707f6aad7 100644
--- a/servers/rendering/shader_language.h
+++ b/servers/rendering/shader_language.h
@@ -711,9 +711,10 @@ public:
HashMap<StringName, Varying> varyings;
HashMap<StringName, Uniform> uniforms;
HashMap<StringName, Struct> structs;
+ HashMap<StringName, Function> functions;
Vector<StringName> render_modes;
- Vector<Function> functions;
+ Vector<Function> vfunctions;
Vector<Constant> vconstants;
Vector<Struct> vstructs;
diff --git a/servers/text/text_server_extension.cpp b/servers/text/text_server_extension.cpp
index 91a2c9797e..e584146d64 100644
--- a/servers/text/text_server_extension.cpp
+++ b/servers/text/text_server_extension.cpp
@@ -51,6 +51,7 @@ void TextServerExtension::_bind_methods() {
/* Font interface */
GDVIRTUAL_BIND(_create_font);
+ GDVIRTUAL_BIND(_create_font_linked_variation, "font_rid");
GDVIRTUAL_BIND(_font_set_data, "font_rid", "data");
GDVIRTUAL_BIND(_font_set_data_ptr, "font_rid", "data_ptr", "data_size");
@@ -412,6 +413,12 @@ RID TextServerExtension::create_font() {
return ret;
}
+RID TextServerExtension::create_font_linked_variation(const RID &p_font_rid) {
+ RID ret;
+ GDVIRTUAL_CALL(_create_font_linked_variation, p_font_rid, ret);
+ return ret;
+}
+
void TextServerExtension::font_set_data(const RID &p_font_rid, const PackedByteArray &p_data) {
GDVIRTUAL_CALL(_font_set_data, p_font_rid, p_data);
}
diff --git a/servers/text/text_server_extension.h b/servers/text/text_server_extension.h
index 0fa483f304..c40230e723 100644
--- a/servers/text/text_server_extension.h
+++ b/servers/text/text_server_extension.h
@@ -80,6 +80,9 @@ public:
virtual RID create_font() override;
GDVIRTUAL0R(RID, _create_font);
+ virtual RID create_font_linked_variation(const RID &p_font_rid) override;
+ GDVIRTUAL1R(RID, _create_font_linked_variation, RID);
+
virtual void font_set_data(const RID &p_font_rid, const PackedByteArray &p_data) override;
virtual void font_set_data_ptr(const RID &p_font_rid, const uint8_t *p_data_ptr, int64_t p_data_size) override;
GDVIRTUAL2(_font_set_data, RID, const PackedByteArray &);
diff --git a/servers/text_server.cpp b/servers/text_server.cpp
index 3c901f573e..e4eb4c730d 100644
--- a/servers/text_server.cpp
+++ b/servers/text_server.cpp
@@ -206,6 +206,7 @@ void TextServer::_bind_methods() {
/* Font Interface */
ClassDB::bind_method(D_METHOD("create_font"), &TextServer::create_font);
+ ClassDB::bind_method(D_METHOD("create_font_linked_variation", "font_rid"), &TextServer::create_font_linked_variation);
ClassDB::bind_method(D_METHOD("font_set_data", "font_rid", "data"), &TextServer::font_set_data);
diff --git a/servers/text_server.h b/servers/text_server.h
index d0cfd87b64..260b44da8b 100644
--- a/servers/text_server.h
+++ b/servers/text_server.h
@@ -237,6 +237,7 @@ public:
/* Font interface */
virtual RID create_font() = 0;
+ virtual RID create_font_linked_variation(const RID &p_font_rid) = 0;
virtual void font_set_data(const RID &p_font_rid, const PackedByteArray &p_data) = 0;
virtual void font_set_data_ptr(const RID &p_font_rid, const uint8_t *p_data_ptr, int64_t p_data_size) = 0;
diff --git a/tests/scene/test_code_edit.h b/tests/scene/test_code_edit.h
index b44a47bf8a..27907b8bb5 100644
--- a/tests/scene/test_code_edit.h
+++ b/tests/scene/test_code_edit.h
@@ -3725,6 +3725,67 @@ TEST_CASE("[SceneTree][CodeEdit] completion") {
CHECK(code_edit->get_line(0) == "sstest");
}
+ SUBCASE("[CodeEdit] autocomplete currently selected option") {
+ code_edit->set_code_completion_enabled(true);
+ REQUIRE(code_edit->is_code_completion_enabled());
+
+ // Initially select item 0.
+ code_edit->insert_text_at_caret("te");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te1", "te1");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te3", "te3");
+ code_edit->update_code_completion_options();
+ CHECK_MESSAGE(code_edit->get_code_completion_selected_index() == 0, "Initially selected item should be 0.");
+
+ // After adding later options shouldn't update selection.
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te1", "te1");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te3", "te3");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te4", "te4"); // Added te4.
+ code_edit->update_code_completion_options();
+ CHECK_MESSAGE(code_edit->get_code_completion_selected_index() == 0, "Adding later options shouldn't update selection.");
+
+ code_edit->set_code_completion_selected_index(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te1", "te1");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te3", "te3");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te4", "te4");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te5", "te5"); // Added te5.
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te6", "te6"); // Added te6.
+ code_edit->update_code_completion_options();
+ CHECK_MESSAGE(code_edit->get_code_completion_selected_index() == 2, "Adding later options shouldn't update selection.");
+
+ // Removing elements after selected element shouldn't update selection.
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te1", "te1");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te3", "te3");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te4", "te4");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te5", "te5"); // Removed te6.
+ code_edit->update_code_completion_options();
+ CHECK_MESSAGE(code_edit->get_code_completion_selected_index() == 2, "Removing elements after selected element shouldn't update selection.");
+
+ // Changing elements after selected element shouldn't update selection.
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te1", "te1");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te3", "te3");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te4", "te4");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te6", "te6"); // Changed te5->te6.
+ code_edit->update_code_completion_options();
+ CHECK_MESSAGE(code_edit->get_code_completion_selected_index() == 2, "Changing elements after selected element shouldn't update selection.");
+
+ // Changing elements before selected element should reset selection.
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te2", "te2"); // Changed te1->te2.
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te3", "te3");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te4", "te4");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te6", "te6");
+ code_edit->update_code_completion_options();
+ CHECK_MESSAGE(code_edit->get_code_completion_selected_index() == 0, "Changing elements before selected element should reset selection.");
+
+ // Removing elements before selected element should reset selection.
+ code_edit->set_code_completion_selected_index(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te3", "te3"); // Removed te2.
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te4", "te4");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te6", "te6");
+ code_edit->update_code_completion_options();
+ CHECK_MESSAGE(code_edit->get_code_completion_selected_index() == 0, "Removing elements before selected element should reset selection.");
+ }
+
memdelete(code_edit);
}