summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/android_builds.yml1
-rw-r--r--SConstruct7
-rw-r--r--core/core_bind.compat.inc16
-rw-r--r--core/core_bind.cpp22
-rw-r--r--core/core_bind.h12
-rw-r--r--core/core_constants.cpp1
-rw-r--r--core/doc_data.cpp6
-rw-r--r--core/extension/extension_api_dump.cpp5
-rw-r--r--core/extension/gdextension.cpp10
-rw-r--r--core/extension/gdextension.h1
-rw-r--r--core/extension/gdextension_compat_hashes.cpp73
-rw-r--r--core/extension/gdextension_interface.cpp10
-rw-r--r--core/extension/gdextension_interface.h21
-rw-r--r--core/io/resource_format_binary.cpp33
-rw-r--r--core/io/resource_loader.cpp14
-rw-r--r--core/math/expression.cpp10
-rw-r--r--core/object/class_db.cpp19
-rw-r--r--core/object/class_db.h4
-rw-r--r--core/object/object.cpp1
-rw-r--r--core/object/object.h5
-rw-r--r--core/object/script_language.cpp17
-rw-r--r--core/object/script_language.h2
-rw-r--r--core/object/worker_thread_pool.cpp20
-rw-r--r--core/object/worker_thread_pool.h2
-rw-r--r--core/os/os.h2
-rw-r--r--core/register_core_types.cpp8
-rw-r--r--core/string/string_name.cpp5
-rw-r--r--core/string/string_name.h3
-rw-r--r--core/string/translation.cpp1
-rw-r--r--core/string/translation.h4
-rw-r--r--core/string/translation_server.cpp1
-rw-r--r--core/string/translation_server.h4
-rw-r--r--core/variant/array.cpp10
-rw-r--r--core/variant/array.h2
-rw-r--r--core/variant/dictionary.cpp276
-rw-r--r--core/variant/dictionary.h16
-rw-r--r--core/variant/type_info.h8
-rw-r--r--core/variant/typed_dictionary.h342
-rw-r--r--core/variant/variant_call.cpp13
-rw-r--r--core/variant/variant_construct.cpp1
-rw-r--r--core/variant/variant_construct.h106
-rw-r--r--core/variant/variant_parser.cpp244
-rw-r--r--core/variant/variant_setget.cpp91
-rw-r--r--doc/classes/@GlobalScope.xml5
-rw-r--r--doc/classes/CompositorEffect.xml11
-rw-r--r--doc/classes/Dictionary.xml95
-rw-r--r--doc/classes/EditorContextMenuPlugin.xml42
-rw-r--r--doc/classes/EditorPlugin.xml23
-rw-r--r--doc/classes/LineEdit.xml21
-rw-r--r--doc/classes/MenuBar.xml8
-rw-r--r--doc/classes/NavigationServer2D.xml12
-rw-r--r--doc/classes/NavigationServer3D.xml36
-rw-r--r--doc/classes/Node2D.xml27
-rw-r--r--doc/classes/OS.xml2
-rw-r--r--doc/classes/ProjectSettings.xml3
-rw-r--r--doc/classes/RDShaderSource.xml1
-rw-r--r--doc/classes/ResourceImporterDynamicFont.xml2
-rw-r--r--doc/classes/ResourceImporterScene.xml3
-rw-r--r--doc/classes/ScriptEditor.xml8
-rw-r--r--doc/classes/Theme.xml2
-rw-r--r--doc/classes/TreeItem.xml10
-rw-r--r--doc/classes/Viewport.xml6
-rwxr-xr-xdoc/tools/make_rst.py35
-rw-r--r--drivers/gles3/shaders/scene.glsl18
-rw-r--r--drivers/gles3/storage/light_storage.cpp5
-rw-r--r--drivers/gles3/storage/material_storage.cpp4
-rw-r--r--drivers/gles3/storage/mesh_storage.cpp4
-rw-r--r--drivers/gles3/storage/mesh_storage.h2
-rw-r--r--drivers/gles3/storage/texture_storage.cpp4
-rw-r--r--drivers/metal/metal_objects.mm6
-rw-r--r--drivers/unix/file_access_unix_pipe.cpp16
-rw-r--r--drivers/unix/file_access_unix_pipe.h2
-rw-r--r--drivers/unix/os_unix.cpp6
-rw-r--r--drivers/unix/os_unix.h2
-rw-r--r--drivers/windows/file_access_windows.cpp50
-rw-r--r--drivers/windows/file_access_windows_pipe.cpp12
-rw-r--r--drivers/windows/file_access_windows_pipe.h2
-rw-r--r--editor/animation_bezier_editor.cpp54
-rw-r--r--editor/animation_track_editor.cpp45
-rw-r--r--editor/connections_dialog.cpp28
-rw-r--r--editor/doc_tools.cpp2
-rw-r--r--editor/editor_data.cpp132
-rw-r--r--editor/editor_data.h29
-rw-r--r--editor/editor_dock_manager.cpp4
-rw-r--r--editor/editor_help.cpp27
-rw-r--r--editor/editor_help_search.cpp3
-rw-r--r--editor/editor_inspector.cpp7
-rw-r--r--editor/editor_interface.cpp5
-rw-r--r--editor/editor_main_screen.cpp291
-rw-r--r--editor/editor_main_screen.h (renamed from core/string/translation.compat.inc)70
-rw-r--r--editor/editor_node.cpp483
-rw-r--r--editor/editor_node.h65
-rw-r--r--editor/editor_paths.cpp20
-rw-r--r--editor/editor_properties.cpp9
-rw-r--r--editor/editor_properties_array_dict.cpp127
-rw-r--r--editor/editor_properties_array_dict.h12
-rw-r--r--editor/editor_settings.cpp11
-rw-r--r--editor/export/editor_export_platform.cpp2
-rw-r--r--editor/filesystem_dock.cpp38
-rw-r--r--editor/gui/editor_spin_slider.cpp22
-rw-r--r--editor/import/3d/resource_importer_scene.cpp17
-rw-r--r--editor/import/3d/resource_importer_scene.h2
-rw-r--r--editor/import/3d/scene_import_settings.cpp66
-rw-r--r--editor/import/3d/scene_import_settings.h4
-rw-r--r--editor/import/resource_importer_wav.cpp4
-rw-r--r--editor/inspector_dock.cpp3
-rw-r--r--editor/plugins/asset_library_editor_plugin.cpp3
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp7
-rw-r--r--editor/plugins/cpu_particles_3d_editor_plugin.cpp2
-rw-r--r--editor/plugins/editor_context_menu_plugin.cpp134
-rw-r--r--editor/plugins/editor_context_menu_plugin.h55
-rw-r--r--editor/plugins/editor_plugin.cpp18
-rw-r--r--editor/plugins/editor_plugin.h14
-rw-r--r--editor/plugins/editor_preview_plugins.cpp42
-rw-r--r--editor/plugins/gpu_particles_3d_editor_plugin.cpp2
-rw-r--r--editor/plugins/mesh_instance_3d_editor_plugin.cpp2
-rw-r--r--editor/plugins/mesh_library_editor_plugin.cpp2
-rw-r--r--editor/plugins/multimesh_editor_plugin.cpp2
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp13
-rw-r--r--editor/plugins/node_3d_editor_plugin.h2
-rw-r--r--editor/plugins/path_2d_editor_plugin.cpp61
-rw-r--r--editor/plugins/path_2d_editor_plugin.h4
-rw-r--r--editor/plugins/path_3d_editor_plugin.cpp118
-rw-r--r--editor/plugins/path_3d_editor_plugin.h13
-rw-r--r--editor/plugins/script_editor_plugin.cpp25
-rw-r--r--editor/plugins/skeleton_2d_editor_plugin.cpp2
-rw-r--r--editor/plugins/skeleton_3d_editor_plugin.cpp61
-rw-r--r--editor/plugins/skeleton_3d_editor_plugin.h11
-rw-r--r--editor/plugins/sprite_2d_editor_plugin.cpp2
-rw-r--r--editor/plugins/tiles/tile_map_layer_editor.cpp6
-rw-r--r--editor/project_manager.cpp43
-rw-r--r--editor/project_manager.h6
-rw-r--r--editor/project_manager/project_dialog.cpp12
-rw-r--r--editor/scene_tree_dock.cpp38
-rw-r--r--editor/scene_tree_dock.h1
-rw-r--r--editor/themes/editor_theme_manager.cpp19
-rw-r--r--main/main.cpp21
-rw-r--r--methods.py100
-rw-r--r--misc/extension_api_validation/4.3-stable.expected7
-rw-r--r--modules/basis_universal/image_compress_basisu.cpp89
-rw-r--r--modules/basis_universal/image_compress_basisu.h1
-rw-r--r--modules/csg/csg_shape.cpp7
-rw-r--r--modules/csg/editor/csg_gizmos.cpp2
-rw-r--r--modules/fbx/fbx_document.cpp8
-rw-r--r--modules/gdscript/editor/gdscript_docgen.cpp91
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp308
-rw-r--r--modules/gdscript/gdscript_analyzer.h2
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp84
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h1
-rw-r--r--modules/gdscript/gdscript_codegen.h1
-rw-r--r--modules/gdscript/gdscript_compiler.cpp24
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp107
-rw-r--r--modules/gdscript/gdscript_editor.cpp8
-rw-r--r--modules/gdscript/gdscript_function.h43
-rw-r--r--modules/gdscript/gdscript_parser.cpp218
-rw-r--r--modules/gdscript/gdscript_parser.h2
-rw-r--r--modules/gdscript/gdscript_tokenizer_buffer.cpp1
-rw-r--r--modules/gdscript/gdscript_vm.cpp194
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd20
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out11
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd214
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out2
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.cfg9
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out3
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.gd7
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out5
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd7
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd7
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd9
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/member_info.gd24
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/member_info.out24
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out3
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd7
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out2
-rw-r--r--modules/gdscript/tests/scripts/utils.notest.gd7
-rw-r--r--modules/gltf/gltf_document.cpp8
-rw-r--r--modules/gridmap/editor/grid_map_editor_plugin.cpp3
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.cpp37
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.h1
-rw-r--r--modules/lightmapper_rd/lm_compute.glsl27
-rw-r--r--modules/minimp3/audio_stream_mp3.cpp3
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs3
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs8
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs69
-rw-r--r--modules/mono/editor/bindings_generator.cpp21
-rw-r--r--modules/mono/editor/editor_internal_calls.cpp3
-rw-r--r--modules/mono/godotsharp_dirs.cpp10
-rw-r--r--modules/navigation/2d/godot_navigation_server_2d.cpp5
-rw-r--r--modules/navigation/2d/godot_navigation_server_2d.h1
-rw-r--r--modules/navigation/3d/godot_navigation_server_3d.cpp21
-rw-r--r--modules/navigation/3d/godot_navigation_server_3d.h3
-rw-r--r--modules/navigation/editor/navigation_mesh_editor_plugin.cpp2
-rw-r--r--modules/navigation/nav_region.cpp17
-rw-r--r--modules/navigation/nav_region.h4
-rw-r--r--modules/openxr/doc_classes/OpenXRCompositionLayer.xml15
-rw-r--r--modules/openxr/extensions/openxr_composition_layer_extension.cpp257
-rw-r--r--modules/openxr/extensions/openxr_composition_layer_extension.h61
-rw-r--r--modules/openxr/openxr_api.cpp31
-rw-r--r--modules/openxr/scene/openxr_composition_layer.cpp179
-rw-r--r--modules/openxr/scene/openxr_composition_layer.h21
-rw-r--r--modules/openxr/scene/openxr_composition_layer_cylinder.cpp16
-rw-r--r--modules/openxr/scene/openxr_composition_layer_cylinder.h13
-rw-r--r--modules/openxr/scene/openxr_composition_layer_equirect.cpp17
-rw-r--r--modules/openxr/scene/openxr_composition_layer_equirect.h14
-rw-r--r--modules/openxr/scene/openxr_composition_layer_quad.cpp14
-rw-r--r--modules/openxr/scene/openxr_composition_layer_quad.h11
-rw-r--r--modules/vorbis/audio_stream_ogg_vorbis.cpp3
-rw-r--r--platform/android/export/export_plugin.cpp5
-rw-r--r--platform/android/java/app/build.gradle1
-rw-r--r--platform/android/java/app/settings.gradle1
-rw-r--r--platform/android/java/build.gradle45
-rw-r--r--platform/android/java/editor/build.gradle43
-rw-r--r--platform/android/java/editor/src/google/java/org/godotengine/editor/GodotEditor.kt (renamed from core/object/object.compat.inc)17
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt (renamed from platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt)108
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt10
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt57
-rw-r--r--platform/android/java/editor/src/meta/AndroidManifest.xml99
-rw-r--r--platform/android/java/editor/src/meta/assets/vr_splash.pngbin0 -> 14766 bytes
-rw-r--r--platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt (renamed from scene/gui/control.compat.inc)80
-rw-r--r--platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt78
-rw-r--r--platform/android/java/lib/build.gradle4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.kt4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java8
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotHost.java9
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java7
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java7
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt (renamed from core/string/translation_server.compat.inc)26
-rw-r--r--platform/android/java/nativeSrcsConfigs/CMakeLists.txt4
-rw-r--r--platform/android/java/settings.gradle1
-rw-r--r--platform/android/java_godot_lib_jni.cpp11
-rw-r--r--platform/android/plugin/godot_plugin_jni.cpp3
-rw-r--r--platform/linuxbsd/wayland/wayland_thread.cpp11
-rw-r--r--platform/macos/export/export_plugin.cpp19
-rw-r--r--platform/web/os_web.cpp2
-rw-r--r--platform/web/os_web.h2
-rw-r--r--platform/windows/detect.py2
-rw-r--r--platform/windows/os_windows.cpp8
-rw-r--r--platform/windows/os_windows.h2
-rw-r--r--scene/2d/canvas_modulate.cpp2
-rw-r--r--scene/2d/light_2d.cpp2
-rw-r--r--scene/2d/light_occluder_2d.cpp2
-rw-r--r--scene/2d/navigation_link_2d.cpp2
-rw-r--r--scene/2d/parallax_layer.cpp2
-rw-r--r--scene/2d/path_2d.cpp2
-rw-r--r--scene/2d/physics/collision_object_2d.cpp2
-rw-r--r--scene/2d/physics/collision_polygon_2d.cpp2
-rw-r--r--scene/2d/physics/collision_shape_2d.cpp2
-rw-r--r--scene/2d/physics/physical_bone_2d.cpp2
-rw-r--r--scene/2d/remote_transform_2d.cpp2
-rw-r--r--scene/2d/skeleton_2d.cpp2
-rw-r--r--scene/2d/tile_map.cpp2
-rw-r--r--scene/3d/decal.cpp2
-rw-r--r--scene/3d/fog_volume.cpp2
-rw-r--r--scene/3d/gpu_particles_collision_3d.cpp2
-rw-r--r--scene/3d/lightmap_gi.cpp21
-rw-r--r--scene/3d/lightmap_gi.h5
-rw-r--r--scene/3d/navigation_link_3d.cpp2
-rw-r--r--scene/3d/navigation_region_3d.cpp2
-rw-r--r--scene/3d/occluder_instance_3d.cpp2
-rw-r--r--scene/3d/path_3d.cpp54
-rw-r--r--scene/3d/path_3d.h3
-rw-r--r--scene/3d/physics/collision_object_3d.cpp2
-rw-r--r--scene/3d/physics/collision_polygon_3d.cpp2
-rw-r--r--scene/3d/physics/collision_shape_3d.cpp2
-rw-r--r--scene/3d/physics/vehicle_body_3d.cpp2
-rw-r--r--scene/3d/remote_transform_3d.cpp2
-rw-r--r--scene/3d/soft_body_3d.cpp2
-rw-r--r--scene/3d/visual_instance_3d.cpp2
-rw-r--r--scene/3d/voxel_gi.cpp2
-rw-r--r--scene/3d/xr_nodes.cpp6
-rw-r--r--scene/animation/animation_mixer.cpp3
-rw-r--r--scene/animation/animation_player.compat.inc10
-rw-r--r--scene/animation/animation_tree.cpp2
-rw-r--r--scene/gui/control.cpp28
-rw-r--r--scene/gui/control.h4
-rw-r--r--scene/gui/file_dialog.cpp3
-rw-r--r--scene/gui/line_edit.cpp539
-rw-r--r--scene/gui/line_edit.h6
-rw-r--r--scene/gui/menu_bar.cpp17
-rw-r--r--scene/gui/range.cpp2
-rw-r--r--scene/gui/rich_text_label.cpp629
-rw-r--r--scene/gui/rich_text_label.h4
-rw-r--r--scene/gui/scroll_container.cpp28
-rw-r--r--scene/gui/spin_box.cpp51
-rw-r--r--scene/gui/spin_box.h3
-rw-r--r--scene/gui/subviewport_container.cpp2
-rw-r--r--scene/gui/tree.cpp18
-rw-r--r--scene/main/viewport.cpp1
-rw-r--r--scene/main/window.compat.inc48
-rw-r--r--scene/main/window.cpp1
-rw-r--r--scene/main/window.h4
-rw-r--r--scene/resources/3d/importer_mesh.cpp16
-rw-r--r--scene/resources/3d/importer_mesh.h2
-rw-r--r--scene/resources/audio_stream_polyphonic.cpp8
-rw-r--r--scene/resources/audio_stream_wav.cpp73
-rw-r--r--scene/resources/audio_stream_wav.h4
-rw-r--r--scene/resources/packed_scene.cpp136
-rw-r--r--scene/resources/packed_scene.h1
-rw-r--r--scene/resources/resource_format_text.cpp64
-rw-r--r--servers/audio/audio_stream.cpp10
-rw-r--r--servers/audio/audio_stream.h1
-rw-r--r--servers/audio_server.cpp50
-rw-r--r--servers/audio_server.h2
-rw-r--r--servers/navigation_server_2d.cpp1
-rw-r--r--servers/navigation_server_2d.h1
-rw-r--r--servers/navigation_server_2d_dummy.h1
-rw-r--r--servers/navigation_server_3d.cpp3
-rw-r--r--servers/navigation_server_3d.h3
-rw-r--r--servers/navigation_server_3d_dummy.h3
-rw-r--r--servers/rendering/dummy/storage/mesh_storage.h2
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp1864
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.h201
-rw-r--r--servers/rendering/renderer_rd/shaders/canvas.glsl92
-rw-r--r--servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl27
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl19
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl18
-rw-r--r--servers/rendering/renderer_rd/storage_rd/material_storage.cpp4
-rw-r--r--servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp4
-rw-r--r--servers/rendering/renderer_rd/storage_rd/mesh_storage.h2
-rw-r--r--servers/rendering/renderer_viewport.cpp7
-rw-r--r--servers/rendering/rendering_device.cpp20
-rw-r--r--servers/rendering/rendering_server_default.cpp10
-rw-r--r--servers/rendering/shader_compiler.cpp30
-rw-r--r--servers/rendering/shader_compiler.h2
-rw-r--r--servers/rendering/shader_language.cpp722
-rw-r--r--servers/rendering/shader_language.h47
-rw-r--r--servers/rendering/storage/mesh_storage.cpp2
-rw-r--r--servers/rendering/storage/mesh_storage.h4
-rw-r--r--servers/rendering_server.cpp1
-rw-r--r--servers/xr/xr_interface.h2
-rw-r--r--tests/core/math/test_expression.h53
-rw-r--r--tests/core/variant/test_dictionary.h39
-rw-r--r--tests/scene/test_parallax_2d.h131
-rw-r--r--tests/scene/test_path_follow_3d.h31
-rw-r--r--tests/test_main.cpp1
-rw-r--r--thirdparty/README.md2
-rw-r--r--thirdparty/thorvg/inc/config.h2
-rw-r--r--thirdparty/thorvg/patches/pr2702-sw_engine-handle-small-cubics.patch96
-rw-r--r--thirdparty/thorvg/patches/pr2716-text-drawing-reliability.patch25
-rw-r--r--thirdparty/thorvg/patches/revert-tvgLines-bezier-precision-change.patch13
-rw-r--r--thirdparty/thorvg/src/common/tvgLines.cpp2
-rw-r--r--thirdparty/thorvg/src/common/tvgMath.cpp8
-rw-r--r--thirdparty/thorvg/src/common/tvgMath.h9
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp10
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h3
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp19
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp2
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp16
-rw-r--r--thirdparty/thorvg/src/renderer/tvgText.h1
-rwxr-xr-xthirdparty/thorvg/update-thorvg.sh2
376 files changed, 9393 insertions, 3668 deletions
diff --git a/.github/workflows/android_builds.yml b/.github/workflows/android_builds.yml
index ee75d53282..69ab830829 100644
--- a/.github/workflows/android_builds.yml
+++ b/.github/workflows/android_builds.yml
@@ -85,6 +85,7 @@ jobs:
run: |
cd platform/android/java
./gradlew generateGodotEditor
+ ./gradlew generateGodotMetaEditor
cd ../../..
ls -l bin/android_editor_builds/
diff --git a/SConstruct b/SConstruct
index 5c1ccd2449..b1819a1dab 100644
--- a/SConstruct
+++ b/SConstruct
@@ -1053,7 +1053,7 @@ if env["ninja"]:
SetOption("experimental", "ninja")
env["NINJA_FILE_NAME"] = env["ninja_file"]
env["NINJA_DISABLE_AUTO_RUN"] = not env["ninja_auto_run"]
- env.Tool("ninja")
+ env.Tool("ninja", "build.ninja")
# Threads
if env["threads"]:
@@ -1092,7 +1092,6 @@ if "check_c_headers" in env:
env.AppendUnique(CPPDEFINES=[headers[header]])
-# FIXME: This method mixes both cosmetic progress stuff and cache handling...
methods.show_progress(env)
# TODO: replace this with `env.Dump(format="json")`
# once we start requiring SCons 4.0 as min version.
@@ -1116,7 +1115,7 @@ atexit.register(print_elapsed_time)
def purge_flaky_files():
- paths_to_keep = ["ninja.build"]
+ paths_to_keep = ["build.ninja"]
for build_failure in GetBuildFailures():
path = build_failure.node.path
if os.path.isfile(path) and path not in paths_to_keep:
@@ -1124,3 +1123,5 @@ def purge_flaky_files():
atexit.register(purge_flaky_files)
+
+methods.clean_cache(env)
diff --git a/core/core_bind.compat.inc b/core/core_bind.compat.inc
index 83b7b33e38..3e8ac3c5de 100644
--- a/core/core_bind.compat.inc
+++ b/core/core_bind.compat.inc
@@ -32,6 +32,8 @@
namespace core_bind {
+// Semaphore
+
void Semaphore::_post_bind_compat_93605() {
post(1);
}
@@ -40,6 +42,16 @@ void Semaphore::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("post"), &Semaphore::_post_bind_compat_93605);
}
-}; // namespace core_bind
+// OS
+
+Dictionary OS::_execute_with_pipe_bind_compat_94434(const String &p_path, const Vector<String> &p_arguments) {
+ return execute_with_pipe(p_path, p_arguments, true);
+}
+
+void OS::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("execute_with_pipe", "path", "arguments"), &OS::_execute_with_pipe_bind_compat_94434);
+}
+
+} // namespace core_bind
-#endif
+#endif // DISABLE_DEPRECATED
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index 4172793f9d..b27981d56b 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -57,8 +57,11 @@ Error ResourceLoader::load_threaded_request(const String &p_path, const String &
ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const String &p_path, Array r_progress) {
float progress = 0;
::ResourceLoader::ThreadLoadStatus tls = ::ResourceLoader::load_threaded_get_status(p_path, &progress);
- r_progress.resize(1);
- r_progress[0] = progress;
+ // Default array should never be modified, it causes the hash of the method to change.
+ if (!ClassDB::is_default_array_arg(r_progress)) {
+ r_progress.resize(1);
+ r_progress[0] = progress;
+ }
return (ThreadLoadStatus)tls;
}
@@ -131,7 +134,7 @@ ResourceUID::ID ResourceLoader::get_resource_uid(const String &p_path) {
void ResourceLoader::_bind_methods() {
ClassDB::bind_method(D_METHOD("load_threaded_request", "path", "type_hint", "use_sub_threads", "cache_mode"), &ResourceLoader::load_threaded_request, DEFVAL(""), DEFVAL(false), DEFVAL(CACHE_MODE_REUSE));
- ClassDB::bind_method(D_METHOD("load_threaded_get_status", "path", "progress"), &ResourceLoader::load_threaded_get_status, DEFVAL(Array()));
+ ClassDB::bind_method(D_METHOD("load_threaded_get_status", "path", "progress"), &ResourceLoader::load_threaded_get_status, DEFVAL_ARRAY);
ClassDB::bind_method(D_METHOD("load_threaded_get", "path"), &ResourceLoader::load_threaded_get);
ClassDB::bind_method(D_METHOD("load", "path", "type_hint", "cache_mode"), &ResourceLoader::load, DEFVAL(""), DEFVAL(CACHE_MODE_REUSE));
@@ -307,19 +310,22 @@ int OS::execute(const String &p_path, const Vector<String> &p_arguments, Array r
String pipe;
int exitcode = 0;
Error err = ::OS::get_singleton()->execute(p_path, args, &pipe, &exitcode, p_read_stderr, nullptr, p_open_console);
- r_output.push_back(pipe);
+ // Default array should never be modified, it causes the hash of the method to change.
+ if (!ClassDB::is_default_array_arg(r_output)) {
+ r_output.push_back(pipe);
+ }
if (err != OK) {
return -1;
}
return exitcode;
}
-Dictionary OS::execute_with_pipe(const String &p_path, const Vector<String> &p_arguments) {
+Dictionary OS::execute_with_pipe(const String &p_path, const Vector<String> &p_arguments, bool p_blocking) {
List<String> args;
for (const String &arg : p_arguments) {
args.push_back(arg);
}
- return ::OS::get_singleton()->execute_with_pipe(p_path, args);
+ return ::OS::get_singleton()->execute_with_pipe(p_path, args, p_blocking);
}
int OS::create_instance(const Vector<String> &p_arguments) {
@@ -618,8 +624,8 @@ void OS::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_system_font_path_for_text", "font_name", "text", "locale", "script", "weight", "stretch", "italic"), &OS::get_system_font_path_for_text, DEFVAL(String()), DEFVAL(String()), DEFVAL(400), DEFVAL(100), DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_executable_path"), &OS::get_executable_path);
ClassDB::bind_method(D_METHOD("read_string_from_stdin"), &OS::read_string_from_stdin);
- ClassDB::bind_method(D_METHOD("execute", "path", "arguments", "output", "read_stderr", "open_console"), &OS::execute, DEFVAL(Array()), DEFVAL(false), DEFVAL(false));
- ClassDB::bind_method(D_METHOD("execute_with_pipe", "path", "arguments"), &OS::execute_with_pipe);
+ ClassDB::bind_method(D_METHOD("execute", "path", "arguments", "output", "read_stderr", "open_console"), &OS::execute, DEFVAL_ARRAY, DEFVAL(false), DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("execute_with_pipe", "path", "arguments", "blocking"), &OS::execute_with_pipe, DEFVAL(true));
ClassDB::bind_method(D_METHOD("create_process", "path", "arguments", "open_console"), &OS::create_process, DEFVAL(false));
ClassDB::bind_method(D_METHOD("create_instance", "arguments"), &OS::create_instance);
ClassDB::bind_method(D_METHOD("kill", "pid"), &OS::kill);
diff --git a/core/core_bind.h b/core/core_bind.h
index 122963e634..7e2686c848 100644
--- a/core/core_bind.h
+++ b/core/core_bind.h
@@ -73,7 +73,7 @@ public:
static ResourceLoader *get_singleton() { return singleton; }
Error load_threaded_request(const String &p_path, const String &p_type_hint = "", bool p_use_sub_threads = false, CacheMode p_cache_mode = CACHE_MODE_REUSE);
- ThreadLoadStatus load_threaded_get_status(const String &p_path, Array r_progress = Array());
+ ThreadLoadStatus load_threaded_get_status(const String &p_path, Array r_progress = ClassDB::default_array_arg);
Ref<Resource> load_threaded_get(const String &p_path);
Ref<Resource> load(const String &p_path, const String &p_type_hint = "", CacheMode p_cache_mode = CACHE_MODE_REUSE);
@@ -128,6 +128,12 @@ protected:
static void _bind_methods();
static OS *singleton;
+#ifndef DISABLE_DEPRECATED
+ Dictionary _execute_with_pipe_bind_compat_94434(const String &p_path, const Vector<String> &p_arguments);
+
+ static void _bind_compatibility_methods();
+#endif
+
public:
enum RenderingDriver {
RENDERING_DRIVER_VULKAN,
@@ -160,8 +166,8 @@ public:
Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const;
String get_executable_path() const;
String read_string_from_stdin();
- int execute(const String &p_path, const Vector<String> &p_arguments, Array r_output = Array(), bool p_read_stderr = false, bool p_open_console = false);
- Dictionary execute_with_pipe(const String &p_path, const Vector<String> &p_arguments);
+ int execute(const String &p_path, const Vector<String> &p_arguments, Array r_output = ClassDB::default_array_arg, bool p_read_stderr = false, bool p_open_console = false);
+ Dictionary execute_with_pipe(const String &p_path, const Vector<String> &p_arguments, bool p_blocking = true);
int create_process(const String &p_path, const Vector<String> &p_arguments, bool p_open_console = false);
int create_instance(const Vector<String> &p_arguments);
Error kill(int p_pid);
diff --git a/core/core_constants.cpp b/core/core_constants.cpp
index 5322e39ec0..68af5abf66 100644
--- a/core/core_constants.cpp
+++ b/core/core_constants.cpp
@@ -671,6 +671,7 @@ void register_global_constants() {
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_INT_IS_OBJECTID);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_INT_IS_POINTER);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_ARRAY_TYPE);
+ BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_DICTIONARY_TYPE);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LOCALE_ID);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LOCALIZABLE_STRING);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_NODE_TYPE);
diff --git a/core/doc_data.cpp b/core/doc_data.cpp
index 672a36c35c..f40e878d52 100644
--- a/core/doc_data.cpp
+++ b/core/doc_data.cpp
@@ -33,6 +33,8 @@
String DocData::get_default_value_string(const Variant &p_value) {
if (p_value.get_type() == Variant::ARRAY) {
return Variant(Array(p_value, 0, StringName(), Variant())).get_construct_string().replace("\n", " ");
+ } else if (p_value.get_type() == Variant::DICTIONARY) {
+ return Variant(Dictionary(p_value, 0, StringName(), Variant(), 0, StringName(), Variant())).get_construct_string().replace("\n", " ");
} else {
return p_value.get_construct_string().replace("\n", " ");
}
@@ -57,6 +59,8 @@ void DocData::return_doc_from_retinfo(DocData::MethodDoc &p_method, const Proper
p_method.return_type = p_retinfo.class_name;
} else if (p_retinfo.type == Variant::ARRAY && p_retinfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
p_method.return_type = p_retinfo.hint_string + "[]";
+ } else if (p_retinfo.type == Variant::DICTIONARY && p_retinfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
+ p_method.return_type = "Dictionary[" + p_retinfo.hint_string.replace(";", ", ") + "]";
} else if (p_retinfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
p_method.return_type = p_retinfo.hint_string;
} else if (p_retinfo.type == Variant::NIL && p_retinfo.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
@@ -89,6 +93,8 @@ void DocData::argument_doc_from_arginfo(DocData::ArgumentDoc &p_argument, const
p_argument.type = p_arginfo.class_name;
} else if (p_arginfo.type == Variant::ARRAY && p_arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
p_argument.type = p_arginfo.hint_string + "[]";
+ } else if (p_arginfo.type == Variant::DICTIONARY && p_arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
+ p_argument.type = "Dictionary[" + p_arginfo.hint_string.replace(";", ", ") + "]";
} else if (p_arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
p_argument.type = p_arginfo.hint_string;
} else if (p_arginfo.type == Variant::NIL) {
diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp
index 848b6f3886..4042d6b80d 100644
--- a/core/extension/extension_api_dump.cpp
+++ b/core/extension/extension_api_dump.cpp
@@ -60,6 +60,9 @@ static String get_property_info_type_name(const PropertyInfo &p_info) {
if (p_info.type == Variant::ARRAY && (p_info.hint == PROPERTY_HINT_ARRAY_TYPE)) {
return String("typedarray::") + p_info.hint_string;
}
+ if (p_info.type == Variant::DICTIONARY && (p_info.hint == PROPERTY_HINT_DICTIONARY_TYPE)) {
+ return String("typeddictionary::") + p_info.hint_string;
+ }
if (p_info.type == Variant::INT && (p_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM))) {
return String("enum::") + String(p_info.class_name);
}
@@ -85,7 +88,7 @@ static String get_property_info_type_name(const PropertyInfo &p_info) {
}
static String get_type_meta_name(const GodotTypeInfo::Metadata metadata) {
- static const char *argmeta[11] = { "none", "int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "float", "double" };
+ static const char *argmeta[13] = { "none", "int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "float", "double", "char16", "char32" };
return argmeta[metadata];
}
diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp
index d4b50facb2..7cba5cb161 100644
--- a/core/extension/gdextension.cpp
+++ b/core/extension/gdextension.cpp
@@ -262,7 +262,6 @@ void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library
p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func;
nullptr, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func;
nullptr, // GDExtensionClassCallVirtualWithData call_virtual_func;
- p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid;
p_extension_funcs->class_userdata, // void *class_userdata;
};
@@ -270,6 +269,7 @@ void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library
p_extension_funcs->notification_func, // GDExtensionClassNotification notification_func;
p_extension_funcs->free_property_list_func, // GDExtensionClassFreePropertyList free_property_list_func;
p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func;
+ p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid;
};
_register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info4, &legacy);
}
@@ -297,7 +297,6 @@ void GDExtension::_register_extension_class2(GDExtensionClassLibraryPtr p_librar
p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func;
p_extension_funcs->get_virtual_call_data_func, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func;
p_extension_funcs->call_virtual_with_data_func, // GDExtensionClassCallVirtualWithData call_virtual_func;
- p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid;
p_extension_funcs->class_userdata, // void *class_userdata;
};
@@ -305,6 +304,7 @@ void GDExtension::_register_extension_class2(GDExtensionClassLibraryPtr p_librar
nullptr, // GDExtensionClassNotification notification_func;
p_extension_funcs->free_property_list_func, // GDExtensionClassFreePropertyList free_property_list_func;
p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func;
+ p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid;
};
_register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info4, &legacy);
}
@@ -332,7 +332,6 @@ void GDExtension::_register_extension_class3(GDExtensionClassLibraryPtr p_librar
p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func;
p_extension_funcs->get_virtual_call_data_func, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func;
p_extension_funcs->call_virtual_with_data_func, // GDExtensionClassCallVirtualWithData call_virtual_func;
- p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid;
p_extension_funcs->class_userdata, // void *class_userdata;
};
@@ -340,6 +339,7 @@ void GDExtension::_register_extension_class3(GDExtensionClassLibraryPtr p_librar
nullptr, // GDExtensionClassNotification notification_func;
nullptr, // GDExtensionClassFreePropertyList free_property_list_func;
p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance2 create_instance_func;
+ p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid;
};
_register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info4, &legacy);
}
@@ -355,7 +355,7 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
StringName parent_class_name = *reinterpret_cast<const StringName *>(p_parent_class_name);
- ERR_FAIL_COND_MSG(!String(class_name).is_valid_ascii_identifier(), "Attempt to register extension class '" + class_name + "', which is not a valid class identifier.");
+ ERR_FAIL_COND_MSG(!String(class_name).is_valid_unicode_identifier(), "Attempt to register extension class '" + class_name + "', which is not a valid class identifier.");
ERR_FAIL_COND_MSG(ClassDB::class_exists(class_name), "Attempt to register extension class '" + class_name + "', which appears to be already registered.");
Extension *parent_extension = nullptr;
@@ -427,6 +427,7 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr
extension->gdextension.notification = p_deprecated_funcs->notification_func;
extension->gdextension.free_property_list = p_deprecated_funcs->free_property_list_func;
extension->gdextension.create_instance = p_deprecated_funcs->create_instance_func;
+ extension->gdextension.get_rid = p_deprecated_funcs->get_rid_func;
}
#endif // DISABLE_DEPRECATED
extension->gdextension.notification2 = p_extension_funcs->notification_func;
@@ -440,7 +441,6 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr
extension->gdextension.get_virtual = p_extension_funcs->get_virtual_func;
extension->gdextension.get_virtual_call_data = p_extension_funcs->get_virtual_call_data_func;
extension->gdextension.call_virtual_with_data = p_extension_funcs->call_virtual_with_data_func;
- extension->gdextension.get_rid = p_extension_funcs->get_rid_func;
extension->gdextension.reloadable = self->reloadable;
#ifdef TOOLS_ENABLED
diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h
index 7bb4294909..706bc7e189 100644
--- a/core/extension/gdextension.h
+++ b/core/extension/gdextension.h
@@ -71,6 +71,7 @@ class GDExtension : public Resource {
GDExtensionClassNotification notification_func = nullptr;
GDExtensionClassFreePropertyList free_property_list_func = nullptr;
GDExtensionClassCreateInstance create_instance_func = nullptr;
+ GDExtensionClassGetRID get_rid_func = nullptr;
#endif // DISABLE_DEPRECATED
};
diff --git a/core/extension/gdextension_compat_hashes.cpp b/core/extension/gdextension_compat_hashes.cpp
index ebbf795070..b07f5b1858 100644
--- a/core/extension/gdextension_compat_hashes.cpp
+++ b/core/extension/gdextension_compat_hashes.cpp
@@ -103,6 +103,14 @@ void GDExtensionCompatHashes::initialize() {
mappings.insert("AcceptDialog", {
{ "add_button", 4158837846, 3328440682 },
});
+ mappings.insert("AnimatedSprite2D", {
+ { "play", 2372066587, 3269405555 },
+ { "play_backwards", 1421762485, 3323268493 },
+ });
+ mappings.insert("AnimatedSprite3D", {
+ { "play", 2372066587, 3269405555 },
+ { "play_backwards", 1421762485, 3323268493 },
+ });
mappings.insert("Animation", {
{ "add_track", 2393815928, 3843682357 },
{ "track_insert_key", 1985425300, 808952278 },
@@ -146,6 +154,12 @@ void GDExtensionCompatHashes::initialize() {
{ "travel", 3683006648, 3823612587 },
{ "start", 3683006648, 3823612587 },
});
+ mappings.insert("AnimationPlayer", {
+ { "play", 3697947785, 3118260607 },
+ { "play", 2221377757, 3118260607 },
+ { "play_backwards", 3890664824, 2787282401 },
+ { "play_with_capture", 3180464118, 1572969103 },
+ });
mappings.insert("ArrayMesh", {
{ "add_surface_from_arrays", 172284304, 1796411378 },
});
@@ -247,13 +261,20 @@ void GDExtensionCompatHashes::initialize() {
});
mappings.insert("DisplayServer", {
{ "global_menu_add_submenu_item", 3806306913, 2828985934 },
- { "global_menu_add_item", 3415468211, 3401266716 },
- { "global_menu_add_check_item", 3415468211, 3401266716 },
- { "global_menu_add_icon_item", 1700867534, 4245856523 },
- { "global_menu_add_icon_check_item", 1700867534, 4245856523 },
- { "global_menu_add_radio_check_item", 3415468211, 3401266716 },
- { "global_menu_add_icon_radio_check_item", 1700867534, 4245856523 },
- { "global_menu_add_multistate_item", 635750054, 3431222859 },
+ { "global_menu_add_item", 3415468211, 3616842746 },
+ { "global_menu_add_item", 3401266716, 3616842746 },
+ { "global_menu_add_check_item", 3415468211, 3616842746 },
+ { "global_menu_add_check_item", 3401266716, 3616842746 },
+ { "global_menu_add_icon_item", 1700867534, 3867083847 },
+ { "global_menu_add_icon_item", 4245856523, 3867083847 },
+ { "global_menu_add_icon_check_item", 1700867534, 3867083847 },
+ { "global_menu_add_icon_check_item", 4245856523, 3867083847 },
+ { "global_menu_add_radio_check_item", 3415468211, 3616842746 },
+ { "global_menu_add_radio_check_item", 3401266716, 3616842746 },
+ { "global_menu_add_icon_radio_check_item", 1700867534, 3867083847 },
+ { "global_menu_add_icon_radio_check_item", 4245856523, 3867083847 },
+ { "global_menu_add_multistate_item", 635750054, 3297554655 },
+ { "global_menu_add_multistate_item", 3431222859, 3297554655 },
{ "global_menu_add_separator", 1041533178, 3214812433 },
{ "tts_speak", 3741216677, 903992738 },
{ "is_touchscreen_available", 4162880507, 3323674545 },
@@ -286,6 +307,12 @@ void GDExtensionCompatHashes::initialize() {
{ "virtual_keyboard_show", 860410478, 3042891259 },
#endif
});
+ mappings.insert("EditorExportPlatform", {
+ { "export_project_files", 425454869, 1063735070 },
+ });
+ mappings.insert("EditorProperty", {
+ { "emit_changed", 3069422438, 1822500399 },
+ });
mappings.insert("ENetConnection", {
{ "create_host_bound", 866250949, 1515002313 },
{ "connect_to_host", 385984708, 2171300490 },
@@ -453,18 +480,35 @@ void GDExtensionCompatHashes::initialize() {
mappings.insert("MultiplayerAPI", {
{ "rpc", 1833408346, 2077486355 },
});
+ mappings.insert("NativeMenu", {
+ { "add_item", 2553375659, 980552939 },
+ { "add_check_item", 2553375659, 980552939 },
+ { "add_icon_item", 2987595282, 1372188274 },
+ { "add_icon_check_item", 2987595282, 1372188274 },
+ { "add_radio_check_item", 2553375659, 980552939 },
+ { "add_icon_radio_check_item", 2987595282, 1372188274 },
+ { "add_multistate_item", 1558592568, 2674635658 },
+ });
mappings.insert("NavigationMeshGenerator", {
- { "parse_source_geometry_data", 3703028813, 685862123 },
- { "bake_from_source_geometry_data", 3669016597, 2469318639 },
+ { "parse_source_geometry_data", 3703028813, 3172802542 },
+ { "parse_source_geometry_data", 685862123, 3172802542 },
+ { "bake_from_source_geometry_data", 3669016597, 1286748856 },
+ { "bake_from_source_geometry_data", 2469318639, 1286748856 },
});
mappings.insert("NavigationServer2D", {
{ "map_get_path", 56240621, 3146466012 },
+ { "parse_source_geometry_data", 1176164995, 1766905497 },
+ { "bake_from_source_geometry_data", 2909414286, 2179660022 },
+ { "bake_from_source_geometry_data_async", 2909414286, 2179660022 },
});
mappings.insert("NavigationServer3D", {
{ "map_get_path", 2121045993, 1187418690 },
- { "parse_source_geometry_data", 3703028813, 685862123 },
- { "bake_from_source_geometry_data", 3669016597, 2469318639 },
- { "bake_from_source_geometry_data_async", 3669016597, 2469318639 },
+ { "parse_source_geometry_data", 3703028813, 3172802542 },
+ { "parse_source_geometry_data", 685862123, 3172802542 },
+ { "bake_from_source_geometry_data", 3669016597, 1286748856 },
+ { "bake_from_source_geometry_data", 2469318639, 1286748856 },
+ { "bake_from_source_geometry_data_async", 3669016597, 1286748856 },
+ { "bake_from_source_geometry_data_async", 2469318639, 1286748856 },
});
mappings.insert("Node", {
{ "add_child", 3070154285, 3863233950 },
@@ -631,6 +675,11 @@ void GDExtensionCompatHashes::initialize() {
mappings.insert("ProjectSettings", {
{ "load_resource_pack", 3001721055, 708980503 },
});
+ mappings.insert("RDShaderFile", {
+ { "bake_from_source_geometry_data_async", 2469318639, 1286748856 },
+ { "set_bytecode", 1558064255, 1526857008 },
+ { "get_spirv", 3340165340, 2689310080 },
+ });
mappings.insert("RegEx", {
{ "search", 4087180739, 3365977994 },
{ "search_all", 3354100289, 849021363 },
diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp
index 0ebe86d0a7..ddf90f6130 100644
--- a/core/extension/gdextension_interface.cpp
+++ b/core/extension/gdextension_interface.cpp
@@ -1199,6 +1199,15 @@ static GDExtensionVariantPtr gdextension_dictionary_operator_index_const(GDExten
return (GDExtensionVariantPtr)&self->operator[](*(const Variant *)p_key);
}
+void gdextension_dictionary_set_typed(GDExtensionTypePtr p_self, GDExtensionVariantType p_key_type, GDExtensionConstStringNamePtr p_key_class_name, GDExtensionConstVariantPtr p_key_script, GDExtensionVariantType p_value_type, GDExtensionConstStringNamePtr p_value_class_name, GDExtensionConstVariantPtr p_value_script) {
+ Dictionary *self = reinterpret_cast<Dictionary *>(p_self);
+ const StringName *key_class_name = reinterpret_cast<const StringName *>(p_key_class_name);
+ const Variant *key_script = reinterpret_cast<const Variant *>(p_key_script);
+ const StringName *value_class_name = reinterpret_cast<const StringName *>(p_value_class_name);
+ const Variant *value_script = reinterpret_cast<const Variant *>(p_value_script);
+ self->set_typed((uint32_t)p_key_type, *key_class_name, *key_script, (uint32_t)p_value_type, *value_class_name, *value_script);
+}
+
/* OBJECT API */
static void gdextension_object_method_bind_call(GDExtensionMethodBindPtr p_method_bind, GDExtensionObjectPtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_arg_count, GDExtensionUninitializedVariantPtr r_return, GDExtensionCallError *r_error) {
@@ -1679,6 +1688,7 @@ void gdextension_setup_interface() {
REGISTER_INTERFACE_FUNC(array_set_typed);
REGISTER_INTERFACE_FUNC(dictionary_operator_index);
REGISTER_INTERFACE_FUNC(dictionary_operator_index_const);
+ REGISTER_INTERFACE_FUNC(dictionary_set_typed);
REGISTER_INTERFACE_FUNC(object_method_bind_call);
REGISTER_INTERFACE_FUNC(object_method_bind_ptrcall);
REGISTER_INTERFACE_FUNC(object_destroy);
diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h
index 9057e04bf3..9e3ce25698 100644
--- a/core/extension/gdextension_interface.h
+++ b/core/extension/gdextension_interface.h
@@ -392,7 +392,6 @@ typedef struct {
GDExtensionClassGetVirtualCallData get_virtual_call_data_func;
// Used to call virtual functions when `get_virtual_call_data_func` is not null.
GDExtensionClassCallVirtualWithData call_virtual_with_data_func;
- GDExtensionClassGetRID get_rid_func;
void *class_userdata; // Per-class user data, later accessible in instance bindings.
} GDExtensionClassCreationInfo4;
@@ -421,7 +420,9 @@ typedef enum {
GDEXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_UINT32,
GDEXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_UINT64,
GDEXTENSION_METHOD_ARGUMENT_METADATA_REAL_IS_FLOAT,
- GDEXTENSION_METHOD_ARGUMENT_METADATA_REAL_IS_DOUBLE
+ GDEXTENSION_METHOD_ARGUMENT_METADATA_REAL_IS_DOUBLE,
+ GDEXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_CHAR16,
+ GDEXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_CHAR32,
} GDExtensionClassMethodArgumentMetadata;
typedef void (*GDExtensionClassMethodCall)(void *method_userdata, GDExtensionClassInstancePtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error);
@@ -2372,6 +2373,22 @@ typedef GDExtensionVariantPtr (*GDExtensionInterfaceDictionaryOperatorIndex)(GDE
*/
typedef GDExtensionVariantPtr (*GDExtensionInterfaceDictionaryOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionConstVariantPtr p_key);
+/**
+ * @name dictionary_set_typed
+ * @since 4.4
+ *
+ * Makes a Dictionary into a typed Dictionary.
+ *
+ * @param p_self A pointer to the Dictionary.
+ * @param p_key_type The type of Variant the Dictionary key will store.
+ * @param p_key_class_name A pointer to a StringName with the name of the object (if p_key_type is GDEXTENSION_VARIANT_TYPE_OBJECT).
+ * @param p_key_script A pointer to a Script object (if p_key_type is GDEXTENSION_VARIANT_TYPE_OBJECT and the base class is extended by a script).
+ * @param p_value_type The type of Variant the Dictionary value will store.
+ * @param p_value_class_name A pointer to a StringName with the name of the object (if p_value_type is GDEXTENSION_VARIANT_TYPE_OBJECT).
+ * @param p_value_script A pointer to a Script object (if p_value_type is GDEXTENSION_VARIANT_TYPE_OBJECT and the base class is extended by a script).
+ */
+typedef void (*GDExtensionInterfaceDictionarySetTyped)(GDExtensionTypePtr p_self, GDExtensionVariantType p_key_type, GDExtensionConstStringNamePtr p_key_class_name, GDExtensionConstVariantPtr p_key_script, GDExtensionVariantType p_value_type, GDExtensionConstStringNamePtr p_value_class_name, GDExtensionConstVariantPtr p_value_script);
+
/* INTERFACE: Object */
/**
diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp
index f71257fa76..b4826c356e 100644
--- a/core/io/resource_format_binary.cpp
+++ b/core/io/resource_format_binary.cpp
@@ -845,14 +845,29 @@ Error ResourceLoaderBinary::load() {
}
}
- if (value.get_type() == Variant::ARRAY) {
- Array set_array = value;
- bool is_get_valid = false;
- Variant get_value = res->get(name, &is_get_valid);
- if (is_get_valid && get_value.get_type() == Variant::ARRAY) {
- Array get_array = get_value;
- if (!set_array.is_same_typed(get_array)) {
- value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script());
+ if (ClassDB::has_property(res->get_class_name(), name)) {
+ if (value.get_type() == Variant::ARRAY) {
+ Array set_array = value;
+ bool is_get_valid = false;
+ Variant get_value = res->get(name, &is_get_valid);
+ if (is_get_valid && get_value.get_type() == Variant::ARRAY) {
+ Array get_array = get_value;
+ if (!set_array.is_same_typed(get_array)) {
+ value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script());
+ }
+ }
+ }
+
+ if (value.get_type() == Variant::DICTIONARY) {
+ Dictionary set_dict = value;
+ bool is_get_valid = false;
+ Variant get_value = res->get(name, &is_get_valid);
+ if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) {
+ Dictionary get_dict = get_value;
+ if (!set_dict.is_same_typed(get_dict)) {
+ value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(),
+ get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script());
+ }
}
}
}
@@ -2064,6 +2079,8 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant
case Variant::DICTIONARY: {
Dictionary d = p_variant;
+ _find_resources(d.get_typed_key_script());
+ _find_resources(d.get_typed_value_script());
List<Variant> keys;
d.get_key_list(&keys);
for (const Variant &E : keys) {
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp
index a083a91a63..f29f9eef98 100644
--- a/core/io/resource_loader.cpp
+++ b/core/io/resource_loader.cpp
@@ -319,6 +319,7 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin
}
// This implementation must allow re-entrancy for a task that started awaiting in a deeper stack frame.
+// The load task token must be manually re-referenced before this is called, which includes threaded runs.
void ResourceLoader::_run_load_task(void *p_userdata) {
ThreadLoadTask &load_task = *(ThreadLoadTask *)p_userdata;
@@ -449,6 +450,9 @@ void ResourceLoader::_run_load_task(void *p_userdata) {
}
}
+ // It's safe now to let the task go in case no one else was grabbing the token.
+ load_task.load_token->unreference();
+
if (unlock_pending) {
thread_load_mutex.unlock();
}
@@ -599,6 +603,11 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path,
}
}
+ // It's important to keep the token alive because until the load completes,
+ // which includes before the thread start, it may happen that no one is grabbing
+ // the token anymore so it's released.
+ load_task_ptr->load_token->reference();
+
if (p_thread_mode == LOAD_THREAD_FROM_CURRENT) {
// The current thread may happen to be a thread from the pool.
WorkerThreadPool::TaskID tid = WorkerThreadPool::get_singleton()->get_caller_task_id();
@@ -783,6 +792,7 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro
// resource loading that means that the task to wait for can be restarted here to break the
// cycle, with as much recursion into this process as needed.
// When the stack is eventually unrolled, the original load will have been notified to go on.
+ load_task.load_token->reference();
_run_load_task(&load_task);
}
@@ -818,6 +828,8 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro
load_task_ptr = &load_task;
}
+ p_thread_load_lock.temp_unlock();
+
Ref<Resource> resource = load_task_ptr->resource;
if (r_error) {
*r_error = load_task_ptr->error;
@@ -855,6 +867,8 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro
}
}
+ p_thread_load_lock.temp_relock();
+
return resource;
}
diff --git a/core/math/expression.cpp b/core/math/expression.cpp
index 636c2c16bf..0692ece1e6 100644
--- a/core/math/expression.cpp
+++ b/core/math/expression.cpp
@@ -30,12 +30,7 @@
#include "expression.h"
-#include "core/io/marshalls.h"
-#include "core/math/math_funcs.h"
#include "core/object/class_db.h"
-#include "core/object/ref_counted.h"
-#include "core/os/os.h"
-#include "core/variant/variant_parser.h"
Error Expression::_get_token(Token &r_token) {
while (true) {
@@ -392,7 +387,6 @@ Error Expression::_get_token(Token &r_token) {
if (is_digit(c)) {
} else if (c == 'e') {
reading = READING_EXP;
-
} else {
reading = READING_DONE;
}
@@ -419,7 +413,9 @@ Error Expression::_get_token(Token &r_token) {
is_first_char = false;
}
- str_ofs--;
+ if (c != 0) {
+ str_ofs--;
+ }
r_token.type = TK_CONSTANT;
diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp
index a65411629f..9826d73a9d 100644
--- a/core/object/class_db.cpp
+++ b/core/object/class_db.cpp
@@ -227,16 +227,12 @@ public:
#endif
bool ClassDB::_is_parent_class(const StringName &p_class, const StringName &p_inherits) {
- if (!classes.has(p_class)) {
- return false;
- }
-
- StringName inherits = p_class;
- while (inherits.operator String().length()) {
- if (inherits == p_inherits) {
+ ClassInfo *c = classes.getptr(p_class);
+ while (c) {
+ if (c->name == p_inherits) {
return true;
}
- inherits = _get_parent_class(inherits);
+ c = c->inherits_ptr;
}
return false;
@@ -2310,4 +2306,11 @@ void ClassDB::cleanup() {
native_structs.clear();
}
+// Array to use in optional parameters on methods and the DEFVAL_ARRAY macro.
+Array ClassDB::default_array_arg = Array::create_read_only();
+
+bool ClassDB::is_default_array_arg(const Array &p_array) {
+ return p_array.is_same_instance(default_array_arg);
+}
+
//
diff --git a/core/object/class_db.h b/core/object/class_db.h
index 620092a6c4..81100d7586 100644
--- a/core/object/class_db.h
+++ b/core/object/class_db.h
@@ -43,6 +43,7 @@
#include <type_traits>
#define DEFVAL(m_defval) (m_defval)
+#define DEFVAL_ARRAY DEFVAL(ClassDB::default_array_arg)
#ifdef DEBUG_METHODS_ENABLED
@@ -181,6 +182,9 @@ public:
};
static HashMap<StringName, NativeStruct> native_structs;
+ static Array default_array_arg;
+ static bool is_default_array_arg(const Array &p_array);
+
private:
// Non-locking variants of get_parent_class and is_parent_class.
static StringName _get_parent_class(const StringName &p_class);
diff --git a/core/object/object.cpp b/core/object/object.cpp
index d6b7d7a7fe..000d5328b4 100644
--- a/core/object/object.cpp
+++ b/core/object/object.cpp
@@ -29,7 +29,6 @@
/**************************************************************************/
#include "object.h"
-#include "object.compat.inc"
#include "core/extension/gdextension_manager.h"
#include "core/io/resource.h"
diff --git a/core/object/object.h b/core/object/object.h
index bc3f663baf..6d22f320af 100644
--- a/core/object/object.h
+++ b/core/object/object.h
@@ -86,6 +86,7 @@ enum PropertyHint {
PROPERTY_HINT_HIDE_QUATERNION_EDIT, /// Only Node3D::transform should hide the quaternion editor.
PROPERTY_HINT_PASSWORD,
PROPERTY_HINT_LAYERS_AVOIDANCE,
+ PROPERTY_HINT_DICTIONARY_TYPE,
PROPERTY_HINT_MAX,
};
@@ -692,11 +693,7 @@ protected:
virtual void _notificationv(int p_notification, bool p_reversed) {}
static void _bind_methods();
-#ifndef DISABLE_DEPRECATED
- static void _bind_compatibility_methods();
-#else
static void _bind_compatibility_methods() {}
-#endif
bool _set(const StringName &p_name, const Variant &p_property) { return false; };
bool _get(const StringName &p_name, Variant &r_property) const { return false; };
void _get_property_list(List<PropertyInfo> *p_list) const {};
diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp
index 57e5195137..d5b7bc768d 100644
--- a/core/object/script_language.cpp
+++ b/core/object/script_language.cpp
@@ -41,6 +41,7 @@ ScriptLanguage *ScriptServer::_languages[MAX_LANGUAGES];
int ScriptServer::_language_count = 0;
bool ScriptServer::languages_ready = false;
Mutex ScriptServer::languages_mutex;
+thread_local bool ScriptServer::thread_entered = false;
bool ScriptServer::scripting_enabled = true;
bool ScriptServer::reload_scripts_on_save = false;
@@ -326,6 +327,10 @@ bool ScriptServer::are_languages_initialized() {
return languages_ready;
}
+bool ScriptServer::thread_is_entered() {
+ return thread_entered;
+}
+
void ScriptServer::set_reload_scripts_on_save(bool p_enable) {
reload_scripts_on_save = p_enable;
}
@@ -335,6 +340,10 @@ bool ScriptServer::is_reload_scripts_on_save_enabled() {
}
void ScriptServer::thread_enter() {
+ if (thread_entered) {
+ return;
+ }
+
MutexLock lock(languages_mutex);
if (!languages_ready) {
return;
@@ -342,9 +351,15 @@ void ScriptServer::thread_enter() {
for (int i = 0; i < _language_count; i++) {
_languages[i]->thread_enter();
}
+
+ thread_entered = true;
}
void ScriptServer::thread_exit() {
+ if (!thread_entered) {
+ return;
+ }
+
MutexLock lock(languages_mutex);
if (!languages_ready) {
return;
@@ -352,6 +367,8 @@ void ScriptServer::thread_exit() {
for (int i = 0; i < _language_count; i++) {
_languages[i]->thread_exit();
}
+
+ thread_entered = false;
}
HashMap<StringName, ScriptServer::GlobalScriptClass> ScriptServer::global_classes;
diff --git a/core/object/script_language.h b/core/object/script_language.h
index e38c344ae5..d9e2ab1d3c 100644
--- a/core/object/script_language.h
+++ b/core/object/script_language.h
@@ -54,6 +54,7 @@ class ScriptServer {
static int _language_count;
static bool languages_ready;
static Mutex languages_mutex;
+ static thread_local bool thread_entered;
static bool scripting_enabled;
static bool reload_scripts_on_save;
@@ -101,6 +102,7 @@ public:
static void init_languages();
static void finish_languages();
static bool are_languages_initialized();
+ static bool thread_is_entered();
};
class PlaceHolderScriptInstance;
diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp
index fe7bbd474c..cf396c2676 100644
--- a/core/object/worker_thread_pool.cpp
+++ b/core/object/worker_thread_pool.cpp
@@ -63,17 +63,14 @@ void WorkerThreadPool::_process_task(Task *p_task) {
// Tasks must start with these at default values. They are free to set-and-forget otherwise.
set_current_thread_safe_for_nodes(false);
MessageQueue::set_thread_singleton_override(nullptr);
+
// Since the WorkerThreadPool is started before the script server,
// its pre-created threads can't have ScriptServer::thread_enter() called on them early.
// Therefore, we do it late at the first opportunity, so in case the task
// about to be run uses scripting, guarantees are held.
+ ScriptServer::thread_enter();
+
task_mutex.lock();
- if (!curr_thread.ready_for_scripting && ScriptServer::are_languages_initialized()) {
- task_mutex.unlock();
- ScriptServer::thread_enter();
- task_mutex.lock();
- curr_thread.ready_for_scripting = true;
- }
p_task->pool_thread_index = pool_thread_index;
prev_task = curr_thread.current_task;
curr_thread.current_task = p_task;
@@ -326,6 +323,8 @@ WorkerThreadPool::TaskID WorkerThreadPool::add_native_task(void (*p_func)(void *
}
WorkerThreadPool::TaskID WorkerThreadPool::_add_task(const Callable &p_callable, void (*p_func)(void *), void *p_userdata, BaseTemplateUserdata *p_template_userdata, bool p_high_priority, const String &p_description) {
+ ERR_FAIL_COND_V_MSG(threads.is_empty(), INVALID_TASK_ID, "Can't add a task because the WorkerThreadPool is either not initialized yet or already terminated.");
+
task_mutex.lock();
// Get a free task
Task *task = task_allocator.alloc();
@@ -514,6 +513,12 @@ void WorkerThreadPool::yield() {
int th_index = get_thread_index();
ERR_FAIL_COND_MSG(th_index == -1, "This function can only be called from a worker thread.");
_wait_collaboratively(&threads[th_index], ThreadData::YIELDING);
+
+ // If this long-lived task started before the scripting server was initialized,
+ // now is a good time to have scripting languages ready for the current thread.
+ // Otherwise, such a piece of setup won't happen unless another task has been
+ // run during the collaborative wait.
+ ScriptServer::thread_enter();
}
void WorkerThreadPool::notify_yield_over(TaskID p_task_id) {
@@ -538,6 +543,7 @@ void WorkerThreadPool::notify_yield_over(TaskID p_task_id) {
}
WorkerThreadPool::GroupID WorkerThreadPool::_add_group_task(const Callable &p_callable, void (*p_func)(void *, uint32_t), void *p_userdata, BaseTemplateUserdata *p_template_userdata, int p_elements, int p_tasks, bool p_high_priority, const String &p_description) {
+ ERR_FAIL_COND_V_MSG(threads.is_empty(), INVALID_TASK_ID, "Can't add a group task because the WorkerThreadPool is either not initialized yet or already terminated.");
ERR_FAIL_COND_V(p_elements < 0, INVALID_TASK_ID);
if (p_tasks < 0) {
p_tasks = MAX(1u, threads.size());
@@ -749,5 +755,5 @@ WorkerThreadPool::WorkerThreadPool() {
}
WorkerThreadPool::~WorkerThreadPool() {
- finish();
+ DEV_ASSERT(threads.size() == 0 && "finish() hasn't been called!");
}
diff --git a/core/object/worker_thread_pool.h b/core/object/worker_thread_pool.h
index 5be4f20927..6374dbe8c7 100644
--- a/core/object/worker_thread_pool.h
+++ b/core/object/worker_thread_pool.h
@@ -112,7 +112,6 @@ private:
uint32_t index = 0;
Thread thread;
- bool ready_for_scripting : 1;
bool signaled : 1;
bool yield_is_over : 1;
Task *current_task = nullptr;
@@ -120,7 +119,6 @@ private:
ConditionVariable cond_var;
ThreadData() :
- ready_for_scripting(false),
signaled(false),
yield_is_over(false) {}
};
diff --git a/core/os/os.h b/core/os/os.h
index 553ae4cf76..30d2a4266f 100644
--- a/core/os/os.h
+++ b/core/os/os.h
@@ -182,7 +182,7 @@ public:
virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return Vector<String>(); };
virtual String get_executable_path() const;
virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) = 0;
- virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) { return Dictionary(); }
+ virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking = true) { return Dictionary(); }
virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) = 0;
virtual Error create_instance(const List<String> &p_arguments, ProcessID *r_child_id = nullptr) { return create_process(get_executable_path(), p_arguments, r_child_id); };
virtual Error kill(const ProcessID &p_pid) = 0;
diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp
index c866ff0415..220ed9da31 100644
--- a/core/register_core_types.cpp
+++ b/core/register_core_types.cpp
@@ -107,8 +107,6 @@ static Time *_time = nullptr;
static core_bind::Geometry2D *_geometry_2d = nullptr;
static core_bind::Geometry3D *_geometry_3d = nullptr;
-static WorkerThreadPool *worker_thread_pool = nullptr;
-
extern Mutex _global_mutex;
static GDExtensionManager *gdextension_manager = nullptr;
@@ -297,8 +295,6 @@ void register_core_types() {
GDREGISTER_NATIVE_STRUCT(AudioFrame, "float left;float right");
GDREGISTER_NATIVE_STRUCT(ScriptLanguageExtensionProfilingInfo, "StringName signature;uint64_t call_count;uint64_t total_time;uint64_t self_time");
- worker_thread_pool = memnew(WorkerThreadPool);
-
OS::get_singleton()->benchmark_end_measure("Core", "Register Types");
}
@@ -349,7 +345,7 @@ void register_core_singletons() {
Engine::get_singleton()->add_singleton(Engine::Singleton("Time", Time::get_singleton()));
Engine::get_singleton()->add_singleton(Engine::Singleton("GDExtensionManager", GDExtensionManager::get_singleton()));
Engine::get_singleton()->add_singleton(Engine::Singleton("ResourceUID", ResourceUID::get_singleton()));
- Engine::get_singleton()->add_singleton(Engine::Singleton("WorkerThreadPool", worker_thread_pool));
+ Engine::get_singleton()->add_singleton(Engine::Singleton("WorkerThreadPool", WorkerThreadPool::get_singleton()));
OS::get_singleton()->benchmark_end_measure("Core", "Register Singletons");
}
@@ -382,8 +378,6 @@ void unregister_core_types() {
// Destroy singletons in reverse order to ensure dependencies are not broken.
- memdelete(worker_thread_pool);
-
memdelete(_engine_debugger);
memdelete(_marshalls);
memdelete(_classdb);
diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp
index 0294dbfbbc..dff19b3a41 100644
--- a/core/string/string_name.cpp
+++ b/core/string/string_name.cpp
@@ -162,6 +162,11 @@ void StringName::unref() {
_data = nullptr;
}
+uint32_t StringName::get_empty_hash() {
+ static uint32_t empty_hash = String::hash("");
+ return empty_hash;
+}
+
bool StringName::operator==(const String &p_name) const {
if (_data) {
return _data->operator==(p_name);
diff --git a/core/string/string_name.h b/core/string/string_name.h
index 288e2c7520..d4b70d311d 100644
--- a/core/string/string_name.h
+++ b/core/string/string_name.h
@@ -83,6 +83,7 @@ class StringName {
static inline Mutex mutex;
static void setup();
static void cleanup();
+ static uint32_t get_empty_hash();
static inline bool configured = false;
#ifdef DEBUG_ENABLED
struct DebugSortReferences {
@@ -139,7 +140,7 @@ public:
if (_data) {
return _data->hash;
} else {
- return 0;
+ return get_empty_hash();
}
}
_FORCE_INLINE_ const void *data_unique_pointer() const {
diff --git a/core/string/translation.cpp b/core/string/translation.cpp
index 33d4a1bcde..020949371f 100644
--- a/core/string/translation.cpp
+++ b/core/string/translation.cpp
@@ -29,7 +29,6 @@
/**************************************************************************/
#include "translation.h"
-#include "translation.compat.inc"
#include "core/os/os.h"
#include "core/os/thread.h"
diff --git a/core/string/translation.h b/core/string/translation.h
index 2c5baae8b7..4e8cffc90c 100644
--- a/core/string/translation.h
+++ b/core/string/translation.h
@@ -51,10 +51,6 @@ class Translation : public Resource {
protected:
static void _bind_methods();
-#ifndef DISABLE_DEPRECATED
- static void _bind_compatibility_methods();
-#endif
-
GDVIRTUAL2RC(StringName, _get_message, StringName, StringName);
GDVIRTUAL4RC(StringName, _get_plural_message, StringName, StringName, int, StringName);
diff --git a/core/string/translation_server.cpp b/core/string/translation_server.cpp
index 4ac79ad10a..d4aa152340 100644
--- a/core/string/translation_server.cpp
+++ b/core/string/translation_server.cpp
@@ -29,7 +29,6 @@
/**************************************************************************/
#include "translation_server.h"
-#include "translation_server.compat.inc"
#include "core/config/project_settings.h"
#include "core/io/resource_loader.h"
diff --git a/core/string/translation_server.h b/core/string/translation_server.h
index ebe81d9712..bb285ab19c 100644
--- a/core/string/translation_server.h
+++ b/core/string/translation_server.h
@@ -74,10 +74,6 @@ class TranslationServer : public Object {
static void _bind_methods();
-#ifndef DISABLE_DEPRECATED
- static void _bind_compatibility_methods();
-#endif
-
struct LocaleScriptInfo {
String name;
String script;
diff --git a/core/variant/array.cpp b/core/variant/array.cpp
index 54cd1eda2f..869499e668 100644
--- a/core/variant/array.cpp
+++ b/core/variant/array.cpp
@@ -803,6 +803,10 @@ bool Array::is_same_typed(const Array &p_other) const {
return _p->typed == p_other._p->typed;
}
+bool Array::is_same_instance(const Array &p_other) const {
+ return _p == p_other._p;
+}
+
uint32_t Array::get_typed_builtin() const {
return _p->typed.type;
}
@@ -815,6 +819,12 @@ Variant Array::get_typed_script() const {
return _p->typed.script;
}
+Array Array::create_read_only() {
+ Array array;
+ array.make_read_only();
+ return array;
+}
+
void Array::make_read_only() {
if (_p->read_only == nullptr) {
_p->read_only = memnew(Variant);
diff --git a/core/variant/array.h b/core/variant/array.h
index 3aa957b312..12824ee3f6 100644
--- a/core/variant/array.h
+++ b/core/variant/array.h
@@ -186,12 +186,14 @@ public:
void set_typed(uint32_t p_type, const StringName &p_class_name, const Variant &p_script);
bool is_typed() const;
bool is_same_typed(const Array &p_other) const;
+ bool is_same_instance(const Array &p_other) const;
uint32_t get_typed_builtin() const;
StringName get_typed_class_name() const;
Variant get_typed_script() const;
void make_read_only();
bool is_read_only() const;
+ static Array create_read_only();
Array(const Array &p_base, uint32_t p_type, const StringName &p_class_name, const Variant &p_script);
Array(const Array &p_from);
diff --git a/core/variant/dictionary.cpp b/core/variant/dictionary.cpp
index 733d13a106..0754814d35 100644
--- a/core/variant/dictionary.cpp
+++ b/core/variant/dictionary.cpp
@@ -32,6 +32,7 @@
#include "core/templates/hash_map.h"
#include "core/templates/safe_refcount.h"
+#include "core/variant/container_type_validate.h"
#include "core/variant/variant.h"
// required in this order by VariantInternal, do not remove this comment.
#include "core/object/class_db.h"
@@ -43,6 +44,9 @@ struct DictionaryPrivate {
SafeRefCount refcount;
Variant *read_only = nullptr; // If enabled, a pointer is used to a temporary value that is used to return read-only values.
HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator> variant_map;
+ ContainerTypeValidate typed_key;
+ ContainerTypeValidate typed_value;
+ Variant *typed_fallback = nullptr; // Allows a typed dictionary to return dummy values when attempting an invalid access.
};
void Dictionary::get_key_list(List<Variant> *p_keys) const {
@@ -120,7 +124,9 @@ Variant *Dictionary::getptr(const Variant &p_key) {
}
Variant Dictionary::get_valid(const Variant &p_key) const {
- HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::ConstIterator E(_p->variant_map.find(p_key));
+ Variant key = p_key;
+ ERR_FAIL_COND_V(!_p->typed_key.validate(key, "get_valid"), Variant());
+ HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::ConstIterator E(_p->variant_map.find(key));
if (!E) {
return Variant();
@@ -129,7 +135,9 @@ Variant Dictionary::get_valid(const Variant &p_key) const {
}
Variant Dictionary::get(const Variant &p_key, const Variant &p_default) const {
- const Variant *result = getptr(p_key);
+ Variant key = p_key;
+ ERR_FAIL_COND_V(!_p->typed_key.validate(key, "get"), p_default);
+ const Variant *result = getptr(key);
if (!result) {
return p_default;
}
@@ -138,10 +146,14 @@ Variant Dictionary::get(const Variant &p_key, const Variant &p_default) const {
}
Variant Dictionary::get_or_add(const Variant &p_key, const Variant &p_default) {
- const Variant *result = getptr(p_key);
+ Variant key = p_key;
+ ERR_FAIL_COND_V(!_p->typed_key.validate(key, "get"), p_default);
+ const Variant *result = getptr(key);
if (!result) {
- operator[](p_key) = p_default;
- return p_default;
+ Variant value = p_default;
+ ERR_FAIL_COND_V(!_p->typed_value.validate(value, "add"), value);
+ operator[](key) = value;
+ return value;
}
return *result;
}
@@ -155,12 +167,16 @@ bool Dictionary::is_empty() const {
}
bool Dictionary::has(const Variant &p_key) const {
+ Variant key = p_key;
+ ERR_FAIL_COND_V(!_p->typed_key.validate(key, "use 'has'"), false);
return _p->variant_map.has(p_key);
}
bool Dictionary::has_all(const Array &p_keys) const {
for (int i = 0; i < p_keys.size(); i++) {
- if (!has(p_keys[i])) {
+ Variant key = p_keys[i];
+ ERR_FAIL_COND_V(!_p->typed_key.validate(key, "use 'has_all'"), false);
+ if (!has(key)) {
return false;
}
}
@@ -168,8 +184,10 @@ bool Dictionary::has_all(const Array &p_keys) const {
}
Variant Dictionary::find_key(const Variant &p_value) const {
+ Variant value = p_value;
+ ERR_FAIL_COND_V(!_p->typed_value.validate(value, "find_key"), Variant());
for (const KeyValue<Variant, Variant> &E : _p->variant_map) {
- if (E.value == p_value) {
+ if (E.value == value) {
return E.key;
}
}
@@ -177,8 +195,10 @@ Variant Dictionary::find_key(const Variant &p_value) const {
}
bool Dictionary::erase(const Variant &p_key) {
+ Variant key = p_key;
+ ERR_FAIL_COND_V(!_p->typed_key.validate(key, "erase"), false);
ERR_FAIL_COND_V_MSG(_p->read_only, false, "Dictionary is in read-only state.");
- return _p->variant_map.erase(p_key);
+ return _p->variant_map.erase(key);
}
bool Dictionary::operator==(const Dictionary &p_dictionary) const {
@@ -238,8 +258,12 @@ void Dictionary::clear() {
void Dictionary::merge(const Dictionary &p_dictionary, bool p_overwrite) {
ERR_FAIL_COND_MSG(_p->read_only, "Dictionary is in read-only state.");
for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
- if (p_overwrite || !has(E.key)) {
- operator[](E.key) = E.value;
+ Variant key = E.key;
+ Variant value = E.value;
+ ERR_FAIL_COND(!_p->typed_key.validate(key, "merge"));
+ ERR_FAIL_COND(!_p->typed_value.validate(value, "merge"));
+ if (p_overwrite || !has(key)) {
+ operator[](key) = value;
}
}
}
@@ -256,6 +280,9 @@ void Dictionary::_unref() const {
if (_p->read_only) {
memdelete(_p->read_only);
}
+ if (_p->typed_fallback) {
+ memdelete(_p->typed_fallback);
+ }
memdelete(_p);
}
_p = nullptr;
@@ -284,6 +311,9 @@ uint32_t Dictionary::recursive_hash(int recursion_count) const {
Array Dictionary::keys() const {
Array varr;
+ if (is_typed_key()) {
+ varr.set_typed(get_typed_key_builtin(), get_typed_key_class_name(), get_typed_key_script());
+ }
if (_p->variant_map.is_empty()) {
return varr;
}
@@ -301,6 +331,9 @@ Array Dictionary::keys() const {
Array Dictionary::values() const {
Array varr;
+ if (is_typed_value()) {
+ varr.set_typed(get_typed_value_builtin(), get_typed_value_class_name(), get_typed_value_script());
+ }
if (_p->variant_map.is_empty()) {
return varr;
}
@@ -316,6 +349,146 @@ Array Dictionary::values() const {
return varr;
}
+void Dictionary::assign(const Dictionary &p_dictionary) {
+ const ContainerTypeValidate &typed_key = _p->typed_key;
+ const ContainerTypeValidate &typed_key_source = p_dictionary._p->typed_key;
+
+ const ContainerTypeValidate &typed_value = _p->typed_value;
+ const ContainerTypeValidate &typed_value_source = p_dictionary._p->typed_value;
+
+ if ((typed_key == typed_key_source || typed_key.type == Variant::NIL || (typed_key_source.type == Variant::OBJECT && typed_key.can_reference(typed_key_source))) &&
+ (typed_value == typed_value_source || typed_value.type == Variant::NIL || (typed_value_source.type == Variant::OBJECT && typed_value.can_reference(typed_value_source)))) {
+ // From same to same or,
+ // from anything to variants or,
+ // from subclasses to base classes.
+ _p->variant_map = p_dictionary._p->variant_map;
+ return;
+ }
+
+ int size = p_dictionary._p->variant_map.size();
+ HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator> variant_map = HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>(size);
+
+ Vector<Variant> key_array;
+ key_array.resize(size);
+ Variant *key_data = key_array.ptrw();
+
+ Vector<Variant> value_array;
+ value_array.resize(size);
+ Variant *value_data = value_array.ptrw();
+
+ if (typed_key == typed_key_source || typed_key.type == Variant::NIL || (typed_key_source.type == Variant::OBJECT && typed_key.can_reference(typed_key_source))) {
+ // From same to same or,
+ // from anything to variants or,
+ // from subclasses to base classes.
+ int i = 0;
+ for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+ const Variant *key = &E.key;
+ key_data[i++] = *key;
+ }
+ } else if ((typed_key_source.type == Variant::NIL && typed_key.type == Variant::OBJECT) || (typed_key_source.type == Variant::OBJECT && typed_key_source.can_reference(typed_key))) {
+ // From variants to objects or,
+ // from base classes to subclasses.
+ int i = 0;
+ for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+ const Variant *key = &E.key;
+ if (key->get_type() != Variant::NIL && (key->get_type() != Variant::OBJECT || !typed_key.validate_object(*key, "assign"))) {
+ ERR_FAIL_MSG(vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type)));
+ }
+ key_data[i++] = *key;
+ }
+ } else if (typed_key.type == Variant::OBJECT || typed_key_source.type == Variant::OBJECT) {
+ ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s]".)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type),
+ Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type)));
+ } else if (typed_key_source.type == Variant::NIL && typed_key.type != Variant::OBJECT) {
+ // From variants to primitives.
+ int i = 0;
+ for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+ const Variant *key = &E.key;
+ if (key->get_type() == typed_key.type) {
+ key_data[i++] = *key;
+ continue;
+ }
+ if (!Variant::can_convert_strict(key->get_type(), typed_key.type)) {
+ ERR_FAIL_MSG(vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type)));
+ }
+ Callable::CallError ce;
+ Variant::construct(typed_key.type, key_data[i++], &key, 1, ce);
+ ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type)));
+ }
+ } else if (Variant::can_convert_strict(typed_key_source.type, typed_key.type)) {
+ // From primitives to different convertible primitives.
+ int i = 0;
+ for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+ const Variant *key = &E.key;
+ Callable::CallError ce;
+ Variant::construct(typed_key.type, key_data[i++], &key, 1, ce);
+ ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type)));
+ }
+ } else {
+ ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s].)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type),
+ Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type)));
+ }
+
+ if (typed_value == typed_value_source || typed_value.type == Variant::NIL || (typed_value_source.type == Variant::OBJECT && typed_value.can_reference(typed_value_source))) {
+ // From same to same or,
+ // from anything to variants or,
+ // from subclasses to base classes.
+ int i = 0;
+ for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+ const Variant *value = &E.value;
+ value_data[i++] = *value;
+ }
+ } else if (((typed_value_source.type == Variant::NIL && typed_value.type == Variant::OBJECT) || (typed_value_source.type == Variant::OBJECT && typed_value_source.can_reference(typed_value)))) {
+ // From variants to objects or,
+ // from base classes to subclasses.
+ int i = 0;
+ for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+ const Variant *value = &E.value;
+ if (value->get_type() != Variant::NIL && (value->get_type() != Variant::OBJECT || !typed_value.validate_object(*value, "assign"))) {
+ ERR_FAIL_MSG(vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type)));
+ }
+ value_data[i++] = *value;
+ }
+ } else if (typed_value.type == Variant::OBJECT || typed_value_source.type == Variant::OBJECT) {
+ ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s]".)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type),
+ Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type)));
+ } else if (typed_value_source.type == Variant::NIL && typed_value.type != Variant::OBJECT) {
+ // From variants to primitives.
+ int i = 0;
+ for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+ const Variant *value = &E.value;
+ if (value->get_type() == typed_value.type) {
+ value_data[i++] = *value;
+ continue;
+ }
+ if (!Variant::can_convert_strict(value->get_type(), typed_value.type)) {
+ ERR_FAIL_MSG(vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type)));
+ }
+ Callable::CallError ce;
+ Variant::construct(typed_value.type, value_data[i++], &value, 1, ce);
+ ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i - 1], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type)));
+ }
+ } else if (Variant::can_convert_strict(typed_value_source.type, typed_value.type)) {
+ // From primitives to different convertible primitives.
+ int i = 0;
+ for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
+ const Variant *value = &E.value;
+ Callable::CallError ce;
+ Variant::construct(typed_value.type, value_data[i++], &value, 1, ce);
+ ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i - 1], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type)));
+ }
+ } else {
+ ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s].)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type),
+ Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type)));
+ }
+
+ for (int i = 0; i < size; i++) {
+ variant_map.insert(key_data[i], value_data[i]);
+ }
+
+ _p->variant_map = variant_map;
+}
+
const Variant *Dictionary::next(const Variant *p_key) const {
if (p_key == nullptr) {
// caller wants to get the first element
@@ -324,7 +497,9 @@ const Variant *Dictionary::next(const Variant *p_key) const {
}
return nullptr;
}
- HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::Iterator E = _p->variant_map.find(*p_key);
+ Variant key = *p_key;
+ ERR_FAIL_COND_V(!_p->typed_key.validate(key, "next"), nullptr);
+ HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::Iterator E = _p->variant_map.find(key);
if (!E) {
return nullptr;
@@ -354,6 +529,8 @@ bool Dictionary::is_read_only() const {
Dictionary Dictionary::recursive_duplicate(bool p_deep, int recursion_count) const {
Dictionary n;
+ n._p->typed_key = _p->typed_key;
+ n._p->typed_value = _p->typed_value;
if (recursion_count > MAX_RECURSION) {
ERR_PRINT("Max recursion reached");
@@ -374,6 +551,76 @@ Dictionary Dictionary::recursive_duplicate(bool p_deep, int recursion_count) con
return n;
}
+void Dictionary::set_typed(uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script) {
+ ERR_FAIL_COND_MSG(_p->read_only, "Dictionary is in read-only state.");
+ ERR_FAIL_COND_MSG(_p->variant_map.size() > 0, "Type can only be set when dictionary is empty.");
+ ERR_FAIL_COND_MSG(_p->refcount.get() > 1, "Type can only be set when dictionary has no more than one user.");
+ ERR_FAIL_COND_MSG(_p->typed_key.type != Variant::NIL || _p->typed_value.type != Variant::NIL, "Type can only be set once.");
+ ERR_FAIL_COND_MSG((p_key_class_name != StringName() && p_key_type != Variant::OBJECT) || (p_value_class_name != StringName() && p_value_type != Variant::OBJECT), "Class names can only be set for type OBJECT.");
+ Ref<Script> key_script = p_key_script;
+ ERR_FAIL_COND_MSG(key_script.is_valid() && p_key_class_name == StringName(), "Script class can only be set together with base class name.");
+ Ref<Script> value_script = p_value_script;
+ ERR_FAIL_COND_MSG(value_script.is_valid() && p_value_class_name == StringName(), "Script class can only be set together with base class name.");
+
+ _p->typed_key.type = Variant::Type(p_key_type);
+ _p->typed_key.class_name = p_key_class_name;
+ _p->typed_key.script = key_script;
+ _p->typed_key.where = "TypedDictionary.Key";
+
+ _p->typed_value.type = Variant::Type(p_value_type);
+ _p->typed_value.class_name = p_value_class_name;
+ _p->typed_value.script = value_script;
+ _p->typed_value.where = "TypedDictionary.Value";
+}
+
+bool Dictionary::is_typed() const {
+ return is_typed_key() || is_typed_value();
+}
+
+bool Dictionary::is_typed_key() const {
+ return _p->typed_key.type != Variant::NIL;
+}
+
+bool Dictionary::is_typed_value() const {
+ return _p->typed_value.type != Variant::NIL;
+}
+
+bool Dictionary::is_same_typed(const Dictionary &p_other) const {
+ return is_same_typed_key(p_other) && is_same_typed_value(p_other);
+}
+
+bool Dictionary::is_same_typed_key(const Dictionary &p_other) const {
+ return _p->typed_key == p_other._p->typed_key;
+}
+
+bool Dictionary::is_same_typed_value(const Dictionary &p_other) const {
+ return _p->typed_value == p_other._p->typed_value;
+}
+
+uint32_t Dictionary::get_typed_key_builtin() const {
+ return _p->typed_key.type;
+}
+
+uint32_t Dictionary::get_typed_value_builtin() const {
+ return _p->typed_value.type;
+}
+
+StringName Dictionary::get_typed_key_class_name() const {
+ return _p->typed_key.class_name;
+}
+
+StringName Dictionary::get_typed_value_class_name() const {
+ return _p->typed_value.class_name;
+}
+
+Variant Dictionary::get_typed_key_script() const {
+ return _p->typed_key.script;
+}
+
+Variant Dictionary::get_typed_value_script() const {
+ return _p->typed_value.script;
+}
+
void Dictionary::operator=(const Dictionary &p_dictionary) {
if (this == &p_dictionary) {
return;
@@ -385,6 +632,13 @@ const void *Dictionary::id() const {
return _p;
}
+Dictionary::Dictionary(const Dictionary &p_base, uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script) {
+ _p = memnew(DictionaryPrivate);
+ _p->refcount.init();
+ set_typed(p_key_type, p_key_class_name, p_key_script, p_value_type, p_value_class_name, p_value_script);
+ assign(p_base);
+}
+
Dictionary::Dictionary(const Dictionary &p_from) {
_p = nullptr;
_ref(p_from);
diff --git a/core/variant/dictionary.h b/core/variant/dictionary.h
index 67178ee7b7..57fbefc8f2 100644
--- a/core/variant/dictionary.h
+++ b/core/variant/dictionary.h
@@ -80,6 +80,7 @@ public:
uint32_t recursive_hash(int recursion_count) const;
void operator=(const Dictionary &p_dictionary);
+ void assign(const Dictionary &p_dictionary);
const Variant *next(const Variant *p_key = nullptr) const;
Array keys() const;
@@ -88,11 +89,26 @@ public:
Dictionary duplicate(bool p_deep = false) const;
Dictionary recursive_duplicate(bool p_deep, int recursion_count) const;
+ void set_typed(uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script);
+ bool is_typed() const;
+ bool is_typed_key() const;
+ bool is_typed_value() const;
+ bool is_same_typed(const Dictionary &p_other) const;
+ bool is_same_typed_key(const Dictionary &p_other) const;
+ bool is_same_typed_value(const Dictionary &p_other) const;
+ uint32_t get_typed_key_builtin() const;
+ uint32_t get_typed_value_builtin() const;
+ StringName get_typed_key_class_name() const;
+ StringName get_typed_value_class_name() const;
+ Variant get_typed_key_script() const;
+ Variant get_typed_value_script() const;
+
void make_read_only();
bool is_read_only() const;
const void *id() const;
+ Dictionary(const Dictionary &p_base, uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script);
Dictionary(const Dictionary &p_from);
Dictionary();
~Dictionary();
diff --git a/core/variant/type_info.h b/core/variant/type_info.h
index d51c80eebe..6bb703f2dd 100644
--- a/core/variant/type_info.h
+++ b/core/variant/type_info.h
@@ -47,7 +47,9 @@ enum Metadata {
METADATA_INT_IS_UINT32,
METADATA_INT_IS_UINT64,
METADATA_REAL_IS_FLOAT,
- METADATA_REAL_IS_DOUBLE
+ METADATA_REAL_IS_DOUBLE,
+ METADATA_INT_IS_CHAR16,
+ METADATA_INT_IS_CHAR32,
};
}
@@ -104,8 +106,8 @@ MAKE_TYPE_INFO_WITH_META(uint32_t, Variant::INT, GodotTypeInfo::METADATA_INT_IS_
MAKE_TYPE_INFO_WITH_META(int32_t, Variant::INT, GodotTypeInfo::METADATA_INT_IS_INT32)
MAKE_TYPE_INFO_WITH_META(uint64_t, Variant::INT, GodotTypeInfo::METADATA_INT_IS_UINT64)
MAKE_TYPE_INFO_WITH_META(int64_t, Variant::INT, GodotTypeInfo::METADATA_INT_IS_INT64)
-MAKE_TYPE_INFO(char16_t, Variant::INT)
-MAKE_TYPE_INFO(char32_t, Variant::INT)
+MAKE_TYPE_INFO_WITH_META(char16_t, Variant::INT, GodotTypeInfo::METADATA_INT_IS_CHAR16)
+MAKE_TYPE_INFO_WITH_META(char32_t, Variant::INT, GodotTypeInfo::METADATA_INT_IS_CHAR32)
MAKE_TYPE_INFO_WITH_META(float, Variant::FLOAT, GodotTypeInfo::METADATA_REAL_IS_FLOAT)
MAKE_TYPE_INFO_WITH_META(double, Variant::FLOAT, GodotTypeInfo::METADATA_REAL_IS_DOUBLE)
diff --git a/core/variant/typed_dictionary.h b/core/variant/typed_dictionary.h
new file mode 100644
index 0000000000..67fc33b4fc
--- /dev/null
+++ b/core/variant/typed_dictionary.h
@@ -0,0 +1,342 @@
+/**************************************************************************/
+/* typed_dictionary.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 TYPED_DICTIONARY_H
+#define TYPED_DICTIONARY_H
+
+#include "core/object/object.h"
+#include "core/variant/binder_common.h"
+#include "core/variant/dictionary.h"
+#include "core/variant/method_ptrcall.h"
+#include "core/variant/type_info.h"
+#include "core/variant/variant.h"
+
+template <typename K, typename V>
+class TypedDictionary : public Dictionary {
+public:
+ _FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) {
+ ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign a dictionary with a different element type.");
+ Dictionary::operator=(p_dictionary);
+ }
+ _FORCE_INLINE_ TypedDictionary(const Variant &p_variant) :
+ TypedDictionary(Dictionary(p_variant)) {
+ }
+ _FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) {
+ set_typed(Variant::OBJECT, K::get_class_static(), Variant(), Variant::OBJECT, V::get_class_static(), Variant());
+ if (is_same_typed(p_dictionary)) {
+ Dictionary::operator=(p_dictionary);
+ } else {
+ assign(p_dictionary);
+ }
+ }
+ _FORCE_INLINE_ TypedDictionary() {
+ set_typed(Variant::OBJECT, K::get_class_static(), Variant(), Variant::OBJECT, V::get_class_static(), Variant());
+ }
+};
+
+template <typename K, typename V>
+struct VariantInternalAccessor<TypedDictionary<K, V>> {
+ static _FORCE_INLINE_ TypedDictionary<K, V> get(const Variant *v) { return *VariantInternal::get_dictionary(v); }
+ static _FORCE_INLINE_ void set(Variant *v, const TypedDictionary<K, V> &p_dictionary) { *VariantInternal::get_dictionary(v) = p_dictionary; }
+};
+
+template <typename K, typename V>
+struct VariantInternalAccessor<const TypedDictionary<K, V> &> {
+ static _FORCE_INLINE_ TypedDictionary<K, V> get(const Variant *v) { return *VariantInternal::get_dictionary(v); }
+ static _FORCE_INLINE_ void set(Variant *v, const TypedDictionary<K, V> &p_dictionary) { *VariantInternal::get_dictionary(v) = p_dictionary; }
+};
+
+template <typename K, typename V>
+struct PtrToArg<TypedDictionary<K, V>> {
+ _FORCE_INLINE_ static TypedDictionary<K, V> convert(const void *p_ptr) {
+ return TypedDictionary<K, V>(*reinterpret_cast<const Dictionary *>(p_ptr));
+ }
+ typedef Dictionary EncodeT;
+ _FORCE_INLINE_ static void encode(TypedDictionary<K, V> p_val, void *p_ptr) {
+ *(Dictionary *)p_ptr = p_val;
+ }
+};
+
+template <typename K, typename V>
+struct PtrToArg<const TypedDictionary<K, V> &> {
+ typedef Dictionary EncodeT;
+ _FORCE_INLINE_ static TypedDictionary<K, V>
+ convert(const void *p_ptr) {
+ return TypedDictionary<K, V>(*reinterpret_cast<const Dictionary *>(p_ptr));
+ }
+};
+
+template <typename K, typename V>
+struct GetTypeInfo<TypedDictionary<K, V>> {
+ static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY;
+ static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;
+ static inline PropertyInfo get_class_info() {
+ return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, vformat("%s;%s", K::get_class_static(), V::get_class_static()));
+ }
+};
+
+template <typename K, typename V>
+struct GetTypeInfo<const TypedDictionary<K, V> &> {
+ static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY;
+ static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;
+ static inline PropertyInfo get_class_info() {
+ return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, vformat("%s;%s", K::get_class_static(), V::get_class_static()));
+ }
+};
+
+// Specialization for the rest of the Variant types.
+
+#define MAKE_TYPED_DICTIONARY_WITH_OBJECT(m_type, m_variant_type) \
+ template <typename T> \
+ class TypedDictionary<T, m_type> : public Dictionary { \
+ public: \
+ _FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) { \
+ ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign an dictionary with a different element type."); \
+ Dictionary::operator=(p_dictionary); \
+ } \
+ _FORCE_INLINE_ TypedDictionary(const Variant &p_variant) : \
+ TypedDictionary(Dictionary(p_variant)) { \
+ } \
+ _FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) { \
+ set_typed(Variant::OBJECT, T::get_class_static(), Variant(), m_variant_type, StringName(), Variant()); \
+ if (is_same_typed(p_dictionary)) { \
+ Dictionary::operator=(p_dictionary); \
+ } else { \
+ assign(p_dictionary); \
+ } \
+ } \
+ _FORCE_INLINE_ TypedDictionary() { \
+ set_typed(Variant::OBJECT, T::get_class_static(), Variant(), m_variant_type, StringName(), Variant()); \
+ } \
+ }; \
+ template <typename T> \
+ struct GetTypeInfo<TypedDictionary<T, m_type>> { \
+ static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \
+ static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \
+ static inline PropertyInfo get_class_info() { \
+ return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \
+ vformat("%s;%s", T::get_class_static(), m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type))); \
+ } \
+ }; \
+ template <typename T> \
+ struct GetTypeInfo<const TypedDictionary<T, m_type> &> { \
+ static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \
+ static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \
+ static inline PropertyInfo get_class_info() { \
+ return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \
+ vformat("%s;%s", T::get_class_static(), m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type))); \
+ } \
+ }; \
+ template <typename T> \
+ class TypedDictionary<m_type, T> : public Dictionary { \
+ public: \
+ _FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) { \
+ ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign an dictionary with a different element type."); \
+ Dictionary::operator=(p_dictionary); \
+ } \
+ _FORCE_INLINE_ TypedDictionary(const Variant &p_variant) : \
+ TypedDictionary(Dictionary(p_variant)) { \
+ } \
+ _FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) { \
+ set_typed(m_variant_type, StringName(), Variant(), Variant::OBJECT, T::get_class_static(), Variant()); \
+ if (is_same_typed(p_dictionary)) { \
+ Dictionary::operator=(p_dictionary); \
+ } else { \
+ assign(p_dictionary); \
+ } \
+ } \
+ _FORCE_INLINE_ TypedDictionary() { \
+ set_typed(m_variant_type, StringName(), Variant(), Variant::OBJECT, T::get_class_static(), Variant()); \
+ } \
+ }; \
+ template <typename T> \
+ struct GetTypeInfo<TypedDictionary<m_type, T>> { \
+ static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \
+ static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \
+ static inline PropertyInfo get_class_info() { \
+ return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \
+ vformat("%s;%s", m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type), T::get_class_static())); \
+ } \
+ }; \
+ template <typename T> \
+ struct GetTypeInfo<const TypedDictionary<m_type, T> &> { \
+ static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \
+ static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \
+ static inline PropertyInfo get_class_info() { \
+ return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \
+ vformat("%s;%s", m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type), T::get_class_static())); \
+ } \
+ };
+
+#define MAKE_TYPED_DICTIONARY_EXPANDED(m_type_key, m_variant_type_key, m_type_value, m_variant_type_value) \
+ template <> \
+ class TypedDictionary<m_type_key, m_type_value> : public Dictionary { \
+ public: \
+ _FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) { \
+ ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign an dictionary with a different element type."); \
+ Dictionary::operator=(p_dictionary); \
+ } \
+ _FORCE_INLINE_ TypedDictionary(const Variant &p_variant) : \
+ TypedDictionary(Dictionary(p_variant)) { \
+ } \
+ _FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) { \
+ set_typed(m_variant_type_key, StringName(), Variant(), m_variant_type_value, StringName(), Variant()); \
+ if (is_same_typed(p_dictionary)) { \
+ Dictionary::operator=(p_dictionary); \
+ } else { \
+ assign(p_dictionary); \
+ } \
+ } \
+ _FORCE_INLINE_ TypedDictionary() { \
+ set_typed(m_variant_type_key, StringName(), Variant(), m_variant_type_value, StringName(), Variant()); \
+ } \
+ }; \
+ template <> \
+ struct GetTypeInfo<TypedDictionary<m_type_key, m_type_value>> { \
+ static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \
+ static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \
+ static inline PropertyInfo get_class_info() { \
+ return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \
+ vformat("%s;%s", m_variant_type_key == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_key), \
+ m_variant_type_value == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_value))); \
+ } \
+ }; \
+ template <> \
+ struct GetTypeInfo<const TypedDictionary<m_type_key, m_type_value> &> { \
+ static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \
+ static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \
+ static inline PropertyInfo get_class_info() { \
+ return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \
+ vformat("%s;%s", m_variant_type_key == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_key), \
+ m_variant_type_value == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_value))); \
+ } \
+ };
+
+#define MAKE_TYPED_DICTIONARY_NIL(m_type, m_variant_type) \
+ MAKE_TYPED_DICTIONARY_WITH_OBJECT(m_type, m_variant_type) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, bool, Variant::BOOL) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint8_t, Variant::INT) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int8_t, Variant::INT) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint16_t, Variant::INT) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int16_t, Variant::INT) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint32_t, Variant::INT) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int32_t, Variant::INT) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint64_t, Variant::INT) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int64_t, Variant::INT) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, float, Variant::FLOAT) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, double, Variant::FLOAT) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, String, Variant::STRING) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector2, Variant::VECTOR2) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector2i, Variant::VECTOR2I) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Rect2, Variant::RECT2) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Rect2i, Variant::RECT2I) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector3, Variant::VECTOR3) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector3i, Variant::VECTOR3I) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Transform2D, Variant::TRANSFORM2D) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Plane, Variant::PLANE) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Quaternion, Variant::QUATERNION) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, AABB, Variant::AABB) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Basis, Variant::BASIS) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Transform3D, Variant::TRANSFORM3D) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Color, Variant::COLOR) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, StringName, Variant::STRING_NAME) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, NodePath, Variant::NODE_PATH) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, RID, Variant::RID) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Callable, Variant::CALLABLE) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Signal, Variant::SIGNAL) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Dictionary, Variant::DICTIONARY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Array, Variant::ARRAY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedByteArray, Variant::PACKED_BYTE_ARRAY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedInt32Array, Variant::PACKED_INT32_ARRAY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedInt64Array, Variant::PACKED_INT64_ARRAY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedFloat32Array, Variant::PACKED_FLOAT32_ARRAY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedFloat64Array, Variant::PACKED_FLOAT64_ARRAY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedStringArray, Variant::PACKED_STRING_ARRAY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedVector2Array, Variant::PACKED_VECTOR2_ARRAY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedVector3Array, Variant::PACKED_VECTOR3_ARRAY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedColorArray, Variant::PACKED_COLOR_ARRAY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedVector4Array, Variant::PACKED_VECTOR4_ARRAY) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, IPAddress, Variant::STRING)
+
+#define MAKE_TYPED_DICTIONARY(m_type, m_variant_type) \
+ MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Variant, Variant::NIL) \
+ MAKE_TYPED_DICTIONARY_NIL(m_type, m_variant_type)
+
+MAKE_TYPED_DICTIONARY_NIL(Variant, Variant::NIL)
+MAKE_TYPED_DICTIONARY(bool, Variant::BOOL)
+MAKE_TYPED_DICTIONARY(uint8_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(int8_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(uint16_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(int16_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(uint32_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(int32_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(uint64_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(int64_t, Variant::INT)
+MAKE_TYPED_DICTIONARY(float, Variant::FLOAT)
+MAKE_TYPED_DICTIONARY(double, Variant::FLOAT)
+MAKE_TYPED_DICTIONARY(String, Variant::STRING)
+MAKE_TYPED_DICTIONARY(Vector2, Variant::VECTOR2)
+MAKE_TYPED_DICTIONARY(Vector2i, Variant::VECTOR2I)
+MAKE_TYPED_DICTIONARY(Rect2, Variant::RECT2)
+MAKE_TYPED_DICTIONARY(Rect2i, Variant::RECT2I)
+MAKE_TYPED_DICTIONARY(Vector3, Variant::VECTOR3)
+MAKE_TYPED_DICTIONARY(Vector3i, Variant::VECTOR3I)
+MAKE_TYPED_DICTIONARY(Transform2D, Variant::TRANSFORM2D)
+MAKE_TYPED_DICTIONARY(Plane, Variant::PLANE)
+MAKE_TYPED_DICTIONARY(Quaternion, Variant::QUATERNION)
+MAKE_TYPED_DICTIONARY(AABB, Variant::AABB)
+MAKE_TYPED_DICTIONARY(Basis, Variant::BASIS)
+MAKE_TYPED_DICTIONARY(Transform3D, Variant::TRANSFORM3D)
+MAKE_TYPED_DICTIONARY(Color, Variant::COLOR)
+MAKE_TYPED_DICTIONARY(StringName, Variant::STRING_NAME)
+MAKE_TYPED_DICTIONARY(NodePath, Variant::NODE_PATH)
+MAKE_TYPED_DICTIONARY(RID, Variant::RID)
+MAKE_TYPED_DICTIONARY(Callable, Variant::CALLABLE)
+MAKE_TYPED_DICTIONARY(Signal, Variant::SIGNAL)
+MAKE_TYPED_DICTIONARY(Dictionary, Variant::DICTIONARY)
+MAKE_TYPED_DICTIONARY(Array, Variant::ARRAY)
+MAKE_TYPED_DICTIONARY(PackedByteArray, Variant::PACKED_BYTE_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedInt32Array, Variant::PACKED_INT32_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedInt64Array, Variant::PACKED_INT64_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedFloat32Array, Variant::PACKED_FLOAT32_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedFloat64Array, Variant::PACKED_FLOAT64_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedStringArray, Variant::PACKED_STRING_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedVector2Array, Variant::PACKED_VECTOR2_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedVector3Array, Variant::PACKED_VECTOR3_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedColorArray, Variant::PACKED_COLOR_ARRAY)
+MAKE_TYPED_DICTIONARY(PackedVector4Array, Variant::PACKED_VECTOR4_ARRAY)
+MAKE_TYPED_DICTIONARY(IPAddress, Variant::STRING)
+
+#undef MAKE_TYPED_DICTIONARY
+#undef MAKE_TYPED_DICTIONARY_NIL
+#undef MAKE_TYPED_DICTIONARY_EXPANDED
+#undef MAKE_TYPED_DICTIONARY_WITH_OBJECT
+
+#endif // TYPED_DICTIONARY_H
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index 83f1f981b3..2da94de875 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -2254,6 +2254,7 @@ static void _register_variant_builtin_methods_misc() {
bind_method(Dictionary, size, sarray(), varray());
bind_method(Dictionary, is_empty, sarray(), varray());
bind_method(Dictionary, clear, sarray(), varray());
+ bind_method(Dictionary, assign, sarray("dictionary"), varray());
bind_method(Dictionary, merge, sarray("dictionary", "overwrite"), varray(false));
bind_method(Dictionary, merged, sarray("dictionary", "overwrite"), varray(false));
bind_method(Dictionary, has, sarray("key"), varray());
@@ -2266,6 +2267,18 @@ static void _register_variant_builtin_methods_misc() {
bind_method(Dictionary, duplicate, sarray("deep"), varray(false));
bind_method(Dictionary, get, sarray("key", "default"), varray(Variant()));
bind_method(Dictionary, get_or_add, sarray("key", "default"), varray(Variant()));
+ bind_method(Dictionary, is_typed, sarray(), varray());
+ bind_method(Dictionary, is_typed_key, sarray(), varray());
+ bind_method(Dictionary, is_typed_value, sarray(), varray());
+ bind_method(Dictionary, is_same_typed, sarray("dictionary"), varray());
+ bind_method(Dictionary, is_same_typed_key, sarray("dictionary"), varray());
+ bind_method(Dictionary, is_same_typed_value, sarray("dictionary"), varray());
+ bind_method(Dictionary, get_typed_key_builtin, sarray(), varray());
+ bind_method(Dictionary, get_typed_value_builtin, sarray(), varray());
+ bind_method(Dictionary, get_typed_key_class_name, sarray(), varray());
+ bind_method(Dictionary, get_typed_value_class_name, sarray(), varray());
+ bind_method(Dictionary, get_typed_key_script, sarray(), varray());
+ bind_method(Dictionary, get_typed_value_script, sarray(), varray());
bind_method(Dictionary, make_read_only, sarray(), varray());
bind_method(Dictionary, is_read_only, sarray(), varray());
bind_method(Dictionary, recursive_equal, sarray("dictionary", "recursion_count"), varray());
diff --git a/core/variant/variant_construct.cpp b/core/variant/variant_construct.cpp
index 1edae407c2..fb75a874e7 100644
--- a/core/variant/variant_construct.cpp
+++ b/core/variant/variant_construct.cpp
@@ -198,6 +198,7 @@ void Variant::_register_variant_constructors() {
add_constructor<VariantConstructNoArgs<Dictionary>>(sarray());
add_constructor<VariantConstructor<Dictionary, Dictionary>>(sarray("from"));
+ add_constructor<VariantConstructorTypedDictionary>(sarray("base", "key_type", "key_class_name", "key_script", "value_type", "value_class_name", "value_script"));
add_constructor<VariantConstructNoArgs<Array>>(sarray());
add_constructor<VariantConstructor<Array, Array>>(sarray("from"));
diff --git a/core/variant/variant_construct.h b/core/variant/variant_construct.h
index a46fadb198..68210a9451 100644
--- a/core/variant/variant_construct.h
+++ b/core/variant/variant_construct.h
@@ -400,6 +400,112 @@ public:
}
};
+class VariantConstructorTypedDictionary {
+public:
+ static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) {
+ if (p_args[0]->get_type() != Variant::DICTIONARY) {
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 0;
+ r_error.expected = Variant::DICTIONARY;
+ return;
+ }
+
+ if (p_args[1]->get_type() != Variant::INT) {
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 1;
+ r_error.expected = Variant::INT;
+ return;
+ }
+
+ if (p_args[2]->get_type() != Variant::STRING_NAME) {
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 2;
+ r_error.expected = Variant::STRING_NAME;
+ return;
+ }
+
+ if (p_args[4]->get_type() != Variant::INT) {
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 4;
+ r_error.expected = Variant::INT;
+ return;
+ }
+
+ if (p_args[5]->get_type() != Variant::STRING_NAME) {
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 5;
+ r_error.expected = Variant::STRING_NAME;
+ return;
+ }
+
+ const Dictionary &base_dict = *VariantGetInternalPtr<Dictionary>::get_ptr(p_args[0]);
+ const uint32_t key_type = p_args[1]->operator uint32_t();
+ const StringName &key_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[2]);
+ const uint32_t value_type = p_args[4]->operator uint32_t();
+ const StringName &value_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[5]);
+ r_ret = Dictionary(base_dict, key_type, key_class_name, *p_args[3], value_type, value_class_name, *p_args[6]);
+ }
+
+ static inline void validated_construct(Variant *r_ret, const Variant **p_args) {
+ const Dictionary &base_dict = *VariantGetInternalPtr<Dictionary>::get_ptr(p_args[0]);
+ const uint32_t key_type = p_args[1]->operator uint32_t();
+ const StringName &key_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[2]);
+ const uint32_t value_type = p_args[4]->operator uint32_t();
+ const StringName &value_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[5]);
+ *r_ret = Dictionary(base_dict, key_type, key_class_name, *p_args[3], value_type, value_class_name, *p_args[6]);
+ }
+
+ static void ptr_construct(void *base, const void **p_args) {
+ const Dictionary &base_dict = PtrToArg<Dictionary>::convert(p_args[0]);
+ const uint32_t key_type = PtrToArg<uint32_t>::convert(p_args[1]);
+ const StringName &key_class_name = PtrToArg<StringName>::convert(p_args[2]);
+ const Variant &key_script = PtrToArg<Variant>::convert(p_args[3]);
+ const uint32_t value_type = PtrToArg<uint32_t>::convert(p_args[4]);
+ const StringName &value_class_name = PtrToArg<StringName>::convert(p_args[5]);
+ const Variant &value_script = PtrToArg<Variant>::convert(p_args[6]);
+ Dictionary dst_arr = Dictionary(base_dict, key_type, key_class_name, key_script, value_type, value_class_name, value_script);
+
+ PtrConstruct<Dictionary>::construct(dst_arr, base);
+ }
+
+ static int get_argument_count() {
+ return 7;
+ }
+
+ static Variant::Type get_argument_type(int p_arg) {
+ switch (p_arg) {
+ case 0: {
+ return Variant::DICTIONARY;
+ } break;
+ case 1: {
+ return Variant::INT;
+ } break;
+ case 2: {
+ return Variant::STRING_NAME;
+ } break;
+ case 3: {
+ return Variant::NIL;
+ } break;
+ case 4: {
+ return Variant::INT;
+ } break;
+ case 5: {
+ return Variant::STRING_NAME;
+ } break;
+ case 6: {
+ return Variant::NIL;
+ } break;
+ default: {
+ return Variant::NIL;
+ } break;
+ }
+ }
+
+ static Variant::Type get_base_type() {
+ return Variant::DICTIONARY;
+ }
+};
+
class VariantConstructorTypedArray {
public:
static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) {
diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp
index 9a0dd712ed..f5f96456d3 100644
--- a/core/variant/variant_parser.cpp
+++ b/core/variant/variant_parser.cpp
@@ -1140,6 +1140,146 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream,
return ERR_PARSE_ERROR;
}
}
+ } else if (id == "Dictionary") {
+ Error err = OK;
+
+ get_token(p_stream, token, line, r_err_str);
+ if (token.type != TK_BRACKET_OPEN) {
+ r_err_str = "Expected '['";
+ return ERR_PARSE_ERROR;
+ }
+
+ get_token(p_stream, token, line, r_err_str);
+ if (token.type != TK_IDENTIFIER) {
+ r_err_str = "Expected type identifier for key";
+ return ERR_PARSE_ERROR;
+ }
+
+ static HashMap<StringName, Variant::Type> builtin_types;
+ if (builtin_types.is_empty()) {
+ for (int i = 1; i < Variant::VARIANT_MAX; i++) {
+ builtin_types[Variant::get_type_name((Variant::Type)i)] = (Variant::Type)i;
+ }
+ }
+
+ Dictionary dict;
+ Variant::Type key_type = Variant::NIL;
+ StringName key_class_name;
+ Variant key_script;
+ bool got_comma_token = false;
+ if (builtin_types.has(token.value)) {
+ key_type = builtin_types.get(token.value);
+ } else if (token.value == "Resource" || token.value == "SubResource" || token.value == "ExtResource") {
+ Variant resource;
+ err = parse_value(token, resource, p_stream, line, r_err_str, p_res_parser);
+ if (err) {
+ if (token.value == "Resource" && err == ERR_PARSE_ERROR && r_err_str == "Expected '('" && token.type == TK_COMMA) {
+ err = OK;
+ r_err_str = String();
+ key_type = Variant::OBJECT;
+ key_class_name = token.value;
+ got_comma_token = true;
+ } else {
+ return err;
+ }
+ } else {
+ Ref<Script> script = resource;
+ if (script.is_valid() && script->is_valid()) {
+ key_type = Variant::OBJECT;
+ key_class_name = script->get_instance_base_type();
+ key_script = script;
+ }
+ }
+ } else if (ClassDB::class_exists(token.value)) {
+ key_type = Variant::OBJECT;
+ key_class_name = token.value;
+ }
+
+ if (!got_comma_token) {
+ get_token(p_stream, token, line, r_err_str);
+ if (token.type != TK_COMMA) {
+ r_err_str = "Expected ',' after key type";
+ return ERR_PARSE_ERROR;
+ }
+ }
+
+ get_token(p_stream, token, line, r_err_str);
+ if (token.type != TK_IDENTIFIER) {
+ r_err_str = "Expected type identifier for value";
+ return ERR_PARSE_ERROR;
+ }
+
+ Variant::Type value_type = Variant::NIL;
+ StringName value_class_name;
+ Variant value_script;
+ bool got_bracket_token = false;
+ if (builtin_types.has(token.value)) {
+ value_type = builtin_types.get(token.value);
+ } else if (token.value == "Resource" || token.value == "SubResource" || token.value == "ExtResource") {
+ Variant resource;
+ err = parse_value(token, resource, p_stream, line, r_err_str, p_res_parser);
+ if (err) {
+ if (token.value == "Resource" && err == ERR_PARSE_ERROR && r_err_str == "Expected '('" && token.type == TK_BRACKET_CLOSE) {
+ err = OK;
+ r_err_str = String();
+ value_type = Variant::OBJECT;
+ value_class_name = token.value;
+ got_comma_token = true;
+ } else {
+ return err;
+ }
+ } else {
+ Ref<Script> script = resource;
+ if (script.is_valid() && script->is_valid()) {
+ value_type = Variant::OBJECT;
+ value_class_name = script->get_instance_base_type();
+ value_script = script;
+ }
+ }
+ } else if (ClassDB::class_exists(token.value)) {
+ value_type = Variant::OBJECT;
+ value_class_name = token.value;
+ }
+
+ if (key_type != Variant::NIL || value_type != Variant::NIL) {
+ dict.set_typed(key_type, key_class_name, key_script, value_type, value_class_name, value_script);
+ }
+
+ if (!got_bracket_token) {
+ get_token(p_stream, token, line, r_err_str);
+ if (token.type != TK_BRACKET_CLOSE) {
+ r_err_str = "Expected ']'";
+ return ERR_PARSE_ERROR;
+ }
+ }
+
+ get_token(p_stream, token, line, r_err_str);
+ if (token.type != TK_PARENTHESIS_OPEN) {
+ r_err_str = "Expected '('";
+ return ERR_PARSE_ERROR;
+ }
+
+ get_token(p_stream, token, line, r_err_str);
+ if (token.type != TK_CURLY_BRACKET_OPEN) {
+ r_err_str = "Expected '{'";
+ return ERR_PARSE_ERROR;
+ }
+
+ Dictionary values;
+ err = _parse_dictionary(values, p_stream, line, r_err_str, p_res_parser);
+ if (err) {
+ return err;
+ }
+
+ get_token(p_stream, token, line, r_err_str);
+ if (token.type != TK_PARENTHESIS_CLOSE) {
+ r_err_str = "Expected ')'";
+ return ERR_PARSE_ERROR;
+ }
+
+ dict.assign(values);
+
+ value = dict;
} else if (id == "Array") {
Error err = OK;
@@ -2036,40 +2176,109 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
case Variant::DICTIONARY: {
Dictionary dict = p_variant;
+
+ if (dict.is_typed()) {
+ p_store_string_func(p_store_string_ud, "Dictionary[");
+
+ Variant::Type key_builtin_type = (Variant::Type)dict.get_typed_key_builtin();
+ StringName key_class_name = dict.get_typed_key_class_name();
+ Ref<Script> key_script = dict.get_typed_key_script();
+
+ if (key_script.is_valid()) {
+ String resource_text;
+ if (p_encode_res_func) {
+ resource_text = p_encode_res_func(p_encode_res_ud, key_script);
+ }
+ if (resource_text.is_empty() && key_script->get_path().is_resource_file()) {
+ resource_text = "Resource(\"" + key_script->get_path() + "\")";
+ }
+
+ if (!resource_text.is_empty()) {
+ p_store_string_func(p_store_string_ud, resource_text);
+ } else {
+ ERR_PRINT("Failed to encode a path to a custom script for a dictionary key type.");
+ p_store_string_func(p_store_string_ud, key_class_name);
+ }
+ } else if (key_class_name != StringName()) {
+ p_store_string_func(p_store_string_ud, key_class_name);
+ } else if (key_builtin_type == Variant::NIL) {
+ p_store_string_func(p_store_string_ud, "Variant");
+ } else {
+ p_store_string_func(p_store_string_ud, Variant::get_type_name(key_builtin_type));
+ }
+
+ p_store_string_func(p_store_string_ud, ", ");
+
+ Variant::Type value_builtin_type = (Variant::Type)dict.get_typed_value_builtin();
+ StringName value_class_name = dict.get_typed_value_class_name();
+ Ref<Script> value_script = dict.get_typed_value_script();
+
+ if (value_script.is_valid()) {
+ String resource_text;
+ if (p_encode_res_func) {
+ resource_text = p_encode_res_func(p_encode_res_ud, value_script);
+ }
+ if (resource_text.is_empty() && value_script->get_path().is_resource_file()) {
+ resource_text = "Resource(\"" + value_script->get_path() + "\")";
+ }
+
+ if (!resource_text.is_empty()) {
+ p_store_string_func(p_store_string_ud, resource_text);
+ } else {
+ ERR_PRINT("Failed to encode a path to a custom script for a dictionary value type.");
+ p_store_string_func(p_store_string_ud, value_class_name);
+ }
+ } else if (value_class_name != StringName()) {
+ p_store_string_func(p_store_string_ud, value_class_name);
+ } else if (value_builtin_type == Variant::NIL) {
+ p_store_string_func(p_store_string_ud, "Variant");
+ } else {
+ p_store_string_func(p_store_string_ud, Variant::get_type_name(value_builtin_type));
+ }
+
+ p_store_string_func(p_store_string_ud, "](");
+ }
+
if (unlikely(p_recursion_count > MAX_RECURSION)) {
ERR_PRINT("Max recursion reached");
p_store_string_func(p_store_string_ud, "{}");
} else {
- p_recursion_count++;
-
List<Variant> keys;
dict.get_key_list(&keys);
keys.sort();
- if (keys.is_empty()) { // Avoid unnecessary line break.
+ if (keys.is_empty()) {
+ // Avoid unnecessary line break.
p_store_string_func(p_store_string_ud, "{}");
- break;
- }
+ } else {
+ p_recursion_count++;
- p_store_string_func(p_store_string_ud, "{\n");
- for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
- write(E->get(), p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat);
- p_store_string_func(p_store_string_ud, ": ");
- write(dict[E->get()], p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat);
- if (E->next()) {
- p_store_string_func(p_store_string_ud, ",\n");
- } else {
- p_store_string_func(p_store_string_ud, "\n");
+ p_store_string_func(p_store_string_ud, "{\n");
+
+ for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
+ write(E->get(), p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat);
+ p_store_string_func(p_store_string_ud, ": ");
+ write(dict[E->get()], p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat);
+ if (E->next()) {
+ p_store_string_func(p_store_string_ud, ",\n");
+ } else {
+ p_store_string_func(p_store_string_ud, "\n");
+ }
}
+
+ p_store_string_func(p_store_string_ud, "}");
}
+ }
- p_store_string_func(p_store_string_ud, "}");
+ if (dict.is_typed()) {
+ p_store_string_func(p_store_string_ud, ")");
}
} break;
case Variant::ARRAY: {
Array array = p_variant;
- if (array.get_typed_builtin() != Variant::NIL) {
+
+ if (array.is_typed()) {
p_store_string_func(p_store_string_ud, "Array[");
Variant::Type builtin_type = (Variant::Type)array.get_typed_builtin();
@@ -2107,6 +2316,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
p_recursion_count++;
p_store_string_func(p_store_string_ud, "[");
+
bool first = true;
for (const Variant &var : array) {
if (first) {
@@ -2120,7 +2330,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
p_store_string_func(p_store_string_ud, "]");
}
- if (array.get_typed_builtin() != Variant::NIL) {
+ if (array.is_typed()) {
p_store_string_func(p_store_string_ud, ")");
}
} break;
diff --git a/core/variant/variant_setget.cpp b/core/variant/variant_setget.cpp
index 0cd4b86fe7..b60ff83cf1 100644
--- a/core/variant/variant_setget.cpp
+++ b/core/variant/variant_setget.cpp
@@ -703,6 +703,50 @@ struct VariantIndexedSetGet_Array {
static uint64_t get_indexed_size(const Variant *base) { return 0; }
};
+struct VariantIndexedSetGet_Dictionary {
+ static void get(const Variant *base, int64_t index, Variant *value, bool *oob) {
+ const Variant *ptr = VariantGetInternalPtr<Dictionary>::get_ptr(base)->getptr(index);
+ if (!ptr) {
+ *oob = true;
+ return;
+ }
+ *value = *ptr;
+ *oob = false;
+ }
+ static void ptr_get(const void *base, int64_t index, void *member) {
+ // Avoid ptrconvert for performance.
+ const Dictionary &v = *reinterpret_cast<const Dictionary *>(base);
+ const Variant *ptr = v.getptr(index);
+ NULL_TEST(ptr);
+ PtrToArg<Variant>::encode(*ptr, member);
+ }
+ static void set(Variant *base, int64_t index, const Variant *value, bool *valid, bool *oob) {
+ if (VariantGetInternalPtr<Dictionary>::get_ptr(base)->is_read_only()) {
+ *valid = false;
+ *oob = true;
+ return;
+ }
+ (*VariantGetInternalPtr<Dictionary>::get_ptr(base))[index] = *value;
+ *oob = false;
+ *valid = true;
+ }
+ static void validated_set(Variant *base, int64_t index, const Variant *value, bool *oob) {
+ if (VariantGetInternalPtr<Dictionary>::get_ptr(base)->is_read_only()) {
+ *oob = true;
+ return;
+ }
+ (*VariantGetInternalPtr<Dictionary>::get_ptr(base))[index] = *value;
+ *oob = false;
+ }
+ static void ptr_set(void *base, int64_t index, const void *member) {
+ Dictionary &v = *reinterpret_cast<Dictionary *>(base);
+ v[index] = PtrToArg<Variant>::convert(member);
+ }
+ static Variant::Type get_index_type() { return Variant::NIL; }
+ static uint32_t get_index_usage() { return PROPERTY_USAGE_DEFAULT; }
+ static uint64_t get_indexed_size(const Variant *base) { return VariantGetInternalPtr<Dictionary>::get_ptr(base)->size(); }
+};
+
struct VariantIndexedSetGet_String {
static void get(const Variant *base, int64_t index, Variant *value, bool *oob) {
int64_t length = VariantGetInternalPtr<String>::get_ptr(base)->length();
@@ -789,51 +833,6 @@ struct VariantIndexedSetGet_String {
static uint64_t get_indexed_size(const Variant *base) { return VariantInternal::get_string(base)->length(); }
};
-#define INDEXED_SETGET_STRUCT_DICT(m_base_type) \
- struct VariantIndexedSetGet_##m_base_type { \
- static void get(const Variant *base, int64_t index, Variant *value, bool *oob) { \
- const Variant *ptr = VariantGetInternalPtr<m_base_type>::get_ptr(base)->getptr(index); \
- if (!ptr) { \
- *oob = true; \
- return; \
- } \
- *value = *ptr; \
- *oob = false; \
- } \
- static void ptr_get(const void *base, int64_t index, void *member) { \
- /* avoid ptrconvert for performance*/ \
- const m_base_type &v = *reinterpret_cast<const m_base_type *>(base); \
- const Variant *ptr = v.getptr(index); \
- NULL_TEST(ptr); \
- PtrToArg<Variant>::encode(*ptr, member); \
- } \
- static void set(Variant *base, int64_t index, const Variant *value, bool *valid, bool *oob) { \
- if (VariantGetInternalPtr<m_base_type>::get_ptr(base)->is_read_only()) { \
- *valid = false; \
- *oob = true; \
- return; \
- } \
- (*VariantGetInternalPtr<m_base_type>::get_ptr(base))[index] = *value; \
- *oob = false; \
- *valid = true; \
- } \
- static void validated_set(Variant *base, int64_t index, const Variant *value, bool *oob) { \
- if (VariantGetInternalPtr<m_base_type>::get_ptr(base)->is_read_only()) { \
- *oob = true; \
- return; \
- } \
- (*VariantGetInternalPtr<m_base_type>::get_ptr(base))[index] = *value; \
- *oob = false; \
- } \
- static void ptr_set(void *base, int64_t index, const void *member) { \
- m_base_type &v = *reinterpret_cast<m_base_type *>(base); \
- v[index] = PtrToArg<Variant>::convert(member); \
- } \
- static Variant::Type get_index_type() { return Variant::NIL; } \
- static uint32_t get_index_usage() { return PROPERTY_USAGE_DEFAULT; } \
- static uint64_t get_indexed_size(const Variant *base) { return VariantGetInternalPtr<m_base_type>::get_ptr(base)->size(); } \
- };
-
INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector2, double, real_t, 2)
INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector2i, int64_t, int32_t, 2)
INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector3, double, real_t, 3)
@@ -858,8 +857,6 @@ INDEXED_SETGET_STRUCT_TYPED(PackedStringArray, String)
INDEXED_SETGET_STRUCT_TYPED(PackedColorArray, Color)
INDEXED_SETGET_STRUCT_TYPED(PackedVector4Array, Vector4)
-INDEXED_SETGET_STRUCT_DICT(Dictionary)
-
struct VariantIndexedSetterGetterInfo {
void (*setter)(Variant *base, int64_t index, const Variant *value, bool *valid, bool *oob) = nullptr;
void (*getter)(const Variant *base, int64_t index, Variant *value, bool *oob) = nullptr;
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index ba448534b1..f222cbc969 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -2915,6 +2915,9 @@
<constant name="PROPERTY_HINT_ARRAY_TYPE" value="31" enum="PropertyHint">
Hints that a property is an [Array] with the stored type specified in the hint string.
</constant>
+ <constant name="PROPERTY_HINT_DICTIONARY_TYPE" value="38" enum="PropertyHint">
+ Hints that a property is a [Dictionary] with the stored types specified in the hint string.
+ </constant>
<constant name="PROPERTY_HINT_LOCALE_ID" value="32" enum="PropertyHint">
Hints that a string property is a locale code. Editing it will show a locale dialog for picking language and country.
</constant>
@@ -2930,7 +2933,7 @@
<constant name="PROPERTY_HINT_PASSWORD" value="36" enum="PropertyHint">
Hints that a string property is a password, and every character is replaced with the secret character.
</constant>
- <constant name="PROPERTY_HINT_MAX" value="38" enum="PropertyHint">
+ <constant name="PROPERTY_HINT_MAX" value="39" enum="PropertyHint">
Represents the size of the [enum PropertyHint] enum.
</constant>
<constant name="PROPERTY_USAGE_NONE" value="0" enum="PropertyUsageFlags" is_bitfield="true">
diff --git a/doc/classes/CompositorEffect.xml b/doc/classes/CompositorEffect.xml
index 76a3887918..9ac54edb11 100644
--- a/doc/classes/CompositorEffect.xml
+++ b/doc/classes/CompositorEffect.xml
@@ -57,6 +57,17 @@
var render_scene_buffers : RenderSceneBuffersRD = render_data.get_render_scene_buffers()
var roughness_buffer = render_scene_buffers.get_texture("forward_clustered", "normal_roughness")
[/codeblock]
+ The raw normal and roughness buffer is stored in an optimized format, different than the one available in Spatial shaders. When sampling the buffer, a conversion function must be applied. Use this function, copied from [url=https://github.com/godotengine/godot/blob/da5f39889f155658cef7f7ec3cc1abb94e17d815/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl#L334-L341]here[/url]:
+ [codeblock]
+ vec4 normal_roughness_compatibility(vec4 p_normal_roughness) {
+ float roughness = p_normal_roughness.w;
+ if (roughness &gt; 0.5) {
+ roughness = 1.0 - roughness;
+ }
+ roughness /= (127.0 / 255.0);
+ return vec4(normalize(p_normal_roughness.xyz * 2.0 - 1.0) * 0.5 + 0.5, roughness);
+ }
+ [/codeblock]
</member>
<member name="needs_separate_specular" type="bool" setter="set_needs_separate_specular" getter="get_needs_separate_specular">
If [code]true[/code] this triggers specular data being rendered to a separate buffer and combined after effects have been applied, only applicable for the Forward+ renderer.
diff --git a/doc/classes/Dictionary.xml b/doc/classes/Dictionary.xml
index b8b4fc7b08..feb2a07e4a 100644
--- a/doc/classes/Dictionary.xml
+++ b/doc/classes/Dictionary.xml
@@ -150,6 +150,19 @@
</constructor>
<constructor name="Dictionary">
<return type="Dictionary" />
+ <param index="0" name="base" type="Dictionary" />
+ <param index="1" name="key_type" type="int" />
+ <param index="2" name="key_class_name" type="StringName" />
+ <param index="3" name="key_script" type="Variant" />
+ <param index="4" name="value_type" type="int" />
+ <param index="5" name="value_class_name" type="StringName" />
+ <param index="6" name="value_script" type="Variant" />
+ <description>
+ Creates a typed dictionary from the [param base] dictionary. A typed dictionary can only contain keys and values of the given types, or that inherit from the given classes, as described by this constructor's parameters.
+ </description>
+ </constructor>
+ <constructor name="Dictionary">
+ <return type="Dictionary" />
<param index="0" name="from" type="Dictionary" />
<description>
Returns the same dictionary as [param from]. If you need a copy of the dictionary, use [method duplicate].
@@ -157,6 +170,13 @@
</constructor>
</constructors>
<methods>
+ <method name="assign">
+ <return type="void" />
+ <param index="0" name="dictionary" type="Dictionary" />
+ <description>
+ Assigns elements of another [param dictionary] into the dictionary. Resizes the dictionary to match [param dictionary]. Performs type conversions if the dictionary is typed.
+ </description>
+ </method>
<method name="clear">
<return type="void" />
<description>
@@ -202,6 +222,42 @@
Gets a value and ensures the key is set. If the [param key] exists in the dictionary, this behaves like [method get]. Otherwise, the [param default] value is inserted into the dictionary and returned.
</description>
</method>
+ <method name="get_typed_key_builtin" qualifiers="const">
+ <return type="int" />
+ <description>
+ Returns the built-in [Variant] type of the typed dictionary's keys as a [enum Variant.Type] constant. If the keys are not typed, returns [constant TYPE_NIL]. See also [method is_typed_key].
+ </description>
+ </method>
+ <method name="get_typed_key_class_name" qualifiers="const">
+ <return type="StringName" />
+ <description>
+ Returns the [b]built-in[/b] class name of the typed dictionary's keys, if the built-in [Variant] type is [constant TYPE_OBJECT]. Otherwise, returns an empty [StringName]. See also [method is_typed_key] and [method Object.get_class].
+ </description>
+ </method>
+ <method name="get_typed_key_script" qualifiers="const">
+ <return type="Variant" />
+ <description>
+ Returns the [Script] instance associated with this typed dictionary's keys, or [code]null[/code] if it does not exist. See also [method is_typed_key].
+ </description>
+ </method>
+ <method name="get_typed_value_builtin" qualifiers="const">
+ <return type="int" />
+ <description>
+ Returns the built-in [Variant] type of the typed dictionary's values as a [enum Variant.Type] constant. If the values are not typed, returns [constant TYPE_NIL]. See also [method is_typed_value].
+ </description>
+ </method>
+ <method name="get_typed_value_class_name" qualifiers="const">
+ <return type="StringName" />
+ <description>
+ Returns the [b]built-in[/b] class name of the typed dictionary's values, if the built-in [Variant] type is [constant TYPE_OBJECT]. Otherwise, returns an empty [StringName]. See also [method is_typed_value] and [method Object.get_class].
+ </description>
+ </method>
+ <method name="get_typed_value_script" qualifiers="const">
+ <return type="Variant" />
+ <description>
+ Returns the [Script] instance associated with this typed dictionary's values, or [code]null[/code] if it does not exist. See also [method is_typed_value].
+ </description>
+ </method>
<method name="has" qualifiers="const">
<return type="bool" />
<param index="0" name="key" type="Variant" />
@@ -284,6 +340,45 @@
Returns [code]true[/code] if the dictionary is read-only. See [method make_read_only]. Dictionaries are automatically read-only if declared with [code]const[/code] keyword.
</description>
</method>
+ <method name="is_same_typed" qualifiers="const">
+ <return type="bool" />
+ <param index="0" name="dictionary" type="Dictionary" />
+ <description>
+ Returns [code]true[/code] if the dictionary is typed the same as [param dictionary].
+ </description>
+ </method>
+ <method name="is_same_typed_key" qualifiers="const">
+ <return type="bool" />
+ <param index="0" name="dictionary" type="Dictionary" />
+ <description>
+ Returns [code]true[/code] if the dictionary's keys are typed the same as [param dictionary]'s keys.
+ </description>
+ </method>
+ <method name="is_same_typed_value" qualifiers="const">
+ <return type="bool" />
+ <param index="0" name="dictionary" type="Dictionary" />
+ <description>
+ Returns [code]true[/code] if the dictionary's values are typed the same as [param dictionary]'s values.
+ </description>
+ </method>
+ <method name="is_typed" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if the dictionary is typed. Typed dictionaries can only store keys/values of their associated type and provide type safety for the [code][][/code] operator. Methods of typed dictionary still return [Variant].
+ </description>
+ </method>
+ <method name="is_typed_key" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if the dictionary's keys are typed.
+ </description>
+ </method>
+ <method name="is_typed_value" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if the dictionary's values are typed.
+ </description>
+ </method>
<method name="keys" qualifiers="const">
<return type="Array" />
<description>
diff --git a/doc/classes/EditorContextMenuPlugin.xml b/doc/classes/EditorContextMenuPlugin.xml
index 7eeee3d7fd..71c4ca0f9b 100644
--- a/doc/classes/EditorContextMenuPlugin.xml
+++ b/doc/classes/EditorContextMenuPlugin.xml
@@ -14,7 +14,7 @@
<return type="void" />
<param index="0" name="paths" type="PackedStringArray" />
<description>
- Called when creating a context menu, custom options can be added by using the [method add_context_menu_item] function.
+ Called when creating a context menu, custom options can be added by using the [method add_context_menu_item] or [method add_context_menu_item_from_shortcut] functions. [param paths] contains currently selected paths (depending on menu), which can be used to conditionally add options.
</description>
</method>
<method name="add_context_menu_item">
@@ -22,14 +22,29 @@
<param index="0" name="name" type="String" />
<param index="1" name="callback" type="Callable" />
<param index="2" name="icon" type="Texture2D" default="null" />
- <param index="3" name="shortcut" type="Shortcut" default="null" />
<description>
- Add custom options to the context menu of the currently specified slot.
- To trigger a [param shortcut] before the context menu is created, please additionally call the [method add_menu_shortcut] function.
+ Add custom option to the context menu of the plugin's specified slot. When the option is activated, [param callback] will be called. Callback should take single [Array] argument; array contents depend on context menu slot.
[codeblock]
func _popup_menu(paths):
add_context_menu_item("File Custom options", handle, ICON)
[/codeblock]
+ If you want to assign shortcut to the menu item, use [method add_context_menu_item_from_shortcut] instead.
+ </description>
+ </method>
+ <method name="add_context_menu_item_from_shortcut">
+ <return type="void" />
+ <param index="0" name="name" type="String" />
+ <param index="1" name="shortcut" type="Shortcut" />
+ <param index="2" name="icon" type="Texture2D" default="null" />
+ <description>
+ Add custom option to the context menu of the plugin's specified slot. The option will have the [param shortcut] assigned and reuse its callback. The shortcut has to be registered beforehand with [method add_menu_shortcut].
+ [codeblock]
+ func _init():
+ add_menu_shortcut(SHORTCUT, handle)
+
+ func _popup_menu(paths):
+ add_context_menu_item_from_shortcut("File Custom options", SHORTCUT, ICON)
+ [/codeblock]
</description>
</method>
<method name="add_menu_shortcut">
@@ -37,13 +52,26 @@
<param index="0" name="shortcut" type="Shortcut" />
<param index="1" name="callback" type="Callable" />
<description>
- To register the shortcut for the context menu, call this function within the [method Object._init] function, even if the context menu has not been created yet.
- Note that this method should only be invoked from [method Object._init]; otherwise, the shortcut will not be registered correctly.
+ Registers a shortcut associated with the plugin's context menu. This method should be called once (e.g. in plugin's [method Object._init]). [param callback] will be called when user presses the specified [param shortcut] while the menu's context is in effect (e.g. FileSystem dock is focused). Callback should take single [Array] argument; array contents depend on context menu slot.
[codeblock]
func _init():
- add_menu_shortcut(SHORTCUT, handle);
+ add_menu_shortcut(SHORTCUT, handle)
[/codeblock]
</description>
</method>
</methods>
+ <constants>
+ <constant name="CONTEXT_SLOT_SCENE_TREE" value="0" enum="ContextMenuSlot">
+ Context menu of Scene dock. [method _popup_menu] will be called with a list of paths to currently selected nodes, while option callback will receive the list of currently selected nodes.
+ </constant>
+ <constant name="CONTEXT_SLOT_FILESYSTEM" value="1" enum="ContextMenuSlot">
+ Context menu of FileSystem dock. [method _popup_menu] and option callback will be called with list of paths of the currently selected files.
+ </constant>
+ <constant name="CONTEXT_SLOT_FILESYSTEM_CREATE" value="3" enum="ContextMenuSlot">
+ The "Create..." submenu of FileSystem dock's context menu. [method _popup_menu] and option callback will be called with list of paths of the currently selected files.
+ </constant>
+ <constant name="CONTEXT_SLOT_SCRIPT_EDITOR" value="2" enum="ContextMenuSlot">
+ Context menu of Scene dock. [method _popup_menu] will be called with the path to the currently edited script, while option callback will receive reference to that script.
+ </constant>
+ </constants>
</class>
diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml
index b3191e5378..de49764f0d 100644
--- a/doc/classes/EditorPlugin.xml
+++ b/doc/classes/EditorPlugin.xml
@@ -403,11 +403,11 @@
</method>
<method name="add_context_menu_plugin">
<return type="void" />
- <param index="0" name="slot" type="int" enum="EditorPlugin.ContextMenuSlot" />
+ <param index="0" name="slot" type="int" enum="EditorContextMenuPlugin.ContextMenuSlot" />
<param index="1" name="plugin" type="EditorContextMenuPlugin" />
<description>
- Adds a plugin to the context menu. [param slot] is the position in the context menu where the plugin will be added.
- Context menus are supported for three commonly used areas: the file system, scene tree, and editor script list panel.
+ Adds a plugin to the context menu. [param slot] is the context menu where the plugin will be added.
+ See [enum EditorContextMenuPlugin.ContextMenuSlot] for available context menus. A plugin instance can belong only to a single context menu slot.
</description>
</method>
<method name="add_control_to_bottom_panel">
@@ -635,10 +635,9 @@
</method>
<method name="remove_context_menu_plugin">
<return type="void" />
- <param index="0" name="slot" type="int" enum="EditorPlugin.ContextMenuSlot" />
- <param index="1" name="plugin" type="EditorContextMenuPlugin" />
+ <param index="0" name="plugin" type="EditorContextMenuPlugin" />
<description>
- Removes a context menu plugin from the specified slot.
+ Removes the specified context menu plugin.
</description>
</method>
<method name="remove_control_from_bottom_panel">
@@ -891,17 +890,5 @@
<constant name="AFTER_GUI_INPUT_CUSTOM" value="2" enum="AfterGUIInput">
Pass the [InputEvent] to other editor plugins except the main [Node3D] one. This can be used to prevent node selection changes and work with sub-gizmos instead.
</constant>
- <constant name="CONTEXT_SLOT_SCENE_TREE" value="0" enum="ContextMenuSlot">
- Context menu slot for the SceneTree.
- </constant>
- <constant name="CONTEXT_SLOT_FILESYSTEM" value="1" enum="ContextMenuSlot">
- Context menu slot for the FileSystem.
- </constant>
- <constant name="CONTEXT_SLOT_SCRIPT_EDITOR" value="2" enum="ContextMenuSlot">
- Context menu slot for the ScriptEditor file list.
- </constant>
- <constant name="CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE" value="3" enum="ContextMenuSlot">
- Context menu slot for the FileSystem create submenu.
- </constant>
</constants>
</class>
diff --git a/doc/classes/LineEdit.xml b/doc/classes/LineEdit.xml
index f938460c2f..a37dd47914 100644
--- a/doc/classes/LineEdit.xml
+++ b/doc/classes/LineEdit.xml
@@ -4,7 +4,14 @@
An input field for single-line text.
</brief_description>
<description>
- [LineEdit] provides an input field for editing a single line of text. It features many built-in shortcuts that are always available ([kbd]Ctrl[/kbd] here maps to [kbd]Cmd[/kbd] on macOS):
+ [LineEdit] provides an input field for editing a single line of text.
+ - When the [LineEdit] control is focused using the keyboard arrow keys, it will only gain focus and not enter edit mode.
+ - To enter edit mode, click on the control with the mouse or press the "ui_text_submit" action (default: [kbd]Enter[/kbd] or [kbd]Kp Enter[/kbd]).
+ - To exit edit mode, press "ui_text_submit" or "ui_cancel" (default: [kbd]Escape[/kbd]) actions.
+ - Check [method is_editing] and [signal editing_toggled] for more information.
+ [b]Important:[/b]
+ - Focusing the [LineEdit] with "ui_focus_next" (default: [kbd]Tab[/kbd]) or "ui_focus_prev" (default: [kbd]Shift + Tab[/kbd]) or [method Control.grab_focus] still enters edit mode (for compatibility).
+ [LineEdit] features many built-in shortcuts that are always available ([kbd]Ctrl[/kbd] here maps to [kbd]Cmd[/kbd] on macOS):
- [kbd]Ctrl + C[/kbd]: Copy
- [kbd]Ctrl + X[/kbd]: Cut
- [kbd]Ctrl + V[/kbd] or [kbd]Ctrl + Y[/kbd]: Paste/"yank"
@@ -139,6 +146,12 @@
Inserts [param text] at the caret. If the resulting value is longer than [member max_length], nothing happens.
</description>
</method>
+ <method name="is_editing" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns whether the [LineEdit] is being edited.
+ </description>
+ </method>
<method name="is_menu_visible" qualifiers="const">
<return type="bool" />
<description>
@@ -301,6 +314,12 @@
</member>
</members>
<signals>
+ <signal name="editing_toggled">
+ <param index="0" name="toggled_on" type="bool" />
+ <description>
+ Emitted when the [LineEdit] switches in or out of edit mode.
+ </description>
+ </signal>
<signal name="text_change_rejected">
<param index="0" name="rejected_substring" type="String" />
<description>
diff --git a/doc/classes/MenuBar.xml b/doc/classes/MenuBar.xml
index 9e4287331c..bcf63f0610 100644
--- a/doc/classes/MenuBar.xml
+++ b/doc/classes/MenuBar.xml
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="MenuBar" inherits="Control" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
- A horizontal menu bar that creates a [MenuButton] for each [PopupMenu] child.
+ A horizontal menu bar that creates a menu for each [PopupMenu] child.
</brief_description>
<description>
- A horizontal menu bar that creates a [MenuButton] for each [PopupMenu] child. New items are created by adding [PopupMenu]s to this node.
+ A horizontal menu bar that creates a menu for each [PopupMenu] child. New items are created by adding [PopupMenu]s to this node.
</description>
<tutorials>
</tutorials>
@@ -105,9 +105,11 @@
</member>
<member name="prefer_global_menu" type="bool" setter="set_prefer_global_menu" getter="is_prefer_global_menu" default="true">
If [code]true[/code], [MenuBar] will use system global menu when supported.
+ [b]Note:[/b] If [code]true[/code] and global menu is supported, this node is not displayed, has zero size, and all its child nodes except [PopupMenu]s are inaccessible.
+ [b]Note:[/b] This property overrides the value of the [member PopupMenu.prefer_native_menu] property of the child nodes.
</member>
<member name="start_index" type="int" setter="set_start_index" getter="get_start_index" default="-1">
- Position in the global menu to insert first [MenuBar] item at.
+ Position order in the global menu to insert [MenuBar] items at. All menu items in the [MenuBar] are always inserted as a continuous range. Menus with lower [member start_index] are inserted first. Menus with [member start_index] equal to [code]-1[/code] are inserted last.
</member>
<member name="switch_on_hover" type="bool" setter="set_switch_on_hover" getter="is_switch_on_hover" default="true">
If [code]true[/code], when the cursor hovers above menu item, it will close the current [PopupMenu] and open the other one.
diff --git a/doc/classes/NavigationServer2D.xml b/doc/classes/NavigationServer2D.xml
index a0d03d7a01..7e78006240 100644
--- a/doc/classes/NavigationServer2D.xml
+++ b/doc/classes/NavigationServer2D.xml
@@ -486,7 +486,7 @@
<param index="0" name="map" type="RID" />
<param index="1" name="to_point" type="Vector2" />
<description>
- Returns the point closest to the provided [param to_point] on the navigation mesh surface.
+ Returns the navigation mesh surface point closest to the provided [param to_point] on the navigation [param map].
</description>
</method>
<method name="map_get_closest_point_owner" qualifiers="const">
@@ -494,7 +494,7 @@
<param index="0" name="map" type="RID" />
<param index="1" name="to_point" type="Vector2" />
<description>
- Returns the owner region RID for the point returned by [method map_get_closest_point].
+ Returns the owner region RID for the navigation mesh surface point closest to the provided [param to_point] on the navigation [param map].
</description>
</method>
<method name="map_get_edge_connection_margin" qualifiers="const">
@@ -768,6 +768,14 @@
Creates a new region.
</description>
</method>
+ <method name="region_get_closest_point" qualifiers="const">
+ <return type="Vector2" />
+ <param index="0" name="region" type="RID" />
+ <param index="1" name="to_point" type="Vector2" />
+ <description>
+ Returns the navigation mesh surface point closest to the provided [param to_point] on the navigation [param region].
+ </description>
+ </method>
<method name="region_get_connection_pathway_end" qualifiers="const">
<return type="Vector2" />
<param index="0" name="region" type="RID" />
diff --git a/doc/classes/NavigationServer3D.xml b/doc/classes/NavigationServer3D.xml
index 42f6235f8b..7e206046d6 100644
--- a/doc/classes/NavigationServer3D.xml
+++ b/doc/classes/NavigationServer3D.xml
@@ -532,7 +532,7 @@
<param index="0" name="map" type="RID" />
<param index="1" name="to_point" type="Vector3" />
<description>
- Returns the point closest to the provided [param to_point] on the navigation mesh surface.
+ Returns the navigation mesh surface point closest to the provided [param to_point] on the navigation [param map].
</description>
</method>
<method name="map_get_closest_point_normal" qualifiers="const">
@@ -540,7 +540,7 @@
<param index="0" name="map" type="RID" />
<param index="1" name="to_point" type="Vector3" />
<description>
- Returns the normal for the point returned by [method map_get_closest_point].
+ Returns the navigation mesh surface normal closest to the provided [param to_point] on the navigation [param map].
</description>
</method>
<method name="map_get_closest_point_owner" qualifiers="const">
@@ -548,7 +548,7 @@
<param index="0" name="map" type="RID" />
<param index="1" name="to_point" type="Vector3" />
<description>
- Returns the owner region RID for the point returned by [method map_get_closest_point].
+ Returns the owner region RID for the navigation mesh surface point closest to the provided [param to_point] on the navigation [param map].
</description>
</method>
<method name="map_get_closest_point_to_segment" qualifiers="const">
@@ -558,7 +558,8 @@
<param index="2" name="end" type="Vector3" />
<param index="3" name="use_collision" type="bool" default="false" />
<description>
- Returns the closest point between the navigation surface and the segment.
+ Returns the navigation mesh surface point closest to the provided [param start] and [param end] segment on the navigation [param map].
+ If [param use_collision] is [code]true[/code], a closest point test is only done when the segment intersects with the navigation mesh surface.
</description>
</method>
<method name="map_get_edge_connection_margin" qualifiers="const">
@@ -908,6 +909,33 @@
Creates a new region.
</description>
</method>
+ <method name="region_get_closest_point" qualifiers="const">
+ <return type="Vector3" />
+ <param index="0" name="region" type="RID" />
+ <param index="1" name="to_point" type="Vector3" />
+ <description>
+ Returns the navigation mesh surface point closest to the provided [param to_point] on the navigation [param region].
+ </description>
+ </method>
+ <method name="region_get_closest_point_normal" qualifiers="const">
+ <return type="Vector3" />
+ <param index="0" name="region" type="RID" />
+ <param index="1" name="to_point" type="Vector3" />
+ <description>
+ Returns the navigation mesh surface normal closest to the provided [param to_point] on the navigation [param region].
+ </description>
+ </method>
+ <method name="region_get_closest_point_to_segment" qualifiers="const">
+ <return type="Vector3" />
+ <param index="0" name="region" type="RID" />
+ <param index="1" name="start" type="Vector3" />
+ <param index="2" name="end" type="Vector3" />
+ <param index="3" name="use_collision" type="bool" default="false" />
+ <description>
+ Returns the navigation mesh surface point closest to the provided [param start] and [param end] segment on the navigation [param region].
+ If [param use_collision] is [code]true[/code], a closest point test is only done when the segment intersects with the navigation mesh surface.
+ </description>
+ </method>
<method name="region_get_connection_pathway_end" qualifiers="const">
<return type="Vector3" />
<param index="0" name="region" type="RID" />
diff --git a/doc/classes/Node2D.xml b/doc/classes/Node2D.xml
index 851290de7b..0b2dfcea03 100644
--- a/doc/classes/Node2D.xml
+++ b/doc/classes/Node2D.xml
@@ -95,43 +95,44 @@
</methods>
<members>
<member name="global_position" type="Vector2" setter="set_global_position" getter="get_global_position">
- Global position.
+ Global position. See also [member position].
</member>
<member name="global_rotation" type="float" setter="set_global_rotation" getter="get_global_rotation">
- Global rotation in radians.
+ Global rotation in radians. See also [member rotation].
</member>
<member name="global_rotation_degrees" type="float" setter="set_global_rotation_degrees" getter="get_global_rotation_degrees">
- Helper property to access [member global_rotation] in degrees instead of radians.
+ Helper property to access [member global_rotation] in degrees instead of radians. See also [member rotation_degrees].
</member>
<member name="global_scale" type="Vector2" setter="set_global_scale" getter="get_global_scale">
- Global scale.
+ Global scale. See also [member scale].
</member>
<member name="global_skew" type="float" setter="set_global_skew" getter="get_global_skew">
- Global skew in radians.
+ Global skew in radians. See also [member skew].
</member>
<member name="global_transform" type="Transform2D" setter="set_global_transform" getter="get_global_transform">
- Global [Transform2D].
+ Global [Transform2D]. See also [member transform].
</member>
<member name="position" type="Vector2" setter="set_position" getter="get_position" default="Vector2(0, 0)">
- Position, relative to the node's parent.
+ Position, relative to the node's parent. See also [member global_position].
</member>
<member name="rotation" type="float" setter="set_rotation" getter="get_rotation" default="0.0">
- Rotation in radians, relative to the node's parent.
+ Rotation in radians, relative to the node's parent. See also [member global_rotation].
[b]Note:[/b] This property is edited in the inspector in degrees. If you want to use degrees in a script, use [member rotation_degrees].
</member>
<member name="rotation_degrees" type="float" setter="set_rotation_degrees" getter="get_rotation_degrees">
- Helper property to access [member rotation] in degrees instead of radians.
+ Helper property to access [member rotation] in degrees instead of radians. See also [member global_rotation_degrees].
</member>
<member name="scale" type="Vector2" setter="set_scale" getter="get_scale" default="Vector2(1, 1)">
- The node's scale. Unscaled value: [code](1, 1)[/code].
+ The node's scale, relative to the node's parent. Unscaled value: [code](1, 1)[/code]. See also [member global_scale].
[b]Note:[/b] Negative X scales in 2D are not decomposable from the transformation matrix. Due to the way scale is represented with transformation matrices in Godot, negative scales on the X axis will be changed to negative scales on the Y axis and a rotation of 180 degrees when decomposed.
</member>
<member name="skew" type="float" setter="set_skew" getter="get_skew" default="0.0">
- Slants the node.
- [b]Note:[/b] Skew is X axis only.
+ If set to a non-zero value, slants the node in one direction or another. This can be used for pseudo-3D effects. See also [member global_skew].
+ [b]Note:[/b] Skew is performed on the X axis only, and [i]between[/i] rotation and scaling.
+ [b]Note:[/b] This property is edited in the inspector in degrees. If you want to use degrees in a script, use [code]skew = deg_to_rad(value_in_degrees)[/code].
</member>
<member name="transform" type="Transform2D" setter="set_transform" getter="get_transform">
- Local [Transform2D].
+ The node's [Transform2D], relative to the node's parent. See also [member global_transform].
</member>
</members>
</class>
diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml
index 9675f5af50..777950c075 100644
--- a/doc/classes/OS.xml
+++ b/doc/classes/OS.xml
@@ -132,8 +132,10 @@
<return type="Dictionary" />
<param index="0" name="path" type="String" />
<param index="1" name="arguments" type="PackedStringArray" />
+ <param index="2" name="blocking" type="bool" default="true" />
<description>
Creates a new process that runs independently of Godot with redirected IO. It will not terminate when Godot terminates. The path specified in [param path] must exist and be an executable file or macOS [code].app[/code] bundle. The path is resolved based on the current platform. The [param arguments] are used in the given order and separated by a space.
+ If [param blocking] is [code]false[/code], created pipes work in non-blocking mode, i.e. read and write operations will return immediately. Use [method FileAccess.get_error] to check if the last read/write operation was successful.
If the process cannot be created, this method returns an empty [Dictionary]. Otherwise, this method returns a [Dictionary] with the following keys:
- [code]"stdio"[/code] - [FileAccess] to access the process stdin and stdout pipes (read/write).
- [code]"stderr"[/code] - [FileAccess] to access the process stderr pipe (read only).
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 497070fa81..80a4ca9a8a 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -2341,6 +2341,9 @@
[b]Note:[/b] This property is only read when the project starts. To change the physics FPS at runtime, set [member Engine.physics_ticks_per_second] instead.
[b]Note:[/b] Only [member physics/common/max_physics_steps_per_frame] physics ticks may be simulated per rendered frame at most. If more physics ticks have to be simulated per rendered frame to keep up with rendering, the project will appear to slow down (even if [code]delta[/code] is used consistently in physics calculations). Therefore, it is recommended to also increase [member physics/common/max_physics_steps_per_frame] if increasing [member physics/common/physics_ticks_per_second] significantly above its default value.
</member>
+ <member name="rendering/2d/batching/item_buffer_size" type="int" setter="" getter="" default="16384">
+ Maximum number of canvas item commands that can be batched into a single draw call.
+ </member>
<member name="rendering/2d/sdf/oversize" type="int" setter="" getter="" default="1">
Controls how much of the original viewport size should be covered by the 2D signed distance field. This SDF can be sampled in [CanvasItem] shaders and is used for [GPUParticles2D] collision. Higher values allow portions of occluders located outside the viewport to still be taken into account in the generated signed distance field, at the cost of performance. If you notice particles falling through [LightOccluder2D]s as the occluders leave the viewport, increase this setting.
The percentage specified is added on each axis and on both sides. For example, with the default setting of 120%, the signed distance field will cover 20% of the viewport's size outside the viewport on each side (top, right, bottom, left).
diff --git a/doc/classes/RDShaderSource.xml b/doc/classes/RDShaderSource.xml
index 062c22f11f..a7b897d56e 100644
--- a/doc/classes/RDShaderSource.xml
+++ b/doc/classes/RDShaderSource.xml
@@ -23,6 +23,7 @@
<param index="1" name="source" type="String" />
<description>
Sets [param source] code for the specified shader [param stage]. Equivalent to setting one of [member source_compute], [member source_fragment], [member source_tesselation_control], [member source_tesselation_evaluation] or [member source_vertex].
+ [b]Note:[/b] If you set the compute shader source code using this method directly, remember to remove the Godot-specific hint [code]#[compute][/code].
</description>
</method>
</methods>
diff --git a/doc/classes/ResourceImporterDynamicFont.xml b/doc/classes/ResourceImporterDynamicFont.xml
index b678a04e34..3727bed8e5 100644
--- a/doc/classes/ResourceImporterDynamicFont.xml
+++ b/doc/classes/ResourceImporterDynamicFont.xml
@@ -54,7 +54,7 @@
Source font size used to generate MSDF textures. Higher values allow for more precision, but are slower to render and require more memory. Only increase this value if you notice a visible lack of precision in glyph rendering. Only effective if [member multichannel_signed_distance_field] is [code]true[/code].
</member>
<member name="multichannel_signed_distance_field" type="bool" setter="" getter="" default="false">
- If set to [code]true[/code], the default font will use multichannel signed distance field (MSDF) for crisp rendering at any size. Since this approach does not rely on rasterizing the font every time its size changes, this allows for resizing the font in real-time without any performance penalty. Text will also not look grainy for [Control]s that are scaled down (or for [Label3D]s viewed from a long distance).
+ If set to [code]true[/code], the font will use multichannel signed distance field (MSDF) for crisp rendering at any size. Since this approach does not rely on rasterizing the font every time its size changes, this allows for resizing the font in real-time without any performance penalty. Text will also not look grainy for [Control]s that are scaled down (or for [Label3D]s viewed from a long distance).
MSDF font rendering can be combined with [member generate_mipmaps] to further improve font rendering quality when scaled down.
</member>
<member name="opentype_features" type="Dictionary" setter="" getter="" default="{}">
diff --git a/doc/classes/ResourceImporterScene.xml b/doc/classes/ResourceImporterScene.xml
index 900e028b25..1565a244fe 100644
--- a/doc/classes/ResourceImporterScene.xml
+++ b/doc/classes/ResourceImporterScene.xml
@@ -68,6 +68,9 @@
<member name="nodes/root_type" type="String" setter="" getter="" default="&quot;&quot;">
Override for the root node type. If empty, the root node will use what the scene specifies, or [Node3D] if the scene does not specify a root type. Using a node type that inherits from [Node3D] is recommended. Otherwise, you'll lose the ability to position the node directly in the 3D editor.
</member>
+ <member name="nodes/use_node_type_suffixes" type="bool" setter="" getter="" default="true">
+ If [code]true[/code], use suffixes in the node names to determine the node type, such as [code]-col[/code] for collision shapes. Disabling this makes editor-imported files more similar to the original files, and more similar to importing files at runtime. See [url=$DOCS_URL/tutorials/assets_pipeline/importing_3d_scenes/node_type_customization.html]Node type customization using name suffixes[/url] for more information.
+ </member>
<member name="skins/use_named_skins" type="bool" setter="" getter="" default="true">
If checked, use named [Skin]s for animation. The [MeshInstance3D] node contains 3 properties of relevance here: a skeleton [NodePath] pointing to the [Skeleton3D] node (usually [code]..[/code]), a mesh, and a skin:
- The [Skeleton3D] node contains a list of bones with names, their pose and rest, a name and a parent bone.
diff --git a/doc/classes/ScriptEditor.xml b/doc/classes/ScriptEditor.xml
index 5cf077c266..67a2af2932 100644
--- a/doc/classes/ScriptEditor.xml
+++ b/doc/classes/ScriptEditor.xml
@@ -99,6 +99,14 @@
[b]Note:[/b] The [EditorSyntaxHighlighter] will still be applied to scripts that are already opened.
</description>
</method>
+ <method name="update_docs_from_script">
+ <return type="void" />
+ <param index="0" name="script" type="Script" />
+ <description>
+ Updates the documentation for the given [param script] if the script's documentation is currently open.
+ [b]Note:[/b] This should be called whenever the script is changed to keep the open documentation state up to date.
+ </description>
+ </method>
</methods>
<signals>
<signal name="editor_script_changed">
diff --git a/doc/classes/Theme.xml b/doc/classes/Theme.xml
index eb3c170583..479456ae66 100644
--- a/doc/classes/Theme.xml
+++ b/doc/classes/Theme.xml
@@ -551,7 +551,7 @@
</member>
<member name="default_font_size" type="int" setter="set_default_font_size" getter="get_default_font_size" default="-1">
The default font size of this theme resource. Used as the default value when trying to fetch a font size value that doesn't exist in this theme or is in invalid state. If the default font size is also missing or invalid, the engine fallback value is used (see [member ThemeDB.fallback_font_size]).
- Values below [code]0[/code] are invalid and can be used to unset the property. Use [method has_default_font_size] to check if this value is valid.
+ Values below [code]1[/code] are invalid and can be used to unset the property. Use [method has_default_font_size] to check if this value is valid.
</member>
</members>
<constants>
diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml
index 8f0c1f5cf0..861a53aaad 100644
--- a/doc/classes/TreeItem.xml
+++ b/doc/classes/TreeItem.xml
@@ -19,7 +19,7 @@
<param index="3" name="disabled" type="bool" default="false" />
<param index="4" name="tooltip_text" type="String" default="&quot;&quot;" />
<description>
- Adds a button with [Texture2D] [param button] at column [param column]. The [param id] is used to identify the button in the according [signal Tree.button_clicked] signal and can be different from the buttons index. If not specified, the next available index is used, which may be retrieved by calling [method get_button_count] immediately before this method. Optionally, the button can be [param disabled] and have a [param tooltip_text].
+ Adds a button with [Texture2D] [param button] to the end of the cell at column [param column]. The [param id] is used to identify the button in the according [signal Tree.button_clicked] signal and can be different from the buttons index. If not specified, the next available index is used, which may be retrieved by calling [method get_button_count] immediately before this method. Optionally, the button can be [param disabled] and have a [param tooltip_text].
</description>
</method>
<method name="add_child">
@@ -650,7 +650,7 @@
<param index="0" name="column" type="int" />
<param index="1" name="texture" type="Texture2D" />
<description>
- Sets the given cell's icon [Texture2D]. The cell has to be in [constant CELL_MODE_ICON] mode.
+ Sets the given cell's icon [Texture2D]. If the cell is in [constant CELL_MODE_ICON] mode, the icon is displayed in the center of the cell. Otherwise, the icon is displayed before the cell's text. [constant CELL_MODE_RANGE] does not display an icon.
</description>
</method>
<method name="set_icon_max_width">
@@ -826,17 +826,17 @@
</members>
<constants>
<constant name="CELL_MODE_STRING" value="0" enum="TreeCellMode">
- Cell shows a string label. When editable, the text can be edited using a [LineEdit], or a [TextEdit] popup if [method set_edit_multiline] is used.
+ Cell shows a string label, optionally with an icon. When editable, the text can be edited using a [LineEdit], or a [TextEdit] popup if [method set_edit_multiline] is used.
</constant>
<constant name="CELL_MODE_CHECK" value="1" enum="TreeCellMode">
- Cell shows a checkbox, optionally with text. The checkbox can be pressed, released, or indeterminate (via [method set_indeterminate]). The checkbox can't be clicked unless the cell is editable.
+ Cell shows a checkbox, optionally with text and an icon. The checkbox can be pressed, released, or indeterminate (via [method set_indeterminate]). The checkbox can't be clicked unless the cell is editable.
</constant>
<constant name="CELL_MODE_RANGE" value="2" enum="TreeCellMode">
Cell shows a numeric range. When editable, it can be edited using a range slider. Use [method set_range] to set the value and [method set_range_config] to configure the range.
This cell can also be used in a text dropdown mode when you assign a text with [method set_text]. Separate options with a comma, e.g. [code]"Option1,Option2,Option3"[/code].
</constant>
<constant name="CELL_MODE_ICON" value="3" enum="TreeCellMode">
- Cell shows an icon. It can't be edited nor display text.
+ Cell shows an icon. It can't be edited nor display text. The icon is always centered within the cell.
</constant>
<constant name="CELL_MODE_CUSTOM" value="4" enum="TreeCellMode">
Cell shows as a clickable button. It will display an arrow similar to [OptionButton], but doesn't feature a dropdown (for that you can use [constant CELL_MODE_RANGE]). Clicking the button emits the [signal Tree.item_edited] signal. The button is flat by default, you can use [method set_custom_as_button] to display it with a [StyleBox].
diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml
index b24f26a764..350fd65197 100644
--- a/doc/classes/Viewport.xml
+++ b/doc/classes/Viewport.xml
@@ -117,6 +117,12 @@
Returns the visible rectangle in global screen coordinates.
</description>
</method>
+ <method name="gui_cancel_drag">
+ <return type="void" />
+ <description>
+ Cancels the drag operation that was previously started through [method Control._get_drag_data] or forced with [method Control.force_drag].
+ </description>
+ </method>
<method name="gui_get_drag_data" qualifiers="const">
<return type="Variant" />
<description>
diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py
index 29e10ea490..101660881b 100755
--- a/doc/tools/make_rst.py
+++ b/doc/tools/make_rst.py
@@ -1509,24 +1509,23 @@ def make_type(klass: str, state: State) -> str:
if klass.find("*") != -1: # Pointer, ignore
return f"``{klass}``"
- link_type = klass
- is_array = False
-
- if link_type.endswith("[]"): # Typed array, strip [] to link to contained type.
- link_type = link_type[:-2]
- is_array = True
-
- if link_type in state.classes:
- type_rst = f":ref:`{link_type}<class_{link_type}>`"
- if is_array:
- type_rst = f":ref:`Array<class_Array>`\\[{type_rst}\\]"
- return type_rst
-
- print_error(f'{state.current_class}.xml: Unresolved type "{link_type}".', state)
- type_rst = f"``{link_type}``"
- if is_array:
- type_rst = f":ref:`Array<class_Array>`\\[{type_rst}\\]"
- return type_rst
+ def resolve_type(link_type: str) -> str:
+ if link_type in state.classes:
+ return f":ref:`{link_type}<class_{link_type}>`"
+ else:
+ print_error(f'{state.current_class}.xml: Unresolved type "{link_type}".', state)
+ return f"``{link_type}``"
+
+ if klass.endswith("[]"): # Typed array, strip [] to link to contained type.
+ return f":ref:`Array<class_Array>`\\[{resolve_type(klass[:-len('[]')])}\\]"
+
+ if klass.startswith("Dictionary["): # Typed dictionary, split elements to link contained types.
+ parts = klass[len("Dictionary[") : -len("]")].partition(", ")
+ key = parts[0]
+ value = parts[2]
+ return f":ref:`Dictionary<class_Dictionary>`\\[{resolve_type(key)}, {resolve_type(value)}\\]"
+
+ return resolve_type(klass)
def make_enum(t: str, is_bitfield: bool, state: State) -> str:
diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl
index 6143ce2167..393ba014c6 100644
--- a/drivers/gles3/shaders/scene.glsl
+++ b/drivers/gles3/shaders/scene.glsl
@@ -1803,22 +1803,22 @@ void main() {
#ifdef LIGHTMAP_BICUBIC_FILTER
vec3 lm_light_l0 = textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 0.0), lightmap_texture_size).rgb;
- vec3 lm_light_l1n1 = textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 1.0), lightmap_texture_size).rgb;
- vec3 lm_light_l1_0 = textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 2.0), lightmap_texture_size).rgb;
- vec3 lm_light_l1p1 = textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 3.0), lightmap_texture_size).rgb;
+ vec3 lm_light_l1n1 = (textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 1.0), lightmap_texture_size).rgb - vec3(0.5)) * 2.0;
+ vec3 lm_light_l1_0 = (textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 2.0), lightmap_texture_size).rgb - vec3(0.5)) * 2.0;
+ vec3 lm_light_l1p1 = (textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 3.0), lightmap_texture_size).rgb - vec3(0.5)) * 2.0;
#else
vec3 lm_light_l0 = textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb;
- vec3 lm_light_l1n1 = textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb;
- vec3 lm_light_l1_0 = textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
- vec3 lm_light_l1p1 = textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
+ vec3 lm_light_l1n1 = (textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb - vec3(0.5)) * 2.0;
+ vec3 lm_light_l1_0 = (textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb - vec3(0.5)) * 2.0;
+ vec3 lm_light_l1p1 = (textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb - vec3(0.5)) * 2.0;
#endif
vec3 n = normalize(lightmap_normal_xform * normal);
ambient_light += lm_light_l0 * lightmap_exposure_normalization;
- ambient_light += lm_light_l1n1 * n.y * lightmap_exposure_normalization;
- ambient_light += lm_light_l1_0 * n.z * lightmap_exposure_normalization;
- ambient_light += lm_light_l1p1 * n.x * lightmap_exposure_normalization;
+ ambient_light += lm_light_l1n1 * n.y * (lm_light_l0 * lightmap_exposure_normalization * 4.0);
+ ambient_light += lm_light_l1_0 * n.z * (lm_light_l0 * lightmap_exposure_normalization * 4.0);
+ ambient_light += lm_light_l1p1 * n.x * (lm_light_l0 * lightmap_exposure_normalization * 4.0);
#else
#ifdef LIGHTMAP_BICUBIC_FILTER
ambient_light += textureArray_bicubic(lightmap_textures, uvw, lightmap_texture_size).rgb * lightmap_exposure_normalization;
diff --git a/drivers/gles3/storage/light_storage.cpp b/drivers/gles3/storage/light_storage.cpp
index aab1aadf02..9b976c2206 100644
--- a/drivers/gles3/storage/light_storage.cpp
+++ b/drivers/gles3/storage/light_storage.cpp
@@ -1521,6 +1521,11 @@ bool LightStorage::_shadow_atlas_find_shadow(ShadowAtlas *shadow_atlas, int *p_i
uint64_t min_pass = 0; // Pass of the existing one, try to use the least recently used one (LRU fashion).
for (int j = 0; j < sc; j++) {
+ if (sarr[j].owner_is_omni != is_omni) {
+ // Existing light instance type doesn't match new light instance type skip.
+ continue;
+ }
+
LightInstance *sli = light_instance_owner.get_or_null(sarr[j].owner);
if (!sli) {
// Found a released light instance.
diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp
index a37eba3b15..7d5af48384 100644
--- a/drivers/gles3/storage/material_storage.cpp
+++ b/drivers/gles3/storage/material_storage.cpp
@@ -348,7 +348,7 @@ static void _fill_std140_variant_ubo_value(ShaderLanguage::DataType type, int p_
}
}
-_FORCE_INLINE_ static void _fill_std140_ubo_value(ShaderLanguage::DataType type, const Vector<ShaderLanguage::ConstantNode::Value> &value, uint8_t *data) {
+_FORCE_INLINE_ static void _fill_std140_ubo_value(ShaderLanguage::DataType type, const Vector<ShaderLanguage::Scalar> &value, uint8_t *data) {
switch (type) {
case ShaderLanguage::TYPE_BOOL: {
uint32_t *gui = (uint32_t *)data;
@@ -572,7 +572,7 @@ void ShaderData::set_default_texture_parameter(const StringName &p_name, RID p_t
Variant ShaderData::get_default_parameter(const StringName &p_parameter) const {
if (uniforms.has(p_parameter)) {
ShaderLanguage::ShaderNode::Uniform uniform = uniforms[p_parameter];
- Vector<ShaderLanguage::ConstantNode::Value> default_value = uniform.default_value;
+ Vector<ShaderLanguage::Scalar> default_value = uniform.default_value;
return ShaderLanguage::constant_value_to_variant(default_value, uniform.type, uniform.array_size, uniform.hint);
}
return Variant();
diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp
index de82d74aff..5058554659 100644
--- a/drivers/gles3/storage/mesh_storage.cpp
+++ b/drivers/gles3/storage/mesh_storage.cpp
@@ -1769,14 +1769,14 @@ AABB MeshStorage::_multimesh_get_custom_aabb(RID p_multimesh) const {
return multimesh->custom_aabb;
}
-AABB MeshStorage::_multimesh_get_aabb(RID p_multimesh) const {
+AABB MeshStorage::_multimesh_get_aabb(RID p_multimesh) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, AABB());
if (multimesh->custom_aabb != AABB()) {
return multimesh->custom_aabb;
}
if (multimesh->aabb_dirty) {
- const_cast<MeshStorage *>(this)->_update_dirty_multimeshes();
+ _update_dirty_multimeshes();
}
return multimesh->aabb;
}
diff --git a/drivers/gles3/storage/mesh_storage.h b/drivers/gles3/storage/mesh_storage.h
index a2edbb9c48..31858cd372 100644
--- a/drivers/gles3/storage/mesh_storage.h
+++ b/drivers/gles3/storage/mesh_storage.h
@@ -510,7 +510,7 @@ public:
virtual RID _multimesh_get_mesh(RID p_multimesh) const override;
virtual void _multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) override;
virtual AABB _multimesh_get_custom_aabb(RID p_multimesh) const override;
- virtual AABB _multimesh_get_aabb(RID p_multimesh) const override;
+ virtual AABB _multimesh_get_aabb(RID p_multimesh) override;
virtual Transform3D _multimesh_instance_get_transform(RID p_multimesh, int p_index) const override;
virtual Transform2D _multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const override;
diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp
index 36393dde86..54012c20e9 100644
--- a/drivers/gles3/storage/texture_storage.cpp
+++ b/drivers/gles3/storage/texture_storage.cpp
@@ -1039,7 +1039,7 @@ Ref<Image> TextureStorage::texture_2d_get(RID p_texture) const {
data.resize(data_size);
ERR_FAIL_COND_V(data.is_empty(), Ref<Image>());
- image = Image::create_from_data(texture->width, texture->height, texture->mipmaps > 1, texture->real_format, data);
+ image = Image::create_from_data(texture->alloc_width, texture->alloc_height, texture->mipmaps > 1, texture->real_format, data);
ERR_FAIL_COND_V(image->is_empty(), Ref<Image>());
if (texture->format != texture->real_format) {
image->convert(texture->format);
@@ -1095,7 +1095,7 @@ Ref<Image> TextureStorage::texture_2d_get(RID p_texture) const {
data.resize(data_size);
ERR_FAIL_COND_V(data.is_empty(), Ref<Image>());
- image = Image::create_from_data(texture->width, texture->height, false, Image::FORMAT_RGBA8, data);
+ image = Image::create_from_data(texture->alloc_width, texture->alloc_height, false, Image::FORMAT_RGBA8, data);
ERR_FAIL_COND_V(image->is_empty(), Ref<Image>());
if (texture->format != Image::FORMAT_RGBA8) {
diff --git a/drivers/metal/metal_objects.mm b/drivers/metal/metal_objects.mm
index abdcccf00c..d3c3d2b232 100644
--- a/drivers/metal/metal_objects.mm
+++ b/drivers/metal/metal_objects.mm
@@ -560,10 +560,10 @@ void MDCommandBuffer::_render_clear_render_area() {
}
}
uint32_t ds_index = subpass.depth_stencil_reference.attachment;
- MDAttachment const &attachment = pass.attachments[ds_index];
- bool shouldClearDepth = (ds_index != RDD::AttachmentReference::UNUSED && attachment.shouldClear(subpass, false));
- bool shouldClearStencil = (ds_index != RDD::AttachmentReference::UNUSED && attachment.shouldClear(subpass, true));
+ bool shouldClearDepth = (ds_index != RDD::AttachmentReference::UNUSED && pass.attachments[ds_index].shouldClear(subpass, false));
+ bool shouldClearStencil = (ds_index != RDD::AttachmentReference::UNUSED && pass.attachments[ds_index].shouldClear(subpass, true));
if (shouldClearDepth || shouldClearStencil) {
+ MDAttachment const &attachment = pass.attachments[ds_index];
BitField<RDD::TextureAspectBits> bits;
if (shouldClearDepth && attachment.type & MDAttachmentType::Depth) {
bits.set_flag(RDD::TEXTURE_ASPECT_DEPTH_BIT);
diff --git a/drivers/unix/file_access_unix_pipe.cpp b/drivers/unix/file_access_unix_pipe.cpp
index 34758e8c7d..0a78429dec 100644
--- a/drivers/unix/file_access_unix_pipe.cpp
+++ b/drivers/unix/file_access_unix_pipe.cpp
@@ -41,7 +41,7 @@
#include <sys/types.h>
#include <unistd.h>
-Error FileAccessUnixPipe::open_existing(int p_rfd, int p_wfd) {
+Error FileAccessUnixPipe::open_existing(int p_rfd, int p_wfd, bool p_blocking) {
// Open pipe using handles created by pipe(fd) call in the OS.execute_with_pipe.
_close();
@@ -51,6 +51,11 @@ Error FileAccessUnixPipe::open_existing(int p_rfd, int p_wfd) {
fd[0] = p_rfd;
fd[1] = p_wfd;
+ if (!p_blocking) {
+ fcntl(fd[0], F_SETFL, fcntl(fd[0], F_GETFL) | O_NONBLOCK);
+ fcntl(fd[1], F_SETFL, fcntl(fd[1], F_GETFL) | O_NONBLOCK);
+ }
+
last_error = OK;
return OK;
}
@@ -74,7 +79,7 @@ Error FileAccessUnixPipe::open_internal(const String &p_path, int p_mode_flags)
ERR_FAIL_COND_V_MSG(!S_ISFIFO(st.st_mode), ERR_ALREADY_IN_USE, "Pipe name is already used by file.");
}
- int f = ::open(path.utf8().get_data(), O_RDWR | O_CLOEXEC);
+ int f = ::open(path.utf8().get_data(), O_RDWR | O_CLOEXEC | O_NONBLOCK);
if (f < 0) {
switch (errno) {
case ENOENT: {
@@ -129,8 +134,11 @@ uint64_t FileAccessUnixPipe::get_buffer(uint8_t *p_dst, uint64_t p_length) const
ERR_FAIL_COND_V_MSG(fd[0] < 0, -1, "Pipe must be opened before use.");
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
- uint64_t read = ::read(fd[0], p_dst, p_length);
- if (read == p_length) {
+ ssize_t read = ::read(fd[0], p_dst, p_length);
+ if (read == -1) {
+ last_error = ERR_FILE_CANT_READ;
+ read = 0;
+ } else if (read != (ssize_t)p_length) {
last_error = ERR_FILE_CANT_READ;
} else {
last_error = OK;
diff --git a/drivers/unix/file_access_unix_pipe.h b/drivers/unix/file_access_unix_pipe.h
index 19acdb5a37..1a4199f239 100644
--- a/drivers/unix/file_access_unix_pipe.h
+++ b/drivers/unix/file_access_unix_pipe.h
@@ -50,7 +50,7 @@ class FileAccessUnixPipe : public FileAccess {
void _close();
public:
- Error open_existing(int p_rfd, int p_wfd);
+ Error open_existing(int p_rfd, int p_wfd, bool p_blocking);
virtual Error open_internal(const String &p_path, int p_mode_flags) override; ///< open a file
virtual bool is_open() const override; ///< true when file is open
diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp
index ce2553456d..8a9b130068 100644
--- a/drivers/unix/os_unix.cpp
+++ b/drivers/unix/os_unix.cpp
@@ -493,7 +493,7 @@ Dictionary OS_Unix::get_memory_info() const {
return meminfo;
}
-Dictionary OS_Unix::execute_with_pipe(const String &p_path, const List<String> &p_arguments) {
+Dictionary OS_Unix::execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking) {
#define CLEAN_PIPES \
if (pipe_in[0] >= 0) { \
::close(pipe_in[0]); \
@@ -578,11 +578,11 @@ Dictionary OS_Unix::execute_with_pipe(const String &p_path, const List<String> &
Ref<FileAccessUnixPipe> main_pipe;
main_pipe.instantiate();
- main_pipe->open_existing(pipe_out[0], pipe_in[1]);
+ main_pipe->open_existing(pipe_out[0], pipe_in[1], p_blocking);
Ref<FileAccessUnixPipe> err_pipe;
err_pipe.instantiate();
- err_pipe->open_existing(pipe_err[0], 0);
+ err_pipe->open_existing(pipe_err[0], 0, p_blocking);
ProcessInfo pi;
process_map_mutex.lock();
diff --git a/drivers/unix/os_unix.h b/drivers/unix/os_unix.h
index df269a59d3..3add5df055 100644
--- a/drivers/unix/os_unix.h
+++ b/drivers/unix/os_unix.h
@@ -83,7 +83,7 @@ public:
virtual Dictionary get_memory_info() const override;
virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override;
- virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override;
+ virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking = true) override;
virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
virtual Error kill(const ProcessID &p_pid) override;
virtual int get_process_id() const override;
diff --git a/drivers/windows/file_access_windows.cpp b/drivers/windows/file_access_windows.cpp
index 0243d863f8..9d6aa13332 100644
--- a/drivers/windows/file_access_windows.cpp
+++ b/drivers/windows/file_access_windows.cpp
@@ -118,11 +118,12 @@ Error FileAccessWindows::open_internal(const String &p_path, int p_mode_flags) {
return ERR_INVALID_PARAMETER;
}
- struct _stat st;
- if (_wstat((LPCWSTR)(path.utf16().get_data()), &st) == 0) {
- if (!S_ISREG(st.st_mode)) {
- return ERR_FILE_CANT_OPEN;
- }
+ if (path.ends_with(":\\") || path.ends_with(":")) {
+ return ERR_FILE_CANT_OPEN;
+ }
+ DWORD file_attr = GetFileAttributesW((LPCWSTR)(path.utf16().get_data()));
+ if (file_attr != INVALID_FILE_ATTRIBUTES && (file_attr & FILE_ATTRIBUTE_DIRECTORY)) {
+ return ERR_FILE_CANT_OPEN;
}
#ifdef TOOLS_ENABLED
@@ -412,15 +413,40 @@ uint64_t FileAccessWindows::_get_modified_time(const String &p_file) {
file = file.substr(0, file.length() - 1);
}
- struct _stat st;
- int rv = _wstat((LPCWSTR)(file.utf16().get_data()), &st);
+ HANDLE handle = CreateFileW((LPCWSTR)(file.utf16().get_data()), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
- if (rv == 0) {
- return st.st_mtime;
- } else {
- print_verbose("Failed to get modified time for: " + p_file + "");
- return 0;
+ if (handle != INVALID_HANDLE_VALUE) {
+ FILETIME ft_create, ft_write;
+
+ bool status = GetFileTime(handle, &ft_create, nullptr, &ft_write);
+
+ CloseHandle(handle);
+
+ if (status) {
+ uint64_t ret = 0;
+
+ // If write time is invalid, fallback to creation time.
+ if (ft_write.dwHighDateTime == 0 && ft_write.dwLowDateTime == 0) {
+ ret = ft_create.dwHighDateTime;
+ ret <<= 32;
+ ret |= ft_create.dwLowDateTime;
+ } else {
+ ret = ft_write.dwHighDateTime;
+ ret <<= 32;
+ ret |= ft_write.dwLowDateTime;
+ }
+
+ const uint64_t WINDOWS_TICKS_PER_SECOND = 10000000;
+ const uint64_t TICKS_TO_UNIX_EPOCH = 116444736000000000LL;
+
+ if (ret >= TICKS_TO_UNIX_EPOCH) {
+ return (ret - TICKS_TO_UNIX_EPOCH) / WINDOWS_TICKS_PER_SECOND;
+ }
+ }
}
+
+ print_verbose("Failed to get modified time for: " + p_file);
+ return 0;
}
BitField<FileAccess::UnixPermissionFlags> FileAccessWindows::_get_unix_permissions(const String &p_file) {
diff --git a/drivers/windows/file_access_windows_pipe.cpp b/drivers/windows/file_access_windows_pipe.cpp
index 0c953b14aa..9bf0f4d852 100644
--- a/drivers/windows/file_access_windows_pipe.cpp
+++ b/drivers/windows/file_access_windows_pipe.cpp
@@ -35,7 +35,7 @@
#include "core/os/os.h"
#include "core/string/print_string.h"
-Error FileAccessWindowsPipe::open_existing(HANDLE p_rfd, HANDLE p_wfd) {
+Error FileAccessWindowsPipe::open_existing(HANDLE p_rfd, HANDLE p_wfd, bool p_blocking) {
// Open pipe using handles created by CreatePipe(rfd, wfd, NULL, 4096) call in the OS.execute_with_pipe.
_close();
@@ -44,6 +44,12 @@ Error FileAccessWindowsPipe::open_existing(HANDLE p_rfd, HANDLE p_wfd) {
fd[0] = p_rfd;
fd[1] = p_wfd;
+ if (!p_blocking) {
+ DWORD mode = PIPE_READMODE_BYTE | PIPE_NOWAIT;
+ SetNamedPipeHandleState(fd[0], &mode, nullptr, nullptr);
+ SetNamedPipeHandleState(fd[1], &mode, nullptr, nullptr);
+ }
+
last_error = OK;
return OK;
}
@@ -58,7 +64,7 @@ Error FileAccessWindowsPipe::open_internal(const String &p_path, int p_mode_flag
HANDLE h = CreateFileW((LPCWSTR)path.utf16().get_data(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (h == INVALID_HANDLE_VALUE) {
- h = CreateNamedPipeW((LPCWSTR)path.utf16().get_data(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 4096, 4096, 0, nullptr);
+ h = CreateNamedPipeW((LPCWSTR)path.utf16().get_data(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_NOWAIT, 1, 4096, 4096, 0, nullptr);
if (h == INVALID_HANDLE_VALUE) {
last_error = ERR_FILE_CANT_OPEN;
return last_error;
@@ -100,7 +106,7 @@ uint64_t FileAccessWindowsPipe::get_buffer(uint8_t *p_dst, uint64_t p_length) co
ERR_FAIL_COND_V_MSG(fd[0] == 0, -1, "Pipe must be opened before use.");
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
- DWORD read = -1;
+ DWORD read = 0;
if (!ReadFile(fd[0], p_dst, p_length, &read, nullptr) || read != p_length) {
last_error = ERR_FILE_CANT_READ;
} else {
diff --git a/drivers/windows/file_access_windows_pipe.h b/drivers/windows/file_access_windows_pipe.h
index 4e9bd036ae..1eb3c6ef2f 100644
--- a/drivers/windows/file_access_windows_pipe.h
+++ b/drivers/windows/file_access_windows_pipe.h
@@ -49,7 +49,7 @@ class FileAccessWindowsPipe : public FileAccess {
void _close();
public:
- Error open_existing(HANDLE p_rfd, HANDLE p_wfd);
+ Error open_existing(HANDLE p_rfd, HANDLE p_wfd, bool p_blocking);
virtual Error open_internal(const String &p_path, int p_mode_flags) override; ///< open a file
virtual bool is_open() const override; ///< true when file is open
diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp
index 24fcfd3930..66ebd07c2a 100644
--- a/editor/animation_bezier_editor.cpp
+++ b/editor/animation_bezier_editor.cpp
@@ -35,6 +35,7 @@
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/gui/editor_spin_slider.h"
+#include "editor/plugins/animation_player_editor_plugin.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/view_panner.h"
#include "scene/resources/text_line.h"
@@ -869,6 +870,11 @@ void AnimationBezierTrackEdit::_change_selected_keys_handle_mode(Animation::Hand
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track_key_pair.first, track_key_pair.second, animation->bezier_track_get_key_out_handle(track_key_pair.first, track_key_pair.second));
undo_redo->add_do_method(editor, "_bezier_track_set_key_handle_mode", animation.ptr(), track_key_pair.first, track_key_pair.second, p_mode, p_auto ? Animation::HANDLE_SET_MODE_AUTO : Animation::HANDLE_SET_MODE_RESET);
}
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
undo_redo->commit_action();
}
@@ -1560,6 +1566,11 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, moving_handle_right, ratio);
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, animation->bezier_track_get_key_out_handle(moving_handle_track, moving_handle_key), ratio);
}
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
undo_redo->commit_action();
moving_handle = 0;
queue_redraw();
@@ -1673,6 +1684,11 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) {
undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point);
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time);
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
undo_redo->commit_action();
queue_redraw();
}
@@ -1773,6 +1789,11 @@ void AnimationBezierTrackEdit::duplicate_selected_keys(real_t p_ofs, bool p_ofs_
i++;
}
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
undo_redo->add_do_method(this, "queue_redraw");
undo_redo->add_undo_method(this, "queue_redraw");
undo_redo->commit_action();
@@ -1822,6 +1843,15 @@ void AnimationBezierTrackEdit::copy_selected_keys(bool p_cut) {
undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, E->value().pos, i == 0);
i++;
}
+
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
+ undo_redo->add_do_method(this, "queue_redraw");
+ undo_redo->add_undo_method(this, "queue_redraw");
+
undo_redo->commit_action();
}
}
@@ -1900,9 +1930,15 @@ void AnimationBezierTrackEdit::paste_keys(real_t p_ofs, bool p_ofs_valid) {
i++;
}
- undo_redo->commit_action();
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
+ undo_redo->add_do_method(this, "queue_redraw");
+ undo_redo->add_undo_method(this, "queue_redraw");
- queue_redraw();
+ undo_redo->commit_action();
}
}
@@ -1917,6 +1953,11 @@ void AnimationBezierTrackEdit::delete_selection() {
}
undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
undo_redo->commit_action();
//selection.clear();
@@ -1926,6 +1967,15 @@ void AnimationBezierTrackEdit::delete_selection() {
void AnimationBezierTrackEdit::_bezier_track_insert_key_at_anim(const Ref<Animation> &p_anim, int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode) {
int idx = p_anim->bezier_track_insert_key(p_track, p_time, p_value, p_in_handle, p_out_handle);
p_anim->bezier_track_set_key_handle_mode(p_track, idx, p_handle_mode);
+
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->create_action(TTR("Animation Bezier Curve Change Call"));
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
+ undo_redo->commit_action();
}
void AnimationBezierTrackEdit::_bind_methods() {
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index 95ba301282..4ec097bdda 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -279,6 +279,11 @@ bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_valu
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_value", track, key, prev);
undo_redo->add_do_method(this, "_update_obj", animation);
undo_redo->add_undo_method(this, "_update_obj", animation);
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
undo_redo->commit_action();
setting = false;
@@ -295,6 +300,11 @@ bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_valu
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, key, prev);
undo_redo->add_do_method(this, "_update_obj", animation);
undo_redo->add_undo_method(this, "_update_obj", animation);
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
undo_redo->commit_action();
setting = false;
@@ -311,6 +321,11 @@ bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_valu
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, key, prev);
undo_redo->add_do_method(this, "_update_obj", animation);
undo_redo->add_undo_method(this, "_update_obj", animation);
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
undo_redo->commit_action();
setting = false;
@@ -331,6 +346,11 @@ bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_valu
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, key, prev_in_handle);
undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, key, prev_out_handle);
undo_redo->add_undo_method(this, "_update_obj", animation);
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
undo_redo->commit_action();
setting = false;
@@ -4345,6 +4365,25 @@ PropertyInfo AnimationTrackEditor::_find_hint_for_track(int p_idx, NodePath &r_b
property_info_base = property_info_base.get_named(leftover_path[i], valid);
}
+ // Hack for the fact that bezier tracks leftover paths can reference
+ // the individual components for types like vectors.
+ if (property_info_base.is_null()) {
+ if (res.is_valid()) {
+ property_info_base = res;
+ } else if (node) {
+ property_info_base = node;
+ }
+
+ if (leftover_path.size()) {
+ leftover_path.remove_at(leftover_path.size() - 1);
+ }
+
+ for (int i = 0; i < leftover_path.size() - 1; i++) {
+ bool valid;
+ property_info_base = property_info_base.get_named(leftover_path[i], valid);
+ }
+ }
+
if (property_info_base.is_null()) {
WARN_PRINT(vformat("Could not determine track hint for '%s:%s' because its base property is null.",
String(path.get_concatenated_names()), String(path.get_concatenated_subnames())));
@@ -4472,7 +4511,11 @@ AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertD
} break;
case Animation::TYPE_BEZIER: {
- int existing = animation->track_find_key(p_id.track_idx, time, Animation::FIND_MODE_APPROX);
+ int existing = -1;
+ if (p_id.track_idx < animation->get_track_count()) {
+ existing = animation->track_find_key(p_id.track_idx, time, Animation::FIND_MODE_APPROX);
+ }
+
if (existing != -1) {
Array arr = animation->track_get_key_value(p_id.track_idx, existing);
arr[0] = p_id.value;
diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp
index eb0ab1174b..ce2c1a5a16 100644
--- a/editor/connections_dialog.cpp
+++ b/editor/connections_dialog.cpp
@@ -34,6 +34,7 @@
#include "core/templates/hash_set.h"
#include "editor/editor_help.h"
#include "editor/editor_inspector.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
@@ -486,11 +487,6 @@ void ConnectDialog::_notification(int p_what) {
type_list->set_item_icon(i, get_editor_theme_icon(type_name));
}
- Ref<StyleBox> style = get_theme_stylebox(CoreStringName(normal), "LineEdit")->duplicate();
- if (style.is_valid()) {
- style->set_content_margin(SIDE_TOP, style->get_content_margin(SIDE_TOP) + 1.0);
- from_signal->add_theme_style_override(CoreStringName(normal), style);
- }
method_search->set_right_icon(get_editor_theme_icon("Search"));
open_method_tree->set_icon(get_editor_theme_icon("Edit"));
} break;
@@ -576,6 +572,22 @@ String ConnectDialog::get_signature(const MethodInfo &p_method, PackedStringArra
type_name = "Array";
}
break;
+ case Variant::DICTIONARY:
+ type_name = "Dictionary";
+ if (pi.hint == PROPERTY_HINT_DICTIONARY_TYPE && !pi.hint_string.is_empty()) {
+ String key_hint = pi.hint_string.get_slice(";", 0);
+ String value_hint = pi.hint_string.get_slice(";", 1);
+ if (key_hint.is_empty() || key_hint.begins_with("res://")) {
+ key_hint = "Variant";
+ }
+ if (value_hint.is_empty() || value_hint.begins_with("res://")) {
+ value_hint = "Variant";
+ }
+ if (key_hint != "Variant" || value_hint != "Variant") {
+ type_name += "[" + key_hint + ", " + value_hint + "]";
+ }
+ }
+ break;
case Variant::OBJECT:
if (pi.class_name != StringName()) {
type_name = pi.class_name;
@@ -1180,7 +1192,7 @@ void ConnectionsDock::_go_to_method(TreeItem &p_item) {
}
if (scr.is_valid() && ScriptEditor::get_singleton()->script_goto_method(scr, cd.method)) {
- EditorNode::get_singleton()->editor_select(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
}
}
@@ -1188,7 +1200,7 @@ void ConnectionsDock::_handle_class_menu_option(int p_option) {
switch (p_option) {
case CLASS_MENU_OPEN_DOCS:
ScriptEditor::get_singleton()->goto_help("class:" + class_menu_doc_class_name);
- EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
break;
}
}
@@ -1218,7 +1230,7 @@ void ConnectionsDock::_handle_signal_menu_option(int p_option) {
} break;
case SIGNAL_MENU_OPEN_DOCS: {
ScriptEditor::get_singleton()->goto_help("class_signal:" + String(meta["class"]) + ":" + String(meta["name"]));
- EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
} break;
}
}
diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp
index 76934c9ec6..79e0c7ebd1 100644
--- a/editor/doc_tools.cpp
+++ b/editor/doc_tools.cpp
@@ -575,6 +575,8 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) {
prop.type = retinfo.class_name;
} else if (retinfo.type == Variant::ARRAY && retinfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
prop.type = retinfo.hint_string + "[]";
+ } else if (retinfo.type == Variant::DICTIONARY && retinfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
+ prop.type = "Dictionary[" + retinfo.hint_string.replace(";", ", ") + "]";
} else if (retinfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
prop.type = retinfo.hint_string;
} else if (retinfo.type == Variant::NIL && retinfo.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp
index e5caa6a352..ee16c61c89 100644
--- a/editor/editor_data.cpp
+++ b/editor/editor_data.cpp
@@ -522,138 +522,6 @@ EditorPlugin *EditorData::get_extension_editor_plugin(const StringName &p_class_
return plugin == nullptr ? nullptr : *plugin;
}
-void EditorData::add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
- ContextMenu cm;
- cm.p_slot = p_slot;
- cm.plugin = p_plugin;
- p_plugin->start_idx = context_menu_plugins.size() * EditorContextMenuPlugin::MAX_ITEMS;
- context_menu_plugins.push_back(cm);
-}
-
-void EditorData::remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
- for (int i = context_menu_plugins.size() - 1; i > -1; i--) {
- if (context_menu_plugins[i].p_slot == p_slot && context_menu_plugins[i].plugin == p_plugin) {
- context_menu_plugins.remove_at(i);
- }
- }
-}
-
-int EditorData::match_context_menu_shortcut(ContextMenuSlot p_slot, const Ref<InputEvent> &p_event) {
- for (ContextMenu &cm : context_menu_plugins) {
- if (cm.p_slot != p_slot) {
- continue;
- }
- HashMap<Ref<Shortcut>, Callable> &cms = cm.plugin->context_menu_shortcuts;
- int shortcut_idx = 0;
- for (KeyValue<Ref<Shortcut>, Callable> &E : cms) {
- const Ref<Shortcut> &p_shortcut = E.key;
- if (p_shortcut->matches_event(p_event)) {
- return EditorData::CONTEXT_MENU_ITEM_ID_BASE + cm.plugin->start_idx + shortcut_idx;
- }
- shortcut_idx++;
- }
- }
- return 0;
-}
-
-void EditorData::add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector<String> &p_paths) {
- bool add_separator = false;
-
- for (ContextMenu &cm : context_menu_plugins) {
- if (cm.p_slot != p_slot) {
- continue;
- }
- cm.plugin->clear_context_menu_items();
- cm.plugin->add_options(p_paths);
- HashMap<String, EditorContextMenuPlugin::ContextMenuItem> &items = cm.plugin->context_menu_items;
- if (items.size() > 0 && !add_separator) {
- add_separator = true;
- p_popup->add_separator();
- }
- for (KeyValue<String, EditorContextMenuPlugin::ContextMenuItem> &E : items) {
- EditorContextMenuPlugin::ContextMenuItem &item = E.value;
-
- if (item.icon.is_valid()) {
- p_popup->add_icon_item(item.icon, item.item_name, item.idx);
- const int icon_size = p_popup->get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
- p_popup->set_item_icon_max_width(-1, icon_size);
- } else {
- p_popup->add_item(item.item_name, item.idx);
- }
- if (item.shortcut.is_valid()) {
- p_popup->set_item_shortcut(-1, item.shortcut, true);
- }
- }
- }
-}
-
-template <typename T>
-void EditorData::invoke_plugin_callback(ContextMenuSlot p_slot, int p_option, const T &p_arg) {
- Variant arg = p_arg;
- Variant *argptr = &arg;
-
- for (int i = 0; i < context_menu_plugins.size(); i++) {
- if (context_menu_plugins[i].p_slot != p_slot || context_menu_plugins[i].plugin.is_null()) {
- continue;
- }
- Ref<EditorContextMenuPlugin> plugin = context_menu_plugins[i].plugin;
-
- // Shortcut callback.
- int shortcut_idx = 0;
- int shortcut_base_idx = EditorData::CONTEXT_MENU_ITEM_ID_BASE + plugin->start_idx;
- for (KeyValue<Ref<Shortcut>, Callable> &E : plugin->context_menu_shortcuts) {
- if (shortcut_base_idx + shortcut_idx == p_option) {
- const Callable &callable = E.value;
- Callable::CallError ce;
- Variant result;
- callable.callp((const Variant **)&argptr, 1, result, ce);
- }
- shortcut_idx++;
- }
- if (p_option < shortcut_base_idx + shortcut_idx) {
- return;
- }
-
- HashMap<String, EditorContextMenuPlugin::ContextMenuItem> &items = plugin->context_menu_items;
- for (KeyValue<String, EditorContextMenuPlugin::ContextMenuItem> &E : items) {
- EditorContextMenuPlugin::ContextMenuItem &item = E.value;
-
- if (p_option != item.idx || !item.callable.is_valid()) {
- continue;
- }
-
- Callable::CallError ce;
- Variant result;
- item.callable.callp((const Variant **)&argptr, 1, result, ce);
-
- if (ce.error != Callable::CallError::CALL_OK) {
- String err = Variant::get_callable_error_text(item.callable, nullptr, 0, ce);
- ERR_PRINT("Error calling function from context menu: " + err);
- }
- }
- }
- // Invoke submenu items.
- if (p_slot == CONTEXT_SLOT_FILESYSTEM) {
- invoke_plugin_callback(CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE, p_option, p_arg);
- }
-}
-
-void EditorData::filesystem_options_pressed(ContextMenuSlot p_slot, int p_option, const Vector<String> &p_selected) {
- invoke_plugin_callback(p_slot, p_option, p_selected);
-}
-
-void EditorData::scene_tree_options_pressed(ContextMenuSlot p_slot, int p_option, const List<Node *> &p_selected) {
- TypedArray<Node> nodes;
- for (Node *selected : p_selected) {
- nodes.append(selected);
- }
- invoke_plugin_callback(p_slot, p_option, nodes);
-}
-
-void EditorData::script_editor_options_pressed(ContextMenuSlot p_slot, int p_option, const Ref<Resource> &p_script) {
- invoke_plugin_callback(p_slot, p_option, p_script);
-}
-
void EditorData::add_custom_type(const String &p_type, const String &p_inherits, const Ref<Script> &p_script, const Ref<Texture2D> &p_icon) {
ERR_FAIL_COND_MSG(p_script.is_null(), "It's not a reference to a valid Script object.");
CustomType ct;
diff --git a/editor/editor_data.h b/editor/editor_data.h
index 754d44c479..5b49304c73 100644
--- a/editor/editor_data.h
+++ b/editor/editor_data.h
@@ -37,7 +37,6 @@
class ConfigFile;
class EditorPlugin;
class EditorUndoRedoManager;
-class EditorContextMenuPlugin;
class PopupMenu;
/**
@@ -125,22 +124,6 @@ public:
uint64_t last_checked_version = 0;
};
- enum ContextMenuSlot {
- CONTEXT_SLOT_SCENE_TREE,
- CONTEXT_SLOT_FILESYSTEM,
- CONTEXT_SLOT_SCRIPT_EDITOR,
- CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE,
- };
-
- inline static constexpr int CONTEXT_MENU_ITEM_ID_BASE = 1000;
-
- struct ContextMenu {
- int p_slot;
- Ref<EditorContextMenuPlugin> plugin;
- };
-
- Vector<ContextMenu> context_menu_plugins;
-
private:
Vector<EditorPlugin *> editor_plugins;
HashMap<StringName, EditorPlugin *> extension_editor_plugins;
@@ -195,18 +178,6 @@ public:
bool has_extension_editor_plugin(const StringName &p_class_name);
EditorPlugin *get_extension_editor_plugin(const StringName &p_class_name);
- // Context menu plugin.
- void add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
- void remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
- int match_context_menu_shortcut(ContextMenuSlot p_slot, const Ref<InputEvent> &p_event);
-
- void add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector<String> &p_paths);
- void filesystem_options_pressed(ContextMenuSlot p_slot, int p_option, const Vector<String> &p_selected);
- void scene_tree_options_pressed(ContextMenuSlot p_slot, int p_option, const List<Node *> &p_selected);
- void script_editor_options_pressed(ContextMenuSlot p_slot, int p_option, const Ref<Resource> &p_script);
- template <typename T>
- void invoke_plugin_callback(ContextMenuSlot p_slot, int p_option, const T &p_arg);
-
void add_undo_redo_inspector_hook_callback(Callable p_callable); // Callbacks should have this signature: void (Object* undo_redo, Object *modified_object, String property, Variant new_value)
void remove_undo_redo_inspector_hook_callback(Callable p_callable);
const Vector<Callable> get_undo_redo_inspector_hook_callback();
diff --git a/editor/editor_dock_manager.cpp b/editor/editor_dock_manager.cpp
index 75135532aa..0bdda41f26 100644
--- a/editor/editor_dock_manager.cpp
+++ b/editor/editor_dock_manager.cpp
@@ -509,7 +509,7 @@ void EditorDockManager::save_docks_to_config(Ref<ConfigFile> p_layout, const Str
}
for (int i = 0; i < hsplits.size(); i++) {
- p_layout->set_value(p_section, "dock_hsplit_" + itos(i + 1), hsplits[i]->get_split_offset());
+ p_layout->set_value(p_section, "dock_hsplit_" + itos(i + 1), int(hsplits[i]->get_split_offset() / EDSCALE));
}
FileSystemDock::get_singleton()->save_layout_to_config(p_layout, p_section);
@@ -605,7 +605,7 @@ void EditorDockManager::load_docks_from_config(Ref<ConfigFile> p_layout, const S
continue;
}
int ofs = p_layout->get_value(p_section, "dock_hsplit_" + itos(i + 1));
- hsplits[i]->set_split_offset(ofs);
+ hsplits[i]->set_split_offset(ofs * EDSCALE);
}
FileSystemDock::get_singleton()->load_layout_from_config(p_layout, p_section);
diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp
index 8bba3493e5..f5f7b8f51c 100644
--- a/editor/editor_help.cpp
+++ b/editor/editor_help.cpp
@@ -39,6 +39,7 @@
#include "core/string/string_builder.h"
#include "core/version_generated.gen.h"
#include "editor/doc_data_compressed.gen.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_property_name_processor.h"
@@ -376,10 +377,10 @@ static void _add_type_to_rt(const String &p_type, const String &p_enum, bool p_i
}
p_rt->push_color(type_color);
- bool add_array = false;
+ bool add_typed_container = false;
if (can_ref) {
if (link_t.ends_with("[]")) {
- add_array = true;
+ add_typed_container = true;
link_t = link_t.trim_suffix("[]");
display_t = display_t.trim_suffix("[]");
@@ -387,6 +388,22 @@ static void _add_type_to_rt(const String &p_type, const String &p_enum, bool p_i
p_rt->add_text("Array");
p_rt->pop(); // meta
p_rt->add_text("[");
+ } else if (link_t.begins_with("Dictionary[")) {
+ add_typed_container = true;
+ link_t = link_t.trim_prefix("Dictionary[").trim_suffix("]");
+ display_t = display_t.trim_prefix("Dictionary[").trim_suffix("]");
+
+ p_rt->push_meta("#Dictionary", RichTextLabel::META_UNDERLINE_ON_HOVER); // class
+ p_rt->add_text("Dictionary");
+ p_rt->pop(); // meta
+ p_rt->add_text("[");
+ p_rt->push_meta("#" + link_t.get_slice(", ", 0), RichTextLabel::META_UNDERLINE_ON_HOVER); // class
+ p_rt->add_text(_contextualize_class_specifier(display_t.get_slice(", ", 0), p_class));
+ p_rt->pop(); // meta
+ p_rt->add_text(", ");
+
+ link_t = link_t.get_slice(", ", 1);
+ display_t = _contextualize_class_specifier(display_t.get_slice(", ", 1), p_class);
} else if (is_bitfield) {
p_rt->push_color(Color(type_color, 0.5));
p_rt->push_hint(TTR("This value is an integer composed as a bitmask of the following flags."));
@@ -405,7 +422,7 @@ static void _add_type_to_rt(const String &p_type, const String &p_enum, bool p_i
p_rt->add_text(display_t);
if (can_ref) {
p_rt->pop(); // meta
- if (add_array) {
+ if (add_typed_container) {
p_rt->add_text("]");
} else if (is_bitfield) {
p_rt->push_color(Color(type_color, 0.5));
@@ -2294,7 +2311,7 @@ void EditorHelp::_update_doc() {
void EditorHelp::_request_help(const String &p_string) {
Error err = _goto_desc(p_string);
if (err == OK) {
- EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
}
}
@@ -3589,7 +3606,7 @@ void EditorHelpBit::_update_labels() {
}
void EditorHelpBit::_go_to_help(const String &p_what) {
- EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
ScriptEditor::get_singleton()->goto_help(p_what);
emit_signal(SNAME("request_hide"));
}
diff --git a/editor/editor_help_search.cpp b/editor/editor_help_search.cpp
index 987c7f9c31..11be765bc2 100644
--- a/editor/editor_help_search.cpp
+++ b/editor/editor_help_search.cpp
@@ -32,6 +32,7 @@
#include "core/os/keyboard.h"
#include "editor/editor_feature_profile.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
@@ -201,7 +202,7 @@ void EditorHelpSearch::_confirmed() {
}
// Activate the script editor and emit the signal with the documentation link to display.
- EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
emit_signal(SNAME("go_to_help"), item->get_metadata(0));
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index a1cae374aa..da50ffc510 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -34,6 +34,7 @@
#include "core/os/keyboard.h"
#include "editor/doc_tools.h"
#include "editor/editor_feature_profile.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_property_name_processor.h"
#include "editor/editor_settings.h"
@@ -1037,7 +1038,7 @@ void EditorProperty::menu_option(int p_option) {
} break;
case MENU_OPEN_DOCUMENTATION: {
ScriptEditor::get_singleton()->goto_help(doc_path);
- EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
} break;
}
}
@@ -1297,7 +1298,7 @@ void EditorInspectorCategory::_handle_menu_option(int p_option) {
switch (p_option) {
case MENU_OPEN_DOCS:
ScriptEditor::get_singleton()->goto_help("class:" + doc_class_name);
- EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
break;
}
}
@@ -2245,7 +2246,7 @@ void EditorInspectorArray::_setup() {
}
move_vbox->add_child(ae.move_texture_rect);
- if (element_position < _get_array_count() - 1) {
+ if (element_position < count - 1) {
ae.move_down = memnew(Button);
ae.move_down->set_icon(get_editor_theme_icon(SNAME("MoveDown")));
ae.move_down->connect(SceneStringName(pressed), callable_mp(this, &EditorInspectorArray::_move_element).bind(element_position, element_position + 2));
diff --git a/editor/editor_interface.cpp b/editor/editor_interface.cpp
index 86b66ef410..fa6198f695 100644
--- a/editor/editor_interface.cpp
+++ b/editor/editor_interface.cpp
@@ -33,6 +33,7 @@
#include "editor/editor_command_palette.h"
#include "editor/editor_feature_profile.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_resource_preview.h"
@@ -215,7 +216,7 @@ Control *EditorInterface::get_base_control() const {
}
VBoxContainer *EditorInterface::get_editor_main_screen() const {
- return EditorNode::get_singleton()->get_main_screen_control();
+ return EditorNode::get_singleton()->get_editor_main_screen()->get_control();
}
ScriptEditor *EditorInterface::get_script_editor() const {
@@ -232,7 +233,7 @@ SubViewport *EditorInterface::get_editor_viewport_3d(int p_idx) const {
}
void EditorInterface::set_main_screen_editor(const String &p_name) {
- EditorNode::get_singleton()->select_editor_by_name(p_name);
+ EditorNode::get_singleton()->get_editor_main_screen()->select_by_name(p_name);
}
void EditorInterface::set_distraction_free_mode(bool p_enter) {
diff --git a/editor/editor_main_screen.cpp b/editor/editor_main_screen.cpp
new file mode 100644
index 0000000000..77bbee5a7f
--- /dev/null
+++ b/editor/editor_main_screen.cpp
@@ -0,0 +1,291 @@
+/**************************************************************************/
+/* editor_main_screen.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 "editor_main_screen.h"
+
+#include "core/io/config_file.h"
+#include "editor/editor_node.h"
+#include "editor/editor_settings.h"
+#include "editor/editor_string_names.h"
+#include "editor/plugins/editor_plugin.h"
+#include "scene/gui/box_container.h"
+#include "scene/gui/button.h"
+
+void EditorMainScreen::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_READY: {
+ if (EDITOR_3D < buttons.size() && buttons[EDITOR_3D]->is_visible()) {
+ // If the 3D editor is enabled, use this as the default.
+ select(EDITOR_3D);
+ return;
+ }
+
+ // Switch to the first main screen plugin that is enabled. Usually this is
+ // 2D, but may be subsequent ones if 2D is disabled in the feature profile.
+ for (int i = 0; i < buttons.size(); i++) {
+ Button *editor_button = buttons[i];
+ if (editor_button->is_visible()) {
+ select(i);
+ return;
+ }
+ }
+
+ select(-1);
+ } break;
+ case NOTIFICATION_THEME_CHANGED: {
+ for (int i = 0; i < buttons.size(); i++) {
+ Button *tb = buttons[i];
+ EditorPlugin *p_editor = editor_table[i];
+ Ref<Texture2D> icon = p_editor->get_icon();
+
+ if (icon.is_valid()) {
+ tb->set_icon(icon);
+ } else if (has_theme_icon(p_editor->get_name(), EditorStringName(EditorIcons))) {
+ tb->set_icon(get_theme_icon(p_editor->get_name(), EditorStringName(EditorIcons)));
+ }
+ }
+ } break;
+ }
+}
+
+void EditorMainScreen::set_button_container(HBoxContainer *p_button_hb) {
+ button_hb = p_button_hb;
+}
+
+void EditorMainScreen::save_layout_to_config(Ref<ConfigFile> p_config_file, const String &p_section) const {
+ int selected_main_editor_idx = -1;
+ for (int i = 0; i < buttons.size(); i++) {
+ if (buttons[i]->is_pressed()) {
+ selected_main_editor_idx = i;
+ break;
+ }
+ }
+ if (selected_main_editor_idx != -1) {
+ p_config_file->set_value(p_section, "selected_main_editor_idx", selected_main_editor_idx);
+ } else {
+ p_config_file->set_value(p_section, "selected_main_editor_idx", Variant());
+ }
+}
+
+void EditorMainScreen::load_layout_from_config(Ref<ConfigFile> p_config_file, const String &p_section) {
+ int selected_main_editor_idx = p_config_file->get_value(p_section, "selected_main_editor_idx", -1);
+ if (selected_main_editor_idx >= 0 && selected_main_editor_idx < buttons.size()) {
+ callable_mp(this, &EditorMainScreen::select).call_deferred(selected_main_editor_idx);
+ }
+}
+
+void EditorMainScreen::set_button_enabled(int p_index, bool p_enabled) {
+ ERR_FAIL_INDEX(p_index, buttons.size());
+ buttons[p_index]->set_visible(p_enabled);
+ if (!p_enabled && buttons[p_index]->is_pressed()) {
+ select(EDITOR_2D);
+ }
+}
+
+bool EditorMainScreen::is_button_enabled(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, buttons.size(), false);
+ return buttons[p_index]->is_visible();
+}
+
+int EditorMainScreen::_get_current_main_editor() const {
+ for (int i = 0; i < editor_table.size(); i++) {
+ if (editor_table[i] == selected_plugin) {
+ return i;
+ }
+ }
+
+ return 0;
+}
+
+void EditorMainScreen::select_next() {
+ int editor = _get_current_main_editor();
+
+ do {
+ if (editor == editor_table.size() - 1) {
+ editor = 0;
+ } else {
+ editor++;
+ }
+ } while (!buttons[editor]->is_visible());
+
+ select(editor);
+}
+
+void EditorMainScreen::select_prev() {
+ int editor = _get_current_main_editor();
+
+ do {
+ if (editor == 0) {
+ editor = editor_table.size() - 1;
+ } else {
+ editor--;
+ }
+ } while (!buttons[editor]->is_visible());
+
+ select(editor);
+}
+
+void EditorMainScreen::select_by_name(const String &p_name) {
+ ERR_FAIL_COND(p_name.is_empty());
+
+ for (int i = 0; i < buttons.size(); i++) {
+ if (buttons[i]->get_text() == p_name) {
+ select(i);
+ return;
+ }
+ }
+
+ ERR_FAIL_MSG("The editor name '" + p_name + "' was not found.");
+}
+
+void EditorMainScreen::select(int p_index) {
+ if (EditorNode::get_singleton()->is_changing_scene()) {
+ return;
+ }
+
+ ERR_FAIL_INDEX(p_index, editor_table.size());
+
+ if (!buttons[p_index]->is_visible()) { // Button hidden, no editor.
+ return;
+ }
+
+ for (int i = 0; i < buttons.size(); i++) {
+ buttons[i]->set_pressed_no_signal(i == p_index);
+ }
+
+ EditorPlugin *new_editor = editor_table[p_index];
+ ERR_FAIL_NULL(new_editor);
+
+ if (selected_plugin == new_editor) {
+ return;
+ }
+
+ if (selected_plugin) {
+ selected_plugin->make_visible(false);
+ }
+
+ selected_plugin = new_editor;
+ selected_plugin->make_visible(true);
+ selected_plugin->selected_notify();
+
+ EditorData &editor_data = EditorNode::get_editor_data();
+ int plugin_count = editor_data.get_editor_plugin_count();
+ for (int i = 0; i < plugin_count; i++) {
+ editor_data.get_editor_plugin(i)->notify_main_screen_changed(selected_plugin->get_name());
+ }
+
+ EditorNode::get_singleton()->update_distraction_free_mode();
+}
+
+int EditorMainScreen::get_selected_index() const {
+ for (int i = 0; i < editor_table.size(); i++) {
+ if (selected_plugin == editor_table[i]) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+int EditorMainScreen::get_plugin_index(EditorPlugin *p_editor) const {
+ int screen = -1;
+ for (int i = 0; i < editor_table.size(); i++) {
+ if (p_editor == editor_table[i]) {
+ screen = i;
+ break;
+ }
+ }
+ return screen;
+}
+
+EditorPlugin *EditorMainScreen::get_selected_plugin() const {
+ return selected_plugin;
+}
+
+VBoxContainer *EditorMainScreen::get_control() const {
+ return main_screen_vbox;
+}
+
+void EditorMainScreen::add_main_plugin(EditorPlugin *p_editor) {
+ Button *tb = memnew(Button);
+ tb->set_toggle_mode(true);
+ tb->set_theme_type_variation("MainScreenButton");
+ tb->set_name(p_editor->get_name());
+ tb->set_text(p_editor->get_name());
+
+ Ref<Texture2D> icon = p_editor->get_icon();
+ if (icon.is_null() && has_theme_icon(p_editor->get_name(), EditorStringName(EditorIcons))) {
+ icon = get_editor_theme_icon(p_editor->get_name());
+ }
+ if (icon.is_valid()) {
+ tb->set_icon(icon);
+ // Make sure the control is updated if the icon is reimported.
+ icon->connect_changed(callable_mp((Control *)tb, &Control::update_minimum_size));
+ }
+
+ tb->connect(SceneStringName(pressed), callable_mp(this, &EditorMainScreen::select).bind(buttons.size()));
+
+ buttons.push_back(tb);
+ button_hb->add_child(tb);
+ editor_table.push_back(p_editor);
+}
+
+void EditorMainScreen::remove_main_plugin(EditorPlugin *p_editor) {
+ // Remove the main editor button and update the bindings of
+ // all buttons behind it to point to the correct main window.
+ for (int i = buttons.size() - 1; i >= 0; i--) {
+ if (p_editor->get_name() == buttons[i]->get_text()) {
+ if (buttons[i]->is_pressed()) {
+ select(EDITOR_SCRIPT);
+ }
+
+ memdelete(buttons[i]);
+ buttons.remove_at(i);
+
+ break;
+ } else {
+ buttons[i]->disconnect(SceneStringName(pressed), callable_mp(this, &EditorMainScreen::select));
+ buttons[i]->connect(SceneStringName(pressed), callable_mp(this, &EditorMainScreen::select).bind(i - 1));
+ }
+ }
+
+ if (selected_plugin == p_editor) {
+ selected_plugin = nullptr;
+ }
+
+ editor_table.erase(p_editor);
+}
+
+EditorMainScreen::EditorMainScreen() {
+ main_screen_vbox = memnew(VBoxContainer);
+ main_screen_vbox->set_name("MainScreen");
+ main_screen_vbox->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ main_screen_vbox->add_theme_constant_override("separation", 0);
+ add_child(main_screen_vbox);
+}
diff --git a/core/string/translation.compat.inc b/editor/editor_main_screen.h
index 68bd1831e4..153a182bc2 100644
--- a/core/string/translation.compat.inc
+++ b/editor/editor_main_screen.h
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* translation.compat.inc */
+/* editor_main_screen.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,14 +28,64 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef DISABLE_DEPRECATED
+#ifndef EDITOR_MAIN_SCREEN_H
+#define EDITOR_MAIN_SCREEN_H
-void Translation::_bind_compatibility_methods() {
- ClassDB::bind_compatibility_method(D_METHOD("add_message", "src_message", "xlated_message", "context"), &Translation::add_message, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("add_plural_message", "src_message", "xlated_messages", "context"), &Translation::add_plural_message, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_message", "src_message", "context"), &Translation::get_message, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_plural_message", "src_message", "src_plural_message", "n", "context"), &Translation::get_plural_message, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("erase_message", "src_message", "context"), &Translation::erase_message, DEFVAL(""));
-}
+#include "scene/gui/panel_container.h"
-#endif
+class Button;
+class ConfigFile;
+class EditorPlugin;
+class HBoxContainer;
+class VBoxContainer;
+
+class EditorMainScreen : public PanelContainer {
+ GDCLASS(EditorMainScreen, PanelContainer);
+
+public:
+ enum EditorTable {
+ EDITOR_2D = 0,
+ EDITOR_3D,
+ EDITOR_SCRIPT,
+ EDITOR_ASSETLIB,
+ };
+
+private:
+ VBoxContainer *main_screen_vbox = nullptr;
+ EditorPlugin *selected_plugin = nullptr;
+
+ HBoxContainer *button_hb = nullptr;
+ Vector<Button *> buttons;
+ Vector<EditorPlugin *> editor_table;
+
+ int _get_current_main_editor() const;
+
+protected:
+ void _notification(int p_what);
+
+public:
+ void set_button_container(HBoxContainer *p_button_hb);
+
+ void save_layout_to_config(Ref<ConfigFile> p_config_file, const String &p_section) const;
+ void load_layout_from_config(Ref<ConfigFile> p_config_file, const String &p_section);
+
+ void set_button_enabled(int p_index, bool p_enabled);
+ bool is_button_enabled(int p_index) const;
+
+ void select_next();
+ void select_prev();
+ void select_by_name(const String &p_name);
+ void select(int p_index);
+ int get_selected_index() const;
+ int get_plugin_index(EditorPlugin *p_editor) const;
+ EditorPlugin *get_selected_plugin() const;
+
+ VBoxContainer *get_control() const;
+
+ void add_main_plugin(EditorPlugin *p_editor);
+ void remove_main_plugin(EditorPlugin *p_editor);
+
+ EditorMainScreen();
+};
+
+#endif // EDITOR_MAIN_SCREEN_H
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 6899a35ded..66fd2cf904 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -45,6 +45,7 @@
#include "core/string/translation_server.h"
#include "core/version.h"
#include "editor/editor_string_names.h"
+#include "editor/plugins/editor_context_menu_plugin.h"
#include "main/main.h"
#include "scene/3d/bone_attachment_3d.h"
#include "scene/animation/animation_tree.h"
@@ -88,6 +89,7 @@
#include "editor/editor_interface.h"
#include "editor/editor_layouts_dialog.h"
#include "editor/editor_log.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_native_shader_source_visualizer.h"
#include "editor/editor_paths.h"
#include "editor/editor_properties.h"
@@ -348,19 +350,19 @@ void EditorNode::shortcut_input(const Ref<InputEvent> &p_event) {
if (ED_IS_SHORTCUT("editor/filter_files", p_event)) {
FileSystemDock::get_singleton()->focus_on_filter();
} else if (ED_IS_SHORTCUT("editor/editor_2d", p_event)) {
- editor_select(EDITOR_2D);
+ editor_main_screen->select(EditorMainScreen::EDITOR_2D);
} else if (ED_IS_SHORTCUT("editor/editor_3d", p_event)) {
- editor_select(EDITOR_3D);
+ editor_main_screen->select(EditorMainScreen::EDITOR_3D);
} else if (ED_IS_SHORTCUT("editor/editor_script", p_event)) {
- editor_select(EDITOR_SCRIPT);
+ editor_main_screen->select(EditorMainScreen::EDITOR_SCRIPT);
} else if (ED_IS_SHORTCUT("editor/editor_help", p_event)) {
emit_signal(SNAME("request_help_search"), "");
} else if (ED_IS_SHORTCUT("editor/editor_assetlib", p_event) && AssetLibraryEditorPlugin::is_available()) {
- editor_select(EDITOR_ASSETLIB);
+ editor_main_screen->select(EditorMainScreen::EDITOR_ASSETLIB);
} else if (ED_IS_SHORTCUT("editor/editor_next", p_event)) {
- _editor_select_next();
+ editor_main_screen->select_next();
} else if (ED_IS_SHORTCUT("editor/editor_prev", p_event)) {
- _editor_select_prev();
+ editor_main_screen->select_prev();
} else if (ED_IS_SHORTCUT("editor/command_palette", p_event)) {
_open_command_palette();
} else if (ED_IS_SHORTCUT("editor/toggle_last_opened_bottom_panel", p_event)) {
@@ -489,26 +491,6 @@ void EditorNode::_gdextensions_reloaded() {
EditorHelp::generate_doc();
}
-void EditorNode::_select_default_main_screen_plugin() {
- if (EDITOR_3D < main_editor_buttons.size() && main_editor_buttons[EDITOR_3D]->is_visible()) {
- // If the 3D editor is enabled, use this as the default.
- editor_select(EDITOR_3D);
- return;
- }
-
- // Switch to the first main screen plugin that is enabled. Usually this is
- // 2D, but may be subsequent ones if 2D is disabled in the feature profile.
- for (int i = 0; i < main_editor_buttons.size(); i++) {
- Button *editor_button = main_editor_buttons[i];
- if (editor_button->is_visible()) {
- editor_select(i);
- return;
- }
- }
-
- editor_select(-1);
-}
-
void EditorNode::_update_theme(bool p_skip_creation) {
if (!p_skip_creation) {
theme = EditorThemeManager::generate_theme(theme);
@@ -546,7 +528,7 @@ void EditorNode::_update_theme(bool p_skip_creation) {
main_vbox->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, theme->get_constant(SNAME("window_border_margin"), EditorStringName(Editor)));
main_vbox->add_theme_constant_override("separation", theme->get_constant(SNAME("top_bar_separation"), EditorStringName(Editor)));
- scene_root_parent->add_theme_style_override(SceneStringName(panel), theme->get_stylebox(SNAME("Content"), EditorStringName(EditorStyles)));
+ editor_main_screen->add_theme_style_override(SceneStringName(panel), theme->get_stylebox(SNAME("Content"), EditorStringName(EditorStyles)));
bottom_panel->add_theme_style_override(SceneStringName(panel), theme->get_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
distraction_free->set_icon(theme->get_icon(SNAME("DistractionFree"), EditorStringName(EditorIcons)));
distraction_free->add_theme_style_override(SceneStringName(pressed), theme->get_stylebox(CoreStringName(normal), "FlatMenuButton"));
@@ -560,18 +542,6 @@ void EditorNode::_update_theme(bool p_skip_creation) {
bottom_panel->add_theme_style_override(SceneStringName(panel), theme->get_stylebox(SNAME("BottomPanelDebuggerOverride"), EditorStringName(EditorStyles)));
}
- for (int i = 0; i < main_editor_buttons.size(); i++) {
- Button *tb = main_editor_buttons[i];
- EditorPlugin *p_editor = editor_table[i];
- Ref<Texture2D> icon = p_editor->get_icon();
-
- if (icon.is_valid()) {
- tb->set_icon(icon);
- } else if (theme->has_icon(p_editor->get_name(), EditorStringName(EditorIcons))) {
- tb->set_icon(theme->get_icon(p_editor->get_name(), EditorStringName(EditorIcons)));
- }
- }
-
_update_renderer_color();
}
@@ -748,8 +718,6 @@ void EditorNode::_notification(int p_what) {
feature_profile_manager->notify_changed();
- _select_default_main_screen_plugin();
-
// Save the project after opening to mark it as last modified, except in headless mode.
if (DisplayServer::get_singleton()->window_can_draw()) {
ProjectSettings::get_singleton()->save();
@@ -1293,38 +1261,10 @@ void EditorNode::_node_renamed() {
}
}
-void EditorNode::_editor_select_next() {
- int editor = _get_current_main_editor();
-
- do {
- if (editor == editor_table.size() - 1) {
- editor = 0;
- } else {
- editor++;
- }
- } while (!main_editor_buttons[editor]->is_visible());
-
- editor_select(editor);
-}
-
void EditorNode::_open_command_palette() {
command_palette->open_popup();
}
-void EditorNode::_editor_select_prev() {
- int editor = _get_current_main_editor();
-
- do {
- if (editor == 0) {
- editor = editor_table.size() - 1;
- } else {
- editor--;
- }
- } while (!main_editor_buttons[editor]->is_visible());
-
- editor_select(editor);
-}
-
Error EditorNode::load_resource(const String &p_resource, bool p_ignore_broken_deps) {
dependency_errors.clear();
@@ -2645,16 +2585,11 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update
if (!inspector_only) {
EditorPlugin *main_plugin = editor_data.get_handling_main_editor(current_obj);
- int plugin_index = 0;
- for (; plugin_index < editor_table.size(); plugin_index++) {
- if (editor_table[plugin_index] == main_plugin) {
- if (!main_editor_buttons[plugin_index]->is_visible()) {
- main_plugin = nullptr; // If button is not visible, then no plugin is active.
- }
-
- break;
- }
+ int plugin_index = editor_main_screen->get_plugin_index(main_plugin);
+ if (main_plugin && plugin_index >= 0 && !editor_main_screen->is_button_enabled(plugin_index)) {
+ main_plugin = nullptr;
}
+ EditorPlugin *editor_plugin_screen = editor_main_screen->get_selected_plugin();
ObjectID editor_owner_id = editor_owner->get_instance_id();
if (main_plugin && !skip_main_plugin) {
@@ -2671,7 +2606,7 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update
editor_plugin_screen->edit(nullptr);
active_plugins[editor_owner_id].erase(editor_plugin_screen);
// Update screen main_plugin.
- editor_select(plugin_index);
+ editor_main_screen->select(plugin_index);
main_plugin->edit(current_obj);
} else {
editor_plugin_screen->edit(current_obj);
@@ -3294,9 +3229,9 @@ void EditorNode::_screenshot(bool p_use_utc) {
}
void EditorNode::_save_screenshot(NodePath p_path) {
- Control *editor_main_screen = EditorInterface::get_singleton()->get_editor_main_screen();
- ERR_FAIL_NULL_MSG(editor_main_screen, "Cannot get the editor main screen control.");
- Viewport *viewport = editor_main_screen->get_viewport();
+ Control *main_screen_control = editor_main_screen->get_control();
+ ERR_FAIL_NULL_MSG(main_screen_control, "Cannot get the editor main screen control.");
+ Viewport *viewport = main_screen_control->get_viewport();
ERR_FAIL_NULL_MSG(viewport, "Cannot get a viewport from the editor main screen.");
Ref<ViewportTexture> texture = viewport->get_texture();
ERR_FAIL_COND_MSG(texture.is_null(), "Cannot get a viewport texture from the editor main screen.");
@@ -3501,97 +3436,9 @@ void EditorNode::_update_file_menu_closed() {
file_menu->set_item_disabled(file_menu->get_item_index(FILE_OPEN_PREV), false);
}
-VBoxContainer *EditorNode::get_main_screen_control() {
- return main_screen_vbox;
-}
-
-void EditorNode::editor_select(int p_which) {
- static bool selecting = false;
- if (selecting || changing_scene) {
- return;
- }
-
- ERR_FAIL_INDEX(p_which, editor_table.size());
-
- if (!main_editor_buttons[p_which]->is_visible()) { // Button hidden, no editor.
- return;
- }
-
- selecting = true;
-
- for (int i = 0; i < main_editor_buttons.size(); i++) {
- main_editor_buttons[i]->set_pressed(i == p_which);
- }
-
- selecting = false;
-
- EditorPlugin *new_editor = editor_table[p_which];
- ERR_FAIL_NULL(new_editor);
-
- if (editor_plugin_screen == new_editor) {
- return;
- }
-
- if (editor_plugin_screen) {
- editor_plugin_screen->make_visible(false);
- }
-
- editor_plugin_screen = new_editor;
- editor_plugin_screen->make_visible(true);
- editor_plugin_screen->selected_notify();
-
- int plugin_count = editor_data.get_editor_plugin_count();
- for (int i = 0; i < plugin_count; i++) {
- editor_data.get_editor_plugin(i)->notify_main_screen_changed(editor_plugin_screen->get_name());
- }
-
- if (EDITOR_GET("interface/editor/separate_distraction_mode")) {
- if (p_which == EDITOR_SCRIPT) {
- set_distraction_free_mode(script_distraction_free);
- } else {
- set_distraction_free_mode(scene_distraction_free);
- }
- }
-}
-
-void EditorNode::select_editor_by_name(const String &p_name) {
- ERR_FAIL_COND(p_name.is_empty());
-
- for (int i = 0; i < main_editor_buttons.size(); i++) {
- if (main_editor_buttons[i]->get_text() == p_name) {
- editor_select(i);
- return;
- }
- }
-
- ERR_FAIL_MSG("The editor name '" + p_name + "' was not found.");
-}
-
void EditorNode::add_editor_plugin(EditorPlugin *p_editor, bool p_config_changed) {
if (p_editor->has_main_screen()) {
- Button *tb = memnew(Button);
- tb->set_toggle_mode(true);
- tb->set_theme_type_variation("MainScreenButton");
- tb->set_name(p_editor->get_name());
- tb->set_text(p_editor->get_name());
-
- Ref<Texture2D> icon = p_editor->get_icon();
- if (icon.is_null() && singleton->theme->has_icon(p_editor->get_name(), EditorStringName(EditorIcons))) {
- icon = singleton->theme->get_icon(p_editor->get_name(), EditorStringName(EditorIcons));
- }
- if (icon.is_valid()) {
- tb->set_icon(icon);
- // Make sure the control is updated if the icon is reimported.
- icon->connect_changed(callable_mp((Control *)tb, &Control::update_minimum_size));
- }
-
- tb->connect(SceneStringName(pressed), callable_mp(singleton, &EditorNode::editor_select).bind(singleton->main_editor_buttons.size()));
-
- singleton->main_editor_buttons.push_back(tb);
- singleton->main_editor_button_hb->add_child(tb);
- singleton->editor_table.push_back(p_editor);
-
- singleton->distraction_free->move_to_front();
+ singleton->editor_main_screen->add_main_plugin(p_editor);
}
singleton->editor_data.add_editor_plugin(p_editor);
singleton->add_child(p_editor);
@@ -3602,29 +3449,7 @@ void EditorNode::add_editor_plugin(EditorPlugin *p_editor, bool p_config_changed
void EditorNode::remove_editor_plugin(EditorPlugin *p_editor, bool p_config_changed) {
if (p_editor->has_main_screen()) {
- // Remove the main editor button and update the bindings of
- // all buttons behind it to point to the correct main window.
- for (int i = singleton->main_editor_buttons.size() - 1; i >= 0; i--) {
- if (p_editor->get_name() == singleton->main_editor_buttons[i]->get_text()) {
- if (singleton->main_editor_buttons[i]->is_pressed()) {
- singleton->editor_select(EDITOR_SCRIPT);
- }
-
- memdelete(singleton->main_editor_buttons[i]);
- singleton->main_editor_buttons.remove_at(i);
-
- break;
- } else {
- singleton->main_editor_buttons[i]->disconnect(SceneStringName(pressed), callable_mp(singleton, &EditorNode::editor_select));
- singleton->main_editor_buttons[i]->connect(SceneStringName(pressed), callable_mp(singleton, &EditorNode::editor_select).bind(i - 1));
- }
- }
-
- if (singleton->editor_plugin_screen == p_editor) {
- singleton->editor_plugin_screen = nullptr;
- }
-
- singleton->editor_table.erase(p_editor);
+ singleton->editor_main_screen->remove_main_plugin(p_editor);
}
p_editor->make_visible(false);
p_editor->clear();
@@ -3848,19 +3673,8 @@ void EditorNode::set_edited_scene_root(Node *p_scene, bool p_auto_add) {
}
}
-int EditorNode::_get_current_main_editor() {
- for (int i = 0; i < editor_table.size(); i++) {
- if (editor_table[i] == editor_plugin_screen) {
- return i;
- }
- }
-
- return 0;
-}
-
Dictionary EditorNode::_get_main_scene_state() {
Dictionary state;
- state["main_tab"] = _get_current_main_editor();
state["scene_tree_offset"] = SceneTreeDock::get_singleton()->get_tree_editor()->get_scene_tree()->get_vscroll_bar()->get_value();
state["property_edit_offset"] = InspectorDock::get_inspector_singleton()->get_scroll_offset();
state["node_filter"] = SceneTreeDock::get_singleton()->get_filter();
@@ -3874,32 +3688,19 @@ void EditorNode::_set_main_scene_state(Dictionary p_state, Node *p_for_scene) {
changing_scene = false;
- int current_tab = -1;
- for (int i = 0; i < editor_table.size(); i++) {
- if (editor_plugin_screen == editor_table[i]) {
- current_tab = i;
- break;
- }
- }
-
- if (p_state.has("editor_index")) {
- int index = p_state["editor_index"];
- if (current_tab < 2) { // If currently in spatial/2d, only switch to spatial/2d. If currently in script, stay there.
- if (index < 2 || !get_edited_scene()) {
- editor_select(index);
- }
- }
- }
-
if (get_edited_scene()) {
+ int current_tab = editor_main_screen->get_selected_index();
if (current_tab < 2) {
- Node *editor_node = SceneTreeDock::get_singleton()->get_tree_editor()->get_selected();
- editor_node = editor_node == nullptr ? get_edited_scene() : editor_node;
+ // Switch between 2D and 3D if currently in 2D or 3D.
+ Node *selected_node = SceneTreeDock::get_singleton()->get_tree_editor()->get_selected();
+ if (!selected_node) {
+ selected_node = get_edited_scene();
+ }
- if (Object::cast_to<CanvasItem>(editor_node)) {
- editor_select(EDITOR_2D);
- } else if (Object::cast_to<Node3D>(editor_node)) {
- editor_select(EDITOR_3D);
+ if (Object::cast_to<CanvasItem>(selected_node)) {
+ editor_main_screen->select(EditorMainScreen::EDITOR_2D);
+ } else if (Object::cast_to<Node3D>(selected_node)) {
+ editor_main_screen->select(EditorMainScreen::EDITOR_3D);
}
}
}
@@ -4065,7 +3866,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b
}
}
- if (p_clear_errors) {
+ if (p_clear_errors && !load_errors_queued_to_display) {
load_errors->clear();
}
@@ -4404,6 +4205,21 @@ bool EditorNode::is_additional_node_in_scene(Node *p_edited_scene, Node *p_reimp
return true;
}
+void EditorNode::get_scene_editor_data_for_node(Node *p_root, Node *p_node, HashMap<NodePath, SceneEditorDataEntry> &p_table) {
+ SceneEditorDataEntry new_entry;
+ new_entry.is_display_folded = p_node->is_displayed_folded();
+
+ if (p_root != p_node) {
+ new_entry.is_editable = p_root->is_editable_instance(p_node);
+ }
+
+ p_table.insert(p_root->get_path_to(p_node), new_entry);
+
+ for (int i = 0; i < p_node->get_child_count(); i++) {
+ get_scene_editor_data_for_node(p_root, p_node->get_child(i), p_table);
+ }
+}
+
void EditorNode::get_preload_scene_modification_table(
Node *p_edited_scene,
Node *p_reimported_root,
@@ -4510,7 +4326,7 @@ void EditorNode::get_preload_scene_modification_table(
void EditorNode::get_preload_modifications_reference_to_nodes(
Node *p_root,
Node *p_node,
- List<Node *> &p_excluded_nodes,
+ HashSet<Node *> &p_excluded_nodes,
List<Node *> &p_instance_list_with_children,
HashMap<NodePath, ModificationNodeEntry> &p_modification_table) {
if (!p_excluded_nodes.find(p_node)) {
@@ -4731,14 +4547,26 @@ void EditorNode::add_io_error(const String &p_error) {
DEV_ASSERT(Thread::get_caller_id() == Thread::get_main_id());
singleton->load_errors->add_image(singleton->theme->get_icon(SNAME("Error"), EditorStringName(EditorIcons)));
singleton->load_errors->add_text(p_error + "\n");
- EditorInterface::get_singleton()->popup_dialog_centered_ratio(singleton->load_error_dialog, 0.5);
+ // When a progress dialog is displayed, we will wait for it ot close before displaying
+ // the io errors to prevent the io popup to set it's parent to the progress dialog.
+ if (singleton->progress_dialog->is_visible()) {
+ singleton->load_errors_queued_to_display = true;
+ } else {
+ EditorInterface::get_singleton()->popup_dialog_centered_ratio(singleton->load_error_dialog, 0.5);
+ }
}
void EditorNode::add_io_warning(const String &p_warning) {
DEV_ASSERT(Thread::get_caller_id() == Thread::get_main_id());
singleton->load_errors->add_image(singleton->theme->get_icon(SNAME("Warning"), EditorStringName(EditorIcons)));
singleton->load_errors->add_text(p_warning + "\n");
- EditorInterface::get_singleton()->popup_dialog_centered_ratio(singleton->load_error_dialog, 0.5);
+ // When a progress dialog is displayed, we will wait for it ot close before displaying
+ // the io errors to prevent the io popup to set it's parent to the progress dialog.
+ if (singleton->progress_dialog->is_visible()) {
+ singleton->load_errors_queued_to_display = true;
+ } else {
+ EditorInterface::get_singleton()->popup_dialog_centered_ratio(singleton->load_error_dialog, 0.5);
+ }
}
bool EditorNode::_find_scene_in_use(Node *p_node, const String &p_path) const {
@@ -5009,6 +4837,14 @@ void EditorNode::progress_end_task_bg(const String &p_task) {
singleton->progress_hb->end_task(p_task);
}
+void EditorNode::_progress_dialog_visibility_changed() {
+ // Open the io errors after the progress dialog is closed.
+ if (load_errors_queued_to_display && !progress_dialog->is_visible()) {
+ EditorInterface::get_singleton()->popup_dialog_centered_ratio(singleton->load_error_dialog, 0.5);
+ load_errors_queued_to_display = false;
+ }
+}
+
String EditorNode::_get_system_info() const {
String distribution_name = OS::get_singleton()->get_distribution_name();
if (distribution_name.is_empty()) {
@@ -5342,18 +5178,7 @@ void EditorNode::_save_central_editor_layout_to_config(Ref<ConfigFile> p_config_
// Main editor (plugin).
- int selected_main_editor_idx = -1;
- for (int i = 0; i < main_editor_buttons.size(); i++) {
- if (main_editor_buttons[i]->is_pressed()) {
- selected_main_editor_idx = i;
- break;
- }
- }
- if (selected_main_editor_idx != -1) {
- p_config_file->set_value(EDITOR_NODE_CONFIG_SECTION, "selected_main_editor_idx", selected_main_editor_idx);
- } else {
- p_config_file->set_value(EDITOR_NODE_CONFIG_SECTION, "selected_main_editor_idx", Variant());
- }
+ editor_main_screen->save_layout_to_config(p_config_file, EDITOR_NODE_CONFIG_SECTION);
}
void EditorNode::_load_central_editor_layout_from_config(Ref<ConfigFile> p_config_file) {
@@ -5375,12 +5200,7 @@ void EditorNode::_load_central_editor_layout_from_config(Ref<ConfigFile> p_confi
// Main editor (plugin).
- if (p_config_file->has_section_key(EDITOR_NODE_CONFIG_SECTION, "selected_main_editor_idx")) {
- int selected_main_editor_idx = p_config_file->get_value(EDITOR_NODE_CONFIG_SECTION, "selected_main_editor_idx");
- if (selected_main_editor_idx >= 0 && selected_main_editor_idx < main_editor_buttons.size()) {
- callable_mp(this, &EditorNode::editor_select).call_deferred(selected_main_editor_idx);
- }
- }
+ editor_main_screen->load_layout_from_config(p_config_file, EDITOR_NODE_CONFIG_SECTION);
}
void EditorNode::_save_window_settings_to_config(Ref<ConfigFile> p_layout, const String &p_section) {
@@ -5701,15 +5521,9 @@ void EditorNode::_cancel_close_scene_tab() {
void EditorNode::_toggle_distraction_free_mode() {
if (EDITOR_GET("interface/editor/separate_distraction_mode")) {
- int screen = -1;
- for (int i = 0; i < editor_table.size(); i++) {
- if (editor_plugin_screen == editor_table[i]) {
- screen = i;
- break;
- }
- }
+ int screen = editor_main_screen->get_selected_index();
- if (screen == EDITOR_SCRIPT) {
+ if (screen == EditorMainScreen::EDITOR_SCRIPT) {
script_distraction_free = !script_distraction_free;
set_distraction_free_mode(script_distraction_free);
} else {
@@ -5721,6 +5535,18 @@ void EditorNode::_toggle_distraction_free_mode() {
}
}
+void EditorNode::update_distraction_free_mode() {
+ if (!EDITOR_GET("interface/editor/separate_distraction_mode")) {
+ return;
+ }
+ int screen = editor_main_screen->get_selected_index();
+ if (screen == EditorMainScreen::EDITOR_SCRIPT) {
+ set_distraction_free_mode(script_distraction_free);
+ } else {
+ set_distraction_free_mode(scene_distraction_free);
+ }
+}
+
void EditorNode::set_distraction_free_mode(bool p_enter) {
distraction_free->set_pressed(p_enter);
@@ -5984,12 +5810,14 @@ void EditorNode::reload_scene(const String &p_path) {
scene_tabs->set_current_tab(current_tab);
}
-void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, List<Node *> &p_instance_list) {
+void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, HashSet<Node *> &p_instance_list) {
String scene_file_path = p_node->get_scene_file_path();
- // This is going to get messy...
+ bool valid_instance_found = false;
+
+ // Attempt to find all the instances matching path we're going to reload.
if (p_node->get_scene_file_path() == p_instance_path) {
- p_instance_list.push_back(p_node);
+ valid_instance_found = true;
} else {
Node *current_node = p_node;
@@ -5997,7 +5825,7 @@ void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node *
while (inherited_state.is_valid()) {
String inherited_path = inherited_state->get_path();
if (inherited_path == p_instance_path) {
- p_instance_list.push_back(p_node);
+ valid_instance_found = true;
break;
}
@@ -6005,6 +5833,19 @@ void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node *
}
}
+ // Instead of adding this instance directly, if its not owned by the scene, walk its ancestors
+ // and find the first node still owned by the scene. This is what we will reloading instead.
+ if (valid_instance_found) {
+ Node *current_node = p_node;
+ while (true) {
+ if (current_node->get_owner() == p_root || current_node->get_owner() == nullptr) {
+ p_instance_list.insert(current_node);
+ break;
+ }
+ current_node = current_node->get_parent();
+ }
+ }
+
for (int i = 0; i < p_node->get_child_count(); i++) {
find_all_instances_inheriting_path_in_node(p_root, p_node->get_child(i), p_instance_path, p_instance_list);
}
@@ -6023,20 +5864,20 @@ void EditorNode::preload_reimporting_with_path_in_edited_scenes(const List<Strin
Node *edited_scene_root = editor_data.get_edited_scene_root(current_scene_idx);
if (edited_scene_root) {
- SceneModificationsEntry scene_motifications;
+ SceneModificationsEntry scene_modifications;
for (const String &instance_path : p_scenes) {
if (editor_data.get_scene_path(current_scene_idx) == instance_path) {
continue;
}
- List<Node *> instance_list;
- find_all_instances_inheriting_path_in_node(edited_scene_root, edited_scene_root, instance_path, instance_list);
- if (instance_list.size() > 0) {
+ HashSet<Node *> instances_to_reimport;
+ find_all_instances_inheriting_path_in_node(edited_scene_root, edited_scene_root, instance_path, instances_to_reimport);
+ if (instances_to_reimport.size() > 0) {
editor_data.set_edited_scene(current_scene_idx);
List<Node *> instance_list_with_children;
- for (Node *original_node : instance_list) {
+ for (Node *original_node : instances_to_reimport) {
InstanceModificationsEntry instance_modifications;
// Fetching all the modified properties of the nodes reimported scene.
@@ -6044,19 +5885,19 @@ void EditorNode::preload_reimporting_with_path_in_edited_scenes(const List<Strin
instance_modifications.original_node = original_node;
instance_modifications.instance_path = instance_path;
- scene_motifications.instance_list.push_back(instance_modifications);
+ scene_modifications.instance_list.push_back(instance_modifications);
instance_list_with_children.push_back(original_node);
get_children_nodes(original_node, instance_list_with_children);
}
// Search the scene to find nodes that references the nodes will be recreated.
- get_preload_modifications_reference_to_nodes(edited_scene_root, edited_scene_root, instance_list, instance_list_with_children, scene_motifications.other_instances_modifications);
+ get_preload_modifications_reference_to_nodes(edited_scene_root, edited_scene_root, instances_to_reimport, instance_list_with_children, scene_modifications.other_instances_modifications);
}
}
- if (scene_motifications.instance_list.size() > 0) {
- scenes_modification_table[current_scene_idx] = scene_motifications;
+ if (scene_modifications.instance_list.size() > 0) {
+ scenes_modification_table[current_scene_idx] = scene_modifications;
}
}
}
@@ -6179,10 +6020,10 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() {
// Instantiate early so that caches cleared on load in SceneState can be rebuilt early.
Node *instantiated_node = nullptr;
- // If we are in a inherit scene, it's easier to create a new base scene and
+ // If we are in a inherited scene, it's easier to create a new base scene and
// grab the node from there.
// When scene_path_to_node is '.' and we have scene_inherited_state, it's because
- // it's a muli-level inheritance scene. We should use
+ // it's a multi-level inheritance scene. We should use
NodePath scene_path_to_node = current_edited_scene->get_path_to(original_node);
Ref<SceneState> scene_state = current_edited_scene->get_scene_inherited_state();
if (scene_path_to_node != "." && scene_state.is_valid() && scene_state->get_path() != instance_modifications.instance_path && scene_state->find_node_by_path(scene_path_to_node) >= 0) {
@@ -6206,9 +6047,9 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() {
if (!instantiated_node) {
// If no base scene was found to create the node, we will use the reimported packed scene directly.
- // But, when the current edited scene is the reimported scene, it's because it's a inherited scene
- // of the reimported scene. In that case, we will not instantiate current_packed_scene, because
- // we would reinstanciate ourself. Using the base scene is better.
+ // But, when the current edited scene is the reimported scene, it's because it's an inherited scene
+ // derived from the reimported scene. In that case, we will not instantiate current_packed_scene, because
+ // we would reinstantiate ourself. Using the base scene is better.
if (current_edited_scene == original_node) {
if (base_packed_scene.is_valid()) {
instantiated_node = base_packed_scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
@@ -6264,6 +6105,17 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() {
// crash when reimporting scenes with animations when "Editable children" was enabled.
replace_history_reimported_nodes(original_node, instantiated_node, original_node);
+ // Reset the editable instance state.
+ HashMap<NodePath, SceneEditorDataEntry> scene_editor_data_table;
+ Node *owner = original_node->get_owner();
+ if (!owner) {
+ owner = original_node;
+ }
+
+ get_scene_editor_data_for_node(owner, original_node, scene_editor_data_table);
+
+ bool original_node_scene_instance_load_placeholder = original_node->get_scene_instance_load_placeholder();
+
// Delete all the remaining node children.
while (original_node->get_child_count()) {
Node *child = original_node->get_child(0);
@@ -6272,16 +6124,6 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() {
child->queue_free();
}
- // Reset the editable instance state.
- bool is_editable = true;
- Node *owner = original_node->get_owner();
- if (owner) {
- is_editable = owner->is_editable_instance(original_node);
- }
-
- bool original_node_is_displayed_folded = original_node->is_displayed_folded();
- bool original_node_scene_instance_load_placeholder = original_node->get_scene_instance_load_placeholder();
-
// Update the name to match
instantiated_node->set_name(original_node->get_name());
@@ -6312,19 +6154,9 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() {
// Mark the old node for deletion.
original_node->queue_free();
- // Restore the folded and placeholder state from the original node.
- instantiated_node->set_display_folded(original_node_is_displayed_folded);
+ // Restore the placeholder state from the original node.
instantiated_node->set_scene_instance_load_placeholder(original_node_scene_instance_load_placeholder);
- if (owner) {
- Ref<SceneState> ss_inst = owner->get_scene_instance_state();
- if (ss_inst.is_valid()) {
- ss_inst->update_instance_resource(instance_modifications.instance_path, current_packed_scene);
- }
-
- owner->set_editable_instance(instantiated_node, is_editable);
- }
-
// Attempt to re-add all the additional nodes.
for (AdditiveNodeEntry additive_node_entry : instance_modifications.addition_list) {
Node *parent_node = instantiated_node->get_node_or_null(additive_node_entry.parent);
@@ -6356,6 +6188,17 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() {
}
}
+ // Restore the scene's editable instance and folded states.
+ for (HashMap<NodePath, SceneEditorDataEntry>::Iterator I = scene_editor_data_table.begin(); I; ++I) {
+ Node *node = owner->get_node_or_null(I->key);
+ if (node) {
+ if (owner != node) {
+ owner->set_editable_instance(node, I->value.is_editable);
+ }
+ node->set_display_folded(I->value.is_display_folded);
+ }
+ }
+
// Restore the selection.
if (selected_node_paths.size()) {
for (NodePath selected_node_path : selected_node_paths) {
@@ -6593,25 +6436,20 @@ void EditorNode::_feature_profile_changed() {
editor_dock_manager->set_dock_enabled(ImportDock::get_singleton(), !fs_dock_disabled && !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_IMPORT_DOCK));
editor_dock_manager->set_dock_enabled(history_dock, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_HISTORY_DOCK));
- main_editor_buttons[EDITOR_3D]->set_visible(!profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D));
- main_editor_buttons[EDITOR_SCRIPT]->set_visible(!profile->is_feature_disabled(EditorFeatureProfile::FEATURE_SCRIPT));
+ editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_3D, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D));
+ editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_SCRIPT, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_SCRIPT));
if (AssetLibraryEditorPlugin::is_available()) {
- main_editor_buttons[EDITOR_ASSETLIB]->set_visible(!profile->is_feature_disabled(EditorFeatureProfile::FEATURE_ASSET_LIB));
- }
- if ((profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D) && singleton->main_editor_buttons[EDITOR_3D]->is_pressed()) ||
- (profile->is_feature_disabled(EditorFeatureProfile::FEATURE_SCRIPT) && singleton->main_editor_buttons[EDITOR_SCRIPT]->is_pressed()) ||
- (AssetLibraryEditorPlugin::is_available() && profile->is_feature_disabled(EditorFeatureProfile::FEATURE_ASSET_LIB) && singleton->main_editor_buttons[EDITOR_ASSETLIB]->is_pressed())) {
- editor_select(EDITOR_2D);
+ editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_ASSETLIB, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_ASSET_LIB));
}
} else {
editor_dock_manager->set_dock_enabled(ImportDock::get_singleton(), true);
editor_dock_manager->set_dock_enabled(NodeDock::get_singleton(), true);
editor_dock_manager->set_dock_enabled(FileSystemDock::get_singleton(), true);
editor_dock_manager->set_dock_enabled(history_dock, true);
- main_editor_buttons[EDITOR_3D]->set_visible(true);
- main_editor_buttons[EDITOR_SCRIPT]->set_visible(true);
+ editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_3D, true);
+ editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_SCRIPT, true);
if (AssetLibraryEditorPlugin::is_available()) {
- main_editor_buttons[EDITOR_ASSETLIB]->set_visible(true);
+ editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_ASSETLIB, true);
}
}
}
@@ -6967,6 +6805,8 @@ EditorNode::EditorNode() {
EditorFileSystem *efs = memnew(EditorFileSystem);
add_child(efs);
+ EditorContextMenuPluginManager::create();
+
// Used for previews.
FileDialog::get_icon_func = _file_dialog_get_icon;
FileDialog::register_func = _file_dialog_register;
@@ -7001,6 +6841,7 @@ EditorNode::EditorNode() {
add_child(resource_preview);
progress_dialog = memnew(ProgressDialog);
progress_dialog->set_unparent_when_invisible(true);
+ progress_dialog->connect(SceneStringName(visibility_changed), callable_mp(this, &EditorNode::_progress_dialog_visibility_changed));
gui_base = memnew(Panel);
add_child(gui_base);
@@ -7146,12 +6987,11 @@ EditorNode::EditorNode() {
scene_tabs->add_extra_button(distraction_free);
distraction_free->connect(SceneStringName(pressed), callable_mp(this, &EditorNode::_toggle_distraction_free_mode));
- scene_root_parent = memnew(PanelContainer);
- scene_root_parent->set_custom_minimum_size(Size2(0, 80) * EDSCALE);
- scene_root_parent->add_theme_style_override(SceneStringName(panel), theme->get_stylebox(SNAME("Content"), EditorStringName(EditorStyles)));
- scene_root_parent->set_draw_behind_parent(true);
- srt->add_child(scene_root_parent);
- scene_root_parent->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ editor_main_screen = memnew(EditorMainScreen);
+ editor_main_screen->set_custom_minimum_size(Size2(0, 80) * EDSCALE);
+ editor_main_screen->set_draw_behind_parent(true);
+ srt->add_child(editor_main_screen);
+ editor_main_screen->set_v_size_flags(Control::SIZE_EXPAND_FILL);
scene_root = memnew(SubViewport);
scene_root->set_embedding_subwindows(true);
@@ -7159,12 +6999,6 @@ EditorNode::EditorNode() {
scene_root->set_disable_input(true);
scene_root->set_as_audio_listener_2d(true);
- main_screen_vbox = memnew(VBoxContainer);
- main_screen_vbox->set_name("MainScreen");
- main_screen_vbox->set_v_size_flags(Control::SIZE_EXPAND_FILL);
- main_screen_vbox->add_theme_constant_override("separation", 0);
- scene_root_parent->add_child(main_screen_vbox);
-
bool global_menu = !bool(EDITOR_GET("interface/editor/use_embedded_menu")) && NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_GLOBAL_MENU);
bool can_expand = bool(EDITOR_GET("interface/editor/expand_to_title")) && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_EXTEND_TO_TITLE);
@@ -7358,8 +7192,9 @@ EditorNode::EditorNode() {
left_spacer->add_child(project_title);
}
- main_editor_button_hb = memnew(HBoxContainer);
+ HBoxContainer *main_editor_button_hb = memnew(HBoxContainer);
main_editor_button_hb->set_mouse_filter(Control::MOUSE_FILTER_STOP);
+ editor_main_screen->set_button_container(main_editor_button_hb);
title_bar->add_child(main_editor_button_hb);
// Options are added and handled by DebuggerEditorPlugin.
@@ -7585,8 +7420,8 @@ EditorNode::EditorNode() {
default_layout->set_value(docks_section, "dock_split_" + itos(i + 1), 0);
}
default_layout->set_value(docks_section, "dock_hsplit_1", 0);
- default_layout->set_value(docks_section, "dock_hsplit_2", 270 * EDSCALE);
- default_layout->set_value(docks_section, "dock_hsplit_3", -270 * EDSCALE);
+ default_layout->set_value(docks_section, "dock_hsplit_2", 270);
+ default_layout->set_value(docks_section, "dock_hsplit_3", -270);
default_layout->set_value(docks_section, "dock_hsplit_4", 0);
_update_layouts_menu();
@@ -7832,7 +7667,6 @@ EditorNode::EditorNode() {
update_spinner_step_msec = OS::get_singleton()->get_ticks_msec();
update_spinner_step_frame = Engine::get_singleton()->get_frames_drawn();
- editor_plugin_screen = nullptr;
editor_plugins_over = memnew(EditorPluginList);
editor_plugins_force_over = memnew(EditorPluginList);
editor_plugins_force_input_forwarding = memnew(EditorPluginList);
@@ -7974,6 +7808,7 @@ EditorNode::~EditorNode() {
EditorInspector::cleanup_plugins();
EditorTranslationParser::get_singleton()->clean_parsers();
ResourceImporterScene::clean_up_importer_plugins();
+ EditorContextMenuPluginManager::cleanup();
remove_print_handler(&print_handler);
EditorHelp::cleanup_doc();
diff --git a/editor/editor_node.h b/editor/editor_node.h
index e9d2c28528..4127dd1539 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -42,27 +42,17 @@ typedef void (*EditorPluginInitializeCallback)();
typedef bool (*EditorBuildCallback)();
class AcceptDialog;
-class CenterContainer;
-class CheckBox;
class ColorPicker;
class ConfirmationDialog;
class Control;
class FileDialog;
-class HBoxContainer;
-class HSplitContainer;
-class LinkButton;
class MenuBar;
class MenuButton;
-class Node2D;
class OptionButton;
class Panel;
class PanelContainer;
-class PopupPanel;
class RichTextLabel;
class SubViewport;
-class TabBar;
-class TabContainer;
-class TextureRect;
class TextureProgressBar;
class Tree;
class VBoxContainer;
@@ -83,44 +73,33 @@ class EditorCommandPalette;
class EditorDockManager;
class EditorExport;
class EditorExportPreset;
-class EditorExtensionManager;
class EditorFeatureProfileManager;
class EditorFileDialog;
class EditorFolding;
-class EditorInspector;
class EditorLayoutsDialog;
class EditorLog;
+class EditorMainScreen;
class EditorNativeShaderSourceVisualizer;
class EditorPluginList;
class EditorQuickOpen;
-class EditorPropertyResource;
class EditorResourcePreview;
class EditorResourceConversionPlugin;
class EditorRunBar;
-class EditorRunNative;
class EditorSceneTabs;
class EditorSelectionHistory;
class EditorSettingsDialog;
class EditorTitleBar;
-class EditorToaster;
-class EditorUndoRedoManager;
class ExportTemplateManager;
class FBXImporterManager;
class FileSystemDock;
class HistoryDock;
-class ImportDock;
-class NodeDock;
class OrphanResourcesDialog;
-class PluginConfigDialog;
class ProgressDialog;
class ProjectExportDialog;
class ProjectSettingsEditor;
-class RunSettingsDialog;
class SceneImportSettingsDialog;
-class ScriptCreateDialog;
class SurfaceUpgradeTool;
class SurfaceUpgradeDialog;
-class WindowWrapper;
struct EditorProgress {
String task;
@@ -135,13 +114,6 @@ class EditorNode : public Node {
GDCLASS(EditorNode, Node);
public:
- enum EditorTable {
- EDITOR_2D = 0,
- EDITOR_3D,
- EDITOR_SCRIPT,
- EDITOR_ASSETLIB
- };
-
enum SceneNameCasing {
SCENE_NAME_CASING_AUTO,
SCENE_NAME_CASING_PASCAL_CASE,
@@ -286,7 +258,6 @@ private:
EditorExport *editor_export = nullptr;
EditorLog *log = nullptr;
EditorNativeShaderSourceVisualizer *native_shader_source_visualizer = nullptr;
- EditorPlugin *editor_plugin_screen = nullptr;
EditorPluginList *editor_plugins_force_input_forwarding = nullptr;
EditorPluginList *editor_plugins_force_over = nullptr;
EditorPluginList *editor_plugins_over = nullptr;
@@ -308,7 +279,6 @@ private:
HashMap<ObjectID, HashSet<EditorPlugin *>> active_plugins;
bool is_main_screen_editing = false;
- PanelContainer *scene_root_parent = nullptr;
Control *gui_base = nullptr;
VBoxContainer *main_vbox = nullptr;
OptionButton *renderer = nullptr;
@@ -349,7 +319,6 @@ private:
Control *right_menu_spacer = nullptr;
EditorTitleBar *title_bar = nullptr;
EditorRunBar *project_run_bar = nullptr;
- VBoxContainer *main_screen_vbox = nullptr;
MenuBar *main_menu = nullptr;
PopupMenu *apple_menu = nullptr;
PopupMenu *file_menu = nullptr;
@@ -369,6 +338,7 @@ private:
RichTextLabel *load_errors = nullptr;
AcceptDialog *load_error_dialog = nullptr;
+ bool load_errors_queued_to_display = false;
RichTextLabel *execute_outputs = nullptr;
AcceptDialog *execute_output_dialog = nullptr;
@@ -423,9 +393,7 @@ private:
String current_path;
MenuButton *update_spinner = nullptr;
- HBoxContainer *main_editor_button_hb = nullptr;
- Vector<Button *> main_editor_buttons;
- Vector<EditorPlugin *> editor_table;
+ EditorMainScreen *editor_main_screen = nullptr;
AudioStreamPreviewGenerator *audio_preview_gen = nullptr;
ProgressDialog *progress_dialog = nullptr;
@@ -575,8 +543,6 @@ private:
void _sources_changed(bool p_exist);
void _node_renamed();
- void _editor_select_next();
- void _editor_select_prev();
void _save_editor_states(const String &p_file, int p_idx = -1);
void _load_editor_plugin_states_from_config(const Ref<ConfigFile> &p_config_file);
void _update_title();
@@ -649,8 +615,6 @@ private:
Dictionary _get_main_scene_state();
void _set_main_scene_state(Dictionary p_state, Node *p_for_scene);
- int _get_current_main_editor();
-
void _save_editor_layout();
void _load_editor_layout();
@@ -688,7 +652,6 @@ private:
void _pick_main_scene_custom_action(const String &p_custom_action_name);
void _immediate_dialog_confirmed();
- void _select_default_main_screen_plugin();
void _begin_first_scan();
@@ -696,6 +659,8 @@ private:
void _remove_all_not_owned_children(Node *p_node, Node *p_owner);
+ void _progress_dialog_visibility_changed();
+
protected:
friend class FileSystemDock;
@@ -707,9 +672,6 @@ public:
void init_plugins();
void _on_plugin_ready(Object *p_script, const String &p_activate_name);
- void editor_select(int p_which);
- void set_visible_editor(EditorTable p_table) { editor_select(p_table); }
-
bool call_build();
// This is a very naive estimation, but we need something now. Will be reworked later.
@@ -724,6 +686,7 @@ public:
static EditorTitleBar *get_title_bar() { return singleton->title_bar; }
static VSplitContainer *get_top_split() { return singleton->top_split; }
static EditorBottomPanel *get_bottom_panel() { return singleton->bottom_panel; }
+ static EditorMainScreen *get_editor_main_screen() { return singleton->editor_main_screen; }
static String adjust_scene_name_casing(const String &p_root_name);
static String adjust_script_name_casing(const String &p_file_name, ScriptLanguage::ScriptNameCasing p_auto_casing);
@@ -755,7 +718,6 @@ public:
static void cleanup();
- EditorPlugin *get_editor_plugin_screen() { return editor_plugin_screen; }
EditorPluginList *get_editor_plugins_force_input_forwarding() { return editor_plugins_force_input_forwarding; }
EditorPluginList *get_editor_plugins_force_over() { return editor_plugins_force_over; }
EditorPluginList *get_editor_plugins_over() { return editor_plugins_over; }
@@ -769,6 +731,7 @@ public:
void new_inherited_scene() { _menu_option_confirm(FILE_NEW_INHERITED_SCENE, false); }
+ void update_distraction_free_mode();
void set_distraction_free_mode(bool p_enter);
bool is_distraction_free_mode_enabled() const;
@@ -791,8 +754,6 @@ public:
void push_node_item(Node *p_node);
void hide_unused_editors(const Object *p_editing_owner = nullptr);
- void select_editor_by_name(const String &p_name);
-
void open_request(const String &p_path);
void edit_foreign_resource(Ref<Resource> p_resource);
@@ -802,7 +763,6 @@ public:
bool is_changing_scene() const;
- VBoxContainer *get_main_screen_control();
SubViewport *get_scene_root() { return scene_root; } // Root of the scene being edited.
void set_edited_scene(Node *p_scene);
@@ -852,12 +812,19 @@ public:
HashMap<NodePath, ModificationNodeEntry> other_instances_modifications;
};
+ struct SceneEditorDataEntry {
+ bool is_editable;
+ bool is_display_folded;
+ };
+
HashMap<int, SceneModificationsEntry> scenes_modification_table;
List<String> scenes_reimported;
List<String> resources_reimported;
void update_node_from_node_modification_entry(Node *p_node, ModificationNodeEntry &p_node_modification);
+ void get_scene_editor_data_for_node(Node *p_root, Node *p_node, HashMap<NodePath, SceneEditorDataEntry> &p_table);
+
void get_preload_scene_modification_table(
Node *p_edited_scene,
Node *p_reimported_root,
@@ -866,7 +833,7 @@ public:
void get_preload_modifications_reference_to_nodes(
Node *p_root,
Node *p_node,
- List<Node *> &p_excluded_nodes,
+ HashSet<Node *> &p_excluded_nodes,
List<Node *> &p_instance_list_with_children,
HashMap<NodePath, ModificationNodeEntry> &p_modification_table);
void get_children_nodes(Node *p_node, List<Node *> &p_nodes);
@@ -927,7 +894,7 @@ public:
void reload_scene(const String &p_path);
- void find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, List<Node *> &p_instance_list);
+ void find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, HashSet<Node *> &p_instance_list);
void preload_reimporting_with_path_in_edited_scenes(const List<String> &p_scenes);
void reload_instances_with_path_in_edited_scenes();
diff --git a/editor/editor_paths.cpp b/editor/editor_paths.cpp
index 7f24e8fd2e..ff869f8a8a 100644
--- a/editor/editor_paths.cpp
+++ b/editor/editor_paths.cpp
@@ -245,7 +245,7 @@ EditorPaths::EditorPaths() {
}
}
- // Check that the project data directory '.gdignore' file exists
+ // Check that the project data directory `.gdignore` file exists.
String project_data_gdignore_file_path = project_data_dir.path_join(".gdignore");
if (!FileAccess::exists(project_data_gdignore_file_path)) {
// Add an empty .gdignore file to avoid scan.
@@ -253,10 +253,26 @@ EditorPaths::EditorPaths() {
if (f.is_valid()) {
f->store_line("");
} else {
- ERR_PRINT("Failed to create file " + project_data_gdignore_file_path);
+ ERR_PRINT("Failed to create file " + project_data_gdignore_file_path.quote() + ".");
}
}
+ // Check that `.editorconfig` file exists.
+ String project_editorconfig_path = "res://.editorconfig";
+ if (!FileAccess::exists(project_editorconfig_path)) {
+ Ref<FileAccess> f = FileAccess::open(project_editorconfig_path, FileAccess::WRITE);
+ if (f.is_valid()) {
+ f->store_line("root = true");
+ f->store_line("");
+ f->store_line("[*]");
+ f->store_line("charset = utf-8");
+ f->close();
+ } else {
+ ERR_PRINT("Failed to create file " + project_editorconfig_path.quote() + ".");
+ }
+ FileAccess::set_hidden_attribute(project_editorconfig_path, true);
+ }
+
Engine::get_singleton()->set_shader_cache_path(project_data_dir);
// Editor metadata dir.
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index 19a4165041..2e46068e07 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -81,7 +81,6 @@ void EditorPropertyText::_text_submitted(const String &p_string) {
}
if (text->has_focus()) {
- text->release_focus();
_text_changed(p_string);
}
}
@@ -2703,7 +2702,11 @@ void EditorPropertyNodePath::_update_menu() {
void EditorPropertyNodePath::_menu_option(int p_idx) {
switch (p_idx) {
case ACTION_CLEAR: {
- emit_changed(get_edited_property(), NodePath());
+ if (editing_node) {
+ emit_changed(get_edited_property(), Variant());
+ } else {
+ emit_changed(get_edited_property(), NodePath());
+ }
update_property();
} break;
@@ -3771,7 +3774,7 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
return editor;
} else {
EditorPropertyDictionary *editor = memnew(EditorPropertyDictionary);
- editor->setup(p_hint);
+ editor->setup(p_hint, p_hint_text);
return editor;
}
} break;
diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp
index f5d016629f..f03eef4d4d 100644
--- a/editor/editor_properties_array_dict.cpp
+++ b/editor/editor_properties_array_dict.cpp
@@ -863,6 +863,26 @@ EditorPropertyArray::EditorPropertyArray() {
///////////////////// DICTIONARY ///////////////////////////
+void EditorPropertyDictionary::initialize_dictionary(Variant &p_dictionary) {
+ if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) {
+ Dictionary dict;
+ StringName key_subtype_class;
+ Ref<Script> key_subtype_script;
+ if (key_subtype == Variant::OBJECT && !key_subtype_hint_string.is_empty() && ClassDB::class_exists(key_subtype_hint_string)) {
+ key_subtype_class = key_subtype_hint_string;
+ }
+ StringName value_subtype_class;
+ Ref<Script> value_subtype_script;
+ if (value_subtype == Variant::OBJECT && !value_subtype_hint_string.is_empty() && ClassDB::class_exists(value_subtype_hint_string)) {
+ value_subtype_class = value_subtype_hint_string;
+ }
+ dict.set_typed(key_subtype, key_subtype_class, key_subtype_script, value_subtype, value_subtype_class, value_subtype_script);
+ p_dictionary = dict;
+ } else {
+ VariantInternal::initialize(&p_dictionary, Variant::DICTIONARY);
+ }
+}
+
void EditorPropertyDictionary::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
if (p_value.get_type() == Variant::OBJECT && p_value.is_null()) {
p_value = Variant(); // `EditorResourcePicker` resets to `Ref<Resource>()`. See GH-82716.
@@ -914,16 +934,29 @@ void EditorPropertyDictionary::_create_new_property_slot(int p_idx) {
EditorProperty *prop = memnew(EditorPropertyNil);
hbox->add_child(prop);
- Button *edit_btn = memnew(Button);
- edit_btn->set_icon(get_editor_theme_icon(SNAME("Edit")));
- edit_btn->set_disabled(is_read_only());
- edit_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_change_type).bind(edit_btn, slots.size()));
- hbox->add_child(edit_btn);
+ bool use_key = p_idx == EditorPropertyDictionaryObject::NEW_KEY_INDEX;
+ bool is_untyped_dict = (use_key ? key_subtype : value_subtype) == Variant::NIL;
+
+ if (is_untyped_dict) {
+ Button *edit_btn = memnew(Button);
+ edit_btn->set_icon(get_editor_theme_icon(SNAME("Edit")));
+ edit_btn->set_disabled(is_read_only());
+ edit_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_change_type).bind(edit_btn, slots.size()));
+ hbox->add_child(edit_btn);
+ } else if (p_idx >= 0) {
+ Button *remove_btn = memnew(Button);
+ remove_btn->set_icon(get_editor_theme_icon(SNAME("Remove")));
+ remove_btn->set_disabled(is_read_only());
+ remove_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_remove_pressed).bind(slots.size()));
+ hbox->add_child(remove_btn);
+ }
+
if (add_panel) {
add_panel->get_child(0)->add_child(hbox);
} else {
property_vbox->add_child(hbox);
}
+
Slot slot;
slot.prop = prop;
slot.object = object;
@@ -969,15 +1002,70 @@ void EditorPropertyDictionary::_change_type_menu(int p_index) {
}
}
-void EditorPropertyDictionary::setup(PropertyHint p_hint) {
- property_hint = p_hint;
+void EditorPropertyDictionary::setup(PropertyHint p_hint, const String &p_hint_string) {
+ PackedStringArray types = p_hint_string.split(";");
+ if (types.size() > 0 && !types[0].is_empty()) {
+ String key = types[0];
+ int hint_key_subtype_separator = key.find(":");
+ if (hint_key_subtype_separator >= 0) {
+ String key_subtype_string = key.substr(0, hint_key_subtype_separator);
+ int slash_pos = key_subtype_string.find("/");
+ if (slash_pos >= 0) {
+ key_subtype_hint = PropertyHint(key_subtype_string.substr(slash_pos + 1, key_subtype_string.size() - slash_pos - 1).to_int());
+ key_subtype_string = key_subtype_string.substr(0, slash_pos);
+ }
+
+ key_subtype_hint_string = key.substr(hint_key_subtype_separator + 1, key.size() - hint_key_subtype_separator - 1);
+ key_subtype = Variant::Type(key_subtype_string.to_int());
+
+ Variant new_key = object->get_new_item_key();
+ VariantInternal::initialize(&new_key, key_subtype);
+ object->set_new_item_key(new_key);
+ }
+ }
+ if (types.size() > 1 && !types[1].is_empty()) {
+ String value = types[1];
+ int hint_value_subtype_separator = value.find(":");
+ if (hint_value_subtype_separator >= 0) {
+ String value_subtype_string = value.substr(0, hint_value_subtype_separator);
+ int slash_pos = value_subtype_string.find("/");
+ if (slash_pos >= 0) {
+ value_subtype_hint = PropertyHint(value_subtype_string.substr(slash_pos + 1, value_subtype_string.size() - slash_pos - 1).to_int());
+ value_subtype_string = value_subtype_string.substr(0, slash_pos);
+ }
+
+ value_subtype_hint_string = value.substr(hint_value_subtype_separator + 1, value.size() - hint_value_subtype_separator - 1);
+ value_subtype = Variant::Type(value_subtype_string.to_int());
+
+ Variant new_value = object->get_new_item_value();
+ VariantInternal::initialize(&new_value, value_subtype);
+ object->set_new_item_value(new_value);
+ }
+ }
}
void EditorPropertyDictionary::update_property() {
Variant updated_val = get_edited_property_value();
+ String dict_type_name = "Dictionary";
+ if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) {
+ String key_subtype_name = "Variant";
+ if (key_subtype == Variant::OBJECT && (key_subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || key_subtype_hint == PROPERTY_HINT_NODE_TYPE)) {
+ key_subtype_name = key_subtype_hint_string;
+ } else if (key_subtype != Variant::NIL) {
+ key_subtype_name = Variant::get_type_name(key_subtype);
+ }
+ String value_subtype_name = "Variant";
+ if (value_subtype == Variant::OBJECT && (value_subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || value_subtype_hint == PROPERTY_HINT_NODE_TYPE)) {
+ value_subtype_name = value_subtype_hint_string;
+ } else if (value_subtype != Variant::NIL) {
+ value_subtype_name = Variant::get_type_name(value_subtype);
+ }
+ dict_type_name += vformat("[%s, %s]", key_subtype_name, value_subtype_name);
+ }
+
if (updated_val.get_type() != Variant::DICTIONARY) {
- edit->set_text(TTR("Dictionary (Nil)")); // This provides symmetry with the array property.
+ edit->set_text(vformat(TTR("(Nil) %s"), dict_type_name)); // This provides symmetry with the array property.
edit->set_pressed(false);
if (container) {
set_bottom_editor(nullptr);
@@ -993,7 +1081,7 @@ void EditorPropertyDictionary::update_property() {
Dictionary dict = updated_val;
object->set_dict(updated_val);
- edit->set_text(vformat(TTR("Dictionary (size %d)"), dict.size()));
+ edit->set_text(vformat(TTR("%s (size %d)"), dict_type_name, dict.size()));
bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());
if (edit->is_pressed() != unfolded) {
@@ -1074,7 +1162,9 @@ void EditorPropertyDictionary::update_property() {
editor->setup("Object");
new_prop = editor;
} else {
- new_prop = EditorInspector::instantiate_property_editor(this, value_type, "", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE);
+ bool use_key = slot.index == EditorPropertyDictionaryObject::NEW_KEY_INDEX;
+ new_prop = EditorInspector::instantiate_property_editor(this, value_type, "", use_key ? key_subtype_hint : value_subtype_hint,
+ use_key ? key_subtype_hint_string : value_subtype_hint_string, PROPERTY_USAGE_NONE);
}
new_prop->set_selectable(false);
new_prop->set_use_folding(is_using_folding());
@@ -1108,6 +1198,13 @@ void EditorPropertyDictionary::update_property() {
}
}
+void EditorPropertyDictionary::_remove_pressed(int p_slot_index) {
+ Dictionary dict = object->get_dict().duplicate();
+ dict.erase(dict.get_key_at_index(p_slot_index));
+
+ emit_changed(get_edited_property(), dict);
+}
+
void EditorPropertyDictionary::_object_id_selected(const StringName &p_property, ObjectID p_id) {
emit_signal(SNAME("object_id_selected"), p_property, p_id);
}
@@ -1140,7 +1237,7 @@ void EditorPropertyDictionary::_notification(int p_what) {
void EditorPropertyDictionary::_edit_pressed() {
Variant prop_val = get_edited_property_value();
if (prop_val.get_type() == Variant::NIL && edit->is_pressed()) {
- VariantInternal::initialize(&prop_val, Variant::DICTIONARY);
+ initialize_dictionary(prop_val);
emit_changed(get_edited_property(), prop_val);
}
@@ -1187,6 +1284,14 @@ EditorPropertyDictionary::EditorPropertyDictionary() {
change_type->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyDictionary::_change_type_menu));
changing_type_index = -1;
has_borders = true;
+
+ key_subtype = Variant::NIL;
+ key_subtype_hint = PROPERTY_HINT_NONE;
+ key_subtype_hint_string = "";
+
+ value_subtype = Variant::NIL;
+ value_subtype_hint = PROPERTY_HINT_NONE;
+ value_subtype_hint_string = "";
}
///////////////////// LOCALIZABLE STRING ///////////////////////////
diff --git a/editor/editor_properties_array_dict.h b/editor/editor_properties_array_dict.h
index 024c04956f..84c3f975be 100644
--- a/editor/editor_properties_array_dict.h
+++ b/editor/editor_properties_array_dict.h
@@ -219,7 +219,6 @@ class EditorPropertyDictionary : public EditorProperty {
EditorSpinSlider *size_sliderv = nullptr;
Button *button_add_item = nullptr;
EditorPaginator *paginator = nullptr;
- PropertyHint property_hint;
LocalVector<Slot> slots;
void _create_new_property_slot(int p_idx);
@@ -231,12 +230,21 @@ class EditorPropertyDictionary : public EditorProperty {
void _add_key_value();
void _object_id_selected(const StringName &p_property, ObjectID p_id);
+ void _remove_pressed(int p_slot_index);
+
+ Variant::Type key_subtype;
+ PropertyHint key_subtype_hint;
+ String key_subtype_hint_string;
+ Variant::Type value_subtype;
+ PropertyHint value_subtype_hint;
+ String value_subtype_hint_string;
+ void initialize_dictionary(Variant &p_dictionary);
protected:
void _notification(int p_what);
public:
- void setup(PropertyHint p_hint);
+ void setup(PropertyHint p_hint, const String &p_hint_string = "");
virtual void update_property() override;
virtual bool is_colored(ColorationMode p_mode) override;
EditorPropertyDictionary();
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index b1a91fa3dd..d0c89c49c4 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -527,15 +527,18 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
// Touchscreen
bool has_touchscreen_ui = DisplayServer::get_singleton()->is_touchscreen_available();
- EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/touchscreen/increase_scrollbar_touch_area", has_touchscreen_ui, "")
- set_restart_if_changed("interface/touchscreen/increase_scrollbar_touch_area", true);
- EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/touchscreen/enable_long_press_as_right_click", has_touchscreen_ui, "")
- set_restart_if_changed("interface/touchscreen/enable_long_press_as_right_click", true);
EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/touchscreen/enable_pan_and_scale_gestures", has_touchscreen_ui, "")
set_restart_if_changed("interface/touchscreen/enable_pan_and_scale_gestures", true);
EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "interface/touchscreen/scale_gizmo_handles", has_touchscreen_ui ? 3 : 1, "1,5,1")
set_restart_if_changed("interface/touchscreen/scale_gizmo_handles", true);
+ // Disable some touchscreen settings by default for the XR Editor.
+ bool is_native_touchscreen = has_touchscreen_ui && !OS::get_singleton()->has_feature("xr_editor");
+ EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/touchscreen/enable_long_press_as_right_click", is_native_touchscreen, "")
+ set_restart_if_changed("interface/touchscreen/enable_long_press_as_right_click", true);
+ EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/touchscreen/increase_scrollbar_touch_area", is_native_touchscreen, "")
+ set_restart_if_changed("interface/touchscreen/increase_scrollbar_touch_area", true);
+
// Scene tabs
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/scene_tabs/display_close_button", 1, "Never,If Tab Active,Always"); // TabBar::CloseButtonDisplayPolicy
_initial_set("interface/scene_tabs/show_thumbnail_on_hover", true);
diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp
index 7ad589a58d..983f4ee36a 100644
--- a/editor/export/editor_export_platform.cpp
+++ b/editor/export/editor_export_platform.cpp
@@ -270,7 +270,7 @@ Error EditorExportPlatform::_save_zip_file(void *p_userdata, const String &p_pat
Ref<ImageTexture> EditorExportPlatform::get_option_icon(int p_index) const {
Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme();
ERR_FAIL_COND_V(theme.is_null(), Ref<ImageTexture>());
- if (EditorNode::get_singleton()->get_main_screen_control()->is_layout_rtl()) {
+ if (EditorNode::get_singleton()->get_gui_base()->is_layout_rtl()) {
return theme->get_icon(SNAME("PlayBackwards"), EditorStringName(EditorIcons));
} else {
return theme->get_icon(SNAME("Play"), EditorStringName(EditorIcons));
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index 8bf2fcbbda..25725635e3 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -2566,9 +2566,12 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected
String dir = ProjectSettings::get_singleton()->globalize_path(fpath);
ScriptEditor::get_singleton()->open_text_file_create_dialog(dir);
} break;
- default:
- EditorNode::get_editor_data().filesystem_options_pressed(EditorData::CONTEXT_SLOT_FILESYSTEM, p_option, p_selected);
- break;
+
+ default: {
+ if (!EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM, p_option, p_selected)) {
+ EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM_CREATE, p_option, p_selected);
+ }
+ }
}
}
@@ -3192,7 +3195,7 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vect
new_menu->add_icon_item(get_editor_theme_icon(SNAME("Object")), TTR("Resource..."), FILE_NEW_RESOURCE);
new_menu->add_icon_item(get_editor_theme_icon(SNAME("TextFile")), TTR("TextFile..."), FILE_NEW_TEXTFILE);
- EditorNode::get_editor_data().add_options_from_plugins(new_menu, EditorData::CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE, p_paths);
+ EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(new_menu, EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM_CREATE, p_paths);
p_popup->add_separator();
}
@@ -3332,8 +3335,7 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vect
current_path = fpath;
}
-
- EditorNode::get_editor_data().add_options_from_plugins(p_popup, EditorData::CONTEXT_SLOT_FILESYSTEM, p_paths);
+ EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(p_popup, EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM, p_paths);
}
void FileSystemDock::_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button) {
@@ -3568,11 +3570,16 @@ void FileSystemDock::_tree_gui_input(Ref<InputEvent> p_event) {
} else if (ED_IS_SHORTCUT("editor/open_search", p_event)) {
focus_on_filter();
} else {
- int match_option = EditorNode::get_editor_data().match_context_menu_shortcut(EditorData::CONTEXT_SLOT_FILESYSTEM, p_event);
- if (match_option) {
- _tree_rmb_option(match_option);
+ Callable custom_callback = EditorContextMenuPluginManager::get_singleton()->match_custom_shortcut(EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM, p_event);
+ if (!custom_callback.is_valid()) {
+ custom_callback = EditorContextMenuPluginManager::get_singleton()->match_custom_shortcut(EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM_CREATE, p_event);
+ }
+
+ if (custom_callback.is_valid()) {
+ EditorContextMenuPluginManager::get_singleton()->invoke_callback(custom_callback, _tree_get_selected(false));
+ } else {
+ return;
}
- return;
}
accept_event();
@@ -3640,7 +3647,16 @@ void FileSystemDock::_file_list_gui_input(Ref<InputEvent> p_event) {
} else if (ED_IS_SHORTCUT("editor/open_search", p_event)) {
focus_on_filter();
} else {
- return;
+ Callable custom_callback = EditorContextMenuPluginManager::get_singleton()->match_custom_shortcut(EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM, p_event);
+ if (!custom_callback.is_valid()) {
+ custom_callback = EditorContextMenuPluginManager::get_singleton()->match_custom_shortcut(EditorContextMenuPlugin::CONTEXT_SLOT_FILESYSTEM_CREATE, p_event);
+ }
+
+ if (custom_callback.is_valid()) {
+ EditorContextMenuPluginManager::get_singleton()->invoke_callback(custom_callback, files->get_selected_items());
+ } else {
+ return;
+ }
}
accept_event();
diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp
index 731f682605..a073a2338b 100644
--- a/editor/gui/editor_spin_slider.cpp
+++ b/editor/gui/editor_spin_slider.cpp
@@ -185,10 +185,6 @@ void EditorSpinSlider::_grabber_gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> mb = p_event;
- if (is_read_only()) {
- return;
- }
-
if (grabbing_grabber) {
if (mb.is_valid()) {
if (mb->get_button_index() == MouseButton::WHEEL_UP) {
@@ -241,7 +237,7 @@ void EditorSpinSlider::_grabber_gui_input(const Ref<InputEvent> &p_event) {
void EditorSpinSlider::_value_input_gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventKey> k = p_event;
- if (k.is_valid() && k->is_pressed() && !is_read_only()) {
+ if (k.is_valid() && k->is_pressed() && !read_only) {
Key code = k->get_keycode();
switch (code) {
@@ -315,7 +311,7 @@ void EditorSpinSlider::_draw_spin_slider() {
bool rtl = is_layout_rtl();
Vector2 size = get_size();
- Ref<StyleBox> sb = get_theme_stylebox(is_read_only() ? SNAME("read_only") : CoreStringName(normal), SNAME("LineEdit"));
+ Ref<StyleBox> sb = get_theme_stylebox(read_only ? SNAME("read_only") : CoreStringName(normal), SNAME("LineEdit"));
if (!flat) {
draw_style_box(sb, Rect2(Vector2(), size));
}
@@ -327,14 +323,14 @@ void EditorSpinSlider::_draw_spin_slider() {
int label_width = font->get_string_size(label, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width;
int number_width = size.width - sb->get_minimum_size().width - label_width - sep;
- Ref<Texture2D> updown = get_theme_icon(is_read_only() ? SNAME("updown_disabled") : SNAME("updown"), SNAME("SpinBox"));
+ Ref<Texture2D> updown = get_theme_icon(read_only ? SNAME("updown_disabled") : SNAME("updown"), SNAME("SpinBox"));
String numstr = get_text_value();
int vofs = (size.height - font->get_height(font_size)) / 2 + font->get_ascent(font_size);
- Color fc = get_theme_color(is_read_only() ? SNAME("font_uneditable_color") : SceneStringName(font_color), SNAME("LineEdit"));
- Color lc = get_theme_color(is_read_only() ? SNAME("read_only_label_color") : SNAME("label_color"));
+ Color fc = get_theme_color(read_only ? SNAME("font_uneditable_color") : SceneStringName(font_color), SNAME("LineEdit"));
+ Color lc = get_theme_color(read_only ? SNAME("read_only_label_color") : SNAME("label_color"));
if (flat && !label.is_empty()) {
Ref<StyleBox> label_bg = get_theme_stylebox(SNAME("label_bg"), SNAME("EditorSpinSlider"));
@@ -384,7 +380,7 @@ void EditorSpinSlider::_draw_spin_slider() {
if (!hide_slider) {
if (get_step() == 1) {
- Ref<Texture2D> updown2 = is_read_only() ? theme_cache.updown_disabled_icon : theme_cache.updown_icon;
+ Ref<Texture2D> updown2 = read_only ? theme_cache.updown_disabled_icon : theme_cache.updown_icon;
int updown_vofs = (size.height - updown2->get_height()) / 2;
if (rtl) {
updown_offset = sb->get_margin(SIDE_LEFT);
@@ -604,7 +600,7 @@ void EditorSpinSlider::_value_focus_exited() {
return;
}
- if (is_read_only()) {
+ if (read_only) {
// Spin slider has become read only while it was being edited.
return;
}
@@ -640,7 +636,7 @@ void EditorSpinSlider::_grabber_mouse_exited() {
void EditorSpinSlider::set_read_only(bool p_enable) {
read_only = p_enable;
- if (read_only && value_input) {
+ if (read_only && value_input && value_input->is_inside_tree()) {
value_input->release_focus();
}
@@ -665,7 +661,7 @@ bool EditorSpinSlider::is_grabbing() const {
}
void EditorSpinSlider::_focus_entered() {
- if (is_read_only()) {
+ if (read_only) {
return;
}
diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp
index 5c28213ca7..cb348f713c 100644
--- a/editor/import/3d/resource_importer_scene.cpp
+++ b/editor/import/3d/resource_importer_scene.cpp
@@ -637,10 +637,10 @@ void _apply_permanent_scale_to_descendants(Node *p_root_node, Vector3 p_scale) {
_apply_scale_to_scalable_node_collection(scalable_node_collection, p_scale);
}
-Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, HashMap<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> &r_collision_map, Pair<PackedVector3Array, PackedInt32Array> *r_occluder_arrays, List<Pair<NodePath, Node *>> &r_node_renames) {
+Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, HashMap<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> &r_collision_map, Pair<PackedVector3Array, PackedInt32Array> *r_occluder_arrays, List<Pair<NodePath, Node *>> &r_node_renames, const HashMap<StringName, Variant> &p_options) {
// Children first.
for (int i = 0; i < p_node->get_child_count(); i++) {
- Node *r = _pre_fix_node(p_node->get_child(i), p_root, r_collision_map, r_occluder_arrays, r_node_renames);
+ Node *r = _pre_fix_node(p_node->get_child(i), p_root, r_collision_map, r_occluder_arrays, r_node_renames, p_options);
if (!r) {
i--; // Was erased.
}
@@ -750,6 +750,14 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, HashMap<R
}
}
+ bool use_node_type_suffixes = true;
+ if (p_options.has("nodes/use_node_type_suffixes")) {
+ use_node_type_suffixes = p_options["nodes/use_node_type_suffixes"];
+ }
+ if (!use_node_type_suffixes) {
+ return p_node;
+ }
+
if (_teststr(name, "colonly") || _teststr(name, "convcolonly")) {
if (isroot) {
return p_node;
@@ -2373,6 +2381,7 @@ void ResourceImporterScene::get_import_options(const String &p_path, List<Import
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "nodes/apply_root_scale"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "nodes/root_scale", PROPERTY_HINT_RANGE, "0.001,1000,0.001"), 1.0));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "nodes/import_as_skeleton_bones"), false));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "nodes/use_node_type_suffixes"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/ensure_tangents"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/generate_lods"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/create_shadow_meshes"), true));
@@ -2854,7 +2863,7 @@ Node *ResourceImporterScene::pre_import(const String &p_source_file, const HashM
HashMap<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> collision_map;
List<Pair<NodePath, Node *>> node_renames;
- _pre_fix_node(scene, scene, collision_map, nullptr, node_renames);
+ _pre_fix_node(scene, scene, collision_map, nullptr, node_renames, p_options);
return scene;
}
@@ -2992,7 +3001,7 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
Pair<PackedVector3Array, PackedInt32Array> occluder_arrays;
List<Pair<NodePath, Node *>> node_renames;
- _pre_fix_node(scene, scene, collision_map, &occluder_arrays, node_renames);
+ _pre_fix_node(scene, scene, collision_map, &occluder_arrays, node_renames, p_options);
for (int i = 0; i < post_importer_plugins.size(); i++) {
post_importer_plugins.write[i]->pre_process(scene, p_options);
diff --git a/editor/import/3d/resource_importer_scene.h b/editor/import/3d/resource_importer_scene.h
index 9759f328d7..fe757dc2a3 100644
--- a/editor/import/3d/resource_importer_scene.h
+++ b/editor/import/3d/resource_importer_scene.h
@@ -288,7 +288,7 @@ public:
virtual int get_import_order() const override { return ResourceImporter::IMPORT_ORDER_SCENE; }
void _pre_fix_global(Node *p_scene, const HashMap<StringName, Variant> &p_options) const;
- Node *_pre_fix_node(Node *p_node, Node *p_root, HashMap<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> &r_collision_map, Pair<PackedVector3Array, PackedInt32Array> *r_occluder_arrays, List<Pair<NodePath, Node *>> &r_node_renames);
+ Node *_pre_fix_node(Node *p_node, Node *p_root, HashMap<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> &r_collision_map, Pair<PackedVector3Array, PackedInt32Array> *r_occluder_arrays, List<Pair<NodePath, Node *>> &r_node_renames, const HashMap<StringName, Variant> &p_options);
Node *_pre_fix_animations(Node *p_node, Node *p_root, const Dictionary &p_node_data, const Dictionary &p_animation_data, float p_animation_fps);
Node *_post_fix_node(Node *p_node, Node *p_root, HashMap<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> &collision_map, Pair<PackedVector3Array, PackedInt32Array> &r_occluder_arrays, HashSet<Ref<ImporterMesh>> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps, float p_applied_root_scale);
Node *_post_fix_animations(Node *p_node, Node *p_root, const Dictionary &p_node_data, const Dictionary &p_animation_data, float p_animation_fps, bool p_remove_immutable_tracks);
diff --git a/editor/import/3d/scene_import_settings.cpp b/editor/import/3d/scene_import_settings.cpp
index 7ca3cb6c3a..011d0135b4 100644
--- a/editor/import/3d/scene_import_settings.cpp
+++ b/editor/import/3d/scene_import_settings.cpp
@@ -37,6 +37,7 @@
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/gui/editor_file_dialog.h"
+#include "editor/plugins/skeleton_3d_editor_plugin.h"
#include "editor/themes/editor_scale.h"
#include "scene/3d/importer_mesh_instance_3d.h"
#include "scene/animation/animation_player.h"
@@ -419,7 +420,9 @@ void SceneImportSettingsDialog::_fill_scene(Node *p_node, TreeItem *p_parent_ite
animation_player->connect(SceneStringName(animation_finished), callable_mp(this, &SceneImportSettingsDialog::_animation_finished));
} else if (Object::cast_to<Skeleton3D>(p_node)) {
category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE;
- skeletons.push_back(Object::cast_to<Skeleton3D>(p_node));
+ Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_node);
+ skeleton->connect(SceneStringName(tree_entered), callable_mp(this, &SceneImportSettingsDialog::_skeleton_tree_entered).bind(skeleton));
+ skeletons.push_back(skeleton);
} else {
category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_NODE;
}
@@ -480,6 +483,31 @@ void SceneImportSettingsDialog::_fill_scene(Node *p_node, TreeItem *p_parent_ite
contents_aabb.merge_with(aabb);
}
}
+
+ Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_node);
+ if (skeleton) {
+ Ref<ArrayMesh> bones_mesh = Skeleton3DGizmoPlugin::get_bones_mesh(skeleton, -1, true);
+
+ bones_mesh_preview->set_mesh(bones_mesh);
+
+ Transform3D accum_xform;
+ Node3D *base = skeleton;
+ while (base) {
+ accum_xform = base->get_transform() * accum_xform;
+ base = Object::cast_to<Node3D>(base->get_parent());
+ }
+
+ bones_mesh_preview->set_transform(accum_xform * skeleton->get_transform());
+
+ AABB aabb = accum_xform.xform(bones_mesh->get_aabb());
+
+ if (first_aabb) {
+ contents_aabb = aabb;
+ first_aabb = false;
+ } else {
+ contents_aabb.merge_with(aabb);
+ }
+ }
}
void SceneImportSettingsDialog::_update_scene() {
@@ -795,6 +823,7 @@ void SceneImportSettingsDialog::_select(Tree *p_from, const String &p_type, cons
selecting = true;
scene_import_settings_data->hide_options = false;
+ bones_mesh_preview->hide();
if (p_type == "Node") {
node_selected->hide(); // Always hide just in case.
mesh_preview->hide();
@@ -834,6 +863,7 @@ void SceneImportSettingsDialog::_select(Tree *p_from, const String &p_type, cons
scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE;
} else if (Object::cast_to<Skeleton3D>(nd.node)) {
scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE;
+ bones_mesh_preview->show();
} else {
scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_NODE;
scene_import_settings_data->hide_options = editing_animation;
@@ -853,6 +883,8 @@ void SceneImportSettingsDialog::_select(Tree *p_from, const String &p_type, cons
scene_import_settings_data->settings = &ad.settings;
scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_ANIMATION;
+
+ _animation_update_skeleton_visibility();
} else if (p_type == "Mesh") {
node_selected->hide();
if (Object::cast_to<Node3D>(scene)) {
@@ -1055,6 +1087,11 @@ void SceneImportSettingsDialog::_animation_slider_value_changed(double p_value)
animation_player->seek(p_value * animation_map[selected_id].animation->get_length(), true);
}
+void SceneImportSettingsDialog::_skeleton_tree_entered(Skeleton3D *p_skeleton) {
+ bones_mesh_preview->set_skeleton_path(p_skeleton->get_path());
+ bones_mesh_preview->set_skin(p_skeleton->register_skin(p_skeleton->create_skin_from_rest_transforms()));
+}
+
void SceneImportSettingsDialog::_animation_finished(const StringName &p_name) {
Animation::LoopMode loop_mode = animation_loop_mode;
@@ -1080,6 +1117,14 @@ void SceneImportSettingsDialog::_animation_finished(const StringName &p_name) {
}
}
+void SceneImportSettingsDialog::_animation_update_skeleton_visibility() {
+ if (animation_toggle_skeleton_visibility->is_pressed()) {
+ bones_mesh_preview->show();
+ } else {
+ bones_mesh_preview->hide();
+ }
+}
+
void SceneImportSettingsDialog::_material_tree_selected() {
if (selecting) {
return;
@@ -1282,6 +1327,8 @@ void SceneImportSettingsDialog::_notification(int p_what) {
light_1_switch->set_icon(theme_cache.light_1_icon);
light_2_switch->set_icon(theme_cache.light_2_icon);
light_rotate_switch->set_icon(theme_cache.rotate_icon);
+
+ animation_toggle_skeleton_visibility->set_icon(get_editor_theme_icon(SNAME("Skeleton3D")));
} break;
case NOTIFICATION_PROCESS: {
@@ -1661,6 +1708,7 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() {
animation_hbox->add_child(animation_stop_button);
animation_stop_button->set_flat(true);
animation_stop_button->set_focus_mode(Control::FOCUS_NONE);
+ animation_stop_button->set_tooltip_text(TTR("Selected Animation Stop"));
animation_stop_button->connect(SceneStringName(pressed), callable_mp(this, &SceneImportSettingsDialog::_stop_current_animation));
animation_slider = memnew(HSlider);
@@ -1673,6 +1721,15 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() {
animation_slider->set_focus_mode(Control::FOCUS_NONE);
animation_slider->connect(SceneStringName(value_changed), callable_mp(this, &SceneImportSettingsDialog::_animation_slider_value_changed));
+ animation_toggle_skeleton_visibility = memnew(Button);
+ animation_hbox->add_child(animation_toggle_skeleton_visibility);
+ animation_toggle_skeleton_visibility->set_toggle_mode(true);
+ animation_toggle_skeleton_visibility->set_flat(true);
+ animation_toggle_skeleton_visibility->set_focus_mode(Control::FOCUS_NONE);
+ animation_toggle_skeleton_visibility->set_tooltip_text(TTR("Toggle Animation Skeleton Visibility"));
+
+ animation_toggle_skeleton_visibility->connect(SceneStringName(pressed), callable_mp(this, &SceneImportSettingsDialog::_animation_update_skeleton_visibility));
+
base_viewport->set_use_own_world_3d(true);
HBoxContainer *viewport_hbox = memnew(HBoxContainer);
@@ -1800,6 +1857,13 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() {
collider_mat->set_albedo(Color(0.5, 0.5, 1.0));
}
+ {
+ bones_mesh_preview = memnew(MeshInstance3D);
+ bones_mesh_preview->set_cast_shadows_setting(GeometryInstance3D::SHADOW_CASTING_SETTING_OFF);
+ bones_mesh_preview->set_skeleton_path(NodePath());
+ base_viewport->add_child(bones_mesh_preview);
+ }
+
inspector = memnew(EditorInspector);
inspector->set_custom_minimum_size(Size2(300 * EDSCALE, 0));
inspector->connect(SNAME("property_edited"), callable_mp(this, &SceneImportSettingsDialog::_inspector_property_edited));
diff --git a/editor/import/3d/scene_import_settings.h b/editor/import/3d/scene_import_settings.h
index bbd0d2c22d..1088acf772 100644
--- a/editor/import/3d/scene_import_settings.h
+++ b/editor/import/3d/scene_import_settings.h
@@ -109,10 +109,12 @@ class SceneImportSettingsDialog : public ConfirmationDialog {
HSlider *animation_slider = nullptr;
Button *animation_play_button = nullptr;
Button *animation_stop_button = nullptr;
+ Button *animation_toggle_skeleton_visibility = nullptr;
Animation::LoopMode animation_loop_mode = Animation::LOOP_NONE;
bool animation_pingpong = false;
bool previous_import_as_skeleton = false;
bool previous_rest_as_reset = false;
+ MeshInstance3D *bones_mesh_preview = nullptr;
Ref<StandardMaterial3D> collider_mat;
@@ -187,9 +189,11 @@ class SceneImportSettingsDialog : public ConfirmationDialog {
void _reset_animation(const String &p_animation_name = "");
void _animation_slider_value_changed(double p_value);
void _animation_finished(const StringName &p_name);
+ void _animation_update_skeleton_visibility();
void _material_tree_selected();
void _mesh_tree_selected();
void _scene_tree_selected();
+ void _skeleton_tree_entered(Skeleton3D *p_skeleton);
void _cleanup();
void _on_light_1_switch_pressed();
void _on_light_2_switch_pressed();
diff --git a/editor/import/resource_importer_wav.cpp b/editor/import/resource_importer_wav.cpp
index 77b3629b07..7a6f39906c 100644
--- a/editor/import/resource_importer_wav.cpp
+++ b/editor/import/resource_importer_wav.cpp
@@ -429,10 +429,10 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s
loop_end = p_options["edit/loop_end"];
// Wrap around to max frames, so `-1` can be used to select the end, etc.
if (loop_begin < 0) {
- loop_begin = CLAMP(loop_begin + frames + 1, 0, frames);
+ loop_begin = CLAMP(loop_begin + frames, 0, frames - 1);
}
if (loop_end < 0) {
- loop_end = CLAMP(loop_end + frames + 1, 0, frames);
+ loop_end = CLAMP(loop_end + frames, 0, frames - 1);
}
}
diff --git a/editor/inspector_dock.cpp b/editor/inspector_dock.cpp
index dc07403213..3a3598c0b9 100644
--- a/editor/inspector_dock.cpp
+++ b/editor/inspector_dock.cpp
@@ -30,6 +30,7 @@
#include "inspector_dock.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
@@ -97,7 +98,7 @@ void InspectorDock::_menu_option_confirm(int p_option, bool p_confirmed) {
case OBJECT_REQUEST_HELP: {
if (current) {
- EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
emit_signal(SNAME("request_help"), current->get_class());
}
} break;
diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp
index e6fe554923..f31346fe67 100644
--- a/editor/plugins/asset_library_editor_plugin.cpp
+++ b/editor/plugins/asset_library_editor_plugin.cpp
@@ -35,6 +35,7 @@
#include "core/io/stream_peer_tls.h"
#include "core/os/keyboard.h"
#include "core/version.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_settings.h"
@@ -1794,7 +1795,7 @@ void AssetLibraryEditorPlugin::make_visible(bool p_visible) {
AssetLibraryEditorPlugin::AssetLibraryEditorPlugin() {
addon_library = memnew(EditorAssetLibrary);
addon_library->set_v_size_flags(Control::SIZE_EXPAND_FILL);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(addon_library);
+ EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(addon_library);
addon_library->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
addon_library->hide();
}
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 4b7bf1674f..e9a796dae7 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -34,6 +34,7 @@
#include "core/input/input.h"
#include "core/os/keyboard.h"
#include "editor/debugger/editor_debugger_node.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
@@ -4011,6 +4012,8 @@ void CanvasItemEditor::_project_settings_changed() {
void CanvasItemEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
+ _update_lock_and_group_button();
+
EditorRunBar::get_singleton()->connect("play_pressed", callable_mp(this, &CanvasItemEditor::_update_override_camera_button).bind(true));
EditorRunBar::get_singleton()->connect("stop_pressed", callable_mp(this, &CanvasItemEditor::_update_override_camera_button).bind(false));
ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &CanvasItemEditor::_project_settings_changed));
@@ -5384,7 +5387,7 @@ CanvasItemEditor::CanvasItemEditor() {
main_menu_hbox->add_child(pivot_button);
pivot_button->set_toggle_mode(true);
pivot_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_EDIT_PIVOT));
- pivot_button->set_tooltip_text(TTR("Click to change object's rotation pivot.") + "\n" + TTR("Shift: Set temporary rotation pivot.") + "\n" + TTR("Click this button while holding Shift to put the rotation pivot in the center of the selected nodes."));
+ pivot_button->set_tooltip_text(TTR("Click to change object's rotation pivot.") + "\n" + TTR("Shift: Set temporary rotation pivot.") + "\n" + TTR("Click this button while holding Shift to put the temporary rotation pivot in the center of the selected nodes."));
pan_button = memnew(Button);
pan_button->set_theme_type_variation("FlatButton");
@@ -5735,7 +5738,7 @@ void CanvasItemEditorPlugin::_notification(int p_what) {
CanvasItemEditorPlugin::CanvasItemEditorPlugin() {
canvas_item_editor = memnew(CanvasItemEditor);
canvas_item_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(canvas_item_editor);
+ EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(canvas_item_editor);
canvas_item_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
canvas_item_editor->hide();
}
diff --git a/editor/plugins/cpu_particles_3d_editor_plugin.cpp b/editor/plugins/cpu_particles_3d_editor_plugin.cpp
index 69f287c134..e0ce330813 100644
--- a/editor/plugins/cpu_particles_3d_editor_plugin.cpp
+++ b/editor/plugins/cpu_particles_3d_editor_plugin.cpp
@@ -208,7 +208,7 @@ void CPUParticles3DEditorPlugin::make_visible(bool p_visible) {
CPUParticles3DEditorPlugin::CPUParticles3DEditorPlugin() {
particles_editor = memnew(CPUParticles3DEditor);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(particles_editor);
+ EditorNode::get_singleton()->get_gui_base()->add_child(particles_editor);
particles_editor->hide();
}
diff --git a/editor/plugins/editor_context_menu_plugin.cpp b/editor/plugins/editor_context_menu_plugin.cpp
index 86d3c0a896..0648327fab 100644
--- a/editor/plugins/editor_context_menu_plugin.cpp
+++ b/editor/plugins/editor_context_menu_plugin.cpp
@@ -32,9 +32,11 @@
#include "core/input/shortcut.h"
#include "editor/editor_node.h"
+#include "editor/editor_string_names.h"
+#include "scene/gui/popup_menu.h"
#include "scene/resources/texture.h"
-void EditorContextMenuPlugin::add_options(const Vector<String> &p_paths) {
+void EditorContextMenuPlugin::get_options(const Vector<String> &p_paths) {
GDVIRTUAL_CALL(_popup_menu, p_paths);
}
@@ -42,24 +44,142 @@ void EditorContextMenuPlugin::add_menu_shortcut(const Ref<Shortcut> &p_shortcut,
context_menu_shortcuts.insert(p_shortcut, p_callable);
}
-void EditorContextMenuPlugin::add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture, const Ref<Shortcut> &p_shortcut) {
+void EditorContextMenuPlugin::add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture) {
ERR_FAIL_COND_MSG(context_menu_items.has(p_name), "Context menu item already registered.");
ERR_FAIL_COND_MSG(context_menu_items.size() == MAX_ITEMS, "Maximum number of context menu items reached.");
+
ContextMenuItem item;
item.item_name = p_name;
item.callable = p_callable;
item.icon = p_texture;
- item.shortcut = p_shortcut;
- item.idx = EditorData::CONTEXT_MENU_ITEM_ID_BASE + start_idx + context_menu_shortcuts.size() + context_menu_items.size();
context_menu_items.insert(p_name, item);
}
-void EditorContextMenuPlugin::clear_context_menu_items() {
- context_menu_items.clear();
+void EditorContextMenuPlugin::add_context_menu_item_from_shortcut(const String &p_name, const Ref<Shortcut> &p_shortcut, const Ref<Texture2D> &p_texture) {
+ Callable *callback = context_menu_shortcuts.getptr(p_shortcut);
+ ERR_FAIL_NULL_MSG(callback, "Shortcut not registered. Use add_menu_shortcut() first.");
+
+ ContextMenuItem item;
+ item.item_name = p_name;
+ item.callable = *callback;
+ item.icon = p_texture;
+ item.shortcut = p_shortcut;
+ context_menu_items.insert(p_name, item);
}
void EditorContextMenuPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_menu_shortcut", "shortcut", "callback"), &EditorContextMenuPlugin::add_menu_shortcut);
- ClassDB::bind_method(D_METHOD("add_context_menu_item", "name", "callback", "icon", "shortcut"), &EditorContextMenuPlugin::add_context_menu_item, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Shortcut>()));
+ ClassDB::bind_method(D_METHOD("add_context_menu_item", "name", "callback", "icon"), &EditorContextMenuPlugin::add_context_menu_item, DEFVAL(Ref<Texture2D>()));
+ ClassDB::bind_method(D_METHOD("add_context_menu_item_from_shortcut", "name", "shortcut", "icon"), &EditorContextMenuPlugin::add_context_menu_item_from_shortcut, DEFVAL(Ref<Texture2D>()));
+
GDVIRTUAL_BIND(_popup_menu, "paths");
+
+ BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCENE_TREE);
+ BIND_ENUM_CONSTANT(CONTEXT_SLOT_FILESYSTEM);
+ BIND_ENUM_CONSTANT(CONTEXT_SLOT_FILESYSTEM_CREATE);
+ BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCRIPT_EDITOR);
+}
+
+void EditorContextMenuPluginManager::add_plugin(EditorContextMenuPlugin::ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
+ ERR_FAIL_COND(p_plugin.is_null());
+ ERR_FAIL_COND(plugin_list.has(p_plugin));
+
+ p_plugin->slot = p_slot;
+ plugin_list.push_back(p_plugin);
+}
+
+void EditorContextMenuPluginManager::remove_plugin(const Ref<EditorContextMenuPlugin> &p_plugin) {
+ ERR_FAIL_COND(p_plugin.is_null());
+ ERR_FAIL_COND(!plugin_list.has(p_plugin));
+
+ plugin_list.erase(p_plugin);
+}
+
+void EditorContextMenuPluginManager::add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector<String> &p_paths) {
+ bool separator_added = false;
+ const int icon_size = p_popup->get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
+ int id = EditorContextMenuPlugin::BASE_ID;
+
+ for (Ref<EditorContextMenuPlugin> &plugin : plugin_list) {
+ if (plugin->slot != p_slot) {
+ continue;
+ }
+ plugin->context_menu_items.clear();
+ plugin->get_options(p_paths);
+
+ HashMap<String, EditorContextMenuPlugin::ContextMenuItem> &items = plugin->context_menu_items;
+ if (items.size() > 0 && !separator_added) {
+ separator_added = true;
+ p_popup->add_separator();
+ }
+
+ for (KeyValue<String, EditorContextMenuPlugin::ContextMenuItem> &E : items) {
+ EditorContextMenuPlugin::ContextMenuItem &item = E.value;
+ item.id = id;
+
+ if (item.icon.is_valid()) {
+ p_popup->add_icon_item(item.icon, item.item_name, id);
+ p_popup->set_item_icon_max_width(-1, icon_size);
+ } else {
+ p_popup->add_item(item.item_name, id);
+ }
+ if (item.shortcut.is_valid()) {
+ p_popup->set_item_shortcut(-1, item.shortcut, true);
+ }
+ id++;
+ }
+ }
+}
+
+Callable EditorContextMenuPluginManager::match_custom_shortcut(EditorContextMenuPlugin::ContextMenuSlot p_slot, const Ref<InputEvent> &p_event) {
+ for (Ref<EditorContextMenuPlugin> &plugin : plugin_list) {
+ if (plugin->slot != p_slot) {
+ continue;
+ }
+
+ for (KeyValue<Ref<Shortcut>, Callable> &E : plugin->context_menu_shortcuts) {
+ if (E.key->matches_event(p_event)) {
+ return E.value;
+ }
+ }
+ }
+ return Callable();
+}
+
+bool EditorContextMenuPluginManager::activate_custom_option(ContextMenuSlot p_slot, int p_option, const Variant &p_arg) {
+ for (Ref<EditorContextMenuPlugin> &plugin : plugin_list) {
+ if (plugin->slot != p_slot) {
+ continue;
+ }
+
+ for (KeyValue<String, EditorContextMenuPlugin::ContextMenuItem> &E : plugin->context_menu_items) {
+ if (E.value.id == p_option) {
+ invoke_callback(E.value.callable, p_arg);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void EditorContextMenuPluginManager::invoke_callback(const Callable &p_callback, const Variant &p_arg) {
+ const Variant *argptr = &p_arg;
+ Callable::CallError ce;
+ Variant result;
+ p_callback.callp(&argptr, 1, result, ce);
+
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_FAIL_MSG("Failed to execute context menu callback: " + Variant::get_callable_error_text(p_callback, &argptr, 1, ce) + ".");
+ }
+}
+
+void EditorContextMenuPluginManager::create() {
+ ERR_FAIL_COND(singleton != nullptr);
+ singleton = memnew(EditorContextMenuPluginManager);
+}
+
+void EditorContextMenuPluginManager::cleanup() {
+ ERR_FAIL_NULL(singleton);
+ memdelete(singleton);
+ singleton = nullptr;
}
diff --git a/editor/plugins/editor_context_menu_plugin.h b/editor/plugins/editor_context_menu_plugin.h
index db05e6c6c7..0232d254ba 100644
--- a/editor/plugins/editor_context_menu_plugin.h
+++ b/editor/plugins/editor_context_menu_plugin.h
@@ -34,19 +34,33 @@
#include "core/object/gdvirtual.gen.inc"
#include "core/object/ref_counted.h"
-class Texture2D;
+class InputEvent;
+class PopupMenu;
class Shortcut;
+class Texture2D;
class EditorContextMenuPlugin : public RefCounted {
GDCLASS(EditorContextMenuPlugin, RefCounted);
-public:
- int start_idx;
+ friend class EditorContextMenuPluginManager;
inline static constexpr int MAX_ITEMS = 100;
+public:
+ enum ContextMenuSlot {
+ CONTEXT_SLOT_SCENE_TREE,
+ CONTEXT_SLOT_FILESYSTEM,
+ CONTEXT_SLOT_SCRIPT_EDITOR,
+ CONTEXT_SLOT_FILESYSTEM_CREATE,
+ };
+ inline static constexpr int BASE_ID = 2000;
+
+private:
+ int slot = -1;
+
+public:
struct ContextMenuItem {
- int idx = 0;
+ int id = 0;
String item_name;
Callable callable;
Ref<Texture2D> icon;
@@ -61,10 +75,37 @@ protected:
GDVIRTUAL1(_popup_menu, Vector<String>);
public:
- virtual void add_options(const Vector<String> &p_paths);
+ virtual void get_options(const Vector<String> &p_paths);
+
void add_menu_shortcut(const Ref<Shortcut> &p_shortcut, const Callable &p_callable);
- void add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture, const Ref<Shortcut> &p_shortcut);
- void clear_context_menu_items();
+ void add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture);
+ void add_context_menu_item_from_shortcut(const String &p_name, const Ref<Shortcut> &p_shortcut, const Ref<Texture2D> &p_texture);
+};
+
+VARIANT_ENUM_CAST(EditorContextMenuPlugin::ContextMenuSlot);
+
+class EditorContextMenuPluginManager : public Object {
+ GDCLASS(EditorContextMenuPluginManager, Object);
+
+ using ContextMenuSlot = EditorContextMenuPlugin::ContextMenuSlot;
+ static inline EditorContextMenuPluginManager *singleton = nullptr;
+
+ LocalVector<Ref<EditorContextMenuPlugin>> plugin_list;
+
+public:
+ static EditorContextMenuPluginManager *get_singleton() { return singleton; }
+
+ void add_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
+ void remove_plugin(const Ref<EditorContextMenuPlugin> &p_plugin);
+
+ void add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector<String> &p_paths);
+ Callable match_custom_shortcut(ContextMenuSlot p_slot, const Ref<InputEvent> &p_event);
+ bool activate_custom_option(ContextMenuSlot p_slot, int p_option, const Variant &p_arg);
+
+ void invoke_callback(const Callable &p_callback, const Variant &p_arg);
+
+ static void create();
+ static void cleanup();
};
#endif // EDITOR_CONTEXT_MENU_PLUGIN_H
diff --git a/editor/plugins/editor_plugin.cpp b/editor/plugins/editor_plugin.cpp
index 0ee67e08ba..c8426bce73 100644
--- a/editor/plugins/editor_plugin.cpp
+++ b/editor/plugins/editor_plugin.cpp
@@ -47,7 +47,6 @@
#include "editor/import/editor_import_plugin.h"
#include "editor/inspector_dock.h"
#include "editor/plugins/canvas_item_editor_plugin.h"
-#include "editor/plugins/editor_context_menu_plugin.h"
#include "editor/plugins/editor_debugger_plugin.h"
#include "editor/plugins/editor_resource_conversion_plugin.h"
#include "editor/plugins/node_3d_editor_plugin.h"
@@ -491,14 +490,12 @@ void EditorPlugin::remove_scene_post_import_plugin(const Ref<EditorScenePostImpo
ResourceImporterScene::remove_post_importer_plugin(p_plugin);
}
-void EditorPlugin::add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
- ERR_FAIL_COND(p_plugin.is_null());
- EditorNode::get_editor_data().add_context_menu_plugin(EditorData::ContextMenuSlot(p_slot), p_plugin);
+void EditorPlugin::add_context_menu_plugin(EditorContextMenuPlugin::ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
+ EditorContextMenuPluginManager::get_singleton()->add_plugin(p_slot, p_plugin);
}
-void EditorPlugin::remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
- ERR_FAIL_COND(p_plugin.is_null());
- EditorNode::get_editor_data().remove_context_menu_plugin(EditorData::ContextMenuSlot(p_slot), p_plugin);
+void EditorPlugin::remove_context_menu_plugin(const Ref<EditorContextMenuPlugin> &p_plugin) {
+ EditorContextMenuPluginManager::get_singleton()->remove_plugin(p_plugin);
}
int find(const PackedStringArray &a, const String &v) {
@@ -641,7 +638,7 @@ void EditorPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_input_event_forwarding_always_enabled"), &EditorPlugin::set_input_event_forwarding_always_enabled);
ClassDB::bind_method(D_METHOD("set_force_draw_over_forwarding_enabled"), &EditorPlugin::set_force_draw_over_forwarding_enabled);
ClassDB::bind_method(D_METHOD("add_context_menu_plugin", "slot", "plugin"), &EditorPlugin::add_context_menu_plugin);
- ClassDB::bind_method(D_METHOD("remove_context_menu_plugin", "slot", "plugin"), &EditorPlugin::remove_context_menu_plugin);
+ ClassDB::bind_method(D_METHOD("remove_context_menu_plugin", "plugin"), &EditorPlugin::remove_context_menu_plugin);
ClassDB::bind_method(D_METHOD("get_editor_interface"), &EditorPlugin::get_editor_interface);
ClassDB::bind_method(D_METHOD("get_script_create_dialog"), &EditorPlugin::get_script_create_dialog);
@@ -707,11 +704,6 @@ void EditorPlugin::_bind_methods() {
BIND_ENUM_CONSTANT(AFTER_GUI_INPUT_PASS);
BIND_ENUM_CONSTANT(AFTER_GUI_INPUT_STOP);
BIND_ENUM_CONSTANT(AFTER_GUI_INPUT_CUSTOM);
-
- BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCENE_TREE);
- BIND_ENUM_CONSTANT(CONTEXT_SLOT_FILESYSTEM);
- BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCRIPT_EDITOR);
- BIND_ENUM_CONSTANT(CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE);
}
EditorUndoRedoManager *EditorPlugin::get_undo_redo() {
diff --git a/editor/plugins/editor_plugin.h b/editor/plugins/editor_plugin.h
index 35f65f03cd..b43cbca661 100644
--- a/editor/plugins/editor_plugin.h
+++ b/editor/plugins/editor_plugin.h
@@ -32,6 +32,7 @@
#define EDITOR_PLUGIN_H
#include "core/io/config_file.h"
+#include "editor/plugins/editor_context_menu_plugin.h"
#include "scene/3d/camera_3d.h"
#include "scene/gui/control.h"
@@ -53,7 +54,6 @@ class EditorToolAddons;
class EditorTranslationParserPlugin;
class EditorUndoRedoManager;
class ScriptCreateDialog;
-class EditorContextMenuPlugin;
class EditorPlugin : public Node {
GDCLASS(EditorPlugin, Node);
@@ -103,13 +103,6 @@ public:
AFTER_GUI_INPUT_CUSTOM,
};
- enum ContextMenuSlot {
- CONTEXT_SLOT_SCENE_TREE,
- CONTEXT_SLOT_FILESYSTEM,
- CONTEXT_SLOT_SCRIPT_EDITOR,
- CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE,
- };
-
protected:
void _notification(int p_what);
@@ -257,8 +250,8 @@ public:
void add_resource_conversion_plugin(const Ref<EditorResourceConversionPlugin> &p_plugin);
void remove_resource_conversion_plugin(const Ref<EditorResourceConversionPlugin> &p_plugin);
- void add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
- void remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
+ void add_context_menu_plugin(EditorContextMenuPlugin::ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
+ void remove_context_menu_plugin(const Ref<EditorContextMenuPlugin> &p_plugin);
void enable_plugin();
void disable_plugin();
@@ -270,7 +263,6 @@ public:
VARIANT_ENUM_CAST(EditorPlugin::CustomControlContainer);
VARIANT_ENUM_CAST(EditorPlugin::DockSlot);
VARIANT_ENUM_CAST(EditorPlugin::AfterGUIInput);
-VARIANT_ENUM_CAST(EditorPlugin::ContextMenuSlot);
typedef EditorPlugin *(*EditorPluginCreateFunc)();
diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp
index 7ac924571d..a2c36b1f3c 100644
--- a/editor/plugins/editor_preview_plugins.cpp
+++ b/editor/plugins/editor_preview_plugins.cpp
@@ -76,7 +76,7 @@ void post_process_preview(Ref<Image> p_image) {
}
bool EditorTexturePreviewPlugin::handles(const String &p_type) const {
- return ClassDB::is_parent_class(p_type, "Texture2D");
+ return ClassDB::is_parent_class(p_type, "Texture2D") || ClassDB::is_parent_class(p_type, "Texture3D") || ClassDB::is_parent_class(p_type, "TextureLayered");
}
bool EditorTexturePreviewPlugin::generate_small_preview_automatically() const {
@@ -85,9 +85,13 @@ bool EditorTexturePreviewPlugin::generate_small_preview_automatically() const {
Ref<Texture2D> EditorTexturePreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
Ref<Image> img;
- Ref<AtlasTexture> atex = p_from;
- if (atex.is_valid()) {
- Ref<Texture2D> tex = atex->get_atlas();
+
+ Ref<AtlasTexture> tex_atlas = p_from;
+ Ref<Texture3D> tex_3d = p_from;
+ Ref<TextureLayered> tex_lyr = p_from;
+
+ if (tex_atlas.is_valid()) {
+ Ref<Texture2D> tex = tex_atlas->get_atlas();
if (!tex.is_valid()) {
return Ref<Texture2D>();
}
@@ -97,11 +101,36 @@ Ref<Texture2D> EditorTexturePreviewPlugin::generate(const Ref<Resource> &p_from,
return Ref<Texture2D>();
}
- if (!atex->get_region().has_area()) {
+ if (!tex_atlas->get_region().has_area()) {
+ return Ref<Texture2D>();
+ }
+
+ img = atlas->get_region(tex_atlas->get_region());
+
+ } else if (tex_3d.is_valid()) {
+ if (tex_3d->get_depth() == 0) {
+ return Ref<Texture2D>();
+ }
+
+ const int mid_depth = (tex_3d->get_depth() - 1) / 2;
+
+ Vector<Ref<Image>> data = tex_3d->get_data();
+ if (!data.is_empty() && data[mid_depth].is_valid()) {
+ img = data[mid_depth]->duplicate();
+ }
+
+ } else if (tex_lyr.is_valid()) {
+ if (tex_lyr->get_layers() == 0) {
return Ref<Texture2D>();
}
- img = atlas->get_region(atex->get_region());
+ const int mid_layer = (tex_lyr->get_layers() - 1) / 2;
+
+ Ref<Image> data = tex_lyr->get_layer_data(mid_layer);
+ if (data.is_valid()) {
+ img = data->duplicate();
+ }
+
} else {
Ref<Texture2D> tex = p_from;
if (tex.is_valid()) {
@@ -115,6 +144,7 @@ Ref<Texture2D> EditorTexturePreviewPlugin::generate(const Ref<Resource> &p_from,
if (img.is_null() || img->is_empty()) {
return Ref<Texture2D>();
}
+
p_metadata["dimensions"] = img->get_size();
img->clear_mipmaps();
diff --git a/editor/plugins/gpu_particles_3d_editor_plugin.cpp b/editor/plugins/gpu_particles_3d_editor_plugin.cpp
index 4e9be0aa53..e711f44ccf 100644
--- a/editor/plugins/gpu_particles_3d_editor_plugin.cpp
+++ b/editor/plugins/gpu_particles_3d_editor_plugin.cpp
@@ -452,7 +452,7 @@ void GPUParticles3DEditorPlugin::make_visible(bool p_visible) {
GPUParticles3DEditorPlugin::GPUParticles3DEditorPlugin() {
particles_editor = memnew(GPUParticles3DEditor);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(particles_editor);
+ EditorNode::get_singleton()->get_gui_base()->add_child(particles_editor);
particles_editor->hide();
}
diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.cpp b/editor/plugins/mesh_instance_3d_editor_plugin.cpp
index 369d6ab009..eda6cdffb1 100644
--- a/editor/plugins/mesh_instance_3d_editor_plugin.cpp
+++ b/editor/plugins/mesh_instance_3d_editor_plugin.cpp
@@ -689,7 +689,7 @@ void MeshInstance3DEditorPlugin::make_visible(bool p_visible) {
MeshInstance3DEditorPlugin::MeshInstance3DEditorPlugin() {
mesh_editor = memnew(MeshInstance3DEditor);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(mesh_editor);
+ EditorNode::get_singleton()->get_gui_base()->add_child(mesh_editor);
mesh_editor->options->hide();
}
diff --git a/editor/plugins/mesh_library_editor_plugin.cpp b/editor/plugins/mesh_library_editor_plugin.cpp
index 58cc670475..d6650bd08f 100644
--- a/editor/plugins/mesh_library_editor_plugin.cpp
+++ b/editor/plugins/mesh_library_editor_plugin.cpp
@@ -308,7 +308,7 @@ void MeshLibraryEditorPlugin::make_visible(bool p_visible) {
MeshLibraryEditorPlugin::MeshLibraryEditorPlugin() {
mesh_library_editor = memnew(MeshLibraryEditor);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(mesh_library_editor);
+ EditorNode::get_singleton()->get_gui_base()->add_child(mesh_library_editor);
mesh_library_editor->set_anchors_and_offsets_preset(Control::PRESET_TOP_WIDE);
mesh_library_editor->set_end(Point2(0, 22));
mesh_library_editor->hide();
diff --git a/editor/plugins/multimesh_editor_plugin.cpp b/editor/plugins/multimesh_editor_plugin.cpp
index 76ffdb02e1..729ceccd25 100644
--- a/editor/plugins/multimesh_editor_plugin.cpp
+++ b/editor/plugins/multimesh_editor_plugin.cpp
@@ -384,7 +384,7 @@ void MultiMeshEditorPlugin::make_visible(bool p_visible) {
MultiMeshEditorPlugin::MultiMeshEditorPlugin() {
multimesh_editor = memnew(MultiMeshEditor);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(multimesh_editor);
+ EditorNode::get_singleton()->get_gui_base()->add_child(multimesh_editor);
multimesh_editor->options->hide();
}
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index 44673e7224..f58dfbb5a5 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -37,6 +37,7 @@
#include "core/math/projection.h"
#include "core/os/keyboard.h"
#include "editor/debugger/editor_debugger_node.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
@@ -810,7 +811,7 @@ ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos) const {
Vector<Node3D *> nodes_with_gizmos = Node3DEditor::get_singleton()->gizmo_bvh_ray_query(pos, pos + ray * camera->get_far());
for (Node3D *spat : nodes_with_gizmos) {
- if (!spat) {
+ if (!spat || _is_node_locked(spat)) {
continue;
}
@@ -1557,7 +1558,7 @@ void Node3DEditorViewport::_surface_focus_exit() {
view_menu->set_disable_shortcuts(true);
}
-bool Node3DEditorViewport ::_is_node_locked(const Node *p_node) {
+bool Node3DEditorViewport::_is_node_locked(const Node *p_node) const {
return p_node->get_meta("_edit_lock_", false);
}
@@ -1935,11 +1936,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
if (after != EditorPlugin::AFTER_GUI_INPUT_CUSTOM) {
// Single item selection.
- Vector<_RayResult> selection;
- _find_items_at_pos(b->get_position(), selection, false);
- if (!selection.is_empty()) {
- clicked = selection[0].item->get_instance_id();
- }
+ clicked = _select_ray(b->get_position());
selection_in_progress = true;
@@ -9376,7 +9373,7 @@ Vector<Node3D *> Node3DEditor::gizmo_bvh_frustum_query(const Vector<Plane> &p_fr
Node3DEditorPlugin::Node3DEditorPlugin() {
spatial_editor = memnew(Node3DEditor);
spatial_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(spatial_editor);
+ EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(spatial_editor);
spatial_editor->hide();
}
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index 2cfe784ca6..c7e6420875 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -460,7 +460,7 @@ private:
bool previewing_camera = false;
bool previewing_cinema = false;
- bool _is_node_locked(const Node *p_node);
+ bool _is_node_locked(const Node *p_node) const;
void _preview_exited_scene();
void _toggle_camera_preview(bool);
void _toggle_cinema_preview(bool);
diff --git a/editor/plugins/path_2d_editor_plugin.cpp b/editor/plugins/path_2d_editor_plugin.cpp
index 24cc15d656..0455b266b4 100644
--- a/editor/plugins/path_2d_editor_plugin.cpp
+++ b/editor/plugins/path_2d_editor_plugin.cpp
@@ -42,7 +42,6 @@
void Path2DEditor::_notification(int p_what) {
switch (p_what) {
- case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_THEME_CHANGED: {
curve_edit->set_icon(get_editor_theme_icon(SNAME("CurveEdit")));
curve_edit_curve->set_icon(get_editor_theme_icon(SNAME("CurveCurve")));
@@ -50,6 +49,8 @@ void Path2DEditor::_notification(int p_what) {
curve_del->set_icon(get_editor_theme_icon(SNAME("CurveDelete")));
curve_close->set_icon(get_editor_theme_icon(SNAME("CurveClose")));
curve_clear_points->set_icon(get_editor_theme_icon(SNAME("Clear")));
+
+ create_curve_button->set_icon(get_editor_theme_icon(SNAME("Curve2D")));
} break;
}
}
@@ -450,6 +451,16 @@ void Path2DEditor::_node_visibility_changed() {
}
canvas_item_editor->update_viewport();
+ _update_toolbar();
+}
+
+void Path2DEditor::_update_toolbar() {
+ if (!node) {
+ return;
+ }
+ bool has_curve = node->get_curve().is_valid();
+ toolbar->set_visible(has_curve);
+ create_curve_button->set_visible(!has_curve);
}
void Path2DEditor::edit(Node *p_path2d) {
@@ -463,6 +474,7 @@ void Path2DEditor::edit(Node *p_path2d) {
if (p_path2d) {
node = Object::cast_to<Path2D>(p_path2d);
+ _update_toolbar();
if (!node->is_connected(SceneStringName(visibility_changed), callable_mp(this, &Path2DEditor::_node_visibility_changed))) {
node->connect(SceneStringName(visibility_changed), callable_mp(this, &Path2DEditor::_node_visibility_changed));
@@ -477,7 +489,7 @@ void Path2DEditor::edit(Node *p_path2d) {
}
void Path2DEditor::_bind_methods() {
- //ClassDB::bind_method(D_METHOD("_menu_option"),&Path2DEditor::_menu_option);
+ ClassDB::bind_method(D_METHOD("_update_toolbar"), &Path2DEditor::_update_toolbar);
ClassDB::bind_method(D_METHOD("_clear_curve_points"), &Path2DEditor::_clear_curve_points);
ClassDB::bind_method(D_METHOD("_restore_curve_points"), &Path2DEditor::_restore_curve_points);
}
@@ -601,6 +613,21 @@ void Path2DEditor::_cancel_current_action() {
action = ACTION_NONE;
}
+void Path2DEditor::_create_curve() {
+ ERR_FAIL_NULL(node);
+
+ Ref<Curve2D> new_curve;
+ new_curve.instantiate();
+
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->create_action(TTR("Create Curve in Path2D"));
+ undo_redo->add_do_property(node, "curve", new_curve);
+ undo_redo->add_undo_property(node, "curve", Ref<Curve2D>());
+ undo_redo->add_do_method(this, "_update_toolbar");
+ undo_redo->add_undo_method(this, "_update_toolbar");
+ undo_redo->commit_action();
+}
+
void Path2DEditor::_confirm_clear_points() {
if (!node || node->get_curve().is_null()) {
return;
@@ -648,6 +675,8 @@ void Path2DEditor::_restore_curve_points(Path2D *p_path2d, const PackedVector2Ar
}
Path2DEditor::Path2DEditor() {
+ toolbar = memnew(HBoxContainer);
+
curve_edit = memnew(Button);
curve_edit->set_theme_type_variation("FlatButton");
curve_edit->set_toggle_mode(true);
@@ -655,7 +684,7 @@ Path2DEditor::Path2DEditor() {
curve_edit->set_focus_mode(Control::FOCUS_NONE);
curve_edit->set_tooltip_text(TTR("Select Points") + "\n" + TTR("Shift+Drag: Select Control Points") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Click: Add Point") + "\n" + TTR("Left Click: Split Segment (in curve)") + "\n" + TTR("Right Click: Delete Point"));
curve_edit->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_EDIT));
- add_child(curve_edit);
+ toolbar->add_child(curve_edit);
curve_edit_curve = memnew(Button);
curve_edit_curve->set_theme_type_variation("FlatButton");
@@ -663,7 +692,7 @@ Path2DEditor::Path2DEditor() {
curve_edit_curve->set_focus_mode(Control::FOCUS_NONE);
curve_edit_curve->set_tooltip_text(TTR("Select Control Points (Shift+Drag)"));
curve_edit_curve->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_EDIT_CURVE));
- add_child(curve_edit_curve);
+ toolbar->add_child(curve_edit_curve);
curve_create = memnew(Button);
curve_create->set_theme_type_variation("FlatButton");
@@ -671,7 +700,7 @@ Path2DEditor::Path2DEditor() {
curve_create->set_focus_mode(Control::FOCUS_NONE);
curve_create->set_tooltip_text(TTR("Add Point (in empty space)") + "\n" + TTR("Right Click: Delete Point"));
curve_create->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CREATE));
- add_child(curve_create);
+ toolbar->add_child(curve_create);
curve_del = memnew(Button);
curve_del->set_theme_type_variation("FlatButton");
@@ -679,42 +708,48 @@ Path2DEditor::Path2DEditor() {
curve_del->set_focus_mode(Control::FOCUS_NONE);
curve_del->set_tooltip_text(TTR("Delete Point"));
curve_del->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_DELETE));
- add_child(curve_del);
+ toolbar->add_child(curve_del);
curve_close = memnew(Button);
curve_close->set_theme_type_variation("FlatButton");
curve_close->set_focus_mode(Control::FOCUS_NONE);
curve_close->set_tooltip_text(TTR("Close Curve"));
curve_close->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CLOSE));
- add_child(curve_close);
+ toolbar->add_child(curve_close);
curve_clear_points = memnew(Button);
curve_clear_points->set_theme_type_variation("FlatButton");
curve_clear_points->set_focus_mode(Control::FOCUS_NONE);
curve_clear_points->set_tooltip_text(TTR("Clear Points"));
curve_clear_points->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_confirm_clear_points));
- add_child(curve_clear_points);
+ toolbar->add_child(curve_clear_points);
clear_points_dialog = memnew(ConfirmationDialog);
clear_points_dialog->set_title(TTR("Please Confirm..."));
clear_points_dialog->set_text(TTR("Remove all curve points?"));
clear_points_dialog->connect(SceneStringName(confirmed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CLEAR_POINTS));
- add_child(clear_points_dialog);
-
- PopupMenu *menu;
+ toolbar->add_child(clear_points_dialog);
handle_menu = memnew(MenuButton);
handle_menu->set_flat(false);
handle_menu->set_theme_type_variation("FlatMenuButton");
handle_menu->set_text(TTR("Options"));
- add_child(handle_menu);
+ toolbar->add_child(handle_menu);
- menu = handle_menu->get_popup();
+ PopupMenu *menu = handle_menu->get_popup();
menu->add_check_item(TTR("Mirror Handle Angles"));
menu->set_item_checked(HANDLE_OPTION_ANGLE, mirror_handle_angle);
menu->add_check_item(TTR("Mirror Handle Lengths"));
menu->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length);
menu->connect(SceneStringName(id_pressed), callable_mp(this, &Path2DEditor::_handle_option_pressed));
+
+ add_child(toolbar);
+
+ create_curve_button = memnew(Button);
+ create_curve_button->set_text(TTR("Create Curve"));
+ create_curve_button->hide();
+ add_child(create_curve_button);
+ create_curve_button->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_create_curve));
}
void Path2DEditorPlugin::edit(Object *p_object) {
diff --git a/editor/plugins/path_2d_editor_plugin.h b/editor/plugins/path_2d_editor_plugin.h
index f45b75a968..f2f03f12f1 100644
--- a/editor/plugins/path_2d_editor_plugin.h
+++ b/editor/plugins/path_2d_editor_plugin.h
@@ -58,6 +58,7 @@ class Path2DEditor : public HBoxContainer {
};
Mode mode = MODE_EDIT;
+ HBoxContainer *toolbar = nullptr;
Button *curve_clear_points = nullptr;
Button *curve_close = nullptr;
Button *curve_create = nullptr;
@@ -66,6 +67,7 @@ class Path2DEditor : public HBoxContainer {
Button *curve_edit_curve = nullptr;
MenuButton *handle_menu = nullptr;
+ Button *create_curve_button = nullptr;
ConfirmationDialog *clear_points_dialog = nullptr;
bool mirror_handle_angle = true;
@@ -100,7 +102,9 @@ class Path2DEditor : public HBoxContainer {
void _cancel_current_action();
void _node_visibility_changed();
+ void _update_toolbar();
+ void _create_curve();
void _confirm_clear_points();
void _clear_curve_points(Path2D *p_path2d);
void _restore_curve_points(Path2D *p_path2d, const PackedVector2Array &p_points);
diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp
index 240206e124..890035e6a6 100644
--- a/editor/plugins/path_3d_editor_plugin.cpp
+++ b/editor/plugins/path_3d_editor_plugin.cpp
@@ -652,6 +652,7 @@ void Path3DEditorPlugin::edit(Object *p_object) {
if (path->get_curve().is_valid()) {
path->get_curve()->emit_signal(CoreStringName(changed));
}
+ _update_toolbar();
}
} else {
Path3D *pre = path;
@@ -686,11 +687,11 @@ void Path3DEditorPlugin::make_visible(bool p_visible) {
}
void Path3DEditorPlugin::_mode_changed(int p_mode) {
- curve_create->set_pressed(p_mode == MODE_CREATE);
- curve_edit_curve->set_pressed(p_mode == MODE_EDIT_CURVE);
- curve_edit_tilt->set_pressed(p_mode == MODE_EDIT_TILT);
- curve_edit->set_pressed(p_mode == MODE_EDIT);
- curve_del->set_pressed(p_mode == MODE_DELETE);
+ curve_create->set_pressed_no_signal(p_mode == MODE_CREATE);
+ curve_edit_curve->set_pressed_no_signal(p_mode == MODE_EDIT_CURVE);
+ curve_edit_tilt->set_pressed_no_signal(p_mode == MODE_EDIT_TILT);
+ curve_edit->set_pressed_no_signal(p_mode == MODE_EDIT);
+ curve_del->set_pressed_no_signal(p_mode == MODE_DELETE);
Node3DEditor::get_singleton()->clear_subgizmo_selection();
}
@@ -732,6 +733,21 @@ void Path3DEditorPlugin::_handle_option_pressed(int p_option) {
}
}
+void Path3DEditorPlugin::_create_curve() {
+ ERR_FAIL_NULL(path);
+
+ Ref<Curve3D> new_curve;
+ new_curve.instantiate();
+
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->create_action(TTR("Create Curve in Path3D"));
+ undo_redo->add_do_property(path, "curve", new_curve);
+ undo_redo->add_undo_property(path, "curve", Ref<Curve3D>());
+ undo_redo->add_do_method(this, "_update_toolbar");
+ undo_redo->add_undo_method(this, "_update_toolbar");
+ undo_redo->commit_action();
+}
+
void Path3DEditorPlugin::_confirm_clear_points() {
if (!path || path->get_curve().is_null() || path->get_curve()->get_point_count() == 0) {
return;
@@ -774,52 +790,33 @@ void Path3DEditorPlugin::_restore_curve_points(const PackedVector3Array &p_point
}
void Path3DEditorPlugin::_update_theme() {
- // TODO: Split the EditorPlugin instance from the UI instance and connect this properly.
- // See the 2D path editor for inspiration.
- curve_edit->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveEdit"), EditorStringName(EditorIcons)));
- curve_edit_curve->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveCurve"), EditorStringName(EditorIcons)));
- curve_edit_tilt->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveTilt"), EditorStringName(EditorIcons)));
- curve_create->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveCreate"), EditorStringName(EditorIcons)));
- curve_del->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveDelete"), EditorStringName(EditorIcons)));
- curve_close->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveClose"), EditorStringName(EditorIcons)));
- curve_clear_points->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Clear"), EditorStringName(EditorIcons)));
+ curve_edit->set_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveEdit")));
+ curve_edit_curve->set_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveCurve")));
+ curve_edit_tilt->set_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveTilt")));
+ curve_create->set_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveCreate")));
+ curve_del->set_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveDelete")));
+ curve_close->set_icon(topmenu_bar->get_editor_theme_icon(SNAME("CurveClose")));
+ curve_clear_points->set_icon(topmenu_bar->get_editor_theme_icon(SNAME("Clear")));
+ create_curve_button->set_icon(topmenu_bar->get_editor_theme_icon(SNAME("Curve3D")));
}
-void Path3DEditorPlugin::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_ENTER_TREE: {
- curve_create->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_CREATE));
- curve_edit_curve->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_EDIT_CURVE));
- curve_edit_tilt->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_EDIT_TILT));
- curve_edit->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_EDIT));
- curve_del->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_DELETE));
- curve_close->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_close_curve));
-
- _update_theme();
- } break;
-
- case NOTIFICATION_READY: {
- // FIXME: This can trigger theme updates when the nodes that we want to update are not yet available.
- // The toolbar should be extracted to a dedicated control and theme updates should be handled through
- // the notification.
- Node3DEditor::get_singleton()->connect(SceneStringName(theme_changed), callable_mp(this, &Path3DEditorPlugin::_update_theme));
- } break;
+void Path3DEditorPlugin::_update_toolbar() {
+ if (!path) {
+ return;
}
+ bool has_curve = path->get_curve().is_valid();
+ toolbar->set_visible(has_curve);
+ create_curve_button->set_visible(!has_curve);
}
void Path3DEditorPlugin::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_update_toolbar"), &Path3DEditorPlugin::_update_toolbar);
ClassDB::bind_method(D_METHOD("_clear_curve_points"), &Path3DEditorPlugin::_clear_curve_points);
ClassDB::bind_method(D_METHOD("_restore_curve_points"), &Path3DEditorPlugin::_restore_curve_points);
}
-Path3DEditorPlugin *Path3DEditorPlugin::singleton = nullptr;
-
Path3DEditorPlugin::Path3DEditorPlugin() {
- path = nullptr;
singleton = this;
- mirror_handle_angle = true;
- mirror_handle_length = true;
-
disk_size = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_settings/path3d_tilt_disk_size", 0.8);
Ref<Path3DGizmoPlugin> gizmo_plugin = memnew(Path3DGizmoPlugin(disk_size));
@@ -828,80 +825,93 @@ Path3DEditorPlugin::Path3DEditorPlugin() {
topmenu_bar = memnew(HBoxContainer);
topmenu_bar->hide();
- Node3DEditor::get_singleton()->add_control_to_menu_panel(topmenu_bar);
+
+ toolbar = memnew(HBoxContainer);
+ topmenu_bar->add_child(toolbar);
curve_edit = memnew(Button);
curve_edit->set_theme_type_variation("FlatButton");
curve_edit->set_toggle_mode(true);
curve_edit->set_focus_mode(Control::FOCUS_NONE);
curve_edit->set_tooltip_text(TTR("Select Points") + "\n" + TTR("Shift+Click: Select multiple Points") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Click: Add Point") + "\n" + TTR("Right Click: Delete Point"));
- topmenu_bar->add_child(curve_edit);
+ toolbar->add_child(curve_edit);
+ curve_edit->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_EDIT));
curve_edit_curve = memnew(Button);
curve_edit_curve->set_theme_type_variation("FlatButton");
curve_edit_curve->set_toggle_mode(true);
curve_edit_curve->set_focus_mode(Control::FOCUS_NONE);
curve_edit_curve->set_tooltip_text(TTR("Select Control Points") + "\n" + TTR("Shift+Click: Drag out Control Points"));
- topmenu_bar->add_child(curve_edit_curve);
+ toolbar->add_child(curve_edit_curve);
+ curve_edit_curve->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_EDIT_CURVE));
curve_edit_tilt = memnew(Button);
curve_edit_tilt->set_theme_type_variation("FlatButton");
curve_edit_tilt->set_toggle_mode(true);
curve_edit_tilt->set_focus_mode(Control::FOCUS_NONE);
curve_edit_tilt->set_tooltip_text(TTR("Select Tilt Handles"));
- topmenu_bar->add_child(curve_edit_tilt);
+ toolbar->add_child(curve_edit_tilt);
+ curve_edit_tilt->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_EDIT_TILT));
curve_create = memnew(Button);
curve_create->set_theme_type_variation("FlatButton");
curve_create->set_toggle_mode(true);
curve_create->set_focus_mode(Control::FOCUS_NONE);
curve_create->set_tooltip_text(TTR("Add Point (in empty space)") + "\n" + TTR("Split Segment (in curve)"));
- topmenu_bar->add_child(curve_create);
+ toolbar->add_child(curve_create);
+ curve_create->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_CREATE));
curve_del = memnew(Button);
curve_del->set_theme_type_variation("FlatButton");
curve_del->set_toggle_mode(true);
curve_del->set_focus_mode(Control::FOCUS_NONE);
curve_del->set_tooltip_text(TTR("Delete Point"));
- topmenu_bar->add_child(curve_del);
+ toolbar->add_child(curve_del);
+ curve_del->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_DELETE));
curve_close = memnew(Button);
curve_close->set_theme_type_variation("FlatButton");
curve_close->set_focus_mode(Control::FOCUS_NONE);
curve_close->set_tooltip_text(TTR("Close Curve"));
- topmenu_bar->add_child(curve_close);
+ toolbar->add_child(curve_close);
+ curve_close->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_close_curve));
curve_clear_points = memnew(Button);
curve_clear_points->set_theme_type_variation("FlatButton");
curve_clear_points->set_focus_mode(Control::FOCUS_NONE);
curve_clear_points->set_tooltip_text(TTR("Clear Points"));
curve_clear_points->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_confirm_clear_points));
- topmenu_bar->add_child(curve_clear_points);
+ toolbar->add_child(curve_clear_points);
clear_points_dialog = memnew(ConfirmationDialog);
clear_points_dialog->set_title(TTR("Please Confirm..."));
clear_points_dialog->set_text(TTR("Remove all curve points?"));
clear_points_dialog->connect(SceneStringName(confirmed), callable_mp(this, &Path3DEditorPlugin::_clear_points));
- topmenu_bar->add_child(clear_points_dialog);
+ toolbar->add_child(clear_points_dialog);
handle_menu = memnew(MenuButton);
handle_menu->set_flat(false);
handle_menu->set_theme_type_variation("FlatMenuButton");
handle_menu->set_text(TTR("Options"));
- topmenu_bar->add_child(handle_menu);
+ toolbar->add_child(handle_menu);
+
+ create_curve_button = memnew(Button);
+ create_curve_button->set_text(TTR("Create Curve"));
+ create_curve_button->hide();
+ topmenu_bar->add_child(create_curve_button);
+ create_curve_button->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_create_curve));
- PopupMenu *menu;
- menu = handle_menu->get_popup();
+ PopupMenu *menu = handle_menu->get_popup();
menu->add_check_item(TTR("Mirror Handle Angles"));
menu->set_item_checked(HANDLE_OPTION_ANGLE, mirror_handle_angle);
menu->add_check_item(TTR("Mirror Handle Lengths"));
menu->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length);
menu->connect(SceneStringName(id_pressed), callable_mp(this, &Path3DEditorPlugin::_handle_option_pressed));
- curve_edit->set_pressed(true);
-}
+ curve_edit->set_pressed_no_signal(true);
-Path3DEditorPlugin::~Path3DEditorPlugin() {
+ topmenu_bar->connect(SceneStringName(theme_changed), callable_mp(this, &Path3DEditorPlugin::_update_theme));
+ Node3DEditor::get_singleton()->add_control_to_menu_panel(topmenu_bar);
}
Ref<EditorNode3DGizmo> Path3DGizmoPlugin::create_gizmo(Node3D *p_spatial) {
diff --git a/editor/plugins/path_3d_editor_plugin.h b/editor/plugins/path_3d_editor_plugin.h
index ee73df1617..60cb7f940f 100644
--- a/editor/plugins/path_3d_editor_plugin.h
+++ b/editor/plugins/path_3d_editor_plugin.h
@@ -113,6 +113,8 @@ class Path3DEditorPlugin : public EditorPlugin {
Ref<Path3DGizmoPlugin> path_3d_gizmo_plugin;
HBoxContainer *topmenu_bar = nullptr;
+
+ HBoxContainer *toolbar = nullptr;
Button *curve_create = nullptr;
Button *curve_edit = nullptr;
Button *curve_edit_curve = nullptr;
@@ -122,6 +124,7 @@ class Path3DEditorPlugin : public EditorPlugin {
Button *curve_clear_points = nullptr;
MenuButton *handle_menu = nullptr;
+ Button *create_curve_button = nullptr;
ConfirmationDialog *clear_points_dialog = nullptr;
float disk_size = 0.8;
@@ -138,14 +141,16 @@ class Path3DEditorPlugin : public EditorPlugin {
Path3D *path = nullptr;
void _update_theme();
+ void _update_toolbar();
void _mode_changed(int p_mode);
void _close_curve();
void _handle_option_pressed(int p_option);
bool handle_clicked = false;
- bool mirror_handle_angle;
- bool mirror_handle_length;
+ bool mirror_handle_angle = true;
+ bool mirror_handle_length = true;
+ void _create_curve();
void _confirm_clear_points();
void _clear_points();
void _clear_curve_points();
@@ -157,13 +162,12 @@ class Path3DEditorPlugin : public EditorPlugin {
};
protected:
- void _notification(int p_what);
static void _bind_methods();
public:
Path3D *get_edited_path() { return path; }
- static Path3DEditorPlugin *singleton;
+ inline static Path3DEditorPlugin *singleton = nullptr;
virtual EditorPlugin::AfterGUIInput forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override;
virtual String get_name() const override { return "Path3D"; }
@@ -178,7 +182,6 @@ public:
void set_handle_clicked(bool clicked) { handle_clicked = clicked; }
Path3DEditorPlugin();
- ~Path3DEditorPlugin();
};
#endif // PATH_3D_EDITOR_PLUGIN_H
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 93c8ae5438..253005c5ce 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -44,6 +44,7 @@
#include "editor/editor_command_palette.h"
#include "editor/editor_help_search.h"
#include "editor/editor_interface.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_script.h"
@@ -1399,13 +1400,12 @@ void ScriptEditor::_menu_option(int p_option) {
}
}
- // Context menu options.
- if (p_option >= EditorData::CONTEXT_MENU_ITEM_ID_BASE) {
+ if (p_option >= EditorContextMenuPlugin::BASE_ID) {
Ref<Resource> resource;
if (current) {
resource = current->get_edited_resource();
}
- EditorNode::get_editor_data().script_editor_options_pressed(EditorData::CONTEXT_SLOT_SCRIPT_EDITOR, p_option, resource);
+ EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_SCRIPT_EDITOR, p_option, resource);
return;
}
@@ -3315,10 +3315,16 @@ void ScriptEditor::shortcut_input(const Ref<InputEvent> &p_event) {
_menu_option(WINDOW_MOVE_DOWN);
accept_event();
}
- // Context menu shortcuts.
- int match_option = EditorNode::get_editor_data().match_context_menu_shortcut(EditorData::CONTEXT_SLOT_SCRIPT_EDITOR, p_event);
- if (match_option) {
- _menu_option(match_option);
+
+ Callable custom_callback = EditorContextMenuPluginManager::get_singleton()->match_custom_shortcut(EditorContextMenuPlugin::CONTEXT_SLOT_SCRIPT_EDITOR, p_event);
+ if (custom_callback.is_valid()) {
+ Ref<Resource> resource;
+ ScriptEditorBase *current = _get_current_editor();
+ if (current) {
+ resource = current->get_edited_resource();
+ }
+ EditorContextMenuPluginManager::get_singleton()->invoke_callback(custom_callback, resource);
+ accept_event();
}
}
@@ -3387,7 +3393,7 @@ void ScriptEditor::_make_script_list_context_menu() {
selected_paths.push_back(path);
}
}
- EditorNode::get_editor_data().add_options_from_plugins(context_menu, EditorData::CONTEXT_SLOT_SCRIPT_EDITOR, selected_paths);
+ EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(context_menu, EditorContextMenuPlugin::CONTEXT_SLOT_SCRIPT_EDITOR, selected_paths);
context_menu->set_position(get_screen_position() + get_local_mouse_position());
context_menu->reset_size();
@@ -4052,6 +4058,7 @@ void ScriptEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("open_script_create_dialog", "base_name", "base_path"), &ScriptEditor::open_script_create_dialog);
ClassDB::bind_method(D_METHOD("goto_help", "topic"), &ScriptEditor::goto_help);
+ ClassDB::bind_method(D_METHOD("update_docs_from_script", "script"), &ScriptEditor::update_docs_from_script);
ADD_SIGNAL(MethodInfo("editor_script_changed", PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script")));
ADD_SIGNAL(MethodInfo("script_close", PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script")));
@@ -4612,7 +4619,7 @@ ScriptEditorPlugin::ScriptEditorPlugin() {
Ref<Shortcut> make_floating_shortcut = ED_SHORTCUT_AND_COMMAND("script_editor/make_floating", TTR("Make Floating"));
window_wrapper->set_wrapped_control(script_editor, make_floating_shortcut);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(window_wrapper);
+ EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(window_wrapper);
window_wrapper->set_v_size_flags(Control::SIZE_EXPAND_FILL);
window_wrapper->hide();
window_wrapper->connect("window_visibility_changed", callable_mp(this, &ScriptEditorPlugin::_window_visibility_changed));
diff --git a/editor/plugins/skeleton_2d_editor_plugin.cpp b/editor/plugins/skeleton_2d_editor_plugin.cpp
index 97c5c0c7dd..8f54641dcb 100644
--- a/editor/plugins/skeleton_2d_editor_plugin.cpp
+++ b/editor/plugins/skeleton_2d_editor_plugin.cpp
@@ -129,7 +129,7 @@ void Skeleton2DEditorPlugin::make_visible(bool p_visible) {
Skeleton2DEditorPlugin::Skeleton2DEditorPlugin() {
sprite_editor = memnew(Skeleton2DEditor);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(sprite_editor);
+ EditorNode::get_singleton()->get_gui_base()->add_child(sprite_editor);
make_visible(false);
//sprite_editor->options->hide();
diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp
index dc4d4db3f8..9fc88b3a6a 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.cpp
+++ b/editor/plugins/skeleton_3d_editor_plugin.cpp
@@ -1348,16 +1348,18 @@ int Skeleton3DEditor::get_selected_bone() const {
return selected_bone;
}
+Skeleton3DGizmoPlugin::SelectionMaterials Skeleton3DGizmoPlugin::selection_materials;
+
Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() {
- unselected_mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
- unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
- unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
- unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
- unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
- unselected_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
-
- selected_mat = Ref<ShaderMaterial>(memnew(ShaderMaterial));
- selected_sh = Ref<Shader>(memnew(Shader));
+ selection_materials.unselected_mat.instantiate();
+ selection_materials.unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+ selection_materials.unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
+ selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
+ selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
+ selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
+
+ selection_materials.selected_mat.instantiate();
+ Ref<Shader> selected_sh = Ref<Shader>(memnew(Shader));
selected_sh->set_code(R"(
// Skeleton 3D gizmo bones shader.
@@ -1376,7 +1378,7 @@ void fragment() {
ALPHA = COLOR.a;
}
)");
- selected_mat->set_shader(selected_sh);
+ selection_materials.selected_mat->set_shader(selected_sh);
// Register properties in editor settings.
EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4));
@@ -1386,6 +1388,11 @@ void fragment() {
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/3d_gizmos/gizmo_settings/bone_shape", PROPERTY_HINT_ENUM, "Wire,Octahedron"));
}
+Skeleton3DGizmoPlugin::~Skeleton3DGizmoPlugin() {
+ selection_materials.unselected_mat.unref();
+ selection_materials.selected_mat.unref();
+}
+
bool Skeleton3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<Skeleton3D>(p_spatial) != nullptr;
}
@@ -1526,6 +1533,11 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
selected = se->get_selected_bone();
}
+ Ref<ArrayMesh> m = get_bones_mesh(skeleton, selected, p_gizmo->is_selected());
+ p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skeleton->register_skin(skeleton->create_skin_from_rest_transforms()));
+}
+
+Ref<ArrayMesh> Skeleton3DGizmoPlugin::get_bones_mesh(Skeleton3D *p_skeleton, int p_selected, bool p_is_selected) {
Color bone_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/skeleton");
Color selected_bone_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/selected_bone");
real_t bone_axis_length = EDITOR_GET("editors/3d_gizmos/gizmo_settings/bone_axis_length");
@@ -1539,11 +1551,11 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
Ref<SurfaceTool> surface_tool(memnew(SurfaceTool));
surface_tool->begin(Mesh::PRIMITIVE_LINES);
- if (p_gizmo->is_selected()) {
- surface_tool->set_material(selected_mat);
+ if (p_is_selected) {
+ surface_tool->set_material(selection_materials.selected_mat);
} else {
- unselected_mat->set_albedo(bone_color);
- surface_tool->set_material(unselected_mat);
+ selection_materials.unselected_mat->set_albedo(bone_color);
+ surface_tool->set_material(selection_materials.unselected_mat);
}
LocalVector<int> bones;
@@ -1557,16 +1569,16 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
weights[0] = 1;
int current_bone_index = 0;
- Vector<int> bones_to_process = skeleton->get_parentless_bones();
+ Vector<int> bones_to_process = p_skeleton->get_parentless_bones();
while (bones_to_process.size() > current_bone_index) {
int current_bone_idx = bones_to_process[current_bone_index];
current_bone_index++;
- Color current_bone_color = (current_bone_idx == selected) ? selected_bone_color : bone_color;
+ Color current_bone_color = (current_bone_idx == p_selected) ? selected_bone_color : bone_color;
Vector<int> child_bones_vector;
- child_bones_vector = skeleton->get_bone_children(current_bone_idx);
+ child_bones_vector = p_skeleton->get_bone_children(current_bone_idx);
int child_bones_size = child_bones_vector.size();
for (int i = 0; i < child_bones_size; i++) {
@@ -1577,8 +1589,8 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
int child_bone_idx = child_bones_vector[i];
- Vector3 v0 = skeleton->get_bone_global_rest(current_bone_idx).origin;
- Vector3 v1 = skeleton->get_bone_global_rest(child_bone_idx).origin;
+ Vector3 v0 = p_skeleton->get_bone_global_rest(current_bone_idx).origin;
+ Vector3 v1 = p_skeleton->get_bone_global_rest(child_bone_idx).origin;
Vector3 d = (v1 - v0).normalized();
real_t dist = v0.distance_to(v1);
@@ -1586,7 +1598,7 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
int closest = -1;
real_t closest_d = 0.0;
for (int j = 0; j < 3; j++) {
- real_t dp = Math::abs(skeleton->get_bone_global_rest(current_bone_idx).basis[j].normalized().dot(d));
+ real_t dp = Math::abs(p_skeleton->get_bone_global_rest(current_bone_idx).basis[j].normalized().dot(d));
if (j == 0 || dp > closest_d) {
closest = j;
}
@@ -1613,7 +1625,7 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
for (int j = 0; j < 3; j++) {
Vector3 axis;
if (first == Vector3()) {
- axis = d.cross(d.cross(skeleton->get_bone_global_rest(current_bone_idx).basis[j])).normalized();
+ axis = d.cross(d.cross(p_skeleton->get_bone_global_rest(current_bone_idx).basis[j])).normalized();
first = axis;
} else {
axis = d.cross(first).normalized();
@@ -1668,7 +1680,7 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
surface_tool->add_vertex(v0);
surface_tool->set_bones(bones);
surface_tool->set_weights(weights);
- surface_tool->add_vertex(v0 + (skeleton->get_bone_global_rest(current_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length);
+ surface_tool->add_vertex(v0 + (p_skeleton->get_bone_global_rest(current_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length);
if (j == closest) {
continue;
@@ -1685,7 +1697,7 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
surface_tool->add_vertex(v1);
surface_tool->set_bones(bones);
surface_tool->set_weights(weights);
- surface_tool->add_vertex(v1 + (skeleton->get_bone_global_rest(child_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length);
+ surface_tool->add_vertex(v1 + (p_skeleton->get_bone_global_rest(child_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length);
if (j == closest) {
continue;
@@ -1698,6 +1710,5 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
}
}
- Ref<ArrayMesh> m = surface_tool->commit();
- p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skeleton->register_skin(skeleton->create_skin_from_rest_transforms()));
+ return surface_tool->commit();
}
diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h
index 0bb58aac23..d4dee1f16f 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.h
+++ b/editor/plugins/skeleton_3d_editor_plugin.h
@@ -260,11 +260,15 @@ public:
class Skeleton3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(Skeleton3DGizmoPlugin, EditorNode3DGizmoPlugin);
- Ref<StandardMaterial3D> unselected_mat;
- Ref<ShaderMaterial> selected_mat;
- Ref<Shader> selected_sh;
+ struct SelectionMaterials {
+ Ref<StandardMaterial3D> unselected_mat;
+ Ref<ShaderMaterial> selected_mat;
+ };
+ static SelectionMaterials selection_materials;
public:
+ static Ref<ArrayMesh> get_bones_mesh(Skeleton3D *p_skeleton, int p_selected, bool p_is_selected);
+
bool has_gizmo(Node3D *p_spatial) override;
String get_gizmo_name() const override;
int get_priority() const override;
@@ -277,6 +281,7 @@ public:
void redraw(EditorNode3DGizmo *p_gizmo) override;
Skeleton3DGizmoPlugin();
+ ~Skeleton3DGizmoPlugin();
};
#endif // SKELETON_3D_EDITOR_PLUGIN_H
diff --git a/editor/plugins/sprite_2d_editor_plugin.cpp b/editor/plugins/sprite_2d_editor_plugin.cpp
index 3647fa2d59..c7db243662 100644
--- a/editor/plugins/sprite_2d_editor_plugin.cpp
+++ b/editor/plugins/sprite_2d_editor_plugin.cpp
@@ -673,7 +673,7 @@ void Sprite2DEditorPlugin::make_visible(bool p_visible) {
Sprite2DEditorPlugin::Sprite2DEditorPlugin() {
sprite_editor = memnew(Sprite2DEditor);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(sprite_editor);
+ EditorNode::get_singleton()->get_gui_base()->add_child(sprite_editor);
make_visible(false);
//sprite_editor->options->hide();
diff --git a/editor/plugins/tiles/tile_map_layer_editor.cpp b/editor/plugins/tiles/tile_map_layer_editor.cpp
index 7e6746dd3c..dcfd92f6f9 100644
--- a/editor/plugins/tiles/tile_map_layer_editor.cpp
+++ b/editor/plugins/tiles/tile_map_layer_editor.cpp
@@ -1364,11 +1364,13 @@ void TileMapLayerEditorTilesPlugin::_stop_dragging() {
Vector2i coords;
HashMap<Vector2i, TileMapCell> cells_undo;
for (int i = 0; i < selection_used_cells.size(); i++) {
- coords = tile_set->map_pattern(top_left, selection_used_cells[i], selection_pattern);
- cells_undo[coords] = TileMapCell(drag_modified[coords].source_id, drag_modified[coords].get_atlas_coords(), drag_modified[coords].alternative_tile);
coords = tile_set->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern);
cells_undo[coords] = TileMapCell(edited_layer->get_cell_source_id(coords), edited_layer->get_cell_atlas_coords(coords), edited_layer->get_cell_alternative_tile(coords));
}
+ for (int i = 0; i < selection_used_cells.size(); i++) {
+ coords = tile_set->map_pattern(top_left, selection_used_cells[i], selection_pattern);
+ cells_undo[coords] = TileMapCell(drag_modified[coords].source_id, drag_modified[coords].get_atlas_coords(), drag_modified[coords].alternative_tile);
+ }
// Build the list of cells to do.
HashMap<Vector2i, TileMapCell> cells_do;
diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp
index 55c361de4b..279590563a 100644
--- a/editor/project_manager.cpp
+++ b/editor/project_manager.cpp
@@ -580,7 +580,7 @@ void ProjectManager::_open_selected_projects_ask() {
if (!unsupported_features.is_empty()) {
String warning_message = "";
for (int i = 0; i < unsupported_features.size(); i++) {
- String feature = unsupported_features[i];
+ const String &feature = unsupported_features[i];
if (feature == "Double Precision") {
warning_message += TTR("Warning: This project uses double precision floats, but this version of\nGodot uses single precision floats. Opening this project may cause data loss.\n\n");
unsupported_features.remove_at(i);
@@ -590,6 +590,7 @@ void ProjectManager::_open_selected_projects_ask() {
unsupported_features.remove_at(i);
i--;
} else if (ProjectList::project_feature_looks_like_version(feature)) {
+ version_convert_feature = feature;
warning_message += vformat(TTR("Warning: This project was last edited in Godot %s. Opening will change it to Godot %s.\n\n"), Variant(feature), Variant(VERSION_BRANCH));
unsupported_features.remove_at(i);
i--;
@@ -610,6 +611,16 @@ void ProjectManager::_open_selected_projects_ask() {
_open_selected_projects();
}
+void ProjectManager::_open_selected_projects_with_migration() {
+#ifndef DISABLE_DEPRECATED
+ if (project_list->get_selected_projects().size() == 1) {
+ // Only migrate if a single project is opened.
+ _minor_project_migrate();
+ }
+#endif
+ _open_selected_projects();
+}
+
void ProjectManager::_install_project(const String &p_zip_path, const String &p_title) {
project_dialog->set_mode(ProjectDialog::MODE_INSTALL);
project_dialog->set_zip_path(p_zip_path);
@@ -877,6 +888,34 @@ void ProjectManager::add_new_tag(const String &p_tag) {
// Project converter/migration tool.
+#ifndef DISABLE_DEPRECATED
+void ProjectManager::_minor_project_migrate() {
+ const ProjectList::Item migrated_project = project_list->get_selected_projects()[0];
+
+ if (version_convert_feature.begins_with("4.3")) {
+ // Migrate layout after scale changes.
+ const float edscale = EDSCALE;
+ if (edscale != 1.0) {
+ Ref<ConfigFile> layout_file;
+ layout_file.instantiate();
+
+ const String layout_path = migrated_project.path.path_join(".godot/editor/editor_layout.cfg");
+ Error err = layout_file->load(layout_path);
+ if (err == OK) {
+ for (int i = 0; i < 4; i++) {
+ const String key = "dock_hsplit_" + itos(i + 1);
+ int old_value = layout_file->get_value("docks", key, 0);
+ if (old_value != 0) {
+ layout_file->set_value("docks", key, old_value / edscale);
+ }
+ }
+ layout_file->save(layout_path);
+ }
+ }
+ }
+}
+#endif
+
void ProjectManager::_full_convert_button_pressed() {
ask_update_settings->hide();
ask_full_convert_dialog->popup_centered(Size2i(600.0 * EDSCALE, 0));
@@ -1487,7 +1526,7 @@ ProjectManager::ProjectManager() {
ask_update_settings = memnew(ConfirmationDialog);
ask_update_settings->set_autowrap(true);
- ask_update_settings->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects));
+ ask_update_settings->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects_with_migration));
full_convert_button = ask_update_settings->add_button(TTR("Convert Full Project"), !GLOBAL_GET("gui/common/swap_cancel_ok"));
full_convert_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_full_convert_button_pressed));
add_child(ask_update_settings);
diff --git a/editor/project_manager.h b/editor/project_manager.h
index 669b5d8b6c..aad51d0e98 100644
--- a/editor/project_manager.h
+++ b/editor/project_manager.h
@@ -176,6 +176,7 @@ class ProjectManager : public Control {
void _run_project_confirm();
void _open_selected_projects();
void _open_selected_projects_ask();
+ void _open_selected_projects_with_migration();
void _install_project(const String &p_zip_path, const String &p_title);
void _import_project();
@@ -223,6 +224,11 @@ class ProjectManager : public Control {
ConfirmationDialog *ask_update_settings = nullptr;
Button *full_convert_button = nullptr;
+ String version_convert_feature;
+
+#ifndef DISABLE_DEPRECATED
+ void _minor_project_migrate();
+#endif
void _full_convert_button_pressed();
void _perform_full_project_conversion();
diff --git a/editor/project_manager/project_dialog.cpp b/editor/project_manager/project_dialog.cpp
index 9b009c0a89..7acda16890 100644
--- a/editor/project_manager/project_dialog.cpp
+++ b/editor/project_manager/project_dialog.cpp
@@ -162,7 +162,7 @@ void ProjectDialog::_validate_path() {
}
}
- if (target_path.is_empty() || target_path.is_relative_path()) {
+ if (target_path.is_relative_path()) {
_set_message(TTR("The path specified is invalid."), MESSAGE_ERROR, target_path_input_type);
return;
}
@@ -352,7 +352,7 @@ void ProjectDialog::_install_path_changed() {
void ProjectDialog::_browse_project_path() {
String path = project_path->get_text();
- if (path.is_empty()) {
+ if (path.is_relative_path()) {
path = EDITOR_GET("filesystem/directories/default_project_path");
}
if (mode == MODE_IMPORT && install_path->is_visible_in_tree()) {
@@ -382,12 +382,16 @@ void ProjectDialog::_browse_project_path() {
void ProjectDialog::_browse_install_path() {
ERR_FAIL_COND_MSG(mode != MODE_IMPORT, "Install path is only used for MODE_IMPORT.");
+ String path = install_path->get_text();
+ if (path.is_relative_path() || !DirAccess::dir_exists_absolute(path)) {
+ path = EDITOR_GET("filesystem/directories/default_project_path");
+ }
if (create_dir->is_pressed()) {
// Select parent directory of install path.
- fdialog_install->set_current_dir(install_path->get_text().get_base_dir());
+ fdialog_install->set_current_dir(path.get_base_dir());
} else {
// Select install path.
- fdialog_install->set_current_dir(install_path->get_text());
+ fdialog_install->set_current_dir(path);
}
fdialog_install->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR);
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index ed64ccfa4c..9f56c586a2 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -37,6 +37,7 @@
#include "core/os/keyboard.h"
#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_feature_profile.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_quick_open.h"
@@ -214,11 +215,12 @@ void SceneTreeDock::shortcut_input(const Ref<InputEvent> &p_event) {
} else if (ED_IS_SHORTCUT("scene_tree/delete", p_event)) {
_tool_selected(TOOL_ERASE);
} else {
- int match_option = EditorNode::get_editor_data().match_context_menu_shortcut(EditorData::CONTEXT_SLOT_SCENE_TREE, p_event);
- if (match_option) {
- _tool_selected(match_option);
+ Callable custom_callback = EditorContextMenuPluginManager::get_singleton()->match_custom_shortcut(EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TREE, p_event);
+ if (custom_callback.is_valid()) {
+ EditorContextMenuPluginManager::get_singleton()->invoke_callback(custom_callback, _get_selection_array());
+ } else {
+ return;
}
- return;
}
// Tool selection was successful, accept the event to stop propagation.
@@ -1197,7 +1199,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
ScriptEditor::get_singleton()->goto_help("class_name:" + class_name);
}
- EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
} break;
case TOOL_AUTO_EXPAND: {
scene_tree->set_auto_expand_selected(!EDITOR_GET("docks/scene_tree/auto_expand_to_selected"), true);
@@ -1241,6 +1243,10 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
break;
}
+ if (!_validate_no_foreign()) {
+ break;
+ }
+
List<Node *> selection = editor_selection->get_selected_node_list();
List<Node *>::Element *e = selection.front();
if (e) {
@@ -1487,10 +1493,8 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
} break;
default: {
- // Editor context plugin.
- if (p_tool >= EditorData::CONTEXT_MENU_ITEM_ID_BASE) {
- List<Node *> selection = editor_selection->get_selected_node_list();
- EditorNode::get_editor_data().scene_tree_options_pressed(EditorData::CONTEXT_SLOT_SCENE_TREE, p_tool, selection);
+ if (p_tool >= EditorContextMenuPlugin::BASE_ID) {
+ EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TREE, p_tool, _get_selection_array());
break;
}
@@ -2932,7 +2936,7 @@ void SceneTreeDock::_create() {
int original_position = -1;
if (only_one_top_node) {
parent = top_node->get_parent();
- original_position = top_node->get_index();
+ original_position = top_node->get_index(false);
} else {
parent = top_node->get_parent()->get_parent();
}
@@ -3362,6 +3366,18 @@ void SceneTreeDock::_normalize_drop(Node *&to_node, int &to_pos, int p_type) {
}
}
+Array SceneTreeDock::_get_selection_array() {
+ List<Node *> selection = editor_selection->get_selected_node_list();
+ TypedArray<Node> array;
+ array.resize(selection.size());
+
+ int i = 0;
+ for (const Node *E : selection) {
+ array[i++] = E;
+ }
+ return array;
+}
+
void SceneTreeDock::_files_dropped(const Vector<String> &p_files, NodePath p_to, int p_type) {
Node *node = get_node(p_to);
ERR_FAIL_NULL(node);
@@ -3762,7 +3778,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
String node_path = root->get_path().rel_path_to(E->get()->get_path());
p_paths.push_back(node_path);
}
- EditorNode::get_editor_data().add_options_from_plugins(menu, EditorData::CONTEXT_SLOT_SCENE_TREE, p_paths);
+ EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(menu, EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TREE, p_paths);
menu->reset_size();
menu->set_position(p_menu_pos);
diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h
index 1807ec5896..d0cdaf8dd1 100644
--- a/editor/scene_tree_dock.h
+++ b/editor/scene_tree_dock.h
@@ -262,6 +262,7 @@ class SceneTreeDock : public VBoxContainer {
bool _has_tracks_to_delete(Node *p_node, List<Node *> &p_to_delete) const;
void _normalize_drop(Node *&to_node, int &to_pos, int p_type);
+ Array _get_selection_array();
void _nodes_dragged(const Array &p_nodes, NodePath p_to, int p_type);
void _files_dropped(const Vector<String> &p_files, NodePath p_to, int p_type);
diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp
index 119508f59b..17bcbacfc2 100644
--- a/editor/themes/editor_theme_manager.cpp
+++ b/editor/themes/editor_theme_manager.cpp
@@ -865,7 +865,6 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
// CheckBox.
{
Ref<StyleBoxFlat> checkbox_style = p_config.panel_container_style->duplicate();
- checkbox_style->set_content_margin_individual((p_config.increased_margin + 2) * EDSCALE, p_config.base_margin * EDSCALE, (p_config.increased_margin + 2) * EDSCALE, p_config.base_margin * EDSCALE);
p_theme->set_stylebox(CoreStringName(normal), "CheckBox", checkbox_style);
p_theme->set_stylebox(SceneStringName(pressed), "CheckBox", checkbox_style);
@@ -1165,9 +1164,6 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
// LineEdit & TextEdit.
{
Ref<StyleBoxFlat> text_editor_style = p_config.button_style->duplicate();
- // The original button_style style has an extra 1 pixel offset that makes LineEdits not align with Buttons,
- // so this compensates for that.
- text_editor_style->set_content_margin(SIDE_TOP, text_editor_style->get_content_margin(SIDE_TOP) - 1 * EDSCALE);
// Don't round the bottom corners to make the line look sharper.
text_editor_style->set_corner_radius(CORNER_BOTTOM_LEFT, 0);
@@ -1419,21 +1415,24 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
p_theme->set_icon("decrement_highlight", "VScrollBar", empty_icon);
p_theme->set_icon("decrement_pressed", "VScrollBar", empty_icon);
+ // Slider
+ const int background_margin = MAX(2, p_config.base_margin / 2);
+
// HSlider.
p_theme->set_icon("grabber_highlight", "HSlider", p_theme->get_icon(SNAME("GuiSliderGrabberHl"), EditorStringName(EditorIcons)));
p_theme->set_icon("grabber", "HSlider", p_theme->get_icon(SNAME("GuiSliderGrabber"), EditorStringName(EditorIcons)));
- p_theme->set_stylebox("slider", "HSlider", make_flat_stylebox(p_config.dark_color_3, 0, p_config.base_margin / 2, 0, p_config.base_margin / 2, p_config.corner_radius));
- p_theme->set_stylebox("grabber_area", "HSlider", make_flat_stylebox(p_config.contrast_color_1, 0, p_config.base_margin / 2, 0, p_config.base_margin / 2, p_config.corner_radius));
- p_theme->set_stylebox("grabber_area_highlight", "HSlider", make_flat_stylebox(p_config.contrast_color_1, 0, p_config.base_margin / 2, 0, p_config.base_margin / 2));
+ p_theme->set_stylebox("slider", "HSlider", make_flat_stylebox(p_config.dark_color_3, 0, background_margin, 0, background_margin, p_config.corner_radius));
+ p_theme->set_stylebox("grabber_area", "HSlider", make_flat_stylebox(p_config.contrast_color_1, 0, background_margin, 0, background_margin, p_config.corner_radius));
+ p_theme->set_stylebox("grabber_area_highlight", "HSlider", make_flat_stylebox(p_config.contrast_color_1, 0, background_margin, 0, background_margin));
p_theme->set_constant("center_grabber", "HSlider", 0);
p_theme->set_constant("grabber_offset", "HSlider", 0);
// VSlider.
p_theme->set_icon("grabber", "VSlider", p_theme->get_icon(SNAME("GuiSliderGrabber"), EditorStringName(EditorIcons)));
p_theme->set_icon("grabber_highlight", "VSlider", p_theme->get_icon(SNAME("GuiSliderGrabberHl"), EditorStringName(EditorIcons)));
- p_theme->set_stylebox("slider", "VSlider", make_flat_stylebox(p_config.dark_color_3, p_config.base_margin / 2, 0, p_config.base_margin / 2, 0, p_config.corner_radius));
- p_theme->set_stylebox("grabber_area", "VSlider", make_flat_stylebox(p_config.contrast_color_1, p_config.base_margin / 2, 0, p_config.base_margin / 2, 0, p_config.corner_radius));
- p_theme->set_stylebox("grabber_area_highlight", "VSlider", make_flat_stylebox(p_config.contrast_color_1, p_config.base_margin / 2, 0, p_config.base_margin / 2, 0));
+ p_theme->set_stylebox("slider", "VSlider", make_flat_stylebox(p_config.dark_color_3, background_margin, 0, background_margin, 0, p_config.corner_radius));
+ p_theme->set_stylebox("grabber_area", "VSlider", make_flat_stylebox(p_config.contrast_color_1, background_margin, 0, background_margin, 0, p_config.corner_radius));
+ p_theme->set_stylebox("grabber_area_highlight", "VSlider", make_flat_stylebox(p_config.contrast_color_1, background_margin, 0, background_margin, 0));
p_theme->set_constant("center_grabber", "VSlider", 0);
p_theme->set_constant("grabber_offset", "VSlider", 0);
}
diff --git a/main/main.cpp b/main/main.cpp
index 1af08bc036..9c9542325e 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -140,6 +140,7 @@ static Engine *engine = nullptr;
static ProjectSettings *globals = nullptr;
static Input *input = nullptr;
static InputMap *input_map = nullptr;
+static WorkerThreadPool *worker_thread_pool = nullptr;
static TranslationServer *translation_server = nullptr;
static Performance *performance = nullptr;
static PackedData *packed_data = nullptr;
@@ -690,6 +691,8 @@ Error Main::test_setup() {
register_core_settings(); // Here globals are present.
+ worker_thread_pool = memnew(WorkerThreadPool);
+
translation_server = memnew(TranslationServer);
tsman = memnew(TextServerManager);
@@ -800,6 +803,8 @@ void Main::test_cleanup() {
ResourceSaver::remove_custom_savers();
PropertyListHelper::clear_base_helpers();
+ WorkerThreadPool::get_singleton()->finish();
+
#ifdef TOOLS_ENABLED
GDExtensionManager::get_singleton()->deinitialize_extensions(GDExtension::INITIALIZATION_LEVEL_EDITOR);
uninitialize_modules(MODULE_INITIALIZATION_LEVEL_EDITOR);
@@ -841,6 +846,9 @@ void Main::test_cleanup() {
if (physics_server_2d_manager) {
memdelete(physics_server_2d_manager);
}
+ if (worker_thread_pool) {
+ memdelete(worker_thread_pool);
+ }
if (globals) {
memdelete(globals);
}
@@ -931,6 +939,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
register_core_settings(); //here globals are present
+ worker_thread_pool = memnew(WorkerThreadPool);
translation_server = memnew(TranslationServer);
performance = memnew(Performance);
GDREGISTER_CLASS(Performance);
@@ -1037,7 +1046,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
if (arg == "--audio-driver" ||
arg == "--display-driver" ||
arg == "--rendering-method" ||
- arg == "--rendering-driver") {
+ arg == "--rendering-driver" ||
+ arg == "--xr-mode") {
if (N) {
forwardable_cli_arguments[CLI_SCOPE_TOOL].push_back(arg);
forwardable_cli_arguments[CLI_SCOPE_TOOL].push_back(N->get());
@@ -2619,6 +2629,10 @@ error:
if (translation_server) {
memdelete(translation_server);
}
+ if (worker_thread_pool) {
+ worker_thread_pool->finish();
+ memdelete(worker_thread_pool);
+ }
if (globals) {
memdelete(globals);
}
@@ -4500,6 +4514,8 @@ void Main::cleanup(bool p_force) {
ResourceLoader::clear_translation_remaps();
ResourceLoader::clear_path_remaps();
+ WorkerThreadPool::get_singleton()->finish();
+
ScriptServer::finish_languages();
// Sync pending commands that may have been queued from a different thread during ScriptServer finalization
@@ -4590,6 +4606,9 @@ void Main::cleanup(bool p_force) {
if (physics_server_2d_manager) {
memdelete(physics_server_2d_manager);
}
+ if (worker_thread_pool) {
+ memdelete(worker_thread_pool);
+ }
if (globals) {
memdelete(globals);
}
diff --git a/methods.py b/methods.py
index bfd08cfc7b..36462fd30b 100644
--- a/methods.py
+++ b/methods.py
@@ -906,21 +906,18 @@ def show_progress(env):
node_count_fname = str(env.Dir("#")) + "/.scons_node_count"
import math
- import time
class cache_progress:
- # The default is 1 GB cache and 12 hours half life
- def __init__(self, path=None, limit=1073741824, half_life=43200):
+ # The default is 1 GB cache
+ def __init__(self, path=None, limit=pow(1024, 3)):
self.path = path
self.limit = limit
- self.exponent_scale = math.log(2) / half_life
if env["verbose"] and path is not None:
screen.write(
"Current cache limit is {} (used: {})\n".format(
self.convert_size(limit), self.convert_size(self.get_size(path))
)
)
- self.delete(self.file_list())
def __call__(self, node, *args, **kw):
nonlocal node_count, node_count_max, node_count_interval, node_count_fname, show_progress
@@ -937,12 +934,66 @@ def show_progress(env):
screen.write("\r[Initial build] ")
screen.flush()
+ def convert_size(self, size_bytes):
+ if size_bytes == 0:
+ return "0 bytes"
+ size_name = ("bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
+ i = int(math.floor(math.log(size_bytes, 1024)))
+ p = math.pow(1024, i)
+ s = round(size_bytes / p, 2)
+ return "%s %s" % (int(s) if i == 0 else s, size_name[i])
+
+ def get_size(self, start_path="."):
+ total_size = 0
+ for dirpath, dirnames, filenames in os.walk(start_path):
+ for f in filenames:
+ fp = os.path.join(dirpath, f)
+ total_size += os.path.getsize(fp)
+ return total_size
+
+ def progress_finish(target, source, env):
+ nonlocal node_count, progressor
+ try:
+ with open(node_count_fname, "w", encoding="utf-8", newline="\n") as f:
+ f.write("%d\n" % node_count)
+ except Exception:
+ pass
+
+ try:
+ with open(node_count_fname, "r", encoding="utf-8") as f:
+ node_count_max = int(f.readline())
+ except Exception:
+ pass
+
+ cache_directory = os.environ.get("SCONS_CACHE")
+ # Simple cache pruning, attached to SCons' progress callback. Trim the
+ # cache directory to a size not larger than cache_limit.
+ cache_limit = float(os.getenv("SCONS_CACHE_LIMIT", 1024)) * 1024 * 1024
+ progressor = cache_progress(cache_directory, cache_limit)
+ Progress(progressor, interval=node_count_interval)
+
+ progress_finish_command = Command("progress_finish", [], progress_finish)
+ AlwaysBuild(progress_finish_command)
+
+
+def clean_cache(env):
+ import atexit
+ import time
+
+ class cache_clean:
+ def __init__(self, path=None, limit=pow(1024, 3)):
+ self.path = path
+ self.limit = limit
+
+ def clean(self):
+ self.delete(self.file_list())
+
def delete(self, files):
if len(files) == 0:
return
if env["verbose"]:
# Utter something
- screen.write("\rPurging %d %s from cache...\n" % (len(files), len(files) > 1 and "files" or "file"))
+ print("Purging %d %s from cache..." % (len(files), "files" if len(files) > 1 else "file"))
[os.remove(f) for f in files]
def file_list(self):
@@ -976,47 +1027,20 @@ def show_progress(env):
else:
return [x[0] for x in file_stat[mark:]]
- def convert_size(self, size_bytes):
- if size_bytes == 0:
- return "0 bytes"
- size_name = ("bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
- i = int(math.floor(math.log(size_bytes, 1024)))
- p = math.pow(1024, i)
- s = round(size_bytes / p, 2)
- return "%s %s" % (int(s) if i == 0 else s, size_name[i])
-
- def get_size(self, start_path="."):
- total_size = 0
- for dirpath, dirnames, filenames in os.walk(start_path):
- for f in filenames:
- fp = os.path.join(dirpath, f)
- total_size += os.path.getsize(fp)
- return total_size
-
- def progress_finish(target, source, env):
- nonlocal node_count, progressor
+ def cache_finally():
+ nonlocal cleaner
try:
- with open(node_count_fname, "w", encoding="utf-8", newline="\n") as f:
- f.write("%d\n" % node_count)
- progressor.delete(progressor.file_list())
+ cleaner.clean()
except Exception:
pass
- try:
- with open(node_count_fname, "r", encoding="utf-8") as f:
- node_count_max = int(f.readline())
- except Exception:
- pass
-
cache_directory = os.environ.get("SCONS_CACHE")
# Simple cache pruning, attached to SCons' progress callback. Trim the
# cache directory to a size not larger than cache_limit.
cache_limit = float(os.getenv("SCONS_CACHE_LIMIT", 1024)) * 1024 * 1024
- progressor = cache_progress(cache_directory, cache_limit)
- Progress(progressor, interval=node_count_interval)
+ cleaner = cache_clean(cache_directory, cache_limit)
- progress_finish_command = Command("progress_finish", [], progress_finish)
- AlwaysBuild(progress_finish_command)
+ atexit.register(cache_finally)
def dump(env):
diff --git a/misc/extension_api_validation/4.3-stable.expected b/misc/extension_api_validation/4.3-stable.expected
index 882f96a0dc..ad77e30e11 100644
--- a/misc/extension_api_validation/4.3-stable.expected
+++ b/misc/extension_api_validation/4.3-stable.expected
@@ -73,3 +73,10 @@ Validate extension JSON: Error: Field 'classes/EditorInterface/methods/popup_nod
Validate extension JSON: Error: Field 'classes/EditorInterface/methods/popup_property_selector/arguments': size changed value in new API, from 3 to 4.
Added optional argument to popup_property_selector and popup_node_selector to specify the current value.
+
+
+GH-94434
+--------
+Validate extension JSON: Error: Field 'classes/OS/methods/execute_with_pipe/arguments': size changed value in new API, from 2 to 3.
+
+Optional argument added. Compatibility method registered.
diff --git a/modules/basis_universal/image_compress_basisu.cpp b/modules/basis_universal/image_compress_basisu.cpp
index 8167fe8c73..ab20d00b5b 100644
--- a/modules/basis_universal/image_compress_basisu.cpp
+++ b/modules/basis_universal/image_compress_basisu.cpp
@@ -84,14 +84,12 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
decompress_format = BASIS_DECOMPRESS_RGBA;
} break;
case Image::USED_CHANNELS_R: {
- decompress_format = BASIS_DECOMPRESS_RGB;
+ decompress_format = BASIS_DECOMPRESS_R;
} break;
case Image::USED_CHANNELS_RG: {
- // Currently RG textures are compressed as DXT5/ETC2_RGBA8 with a RA -> RG swizzle,
- // as BasisUniversal didn't use to support ETC2_RG11 transcoding.
params.m_force_alpha = true;
image->convert_rg_to_ra_rgba8();
- decompress_format = BASIS_DECOMPRESS_RG_AS_RA;
+ decompress_format = BASIS_DECOMPRESS_RG;
} break;
case Image::USED_CHANNELS_RGB: {
decompress_format = BASIS_DECOMPRESS_RGB;
@@ -219,15 +217,68 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
// Get supported compression formats.
bool bptc_supported = RS::get_singleton()->has_os_feature("bptc");
bool astc_supported = RS::get_singleton()->has_os_feature("astc");
+ bool rgtc_supported = RS::get_singleton()->has_os_feature("rgtc");
bool s3tc_supported = RS::get_singleton()->has_os_feature("s3tc");
bool etc2_supported = RS::get_singleton()->has_os_feature("etc2");
bool needs_ra_rg_swap = false;
+ bool needs_rg_trim = false;
+
+ BasisDecompressFormat decompress_format = (BasisDecompressFormat)(*(uint32_t *)(src_ptr));
- switch (*(uint32_t *)(src_ptr)) {
+ switch (decompress_format) {
+ case BASIS_DECOMPRESS_R: {
+ if (rgtc_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFBC4_R;
+ image_format = Image::FORMAT_RGTC_R;
+ } else if (s3tc_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFBC1;
+ image_format = Image::FORMAT_DXT1;
+ } else if (etc2_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFETC2_EAC_R11;
+ image_format = Image::FORMAT_ETC2_R11;
+ } else {
+ // No supported VRAM compression formats, decompress.
+ basisu_format = basist::transcoder_texture_format::cTFRGBA32;
+ image_format = Image::FORMAT_RGBA8;
+ needs_rg_trim = true;
+ }
+
+ } break;
case BASIS_DECOMPRESS_RG: {
- // RGTC transcoding is currently performed with RG_AS_RA, fail.
- ERR_FAIL_V(image);
+ if (rgtc_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFBC5_RG;
+ image_format = Image::FORMAT_RGTC_RG;
+ } else if (s3tc_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFBC3;
+ image_format = Image::FORMAT_DXT5_RA_AS_RG;
+ } else if (etc2_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFETC2_EAC_RG11;
+ image_format = Image::FORMAT_ETC2_RG11;
+ } else {
+ // No supported VRAM compression formats, decompress.
+ basisu_format = basist::transcoder_texture_format::cTFRGBA32;
+ image_format = Image::FORMAT_RGBA8;
+ needs_ra_rg_swap = true;
+ needs_rg_trim = true;
+ }
+
+ } break;
+ case BASIS_DECOMPRESS_RG_AS_RA: {
+ if (s3tc_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFBC3;
+ image_format = Image::FORMAT_DXT5_RA_AS_RG;
+ } else if (etc2_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFETC2;
+ image_format = Image::FORMAT_ETC2_RA_AS_RG;
+ } else {
+ // No supported VRAM compression formats, decompress.
+ basisu_format = basist::transcoder_texture_format::cTFRGBA32;
+ image_format = Image::FORMAT_RGBA8;
+ needs_ra_rg_swap = true;
+ needs_rg_trim = true;
+ }
+
} break;
case BASIS_DECOMPRESS_RGB: {
if (bptc_supported) {
@@ -267,20 +318,7 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
basisu_format = basist::transcoder_texture_format::cTFRGBA32;
image_format = Image::FORMAT_RGBA8;
}
- } break;
- case BASIS_DECOMPRESS_RG_AS_RA: {
- if (s3tc_supported) {
- basisu_format = basist::transcoder_texture_format::cTFBC3;
- image_format = Image::FORMAT_DXT5_RA_AS_RG;
- } else if (etc2_supported) {
- basisu_format = basist::transcoder_texture_format::cTFETC2;
- image_format = Image::FORMAT_ETC2_RA_AS_RG;
- } else {
- // No supported VRAM compression formats, decompress.
- basisu_format = basist::transcoder_texture_format::cTFRGBA32;
- image_format = Image::FORMAT_RGBA8;
- needs_ra_rg_swap = true;
- }
+
} break;
}
@@ -324,6 +362,15 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
image->convert_ra_rgba8_to_rg();
}
+ if (needs_rg_trim) {
+ // Remove unnecessary color channels from uncompressed textures.
+ if (decompress_format == BASIS_DECOMPRESS_R) {
+ image->convert(Image::FORMAT_R8);
+ } else if (decompress_format == BASIS_DECOMPRESS_RG || decompress_format == BASIS_DECOMPRESS_RG_AS_RA) {
+ image->convert(Image::FORMAT_RG8);
+ }
+ }
+
return image;
}
diff --git a/modules/basis_universal/image_compress_basisu.h b/modules/basis_universal/image_compress_basisu.h
index ac5d62ae73..5e36d448f6 100644
--- a/modules/basis_universal/image_compress_basisu.h
+++ b/modules/basis_universal/image_compress_basisu.h
@@ -38,6 +38,7 @@ enum BasisDecompressFormat {
BASIS_DECOMPRESS_RGB,
BASIS_DECOMPRESS_RGBA,
BASIS_DECOMPRESS_RG_AS_RA,
+ BASIS_DECOMPRESS_R,
};
void basis_universal_init();
diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp
index 8777651545..8c81c0ce4e 100644
--- a/modules/csg/csg_shape.cpp
+++ b/modules/csg/csg_shape.cpp
@@ -513,7 +513,7 @@ Ref<ConcavePolygonShape3D> CSGShape3D::bake_collision_shape() {
}
bool CSGShape3D::_is_debug_collision_shape_visible() {
- return is_inside_tree() && (get_tree()->is_debugging_collisions_hint() || Engine::get_singleton()->is_editor_hint());
+ return !Engine::get_singleton()->is_editor_hint() && is_inside_tree() && get_tree()->is_debugging_collisions_hint();
}
void CSGShape3D::_update_debug_collision_shape() {
@@ -604,11 +604,6 @@ void CSGShape3D::_notification(int p_what) {
// Update this node's parent only if its own visibility has changed, not the visibility of parent nodes
parent_shape->_make_dirty();
}
- if (is_visible()) {
- _update_debug_collision_shape();
- } else {
- _clear_debug_collision_shape();
- }
last_visible = is_visible();
} break;
diff --git a/modules/csg/editor/csg_gizmos.cpp b/modules/csg/editor/csg_gizmos.cpp
index 72676f4a40..3712441e51 100644
--- a/modules/csg/editor/csg_gizmos.cpp
+++ b/modules/csg/editor/csg_gizmos.cpp
@@ -541,7 +541,7 @@ EditorPluginCSG::EditorPluginCSG() {
Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin);
csg_shape_editor = memnew(CSGShapeEditor);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(csg_shape_editor);
+ EditorNode::get_singleton()->get_gui_base()->add_child(csg_shape_editor);
}
#endif // TOOLS_ENABLED
diff --git a/modules/fbx/fbx_document.cpp b/modules/fbx/fbx_document.cpp
index 8d4d0234da..ce097092fb 100644
--- a/modules/fbx/fbx_document.cpp
+++ b/modules/fbx/fbx_document.cpp
@@ -288,14 +288,8 @@ String FBXDocument::_gen_unique_name(HashSet<String> &unique_names, const String
}
String FBXDocument::_sanitize_animation_name(const String &p_name) {
- // Animations disallow the normal node invalid characters as well as "," and "["
- // (See animation/animation_player.cpp::add_animation)
-
- // TODO: Consider adding invalid_characters or a validate_animation_name to animation_player to mirror Node.
String anim_name = p_name.validate_node_name();
- anim_name = anim_name.replace(",", "");
- anim_name = anim_name.replace("[", "");
- return anim_name;
+ return AnimationLibrary::validate_library_name(anim_name);
}
String FBXDocument::_gen_unique_animation_name(Ref<FBXState> p_state, const String &p_name) {
diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp
index 35b69fab8c..32ef429b0d 100644
--- a/modules/gdscript/editor/gdscript_docgen.cpp
+++ b/modules/gdscript/editor/gdscript_docgen.cpp
@@ -84,6 +84,15 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type
return;
}
}
+ if (p_gdtype.builtin_type == Variant::DICTIONARY && p_gdtype.has_container_element_types()) {
+ String key, value;
+ _doctype_from_gdtype(p_gdtype.get_container_element_type_or_variant(0), key, r_enum);
+ _doctype_from_gdtype(p_gdtype.get_container_element_type_or_variant(1), value, r_enum);
+ if (key != "Variant" || value != "Variant") {
+ r_type = "Dictionary[" + key + ", " + value + "]";
+ return;
+ }
+ }
r_type = Variant::get_type_name(p_gdtype.builtin_type);
return;
case GDType::NATIVE:
@@ -155,34 +164,82 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re
return "<Object>";
case Variant::DICTIONARY: {
const Dictionary dict = p_variant;
+ String result;
- if (dict.is_empty()) {
- return "{}";
- }
+ if (dict.is_typed()) {
+ result += "Dictionary[";
+
+ Ref<Script> key_script = dict.get_typed_key_script();
+ if (key_script.is_valid()) {
+ if (key_script->get_global_name() != StringName()) {
+ result += key_script->get_global_name();
+ } else if (!key_script->get_path().get_file().is_empty()) {
+ result += key_script->get_path().get_file();
+ } else {
+ result += dict.get_typed_key_class_name();
+ }
+ } else if (dict.get_typed_key_class_name() != StringName()) {
+ result += dict.get_typed_key_class_name();
+ } else if (dict.is_typed_key()) {
+ result += Variant::get_type_name((Variant::Type)dict.get_typed_key_builtin());
+ } else {
+ result += "Variant";
+ }
+
+ result += ", ";
- if (p_recursion_level > MAX_RECURSION_LEVEL) {
- return "{...}";
+ Ref<Script> value_script = dict.get_typed_value_script();
+ if (value_script.is_valid()) {
+ if (value_script->get_global_name() != StringName()) {
+ result += value_script->get_global_name();
+ } else if (!value_script->get_path().get_file().is_empty()) {
+ result += value_script->get_path().get_file();
+ } else {
+ result += dict.get_typed_value_class_name();
+ }
+ } else if (dict.get_typed_value_class_name() != StringName()) {
+ result += dict.get_typed_value_class_name();
+ } else if (dict.is_typed_value()) {
+ result += Variant::get_type_name((Variant::Type)dict.get_typed_value_builtin());
+ } else {
+ result += "Variant";
+ }
+
+ result += "](";
}
- List<Variant> keys;
- dict.get_key_list(&keys);
- keys.sort();
+ if (dict.is_empty()) {
+ result += "{}";
+ } else if (p_recursion_level > MAX_RECURSION_LEVEL) {
+ result += "{...}";
+ } else {
+ result += "{";
+
+ 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 += ", ";
+ for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
+ if (E->prev()) {
+ result += ", ";
+ }
+ result += _docvalue_from_variant(E->get(), p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[E->get()], p_recursion_level + 1);
}
- data += _docvalue_from_variant(E->get(), p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[E->get()], p_recursion_level + 1);
+
+ result += "}";
+ }
+
+ if (dict.is_typed()) {
+ result += ")";
}
- return "{" + data + "}";
+ return result;
} break;
case Variant::ARRAY: {
const Array array = p_variant;
String result;
- if (array.get_typed_builtin() != Variant::NIL) {
+ if (array.is_typed()) {
result += "Array[";
Ref<Script> script = array.get_typed_script();
@@ -209,16 +266,18 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re
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) {
+ if (array.is_typed()) {
result += ")";
}
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 2c5e6d46e7..7f0d5005cb 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -724,6 +724,18 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
result.set_container_element_type(0, container_type);
}
}
+ if (result.builtin_type == Variant::DICTIONARY) {
+ GDScriptParser::DataType key_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(0)));
+ if (key_type.kind != GDScriptParser::DataType::VARIANT) {
+ key_type.is_constant = false;
+ result.set_container_element_type(0, key_type);
+ }
+ GDScriptParser::DataType value_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(1)));
+ if (value_type.kind != GDScriptParser::DataType::VARIANT) {
+ value_type.is_constant = false;
+ result.set_container_element_type(1, value_type);
+ }
+ }
} else if (class_exists(first)) {
// Native engine classes.
result.kind = GDScriptParser::DataType::NATIVE;
@@ -884,11 +896,16 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
if (!p_type->container_types.is_empty()) {
if (result.builtin_type == Variant::ARRAY) {
if (p_type->container_types.size() != 1) {
- push_error("Arrays require exactly one collection element type.", p_type);
+ push_error(R"(Typed arrays require exactly one collection element type.)", p_type);
+ return bad_type;
+ }
+ } else if (result.builtin_type == Variant::DICTIONARY) {
+ if (p_type->container_types.size() != 2) {
+ push_error(R"(Typed dictionaries require exactly two collection element types.)", p_type);
return bad_type;
}
} else {
- push_error("Only arrays can specify collection element types.", p_type);
+ push_error(R"(Only arrays and dictionaries can specify collection element types.)", p_type);
return bad_type;
}
}
@@ -1926,6 +1943,11 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
if (has_specified_type && specified_type.has_container_element_type(0)) {
update_array_literal_element_type(array, specified_type.get_container_element_type(0));
}
+ } else if (p_assignable->initializer->type == GDScriptParser::Node::DICTIONARY) {
+ GDScriptParser::DictionaryNode *dictionary = static_cast<GDScriptParser::DictionaryNode *>(p_assignable->initializer);
+ if (has_specified_type && specified_type.has_container_element_types()) {
+ update_dictionary_literal_element_type(dictionary, specified_type.get_container_element_type_or_variant(0), specified_type.get_container_element_type_or_variant(1));
+ }
}
if (is_constant && !p_assignable->initializer->is_constant) {
@@ -1987,7 +2009,7 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
} else {
push_error(vformat(R"(Cannot assign a value of type %s to %s "%s" with specified type %s.)", initializer_type.to_string(), p_kind, p_assignable->identifier->name, specified_type.to_string()), p_assignable->initializer);
}
- } else if (specified_type.has_container_element_type(0) && !initializer_type.has_container_element_type(0)) {
+ } else if ((specified_type.has_container_element_type(0) && !initializer_type.has_container_element_type(0)) || (specified_type.has_container_element_type(1) && !initializer_type.has_container_element_type(1))) {
mark_node_unsafe(p_assignable->initializer);
#ifdef DEBUG_ENABLED
} else if (specified_type.builtin_type == Variant::INT && initializer_type.builtin_type == Variant::FLOAT) {
@@ -2229,8 +2251,12 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
} else if (!is_type_compatible(specified_type, variable_type)) {
p_for->use_conversion_assign = true;
}
- if (p_for->list && p_for->list->type == GDScriptParser::Node::ARRAY) {
- update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_for->list), specified_type);
+ if (p_for->list) {
+ if (p_for->list->type == GDScriptParser::Node::ARRAY) {
+ update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_for->list), specified_type);
+ } else if (p_for->list->type == GDScriptParser::Node::DICTIONARY) {
+ update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_for->list), specified_type, GDScriptParser::DataType::get_variant_type());
+ }
}
}
p_for->variable->set_datatype(specified_type);
@@ -2432,6 +2458,9 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
} else {
if (p_return->return_value->type == GDScriptParser::Node::ARRAY && has_expected_type && expected_type.has_container_element_type(0)) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_return->return_value), expected_type.get_container_element_type(0));
+ } else if (p_return->return_value->type == GDScriptParser::Node::DICTIONARY && has_expected_type && expected_type.has_container_element_types()) {
+ update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_return->return_value),
+ expected_type.get_container_element_type_or_variant(0), expected_type.get_container_element_type_or_variant(1));
}
if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) {
update_const_expression_builtin_type(p_return->return_value, expected_type, "return");
@@ -2678,6 +2707,54 @@ void GDScriptAnalyzer::update_array_literal_element_type(GDScriptParser::ArrayNo
p_array->set_datatype(array_type);
}
+// When a dictionary literal is stored (or passed as function argument) to a typed context, we then assume the dictionary is typed.
+// This function determines which type is that (if any).
+void GDScriptAnalyzer::update_dictionary_literal_element_type(GDScriptParser::DictionaryNode *p_dictionary, const GDScriptParser::DataType &p_key_element_type, const GDScriptParser::DataType &p_value_element_type) {
+ GDScriptParser::DataType expected_key_type = p_key_element_type;
+ GDScriptParser::DataType expected_value_type = p_value_element_type;
+ expected_key_type.container_element_types.clear(); // Nested types (like `Dictionary[String, Array[int]]`) are not currently supported.
+ expected_value_type.container_element_types.clear();
+
+ for (int i = 0; i < p_dictionary->elements.size(); i++) {
+ GDScriptParser::ExpressionNode *key_element_node = p_dictionary->elements[i].key;
+ if (key_element_node->is_constant) {
+ update_const_expression_builtin_type(key_element_node, expected_key_type, "include");
+ }
+ const GDScriptParser::DataType &actual_key_type = key_element_node->get_datatype();
+ if (actual_key_type.has_no_type() || actual_key_type.is_variant() || !actual_key_type.is_hard_type()) {
+ mark_node_unsafe(key_element_node);
+ } else if (!is_type_compatible(expected_key_type, actual_key_type, true, p_dictionary)) {
+ if (is_type_compatible(actual_key_type, expected_key_type)) {
+ mark_node_unsafe(key_element_node);
+ } else {
+ push_error(vformat(R"(Cannot have a key of type "%s" in a dictionary of type "Dictionary[%s, %s]".)", actual_key_type.to_string(), expected_key_type.to_string(), expected_value_type.to_string()), key_element_node);
+ return;
+ }
+ }
+
+ GDScriptParser::ExpressionNode *value_element_node = p_dictionary->elements[i].value;
+ if (value_element_node->is_constant) {
+ update_const_expression_builtin_type(value_element_node, expected_value_type, "include");
+ }
+ const GDScriptParser::DataType &actual_value_type = value_element_node->get_datatype();
+ if (actual_value_type.has_no_type() || actual_value_type.is_variant() || !actual_value_type.is_hard_type()) {
+ mark_node_unsafe(value_element_node);
+ } else if (!is_type_compatible(expected_value_type, actual_value_type, true, p_dictionary)) {
+ if (is_type_compatible(actual_value_type, expected_value_type)) {
+ mark_node_unsafe(value_element_node);
+ } else {
+ push_error(vformat(R"(Cannot have a value of type "%s" in a dictionary of type "Dictionary[%s, %s]".)", actual_value_type.to_string(), expected_key_type.to_string(), expected_value_type.to_string()), value_element_node);
+ return;
+ }
+ }
+ }
+
+ GDScriptParser::DataType dictionary_type = p_dictionary->get_datatype();
+ dictionary_type.set_container_element_type(0, expected_key_type);
+ dictionary_type.set_container_element_type(1, expected_value_type);
+ p_dictionary->set_datatype(dictionary_type);
+}
+
void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assignment) {
reduce_expression(p_assignment->assigned_value);
@@ -2770,9 +2847,12 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
}
}
- // Check if assigned value is an array literal, so we can make it a typed array too if appropriate.
+ // Check if assigned value is an array/dictionary literal, so we can make it a typed container too if appropriate.
if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.is_hard_type() && assignee_type.has_container_element_type(0)) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value), assignee_type.get_container_element_type(0));
+ } else if (p_assignment->assigned_value->type == GDScriptParser::Node::DICTIONARY && assignee_type.is_hard_type() && assignee_type.has_container_element_types()) {
+ update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_assignment->assigned_value),
+ assignee_type.get_container_element_type_or_variant(0), assignee_type.get_container_element_type_or_variant(1));
}
if (p_assignment->operation == GDScriptParser::AssignmentNode::OP_NONE && assignee_type.is_hard_type() && p_assignment->assigned_value->is_constant) {
@@ -2850,8 +2930,8 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
// weak non-variant assignee and incompatible result
downgrades_assignee = true;
}
- } else if (assignee_type.has_container_element_type(0) && !op_type.has_container_element_type(0)) {
- // typed array assignee and untyped array result
+ } else if ((assignee_type.has_container_element_type(0) && !op_type.has_container_element_type(0)) || (assignee_type.has_container_element_type(1) && !op_type.has_container_element_type(1))) {
+ // Typed assignee and untyped result.
mark_node_unsafe(p_assignment);
}
}
@@ -3049,10 +3129,13 @@ const char *check_for_renamed_identifier(String identifier, GDScriptParser::Node
void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_await, bool p_is_root) {
bool all_is_constant = true;
HashMap<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing.
+ HashMap<int, GDScriptParser::DictionaryNode *> dictionaries; // Same, but for dictionaries.
for (int i = 0; i < p_call->arguments.size(); i++) {
reduce_expression(p_call->arguments[i]);
if (p_call->arguments[i]->type == GDScriptParser::Node::ARRAY) {
arrays[i] = static_cast<GDScriptParser::ArrayNode *>(p_call->arguments[i]);
+ } else if (p_call->arguments[i]->type == GDScriptParser::Node::DICTIONARY) {
+ dictionaries[i] = static_cast<GDScriptParser::DictionaryNode *>(p_call->arguments[i]);
}
all_is_constant = all_is_constant && p_call->arguments[i]->is_constant;
}
@@ -3457,6 +3540,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
update_array_literal_element_type(E.value, par_types.get(index).get_container_element_type(0));
}
}
+ for (const KeyValue<int, GDScriptParser::DictionaryNode *> &E : dictionaries) {
+ int index = E.key;
+ if (index < par_types.size() && par_types.get(index).is_hard_type() && par_types.get(index).has_container_element_types()) {
+ GDScriptParser::DataType key = par_types.get(index).get_container_element_type_or_variant(0);
+ GDScriptParser::DataType value = par_types.get(index).get_container_element_type_or_variant(1);
+ update_dictionary_literal_element_type(E.value, key, value);
+ }
+ }
validate_call_arg(par_types, default_arg_count, method_flags.has_flag(METHOD_FLAG_VARARG), p_call);
if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) {
@@ -3601,6 +3692,11 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_cast->operand), cast_type.get_container_element_type(0));
}
+ if (p_cast->operand->type == GDScriptParser::Node::DICTIONARY && cast_type.has_container_element_types()) {
+ update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_cast->operand),
+ cast_type.get_container_element_type_or_variant(0), cast_type.get_container_element_type_or_variant(1));
+ }
+
if (!cast_type.is_variant()) {
GDScriptParser::DataType op_type = p_cast->operand->get_datatype();
if (op_type.is_variant() || !op_type.is_hard_type()) {
@@ -4625,10 +4721,23 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
reduce_identifier_from_base(p_subscript->attribute, &base_type);
GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype();
if (attr_type.is_set()) {
- valid = !attr_type.is_pseudo_type || p_can_be_pseudo_type;
- result_type = attr_type;
- p_subscript->is_constant = p_subscript->attribute->is_constant;
- p_subscript->reduced_value = p_subscript->attribute->reduced_value;
+ if (base_type.builtin_type == Variant::DICTIONARY && base_type.has_container_element_types()) {
+ Variant::Type key_type = base_type.get_container_element_type_or_variant(0).builtin_type;
+ valid = key_type == Variant::NIL || key_type == Variant::STRING || key_type == Variant::STRING_NAME;
+ if (base_type.has_container_element_type(1)) {
+ result_type = base_type.get_container_element_type(1);
+ result_type.type_source = base_type.type_source;
+ } else {
+ result_type.builtin_type = Variant::NIL;
+ result_type.kind = GDScriptParser::DataType::VARIANT;
+ result_type.type_source = GDScriptParser::DataType::UNDETECTED;
+ }
+ } else {
+ valid = !attr_type.is_pseudo_type || p_can_be_pseudo_type;
+ result_type = attr_type;
+ p_subscript->is_constant = p_subscript->attribute->is_constant;
+ p_subscript->reduced_value = p_subscript->attribute->reduced_value;
+ }
} else if (!base_type.is_meta_type || !base_type.is_constant) {
valid = base_type.kind != GDScriptParser::DataType::BUILTIN;
#ifdef DEBUG_ENABLED
@@ -4735,8 +4844,40 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
case Variant::SIGNAL:
case Variant::STRING_NAME:
break;
- // Here for completeness.
+ // Support depends on if the dictionary has a typed key, otherwise anything is valid.
case Variant::DICTIONARY:
+ if (base_type.has_container_element_type(0)) {
+ GDScriptParser::DataType key_type = base_type.get_container_element_type(0);
+ switch (index_type.builtin_type) {
+ // Null value will be treated as an empty object, allow.
+ case Variant::NIL:
+ error = key_type.builtin_type != Variant::OBJECT;
+ break;
+ // Objects are parsed for validity in a similar manner to container types.
+ case Variant::OBJECT:
+ if (key_type.builtin_type == Variant::OBJECT) {
+ error = !key_type.can_reference(index_type);
+ } else {
+ error = key_type.builtin_type != Variant::NIL;
+ }
+ break;
+ // String and StringName interchangeable in this context.
+ case Variant::STRING:
+ case Variant::STRING_NAME:
+ error = key_type.builtin_type != Variant::STRING_NAME && key_type.builtin_type != Variant::STRING;
+ break;
+ // Ints are valid indices for floats, but not the other way around.
+ case Variant::INT:
+ error = key_type.builtin_type != Variant::INT && key_type.builtin_type != Variant::FLOAT;
+ break;
+ // All other cases require the types to match exactly.
+ default:
+ error = key_type.builtin_type != index_type.builtin_type;
+ break;
+ }
+ }
+ break;
+ // Here for completeness.
case Variant::VARIANT_MAX:
break;
}
@@ -4825,7 +4966,6 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
case Variant::PROJECTION:
case Variant::PLANE:
case Variant::COLOR:
- case Variant::DICTIONARY:
case Variant::OBJECT:
result_type.kind = GDScriptParser::DataType::VARIANT;
result_type.type_source = GDScriptParser::DataType::UNDETECTED;
@@ -4840,6 +4980,16 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
result_type.type_source = GDScriptParser::DataType::UNDETECTED;
}
break;
+ // Can have two element types, but we only care about the value.
+ case Variant::DICTIONARY:
+ if (base_type.has_container_element_type(1)) {
+ result_type = base_type.get_container_element_type(1);
+ result_type.type_source = base_type.type_source;
+ } else {
+ result_type.kind = GDScriptParser::DataType::VARIANT;
+ result_type.type_source = GDScriptParser::DataType::UNDETECTED;
+ }
+ break;
// Here for completeness.
case Variant::VARIANT_MAX:
break;
@@ -5019,7 +5169,9 @@ Variant GDScriptAnalyzer::make_array_reduced_value(GDScriptParser::ArrayNode *p_
}
Variant GDScriptAnalyzer::make_dictionary_reduced_value(GDScriptParser::DictionaryNode *p_dictionary, bool &is_reduced) {
- Dictionary dictionary;
+ Dictionary dictionary = p_dictionary->get_datatype().has_container_element_types()
+ ? make_dictionary_from_element_datatype(p_dictionary->get_datatype().get_container_element_type_or_variant(0), p_dictionary->get_datatype().get_container_element_type_or_variant(1))
+ : Dictionary();
for (int i = 0; i < p_dictionary->elements.size(); i++) {
const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i];
@@ -5106,6 +5258,49 @@ Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::D
return array;
}
+Dictionary GDScriptAnalyzer::make_dictionary_from_element_datatype(const GDScriptParser::DataType &p_key_element_datatype, const GDScriptParser::DataType &p_value_element_datatype, const GDScriptParser::Node *p_source_node) {
+ Dictionary dictionary;
+ StringName key_name;
+ Variant key_script;
+ StringName value_name;
+ Variant value_script;
+
+ if (p_key_element_datatype.builtin_type == Variant::OBJECT) {
+ Ref<Script> script_type = p_key_element_datatype.script_type;
+ if (p_key_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) {
+ Error err = OK;
+ Ref<GDScript> scr = get_depended_shallow_script(p_key_element_datatype.script_path, err);
+ if (err) {
+ push_error(vformat(R"(Error while getting cache for script "%s".)", p_key_element_datatype.script_path), p_source_node);
+ return dictionary;
+ }
+ script_type.reference_ptr(scr->find_class(p_key_element_datatype.class_type->fqcn));
+ }
+
+ key_name = p_key_element_datatype.native_type;
+ key_script = script_type;
+ }
+
+ if (p_value_element_datatype.builtin_type == Variant::OBJECT) {
+ Ref<Script> script_type = p_value_element_datatype.script_type;
+ if (p_value_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) {
+ Error err = OK;
+ Ref<GDScript> scr = get_depended_shallow_script(p_value_element_datatype.script_path, err);
+ if (err) {
+ push_error(vformat(R"(Error while getting cache for script "%s".)", p_value_element_datatype.script_path), p_source_node);
+ return dictionary;
+ }
+ script_type.reference_ptr(scr->find_class(p_value_element_datatype.class_type->fqcn));
+ }
+
+ value_name = p_value_element_datatype.native_type;
+ value_script = script_type;
+ }
+
+ dictionary.set_typed(p_key_element_datatype.builtin_type, key_name, key_script, p_value_element_datatype.builtin_type, value_name, value_script);
+ return dictionary;
+}
+
Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNode *p_variable) {
Variant result = Variant();
@@ -5121,6 +5316,10 @@ Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNo
if (datatype.kind == GDScriptParser::DataType::BUILTIN && datatype.builtin_type != Variant::OBJECT) {
if (datatype.builtin_type == Variant::ARRAY && datatype.has_container_element_type(0)) {
result = make_array_from_element_datatype(datatype.get_container_element_type(0));
+ } else if (datatype.builtin_type == Variant::DICTIONARY && datatype.has_container_element_types()) {
+ GDScriptParser::DataType key = datatype.get_container_element_type_or_variant(0);
+ GDScriptParser::DataType value = datatype.get_container_element_type_or_variant(1);
+ result = make_dictionary_from_element_datatype(key, value);
} else {
VariantInternal::initialize(&result, datatype.builtin_type);
}
@@ -5149,6 +5348,22 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va
} else if (array.get_typed_builtin() != Variant::NIL) {
result.set_container_element_type(0, type_from_metatype(make_builtin_meta_type((Variant::Type)array.get_typed_builtin())));
}
+ } else if (p_value.get_type() == Variant::DICTIONARY) {
+ const Dictionary &dict = p_value;
+ if (dict.get_typed_key_script()) {
+ result.set_container_element_type(0, type_from_metatype(make_script_meta_type(dict.get_typed_key_script())));
+ } else if (dict.get_typed_key_class_name()) {
+ result.set_container_element_type(0, type_from_metatype(make_native_meta_type(dict.get_typed_key_class_name())));
+ } else if (dict.get_typed_key_builtin() != Variant::NIL) {
+ result.set_container_element_type(0, type_from_metatype(make_builtin_meta_type((Variant::Type)dict.get_typed_key_builtin())));
+ }
+ if (dict.get_typed_value_script()) {
+ result.set_container_element_type(1, type_from_metatype(make_script_meta_type(dict.get_typed_value_script())));
+ } else if (dict.get_typed_value_class_name()) {
+ result.set_container_element_type(1, type_from_metatype(make_native_meta_type(dict.get_typed_value_class_name())));
+ } else if (dict.get_typed_value_builtin() != Variant::NIL) {
+ result.set_container_element_type(1, type_from_metatype(make_builtin_meta_type((Variant::Type)dict.get_typed_value_builtin())));
+ }
} else if (p_value.get_type() == Variant::OBJECT) {
// Object is treated as a native type, not a builtin type.
result.kind = GDScriptParser::DataType::NATIVE;
@@ -5281,6 +5496,60 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo
}
elem_type.is_constant = false;
result.set_container_element_type(0, elem_type);
+ } else if (p_property.type == Variant::DICTIONARY && p_property.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
+ // Check element type.
+ StringName key_elem_type_name = p_property.hint_string.get_slice(";", 0);
+ GDScriptParser::DataType key_elem_type;
+ key_elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+
+ Variant::Type key_elem_builtin_type = GDScriptParser::get_builtin_type(key_elem_type_name);
+ if (key_elem_builtin_type < Variant::VARIANT_MAX) {
+ // Builtin type.
+ key_elem_type.kind = GDScriptParser::DataType::BUILTIN;
+ key_elem_type.builtin_type = key_elem_builtin_type;
+ } else if (class_exists(key_elem_type_name)) {
+ key_elem_type.kind = GDScriptParser::DataType::NATIVE;
+ key_elem_type.builtin_type = Variant::OBJECT;
+ key_elem_type.native_type = key_elem_type_name;
+ } else if (ScriptServer::is_global_class(key_elem_type_name)) {
+ // Just load this as it shouldn't be a GDScript.
+ Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(key_elem_type_name));
+ key_elem_type.kind = GDScriptParser::DataType::SCRIPT;
+ key_elem_type.builtin_type = Variant::OBJECT;
+ key_elem_type.native_type = script->get_instance_base_type();
+ key_elem_type.script_type = script;
+ } else {
+ ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed dictionary.");
+ }
+ key_elem_type.is_constant = false;
+
+ StringName value_elem_type_name = p_property.hint_string.get_slice(";", 1);
+ GDScriptParser::DataType value_elem_type;
+ value_elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+
+ Variant::Type value_elem_builtin_type = GDScriptParser::get_builtin_type(value_elem_type_name);
+ if (value_elem_builtin_type < Variant::VARIANT_MAX) {
+ // Builtin type.
+ value_elem_type.kind = GDScriptParser::DataType::BUILTIN;
+ value_elem_type.builtin_type = value_elem_builtin_type;
+ } else if (class_exists(value_elem_type_name)) {
+ value_elem_type.kind = GDScriptParser::DataType::NATIVE;
+ value_elem_type.builtin_type = Variant::OBJECT;
+ value_elem_type.native_type = value_elem_type_name;
+ } else if (ScriptServer::is_global_class(value_elem_type_name)) {
+ // Just load this as it shouldn't be a GDScript.
+ Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(value_elem_type_name));
+ value_elem_type.kind = GDScriptParser::DataType::SCRIPT;
+ value_elem_type.builtin_type = Variant::OBJECT;
+ value_elem_type.native_type = script->get_instance_base_type();
+ value_elem_type.script_type = script;
+ } else {
+ ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed dictionary.");
+ }
+ value_elem_type.is_constant = false;
+
+ result.set_container_element_type(0, key_elem_type);
+ result.set_container_element_type(1, value_elem_type);
} else if (p_property.type == Variant::INT) {
// Check if it's enum.
if ((p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && p_property.class_name != StringName()) {
@@ -5701,6 +5970,15 @@ bool GDScriptAnalyzer::check_type_compatibility(const GDScriptParser::DataType &
valid = p_target.get_container_element_type(0) == p_source.get_container_element_type(0);
}
}
+ if (valid && p_target.builtin_type == Variant::DICTIONARY && p_source.builtin_type == Variant::DICTIONARY) {
+ // Check the element types.
+ if (p_target.has_container_element_type(0) && p_source.has_container_element_type(0)) {
+ valid = p_target.get_container_element_type(0) == p_source.get_container_element_type(0);
+ }
+ if (valid && p_target.has_container_element_type(1) && p_source.has_container_element_type(1)) {
+ valid = p_target.get_container_element_type(1) == p_source.get_container_element_type(1);
+ }
+ }
return valid;
}
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index 25e5aa9a2c..3b781409a4 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -125,6 +125,7 @@ class GDScriptAnalyzer {
// Helpers.
Array make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node = nullptr);
+ Dictionary make_dictionary_from_element_datatype(const GDScriptParser::DataType &p_key_element_datatype, const GDScriptParser::DataType &p_value_element_datatype, const GDScriptParser::Node *p_source_node = nullptr);
GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source);
static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type);
GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false, bool p_is_readonly = false) const;
@@ -137,6 +138,7 @@ class GDScriptAnalyzer {
GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source);
void update_const_expression_builtin_type(GDScriptParser::ExpressionNode *p_expression, const GDScriptParser::DataType &p_type, const char *p_usage, bool p_is_cast = false);
void update_array_literal_element_type(GDScriptParser::ArrayNode *p_array, const GDScriptParser::DataType &p_element_type);
+ void update_dictionary_literal_element_type(GDScriptParser::DictionaryNode *p_dictionary, const GDScriptParser::DataType &p_key_element_type, const GDScriptParser::DataType &p_value_element_type);
bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr);
void push_error(const String &p_message, const GDScriptParser::Node *p_origin = nullptr);
void mark_node_unsafe(const GDScriptParser::Node *p_node);
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index 4cda3d3037..b77c641eb5 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -634,6 +634,18 @@ void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const A
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
+ } else if (p_type.builtin_type == Variant::DICTIONARY && p_type.has_container_element_types()) {
+ const GDScriptDataType &key_element_type = p_type.get_container_element_type_or_variant(0);
+ const GDScriptDataType &value_element_type = p_type.get_container_element_type_or_variant(1);
+ append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_DICTIONARY);
+ append(p_target);
+ append(p_source);
+ append(get_constant_pos(key_element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(get_constant_pos(value_element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(key_element_type.builtin_type);
+ append(key_element_type.native_type);
+ append(value_element_type.builtin_type);
+ append(value_element_type.native_type);
} else {
append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_BUILTIN);
append(p_target);
@@ -889,6 +901,18 @@ void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_ta
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
+ } else if (p_target.type.builtin_type == Variant::DICTIONARY && p_target.type.has_container_element_types()) {
+ const GDScriptDataType &key_type = p_target.type.get_container_element_type_or_variant(0);
+ const GDScriptDataType &value_type = p_target.type.get_container_element_type_or_variant(1);
+ append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_DICTIONARY);
+ append(p_target);
+ append(p_source);
+ append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(key_type.builtin_type);
+ append(key_type.native_type);
+ append(value_type.builtin_type);
+ append(value_type.native_type);
} else {
append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN);
append(p_target);
@@ -935,6 +959,18 @@ void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Addr
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
+ } else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type == Variant::DICTIONARY && p_target.type.has_container_element_types()) {
+ const GDScriptDataType &key_type = p_target.type.get_container_element_type_or_variant(0);
+ const GDScriptDataType &value_type = p_target.type.get_container_element_type_or_variant(1);
+ append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_DICTIONARY);
+ append(p_target);
+ append(p_source);
+ append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(key_type.builtin_type);
+ append(key_type.native_type);
+ append(value_type.builtin_type);
+ append(value_type.native_type);
} else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) {
// Need conversion.
append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN);
@@ -1434,6 +1470,23 @@ void GDScriptByteCodeGenerator::write_construct_dictionary(const Address &p_targ
ct.cleanup();
}
+void GDScriptByteCodeGenerator::write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) {
+ append_opcode_and_argcount(GDScriptFunction::OPCODE_CONSTRUCT_TYPED_DICTIONARY, 3 + p_arguments.size());
+ for (int i = 0; i < p_arguments.size(); i++) {
+ append(p_arguments[i]);
+ }
+ CallTarget ct = get_call_target(p_target);
+ append(ct.target);
+ append(get_constant_pos(p_key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(get_constant_pos(p_value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(p_arguments.size() / 2); // This is number of key-value pairs, so only half of actual arguments.
+ append(p_key_type.builtin_type);
+ append(p_key_type.native_type);
+ append(p_value_type.builtin_type);
+ append(p_value_type.native_type);
+ ct.cleanup();
+}
+
void GDScriptByteCodeGenerator::write_await(const Address &p_target, const Address &p_operand) {
append_opcode(GDScriptFunction::OPCODE_AWAIT);
append(p_operand);
@@ -1711,6 +1764,19 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) {
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
+ } else if (function->return_type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type == Variant::DICTIONARY &&
+ function->return_type.has_container_element_types()) {
+ // Typed dictionary.
+ const GDScriptDataType &key_type = function->return_type.get_container_element_type_or_variant(0);
+ const GDScriptDataType &value_type = function->return_type.get_container_element_type_or_variant(1);
+ append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_DICTIONARY);
+ append(p_return_value);
+ append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(key_type.builtin_type);
+ append(key_type.native_type);
+ append(value_type.builtin_type);
+ append(value_type.native_type);
} else if (function->return_type.kind == GDScriptDataType::BUILTIN && p_return_value.type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type != p_return_value.type.builtin_type) {
// Add conversion.
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN);
@@ -1735,6 +1801,17 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) {
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
+ } else if (function->return_type.builtin_type == Variant::DICTIONARY && function->return_type.has_container_element_types()) {
+ const GDScriptDataType &key_type = function->return_type.get_container_element_type_or_variant(0);
+ const GDScriptDataType &value_type = function->return_type.get_container_element_type_or_variant(1);
+ append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_DICTIONARY);
+ append(p_return_value);
+ append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
+ append(key_type.builtin_type);
+ append(key_type.native_type);
+ append(value_type.builtin_type);
+ append(value_type.native_type);
} else {
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN);
append(p_return_value);
@@ -1803,6 +1880,13 @@ void GDScriptByteCodeGenerator::clear_address(const Address &p_address) {
case Variant::BOOL:
write_assign_false(p_address);
break;
+ case Variant::DICTIONARY:
+ if (p_address.type.has_container_element_types()) {
+ write_construct_typed_dictionary(p_address, p_address.type.get_container_element_type_or_variant(0), p_address.type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>());
+ } else {
+ write_construct(p_address, p_address.type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
+ }
+ break;
case Variant::ARRAY:
if (p_address.type.has_container_element_type(0)) {
write_construct_typed_array(p_address, p_address.type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h
index 34f56a2f5c..6303db71fd 100644
--- a/modules/gdscript/gdscript_byte_codegen.h
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -529,6 +529,7 @@ public:
virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) override;
virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) override;
virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) override;
+ virtual void write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) override;
virtual void write_await(const Address &p_target, const Address &p_operand) override;
virtual void write_if(const Address &p_condition) override;
virtual void write_else() override;
diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h
index c1c0b61395..f3c4acf1c3 100644
--- a/modules/gdscript/gdscript_codegen.h
+++ b/modules/gdscript/gdscript_codegen.h
@@ -142,6 +142,7 @@ public:
virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) = 0;
virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) = 0;
virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) = 0;
+ virtual void write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) = 0;
virtual void write_await(const Address &p_target, const Address &p_operand) = 0;
virtual void write_if(const Address &p_condition) = 0;
virtual void write_else() = 0;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index d8b44a558f..eebf282633 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -532,10 +532,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
Vector<GDScriptCodeGenerator::Address> elements;
// Create the result temporary first since it's the last to be killed.
- GDScriptDataType dict_type;
- dict_type.has_type = true;
- dict_type.kind = GDScriptDataType::BUILTIN;
- dict_type.builtin_type = Variant::DICTIONARY;
+ GDScriptDataType dict_type = _gdtype_from_datatype(dn->get_datatype(), codegen.script);
GDScriptCodeGenerator::Address result = codegen.add_temporary(dict_type);
for (int i = 0; i < dn->elements.size(); i++) {
@@ -566,7 +563,11 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
elements.push_back(element);
}
- gen->write_construct_dictionary(result, elements);
+ if (dict_type.has_container_element_types()) {
+ gen->write_construct_typed_dictionary(result, dict_type.get_container_element_type_or_variant(0), dict_type.get_container_element_type_or_variant(1), elements);
+ } else {
+ gen->write_construct_dictionary(result, elements);
+ }
for (int i = 0; i < elements.size(); i++) {
if (elements[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) {
@@ -2325,8 +2326,11 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
- if (field_type.has_container_element_type(0)) {
+ if (field_type.builtin_type == Variant::ARRAY && field_type.has_container_element_type(0)) {
codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
+ } else if (field_type.builtin_type == Variant::DICTIONARY && field_type.has_container_element_types()) {
+ codegen.generator->write_construct_typed_dictionary(dst_address, field_type.get_container_element_type_or_variant(0),
+ field_type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>());
} else if (field_type.kind == GDScriptDataType::BUILTIN) {
codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
}
@@ -2515,11 +2519,17 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
if (field_type.has_type) {
codegen.generator->write_newline(field->start_line);
- if (field_type.has_container_element_type(0)) {
+ if (field_type.builtin_type == Variant::ARRAY && field_type.has_container_element_type(0)) {
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
codegen.generator->write_construct_typed_array(temp, field_type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index);
codegen.generator->pop_temporary();
+ } else if (field_type.builtin_type == Variant::DICTIONARY && field_type.has_container_element_types()) {
+ GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
+ codegen.generator->write_construct_typed_dictionary(temp, field_type.get_container_element_type_or_variant(0),
+ field_type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>());
+ codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index);
+ codegen.generator->pop_temporary();
} else if (field_type.kind == GDScriptDataType::BUILTIN) {
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
codegen.generator->write_construct(temp, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp
index 0331045078..bc063693a3 100644
--- a/modules/gdscript/gdscript_disassembler.cpp
+++ b/modules/gdscript/gdscript_disassembler.cpp
@@ -176,6 +176,47 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 6;
} break;
+ case OPCODE_TYPE_TEST_DICTIONARY: {
+ text += "type test ";
+ text += DADDR(1);
+ text += " = ";
+ text += DADDR(2);
+ text += " is Dictionary[";
+
+ Ref<Script> key_script_type = get_constant(_code_ptr[ip + 3] & ADDR_MASK);
+ Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5];
+ StringName key_native_type = get_global_name(_code_ptr[ip + 6]);
+
+ if (key_script_type.is_valid() && key_script_type->is_valid()) {
+ text += "script(";
+ text += GDScript::debug_get_script_name(key_script_type);
+ text += ")";
+ } else if (key_native_type != StringName()) {
+ text += key_native_type;
+ } else {
+ text += Variant::get_type_name(key_builtin_type);
+ }
+
+ text += ", ";
+
+ Ref<Script> value_script_type = get_constant(_code_ptr[ip + 4] & ADDR_MASK);
+ Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7];
+ StringName value_native_type = get_global_name(_code_ptr[ip + 8]);
+
+ if (value_script_type.is_valid() && value_script_type->is_valid()) {
+ text += "script(";
+ text += GDScript::debug_get_script_name(value_script_type);
+ text += ")";
+ } else if (value_native_type != StringName()) {
+ text += value_native_type;
+ } else {
+ text += Variant::get_type_name(value_builtin_type);
+ }
+
+ text += "]";
+
+ incr += 9;
+ } break;
case OPCODE_TYPE_TEST_NATIVE: {
text += "type test ";
text += DADDR(1);
@@ -399,6 +440,14 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 6;
} break;
+ case OPCODE_ASSIGN_TYPED_DICTIONARY: {
+ text += "assign typed dictionary ";
+ text += DADDR(1);
+ text += " = ";
+ text += DADDR(2);
+
+ incr += 9;
+ } break;
case OPCODE_ASSIGN_TYPED_NATIVE: {
text += "assign typed native (";
text += DADDR(3);
@@ -564,6 +613,58 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 3 + argc * 2;
} break;
+ case OPCODE_CONSTRUCT_TYPED_DICTIONARY: {
+ int instr_var_args = _code_ptr[++ip];
+ int argc = _code_ptr[ip + 1 + instr_var_args];
+
+ Ref<Script> key_script_type = get_constant(_code_ptr[ip + argc * 2 + 2] & ADDR_MASK);
+ Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + argc * 2 + 5];
+ StringName key_native_type = get_global_name(_code_ptr[ip + argc * 2 + 6]);
+
+ String key_type_name;
+ if (key_script_type.is_valid() && key_script_type->is_valid()) {
+ key_type_name = "script(" + GDScript::debug_get_script_name(key_script_type) + ")";
+ } else if (key_native_type != StringName()) {
+ key_type_name = key_native_type;
+ } else {
+ key_type_name = Variant::get_type_name(key_builtin_type);
+ }
+
+ Ref<Script> value_script_type = get_constant(_code_ptr[ip + argc * 2 + 3] & ADDR_MASK);
+ Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + argc * 2 + 7];
+ StringName value_native_type = get_global_name(_code_ptr[ip + argc * 2 + 8]);
+
+ String value_type_name;
+ if (value_script_type.is_valid() && value_script_type->is_valid()) {
+ value_type_name = "script(" + GDScript::debug_get_script_name(value_script_type) + ")";
+ } else if (value_native_type != StringName()) {
+ value_type_name = value_native_type;
+ } else {
+ value_type_name = Variant::get_type_name(value_builtin_type);
+ }
+
+ text += "make_typed_dict (";
+ text += key_type_name;
+ text += ", ";
+ text += value_type_name;
+ text += ") ";
+
+ text += DADDR(1 + argc * 2);
+ text += " = {";
+
+ for (int i = 0; i < argc; i++) {
+ if (i > 0) {
+ text += ", ";
+ }
+ text += DADDR(1 + i * 2 + 0);
+ text += ": ";
+ text += DADDR(1 + i * 2 + 1);
+ }
+
+ text += "}";
+
+ incr += 9 + argc * 2;
+ } break;
case OPCODE_CALL:
case OPCODE_CALL_RETURN:
case OPCODE_CALL_ASYNC: {
@@ -978,6 +1079,12 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 5;
} break;
+ case OPCODE_RETURN_TYPED_DICTIONARY: {
+ text += "return typed dictionary ";
+ text += DADDR(1);
+
+ incr += 8;
+ } break;
case OPCODE_RETURN_TYPED_NATIVE: {
text += "return typed native (";
text += DADDR(2);
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index db7bef2f07..cf1cd55355 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -697,6 +697,10 @@ static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg, co
return _trim_parent_class(class_name, p_base_class);
} else if (p_info.type == Variant::ARRAY && p_info.hint == PROPERTY_HINT_ARRAY_TYPE && !p_info.hint_string.is_empty()) {
return "Array[" + _trim_parent_class(p_info.hint_string, p_base_class) + "]";
+ } else if (p_info.type == Variant::DICTIONARY && p_info.hint == PROPERTY_HINT_DICTIONARY_TYPE && !p_info.hint_string.is_empty()) {
+ const String key = p_info.hint_string.get_slice(";", 0);
+ const String value = p_info.hint_string.get_slice(";", 1);
+ return "Dictionary[" + _trim_parent_class(key, p_base_class) + ", " + _trim_parent_class(value, p_base_class) + "]";
} else if (p_info.type == Variant::NIL) {
if (p_is_arg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) {
return "Variant";
@@ -3482,10 +3486,10 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
opt = opt.substr(1);
}
- // The path needs quotes if at least one of its components (excluding `/` separations)
+ // The path needs quotes if at least one of its components (excluding `%` prefix and `/` separations)
// is not a valid identifier.
bool path_needs_quote = false;
- for (const String &part : opt.split("/")) {
+ for (const String &part : opt.trim_prefix("%").split("/")) {
if (!part.is_valid_ascii_identifier()) {
path_needs_quote = true;
break;
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index ac4bab6d84..6433072b55 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -93,6 +93,41 @@ public:
} else {
valid = false;
}
+ } else if (valid && builtin_type == Variant::DICTIONARY && has_container_element_types()) {
+ Dictionary dictionary = p_variant;
+ if (dictionary.is_typed()) {
+ if (dictionary.is_typed_key()) {
+ GDScriptDataType key = get_container_element_type_or_variant(0);
+ Variant::Type key_builtin_type = (Variant::Type)dictionary.get_typed_key_builtin();
+ StringName key_native_type = dictionary.get_typed_key_class_name();
+ Ref<Script> key_script_type_ref = dictionary.get_typed_key_script();
+
+ if (key_script_type_ref.is_valid()) {
+ valid = (key.kind == SCRIPT || key.kind == GDSCRIPT) && key.script_type == key_script_type_ref.ptr();
+ } else if (key_native_type != StringName()) {
+ valid = key.kind == NATIVE && key.native_type == key_native_type;
+ } else {
+ valid = key.kind == BUILTIN && key.builtin_type == key_builtin_type;
+ }
+ }
+
+ if (valid && dictionary.is_typed_value()) {
+ GDScriptDataType value = get_container_element_type_or_variant(1);
+ Variant::Type value_builtin_type = (Variant::Type)dictionary.get_typed_value_builtin();
+ StringName value_native_type = dictionary.get_typed_value_class_name();
+ Ref<Script> value_script_type_ref = dictionary.get_typed_value_script();
+
+ if (value_script_type_ref.is_valid()) {
+ valid = (value.kind == SCRIPT || value.kind == GDSCRIPT) && value.script_type == value_script_type_ref.ptr();
+ } else if (value_native_type != StringName()) {
+ valid = value.kind == NATIVE && value.native_type == value_native_type;
+ } else {
+ valid = value.kind == BUILTIN && value.builtin_type == value_builtin_type;
+ }
+ }
+ } else {
+ valid = false;
+ }
} else if (!valid && p_allow_implicit_conversion) {
valid = Variant::can_convert_strict(var_type, builtin_type);
}
@@ -156,6 +191,10 @@ public:
}
return true;
case Variant::DICTIONARY:
+ if (has_container_element_types()) {
+ return get_container_element_type_or_variant(0).can_contain_object() || get_container_element_type_or_variant(1).can_contain_object();
+ }
+ return true;
case Variant::NIL:
case Variant::OBJECT:
return true;
@@ -220,6 +259,7 @@ public:
OPCODE_OPERATOR_VALIDATED,
OPCODE_TYPE_TEST_BUILTIN,
OPCODE_TYPE_TEST_ARRAY,
+ OPCODE_TYPE_TEST_DICTIONARY,
OPCODE_TYPE_TEST_NATIVE,
OPCODE_TYPE_TEST_SCRIPT,
OPCODE_SET_KEYED,
@@ -242,6 +282,7 @@ public:
OPCODE_ASSIGN_FALSE,
OPCODE_ASSIGN_TYPED_BUILTIN,
OPCODE_ASSIGN_TYPED_ARRAY,
+ OPCODE_ASSIGN_TYPED_DICTIONARY,
OPCODE_ASSIGN_TYPED_NATIVE,
OPCODE_ASSIGN_TYPED_SCRIPT,
OPCODE_CAST_TO_BUILTIN,
@@ -252,6 +293,7 @@ public:
OPCODE_CONSTRUCT_ARRAY,
OPCODE_CONSTRUCT_TYPED_ARRAY,
OPCODE_CONSTRUCT_DICTIONARY,
+ OPCODE_CONSTRUCT_TYPED_DICTIONARY,
OPCODE_CALL,
OPCODE_CALL_RETURN,
OPCODE_CALL_ASYNC,
@@ -280,6 +322,7 @@ public:
OPCODE_RETURN,
OPCODE_RETURN_TYPED_BUILTIN,
OPCODE_RETURN_TYPED_ARRAY,
+ OPCODE_RETURN_TYPED_DICTIONARY,
OPCODE_RETURN_TYPED_NATIVE,
OPCODE_RETURN_TYPED_SCRIPT,
OPCODE_ITERATE_BEGIN,
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 92f9c5fa11..65aa150be3 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -3568,7 +3568,7 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
type->type_chain.push_back(type_element);
if (match(GDScriptTokenizer::Token::BRACKET_OPEN)) {
- // Typed collection (like Array[int]).
+ // Typed collection (like Array[int], Dictionary[String, int]).
bool first_pass = true;
do {
TypeNode *container_type = parse_type(false); // Don't allow void for element type.
@@ -4385,6 +4385,14 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
export_type.type_source = variable->datatype.type_source;
}
+ bool is_dict = false;
+ if (export_type.builtin_type == Variant::DICTIONARY && export_type.has_container_element_types()) {
+ is_dict = true;
+ DataType inner_type = export_type.get_container_element_type_or_variant(1);
+ export_type = export_type.get_container_element_type_or_variant(0);
+ export_type.set_container_element_type(0, inner_type); // Store earlier extracted value within key to separately parse after.
+ }
+
bool use_default_variable_type_check = true;
if (p_annotation->name == SNAME("@export_range")) {
@@ -4412,8 +4420,13 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
}
if (export_type.is_variant() || export_type.has_no_type()) {
- push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation);
- return false;
+ if (is_dict) {
+ // Dictionary allowed to have a variant key/value.
+ export_type.kind = GDScriptParser::DataType::BUILTIN;
+ } else {
+ push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation);
+ return false;
+ }
}
switch (export_type.kind) {
@@ -4473,6 +4486,90 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
push_error(vformat(R"(Node export is only supported in Node-derived classes, but the current class inherits "%s".)", p_class->base_type.to_string()), p_annotation);
return false;
}
+
+ if (is_dict) {
+ String key_prefix = itos(variable->export_info.type);
+ if (variable->export_info.hint) {
+ key_prefix += "/" + itos(variable->export_info.hint);
+ }
+ key_prefix += ":" + variable->export_info.hint_string;
+
+ // Now parse value.
+ export_type = export_type.get_container_element_type(0);
+
+ if (export_type.is_variant() || export_type.has_no_type()) {
+ export_type.kind = GDScriptParser::DataType::BUILTIN;
+ }
+ switch (export_type.kind) {
+ case GDScriptParser::DataType::BUILTIN:
+ variable->export_info.type = export_type.builtin_type;
+ variable->export_info.hint = PROPERTY_HINT_NONE;
+ variable->export_info.hint_string = String();
+ break;
+ case GDScriptParser::DataType::NATIVE:
+ case GDScriptParser::DataType::SCRIPT:
+ case GDScriptParser::DataType::CLASS: {
+ const StringName class_name = _find_narrowest_native_or_global_class(export_type);
+ if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) {
+ variable->export_info.type = Variant::OBJECT;
+ variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
+ variable->export_info.hint_string = class_name;
+ } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) {
+ variable->export_info.type = Variant::OBJECT;
+ variable->export_info.hint = PROPERTY_HINT_NODE_TYPE;
+ variable->export_info.hint_string = class_name;
+ } else {
+ push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);
+ return false;
+ }
+ } break;
+ case GDScriptParser::DataType::ENUM: {
+ if (export_type.is_meta_type) {
+ variable->export_info.type = Variant::DICTIONARY;
+ } else {
+ variable->export_info.type = Variant::INT;
+ variable->export_info.hint = PROPERTY_HINT_ENUM;
+
+ String enum_hint_string;
+ bool first = true;
+ for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) {
+ if (!first) {
+ enum_hint_string += ",";
+ } else {
+ first = false;
+ }
+ enum_hint_string += E.key.operator String().capitalize().xml_escape();
+ enum_hint_string += ":";
+ enum_hint_string += String::num_int64(E.value).xml_escape();
+ }
+
+ variable->export_info.hint_string = enum_hint_string;
+ variable->export_info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM;
+ variable->export_info.class_name = String(export_type.native_type).replace("::", ".");
+ }
+ } break;
+ default:
+ push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);
+ return false;
+ }
+
+ if (variable->export_info.hint == PROPERTY_HINT_NODE_TYPE && !ClassDB::is_parent_class(p_class->base_type.native_type, SNAME("Node"))) {
+ push_error(vformat(R"(Node export is only supported in Node-derived classes, but the current class inherits "%s".)", p_class->base_type.to_string()), p_annotation);
+ return false;
+ }
+
+ String value_prefix = itos(variable->export_info.type);
+ if (variable->export_info.hint) {
+ value_prefix += "/" + itos(variable->export_info.hint);
+ }
+ value_prefix += ":" + variable->export_info.hint_string;
+
+ variable->export_info.type = Variant::DICTIONARY;
+ variable->export_info.hint = PROPERTY_HINT_TYPE_STRING;
+ variable->export_info.hint_string = key_prefix + ";" + value_prefix;
+ variable->export_info.usage = PROPERTY_USAGE_DEFAULT;
+ variable->export_info.class_name = StringName();
+ }
} else if (p_annotation->name == SNAME("@export_enum")) {
use_default_variable_type_check = false;
@@ -4794,7 +4891,10 @@ String GDScriptParser::DataType::to_string() const {
return "null";
}
if (builtin_type == Variant::ARRAY && has_container_element_type(0)) {
- return vformat("Array[%s]", container_element_types[0].to_string());
+ return vformat("Array[%s]", get_container_element_type(0).to_string());
+ }
+ if (builtin_type == Variant::DICTIONARY && has_container_element_types()) {
+ return vformat("Dictionary[%s, %s]", get_container_element_type_or_variant(0).to_string(), get_container_element_type_or_variant(1).to_string());
}
return Variant::get_type_name(builtin_type);
case NATIVE:
@@ -4883,6 +4983,72 @@ PropertyInfo GDScriptParser::DataType::to_property_info(const String &p_name) co
case UNRESOLVED:
break;
}
+ } else if (builtin_type == Variant::DICTIONARY && has_container_element_types()) {
+ const DataType key_type = get_container_element_type_or_variant(0);
+ const DataType value_type = get_container_element_type_or_variant(1);
+ if ((key_type.kind == VARIANT && value_type.kind == VARIANT) || key_type.kind == RESOLVING ||
+ key_type.kind == UNRESOLVED || value_type.kind == RESOLVING || value_type.kind == UNRESOLVED) {
+ break;
+ }
+ String key_hint, value_hint;
+ switch (key_type.kind) {
+ case BUILTIN:
+ key_hint = Variant::get_type_name(key_type.builtin_type);
+ break;
+ case NATIVE:
+ key_hint = key_type.native_type;
+ break;
+ case SCRIPT:
+ if (key_type.script_type.is_valid() && key_type.script_type->get_global_name() != StringName()) {
+ key_hint = key_type.script_type->get_global_name();
+ } else {
+ key_hint = key_type.native_type;
+ }
+ break;
+ case CLASS:
+ if (key_type.class_type != nullptr && key_type.class_type->get_global_name() != StringName()) {
+ key_hint = key_type.class_type->get_global_name();
+ } else {
+ key_hint = key_type.native_type;
+ }
+ break;
+ case ENUM:
+ key_hint = String(key_type.native_type).replace("::", ".");
+ break;
+ default:
+ key_hint = "Variant";
+ break;
+ }
+ switch (value_type.kind) {
+ case BUILTIN:
+ value_hint = Variant::get_type_name(value_type.builtin_type);
+ break;
+ case NATIVE:
+ value_hint = value_type.native_type;
+ break;
+ case SCRIPT:
+ if (value_type.script_type.is_valid() && value_type.script_type->get_global_name() != StringName()) {
+ value_hint = value_type.script_type->get_global_name();
+ } else {
+ value_hint = value_type.native_type;
+ }
+ break;
+ case CLASS:
+ if (value_type.class_type != nullptr && value_type.class_type->get_global_name() != StringName()) {
+ value_hint = value_type.class_type->get_global_name();
+ } else {
+ value_hint = value_type.native_type;
+ }
+ break;
+ case ENUM:
+ value_hint = String(value_type.native_type).replace("::", ".");
+ break;
+ default:
+ value_hint = "Variant";
+ break;
+ }
+ result.hint = PROPERTY_HINT_DICTIONARY_TYPE;
+ result.hint_string = key_hint + ";" + value_hint;
}
break;
case NATIVE:
@@ -4967,6 +5133,50 @@ GDScriptParser::DataType GDScriptParser::DataType::get_typed_container_type() co
return type;
}
+bool GDScriptParser::DataType::can_reference(const GDScriptParser::DataType &p_other) const {
+ if (p_other.is_meta_type) {
+ return false;
+ } else if (builtin_type != p_other.builtin_type) {
+ return false;
+ } else if (builtin_type != Variant::OBJECT) {
+ return true;
+ }
+
+ if (native_type == StringName()) {
+ return true;
+ } else if (p_other.native_type == StringName()) {
+ return false;
+ } else if (native_type != p_other.native_type && !ClassDB::is_parent_class(p_other.native_type, native_type)) {
+ return false;
+ }
+
+ Ref<Script> script = script_type;
+ if (kind == GDScriptParser::DataType::CLASS && script.is_null()) {
+ Error err = OK;
+ Ref<GDScript> scr = GDScriptCache::get_shallow_script(script_path, err);
+ ERR_FAIL_COND_V_MSG(err, false, vformat(R"(Error while getting cache for script "%s".)", script_path));
+ script.reference_ptr(scr->find_class(class_type->fqcn));
+ }
+
+ Ref<Script> script_other = p_other.script_type;
+ if (p_other.kind == GDScriptParser::DataType::CLASS && script_other.is_null()) {
+ Error err = OK;
+ Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_other.script_path, err);
+ ERR_FAIL_COND_V_MSG(err, false, vformat(R"(Error while getting cache for script "%s".)", p_other.script_path));
+ script_other.reference_ptr(scr->find_class(p_other.class_type->fqcn));
+ }
+
+ if (script.is_null()) {
+ return true;
+ } else if (script_other.is_null()) {
+ return false;
+ } else if (script != script_other && !script_other->inherits_script(script)) {
+ return false;
+ }
+
+ return true;
+}
+
void GDScriptParser::complete_extents(Node *p_node) {
while (!nodes_in_progress.is_empty() && nodes_in_progress.back()->get() != p_node) {
ERR_PRINT("Parser bug: Mismatch in extents tracking stack.");
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 2c7e730772..7840474a89 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -189,6 +189,8 @@ public:
GDScriptParser::DataType get_typed_container_type() const;
+ bool can_reference(const DataType &p_other) const;
+
bool operator==(const DataType &p_other) const {
if (type_source == UNDETECTED || p_other.type_source == UNDETECTED) {
return true; // Can be considered equal for parsing purposes.
diff --git a/modules/gdscript/gdscript_tokenizer_buffer.cpp b/modules/gdscript/gdscript_tokenizer_buffer.cpp
index e53bc5bc41..2046480f0e 100644
--- a/modules/gdscript/gdscript_tokenizer_buffer.cpp
+++ b/modules/gdscript/gdscript_tokenizer_buffer.cpp
@@ -296,6 +296,7 @@ Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code,
encode_uint32(identifier_map.size(), &contents.write[0]);
encode_uint32(constant_map.size(), &contents.write[4]);
encode_uint32(token_lines.size(), &contents.write[8]);
+ encode_uint32(0, &contents.write[12]); // Unused, kept for compatibility. Please remove at next `TOKENIZER_VERSION` increment.
encode_uint32(token_counter, &contents.write[16]);
int buf_pos = 20;
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index ddb0cf9502..4617a0dbb9 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -84,9 +84,15 @@ static String _get_var_type(const Variant *p_var) {
if (p_var->get_type() == Variant::ARRAY) {
basestr = "Array";
const Array *p_array = VariantInternal::get_array(p_var);
- Variant::Type builtin_type = (Variant::Type)p_array->get_typed_builtin();
- if (builtin_type != Variant::NIL) {
- basestr += "[" + _get_element_type(builtin_type, p_array->get_typed_class_name(), p_array->get_typed_script()) + "]";
+ if (p_array->is_typed()) {
+ basestr += "[" + _get_element_type((Variant::Type)p_array->get_typed_builtin(), p_array->get_typed_class_name(), p_array->get_typed_script()) + "]";
+ }
+ } else if (p_var->get_type() == Variant::DICTIONARY) {
+ basestr = "Dictionary";
+ const Dictionary *p_dictionary = VariantInternal::get_dictionary(p_var);
+ if (p_dictionary->is_typed()) {
+ basestr += "[" + _get_element_type((Variant::Type)p_dictionary->get_typed_key_builtin(), p_dictionary->get_typed_key_class_name(), p_dictionary->get_typed_key_script()) +
+ ", " + _get_element_type((Variant::Type)p_dictionary->get_typed_value_builtin(), p_dictionary->get_typed_value_class_name(), p_dictionary->get_typed_value_script()) + "]";
}
} else {
basestr = Variant::get_type_name(p_var->get_type());
@@ -120,6 +126,16 @@ Variant GDScriptFunction::_get_default_variant_for_data_type(const GDScriptDataT
}
return array;
+ } else if (p_data_type.builtin_type == Variant::DICTIONARY) {
+ Dictionary dict;
+ // Typed dictionary.
+ if (p_data_type.has_container_element_types()) {
+ const GDScriptDataType &key_type = p_data_type.get_container_element_type_or_variant(0);
+ const GDScriptDataType &value_type = p_data_type.get_container_element_type_or_variant(1);
+ dict.set_typed(key_type.builtin_type, key_type.native_type, key_type.script_type, value_type.builtin_type, value_type.native_type, value_type.script_type);
+ }
+
+ return dict;
} else {
Callable::CallError ce;
Variant variant;
@@ -153,6 +169,9 @@ String GDScriptFunction::_get_call_error(const String &p_where, const Variant **
if (p_err.expected == Variant::ARRAY && p_argptrs[p_err.argument]->get_type() == p_err.expected) {
return "Invalid type in " + p_where + ". The array of argument " + itos(p_err.argument + 1) + " (" + _get_var_type(p_argptrs[p_err.argument]) + ") does not have the same element type as the expected typed array argument.";
}
+ if (p_err.expected == Variant::DICTIONARY && p_argptrs[p_err.argument]->get_type() == p_err.expected) {
+ return "Invalid type in " + p_where + ". The dictionary of argument " + itos(p_err.argument + 1) + " (" + _get_var_type(p_argptrs[p_err.argument]) + ") does not have the same element type as the expected typed dictionary argument.";
+ }
#endif // DEBUG_ENABLED
return "Invalid type in " + p_where + ". Cannot convert argument " + itos(p_err.argument + 1) + " from " + Variant::get_type_name(p_argptrs[p_err.argument]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + ".";
case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS:
@@ -215,6 +234,7 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_OPERATOR_VALIDATED, \
&&OPCODE_TYPE_TEST_BUILTIN, \
&&OPCODE_TYPE_TEST_ARRAY, \
+ &&OPCODE_TYPE_TEST_DICTIONARY, \
&&OPCODE_TYPE_TEST_NATIVE, \
&&OPCODE_TYPE_TEST_SCRIPT, \
&&OPCODE_SET_KEYED, \
@@ -237,6 +257,7 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_ASSIGN_FALSE, \
&&OPCODE_ASSIGN_TYPED_BUILTIN, \
&&OPCODE_ASSIGN_TYPED_ARRAY, \
+ &&OPCODE_ASSIGN_TYPED_DICTIONARY, \
&&OPCODE_ASSIGN_TYPED_NATIVE, \
&&OPCODE_ASSIGN_TYPED_SCRIPT, \
&&OPCODE_CAST_TO_BUILTIN, \
@@ -247,6 +268,7 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_CONSTRUCT_ARRAY, \
&&OPCODE_CONSTRUCT_TYPED_ARRAY, \
&&OPCODE_CONSTRUCT_DICTIONARY, \
+ &&OPCODE_CONSTRUCT_TYPED_DICTIONARY, \
&&OPCODE_CALL, \
&&OPCODE_CALL_RETURN, \
&&OPCODE_CALL_ASYNC, \
@@ -275,6 +297,7 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_RETURN, \
&&OPCODE_RETURN_TYPED_BUILTIN, \
&&OPCODE_RETURN_TYPED_ARRAY, \
+ &&OPCODE_RETURN_TYPED_DICTIONARY, \
&&OPCODE_RETURN_TYPED_NATIVE, \
&&OPCODE_RETURN_TYPED_SCRIPT, \
&&OPCODE_ITERATE_BEGIN, \
@@ -548,7 +571,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
return _get_default_variant_for_data_type(return_type);
}
if (argument_types[i].kind == GDScriptDataType::BUILTIN) {
- if (argument_types[i].builtin_type == Variant::ARRAY && argument_types[i].has_container_element_type(0)) {
+ if (argument_types[i].builtin_type == Variant::DICTIONARY && argument_types[i].has_container_element_types()) {
+ const GDScriptDataType &arg_key_type = argument_types[i].get_container_element_type_or_variant(0);
+ const GDScriptDataType &arg_value_type = argument_types[i].get_container_element_type_or_variant(1);
+ Dictionary dict(p_args[i]->operator Dictionary(), arg_key_type.builtin_type, arg_key_type.native_type, arg_key_type.script_type, arg_value_type.builtin_type, arg_value_type.native_type, arg_value_type.script_type);
+ memnew_placement(&stack[i + 3], Variant(dict));
+ } else if (argument_types[i].builtin_type == Variant::ARRAY && argument_types[i].has_container_element_type(0)) {
const GDScriptDataType &arg_type = argument_types[i].container_element_types[0];
Array array(p_args[i]->operator Array(), arg_type.builtin_type, arg_type.native_type, arg_type.script_type);
memnew_placement(&stack[i + 3], Variant(array));
@@ -827,6 +855,36 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
+ OPCODE(OPCODE_TYPE_TEST_DICTIONARY) {
+ CHECK_SPACE(9);
+
+ GET_VARIANT_PTR(dst, 0);
+ GET_VARIANT_PTR(value, 1);
+
+ GET_VARIANT_PTR(key_script_type, 2);
+ Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5];
+ int key_native_type_idx = _code_ptr[ip + 6];
+ GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count);
+ const StringName key_native_type = _global_names_ptr[key_native_type_idx];
+
+ GET_VARIANT_PTR(value_script_type, 3);
+ Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7];
+ int value_native_type_idx = _code_ptr[ip + 8];
+ GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count);
+ const StringName value_native_type = _global_names_ptr[value_native_type_idx];
+
+ bool result = false;
+ if (value->get_type() == Variant::DICTIONARY) {
+ Dictionary *dictionary = VariantInternal::get_dictionary(value);
+ result = dictionary->get_typed_key_builtin() == ((uint32_t)key_builtin_type) && dictionary->get_typed_key_class_name() == key_native_type && dictionary->get_typed_key_script() == *key_script_type &&
+ dictionary->get_typed_value_builtin() == ((uint32_t)value_builtin_type) && dictionary->get_typed_value_class_name() == value_native_type && dictionary->get_typed_value_script() == *value_script_type;
+ }
+
+ *dst = result;
+ ip += 9;
+ }
+ DISPATCH_OPCODE;
+
OPCODE(OPCODE_TYPE_TEST_NATIVE) {
CHECK_SPACE(4);
@@ -1384,6 +1442,50 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
+ OPCODE(OPCODE_ASSIGN_TYPED_DICTIONARY) {
+ CHECK_SPACE(9);
+ GET_VARIANT_PTR(dst, 0);
+ GET_VARIANT_PTR(src, 1);
+
+ GET_VARIANT_PTR(key_script_type, 2);
+ Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5];
+ int key_native_type_idx = _code_ptr[ip + 6];
+ GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count);
+ const StringName key_native_type = _global_names_ptr[key_native_type_idx];
+
+ GET_VARIANT_PTR(value_script_type, 3);
+ Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7];
+ int value_native_type_idx = _code_ptr[ip + 8];
+ GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count);
+ const StringName value_native_type = _global_names_ptr[value_native_type_idx];
+
+ if (src->get_type() != Variant::DICTIONARY) {
+#ifdef DEBUG_ENABLED
+ err_text = vformat(R"(Trying to assign a value of type "%s" to a variable of type "Dictionary[%s, %s]".)",
+ _get_var_type(src), _get_element_type(key_builtin_type, key_native_type, *key_script_type),
+ _get_element_type(value_builtin_type, value_native_type, *value_script_type));
+#endif // DEBUG_ENABLED
+ OPCODE_BREAK;
+ }
+
+ Dictionary *dictionary = VariantInternal::get_dictionary(src);
+
+ if (dictionary->get_typed_key_builtin() != ((uint32_t)key_builtin_type) || dictionary->get_typed_key_class_name() != key_native_type || dictionary->get_typed_key_script() != *key_script_type ||
+ dictionary->get_typed_value_builtin() != ((uint32_t)value_builtin_type) || dictionary->get_typed_value_class_name() != value_native_type || dictionary->get_typed_value_script() != *value_script_type) {
+#ifdef DEBUG_ENABLED
+ err_text = vformat(R"(Trying to assign a dictionary of type "%s" to a variable of type "Dictionary[%s, %s]".)",
+ _get_var_type(src), _get_element_type(key_builtin_type, key_native_type, *key_script_type),
+ _get_element_type(value_builtin_type, value_native_type, *value_script_type));
+#endif // DEBUG_ENABLED
+ OPCODE_BREAK;
+ }
+
+ *dst = *src;
+
+ ip += 9;
+ }
+ DISPATCH_OPCODE;
+
OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) {
CHECK_SPACE(4);
GET_VARIANT_PTR(dst, 0);
@@ -1703,12 +1805,51 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GET_INSTRUCTION_ARG(dst, argc * 2);
+ *dst = Variant(); // Clear potential previous typed dictionary.
+
*dst = dict;
ip += 2;
}
DISPATCH_OPCODE;
+ OPCODE(OPCODE_CONSTRUCT_TYPED_DICTIONARY) {
+ LOAD_INSTRUCTION_ARGS
+ CHECK_SPACE(6 + instr_arg_count);
+ ip += instr_arg_count;
+
+ int argc = _code_ptr[ip + 1];
+
+ GET_INSTRUCTION_ARG(key_script_type, argc * 2 + 1);
+ Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 2];
+ int key_native_type_idx = _code_ptr[ip + 3];
+ GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count);
+ const StringName key_native_type = _global_names_ptr[key_native_type_idx];
+
+ GET_INSTRUCTION_ARG(value_script_type, argc * 2 + 2);
+ Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 4];
+ int value_native_type_idx = _code_ptr[ip + 5];
+ GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count);
+ const StringName value_native_type = _global_names_ptr[value_native_type_idx];
+
+ Dictionary dict;
+
+ for (int i = 0; i < argc; i++) {
+ GET_INSTRUCTION_ARG(k, i * 2 + 0);
+ GET_INSTRUCTION_ARG(v, i * 2 + 1);
+ dict[*k] = *v;
+ }
+
+ GET_INSTRUCTION_ARG(dst, argc * 2);
+
+ *dst = Variant(); // Clear potential previous typed dictionary.
+
+ *dst = Dictionary(dict, key_builtin_type, key_native_type, *key_script_type, value_builtin_type, value_native_type, *value_script_type);
+
+ ip += 6;
+ }
+ DISPATCH_OPCODE;
+
OPCODE(OPCODE_CALL_ASYNC)
OPCODE(OPCODE_CALL_RETURN)
OPCODE(OPCODE_CALL) {
@@ -2657,6 +2798,51 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
OPCODE_BREAK;
}
+ OPCODE(OPCODE_RETURN_TYPED_DICTIONARY) {
+ CHECK_SPACE(8);
+ GET_VARIANT_PTR(r, 0);
+
+ GET_VARIANT_PTR(key_script_type, 1);
+ Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 4];
+ int key_native_type_idx = _code_ptr[ip + 5];
+ GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count);
+ const StringName key_native_type = _global_names_ptr[key_native_type_idx];
+
+ GET_VARIANT_PTR(value_script_type, 2);
+ Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 6];
+ int value_native_type_idx = _code_ptr[ip + 7];
+ GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count);
+ const StringName value_native_type = _global_names_ptr[value_native_type_idx];
+
+ if (r->get_type() != Variant::DICTIONARY) {
+#ifdef DEBUG_ENABLED
+ err_text = vformat(R"(Trying to return a value of type "%s" where expected return type is "Dictionary[%s, %s]".)",
+ _get_var_type(r), _get_element_type(key_builtin_type, key_native_type, *key_script_type),
+ _get_element_type(value_builtin_type, value_native_type, *value_script_type));
+#endif // DEBUG_ENABLED
+ OPCODE_BREAK;
+ }
+
+ Dictionary *dictionary = VariantInternal::get_dictionary(r);
+
+ if (dictionary->get_typed_key_builtin() != ((uint32_t)key_builtin_type) || dictionary->get_typed_key_class_name() != key_native_type || dictionary->get_typed_key_script() != *key_script_type ||
+ dictionary->get_typed_value_builtin() != ((uint32_t)value_builtin_type) || dictionary->get_typed_value_class_name() != value_native_type || dictionary->get_typed_value_script() != *value_script_type) {
+#ifdef DEBUG_ENABLED
+ err_text = vformat(R"(Trying to return a dictionary of type "%s" where expected return type is "Dictionary[%s, %s]".)",
+ _get_var_type(r), _get_element_type(key_builtin_type, key_native_type, *key_script_type),
+ _get_element_type(value_builtin_type, value_native_type, *value_script_type));
+#endif // DEBUG_ENABLED
+ OPCODE_BREAK;
+ }
+
+ retvalue = *dictionary;
+
+#ifdef DEBUG_ENABLED
+ exit_ok = true;
+#endif // DEBUG_ENABLED
+ OPCODE_BREAK;
+ }
+
OPCODE(OPCODE_RETURN_TYPED_NATIVE) {
CHECK_SPACE(3);
GET_VARIANT_PTR(r, 0);
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd
new file mode 100644
index 0000000000..c180cca03c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd
@@ -0,0 +1,3 @@
+func test():
+ for key: int in { "a": 1 }:
+ print(key)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out
new file mode 100644
index 0000000000..8530783673
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot include a value of type "String" as "int".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd
new file mode 100644
index 0000000000..75d1b7fe62
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd
@@ -0,0 +1,4 @@
+func test():
+ var differently: Dictionary[float, float] = { 1.0: 0.0 }
+ var typed: Dictionary[int, int] = differently
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out
new file mode 100644
index 0000000000..e05d4be8c9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot assign a value of type Dictionary[float, float] to variable "typed" with specified type Dictionary[int, int].
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd
new file mode 100644
index 0000000000..e0af71823a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd
@@ -0,0 +1,2 @@
+func test():
+ const dict: Dictionary[int, int] = { "Hello": "World" }
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out
new file mode 100644
index 0000000000..8530783673
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot include a value of type "String" as "int".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd
new file mode 100644
index 0000000000..814ba12aef
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd
@@ -0,0 +1,4 @@
+func test():
+ var unconvertible := 1
+ var typed: Dictionary[Object, Object] = { unconvertible: unconvertible }
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out
new file mode 100644
index 0000000000..9d6c9d9144
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot have a key of type "int" in a dictionary of type "Dictionary[Object, Object]".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd
new file mode 100644
index 0000000000..73d8ce2b96
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd
@@ -0,0 +1,7 @@
+func expect_typed(typed: Dictionary[int, int]):
+ print(typed.size())
+
+func test():
+ var differently: Dictionary[float, float] = { 1.0: 0.0 }
+ expect_typed(differently)
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out
new file mode 100644
index 0000000000..302109cf8a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Invalid argument for "expect_typed()" function: argument 1 should be "Dictionary[int, int]" but is "Dictionary[float, float]".
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd
new file mode 100644
index 0000000000..65f5e7da07
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd
@@ -0,0 +1,20 @@
+func print_untyped(dictionary = { 0: 1 }) -> void:
+ print(dictionary)
+ print(dictionary.get_typed_key_builtin())
+ print(dictionary.get_typed_value_builtin())
+
+func print_inferred(dictionary := { 2: 3 }) -> void:
+ print(dictionary)
+ print(dictionary.get_typed_key_builtin())
+ print(dictionary.get_typed_value_builtin())
+
+func print_typed(dictionary: Dictionary[int, int] = { 4: 5 }) -> void:
+ print(dictionary)
+ print(dictionary.get_typed_key_builtin())
+ print(dictionary.get_typed_value_builtin())
+
+func test():
+ print_untyped()
+ print_inferred()
+ print_typed()
+ print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out
new file mode 100644
index 0000000000..c31561bee3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out
@@ -0,0 +1,11 @@
+GDTEST_OK
+{ 0: 1 }
+0
+0
+{ 2: 3 }
+0
+0
+{ 4: 5 }
+2
+2
+ok
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd
new file mode 100644
index 0000000000..0aa3de2c4a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd
@@ -0,0 +1,4 @@
+func test():
+ var dict := { 0: 0 }
+ dict[0] = 1
+ print(dict[0])
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out
new file mode 100644
index 0000000000..a7f1357bb2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+1
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd
new file mode 100644
index 0000000000..9d3fffd1de
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd
@@ -0,0 +1,214 @@
+class A: pass
+class B extends A: pass
+
+enum E { E0 = 391, E1 = 193 }
+
+func floats_identity(floats: Dictionary[float, float]): return floats
+
+class Members:
+ var one: Dictionary[int, int] = { 104: 401 }
+ var two: Dictionary[int, int] = one
+
+ func check_passing() -> bool:
+ Utils.check(str(one) == '{ 104: 401 }')
+ Utils.check(str(two) == '{ 104: 401 }')
+ two[582] = 285
+ Utils.check(str(one) == '{ 104: 401, 582: 285 }')
+ Utils.check(str(two) == '{ 104: 401, 582: 285 }')
+ two = { 486: 684 }
+ Utils.check(str(one) == '{ 104: 401, 582: 285 }')
+ Utils.check(str(two) == '{ 486: 684 }')
+ return true
+
+
+@warning_ignore("unsafe_method_access")
+@warning_ignore("assert_always_true")
+@warning_ignore("return_value_discarded")
+func test():
+ var untyped_basic = { 459: 954 }
+ Utils.check(str(untyped_basic) == '{ 459: 954 }')
+ Utils.check(untyped_basic.get_typed_key_builtin() == TYPE_NIL)
+ Utils.check(untyped_basic.get_typed_value_builtin() == TYPE_NIL)
+
+ var inferred_basic := { 366: 663 }
+ Utils.check(str(inferred_basic) == '{ 366: 663 }')
+ Utils.check(inferred_basic.get_typed_key_builtin() == TYPE_NIL)
+ Utils.check(inferred_basic.get_typed_value_builtin() == TYPE_NIL)
+
+ var typed_basic: Dictionary = { 521: 125 }
+ Utils.check(str(typed_basic) == '{ 521: 125 }')
+ Utils.check(typed_basic.get_typed_key_builtin() == TYPE_NIL)
+ Utils.check(typed_basic.get_typed_value_builtin() == TYPE_NIL)
+
+
+ var empty_floats: Dictionary[float, float] = {}
+ Utils.check(str(empty_floats) == '{ }')
+ Utils.check(empty_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(empty_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+ untyped_basic = empty_floats
+ Utils.check(untyped_basic.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(untyped_basic.get_typed_value_builtin() == TYPE_FLOAT)
+
+ inferred_basic = empty_floats
+ Utils.check(inferred_basic.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(inferred_basic.get_typed_value_builtin() == TYPE_FLOAT)
+
+ typed_basic = empty_floats
+ Utils.check(typed_basic.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(typed_basic.get_typed_value_builtin() == TYPE_FLOAT)
+
+ empty_floats[705.0] = 507.0
+ untyped_basic[430.0] = 34.0
+ inferred_basic[263.0] = 362.0
+ typed_basic[518.0] = 815.0
+ Utils.check(str(empty_floats) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
+ Utils.check(str(untyped_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
+ Utils.check(str(inferred_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
+ Utils.check(str(typed_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
+
+
+ const constant_float := 950.0
+ const constant_int := 170
+ var typed_float := 954.0
+ var filled_floats: Dictionary[float, float] = { constant_float: constant_int, typed_float: empty_floats[430.0] + empty_floats[263.0] }
+ Utils.check(str(filled_floats) == '{ 950: 170, 954: 396 }')
+ Utils.check(filled_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(filled_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+ var casted_floats := { empty_floats[263.0] * 2: empty_floats[263.0] / 2 } as Dictionary[float, float]
+ Utils.check(str(casted_floats) == '{ 724: 181 }')
+ Utils.check(casted_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(casted_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+ var returned_floats = (func () -> Dictionary[float, float]: return { 554: 455 }).call()
+ Utils.check(str(returned_floats) == '{ 554: 455 }')
+ Utils.check(returned_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(returned_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+ var passed_floats = floats_identity({ 663.0 if randf() > 0.5 else 663.0: 366.0 if randf() <= 0.5 else 366.0 })
+ Utils.check(str(passed_floats) == '{ 663: 366 }')
+ Utils.check(passed_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(passed_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+ var default_floats = (func (floats: Dictionary[float, float] = { 364.0: 463.0 }): return floats).call()
+ Utils.check(str(default_floats) == '{ 364: 463 }')
+ Utils.check(default_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(default_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+ var typed_int := 556
+ var converted_floats: Dictionary[float, float] = { typed_int: typed_int }
+ converted_floats[498.0] = 894
+ Utils.check(str(converted_floats) == '{ 556: 556, 498: 894 }')
+ Utils.check(converted_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(converted_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+
+ const constant_basic = { 228: 822 }
+ Utils.check(str(constant_basic) == '{ 228: 822 }')
+ Utils.check(constant_basic.get_typed_key_builtin() == TYPE_NIL)
+ Utils.check(constant_basic.get_typed_value_builtin() == TYPE_NIL)
+
+ const constant_floats: Dictionary[float, float] = { constant_float - constant_basic[228] - constant_int: constant_float + constant_basic[228] + constant_int }
+ Utils.check(str(constant_floats) == '{ -42: 1942 }')
+ Utils.check(constant_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(constant_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+
+ var source_floats: Dictionary[float, float] = { 999.74: 47.999 }
+ untyped_basic = source_floats
+ var destination_floats: Dictionary[float, float] = untyped_basic
+ destination_floats[999.74] -= 0.999
+ Utils.check(str(source_floats) == '{ 999.74: 47 }')
+ Utils.check(str(untyped_basic) == '{ 999.74: 47 }')
+ Utils.check(str(destination_floats) == '{ 999.74: 47 }')
+ Utils.check(destination_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(destination_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+
+ var duplicated_floats := empty_floats.duplicate()
+ duplicated_floats.erase(705.0)
+ duplicated_floats.erase(430.0)
+ duplicated_floats.erase(518.0)
+ duplicated_floats[263.0] *= 3
+ Utils.check(str(duplicated_floats) == '{ 263: 1086 }')
+ Utils.check(duplicated_floats.get_typed_key_builtin() == TYPE_FLOAT)
+ Utils.check(duplicated_floats.get_typed_value_builtin() == TYPE_FLOAT)
+
+
+ var b_objects: Dictionary[int, B] = { 0: B.new(), 1: B.new() as A, 2: null }
+ Utils.check(b_objects.size() == 3)
+ Utils.check(b_objects.get_typed_value_builtin() == TYPE_OBJECT)
+ Utils.check(b_objects.get_typed_value_script() == B)
+
+ var a_objects: Dictionary[int, A] = { 0: A.new(), 1: B.new(), 2: null, 3: b_objects[0] }
+ Utils.check(a_objects.size() == 4)
+ Utils.check(a_objects.get_typed_value_builtin() == TYPE_OBJECT)
+ Utils.check(a_objects.get_typed_value_script() == A)
+
+ var a_passed = (func check_a_passing(p_objects: Dictionary[int, A]): return p_objects.size()).call(a_objects)
+ Utils.check(a_passed == 4)
+
+ var b_passed = (func check_b_passing(basic: Dictionary): return basic[0] != null).call(b_objects)
+ Utils.check(b_passed == true)
+
+
+ var empty_strings: Dictionary[String, String] = {}
+ var empty_bools: Dictionary[bool, bool] = {}
+ var empty_basic_one := {}
+ var empty_basic_two := {}
+ Utils.check(empty_strings == empty_bools)
+ Utils.check(empty_basic_one == empty_basic_two)
+ Utils.check(empty_strings.hash() == empty_bools.hash())
+ Utils.check(empty_basic_one.hash() == empty_basic_two.hash())
+
+
+ var assign_source: Dictionary[int, int] = { 527: 725 }
+ var assign_target: Dictionary[int, int] = {}
+ assign_target.assign(assign_source)
+ Utils.check(str(assign_source) == '{ 527: 725 }')
+ Utils.check(str(assign_target) == '{ 527: 725 }')
+ assign_source[657] = 756
+ Utils.check(str(assign_source) == '{ 527: 725, 657: 756 }')
+ Utils.check(str(assign_target) == '{ 527: 725 }')
+
+
+ var defaults_passed = (func check_defaults_passing(one: Dictionary[int, int] = {}, two := one):
+ one[887] = 788
+ two[198] = 891
+ Utils.check(str(one) == '{ 887: 788, 198: 891 }')
+ Utils.check(str(two) == '{ 887: 788, 198: 891 }')
+ two = {130: 31}
+ Utils.check(str(one) == '{ 887: 788, 198: 891 }')
+ Utils.check(str(two) == '{ 130: 31 }')
+ return true
+ ).call()
+ Utils.check(defaults_passed == true)
+
+
+ var members := Members.new()
+ var members_passed := members.check_passing()
+ Utils.check(members_passed == true)
+
+
+ var typed_enums: Dictionary[E, E] = {}
+ typed_enums[E.E0] = E.E1
+ Utils.check(str(typed_enums) == '{ 391: 193 }')
+ Utils.check(typed_enums.get_typed_key_builtin() == TYPE_INT)
+ Utils.check(typed_enums.get_typed_value_builtin() == TYPE_INT)
+
+ const const_enums: Dictionary[E, E] = {}
+ Utils.check(const_enums.get_typed_key_builtin() == TYPE_INT)
+ Utils.check(const_enums.get_typed_key_class_name() == &'')
+ Utils.check(const_enums.get_typed_value_builtin() == TYPE_INT)
+ Utils.check(const_enums.get_typed_value_class_name() == &'')
+
+
+ var a := A.new()
+ var b := B.new()
+ var typed_natives: Dictionary[RefCounted, RefCounted] = { a: b }
+ var typed_scripts = Dictionary(typed_natives, TYPE_OBJECT, "RefCounted", A, TYPE_OBJECT, "RefCounted", B)
+ Utils.check(typed_scripts[a] == b)
+
+
+ print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd
new file mode 100644
index 0000000000..f4a23ade14
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd
@@ -0,0 +1,9 @@
+class Inner:
+ var prop = "Inner"
+
+var dict: Dictionary[int, Inner] = { 0: Inner.new() }
+
+
+func test():
+ var element: Inner = dict[0]
+ print(element.prop)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out
new file mode 100644
index 0000000000..8f250d2632
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+Inner
diff --git a/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.cfg b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.cfg
new file mode 100644
index 0000000000..36c150f6e3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.cfg
@@ -0,0 +1,9 @@
+[input]
+scene="res://completion/get_node/get_node.tscn"
+[output]
+include=[
+ {"display": "%UniqueA"},
+]
+exclude=[
+ {"display": "\"%UniqueA\""},
+]
diff --git a/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.gd b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.gd
new file mode 100644
index 0000000000..def050e938
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.gd
@@ -0,0 +1,5 @@
+extends Node
+
+func a():
+ $➡
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd
new file mode 100644
index 0000000000..57e6489484
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd
@@ -0,0 +1,5 @@
+func test():
+ var my_dictionary: Dictionary[int, String] = { 1: "one", 2: "two", 3: "three" }
+ var inferred_dictionary := { 1: "one", 2: "two", 3: "three" } # This is Dictionary[int, String].
+ print(my_dictionary)
+ print(inferred_dictionary)
diff --git a/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out
new file mode 100644
index 0000000000..6021c338ee
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+{ 1: "one", 2: "two", 3: "three" }
+{ 1: "one", 2: "two", 3: "three" }
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd
new file mode 100644
index 0000000000..75004742a2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd
@@ -0,0 +1,4 @@
+func test():
+ var basic := { 1: 1 }
+ var typed: Dictionary[int, int] = basic
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out
new file mode 100644
index 0000000000..cadb17f570
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/typed_dictionary_assign_basic_to_typed.gd
+>> 3
+>> Trying to assign a dictionary of type "Dictionary" to a variable of type "Dictionary[int, int]".
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd
new file mode 100644
index 0000000000..e5ab4a1a85
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd
@@ -0,0 +1,4 @@
+func test():
+ var differently: Variant = { 1.0: 0.0 } as Dictionary[float, float]
+ var typed: Dictionary[int, int] = differently
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out
new file mode 100644
index 0000000000..fe1e5d1285
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/typed_dictionary_assign_differently_typed.gd
+>> 3
+>> Trying to assign a dictionary of type "Dictionary[float, float]" to a variable of type "Dictionary[int, int]".
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.gd
new file mode 100644
index 0000000000..6cc0e57255
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.gd
@@ -0,0 +1,7 @@
+class Foo: pass
+class Bar extends Foo: pass
+class Baz extends Foo: pass
+
+func test():
+ var typed: Dictionary[Bar, Bar] = { Baz.new() as Foo: Baz.new() as Foo }
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out
new file mode 100644
index 0000000000..18a4c360e2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out
@@ -0,0 +1,5 @@
+GDTEST_RUNTIME_ERROR
+>> ERROR
+>> Method/function failed.
+>> Unable to convert key from "Object" to "Object".
+not ok
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd
new file mode 100644
index 0000000000..8f7d732584
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd
@@ -0,0 +1,7 @@
+func expect_typed(typed: Dictionary[int, int]):
+ print(typed.size())
+
+func test():
+ var basic := { 1: 1 }
+ expect_typed(basic)
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out
new file mode 100644
index 0000000000..fb45461701
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/typed_dictionary_pass_basic_to_typed.gd
+>> 6
+>> Invalid type in function 'expect_typed' in base 'RefCounted (typed_dictionary_pass_basic_to_typed.gd)'. The dictionary of argument 1 (Dictionary) does not have the same element type as the expected typed dictionary argument.
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd
new file mode 100644
index 0000000000..978a9fdfee
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd
@@ -0,0 +1,7 @@
+func expect_typed(typed: Dictionary[int, int]):
+ print(typed.size())
+
+func test():
+ var differently: Variant = { 1.0: 0.0 } as Dictionary[float, float]
+ expect_typed(differently)
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out
new file mode 100644
index 0000000000..4036a1bf01
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/typed_dictionary_pass_differently_to_typed.gd
+>> 6
+>> Invalid type in function 'expect_typed' in base 'RefCounted (typed_dictionary_pass_differently_to_typed.gd)'. The dictionary of argument 1 (Dictionary[float, float]) does not have the same element type as the expected typed dictionary argument.
diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd
index bc899a3a6f..393500bd9b 100644
--- a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd
@@ -28,13 +28,18 @@ func test():
prints(var_to_str(e), var_to_str(elem))
print("Test String-keys dictionary.")
- var d1 := {a = 1, b = 2, c = 3}
+ var d1 := { a = 1, b = 2, c = 3 }
for k: StringName in d1:
var key := k
prints(var_to_str(k), var_to_str(key))
print("Test RefCounted-keys dictionary.")
- var d2 := {RefCounted.new(): 1, Resource.new(): 2, ConfigFile.new(): 3}
+ var d2 := { RefCounted.new(): 1, Resource.new(): 2, ConfigFile.new(): 3 }
for k: RefCounted in d2:
var key := k
prints(k.get_class(), key.get_class())
+
+ print("Test implicitly typed dictionary literal.")
+ for k: StringName in { x = 123, y = 456, z = 789 }:
+ var key := k
+ prints(var_to_str(k), var_to_str(key))
diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out
index eeebdc4be5..89cc1b76fe 100644
--- a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out
+++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out
@@ -27,3 +27,7 @@ Test RefCounted-keys dictionary.
RefCounted RefCounted
Resource Resource
ConfigFile ConfigFile
+Test implicitly typed dictionary literal.
+&"x" &"x"
+&"y" &"y"
+&"z" &"z"
diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.gd b/modules/gdscript/tests/scripts/runtime/features/member_info.gd
index 91d5a501c8..4ce53aa395 100644
--- a/modules/gdscript/tests/scripts/runtime/features/member_info.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/member_info.gd
@@ -31,6 +31,16 @@ var test_var_hard_array_my_enum: Array[MyEnum]
var test_var_hard_array_resource: Array[Resource]
var test_var_hard_array_this: Array[TestMemberInfo]
var test_var_hard_array_my_class: Array[MyClass]
+var test_var_hard_dictionary: Dictionary
+var test_var_hard_dictionary_int_variant: Dictionary[int, Variant]
+var test_var_hard_dictionary_variant_int: Dictionary[Variant, int]
+var test_var_hard_dictionary_int_int: Dictionary[int, int]
+var test_var_hard_dictionary_variant_type: Dictionary[Variant.Type, Variant.Type]
+var test_var_hard_dictionary_node_process_mode: Dictionary[Node.ProcessMode, Node.ProcessMode]
+var test_var_hard_dictionary_my_enum: Dictionary[MyEnum, MyEnum]
+var test_var_hard_dictionary_resource: Dictionary[Resource, Resource]
+var test_var_hard_dictionary_this: Dictionary[TestMemberInfo, TestMemberInfo]
+var test_var_hard_dictionary_my_class: Dictionary[MyClass, MyClass]
var test_var_hard_resource: Resource
var test_var_hard_this: TestMemberInfo
var test_var_hard_my_class: MyClass
@@ -43,17 +53,17 @@ func test_func_weak_null(): return null
func test_func_weak_int(): return 1
func test_func_hard_variant() -> Variant: return null
func test_func_hard_int() -> int: return 1
-func test_func_args_1(_a: int, _b: Array[int], _c: int = 1, _d = 2): pass
+func test_func_args_1(_a: int, _b: Array[int], _c: Dictionary[int, int], _d: int = 1, _e = 2): pass
func test_func_args_2(_a = 1, _b = _a, _c = [2], _d = 3): pass
signal test_signal_1()
signal test_signal_2(a: Variant, b)
-signal test_signal_3(a: int, b: Array[int])
-signal test_signal_4(a: Variant.Type, b: Array[Variant.Type])
-signal test_signal_5(a: MyEnum, b: Array[MyEnum])
-signal test_signal_6(a: Resource, b: Array[Resource])
-signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo])
-signal test_signal_8(a: MyClass, b: Array[MyClass])
+signal test_signal_3(a: int, b: Array[int], c: Dictionary[int, int])
+signal test_signal_4(a: Variant.Type, b: Array[Variant.Type], c: Dictionary[Variant.Type, Variant.Type])
+signal test_signal_5(a: MyEnum, b: Array[MyEnum], c: Dictionary[MyEnum, MyEnum])
+signal test_signal_6(a: Resource, b: Array[Resource], c: Dictionary[Resource, Resource])
+signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo], c: Dictionary[TestMemberInfo, TestMemberInfo])
+signal test_signal_8(a: MyClass, b: Array[MyClass], c: Dictionary[MyClass, MyClass])
func no_exec():
test_signal_1.emit()
diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.out b/modules/gdscript/tests/scripts/runtime/features/member_info.out
index 7c826ac05a..2baf451aa5 100644
--- a/modules/gdscript/tests/scripts/runtime/features/member_info.out
+++ b/modules/gdscript/tests/scripts/runtime/features/member_info.out
@@ -23,6 +23,16 @@ var test_var_hard_array_my_enum: Array[TestMemberInfo.MyEnum]
var test_var_hard_array_resource: Array[Resource]
var test_var_hard_array_this: Array[TestMemberInfo]
var test_var_hard_array_my_class: Array[RefCounted]
+var test_var_hard_dictionary: Dictionary
+var test_var_hard_dictionary_int_variant: Dictionary[int, Variant]
+var test_var_hard_dictionary_variant_int: Dictionary[Variant, int]
+var test_var_hard_dictionary_int_int: Dictionary[int, int]
+var test_var_hard_dictionary_variant_type: Dictionary[Variant.Type, Variant.Type]
+var test_var_hard_dictionary_node_process_mode: Dictionary[Node.ProcessMode, Node.ProcessMode]
+var test_var_hard_dictionary_my_enum: Dictionary[TestMemberInfo.MyEnum, TestMemberInfo.MyEnum]
+var test_var_hard_dictionary_resource: Dictionary[Resource, Resource]
+var test_var_hard_dictionary_this: Dictionary[TestMemberInfo, TestMemberInfo]
+var test_var_hard_dictionary_my_class: Dictionary[RefCounted, RefCounted]
var test_var_hard_resource: Resource
var test_var_hard_this: TestMemberInfo
var test_var_hard_my_class: RefCounted
@@ -33,13 +43,13 @@ func test_func_weak_null() -> Variant
func test_func_weak_int() -> Variant
func test_func_hard_variant() -> Variant
func test_func_hard_int() -> int
-func test_func_args_1(_a: int, _b: Array[int], _c: int = 1, _d: Variant = 2) -> void
+func test_func_args_1(_a: int, _b: Array[int], _c: Dictionary[int, int], _d: int = 1, _e: Variant = 2) -> void
func test_func_args_2(_a: Variant = 1, _b: Variant = null, _c: Variant = null, _d: Variant = 3) -> void
signal test_signal_1()
signal test_signal_2(a: Variant, b: Variant)
-signal test_signal_3(a: int, b: Array[int])
-signal test_signal_4(a: Variant.Type, b: Array[Variant.Type])
-signal test_signal_5(a: TestMemberInfo.MyEnum, b: Array[TestMemberInfo.MyEnum])
-signal test_signal_6(a: Resource, b: Array[Resource])
-signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo])
-signal test_signal_8(a: RefCounted, b: Array[RefCounted])
+signal test_signal_3(a: int, b: Array[int], c: Dictionary[int, int])
+signal test_signal_4(a: Variant.Type, b: Array[Variant.Type], c: Dictionary[Variant.Type, Variant.Type])
+signal test_signal_5(a: TestMemberInfo.MyEnum, b: Array[TestMemberInfo.MyEnum], c: Dictionary[TestMemberInfo.MyEnum, TestMemberInfo.MyEnum])
+signal test_signal_6(a: Resource, b: Array[Resource], c: Dictionary[Resource, Resource])
+signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo], c: Dictionary[TestMemberInfo, TestMemberInfo])
+signal test_signal_8(a: RefCounted, b: Array[RefCounted], c: Dictionary[RefCounted, RefCounted])
diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd
new file mode 100644
index 0000000000..0371ee5630
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd
@@ -0,0 +1,6 @@
+func test_param(dictionary: Dictionary[int, String]) -> void:
+ print(dictionary.get_typed_key_builtin() == TYPE_INT)
+ print(dictionary.get_typed_value_builtin() == TYPE_STRING)
+
+func test() -> void:
+ test_param({ 123: "some_string" })
diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out
new file mode 100644
index 0000000000..9d111a8322
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+true
+true
diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd
new file mode 100644
index 0000000000..ee51440d80
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd
@@ -0,0 +1,7 @@
+func test():
+ var untyped: Variant = 32
+ var typed: Dictionary[int, int] = { untyped: untyped }
+ Utils.check(typed.get_typed_key_builtin() == TYPE_INT)
+ Utils.check(typed.get_typed_value_builtin() == TYPE_INT)
+ Utils.check(str(typed) == '{ 32: 32 }')
+ print('ok')
diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd
index 5d615d8557..1e2788f765 100644
--- a/modules/gdscript/tests/scripts/utils.notest.gd
+++ b/modules/gdscript/tests/scripts/utils.notest.gd
@@ -24,6 +24,11 @@ static func get_type(property: Dictionary, is_return: bool = false) -> String:
if str(property.hint_string).is_empty():
return "Array[<unknown type>]"
return "Array[%s]" % property.hint_string
+ TYPE_DICTIONARY:
+ if property.hint == PROPERTY_HINT_DICTIONARY_TYPE:
+ if str(property.hint_string).is_empty():
+ return "Dictionary[<unknown type>, <unknown type>]"
+ return "Dictionary[%s]" % str(property.hint_string).replace(";", ", ")
TYPE_OBJECT:
if not str(property.class_name).is_empty():
return property.class_name
@@ -188,6 +193,8 @@ static func get_property_hint_name(hint: PropertyHint) -> String:
return "PROPERTY_HINT_INT_IS_POINTER"
PROPERTY_HINT_ARRAY_TYPE:
return "PROPERTY_HINT_ARRAY_TYPE"
+ PROPERTY_HINT_DICTIONARY_TYPE:
+ return "PROPERTY_HINT_DICTIONARY_TYPE"
PROPERTY_HINT_LOCALE_ID:
return "PROPERTY_HINT_LOCALE_ID"
PROPERTY_HINT_LOCALIZABLE_STRING:
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index 4653df7afe..cf1a1ea4b3 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -491,14 +491,8 @@ String GLTFDocument::_gen_unique_name(Ref<GLTFState> p_state, const String &p_na
}
String GLTFDocument::_sanitize_animation_name(const String &p_name) {
- // Animations disallow the normal node invalid characters as well as "," and "["
- // (See animation/animation_player.cpp::add_animation)
-
- // TODO: Consider adding invalid_characters or a validate_animation_name to animation_player to mirror Node.
String anim_name = p_name.validate_node_name();
- anim_name = anim_name.replace(",", "");
- anim_name = anim_name.replace("[", "");
- return anim_name;
+ return AnimationLibrary::validate_library_name(anim_name);
}
String GLTFDocument::_gen_unique_animation_name(Ref<GLTFState> p_state, const String &p_name) {
diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp
index ea63e07104..6b010335e6 100644
--- a/modules/gridmap/editor/grid_map_editor_plugin.cpp
+++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp
@@ -34,6 +34,7 @@
#include "core/input/input.h"
#include "core/os/keyboard.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
@@ -958,7 +959,7 @@ void GridMapEditor::edit(GridMap *p_gridmap) {
_update_selection_transform();
_update_paste_indicator();
- spatial_editor = Object::cast_to<Node3DEditorPlugin>(EditorNode::get_singleton()->get_editor_plugin_screen());
+ spatial_editor = Object::cast_to<Node3DEditorPlugin>(EditorNode::get_singleton()->get_editor_main_screen()->get_selected_plugin());
if (!node) {
set_process(false);
diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp
index 89495e2c83..8ba6f9e2ba 100644
--- a/modules/lightmapper_rd/lightmapper_rd.cpp
+++ b/modules/lightmapper_rd/lightmapper_rd.cpp
@@ -793,6 +793,35 @@ LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShade
return BAKE_OK;
}
+LightmapperRD::BakeError LightmapperRD::_pack_l1(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices) {
+ Vector<RD::Uniform> uniforms = dilate_or_denoise_common_uniforms(source_light_tex, dest_light_tex);
+
+ RID compute_shader_pack = rd->shader_create_from_spirv(compute_shader->get_spirv_stages("pack_coeffs"));
+ ERR_FAIL_COND_V(compute_shader_pack.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //internal check, should not happen
+ RID compute_shader_pack_pipeline = rd->compute_pipeline_create(compute_shader_pack);
+
+ RID dilate_uniform_set = rd->uniform_set_create(uniforms, compute_shader_pack, 1);
+
+ RD::ComputeListID compute_list = rd->compute_list_begin();
+ rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_pack_pipeline);
+ rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0);
+ rd->compute_list_bind_uniform_set(compute_list, dilate_uniform_set, 1);
+ push_constant.region_ofs[0] = 0;
+ push_constant.region_ofs[1] = 0;
+ Vector3i group_size(Math::division_round_up(atlas_size.x, 8), Math::division_round_up(atlas_size.y, 8), 1); //restore group size
+
+ for (int i = 0; i < atlas_slices; i++) {
+ push_constant.atlas_slice = i;
+ rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant));
+ rd->compute_list_dispatch(compute_list, group_size.x, group_size.y, group_size.z);
+ //no barrier, let them run all together
+ }
+ rd->compute_list_end();
+ rd->free(compute_shader_pack);
+
+ return BAKE_OK;
+}
+
Error LightmapperRD::_store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name) {
Vector<uint8_t> data = p_rd->texture_get_data(p_atlas_tex, p_index);
Ref<Image> img = Image::create_from_data(p_atlas_size.width, p_atlas_size.height, false, Image::FORMAT_RGBAH, data);
@@ -2002,6 +2031,14 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
}
}
+ if (p_bake_sh) {
+ SWAP(light_accum_tex, light_accum_tex2);
+ BakeError error = _pack_l1(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, light_accum_tex, atlas_size, atlas_slices);
+ if (unlikely(error != BAKE_OK)) {
+ return error;
+ }
+ }
+
#ifdef DEBUG_TEXTURES
for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) {
diff --git a/modules/lightmapper_rd/lightmapper_rd.h b/modules/lightmapper_rd/lightmapper_rd.h
index 59c2d52e69..f43da39670 100644
--- a/modules/lightmapper_rd/lightmapper_rd.h
+++ b/modules/lightmapper_rd/lightmapper_rd.h
@@ -273,6 +273,7 @@ class LightmapperRD : public Lightmapper {
BakeError _dilate(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices);
BakeError _denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, int p_denoiser_range, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function, void *p_bake_userdata);
+ BakeError _pack_l1(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices);
Error _store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name);
Ref<Image> _read_pfm(const String &p_name);
diff --git a/modules/lightmapper_rd/lm_compute.glsl b/modules/lightmapper_rd/lm_compute.glsl
index 88fc316679..2c85fff6f3 100644
--- a/modules/lightmapper_rd/lm_compute.glsl
+++ b/modules/lightmapper_rd/lm_compute.glsl
@@ -6,6 +6,7 @@ dilate = "#define MODE_DILATE";
unocclude = "#define MODE_UNOCCLUDE";
light_probes = "#define MODE_LIGHT_PROBES";
denoise = "#define MODE_DENOISE";
+pack_coeffs = "#define MODE_PACK_L1_COEFFS";
#[compute]
@@ -63,7 +64,7 @@ layout(rgba16f, set = 1, binding = 4) uniform restrict image2DArray accum_light;
layout(set = 1, binding = 5) uniform texture2D environment;
#endif
-#if defined(MODE_DILATE) || defined(MODE_DENOISE)
+#if defined(MODE_DILATE) || defined(MODE_DENOISE) || defined(MODE_PACK_L1_COEFFS)
layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2DArray dest_light;
layout(set = 1, binding = 1) uniform texture2DArray source_light;
#endif
@@ -1037,4 +1038,28 @@ void main() {
imageStore(dest_light, ivec3(atlas_pos, lightmap_slice), vec4(denoised_rgb, input_light.a));
}
#endif
+
+#ifdef MODE_PACK_L1_COEFFS
+ vec4 base_coeff = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos, params.atlas_slice * 4), 0);
+
+ for (int i = 1; i < 4; i++) {
+ vec4 c = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos, params.atlas_slice * 4 + i), 0);
+
+ if (abs(base_coeff.r) > 0.0) {
+ c.r /= (base_coeff.r * 8);
+ }
+
+ if (abs(base_coeff.g) > 0.0) {
+ c.g /= (base_coeff.g * 8);
+ }
+
+ if (abs(base_coeff.b) > 0.0) {
+ c.b /= (base_coeff.b * 8);
+ }
+
+ c.rgb += vec3(0.5);
+ c.rgb = clamp(c.rgb, vec3(0.0), vec3(1.0));
+ imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice * 4 + i), c);
+ }
+#endif
}
diff --git a/modules/minimp3/audio_stream_mp3.cpp b/modules/minimp3/audio_stream_mp3.cpp
index 394213963a..e29fe9295a 100644
--- a/modules/minimp3/audio_stream_mp3.cpp
+++ b/modules/minimp3/audio_stream_mp3.cpp
@@ -159,6 +159,9 @@ Ref<AudioSamplePlayback> AudioStreamPlaybackMP3::get_sample_playback() const {
void AudioStreamPlaybackMP3::set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) {
sample_playback = p_playback;
+ if (sample_playback.is_valid()) {
+ sample_playback->stream_playback = Ref<AudioStreamPlayback>(this);
+ }
}
void AudioStreamPlaybackMP3::set_parameter(const StringName &p_name, const Variant &p_value) {
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs
index 6a3884dabf..c734dc7be1 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs
@@ -807,7 +807,7 @@ partial class ExportedFields
properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.@_fieldRid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldGodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
- properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotGenericDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotGenericDictionary, hint: (global::Godot.PropertyHint)38, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldGodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)31, name: PropertyName.@_fieldEmptyInt64Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
return properties;
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs
index 3c48740773..0de840aa34 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs
@@ -925,7 +925,7 @@ partial class ExportedProperties
properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.@PropertyRid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@PropertyGodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
- properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotGenericDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotGenericDictionary, hint: (global::Godot.PropertyHint)38, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@PropertyGodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
return properties;
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
index 4cf6a9f431..bb4c4824e7 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
@@ -88,7 +88,8 @@ namespace Godot.SourceGenerators
HideQuaternionEdit = 35,
Password = 36,
LayersAvoidance = 37,
- Max = 38
+ DictionaryType = 38,
+ Max = 39
}
[Flags]
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs
index d272832950..f5f51722b4 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs
@@ -274,6 +274,14 @@ namespace Godot.SourceGenerators
return null;
}
+ public static ITypeSymbol[]? GetGenericElementTypes(ITypeSymbol typeSymbol)
+ {
+ if (typeSymbol is INamedTypeSymbol { IsGenericType: true } genericType)
+ return genericType.TypeArguments.ToArray();
+
+ return null;
+ }
+
private static StringBuilder Append(this StringBuilder source, string a, string b)
=> source.Append(a).Append(b);
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
index 29bae6413d..0f86b3b91c 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
@@ -728,8 +729,72 @@ namespace Godot.SourceGenerators
if (!isTypeArgument && variantType == VariantType.Dictionary)
{
- // TODO: Dictionaries are not supported in the inspector
- return false;
+ var elementTypes = MarshalUtils.GetGenericElementTypes(type);
+
+ if (elementTypes == null)
+ return false; // Non-generic Dictionary, so there's no hint to add
+ Debug.Assert(elementTypes.Length == 2);
+
+ var keyElementMarshalType = MarshalUtils.ConvertManagedTypeToMarshalType(elementTypes[0], typeCache)!.Value;
+ var keyElementVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(keyElementMarshalType)!.Value;
+ var keyIsPresetHint = false;
+ var keyHintString = (string?)null;
+
+ if (keyElementVariantType == VariantType.String || keyElementVariantType == VariantType.StringName)
+ keyIsPresetHint = GetStringArrayEnumHint(keyElementVariantType, exportAttr, out keyHintString);
+
+ if (!keyIsPresetHint)
+ {
+ bool hintRes = TryGetMemberExportHint(typeCache, elementTypes[0],
+ exportAttr, keyElementVariantType, isTypeArgument: true,
+ out var keyElementHint, out var keyElementHintString);
+
+ // Format: type/hint:hint_string
+ if (hintRes)
+ {
+ keyHintString = (int)keyElementVariantType + "/" + (int)keyElementHint + ":";
+
+ if (keyElementHintString != null)
+ keyHintString += keyElementHintString;
+ }
+ else
+ {
+ keyHintString = (int)keyElementVariantType + "/" + (int)PropertyHint.None + ":";
+ }
+ }
+
+ var valueElementMarshalType = MarshalUtils.ConvertManagedTypeToMarshalType(elementTypes[1], typeCache)!.Value;
+ var valueElementVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(valueElementMarshalType)!.Value;
+ var valueIsPresetHint = false;
+ var valueHintString = (string?)null;
+
+ if (valueElementVariantType == VariantType.String || valueElementVariantType == VariantType.StringName)
+ valueIsPresetHint = GetStringArrayEnumHint(valueElementVariantType, exportAttr, out valueHintString);
+
+ if (!valueIsPresetHint)
+ {
+ bool hintRes = TryGetMemberExportHint(typeCache, elementTypes[1],
+ exportAttr, valueElementVariantType, isTypeArgument: true,
+ out var valueElementHint, out var valueElementHintString);
+
+ // Format: type/hint:hint_string
+ if (hintRes)
+ {
+ valueHintString = (int)valueElementVariantType + "/" + (int)valueElementHint + ":";
+
+ if (valueElementHintString != null)
+ valueHintString += valueElementHintString;
+ }
+ else
+ {
+ valueHintString = (int)valueElementVariantType + "/" + (int)PropertyHint.None + ":";
+ }
+ }
+
+ hint = PropertyHint.DictionaryType;
+
+ hintString = keyHintString != null && valueHintString != null ? $"{keyHintString};{valueHintString}" : null;
+ return hintString != null;
}
return false;
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index b26f6d1bbf..3222c58c4e 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -3448,6 +3448,12 @@ StringName BindingsGenerator::_get_int_type_name_from_meta(GodotTypeInfo::Metada
case GodotTypeInfo::METADATA_INT_IS_UINT64:
return "ulong";
break;
+ case GodotTypeInfo::METADATA_INT_IS_CHAR16:
+ return "char";
+ break;
+ case GodotTypeInfo::METADATA_INT_IS_CHAR32:
+ // To prevent breaking compatibility, C# bindings need to keep using `long`.
+ return "long";
default:
// Assume INT64
return "long";
@@ -3809,6 +3815,11 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
} else if (return_info.type == Variant::ARRAY && return_info.hint == PROPERTY_HINT_ARRAY_TYPE) {
imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic";
imethod.return_type.generic_type_parameters.push_back(TypeReference(return_info.hint_string));
+ } else if (return_info.type == Variant::DICTIONARY && return_info.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
+ imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic";
+ Vector<String> split = return_info.hint_string.split(";");
+ imethod.return_type.generic_type_parameters.push_back(TypeReference(split.get(0)));
+ imethod.return_type.generic_type_parameters.push_back(TypeReference(split.get(1)));
} else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {
imethod.return_type.cname = return_info.hint_string;
} else if (return_info.type == Variant::NIL && return_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
@@ -3836,6 +3847,11 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
} else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";
iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string));
+ } else if (arginfo.type == Variant::DICTIONARY && arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
+ iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";
+ Vector<String> split = arginfo.hint_string.split(";");
+ iarg.type.generic_type_parameters.push_back(TypeReference(split.get(0)));
+ iarg.type.generic_type_parameters.push_back(TypeReference(split.get(1)));
} else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
iarg.type.cname = arginfo.hint_string;
} else if (arginfo.type == Variant::NIL) {
@@ -3963,6 +3979,11 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
} else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";
iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string));
+ } else if (arginfo.type == Variant::DICTIONARY && arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
+ iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";
+ Vector<String> split = arginfo.hint_string.split(";");
+ iarg.type.generic_type_parameters.push_back(TypeReference(split.get(0)));
+ iarg.type.generic_type_parameters.push_back(TypeReference(split.get(1)));
} else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
iarg.type.cname = arginfo.hint_string;
} else if (arginfo.type == Variant::NIL) {
diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp
index 7322a47630..df240a5965 100644
--- a/modules/mono/editor/editor_internal_calls.cpp
+++ b/modules/mono/editor/editor_internal_calls.cpp
@@ -41,6 +41,7 @@
#include "core/os/os.h"
#include "core/version.h"
#include "editor/debugger/editor_debugger_node.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_settings.h"
@@ -165,7 +166,7 @@ bool godot_icall_Internal_ScriptEditorEdit(Resource *p_resource, int32_t p_line,
}
void godot_icall_Internal_EditorNodeShowScriptScreen() {
- EditorNode::get_singleton()->editor_select(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
}
void godot_icall_Internal_EditorRunPlay() {
diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp
index 3935854a29..039263b405 100644
--- a/modules/mono/godotsharp_dirs.cpp
+++ b/modules/mono/godotsharp_dirs.cpp
@@ -192,8 +192,14 @@ private:
}
}
if (!has_data) {
- // 3. Extract the data to a temporary location to load from there.
- Ref<DirAccess> da = DirAccess::create_for_path(packed_path);
+ // 3. Extract the data to a temporary location to load from there, delete old data if it exists but is not up-to-date.
+ Ref<DirAccess> da;
+ if (DirAccess::exists(data_dir_root)) {
+ da = DirAccess::open(data_dir_root);
+ ERR_FAIL_COND(da.is_null());
+ ERR_FAIL_COND(da->erase_contents_recursive() != OK);
+ }
+ da = DirAccess::create_for_path(packed_path);
ERR_FAIL_COND(da.is_null());
ERR_FAIL_COND(da->copy_dir(packed_path, data_dir_root) != OK);
}
diff --git a/modules/navigation/2d/godot_navigation_server_2d.cpp b/modules/navigation/2d/godot_navigation_server_2d.cpp
index bf69adc14c..2af125d434 100644
--- a/modules/navigation/2d/godot_navigation_server_2d.cpp
+++ b/modules/navigation/2d/godot_navigation_server_2d.cpp
@@ -318,6 +318,11 @@ int FORWARD_1_C(region_get_connections_count, RID, p_region, rid_to_rid);
Vector2 FORWARD_2_R_C(v3_to_v2, region_get_connection_pathway_start, RID, p_region, int, p_connection_id, rid_to_rid, int_to_int);
Vector2 FORWARD_2_R_C(v3_to_v2, region_get_connection_pathway_end, RID, p_region, int, p_connection_id, rid_to_rid, int_to_int);
+Vector2 GodotNavigationServer2D::region_get_closest_point(RID p_region, const Vector2 &p_point) const {
+ Vector3 result = NavigationServer3D::get_singleton()->region_get_closest_point(p_region, v2_to_v3(p_point));
+ return v3_to_v2(result);
+}
+
Vector2 GodotNavigationServer2D::region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const {
Vector3 result = NavigationServer3D::get_singleton()->region_get_random_point(p_region, p_navigation_layers, p_uniformly);
return v3_to_v2(result);
diff --git a/modules/navigation/2d/godot_navigation_server_2d.h b/modules/navigation/2d/godot_navigation_server_2d.h
index ea77fa5e6e..1579ca2907 100644
--- a/modules/navigation/2d/godot_navigation_server_2d.h
+++ b/modules/navigation/2d/godot_navigation_server_2d.h
@@ -101,6 +101,7 @@ public:
virtual int region_get_connections_count(RID p_region) const override;
virtual Vector2 region_get_connection_pathway_start(RID p_region, int p_connection_id) const override;
virtual Vector2 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override;
+ virtual Vector2 region_get_closest_point(RID p_region, const Vector2 &p_point) const override;
virtual Vector2 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const override;
virtual RID link_create() override;
diff --git a/modules/navigation/3d/godot_navigation_server_3d.cpp b/modules/navigation/3d/godot_navigation_server_3d.cpp
index 11a5de608b..5dfc39f6f5 100644
--- a/modules/navigation/3d/godot_navigation_server_3d.cpp
+++ b/modules/navigation/3d/godot_navigation_server_3d.cpp
@@ -536,6 +536,27 @@ Vector3 GodotNavigationServer3D::region_get_connection_pathway_end(RID p_region,
return Vector3();
}
+Vector3 GodotNavigationServer3D::region_get_closest_point_to_segment(RID p_region, const Vector3 &p_from, const Vector3 &p_to, bool p_use_collision) const {
+ const NavRegion *region = region_owner.get_or_null(p_region);
+ ERR_FAIL_NULL_V(region, Vector3());
+
+ return region->get_closest_point_to_segment(p_from, p_to, p_use_collision);
+}
+
+Vector3 GodotNavigationServer3D::region_get_closest_point(RID p_region, const Vector3 &p_point) const {
+ const NavRegion *region = region_owner.get_or_null(p_region);
+ ERR_FAIL_NULL_V(region, Vector3());
+
+ return region->get_closest_point_info(p_point).point;
+}
+
+Vector3 GodotNavigationServer3D::region_get_closest_point_normal(RID p_region, const Vector3 &p_point) const {
+ const NavRegion *region = region_owner.get_or_null(p_region);
+ ERR_FAIL_NULL_V(region, Vector3());
+
+ return region->get_closest_point_info(p_point).normal;
+}
+
Vector3 GodotNavigationServer3D::region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const {
const NavRegion *region = region_owner.get_or_null(p_region);
ERR_FAIL_NULL_V(region, Vector3());
diff --git a/modules/navigation/3d/godot_navigation_server_3d.h b/modules/navigation/3d/godot_navigation_server_3d.h
index 12a1132f07..eae6ea2860 100644
--- a/modules/navigation/3d/godot_navigation_server_3d.h
+++ b/modules/navigation/3d/godot_navigation_server_3d.h
@@ -178,6 +178,9 @@ public:
virtual int region_get_connections_count(RID p_region) const override;
virtual Vector3 region_get_connection_pathway_start(RID p_region, int p_connection_id) const override;
virtual Vector3 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override;
+ virtual Vector3 region_get_closest_point_to_segment(RID p_region, const Vector3 &p_from, const Vector3 &p_to, bool p_use_collision = false) const override;
+ virtual Vector3 region_get_closest_point(RID p_region, const Vector3 &p_point) const override;
+ virtual Vector3 region_get_closest_point_normal(RID p_region, const Vector3 &p_point) const override;
virtual Vector3 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const override;
virtual RID link_create() override;
diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
index f37ed9b168..7f0cbc7b5e 100644
--- a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
+++ b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
@@ -176,7 +176,7 @@ void NavigationMeshEditorPlugin::make_visible(bool p_visible) {
NavigationMeshEditorPlugin::NavigationMeshEditorPlugin() {
navigation_mesh_editor = memnew(NavigationMeshEditor);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(navigation_mesh_editor);
+ EditorNode::get_singleton()->get_gui_base()->add_child(navigation_mesh_editor);
add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, navigation_mesh_editor->bake_hbox);
navigation_mesh_editor->hide();
navigation_mesh_editor->bake_hbox->hide();
diff --git a/modules/navigation/nav_region.cpp b/modules/navigation/nav_region.cpp
index 7a44adecbc..2c91b80af2 100644
--- a/modules/navigation/nav_region.cpp
+++ b/modules/navigation/nav_region.cpp
@@ -105,7 +105,22 @@ void NavRegion::set_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh) {
polygons_dirty = true;
}
+Vector3 NavRegion::get_closest_point_to_segment(const Vector3 &p_from, const Vector3 &p_to, bool p_use_collision) const {
+ RWLockRead read_lock(region_rwlock);
+
+ return NavMeshQueries3D::polygons_get_closest_point_to_segment(
+ get_polygons(), p_from, p_to, p_use_collision);
+}
+
+gd::ClosestPointQueryResult NavRegion::get_closest_point_info(const Vector3 &p_point) const {
+ RWLockRead read_lock(region_rwlock);
+
+ return NavMeshQueries3D::polygons_get_closest_point_info(get_polygons(), p_point);
+}
+
Vector3 NavRegion::get_random_point(uint32_t p_navigation_layers, bool p_uniformly) const {
+ RWLockRead read_lock(region_rwlock);
+
if (!get_enabled()) {
return Vector3();
}
@@ -114,6 +129,8 @@ Vector3 NavRegion::get_random_point(uint32_t p_navigation_layers, bool p_uniform
}
bool NavRegion::sync() {
+ RWLockWrite write_lock(region_rwlock);
+
bool something_changed = polygons_dirty /* || something_dirty? */;
update_polygons();
diff --git a/modules/navigation/nav_region.h b/modules/navigation/nav_region.h
index 662a32c47a..c015802b92 100644
--- a/modules/navigation/nav_region.h
+++ b/modules/navigation/nav_region.h
@@ -38,6 +38,8 @@
#include "scene/resources/navigation_mesh.h"
class NavRegion : public NavBase {
+ RWLock region_rwlock;
+
NavMap *map = nullptr;
Transform3D transform;
bool enabled = true;
@@ -88,6 +90,8 @@ public:
return polygons;
}
+ Vector3 get_closest_point_to_segment(const Vector3 &p_from, const Vector3 &p_to, bool p_use_collision) const;
+ gd::ClosestPointQueryResult get_closest_point_info(const Vector3 &p_point) const;
Vector3 get_random_point(uint32_t p_navigation_layers, bool p_uniformly) const;
real_t get_surface_area() const { return surface_area; };
diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayer.xml b/modules/openxr/doc_classes/OpenXRCompositionLayer.xml
index 6f4ac00f3e..341b50065c 100644
--- a/modules/openxr/doc_classes/OpenXRCompositionLayer.xml
+++ b/modules/openxr/doc_classes/OpenXRCompositionLayer.xml
@@ -10,6 +10,13 @@
<tutorials>
</tutorials>
<methods>
+ <method name="get_android_surface">
+ <return type="JavaObject" />
+ <description>
+ Returns a [JavaObject] representing an [code]android.view.Surface[/code] if [member use_android_surface] is enabled and OpenXR has created the surface. Otherwise, this will return [code]null[/code].
+ [b]Note:[/b] The surface can only be created during an active OpenXR session. So, if [member use_android_surface] is enabled outside of an OpenXR session, it won't be created until a new session fully starts.
+ </description>
+ </method>
<method name="intersects_ray" qualifiers="const">
<return type="Vector2" />
<param index="0" name="origin" type="Vector3" />
@@ -32,6 +39,9 @@
Enables the blending the layer using its alpha channel.
Can be combined with [member Viewport.transparent_bg] to give the layer a transparent background.
</member>
+ <member name="android_surface_size" type="Vector2i" setter="set_android_surface_size" getter="get_android_surface_size" default="Vector2i(1024, 1024)">
+ The size of the Android surface to create if [member use_android_surface] is enabled.
+ </member>
<member name="enable_hole_punch" type="bool" setter="set_enable_hole_punch" getter="get_enable_hole_punch" default="false">
Enables a technique called "hole punching", which allows putting the composition layer behind the main projection layer (i.e. setting [member sort_order] to a negative value) while "punching a hole" through everything rendered by Godot so that the layer is still visible.
This can be used to create the illusion that the composition layer exists in the same 3D space as everything rendered by Godot, allowing objects to appear to pass both behind or in front of the composition layer.
@@ -43,5 +53,10 @@
The sort order for this composition layer. Higher numbers will be shown in front of lower numbers.
[b]Note:[/b] This will have no effect if a fallback mesh is being used.
</member>
+ <member name="use_android_surface" type="bool" setter="set_use_android_surface" getter="get_use_android_surface" default="false">
+ If enabled, an Android surface will be created (with the dimensions from [member android_surface_size]) which will provide the 2D content for the composition layer, rather than using [member layer_viewport].
+ See [method get_android_surface] for information about how to get the surface so that your application can draw to it.
+ [b]Note:[/b] This will only work in Android builds.
+ </member>
</members>
</class>
diff --git a/modules/openxr/extensions/openxr_composition_layer_extension.cpp b/modules/openxr/extensions/openxr_composition_layer_extension.cpp
index 8a448afc08..83e45ffe7f 100644
--- a/modules/openxr/extensions/openxr_composition_layer_extension.cpp
+++ b/modules/openxr/extensions/openxr_composition_layer_extension.cpp
@@ -30,6 +30,12 @@
#include "openxr_composition_layer_extension.h"
+#ifdef ANDROID_ENABLED
+#include <openxr/openxr.h>
+#include <openxr/openxr_platform.h>
+#endif
+
+#include "platform/android/api/java_class_wrapper.h"
#include "servers/rendering/rendering_server_globals.h"
////////////////////////////////////////////////////////////////////////////
@@ -55,18 +61,37 @@ HashMap<String, bool *> OpenXRCompositionLayerExtension::get_requested_extension
request_extensions[XR_KHR_COMPOSITION_LAYER_CYLINDER_EXTENSION_NAME] = &cylinder_ext_available;
request_extensions[XR_KHR_COMPOSITION_LAYER_EQUIRECT2_EXTENSION_NAME] = &equirect_ext_available;
+#ifdef ANDROID_ENABLED
+ request_extensions[XR_KHR_ANDROID_SURFACE_SWAPCHAIN_EXTENSION_NAME] = &android_surface_ext_available;
+#endif
+
return request_extensions;
}
+void OpenXRCompositionLayerExtension::on_instance_created(const XrInstance p_instance) {
+#ifdef ANDROID_ENABLED
+ EXT_INIT_XR_FUNC(xrDestroySwapchain);
+ EXT_INIT_XR_FUNC(xrCreateSwapchainAndroidSurfaceKHR);
+#endif
+}
+
void OpenXRCompositionLayerExtension::on_session_created(const XrSession p_session) {
OpenXRAPI::get_singleton()->register_composition_layer_provider(this);
}
void OpenXRCompositionLayerExtension::on_session_destroyed() {
OpenXRAPI::get_singleton()->unregister_composition_layer_provider(this);
+
+#ifdef ANDROID_ENABLED
+ free_queued_android_surface_swapchains();
+#endif
}
void OpenXRCompositionLayerExtension::on_pre_render() {
+#ifdef ANDROID_ENABLED
+ free_queued_android_surface_swapchains();
+#endif
+
for (OpenXRViewportCompositionLayerProvider *composition_layer : composition_layers) {
composition_layer->on_pre_render();
}
@@ -113,6 +138,37 @@ bool OpenXRCompositionLayerExtension::is_available(XrStructureType p_which) {
}
}
+#ifdef ANDROID_ENABLED
+bool OpenXRCompositionLayerExtension::create_android_surface_swapchain(XrSwapchainCreateInfo *p_info, XrSwapchain *r_swapchain, jobject *r_surface) {
+ if (android_surface_ext_available) {
+ OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
+ ERR_FAIL_NULL_V(openxr_api, false);
+
+ // @todo We need a way to add to the next pointer chain.
+ XrResult result = xrCreateSwapchainAndroidSurfaceKHR(openxr_api->get_session(), p_info, r_swapchain, r_surface);
+ if (XR_FAILED(result)) {
+ print_line("OpenXR: Failed to create Android surface swapchain [", openxr_api->get_error_string(result), "]");
+ return false;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+void OpenXRCompositionLayerExtension::free_android_surface_swapchain(XrSwapchain p_swapchain) {
+ android_surface_swapchain_free_queue.push_back(p_swapchain);
+}
+
+void OpenXRCompositionLayerExtension::free_queued_android_surface_swapchains() {
+ for (XrSwapchain swapchain : android_surface_swapchain_free_queue) {
+ xrDestroySwapchain(swapchain);
+ }
+ android_surface_swapchain_free_queue.clear();
+}
+#endif
+
////////////////////////////////////////////////////////////////////////////
// OpenXRViewportCompositionLayerProvider
@@ -127,8 +183,12 @@ OpenXRViewportCompositionLayerProvider::~OpenXRViewportCompositionLayerProvider(
extension->on_viewport_composition_layer_destroyed(composition_layer);
}
- // This will reset the viewport and free the swapchain too.
- set_viewport(RID(), Size2i());
+ if (use_android_surface) {
+ free_swapchain();
+ } else {
+ // This will reset the viewport and free the swapchain too.
+ set_viewport(RID(), Size2i());
+ }
}
void OpenXRViewportCompositionLayerProvider::set_alpha_blend(bool p_alpha_blend) {
@@ -143,24 +203,92 @@ void OpenXRViewportCompositionLayerProvider::set_alpha_blend(bool p_alpha_blend)
}
void OpenXRViewportCompositionLayerProvider::set_viewport(RID p_viewport, Size2i p_size) {
+ ERR_FAIL_COND(use_android_surface);
+
RenderingServer *rs = RenderingServer::get_singleton();
ERR_FAIL_NULL(rs);
- if (viewport != p_viewport) {
- if (viewport.is_valid()) {
- RID rt = rs->viewport_get_render_target(viewport);
+ if (subviewport.viewport != p_viewport) {
+ if (subviewport.viewport.is_valid()) {
+ RID rt = rs->viewport_get_render_target(subviewport.viewport);
RSG::texture_storage->render_target_set_override(rt, RID(), RID(), RID());
}
- viewport = p_viewport;
+ subviewport.viewport = p_viewport;
- if (viewport.is_valid()) {
- viewport_size = p_size;
+ if (subviewport.viewport.is_valid()) {
+ subviewport.viewport_size = p_size;
} else {
free_swapchain();
- viewport_size = Size2i();
+ subviewport.viewport_size = Size2i();
+ }
+ }
+}
+
+void OpenXRViewportCompositionLayerProvider::set_use_android_surface(bool p_use_android_surface, Size2i p_size) {
+#ifdef ANDROID_ENABLED
+ if (p_use_android_surface == use_android_surface) {
+ return;
+ }
+
+ use_android_surface = p_use_android_surface;
+
+ if (use_android_surface) {
+ if (!composition_layer_extension->is_android_surface_swapchain_available()) {
+ ERR_PRINT_ONCE("OpenXR: Cannot use Android surface for composition layer because the extension isn't available");
}
+
+ if (subviewport.viewport.is_valid()) {
+ set_viewport(RID(), Size2i());
+ }
+
+ swapchain_size = p_size;
+ } else {
+ free_swapchain();
+ }
+#endif
+}
+
+#ifdef ANDROID_ENABLED
+void OpenXRViewportCompositionLayerProvider::create_android_surface() {
+ ERR_FAIL_COND(android_surface.swapchain != XR_NULL_HANDLE || android_surface.surface.is_valid());
+ ERR_FAIL_COND(!openxr_api || !openxr_api->is_running());
+
+ // The XR_FB_android_surface_swapchain_create extension mandates that format, sampleCount,
+ // faceCount, arraySize, and mipCount must be zero.
+ XrSwapchainCreateInfo info = {
+ XR_TYPE_SWAPCHAIN_CREATE_INFO, // type
+ nullptr, // next
+ 0, // createFlags
+ XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, // usageFlags
+ 0, // format
+ 0, // sampleCount
+ (uint32_t)swapchain_size.x, // width
+ (uint32_t)swapchain_size.y, // height
+ 0, // faceCount
+ 0, // arraySize
+ 0, // mipCount
+ };
+
+ jobject surface;
+ composition_layer_extension->create_android_surface_swapchain(&info, &android_surface.swapchain, &surface);
+
+ if (surface) {
+ android_surface.surface = Ref<JavaObject>(memnew(JavaObject(JavaClassWrapper::get_singleton()->wrap("android.view.Surface"), surface)));
+ }
+}
+#endif
+
+Ref<JavaObject> OpenXRViewportCompositionLayerProvider::get_android_surface() {
+#ifdef ANDROID_ENABLED
+ if (use_android_surface) {
+ if (android_surface.surface.is_null()) {
+ create_android_surface();
+ }
+ return android_surface.surface;
}
+#endif
+ return Ref<JavaObject>();
}
void OpenXRViewportCompositionLayerProvider::set_extension_property_values(const Dictionary &p_extension_property_values) {
@@ -169,16 +297,25 @@ void OpenXRViewportCompositionLayerProvider::set_extension_property_values(const
}
void OpenXRViewportCompositionLayerProvider::on_pre_render() {
+#ifdef ANDROID_ENABLED
+ if (use_android_surface) {
+ if (android_surface.surface.is_null()) {
+ create_android_surface();
+ }
+ return;
+ }
+#endif
+
RenderingServer *rs = RenderingServer::get_singleton();
ERR_FAIL_NULL(rs);
- if (viewport.is_valid() && openxr_api && openxr_api->is_running()) {
- RS::ViewportUpdateMode update_mode = rs->viewport_get_update_mode(viewport);
+ if (subviewport.viewport.is_valid() && openxr_api && openxr_api->is_running()) {
+ RS::ViewportUpdateMode update_mode = rs->viewport_get_update_mode(subviewport.viewport);
if (update_mode == RS::VIEWPORT_UPDATE_ONCE || update_mode == RS::VIEWPORT_UPDATE_ALWAYS) {
// Update our XR swapchain
if (update_and_acquire_swapchain(update_mode == RS::VIEWPORT_UPDATE_ONCE)) {
// Render to our XR swapchain image.
- RID rt = rs->viewport_get_render_target(viewport);
+ RID rt = rs->viewport_get_render_target(subviewport.viewport);
RSG::texture_storage->render_target_set_override(rt, get_current_swapchain_texture(), RID(), RID());
}
}
@@ -196,48 +333,36 @@ XrCompositionLayerBaseHeader *OpenXRViewportCompositionLayerProvider::get_compos
return nullptr;
}
- if (swapchain_info.get_swapchain() == XR_NULL_HANDLE) {
+ XrSwapchainSubImage subimage = {
+ 0, // swapchain
+ { { 0, 0 }, { 0, 0 } }, // imageRect
+ 0, // imageArrayIndex
+ };
+ update_swapchain_sub_image(subimage);
+
+ if (subimage.swapchain == XR_NULL_HANDLE) {
// Don't have a swapchain to display? Ignore our layer.
return nullptr;
}
- if (swapchain_info.is_image_acquired()) {
- swapchain_info.release();
- }
-
// Update the layer struct for the swapchain.
switch (composition_layer->type) {
case XR_TYPE_COMPOSITION_LAYER_QUAD: {
XrCompositionLayerQuad *quad_layer = (XrCompositionLayerQuad *)composition_layer;
quad_layer->space = openxr_api->get_play_space();
- quad_layer->subImage.swapchain = swapchain_info.get_swapchain();
- quad_layer->subImage.imageArrayIndex = 0;
- quad_layer->subImage.imageRect.offset.x = 0;
- quad_layer->subImage.imageRect.offset.y = 0;
- quad_layer->subImage.imageRect.extent.width = swapchain_size.width;
- quad_layer->subImage.imageRect.extent.height = swapchain_size.height;
+ quad_layer->subImage = subimage;
} break;
case XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR: {
XrCompositionLayerCylinderKHR *cylinder_layer = (XrCompositionLayerCylinderKHR *)composition_layer;
cylinder_layer->space = openxr_api->get_play_space();
- cylinder_layer->subImage.swapchain = swapchain_info.get_swapchain();
- cylinder_layer->subImage.imageArrayIndex = 0;
- cylinder_layer->subImage.imageRect.offset.x = 0;
- cylinder_layer->subImage.imageRect.offset.y = 0;
- cylinder_layer->subImage.imageRect.extent.width = swapchain_size.width;
- cylinder_layer->subImage.imageRect.extent.height = swapchain_size.height;
+ cylinder_layer->subImage = subimage;
} break;
case XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR: {
XrCompositionLayerEquirect2KHR *equirect_layer = (XrCompositionLayerEquirect2KHR *)composition_layer;
equirect_layer->space = openxr_api->get_play_space();
- equirect_layer->subImage.swapchain = swapchain_info.get_swapchain();
- equirect_layer->subImage.imageArrayIndex = 0;
- equirect_layer->subImage.imageRect.offset.x = 0;
- equirect_layer->subImage.imageRect.offset.y = 0;
- equirect_layer->subImage.imageRect.extent.width = swapchain_size.width;
- equirect_layer->subImage.imageRect.extent.height = swapchain_size.height;
+ equirect_layer->subImage = subimage;
} break;
default: {
@@ -261,27 +386,49 @@ XrCompositionLayerBaseHeader *OpenXRViewportCompositionLayerProvider::get_compos
return composition_layer;
}
+void OpenXRViewportCompositionLayerProvider::update_swapchain_sub_image(XrSwapchainSubImage &r_subimage) {
+#ifdef ANDROID_ENABLED
+ if (use_android_surface) {
+ r_subimage.swapchain = android_surface.swapchain;
+ } else
+#endif
+ {
+ XrSwapchain swapchain = subviewport.swapchain_info.get_swapchain();
+
+ if (swapchain && subviewport.swapchain_info.is_image_acquired()) {
+ subviewport.swapchain_info.release();
+ }
+
+ r_subimage.swapchain = swapchain;
+ }
+
+ r_subimage.imageRect.extent.width = swapchain_size.width;
+ r_subimage.imageRect.extent.height = swapchain_size.height;
+}
+
bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p_static_image) {
+ ERR_FAIL_COND_V(use_android_surface, false);
+
if (openxr_api == nullptr || composition_layer_extension == nullptr) {
// OpenXR not initialized or we're in the editor?
return false;
}
- if (!composition_layer_extension->is_available(composition_layer->type)) {
+ if (!composition_layer_extension->is_available(get_openxr_type())) {
// Selected type is not supported?
return false;
}
// See if our current swapchain is outdated.
- if (swapchain_info.get_swapchain() != XR_NULL_HANDLE) {
+ if (subviewport.swapchain_info.get_swapchain() != XR_NULL_HANDLE) {
// If this swap chain, or the previous one, were static, then we can't reuse it.
- if (swapchain_size == viewport_size && !p_static_image && !static_image) {
+ if (swapchain_size == subviewport.viewport_size && !p_static_image && !subviewport.static_image) {
// We're all good! Just acquire it.
// We can ignore should_render here, return will be false.
bool should_render = true;
- return swapchain_info.acquire(should_render);
+ return subviewport.swapchain_info.acquire(should_render);
}
- swapchain_info.queue_free();
+ subviewport.swapchain_info.queue_free();
}
// Create our new swap chain
@@ -292,7 +439,7 @@ bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p
if (p_static_image) {
create_flags |= XR_SWAPCHAIN_CREATE_STATIC_IMAGE_BIT;
}
- if (!swapchain_info.create(create_flags, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, swapchain_format, viewport_size.width, viewport_size.height, sample_count, array_size)) {
+ if (!subviewport.swapchain_info.create(create_flags, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, swapchain_format, subviewport.viewport_size.width, subviewport.viewport_size.height, sample_count, array_size)) {
swapchain_size = Size2i();
return false;
}
@@ -300,26 +447,40 @@ bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p
// Acquire our image so we can start rendering into it,
// we can ignore should_render here, ret will be false.
bool should_render = true;
- bool ret = swapchain_info.acquire(should_render);
+ bool ret = subviewport.swapchain_info.acquire(should_render);
- swapchain_size = viewport_size;
- static_image = p_static_image;
+ swapchain_size = subviewport.viewport_size;
+ subviewport.static_image = p_static_image;
return ret;
}
void OpenXRViewportCompositionLayerProvider::free_swapchain() {
- if (swapchain_info.get_swapchain() != XR_NULL_HANDLE) {
- swapchain_info.queue_free();
+#ifdef ANDROID_ENABLED
+ if (use_android_surface) {
+ if (android_surface.swapchain != XR_NULL_HANDLE) {
+ composition_layer_extension->free_android_surface_swapchain(android_surface.swapchain);
+
+ android_surface.swapchain = XR_NULL_HANDLE;
+ android_surface.surface.unref();
+ }
+ } else
+#endif
+ {
+ if (subviewport.swapchain_info.get_swapchain() != XR_NULL_HANDLE) {
+ subviewport.swapchain_info.queue_free();
+ }
+ subviewport.static_image = false;
}
swapchain_size = Size2i();
- static_image = false;
}
RID OpenXRViewportCompositionLayerProvider::get_current_swapchain_texture() {
+ ERR_FAIL_COND_V(use_android_surface, RID());
+
if (openxr_api == nullptr) {
return RID();
}
- return swapchain_info.get_image();
+ return subviewport.swapchain_info.get_image();
}
diff --git a/modules/openxr/extensions/openxr_composition_layer_extension.h b/modules/openxr/extensions/openxr_composition_layer_extension.h
index 34e330a60a..bce34f098c 100644
--- a/modules/openxr/extensions/openxr_composition_layer_extension.h
+++ b/modules/openxr/extensions/openxr_composition_layer_extension.h
@@ -36,6 +36,15 @@
#include "../openxr_api.h"
+#ifdef ANDROID_ENABLED
+#include <jni.h>
+
+// Copied here from openxr_platform.h, in order to avoid including that whole header,
+// which can cause compilation issues on some platforms.
+typedef XrResult(XRAPI_PTR *PFN_xrCreateSwapchainAndroidSurfaceKHR)(XrSession session, const XrSwapchainCreateInfo *info, XrSwapchain *swapchain, jobject *surface);
+#endif
+
+class JavaObject;
class OpenXRViewportCompositionLayerProvider;
// This extension provides access to composition layers for displaying 2D content through the XR compositor.
@@ -49,6 +58,7 @@ public:
virtual ~OpenXRCompositionLayerExtension() override;
virtual HashMap<String, bool *> get_requested_extensions() override;
+ virtual void on_instance_created(const XrInstance p_instance) override;
virtual void on_session_created(const XrSession p_session) override;
virtual void on_session_destroyed() override;
virtual void on_pre_render() override;
@@ -61,6 +71,12 @@ public:
void unregister_viewport_composition_layer_provider(OpenXRViewportCompositionLayerProvider *p_composition_layer);
bool is_available(XrStructureType p_which);
+ bool is_android_surface_swapchain_available() { return android_surface_ext_available; }
+
+#ifdef ANDROID_ENABLED
+ bool create_android_surface_swapchain(XrSwapchainCreateInfo *p_info, XrSwapchain *r_swapchain, jobject *r_surface);
+ void free_android_surface_swapchain(XrSwapchain p_swapchain);
+#endif
private:
static OpenXRCompositionLayerExtension *singleton;
@@ -69,6 +85,15 @@ private:
bool cylinder_ext_available = false;
bool equirect_ext_available = false;
+ bool android_surface_ext_available = false;
+
+#ifdef ANDROID_ENABLED
+ Vector<XrSwapchain> android_surface_swapchain_free_queue;
+ void free_queued_android_surface_swapchains();
+
+ EXT_PROTO_XRRESULT_FUNC1(xrDestroySwapchain, (XrSwapchain), swapchain)
+ EXT_PROTO_XRRESULT_FUNC4(xrCreateSwapchainAndroidSurfaceKHR, (XrSession), session, (const XrSwapchainCreateInfo *), info, (XrSwapchain *), swapchain, (jobject *), surface)
+#endif
};
class OpenXRViewportCompositionLayerProvider {
@@ -78,20 +103,37 @@ class OpenXRViewportCompositionLayerProvider {
Dictionary extension_property_values;
bool extension_property_values_changed = true;
- RID viewport;
- Size2i viewport_size;
-
- OpenXRAPI::OpenXRSwapChainInfo swapchain_info;
+ struct {
+ RID viewport;
+ Size2i viewport_size;
+ OpenXRAPI::OpenXRSwapChainInfo swapchain_info;
+ bool static_image = false;
+ } subviewport;
+
+#ifdef ANDROID_ENABLED
+ struct {
+ XrSwapchain swapchain = XR_NULL_HANDLE;
+ Ref<JavaObject> surface;
+ } android_surface;
+#endif
+
+ bool use_android_surface = false;
Size2i swapchain_size;
- bool static_image = false;
OpenXRAPI *openxr_api = nullptr;
OpenXRCompositionLayerExtension *composition_layer_extension = nullptr;
+ // Only for SubViewports.
bool update_and_acquire_swapchain(bool p_static_image);
- void free_swapchain();
RID get_current_swapchain_texture();
+ void update_swapchain_sub_image(XrSwapchainSubImage &r_swapchain_sub_image);
+ void free_swapchain();
+
+#ifdef ANDROID_ENABLED
+ void create_android_surface();
+#endif
+
public:
XrStructureType get_openxr_type() { return composition_layer->type; }
@@ -102,7 +144,12 @@ public:
bool get_alpha_blend() const { return alpha_blend; }
void set_viewport(RID p_viewport, Size2i p_size);
- RID get_viewport() const { return viewport; }
+ RID get_viewport() const { return subviewport.viewport; }
+
+ void set_use_android_surface(bool p_enable, Size2i p_size);
+ bool get_use_android_surface() const { return use_android_surface; }
+
+ Ref<JavaObject> get_android_surface();
void set_extension_property_values(const Dictionary &p_property_values);
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index ecf7c05789..73b6f6c1c9 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -271,17 +271,16 @@ OpenXRAPI *OpenXRAPI::singleton = nullptr;
Vector<OpenXRExtensionWrapper *> OpenXRAPI::registered_extension_wrappers;
bool OpenXRAPI::openxr_is_enabled(bool p_check_run_in_editor) {
- // @TODO we need an overrule switch so we can force enable openxr, i.e run "godot --openxr_enabled"
-
- if (Engine::get_singleton()->is_editor_hint() && p_check_run_in_editor) {
- // Disabled for now, using XR inside of the editor we'll be working on during the coming months.
- return false;
- } else {
- if (XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT) {
- return GLOBAL_GET("xr/openxr/enabled");
+ if (XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT) {
+ if (Engine::get_singleton()->is_editor_hint() && p_check_run_in_editor) {
+ // For now, don't start OpenXR when the editor starts up. In the future, this may change
+ // if we want to integrate more XR features into the editor experience.
+ return false;
} else {
- return XRServer::get_xr_mode() == XRServer::XRMODE_ON;
+ return GLOBAL_GET("xr/openxr/enabled");
}
+ } else {
+ return XRServer::get_xr_mode() == XRServer::XRMODE_ON;
}
}
@@ -557,14 +556,11 @@ bool OpenXRAPI::create_instance() {
extension_ptrs.push_back(enabled_extensions[i].get_data());
}
- // Get our project name
- String project_name = GLOBAL_GET("application/config/name");
-
// Create our OpenXR instance
XrApplicationInfo application_info{
- "", // applicationName, we'll set this down below
+ "Godot Engine", // applicationName, if we're running a game we'll update this down below.
1, // applicationVersion, we don't currently have this
- "Godot Game Engine", // engineName
+ "Godot Engine", // engineName
VERSION_MAJOR * 10000 + VERSION_MINOR * 100 + VERSION_PATCH, // engineVersion 4.0 -> 40000, 4.0.1 -> 40001, 4.1 -> 40100, etc.
XR_API_VERSION_1_0 // apiVersion
};
@@ -588,7 +584,11 @@ bool OpenXRAPI::create_instance() {
extension_ptrs.ptr() // enabledExtensionNames
};
- copy_string_to_char_buffer(project_name, instance_create_info.applicationInfo.applicationName, XR_MAX_APPLICATION_NAME_SIZE);
+ // Get our project name
+ String project_name = GLOBAL_GET("application/config/name");
+ if (!project_name.is_empty()) {
+ copy_string_to_char_buffer(project_name, instance_create_info.applicationInfo.applicationName, XR_MAX_APPLICATION_NAME_SIZE);
+ }
XrResult result = xrCreateInstance(&instance_create_info, &instance);
ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "Failed to create XR instance.");
@@ -2583,7 +2583,6 @@ OpenXRAPI::OpenXRAPI() {
if (Engine::get_singleton()->is_editor_hint()) {
// Enabled OpenXR in the editor? Adjust our settings for the editor
-
} else {
// Load settings from project settings
int form_factor_setting = GLOBAL_GET("xr/openxr/form_factor");
diff --git a/modules/openxr/scene/openxr_composition_layer.cpp b/modules/openxr/scene/openxr_composition_layer.cpp
index f69a907be9..697369d516 100644
--- a/modules/openxr/scene/openxr_composition_layer.cpp
+++ b/modules/openxr/scene/openxr_composition_layer.cpp
@@ -38,6 +38,8 @@
#include "scene/3d/xr_nodes.h"
#include "scene/main/viewport.h"
+#include "platform/android/api/java_class_wrapper.h"
+
Vector<OpenXRCompositionLayer *> OpenXRCompositionLayer::composition_layer_nodes;
static const char *HOLE_PUNCH_SHADER_CODE =
@@ -47,7 +49,10 @@ static const char *HOLE_PUNCH_SHADER_CODE =
"\tALBEDO = vec3(0.0, 0.0, 0.0);\n"
"}\n";
-OpenXRCompositionLayer::OpenXRCompositionLayer() {
+OpenXRCompositionLayer::OpenXRCompositionLayer(XrCompositionLayerBaseHeader *p_composition_layer) {
+ composition_layer_base_header = p_composition_layer;
+ openxr_layer_provider = memnew(OpenXRViewportCompositionLayerProvider(composition_layer_base_header));
+
openxr_api = OpenXRAPI::get_singleton();
composition_layer_extension = OpenXRCompositionLayerExtension::get_singleton();
@@ -85,6 +90,12 @@ void OpenXRCompositionLayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_layer_viewport", "viewport"), &OpenXRCompositionLayer::set_layer_viewport);
ClassDB::bind_method(D_METHOD("get_layer_viewport"), &OpenXRCompositionLayer::get_layer_viewport);
+ ClassDB::bind_method(D_METHOD("set_use_android_surface", "enable"), &OpenXRCompositionLayer::set_use_android_surface);
+ ClassDB::bind_method(D_METHOD("get_use_android_surface"), &OpenXRCompositionLayer::get_use_android_surface);
+
+ ClassDB::bind_method(D_METHOD("set_android_surface_size", "size"), &OpenXRCompositionLayer::set_android_surface_size);
+ ClassDB::bind_method(D_METHOD("get_android_surface_size"), &OpenXRCompositionLayer::get_android_surface_size);
+
ClassDB::bind_method(D_METHOD("set_enable_hole_punch", "enable"), &OpenXRCompositionLayer::set_enable_hole_punch);
ClassDB::bind_method(D_METHOD("get_enable_hole_punch"), &OpenXRCompositionLayer::get_enable_hole_punch);
@@ -94,11 +105,14 @@ void OpenXRCompositionLayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_alpha_blend", "enabled"), &OpenXRCompositionLayer::set_alpha_blend);
ClassDB::bind_method(D_METHOD("get_alpha_blend"), &OpenXRCompositionLayer::get_alpha_blend);
+ ClassDB::bind_method(D_METHOD("get_android_surface"), &OpenXRCompositionLayer::get_android_surface);
ClassDB::bind_method(D_METHOD("is_natively_supported"), &OpenXRCompositionLayer::is_natively_supported);
ClassDB::bind_method(D_METHOD("intersects_ray", "origin", "direction"), &OpenXRCompositionLayer::intersects_ray);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "layer_viewport", PROPERTY_HINT_NODE_TYPE, "SubViewport"), "set_layer_viewport", "get_layer_viewport");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_android_surface", PROPERTY_HINT_NONE, ""), "set_use_android_surface", "get_use_android_surface");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "android_surface_size", PROPERTY_HINT_NONE, ""), "set_android_surface_size", "get_android_surface_size");
ADD_PROPERTY(PropertyInfo(Variant::INT, "sort_order", PROPERTY_HINT_NONE, ""), "set_sort_order", "get_sort_order");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "alpha_blend", PROPERTY_HINT_NONE, ""), "set_alpha_blend", "get_alpha_blend");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_hole_punch", PROPERTY_HINT_NONE, ""), "set_enable_hole_punch", "get_enable_hole_punch");
@@ -108,7 +122,7 @@ bool OpenXRCompositionLayer::_should_use_fallback_node() {
if (Engine::get_singleton()->is_editor_hint()) {
return true;
} else if (openxr_session_running) {
- return enable_hole_punch || !is_natively_supported();
+ return enable_hole_punch || (!is_natively_supported() && !use_android_surface);
}
return false;
}
@@ -128,10 +142,36 @@ void OpenXRCompositionLayer::_remove_fallback_node() {
fallback = nullptr;
}
+void OpenXRCompositionLayer::_setup_composition_layer_provider() {
+ if (use_android_surface || layer_viewport) {
+ if (composition_layer_extension) {
+ composition_layer_extension->register_viewport_composition_layer_provider(openxr_layer_provider);
+ }
+
+ // NOTE: We don't setup/clear when using Android surfaces, so we don't destroy the surface unexpectedly.
+ if (layer_viewport) {
+ // Set our properties on the layer provider, which will create all the necessary resources (ex swap chains).
+ openxr_layer_provider->set_viewport(layer_viewport->get_viewport_rid(), layer_viewport->get_size());
+ }
+ }
+}
+
+void OpenXRCompositionLayer::_clear_composition_layer_provider() {
+ if (composition_layer_extension) {
+ composition_layer_extension->unregister_viewport_composition_layer_provider(openxr_layer_provider);
+ }
+
+ // NOTE: We don't setup/clear when using Android surfaces, so we don't destroy the surface unexpectedly.
+ if (!use_android_surface) {
+ // This will reset the viewport and free all the resources (ex swap chains) used by the layer.
+ openxr_layer_provider->set_viewport(RID(), Size2i());
+ }
+}
+
void OpenXRCompositionLayer::_on_openxr_session_begun() {
openxr_session_running = true;
- if (layer_viewport && is_natively_supported() && is_visible() && is_inside_tree()) {
- openxr_layer_provider->set_viewport(layer_viewport->get_viewport_rid(), layer_viewport->get_size());
+ if (is_natively_supported() && is_visible() && is_inside_tree()) {
+ _setup_composition_layer_provider();
}
if (!fallback && _should_use_fallback_node()) {
_create_fallback_node();
@@ -142,9 +182,8 @@ void OpenXRCompositionLayer::_on_openxr_session_stopping() {
openxr_session_running = false;
if (fallback && !_should_use_fallback_node()) {
_remove_fallback_node();
- } else {
- openxr_layer_provider->set_viewport(RID(), Size2i());
}
+ _clear_composition_layer_provider();
}
void OpenXRCompositionLayer::update_fallback_mesh() {
@@ -162,6 +201,7 @@ XrPosef OpenXRCompositionLayer::get_openxr_pose() {
}
bool OpenXRCompositionLayer::is_viewport_in_use(SubViewport *p_viewport) {
+ ERR_FAIL_NULL_V(p_viewport, false);
for (const OpenXRCompositionLayer *other_composition_layer : composition_layer_nodes) {
if (other_composition_layer != this && other_composition_layer->is_inside_tree() && other_composition_layer->get_layer_viewport() == p_viewport) {
return true;
@@ -178,6 +218,9 @@ void OpenXRCompositionLayer::set_layer_viewport(SubViewport *p_viewport) {
if (p_viewport != nullptr) {
ERR_FAIL_COND_EDMSG(is_viewport_in_use(p_viewport), RTR("Cannot use the same SubViewport with multiple OpenXR composition layers. Clear it from its current layer first."));
}
+ if (use_android_surface) {
+ ERR_FAIL_COND_MSG(p_viewport != nullptr, RTR("Cannot set SubViewport on an OpenXR composition layer when using an Android surface."));
+ }
layer_viewport = p_viewport;
@@ -200,6 +243,41 @@ void OpenXRCompositionLayer::set_layer_viewport(SubViewport *p_viewport) {
}
}
+void OpenXRCompositionLayer::set_use_android_surface(bool p_use_android_surface) {
+ if (use_android_surface == p_use_android_surface) {
+ return;
+ }
+
+ use_android_surface = p_use_android_surface;
+ if (use_android_surface) {
+ set_layer_viewport(nullptr);
+ openxr_layer_provider->set_use_android_surface(true, android_surface_size);
+ } else {
+ openxr_layer_provider->set_use_android_surface(false, Size2i());
+ }
+
+ notify_property_list_changed();
+}
+
+bool OpenXRCompositionLayer::get_use_android_surface() const {
+ return use_android_surface;
+}
+
+void OpenXRCompositionLayer::set_android_surface_size(Size2i p_size) {
+ if (android_surface_size == p_size) {
+ return;
+ }
+
+ android_surface_size = p_size;
+ if (use_android_surface) {
+ openxr_layer_provider->set_use_android_surface(true, android_surface_size);
+ }
+}
+
+Size2i OpenXRCompositionLayer::get_android_surface_size() const {
+ return android_surface_size;
+}
+
SubViewport *OpenXRCompositionLayer::get_layer_viewport() const {
return layer_viewport;
}
@@ -228,33 +306,23 @@ bool OpenXRCompositionLayer::get_enable_hole_punch() const {
}
void OpenXRCompositionLayer::set_sort_order(int p_order) {
- if (openxr_layer_provider) {
- openxr_layer_provider->set_sort_order(p_order);
- update_configuration_warnings();
- }
+ openxr_layer_provider->set_sort_order(p_order);
+ update_configuration_warnings();
}
int OpenXRCompositionLayer::get_sort_order() const {
- if (openxr_layer_provider) {
- return openxr_layer_provider->get_sort_order();
- }
- return 1;
+ return openxr_layer_provider->get_sort_order();
}
void OpenXRCompositionLayer::set_alpha_blend(bool p_alpha_blend) {
- if (openxr_layer_provider) {
- openxr_layer_provider->set_alpha_blend(p_alpha_blend);
- if (fallback) {
- _reset_fallback_material();
- }
+ openxr_layer_provider->set_alpha_blend(p_alpha_blend);
+ if (fallback) {
+ _reset_fallback_material();
}
}
bool OpenXRCompositionLayer::get_alpha_blend() const {
- if (openxr_layer_provider) {
- return openxr_layer_provider->get_alpha_blend();
- }
- return false;
+ return openxr_layer_provider->get_alpha_blend();
}
bool OpenXRCompositionLayer::is_natively_supported() const {
@@ -264,6 +332,10 @@ bool OpenXRCompositionLayer::is_natively_supported() const {
return false;
}
+Ref<JavaObject> OpenXRCompositionLayer::get_android_surface() {
+ return openxr_layer_provider->get_android_surface();
+}
+
Vector2 OpenXRCompositionLayer::intersects_ray(const Vector3 &p_origin, const Vector3 &p_direction) const {
return Vector2(-1.0, -1.0);
}
@@ -301,10 +373,7 @@ void OpenXRCompositionLayer::_reset_fallback_material() {
Ref<ViewportTexture> texture = material->get_texture(StandardMaterial3D::TEXTURE_ALBEDO);
if (texture.is_null()) {
- texture.instantiate();
- // ViewportTexture can't be configured without a local scene, so use this hack to set it.
- HashMap<Ref<Resource>, Ref<Resource>> remap_cache;
- texture->configure_for_local_scene(this, remap_cache);
+ texture = layer_viewport->get_texture();
}
Node *loc_scene = texture->get_local_scene();
@@ -321,12 +390,10 @@ void OpenXRCompositionLayer::_notification(int p_what) {
case NOTIFICATION_POSTINITIALIZE: {
composition_layer_nodes.push_back(this);
- if (openxr_layer_provider) {
- for (OpenXRExtensionWrapper *extension : OpenXRAPI::get_registered_extension_wrappers()) {
- extension_property_values.merge(extension->get_viewport_composition_layer_extension_property_defaults());
- }
- openxr_layer_provider->set_extension_property_values(extension_property_values);
+ for (OpenXRExtensionWrapper *extension : OpenXRAPI::get_registered_extension_wrappers()) {
+ extension_property_values.merge(extension->get_viewport_composition_layer_extension_property_defaults());
}
+ openxr_layer_provider->set_extension_property_values(extension_property_values);
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
if (fallback) {
@@ -339,10 +406,10 @@ void OpenXRCompositionLayer::_notification(int p_what) {
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
if (!fallback && openxr_session_running && is_inside_tree()) {
- if (layer_viewport && is_visible()) {
- openxr_layer_provider->set_viewport(layer_viewport->get_viewport_rid(), layer_viewport->get_size());
+ if (is_visible()) {
+ _setup_composition_layer_provider();
} else {
- openxr_layer_provider->set_viewport(RID(), Size2i());
+ _clear_composition_layer_provider();
}
}
update_configuration_warnings();
@@ -351,25 +418,15 @@ void OpenXRCompositionLayer::_notification(int p_what) {
update_configuration_warnings();
} break;
case NOTIFICATION_ENTER_TREE: {
- if (composition_layer_extension) {
- composition_layer_extension->register_viewport_composition_layer_provider(openxr_layer_provider);
- }
-
- if (is_viewport_in_use(layer_viewport)) {
- set_layer_viewport(nullptr);
- } else if (!fallback && layer_viewport && openxr_session_running && is_visible()) {
- openxr_layer_provider->set_viewport(layer_viewport->get_viewport_rid(), layer_viewport->get_size());
+ if (layer_viewport && is_viewport_in_use(layer_viewport)) {
+ _clear_composition_layer_provider();
+ } else if (openxr_session_running && is_visible()) {
+ _setup_composition_layer_provider();
}
} break;
case NOTIFICATION_EXIT_TREE: {
- if (composition_layer_extension) {
- composition_layer_extension->unregister_viewport_composition_layer_provider(openxr_layer_provider);
- }
-
- if (!fallback) {
- // This will clean up existing resources.
- openxr_layer_provider->set_viewport(RID(), Size2i());
- }
+ // This will clean up existing resources.
+ _clear_composition_layer_provider();
} break;
}
}
@@ -401,13 +458,27 @@ bool OpenXRCompositionLayer::_get(const StringName &p_property, Variant &r_value
bool OpenXRCompositionLayer::_set(const StringName &p_property, const Variant &p_value) {
extension_property_values[p_property] = p_value;
- if (openxr_layer_provider) {
- openxr_layer_provider->set_extension_property_values(extension_property_values);
- }
+ openxr_layer_provider->set_extension_property_values(extension_property_values);
return true;
}
+void OpenXRCompositionLayer::_validate_property(PropertyInfo &p_property) const {
+ if (p_property.name == "layer_viewport") {
+ if (use_android_surface) {
+ p_property.usage &= ~PROPERTY_USAGE_EDITOR;
+ } else {
+ p_property.usage |= PROPERTY_USAGE_EDITOR;
+ }
+ } else if (p_property.name == "android_surface_size") {
+ if (use_android_surface) {
+ p_property.usage |= PROPERTY_USAGE_EDITOR;
+ } else {
+ p_property.usage &= ~PROPERTY_USAGE_EDITOR;
+ }
+ }
+}
+
PackedStringArray OpenXRCompositionLayer::get_configuration_warnings() const {
PackedStringArray warnings = Node3D::get_configuration_warnings();
diff --git a/modules/openxr/scene/openxr_composition_layer.h b/modules/openxr/scene/openxr_composition_layer.h
index 55cae27d23..26b40236d2 100644
--- a/modules/openxr/scene/openxr_composition_layer.h
+++ b/modules/openxr/scene/openxr_composition_layer.h
@@ -35,6 +35,7 @@
#include "scene/3d/node_3d.h"
+class JavaObject;
class MeshInstance3D;
class Mesh;
class OpenXRAPI;
@@ -45,7 +46,12 @@ class SubViewport;
class OpenXRCompositionLayer : public Node3D {
GDCLASS(OpenXRCompositionLayer, Node3D);
+ XrCompositionLayerBaseHeader *composition_layer_base_header = nullptr;
+ OpenXRViewportCompositionLayerProvider *openxr_layer_provider = nullptr;
+
SubViewport *layer_viewport = nullptr;
+ bool use_android_surface = false;
+ Size2i android_surface_size = Size2i(1024, 1024);
bool enable_hole_punch = false;
MeshInstance3D *fallback = nullptr;
bool should_update_fallback_mesh = false;
@@ -58,10 +64,12 @@ class OpenXRCompositionLayer : public Node3D {
void _reset_fallback_material();
void _remove_fallback_node();
+ void _setup_composition_layer_provider();
+ void _clear_composition_layer_provider();
+
protected:
OpenXRAPI *openxr_api = nullptr;
OpenXRCompositionLayerExtension *composition_layer_extension = nullptr;
- OpenXRViewportCompositionLayerProvider *openxr_layer_provider = nullptr;
static void _bind_methods();
@@ -69,6 +77,7 @@ protected:
void _get_property_list(List<PropertyInfo> *p_property_list) const;
bool _get(const StringName &p_property, Variant &r_value) const;
bool _set(const StringName &p_property, const Variant &p_value);
+ void _validate_property(PropertyInfo &p_property) const;
virtual void _on_openxr_session_begun();
virtual void _on_openxr_session_stopping();
@@ -82,10 +91,18 @@ protected:
static Vector<OpenXRCompositionLayer *> composition_layer_nodes;
bool is_viewport_in_use(SubViewport *p_viewport);
+ OpenXRCompositionLayer(XrCompositionLayerBaseHeader *p_composition_layer);
+
public:
void set_layer_viewport(SubViewport *p_viewport);
SubViewport *get_layer_viewport() const;
+ void set_use_android_surface(bool p_use_android_surface);
+ bool get_use_android_surface() const;
+
+ void set_android_surface_size(Size2i p_size);
+ Size2i get_android_surface_size() const;
+
void set_enable_hole_punch(bool p_enable);
bool get_enable_hole_punch() const;
@@ -95,13 +112,13 @@ public:
void set_alpha_blend(bool p_alpha_blend);
bool get_alpha_blend() const;
+ Ref<JavaObject> get_android_surface();
bool is_natively_supported() const;
virtual PackedStringArray get_configuration_warnings() const override;
virtual Vector2 intersects_ray(const Vector3 &p_origin, const Vector3 &p_direction) const;
- OpenXRCompositionLayer();
~OpenXRCompositionLayer();
};
diff --git a/modules/openxr/scene/openxr_composition_layer_cylinder.cpp b/modules/openxr/scene/openxr_composition_layer_cylinder.cpp
index 6c8d2fc885..727586467a 100644
--- a/modules/openxr/scene/openxr_composition_layer_cylinder.cpp
+++ b/modules/openxr/scene/openxr_composition_layer_cylinder.cpp
@@ -38,20 +38,8 @@
#include "scene/main/viewport.h"
#include "scene/resources/mesh.h"
-OpenXRCompositionLayerCylinder::OpenXRCompositionLayerCylinder() {
- composition_layer = {
- XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR, // type
- nullptr, // next
- 0, // layerFlags
- XR_NULL_HANDLE, // space
- XR_EYE_VISIBILITY_BOTH, // eyeVisibility
- {}, // subImage
- { { 0, 0, 0, 0 }, { 0, 0, 0 } }, // pose
- radius, // radius
- central_angle, // centralAngle
- aspect_ratio, // aspectRatio
- };
- openxr_layer_provider = memnew(OpenXRViewportCompositionLayerProvider((XrCompositionLayerBaseHeader *)&composition_layer));
+OpenXRCompositionLayerCylinder::OpenXRCompositionLayerCylinder() :
+ OpenXRCompositionLayer((XrCompositionLayerBaseHeader *)&composition_layer) {
XRServer::get_singleton()->connect("reference_frame_changed", callable_mp(this, &OpenXRCompositionLayerCylinder::update_transform));
}
diff --git a/modules/openxr/scene/openxr_composition_layer_cylinder.h b/modules/openxr/scene/openxr_composition_layer_cylinder.h
index 9bd5a42d36..a701575972 100644
--- a/modules/openxr/scene/openxr_composition_layer_cylinder.h
+++ b/modules/openxr/scene/openxr_composition_layer_cylinder.h
@@ -38,7 +38,18 @@
class OpenXRCompositionLayerCylinder : public OpenXRCompositionLayer {
GDCLASS(OpenXRCompositionLayerCylinder, OpenXRCompositionLayer);
- XrCompositionLayerCylinderKHR composition_layer;
+ XrCompositionLayerCylinderKHR composition_layer = {
+ XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR, // type
+ nullptr, // next
+ 0, // layerFlags
+ XR_NULL_HANDLE, // space
+ XR_EYE_VISIBILITY_BOTH, // eyeVisibility
+ {}, // subImage
+ { { 0, 0, 0, 0 }, { 0, 0, 0 } }, // pose
+ 1.0, // radius
+ Math_PI / 2.0, // centralAngle
+ 1.0, // aspectRatio
+ };
float radius = 1.0;
float aspect_ratio = 1.0;
diff --git a/modules/openxr/scene/openxr_composition_layer_equirect.cpp b/modules/openxr/scene/openxr_composition_layer_equirect.cpp
index b6f5d27ffe..2fce26c965 100644
--- a/modules/openxr/scene/openxr_composition_layer_equirect.cpp
+++ b/modules/openxr/scene/openxr_composition_layer_equirect.cpp
@@ -38,21 +38,8 @@
#include "scene/main/viewport.h"
#include "scene/resources/mesh.h"
-OpenXRCompositionLayerEquirect::OpenXRCompositionLayerEquirect() {
- composition_layer = {
- XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR, // type
- nullptr, // next
- 0, // layerFlags
- XR_NULL_HANDLE, // space
- XR_EYE_VISIBILITY_BOTH, // eyeVisibility
- {}, // subImage
- { { 0, 0, 0, 0 }, { 0, 0, 0 } }, // pose
- radius, // radius
- central_horizontal_angle, // centralHorizontalAngle
- upper_vertical_angle, // upperVerticalAngle
- -lower_vertical_angle, // lowerVerticalAngle
- };
- openxr_layer_provider = memnew(OpenXRViewportCompositionLayerProvider((XrCompositionLayerBaseHeader *)&composition_layer));
+OpenXRCompositionLayerEquirect::OpenXRCompositionLayerEquirect() :
+ OpenXRCompositionLayer((XrCompositionLayerBaseHeader *)&composition_layer) {
XRServer::get_singleton()->connect("reference_frame_changed", callable_mp(this, &OpenXRCompositionLayerEquirect::update_transform));
}
diff --git a/modules/openxr/scene/openxr_composition_layer_equirect.h b/modules/openxr/scene/openxr_composition_layer_equirect.h
index af6cd92cbe..45a65fe5aa 100644
--- a/modules/openxr/scene/openxr_composition_layer_equirect.h
+++ b/modules/openxr/scene/openxr_composition_layer_equirect.h
@@ -38,7 +38,19 @@
class OpenXRCompositionLayerEquirect : public OpenXRCompositionLayer {
GDCLASS(OpenXRCompositionLayerEquirect, OpenXRCompositionLayer);
- XrCompositionLayerEquirect2KHR composition_layer;
+ XrCompositionLayerEquirect2KHR composition_layer = {
+ XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR, // type
+ nullptr, // next
+ 0, // layerFlags
+ XR_NULL_HANDLE, // space
+ XR_EYE_VISIBILITY_BOTH, // eyeVisibility
+ {}, // subImage
+ { { 0, 0, 0, 0 }, { 0, 0, 0 } }, // pose
+ 1.0, // radius
+ Math_PI / 2.0, // centralHorizontalAngle
+ Math_PI / 4.0, // upperVerticalAngle
+ -Math_PI / 4.0, // lowerVerticalAngle
+ };
float radius = 1.0;
float central_horizontal_angle = Math_PI / 2.0;
diff --git a/modules/openxr/scene/openxr_composition_layer_quad.cpp b/modules/openxr/scene/openxr_composition_layer_quad.cpp
index 21919496d6..4a00fd371e 100644
--- a/modules/openxr/scene/openxr_composition_layer_quad.cpp
+++ b/modules/openxr/scene/openxr_composition_layer_quad.cpp
@@ -38,18 +38,8 @@
#include "scene/main/viewport.h"
#include "scene/resources/3d/primitive_meshes.h"
-OpenXRCompositionLayerQuad::OpenXRCompositionLayerQuad() {
- composition_layer = {
- XR_TYPE_COMPOSITION_LAYER_QUAD, // type
- nullptr, // next
- 0, // layerFlags
- XR_NULL_HANDLE, // space
- XR_EYE_VISIBILITY_BOTH, // eyeVisibility
- {}, // subImage
- { { 0, 0, 0, 0 }, { 0, 0, 0 } }, // pose
- { (float)quad_size.x, (float)quad_size.y }, // size
- };
- openxr_layer_provider = memnew(OpenXRViewportCompositionLayerProvider((XrCompositionLayerBaseHeader *)&composition_layer));
+OpenXRCompositionLayerQuad::OpenXRCompositionLayerQuad() :
+ OpenXRCompositionLayer((XrCompositionLayerBaseHeader *)&composition_layer) {
XRServer::get_singleton()->connect("reference_frame_changed", callable_mp(this, &OpenXRCompositionLayerQuad::update_transform));
}
diff --git a/modules/openxr/scene/openxr_composition_layer_quad.h b/modules/openxr/scene/openxr_composition_layer_quad.h
index 0c3172dbb2..a4ccfc6d8e 100644
--- a/modules/openxr/scene/openxr_composition_layer_quad.h
+++ b/modules/openxr/scene/openxr_composition_layer_quad.h
@@ -38,7 +38,16 @@
class OpenXRCompositionLayerQuad : public OpenXRCompositionLayer {
GDCLASS(OpenXRCompositionLayerQuad, OpenXRCompositionLayer);
- XrCompositionLayerQuad composition_layer;
+ XrCompositionLayerQuad composition_layer = {
+ XR_TYPE_COMPOSITION_LAYER_QUAD, // type
+ nullptr, // next
+ 0, // layerFlags
+ XR_NULL_HANDLE, // space
+ XR_EYE_VISIBILITY_BOTH, // eyeVisibility
+ {}, // subImage
+ { { 0, 0, 0, 0 }, { 0, 0, 0 } }, // pose
+ { 1.0, 1.0 }, // size
+ };
Size2 quad_size = Size2(1.0, 1.0);
diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp
index ff032c88c6..c89534a60c 100644
--- a/modules/vorbis/audio_stream_ogg_vorbis.cpp
+++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp
@@ -390,6 +390,9 @@ Ref<AudioSamplePlayback> AudioStreamPlaybackOggVorbis::get_sample_playback() con
void AudioStreamPlaybackOggVorbis::set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) {
sample_playback = p_playback;
+ if (sample_playback.is_valid()) {
+ sample_playback->stream_playback = Ref<AudioStreamPlayback>(this);
+ }
}
AudioStreamPlaybackOggVorbis::~AudioStreamPlaybackOggVorbis() {
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index f8ac591a78..d78f8db79c 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -2276,6 +2276,11 @@ String EditorExportPlatformAndroid::get_apksigner_path(int p_target_sdk, bool p_
bool failed = false;
String version_to_use;
+ String java_sdk_path = EDITOR_GET("export/android/java_sdk_path");
+ if (!java_sdk_path.is_empty()) {
+ OS::get_singleton()->set_environment("JAVA_HOME", java_sdk_path);
+ }
+
List<String> args;
args.push_back("--version");
String output;
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle
index 05b4f379b3..b9d15deec9 100644
--- a/platform/android/java/app/build.gradle
+++ b/platform/android/java/app/build.gradle
@@ -12,6 +12,7 @@ allprojects {
mavenCentral()
gradlePluginPortal()
maven { url "https://plugins.gradle.org/m2/" }
+ maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/"}
// Godot user plugins custom maven repos
String[] mavenRepos = getGodotPluginsMavenRepos()
diff --git a/platform/android/java/app/settings.gradle b/platform/android/java/app/settings.gradle
index dcac44e393..e758d4e99a 100644
--- a/platform/android/java/app/settings.gradle
+++ b/platform/android/java/app/settings.gradle
@@ -11,6 +11,7 @@ pluginManagement {
mavenCentral()
gradlePluginPortal()
maven { url "https://plugins.gradle.org/m2/" }
+ maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/"}
}
}
diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle
index 771bda6948..974f072c18 100644
--- a/platform/android/java/build.gradle
+++ b/platform/android/java/build.gradle
@@ -18,12 +18,14 @@ allprojects {
mavenCentral()
gradlePluginPortal()
maven { url "https://plugins.gradle.org/m2/" }
+ maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/"}
}
}
ext {
supportedAbis = ["arm32", "arm64", "x86_32", "x86_64"]
supportedFlavors = ["editor", "template"]
+ supportedEditorVendors = ["google", "meta"]
supportedFlavorsBuildTypes = [
"editor": ["dev", "debug", "release"],
"template": ["dev", "debug", "release"]
@@ -92,15 +94,20 @@ def templateExcludedBuildTask() {
/**
* Generates the build tasks for the given flavor
* @param flavor Must be one of the supported flavors ('template' / 'editor')
+ * @param editorVendor Must be one of the supported editor vendors ('google' / 'meta')
*/
-def generateBuildTasks(String flavor = "template") {
+def generateBuildTasks(String flavor = "template", String editorVendor = "google") {
if (!supportedFlavors.contains(flavor)) {
throw new GradleException("Invalid build flavor: $flavor")
}
+ if (!supportedEditorVendors.contains(editorVendor)) {
+ throw new GradleException("Invalid editor vendor: $editorVendor")
+ }
+ String capitalizedEditorVendor = editorVendor.capitalize()
def buildTasks = []
- // Only build the apks and aar files for which we have native shared libraries unless we intend
+ // Only build the binary files for which we have native shared libraries unless we intend
// to run the scons build tasks.
boolean excludeSconsBuildTasks = excludeSconsBuildTasks()
boolean isTemplate = flavor == "template"
@@ -163,28 +170,28 @@ def generateBuildTasks(String flavor = "template") {
}
} else {
// Copy the generated editor apk to the bin directory.
- String copyEditorApkTaskName = "copyEditor${capitalizedTarget}ApkToBin"
+ String copyEditorApkTaskName = "copyEditor${capitalizedEditorVendor}${capitalizedTarget}ApkToBin"
if (tasks.findByName(copyEditorApkTaskName) != null) {
buildTasks += tasks.getByName(copyEditorApkTaskName)
} else {
buildTasks += tasks.create(name: copyEditorApkTaskName, type: Copy) {
- dependsOn ":editor:assemble${capitalizedTarget}"
- from("editor/build/outputs/apk/${target}")
+ dependsOn ":editor:assemble${capitalizedEditorVendor}${capitalizedTarget}"
+ from("editor/build/outputs/apk/${editorVendor}/${target}")
into(androidEditorBuildsDir)
- include("android_editor-${target}*.apk")
+ include("android_editor-${editorVendor}-${target}*.apk")
}
}
// Copy the generated editor aab to the bin directory.
- String copyEditorAabTaskName = "copyEditor${capitalizedTarget}AabToBin"
+ String copyEditorAabTaskName = "copyEditor${capitalizedEditorVendor}${capitalizedTarget}AabToBin"
if (tasks.findByName(copyEditorAabTaskName) != null) {
buildTasks += tasks.getByName(copyEditorAabTaskName)
} else {
buildTasks += tasks.create(name: copyEditorAabTaskName, type: Copy) {
- dependsOn ":editor:bundle${capitalizedTarget}"
- from("editor/build/outputs/bundle/${target}")
+ dependsOn ":editor:bundle${capitalizedEditorVendor}${capitalizedTarget}"
+ from("editor/build/outputs/bundle/${editorVendor}${capitalizedTarget}")
into(androidEditorBuildsDir)
- include("android_editor-${target}*.aab")
+ include("android_editor-${editorVendor}-${target}*.aab")
}
}
}
@@ -197,15 +204,27 @@ def generateBuildTasks(String flavor = "template") {
}
/**
- * Generate the Godot Editor Android apk.
+ * Generate the Godot Editor Android binaries.
*
* Note: Unless the 'generateNativeLibs` argument is specified, the Godot 'tools' shared libraries
* must have been generated (via scons) prior to running this gradle task.
- * The task will only build the apk(s) for which the shared libraries is available.
+ * The task will only build the binaries for which the shared libraries is available.
*/
task generateGodotEditor {
gradle.startParameter.excludedTaskNames += templateExcludedBuildTask()
- dependsOn = generateBuildTasks("editor")
+ dependsOn = generateBuildTasks("editor", "google")
+}
+
+/**
+ * Generate the Godot Editor Android binaries for Meta devices.
+ *
+ * Note: Unless the 'generateNativeLibs` argument is specified, the Godot 'tools' shared libraries
+ * must have been generated (via scons) prior to running this gradle task.
+ * The task will only build the binaries for which the shared libraries is available.
+ */
+task generateGodotMetaEditor {
+ gradle.startParameter.excludedTaskNames += templateExcludedBuildTask()
+ dependsOn = generateBuildTasks("editor", "meta")
}
/**
diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle
index b8b4233636..54d6b9b5f3 100644
--- a/platform/android/java/editor/build.gradle
+++ b/platform/android/java/editor/build.gradle
@@ -5,16 +5,6 @@ plugins {
id 'base'
}
-dependencies {
- implementation "androidx.fragment:fragment:$versions.fragmentVersion"
- implementation project(":lib")
-
- implementation "androidx.window:window:1.3.0"
- implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion"
- implementation "androidx.constraintlayout:constraintlayout:2.1.4"
- implementation "org.bouncycastle:bcprov-jdk15to18:1.77"
-}
-
ext {
// Retrieve the build number from the environment variable; default to 0 if none is specified.
// The build number is added as a suffix to the version code for upload to the Google Play store.
@@ -154,4 +144,37 @@ android {
doNotStrip '**/*.so'
}
}
+
+ flavorDimensions = ["vendor"]
+ productFlavors {
+ google {
+ dimension "vendor"
+ missingDimensionStrategy 'products', 'editor'
+ }
+ meta {
+ dimension "vendor"
+ missingDimensionStrategy 'products', 'editor'
+ ndk {
+ //noinspection ChromeOsAbiSupport
+ abiFilters "arm64-v8a"
+ }
+ applicationIdSuffix ".meta"
+ versionNameSuffix "-meta"
+ minSdkVersion 23
+ targetSdkVersion 32
+ }
+ }
+}
+
+dependencies {
+ implementation "androidx.fragment:fragment:$versions.fragmentVersion"
+ implementation project(":lib")
+
+ implementation "androidx.window:window:1.3.0"
+ implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion"
+ implementation "androidx.constraintlayout:constraintlayout:2.1.4"
+ implementation "org.bouncycastle:bcprov-jdk15to18:1.77"
+
+ // Meta dependencies
+ metaImplementation "org.godotengine:godot-openxr-vendors-meta:3.0.0-stable"
}
diff --git a/core/object/object.compat.inc b/platform/android/java/editor/src/google/java/org/godotengine/editor/GodotEditor.kt
index bf1e99fc9b..f15d9f7768 100644
--- a/core/object/object.compat.inc
+++ b/platform/android/java/editor/src/google/java/org/godotengine/editor/GodotEditor.kt
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* object.compat.inc */
+/* GodotEditor.kt */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,13 +28,12 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef DISABLE_DEPRECATED
+package org.godotengine.editor
-#include "core/object/class_db.h"
-
-void Object::_bind_compatibility_methods() {
- ClassDB::bind_compatibility_method(D_METHOD("tr", "message", "context"), &Object::tr, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("tr_n", "message", "plural_message", "n", "context"), &Object::tr_n, DEFVAL(""));
+/**
+ * Primary window of the Godot Editor.
+ *
+ * This is the implementation of the editor used when running on regular Android devices.
+ */
+open class GodotEditor : BaseGodotEditor() {
}
-
-#endif
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/BaseGodotEditor.kt
index 405b2fb57f..d296d6ad03 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/BaseGodotEditor.kt
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* GodotEditor.kt */
+/* BaseGodotEditor.kt */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -52,6 +52,8 @@ import org.godotengine.godot.GodotLib
import org.godotengine.godot.error.Error
import org.godotengine.godot.utils.PermissionsUtil
import org.godotengine.godot.utils.ProcessPhoenix
+import org.godotengine.godot.utils.isHorizonOSDevice
+import org.godotengine.godot.utils.isNativeXRDevice
import java.util.*
import kotlin.math.min
@@ -61,13 +63,11 @@ import kotlin.math.min
* This provides the basic templates for the activities making up this application.
* Each derived activity runs in its own process, which enable up to have several instances of
* the Godot engine up and running at the same time.
- *
- * It also plays the role of the primary editor window.
*/
-open class GodotEditor : GodotActivity() {
+abstract class BaseGodotEditor : GodotActivity() {
companion object {
- private val TAG = GodotEditor::class.java.simpleName
+ private val TAG = BaseGodotEditor::class.java.simpleName
private const val WAIT_FOR_DEBUGGER = false
@@ -81,12 +81,13 @@ open class GodotEditor : GodotActivity() {
// Command line arguments
private const val FULLSCREEN_ARG = "--fullscreen"
private const val FULLSCREEN_ARG_SHORT = "-f"
- private const val EDITOR_ARG = "--editor"
- private const val EDITOR_ARG_SHORT = "-e"
- private const val EDITOR_PROJECT_MANAGER_ARG = "--project-manager"
- private const val EDITOR_PROJECT_MANAGER_ARG_SHORT = "-p"
- private const val BREAKPOINTS_ARG = "--breakpoints"
- private const val BREAKPOINTS_ARG_SHORT = "-b"
+ internal const val EDITOR_ARG = "--editor"
+ internal const val EDITOR_ARG_SHORT = "-e"
+ internal const val EDITOR_PROJECT_MANAGER_ARG = "--project-manager"
+ internal const val EDITOR_PROJECT_MANAGER_ARG_SHORT = "-p"
+ internal const val BREAKPOINTS_ARG = "--breakpoints"
+ internal const val BREAKPOINTS_ARG_SHORT = "-b"
+ internal const val XR_MODE_ARG = "--xr-mode"
// Info for the various classes used by the editor
internal val EDITOR_MAIN_INFO = EditorWindowInfo(GodotEditor::class.java, 777, "")
@@ -122,6 +123,20 @@ open class GodotEditor : GodotActivity() {
internal open fun getEditorWindowInfo() = EDITOR_MAIN_INFO
+ /**
+ * Set of permissions to be excluded when requesting all permissions at startup.
+ *
+ * The permissions in this set will be requested on demand based on use cases.
+ */
+ @CallSuper
+ protected open fun getExcludedPermissions(): MutableSet<String> {
+ return mutableSetOf(
+ // The RECORD_AUDIO permission is requested when the "audio/driver/enable_input" project
+ // setting is enabled.
+ Manifest.permission.RECORD_AUDIO
+ )
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
@@ -131,8 +146,8 @@ open class GodotEditor : GodotActivity() {
}
// 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))
+ // requested on demand based on use cases.
+ PermissionsUtil.requestManifestPermissions(this, getExcludedPermissions())
val params = intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
Log.d(TAG, "Starting intent $intent with parameters ${params.contentToString()}")
@@ -152,8 +167,6 @@ open class GodotEditor : GodotActivity() {
val longPressEnabled = enableLongPressGestures()
val panScaleEnabled = enablePanAndScaleGestures()
- checkForProjectPermissionsToEnable()
-
runOnUiThread {
// Enable long press, panning and scaling gestures
godotFragment?.godot?.renderView?.inputHandler?.apply {
@@ -171,17 +184,6 @@ open class GodotEditor : GodotActivity() {
}
}
- /**
- * Check for project permissions to enable
- */
- protected open fun checkForProjectPermissionsToEnable() {
- // Check for RECORD_AUDIO permission
- val audioInputEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("audio/driver/enable_input"))
- if (audioInputEnabled) {
- PermissionsUtil.requestPermission(Manifest.permission.RECORD_AUDIO, this)
- }
- }
-
@CallSuper
protected open fun updateCommandLineParams(args: List<String>) {
// Update the list of command line params with the new args
@@ -196,7 +198,7 @@ open class GodotEditor : GodotActivity() {
final override fun getCommandLine() = commandLineParams
- protected open fun getEditorWindowInfo(args: Array<String>): EditorWindowInfo {
+ protected open fun retrieveEditorWindowInfo(args: Array<String>): EditorWindowInfo {
var hasEditor = false
var i = 0
@@ -273,7 +275,7 @@ open class GodotEditor : GodotActivity() {
}
override fun onNewGodotInstanceRequested(args: Array<String>): Int {
- val editorWindowInfo = getEditorWindowInfo(args)
+ val editorWindowInfo = retrieveEditorWindowInfo(args)
// Launch a new activity
val sourceView = godotFragment?.view
@@ -405,20 +407,26 @@ open class GodotEditor : GodotActivity() {
return when (policy) {
LaunchPolicy.AUTO -> {
- try {
- when (Integer.parseInt(GodotLib.getEditorSetting("run/window_placement/android_window"))) {
- ANDROID_WINDOW_SAME_AS_EDITOR -> LaunchPolicy.SAME
- ANDROID_WINDOW_SIDE_BY_SIDE_WITH_EDITOR -> LaunchPolicy.ADJACENT
- ANDROID_WINDOW_SAME_AS_EDITOR_AND_LAUNCH_IN_PIP_MODE -> LaunchPolicy.SAME_AND_LAUNCH_IN_PIP_MODE
- else -> {
- // ANDROID_WINDOW_AUTO
- defaultLaunchPolicy
+ if (isHorizonOSDevice()) {
+ // Horizon OS UX is more desktop-like and has support for launching adjacent
+ // windows. So we always want to launch in adjacent mode when auto is selected.
+ LaunchPolicy.ADJACENT
+ } else {
+ try {
+ when (Integer.parseInt(GodotLib.getEditorSetting("run/window_placement/android_window"))) {
+ ANDROID_WINDOW_SAME_AS_EDITOR -> LaunchPolicy.SAME
+ ANDROID_WINDOW_SIDE_BY_SIDE_WITH_EDITOR -> LaunchPolicy.ADJACENT
+ ANDROID_WINDOW_SAME_AS_EDITOR_AND_LAUNCH_IN_PIP_MODE -> LaunchPolicy.SAME_AND_LAUNCH_IN_PIP_MODE
+ else -> {
+ // ANDROID_WINDOW_AUTO
+ defaultLaunchPolicy
+ }
}
+ } catch (e: NumberFormatException) {
+ Log.w(TAG, "Error parsing the Android window placement editor setting", e)
+ // Fall-back to the default launch policy
+ defaultLaunchPolicy
}
- } catch (e: NumberFormatException) {
- Log.w(TAG, "Error parsing the Android window placement editor setting", e)
- // Fall-back to the default launch policy
- defaultLaunchPolicy
}
}
@@ -431,8 +439,16 @@ open class GodotEditor : GodotActivity() {
/**
* Returns true the if the device supports picture-in-picture (PiP)
*/
- protected open fun hasPiPSystemFeature() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
- packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
+ protected open fun hasPiPSystemFeature(): Boolean {
+ if (isNativeXRDevice()) {
+ // Known native XR devices do not support PiP.
+ // Will need to revisit as they update their OS.
+ return false
+ }
+
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
+ packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
+ }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
@@ -495,4 +511,12 @@ open class GodotEditor : GodotActivity() {
val godot = godot ?: return Error.ERR_UNCONFIGURED
return verifyApk(godot.fileAccessHandler, apkPath)
}
+
+ override fun supportsFeature(featureTag: String): Boolean {
+ if (featureTag == "xr_editor") {
+ return isNativeXRDevice();
+ }
+
+ return false
+ }
}
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt
index b16e62149a..f5a6ed7dab 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt
@@ -42,9 +42,9 @@ import android.util.Log
import java.util.concurrent.ConcurrentHashMap
/**
- * Used by the [GodotEditor] classes to dispatch messages across processes.
+ * Used by the [BaseGodotEditor] classes to dispatch messages across processes.
*/
-internal class EditorMessageDispatcher(private val editor: GodotEditor) {
+internal class EditorMessageDispatcher(private val editor: BaseGodotEditor) {
companion object {
private val TAG = EditorMessageDispatcher::class.java.simpleName
@@ -173,7 +173,11 @@ internal class EditorMessageDispatcher(private val editor: GodotEditor) {
// to the sender.
val senderId = messengerBundle.getInt(KEY_EDITOR_ID)
val senderMessenger: Messenger? = messengerBundle.getParcelable(KEY_EDITOR_MESSENGER)
- registerMessenger(senderId, senderMessenger)
+ registerMessenger(senderId, senderMessenger) {
+ // Terminate current instance when parent is no longer available.
+ Log.d(TAG, "Terminating current editor instance because parent is no longer available")
+ editor.finish()
+ }
// Register ourselves to the sender so that it can communicate with us.
registerSelfTo(pm, senderMessenger, editor.getEditorWindowInfo().windowId)
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt
index 6b4bf255f2..e52d566347 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt
@@ -30,6 +30,7 @@
package org.godotengine.editor
+import android.Manifest
import android.annotation.SuppressLint
import android.app.PictureInPictureParams
import android.content.Intent
@@ -38,12 +39,15 @@ import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.View
+import androidx.annotation.CallSuper
import org.godotengine.godot.GodotLib
+import org.godotengine.godot.utils.PermissionsUtil
+import org.godotengine.godot.utils.ProcessPhoenix
/**
* Drives the 'run project' window of the Godot Editor.
*/
-class GodotGame : GodotEditor() {
+open class GodotGame : GodotEditor() {
companion object {
private val TAG = GodotGame::class.java.simpleName
@@ -136,8 +140,53 @@ class GodotGame : GodotEditor() {
override fun enablePanAndScaleGestures() = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures"))
- override fun checkForProjectPermissionsToEnable() {
- // Nothing to do.. by the time we get here, the project permissions will have already
- // been requested by the Editor window.
+ override fun onGodotSetupCompleted() {
+ super.onGodotSetupCompleted()
+ Log.v(TAG, "OnGodotSetupCompleted")
+
+ // Check if we should be running in XR instead (if available) as it's possible we were
+ // launched from the project manager which doesn't have that information.
+ val launchingArgs = intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
+ if (launchingArgs != null) {
+ val editorWindowInfo = retrieveEditorWindowInfo(launchingArgs)
+ if (editorWindowInfo != getEditorWindowInfo()) {
+ val relaunchIntent = getNewGodotInstanceIntent(editorWindowInfo, launchingArgs)
+ relaunchIntent.putExtra(EXTRA_NEW_LAUNCH, true)
+ .putExtra(EditorMessageDispatcher.EXTRA_MSG_DISPATCHER_PAYLOAD, intent.getBundleExtra(EditorMessageDispatcher.EXTRA_MSG_DISPATCHER_PAYLOAD))
+
+ Log.d(TAG, "Relaunching XR project using ${editorWindowInfo.windowClassName} with parameters ${launchingArgs.contentToString()}")
+ val godot = godot
+ if (godot != null) {
+ godot.destroyAndKillProcess {
+ ProcessPhoenix.triggerRebirth(this, relaunchIntent)
+ }
+ } else {
+ ProcessPhoenix.triggerRebirth(this, relaunchIntent)
+ }
+ return
+ }
+ }
+
+ // Request project runtime permissions if necessary
+ val permissionsToEnable = getProjectPermissionsToEnable()
+ if (permissionsToEnable.isNotEmpty()) {
+ PermissionsUtil.requestPermissions(this, permissionsToEnable)
+ }
+ }
+
+ /**
+ * Check for project permissions to enable
+ */
+ @CallSuper
+ protected open fun getProjectPermissionsToEnable(): MutableList<String> {
+ val permissionsToEnable = mutableListOf<String>()
+
+ // Check for RECORD_AUDIO permission
+ val audioInputEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("audio/driver/enable_input"))
+ if (audioInputEnabled) {
+ permissionsToEnable.add(Manifest.permission.RECORD_AUDIO)
+ }
+
+ return permissionsToEnable
}
}
diff --git a/platform/android/java/editor/src/meta/AndroidManifest.xml b/platform/android/java/editor/src/meta/AndroidManifest.xml
new file mode 100644
index 0000000000..06442ac4e6
--- /dev/null
+++ b/platform/android/java/editor/src/meta/AndroidManifest.xml
@@ -0,0 +1,99 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:horizonos="http://schemas.horizonos/sdk">
+
+ <horizonos:uses-horizonos-sdk
+ horizonos:minSdkVersion="69"
+ horizonos:targetSdkVersion="69" />
+
+ <uses-feature
+ android:name="android.hardware.vr.headtracking"
+ android:required="true"
+ android:version="1"/>
+
+ <!-- Oculus Quest hand tracking -->
+ <uses-permission android:name="com.oculus.permission.HAND_TRACKING" />
+ <uses-feature
+ android:name="oculus.software.handtracking"
+ android:required="false" />
+
+ <!-- Passthrough feature flag -->
+ <uses-feature android:name="com.oculus.feature.PASSTHROUGH"
+ android:required="false" />
+
+ <!-- Overlay keyboard support -->
+ <uses-feature android:name="oculus.software.overlay_keyboard" android:required="false"/>
+
+ <!-- Render model -->
+ <uses-permission android:name="com.oculus.permission.RENDER_MODEL" />
+ <uses-feature android:name="com.oculus.feature.RENDER_MODEL" android:required="false" />
+
+ <!-- Anchor api -->
+ <uses-permission android:name="com.oculus.permission.USE_ANCHOR_API" />
+
+ <!-- Scene api -->
+ <uses-permission android:name="com.oculus.permission.USE_SCENE" />
+
+ <application>
+
+ <activity
+ android:name=".GodotEditor"
+ android:exported="true"
+ android:screenOrientation="landscape"
+ tools:node="merge"
+ tools:replace="android:screenOrientation">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.oculus.intent.category.2D" />
+ </intent-filter>
+
+ <meta-data android:name="com.oculus.vrshell.free_resizing_lock_aspect_ratio" android:value="true"/>
+ </activity>
+
+ <activity
+ android:name=".GodotXRGame"
+ android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
+ android:process=":GodotXRGame"
+ android:launchMode="singleTask"
+ android:icon="@mipmap/ic_play_window"
+ android:label="@string/godot_game_activity_name"
+ android:exported="false"
+ android:screenOrientation="landscape"
+ android:resizeableActivity="false"
+ android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="com.oculus.intent.category.VR" />
+ <category android:name="org.khronos.openxr.intent.category.IMMERSIVE_HMD" />
+ </intent-filter>
+ </activity>
+
+ <!-- Supported Meta devices -->
+ <meta-data
+ android:name="com.oculus.supportedDevices"
+ android:value="quest3|questpro"
+ tools:replace="android:value" />
+
+ <!--
+ We remove this meta-data originating from the vendors plugin as we only need the loader for
+ now since the project being edited provides its own version of the vendors plugin.
+
+ This needs to be removed once we start implementing the immersive version of the project
+ manager and editor windows.
+ -->
+ <meta-data
+ android:name="org.godotengine.plugin.v2.GodotOpenXRMeta"
+ android:value="org.godotengine.openxr.vendors.meta.GodotOpenXRMeta"
+ tools:node="remove" />
+
+ <!-- Enable system splash screen -->
+ <meta-data android:name="com.oculus.ossplash" android:value="true"/>
+ <!-- Enable passthrough background during the splash screen -->
+ <meta-data android:name="com.oculus.ossplash.background" android:value="passthrough-contextual"/>
+
+ </application>
+
+</manifest>
diff --git a/platform/android/java/editor/src/meta/assets/vr_splash.png b/platform/android/java/editor/src/meta/assets/vr_splash.png
new file mode 100644
index 0000000000..7bddd4325a
--- /dev/null
+++ b/platform/android/java/editor/src/meta/assets/vr_splash.png
Binary files differ
diff --git a/scene/gui/control.compat.inc b/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt
index 96ee720d90..328ff9a3bd 100644
--- a/scene/gui/control.compat.inc
+++ b/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* control.compat.inc */
+/* GodotEditor.kt */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,21 +28,65 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef DISABLE_DEPRECATED
-
-void Control::_bind_compatibility_methods() {
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_icon", "name", "theme_type"), &Control::get_theme_icon, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_stylebox", "name", "theme_type"), &Control::get_theme_stylebox, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_font", "name", "theme_type"), &Control::get_theme_font, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_font_size", "name", "theme_type"), &Control::get_theme_font_size, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_color", "name", "theme_type"), &Control::get_theme_color, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_constant", "name", "theme_type"), &Control::get_theme_constant, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_icon", "name", "theme_type"), &Control::has_theme_icon, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_stylebox", "name", "theme_type"), &Control::has_theme_stylebox, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_font", "name", "theme_type"), &Control::has_theme_font, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_font_size", "name", "theme_type"), &Control::has_theme_font_size, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_color", "name", "theme_type"), &Control::has_theme_color, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_constant", "name", "theme_type"), &Control::has_theme_constant, DEFVAL(""));
-}
+package org.godotengine.editor
+
+import org.godotengine.godot.GodotLib
+import org.godotengine.godot.utils.isNativeXRDevice
+
+/**
+ * Primary window of the Godot Editor.
+ *
+ * This is the implementation of the editor used when running on Meta devices.
+ */
+open class GodotEditor : BaseGodotEditor() {
+
+ companion object {
+ private val TAG = GodotEditor::class.java.simpleName
+
+ internal val XR_RUN_GAME_INFO = EditorWindowInfo(GodotXRGame::class.java, 1667, ":GodotXRGame")
+
+ internal const val USE_SCENE_PERMISSION = "com.oculus.permission.USE_SCENE"
+ }
-#endif
+ override fun getExcludedPermissions(): MutableSet<String> {
+ val excludedPermissions = super.getExcludedPermissions()
+ // The USE_SCENE permission is requested when the "xr/openxr/enabled" project setting
+ // is enabled.
+ excludedPermissions.add(USE_SCENE_PERMISSION)
+ return excludedPermissions
+ }
+
+ override fun retrieveEditorWindowInfo(args: Array<String>): EditorWindowInfo {
+ var hasEditor = false
+ var xrModeOn = false
+
+ var i = 0
+ while (i < args.size) {
+ when (args[i++]) {
+ EDITOR_ARG, EDITOR_ARG_SHORT, EDITOR_PROJECT_MANAGER_ARG, EDITOR_PROJECT_MANAGER_ARG_SHORT -> hasEditor = true
+ XR_MODE_ARG -> {
+ val argValue = args[i++]
+ xrModeOn = xrModeOn || ("on" == argValue)
+ }
+ }
+ }
+
+ return if (hasEditor) {
+ EDITOR_MAIN_INFO
+ } else {
+ val openxrEnabled = GodotLib.getGlobal("xr/openxr/enabled").toBoolean()
+ if (openxrEnabled && isNativeXRDevice()) {
+ XR_RUN_GAME_INFO
+ } else {
+ RUN_GAME_INFO
+ }
+ }
+ }
+
+ override fun getEditorWindowInfoForInstanceId(instanceId: Int): EditorWindowInfo? {
+ return when (instanceId) {
+ XR_RUN_GAME_INFO.windowId -> XR_RUN_GAME_INFO
+ else -> super.getEditorWindowInfoForInstanceId(instanceId)
+ }
+ }
+}
diff --git a/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt b/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt
new file mode 100644
index 0000000000..5db2879aad
--- /dev/null
+++ b/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt
@@ -0,0 +1,78 @@
+/*************************************************************************/
+/* GodotXRGame.kt */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+package org.godotengine.editor
+
+import org.godotengine.godot.GodotLib
+import org.godotengine.godot.xr.XRMode
+
+/**
+ * Provide support for running XR apps / games from the editor window.
+ */
+open class GodotXRGame: GodotGame() {
+
+ override fun overrideOrientationRequest() = true
+
+ override fun updateCommandLineParams(args: List<String>) {
+ val updatedArgs = ArrayList<String>()
+ if (!args.contains(XRMode.OPENXR.cmdLineArg)) {
+ updatedArgs.add(XRMode.OPENXR.cmdLineArg)
+ }
+ if (!args.contains(XR_MODE_ARG)) {
+ updatedArgs.add(XR_MODE_ARG)
+ updatedArgs.add("on")
+ }
+ updatedArgs.addAll(args)
+
+ super.updateCommandLineParams(updatedArgs)
+ }
+
+ override fun getEditorWindowInfo() = XR_RUN_GAME_INFO
+
+ override fun getProjectPermissionsToEnable(): MutableList<String> {
+ val permissionsToEnable = super.getProjectPermissionsToEnable()
+
+ val openxrEnabled = GodotLib.getGlobal("xr/openxr/enabled").toBoolean()
+ if (openxrEnabled) {
+ // We only request permissions when the `automatically_request_runtime_permissions`
+ // project setting is enabled.
+ // If the project setting is not defined, we fall-back to the default behavior which is
+ // to automatically request permissions.
+ val automaticallyRequestPermissionsSetting = GodotLib.getGlobal("xr/openxr/extensions/automatically_request_runtime_permissions")
+ val automaticPermissionsRequestEnabled = automaticallyRequestPermissionsSetting.isNullOrEmpty() ||
+ automaticallyRequestPermissionsSetting.toBoolean()
+ if (automaticPermissionsRequestEnabled) {
+ permissionsToEnable.add(USE_SCENE_PERMISSION)
+ }
+ }
+
+ return permissionsToEnable
+ }
+}
diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle
index 81ab598b90..f6aee434e5 100644
--- a/platform/android/java/lib/build.gradle
+++ b/platform/android/java/lib/build.gradle
@@ -51,7 +51,7 @@ android {
}
}
- flavorDimensions "products"
+ flavorDimensions = ["products"]
productFlavors {
editor {}
template {}
@@ -104,7 +104,7 @@ android {
}
boolean devBuild = buildType == "dev"
- boolean debugSymbols = devBuild || isAndroidStudio()
+ boolean debugSymbols = devBuild
boolean runTests = devBuild
boolean productionBuild = !devBuild
boolean storeRelease = buildType == "release"
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 38bd336e2d..5b1d09e749 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -992,6 +992,10 @@ class Godot(private val context: Context) {
*/
@Keep
private fun hasFeature(feature: String): Boolean {
+ if (primaryHost?.supportsFeature(feature) ?: false) {
+ return true;
+ }
+
for (plugin in pluginRegistry.allPlugins) {
if (plugin.supportsFeature(feature)) {
return true
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java
index e0f5744368..d60595c0bb 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java
@@ -501,4 +501,12 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH
}
return Error.ERR_UNAVAILABLE;
}
+
+ @Override
+ public boolean supportsFeature(String featureTag) {
+ if (parentHost != null) {
+ return parentHost.supportsFeature(featureTag);
+ }
+ return false;
+ }
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java
index f1c84e90a7..344b73f799 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java
@@ -136,4 +136,13 @@ public interface GodotHost {
default Error verifyApk(@NonNull String apkPath) {
return Error.ERR_UNAVAILABLE;
}
+
+ /**
+ * Returns whether the given feature tag is supported.
+ *
+ * @see <a href="https://docs.godotengine.org/en/stable/tutorials/export/feature_tags.html">Feature tags</a>
+ */
+ default boolean supportsFeature(String featureTag) {
+ return false;
+ }
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
index 30821eaa8e..9db9ef6080 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
@@ -31,6 +31,7 @@
package org.godotengine.godot;
import org.godotengine.godot.input.GodotInputHandler;
+import org.godotengine.godot.utils.DeviceUtils;
import android.view.SurfaceView;
@@ -63,7 +64,11 @@ public interface GodotRenderView {
void setPointerIcon(int pointerType);
+ /**
+ * @return true if pointer capture is supported.
+ */
default boolean canCapturePointer() {
- return getInputHandler().canCapturePointer();
+ // Pointer capture is not supported on Horizon OS
+ return !DeviceUtils.isHorizonOSDevice() && getInputHandler().canCapturePointer();
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
index d5b05913d8..8b3880d32d 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
@@ -68,6 +68,7 @@ class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView {
setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT));
}
setFocusableInTouchMode(true);
+ setClickable(false);
}
@Override
@@ -132,17 +133,17 @@ class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView {
@Override
public boolean onKeyUp(final int keyCode, KeyEvent event) {
- return mInputHandler.onKeyUp(keyCode, event);
+ return mInputHandler.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
}
@Override
public boolean onKeyDown(final int keyCode, KeyEvent event) {
- return mInputHandler.onKeyDown(keyCode, event);
+ return mInputHandler.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
}
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
- return mInputHandler.onGenericMotionEvent(event);
+ return mInputHandler.onGenericMotionEvent(event) || super.onGenericMotionEvent(event);
}
@Override
diff --git a/core/string/translation_server.compat.inc b/platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt
index 9d1ee8b9df..abe0c5f885 100644
--- a/core/string/translation_server.compat.inc
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* translation_server.compat.inc */
+/* DeviceUtils.kt */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,11 +28,25 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef DISABLE_DEPRECATED
+/**
+ * Contains utility methods for detecting specific devices.
+ */
+@file:JvmName("DeviceUtils")
-void TranslationServer::_bind_compatibility_methods() {
- ClassDB::bind_compatibility_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL(""));
+package org.godotengine.godot.utils
+
+import android.os.Build
+
+/**
+ * Returns true if running on Meta's Horizon OS.
+ */
+fun isHorizonOSDevice(): Boolean {
+ return "Oculus".equals(Build.BRAND, true)
}
-#endif
+/**
+ * Returns true if running on a native Android XR device.
+ */
+fun isNativeXRDevice(): Boolean {
+ return isHorizonOSDevice()
+}
diff --git a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
index 96b6dfc9f3..a7d2774db5 100644
--- a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
+++ b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt
@@ -8,6 +8,7 @@ set(CMAKE_CXX_EXTENSIONS OFF)
set(GODOT_ROOT_DIR ../../../..)
set(ANDROID_ROOT_DIR "${GODOT_ROOT_DIR}/platform/android" CACHE STRING "")
+set(OPENXR_INCLUDE_DIR "${GODOT_ROOT_DIR}/thirdparty/openxr/include" CACHE STRING "")
# Get sources
file(GLOB_RECURSE SOURCES ${GODOT_ROOT_DIR}/*.c**)
@@ -17,6 +18,7 @@ add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})
target_include_directories(${PROJECT_NAME}
SYSTEM PUBLIC
${GODOT_ROOT_DIR}
- ${ANDROID_ROOT_DIR})
+ ${ANDROID_ROOT_DIR}
+ ${OPENXR_INCLUDE_DIR})
add_definitions(-DUNIX_ENABLED -DVULKAN_ENABLED -DANDROID_ENABLED -DGLES3_ENABLED -DTOOLS_ENABLED)
diff --git a/platform/android/java/settings.gradle b/platform/android/java/settings.gradle
index 3137e74244..41418a4a45 100644
--- a/platform/android/java/settings.gradle
+++ b/platform/android/java/settings.gradle
@@ -14,6 +14,7 @@ pluginManagement {
mavenCentral()
gradlePluginPortal()
maven { url "https://plugins.gradle.org/m2/" }
+ maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/"}
}
}
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index 390677df22..6086f67a1e 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -472,19 +472,22 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env,
JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getGlobal(JNIEnv *env, jclass clazz, jstring path) {
String js = jstring_to_string(path, env);
- return env->NewStringUTF(GLOBAL_GET(js).operator String().utf8().get_data());
+ Variant setting_with_override = GLOBAL_GET(js);
+ String setting_value = (setting_with_override.get_type() == Variant::NIL) ? "" : setting_with_override;
+ return env->NewStringUTF(setting_value.utf8().get_data());
}
JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getEditorSetting(JNIEnv *env, jclass clazz, jstring p_setting_key) {
- String editor_setting = "";
+ String editor_setting_value = "";
#ifdef TOOLS_ENABLED
String godot_setting_key = jstring_to_string(p_setting_key, env);
- editor_setting = EDITOR_GET(godot_setting_key).operator String();
+ Variant editor_setting = EDITOR_GET(godot_setting_key);
+ editor_setting_value = (editor_setting.get_type() == Variant::NIL) ? "" : editor_setting;
#else
WARN_PRINT("Access to the Editor Settings in only available on Editor builds");
#endif
- return env->NewStringUTF(editor_setting.utf8().get_data());
+ return env->NewStringUTF(editor_setting_value.utf8().get_data());
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_callobject(JNIEnv *env, jclass clazz, jlong ID, jstring method, jobjectArray params) {
diff --git a/platform/android/plugin/godot_plugin_jni.cpp b/platform/android/plugin/godot_plugin_jni.cpp
index e3cde145cb..75c8dd9528 100644
--- a/platform/android/plugin/godot_plugin_jni.cpp
+++ b/platform/android/plugin/godot_plugin_jni.cpp
@@ -35,7 +35,6 @@
#include "string_android.h"
#include "core/config/engine.h"
-#include "core/config/project_settings.h"
#include "core/error/error_macros.h"
static HashMap<String, JNISingleton *> jni_singletons;
@@ -43,7 +42,6 @@ static HashMap<String, JNISingleton *> jni_singletons;
void unregister_plugins_singletons() {
for (const KeyValue<String, JNISingleton *> &E : jni_singletons) {
Engine::get_singleton()->remove_singleton(E.key);
- ProjectSettings::get_singleton()->set(E.key, Variant());
if (E.value) {
memdelete(E.value);
@@ -64,7 +62,6 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeR
jni_singletons[singname] = s;
Engine::get_singleton()->add_singleton(Engine::Singleton(singname, s));
- ProjectSettings::get_singleton()->set(singname, s);
return true;
}
diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp
index ab13105d18..08b20c5b42 100644
--- a/platform/linuxbsd/wayland/wayland_thread.cpp
+++ b/platform/linuxbsd/wayland/wayland_thread.cpp
@@ -169,12 +169,13 @@ Vector<uint8_t> WaylandThread::_wp_primary_selection_offer_read(struct wl_displa
int fds[2];
if (pipe(fds) == 0) {
- // This function expects to return a string, so we can only ask for a MIME of
- // "text/plain"
zwp_primary_selection_offer_v1_receive(p_offer, p_mime, fds[1]);
- // Wait for the compositor to know about the pipe.
- wl_display_roundtrip(p_display);
+ // NOTE: It's important to just flush and not roundtrip here as we would risk
+ // running some cleanup event, like for example `wl_data_device::leave`. We're
+ // going to wait for the message anyways as the read will probably block if
+ // the compositor doesn't read from the other end of the pipe.
+ wl_display_flush(p_display);
// Close the write end of the pipe, which we don't need and would otherwise
// just stall our next `read`s.
@@ -1394,6 +1395,8 @@ void WaylandThread::_wl_pointer_on_leave(void *data, struct wl_pointer *wl_point
ss->pointed_surface = nullptr;
+ ss->pointer_data_buffer.pressed_button_mask.clear();
+
Ref<WindowEventMessage> msg;
msg.instantiate();
msg->event = DisplayServer::WINDOW_EVENT_MOUSE_EXIT;
diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp
index 770871ae19..c99e9cdd0c 100644
--- a/platform/macos/export/export_plugin.cpp
+++ b/platform/macos/export/export_plugin.cpp
@@ -357,17 +357,13 @@ List<String> EditorExportPlatformMacOS::get_binary_extensions(const Ref<EditorEx
list.push_back("dmg");
#endif
list.push_back("zip");
-#ifndef WINDOWS_ENABLED
list.push_back("app");
-#endif
} else if (dist_type == 1) {
#ifdef MACOS_ENABLED
list.push_back("dmg");
#endif
list.push_back("zip");
-#ifndef WINDOWS_ENABLED
list.push_back("app");
-#endif
} else if (dist_type == 2) {
#ifdef MACOS_ENABLED
list.push_back("pkg");
@@ -1903,6 +1899,11 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
if (is_executable(file)) {
// chmod with 0755 if the file is executable.
FileAccess::set_unix_permissions(file, 0755);
+#ifndef UNIX_ENABLED
+ if (export_format == "app") {
+ add_message(EXPORT_MESSAGE_INFO, TTR("Export"), vformat(TTR("Unable to set Unix permissions for executable \"%s\". Use \"chmod +x\" to set it after transferring the exported .app to macOS or Linux."), "Contents/MacOS/" + file.get_file()));
+ }
+#endif
}
} else {
add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not open \"%s\"."), file));
@@ -1928,6 +1929,11 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
if ((con_scr == 1 && p_debug) || (con_scr == 2)) {
err = _export_debug_script(p_preset, pkg_name, tmp_app_path_name.get_file() + "/Contents/MacOS/" + pkg_name, scr_path);
FileAccess::set_unix_permissions(scr_path, 0755);
+#ifndef UNIX_ENABLED
+ if (export_format == "app") {
+ add_message(EXPORT_MESSAGE_INFO, TTR("Export"), vformat(TTR("Unable to set Unix permissions for executable \"%s\". Use \"chmod +x\" to set it after transferring the exported .app to macOS or Linux."), scr_path.get_file()));
+ }
+#endif
if (err != OK) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Could not create console wrapper."));
}
@@ -2156,6 +2162,11 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
_code_sign(p_preset, tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), hlp_ent_path, false, true);
}
FileAccess::set_unix_permissions(tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), 0755);
+#ifndef UNIX_ENABLED
+ if (export_format == "app") {
+ add_message(EXPORT_MESSAGE_INFO, TTR("Export"), vformat(TTR("Unable to set Unix permissions for executable \"%s\". Use \"chmod +x\" to set it after transferring the exported .app to macOS or Linux."), "Contents/Helpers/" + hlp_path.get_file()));
+ }
+#endif
}
}
diff --git a/platform/web/os_web.cpp b/platform/web/os_web.cpp
index ef8f90421b..51facbaa84 100644
--- a/platform/web/os_web.cpp
+++ b/platform/web/os_web.cpp
@@ -105,7 +105,7 @@ Error OS_Web::execute(const String &p_path, const List<String> &p_arguments, Str
return create_process(p_path, p_arguments);
}
-Dictionary OS_Web::execute_with_pipe(const String &p_path, const List<String> &p_arguments) {
+Dictionary OS_Web::execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking) {
ERR_FAIL_V_MSG(Dictionary(), "OS::execute_with_pipe is not available on the Web platform.");
}
diff --git a/platform/web/os_web.h b/platform/web/os_web.h
index 55a5fcc6c6..1ddb745965 100644
--- a/platform/web/os_web.h
+++ b/platform/web/os_web.h
@@ -80,7 +80,7 @@ public:
bool main_loop_iterate();
Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override;
- Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override;
+ Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking = true) override;
Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
Error kill(const ProcessID &p_pid) override;
int get_process_id() const override;
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index 92ac921cee..684a7c34a0 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -666,7 +666,7 @@ def get_ar_version(env):
print_warning("Couldn't check version of `ar`.")
return ret
- match = re.search(r"GNU ar \(GNU Binutils\) (\d+)\.(\d+)(:?\.(\d+))?", output)
+ match = re.search(r"GNU ar \(GNU Binutils\) (\d+)\.(\d+)(?:\.(\d+))?", output)
if match:
ret["major"] = int(match[1])
ret["minor"] = int(match[2])
diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp
index bcc6a64671..e6e8c6aaef 100644
--- a/platform/windows/os_windows.cpp
+++ b/platform/windows/os_windows.cpp
@@ -101,7 +101,7 @@ static String fix_path(const String &p_path) {
}
path = path.simplify_path();
path = path.replace("/", "\\");
- if (!path.is_network_share_path() && !path.begins_with(R"(\\?\)")) {
+ if (path.size() >= MAX_PATH && !path.is_network_share_path() && !path.begins_with(R"(\\?\)")) {
path = R"(\\?\)" + path;
}
return path;
@@ -878,7 +878,7 @@ Dictionary OS_Windows::get_memory_info() const {
return meminfo;
}
-Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String> &p_arguments) {
+Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking) {
#define CLEAN_PIPES \
if (pipe_in[0] != 0) { \
CloseHandle(pipe_in[0]); \
@@ -977,11 +977,11 @@ Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String
Ref<FileAccessWindowsPipe> main_pipe;
main_pipe.instantiate();
- main_pipe->open_existing(pipe_out[0], pipe_in[1]);
+ main_pipe->open_existing(pipe_out[0], pipe_in[1], p_blocking);
Ref<FileAccessWindowsPipe> err_pipe;
err_pipe.instantiate();
- err_pipe->open_existing(pipe_err[0], 0);
+ err_pipe->open_existing(pipe_err[0], 0, p_blocking);
ret["stdio"] = main_pipe;
ret["stderr"] = err_pipe;
diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h
index 9c7b98d7fd..4f9bc049ee 100644
--- a/platform/windows/os_windows.h
+++ b/platform/windows/os_windows.h
@@ -188,7 +188,7 @@ public:
virtual Dictionary get_memory_info() const override;
virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override;
- virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override;
+ virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking = true) override;
virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
virtual Error kill(const ProcessID &p_pid) override;
virtual int get_process_id() const override;
diff --git a/scene/2d/canvas_modulate.cpp b/scene/2d/canvas_modulate.cpp
index 2c5c6a1a16..dc83775c71 100644
--- a/scene/2d/canvas_modulate.cpp
+++ b/scene/2d/canvas_modulate.cpp
@@ -114,7 +114,7 @@ Color CanvasModulate::get_color() const {
}
PackedStringArray CanvasModulate::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
if (is_in_canvas && is_visible_in_tree()) {
List<Node *> nodes;
diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp
index 5ce26b3ed4..50c5873781 100644
--- a/scene/2d/light_2d.cpp
+++ b/scene/2d/light_2d.cpp
@@ -417,7 +417,7 @@ Vector2 PointLight2D::get_texture_offset() const {
}
PackedStringArray PointLight2D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
if (!texture.is_valid()) {
warnings.push_back(RTR("A texture with the shape of the light must be supplied to the \"Texture\" property."));
diff --git a/scene/2d/light_occluder_2d.cpp b/scene/2d/light_occluder_2d.cpp
index 092c987ac0..7c3fb61d04 100644
--- a/scene/2d/light_occluder_2d.cpp
+++ b/scene/2d/light_occluder_2d.cpp
@@ -263,7 +263,7 @@ int LightOccluder2D::get_occluder_light_mask() const {
}
PackedStringArray LightOccluder2D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
if (!occluder_polygon.is_valid()) {
warnings.push_back(RTR("An occluder polygon must be set (or drawn) for this occluder to take effect."));
diff --git a/scene/2d/navigation_link_2d.cpp b/scene/2d/navigation_link_2d.cpp
index 111f5a7b78..4961e18dc9 100644
--- a/scene/2d/navigation_link_2d.cpp
+++ b/scene/2d/navigation_link_2d.cpp
@@ -328,7 +328,7 @@ void NavigationLink2D::set_travel_cost(real_t p_travel_cost) {
}
PackedStringArray NavigationLink2D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
if (start_position.is_equal_approx(end_position)) {
warnings.push_back(RTR("NavigationLink2D start position should be different than the end position to be useful."));
diff --git a/scene/2d/parallax_layer.cpp b/scene/2d/parallax_layer.cpp
index 023e9201fc..24f261deb6 100644
--- a/scene/2d/parallax_layer.cpp
+++ b/scene/2d/parallax_layer.cpp
@@ -133,7 +133,7 @@ void ParallaxLayer::set_base_offset_and_scale(const Point2 &p_offset, real_t p_s
}
PackedStringArray ParallaxLayer::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
if (!Object::cast_to<ParallaxBackground>(get_parent())) {
warnings.push_back(RTR("ParallaxLayer node only works when set as child of a ParallaxBackground node."));
diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp
index c3768386e9..5813ab02e3 100644
--- a/scene/2d/path_2d.cpp
+++ b/scene/2d/path_2d.cpp
@@ -288,7 +288,7 @@ void PathFollow2D::_validate_property(PropertyInfo &p_property) const {
}
PackedStringArray PathFollow2D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
if (is_visible_in_tree() && is_inside_tree()) {
if (!Object::cast_to<Path2D>(get_parent())) {
diff --git a/scene/2d/physics/collision_object_2d.cpp b/scene/2d/physics/collision_object_2d.cpp
index 00b6085f0c..27ee6b883c 100644
--- a/scene/2d/physics/collision_object_2d.cpp
+++ b/scene/2d/physics/collision_object_2d.cpp
@@ -582,7 +582,7 @@ void CollisionObject2D::_update_pickable() {
}
PackedStringArray CollisionObject2D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
if (shapes.is_empty()) {
warnings.push_back(RTR("This node has no shape, so it can't collide or interact with other objects.\nConsider adding a CollisionShape2D or CollisionPolygon2D as a child to define its shape."));
diff --git a/scene/2d/physics/collision_polygon_2d.cpp b/scene/2d/physics/collision_polygon_2d.cpp
index a9b47ef4d4..b49badac1f 100644
--- a/scene/2d/physics/collision_polygon_2d.cpp
+++ b/scene/2d/physics/collision_polygon_2d.cpp
@@ -232,7 +232,7 @@ bool CollisionPolygon2D::_edit_is_selected_on_click(const Point2 &p_point, doubl
#endif
PackedStringArray CollisionPolygon2D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
if (!Object::cast_to<CollisionObject2D>(get_parent())) {
warnings.push_back(RTR("CollisionPolygon2D only serves to provide a collision shape to a CollisionObject2D derived node. Please only use it as a child of Area2D, StaticBody2D, RigidBody2D, CharacterBody2D, etc. to give them a shape."));
diff --git a/scene/2d/physics/collision_shape_2d.cpp b/scene/2d/physics/collision_shape_2d.cpp
index 6fc29ffe63..bdd0d06b5e 100644
--- a/scene/2d/physics/collision_shape_2d.cpp
+++ b/scene/2d/physics/collision_shape_2d.cpp
@@ -174,7 +174,7 @@ bool CollisionShape2D::_edit_is_selected_on_click(const Point2 &p_point, double
}
PackedStringArray CollisionShape2D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
CollisionObject2D *col_object = Object::cast_to<CollisionObject2D>(get_parent());
if (col_object == nullptr) {
diff --git a/scene/2d/physics/physical_bone_2d.cpp b/scene/2d/physics/physical_bone_2d.cpp
index 77bb8c24b8..19274c8084 100644
--- a/scene/2d/physics/physical_bone_2d.cpp
+++ b/scene/2d/physics/physical_bone_2d.cpp
@@ -107,7 +107,7 @@ void PhysicalBone2D::_find_joint_child() {
}
PackedStringArray PhysicalBone2D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = RigidBody2D::get_configuration_warnings();
if (!parent_skeleton) {
warnings.push_back(RTR("A PhysicalBone2D only works with a Skeleton2D or another PhysicalBone2D as a parent node!"));
diff --git a/scene/2d/remote_transform_2d.cpp b/scene/2d/remote_transform_2d.cpp
index 920f5720fa..1816a3409b 100644
--- a/scene/2d/remote_transform_2d.cpp
+++ b/scene/2d/remote_transform_2d.cpp
@@ -211,7 +211,7 @@ void RemoteTransform2D::force_update_cache() {
}
PackedStringArray RemoteTransform2D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
if (!has_node(remote_node) || !Object::cast_to<Node2D>(get_node(remote_node))) {
warnings.push_back(RTR("Path property must point to a valid Node2D node to work."));
diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp
index f9e8c831d1..90bfb4c84c 100644
--- a/scene/2d/skeleton_2d.cpp
+++ b/scene/2d/skeleton_2d.cpp
@@ -412,7 +412,7 @@ int Bone2D::get_index_in_skeleton() const {
}
PackedStringArray Bone2D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
if (!skeleton) {
if (parent_bone) {
warnings.push_back(RTR("This Bone2D chain should end at a Skeleton2D node."));
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index b10f2097da..45cfb8cf33 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -827,7 +827,7 @@ TypedArray<Vector2i> TileMap::get_surrounding_cells(const Vector2i &p_coords) {
}
PackedStringArray TileMap::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node2D::get_configuration_warnings();
warnings.push_back(RTR("The TileMap node is deprecated as it is superseded by the use of multiple TileMapLayer nodes.\nTo convert a TileMap to a set of TileMapLayer nodes, open the TileMap bottom panel with this node selected, click the toolbox icon in the top-right corner and choose \"Extract TileMap layers as individual TileMapLayer nodes\"."));
diff --git a/scene/3d/decal.cpp b/scene/3d/decal.cpp
index 485599d0fb..8702b1d3da 100644
--- a/scene/3d/decal.cpp
+++ b/scene/3d/decal.cpp
@@ -163,7 +163,7 @@ void Decal::_validate_property(PropertyInfo &p_property) const {
}
PackedStringArray Decal::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
warnings.push_back(RTR("Decals are only available when using the Forward+ or Mobile rendering backends."));
diff --git a/scene/3d/fog_volume.cpp b/scene/3d/fog_volume.cpp
index 54631a8dff..195074ba2f 100644
--- a/scene/3d/fog_volume.cpp
+++ b/scene/3d/fog_volume.cpp
@@ -116,7 +116,7 @@ AABB FogVolume::get_aabb() const {
}
PackedStringArray FogVolume::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
Ref<Environment> environment = get_viewport()->find_world_3d()->get_environment();
diff --git a/scene/3d/gpu_particles_collision_3d.cpp b/scene/3d/gpu_particles_collision_3d.cpp
index 3a05ec9c9e..9791f23bc3 100644
--- a/scene/3d/gpu_particles_collision_3d.cpp
+++ b/scene/3d/gpu_particles_collision_3d.cpp
@@ -524,7 +524,7 @@ Ref<Image> GPUParticlesCollisionSDF3D::bake() {
}
PackedStringArray GPUParticlesCollisionSDF3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = GPUParticlesCollision3D::get_configuration_warnings();
if (bake_mask == 0) {
warnings.push_back(RTR("The Bake Mask has no bits enabled, which means baking will not produce any collision for this GPUParticlesCollisionSDF3D.\nTo resolve this, enable at least one bit in the Bake Mask property."));
diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp
index 3f8b0dfb8e..26a574cd26 100644
--- a/scene/3d/lightmap_gi.cpp
+++ b/scene/3d/lightmap_gi.cpp
@@ -151,6 +151,14 @@ bool LightmapGIData::is_using_spherical_harmonics() const {
return uses_spherical_harmonics;
}
+void LightmapGIData::_set_uses_packed_directional(bool p_enable) {
+ _uses_packed_directional = p_enable;
+}
+
+bool LightmapGIData::_is_using_packed_directional() const {
+ return _uses_packed_directional;
+}
+
void LightmapGIData::set_capture_data(const AABB &p_bounds, bool p_interior, const PackedVector3Array &p_points, const PackedColorArray &p_point_sh, const PackedInt32Array &p_tetrahedra, const PackedInt32Array &p_bsp_tree, float p_baked_exposure) {
if (p_points.size()) {
int pc = p_points.size();
@@ -255,6 +263,9 @@ void LightmapGIData::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_uses_spherical_harmonics", "uses_spherical_harmonics"), &LightmapGIData::set_uses_spherical_harmonics);
ClassDB::bind_method(D_METHOD("is_using_spherical_harmonics"), &LightmapGIData::is_using_spherical_harmonics);
+ ClassDB::bind_method(D_METHOD("_set_uses_packed_directional", "_uses_packed_directional"), &LightmapGIData::_set_uses_packed_directional);
+ ClassDB::bind_method(D_METHOD("_is_using_packed_directional"), &LightmapGIData::_is_using_packed_directional);
+
ClassDB::bind_method(D_METHOD("add_user", "path", "uv_scale", "slice_index", "sub_instance"), &LightmapGIData::add_user);
ClassDB::bind_method(D_METHOD("get_user_count"), &LightmapGIData::get_user_count);
ClassDB::bind_method(D_METHOD("get_user_path", "user_idx"), &LightmapGIData::get_user_path);
@@ -267,6 +278,7 @@ void LightmapGIData::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uses_spherical_harmonics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_uses_spherical_harmonics", "is_using_spherical_harmonics");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "user_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_user_data", "_get_user_data");
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "probe_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_probe_data", "_get_probe_data");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "_uses_packed_directional", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_uses_packed_directional", "_is_using_packed_directional");
#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("set_light_texture", "light_texture"), &LightmapGIData::set_light_texture);
@@ -1187,6 +1199,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
}
gi_data->set_lightmap_textures(textures);
+ gi_data->_set_uses_packed_directional(directional); // New SH lightmaps are packed automatically.
gi_data->set_uses_spherical_harmonics(directional);
for (int i = 0; i < lightmapper->get_bake_mesh_count(); i++) {
@@ -1352,6 +1365,12 @@ void LightmapGI::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_POST_ENTER_TREE: {
if (light_data.is_valid()) {
+ ERR_FAIL_COND_MSG(
+ light_data->is_using_spherical_harmonics() && !light_data->_is_using_packed_directional(),
+ vformat(
+ "%s (%s): The directional lightmap textures are stored in a format that isn't supported anymore. Please bake lightmaps again to make lightmaps display from this node again.",
+ get_light_data()->get_path(), get_name()));
+
_assign_lightmaps();
}
} break;
@@ -1581,7 +1600,7 @@ Ref<CameraAttributes> LightmapGI::get_camera_attributes() const {
}
PackedStringArray LightmapGI::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
warnings.push_back(RTR("Lightmap can only be baked from a device that supports the RD backends. Lightmap baking may fail."));
diff --git a/scene/3d/lightmap_gi.h b/scene/3d/lightmap_gi.h
index 67480132b6..6377c420d1 100644
--- a/scene/3d/lightmap_gi.h
+++ b/scene/3d/lightmap_gi.h
@@ -49,6 +49,8 @@ class LightmapGIData : public Resource {
bool uses_spherical_harmonics = false;
bool interior = false;
+ bool _uses_packed_directional = false;
+
RID lightmap;
AABB bounds;
float baked_exposure = 1.0;
@@ -92,6 +94,9 @@ public:
void set_uses_spherical_harmonics(bool p_enable);
bool is_using_spherical_harmonics() const;
+ void _set_uses_packed_directional(bool p_enable);
+ bool _is_using_packed_directional() const;
+
bool is_interior() const;
float get_baked_exposure() const;
diff --git a/scene/3d/navigation_link_3d.cpp b/scene/3d/navigation_link_3d.cpp
index bebba9a6c0..0cce21b9d0 100644
--- a/scene/3d/navigation_link_3d.cpp
+++ b/scene/3d/navigation_link_3d.cpp
@@ -453,7 +453,7 @@ void NavigationLink3D::set_travel_cost(real_t p_travel_cost) {
}
PackedStringArray NavigationLink3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (start_position.is_equal_approx(end_position)) {
warnings.push_back(RTR("NavigationLink3D start position should be different than the end position to be useful."));
diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp
index d7397a932d..c0c254e7ed 100644
--- a/scene/3d/navigation_region_3d.cpp
+++ b/scene/3d/navigation_region_3d.cpp
@@ -271,7 +271,7 @@ bool NavigationRegion3D::is_baking() const {
}
PackedStringArray NavigationRegion3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (is_visible_in_tree() && is_inside_tree()) {
if (!navigation_mesh.is_valid()) {
diff --git a/scene/3d/occluder_instance_3d.cpp b/scene/3d/occluder_instance_3d.cpp
index 6982df12f6..6d88323c76 100644
--- a/scene/3d/occluder_instance_3d.cpp
+++ b/scene/3d/occluder_instance_3d.cpp
@@ -691,7 +691,7 @@ OccluderInstance3D::BakeError OccluderInstance3D::bake_scene(Node *p_from_node,
}
PackedStringArray OccluderInstance3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
if (!bool(GLOBAL_GET("rendering/occlusion_culling/use_occlusion_culling"))) {
warnings.push_back(RTR("Occlusion culling is disabled in the Project Settings, which means occlusion culling won't be performed in the root viewport.\nTo resolve this, open the Project Settings and enable Rendering > Occlusion Culling > Use Occlusion Culling."));
diff --git a/scene/3d/path_3d.cpp b/scene/3d/path_3d.cpp
index dc030b6a0f..64259a24b0 100644
--- a/scene/3d/path_3d.cpp
+++ b/scene/3d/path_3d.cpp
@@ -216,24 +216,7 @@ void Path3D::_bind_methods() {
ADD_SIGNAL(MethodInfo("curve_changed"));
}
-// Update transform, in deferred mode by default to avoid superfluity.
-void PathFollow3D::update_transform(bool p_immediate) {
- transform_dirty = true;
-
- if (p_immediate) {
- _update_transform();
- } else {
- callable_mp(this, &PathFollow3D::_update_transform).call_deferred();
- }
-}
-
-// Update transform immediately .
-void PathFollow3D::_update_transform() {
- if (!transform_dirty) {
- return;
- }
- transform_dirty = false;
-
+void PathFollow3D::update_transform() {
if (!path) {
return;
}
@@ -286,9 +269,7 @@ void PathFollow3D::_notification(int p_what) {
Node *parent = get_parent();
if (parent) {
path = Object::cast_to<Path3D>(parent);
- if (path) {
- update_transform();
- }
+ update_transform();
}
} break;
@@ -318,7 +299,7 @@ void PathFollow3D::_validate_property(PropertyInfo &p_property) const {
}
PackedStringArray PathFollow3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (is_visible_in_tree() && is_inside_tree()) {
if (!Object::cast_to<Path3D>(get_parent())) {
@@ -414,6 +395,9 @@ void PathFollow3D::_bind_methods() {
void PathFollow3D::set_progress(real_t p_progress) {
ERR_FAIL_COND(!isfinite(p_progress));
+ if (progress == p_progress) {
+ return;
+ }
progress = p_progress;
if (path) {
@@ -435,10 +419,11 @@ void PathFollow3D::set_progress(real_t p_progress) {
}
void PathFollow3D::set_h_offset(real_t p_h_offset) {
- h_offset = p_h_offset;
- if (path) {
- update_transform();
+ if (h_offset == p_h_offset) {
+ return;
}
+ h_offset = p_h_offset;
+ update_transform();
}
real_t PathFollow3D::get_h_offset() const {
@@ -446,10 +431,11 @@ real_t PathFollow3D::get_h_offset() const {
}
void PathFollow3D::set_v_offset(real_t p_v_offset) {
- v_offset = p_v_offset;
- if (path) {
- update_transform();
+ if (v_offset == p_v_offset) {
+ return;
}
+ v_offset = p_v_offset;
+ update_transform();
}
real_t PathFollow3D::get_v_offset() const {
@@ -476,6 +462,9 @@ real_t PathFollow3D::get_progress_ratio() const {
}
void PathFollow3D::set_rotation_mode(RotationMode p_rotation_mode) {
+ if (rotation_mode == p_rotation_mode) {
+ return;
+ }
rotation_mode = p_rotation_mode;
update_configuration_warnings();
@@ -487,6 +476,9 @@ PathFollow3D::RotationMode PathFollow3D::get_rotation_mode() const {
}
void PathFollow3D::set_use_model_front(bool p_use_model_front) {
+ if (use_model_front == p_use_model_front) {
+ return;
+ }
use_model_front = p_use_model_front;
update_transform();
}
@@ -496,6 +488,9 @@ bool PathFollow3D::is_using_model_front() const {
}
void PathFollow3D::set_loop(bool p_loop) {
+ if (loop == p_loop) {
+ return;
+ }
loop = p_loop;
update_transform();
}
@@ -505,6 +500,9 @@ bool PathFollow3D::has_loop() const {
}
void PathFollow3D::set_tilt_enabled(bool p_enabled) {
+ if (tilt_enabled == p_enabled) {
+ return;
+ }
tilt_enabled = p_enabled;
update_transform();
}
diff --git a/scene/3d/path_3d.h b/scene/3d/path_3d.h
index 0c9111bb8e..fb4f301375 100644
--- a/scene/3d/path_3d.h
+++ b/scene/3d/path_3d.h
@@ -90,7 +90,6 @@ protected:
void _validate_property(PropertyInfo &p_property) const;
void _notification(int p_what);
- void _update_transform();
static void _bind_methods();
@@ -124,7 +123,7 @@ public:
PackedStringArray get_configuration_warnings() const override;
- void update_transform(bool p_immediate = false);
+ void update_transform();
static Transform3D correct_posture(Transform3D p_transform, PathFollow3D::RotationMode p_rotation_mode);
diff --git a/scene/3d/physics/collision_object_3d.cpp b/scene/3d/physics/collision_object_3d.cpp
index f11aa7012a..f0a5013ca2 100644
--- a/scene/3d/physics/collision_object_3d.cpp
+++ b/scene/3d/physics/collision_object_3d.cpp
@@ -731,7 +731,7 @@ bool CollisionObject3D::get_capture_input_on_drag() const {
}
PackedStringArray CollisionObject3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (shapes.is_empty()) {
warnings.push_back(RTR("This node has no shape, so it can't collide or interact with other objects.\nConsider adding a CollisionShape3D or CollisionPolygon3D as a child to define its shape."));
diff --git a/scene/3d/physics/collision_polygon_3d.cpp b/scene/3d/physics/collision_polygon_3d.cpp
index 76cd4db779..bf8dec7b54 100644
--- a/scene/3d/physics/collision_polygon_3d.cpp
+++ b/scene/3d/physics/collision_polygon_3d.cpp
@@ -169,7 +169,7 @@ void CollisionPolygon3D::set_margin(real_t p_margin) {
}
PackedStringArray CollisionPolygon3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (!Object::cast_to<CollisionObject3D>(get_parent())) {
warnings.push_back(RTR("CollisionPolygon3D only serves to provide a collision shape to a CollisionObject3D derived node.\nPlease only use it as a child of Area3D, StaticBody3D, RigidBody3D, CharacterBody3D, etc. to give them a shape."));
diff --git a/scene/3d/physics/collision_shape_3d.cpp b/scene/3d/physics/collision_shape_3d.cpp
index f3492a3cf3..304fa74b06 100644
--- a/scene/3d/physics/collision_shape_3d.cpp
+++ b/scene/3d/physics/collision_shape_3d.cpp
@@ -120,7 +120,7 @@ void CollisionShape3D::resource_changed(Ref<Resource> res) {
#endif
PackedStringArray CollisionShape3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
CollisionObject3D *col_object = Object::cast_to<CollisionObject3D>(get_parent());
if (col_object == nullptr) {
diff --git a/scene/3d/physics/vehicle_body_3d.cpp b/scene/3d/physics/vehicle_body_3d.cpp
index b4c321cf5f..5073705145 100644
--- a/scene/3d/physics/vehicle_body_3d.cpp
+++ b/scene/3d/physics/vehicle_body_3d.cpp
@@ -106,7 +106,7 @@ void VehicleWheel3D::_notification(int p_what) {
}
PackedStringArray VehicleWheel3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (!Object::cast_to<VehicleBody3D>(get_parent())) {
warnings.push_back(RTR("VehicleWheel3D serves to provide a wheel system to a VehicleBody3D. Please use it as a child of a VehicleBody3D."));
diff --git a/scene/3d/remote_transform_3d.cpp b/scene/3d/remote_transform_3d.cpp
index e580882c46..f970879aa4 100644
--- a/scene/3d/remote_transform_3d.cpp
+++ b/scene/3d/remote_transform_3d.cpp
@@ -211,7 +211,7 @@ void RemoteTransform3D::force_update_cache() {
}
PackedStringArray RemoteTransform3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (!has_node(remote_node) || !Object::cast_to<Node3D>(get_node(remote_node))) {
warnings.push_back(RTR("The \"Remote Path\" property must point to a valid Node3D or Node3D-derived node to work."));
diff --git a/scene/3d/soft_body_3d.cpp b/scene/3d/soft_body_3d.cpp
index 4fe5dd2385..7f67bde0cf 100644
--- a/scene/3d/soft_body_3d.cpp
+++ b/scene/3d/soft_body_3d.cpp
@@ -383,7 +383,7 @@ void SoftBody3D::_bind_methods() {
}
PackedStringArray SoftBody3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = MeshInstance3D::get_configuration_warnings();
if (mesh.is_null()) {
warnings.push_back(RTR("This body will be ignored until you set a mesh."));
diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp
index 79a01450dd..a59754c8cc 100644
--- a/scene/3d/visual_instance_3d.cpp
+++ b/scene/3d/visual_instance_3d.cpp
@@ -497,7 +497,7 @@ bool GeometryInstance3D::is_ignoring_occlusion_culling() {
}
PackedStringArray GeometryInstance3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
if (!Math::is_zero_approx(visibility_range_end) && visibility_range_end <= visibility_range_begin) {
warnings.push_back(RTR("The GeometryInstance3D visibility range's End distance is set to a non-zero value, but is lower than the Begin distance.\nThis means the GeometryInstance3D will never be visible.\nTo resolve this, set the End distance to 0 or to a value greater than the Begin distance."));
diff --git a/scene/3d/voxel_gi.cpp b/scene/3d/voxel_gi.cpp
index ffca856fba..80ff176a98 100644
--- a/scene/3d/voxel_gi.cpp
+++ b/scene/3d/voxel_gi.cpp
@@ -518,7 +518,7 @@ AABB VoxelGI::get_aabb() const {
}
PackedStringArray VoxelGI::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
warnings.push_back(RTR("VoxelGI nodes are not supported when using the GL Compatibility backend yet. Support will be added in a future release."));
diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp
index b71f9bc0c4..214c1f77ca 100644
--- a/scene/3d/xr_nodes.cpp
+++ b/scene/3d/xr_nodes.cpp
@@ -77,7 +77,7 @@ void XRCamera3D::_pose_changed(const Ref<XRPose> &p_pose) {
}
PackedStringArray XRCamera3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Camera3D::get_configuration_warnings();
if (is_visible() && is_inside_tree()) {
// Warn if the node has a parent which isn't an XROrigin3D!
@@ -461,7 +461,7 @@ XRNode3D::~XRNode3D() {
}
PackedStringArray XRNode3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (is_visible() && is_inside_tree()) {
// Warn if the node has a parent which isn't an XROrigin3D!
@@ -644,7 +644,7 @@ Plane XRAnchor3D::get_plane() const {
Vector<XROrigin3D *> XROrigin3D::origin_nodes;
PackedStringArray XROrigin3D::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Node3D::get_configuration_warnings();
if (is_visible() && is_inside_tree()) {
bool has_camera = false;
diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp
index 1c5f40f56e..664302d45e 100644
--- a/scene/animation/animation_mixer.cpp
+++ b/scene/animation/animation_mixer.cpp
@@ -1636,6 +1636,9 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
if (t_obj->call(SNAME("get_is_sample"))) {
+ if (t->audio_stream_playback->get_sample_playback().is_valid()) {
+ AudioServer::get_singleton()->stop_sample_playback(t->audio_stream_playback->get_sample_playback());
+ }
Ref<AudioSamplePlayback> sample_playback;
sample_playback.instantiate();
sample_playback->stream = stream;
diff --git a/scene/animation/animation_player.compat.inc b/scene/animation/animation_player.compat.inc
index 39efacc4ca..974eb2a7d8 100644
--- a/scene/animation/animation_player.compat.inc
+++ b/scene/animation/animation_player.compat.inc
@@ -58,14 +58,6 @@ void AnimationPlayer::_seek_bind_compat_80813(double p_time, bool p_update) {
seek(p_time, p_update, false);
}
-void AnimationPlayer::_play_compat_84906(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) {
- play(p_name, p_custom_blend, p_custom_scale, p_from_end);
-}
-
-void AnimationPlayer::_play_backwards_compat_84906(const StringName &p_name, double p_custom_blend) {
- play_backwards(p_name, p_custom_blend);
-}
-
void AnimationPlayer::_bind_compatibility_methods() {
ClassDB::bind_method(D_METHOD("set_process_callback", "mode"), &AnimationPlayer::_set_process_callback_bind_compat_80813);
ClassDB::bind_method(D_METHOD("get_process_callback"), &AnimationPlayer::_get_process_callback_bind_compat_80813);
@@ -74,8 +66,6 @@ void AnimationPlayer::_bind_compatibility_methods() {
ClassDB::bind_method(D_METHOD("set_root", "path"), &AnimationPlayer::_set_root_bind_compat_80813);
ClassDB::bind_method(D_METHOD("get_root"), &AnimationPlayer::_get_root_bind_compat_80813);
ClassDB::bind_compatibility_method(D_METHOD("seek", "seconds", "update"), &AnimationPlayer::_seek_bind_compat_80813, DEFVAL(false));
- ClassDB::bind_compatibility_method(D_METHOD("play", "name", "custom_blend", "custom_speed", "from_end"), &AnimationPlayer::_play_compat_84906, DEFVAL(""), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false));
- ClassDB::bind_compatibility_method(D_METHOD("play_backwards", "name", "custom_blend"), &AnimationPlayer::_play_backwards_compat_84906, DEFVAL(""), DEFVAL(-1));
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_PHYSICS);
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_IDLE);
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_MANUAL);
diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp
index 867bbda4b3..19080e61de 100644
--- a/scene/animation/animation_tree.cpp
+++ b/scene/animation/animation_tree.cpp
@@ -681,7 +681,7 @@ uint64_t AnimationTree::get_last_process_pass() const {
}
PackedStringArray AnimationTree::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = AnimationMixer::get_configuration_warnings();
if (!root_animation_node.is_valid()) {
warnings.push_back(RTR("No root AnimationNode for the graph is set."));
}
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index 15ada0021a..cecddebe88 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -29,7 +29,6 @@
/**************************************************************************/
#include "control.h"
-#include "control.compat.inc"
#include "container.h"
#include "core/config/project_settings.h"
@@ -248,7 +247,7 @@ void Control::get_argument_options(const StringName &p_function, int p_idx, List
PackedStringArray Control::get_configuration_warnings() const {
ERR_READ_THREAD_GUARD_V(PackedStringArray());
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = CanvasItem::get_configuration_warnings();
if (data.mouse_filter == MOUSE_FILTER_IGNORE && !data.tooltip.is_empty()) {
warnings.push_back(RTR("The Hint Tooltip won't be displayed as the control's Mouse Filter is set to \"Ignore\". To solve this, set the Mouse Filter to \"Stop\" or \"Pass\"."));
@@ -2356,6 +2355,24 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons
points[2] = xform.xform(c->get_size());
points[3] = xform.xform(Point2(0, c->get_size().y));
+ // Tie-breaking aims to address situations where a potential focus neighbor's bounding rect
+ // is right next to the currently focused control (e.g. in BoxContainer with
+ // separation overridden to 0). This needs specific handling so that the correct
+ // focus neighbor is selected.
+
+ // Calculate centers of the potential neighbor, currently focused, and closest controls.
+ Point2 center = xform.xform(0.5 * c->get_size());
+ // We only have the points, not an actual reference.
+ Point2 p_center = 0.25 * (p_points[0] + p_points[1] + p_points[2] + p_points[3]);
+ Point2 closest_center;
+ bool should_tiebreak = false;
+ if (*r_closest != nullptr) {
+ should_tiebreak = true;
+ Control *closest = *r_closest;
+ Transform2D closest_xform = closest->get_global_transform();
+ closest_center = closest_xform.xform(0.5 * closest->get_size());
+ }
+
real_t min = 1e7;
for (int i = 0; i < 4; i++) {
@@ -2376,10 +2393,15 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons
Vector2 pa, pb;
real_t d = Geometry2D::get_closest_points_between_segments(la, lb, fa, fb, pa, pb);
- //real_t d = Geometry2D::get_closest_distance_between_segments(Vector3(la.x,la.y,0),Vector3(lb.x,lb.y,0),Vector3(fa.x,fa.y,0),Vector3(fb.x,fb.y,0));
if (d < r_closest_dist) {
r_closest_dist = d;
*r_closest = c;
+ } else if (should_tiebreak && d == r_closest_dist) {
+ // Tie-break in favor of the control most aligned with p_dir.
+ if (p_dir.dot((center - p_center).normalized()) > p_dir.dot((closest_center - p_center).normalized())) {
+ r_closest_dist = d;
+ *r_closest = c;
+ }
}
}
}
diff --git a/scene/gui/control.h b/scene/gui/control.h
index c784d4330d..2655b14562 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -348,10 +348,6 @@ protected:
void _notification(int p_notification);
static void _bind_methods();
-#ifndef DISABLE_DEPRECATED
- static void _bind_compatibility_methods();
-#endif
-
// Exposed virtual methods.
GDVIRTUAL1RC(bool, _has_point, Vector2)
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 373488b0fc..1fc8586448 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -76,6 +76,7 @@ void FileDialog::popup(const Rect2i &p_rect) {
#ifdef TOOLS_ENABLED
if (is_part_of_edited_scene()) {
ConfirmationDialog::popup(p_rect);
+ return;
}
#endif
@@ -1380,7 +1381,7 @@ void FileDialog::set_use_native_dialog(bool p_native) {
#endif
// Replace the built-in dialog with the native one if it's currently visible.
- if (is_visible() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
+ if (is_inside_tree() && is_visible() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
ConfirmationDialog::set_visible(false);
_native_popup();
}
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index c2818edd9c..3b5d4fc33e 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -45,6 +45,70 @@
#include "editor/editor_settings.h"
#endif
+void LineEdit::_edit() {
+ if (!is_inside_tree()) {
+ return;
+ }
+
+ if (!has_focus()) {
+ grab_focus();
+ }
+
+ if (!editable || editing) {
+ return;
+ }
+
+ editing = true;
+ _validate_caret_can_draw();
+
+ DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID;
+ if (wid != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
+ DisplayServer::get_singleton()->window_set_ime_active(true, wid);
+ Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2) + get_global_position();
+ if (get_window()->get_embedder()) {
+ pos += get_viewport()->get_popup_base_transform().get_origin();
+ }
+ DisplayServer::get_singleton()->window_set_ime_position(pos, wid);
+ }
+
+ show_virtual_keyboard();
+ queue_redraw();
+ emit_signal(SNAME("editing_toggled"), true);
+}
+
+void LineEdit::_unedit() {
+ if (!editing) {
+ return;
+ }
+
+ editing = false;
+ _validate_caret_can_draw();
+
+ DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID;
+ if (wid != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
+ DisplayServer::get_singleton()->window_set_ime_position(Point2(), wid);
+ DisplayServer::get_singleton()->window_set_ime_active(false, wid);
+ }
+ ime_text = "";
+ ime_selection = Point2();
+ _shape();
+ set_caret_column(caret_column); // Update scroll_offset.
+
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
+ DisplayServer::get_singleton()->virtual_keyboard_hide();
+ }
+
+ if (deselect_on_focus_loss_enabled && !selection.drag_attempt) {
+ deselect();
+ }
+
+ emit_signal(SNAME("editing_toggled"), false);
+}
+
+bool LineEdit::is_editing() const {
+ return editing;
+}
+
void LineEdit::_swap_current_input_direction() {
if (input_direction == TEXT_DIRECTION_LTR) {
input_direction = TEXT_DIRECTION_RTL;
@@ -52,7 +116,6 @@ void LineEdit::_swap_current_input_direction() {
input_direction = TEXT_DIRECTION_LTR;
}
set_caret_column(get_caret_column());
- queue_redraw();
}
void LineEdit::_move_caret_left(bool p_select, bool p_move_by_word) {
@@ -240,6 +303,11 @@ void LineEdit::_delete(bool p_word, bool p_all_to_right) {
}
void LineEdit::unhandled_key_input(const Ref<InputEvent> &p_event) {
+ // Return to prevent editing if just focused.
+ if (!editing) {
+ return;
+ }
+
Ref<InputEventKey> k = p_event;
if (k.is_valid()) {
@@ -265,26 +333,38 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> b = p_event;
- if (b.is_valid()) {
- if (ime_text.length() != 0) {
- // Ignore mouse clicks in IME input mode.
- return;
- }
- if (b->is_pressed() && b->get_button_index() == MouseButton::RIGHT && context_menu_enabled) {
- _update_context_menu();
- menu->set_position(get_screen_position() + get_local_mouse_position());
- menu->reset_size();
- menu->popup();
- grab_focus();
+ // Ignore mouse clicks in IME input mode.
+ if (b.is_valid() && ime_text.is_empty()) {
+ if (b->is_pressed() && b->get_button_index() == MouseButton::RIGHT) {
+ if (editable && !selection.enabled) {
+ set_caret_at_pixel_pos(b->get_position().x);
+ }
+
+ if (context_menu_enabled) {
+ _update_context_menu();
+ menu->set_position(get_screen_position() + get_local_mouse_position());
+ menu->reset_size();
+ menu->popup();
+ }
+
+ if (editable && !editing) {
+ _edit();
+ }
+
accept_event();
return;
}
- if (is_middle_mouse_paste_enabled() && b->is_pressed() && b->get_button_index() == MouseButton::MIDDLE && is_editable() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
+ if (editable && is_middle_mouse_paste_enabled() && b->is_pressed() && b->get_button_index() == MouseButton::MIDDLE && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
String paste_buffer = DisplayServer::get_singleton()->clipboard_get_primary().strip_escapes();
deselect();
set_caret_at_pixel_pos(b->get_position().x);
+
+ if (!editing) {
+ _edit();
+ }
+
if (!paste_buffer.is_empty()) {
insert_text_at_caret(paste_buffer);
@@ -295,7 +375,6 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
text_changed_dirty = true;
}
}
- grab_focus();
accept_event();
return;
}
@@ -304,10 +383,13 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
return;
}
- _reset_caret_blink_timer();
+ if (editing) {
+ _reset_caret_blink_timer();
+ }
+
if (b->is_pressed()) {
accept_event(); // Don't pass event further when clicked on text field.
- if (!text.is_empty() && is_editable() && _is_over_clear_button(b->get_position())) {
+ if (editable && !text.is_empty() && _is_over_clear_button(b->get_position())) {
clear_button_status.press_attempt = true;
clear_button_status.pressing_inside = true;
queue_redraw();
@@ -330,7 +412,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
const int triple_click_tolerance = 5;
const bool is_triple_click = !b->is_double_click() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < triple_click_timeout && b->get_position().distance_to(last_dblclk_pos) < triple_click_tolerance;
- if (is_triple_click && text.length()) {
+ if (is_triple_click && !text.is_empty()) {
// Triple-click select all.
selection.enabled = true;
selection.begin = 0;
@@ -377,13 +459,17 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
}
}
+ if (editable && !editing) {
+ _edit();
+ return;
+ }
queue_redraw();
} else {
if (selection.enabled && !pass && b->get_button_index() == MouseButton::LEFT && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text());
}
- if (!text.is_empty() && is_editable() && clear_button_enabled) {
+ if (editable && !text.is_empty() && clear_button_enabled) {
bool press_attempt = clear_button_status.press_attempt;
clear_button_status.press_attempt = false;
if (press_attempt && clear_button_status.pressing_inside && _is_over_clear_button(b->get_position())) {
@@ -416,7 +502,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseMotion> m = p_event;
if (m.is_valid()) {
- if (!text.is_empty() && is_editable() && clear_button_enabled) {
+ if (editable && !text.is_empty() && clear_button_enabled) {
bool last_press_inside = clear_button_status.pressing_inside;
clear_button_status.pressing_inside = clear_button_status.press_attempt && _is_over_clear_button(m->get_position());
if (last_press_inside != clear_button_status.pressing_inside) {
@@ -462,221 +548,240 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventKey> k = p_event;
- if (k.is_valid()) {
- if (!k->is_pressed()) {
- if (alt_start && k->get_keycode() == Key::ALT) {
- alt_start = false;
- if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) {
- char32_t ucodestr[2] = { (char32_t)alt_code, 0 };
- insert_text_at_caret(ucodestr);
- }
- accept_event();
- return;
- }
- return;
- }
-
- // Alt + Unicode input:
- if (k->is_alt_pressed()) {
- if (!alt_start) {
- if (k->get_keycode() == Key::KP_ADD) {
- alt_start = true;
- alt_code = 0;
- accept_event();
- return;
- }
- } else {
- if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) {
- alt_code = alt_code << 4;
- alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0);
- }
- if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) {
- alt_code = alt_code << 4;
- alt_code += (uint32_t)(k->get_keycode() - Key::KP_0);
- }
- if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) {
- alt_code = alt_code << 4;
- alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10;
- }
- accept_event();
- return;
- }
- }
+ if (k.is_null()) {
+ return;
+ }
- if (context_menu_enabled) {
- if (k->is_action("ui_menu", true)) {
- _update_context_menu();
- Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2);
- menu->set_position(get_screen_position() + pos);
- menu->reset_size();
- menu->popup();
- menu->grab_focus();
+ if (editable && !editing && k->is_action_pressed("ui_text_submit", false)) {
+ _edit();
+ return;
+ }
- accept_event();
- return;
- }
- }
+ if (!editing) {
+ return;
+ }
- // Default is ENTER and KP_ENTER. Cannot use ui_accept as default includes SPACE.
- if (k->is_action("ui_text_submit", false)) {
- emit_signal(SNAME("text_submitted"), text);
- if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
- DisplayServer::get_singleton()->virtual_keyboard_hide();
+ if (!k->is_pressed()) {
+ if (alt_start && k->get_keycode() == Key::ALT) {
+ alt_start = false;
+ if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) {
+ char32_t ucodestr[2] = { (char32_t)alt_code, 0 };
+ insert_text_at_caret(ucodestr);
}
accept_event();
return;
}
+ return;
+ }
- if (k->is_action("ui_cancel")) {
- callable_mp((Control *)this, &Control::release_focus).call_deferred();
- accept_event();
- return;
- }
-
- if (is_shortcut_keys_enabled()) {
- if (k->is_action("ui_copy", true)) {
- copy_text();
+ // Alt + Unicode input:
+ if (k->is_alt_pressed()) {
+ if (!alt_start) {
+ if (k->get_keycode() == Key::KP_ADD) {
+ alt_start = true;
+ alt_code = 0;
accept_event();
return;
}
-
- if (k->is_action("ui_text_select_all", true)) {
- select();
- accept_event();
- return;
+ } else {
+ if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) {
+ alt_code = alt_code << 4;
+ alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0);
}
-
- // Cut / Paste
- if (k->is_action("ui_cut", true)) {
- cut_text();
- accept_event();
- return;
+ if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) {
+ alt_code = alt_code << 4;
+ alt_code += (uint32_t)(k->get_keycode() - Key::KP_0);
}
-
- if (k->is_action("ui_paste", true)) {
- paste_text();
- accept_event();
- return;
+ if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) {
+ alt_code = alt_code << 4;
+ alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10;
}
-
- // Undo / Redo
- if (k->is_action("ui_undo", true)) {
- undo();
- accept_event();
- return;
- }
-
- if (k->is_action("ui_redo", true)) {
- redo();
- accept_event();
- return;
- }
- }
-
- // BACKSPACE
- if (k->is_action("ui_text_backspace_all_to_left", true)) {
- _backspace(false, true);
- accept_event();
- return;
- }
- if (k->is_action("ui_text_backspace_word", true)) {
- _backspace(true);
- accept_event();
- return;
- }
- if (k->is_action("ui_text_backspace", true)) {
- _backspace();
accept_event();
return;
}
+ }
+
+ if (context_menu_enabled) {
+ if (k->is_action("ui_menu", true)) {
+ _update_context_menu();
+ Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2);
+ menu->set_position(get_screen_position() + pos);
+ menu->reset_size();
+ menu->popup();
+ menu->grab_focus();
- // DELETE
- if (k->is_action("ui_text_delete_all_to_right", true)) {
- _delete(false, true);
- accept_event();
- return;
- }
- if (k->is_action("ui_text_delete_word", true)) {
- _delete(true);
accept_event();
return;
}
- if (k->is_action("ui_text_delete", true)) {
- _delete();
- accept_event();
- return;
+ }
+
+ // Default is ENTER and KP_ENTER. Cannot use ui_accept as default includes SPACE.
+ if (k->is_action_pressed("ui_text_submit")) {
+ emit_signal(SNAME("text_submitted"), text);
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
+ DisplayServer::get_singleton()->virtual_keyboard_hide();
}
- // Cursor Movement
+ if (editing) {
+ _unedit();
+ }
- k = k->duplicate();
- bool shift_pressed = k->is_shift_pressed();
- // Remove shift or else actions will not match. Use above variable for selection.
- k->set_shift_pressed(false);
+ accept_event();
+ return;
+ }
- if (k->is_action("ui_text_caret_word_left", true)) {
- _move_caret_left(shift_pressed, true);
- accept_event();
- return;
+ if (k->is_action("ui_cancel")) {
+ if (editing) {
+ _unedit();
}
- if (k->is_action("ui_text_caret_left", true)) {
- _move_caret_left(shift_pressed);
+
+ accept_event();
+ return;
+ }
+
+ if (is_shortcut_keys_enabled()) {
+ if (k->is_action("ui_copy", true)) {
+ copy_text();
accept_event();
return;
}
- if (k->is_action("ui_text_caret_word_right", true)) {
- _move_caret_right(shift_pressed, true);
+
+ if (k->is_action("ui_text_select_all", true)) {
+ select();
accept_event();
return;
}
- if (k->is_action("ui_text_caret_right", true)) {
- _move_caret_right(shift_pressed, false);
+
+ // Cut / Paste
+ if (k->is_action("ui_cut", true)) {
+ cut_text();
accept_event();
return;
}
- // Up = Home, Down = End
- if (k->is_action("ui_text_caret_up", true) || k->is_action("ui_text_caret_line_start", true) || k->is_action("ui_text_caret_page_up", true)) {
- _move_caret_start(shift_pressed);
+ if (k->is_action("ui_paste", true)) {
+ paste_text();
accept_event();
return;
}
- if (k->is_action("ui_text_caret_down", true) || k->is_action("ui_text_caret_line_end", true) || k->is_action("ui_text_caret_page_down", true)) {
- _move_caret_end(shift_pressed);
+
+ // Undo / Redo
+ if (k->is_action("ui_undo", true)) {
+ undo();
accept_event();
return;
}
- // Misc
- if (k->is_action("ui_swap_input_direction", true)) {
- _swap_current_input_direction();
+ if (k->is_action("ui_redo", true)) {
+ redo();
accept_event();
return;
}
+ }
- _reset_caret_blink_timer();
+ // BACKSPACE
+ if (k->is_action("ui_text_backspace_all_to_left", true)) {
+ _backspace(false, true);
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_backspace_word", true)) {
+ _backspace(true);
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_backspace", true)) {
+ _backspace();
+ accept_event();
+ return;
+ }
- // Allow unicode handling if:
- // * No Modifiers are pressed (except shift)
- bool allow_unicode_handling = !(k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
+ // DELETE
+ if (k->is_action("ui_text_delete_all_to_right", true)) {
+ _delete(false, true);
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_delete_word", true)) {
+ _delete(true);
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_delete", true)) {
+ _delete();
+ accept_event();
+ return;
+ }
- if (allow_unicode_handling && editable && k->get_unicode() >= 32) {
- // Handle Unicode if no modifiers are active.
- selection_delete();
- char32_t ucodestr[2] = { (char32_t)k->get_unicode(), 0 };
- int prev_len = text.length();
- insert_text_at_caret(ucodestr);
- if (text.length() != prev_len) {
- if (!text_changed_dirty) {
- if (is_inside_tree()) {
- callable_mp(this, &LineEdit::_text_changed).call_deferred();
- }
- text_changed_dirty = true;
+ // Cursor Movement
+
+ k = k->duplicate();
+ bool shift_pressed = k->is_shift_pressed();
+ // Remove shift or else actions will not match. Use above variable for selection.
+ k->set_shift_pressed(false);
+
+ if (k->is_action("ui_text_caret_word_left", true)) {
+ _move_caret_left(shift_pressed, true);
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_caret_left", true)) {
+ _move_caret_left(shift_pressed);
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_caret_word_right", true)) {
+ _move_caret_right(shift_pressed, true);
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_caret_right", true)) {
+ _move_caret_right(shift_pressed, false);
+ accept_event();
+ return;
+ }
+
+ // Up = Home, Down = End
+ if (k->is_action("ui_text_caret_up", true) || k->is_action("ui_text_caret_line_start", true) || k->is_action("ui_text_caret_page_up", true)) {
+ _move_caret_start(shift_pressed);
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_caret_down", true) || k->is_action("ui_text_caret_line_end", true) || k->is_action("ui_text_caret_page_down", true)) {
+ _move_caret_end(shift_pressed);
+ accept_event();
+ return;
+ }
+
+ // Misc
+ if (k->is_action("ui_swap_input_direction", true)) {
+ _swap_current_input_direction();
+ accept_event();
+ return;
+ }
+
+ _reset_caret_blink_timer();
+
+ // Allow unicode handling if:
+ // * No Modifiers are pressed (except shift)
+ bool allow_unicode_handling = !(k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
+
+ if (allow_unicode_handling && editable && k->get_unicode() >= 32) {
+ // Handle Unicode if no modifiers are active.
+ selection_delete();
+ char32_t ucodestr[2] = { (char32_t)k->get_unicode(), 0 };
+ int prev_len = text.length();
+ insert_text_at_caret(ucodestr);
+ if (text.length() != prev_len) {
+ if (!text_changed_dirty) {
+ if (is_inside_tree()) {
+ callable_mp(this, &LineEdit::_text_changed).call_deferred();
}
+ text_changed_dirty = true;
}
- accept_event();
- return;
}
+ accept_event();
+ return;
}
}
@@ -1107,7 +1212,7 @@ void LineEdit::_notification(int p_what) {
}
}
- if (has_focus()) {
+ if (editing) {
DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID;
if (wid != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
DisplayServer::get_singleton()->window_set_ime_active(true, wid);
@@ -1121,8 +1226,6 @@ void LineEdit::_notification(int p_what) {
} break;
case NOTIFICATION_FOCUS_ENTER: {
- _validate_caret_can_draw();
-
if (select_all_on_focus) {
if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
// Select all when the mouse button is up.
@@ -1132,43 +1235,20 @@ void LineEdit::_notification(int p_what) {
}
}
- DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID;
- if (wid != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
- DisplayServer::get_singleton()->window_set_ime_active(true, wid);
- Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2) + get_global_position();
- if (get_window()->get_embedder()) {
- pos += get_viewport()->get_popup_base_transform().get_origin();
- }
- DisplayServer::get_singleton()->window_set_ime_position(pos, wid);
+ // Only allow editing if the LineEdit is not focused with arrow keys.
+ if (!(Input::get_singleton()->is_action_pressed("ui_up") || Input::get_singleton()->is_action_pressed("ui_down") || Input::get_singleton()->is_action_pressed("ui_left") || Input::get_singleton()->is_action_pressed("ui_right"))) {
+ _edit();
}
-
- show_virtual_keyboard();
} break;
case NOTIFICATION_FOCUS_EXIT: {
- _validate_caret_can_draw();
-
- DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID;
- if (wid != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
- DisplayServer::get_singleton()->window_set_ime_position(Point2(), wid);
- DisplayServer::get_singleton()->window_set_ime_active(false, wid);
- }
- ime_text = "";
- ime_selection = Point2();
- _shape();
- set_caret_column(caret_column); // Update scroll_offset.
-
- if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
- DisplayServer::get_singleton()->virtual_keyboard_hide();
- }
-
- if (deselect_on_focus_loss_enabled && !selection.drag_attempt) {
- deselect();
+ if (editing) {
+ _unedit();
}
} break;
case MainLoop::NOTIFICATION_OS_IME_UPDATE: {
- if (has_focus()) {
+ if (editing) {
ime_text = DisplayServer::get_singleton()->ime_get_text();
ime_selection = DisplayServer::get_singleton()->ime_get_selection();
@@ -1178,8 +1258,6 @@ void LineEdit::_notification(int p_what) {
_shape();
set_caret_column(caret_column); // Update scroll_offset.
-
- queue_redraw();
}
} break;
@@ -1419,7 +1497,7 @@ Vector2 LineEdit::get_caret_pixel_pos() {
Vector2 ret;
CaretInfo caret;
// Get position of the start of caret.
- if (ime_text.length() != 0 && ime_selection.x != 0) {
+ if (!ime_text.is_empty() && ime_selection.x != 0) {
caret = TS->shaped_text_get_carets(text_rid, caret_column + ime_selection.x);
} else {
caret = TS->shaped_text_get_carets(text_rid, caret_column);
@@ -1432,7 +1510,7 @@ Vector2 LineEdit::get_caret_pixel_pos() {
}
// Get position of the end of caret.
- if (ime_text.length() != 0) {
+ if (!ime_text.is_empty()) {
if (ime_selection.y != 0) {
caret = TS->shaped_text_get_carets(text_rid, caret_column + ime_selection.x + ime_selection.y);
} else {
@@ -1525,11 +1603,11 @@ void LineEdit::_validate_caret_can_draw() {
draw_caret = true;
caret_blink_timer = 0.0;
}
- caret_can_draw = editable && (window_has_focus || (menu && menu->has_focus())) && (has_focus() || caret_force_displayed);
+ caret_can_draw = editing && (window_has_focus || (menu && menu->has_focus())) && (has_focus() || caret_force_displayed);
}
void LineEdit::delete_char() {
- if ((text.length() <= 0) || (caret_column == 0)) {
+ if (text.is_empty() || caret_column == 0) {
return;
}
@@ -1661,7 +1739,7 @@ void LineEdit::clear() {
_text_changed();
// This should reset virtual keyboard state if needed.
- if (has_focus()) {
+ if (editing) {
show_virtual_keyboard();
}
}
@@ -1834,7 +1912,8 @@ Size2 LineEdit::get_minimum_size() const {
Size2 min_size;
// Minimum size of text.
- float em_space_size = font->get_char_size('M', font_size).x;
+ // W is wider than M in most fonts, Using M may result in hiding the last digit when using float values in SpinBox, ie. ColorPicker RAW values.
+ float em_space_size = font->get_char_size('W', font_size).x;
min_size.width = theme_cache.minimum_character_width * em_space_size;
if (expand_to_text_length) {
@@ -1932,7 +2011,8 @@ void LineEdit::select_all() {
return;
}
- if (!text.length()) {
+ if (text.is_empty()) {
+ set_caret_column(0);
return;
}
@@ -1948,6 +2028,10 @@ void LineEdit::set_editable(bool p_editable) {
}
editable = p_editable;
+
+ if (!editable && editing) {
+ _unedit();
+ }
_validate_caret_can_draw();
update_minimum_size();
@@ -2328,6 +2412,7 @@ void LineEdit::_emit_text_change() {
emit_signal(SceneStringName(text_changed), text);
text_changed_dirty = false;
}
+
PackedStringArray LineEdit::get_configuration_warnings() const {
PackedStringArray warnings = Control::get_configuration_warnings();
if (secret_character.length() > 1) {
@@ -2347,13 +2432,13 @@ void LineEdit::_shape() {
TS->shaped_text_clear(text_rid);
String t;
- if (text.length() == 0 && ime_text.length() == 0) {
+ if (text.is_empty() && ime_text.is_empty()) {
t = placeholder_translated;
} else if (pass) {
- String s = (secret_character.length() > 0) ? secret_character.left(1) : U"•";
+ String s = secret_character.is_empty() ? U"•" : secret_character.left(1);
t = s.repeat(text.length() + ime_text.length());
} else {
- if (ime_text.length() > 0) {
+ if (!ime_text.is_empty()) {
t = text.substr(0, caret_column) + ime_text + text.substr(caret_column, text.length());
} else {
t = text;
@@ -2562,6 +2647,7 @@ void LineEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &LineEdit::set_horizontal_alignment);
ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &LineEdit::get_horizontal_alignment);
+ ClassDB::bind_method(D_METHOD("is_editing"), &LineEdit::is_editing);
ClassDB::bind_method(D_METHOD("clear"), &LineEdit::clear);
ClassDB::bind_method(D_METHOD("select", "from", "to"), &LineEdit::select, DEFVAL(0), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("select_all"), &LineEdit::select_all);
@@ -2642,6 +2728,7 @@ void LineEdit::_bind_methods() {
ADD_SIGNAL(MethodInfo("text_changed", PropertyInfo(Variant::STRING, "new_text")));
ADD_SIGNAL(MethodInfo("text_change_rejected", PropertyInfo(Variant::STRING, "rejected_substring")));
ADD_SIGNAL(MethodInfo("text_submitted", PropertyInfo(Variant::STRING, "new_text")));
+ ADD_SIGNAL(MethodInfo("editing_toggled", PropertyInfo(Variant::BOOL, "toggled_on")));
BIND_ENUM_CONSTANT(MENU_CUT);
BIND_ENUM_CONSTANT(MENU_COPY);
diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h
index 993bc727e4..984512745a 100644
--- a/scene/gui/line_edit.h
+++ b/scene/gui/line_edit.h
@@ -86,6 +86,7 @@ public:
private:
HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT;
+ bool editing = false;
bool editable = false;
bool pass = false;
bool text_changed_dirty = false;
@@ -205,6 +206,9 @@ private:
float base_scale = 1.0;
} theme_cache;
+ void _edit();
+ void _unedit();
+
void _clear_undo_stack();
void _clear_redo();
void _create_undo_state();
@@ -257,6 +261,8 @@ protected:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
public:
+ bool is_editing() const;
+
void set_horizontal_alignment(HorizontalAlignment p_alignment);
HorizontalAlignment get_horizontal_alignment() const;
diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp
index c8b022d622..46c9c7cccc 100644
--- a/scene/gui/menu_bar.cpp
+++ b/scene/gui/menu_bar.cpp
@@ -218,15 +218,18 @@ void MenuBar::bind_global_menu() {
int global_start_idx = -1;
int count = nmenu->get_item_count(main_menu);
String prev_tag;
- for (int i = 0; i < count; i++) {
- String tag = nmenu->get_item_tag(main_menu, i).operator String().get_slice("#", 1);
- if (!tag.is_empty() && tag != prev_tag) {
- if (i >= start_index) {
- global_start_idx = i;
- break;
+ if (start_index >= 0) {
+ for (int i = 0; i < count; i++) {
+ String tag = nmenu->get_item_tag(main_menu, i).operator String().get_slice("#", 1);
+ if (!tag.is_empty() && tag != prev_tag) {
+ MenuBar *mb = Object::cast_to<MenuBar>(ObjectDB::get_instance(ObjectID(static_cast<uint64_t>(tag.to_int()))));
+ if (mb && mb->get_start_index() >= start_index) {
+ global_start_idx = i;
+ break;
+ }
}
+ prev_tag = tag;
}
- prev_tag = tag;
}
if (global_start_idx == -1) {
global_start_idx = count;
diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp
index 83359653f1..d7b1a4933d 100644
--- a/scene/gui/range.cpp
+++ b/scene/gui/range.cpp
@@ -31,7 +31,7 @@
#include "range.h"
PackedStringArray Range::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Control::get_configuration_warnings();
if (shared->exp_ratio && shared->min <= 0) {
warnings.push_back(RTR("If \"Exp Edit\" is enabled, \"Min Value\" must be greater than 0."));
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 00175b4347..0c3c90d070 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -333,6 +333,8 @@ void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref<
font_size = font_size_it->font_size;
}
TS->shaped_set_span_update_font(t, i, font->get_rids(), font_size, font->get_opentype_features());
+ } else {
+ TS->shaped_set_span_update_font(t, i, p_base_font->get_rids(), p_base_font_size, p_base_font->get_opentype_features());
}
}
@@ -3045,7 +3047,7 @@ void RichTextLabel::add_text(const String &p_text) {
int pos = 0;
while (pos < p_text.length()) {
- int end = p_text.find("\n", pos);
+ int end = p_text.find_char('\n', pos);
String line;
bool eol = false;
if (end == -1) {
@@ -3412,6 +3414,21 @@ bool RichTextLabel::remove_paragraph(int p_paragraph, bool p_no_invalidate) {
selection.click_item = nullptr;
selection.active = false;
+ if (is_processing_internal()) {
+ bool process_enabled = false;
+ Item *it = main;
+ while (it) {
+ Vector<ItemFX *> fx_stack;
+ _fetch_item_fx_stack(it, fx_stack);
+ if (fx_stack.size()) {
+ process_enabled = true;
+ break;
+ }
+ it = _get_next_item(it, true);
+ }
+ set_process_internal(process_enabled);
+ }
+
if (p_no_invalidate) {
// Do not invalidate cache, only update vertical offsets of the paragraphs after deleted one and scrollbar.
int to_line = main->first_invalid_line.load() - 1;
@@ -3985,6 +4002,7 @@ void RichTextLabel::pop_all() {
void RichTextLabel::clear() {
_stop_thread();
+ set_process_internal(false);
MutexLock data_lock(data_mutex);
main->_clear_children();
@@ -4094,6 +4112,74 @@ void RichTextLabel::parse_bbcode(const String &p_bbcode) {
append_text(p_bbcode);
}
+String RichTextLabel::_get_tag_value(const String &p_tag) {
+ return p_tag.substr(p_tag.find_char('=') + 1);
+}
+
+int RichTextLabel::_find_unquoted(const String &p_src, char32_t p_chr, int p_from) {
+ if (p_from < 0) {
+ return -1;
+ }
+
+ const int len = p_src.length();
+ if (len == 0) {
+ return -1;
+ }
+
+ const char32_t *src = p_src.get_data();
+ bool in_single_quote = false;
+ bool in_double_quote = false;
+ for (int i = p_from; i < len; i++) {
+ if (in_double_quote) {
+ if (src[i] == '"') {
+ in_double_quote = false;
+ }
+ } else if (in_single_quote) {
+ if (src[i] == '\'') {
+ in_single_quote = false;
+ }
+ } else {
+ if (src[i] == '"') {
+ in_double_quote = true;
+ } else if (src[i] == '\'') {
+ in_single_quote = true;
+ } else if (src[i] == p_chr) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+}
+
+Vector<String> RichTextLabel::_split_unquoted(const String &p_src, char32_t p_splitter) {
+ Vector<String> ret;
+
+ if (p_src.is_empty()) {
+ return ret;
+ }
+
+ int from = 0;
+ int len = p_src.length();
+
+ while (true) {
+ int end = _find_unquoted(p_src, p_splitter, from);
+ if (end < 0) {
+ end = len;
+ }
+ if (end > from) {
+ ret.push_back(p_src.substr(from, end - from));
+ }
+ if (end == len) {
+ break;
+ }
+
+ from = end + 1;
+ }
+
+ return ret;
+}
+
void RichTextLabel::append_text(const String &p_bbcode) {
_stop_thread();
MutexLock data_lock(data_mutex);
@@ -4109,10 +4195,8 @@ void RichTextLabel::append_text(const String &p_bbcode) {
bool after_list_open_tag = false;
bool after_list_close_tag = false;
- set_process_internal(false);
-
while (pos <= p_bbcode.length()) {
- int brk_pos = p_bbcode.find("[", pos);
+ int brk_pos = p_bbcode.find_char('[', pos);
if (brk_pos < 0) {
brk_pos = p_bbcode.length();
@@ -4137,7 +4221,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
break; //nothing else to add
}
- int brk_end = p_bbcode.find("]", brk_pos + 1);
+ int brk_end = _find_unquoted(p_bbcode, ']', brk_pos + 1);
if (brk_end == -1) {
//no close, add the rest
@@ -4147,7 +4231,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
}
String tag = p_bbcode.substr(brk_pos + 1, brk_end - brk_pos - 1);
- Vector<String> split_tag_block = tag.split(" ", false);
+ Vector<String> split_tag_block = _split_unquoted(tag, ' ');
// Find optional parameters.
String bbcode_name;
@@ -4157,7 +4241,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
bbcode_name = split_tag_block[0];
for (int i = 1; i < split_tag_block.size(); i++) {
const String &expr = split_tag_block[i];
- int value_pos = expr.find("=");
+ int value_pos = expr.find_char('=');
if (value_pos > -1) {
bbcode_options[expr.substr(0, value_pos)] = expr.substr(value_pos + 1).unquote();
}
@@ -4168,7 +4252,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
// Find main parameter.
String bbcode_value;
- int main_value_pos = bbcode_name.find("=");
+ int main_value_pos = bbcode_name.find_char('=');
if (main_value_pos > -1) {
bbcode_value = bbcode_name.substr(main_value_pos + 1);
bbcode_name = bbcode_name.substr(0, main_value_pos);
@@ -4267,10 +4351,10 @@ void RichTextLabel::append_text(const String &p_bbcode) {
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag.begins_with("table=")) {
- Vector<String> subtag = tag.substr(6, tag.length()).split(",");
+ Vector<String> subtag = _split_unquoted(_get_tag_value(tag), U',');
_normalize_subtags(subtag);
- int columns = subtag[0].to_int();
+ int columns = (subtag.is_empty()) ? 1 : subtag[0].to_int();
if (columns < 1) {
columns = 1;
}
@@ -4317,7 +4401,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag.begins_with("cell=")) {
- int ratio = tag.substr(5, tag.length()).to_int();
+ int ratio = _get_tag_value(tag).to_int();
if (ratio < 1) {
ratio = 1;
}
@@ -4328,54 +4412,45 @@ void RichTextLabel::append_text(const String &p_bbcode) {
pos = brk_end + 1;
tag_stack.push_front("cell");
} else if (tag.begins_with("cell ")) {
- Vector<String> subtag = tag.substr(5, tag.length()).split(" ");
- _normalize_subtags(subtag);
-
- for (int i = 0; i < subtag.size(); i++) {
- Vector<String> subtag_a = subtag[i].split("=");
- _normalize_subtags(subtag_a);
-
- if (subtag_a.size() == 2) {
- if (subtag_a[0] == "expand") {
- int ratio = subtag_a[1].to_int();
- if (ratio < 1) {
- ratio = 1;
- }
- set_table_column_expand(get_current_table_column(), true, ratio);
- }
+ OptionMap::Iterator expand_option = bbcode_options.find("expand");
+ if (expand_option) {
+ int ratio = expand_option->value.to_int();
+ if (ratio < 1) {
+ ratio = 1;
}
+ set_table_column_expand(get_current_table_column(), true, ratio);
}
+
push_cell();
const Color fallback_color = Color(0, 0, 0, 0);
- for (int i = 0; i < subtag.size(); i++) {
- Vector<String> subtag_a = subtag[i].split("=");
- _normalize_subtags(subtag_a);
-
- if (subtag_a.size() == 2) {
- if (subtag_a[0] == "border") {
- Color color = Color::from_string(subtag_a[1], fallback_color);
- set_cell_border_color(color);
- } else if (subtag_a[0] == "bg") {
- Vector<String> subtag_b = subtag_a[1].split(",");
- _normalize_subtags(subtag_b);
- if (subtag_b.size() == 2) {
- Color color1 = Color::from_string(subtag_b[0], fallback_color);
- Color color2 = Color::from_string(subtag_b[1], fallback_color);
- set_cell_row_background_color(color1, color2);
- }
- if (subtag_b.size() == 1) {
- Color color1 = Color::from_string(subtag_a[1], fallback_color);
- set_cell_row_background_color(color1, color1);
- }
- } else if (subtag_a[0] == "padding") {
- Vector<String> subtag_b = subtag_a[1].split(",");
- _normalize_subtags(subtag_b);
+ OptionMap::Iterator border_option = bbcode_options.find("border");
+ if (border_option) {
+ Color color = Color::from_string(border_option->value, fallback_color);
+ set_cell_border_color(color);
+ }
+ OptionMap::Iterator bg_option = bbcode_options.find("bg");
+ if (bg_option) {
+ Vector<String> subtag_b = _split_unquoted(bg_option->value, U',');
+ _normalize_subtags(subtag_b);
- if (subtag_b.size() == 4) {
- set_cell_padding(Rect2(subtag_b[0].to_float(), subtag_b[1].to_float(), subtag_b[2].to_float(), subtag_b[3].to_float()));
- }
- }
+ if (subtag_b.size() == 2) {
+ Color color1 = Color::from_string(subtag_b[0], fallback_color);
+ Color color2 = Color::from_string(subtag_b[1], fallback_color);
+ set_cell_row_background_color(color1, color2);
+ }
+ if (subtag_b.size() == 1) {
+ Color color1 = Color::from_string(bg_option->value, fallback_color);
+ set_cell_row_background_color(color1, color1);
+ }
+ }
+ OptionMap::Iterator padding_option = bbcode_options.find("padding");
+ if (padding_option) {
+ Vector<String> subtag_b = _split_unquoted(padding_option->value, U',');
+ _normalize_subtags(subtag_b);
+
+ if (subtag_b.size() == 4) {
+ set_cell_padding(Rect2(subtag_b[0].to_float(), subtag_b[1].to_float(), subtag_b[2].to_float(), subtag_b[3].to_float()));
}
}
@@ -4392,7 +4467,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag.begins_with("char=")) {
- int32_t char_code = tag.substr(5, tag.length()).hex_to_int();
+ int32_t char_code = _get_tag_value(tag).hex_to_int();
add_text(String::chr(char_code));
pos = brk_end + 1;
} else if (tag == "lb") {
@@ -4471,7 +4546,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag.begins_with("ul bullet=")) {
- String bullet = tag.substr(10, 1);
+ String bullet = _get_tag_value(tag);
indent_level++;
push_list(indent_level, LIST_DOTS, false, bullet);
pos = brk_end + 1;
@@ -4507,7 +4582,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag.begins_with("lang=")) {
- String lang = tag.substr(5, tag.length()).unquote();
+ String lang = _get_tag_value(tag).unquote();
push_language(lang);
pos = brk_end + 1;
tag_stack.push_front("lang");
@@ -4516,89 +4591,104 @@ void RichTextLabel::append_text(const String &p_bbcode) {
pos = brk_end + 1;
tag_stack.push_front("p");
} else if (tag.begins_with("p ")) {
- Vector<String> subtag = tag.substr(2, tag.length()).split(" ");
- _normalize_subtags(subtag);
-
HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT;
Control::TextDirection dir = Control::TEXT_DIRECTION_INHERITED;
String lang = language;
PackedFloat32Array tab_stops = default_tab_stops;
TextServer::StructuredTextParser st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT;
BitField<TextServer::JustificationFlag> jst_flags = default_jst_flags;
- for (int i = 0; i < subtag.size(); i++) {
- Vector<String> subtag_a = subtag[i].split("=");
- _normalize_subtags(subtag_a);
-
- if (subtag_a.size() == 2) {
- if (subtag_a[0] == "justification_flags" || subtag_a[0] == "jst") {
- Vector<String> subtag_b = subtag_a[1].split(",");
- jst_flags = 0; // Clear flags.
- for (const String &E : subtag_b) {
- if (E == "kashida" || E == "k") {
- jst_flags.set_flag(TextServer::JUSTIFICATION_KASHIDA);
- } else if (E == "word" || E == "w") {
- jst_flags.set_flag(TextServer::JUSTIFICATION_WORD_BOUND);
- } else if (E == "trim" || E == "tr") {
- jst_flags.set_flag(TextServer::JUSTIFICATION_TRIM_EDGE_SPACES);
- } else if (E == "after_last_tab" || E == "lt") {
- jst_flags.set_flag(TextServer::JUSTIFICATION_AFTER_LAST_TAB);
- } else if (E == "skip_last" || E == "sl") {
- jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE);
- } else if (E == "skip_last_with_chars" || E == "sv") {
- jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS);
- } else if (E == "do_not_skip_single" || E == "ns") {
- jst_flags.set_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE);
- }
- }
- } else if (subtag_a[0] == "tab_stops") {
- Vector<String> splitters;
- splitters.push_back(",");
- splitters.push_back(";");
- tab_stops = subtag_a[1].split_floats_mk(splitters);
- } else if (subtag_a[0] == "align") {
- if (subtag_a[1] == "l" || subtag_a[1] == "left") {
- alignment = HORIZONTAL_ALIGNMENT_LEFT;
- } else if (subtag_a[1] == "c" || subtag_a[1] == "center") {
- alignment = HORIZONTAL_ALIGNMENT_CENTER;
- } else if (subtag_a[1] == "r" || subtag_a[1] == "right") {
- alignment = HORIZONTAL_ALIGNMENT_RIGHT;
- } else if (subtag_a[1] == "f" || subtag_a[1] == "fill") {
- alignment = HORIZONTAL_ALIGNMENT_FILL;
- }
- } else if (subtag_a[0] == "dir" || subtag_a[0] == "direction") {
- if (subtag_a[1] == "a" || subtag_a[1] == "auto") {
- dir = Control::TEXT_DIRECTION_AUTO;
- } else if (subtag_a[1] == "l" || subtag_a[1] == "ltr") {
- dir = Control::TEXT_DIRECTION_LTR;
- } else if (subtag_a[1] == "r" || subtag_a[1] == "rtl") {
- dir = Control::TEXT_DIRECTION_RTL;
- }
- } else if (subtag_a[0] == "lang" || subtag_a[0] == "language") {
- lang = subtag_a[1];
- } else if (subtag_a[0] == "st" || subtag_a[0] == "bidi_override") {
- if (subtag_a[1] == "d" || subtag_a[1] == "default") {
- st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT;
- } else if (subtag_a[1] == "u" || subtag_a[1] == "uri") {
- st_parser_type = TextServer::STRUCTURED_TEXT_URI;
- } else if (subtag_a[1] == "f" || subtag_a[1] == "file") {
- st_parser_type = TextServer::STRUCTURED_TEXT_FILE;
- } else if (subtag_a[1] == "e" || subtag_a[1] == "email") {
- st_parser_type = TextServer::STRUCTURED_TEXT_EMAIL;
- } else if (subtag_a[1] == "l" || subtag_a[1] == "list") {
- st_parser_type = TextServer::STRUCTURED_TEXT_LIST;
- } else if (subtag_a[1] == "n" || subtag_a[1] == "gdscript") {
- st_parser_type = TextServer::STRUCTURED_TEXT_GDSCRIPT;
- } else if (subtag_a[1] == "c" || subtag_a[1] == "custom") {
- st_parser_type = TextServer::STRUCTURED_TEXT_CUSTOM;
- }
+
+ OptionMap::Iterator justification_flags_option = bbcode_options.find("justification_flags");
+ if (!justification_flags_option) {
+ justification_flags_option = bbcode_options.find("jst");
+ }
+ if (justification_flags_option) {
+ Vector<String> subtag_b = _split_unquoted(justification_flags_option->value, U',');
+ jst_flags = 0; // Clear flags.
+ for (const String &E : subtag_b) {
+ if (E == "kashida" || E == "k") {
+ jst_flags.set_flag(TextServer::JUSTIFICATION_KASHIDA);
+ } else if (E == "word" || E == "w") {
+ jst_flags.set_flag(TextServer::JUSTIFICATION_WORD_BOUND);
+ } else if (E == "trim" || E == "tr") {
+ jst_flags.set_flag(TextServer::JUSTIFICATION_TRIM_EDGE_SPACES);
+ } else if (E == "after_last_tab" || E == "lt") {
+ jst_flags.set_flag(TextServer::JUSTIFICATION_AFTER_LAST_TAB);
+ } else if (E == "skip_last" || E == "sl") {
+ jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE);
+ } else if (E == "skip_last_with_chars" || E == "sv") {
+ jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS);
+ } else if (E == "do_not_skip_single" || E == "ns") {
+ jst_flags.set_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE);
}
}
}
+ OptionMap::Iterator tab_stops_option = bbcode_options.find("tab_stops");
+ if (tab_stops_option) {
+ Vector<String> splitters;
+ splitters.push_back(",");
+ splitters.push_back(";");
+ tab_stops = tab_stops_option->value.split_floats_mk(splitters);
+ }
+ OptionMap::Iterator align_option = bbcode_options.find("align");
+ if (align_option) {
+ if (align_option->value == "l" || align_option->value == "left") {
+ alignment = HORIZONTAL_ALIGNMENT_LEFT;
+ } else if (align_option->value == "c" || align_option->value == "center") {
+ alignment = HORIZONTAL_ALIGNMENT_CENTER;
+ } else if (align_option->value == "r" || align_option->value == "right") {
+ alignment = HORIZONTAL_ALIGNMENT_RIGHT;
+ } else if (align_option->value == "f" || align_option->value == "fill") {
+ alignment = HORIZONTAL_ALIGNMENT_FILL;
+ }
+ }
+ OptionMap::Iterator direction_option = bbcode_options.find("direction");
+ if (!direction_option) {
+ direction_option = bbcode_options.find("dir");
+ }
+ if (direction_option) {
+ if (direction_option->value == "a" || direction_option->value == "auto") {
+ dir = Control::TEXT_DIRECTION_AUTO;
+ } else if (direction_option->value == "l" || direction_option->value == "ltr") {
+ dir = Control::TEXT_DIRECTION_LTR;
+ } else if (direction_option->value == "r" || direction_option->value == "rtl") {
+ dir = Control::TEXT_DIRECTION_RTL;
+ }
+ }
+ OptionMap::Iterator language_option = bbcode_options.find("language");
+ if (!language_option) {
+ language_option = bbcode_options.find("lang");
+ }
+ if (language_option) {
+ lang = language_option->value;
+ }
+ OptionMap::Iterator bidi_override_option = bbcode_options.find("bidi_override");
+ if (!bidi_override_option) {
+ bidi_override_option = bbcode_options.find("st");
+ }
+ if (bidi_override_option) {
+ if (bidi_override_option->value == "d" || bidi_override_option->value == "default") {
+ st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT;
+ } else if (bidi_override_option->value == "u" || bidi_override_option->value == "uri") {
+ st_parser_type = TextServer::STRUCTURED_TEXT_URI;
+ } else if (bidi_override_option->value == "f" || bidi_override_option->value == "file") {
+ st_parser_type = TextServer::STRUCTURED_TEXT_FILE;
+ } else if (bidi_override_option->value == "e" || bidi_override_option->value == "email") {
+ st_parser_type = TextServer::STRUCTURED_TEXT_EMAIL;
+ } else if (bidi_override_option->value == "l" || bidi_override_option->value == "list") {
+ st_parser_type = TextServer::STRUCTURED_TEXT_LIST;
+ } else if (bidi_override_option->value == "n" || bidi_override_option->value == "gdscript") {
+ st_parser_type = TextServer::STRUCTURED_TEXT_GDSCRIPT;
+ } else if (bidi_override_option->value == "c" || bidi_override_option->value == "custom") {
+ st_parser_type = TextServer::STRUCTURED_TEXT_CUSTOM;
+ }
+ }
+
push_paragraph(alignment, dir, lang, st_parser_type, jst_flags, tab_stops);
pos = brk_end + 1;
tag_stack.push_front("p");
} else if (tag == "url") {
- int end = p_bbcode.find("[", brk_end);
+ int end = p_bbcode.find_char('[', brk_end);
if (end == -1) {
end = p_bbcode.length();
}
@@ -4609,19 +4699,16 @@ void RichTextLabel::append_text(const String &p_bbcode) {
tag_stack.push_front(tag);
} else if (tag.begins_with("url=")) {
- String url = tag.substr(4, tag.length()).unquote();
+ String url = _get_tag_value(tag).unquote();
push_meta(url, META_UNDERLINE_ALWAYS);
pos = brk_end + 1;
tag_stack.push_front("url");
} else if (tag.begins_with("hint=")) {
- String description = tag.substr(5, tag.length()).unquote();
+ String description = _get_tag_value(tag).unquote();
push_hint(description);
pos = brk_end + 1;
tag_stack.push_front("hint");
} else if (tag.begins_with("dropcap")) {
- Vector<String> subtag = tag.substr(5, tag.length()).split(" ");
- _normalize_subtags(subtag);
-
int fs = theme_cache.normal_font_size * 3;
Ref<Font> f = theme_cache.normal_font;
Color color = theme_cache.default_color;
@@ -4629,39 +4716,47 @@ void RichTextLabel::append_text(const String &p_bbcode) {
int outline_size = theme_cache.outline_size;
Rect2 dropcap_margins;
- for (int i = 0; i < subtag.size(); i++) {
- Vector<String> subtag_a = subtag[i].split("=");
- _normalize_subtags(subtag_a);
-
- if (subtag_a.size() == 2) {
- if (subtag_a[0] == "font" || subtag_a[0] == "f") {
- const String &fnt = subtag_a[1];
- Ref<Font> font = ResourceLoader::load(fnt, "Font");
- if (font.is_valid()) {
- f = font;
- }
- } else if (subtag_a[0] == "font_size") {
- fs = subtag_a[1].to_int();
- } else if (subtag_a[0] == "margins") {
- Vector<String> subtag_b = subtag_a[1].split(",");
- _normalize_subtags(subtag_b);
+ OptionMap::Iterator font_option = bbcode_options.find("font");
+ if (!font_option) {
+ font_option = bbcode_options.find("f");
+ }
+ if (font_option) {
+ const String &fnt = font_option->value;
+ Ref<Font> font = ResourceLoader::load(fnt, "Font");
+ if (font.is_valid()) {
+ f = font;
+ }
+ }
+ OptionMap::Iterator font_size_option = bbcode_options.find("font_size");
+ if (font_size_option) {
+ fs = font_size_option->value.to_int();
+ }
+ OptionMap::Iterator margins_option = bbcode_options.find("margins");
+ if (margins_option) {
+ Vector<String> subtag_b = _split_unquoted(margins_option->value, U',');
+ _normalize_subtags(subtag_b);
- if (subtag_b.size() == 4) {
- dropcap_margins.position.x = subtag_b[0].to_float();
- dropcap_margins.position.y = subtag_b[1].to_float();
- dropcap_margins.size.x = subtag_b[2].to_float();
- dropcap_margins.size.y = subtag_b[3].to_float();
- }
- } else if (subtag_a[0] == "outline_size") {
- outline_size = subtag_a[1].to_int();
- } else if (subtag_a[0] == "color") {
- color = Color::from_string(subtag_a[1], color);
- } else if (subtag_a[0] == "outline_color") {
- outline_color = Color::from_string(subtag_a[1], outline_color);
- }
+ if (subtag_b.size() == 4) {
+ dropcap_margins.position.x = subtag_b[0].to_float();
+ dropcap_margins.position.y = subtag_b[1].to_float();
+ dropcap_margins.size.x = subtag_b[2].to_float();
+ dropcap_margins.size.y = subtag_b[3].to_float();
}
}
- int end = p_bbcode.find("[", brk_end);
+ OptionMap::Iterator outline_size_option = bbcode_options.find("outline_size");
+ if (outline_size_option) {
+ outline_size = outline_size_option->value.to_int();
+ }
+ OptionMap::Iterator color_option = bbcode_options.find("color");
+ if (color_option) {
+ color = Color::from_string(color_option->value, color);
+ }
+ OptionMap::Iterator outline_color_option = bbcode_options.find("outline_color");
+ if (outline_color_option) {
+ outline_color = Color::from_string(outline_color_option->value, outline_color);
+ }
+
+ int end = p_bbcode.find_char('[', brk_end);
if (end == -1) {
end = p_bbcode.length();
}
@@ -4675,7 +4770,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
} else if (tag.begins_with("img")) {
int alignment = INLINE_ALIGNMENT_CENTER;
if (tag.begins_with("img=")) {
- Vector<String> subtag = tag.substr(4, tag.length()).split(",");
+ Vector<String> subtag = _split_unquoted(_get_tag_value(tag), U',');
_normalize_subtags(subtag);
if (subtag.size() > 1) {
@@ -4706,7 +4801,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
}
}
- int end = p_bbcode.find("[", brk_end);
+ int end = p_bbcode.find_char('[', brk_end);
if (end == -1) {
end = p_bbcode.length();
}
@@ -4718,7 +4813,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
Rect2 region;
OptionMap::Iterator region_option = bbcode_options.find("region");
if (region_option) {
- Vector<String> region_values = region_option->value.split(",", false);
+ Vector<String> region_values = _split_unquoted(region_option->value, U',');
if (region_values.size() == 4) {
region.position.x = region_values[0].to_float();
region.position.y = region_values[1].to_float();
@@ -4780,27 +4875,27 @@ void RichTextLabel::append_text(const String &p_bbcode) {
pos = end;
tag_stack.push_front(bbcode_name);
} else if (tag.begins_with("color=")) {
- String color_str = tag.substr(6, tag.length()).unquote();
+ String color_str = _get_tag_value(tag).unquote();
Color color = Color::from_string(color_str, theme_cache.default_color);
push_color(color);
pos = brk_end + 1;
tag_stack.push_front("color");
} else if (tag.begins_with("outline_color=")) {
- String color_str = tag.substr(14, tag.length()).unquote();
+ String color_str = _get_tag_value(tag).unquote();
Color color = Color::from_string(color_str, theme_cache.default_color);
push_outline_color(color);
pos = brk_end + 1;
tag_stack.push_front("outline_color");
} else if (tag.begins_with("font_size=")) {
- int fnt_size = tag.substr(10, tag.length()).to_int();
+ int fnt_size = _get_tag_value(tag).to_int();
push_font_size(fnt_size);
pos = brk_end + 1;
tag_stack.push_front("font_size");
} else if (tag.begins_with("opentype_features=") || tag.begins_with("otf=")) {
- int value_pos = tag.find("=");
+ int value_pos = tag.find_char('=');
String fnt_ftr = tag.substr(value_pos + 1);
Vector<String> subtag = fnt_ftr.split(",");
_normalize_subtags(subtag);
@@ -4844,7 +4939,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
tag_stack.push_front(tag.substr(0, value_pos));
} else if (tag.begins_with("font=")) {
- String fnt = tag.substr(5, tag.length()).unquote();
+ String fnt = _get_tag_value(tag).unquote();
Ref<Font> fc = ResourceLoader::load(fnt, "Font");
if (fc.is_valid()) {
@@ -4855,11 +4950,9 @@ void RichTextLabel::append_text(const String &p_bbcode) {
tag_stack.push_front("font");
} else if (tag.begins_with("font ")) {
- Vector<String> subtag = tag.substr(2, tag.length()).split(" ");
- _normalize_subtags(subtag);
-
Ref<Font> font = theme_cache.normal_font;
DefaultFont def_font = NORMAL_FONT;
+ int fnt_size = -1;
ItemFont *font_it = _find_font(current);
if (font_it) {
@@ -4872,75 +4965,122 @@ void RichTextLabel::append_text(const String &p_bbcode) {
Ref<FontVariation> fc;
fc.instantiate();
- int fnt_size = -1;
- for (int i = 1; i < subtag.size(); i++) {
- Vector<String> subtag_a = subtag[i].split("=", true, 1);
- _normalize_subtags(subtag_a);
-
- if (subtag_a.size() == 2) {
- if (subtag_a[0] == "name" || subtag_a[0] == "n") {
- const String &fnt = subtag_a[1];
- Ref<Font> font_data = ResourceLoader::load(fnt, "Font");
- if (font_data.is_valid()) {
- font = font_data;
- def_font = CUSTOM_FONT;
- }
- } else if (subtag_a[0] == "size" || subtag_a[0] == "s") {
- fnt_size = subtag_a[1].to_int();
- } else if (subtag_a[0] == "glyph_spacing" || subtag_a[0] == "gl") {
- int spacing = subtag_a[1].to_int();
- fc->set_spacing(TextServer::SPACING_GLYPH, spacing);
- } else if (subtag_a[0] == "space_spacing" || subtag_a[0] == "sp") {
- int spacing = subtag_a[1].to_int();
- fc->set_spacing(TextServer::SPACING_SPACE, spacing);
- } else if (subtag_a[0] == "top_spacing" || subtag_a[0] == "top") {
- int spacing = subtag_a[1].to_int();
- fc->set_spacing(TextServer::SPACING_TOP, spacing);
- } else if (subtag_a[0] == "bottom_spacing" || subtag_a[0] == "bt") {
- int spacing = subtag_a[1].to_int();
- fc->set_spacing(TextServer::SPACING_BOTTOM, spacing);
- } else if (subtag_a[0] == "embolden" || subtag_a[0] == "emb") {
- float emb = subtag_a[1].to_float();
- fc->set_variation_embolden(emb);
- } else if (subtag_a[0] == "face_index" || subtag_a[0] == "fi") {
- int fi = subtag_a[1].to_int();
- fc->set_variation_face_index(fi);
- } else if (subtag_a[0] == "slant" || subtag_a[0] == "sln") {
- float slant = subtag_a[1].to_float();
- fc->set_variation_transform(Transform2D(1.0, slant, 0.0, 1.0, 0.0, 0.0));
- } else if (subtag_a[0] == "opentype_variation" || subtag_a[0] == "otv") {
- Dictionary variations;
- if (!subtag_a[1].is_empty()) {
- Vector<String> variation_tags = subtag_a[1].split(",");
- for (int j = 0; j < variation_tags.size(); j++) {
- Vector<String> subtag_b = variation_tags[j].split("=");
- _normalize_subtags(subtag_b);
-
- if (subtag_b.size() == 2) {
- variations[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float();
- }
- }
- fc->set_variation_opentype(variations);
+ OptionMap::Iterator name_option = bbcode_options.find("name");
+ if (!name_option) {
+ name_option = bbcode_options.find("n");
+ }
+ if (name_option) {
+ const String &fnt = name_option->value;
+ Ref<Font> font_data = ResourceLoader::load(fnt, "Font");
+ if (font_data.is_valid()) {
+ font = font_data;
+ def_font = CUSTOM_FONT;
+ }
+ }
+ OptionMap::Iterator size_option = bbcode_options.find("size");
+ if (!size_option) {
+ size_option = bbcode_options.find("s");
+ }
+ if (size_option) {
+ fnt_size = size_option->value.to_int();
+ }
+ OptionMap::Iterator glyph_spacing_option = bbcode_options.find("glyph_spacing");
+ if (!glyph_spacing_option) {
+ glyph_spacing_option = bbcode_options.find("gl");
+ }
+ if (glyph_spacing_option) {
+ int spacing = glyph_spacing_option->value.to_int();
+ fc->set_spacing(TextServer::SPACING_GLYPH, spacing);
+ }
+ OptionMap::Iterator space_spacing_option = bbcode_options.find("space_spacing");
+ if (!space_spacing_option) {
+ space_spacing_option = bbcode_options.find("sp");
+ }
+ if (space_spacing_option) {
+ int spacing = space_spacing_option->value.to_int();
+ fc->set_spacing(TextServer::SPACING_SPACE, spacing);
+ }
+ OptionMap::Iterator top_spacing_option = bbcode_options.find("top_spacing");
+ if (!top_spacing_option) {
+ top_spacing_option = bbcode_options.find("top");
+ }
+ if (top_spacing_option) {
+ int spacing = top_spacing_option->value.to_int();
+ fc->set_spacing(TextServer::SPACING_TOP, spacing);
+ }
+ OptionMap::Iterator bottom_spacing_option = bbcode_options.find("bottom_spacing");
+ if (!bottom_spacing_option) {
+ bottom_spacing_option = bbcode_options.find("bt");
+ }
+ if (bottom_spacing_option) {
+ int spacing = bottom_spacing_option->value.to_int();
+ fc->set_spacing(TextServer::SPACING_BOTTOM, spacing);
+ }
+ OptionMap::Iterator embolden_option = bbcode_options.find("embolden");
+ if (!embolden_option) {
+ embolden_option = bbcode_options.find("emb");
+ }
+ if (embolden_option) {
+ float emb = embolden_option->value.to_float();
+ fc->set_variation_embolden(emb);
+ }
+ OptionMap::Iterator face_index_option = bbcode_options.find("face_index");
+ if (!face_index_option) {
+ face_index_option = bbcode_options.find("fi");
+ }
+ if (face_index_option) {
+ int fi = face_index_option->value.to_int();
+ fc->set_variation_face_index(fi);
+ }
+ OptionMap::Iterator slant_option = bbcode_options.find("slant");
+ if (!slant_option) {
+ slant_option = bbcode_options.find("sln");
+ }
+ if (slant_option) {
+ float slant = slant_option->value.to_float();
+ fc->set_variation_transform(Transform2D(1.0, slant, 0.0, 1.0, 0.0, 0.0));
+ }
+ OptionMap::Iterator opentype_variation_option = bbcode_options.find("opentype_variation");
+ if (!opentype_variation_option) {
+ opentype_variation_option = bbcode_options.find("otv");
+ }
+ if (opentype_variation_option) {
+ Dictionary variations;
+ if (!opentype_variation_option->value.is_empty()) {
+ Vector<String> variation_tags = opentype_variation_option->value.split(",");
+ for (int j = 0; j < variation_tags.size(); j++) {
+ Vector<String> subtag_b = variation_tags[j].split("=");
+ _normalize_subtags(subtag_b);
+
+ if (subtag_b.size() == 2) {
+ variations[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float();
}
- } else if (subtag_a[0] == "opentype_features" || subtag_a[0] == "otf") {
- Dictionary features;
- if (!subtag_a[1].is_empty()) {
- Vector<String> feature_tags = subtag_a[1].split(",");
- for (int j = 0; j < feature_tags.size(); j++) {
- Vector<String> subtag_b = feature_tags[j].split("=");
- _normalize_subtags(subtag_b);
-
- if (subtag_b.size() == 2) {
- features[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float();
- } else if (subtag_b.size() == 1) {
- features[TS->name_to_tag(subtag_b[0])] = 1;
- }
- }
- fc->set_opentype_features(features);
+ }
+ fc->set_variation_opentype(variations);
+ }
+ }
+ OptionMap::Iterator opentype_features_option = bbcode_options.find("opentype_features");
+ if (!opentype_features_option) {
+ opentype_features_option = bbcode_options.find("otf");
+ }
+ if (opentype_features_option) {
+ Dictionary features;
+ if (!opentype_features_option->value.is_empty()) {
+ Vector<String> feature_tags = opentype_features_option->value.split(",");
+ for (int j = 0; j < feature_tags.size(); j++) {
+ Vector<String> subtag_b = feature_tags[j].split("=");
+ _normalize_subtags(subtag_b);
+
+ if (subtag_b.size() == 2) {
+ features[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float();
+ } else if (subtag_b.size() == 1) {
+ features[TS->name_to_tag(subtag_b[0])] = 1;
}
}
+ fc->set_opentype_features(features);
}
}
+
fc->set_base_font(font);
if (def_font != CUSTOM_FONT) {
@@ -4953,7 +5093,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
tag_stack.push_front("font");
} else if (tag.begins_with("outline_size=")) {
- int fnt_size = tag.substr(13, tag.length()).to_int();
+ int fnt_size = _get_tag_value(tag).to_int();
if (fnt_size > 0) {
push_outline_size(fnt_size);
}
@@ -5092,7 +5232,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
tag_stack.push_front("pulse");
set_process_internal(true);
} else if (tag.begins_with("bgcolor=")) {
- String color_str = tag.substr(8, tag.length()).unquote();
+ String color_str = _get_tag_value(tag).unquote();
Color color = Color::from_string(color_str, theme_cache.default_color);
push_bgcolor(color);
@@ -5100,7 +5240,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
tag_stack.push_front("bgcolor");
} else if (tag.begins_with("fgcolor=")) {
- String color_str = tag.substr(8, tag.length()).unquote();
+ String color_str = _get_tag_value(tag).unquote();
Color color = Color::from_string(color_str, theme_cache.default_color);
push_fgcolor(color);
@@ -5129,17 +5269,6 @@ void RichTextLabel::append_text(const String &p_bbcode) {
}
}
}
-
- Vector<ItemFX *> fx_items;
- for (Item *E : main->subitems) {
- Item *subitem = static_cast<Item *>(E);
- _fetch_item_fx_stack(subitem, fx_items);
-
- if (fx_items.size()) {
- set_process_internal(true);
- break;
- }
- }
}
void RichTextLabel::scroll_to_selection() {
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
index 6da13e7b2d..a01da02b27 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -614,6 +614,10 @@ private:
String _get_prefix(Item *p_item, const Vector<int> &p_list_index, const Vector<ItemList *> &p_list_items);
+ static int _find_unquoted(const String &p_src, char32_t p_chr, int p_from);
+ static Vector<String> _split_unquoted(const String &p_src, char32_t p_splitter);
+ static String _get_tag_value(const String &p_tag);
+
#ifndef DISABLE_DEPRECATED
// Kept for compatibility from 3.x to 4.0.
bool _set(const StringName &p_name, const Variant &p_value);
diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp
index d96809b67a..f1902bade4 100644
--- a/scene/gui/scroll_container.cpp
+++ b/scene/gui/scroll_container.cpp
@@ -35,8 +35,6 @@
#include "scene/theme/theme_db.h"
Size2 ScrollContainer::get_minimum_size() const {
- Size2 min_size;
-
// Calculated in this function, as it needs to traverse all child controls once to calculate;
// and needs to be calculated before being used by update_scrollbars().
largest_child_min_size = Size2();
@@ -55,21 +53,23 @@ Size2 ScrollContainer::get_minimum_size() const {
largest_child_min_size = largest_child_min_size.max(child_min_size);
}
+ Size2 min_size;
+ const Size2 size = get_size();
+
if (horizontal_scroll_mode == SCROLL_MODE_DISABLED) {
- min_size.x = MAX(min_size.x, largest_child_min_size.x);
- }
- if (vertical_scroll_mode == SCROLL_MODE_DISABLED) {
- min_size.y = MAX(min_size.y, largest_child_min_size.y);
+ min_size.x = largest_child_min_size.x;
+ bool v_scroll_show = vertical_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (vertical_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.y > size.y);
+ if (v_scroll_show && v_scroll->get_parent() == this) {
+ min_size.x += v_scroll->get_minimum_size().x;
+ }
}
- bool h_scroll_show = horizontal_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (horizontal_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.x > min_size.x);
- bool v_scroll_show = vertical_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (vertical_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.y > min_size.y);
-
- if (h_scroll_show && h_scroll->get_parent() == this) {
- min_size.y += h_scroll->get_minimum_size().y;
- }
- if (v_scroll_show && v_scroll->get_parent() == this) {
- min_size.x += v_scroll->get_minimum_size().x;
+ if (vertical_scroll_mode == SCROLL_MODE_DISABLED) {
+ min_size.y = largest_child_min_size.y;
+ bool h_scroll_show = horizontal_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (horizontal_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.x > size.x);
+ if (h_scroll_show && h_scroll->get_parent() == this) {
+ min_size.y += h_scroll->get_minimum_size().y;
+ }
}
min_size += theme_cache.panel_style->get_minimum_size();
diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp
index ac81f0de56..01c2b9bffe 100644
--- a/scene/gui/spin_box.cpp
+++ b/scene/gui/spin_box.cpp
@@ -46,7 +46,7 @@ void SpinBox::_update_text(bool p_keep_line_edit) {
value = TS->format_number(value);
}
- if (!line_edit->has_focus()) {
+ if (!line_edit->is_editing()) {
if (!prefix.is_empty()) {
value = prefix + " " + value;
}
@@ -197,13 +197,13 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
}
} break;
case MouseButton::WHEEL_UP: {
- if (line_edit->has_focus()) {
+ if (line_edit->is_editing()) {
set_value(get_value() + step * mb->get_factor());
accept_event();
}
} break;
case MouseButton::WHEEL_DOWN: {
- if (line_edit->has_focus()) {
+ if (line_edit->is_editing()) {
set_value(get_value() - step * mb->get_factor());
accept_event();
}
@@ -253,34 +253,26 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
}
}
-void SpinBox::_line_edit_focus_enter() {
- int col = line_edit->get_caret_column();
- _update_text();
- line_edit->set_caret_column(col);
+void SpinBox::_line_edit_editing_toggled(bool p_toggled_on) {
+ if (p_toggled_on) {
+ int col = line_edit->get_caret_column();
+ _update_text();
+ line_edit->set_caret_column(col);
- // LineEdit text might change and it clears any selection. Have to re-select here.
- if (line_edit->is_select_all_on_focus() && !Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
- line_edit->select_all();
- }
-}
+ // LineEdit text might change and it clears any selection. Have to re-select here.
+ if (line_edit->is_select_all_on_focus() && !Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
+ line_edit->select_all();
+ }
+ } else {
+ // Discontinue because the focus_exit was caused by canceling.
+ if (Input::get_singleton()->is_action_pressed("ui_cancel")) {
+ _update_text();
+ return;
+ }
-void SpinBox::_line_edit_focus_exit() {
- // Discontinue because the focus_exit was caused by left-clicking the arrows.
- const Viewport *viewport = get_viewport();
- if (!viewport || viewport->gui_get_focus_owner() == get_line_edit()) {
- return;
- }
- // Discontinue because the focus_exit was caused by right-click context menu.
- if (line_edit->is_menu_visible()) {
- return;
- }
- // Discontinue because the focus_exit was caused by canceling.
- if (Input::get_singleton()->is_action_pressed("ui_cancel")) {
- _update_text();
- return;
+ line_edit->deselect();
+ _text_submitted(line_edit->get_text());
}
-
- _text_submitted(line_edit->get_text());
}
inline void SpinBox::_compute_sizes() {
@@ -602,8 +594,7 @@ SpinBox::SpinBox() {
line_edit->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT);
line_edit->connect("text_submitted", callable_mp(this, &SpinBox::_text_submitted), CONNECT_DEFERRED);
- line_edit->connect(SceneStringName(focus_entered), callable_mp(this, &SpinBox::_line_edit_focus_enter), CONNECT_DEFERRED);
- line_edit->connect(SceneStringName(focus_exited), callable_mp(this, &SpinBox::_line_edit_focus_exit), CONNECT_DEFERRED);
+ line_edit->connect("editing_toggled", callable_mp(this, &SpinBox::_line_edit_editing_toggled), CONNECT_DEFERRED);
line_edit->connect(SceneStringName(gui_input), callable_mp(this, &SpinBox::_line_edit_input));
range_click_timer = memnew(Timer);
diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h
index 592805f43a..294dc3e5d5 100644
--- a/scene/gui/spin_box.h
+++ b/scene/gui/spin_box.h
@@ -86,8 +86,7 @@ class SpinBox : public Range {
bool down_button_disabled = false;
} state_cache;
- void _line_edit_focus_enter();
- void _line_edit_focus_exit();
+ void _line_edit_editing_toggled(bool p_toggled_on);
inline void _compute_sizes();
inline int _get_widest_button_icon_width();
diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp
index c715aceb0b..a443ae9abf 100644
--- a/scene/gui/subviewport_container.cpp
+++ b/scene/gui/subviewport_container.cpp
@@ -259,7 +259,7 @@ void SubViewportContainer::remove_child_notify(Node *p_child) {
}
PackedStringArray SubViewportContainer::get_configuration_warnings() const {
- PackedStringArray warnings = Node::get_configuration_warnings();
+ PackedStringArray warnings = Container::get_configuration_warnings();
bool has_viewport = false;
for (int i = 0; i < get_child_count(); i++) {
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index d00a5821cf..e4f52ee8ee 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -3494,29 +3494,37 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
accept_event();
}
- if (!selected_item || select_mode == SELECT_ROW || selected_col > (columns.size() - 1)) {
+ if (!selected_item || selected_col > (columns.size() - 1)) {
return;
}
+
if (k.is_valid() && k->is_shift_pressed()) {
selected_item->set_collapsed_recursive(false);
- } else {
+ } else if (select_mode != SELECT_ROW) {
_go_right();
+ } else if (selected_item->get_first_child() != nullptr && selected_item->is_collapsed()) {
+ selected_item->set_collapsed(false);
+ } else {
+ _go_down();
}
} else if (p_event->is_action("ui_left") && p_event->is_pressed()) {
if (!cursor_can_exit_tree) {
accept_event();
}
- if (!selected_item || select_mode == SELECT_ROW || selected_col < 0) {
+ if (!selected_item || selected_col < 0) {
return;
}
if (k.is_valid() && k->is_shift_pressed()) {
selected_item->set_collapsed_recursive(true);
- } else {
+ } else if (select_mode != SELECT_ROW) {
_go_left();
+ } else if (selected_item->get_first_child() != nullptr && !selected_item->is_collapsed()) {
+ selected_item->set_collapsed(true);
+ } else {
+ _go_up();
}
-
} else if (p_event->is_action("ui_up") && p_event->is_pressed() && !is_command) {
if (!cursor_can_exit_tree) {
accept_event();
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index d4d60545bd..de2332125f 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -4695,6 +4695,7 @@ void Viewport::_bind_methods() {
ClassDB::bind_method(D_METHOD("warp_mouse", "position"), &Viewport::warp_mouse);
ClassDB::bind_method(D_METHOD("update_mouse_cursor_state"), &Viewport::update_mouse_cursor_state);
+ ClassDB::bind_method(D_METHOD("gui_cancel_drag"), &Viewport::gui_cancel_drag);
ClassDB::bind_method(D_METHOD("gui_get_drag_data"), &Viewport::gui_get_drag_data);
ClassDB::bind_method(D_METHOD("gui_is_dragging"), &Viewport::gui_is_dragging);
ClassDB::bind_method(D_METHOD("gui_is_drag_successful"), &Viewport::gui_is_drag_successful);
diff --git a/scene/main/window.compat.inc b/scene/main/window.compat.inc
deleted file mode 100644
index 0bba01bb1b..0000000000
--- a/scene/main/window.compat.inc
+++ /dev/null
@@ -1,48 +0,0 @@
-/**************************************************************************/
-/* window.compat.inc */
-/**************************************************************************/
-/* 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 DISABLE_DEPRECATED
-
-void Window::_bind_compatibility_methods() {
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_icon", "name", "theme_type"), &Window::get_theme_icon, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_stylebox", "name", "theme_type"), &Window::get_theme_stylebox, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_font", "name", "theme_type"), &Window::get_theme_font, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_font_size", "name", "theme_type"), &Window::get_theme_font_size, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_color", "name", "theme_type"), &Window::get_theme_color, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("get_theme_constant", "name", "theme_type"), &Window::get_theme_constant, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_icon", "name", "theme_type"), &Window::has_theme_icon, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_stylebox", "name", "theme_type"), &Window::has_theme_stylebox, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_font", "name", "theme_type"), &Window::has_theme_font, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_font_size", "name", "theme_type"), &Window::has_theme_font_size, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_color", "name", "theme_type"), &Window::has_theme_color, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("has_theme_constant", "name", "theme_type"), &Window::has_theme_constant, DEFVAL(""));
-}
-
-#endif
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index aaa34a4840..dafbbb867b 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -29,7 +29,6 @@
/**************************************************************************/
#include "window.h"
-#include "window.compat.inc"
#include "core/config/project_settings.h"
#include "core/debugger/engine_debugger.h"
diff --git a/scene/main/window.h b/scene/main/window.h
index 33d593711f..84d2febe51 100644
--- a/scene/main/window.h
+++ b/scene/main/window.h
@@ -247,10 +247,6 @@ protected:
void _notification(int p_what);
static void _bind_methods();
-#ifndef DISABLE_DEPRECATED
- static void _bind_compatibility_methods();
-#endif
-
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
diff --git a/scene/resources/3d/importer_mesh.cpp b/scene/resources/3d/importer_mesh.cpp
index 4f4c485db3..47cd64f19a 100644
--- a/scene/resources/3d/importer_mesh.cpp
+++ b/scene/resources/3d/importer_mesh.cpp
@@ -34,6 +34,7 @@
#include "core/math/convex_hull.h"
#include "core/math/random_pcg.h"
#include "core/math/static_raycaster.h"
+#include "scene/resources/animation_library.h"
#include "scene/resources/surface_tool.h"
#include <cstdint>
@@ -134,9 +135,18 @@ void ImporterMesh::Surface::_split_normals(Array &r_arrays, const LocalVector<in
}
}
+String ImporterMesh::validate_blend_shape_name(const String &p_name) {
+ String name = p_name;
+ const char *characters = ":";
+ for (const char *p = characters; *p; p++) {
+ name = name.replace(String::chr(*p), "_");
+ }
+ return name;
+}
+
void ImporterMesh::add_blend_shape(const String &p_name) {
ERR_FAIL_COND(surfaces.size() > 0);
- blend_shapes.push_back(p_name);
+ blend_shapes.push_back(validate_blend_shape_name(p_name));
}
int ImporterMesh::get_blend_shape_count() const {
@@ -849,7 +859,7 @@ void ImporterMesh::create_shadow_mesh() {
index_wptr[j] = vertex_remap[index];
}
- if (SurfaceTool::optimize_vertex_cache_func) {
+ if (SurfaceTool::optimize_vertex_cache_func && surfaces[i].primitive == Mesh::PRIMITIVE_TRIANGLES) {
SurfaceTool::optimize_vertex_cache_func((unsigned int *)index_wptr, (const unsigned int *)index_wptr, index_count, new_vertices.size());
}
@@ -871,7 +881,7 @@ void ImporterMesh::create_shadow_mesh() {
index_wptr[k] = vertex_remap[index];
}
- if (SurfaceTool::optimize_vertex_cache_func) {
+ if (SurfaceTool::optimize_vertex_cache_func && surfaces[i].primitive == Mesh::PRIMITIVE_TRIANGLES) {
SurfaceTool::optimize_vertex_cache_func((unsigned int *)index_wptr, (const unsigned int *)index_wptr, index_count, new_vertices.size());
}
diff --git a/scene/resources/3d/importer_mesh.h b/scene/resources/3d/importer_mesh.h
index 5eb4ee884e..c7e3a059d6 100644
--- a/scene/resources/3d/importer_mesh.h
+++ b/scene/resources/3d/importer_mesh.h
@@ -95,6 +95,8 @@ public:
int get_blend_shape_count() const;
String get_blend_shape_name(int p_blend_shape) const;
+ static String validate_blend_shape_name(const String &p_name);
+
void add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const TypedArray<Array> &p_blend_shapes = Array(), const Dictionary &p_lods = Dictionary(), const Ref<Material> &p_material = Ref<Material>(), const String &p_name = String(), const uint64_t p_flags = 0);
int get_surface_count() const;
diff --git a/scene/resources/audio_stream_polyphonic.cpp b/scene/resources/audio_stream_polyphonic.cpp
index 999b0c9f0a..c7b8b1c723 100644
--- a/scene/resources/audio_stream_polyphonic.cpp
+++ b/scene/resources/audio_stream_polyphonic.cpp
@@ -247,6 +247,11 @@ AudioStreamPlaybackPolyphonic::ID AudioStreamPlaybackPolyphonic::play_stream(con
sp->volume_vector.write[2] = AudioFrame(linear_volume, linear_volume);
sp->volume_vector.write[3] = AudioFrame(linear_volume, linear_volume);
sp->bus = p_bus;
+
+ if (streams[i].stream_playback->get_sample_playback().is_valid()) {
+ AudioServer::get_singleton()->stop_playback_stream(sp);
+ }
+
streams[i].stream_playback->set_sample_playback(sp);
AudioServer::get_singleton()->start_sample_playback(sp);
}
@@ -315,6 +320,9 @@ Ref<AudioSamplePlayback> AudioStreamPlaybackPolyphonic::get_sample_playback() co
void AudioStreamPlaybackPolyphonic::set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) {
sample_playback = p_playback;
+ if (sample_playback.is_valid()) {
+ sample_playback->stream_playback = Ref<AudioStreamPlayback>(this);
+ }
}
void AudioStreamPlaybackPolyphonic::_bind_methods() {
diff --git a/scene/resources/audio_stream_wav.cpp b/scene/resources/audio_stream_wav.cpp
index 08ebacc2b3..f9787dde2e 100644
--- a/scene/resources/audio_stream_wav.cpp
+++ b/scene/resources/audio_stream_wav.cpp
@@ -123,10 +123,8 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst,
int16_t nibble, diff, step;
p_ima_adpcm[i].last_nibble++;
- const uint8_t *src_ptr = (const uint8_t *)base->data;
- src_ptr += AudioStreamWAV::DATA_PAD;
- uint8_t nbb = src_ptr[(p_ima_adpcm[i].last_nibble >> 1) * (is_stereo ? 2 : 1) + i];
+ uint8_t nbb = p_src[(p_ima_adpcm[i].last_nibble >> 1) * (is_stereo ? 2 : 1) + i];
nibble = (p_ima_adpcm[i].last_nibble & 1) ? (nbb >> 4) : (nbb & 0xF);
step = _ima_adpcm_step_table[p_ima_adpcm[i].step_index];
@@ -184,9 +182,8 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst,
if (p_qoa->data_ofs != new_data_ofs) {
p_qoa->data_ofs = new_data_ofs;
- const uint8_t *src_ptr = (const uint8_t *)base->data;
- src_ptr += p_qoa->data_ofs + AudioStreamWAV::DATA_PAD;
- qoa_decode_frame(src_ptr, p_qoa->frame_len, &p_qoa->desc, p_qoa->dec, &p_qoa->dec_len);
+ const uint8_t *ofs_src = (uint8_t *)p_src + p_qoa->data_ofs;
+ qoa_decode_frame(ofs_src, p_qoa->frame_len, &p_qoa->desc, p_qoa->dec.ptr(), &p_qoa->dec_len);
}
uint32_t dec_idx = (interp_pos % QOA_FRAME_LEN) * p_qoa->desc.channels;
@@ -267,7 +264,7 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst,
}
int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
- if (!base->data || !active) {
+ if (base->data.is_empty() || !active) {
for (int i = 0; i < p_frames; i++) {
p_buffer[i] = AudioFrame(0, 0);
}
@@ -300,7 +297,7 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_
int64_t loop_end_fp = ((int64_t)base->loop_end << MIX_FRAC_BITS);
int64_t length_fp = ((int64_t)len << MIX_FRAC_BITS);
int64_t begin_limit = (base->loop_mode != AudioStreamWAV::LOOP_DISABLED) ? loop_begin_fp : 0;
- int64_t end_limit = (base->loop_mode != AudioStreamWAV::LOOP_DISABLED) ? loop_end_fp : length_fp;
+ int64_t end_limit = (base->loop_mode != AudioStreamWAV::LOOP_DISABLED) ? loop_end_fp : length_fp - MIX_FRAC_LEN;
bool is_stereo = base->stereo;
int32_t todo = p_frames;
@@ -324,8 +321,7 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_
/* audio data */
- uint8_t *dataptr = (uint8_t *)base->data;
- const void *data = dataptr + AudioStreamWAV::DATA_PAD;
+ const uint8_t *data = base->data.ptr() + AudioStreamWAV::DATA_PAD;
AudioFrame *dst_buff = p_buffer;
if (format == AudioStreamWAV::FORMAT_IMA_ADPCM) {
@@ -479,15 +475,14 @@ Ref<AudioSamplePlayback> AudioStreamPlaybackWAV::get_sample_playback() const {
void AudioStreamPlaybackWAV::set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) {
sample_playback = p_playback;
+ if (sample_playback.is_valid()) {
+ sample_playback->stream_playback = Ref<AudioStreamPlayback>(this);
+ }
}
AudioStreamPlaybackWAV::AudioStreamPlaybackWAV() {}
-AudioStreamPlaybackWAV::~AudioStreamPlaybackWAV() {
- if (qoa.dec) {
- memfree(qoa.dec);
- }
-}
+AudioStreamPlaybackWAV::~AudioStreamPlaybackWAV() {}
/////////////////////
@@ -554,7 +549,7 @@ double AudioStreamWAV::get_length() const {
break;
case AudioStreamWAV::FORMAT_QOA:
qoa_desc desc = {};
- qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, &desc);
+ qoa_decode_header(data.ptr() + DATA_PAD, data_bytes, &desc);
len = desc.samples * desc.channels;
break;
}
@@ -572,22 +567,16 @@ bool AudioStreamWAV::is_monophonic() const {
void AudioStreamWAV::set_data(const Vector<uint8_t> &p_data) {
AudioServer::get_singleton()->lock();
- if (data) {
- memfree(data);
- data = nullptr;
- data_bytes = 0;
- }
- int datalen = p_data.size();
- if (datalen) {
- const uint8_t *r = p_data.ptr();
- int alloc_len = datalen + DATA_PAD * 2;
- data = memalloc(alloc_len); //alloc with some padding for interpolation
- memset(data, 0, alloc_len);
- uint8_t *dataptr = (uint8_t *)data;
- memcpy(dataptr + DATA_PAD, r, datalen);
- data_bytes = datalen;
- }
+ int src_data_len = p_data.size();
+
+ data.clear();
+
+ int alloc_len = src_data_len + DATA_PAD * 2;
+ data.resize(alloc_len);
+ memset(data.ptr(), 0, alloc_len);
+ memcpy(data.ptr() + DATA_PAD, p_data.ptr(), src_data_len);
+ data_bytes = src_data_len;
AudioServer::get_singleton()->unlock();
}
@@ -595,13 +584,9 @@ void AudioStreamWAV::set_data(const Vector<uint8_t> &p_data) {
Vector<uint8_t> AudioStreamWAV::get_data() const {
Vector<uint8_t> pv;
- if (data) {
+ if (!data.is_empty()) {
pv.resize(data_bytes);
- {
- uint8_t *w = pv.ptrw();
- uint8_t *dataptr = (uint8_t *)data;
- memcpy(w, dataptr + DATA_PAD, data_bytes);
- }
+ memcpy(pv.ptrw(), data.ptr() + DATA_PAD, data_bytes);
}
return pv;
@@ -693,12 +678,12 @@ Ref<AudioStreamPlayback> AudioStreamWAV::instantiate_playback() {
sample->base = Ref<AudioStreamWAV>(this);
if (format == AudioStreamWAV::FORMAT_QOA) {
- uint32_t ffp = qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, &sample->qoa.desc);
+ uint32_t ffp = qoa_decode_header(data.ptr() + DATA_PAD, data_bytes, &sample->qoa.desc);
ERR_FAIL_COND_V(ffp != 8, Ref<AudioStreamPlaybackWAV>());
sample->qoa.frame_len = qoa_max_frame_size(&sample->qoa.desc);
int samples_len = (sample->qoa.desc.samples > QOA_FRAME_LEN ? QOA_FRAME_LEN : sample->qoa.desc.samples);
- int alloc_len = sample->qoa.desc.channels * samples_len * sizeof(int16_t);
- sample->qoa.dec = (int16_t *)memalloc(alloc_len);
+ int dec_len = sample->qoa.desc.channels * samples_len;
+ sample->qoa.dec.resize(dec_len);
}
return sample;
@@ -780,10 +765,4 @@ void AudioStreamWAV::_bind_methods() {
AudioStreamWAV::AudioStreamWAV() {}
-AudioStreamWAV::~AudioStreamWAV() {
- if (data) {
- memfree(data);
- data = nullptr;
- data_bytes = 0;
- }
-}
+AudioStreamWAV::~AudioStreamWAV() {}
diff --git a/scene/resources/audio_stream_wav.h b/scene/resources/audio_stream_wav.h
index 47aa10e790..bc62e8883a 100644
--- a/scene/resources/audio_stream_wav.h
+++ b/scene/resources/audio_stream_wav.h
@@ -62,7 +62,7 @@ class AudioStreamPlaybackWAV : public AudioStreamPlayback {
qoa_desc desc = {};
uint32_t data_ofs = 0;
uint32_t frame_len = 0;
- int16_t *dec = nullptr;
+ LocalVector<int16_t> dec;
uint32_t dec_len = 0;
int64_t cache_pos = -1;
int16_t cache[2] = { 0, 0 };
@@ -137,7 +137,7 @@ private:
int loop_begin = 0;
int loop_end = 0;
int mix_rate = 44100;
- void *data = nullptr;
+ LocalVector<uint8_t> data;
uint32_t data_bytes = 0;
protected:
diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp
index 27db65bb1a..69dc71e414 100644
--- a/scene/resources/packed_scene.cpp
+++ b/scene/resources/packed_scene.cpp
@@ -369,6 +369,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
value = make_local_resource(value, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state);
}
}
+
if (value.get_type() == Variant::ARRAY) {
Array set_array = value;
value = setup_resources_in_array(set_array, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state);
@@ -383,25 +384,20 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
}
}
}
- if (value.get_type() == Variant::DICTIONARY) {
- Dictionary dictionary = value;
- const Array keys = dictionary.keys();
- const Array values = dictionary.values();
-
- if (has_local_resource(values) || has_local_resource(keys)) {
- Array duplicated_keys = keys.duplicate(true);
- Array duplicated_values = values.duplicate(true);
- duplicated_keys = setup_resources_in_array(duplicated_keys, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state);
- duplicated_values = setup_resources_in_array(duplicated_values, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state);
+ if (value.get_type() == Variant::DICTIONARY) {
+ Dictionary set_dict = value;
+ value = setup_resources_in_dictionary(set_dict, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state);
- dictionary.clear();
+ bool is_get_valid = false;
+ Variant get_value = node->get(snames[nprops[j].name], &is_get_valid);
- for (int dictionary_index = 0; dictionary_index < keys.size(); dictionary_index++) {
- dictionary[duplicated_keys[dictionary_index]] = duplicated_values[dictionary_index];
+ if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) {
+ Dictionary get_dict = get_value;
+ if (!set_dict.is_same_typed(get_dict)) {
+ value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(),
+ get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script());
}
-
- value = dictionary;
}
}
@@ -539,6 +535,30 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
array.set(i, dnp.base->get_node_or_null(paths[i]));
}
dnp.base->set(dnp.property, array);
+ } else if (dnp.value.get_type() == Variant::DICTIONARY) {
+ Dictionary paths = dnp.value;
+
+ bool valid;
+ Dictionary dict = dnp.base->get(dnp.property, &valid);
+ ERR_CONTINUE(!valid);
+ dict = dict.duplicate();
+ bool convert_key = dict.get_typed_key_builtin() == Variant::OBJECT &&
+ ClassDB::is_parent_class(dict.get_typed_key_class_name(), "Node");
+ bool convert_value = dict.get_typed_value_builtin() == Variant::OBJECT &&
+ ClassDB::is_parent_class(dict.get_typed_value_class_name(), "Node");
+
+ for (int i = 0; i < paths.size(); i++) {
+ Variant key = paths.get_key_at_index(i);
+ if (convert_key) {
+ key = dnp.base->get_node_or_null(key);
+ }
+ Variant value = paths.get_value_at_index(i);
+ if (convert_value) {
+ value = dnp.base->get_node_or_null(value);
+ }
+ dict[key] = value;
+ }
+ dnp.base->set(dnp.property, dict);
} else {
dnp.base->set(dnp.property, dnp.base->get_node_or_null(dnp.value));
}
@@ -641,6 +661,26 @@ Array SceneState::setup_resources_in_array(Array &p_array_to_scan, const SceneSt
return p_array_to_scan;
}
+Dictionary SceneState::setup_resources_in_dictionary(Dictionary &p_dictionary_to_scan, const SceneState::NodeData &p_n, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_sub_scene, Node *p_node, const StringName p_sname, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_scene, int p_i, Node **p_ret_nodes, SceneState::GenEditState p_edit_state) const {
+ const Array keys = p_dictionary_to_scan.keys();
+ const Array values = p_dictionary_to_scan.values();
+
+ if (has_local_resource(values) || has_local_resource(keys)) {
+ Array duplicated_keys = keys.duplicate(true);
+ Array duplicated_values = values.duplicate(true);
+
+ duplicated_keys = setup_resources_in_array(duplicated_keys, p_n, p_resources_local_to_sub_scene, p_node, p_sname, p_resources_local_to_scene, p_i, p_ret_nodes, p_edit_state);
+ duplicated_values = setup_resources_in_array(duplicated_values, p_n, p_resources_local_to_sub_scene, p_node, p_sname, p_resources_local_to_scene, p_i, p_ret_nodes, p_edit_state);
+ p_dictionary_to_scan.clear();
+
+ for (int i = 0; i < keys.size(); i++) {
+ p_dictionary_to_scan[duplicated_keys[i]] = duplicated_values[i];
+ }
+ }
+
+ return p_dictionary_to_scan;
+}
+
bool SceneState::has_local_resource(const Array &p_array) const {
for (int i = 0; i < p_array.size(); i++) {
Ref<Resource> res = p_array[i];
@@ -810,6 +850,53 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has
value = new_array;
}
}
+ } else if (E.type == Variant::DICTIONARY && E.hint == PROPERTY_HINT_TYPE_STRING) {
+ int key_value_separator = E.hint_string.find(";");
+ if (key_value_separator >= 0) {
+ int key_subtype_separator = E.hint_string.find(":");
+ String key_subtype_string = E.hint_string.substr(0, key_subtype_separator);
+ int key_slash_pos = key_subtype_string.find("/");
+ PropertyHint key_subtype_hint = PropertyHint::PROPERTY_HINT_NONE;
+ if (key_slash_pos >= 0) {
+ key_subtype_hint = PropertyHint(key_subtype_string.get_slice("/", 1).to_int());
+ key_subtype_string = key_subtype_string.substr(0, key_slash_pos);
+ }
+ Variant::Type key_subtype = Variant::Type(key_subtype_string.to_int());
+ bool convert_key = key_subtype == Variant::OBJECT && key_subtype_hint == PROPERTY_HINT_NODE_TYPE;
+
+ int value_subtype_separator = E.hint_string.find(":", key_value_separator) - (key_value_separator + 1);
+ String value_subtype_string = E.hint_string.substr(key_value_separator + 1, value_subtype_separator);
+ int value_slash_pos = value_subtype_string.find("/");
+ PropertyHint value_subtype_hint = PropertyHint::PROPERTY_HINT_NONE;
+ if (value_slash_pos >= 0) {
+ value_subtype_hint = PropertyHint(value_subtype_string.get_slice("/", 1).to_int());
+ value_subtype_string = value_subtype_string.substr(0, value_slash_pos);
+ }
+ Variant::Type value_subtype = Variant::Type(value_subtype_string.to_int());
+ bool convert_value = value_subtype == Variant::OBJECT && value_subtype_hint == PROPERTY_HINT_NODE_TYPE;
+
+ if (convert_key || convert_value) {
+ use_deferred_node_path_bit = true;
+ Dictionary dict = value;
+ Dictionary new_dict;
+ for (int i = 0; i < dict.size(); i++) {
+ Variant new_key = dict.get_key_at_index(i);
+ if (convert_key && new_key.get_type() == Variant::OBJECT) {
+ if (Node *n = Object::cast_to<Node>(new_key)) {
+ new_key = p_node->get_path_to(n);
+ }
+ }
+ Variant new_value = dict.get_value_at_index(i);
+ if (convert_value && new_value.get_type() == Variant::OBJECT) {
+ if (Node *n = Object::cast_to<Node>(new_value)) {
+ new_value = p_node->get_path_to(n);
+ }
+ }
+ new_dict[new_key] = new_value;
+ }
+ value = new_dict;
+ }
+ }
}
if (!pinned_props.has(name)) {
@@ -1267,25 +1354,6 @@ Ref<SceneState> SceneState::get_base_scene_state() const {
return Ref<SceneState>();
}
-void SceneState::update_instance_resource(String p_path, Ref<PackedScene> p_packed_scene) {
- ERR_FAIL_COND(p_packed_scene.is_null());
-
- for (const NodeData &nd : nodes) {
- if (nd.instance >= 0) {
- if (!(nd.instance & FLAG_INSTANCE_IS_PLACEHOLDER)) {
- int instance_id = nd.instance & FLAG_MASK;
- Ref<PackedScene> original_packed_scene = variants[instance_id];
- if (original_packed_scene.is_valid()) {
- if (original_packed_scene->get_path() == p_path) {
- variants.remove_at(instance_id);
- variants.insert(instance_id, p_packed_scene);
- }
- }
- }
- }
- }
-}
-
int SceneState::find_node_by_path(const NodePath &p_node) const {
ERR_FAIL_COND_V_MSG(node_path_cache.is_empty(), -1, "This operation requires the node cache to have been built.");
diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h
index d27def1760..9f8088910f 100644
--- a/scene/resources/packed_scene.h
+++ b/scene/resources/packed_scene.h
@@ -158,6 +158,7 @@ public:
Node *instantiate(GenEditState p_edit_state) const;
Array setup_resources_in_array(Array &array_to_scan, const SceneState::NodeData &n, HashMap<Ref<Resource>, Ref<Resource>> &resources_local_to_sub_scene, Node *node, const StringName sname, HashMap<Ref<Resource>, Ref<Resource>> &resources_local_to_scene, int i, Node **ret_nodes, SceneState::GenEditState p_edit_state) const;
+ Dictionary setup_resources_in_dictionary(Dictionary &p_dictionary_to_scan, const SceneState::NodeData &p_n, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_sub_scene, Node *p_node, const StringName p_sname, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_scene, int p_i, Node **p_ret_nodes, SceneState::GenEditState p_edit_state) const;
Variant make_local_resource(Variant &value, const SceneState::NodeData &p_node_data, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_sub_scene, Node *p_node, const StringName p_sname, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_scene, int p_i, Node **p_ret_nodes, SceneState::GenEditState p_edit_state) const;
bool has_local_resource(const Array &p_array) const;
diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp
index e9df20d9db..d531eea311 100644
--- a/scene/resources/resource_format_text.cpp
+++ b/scene/resources/resource_format_text.cpp
@@ -612,14 +612,29 @@ Error ResourceLoaderText::load() {
}
}
- if (value.get_type() == Variant::ARRAY) {
- Array set_array = value;
- bool is_get_valid = false;
- Variant get_value = res->get(assign, &is_get_valid);
- if (is_get_valid && get_value.get_type() == Variant::ARRAY) {
- Array get_array = get_value;
- if (!set_array.is_same_typed(get_array)) {
- value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script());
+ if (ClassDB::has_property(res->get_class_name(), assign)) {
+ if (value.get_type() == Variant::ARRAY) {
+ Array set_array = value;
+ bool is_get_valid = false;
+ Variant get_value = res->get(assign, &is_get_valid);
+ if (is_get_valid && get_value.get_type() == Variant::ARRAY) {
+ Array get_array = get_value;
+ if (!set_array.is_same_typed(get_array)) {
+ value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script());
+ }
+ }
+ }
+
+ if (value.get_type() == Variant::DICTIONARY) {
+ Dictionary set_dict = value;
+ bool is_get_valid = false;
+ Variant get_value = res->get(assign, &is_get_valid);
+ if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) {
+ Dictionary get_dict = get_value;
+ if (!set_dict.is_same_typed(get_dict)) {
+ value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(),
+ get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script());
+ }
}
}
}
@@ -739,14 +754,29 @@ Error ResourceLoaderText::load() {
}
}
- if (value.get_type() == Variant::ARRAY) {
- Array set_array = value;
- bool is_get_valid = false;
- Variant get_value = resource->get(assign, &is_get_valid);
- if (is_get_valid && get_value.get_type() == Variant::ARRAY) {
- Array get_array = get_value;
- if (!set_array.is_same_typed(get_array)) {
- value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script());
+ if (ClassDB::has_property(resource->get_class_name(), assign)) {
+ if (value.get_type() == Variant::ARRAY) {
+ Array set_array = value;
+ bool is_get_valid = false;
+ Variant get_value = resource->get(assign, &is_get_valid);
+ if (is_get_valid && get_value.get_type() == Variant::ARRAY) {
+ Array get_array = get_value;
+ if (!set_array.is_same_typed(get_array)) {
+ value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script());
+ }
+ }
+ }
+
+ if (value.get_type() == Variant::DICTIONARY) {
+ Dictionary set_dict = value;
+ bool is_get_valid = false;
+ Variant get_value = resource->get(assign, &is_get_valid);
+ if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) {
+ Dictionary get_dict = get_value;
+ if (!set_dict.is_same_typed(get_dict)) {
+ value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(),
+ get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script());
+ }
}
}
}
@@ -1642,6 +1672,8 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant,
} break;
case Variant::DICTIONARY: {
Dictionary d = p_variant;
+ _find_resources(d.get_typed_key_script());
+ _find_resources(d.get_typed_value_script());
List<Variant> keys;
d.get_key_list(&keys);
for (const Variant &E : keys) {
diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp
index e2c8686911..ece088a694 100644
--- a/servers/audio/audio_stream.cpp
+++ b/servers/audio/audio_stream.cpp
@@ -173,12 +173,12 @@ int AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale,
}
float mu2 = mu * mu;
- AudioFrame a0 = 3 * y1 - 3 * y2 + y3 - y0;
- AudioFrame a1 = 2 * y0 - 5 * y1 + 4 * y2 - y3;
- AudioFrame a2 = y2 - y0;
- AudioFrame a3 = 2 * y1;
+ float h11 = mu2 * (mu - 1);
+ float z = mu2 - h11;
+ float h01 = z - h11;
+ float h10 = mu - z;
- p_buffer[i] = (a0 * mu * mu2 + a1 * mu2 + a2 * mu + a3) / 2;
+ p_buffer[i] = y1 + (y2 - y1) * h01 + ((y2 - y0) * h10 + (y3 - y1) * h11) * 0.5;
mix_offset += mix_increment;
diff --git a/servers/audio/audio_stream.h b/servers/audio/audio_stream.h
index c41545aeba..7f2b68a796 100644
--- a/servers/audio/audio_stream.h
+++ b/servers/audio/audio_stream.h
@@ -48,6 +48,7 @@ class AudioSamplePlayback : public RefCounted {
public:
Ref<AudioStream> stream;
+ Ref<AudioStreamPlayback> stream_playback;
float offset = 0.0f;
float pitch_scale = 1.0;
diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp
index 332f8984a2..5835ecfed0 100644
--- a/servers/audio_server.cpp
+++ b/servers/audio_server.cpp
@@ -501,12 +501,7 @@ void AudioServer::_mix_step() {
switch (playback->state.load()) {
case AudioStreamPlaybackListNode::AWAITING_DELETION:
case AudioStreamPlaybackListNode::FADE_OUT_TO_DELETION:
- playback_list.erase(playback, [](AudioStreamPlaybackListNode *p) {
- delete p->prev_bus_details;
- delete p->bus_details.load();
- p->stream_playback.unref();
- delete p;
- });
+ _delete_stream_playback_list_node(playback);
break;
case AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE: {
// Pause the stream.
@@ -697,6 +692,23 @@ AudioServer::AudioStreamPlaybackListNode *AudioServer::_find_playback_list_node(
return nullptr;
}
+void AudioServer::_delete_stream_playback(Ref<AudioStreamPlayback> p_playback) {
+ ERR_FAIL_COND(p_playback.is_null());
+ AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
+ if (playback_node) {
+ _delete_stream_playback_list_node(playback_node);
+ }
+}
+
+void AudioServer::_delete_stream_playback_list_node(AudioStreamPlaybackListNode *p_playback_node) {
+ playback_list.erase(p_playback_node, [](AudioStreamPlaybackListNode *p) {
+ delete p->prev_bus_details;
+ delete p->bus_details.load();
+ p->stream_playback.unref();
+ delete p;
+ });
+}
+
bool AudioServer::thread_has_channel_mix_buffer(int p_bus, int p_buffer) const {
if (p_bus < 0 || p_bus >= buses.size()) {
return false;
@@ -1227,8 +1239,12 @@ void AudioServer::stop_playback_stream(Ref<AudioStreamPlayback> p_playback) {
ERR_FAIL_COND(p_playback.is_null());
// Handle sample playback.
- if (p_playback->get_is_sample() && p_playback->get_sample_playback().is_valid()) {
- AudioServer::get_singleton()->stop_sample_playback(p_playback->get_sample_playback());
+ if (p_playback->get_is_sample()) {
+ if (p_playback->get_sample_playback().is_valid()) {
+ AudioServer::get_singleton()->stop_sample_playback(p_playback->get_sample_playback());
+ } else {
+ _delete_stream_playback(p_playback);
+ }
return;
}
@@ -1370,8 +1386,12 @@ void AudioServer::set_playback_highshelf_params(Ref<AudioStreamPlayback> p_playb
bool AudioServer::is_playback_active(Ref<AudioStreamPlayback> p_playback) {
ERR_FAIL_COND_V(p_playback.is_null(), false);
- if (p_playback->get_is_sample() && p_playback->get_sample_playback().is_valid()) {
- return sample_playback_list.has(p_playback->get_sample_playback());
+ if (p_playback->get_is_sample()) {
+ if (p_playback->get_sample_playback().is_valid()) {
+ return sample_playback_list.has(p_playback->get_sample_playback());
+ } else {
+ return false;
+ }
}
AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
@@ -1467,7 +1487,7 @@ void AudioServer::init() {
void AudioServer::update() {
#ifdef DEBUG_ENABLED
- if (EngineDebugger::is_profiling("servers")) {
+ if (EngineDebugger::is_profiling(SNAME("servers"))) {
// Driver time includes server time + effects times
// Server time includes effects times
uint64_t driver_time = AudioDriver::get_singleton()->get_profiling_time();
@@ -1845,8 +1865,12 @@ void AudioServer::start_sample_playback(const Ref<AudioSamplePlayback> &p_playba
void AudioServer::stop_sample_playback(const Ref<AudioSamplePlayback> &p_playback) {
ERR_FAIL_COND_MSG(p_playback.is_null(), "Parameter p_playback is null.");
- AudioDriver::get_singleton()->stop_sample_playback(p_playback);
- sample_playback_list.erase(p_playback);
+ if (sample_playback_list.has(p_playback)) {
+ sample_playback_list.erase(p_playback);
+ AudioDriver::get_singleton()->stop_sample_playback(p_playback);
+ p_playback->stream_playback->set_sample_playback(nullptr);
+ stop_playback_stream(p_playback->stream_playback);
+ }
}
void AudioServer::set_sample_playback_pause(const Ref<AudioSamplePlayback> &p_playback, bool p_paused) {
diff --git a/servers/audio_server.h b/servers/audio_server.h
index 2d6fc60860..16fcc029b3 100644
--- a/servers/audio_server.h
+++ b/servers/audio_server.h
@@ -297,6 +297,8 @@ private:
SafeList<AudioStreamPlaybackListNode *> playback_list;
SafeList<AudioStreamPlaybackBusDetails *> bus_details_graveyard;
+ void _delete_stream_playback(Ref<AudioStreamPlayback> p_playback);
+ void _delete_stream_playback_list_node(AudioStreamPlaybackListNode *p_node);
// TODO document if this is necessary.
SafeList<AudioStreamPlaybackBusDetails *> bus_details_graveyard_frame_old;
diff --git a/servers/navigation_server_2d.cpp b/servers/navigation_server_2d.cpp
index c5ce82265b..ceb5e909a3 100644
--- a/servers/navigation_server_2d.cpp
+++ b/servers/navigation_server_2d.cpp
@@ -86,6 +86,7 @@ void NavigationServer2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("region_get_connections_count", "region"), &NavigationServer2D::region_get_connections_count);
ClassDB::bind_method(D_METHOD("region_get_connection_pathway_start", "region", "connection"), &NavigationServer2D::region_get_connection_pathway_start);
ClassDB::bind_method(D_METHOD("region_get_connection_pathway_end", "region", "connection"), &NavigationServer2D::region_get_connection_pathway_end);
+ ClassDB::bind_method(D_METHOD("region_get_closest_point", "region", "to_point"), &NavigationServer2D::region_get_closest_point);
ClassDB::bind_method(D_METHOD("region_get_random_point", "region", "navigation_layers", "uniformly"), &NavigationServer2D::region_get_random_point);
ClassDB::bind_method(D_METHOD("link_create"), &NavigationServer2D::link_create);
diff --git a/servers/navigation_server_2d.h b/servers/navigation_server_2d.h
index a8d9678a6f..250183300f 100644
--- a/servers/navigation_server_2d.h
+++ b/servers/navigation_server_2d.h
@@ -149,6 +149,7 @@ public:
virtual Vector2 region_get_connection_pathway_start(RID p_region, int p_connection_id) const = 0;
virtual Vector2 region_get_connection_pathway_end(RID p_region, int p_connection_id) const = 0;
+ virtual Vector2 region_get_closest_point(RID p_region, const Vector2 &p_point) const = 0;
virtual Vector2 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const = 0;
/// Creates a new link between positions in the nav map.
diff --git a/servers/navigation_server_2d_dummy.h b/servers/navigation_server_2d_dummy.h
index 465cfcca98..0664b37ef0 100644
--- a/servers/navigation_server_2d_dummy.h
+++ b/servers/navigation_server_2d_dummy.h
@@ -83,6 +83,7 @@ public:
int region_get_connections_count(RID p_region) const override { return 0; }
Vector2 region_get_connection_pathway_start(RID p_region, int p_connection_id) const override { return Vector2(); }
Vector2 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override { return Vector2(); }
+ Vector2 region_get_closest_point(RID p_region, const Vector2 &p_point) const override { return Vector2(); }
Vector2 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const override { return Vector2(); };
RID link_create() override { return RID(); }
diff --git a/servers/navigation_server_3d.cpp b/servers/navigation_server_3d.cpp
index f4ffcf5a3e..572309c429 100644
--- a/servers/navigation_server_3d.cpp
+++ b/servers/navigation_server_3d.cpp
@@ -99,6 +99,9 @@ void NavigationServer3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("region_get_connections_count", "region"), &NavigationServer3D::region_get_connections_count);
ClassDB::bind_method(D_METHOD("region_get_connection_pathway_start", "region", "connection"), &NavigationServer3D::region_get_connection_pathway_start);
ClassDB::bind_method(D_METHOD("region_get_connection_pathway_end", "region", "connection"), &NavigationServer3D::region_get_connection_pathway_end);
+ ClassDB::bind_method(D_METHOD("region_get_closest_point_to_segment", "region", "start", "end", "use_collision"), &NavigationServer3D::region_get_closest_point_to_segment, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("region_get_closest_point", "region", "to_point"), &NavigationServer3D::region_get_closest_point);
+ ClassDB::bind_method(D_METHOD("region_get_closest_point_normal", "region", "to_point"), &NavigationServer3D::region_get_closest_point_normal);
ClassDB::bind_method(D_METHOD("region_get_random_point", "region", "navigation_layers", "uniformly"), &NavigationServer3D::region_get_random_point);
ClassDB::bind_method(D_METHOD("link_create"), &NavigationServer3D::link_create);
diff --git a/servers/navigation_server_3d.h b/servers/navigation_server_3d.h
index cdacf8e7e4..6dbbd35648 100644
--- a/servers/navigation_server_3d.h
+++ b/servers/navigation_server_3d.h
@@ -168,6 +168,9 @@ public:
virtual Vector3 region_get_connection_pathway_start(RID p_region, int p_connection_id) const = 0;
virtual Vector3 region_get_connection_pathway_end(RID p_region, int p_connection_id) const = 0;
+ virtual Vector3 region_get_closest_point_to_segment(RID p_region, const Vector3 &p_from, const Vector3 &p_to, bool p_use_collision = false) const = 0;
+ virtual Vector3 region_get_closest_point(RID p_region, const Vector3 &p_point) const = 0;
+ virtual Vector3 region_get_closest_point_normal(RID p_region, const Vector3 &p_point) const = 0;
virtual Vector3 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const = 0;
/// Creates a new link between positions in the nav map.
diff --git a/servers/navigation_server_3d_dummy.h b/servers/navigation_server_3d_dummy.h
index 5c9e97d226..210c404365 100644
--- a/servers/navigation_server_3d_dummy.h
+++ b/servers/navigation_server_3d_dummy.h
@@ -93,6 +93,9 @@ public:
int region_get_connections_count(RID p_region) const override { return 0; }
Vector3 region_get_connection_pathway_start(RID p_region, int p_connection_id) const override { return Vector3(); }
Vector3 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override { return Vector3(); }
+ Vector3 region_get_closest_point_to_segment(RID p_region, const Vector3 &p_from, const Vector3 &p_to, bool p_use_collision = false) const override { return Vector3(); }
+ Vector3 region_get_closest_point(RID p_region, const Vector3 &p_point) const override { return Vector3(); }
+ Vector3 region_get_closest_point_normal(RID p_region, const Vector3 &p_point) const override { return Vector3(); }
Vector3 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const override { return Vector3(); }
RID link_create() override { return RID(); }
diff --git a/servers/rendering/dummy/storage/mesh_storage.h b/servers/rendering/dummy/storage/mesh_storage.h
index ec19562147..b0953b5dce 100644
--- a/servers/rendering/dummy/storage/mesh_storage.h
+++ b/servers/rendering/dummy/storage/mesh_storage.h
@@ -163,7 +163,7 @@ public:
virtual AABB _multimesh_get_custom_aabb(RID p_multimesh) const override { return AABB(); }
virtual RID _multimesh_get_mesh(RID p_multimesh) const override { return RID(); }
- virtual AABB _multimesh_get_aabb(RID p_multimesh) const override { return AABB(); }
+ virtual AABB _multimesh_get_aabb(RID p_multimesh) override { return AABB(); }
virtual Transform3D _multimesh_instance_get_transform(RID p_multimesh, int p_index) const override { return Transform3D(); }
virtual Transform2D _multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const override { return Transform2D(); }
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
index f51b4ae8d0..b0851efe5f 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
@@ -351,710 +351,12 @@ void RendererCanvasRenderRD::free_polygon(PolygonID p_polygon) {
////////////////////
-void RendererCanvasRenderRD::_bind_canvas_texture(RD::DrawListID p_draw_list, RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID &r_last_texture, PushConstant &push_constant, Size2 &r_texpixel_size, bool p_texture_is_data) {
- if (p_texture == RID()) {
- p_texture = default_canvas_texture;
- }
-
- if (r_last_texture == p_texture) {
- return; //nothing to do, its the same
- }
-
- RID uniform_set;
- Color specular_shininess;
- Size2i size;
- bool use_normal;
- bool use_specular;
-
- bool success = RendererRD::TextureStorage::get_singleton()->canvas_texture_get_uniform_set(p_texture, p_base_filter, p_base_repeat, shader.default_version_rd_shader, CANVAS_TEXTURE_UNIFORM_SET, bool(push_constant.flags & FLAGS_CONVERT_ATTRIBUTES_TO_LINEAR), uniform_set, size, specular_shininess, use_normal, use_specular, p_texture_is_data);
- //something odd happened
- if (!success) {
- _bind_canvas_texture(p_draw_list, default_canvas_texture, p_base_filter, p_base_repeat, r_last_texture, push_constant, r_texpixel_size);
- return;
- }
-
- RD::get_singleton()->draw_list_bind_uniform_set(p_draw_list, uniform_set, CANVAS_TEXTURE_UNIFORM_SET);
-
- if (specular_shininess.a < 0.999) {
- push_constant.flags |= FLAGS_DEFAULT_SPECULAR_MAP_USED;
- } else {
- push_constant.flags &= ~FLAGS_DEFAULT_SPECULAR_MAP_USED;
- }
-
- if (use_normal) {
- push_constant.flags |= FLAGS_DEFAULT_NORMAL_MAP_USED;
- } else {
- push_constant.flags &= ~FLAGS_DEFAULT_NORMAL_MAP_USED;
- }
-
- push_constant.specular_shininess = uint32_t(CLAMP(specular_shininess.a * 255.0, 0, 255)) << 24;
- push_constant.specular_shininess |= uint32_t(CLAMP(specular_shininess.b * 255.0, 0, 255)) << 16;
- push_constant.specular_shininess |= uint32_t(CLAMP(specular_shininess.g * 255.0, 0, 255)) << 8;
- push_constant.specular_shininess |= uint32_t(CLAMP(specular_shininess.r * 255.0, 0, 255));
-
- r_texpixel_size.x = 1.0 / float(size.x);
- r_texpixel_size.y = 1.0 / float(size.y);
-
- push_constant.color_texture_pixel_size[0] = r_texpixel_size.x;
- push_constant.color_texture_pixel_size[1] = r_texpixel_size.y;
-
- r_last_texture = p_texture;
-}
-
_FORCE_INLINE_ static uint32_t _indices_to_primitives(RS::PrimitiveType p_primitive, uint32_t p_indices) {
static const uint32_t divisor[RS::PRIMITIVE_MAX] = { 1, 2, 1, 3, 1 };
static const uint32_t subtractor[RS::PRIMITIVE_MAX] = { 0, 0, 1, 0, 1 };
return (p_indices - subtractor[p_primitive]) / divisor[p_primitive];
}
-void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, RID p_render_target, const Item *p_item, RD::FramebufferFormatID p_framebuffer_format, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, Light *p_lights, PipelineVariants *p_pipeline_variants, bool &r_sdf_used, const Point2 &p_repeat_offset, RenderingMethod::RenderInfo *r_render_info) {
- //create an empty push constant
- RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton();
- RendererRD::MeshStorage *mesh_storage = RendererRD::MeshStorage::get_singleton();
- RendererRD::ParticlesStorage *particles_storage = RendererRD::ParticlesStorage::get_singleton();
-
- RS::CanvasItemTextureFilter current_filter = default_filter;
- RS::CanvasItemTextureRepeat current_repeat = default_repeat;
-
- if (p_item->texture_filter != RS::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT) {
- current_filter = p_item->texture_filter;
- }
-
- if (p_item->texture_repeat != RS::CANVAS_ITEM_TEXTURE_REPEAT_DEFAULT) {
- current_repeat = p_item->texture_repeat;
- }
-
- PushConstant push_constant;
- Transform2D base_transform = p_item->final_transform;
- if (p_item->repeat_source_item && (p_repeat_offset.x || p_repeat_offset.y)) {
- base_transform.columns[2] += p_item->repeat_source_item->final_transform.basis_xform(p_repeat_offset);
- }
- base_transform = p_canvas_transform_inverse * base_transform;
-
- Transform2D draw_transform;
- _update_transform_2d_to_mat2x3(base_transform, push_constant.world);
-
- Color base_color = p_item->final_modulate;
- bool use_linear_colors = texture_storage->render_target_is_using_hdr(p_render_target);
-
- for (int i = 0; i < 4; i++) {
- push_constant.modulation[i] = 0;
- push_constant.ninepatch_margins[i] = 0;
- push_constant.src_rect[i] = 0;
- push_constant.dst_rect[i] = 0;
- }
- push_constant.flags = 0;
- push_constant.color_texture_pixel_size[0] = 0;
- push_constant.color_texture_pixel_size[1] = 0;
-
- push_constant.pad[0] = 0;
- push_constant.pad[1] = 0;
-
- push_constant.lights[0] = 0;
- push_constant.lights[1] = 0;
- push_constant.lights[2] = 0;
- push_constant.lights[3] = 0;
-
- uint32_t base_flags = 0;
- base_flags |= use_linear_colors ? FLAGS_CONVERT_ATTRIBUTES_TO_LINEAR : 0;
-
- uint16_t light_count = 0;
- PipelineLightMode light_mode;
-
- {
- Light *light = p_lights;
-
- while (light) {
- if (light->render_index_cache >= 0 && p_item->light_mask & light->item_mask && p_item->z_final >= light->z_min && p_item->z_final <= light->z_max && p_item->global_rect_cache.intersects_transformed(light->xform_cache, light->rect_cache)) {
- uint32_t light_index = light->render_index_cache;
- push_constant.lights[light_count >> 2] |= light_index << ((light_count & 3) * 8);
-
- light_count++;
-
- if (light_count == MAX_LIGHTS_PER_ITEM - 1) {
- break;
- }
- }
- light = light->next_ptr;
- }
-
- base_flags |= light_count << FLAGS_LIGHT_COUNT_SHIFT;
- }
-
- light_mode = (light_count > 0 || using_directional_lights) ? PIPELINE_LIGHT_MODE_ENABLED : PIPELINE_LIGHT_MODE_DISABLED;
-
- PipelineVariants *pipeline_variants = p_pipeline_variants;
-
- bool reclip = false;
-
- RID last_texture;
- Size2 texpixel_size;
-
- bool skipping = false;
-
- const Item::Command *c = p_item->commands;
- while (c) {
- if (skipping && c->type != Item::Command::TYPE_ANIMATION_SLICE) {
- c = c->next;
- continue;
- }
-
- push_constant.flags = base_flags | (push_constant.flags & (FLAGS_DEFAULT_NORMAL_MAP_USED | FLAGS_DEFAULT_SPECULAR_MAP_USED)); // Reset on each command for safety, keep canvastexture binding config.
-
- switch (c->type) {
- case Item::Command::TYPE_RECT: {
- const Item::CommandRect *rect = static_cast<const Item::CommandRect *>(c);
-
- if (rect->flags & CANVAS_RECT_TILE) {
- current_repeat = RenderingServer::CanvasItemTextureRepeat::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED;
- }
-
- Color modulated = rect->modulate * base_color;
- if (use_linear_colors) {
- modulated = modulated.srgb_to_linear();
- }
-
- //bind pipeline
- if (rect->flags & CANVAS_RECT_LCD) {
- RID pipeline = pipeline_variants->variants[light_mode][PIPELINE_VARIANT_QUAD_LCD_BLEND].get_render_pipeline(RD::INVALID_ID, p_framebuffer_format);
- RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
- RD::get_singleton()->draw_list_set_blend_constants(p_draw_list, modulated);
- } else {
- RID pipeline = pipeline_variants->variants[light_mode][PIPELINE_VARIANT_QUAD].get_render_pipeline(RD::INVALID_ID, p_framebuffer_format);
- RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
- }
-
- //bind textures
-
- _bind_canvas_texture(p_draw_list, rect->texture, current_filter, current_repeat, last_texture, push_constant, texpixel_size, bool(rect->flags & CANVAS_RECT_MSDF));
-
- Rect2 src_rect;
- Rect2 dst_rect;
-
- if (rect->texture != RID()) {
- src_rect = (rect->flags & CANVAS_RECT_REGION) ? Rect2(rect->source.position * texpixel_size, rect->source.size * texpixel_size) : Rect2(0, 0, 1, 1);
- dst_rect = Rect2(rect->rect.position, rect->rect.size);
-
- if (dst_rect.size.width < 0) {
- dst_rect.position.x += dst_rect.size.width;
- dst_rect.size.width *= -1;
- }
- if (dst_rect.size.height < 0) {
- dst_rect.position.y += dst_rect.size.height;
- dst_rect.size.height *= -1;
- }
-
- if (rect->flags & CANVAS_RECT_FLIP_H) {
- src_rect.size.x *= -1;
- push_constant.flags |= FLAGS_FLIP_H;
- }
-
- if (rect->flags & CANVAS_RECT_FLIP_V) {
- src_rect.size.y *= -1;
- push_constant.flags |= FLAGS_FLIP_V;
- }
-
- if (rect->flags & CANVAS_RECT_TRANSPOSE) {
- push_constant.flags |= FLAGS_TRANSPOSE_RECT;
- }
-
- if (rect->flags & CANVAS_RECT_CLIP_UV) {
- push_constant.flags |= FLAGS_CLIP_RECT_UV;
- }
-
- } else {
- dst_rect = Rect2(rect->rect.position, rect->rect.size);
-
- if (dst_rect.size.width < 0) {
- dst_rect.position.x += dst_rect.size.width;
- dst_rect.size.width *= -1;
- }
- if (dst_rect.size.height < 0) {
- dst_rect.position.y += dst_rect.size.height;
- dst_rect.size.height *= -1;
- }
-
- src_rect = Rect2(0, 0, 1, 1);
- }
-
- if (rect->flags & CANVAS_RECT_MSDF) {
- push_constant.flags |= FLAGS_USE_MSDF;
- push_constant.msdf[0] = rect->px_range; // Pixel range.
- push_constant.msdf[1] = rect->outline; // Outline size.
- push_constant.msdf[2] = 0.f; // Reserved.
- push_constant.msdf[3] = 0.f; // Reserved.
- } else if (rect->flags & CANVAS_RECT_LCD) {
- push_constant.flags |= FLAGS_USE_LCD;
- }
-
- push_constant.modulation[0] = modulated.r;
- push_constant.modulation[1] = modulated.g;
- push_constant.modulation[2] = modulated.b;
- push_constant.modulation[3] = modulated.a;
-
- push_constant.src_rect[0] = src_rect.position.x;
- push_constant.src_rect[1] = src_rect.position.y;
- push_constant.src_rect[2] = src_rect.size.width;
- push_constant.src_rect[3] = src_rect.size.height;
-
- push_constant.dst_rect[0] = dst_rect.position.x;
- push_constant.dst_rect[1] = dst_rect.position.y;
- push_constant.dst_rect[2] = dst_rect.size.width;
- push_constant.dst_rect[3] = dst_rect.size.height;
-
- RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
- RD::get_singleton()->draw_list_bind_index_array(p_draw_list, shader.quad_index_array);
- RD::get_singleton()->draw_list_draw(p_draw_list, true);
-
- if (r_render_info) {
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_OBJECTS_IN_FRAME]++;
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_PRIMITIVES_IN_FRAME] += 2;
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_DRAW_CALLS_IN_FRAME]++;
- }
-
- } break;
-
- case Item::Command::TYPE_NINEPATCH: {
- const Item::CommandNinePatch *np = static_cast<const Item::CommandNinePatch *>(c);
-
- //bind pipeline
- {
- RID pipeline = pipeline_variants->variants[light_mode][PIPELINE_VARIANT_NINEPATCH].get_render_pipeline(RD::INVALID_ID, p_framebuffer_format);
- RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
- }
-
- //bind textures
-
- _bind_canvas_texture(p_draw_list, np->texture, current_filter, current_repeat, last_texture, push_constant, texpixel_size);
-
- Rect2 src_rect;
- Rect2 dst_rect(np->rect.position.x, np->rect.position.y, np->rect.size.x, np->rect.size.y);
-
- if (np->texture == RID()) {
- texpixel_size = Size2(1, 1);
- src_rect = Rect2(0, 0, 1, 1);
-
- } else {
- if (np->source != Rect2()) {
- src_rect = Rect2(np->source.position.x * texpixel_size.width, np->source.position.y * texpixel_size.height, np->source.size.x * texpixel_size.width, np->source.size.y * texpixel_size.height);
- push_constant.color_texture_pixel_size[0] = 1.0 / np->source.size.width;
- push_constant.color_texture_pixel_size[1] = 1.0 / np->source.size.height;
-
- } else {
- src_rect = Rect2(0, 0, 1, 1);
- }
- }
-
- Color modulated = np->color * base_color;
- if (use_linear_colors) {
- modulated = modulated.srgb_to_linear();
- }
-
- push_constant.modulation[0] = modulated.r;
- push_constant.modulation[1] = modulated.g;
- push_constant.modulation[2] = modulated.b;
- push_constant.modulation[3] = modulated.a;
-
- push_constant.src_rect[0] = src_rect.position.x;
- push_constant.src_rect[1] = src_rect.position.y;
- push_constant.src_rect[2] = src_rect.size.width;
- push_constant.src_rect[3] = src_rect.size.height;
-
- push_constant.dst_rect[0] = dst_rect.position.x;
- push_constant.dst_rect[1] = dst_rect.position.y;
- push_constant.dst_rect[2] = dst_rect.size.width;
- push_constant.dst_rect[3] = dst_rect.size.height;
-
- push_constant.flags |= int(np->axis_x) << FLAGS_NINEPATCH_H_MODE_SHIFT;
- push_constant.flags |= int(np->axis_y) << FLAGS_NINEPATCH_V_MODE_SHIFT;
-
- if (np->draw_center) {
- push_constant.flags |= FLAGS_NINEPACH_DRAW_CENTER;
- }
-
- push_constant.ninepatch_margins[0] = np->margin[SIDE_LEFT];
- push_constant.ninepatch_margins[1] = np->margin[SIDE_TOP];
- push_constant.ninepatch_margins[2] = np->margin[SIDE_RIGHT];
- push_constant.ninepatch_margins[3] = np->margin[SIDE_BOTTOM];
-
- RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
- RD::get_singleton()->draw_list_bind_index_array(p_draw_list, shader.quad_index_array);
- RD::get_singleton()->draw_list_draw(p_draw_list, true);
-
- if (r_render_info) {
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_OBJECTS_IN_FRAME]++;
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_PRIMITIVES_IN_FRAME] += 2;
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_DRAW_CALLS_IN_FRAME]++;
- }
-
- // Restore if overridden.
- push_constant.color_texture_pixel_size[0] = texpixel_size.x;
- push_constant.color_texture_pixel_size[1] = texpixel_size.y;
-
- } break;
- case Item::Command::TYPE_POLYGON: {
- const Item::CommandPolygon *polygon = static_cast<const Item::CommandPolygon *>(c);
-
- PolygonBuffers *pb = polygon_buffers.polygons.getptr(polygon->polygon.polygon_id);
- ERR_CONTINUE(!pb);
- //bind pipeline
- {
- static const PipelineVariant variant[RS::PRIMITIVE_MAX] = { PIPELINE_VARIANT_ATTRIBUTE_POINTS, PIPELINE_VARIANT_ATTRIBUTE_LINES, PIPELINE_VARIANT_ATTRIBUTE_LINES_STRIP, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLES, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLE_STRIP };
- ERR_CONTINUE(polygon->primitive < 0 || polygon->primitive >= RS::PRIMITIVE_MAX);
- RID pipeline = pipeline_variants->variants[light_mode][variant[polygon->primitive]].get_render_pipeline(pb->vertex_format_id, p_framebuffer_format);
- RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
- }
-
- if (polygon->primitive == RS::PRIMITIVE_LINES) {
- //not supported in most hardware, so pointless
- //RD::get_singleton()->draw_list_set_line_width(p_draw_list, polygon->line_width);
- }
-
- //bind textures
-
- _bind_canvas_texture(p_draw_list, polygon->texture, current_filter, current_repeat, last_texture, push_constant, texpixel_size);
-
- Color color = base_color;
- if (use_linear_colors) {
- color = color.srgb_to_linear();
- }
-
- push_constant.modulation[0] = color.r;
- push_constant.modulation[1] = color.g;
- push_constant.modulation[2] = color.b;
- push_constant.modulation[3] = color.a;
-
- for (int j = 0; j < 4; j++) {
- push_constant.src_rect[j] = 0;
- push_constant.dst_rect[j] = 0;
- push_constant.ninepatch_margins[j] = 0;
- }
-
- RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
- RD::get_singleton()->draw_list_bind_vertex_array(p_draw_list, pb->vertex_array);
- if (pb->indices.is_valid()) {
- RD::get_singleton()->draw_list_bind_index_array(p_draw_list, pb->indices);
- }
- RD::get_singleton()->draw_list_draw(p_draw_list, pb->indices.is_valid());
-
- if (r_render_info) {
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_OBJECTS_IN_FRAME]++;
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_PRIMITIVES_IN_FRAME] += _indices_to_primitives(polygon->primitive, pb->primitive_count);
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_DRAW_CALLS_IN_FRAME]++;
- }
-
- } break;
- case Item::Command::TYPE_PRIMITIVE: {
- const Item::CommandPrimitive *primitive = static_cast<const Item::CommandPrimitive *>(c);
-
- //bind pipeline
- {
- static const PipelineVariant variant[4] = { PIPELINE_VARIANT_PRIMITIVE_POINTS, PIPELINE_VARIANT_PRIMITIVE_LINES, PIPELINE_VARIANT_PRIMITIVE_TRIANGLES, PIPELINE_VARIANT_PRIMITIVE_TRIANGLES };
- ERR_CONTINUE(primitive->point_count == 0 || primitive->point_count > 4);
- RID pipeline = pipeline_variants->variants[light_mode][variant[primitive->point_count - 1]].get_render_pipeline(RD::INVALID_ID, p_framebuffer_format);
- RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
- }
-
- //bind textures
-
- _bind_canvas_texture(p_draw_list, primitive->texture, current_filter, current_repeat, last_texture, push_constant, texpixel_size);
-
- RD::get_singleton()->draw_list_bind_index_array(p_draw_list, primitive_arrays.index_array[MIN(3u, primitive->point_count) - 1]);
-
- for (uint32_t j = 0; j < MIN(3u, primitive->point_count); j++) {
- push_constant.points[j * 2 + 0] = primitive->points[j].x;
- push_constant.points[j * 2 + 1] = primitive->points[j].y;
- push_constant.uvs[j * 2 + 0] = primitive->uvs[j].x;
- push_constant.uvs[j * 2 + 1] = primitive->uvs[j].y;
- Color col = primitive->colors[j] * base_color;
- if (use_linear_colors) {
- col = col.srgb_to_linear();
- }
- push_constant.colors[j * 2 + 0] = (uint32_t(Math::make_half_float(col.g)) << 16) | Math::make_half_float(col.r);
- push_constant.colors[j * 2 + 1] = (uint32_t(Math::make_half_float(col.a)) << 16) | Math::make_half_float(col.b);
- }
- RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
- RD::get_singleton()->draw_list_draw(p_draw_list, true);
-
- if (r_render_info) {
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_OBJECTS_IN_FRAME]++;
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_PRIMITIVES_IN_FRAME]++;
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_DRAW_CALLS_IN_FRAME]++;
- }
-
- if (primitive->point_count == 4) {
- for (uint32_t j = 1; j < 3; j++) {
- //second half of triangle
- push_constant.points[j * 2 + 0] = primitive->points[j + 1].x;
- push_constant.points[j * 2 + 1] = primitive->points[j + 1].y;
- push_constant.uvs[j * 2 + 0] = primitive->uvs[j + 1].x;
- push_constant.uvs[j * 2 + 1] = primitive->uvs[j + 1].y;
- Color col = primitive->colors[j + 1] * base_color;
- if (use_linear_colors) {
- col = col.srgb_to_linear();
- }
- push_constant.colors[j * 2 + 0] = (uint32_t(Math::make_half_float(col.g)) << 16) | Math::make_half_float(col.r);
- push_constant.colors[j * 2 + 1] = (uint32_t(Math::make_half_float(col.a)) << 16) | Math::make_half_float(col.b);
- }
-
- RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
- RD::get_singleton()->draw_list_draw(p_draw_list, true);
-
- if (r_render_info) {
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_OBJECTS_IN_FRAME]++;
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_PRIMITIVES_IN_FRAME]++;
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_DRAW_CALLS_IN_FRAME]++;
- }
- }
-
- } break;
- case Item::Command::TYPE_MESH:
- case Item::Command::TYPE_MULTIMESH:
- case Item::Command::TYPE_PARTICLES: {
- RID mesh;
- RID mesh_instance;
- RID texture;
- Color modulate(1, 1, 1, 1);
- float world_backup[6];
- int instance_count = 1;
-
- for (int j = 0; j < 6; j++) {
- world_backup[j] = push_constant.world[j];
- }
-
- if (c->type == Item::Command::TYPE_MESH) {
- const Item::CommandMesh *m = static_cast<const Item::CommandMesh *>(c);
- mesh = m->mesh;
- mesh_instance = m->mesh_instance;
- texture = m->texture;
- modulate = m->modulate;
- _update_transform_2d_to_mat2x3(base_transform * draw_transform * m->transform, push_constant.world);
- } else if (c->type == Item::Command::TYPE_MULTIMESH) {
- const Item::CommandMultiMesh *mm = static_cast<const Item::CommandMultiMesh *>(c);
- RID multimesh = mm->multimesh;
- mesh = mesh_storage->multimesh_get_mesh(multimesh);
- texture = mm->texture;
-
- if (mesh_storage->multimesh_get_transform_format(multimesh) != RS::MULTIMESH_TRANSFORM_2D) {
- break;
- }
-
- instance_count = mesh_storage->multimesh_get_instances_to_draw(multimesh);
-
- if (instance_count == 0) {
- break;
- }
-
- RID uniform_set = mesh_storage->multimesh_get_2d_uniform_set(multimesh, shader.default_version_rd_shader, TRANSFORMS_UNIFORM_SET);
- RD::get_singleton()->draw_list_bind_uniform_set(p_draw_list, uniform_set, TRANSFORMS_UNIFORM_SET);
- push_constant.flags |= 1; //multimesh, trails disabled
- if (mesh_storage->multimesh_uses_colors(multimesh)) {
- push_constant.flags |= FLAGS_INSTANCING_HAS_COLORS;
- }
- if (mesh_storage->multimesh_uses_custom_data(multimesh)) {
- push_constant.flags |= FLAGS_INSTANCING_HAS_CUSTOM_DATA;
- }
- } else if (c->type == Item::Command::TYPE_PARTICLES) {
- const Item::CommandParticles *pt = static_cast<const Item::CommandParticles *>(c);
- ERR_BREAK(particles_storage->particles_get_mode(pt->particles) != RS::PARTICLES_MODE_2D);
- particles_storage->particles_request_process(pt->particles);
-
- if (particles_storage->particles_is_inactive(pt->particles) || particles_storage->particles_get_frame_counter(pt->particles) == 0) {
- break;
- }
-
- RenderingServerDefault::redraw_request(); // active particles means redraw request
-
- int dpc = particles_storage->particles_get_draw_passes(pt->particles);
- if (dpc == 0) {
- break; //nothing to draw
- }
- uint32_t divisor = 1;
- instance_count = particles_storage->particles_get_amount(pt->particles, divisor);
-
- RID uniform_set = particles_storage->particles_get_instance_buffer_uniform_set(pt->particles, shader.default_version_rd_shader, TRANSFORMS_UNIFORM_SET);
- RD::get_singleton()->draw_list_bind_uniform_set(p_draw_list, uniform_set, TRANSFORMS_UNIFORM_SET);
-
- push_constant.flags |= divisor;
- instance_count /= divisor;
-
- push_constant.flags |= FLAGS_INSTANCING_HAS_COLORS;
- push_constant.flags |= FLAGS_INSTANCING_HAS_CUSTOM_DATA;
-
- mesh = particles_storage->particles_get_draw_pass_mesh(pt->particles, 0); //higher ones are ignored
- texture = pt->texture;
-
- if (particles_storage->particles_has_collision(pt->particles) && texture_storage->render_target_is_sdf_enabled(p_render_target)) {
- //pass collision information
- Transform2D xform = p_item->final_transform;
-
- RID sdf_texture = texture_storage->render_target_get_sdf_texture(p_render_target);
-
- Rect2 to_screen;
- {
- Rect2 sdf_rect = texture_storage->render_target_get_sdf_rect(p_render_target);
-
- to_screen.size = Vector2(1.0 / sdf_rect.size.width, 1.0 / sdf_rect.size.height);
- to_screen.position = -sdf_rect.position * to_screen.size;
- }
-
- particles_storage->particles_set_canvas_sdf_collision(pt->particles, true, xform, to_screen, sdf_texture);
- } else {
- particles_storage->particles_set_canvas_sdf_collision(pt->particles, false, Transform2D(), Rect2(), RID());
- }
-
- // Signal that SDF texture needs to be updated.
- r_sdf_used |= particles_storage->particles_has_collision(pt->particles);
- }
-
- if (mesh.is_null()) {
- break;
- }
-
- _bind_canvas_texture(p_draw_list, texture, current_filter, current_repeat, last_texture, push_constant, texpixel_size);
-
- uint32_t surf_count = mesh_storage->mesh_get_surface_count(mesh);
- static const PipelineVariant variant[RS::PRIMITIVE_MAX] = { PIPELINE_VARIANT_ATTRIBUTE_POINTS, PIPELINE_VARIANT_ATTRIBUTE_LINES, PIPELINE_VARIANT_ATTRIBUTE_LINES_STRIP, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLES, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLE_STRIP };
-
- Color modulated = modulate * base_color;
- if (use_linear_colors) {
- modulated = modulated.srgb_to_linear();
- }
-
- push_constant.modulation[0] = modulated.r;
- push_constant.modulation[1] = modulated.g;
- push_constant.modulation[2] = modulated.b;
- push_constant.modulation[3] = modulated.a;
-
- for (int j = 0; j < 4; j++) {
- push_constant.src_rect[j] = 0;
- push_constant.dst_rect[j] = 0;
- push_constant.ninepatch_margins[j] = 0;
- }
-
- for (uint32_t j = 0; j < surf_count; j++) {
- void *surface = mesh_storage->mesh_get_surface(mesh, j);
-
- RS::PrimitiveType primitive = mesh_storage->mesh_surface_get_primitive(surface);
- ERR_CONTINUE(primitive < 0 || primitive >= RS::PRIMITIVE_MAX);
-
- uint64_t input_mask = pipeline_variants->variants[light_mode][variant[primitive]].get_vertex_input_mask();
-
- RID vertex_array;
- RD::VertexFormatID vertex_format = RD::INVALID_FORMAT_ID;
-
- if (mesh_instance.is_valid()) {
- mesh_storage->mesh_instance_surface_get_vertex_arrays_and_format(mesh_instance, j, input_mask, false, vertex_array, vertex_format);
- } else {
- mesh_storage->mesh_surface_get_vertex_arrays_and_format(surface, input_mask, false, vertex_array, vertex_format);
- }
-
- RID pipeline = pipeline_variants->variants[light_mode][variant[primitive]].get_render_pipeline(vertex_format, p_framebuffer_format);
- RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
-
- RID index_array = mesh_storage->mesh_surface_get_index_array(surface, 0);
-
- if (index_array.is_valid()) {
- RD::get_singleton()->draw_list_bind_index_array(p_draw_list, index_array);
- }
-
- RD::get_singleton()->draw_list_bind_vertex_array(p_draw_list, vertex_array);
- RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
-
- RD::get_singleton()->draw_list_draw(p_draw_list, index_array.is_valid(), instance_count);
-
- if (r_render_info) {
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_OBJECTS_IN_FRAME]++;
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_PRIMITIVES_IN_FRAME] += _indices_to_primitives(primitive, mesh_storage->mesh_surface_get_vertices_drawn_count(surface)) * instance_count;
- r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_DRAW_CALLS_IN_FRAME]++;
- }
- }
-
- for (int j = 0; j < 6; j++) {
- push_constant.world[j] = world_backup[j];
- }
- } break;
- case Item::Command::TYPE_TRANSFORM: {
- const Item::CommandTransform *transform = static_cast<const Item::CommandTransform *>(c);
- draw_transform = transform->xform;
- _update_transform_2d_to_mat2x3(base_transform * transform->xform, push_constant.world);
-
- } break;
- case Item::Command::TYPE_CLIP_IGNORE: {
- const Item::CommandClipIgnore *ci = static_cast<const Item::CommandClipIgnore *>(c);
- if (current_clip) {
- if (ci->ignore != reclip) {
- if (ci->ignore) {
- RD::get_singleton()->draw_list_disable_scissor(p_draw_list);
- reclip = true;
- } else {
- RD::get_singleton()->draw_list_enable_scissor(p_draw_list, current_clip->final_clip_rect);
- reclip = false;
- }
- }
- }
-
- } break;
- case Item::Command::TYPE_ANIMATION_SLICE: {
- const Item::CommandAnimationSlice *as = static_cast<const Item::CommandAnimationSlice *>(c);
- double current_time = RendererCompositorRD::get_singleton()->get_total_time();
- double local_time = Math::fposmod(current_time - as->offset, as->animation_length);
- skipping = !(local_time >= as->slice_begin && local_time < as->slice_end);
-
- RenderingServerDefault::redraw_request(); // animation visible means redraw request
- } break;
- }
-
- c = c->next;
- }
-#ifdef DEBUG_ENABLED
- if (debug_redraw && p_item->debug_redraw_time > 0.0) {
- Color dc = debug_redraw_color;
- dc.a *= p_item->debug_redraw_time / debug_redraw_time;
-
- RID pipeline = pipeline_variants->variants[PIPELINE_LIGHT_MODE_DISABLED][PIPELINE_VARIANT_QUAD].get_render_pipeline(RD::INVALID_ID, p_framebuffer_format);
- RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
-
- //bind textures
-
- _bind_canvas_texture(p_draw_list, RID(), current_filter, current_repeat, last_texture, push_constant, texpixel_size);
-
- Rect2 src_rect;
- Rect2 dst_rect;
-
- dst_rect = Rect2(Vector2(), p_item->rect.size);
- src_rect = Rect2(0, 0, 1, 1);
-
- push_constant.modulation[0] = dc.r;
- push_constant.modulation[1] = dc.g;
- push_constant.modulation[2] = dc.b;
- push_constant.modulation[3] = dc.a;
-
- push_constant.src_rect[0] = src_rect.position.x;
- push_constant.src_rect[1] = src_rect.position.y;
- push_constant.src_rect[2] = src_rect.size.width;
- push_constant.src_rect[3] = src_rect.size.height;
-
- push_constant.dst_rect[0] = dst_rect.position.x;
- push_constant.dst_rect[1] = dst_rect.position.y;
- push_constant.dst_rect[2] = dst_rect.size.width;
- push_constant.dst_rect[3] = dst_rect.size.height;
-
- RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
- RD::get_singleton()->draw_list_bind_index_array(p_draw_list, shader.quad_index_array);
- RD::get_singleton()->draw_list_draw(p_draw_list, true);
-
- p_item->debug_redraw_time -= RSG::rasterizer->get_frame_delta_time();
-
- RenderingServerDefault::redraw_request();
- }
-#endif
- if (current_clip && reclip) {
- //will make it re-enable clipping if needed afterwards
- current_clip = nullptr;
- }
-}
-
RID RendererCanvasRenderRD::_create_base_uniform_set(RID p_to_render_target, bool p_backbuffer) {
RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton();
RendererRD::MaterialStorage *material_storage = RendererRD::MaterialStorage::get_singleton();
@@ -1148,127 +450,6 @@ RID RendererCanvasRenderRD::_create_base_uniform_set(RID p_to_render_target, boo
return uniform_set;
}
-void RendererCanvasRenderRD::_render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer, RenderingMethod::RenderInfo *r_render_info) {
- RendererRD::MaterialStorage *material_storage = RendererRD::MaterialStorage::get_singleton();
- RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton();
-
- Item *current_clip = nullptr;
-
- Transform2D canvas_transform_inverse = p_canvas_transform_inverse;
-
- RID framebuffer;
- RID fb_uniform_set;
- bool clear = false;
- Vector<Color> clear_colors;
-
- if (p_to_backbuffer) {
- framebuffer = texture_storage->render_target_get_rd_backbuffer_framebuffer(p_to_render_target);
- fb_uniform_set = texture_storage->render_target_get_backbuffer_uniform_set(p_to_render_target);
- } else {
- framebuffer = texture_storage->render_target_get_rd_framebuffer(p_to_render_target);
- texture_storage->render_target_set_msaa_needs_resolve(p_to_render_target, false); // If MSAA is enabled, our framebuffer will be resolved!
-
- if (texture_storage->render_target_is_clear_requested(p_to_render_target)) {
- clear = true;
- clear_colors.push_back(texture_storage->render_target_get_clear_request_color(p_to_render_target));
- texture_storage->render_target_disable_clear_request(p_to_render_target);
- }
- // TODO: Obtain from framebuffer format eventually when this is implemented.
- fb_uniform_set = texture_storage->render_target_get_framebuffer_uniform_set(p_to_render_target);
- }
-
- if (fb_uniform_set.is_null() || !RD::get_singleton()->uniform_set_is_valid(fb_uniform_set)) {
- fb_uniform_set = _create_base_uniform_set(p_to_render_target, p_to_backbuffer);
- }
-
- RD::FramebufferFormatID fb_format = RD::get_singleton()->framebuffer_get_format(framebuffer);
-
- RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, clear ? RD::INITIAL_ACTION_CLEAR : RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_DISCARD, clear_colors, 1, 0, Rect2(), RDD::BreadcrumbMarker::UI_PASS);
-
- RD::get_singleton()->draw_list_bind_uniform_set(draw_list, fb_uniform_set, BASE_UNIFORM_SET);
- RD::get_singleton()->draw_list_bind_uniform_set(draw_list, state.default_transforms_uniform_set, TRANSFORMS_UNIFORM_SET);
-
- RID prev_material;
-
- PipelineVariants *pipeline_variants = &shader.pipeline_variants;
-
- for (int i = 0; i < p_item_count; i++) {
- Item *ci = items[i];
-
- if (current_clip != ci->final_clip_owner) {
- current_clip = ci->final_clip_owner;
-
- //setup clip
- if (current_clip) {
- RD::get_singleton()->draw_list_enable_scissor(draw_list, current_clip->final_clip_rect);
-
- } else {
- RD::get_singleton()->draw_list_disable_scissor(draw_list);
- }
- }
-
- RID material = ci->material_owner == nullptr ? ci->material : ci->material_owner->material;
-
- if (ci->use_canvas_group) {
- if (ci->canvas_group->mode == RS::CANVAS_GROUP_MODE_CLIP_AND_DRAW) {
- material = default_clip_children_material;
- } else {
- if (material.is_null()) {
- if (ci->canvas_group->mode == RS::CANVAS_GROUP_MODE_CLIP_ONLY) {
- material = default_clip_children_material;
- } else {
- material = default_canvas_group_material;
- }
- }
- }
- }
-
- if (material != prev_material) {
- CanvasMaterialData *material_data = nullptr;
- if (material.is_valid()) {
- material_data = static_cast<CanvasMaterialData *>(material_storage->material_get_data(material, RendererRD::MaterialStorage::SHADER_TYPE_2D));
- }
-
- if (material_data) {
- if (material_data->shader_data->version.is_valid() && material_data->shader_data->valid) {
- pipeline_variants = &material_data->shader_data->pipeline_variants;
- // Update uniform set.
- RID uniform_set = texture_storage->render_target_is_using_hdr(p_to_render_target) ? material_data->uniform_set : material_data->uniform_set_srgb;
- if (uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(uniform_set)) { // Material may not have a uniform set.
- RD::get_singleton()->draw_list_bind_uniform_set(draw_list, uniform_set, MATERIAL_UNIFORM_SET);
- material_data->set_as_used();
- }
- } else {
- pipeline_variants = &shader.pipeline_variants;
- }
- } else {
- pipeline_variants = &shader.pipeline_variants;
- }
- }
-
- if (!ci->repeat_size.x && !ci->repeat_size.y) {
- _render_item(draw_list, p_to_render_target, ci, fb_format, canvas_transform_inverse, current_clip, p_lights, pipeline_variants, r_sdf_used, Point2(), r_render_info);
- } else {
- Point2 start_pos = ci->repeat_size * -(ci->repeat_times / 2);
- Point2 offset;
-
- int repeat_times_x = ci->repeat_size.x ? ci->repeat_times : 0;
- int repeat_times_y = ci->repeat_size.y ? ci->repeat_times : 0;
- for (int ry = 0; ry <= repeat_times_y; ry++) {
- offset.y = start_pos.y + ry * ci->repeat_size.y;
- for (int rx = 0; rx <= repeat_times_x; rx++) {
- offset.x = start_pos.x + rx * ci->repeat_size.x;
- _render_item(draw_list, p_to_render_target, ci, fb_format, canvas_transform_inverse, current_clip, p_lights, pipeline_variants, r_sdf_used, offset, r_render_info);
- }
- }
- }
-
- prev_material = material;
- }
-
- RD::get_singleton()->draw_list_end();
-}
-
void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_light_list, const Transform2D &p_canvas_transform, RenderingServer::CanvasItemTextureFilter p_default_filter, RenderingServer::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used, RenderingMethod::RenderInfo *r_render_info) {
RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton();
RendererRD::MaterialStorage *material_storage = RendererRD::MaterialStorage::get_singleton();
@@ -1509,11 +690,18 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
Item *canvas_group_owner = nullptr;
bool skip_item = false;
+ state.last_instance_index = 0;
+
bool update_skeletons = false;
bool time_used = false;
bool backbuffer_cleared = false;
+ RenderTarget to_render_target;
+ to_render_target.render_target = p_to_render_target;
+ bool use_linear_colors = texture_storage->render_target_is_using_hdr(p_to_render_target);
+ to_render_target.base_flags = use_linear_colors ? FLAGS_CONVERT_ATTRIBUTES_TO_LINEAR : 0;
+
while (ci) {
if (ci->copy_back_buffer && canvas_group_owner == nullptr) {
backbuffer_copy = true;
@@ -1572,8 +760,7 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
mesh_storage->update_mesh_instances();
update_skeletons = false;
}
-
- _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info);
+ _render_batch_items(to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info);
item_count = 0;
if (ci->canvas_group_owner->canvas_group->mode != RS::CANVAS_GROUP_MODE_TRANSPARENT) {
@@ -1605,7 +792,7 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
update_skeletons = false;
}
- _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, true, r_render_info);
+ _render_batch_items(to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, true, r_render_info);
item_count = 0;
if (ci->canvas_group->blur_mipmaps) {
@@ -1629,7 +816,7 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
update_skeletons = false;
}
- _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info);
+ _render_batch_items(to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info);
item_count = 0;
texture_storage->render_target_copy_to_back_buffer(p_to_render_target, back_buffer_rect, backbuffer_gen_mipmaps);
@@ -1659,7 +846,7 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
update_skeletons = false;
}
- _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, canvas_group_owner != nullptr, r_render_info);
+ _render_batch_items(to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, canvas_group_owner != nullptr, r_render_info);
//then reset
item_count = 0;
}
@@ -1670,6 +857,9 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
if (time_used) {
RenderingServerDefault::redraw_request();
}
+
+ state.current_data_buffer_index = (state.current_data_buffer_index + 1) % state.canvas_instance_data_buffers.size();
+ state.current_instance_buffer_index = 0;
}
RID RendererCanvasRenderRD::light_create() {
@@ -2635,7 +1825,7 @@ RendererCanvasRenderRD::RendererCanvasRenderRD() {
actions.base_uniform_string = "material.";
actions.default_filter = ShaderLanguage::FILTER_LINEAR;
actions.default_repeat = ShaderLanguage::REPEAT_DISABLE;
- actions.base_varying_index = 4;
+ actions.base_varying_index = 5;
actions.global_buffer_array_variable = "global_shader_uniforms.data";
@@ -2843,7 +2033,19 @@ void fragment() {
material_storage->material_set_shader(default_clip_children_material, default_clip_children_shader);
}
- static_assert(sizeof(PushConstant) == 128);
+ {
+ state.max_instances_per_buffer = uint32_t(GLOBAL_GET("rendering/2d/batching/item_buffer_size"));
+ state.max_instance_buffer_size = state.max_instances_per_buffer * sizeof(InstanceData);
+ state.canvas_instance_data_buffers.resize(3);
+ state.canvas_instance_batches.reserve(200);
+
+ for (int i = 0; i < 3; i++) {
+ DataBuffer db;
+ db.instance_buffers.push_back(RD::get_singleton()->storage_buffer_create(state.max_instance_buffer_size));
+ state.canvas_instance_data_buffers[i] = db;
+ }
+ state.instance_data_array = memnew_arr(InstanceData, state.max_instances_per_buffer);
+ }
}
bool RendererCanvasRenderRD::free(RID p_rid) {
@@ -2893,6 +2095,1009 @@ void RendererCanvasRenderRD::set_debug_redraw(bool p_enabled, double p_time, con
debug_redraw_color = p_color;
}
+void RendererCanvasRenderRD::_render_batch_items(RenderTarget p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer, RenderingMethod::RenderInfo *r_render_info) {
+ // Record batches
+ uint32_t instance_index = 0;
+ {
+ RendererRD::MaterialStorage *material_storage = RendererRD::MaterialStorage::get_singleton();
+ Item *current_clip = nullptr;
+
+ // Record Batches.
+ // First item always forms its own batch.
+ bool batch_broken = false;
+ Batch *current_batch = _new_batch(batch_broken);
+ // Override the start position and index as we want to start from where we finished off last time.
+ current_batch->start = state.last_instance_index;
+
+ for (int i = 0; i < p_item_count; i++) {
+ Item *ci = items[i];
+
+ if (ci->final_clip_owner != current_batch->clip) {
+ current_batch = _new_batch(batch_broken);
+ current_batch->clip = ci->final_clip_owner;
+ current_clip = ci->final_clip_owner;
+ }
+
+ RID material = ci->material_owner == nullptr ? ci->material : ci->material_owner->material;
+
+ if (ci->use_canvas_group) {
+ if (ci->canvas_group->mode == RS::CANVAS_GROUP_MODE_CLIP_AND_DRAW) {
+ material = default_clip_children_material;
+ } else {
+ if (material.is_null()) {
+ if (ci->canvas_group->mode == RS::CANVAS_GROUP_MODE_CLIP_ONLY) {
+ material = default_clip_children_material;
+ } else {
+ material = default_canvas_group_material;
+ }
+ }
+ }
+ }
+
+ if (material != current_batch->material) {
+ current_batch = _new_batch(batch_broken);
+
+ CanvasMaterialData *material_data = nullptr;
+ if (material.is_valid()) {
+ material_data = static_cast<CanvasMaterialData *>(material_storage->material_get_data(material, RendererRD::MaterialStorage::SHADER_TYPE_2D));
+ }
+
+ current_batch->material = material;
+ current_batch->material_data = material_data;
+ }
+
+ Transform2D base_transform = p_canvas_transform_inverse * ci->final_transform;
+ if (!ci->repeat_size.x && !ci->repeat_size.y) {
+ _record_item_commands(ci, p_to_render_target, base_transform, current_clip, p_lights, instance_index, batch_broken, r_sdf_used);
+ } else {
+ Point2 start_pos = ci->repeat_size * -(ci->repeat_times / 2);
+ Point2 end_pos = ci->repeat_size * ci->repeat_times + ci->repeat_size + start_pos;
+ Point2 pos = start_pos;
+ do {
+ do {
+ Transform2D transform = base_transform * Transform2D(0, pos / ci->xform_curr.get_scale());
+ _record_item_commands(ci, p_to_render_target, transform, current_clip, p_lights, instance_index, batch_broken, r_sdf_used);
+ pos.y += ci->repeat_size.y;
+ } while (pos.y < end_pos.y);
+
+ pos.x += ci->repeat_size.x;
+ pos.y = start_pos.y;
+ } while (pos.x < end_pos.x);
+ }
+ }
+
+ // Copy over remaining data needed for rendering.
+ if (instance_index > 0) {
+ RD::get_singleton()->buffer_update(
+ state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[state.current_instance_buffer_index],
+ state.last_instance_index * sizeof(InstanceData),
+ instance_index * sizeof(InstanceData),
+ state.instance_data_array);
+ }
+ }
+
+ if (state.canvas_instance_batches.is_empty()) {
+ // Nothing to render, just return.
+ return;
+ }
+
+ // Render batches
+
+ RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton();
+
+ RID framebuffer;
+ RID fb_uniform_set;
+ bool clear = false;
+ Vector<Color> clear_colors;
+
+ if (p_to_backbuffer) {
+ framebuffer = texture_storage->render_target_get_rd_backbuffer_framebuffer(p_to_render_target.render_target);
+ fb_uniform_set = texture_storage->render_target_get_backbuffer_uniform_set(p_to_render_target.render_target);
+ } else {
+ framebuffer = texture_storage->render_target_get_rd_framebuffer(p_to_render_target.render_target);
+ texture_storage->render_target_set_msaa_needs_resolve(p_to_render_target.render_target, false); // If MSAA is enabled, our framebuffer will be resolved!
+
+ if (texture_storage->render_target_is_clear_requested(p_to_render_target.render_target)) {
+ clear = true;
+ clear_colors.push_back(texture_storage->render_target_get_clear_request_color(p_to_render_target.render_target));
+ texture_storage->render_target_disable_clear_request(p_to_render_target.render_target);
+ }
+ // TODO: Obtain from framebuffer format eventually when this is implemented.
+ fb_uniform_set = texture_storage->render_target_get_framebuffer_uniform_set(p_to_render_target.render_target);
+ }
+
+ if (fb_uniform_set.is_null() || !RD::get_singleton()->uniform_set_is_valid(fb_uniform_set)) {
+ fb_uniform_set = _create_base_uniform_set(p_to_render_target.render_target, p_to_backbuffer);
+ }
+
+ RD::FramebufferFormatID fb_format = RD::get_singleton()->framebuffer_get_format(framebuffer);
+
+ RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, clear ? RD::INITIAL_ACTION_CLEAR : RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_DISCARD, clear_colors);
+
+ RD::get_singleton()->draw_list_bind_uniform_set(draw_list, fb_uniform_set, BASE_UNIFORM_SET);
+ RD::get_singleton()->draw_list_bind_uniform_set(draw_list, state.default_transforms_uniform_set, TRANSFORMS_UNIFORM_SET);
+
+ Item *current_clip = nullptr;
+ state.current_tex_uniform_set = RID();
+
+ for (uint32_t i = 0; i <= state.current_batch_index; i++) {
+ Batch *current_batch = &state.canvas_instance_batches[i];
+ // Skipping when there is no instances.
+ if (current_batch->instance_count == 0) {
+ continue;
+ }
+
+ //setup clip
+ if (current_clip != current_batch->clip) {
+ current_clip = current_batch->clip;
+ if (current_clip) {
+ RD::get_singleton()->draw_list_enable_scissor(draw_list, current_clip->final_clip_rect);
+ } else {
+ RD::get_singleton()->draw_list_disable_scissor(draw_list);
+ }
+ }
+
+ PipelineVariants *pipeline_variants = &shader.pipeline_variants;
+
+ CanvasMaterialData *material_data = current_batch->material_data;
+ if (material_data) {
+ if (material_data->shader_data->version.is_valid() && material_data->shader_data->valid) {
+ pipeline_variants = &material_data->shader_data->pipeline_variants;
+ // Update uniform set.
+ RID uniform_set = texture_storage->render_target_is_using_hdr(p_to_render_target.render_target) ? material_data->uniform_set : material_data->uniform_set_srgb;
+ if (uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(uniform_set)) { // Material may not have a uniform set.
+ RD::get_singleton()->draw_list_bind_uniform_set(draw_list, uniform_set, MATERIAL_UNIFORM_SET);
+ material_data->set_as_used();
+ }
+ }
+ }
+
+ _render_batch(draw_list, pipeline_variants, fb_format, p_lights, current_batch, r_render_info);
+ }
+
+ RD::get_singleton()->draw_list_end();
+
+ state.current_batch_index = 0;
+ state.canvas_instance_batches.clear();
+ state.last_instance_index += instance_index;
+}
+
+void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTarget p_render_target, const Transform2D &p_base_transform, Item *&r_current_clip, Light *p_lights, uint32_t &r_index, bool &r_batch_broken, bool &r_sdf_used) {
+ Batch *current_batch = &state.canvas_instance_batches[state.current_batch_index];
+
+ RenderingServer::CanvasItemTextureFilter texture_filter = p_item->texture_filter == RS::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT ? default_filter : p_item->texture_filter;
+ RenderingServer::CanvasItemTextureRepeat texture_repeat = p_item->texture_repeat == RS::CANVAS_ITEM_TEXTURE_REPEAT_DEFAULT ? default_repeat : p_item->texture_repeat;
+
+ Transform2D base_transform = p_base_transform;
+
+ float world[6];
+ Transform2D draw_transform; // Used by transform command
+ _update_transform_2d_to_mat2x3(base_transform, world);
+
+ Color base_color = p_item->final_modulate;
+ bool use_linear_colors = bool(p_render_target.base_flags & FLAGS_CONVERT_ATTRIBUTES_TO_LINEAR);
+ uint32_t base_flags = p_render_target.base_flags;
+
+ bool reclip = false;
+
+ bool skipping = false;
+
+ // TODO: consider making lights a per-batch property and then baking light operations in the shader for better performance.
+ uint32_t lights[4] = { 0, 0, 0, 0 };
+
+ uint16_t light_count = 0;
+ PipelineLightMode light_mode;
+
+ {
+ Light *light = p_lights;
+
+ while (light) {
+ if (light->render_index_cache >= 0 && p_item->light_mask & light->item_mask && p_item->z_final >= light->z_min && p_item->z_final <= light->z_max && p_item->global_rect_cache.intersects_transformed(light->xform_cache, light->rect_cache)) {
+ uint32_t light_index = light->render_index_cache;
+ lights[light_count >> 2] |= light_index << ((light_count & 3) * 8);
+
+ light_count++;
+
+ if (light_count == state.max_lights_per_item - 1) {
+ break;
+ }
+ }
+ light = light->next_ptr;
+ }
+
+ base_flags |= light_count << FLAGS_LIGHT_COUNT_SHIFT;
+ }
+
+ light_mode = (light_count > 0 || using_directional_lights) ? PIPELINE_LIGHT_MODE_ENABLED : PIPELINE_LIGHT_MODE_DISABLED;
+
+ if (light_mode != current_batch->light_mode) {
+ current_batch = _new_batch(r_batch_broken);
+ current_batch->light_mode = light_mode;
+ }
+
+ // new_instance_data should be called after the current_batch is set.
+ auto new_instance_data = [&]() -> InstanceData * {
+ InstanceData *instance_data = &state.instance_data_array[r_index];
+ // Zero out most fields.
+ for (int i = 0; i < 4; i++) {
+ instance_data->modulation[i] = 0.0;
+ instance_data->ninepatch_margins[i] = 0.0;
+ instance_data->src_rect[i] = 0.0;
+ instance_data->dst_rect[i] = 0.0;
+ }
+
+ instance_data->pad[0] = 0.0;
+ instance_data->pad[1] = 0.0;
+
+ instance_data->lights[0] = lights[0];
+ instance_data->lights[1] = lights[1];
+ instance_data->lights[2] = lights[2];
+ instance_data->lights[3] = lights[3];
+
+ for (int i = 0; i < 6; i++) {
+ instance_data->world[i] = world[i];
+ }
+
+ instance_data->flags = base_flags | current_batch->tex_flags; // Reset on each command for safety, keep canvas texture binding config.
+
+ instance_data->color_texture_pixel_size[0] = current_batch->tex_texpixel_size.width;
+ instance_data->color_texture_pixel_size[1] = current_batch->tex_texpixel_size.height;
+ instance_data->specular_shininess = current_batch->tex_specular_shininess;
+
+ return instance_data;
+ };
+
+ const Item::Command *c = p_item->commands;
+ while (c) {
+ if (skipping && c->type != Item::Command::TYPE_ANIMATION_SLICE) {
+ c = c->next;
+ continue;
+ }
+
+ switch (c->type) {
+ case Item::Command::TYPE_RECT: {
+ const Item::CommandRect *rect = static_cast<const Item::CommandRect *>(c);
+
+ // 1: If commands are different, start a new batch.
+ if (current_batch->command_type != Item::Command::TYPE_RECT) {
+ current_batch = _new_batch(r_batch_broken);
+ current_batch->command_type = Item::Command::TYPE_RECT;
+ current_batch->command = c;
+ // default variant
+ current_batch->pipeline_variant = PIPELINE_VARIANT_QUAD;
+ }
+
+ if (bool(rect->flags & CANVAS_RECT_TILE)) {
+ texture_repeat = RenderingServer::CanvasItemTextureRepeat::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED;
+ }
+
+ bool has_msdf = bool(rect->flags & CANVAS_RECT_MSDF);
+ TextureState tex_state(rect->texture, texture_filter, texture_repeat, has_msdf, use_linear_colors);
+
+ if (tex_state != current_batch->tex_state) {
+ current_batch = _new_batch(r_batch_broken);
+ current_batch->set_tex_state(tex_state);
+ _prepare_batch_texture(current_batch, rect->texture);
+ }
+
+ Color modulated = rect->modulate * base_color;
+ if (use_linear_colors) {
+ modulated = modulated.srgb_to_linear();
+ }
+
+ bool has_blend = bool(rect->flags & CANVAS_RECT_LCD);
+ // Start a new batch if the blend mode has changed,
+ // or blend mode is enabled and the modulation has changed.
+ if (has_blend != current_batch->has_blend || (has_blend && modulated != current_batch->modulate)) {
+ current_batch = _new_batch(r_batch_broken);
+ current_batch->has_blend = has_blend;
+ current_batch->modulate = modulated;
+ current_batch->pipeline_variant = has_blend ? PIPELINE_VARIANT_QUAD_LCD_BLEND : PIPELINE_VARIANT_QUAD;
+ }
+
+ InstanceData *instance_data = new_instance_data();
+ Rect2 src_rect;
+ Rect2 dst_rect;
+
+ if (rect->texture.is_valid()) {
+ src_rect = (rect->flags & CANVAS_RECT_REGION) ? Rect2(rect->source.position * current_batch->tex_texpixel_size, rect->source.size * current_batch->tex_texpixel_size) : Rect2(0, 0, 1, 1);
+ dst_rect = Rect2(rect->rect.position, rect->rect.size);
+
+ if (dst_rect.size.width < 0) {
+ dst_rect.position.x += dst_rect.size.width;
+ dst_rect.size.width *= -1;
+ }
+ if (dst_rect.size.height < 0) {
+ dst_rect.position.y += dst_rect.size.height;
+ dst_rect.size.height *= -1;
+ }
+
+ if (rect->flags & CANVAS_RECT_FLIP_H) {
+ src_rect.size.x *= -1;
+ instance_data->flags |= FLAGS_FLIP_H;
+ }
+
+ if (rect->flags & CANVAS_RECT_FLIP_V) {
+ src_rect.size.y *= -1;
+ instance_data->flags |= FLAGS_FLIP_V;
+ }
+
+ if (rect->flags & CANVAS_RECT_TRANSPOSE) {
+ instance_data->flags |= FLAGS_TRANSPOSE_RECT;
+ }
+
+ if (rect->flags & CANVAS_RECT_CLIP_UV) {
+ instance_data->flags |= FLAGS_CLIP_RECT_UV;
+ }
+
+ } else {
+ dst_rect = Rect2(rect->rect.position, rect->rect.size);
+
+ if (dst_rect.size.width < 0) {
+ dst_rect.position.x += dst_rect.size.width;
+ dst_rect.size.width *= -1;
+ }
+ if (dst_rect.size.height < 0) {
+ dst_rect.position.y += dst_rect.size.height;
+ dst_rect.size.height *= -1;
+ }
+
+ src_rect = Rect2(0, 0, 1, 1);
+ }
+
+ if (has_msdf) {
+ instance_data->flags |= FLAGS_USE_MSDF;
+ instance_data->msdf[0] = rect->px_range; // Pixel range.
+ instance_data->msdf[1] = rect->outline; // Outline size.
+ instance_data->msdf[2] = 0.f; // Reserved.
+ instance_data->msdf[3] = 0.f; // Reserved.
+ } else if (rect->flags & CANVAS_RECT_LCD) {
+ instance_data->flags |= FLAGS_USE_LCD;
+ }
+
+ instance_data->modulation[0] = modulated.r;
+ instance_data->modulation[1] = modulated.g;
+ instance_data->modulation[2] = modulated.b;
+ instance_data->modulation[3] = modulated.a;
+
+ instance_data->src_rect[0] = src_rect.position.x;
+ instance_data->src_rect[1] = src_rect.position.y;
+ instance_data->src_rect[2] = src_rect.size.width;
+ instance_data->src_rect[3] = src_rect.size.height;
+
+ instance_data->dst_rect[0] = dst_rect.position.x;
+ instance_data->dst_rect[1] = dst_rect.position.y;
+ instance_data->dst_rect[2] = dst_rect.size.width;
+ instance_data->dst_rect[3] = dst_rect.size.height;
+
+ _add_to_batch(r_index, r_batch_broken, current_batch);
+ } break;
+
+ case Item::Command::TYPE_NINEPATCH: {
+ const Item::CommandNinePatch *np = static_cast<const Item::CommandNinePatch *>(c);
+
+ if (current_batch->command_type != Item::Command::TYPE_NINEPATCH) {
+ current_batch = _new_batch(r_batch_broken);
+ current_batch->command_type = Item::Command::TYPE_NINEPATCH;
+ current_batch->command = c;
+ current_batch->pipeline_variant = PipelineVariant::PIPELINE_VARIANT_NINEPATCH;
+ }
+
+ TextureState tex_state(np->texture, texture_filter, texture_repeat, false, use_linear_colors);
+ if (tex_state != current_batch->tex_state) {
+ current_batch = _new_batch(r_batch_broken);
+ current_batch->set_tex_state(tex_state);
+ _prepare_batch_texture(current_batch, np->texture);
+ }
+
+ InstanceData *instance_data = new_instance_data();
+
+ Rect2 src_rect;
+ Rect2 dst_rect(np->rect.position.x, np->rect.position.y, np->rect.size.x, np->rect.size.y);
+
+ if (np->texture.is_null()) {
+ src_rect = Rect2(0, 0, 1, 1);
+ } else {
+ if (np->source != Rect2()) {
+ src_rect = Rect2(np->source.position.x * current_batch->tex_texpixel_size.width, np->source.position.y * current_batch->tex_texpixel_size.height, np->source.size.x * current_batch->tex_texpixel_size.width, np->source.size.y * current_batch->tex_texpixel_size.height);
+ instance_data->color_texture_pixel_size[0] = 1.0 / np->source.size.width;
+ instance_data->color_texture_pixel_size[1] = 1.0 / np->source.size.height;
+ } else {
+ src_rect = Rect2(0, 0, 1, 1);
+ }
+ }
+
+ Color modulated = np->color * base_color;
+ if (use_linear_colors) {
+ modulated = modulated.srgb_to_linear();
+ }
+
+ instance_data->modulation[0] = modulated.r;
+ instance_data->modulation[1] = modulated.g;
+ instance_data->modulation[2] = modulated.b;
+ instance_data->modulation[3] = modulated.a;
+
+ instance_data->src_rect[0] = src_rect.position.x;
+ instance_data->src_rect[1] = src_rect.position.y;
+ instance_data->src_rect[2] = src_rect.size.width;
+ instance_data->src_rect[3] = src_rect.size.height;
+
+ instance_data->dst_rect[0] = dst_rect.position.x;
+ instance_data->dst_rect[1] = dst_rect.position.y;
+ instance_data->dst_rect[2] = dst_rect.size.width;
+ instance_data->dst_rect[3] = dst_rect.size.height;
+
+ instance_data->flags |= int(np->axis_x) << FLAGS_NINEPATCH_H_MODE_SHIFT;
+ instance_data->flags |= int(np->axis_y) << FLAGS_NINEPATCH_V_MODE_SHIFT;
+
+ if (np->draw_center) {
+ instance_data->flags |= FLAGS_NINEPACH_DRAW_CENTER;
+ }
+
+ instance_data->ninepatch_margins[0] = np->margin[SIDE_LEFT];
+ instance_data->ninepatch_margins[1] = np->margin[SIDE_TOP];
+ instance_data->ninepatch_margins[2] = np->margin[SIDE_RIGHT];
+ instance_data->ninepatch_margins[3] = np->margin[SIDE_BOTTOM];
+
+ _add_to_batch(r_index, r_batch_broken, current_batch);
+ } break;
+
+ case Item::Command::TYPE_POLYGON: {
+ const Item::CommandPolygon *polygon = static_cast<const Item::CommandPolygon *>(c);
+
+ // Polygon's can't be batched, so always create a new batch
+ current_batch = _new_batch(r_batch_broken);
+
+ current_batch->command_type = Item::Command::TYPE_POLYGON;
+ current_batch->command = c;
+
+ TextureState tex_state(polygon->texture, texture_filter, texture_repeat, false, use_linear_colors);
+ if (tex_state != current_batch->tex_state) {
+ current_batch = _new_batch(r_batch_broken);
+ current_batch->set_tex_state(tex_state);
+ _prepare_batch_texture(current_batch, polygon->texture);
+ }
+
+ // pipeline variant
+ {
+ static const PipelineVariant variant[RS::PRIMITIVE_MAX] = { PIPELINE_VARIANT_ATTRIBUTE_POINTS, PIPELINE_VARIANT_ATTRIBUTE_LINES, PIPELINE_VARIANT_ATTRIBUTE_LINES_STRIP, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLES, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLE_STRIP };
+ ERR_CONTINUE(polygon->primitive < 0 || polygon->primitive >= RS::PRIMITIVE_MAX);
+ current_batch->pipeline_variant = variant[polygon->primitive];
+ }
+
+ InstanceData *instance_data = new_instance_data();
+
+ Color color = base_color;
+ if (use_linear_colors) {
+ color = color.srgb_to_linear();
+ }
+
+ instance_data->modulation[0] = color.r;
+ instance_data->modulation[1] = color.g;
+ instance_data->modulation[2] = color.b;
+ instance_data->modulation[3] = color.a;
+
+ _add_to_batch(r_index, r_batch_broken, current_batch);
+ } break;
+
+ case Item::Command::TYPE_PRIMITIVE: {
+ const Item::CommandPrimitive *primitive = static_cast<const Item::CommandPrimitive *>(c);
+
+ if (primitive->point_count != current_batch->primitive_points || current_batch->command_type != Item::Command::TYPE_PRIMITIVE) {
+ current_batch = _new_batch(r_batch_broken);
+ current_batch->command_type = Item::Command::TYPE_PRIMITIVE;
+ current_batch->command = c;
+ current_batch->primitive_points = primitive->point_count;
+
+ static const PipelineVariant variant[4] = { PIPELINE_VARIANT_PRIMITIVE_POINTS, PIPELINE_VARIANT_PRIMITIVE_LINES, PIPELINE_VARIANT_PRIMITIVE_TRIANGLES, PIPELINE_VARIANT_PRIMITIVE_TRIANGLES };
+ ERR_CONTINUE(primitive->point_count == 0 || primitive->point_count > 4);
+ current_batch->pipeline_variant = variant[primitive->point_count - 1];
+
+ TextureState tex_state(primitive->texture, texture_filter, texture_repeat, false, use_linear_colors);
+ if (tex_state != current_batch->tex_state) {
+ current_batch = _new_batch(r_batch_broken);
+ current_batch->set_tex_state(tex_state);
+ _prepare_batch_texture(current_batch, primitive->texture);
+ }
+ }
+
+ InstanceData *instance_data = new_instance_data();
+
+ for (uint32_t j = 0; j < MIN(3u, primitive->point_count); j++) {
+ instance_data->points[j * 2 + 0] = primitive->points[j].x;
+ instance_data->points[j * 2 + 1] = primitive->points[j].y;
+ instance_data->uvs[j * 2 + 0] = primitive->uvs[j].x;
+ instance_data->uvs[j * 2 + 1] = primitive->uvs[j].y;
+ Color col = primitive->colors[j] * base_color;
+ if (use_linear_colors) {
+ col = col.srgb_to_linear();
+ }
+ instance_data->colors[j * 2 + 0] = (uint32_t(Math::make_half_float(col.g)) << 16) | Math::make_half_float(col.r);
+ instance_data->colors[j * 2 + 1] = (uint32_t(Math::make_half_float(col.a)) << 16) | Math::make_half_float(col.b);
+ }
+
+ _add_to_batch(r_index, r_batch_broken, current_batch);
+
+ if (primitive->point_count == 4) {
+ instance_data = new_instance_data();
+
+ for (uint32_t j = 0; j < 3; j++) {
+ int offset = j == 0 ? 0 : 1;
+ // Second triangle in the quad. Uses vertices 0, 2, 3.
+ instance_data->points[j * 2 + 0] = primitive->points[j + offset].x;
+ instance_data->points[j * 2 + 1] = primitive->points[j + offset].y;
+ instance_data->uvs[j * 2 + 0] = primitive->uvs[j + offset].x;
+ instance_data->uvs[j * 2 + 1] = primitive->uvs[j + offset].y;
+ Color col = primitive->colors[j] * base_color;
+ if (use_linear_colors) {
+ col = col.srgb_to_linear();
+ }
+ instance_data->colors[j * 2 + 0] = (uint32_t(Math::make_half_float(col.g)) << 16) | Math::make_half_float(col.r);
+ instance_data->colors[j * 2 + 1] = (uint32_t(Math::make_half_float(col.a)) << 16) | Math::make_half_float(col.b);
+ }
+
+ _add_to_batch(r_index, r_batch_broken, current_batch);
+ }
+ } break;
+
+ case Item::Command::TYPE_MESH:
+ case Item::Command::TYPE_MULTIMESH:
+ case Item::Command::TYPE_PARTICLES: {
+ // Mesh's can't be batched, so always create a new batch
+ current_batch = _new_batch(r_batch_broken);
+ current_batch->command = c;
+ current_batch->command_type = c->type;
+
+ InstanceData *instance_data = nullptr;
+
+ Color modulate(1, 1, 1, 1);
+ if (c->type == Item::Command::TYPE_MESH) {
+ const Item::CommandMesh *m = static_cast<const Item::CommandMesh *>(c);
+ TextureState tex_state(m->texture, texture_filter, texture_repeat, false, use_linear_colors);
+ current_batch->set_tex_state(tex_state);
+ _prepare_batch_texture(current_batch, m->texture);
+ instance_data = new_instance_data();
+
+ current_batch->mesh_instance_count = 1;
+ _update_transform_2d_to_mat2x3(base_transform * draw_transform * m->transform, instance_data->world);
+ modulate = m->modulate;
+ } else if (c->type == Item::Command::TYPE_MULTIMESH) {
+ RendererRD::MeshStorage *mesh_storage = RendererRD::MeshStorage::get_singleton();
+
+ const Item::CommandMultiMesh *mm = static_cast<const Item::CommandMultiMesh *>(c);
+ RID multimesh = mm->multimesh;
+
+ if (mesh_storage->multimesh_get_transform_format(multimesh) != RS::MULTIMESH_TRANSFORM_2D) {
+ break;
+ }
+
+ current_batch->mesh_instance_count = mesh_storage->multimesh_get_instances_to_draw(multimesh);
+ if (current_batch->mesh_instance_count == 0) {
+ break;
+ }
+
+ TextureState tex_state(mm->texture, texture_filter, texture_repeat, false, use_linear_colors);
+ current_batch->set_tex_state(tex_state);
+ _prepare_batch_texture(current_batch, mm->texture);
+ instance_data = new_instance_data();
+
+ instance_data->flags |= 1; // multimesh, trails disabled
+
+ if (mesh_storage->multimesh_uses_colors(mm->multimesh)) {
+ instance_data->flags |= FLAGS_INSTANCING_HAS_COLORS;
+ }
+ if (mesh_storage->multimesh_uses_custom_data(mm->multimesh)) {
+ instance_data->flags |= FLAGS_INSTANCING_HAS_CUSTOM_DATA;
+ }
+ } else if (c->type == Item::Command::TYPE_PARTICLES) {
+ RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton();
+ RendererRD::ParticlesStorage *particles_storage = RendererRD::ParticlesStorage::get_singleton();
+
+ const Item::CommandParticles *pt = static_cast<const Item::CommandParticles *>(c);
+ TextureState tex_state(pt->texture, texture_filter, texture_repeat, false, use_linear_colors);
+ current_batch->set_tex_state(tex_state);
+ _prepare_batch_texture(current_batch, pt->texture);
+
+ instance_data = new_instance_data();
+
+ uint32_t divisor = 1;
+ current_batch->mesh_instance_count = particles_storage->particles_get_amount(pt->particles, divisor);
+ instance_data->flags |= (divisor & FLAGS_INSTANCING_MASK);
+ current_batch->mesh_instance_count /= divisor;
+
+ RID particles = pt->particles;
+
+ instance_data->flags |= FLAGS_INSTANCING_HAS_COLORS;
+ instance_data->flags |= FLAGS_INSTANCING_HAS_CUSTOM_DATA;
+
+ if (particles_storage->particles_has_collision(particles) && texture_storage->render_target_is_sdf_enabled(p_render_target.render_target)) {
+ // Pass collision information.
+ Transform2D xform = p_item->final_transform;
+
+ RID sdf_texture = texture_storage->render_target_get_sdf_texture(p_render_target.render_target);
+
+ Rect2 to_screen;
+ {
+ Rect2 sdf_rect = texture_storage->render_target_get_sdf_rect(p_render_target.render_target);
+
+ to_screen.size = Vector2(1.0 / sdf_rect.size.width, 1.0 / sdf_rect.size.height);
+ to_screen.position = -sdf_rect.position * to_screen.size;
+ }
+
+ particles_storage->particles_set_canvas_sdf_collision(pt->particles, true, xform, to_screen, sdf_texture);
+ } else {
+ particles_storage->particles_set_canvas_sdf_collision(pt->particles, false, Transform2D(), Rect2(), RID());
+ }
+ r_sdf_used |= particles_storage->particles_has_collision(particles);
+ }
+
+ Color modulated = modulate * base_color;
+ if (use_linear_colors) {
+ modulated = modulated.srgb_to_linear();
+ }
+
+ instance_data->modulation[0] = modulated.r;
+ instance_data->modulation[1] = modulated.g;
+ instance_data->modulation[2] = modulated.b;
+ instance_data->modulation[3] = modulated.a;
+
+ _add_to_batch(r_index, r_batch_broken, current_batch);
+ } break;
+
+ case Item::Command::TYPE_TRANSFORM: {
+ const Item::CommandTransform *transform = static_cast<const Item::CommandTransform *>(c);
+ draw_transform = transform->xform;
+ _update_transform_2d_to_mat2x3(base_transform * transform->xform, world);
+ } break;
+
+ case Item::Command::TYPE_CLIP_IGNORE: {
+ const Item::CommandClipIgnore *ci = static_cast<const Item::CommandClipIgnore *>(c);
+ if (r_current_clip) {
+ if (ci->ignore != reclip) {
+ current_batch = _new_batch(r_batch_broken);
+ if (ci->ignore) {
+ current_batch->clip = nullptr;
+ reclip = true;
+ } else {
+ current_batch->clip = r_current_clip;
+ reclip = false;
+ }
+ }
+ }
+ } break;
+
+ case Item::Command::TYPE_ANIMATION_SLICE: {
+ const Item::CommandAnimationSlice *as = static_cast<const Item::CommandAnimationSlice *>(c);
+ double current_time = RSG::rasterizer->get_total_time();
+ double local_time = Math::fposmod(current_time - as->offset, as->animation_length);
+ skipping = !(local_time >= as->slice_begin && local_time < as->slice_end);
+
+ RenderingServerDefault::redraw_request(); // animation visible means redraw request
+ } break;
+ }
+
+ c = c->next;
+ r_batch_broken = false;
+ }
+
+ if (r_current_clip && reclip) {
+ // will make it re-enable clipping if needed afterwards
+ r_current_clip = nullptr;
+ }
+}
+
+void RendererCanvasRenderRD::_render_batch(RD::DrawListID p_draw_list, PipelineVariants *p_pipeline_variants, RenderingDevice::FramebufferFormatID p_framebuffer_format, Light *p_lights, Batch const *p_batch, RenderingMethod::RenderInfo *r_render_info) {
+ UniformSetCacheRD *uniform_set_cache = UniformSetCacheRD::get_singleton();
+ ERR_FAIL_NULL(uniform_set_cache);
+
+ ERR_FAIL_NULL(p_batch->command);
+
+ _bind_canvas_texture(p_draw_list, p_batch->tex_uniform_set);
+
+ switch (p_batch->command_type) {
+ case Item::Command::TYPE_RECT:
+ case Item::Command::TYPE_NINEPATCH: {
+ RID pipeline = p_pipeline_variants->variants[p_batch->light_mode][p_batch->pipeline_variant].get_render_pipeline(RD::INVALID_ID, p_framebuffer_format);
+ RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
+ if (p_batch->has_blend) {
+ RD::get_singleton()->draw_list_set_blend_constants(p_draw_list, p_batch->modulate);
+ }
+
+ PushConstant push_constant;
+ push_constant.base_instance_index = p_batch->start;
+ RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
+
+ RD::Uniform u_instance_data(RD::UNIFORM_TYPE_STORAGE_BUFFER, 0, state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[p_batch->instance_buffer_index]);
+ RD::get_singleton()->draw_list_bind_uniform_set(
+ p_draw_list,
+ uniform_set_cache->get_cache(shader.default_version_rd_shader, INSTANCE_DATA_UNIFORM_SET, u_instance_data),
+ INSTANCE_DATA_UNIFORM_SET);
+
+ RD::get_singleton()->draw_list_bind_index_array(p_draw_list, shader.quad_index_array);
+ RD::get_singleton()->draw_list_draw(p_draw_list, true, p_batch->instance_count);
+
+ if (r_render_info) {
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_OBJECTS_IN_FRAME] += p_batch->instance_count;
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_PRIMITIVES_IN_FRAME] += 2 * p_batch->instance_count;
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_DRAW_CALLS_IN_FRAME]++;
+ }
+ } break;
+
+ case Item::Command::TYPE_POLYGON: {
+ const Item::CommandPolygon *polygon = static_cast<const Item::CommandPolygon *>(p_batch->command);
+
+ PolygonBuffers *pb = polygon_buffers.polygons.getptr(polygon->polygon.polygon_id);
+ ERR_FAIL_NULL(pb);
+
+ RID pipeline = p_pipeline_variants->variants[p_batch->light_mode][p_batch->pipeline_variant].get_render_pipeline(pb->vertex_format_id, p_framebuffer_format);
+ RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
+
+ PushConstant push_constant;
+ push_constant.base_instance_index = p_batch->start;
+ RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
+
+ RD::Uniform u_instance_data(RD::UNIFORM_TYPE_STORAGE_BUFFER, 0, state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[p_batch->instance_buffer_index]);
+ RD::get_singleton()->draw_list_bind_uniform_set(
+ p_draw_list,
+ uniform_set_cache->get_cache(shader.default_version_rd_shader, INSTANCE_DATA_UNIFORM_SET, u_instance_data),
+ INSTANCE_DATA_UNIFORM_SET);
+
+ RD::get_singleton()->draw_list_bind_vertex_array(p_draw_list, pb->vertex_array);
+ if (pb->indices.is_valid()) {
+ RD::get_singleton()->draw_list_bind_index_array(p_draw_list, pb->indices);
+ }
+
+ RD::get_singleton()->draw_list_draw(p_draw_list, pb->indices.is_valid());
+ if (r_render_info) {
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_OBJECTS_IN_FRAME]++;
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_PRIMITIVES_IN_FRAME] += _indices_to_primitives(polygon->primitive, pb->primitive_count);
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_DRAW_CALLS_IN_FRAME]++;
+ }
+ } break;
+
+ case Item::Command::TYPE_PRIMITIVE: {
+ const Item::CommandPrimitive *primitive = static_cast<const Item::CommandPrimitive *>(p_batch->command);
+
+ RID pipeline = p_pipeline_variants->variants[p_batch->light_mode][p_batch->pipeline_variant].get_render_pipeline(RD::INVALID_ID, p_framebuffer_format);
+ RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
+
+ PushConstant push_constant;
+ push_constant.base_instance_index = p_batch->start;
+ RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
+
+ RD::Uniform u_instance_data(RD::UNIFORM_TYPE_STORAGE_BUFFER, 0, state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[p_batch->instance_buffer_index]);
+ RD::get_singleton()->draw_list_bind_uniform_set(
+ p_draw_list,
+ uniform_set_cache->get_cache(shader.default_version_rd_shader, INSTANCE_DATA_UNIFORM_SET, u_instance_data),
+ INSTANCE_DATA_UNIFORM_SET);
+
+ RD::get_singleton()->draw_list_bind_index_array(p_draw_list, primitive_arrays.index_array[MIN(3u, primitive->point_count) - 1]);
+ uint32_t instance_count = p_batch->instance_count;
+ RD::get_singleton()->draw_list_draw(p_draw_list, true, instance_count);
+
+ if (r_render_info) {
+ const RenderingServer::PrimitiveType rs_primitive[5] = { RS::PRIMITIVE_POINTS, RS::PRIMITIVE_POINTS, RS::PRIMITIVE_LINES, RS::PRIMITIVE_TRIANGLES, RS::PRIMITIVE_TRIANGLES };
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_OBJECTS_IN_FRAME] += instance_count;
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_PRIMITIVES_IN_FRAME] += _indices_to_primitives(rs_primitive[p_batch->primitive_points], p_batch->primitive_points) * instance_count;
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_DRAW_CALLS_IN_FRAME]++;
+ }
+ } break;
+
+ case Item::Command::TYPE_MESH:
+ case Item::Command::TYPE_MULTIMESH:
+ case Item::Command::TYPE_PARTICLES: {
+ RendererRD::MeshStorage *mesh_storage = RendererRD::MeshStorage::get_singleton();
+ RendererRD::ParticlesStorage *particles_storage = RendererRD::ParticlesStorage::get_singleton();
+
+ RID mesh;
+ RID mesh_instance;
+
+ if (p_batch->command_type == Item::Command::TYPE_MESH) {
+ const Item::CommandMesh *m = static_cast<const Item::CommandMesh *>(p_batch->command);
+ mesh = m->mesh;
+ mesh_instance = m->mesh_instance;
+ } else if (p_batch->command_type == Item::Command::TYPE_MULTIMESH) {
+ const Item::CommandMultiMesh *mm = static_cast<const Item::CommandMultiMesh *>(p_batch->command);
+ RID multimesh = mm->multimesh;
+ mesh = mesh_storage->multimesh_get_mesh(multimesh);
+
+ RID uniform_set = mesh_storage->multimesh_get_2d_uniform_set(multimesh, shader.default_version_rd_shader, TRANSFORMS_UNIFORM_SET);
+ RD::get_singleton()->draw_list_bind_uniform_set(p_draw_list, uniform_set, TRANSFORMS_UNIFORM_SET);
+ } else if (p_batch->command_type == Item::Command::TYPE_PARTICLES) {
+ const Item::CommandParticles *pt = static_cast<const Item::CommandParticles *>(p_batch->command);
+ RID particles = pt->particles;
+ mesh = particles_storage->particles_get_draw_pass_mesh(particles, 0);
+
+ ERR_BREAK(particles_storage->particles_get_mode(particles) != RS::PARTICLES_MODE_2D);
+ particles_storage->particles_request_process(particles);
+
+ if (particles_storage->particles_is_inactive(particles)) {
+ break;
+ }
+
+ RenderingServerDefault::redraw_request(); // Active particles means redraw request.
+
+ int dpc = particles_storage->particles_get_draw_passes(particles);
+ if (dpc == 0) {
+ break; // Nothing to draw.
+ }
+
+ RID uniform_set = particles_storage->particles_get_instance_buffer_uniform_set(pt->particles, shader.default_version_rd_shader, TRANSFORMS_UNIFORM_SET);
+ RD::get_singleton()->draw_list_bind_uniform_set(p_draw_list, uniform_set, TRANSFORMS_UNIFORM_SET);
+ }
+
+ if (mesh.is_null()) {
+ break;
+ }
+
+ RD::Uniform u_instance_data(RD::UNIFORM_TYPE_STORAGE_BUFFER, 0, state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[p_batch->instance_buffer_index]);
+ RD::get_singleton()->draw_list_bind_uniform_set(
+ p_draw_list,
+ uniform_set_cache->get_cache(shader.default_version_rd_shader, INSTANCE_DATA_UNIFORM_SET, u_instance_data),
+ INSTANCE_DATA_UNIFORM_SET);
+
+ uint32_t surf_count = mesh_storage->mesh_get_surface_count(mesh);
+ static const PipelineVariant variant[RS::PRIMITIVE_MAX] = { PIPELINE_VARIANT_ATTRIBUTE_POINTS, PIPELINE_VARIANT_ATTRIBUTE_LINES, PIPELINE_VARIANT_ATTRIBUTE_LINES_STRIP, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLES, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLE_STRIP };
+
+ for (uint32_t j = 0; j < surf_count; j++) {
+ void *surface = mesh_storage->mesh_get_surface(mesh, j);
+
+ RS::PrimitiveType primitive = mesh_storage->mesh_surface_get_primitive(surface);
+ ERR_CONTINUE(primitive < 0 || primitive >= RS::PRIMITIVE_MAX);
+
+ uint64_t input_mask = p_pipeline_variants->variants[p_batch->light_mode][variant[primitive]].get_vertex_input_mask();
+
+ RID vertex_array;
+ RD::VertexFormatID vertex_format = RD::INVALID_FORMAT_ID;
+
+ if (mesh_instance.is_valid()) {
+ mesh_storage->mesh_instance_surface_get_vertex_arrays_and_format(mesh_instance, j, input_mask, false, vertex_array, vertex_format);
+ } else {
+ mesh_storage->mesh_surface_get_vertex_arrays_and_format(surface, input_mask, false, vertex_array, vertex_format);
+ }
+
+ RID pipeline = p_pipeline_variants->variants[p_batch->light_mode][variant[primitive]].get_render_pipeline(vertex_format, p_framebuffer_format);
+ RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
+
+ PushConstant push_constant;
+ push_constant.base_instance_index = p_batch->start;
+ RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
+
+ RID index_array = mesh_storage->mesh_surface_get_index_array(surface, 0);
+
+ if (index_array.is_valid()) {
+ RD::get_singleton()->draw_list_bind_index_array(p_draw_list, index_array);
+ }
+
+ RD::get_singleton()->draw_list_bind_vertex_array(p_draw_list, vertex_array);
+ RD::get_singleton()->draw_list_draw(p_draw_list, index_array.is_valid(), p_batch->mesh_instance_count);
+
+ if (r_render_info) {
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_OBJECTS_IN_FRAME]++;
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_PRIMITIVES_IN_FRAME] += _indices_to_primitives(primitive, mesh_storage->mesh_surface_get_vertices_drawn_count(surface)) * p_batch->mesh_instance_count;
+ r_render_info->info[RS::VIEWPORT_RENDER_INFO_TYPE_CANVAS][RS::VIEWPORT_RENDER_INFO_DRAW_CALLS_IN_FRAME]++;
+ }
+ }
+ } break;
+ case Item::Command::TYPE_TRANSFORM:
+ case Item::Command::TYPE_CLIP_IGNORE:
+ case Item::Command::TYPE_ANIMATION_SLICE: {
+ // Can ignore these as they only impact batch creation.
+ } break;
+ }
+}
+
+RendererCanvasRenderRD::Batch *RendererCanvasRenderRD::_new_batch(bool &r_batch_broken) {
+ if (state.canvas_instance_batches.size() == 0) {
+ state.canvas_instance_batches.push_back(Batch());
+ return state.canvas_instance_batches.ptr();
+ }
+
+ if (r_batch_broken || state.canvas_instance_batches[state.current_batch_index].instance_count == 0) {
+ return &state.canvas_instance_batches[state.current_batch_index];
+ }
+
+ r_batch_broken = true;
+
+ // Copy the properties of the current batch, we will manually update the things that changed.
+ Batch new_batch = state.canvas_instance_batches[state.current_batch_index];
+ new_batch.instance_count = 0;
+ new_batch.start = state.canvas_instance_batches[state.current_batch_index].start + state.canvas_instance_batches[state.current_batch_index].instance_count;
+ new_batch.instance_buffer_index = state.current_instance_buffer_index;
+ state.current_batch_index++;
+ state.canvas_instance_batches.push_back(new_batch);
+ return &state.canvas_instance_batches[state.current_batch_index];
+}
+
+void RendererCanvasRenderRD::_add_to_batch(uint32_t &r_index, bool &r_batch_broken, Batch *&r_current_batch) {
+ r_current_batch->instance_count++;
+ r_index++;
+ if (r_index + state.last_instance_index >= state.max_instances_per_buffer) {
+ // Copy over all data needed for rendering right away
+ // then go back to recording item commands.
+ RD::get_singleton()->buffer_update(
+ state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[state.current_instance_buffer_index],
+ state.last_instance_index * sizeof(InstanceData),
+ r_index * sizeof(InstanceData),
+ state.instance_data_array);
+ _allocate_instance_buffer();
+ r_index = 0;
+ state.last_instance_index = 0;
+ r_batch_broken = false; // Force a new batch to be created
+ r_current_batch = _new_batch(r_batch_broken);
+ r_current_batch->start = 0;
+ }
+}
+
+void RendererCanvasRenderRD::_allocate_instance_buffer() {
+ state.current_instance_buffer_index++;
+
+ if (state.current_instance_buffer_index < state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers.size()) {
+ // We already allocated another buffer in a previous frame, so we can just use it.
+ return;
+ }
+
+ // Allocate a new buffer.
+ RID buf = RD::get_singleton()->storage_buffer_create(state.max_instance_buffer_size);
+ state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers.push_back(buf);
+}
+
+void RendererCanvasRenderRD::_prepare_batch_texture(Batch *p_current_batch, RID p_texture) const {
+ if (p_texture.is_null()) {
+ p_texture = default_canvas_texture;
+ }
+
+ Color specular_shininess;
+ bool use_normal;
+ bool use_specular;
+ Size2i size;
+ bool success = RendererRD::TextureStorage::get_singleton()->canvas_texture_get_uniform_set(
+ p_texture,
+ p_current_batch->tex_state.texture_filter(),
+ p_current_batch->tex_state.texture_repeat(),
+ shader.default_version_rd_shader,
+ CANVAS_TEXTURE_UNIFORM_SET,
+ p_current_batch->tex_state.linear_colors(),
+ p_current_batch->tex_uniform_set,
+ size,
+ specular_shininess,
+ use_normal,
+ use_specular,
+ p_current_batch->tex_state.texture_is_data());
+ // something odd happened
+ if (!success) {
+ _prepare_batch_texture(p_current_batch, default_canvas_texture);
+ return;
+ }
+
+ // cache values to be copied to instance data
+ if (specular_shininess.a < 0.999) {
+ p_current_batch->tex_flags |= FLAGS_DEFAULT_SPECULAR_MAP_USED;
+ }
+
+ if (use_normal) {
+ p_current_batch->tex_flags |= FLAGS_DEFAULT_NORMAL_MAP_USED;
+ }
+
+ uint8_t a = uint8_t(CLAMP(specular_shininess.a * 255.0, 0.0, 255.0));
+ uint8_t b = uint8_t(CLAMP(specular_shininess.b * 255.0, 0.0, 255.0));
+ uint8_t g = uint8_t(CLAMP(specular_shininess.g * 255.0, 0.0, 255.0));
+ uint8_t r = uint8_t(CLAMP(specular_shininess.r * 255.0, 0.0, 255.0));
+ p_current_batch->tex_specular_shininess = uint32_t(a) << 24 | uint32_t(b) << 16 | uint32_t(g) << 8 | uint32_t(r);
+
+ p_current_batch->tex_texpixel_size = Vector2(1.0 / float(size.width), 1.0 / float(size.height));
+}
+
+void RendererCanvasRenderRD::_bind_canvas_texture(RD::DrawListID p_draw_list, RID p_uniform_set) {
+ if (state.current_tex_uniform_set == p_uniform_set) {
+ return;
+ }
+
+ state.current_tex_uniform_set = p_uniform_set;
+
+ RD::get_singleton()->draw_list_bind_uniform_set(p_draw_list, p_uniform_set, CANVAS_TEXTURE_UNIFORM_SET);
+}
+
RendererCanvasRenderRD::~RendererCanvasRenderRD() {
RendererRD::MaterialStorage *material_storage = RendererRD::MaterialStorage::get_singleton();
//canvas state
@@ -2936,6 +3141,13 @@ RendererCanvasRenderRD::~RendererCanvasRenderRD() {
}
RD::get_singleton()->free(state.shadow_texture);
+ memdelete_arr(state.instance_data_array);
+ for (uint32_t i = 0; i < state.canvas_instance_data_buffers.size(); i++) {
+ for (uint32_t j = 0; j < state.canvas_instance_data_buffers[i].instance_buffers.size(); j++) {
+ RD::get_singleton()->free(state.canvas_instance_data_buffers[i].instance_buffers[j]);
+ }
+ }
+
RendererRD::TextureStorage::get_singleton()->canvas_texture_free(default_canvas_texture);
//pipelines don't need freeing, they are all gone after shaders are gone
}
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
index 9deb4814c7..87de07464e 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
@@ -46,6 +46,7 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
MATERIAL_UNIFORM_SET = 1,
TRANSFORMS_UNIFORM_SET = 2,
CANVAS_TEXTURE_UNIFORM_SET = 3,
+ INSTANCE_DATA_UNIFORM_SET = 4,
};
const int SAMPLERS_BINDING_FIRST_INDEX = 10;
@@ -335,6 +336,146 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
//state that does not vary across rendering all items
+ struct InstanceData {
+ float world[6];
+ uint32_t flags;
+ uint32_t specular_shininess;
+ union {
+ //rect
+ struct {
+ float modulation[4];
+ union {
+ float msdf[4];
+ float ninepatch_margins[4];
+ };
+ float dst_rect[4];
+ float src_rect[4];
+ float pad[2];
+ };
+ //primitive
+ struct {
+ float points[6]; // vec2 points[3]
+ float uvs[6]; // vec2 points[3]
+ uint32_t colors[6]; // colors encoded as half
+ };
+ };
+ float color_texture_pixel_size[2];
+ uint32_t lights[4];
+ };
+
+ struct PushConstant {
+ uint32_t base_instance_index;
+ uint32_t pad1;
+ uint32_t pad2;
+ uint32_t pad3;
+ };
+
+ // TextureState is used to determine when a new batch is required due to a change of texture state.
+ struct TextureState {
+ static const uint32_t FILTER_SHIFT = 0;
+ static const uint32_t FILTER_BITS = 3;
+ static const uint32_t FILTER_MASK = (1 << FILTER_BITS) - 1;
+ static const uint32_t REPEAT_SHIFT = FILTER_BITS;
+ static const uint32_t REPEAT_BITS = 2;
+ static const uint32_t REPEAT_MASK = (1 << REPEAT_BITS) - 1;
+ static const uint32_t TEXTURE_IS_DATA_SHIFT = REPEAT_SHIFT + REPEAT_BITS;
+ static const uint32_t TEXTURE_IS_DATA_BITS = 1;
+ static const uint32_t TEXTURE_IS_DATA_MASK = (1 << TEXTURE_IS_DATA_BITS) - 1;
+ static const uint32_t LINEAR_COLORS_SHIFT = TEXTURE_IS_DATA_SHIFT + TEXTURE_IS_DATA_BITS;
+ static const uint32_t LINEAR_COLORS_BITS = 1;
+ static const uint32_t LINEAR_COLORS_MASK = (1 << LINEAR_COLORS_BITS) - 1;
+
+ RID texture;
+ uint32_t other = 0;
+
+ TextureState() {}
+
+ TextureState(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, bool p_texture_is_data, bool p_use_linear_colors) {
+ texture = p_texture;
+ other = (((uint32_t)p_base_filter & FILTER_MASK) << FILTER_SHIFT) |
+ (((uint32_t)p_base_repeat & REPEAT_MASK) << REPEAT_SHIFT) |
+ (((uint32_t)p_texture_is_data & TEXTURE_IS_DATA_MASK) << TEXTURE_IS_DATA_SHIFT) |
+ (((uint32_t)p_use_linear_colors & LINEAR_COLORS_MASK) << LINEAR_COLORS_SHIFT);
+ }
+
+ _FORCE_INLINE_ RS::CanvasItemTextureFilter texture_filter() const {
+ return (RS::CanvasItemTextureFilter)((other >> FILTER_SHIFT) & FILTER_MASK);
+ }
+
+ _FORCE_INLINE_ RS::CanvasItemTextureRepeat texture_repeat() const {
+ return (RS::CanvasItemTextureRepeat)((other >> REPEAT_SHIFT) & REPEAT_MASK);
+ }
+
+ _FORCE_INLINE_ bool linear_colors() const {
+ return (other >> LINEAR_COLORS_SHIFT) & LINEAR_COLORS_MASK;
+ }
+
+ _FORCE_INLINE_ bool texture_is_data() const {
+ return (other >> TEXTURE_IS_DATA_SHIFT) & TEXTURE_IS_DATA_MASK;
+ }
+
+ bool operator==(const TextureState &p_val) const {
+ return (texture == p_val.texture) && (other == p_val.other);
+ }
+
+ bool operator!=(const TextureState &p_val) const {
+ return (texture != p_val.texture) || (other != p_val.other);
+ }
+ };
+
+ struct Batch {
+ // Position in the UBO measured in bytes
+ uint32_t start = 0;
+ uint32_t instance_count = 0;
+ uint32_t instance_buffer_index = 0;
+
+ TextureState tex_state;
+ RID tex_uniform_set;
+
+ // The following tex_ prefixed fields are used to cache the texture data for the current batch.
+ // These values are applied to new InstanceData for the batch
+
+ // The cached specular shininess derived from the current texture.
+ uint32_t tex_specular_shininess = 0;
+ // The cached texture flags, such as FLAGS_DEFAULT_SPECULAR_MAP_USED and FLAGS_DEFAULT_NORMAL_MAP_USED
+ uint32_t tex_flags = 0;
+ // The cached texture pixel size.
+ Vector2 tex_texpixel_size;
+
+ Color modulate = Color(1.0, 1.0, 1.0, 1.0);
+
+ Item *clip = nullptr;
+
+ RID material;
+ CanvasMaterialData *material_data = nullptr;
+ PipelineLightMode light_mode = PipelineLightMode::PIPELINE_LIGHT_MODE_DISABLED;
+ PipelineVariant pipeline_variant = PipelineVariant::PIPELINE_VARIANT_QUAD;
+
+ const Item::Command *command = nullptr;
+ Item::Command::Type command_type = Item::Command::TYPE_ANIMATION_SLICE; // Can default to any type that doesn't form a batch.
+
+ // batch-specific data
+ union {
+ // TYPE_PRIMITIVE
+ uint32_t primitive_points = 0;
+ // TYPE_PARTICLES
+ uint32_t mesh_instance_count;
+ };
+ bool has_blend = false;
+
+ void set_tex_state(TextureState &p_tex_state) {
+ tex_state = p_tex_state;
+ tex_uniform_set = RID();
+ tex_texpixel_size = Size2();
+ tex_specular_shininess = 0;
+ tex_flags = 0;
+ }
+ };
+
+ struct DataBuffer {
+ LocalVector<RID> instance_buffers;
+ };
+
struct State {
//state buffer
struct Buffer {
@@ -357,6 +498,19 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
uint32_t pad2;
};
+ LocalVector<DataBuffer> canvas_instance_data_buffers;
+ LocalVector<Batch> canvas_instance_batches;
+ uint32_t current_data_buffer_index = 0;
+ uint32_t current_instance_buffer_index = 0;
+ uint32_t current_batch_index = 0;
+ uint32_t last_instance_index = 0;
+ InstanceData *instance_data_array = nullptr;
+
+ uint32_t max_instances_per_buffer = 16384;
+ uint32_t max_instance_buffer_size = 16384 * sizeof(InstanceData);
+
+ RID current_tex_uniform_set;
+
LightUniform *light_uniforms = nullptr;
RID lights_uniform_buffer;
@@ -376,33 +530,6 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
} state;
- struct PushConstant {
- float world[6];
- uint32_t flags;
- uint32_t specular_shininess;
- union {
- //rect
- struct {
- float modulation[4];
- union {
- float msdf[4];
- float ninepatch_margins[4];
- };
- float dst_rect[4];
- float src_rect[4];
- float pad[2];
- };
- //primitive
- struct {
- float points[6]; // vec2 points[3]
- float uvs[6]; // vec2 points[3]
- uint32_t colors[6]; // colors encoded as half
- };
- };
- float color_texture_pixel_size[2];
- uint32_t lights[4];
- };
-
Item *items[MAX_RENDER_ITEMS];
bool using_directional_lights = false;
@@ -422,9 +549,23 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
Color debug_redraw_color;
double debug_redraw_time = 1.0;
- inline void _bind_canvas_texture(RD::DrawListID p_draw_list, RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID &r_last_texture, PushConstant &push_constant, Size2 &r_texpixel_size, bool p_texture_is_data = false); //recursive, so regular inline used instead.
- void _render_item(RenderingDevice::DrawListID p_draw_list, RID p_render_target, const Item *p_item, RenderingDevice::FramebufferFormatID p_framebuffer_format, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, Light *p_lights, PipelineVariants *p_pipeline_variants, bool &r_sdf_used, const Point2 &p_repeat_offset, RenderingMethod::RenderInfo *r_render_info = nullptr);
- void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer = false, RenderingMethod::RenderInfo *r_render_info = nullptr);
+ // A structure to store cached render target information
+ struct RenderTarget {
+ // Current render target for the canvas.
+ RID render_target;
+ // The base flags for each InstanceData, derived from the render target.
+ // Either FLAGS_CONVERT_ATTRIBUTES_TO_LINEAR or 0
+ uint32_t base_flags = 0;
+ };
+
+ void _render_batch_items(RenderTarget p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer = false, RenderingMethod::RenderInfo *r_render_info = nullptr);
+ void _record_item_commands(const Item *p_item, RenderTarget p_render_target, const Transform2D &p_base_transform, Item *&r_current_clip, Light *p_lights, uint32_t &r_index, bool &r_batch_broken, bool &r_sdf_used);
+ void _render_batch(RD::DrawListID p_draw_list, PipelineVariants *p_pipeline_variants, RenderingDevice::FramebufferFormatID p_framebuffer_format, Light *p_lights, Batch const *p_batch, RenderingMethod::RenderInfo *r_render_info = nullptr);
+ void _prepare_batch_texture(Batch *p_current_batch, RID p_texture) const;
+ void _bind_canvas_texture(RD::DrawListID p_draw_list, RID p_uniform_set);
+ [[nodiscard]] Batch *_new_batch(bool &r_batch_broken);
+ void _add_to_batch(uint32_t &r_index, bool &r_batch_broken, Batch *&r_current_batch);
+ void _allocate_instance_buffer();
_FORCE_INLINE_ void _update_transform_2d_to_mat2x4(const Transform2D &p_transform, float *p_mat2x4);
_FORCE_INLINE_ void _update_transform_2d_to_mat2x3(const Transform2D &p_transform, float *p_mat2x3);
diff --git a/servers/rendering/renderer_rd/shaders/canvas.glsl b/servers/rendering/renderer_rd/shaders/canvas.glsl
index 4426d9eb66..2154d56faf 100644
--- a/servers/rendering/renderer_rd/shaders/canvas.glsl
+++ b/servers/rendering/renderer_rd/shaders/canvas.glsl
@@ -24,6 +24,12 @@ layout(location = 11) in vec4 weight_attrib;
#include "canvas_uniforms_inc.glsl"
+#ifndef USE_ATTRIBUTES
+
+layout(location = 4) out flat uint instance_index_interp;
+
+#endif // USE_ATTRIBUTES
+
layout(location = 0) out vec2 uv_interp;
layout(location = 1) out vec4 color_interp;
layout(location = 2) out vec2 vertex_interp;
@@ -59,6 +65,14 @@ void main() {
vec4 custom1 = vec4(0.0);
#endif
+#ifdef USE_ATTRIBUTES
+ uint instance_index = params.base_instance_index;
+#else
+ uint instance_index = gl_InstanceIndex + params.base_instance_index;
+ instance_index_interp = instance_index;
+#endif // USE_ATTRIBUTES
+ const InstanceData draw_data = instances.data[instance_index];
+
#ifdef USE_PRIMITIVE
//weird bug,
@@ -117,13 +131,10 @@ void main() {
mat4 model_matrix = mat4(vec4(draw_data.world_x, 0.0, 0.0), vec4(draw_data.world_y, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(draw_data.world_ofs, 0.0, 1.0));
-#define FLAGS_INSTANCING_MASK 0x7F
-#define FLAGS_INSTANCING_HAS_COLORS (1 << 7)
-#define FLAGS_INSTANCING_HAS_CUSTOM_DATA (1 << 8)
+#ifdef USE_ATTRIBUTES
uint instancing = draw_data.flags & FLAGS_INSTANCING_MASK;
-#ifdef USE_ATTRIBUTES
if (instancing > 1) {
// trails
@@ -160,38 +171,27 @@ void main() {
vertex = new_vertex;
color *= pcolor;
- } else
-#endif // USE_ATTRIBUTES
- {
- if (instancing == 1) {
- uint stride = 2;
- {
- if (bool(draw_data.flags & FLAGS_INSTANCING_HAS_COLORS)) {
- stride += 1;
- }
- if (bool(draw_data.flags & FLAGS_INSTANCING_HAS_CUSTOM_DATA)) {
- stride += 1;
- }
- }
-
- uint offset = stride * gl_InstanceIndex;
+ } else if (instancing == 1) {
+ uint stride = 2 + bitfieldExtract(draw_data.flags, FLAGS_INSTANCING_HAS_COLORS_SHIFT, 1) + bitfieldExtract(draw_data.flags, FLAGS_INSTANCING_HAS_CUSTOM_DATA_SHIFT, 1);
- mat4 matrix = mat4(transforms.data[offset + 0], transforms.data[offset + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));
- offset += 2;
+ uint offset = stride * gl_InstanceIndex;
- if (bool(draw_data.flags & FLAGS_INSTANCING_HAS_COLORS)) {
- color *= transforms.data[offset];
- offset += 1;
- }
+ mat4 matrix = mat4(transforms.data[offset + 0], transforms.data[offset + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));
+ offset += 2;
- if (bool(draw_data.flags & FLAGS_INSTANCING_HAS_CUSTOM_DATA)) {
- instance_custom = transforms.data[offset];
- }
+ if (bool(draw_data.flags & FLAGS_INSTANCING_HAS_COLORS)) {
+ color *= transforms.data[offset];
+ offset += 1;
+ }
- matrix = transpose(matrix);
- model_matrix = model_matrix * matrix;
+ if (bool(draw_data.flags & FLAGS_INSTANCING_HAS_CUSTOM_DATA)) {
+ instance_custom = transforms.data[offset];
}
+
+ matrix = transpose(matrix);
+ model_matrix = model_matrix * matrix;
}
+#endif // USE_ATTRIBUTES
#ifdef USE_POINT_SIZE
float point_size = 1.0;
@@ -241,6 +241,10 @@ void main() {
#include "canvas_uniforms_inc.glsl"
+#ifndef USE_ATTRIBUTES
+layout(location = 4) in flat uint instance_index;
+#endif // USE_ATTRIBUTES
+
layout(location = 0) in vec2 uv_interp;
layout(location = 1) in vec4 color_interp;
layout(location = 2) in vec2 vertex_interp;
@@ -320,6 +324,12 @@ vec4 light_compute(
#ifdef USE_NINEPATCH
float map_ninepatch_axis(float pixel, float draw_size, float tex_pixel_size, float margin_begin, float margin_end, int np_repeat, inout int draw_center) {
+#ifdef USE_ATTRIBUTES
+ const InstanceData draw_data = instances.data[params.base_instance_index];
+#else
+ const InstanceData draw_data = instances.data[instance_index];
+#endif // USE_ATTRIBUTES
+
float tex_size = 1.0 / tex_pixel_size;
if (pixel < margin_begin) {
@@ -327,9 +337,7 @@ float map_ninepatch_axis(float pixel, float draw_size, float tex_pixel_size, flo
} else if (pixel >= draw_size - margin_end) {
return (tex_size - (draw_size - pixel)) * tex_pixel_size;
} else {
- if (!bool(draw_data.flags & FLAGS_NINEPACH_DRAW_CENTER)) {
- draw_center--;
- }
+ draw_center -= 1 - int(bitfieldExtract(draw_data.flags, FLAGS_NINEPACH_DRAW_CENTER_SHIFT, 1));
// np_repeat is passed as uniform using NinePatchRect::AxisStretchMode enum.
if (np_repeat == 0) { // Stretch.
@@ -462,14 +470,20 @@ void main() {
vec2 uv = uv_interp;
vec2 vertex = vertex_interp;
+#ifdef USE_ATTRIBUTES
+ const InstanceData draw_data = instances.data[params.base_instance_index];
+#else
+ const InstanceData draw_data = instances.data[instance_index];
+#endif // USE_ATTRIBUTES
+
#if !defined(USE_ATTRIBUTES) && !defined(USE_PRIMITIVE)
#ifdef USE_NINEPATCH
int draw_center = 2;
uv = vec2(
- map_ninepatch_axis(pixel_size_interp.x, abs(draw_data.dst_rect.z), draw_data.color_texture_pixel_size.x, draw_data.ninepatch_margins.x, draw_data.ninepatch_margins.z, int(draw_data.flags >> FLAGS_NINEPATCH_H_MODE_SHIFT) & 0x3, draw_center),
- map_ninepatch_axis(pixel_size_interp.y, abs(draw_data.dst_rect.w), draw_data.color_texture_pixel_size.y, draw_data.ninepatch_margins.y, draw_data.ninepatch_margins.w, int(draw_data.flags >> FLAGS_NINEPATCH_V_MODE_SHIFT) & 0x3, draw_center));
+ map_ninepatch_axis(pixel_size_interp.x, abs(draw_data.dst_rect.z), draw_data.color_texture_pixel_size.x, draw_data.ninepatch_margins.x, draw_data.ninepatch_margins.z, int(bitfieldExtract(draw_data.flags, FLAGS_NINEPATCH_H_MODE_SHIFT, 2)), draw_center),
+ map_ninepatch_axis(pixel_size_interp.y, abs(draw_data.dst_rect.w), draw_data.color_texture_pixel_size.y, draw_data.ninepatch_margins.y, draw_data.ninepatch_margins.w, int(bitfieldExtract(draw_data.flags, FLAGS_NINEPATCH_V_MODE_SHIFT, 2)), draw_center));
if (draw_center == 0) {
color.a = 0.0;
@@ -519,8 +533,8 @@ void main() {
color *= texture(sampler2D(color_texture, texture_sampler), uv);
}
- uint light_count = (draw_data.flags >> FLAGS_LIGHT_COUNT_SHIFT) & 0xF; //max 16 lights
- bool using_light = light_count > 0 || canvas_data.directional_light_count > 0;
+ uint light_count = bitfieldExtract(draw_data.flags, FLAGS_LIGHT_COUNT_SHIFT, 4); //max 16 lights
+ bool using_light = (light_count + canvas_data.directional_light_count) > 0;
vec3 normal;
@@ -652,9 +666,7 @@ void main() {
if (i >= light_count) {
break;
}
- uint light_base = draw_data.lights[i >> 2];
- light_base >>= (i & 3) * 8;
- light_base &= 0xFF;
+ uint light_base = bitfieldExtract(draw_data.lights[i >> 2], (int(i) & 0x3) * 8, 8);
vec2 tex_uv = (vec4(vertex, 0.0, 1.0) * mat4(light_array.data[light_base].texture_matrix[0], light_array.data[light_base].texture_matrix[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))).xy; //multiply inverse given its transposed. Optimizer removes useless operations.
vec2 tex_uv_atlas = tex_uv * light_array.data[light_base].atlas_rect.zw + light_array.data[light_base].atlas_rect.xy;
diff --git a/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl b/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl
index 8649f4710b..7cf5b4576e 100644
--- a/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl
+++ b/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl
@@ -7,13 +7,16 @@
//1 means enabled, 2+ means trails in use
#define FLAGS_INSTANCING_MASK 0x7F
-#define FLAGS_INSTANCING_HAS_COLORS (1 << 7)
-#define FLAGS_INSTANCING_HAS_CUSTOM_DATA (1 << 8)
+#define FLAGS_INSTANCING_HAS_COLORS_SHIFT 7
+#define FLAGS_INSTANCING_HAS_COLORS (1 << FLAGS_INSTANCING_HAS_COLORS_SHIFT)
+#define FLAGS_INSTANCING_HAS_CUSTOM_DATA_SHIFT 8
+#define FLAGS_INSTANCING_HAS_CUSTOM_DATA (1 << FLAGS_INSTANCING_HAS_CUSTOM_DATA_SHIFT)
#define FLAGS_CLIP_RECT_UV (1 << 9)
#define FLAGS_TRANSPOSE_RECT (1 << 10)
#define FLAGS_CONVERT_ATTRIBUTES_TO_LINEAR (1 << 11)
-#define FLAGS_NINEPACH_DRAW_CENTER (1 << 12)
+#define FLAGS_NINEPACH_DRAW_CENTER_SHIFT 12
+#define FLAGS_NINEPACH_DRAW_CENTER (1 << FLAGS_NINEPACH_DRAW_CENTER_SHIFT)
#define FLAGS_NINEPATCH_H_MODE_SHIFT 16
#define FLAGS_NINEPATCH_V_MODE_SHIFT 18
@@ -29,9 +32,7 @@
#define FLAGS_FLIP_H (1 << 30)
#define FLAGS_FLIP_V (1 << 31)
-// Push Constant
-
-layout(push_constant, std430) uniform DrawData {
+struct InstanceData {
vec2 world_x;
vec2 world_y;
vec2 world_ofs;
@@ -51,8 +52,20 @@ layout(push_constant, std430) uniform DrawData {
#endif
vec2 color_texture_pixel_size;
uint lights[4];
+};
+
+layout(set = 4, binding = 0, std430) restrict readonly buffer DrawData {
+ InstanceData data[];
+}
+instances;
+
+layout(push_constant, std430) uniform Params {
+ uint base_instance_index; // base index to instance data
+ uint pad1;
+ uint pad2;
+ uint pad3;
}
-draw_data;
+params;
// In vulkan, sets should always be ordered using the following logic:
// Lower Sets: Sets that change format and layout less often
diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
index aafb9b4764..cfde97af95 100644
--- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
@@ -1513,7 +1513,6 @@ void fragment_shader(in SceneData scene_data) {
if (uses_sh) {
uvw.z *= 4.0; //SH textures use 4 times more data
-
vec3 lm_light_l0;
vec3 lm_light_l1n1;
vec3 lm_light_l1_0;
@@ -1521,23 +1520,23 @@ void fragment_shader(in SceneData scene_data) {
if (sc_use_lightmap_bicubic_filter) {
lm_light_l0 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 0.0), lightmaps.data[ofs].light_texture_size).rgb;
- lm_light_l1n1 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 1.0), lightmaps.data[ofs].light_texture_size).rgb;
- lm_light_l1_0 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 2.0), lightmaps.data[ofs].light_texture_size).rgb;
- lm_light_l1p1 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 3.0), lightmaps.data[ofs].light_texture_size).rgb;
+ lm_light_l1n1 = (textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 1.0), lightmaps.data[ofs].light_texture_size).rgb - vec3(0.5)) * 2.0;
+ lm_light_l1_0 = (textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 2.0), lightmaps.data[ofs].light_texture_size).rgb - vec3(0.5)) * 2.0;
+ lm_light_l1p1 = (textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 3.0), lightmaps.data[ofs].light_texture_size).rgb - vec3(0.5)) * 2.0;
} else {
lm_light_l0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb;
- lm_light_l1n1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb;
- lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
- lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
+ lm_light_l1n1 = (textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb - vec3(0.5)) * 2.0;
+ lm_light_l1_0 = (textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb - vec3(0.5)) * 2.0;
+ lm_light_l1p1 = (textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb - vec3(0.5)) * 2.0;
}
vec3 n = normalize(lightmaps.data[ofs].normal_xform * normal);
float en = lightmaps.data[ofs].exposure_normalization;
ambient_light += lm_light_l0 * en;
- ambient_light += lm_light_l1n1 * n.y * en;
- ambient_light += lm_light_l1_0 * n.z * en;
- ambient_light += lm_light_l1p1 * n.x * en;
+ ambient_light += lm_light_l1n1 * n.y * (lm_light_l0 * en * 4.0);
+ ambient_light += lm_light_l1_0 * n.z * (lm_light_l0 * en * 4.0);
+ ambient_light += lm_light_l1p1 * n.x * (lm_light_l0 * en * 4.0);
} else {
if (sc_use_lightmap_bicubic_filter) {
diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
index c266161834..b21769f207 100644
--- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
@@ -1280,23 +1280,23 @@ void main() {
if (sc_use_lightmap_bicubic_filter) {
lm_light_l0 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 0.0), lightmaps.data[ofs].light_texture_size).rgb;
- lm_light_l1n1 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 1.0), lightmaps.data[ofs].light_texture_size).rgb;
- lm_light_l1_0 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 2.0), lightmaps.data[ofs].light_texture_size).rgb;
- lm_light_l1p1 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 3.0), lightmaps.data[ofs].light_texture_size).rgb;
+ lm_light_l1n1 = (textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 1.0), lightmaps.data[ofs].light_texture_size).rgb - vec3(0.5)) * 2.0;
+ lm_light_l1_0 = (textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 2.0), lightmaps.data[ofs].light_texture_size).rgb - vec3(0.5)) * 2.0;
+ lm_light_l1p1 = (textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 3.0), lightmaps.data[ofs].light_texture_size).rgb - vec3(0.5)) * 2.0;
} else {
lm_light_l0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb;
- lm_light_l1n1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb;
- lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
- lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
+ lm_light_l1n1 = (textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb - vec3(0.5)) * 2.0;
+ lm_light_l1_0 = (textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb - vec3(0.5)) * 2.0;
+ lm_light_l1p1 = (textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb - vec3(0.5)) * 2.0;
}
vec3 n = normalize(lightmaps.data[ofs].normal_xform * normal);
float exposure_normalization = lightmaps.data[ofs].exposure_normalization;
ambient_light += lm_light_l0 * exposure_normalization;
- ambient_light += lm_light_l1n1 * n.y * exposure_normalization;
- ambient_light += lm_light_l1_0 * n.z * exposure_normalization;
- ambient_light += lm_light_l1p1 * n.x * exposure_normalization;
+ ambient_light += lm_light_l1n1 * n.y * (lm_light_l0 * exposure_normalization * 4.0);
+ ambient_light += lm_light_l1_0 * n.z * (lm_light_l0 * exposure_normalization * 4.0);
+ ambient_light += lm_light_l1p1 * n.x * (lm_light_l0 * exposure_normalization * 4.0);
} else {
if (sc_use_lightmap_bicubic_filter) {
ambient_light += textureArray_bicubic(lightmap_textures[ofs], uvw, lightmaps.data[ofs].light_texture_size).rgb * lightmaps.data[ofs].exposure_normalization;
diff --git a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
index 63dc54e24c..9f390c99f9 100644
--- a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
@@ -342,7 +342,7 @@ static void _fill_std140_variant_ubo_value(ShaderLanguage::DataType type, int p_
}
}
-_FORCE_INLINE_ static void _fill_std140_ubo_value(ShaderLanguage::DataType type, const Vector<ShaderLanguage::ConstantNode::Value> &value, uint8_t *data) {
+_FORCE_INLINE_ static void _fill_std140_ubo_value(ShaderLanguage::DataType type, const Vector<ShaderLanguage::Scalar> &value, uint8_t *data) {
switch (type) {
case ShaderLanguage::TYPE_BOOL: {
uint32_t *gui = (uint32_t *)data;
@@ -566,7 +566,7 @@ void MaterialStorage::ShaderData::set_default_texture_parameter(const StringName
Variant MaterialStorage::ShaderData::get_default_parameter(const StringName &p_parameter) const {
if (uniforms.has(p_parameter)) {
ShaderLanguage::ShaderNode::Uniform uniform = uniforms[p_parameter];
- Vector<ShaderLanguage::ConstantNode::Value> default_value = uniform.default_value;
+ Vector<ShaderLanguage::Scalar> default_value = uniform.default_value;
return ShaderLanguage::constant_value_to_variant(default_value, uniform.type, uniform.array_size, uniform.hint);
}
return Variant();
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
index 9bd62ba065..9ae39691dc 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
@@ -2041,7 +2041,7 @@ AABB MeshStorage::_multimesh_get_custom_aabb(RID p_multimesh) const {
return multimesh->custom_aabb;
}
-AABB MeshStorage::_multimesh_get_aabb(RID p_multimesh) const {
+AABB MeshStorage::_multimesh_get_aabb(RID p_multimesh) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, AABB());
if (multimesh->custom_aabb != AABB()) {
@@ -2049,7 +2049,7 @@ AABB MeshStorage::_multimesh_get_aabb(RID p_multimesh) const {
}
if (multimesh->aabb_dirty) {
- const_cast<MeshStorage *>(this)->_update_dirty_multimeshes();
+ _update_dirty_multimeshes();
}
return multimesh->aabb;
}
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
index 4344db783d..f811314fb6 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
@@ -652,7 +652,7 @@ public:
virtual void _multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) override;
virtual AABB _multimesh_get_custom_aabb(RID p_multimesh) const override;
- virtual AABB _multimesh_get_aabb(RID p_multimesh) const override;
+ virtual AABB _multimesh_get_aabb(RID p_multimesh) override;
virtual MultiMeshInterpolator *_multimesh_get_interpolator(RID p_multimesh) const override;
diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp
index 7e45eba1de..781d29ffaa 100644
--- a/servers/rendering/renderer_viewport.cpp
+++ b/servers/rendering/renderer_viewport.cpp
@@ -818,8 +818,9 @@ void RendererViewport::draw_viewports(bool p_swap_buffers) {
blit.dst_rect.size = vp->size;
}
- if (!blit_to_screen_list.has(vp->viewport_to_screen)) {
- blit_to_screen_list[vp->viewport_to_screen] = Vector<BlitToScreen>();
+ Vector<BlitToScreen> *blits = blit_to_screen_list.getptr(vp->viewport_to_screen);
+ if (blits == nullptr) {
+ blits = &blit_to_screen_list.insert(vp->viewport_to_screen, Vector<BlitToScreen>())->value;
}
if (OS::get_singleton()->get_current_rendering_driver_name().begins_with("opengl3")) {
@@ -828,7 +829,7 @@ void RendererViewport::draw_viewports(bool p_swap_buffers) {
RSG::rasterizer->blit_render_targets_to_screen(vp->viewport_to_screen, blit_to_screen_vec.ptr(), 1);
RSG::rasterizer->gl_end_frame(p_swap_buffers);
} else {
- blit_to_screen_list[vp->viewport_to_screen].push_back(blit);
+ blits->push_back(blit);
}
}
}
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index f0f267c246..e322bba768 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -138,17 +138,17 @@ RenderingDevice::ShaderSPIRVGetCacheKeyFunction RenderingDevice::get_spirv_cache
/***************************/
void RenderingDevice::_add_dependency(RID p_id, RID p_depends_on) {
- if (!dependency_map.has(p_depends_on)) {
- dependency_map[p_depends_on] = HashSet<RID>();
+ HashSet<RID> *set = dependency_map.getptr(p_depends_on);
+ if (set == nullptr) {
+ set = &dependency_map.insert(p_depends_on, HashSet<RID>())->value;
}
+ set->insert(p_id);
- dependency_map[p_depends_on].insert(p_id);
-
- if (!reverse_dependency_map.has(p_id)) {
- reverse_dependency_map[p_id] = HashSet<RID>();
+ set = reverse_dependency_map.getptr(p_id);
+ if (set == nullptr) {
+ set = &reverse_dependency_map.insert(p_id, HashSet<RID>())->value;
}
-
- reverse_dependency_map[p_id].insert(p_depends_on);
+ set->insert(p_depends_on);
}
void RenderingDevice::_free_dependencies(RID p_id) {
@@ -1641,8 +1641,8 @@ Vector<uint8_t> RenderingDevice::texture_get_data(RID p_texture, uint32_t p_laye
copy_region.texture_region_size.z = d;
command_buffer_texture_copy_regions_vector.push_back(copy_region);
- w = (w >> 1);
- h = (h >> 1);
+ w = MAX(1u, w >> 1);
+ h = MAX(1u, h >> 1);
d = MAX(1u, d >> 1);
}
diff --git a/servers/rendering/rendering_server_default.cpp b/servers/rendering/rendering_server_default.cpp
index 86efccef9a..b994ebf337 100644
--- a/servers/rendering/rendering_server_default.cpp
+++ b/servers/rendering/rendering_server_default.cpp
@@ -150,12 +150,10 @@ void RenderingServerDefault::_draw(bool p_swap_buffers, double frame_step) {
double time = frame_profile[i + 1].gpu_msec - frame_profile[i].gpu_msec;
- if (name[0] != '<' && name[0] != '>') {
- if (print_gpu_profile_task_time.has(name)) {
- print_gpu_profile_task_time[name] += time;
- } else {
- print_gpu_profile_task_time[name] = time;
- }
+ if (print_gpu_profile_task_time.has(name)) {
+ print_gpu_profile_task_time[name] += time;
+ } else {
+ print_gpu_profile_task_time[name] = time;
}
}
diff --git a/servers/rendering/shader_compiler.cpp b/servers/rendering/shader_compiler.cpp
index 49e005ca96..29183eac6f 100644
--- a/servers/rendering/shader_compiler.cpp
+++ b/servers/rendering/shader_compiler.cpp
@@ -185,7 +185,7 @@ static String f2sp0(float p_float) {
return num;
}
-static String get_constant_text(SL::DataType p_type, const Vector<SL::ConstantNode::Value> &p_values) {
+static String get_constant_text(SL::DataType p_type, const Vector<SL::Scalar> &p_values, bool p_is_op) {
switch (p_type) {
case SL::TYPE_BOOL:
return p_values[0].boolean ? "true" : "false";
@@ -205,7 +205,7 @@ static String get_constant_text(SL::DataType p_type, const Vector<SL::ConstantNo
}
case SL::TYPE_INT:
- return itos(p_values[0].sint);
+ return itos(p_is_op ? Math::abs(p_values[0].sint) : p_values[0].sint); // To prevent writing unary minus twice in operator expression parsing.
case SL::TYPE_IVEC2:
case SL::TYPE_IVEC3:
case SL::TYPE_IVEC4: {
@@ -238,7 +238,7 @@ static String get_constant_text(SL::DataType p_type, const Vector<SL::ConstantNo
return text;
} break;
case SL::TYPE_FLOAT:
- return f2sp0(p_values[0].real);
+ return f2sp0(p_is_op ? Math::abs(p_values[0].real) : p_values[0].real); // To prevent writing unary minus twice in operator expression parsing.
case SL::TYPE_VEC2:
case SL::TYPE_VEC3:
case SL::TYPE_VEC4: {
@@ -446,7 +446,7 @@ static String _get_global_shader_uniform_from_type_and_index(const String &p_buf
}
}
-String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, GeneratedCode &r_gen_code, IdentifierActions &p_actions, const DefaultIdentifierActions &p_default_actions, bool p_assigning, bool p_use_scope) {
+String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, GeneratedCode &r_gen_code, IdentifierActions &p_actions, const DefaultIdentifierActions &p_default_actions, bool p_assigning, bool p_use_scope, bool p_is_op) {
String code;
switch (p_node->type) {
@@ -1090,7 +1090,7 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
SL::ConstantNode *cnode = (SL::ConstantNode *)p_node;
if (cnode->array_size == 0) {
- return get_constant_text(cnode->datatype, cnode->values);
+ return get_constant_text(cnode->datatype, cnode->values, p_is_op);
} else {
if (cnode->get_datatype() == SL::TYPE_STRUCT) {
code += _mkid(cnode->struct_name);
@@ -1128,18 +1128,18 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
case SL::OP_ASSIGN_BIT_AND:
case SL::OP_ASSIGN_BIT_OR:
case SL::OP_ASSIGN_BIT_XOR:
- code = _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, true) + _opstr(onode->op) + _dump_node_code(onode->arguments[1], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
+ code = _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, true, true, true) + _opstr(onode->op) + _dump_node_code(onode->arguments[1], p_level, r_gen_code, p_actions, p_default_actions, p_assigning, true, true);
break;
case SL::OP_BIT_INVERT:
case SL::OP_NEGATE:
case SL::OP_NOT:
case SL::OP_DECREMENT:
case SL::OP_INCREMENT:
- code = _opstr(onode->op) + _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
+ code = _opstr(onode->op) + _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning, true, true);
break;
case SL::OP_POST_DECREMENT:
case SL::OP_POST_INCREMENT:
- code = _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning) + _opstr(onode->op);
+ code = _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning, true, true) + _opstr(onode->op);
break;
case SL::OP_CALL:
case SL::OP_STRUCT:
@@ -1235,7 +1235,7 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
}
}
- String node_code = _dump_node_code(onode->arguments[i], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
+ String node_code = _dump_node_code(onode->arguments[i], p_level, r_gen_code, p_actions, p_default_actions, p_assigning, true, true);
if (is_texture_func && i == 1) {
// If we're doing a texture lookup we need to check our texture argument
StringName texture_uniform;
@@ -1352,19 +1352,19 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
}
} break;
case SL::OP_INDEX: {
- code += _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
+ code += _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning, true, true);
code += "[";
- code += _dump_node_code(onode->arguments[1], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
+ code += _dump_node_code(onode->arguments[1], p_level, r_gen_code, p_actions, p_default_actions, p_assigning, true, true);
code += "]";
} break;
case SL::OP_SELECT_IF: {
code += "(";
- code += _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
+ code += _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning, true, true);
code += "?";
- code += _dump_node_code(onode->arguments[1], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
+ code += _dump_node_code(onode->arguments[1], p_level, r_gen_code, p_actions, p_default_actions, p_assigning, true, true);
code += ":";
- code += _dump_node_code(onode->arguments[2], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
+ code += _dump_node_code(onode->arguments[2], p_level, r_gen_code, p_actions, p_default_actions, p_assigning, true, true);
code += ")";
} break;
@@ -1376,7 +1376,7 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
if (p_use_scope) {
code += "(";
}
- code += _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning) + " " + _opstr(onode->op) + " " + _dump_node_code(onode->arguments[1], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
+ code += _dump_node_code(onode->arguments[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning) + " " + _opstr(onode->op) + " " + _dump_node_code(onode->arguments[1], p_level, r_gen_code, p_actions, p_default_actions, p_assigning, true, true);
if (p_use_scope) {
code += ")";
}
diff --git a/servers/rendering/shader_compiler.h b/servers/rendering/shader_compiler.h
index 66106d7eb7..bf0cfd7032 100644
--- a/servers/rendering/shader_compiler.h
+++ b/servers/rendering/shader_compiler.h
@@ -109,7 +109,7 @@ private:
String _get_sampler_name(ShaderLanguage::TextureFilter p_filter, ShaderLanguage::TextureRepeat p_repeat);
void _dump_function_deps(const ShaderLanguage::ShaderNode *p_node, const StringName &p_for_func, const HashMap<StringName, String> &p_func_code, String &r_to_add, HashSet<StringName> &added);
- String _dump_node_code(const ShaderLanguage::Node *p_node, int p_level, GeneratedCode &r_gen_code, IdentifierActions &p_actions, const DefaultIdentifierActions &p_default_actions, bool p_assigning, bool p_scope = true);
+ String _dump_node_code(const ShaderLanguage::Node *p_node, int p_level, GeneratedCode &r_gen_code, IdentifierActions &p_actions, const DefaultIdentifierActions &p_default_actions, bool p_assigning, bool p_scope = true, bool p_is_op = false);
const ShaderLanguage::ShaderNode *shader = nullptr;
const ShaderLanguage::FunctionNode *function = nullptr;
diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp
index 4eaf7fcb55..5a3c5d2fd0 100644
--- a/servers/rendering/shader_language.cpp
+++ b/servers/rendering/shader_language.cpp
@@ -1362,7 +1362,7 @@ void ShaderLanguage::_parse_used_identifier(const StringName &p_identifier, Iden
}
#endif // DEBUG_ENABLED
-bool ShaderLanguage::_find_identifier(const BlockNode *p_block, bool p_allow_reassign, const FunctionInfo &p_function_info, const StringName &p_identifier, DataType *r_data_type, IdentifierType *r_type, bool *r_is_const, int *r_array_size, StringName *r_struct_name, ConstantNode::Value *r_constant_value) {
+bool ShaderLanguage::_find_identifier(const BlockNode *p_block, bool p_allow_reassign, const FunctionInfo &p_function_info, const StringName &p_identifier, DataType *r_data_type, IdentifierType *r_type, bool *r_is_const, int *r_array_size, StringName *r_struct_name, Vector<Scalar> *r_constant_values) {
if (is_shader_inc) {
for (int i = 0; i < RenderingServer::SHADER_MAX; i++) {
for (const KeyValue<StringName, FunctionInfo> &E : ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(i))) {
@@ -1424,8 +1424,8 @@ bool ShaderLanguage::_find_identifier(const BlockNode *p_block, bool p_allow_rea
if (r_struct_name) {
*r_struct_name = p_block->variables[p_identifier].struct_name;
}
- if (r_constant_value) {
- *r_constant_value = p_block->variables[p_identifier].value;
+ if (r_constant_values && !p_block->variables[p_identifier].values.is_empty()) {
+ *r_constant_values = p_block->variables[p_identifier].values;
}
if (r_type) {
*r_type = IDENTIFIER_LOCAL_VAR;
@@ -1507,13 +1507,9 @@ bool ShaderLanguage::_find_identifier(const BlockNode *p_block, bool p_allow_rea
if (r_struct_name) {
*r_struct_name = shader->constants[p_identifier].struct_name;
}
- if (r_constant_value) {
- if (shader->constants[p_identifier].initializer && shader->constants[p_identifier].initializer->type == Node::NODE_TYPE_CONSTANT) {
- ConstantNode *cnode = static_cast<ConstantNode *>(shader->constants[p_identifier].initializer);
-
- if (cnode->values.size() == 1) {
- *r_constant_value = cnode->values[0];
- }
+ if (r_constant_values) {
+ if (shader->constants[p_identifier].initializer && !shader->constants[p_identifier].initializer->get_values().is_empty()) {
+ *r_constant_values = shader->constants[p_identifier].initializer->get_values();
}
}
if (r_type) {
@@ -1544,7 +1540,7 @@ bool ShaderLanguage::_find_identifier(const BlockNode *p_block, bool p_allow_rea
return false;
}
-bool ShaderLanguage::_validate_operator(OperatorNode *p_op, DataType *r_ret_type, int *r_ret_size) {
+bool ShaderLanguage::_validate_operator(const BlockNode *p_block, OperatorNode *p_op, DataType *r_ret_type, int *r_ret_size) {
bool valid = false;
DataType ret_type = TYPE_VOID;
int ret_size = 0;
@@ -2007,9 +2003,384 @@ bool ShaderLanguage::_validate_operator(OperatorNode *p_op, DataType *r_ret_type
if (r_ret_size) {
*r_ret_size = ret_size;
}
+
+ if (valid && (!p_block || p_block->use_op_eval)) {
+ // Need to be placed here and not in the `_reduce_expression` because otherwise expressions like `1 + 2 / 2` will not work correctly.
+ valid = _eval_operator(p_block, p_op);
+ }
+
return valid;
}
+Vector<ShaderLanguage::Scalar> ShaderLanguage::_get_node_values(const BlockNode *p_block, Node *p_node) {
+ Vector<Scalar> result;
+
+ switch (p_node->type) {
+ case Node::NODE_TYPE_VARIABLE: {
+ _find_identifier(p_block, false, FunctionInfo(), static_cast<VariableNode *>(p_node)->name, nullptr, nullptr, nullptr, nullptr, nullptr, &result);
+ } break;
+ default: {
+ result = p_node->get_values();
+ } break;
+ }
+
+ return result;
+}
+
+bool ShaderLanguage::_eval_operator(const BlockNode *p_block, OperatorNode *p_op) {
+ bool is_valid = true;
+
+ switch (p_op->op) {
+ case OP_EQUAL:
+ case OP_NOT_EQUAL:
+ case OP_LESS:
+ case OP_LESS_EQUAL:
+ case OP_GREATER:
+ case OP_GREATER_EQUAL:
+ case OP_AND:
+ case OP_OR:
+ case OP_ADD:
+ case OP_SUB:
+ case OP_MUL:
+ case OP_DIV:
+ case OP_MOD:
+ case OP_SHIFT_LEFT:
+ case OP_SHIFT_RIGHT:
+ case OP_BIT_AND:
+ case OP_BIT_OR:
+ case OP_BIT_XOR: {
+ DataType a = p_op->arguments[0]->get_datatype();
+ DataType b = p_op->arguments[1]->get_datatype();
+
+ bool is_op_vec_transform = false;
+ if (p_op->op == OP_MUL) {
+ DataType ta = a;
+ DataType tb = b;
+
+ if (ta > tb) {
+ SWAP(ta, tb);
+ }
+ if (ta == TYPE_VEC2 && tb == TYPE_MAT2) {
+ is_op_vec_transform = true;
+ } else if (ta == TYPE_VEC3 && tb == TYPE_MAT3) {
+ is_op_vec_transform = true;
+ } else if (ta == TYPE_VEC4 && tb == TYPE_MAT4) {
+ is_op_vec_transform = true;
+ }
+ }
+
+ Vector<Scalar> va = _get_node_values(p_block, p_op->arguments[0]);
+ Vector<Scalar> vb = _get_node_values(p_block, p_op->arguments[1]);
+
+ if (is_op_vec_transform) {
+ p_op->values = _eval_vector_transform(va, vb, a, b, p_op->get_datatype());
+ } else {
+ p_op->values = _eval_vector(va, vb, a, b, p_op->get_datatype(), p_op->op, is_valid);
+ }
+ } break;
+ case OP_NOT:
+ case OP_NEGATE:
+ case OP_BIT_INVERT: {
+ p_op->values = _eval_unary_vector(_get_node_values(p_block, p_op->arguments[0]), p_op->get_datatype(), p_op->op);
+ } break;
+ default: {
+ } break;
+ }
+
+ return is_valid;
+}
+
+ShaderLanguage::Scalar ShaderLanguage::_eval_unary_scalar(const Scalar &p_a, Operator p_op, DataType p_ret_type) {
+ Scalar scalar;
+
+ switch (p_op) {
+ case OP_NOT: {
+ scalar.boolean = !p_a.boolean;
+ } break;
+ case OP_NEGATE: {
+ if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) {
+ scalar.sint = -p_a.sint;
+ } else if (p_ret_type >= TYPE_UINT && p_ret_type <= TYPE_UVEC4) {
+ // Intentionally wrap the unsigned int value, because GLSL does.
+ scalar.uint = 0 - p_a.uint;
+ } else { // float types
+ scalar.real = -scalar.real;
+ }
+ } break;
+ case OP_BIT_INVERT: {
+ if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) {
+ scalar.sint = ~p_a.sint;
+ } else { // uint types
+ scalar.uint = ~p_a.uint;
+ }
+ } break;
+ default: {
+ } break;
+ }
+
+ return scalar;
+}
+
+ShaderLanguage::Scalar ShaderLanguage::_eval_scalar(const Scalar &p_a, const Scalar &p_b, Operator p_op, DataType p_ret_type, bool &r_is_valid) {
+ Scalar scalar;
+
+ switch (p_op) {
+ case OP_EQUAL: {
+ scalar.boolean = p_a.boolean == p_b.boolean;
+ } break;
+ case OP_NOT_EQUAL: {
+ scalar.boolean = p_a.boolean != p_b.boolean;
+ } break;
+ case OP_LESS: {
+ if (p_ret_type == TYPE_INT) {
+ scalar.boolean = p_a.sint < p_b.sint;
+ } else if (p_ret_type == TYPE_UINT) {
+ scalar.boolean = p_a.uint < p_b.uint;
+ } else { // float type
+ scalar.boolean = p_a.real < p_b.real;
+ }
+ } break;
+ case OP_LESS_EQUAL: {
+ if (p_ret_type == TYPE_INT) {
+ scalar.boolean = p_a.sint <= p_b.sint;
+ } else if (p_ret_type == TYPE_UINT) {
+ scalar.boolean = p_a.uint <= p_b.uint;
+ } else { // float type
+ scalar.boolean = p_a.real <= p_b.real;
+ }
+ } break;
+ case OP_GREATER: {
+ if (p_ret_type == TYPE_INT) {
+ scalar.boolean = p_a.sint > p_b.sint;
+ } else if (p_ret_type == TYPE_UINT) {
+ scalar.boolean = p_a.uint > p_b.uint;
+ } else { // float type
+ scalar.boolean = p_a.real > p_b.real;
+ }
+ } break;
+ case OP_GREATER_EQUAL: {
+ if (p_ret_type == TYPE_INT) {
+ scalar.boolean = p_a.sint >= p_b.sint;
+ } else if (p_ret_type == TYPE_UINT) {
+ scalar.boolean = p_a.uint >= p_b.uint;
+ } else { // float type
+ scalar.boolean = p_a.real >= p_b.real;
+ }
+ } break;
+ case OP_AND: {
+ scalar.boolean = p_a.boolean && p_b.boolean;
+ } break;
+ case OP_OR: {
+ scalar.boolean = p_a.boolean || p_b.boolean;
+ } break;
+ case OP_ADD: {
+ if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) {
+ scalar.sint = p_a.sint + p_b.sint;
+ } else if (p_ret_type >= TYPE_UINT && p_ret_type <= TYPE_UVEC4) {
+ scalar.uint = p_a.uint + p_b.uint;
+ } else { // float + matrix types
+ scalar.real = p_a.real + p_b.real;
+ }
+ } break;
+ case OP_SUB: {
+ if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) {
+ scalar.sint = p_a.sint - p_b.sint;
+ } else if (p_ret_type >= TYPE_UINT && p_ret_type <= TYPE_UVEC4) {
+ scalar.uint = p_a.uint - p_b.uint;
+ } else { // float + matrix types
+ scalar.real = p_a.real - p_b.real;
+ }
+ } break;
+ case OP_MUL: {
+ if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) {
+ scalar.sint = p_a.sint * p_b.sint;
+ } else if (p_ret_type >= TYPE_UINT && p_ret_type <= TYPE_UVEC4) {
+ scalar.uint = p_a.uint * p_b.uint;
+ } else { // float + matrix types
+ scalar.real = p_a.real * p_b.real;
+ }
+ } break;
+ case OP_DIV: {
+ if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) {
+ if (p_b.sint == 0) {
+ _set_error(RTR("Division by zero error."));
+ r_is_valid = false;
+ break;
+ }
+ scalar.sint = p_a.sint / p_b.sint;
+ } else if (p_ret_type == TYPE_UINT && p_ret_type <= TYPE_UVEC4) {
+ if (p_b.uint == 0U) {
+ _set_error(RTR("Division by zero error."));
+ r_is_valid = false;
+ break;
+ }
+ scalar.uint = p_a.uint / p_b.uint;
+ } else { // float + matrix types
+ scalar.real = p_a.real / p_b.real;
+ }
+ } break;
+ case OP_MOD: {
+ if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) {
+ if (p_b.sint == 0) {
+ _set_error(RTR("Modulo by zero error."));
+ r_is_valid = false;
+ break;
+ }
+ scalar.sint = p_a.sint % p_b.sint;
+ } else { // uint types
+ if (p_b.uint == 0U) {
+ _set_error(RTR("Modulo by zero error."));
+ r_is_valid = false;
+ break;
+ }
+ scalar.uint = p_a.uint % p_b.uint;
+ }
+ } break;
+ case OP_SHIFT_LEFT: {
+ if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) {
+ scalar.sint = p_a.sint << p_b.sint;
+ } else { // uint types
+ scalar.uint = p_a.uint << p_b.uint;
+ }
+ } break;
+ case OP_SHIFT_RIGHT: {
+ if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) {
+ scalar.sint = p_a.sint >> p_b.sint;
+ } else { // uint types
+ scalar.uint = p_a.uint >> p_b.uint;
+ }
+ } break;
+ case OP_BIT_AND: {
+ if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) {
+ scalar.sint = p_a.sint & p_b.sint;
+ } else { // uint types
+ scalar.uint = p_a.uint & p_b.uint;
+ }
+ } break;
+ case OP_BIT_OR: {
+ if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) {
+ scalar.sint = p_a.sint | p_b.sint;
+ } else { // uint types
+ scalar.uint = p_a.uint | p_b.uint;
+ }
+ } break;
+ case OP_BIT_XOR: {
+ if (p_ret_type >= TYPE_INT && p_ret_type <= TYPE_IVEC4) {
+ scalar.sint = p_a.sint ^ p_b.sint;
+ } else { // uint types
+ scalar.uint = p_a.uint ^ p_b.uint;
+ }
+ } break;
+ default: {
+ } break;
+ }
+
+ return scalar;
+}
+
+Vector<ShaderLanguage::Scalar> ShaderLanguage::_eval_unary_vector(const Vector<Scalar> &p_va, DataType p_ret_type, Operator p_op) {
+ uint32_t size = get_datatype_component_count(p_ret_type);
+ if (p_va.size() != p_ret_type) {
+ return Vector<Scalar>(); // Non-evaluable values should not be parsed further.
+ }
+ Vector<Scalar> value;
+ value.resize(size);
+
+ Scalar *w = value.ptrw();
+ for (uint32_t i = 0U; i < size; i++) {
+ w[i] = _eval_unary_scalar(p_va[i], p_op, p_ret_type);
+ }
+ return value;
+}
+
+Vector<ShaderLanguage::Scalar> ShaderLanguage::_eval_vector(const Vector<Scalar> &p_va, const Vector<Scalar> &p_vb, DataType p_left_type, DataType p_right_type, DataType p_ret_type, Operator p_op, bool &r_is_valid) {
+ uint32_t left_size = get_datatype_component_count(p_left_type);
+ uint32_t right_size = get_datatype_component_count(p_right_type);
+
+ if (p_va.size() != left_size || p_vb.size() != right_size) {
+ return Vector<Scalar>(); // Non-evaluable values should not be parsed further.
+ }
+
+ uint32_t ret_size = get_datatype_component_count(p_ret_type);
+ Vector<Scalar> value;
+ value.resize(ret_size);
+
+ Scalar *w = value.ptrw();
+ for (uint32_t i = 0U; i < ret_size; i++) {
+ w[i] = _eval_scalar(p_va[MIN(i, left_size - 1)], p_vb[MIN(i, right_size - 1)], p_op, p_ret_type, r_is_valid);
+ if (!r_is_valid) {
+ return value;
+ }
+ }
+ return value;
+}
+
+Vector<ShaderLanguage::Scalar> ShaderLanguage::_eval_vector_transform(const Vector<Scalar> &p_va, const Vector<Scalar> &p_vb, DataType p_left_type, DataType p_right_type, DataType p_ret_type) {
+ uint32_t left_size = get_datatype_component_count(p_left_type);
+ uint32_t right_size = get_datatype_component_count(p_right_type);
+
+ if (p_va.size() != left_size || p_vb.size() != right_size) {
+ return Vector<Scalar>(); // Non-evaluable values should not be parsed further.
+ }
+
+ uint32_t ret_size = get_datatype_component_count(p_ret_type);
+ Vector<Scalar> value;
+ value.resize_zeroed(ret_size);
+
+ Scalar *w = value.ptrw();
+ switch (p_ret_type) {
+ case TYPE_VEC2: {
+ if (left_size == 2) { // v * m
+ Vector2 v = Vector2(p_va[0].real, p_va[1].real);
+
+ w[0].real = (p_vb[0].real * v.x + p_vb[1].real * v.y);
+ w[1].real = (p_vb[2].real * v.x + p_vb[3].real * v.y);
+ } else { // m * v
+ Vector2 v = Vector2(p_vb[0].real, p_vb[1].real);
+
+ w[0].real = (p_va[0].real * v.x + p_va[2].real * v.y);
+ w[1].real = (p_va[1].real * v.x + p_va[3].real * v.y);
+ }
+ } break;
+ case TYPE_VEC3: {
+ if (left_size == 3) { // v * m
+ Vector3 v = Vector3(p_va[0].real, p_va[1].real, p_va[2].real);
+
+ w[0].real = (p_vb[0].real * v.x + p_vb[1].real * v.y + p_vb[2].real * v.z);
+ w[1].real = (p_vb[3].real * v.x + p_vb[4].real * v.y + p_vb[5].real * v.z);
+ w[2].real = (p_vb[6].real * v.x + p_vb[7].real * v.y + p_vb[8].real * v.z);
+ } else { // m * v
+ Vector3 v = Vector3(p_vb[0].real, p_vb[1].real, p_vb[2].real);
+
+ w[0].real = (p_va[0].real * v.x + p_va[3].real * v.y + p_va[6].real * v.z);
+ w[1].real = (p_va[1].real * v.x + p_va[4].real * v.y + p_va[7].real * v.z);
+ w[2].real = (p_va[2].real * v.x + p_va[5].real * v.y + p_va[8].real * v.z);
+ }
+ } break;
+ case TYPE_VEC4: {
+ if (left_size == 4) { // v * m
+ Vector4 v = Vector4(p_va[0].real, p_va[1].real, p_va[2].real, p_va[3].real);
+
+ w[0].real = (p_vb[0].real * v.x + p_vb[1].real * v.y + p_vb[2].real * v.z + p_vb[3].real * v.w);
+ w[1].real = (p_vb[4].real * v.x + p_vb[5].real * v.y + p_vb[6].real * v.z + p_vb[7].real * v.w);
+ w[2].real = (p_vb[8].real * v.x + p_vb[9].real * v.y + p_vb[10].real * v.z + p_vb[11].real * v.w);
+ w[3].real = (p_vb[12].real * v.x + p_vb[13].real * v.y + p_vb[14].real * v.z + p_vb[15].real * v.w);
+ } else { // m * v
+ Vector4 v = Vector4(p_vb[0].real, p_vb[1].real, p_vb[2].real, p_vb[3].real);
+
+ w[0].real = (p_vb[0].real * v.x + p_vb[4].real * v.y + p_vb[8].real * v.z + p_vb[12].real * v.w);
+ w[1].real = (p_vb[1].real * v.x + p_vb[5].real * v.y + p_vb[9].real * v.z + p_vb[13].real * v.w);
+ w[2].real = (p_vb[2].real * v.x + p_vb[6].real * v.y + p_vb[10].real * v.z + p_vb[14].real * v.w);
+ w[3].real = (p_vb[3].real * v.x + p_vb[7].real * v.y + p_vb[11].real * v.z + p_vb[15].real * v.w);
+ }
+ } break;
+ default: {
+ } break;
+ }
+
+ return value;
+}
+
const ShaderLanguage::BuiltinFuncDef ShaderLanguage::builtin_func_defs[] = {
// Constructors.
@@ -3271,34 +3642,15 @@ bool ShaderLanguage::_validate_function_call(BlockNode *p_block, const FunctionI
int max = builtin_func_const_args[constarg_idx].max;
bool error = false;
- if (p_func->arguments[arg]->type == Node::NODE_TYPE_VARIABLE) {
- const VariableNode *vn = static_cast<VariableNode *>(p_func->arguments[arg]);
-
- bool is_const = false;
- ConstantNode::Value value;
- value.sint = -1;
-
- _find_identifier(p_block, false, p_function_info, vn->name, nullptr, nullptr, &is_const, nullptr, nullptr, &value);
- if (!is_const || value.sint < min || value.sint > max) {
+ Vector<Scalar> values = _get_node_values(p_block, p_func->arguments[arg]);
+ if (p_func->arguments[arg]->get_datatype() == TYPE_INT && !values.is_empty()) {
+ if (values[0].sint < min || values[0].sint > max) {
error = true;
}
} else {
- if (p_func->arguments[arg]->type == Node::NODE_TYPE_CONSTANT) {
- const ConstantNode *cn = static_cast<ConstantNode *>(p_func->arguments[arg]);
-
- if (cn->get_datatype() == TYPE_INT && cn->values.size() == 1) {
- int value = cn->values[0].sint;
-
- if (value < min || value > max) {
- error = true;
- }
- } else {
- error = true;
- }
- } else {
- error = true;
- }
+ error = true;
}
+
if (error) {
_set_error(vformat(RTR("Expected integer constant within [%d..%d] range."), min, max));
return false;
@@ -3760,7 +4112,7 @@ bool ShaderLanguage::is_token_hint(TokenType p_type) {
return int(p_type) > int(TK_RENDER_MODE) && int(p_type) < int(TK_SHADER_TYPE);
}
-bool ShaderLanguage::convert_constant(ConstantNode *p_constant, DataType p_to_type, ConstantNode::Value *p_value) {
+bool ShaderLanguage::convert_constant(ConstantNode *p_constant, DataType p_to_type, Scalar *p_value) {
if (p_constant->datatype == p_to_type) {
if (p_value) {
for (int i = 0; i < p_constant->values.size(); i++) {
@@ -3828,7 +4180,7 @@ bool ShaderLanguage::is_sampler_type(DataType p_type) {
return p_type > TYPE_MAT4 && p_type < TYPE_STRUCT;
}
-Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::ConstantNode::Value> &p_value, DataType p_type, int p_array_size, ShaderLanguage::ShaderNode::Uniform::Hint p_hint) {
+Variant ShaderLanguage::constant_value_to_variant(const Vector<Scalar> &p_value, DataType p_type, int p_array_size, ShaderLanguage::ShaderNode::Uniform::Hint p_hint) {
int array_size = p_array_size;
if (p_value.size() > 0) {
@@ -4437,6 +4789,52 @@ uint32_t ShaderLanguage::get_datatype_size(ShaderLanguage::DataType p_type) {
ERR_FAIL_V(0);
}
+uint32_t ShaderLanguage::get_datatype_component_count(ShaderLanguage::DataType p_type) {
+ switch (p_type) {
+ case TYPE_BOOL:
+ return 1U;
+ case TYPE_BVEC2:
+ return 2U;
+ case TYPE_BVEC3:
+ return 3U;
+ case TYPE_BVEC4:
+ return 4U;
+ case TYPE_INT:
+ return 1U;
+ case TYPE_IVEC2:
+ return 2U;
+ case TYPE_IVEC3:
+ return 3U;
+ case TYPE_IVEC4:
+ return 4U;
+ case TYPE_UINT:
+ return 1U;
+ case TYPE_UVEC2:
+ return 2U;
+ case TYPE_UVEC3:
+ return 3U;
+ case TYPE_UVEC4:
+ return 4U;
+ case TYPE_FLOAT:
+ return 1U;
+ case TYPE_VEC2:
+ return 2U;
+ case TYPE_VEC3:
+ return 3U;
+ case TYPE_VEC4:
+ return 4U;
+ case TYPE_MAT2:
+ return 4U;
+ case TYPE_MAT3:
+ return 9U;
+ case TYPE_MAT4:
+ return 16U;
+ default:
+ break;
+ }
+ return 0U;
+}
+
void ShaderLanguage::get_keyword_list(List<String> *r_keywords) {
HashSet<String> kws;
@@ -4929,45 +5327,30 @@ Error ShaderLanguage::_parse_array_size(BlockNode *p_block, const FunctionInfo &
*r_unknown_size = true;
}
} else {
- int array_size = 0;
+ _set_tkpos(pos);
- if (!tk.is_integer_constant() || ((int)tk.constant) <= 0) {
- _set_tkpos(pos);
- Node *n = _parse_and_reduce_expression(p_block, p_function_info);
- if (n) {
- if (n->type == Node::NODE_TYPE_VARIABLE) {
- VariableNode *vn = static_cast<VariableNode *>(n);
- if (vn) {
- ConstantNode::Value v;
- DataType data_type;
- bool is_const = false;
+ int array_size = 0;
+ Node *expr = _parse_and_reduce_expression(p_block, p_function_info);
- _find_identifier(p_block, false, p_function_info, vn->name, &data_type, nullptr, &is_const, nullptr, nullptr, &v);
+ if (expr) {
+ Vector<Scalar> values = _get_node_values(p_block, expr);
- if (is_const) {
- if (data_type == TYPE_INT) {
- int32_t value = v.sint;
- if (value > 0) {
- array_size = value;
- }
- } else if (data_type == TYPE_UINT) {
- uint32_t value = v.uint;
- if (value > 0U) {
- array_size = value;
- }
- }
- }
- }
- } else if (n->type == Node::NODE_TYPE_OPERATOR) {
- _set_error(vformat(RTR("Array size expressions are not supported.")));
- return ERR_PARSE_ERROR;
- }
- if (r_size_expression != nullptr) {
- *r_size_expression = n;
+ if (!values.is_empty()) {
+ switch (expr->get_datatype()) {
+ case TYPE_INT: {
+ array_size = values[0].sint;
+ } break;
+ case TYPE_UINT: {
+ array_size = (int)values[0].uint;
+ } break;
+ default: {
+ } break;
}
}
- } else if (((int)tk.constant) > 0) {
- array_size = (uint32_t)tk.constant;
+
+ if (r_size_expression != nullptr) {
+ *r_size_expression = expr;
+ }
}
if (array_size <= 0) {
@@ -5064,7 +5447,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_array_constructor(BlockNode *p_bloc
idx++;
}
if (!auto_size && !undefined_size && an->initializer.size() != array_size) {
- _set_error(RTR("Array size mismatch."));
+ _set_error(vformat(RTR("Array size mismatch. Expected %d elements (found %d)."), array_size, an->initializer.size()));
return nullptr;
}
} else {
@@ -5185,7 +5568,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_array_constructor(BlockNode *p_bloc
}
}
if (an->initializer.size() != p_array_size) {
- _set_error(RTR("Array size mismatch."));
+ _set_error(vformat(RTR("Array size mismatch. Expected %d elements (found %d)."), p_array_size, an->initializer.size()));
return nullptr;
}
} else {
@@ -5230,7 +5613,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
} else if (tk.type == TK_FLOAT_CONSTANT) {
ConstantNode *constant = alloc_node<ConstantNode>();
- ConstantNode::Value v;
+ Scalar v;
v.real = tk.constant;
constant->values.push_back(v);
constant->datatype = TYPE_FLOAT;
@@ -5238,7 +5621,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
} else if (tk.type == TK_INT_CONSTANT) {
ConstantNode *constant = alloc_node<ConstantNode>();
- ConstantNode::Value v;
+ Scalar v;
v.sint = tk.constant;
constant->values.push_back(v);
constant->datatype = TYPE_INT;
@@ -5246,7 +5629,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
} else if (tk.type == TK_UINT_CONSTANT) {
ConstantNode *constant = alloc_node<ConstantNode>();
- ConstantNode::Value v;
+ Scalar v;
v.uint = tk.constant;
constant->values.push_back(v);
constant->datatype = TYPE_UINT;
@@ -5255,7 +5638,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
} else if (tk.type == TK_TRUE) {
//handle true constant
ConstantNode *constant = alloc_node<ConstantNode>();
- ConstantNode::Value v;
+ Scalar v;
v.boolean = true;
constant->values.push_back(v);
constant->datatype = TYPE_BOOL;
@@ -5264,7 +5647,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
} else if (tk.type == TK_FALSE) {
//handle false constant
ConstantNode *constant = alloc_node<ConstantNode>();
- ConstantNode::Value v;
+ Scalar v;
v.boolean = false;
constant->values.push_back(v);
constant->datatype = TYPE_BOOL;
@@ -6527,7 +6910,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
op->op = tk.type == TK_OP_DECREMENT ? OP_POST_DECREMENT : OP_POST_INCREMENT;
op->arguments.push_back(expr);
- if (!_validate_operator(op, &op->return_cache, &op->return_array_size)) {
+ if (!_validate_operator(p_block, op, &op->return_cache, &op->return_array_size)) {
_set_error(RTR("Invalid base type for increment/decrement operator."));
return nullptr;
}
@@ -6876,7 +7259,11 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
expression.write[i].is_op = false;
expression.write[i].node = op;
- if (!_validate_operator(op, &op->return_cache, &op->return_array_size)) {
+ if (!_validate_operator(p_block, op, &op->return_cache, &op->return_array_size)) {
+ if (error_set) {
+ return nullptr;
+ }
+
String at;
for (int j = 0; j < op->arguments.size(); j++) {
if (j > 0) {
@@ -6914,7 +7301,11 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
expression.write[next_op - 1].is_op = false;
expression.write[next_op - 1].node = op;
- if (!_validate_operator(op, &op->return_cache, &op->return_array_size)) {
+ if (!_validate_operator(p_block, op, &op->return_cache, &op->return_array_size)) {
+ if (error_set) {
+ return nullptr;
+ }
+
String at;
for (int i = 0; i < op->arguments.size(); i++) {
if (i > 0) {
@@ -6950,6 +7341,11 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
}
if (_is_operator_assign(op->op)) {
+ if (p_block && expression[next_op - 1].node->type == Node::NODE_TYPE_VARIABLE) {
+ VariableNode *vn = static_cast<VariableNode *>(expression[next_op - 1].node);
+ p_block->use_op_eval = vn->is_const;
+ }
+
String assign_message;
if (!_validate_assign(expression[next_op - 1].node, p_function_info, &assign_message)) {
_set_error(assign_message);
@@ -6972,7 +7368,11 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
//replace all 3 nodes by this operator and make it an expression
- if (!_validate_operator(op, &op->return_cache, &op->return_array_size)) {
+ if (!_validate_operator(p_block, op, &op->return_cache, &op->return_array_size)) {
+ if (error_set) {
+ return nullptr;
+ }
+
String at;
for (int i = 0; i < op->arguments.size(); i++) {
if (i > 0) {
@@ -6998,6 +7398,10 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
}
}
+ if (p_block) {
+ p_block->use_op_eval = true;
+ }
+
if (p_previous_expression_info != nullptr) {
p_previous_expression_info->expression->push_back(expression[0]);
}
@@ -7020,7 +7424,7 @@ ShaderLanguage::Node *ShaderLanguage::_reduce_expression(BlockNode *p_block, Sha
DataType base = get_scalar_type(type);
int cardinality = get_cardinality(type);
- Vector<ConstantNode::Value> values;
+ Vector<Scalar> values;
for (int i = 1; i < op->arguments.size(); i++) {
op->arguments.write[i] = _reduce_expression(p_block, op->arguments[i]);
@@ -7032,7 +7436,7 @@ ShaderLanguage::Node *ShaderLanguage::_reduce_expression(BlockNode *p_block, Sha
values.push_back(cn->values[j]);
}
} else if (get_scalar_type(cn->datatype) == cn->datatype) {
- ConstantNode::Value v;
+ Scalar v;
if (!convert_constant(cn, base, &v)) {
return p_node;
}
@@ -7048,8 +7452,8 @@ ShaderLanguage::Node *ShaderLanguage::_reduce_expression(BlockNode *p_block, Sha
if (values.size() == 1) {
if (type >= TYPE_MAT2 && type <= TYPE_MAT4) {
- ConstantNode::Value value = values[0];
- ConstantNode::Value zero;
+ Scalar value = values[0];
+ Scalar zero;
zero.real = 0.0f;
int size = 2 + (type - TYPE_MAT2);
@@ -7060,7 +7464,7 @@ ShaderLanguage::Node *ShaderLanguage::_reduce_expression(BlockNode *p_block, Sha
}
}
} else {
- ConstantNode::Value value = values[0];
+ Scalar value = values[0];
for (int i = 1; i < cardinality; i++) {
values.push_back(value);
}
@@ -7081,10 +7485,10 @@ ShaderLanguage::Node *ShaderLanguage::_reduce_expression(BlockNode *p_block, Sha
DataType base = get_scalar_type(cn->datatype);
- Vector<ConstantNode::Value> values;
+ Vector<Scalar> values;
for (int i = 0; i < cn->values.size(); i++) {
- ConstantNode::Value nv;
+ Scalar nv;
switch (base) {
case TYPE_BOOL: {
nv.boolean = !cn->values[i].boolean;
@@ -7515,7 +7919,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
decl.size = decl.initializer.size();
var.array_size = decl.initializer.size();
} else if (decl.initializer.size() != var.array_size) {
- _set_error(RTR("Array size mismatch."));
+ _set_error(vformat(RTR("Array size mismatch. Expected %d elements (found %d)."), var.array_size, decl.initializer.size()));
return ERR_PARSE_ERROR;
}
tk = _get_token();
@@ -7537,7 +7941,9 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
array_size = var.array_size;
} else if (tk.type == TK_OP_ASSIGN) {
- //variable created with assignment! must parse an expression
+ p_block->use_op_eval = is_const;
+
+ // Variable created with assignment! Must parse an expression.
Node *n = _parse_and_reduce_expression(p_block, p_function_info);
if (!n) {
return ERR_PARSE_ERROR;
@@ -7552,11 +7958,8 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
}
}
- if (n->type == Node::NODE_TYPE_CONSTANT) {
- ConstantNode *const_node = static_cast<ConstantNode *>(n);
- if (const_node && const_node->values.size() == 1) {
- var.value = const_node->values[0];
- }
+ if (is_const) {
+ var.values = n->get_values();
}
if (!_compare_datatypes(var.type, var.struct_name, var.array_size, n->get_datatype(), n->get_datatype_name(), n->get_array_size())) {
@@ -7668,12 +8071,10 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
if (!n) {
return ERR_PARSE_ERROR;
}
- {
- const ShaderLanguage::DataType switch_type = n->get_datatype();
- if (switch_type != TYPE_INT && switch_type != TYPE_UINT) {
- _set_error(RTR("Expected an integer expression."));
- return ERR_PARSE_ERROR;
- }
+ const ShaderLanguage::DataType data_type = n->get_datatype();
+ if (data_type != TYPE_INT && data_type != TYPE_UINT) {
+ _set_error(RTR("Expected an integer or unsigned integer expression."));
+ return ERR_PARSE_ERROR;
}
tk = _get_token();
if (tk.type != TK_PARENTHESIS_CLOSE) {
@@ -7688,11 +8089,22 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
BlockNode *switch_block = alloc_node<BlockNode>();
switch_block->block_type = BlockNode::BLOCK_TYPE_SWITCH;
switch_block->parent_block = p_block;
+ switch_block->expected_type = data_type;
cf->expressions.push_back(n);
cf->blocks.push_back(switch_block);
p_block->statements.push_back(cf);
- int prev_type = TK_CF_CASE;
+ pos = _get_tkpos();
+ tk = _get_token();
+ TokenType prev_type;
+ if (tk.type == TK_CF_CASE || tk.type == TK_CF_DEFAULT) {
+ prev_type = tk.type;
+ _set_tkpos(pos);
+ } else {
+ _set_expected_error("case", "default");
+ return ERR_PARSE_ERROR;
+ }
+
while (true) { // Go-through multiple cases.
if (_parse_block(switch_block, p_function_info, true, true, false) != OK) {
@@ -7714,43 +8126,6 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
_set_tkpos(pos);
continue;
} else {
- HashSet<int> constants;
- for (ShaderLanguage::Node *statement : switch_block->statements) { // Checks for duplicates.
- ControlFlowNode *flow = static_cast<ControlFlowNode *>(statement);
- if (flow) {
- if (flow->flow_op == FLOW_OP_CASE) {
- if (flow->expressions[0]->type == Node::NODE_TYPE_CONSTANT) {
- ConstantNode *cn = static_cast<ConstantNode *>(flow->expressions[0]);
- if (!cn || cn->values.is_empty()) {
- return ERR_PARSE_ERROR;
- }
- if (constants.has(cn->values[0].sint)) {
- _set_error(vformat(RTR("Duplicated case label: %d."), cn->values[0].sint));
- return ERR_PARSE_ERROR;
- }
- constants.insert(cn->values[0].sint);
- } else if (flow->expressions[0]->type == Node::NODE_TYPE_VARIABLE) {
- VariableNode *vn = static_cast<VariableNode *>(flow->expressions[0]);
- if (!vn) {
- return ERR_PARSE_ERROR;
- }
- ConstantNode::Value v;
- _find_identifier(p_block, false, p_function_info, vn->name, nullptr, nullptr, nullptr, nullptr, nullptr, &v);
- if (constants.has(v.sint)) {
- _set_error(vformat(RTR("Duplicated case label: %d."), v.sint));
- return ERR_PARSE_ERROR;
- }
- constants.insert(v.sint);
- }
- } else if (flow->flow_op == FLOW_OP_DEFAULT) {
- continue;
- } else {
- return ERR_PARSE_ERROR;
- }
- } else {
- return ERR_PARSE_ERROR;
- }
- }
break;
}
}
@@ -7781,36 +8156,77 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
if (!tk.is_integer_constant()) {
bool correct_constant_expression = false;
- DataType data_type;
if (tk.type == TK_IDENTIFIER) {
+ DataType data_type;
bool is_const;
+
_find_identifier(p_block, false, p_function_info, tk.text, &data_type, nullptr, &is_const);
- if (is_const) {
- if (data_type == TYPE_INT) {
- correct_constant_expression = true;
- }
+ if (is_const && data_type == p_block->expected_type) {
+ correct_constant_expression = true;
}
}
+
if (!correct_constant_expression) {
- _set_error(RTR("Expected an integer constant."));
+ if (p_block->expected_type == TYPE_UINT) {
+ _set_error(RTR("Expected an unsigned integer constant."));
+ } else {
+ _set_error(RTR("Expected an integer constant."));
+ }
return ERR_PARSE_ERROR;
}
VariableNode *vn = alloc_node<VariableNode>();
vn->name = tk.text;
+ {
+ Vector<Scalar> v;
+ DataType data_type;
+
+ _find_identifier(p_block, false, p_function_info, vn->name, &data_type, nullptr, nullptr, nullptr, nullptr, &v);
+ if (data_type == TYPE_INT) {
+ if (p_block->constants.has(v[0].sint)) {
+ _set_error(vformat(RTR("Duplicated case label: %d."), v[0].sint));
+ return ERR_PARSE_ERROR;
+ }
+ p_block->constants.insert(v[0].sint);
+ } else {
+ if (p_block->constants.has(v[0].uint)) {
+ _set_error(vformat(RTR("Duplicated case label: %d."), v[0].uint));
+ return ERR_PARSE_ERROR;
+ }
+ p_block->constants.insert(v[0].uint);
+ }
+ }
n = vn;
} else {
- ConstantNode::Value v;
- if (tk.type == TK_UINT_CONSTANT) {
+ ConstantNode *cn = alloc_node<ConstantNode>();
+ Scalar v;
+ if (p_block->expected_type == TYPE_UINT) {
+ if (tk.type != TK_UINT_CONSTANT) {
+ _set_error(RTR("Expected an unsigned integer constant."));
+ return ERR_PARSE_ERROR;
+ }
v.uint = (uint32_t)tk.constant;
+ if (p_block->constants.has(v.uint)) {
+ _set_error(vformat(RTR("Duplicated case label: %d."), v.uint));
+ return ERR_PARSE_ERROR;
+ }
+ p_block->constants.insert(v.uint);
+ cn->datatype = TYPE_UINT;
} else {
- v.sint = (int)tk.constant * sign;
+ if (tk.type != TK_INT_CONSTANT) {
+ _set_error(RTR("Expected an integer constant."));
+ return ERR_PARSE_ERROR;
+ }
+ v.sint = (int32_t)tk.constant * sign;
+ if (p_block->constants.has(v.sint)) {
+ _set_error(vformat(RTR("Duplicated case label: %d."), v.sint));
+ return ERR_PARSE_ERROR;
+ }
+ p_block->constants.insert(v.sint);
+ cn->datatype = TYPE_INT;
}
-
- ConstantNode *cn = alloc_node<ConstantNode>();
cn->values.push_back(v);
- cn->datatype = (tk.type == TK_UINT_CONSTANT ? TYPE_UINT : TYPE_INT);
n = cn;
}
@@ -9678,7 +10094,7 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
decl.size = decl.initializer.size();
constant.array_size = decl.initializer.size();
} else if (decl.initializer.size() != constant.array_size) {
- _set_error(RTR("Array size mismatch."));
+ _set_error(vformat(RTR("Array size mismatch. Expected %d elements (found %d)."), constant.array_size, decl.initializer.size()));
return ERR_PARSE_ERROR;
}
} else {
diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h
index 63dca99654..b0d579dfe7 100644
--- a/servers/rendering/shader_language.h
+++ b/servers/rendering/shader_language.h
@@ -355,6 +355,13 @@ public:
}
};
+ union Scalar {
+ bool boolean = false;
+ float real;
+ int32_t sint;
+ uint32_t uint;
+ };
+
struct Node {
Node *next = nullptr;
@@ -379,6 +386,7 @@ public:
virtual String get_datatype_name() const { return ""; }
virtual int get_array_size() const { return 0; }
virtual bool is_indexed() const { return false; }
+ virtual Vector<Scalar> get_values() const { return Vector<Scalar>(); }
Node(Type t) :
type(t) {}
@@ -402,11 +410,13 @@ public:
Operator op = OP_EQUAL;
StringName struct_name;
Vector<Node *> arguments;
+ Vector<Scalar> values;
virtual DataType get_datatype() const override { return return_cache; }
virtual String get_datatype_name() const override { return String(struct_name); }
virtual int get_array_size() const override { return return_array_size; }
virtual bool is_indexed() const override { return op == OP_INDEX; }
+ virtual Vector<Scalar> get_values() const override { return values; }
OperatorNode() :
Node(NODE_TYPE_OPERATOR) {}
@@ -485,19 +495,15 @@ public:
String struct_name = "";
int array_size = 0;
- union Value {
- bool boolean = false;
- float real;
- int32_t sint;
- uint32_t uint;
- };
-
- Vector<Value> values;
+ Vector<Scalar> values;
Vector<VariableDeclarationNode::Declaration> array_declarations;
virtual DataType get_datatype() const override { return datatype; }
virtual String get_datatype_name() const override { return struct_name; }
virtual int get_array_size() const override { return array_size; }
+ virtual Vector<Scalar> get_values() const override {
+ return values;
+ }
ConstantNode() :
Node(NODE_TYPE_CONSTANT) {}
@@ -529,13 +535,17 @@ public:
int line; //for completion
int array_size;
bool is_const;
- ConstantNode::Value value;
+ Vector<Scalar> values;
};
HashMap<StringName, Variable> variables;
List<Node *> statements;
bool single_statement = false;
bool use_comma_between_statements = false;
+ bool use_op_eval = true;
+
+ DataType expected_type = TYPE_VOID;
+ HashSet<int> constants;
BlockNode() :
Node(NODE_TYPE_BLOCK) {}
@@ -657,7 +667,7 @@ public:
DataType type = TYPE_VOID;
DataPrecision precision = PRECISION_DEFAULT;
int array_size = 0;
- Vector<ConstantNode::Value> default_value;
+ Vector<Scalar> default_value;
Scope scope = SCOPE_LOCAL;
Hint hint = HINT_NONE;
bool use_color = false;
@@ -803,15 +813,16 @@ public:
static bool is_token_operator_assign(TokenType p_type);
static bool is_token_hint(TokenType p_type);
- static bool convert_constant(ConstantNode *p_constant, DataType p_to_type, ConstantNode::Value *p_value = nullptr);
+ static bool convert_constant(ConstantNode *p_constant, DataType p_to_type, Scalar *p_value = nullptr);
static DataType get_scalar_type(DataType p_type);
static int get_cardinality(DataType p_type);
static bool is_scalar_type(DataType p_type);
static bool is_float_type(DataType p_type);
static bool is_sampler_type(DataType p_type);
- static Variant constant_value_to_variant(const Vector<ShaderLanguage::ConstantNode::Value> &p_value, DataType p_type, int p_array_size, ShaderLanguage::ShaderNode::Uniform::Hint p_hint = ShaderLanguage::ShaderNode::Uniform::HINT_NONE);
+ static Variant constant_value_to_variant(const Vector<Scalar> &p_value, DataType p_type, int p_array_size, ShaderLanguage::ShaderNode::Uniform::Hint p_hint = ShaderLanguage::ShaderNode::Uniform::HINT_NONE);
static PropertyInfo uniform_to_property_info(const ShaderNode::Uniform &p_uniform);
static uint32_t get_datatype_size(DataType p_type);
+ static uint32_t get_datatype_component_count(DataType p_type);
static void get_keyword_list(List<String> *r_keywords);
static bool is_control_flow_keyword(String p_keyword);
@@ -1070,13 +1081,21 @@ private:
IdentifierType last_type = IDENTIFIER_MAX;
- bool _find_identifier(const BlockNode *p_block, bool p_allow_reassign, const FunctionInfo &p_function_info, const StringName &p_identifier, DataType *r_data_type = nullptr, IdentifierType *r_type = nullptr, bool *r_is_const = nullptr, int *r_array_size = nullptr, StringName *r_struct_name = nullptr, ConstantNode::Value *r_constant_value = nullptr);
+ bool _find_identifier(const BlockNode *p_block, bool p_allow_reassign, const FunctionInfo &p_function_info, const StringName &p_identifier, DataType *r_data_type = nullptr, IdentifierType *r_type = nullptr, bool *r_is_const = nullptr, int *r_array_size = nullptr, StringName *r_struct_name = nullptr, Vector<Scalar> *r_constant_values = nullptr);
#ifdef DEBUG_ENABLED
void _parse_used_identifier(const StringName &p_identifier, IdentifierType p_type, const StringName &p_function);
#endif // DEBUG_ENABLED
bool _is_operator_assign(Operator p_op) const;
bool _validate_assign(Node *p_node, const FunctionInfo &p_function_info, String *r_message = nullptr);
- bool _validate_operator(OperatorNode *p_op, DataType *r_ret_type = nullptr, int *r_ret_size = nullptr);
+ bool _validate_operator(const BlockNode *p_block, OperatorNode *p_op, DataType *r_ret_type = nullptr, int *r_ret_size = nullptr);
+
+ Vector<Scalar> _get_node_values(const BlockNode *p_block, Node *p_node);
+ bool _eval_operator(const BlockNode *p_block, OperatorNode *p_op);
+ Scalar _eval_unary_scalar(const Scalar &p_a, Operator p_op, DataType p_ret_type);
+ Scalar _eval_scalar(const Scalar &p_a, const Scalar &p_b, Operator p_op, DataType p_ret_type, bool &r_is_valid);
+ Vector<Scalar> _eval_unary_vector(const Vector<Scalar> &p_va, DataType p_ret_type, Operator p_op);
+ Vector<Scalar> _eval_vector(const Vector<Scalar> &p_va, const Vector<Scalar> &p_vb, DataType p_left_type, DataType p_right_type, DataType p_ret_type, Operator p_op, bool &r_is_valid);
+ Vector<Scalar> _eval_vector_transform(const Vector<Scalar> &p_va, const Vector<Scalar> &p_vb, DataType p_left_type, DataType p_right_type, DataType p_ret_type);
struct BuiltinEntry {
const char *name;
diff --git a/servers/rendering/storage/mesh_storage.cpp b/servers/rendering/storage/mesh_storage.cpp
index 221ebaa277..6680920c98 100644
--- a/servers/rendering/storage/mesh_storage.cpp
+++ b/servers/rendering/storage/mesh_storage.cpp
@@ -285,7 +285,7 @@ int RendererMeshStorage::multimesh_get_visible_instances(RID p_multimesh) const
return _multimesh_get_visible_instances(p_multimesh);
}
-AABB RendererMeshStorage::multimesh_get_aabb(RID p_multimesh) const {
+AABB RendererMeshStorage::multimesh_get_aabb(RID p_multimesh) {
return _multimesh_get_aabb(p_multimesh);
}
diff --git a/servers/rendering/storage/mesh_storage.h b/servers/rendering/storage/mesh_storage.h
index ecd2a967d0..5e3a4738e6 100644
--- a/servers/rendering/storage/mesh_storage.h
+++ b/servers/rendering/storage/mesh_storage.h
@@ -151,7 +151,7 @@ public:
virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible);
virtual int multimesh_get_visible_instances(RID p_multimesh) const;
- virtual AABB multimesh_get_aabb(RID p_multimesh) const;
+ virtual AABB multimesh_get_aabb(RID p_multimesh);
virtual RID _multimesh_allocate() = 0;
virtual void _multimesh_initialize(RID p_rid) = 0;
@@ -183,7 +183,7 @@ public:
virtual void _multimesh_set_visible_instances(RID p_multimesh, int p_visible) = 0;
virtual int _multimesh_get_visible_instances(RID p_multimesh) const = 0;
- virtual AABB _multimesh_get_aabb(RID p_multimesh) const = 0;
+ virtual AABB _multimesh_get_aabb(RID p_multimesh) = 0;
// Multimesh is responsible for allocating / destroying a MultiMeshInterpolator object.
// This allows shared functionality for interpolation across backends.
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index 1848d5602e..f354e83893 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -3548,6 +3548,7 @@ void RenderingServer::init() {
GLOBAL_DEF("rendering/lights_and_shadows/positional_shadow/soft_shadow_filter_quality.mobile", 0);
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/2d/shadow_atlas/size", PROPERTY_HINT_RANGE, "128,16384"), 2048);
+ GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/2d/batching/item_buffer_size", PROPERTY_HINT_RANGE, "128,1048576,1"), 16384);
// Number of commands that can be drawn per frame.
GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/gl_compatibility/item_buffer_size", PROPERTY_HINT_RANGE, "128,1048576,1"), 16384);
diff --git a/servers/xr/xr_interface.h b/servers/xr/xr_interface.h
index 7beec219bb..6b6a67a04c 100644
--- a/servers/xr/xr_interface.h
+++ b/servers/xr/xr_interface.h
@@ -44,7 +44,7 @@ struct BlitToScreen;
The idea is that we subclass this class, implement the logic, and then instantiate a singleton of each interface
when Godot starts. These instances do not initialize themselves but register themselves with the AR/VR server.
- If the user wants to enable AR/VR the choose the interface they want to use and initialize it.
+ If the user wants to enable AR/VR, they can choose the interface they want to use and initialize it.
Note that we may make this into a fully instantiable class for GDExtension support.
*/
diff --git a/tests/core/math/test_expression.h b/tests/core/math/test_expression.h
index 512d7932f9..c3e4280491 100644
--- a/tests/core/math/test_expression.h
+++ b/tests/core/math/test_expression.h
@@ -122,6 +122,59 @@ TEST_CASE("[Expression] Floating-point arithmetic") {
"Float multiplication-addition-subtraction-division should return the expected result.");
}
+TEST_CASE("[Expression] Floating-point notation") {
+ Expression expression;
+
+ CHECK_MESSAGE(
+ expression.parse("2.") == OK,
+ "The expression should parse successfully.");
+ CHECK_MESSAGE(
+ double(expression.execute()) == doctest::Approx(2.0),
+ "The expression should return the expected result.");
+
+ CHECK_MESSAGE(
+ expression.parse("(2.)") == OK,
+ "The expression should parse successfully.");
+ CHECK_MESSAGE(
+ double(expression.execute()) == doctest::Approx(2.0),
+ "The expression should return the expected result.");
+
+ CHECK_MESSAGE(
+ expression.parse(".3") == OK,
+ "The expression should parse successfully.");
+ CHECK_MESSAGE(
+ double(expression.execute()) == doctest::Approx(0.3),
+ "The expression should return the expected result.");
+
+ CHECK_MESSAGE(
+ expression.parse("2.+5.") == OK,
+ "The expression should parse successfully.");
+ CHECK_MESSAGE(
+ double(expression.execute()) == doctest::Approx(7.0),
+ "The expression should return the expected result.");
+
+ CHECK_MESSAGE(
+ expression.parse(".3-.8") == OK,
+ "The expression should parse successfully.");
+ CHECK_MESSAGE(
+ double(expression.execute()) == doctest::Approx(-0.5),
+ "The expression should return the expected result.");
+
+ CHECK_MESSAGE(
+ expression.parse("2.+.2") == OK,
+ "The expression should parse successfully.");
+ CHECK_MESSAGE(
+ double(expression.execute()) == doctest::Approx(2.2),
+ "The expression should return the expected result.");
+
+ CHECK_MESSAGE(
+ expression.parse(".0*0.") == OK,
+ "The expression should parse successfully.");
+ CHECK_MESSAGE(
+ double(expression.execute()) == doctest::Approx(0.0),
+ "The expression should return the expected result.");
+}
+
TEST_CASE("[Expression] Scientific notation") {
Expression expression;
diff --git a/tests/core/variant/test_dictionary.h b/tests/core/variant/test_dictionary.h
index 7061bc66dc..48a48f6ca6 100644
--- a/tests/core/variant/test_dictionary.h
+++ b/tests/core/variant/test_dictionary.h
@@ -31,7 +31,7 @@
#ifndef TEST_DICTIONARY_H
#define TEST_DICTIONARY_H
-#include "core/variant/dictionary.h"
+#include "core/variant/typed_dictionary.h"
#include "tests/test_macros.h"
namespace TestDictionary {
@@ -536,6 +536,43 @@ TEST_CASE("[Dictionary] Order and find") {
CHECK_EQ(d.find_key("does not exist"), Variant());
}
+TEST_CASE("[Dictionary] Typed copying") {
+ TypedDictionary<int, int> d1;
+ d1[0] = 1;
+
+ TypedDictionary<double, double> d2;
+ d2[0] = 1.0;
+
+ Dictionary d3 = d1;
+ TypedDictionary<int, int> d4 = d3;
+
+ Dictionary d5 = d2;
+ TypedDictionary<int, int> d6 = d5;
+
+ d3[0] = 2;
+ d4[0] = 3;
+
+ // Same typed TypedDictionary should be shared.
+ CHECK_EQ(d1[0], Variant(3));
+ CHECK_EQ(d3[0], Variant(3));
+ CHECK_EQ(d4[0], Variant(3));
+
+ d5[0] = 2.0;
+ d6[0] = 3.0;
+
+ // Different typed TypedDictionary should not be shared.
+ CHECK_EQ(d2[0], Variant(2.0));
+ CHECK_EQ(d5[0], Variant(2.0));
+ CHECK_EQ(d6[0], Variant(3.0));
+
+ d1.clear();
+ d2.clear();
+ d3.clear();
+ d4.clear();
+ d5.clear();
+ d6.clear();
+}
+
} // namespace TestDictionary
#endif // TEST_DICTIONARY_H
diff --git a/tests/scene/test_parallax_2d.h b/tests/scene/test_parallax_2d.h
new file mode 100644
index 0000000000..2fdbf09e09
--- /dev/null
+++ b/tests/scene/test_parallax_2d.h
@@ -0,0 +1,131 @@
+/**************************************************************************/
+/* test_parallax_2d.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 TEST_PARALLAX_2D_H
+#define TEST_PARALLAX_2D_H
+
+#include "scene/2d/parallax_2d.h"
+#include "tests/test_macros.h"
+
+namespace TestParallax2D {
+
+// Test cases for the Parallax2D class to ensure its properties are set and retrieved correctly.
+
+TEST_CASE("[SceneTree][Parallax2D] Scroll Scale") {
+ // Test setting and getting the scroll scale.
+ Parallax2D *parallax = memnew(Parallax2D);
+ Size2 scale(2, 2);
+ parallax->set_scroll_scale(scale);
+ CHECK(parallax->get_scroll_scale() == scale);
+ memdelete(parallax);
+}
+
+TEST_CASE("[SceneTree][Parallax2D] Repeat Size") {
+ // Test setting and getting the repeat size.
+ Parallax2D *parallax = memnew(Parallax2D);
+ Size2 size(100, 100);
+ parallax->set_repeat_size(size);
+ CHECK(parallax->get_repeat_size() == size);
+ memdelete(parallax);
+}
+
+TEST_CASE("[SceneTree][Parallax2D] Repeat Times") {
+ // Test setting and getting the repeat times.
+ Parallax2D *parallax = memnew(Parallax2D);
+ int times = 5;
+ parallax->set_repeat_times(times);
+ CHECK(parallax->get_repeat_times() == times);
+ memdelete(parallax);
+}
+
+TEST_CASE("[SceneTree][Parallax2D] Autoscroll") {
+ // Test setting and getting the autoscroll values.
+ Parallax2D *parallax = memnew(Parallax2D);
+ Point2 autoscroll(1, 1);
+ parallax->set_autoscroll(autoscroll);
+ CHECK(parallax->get_autoscroll() == autoscroll);
+ memdelete(parallax);
+}
+
+TEST_CASE("[SceneTree][Parallax2D] Scroll Offset") {
+ // Test setting and getting the scroll offset.
+ Parallax2D *parallax = memnew(Parallax2D);
+ Point2 offset(10, 10);
+ parallax->set_scroll_offset(offset);
+ CHECK(parallax->get_scroll_offset() == offset);
+ memdelete(parallax);
+}
+
+TEST_CASE("[SceneTree][Parallax2D] Screen Offset") {
+ // Test setting and getting the screen offset.
+ Parallax2D *parallax = memnew(Parallax2D);
+ Point2 offset(20, 20);
+ parallax->set_screen_offset(offset);
+ CHECK(parallax->get_screen_offset() == offset);
+ memdelete(parallax);
+}
+
+TEST_CASE("[SceneTree][Parallax2D] Limit Begin") {
+ // Test setting and getting the limit begin values.
+ Parallax2D *parallax = memnew(Parallax2D);
+ Point2 limit_begin(-100, -100);
+ parallax->set_limit_begin(limit_begin);
+ CHECK(parallax->get_limit_begin() == limit_begin);
+ memdelete(parallax);
+}
+
+TEST_CASE("[SceneTree][Parallax2D] Limit End") {
+ // Test setting and getting the limit end values.
+ Parallax2D *parallax = memnew(Parallax2D);
+ Point2 limit_end(100, 100);
+ parallax->set_limit_end(limit_end);
+ CHECK(parallax->get_limit_end() == limit_end);
+ memdelete(parallax);
+}
+
+TEST_CASE("[SceneTree][Parallax2D] Follow Viewport") {
+ // Test setting and getting the follow viewport flag.
+ Parallax2D *parallax = memnew(Parallax2D);
+ parallax->set_follow_viewport(false);
+ CHECK_FALSE(parallax->get_follow_viewport());
+ memdelete(parallax);
+}
+
+TEST_CASE("[SceneTree][Parallax2D] Ignore Camera Scroll") {
+ // Test setting and getting the ignore camera scroll flag.
+ Parallax2D *parallax = memnew(Parallax2D);
+ parallax->set_ignore_camera_scroll(true);
+ CHECK(parallax->is_ignore_camera_scroll());
+ memdelete(parallax);
+}
+
+} // namespace TestParallax2D
+
+#endif // TEST_PARALLAX_2D_H
diff --git a/tests/scene/test_path_follow_3d.h b/tests/scene/test_path_follow_3d.h
index d08af3a70c..6a384bec2b 100644
--- a/tests/scene/test_path_follow_3d.h
+++ b/tests/scene/test_path_follow_3d.h
@@ -60,39 +60,30 @@ TEST_CASE("[SceneTree][PathFollow3D] Sampling with progress ratio") {
SceneTree::get_singleton()->get_root()->add_child(path);
path_follow_3d->set_progress_ratio(0);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(0, 0, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(0.125);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(50, 0, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(0.25);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 0, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(0.375);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 50, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(0.5);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 100, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(0.625);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 100, 50), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(0.75);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 100, 100), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(0.875);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 50, 100), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(1);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 0, 100), path_follow_3d->get_transform().get_origin()));
memdelete(path);
@@ -113,39 +104,30 @@ TEST_CASE("[SceneTree][PathFollow3D] Sampling with progress") {
SceneTree::get_singleton()->get_root()->add_child(path);
path_follow_3d->set_progress(0);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(0, 0, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(50);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(50, 0, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(100);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 0, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(150);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 50, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(200);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 100, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(250);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 100, 50), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(300);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 100, 100), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(350);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 50, 100), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(400);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 0, 100), path_follow_3d->get_transform().get_origin()));
memdelete(path);
@@ -163,13 +145,11 @@ TEST_CASE("[SceneTree][PathFollow3D] Removal of a point in curve") {
SceneTree::get_singleton()->get_root()->add_child(path);
path_follow_3d->set_progress_ratio(0.5);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(100, 0, 0), path_follow_3d->get_transform().get_origin()));
curve->remove_point(1);
path_follow_3d->set_progress_ratio(0.5);
- path_follow_3d->update_transform(true);
CHECK_MESSAGE(
is_equal_approx(Vector3(50, 50, 0), path_follow_3d->get_transform().get_origin()),
"Path follow's position should be updated after removing a point from the curve");
@@ -270,47 +250,36 @@ TEST_CASE("[SceneTree][PathFollow3D] Calculate forward vector") {
path_follow_3d->set_rotation_mode(PathFollow3D::RotationMode::ROTATION_ORIENTED);
path_follow_3d->set_progress(-50);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(0);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(50);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(100);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(100 + dist_cube_100 / 2);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(-0.577348, -0.577348, 0.577348), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(100 + dist_cube_100 - 0.01);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(-0.577348, -0.577348, 0.577348), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(250 + dist_cube_100);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(0, 0, -1), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(400 + dist_cube_100 - 0.01);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(0, 0, -1), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(400 + 1.5 * dist_cube_100);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(0.577348, 0.577348, 0.577348), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(400 + 2 * dist_cube_100 - 0.01);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(0.577348, 0.577348, 0.577348), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(500 + 2 * dist_cube_100);
- path_follow_3d->update_transform(true);
CHECK(is_equal_approx(Vector3(1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2)));
memdelete(path);
diff --git a/tests/test_main.cpp b/tests/test_main.cpp
index 4487167dba..2b6461e9ca 100644
--- a/tests/test_main.cpp
+++ b/tests/test_main.cpp
@@ -119,6 +119,7 @@
#include "tests/scene/test_node.h"
#include "tests/scene/test_node_2d.h"
#include "tests/scene/test_packed_scene.h"
+#include "tests/scene/test_parallax_2d.h"
#include "tests/scene/test_path_2d.h"
#include "tests/scene/test_path_follow_2d.h"
#include "tests/scene/test_sprite_frames.h"
diff --git a/thirdparty/README.md b/thirdparty/README.md
index f8b41a8c52..dbe20ba2a5 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -907,7 +907,7 @@ instead of `miniz.h` as an external dependency.
## thorvg
- Upstream: https://github.com/thorvg/thorvg
-- Version: 0.14.7 (e3a6bf5229a9671c385ee78bc33e6e6b611a9729, 2024)
+- Version: 0.14.9 (81a0fbfd590873b21e53c3af77969c71d3d9b586, 2024)
- License: MIT
Files extracted from upstream source:
diff --git a/thirdparty/thorvg/inc/config.h b/thirdparty/thorvg/inc/config.h
index 02fec07448..654d70f918 100644
--- a/thirdparty/thorvg/inc/config.h
+++ b/thirdparty/thorvg/inc/config.h
@@ -15,5 +15,5 @@
// For internal debugging:
//#define THORVG_LOG_ENABLED
-#define THORVG_VERSION_STRING "0.14.8"
+#define THORVG_VERSION_STRING "0.14.9"
#endif
diff --git a/thirdparty/thorvg/patches/pr2702-sw_engine-handle-small-cubics.patch b/thirdparty/thorvg/patches/pr2702-sw_engine-handle-small-cubics.patch
deleted file mode 100644
index 69f4a5cf85..0000000000
--- a/thirdparty/thorvg/patches/pr2702-sw_engine-handle-small-cubics.patch
+++ /dev/null
@@ -1,96 +0,0 @@
-From ac7d208ed8e4651c93ce1b2384070fccac9b6cb6 Mon Sep 17 00:00:00 2001
-From: Mira Grudzinska <mira@lottiefiles.com>
-Date: Sun, 1 Sep 2024 22:36:18 +0200
-Subject: [PATCH] sw_engine: handle small cubics
-
-During the stroke's outline calculation, the function
-handling small cubics set all angles to zero. Such cases
-should be ignored, as further processing caused errors -
-when the cubic was small but not zero, setting the angles
-to zero resulted in incorrect outlines.
-
-@Issue: https://github.com/godotengine/godot/issues/96262
----
- src/renderer/sw_engine/tvgSwCommon.h | 3 ++-
- src/renderer/sw_engine/tvgSwMath.cpp | 19 ++++++++++++-------
- src/renderer/sw_engine/tvgSwStroke.cpp | 16 +++++++++++-----
- 3 files changed, 25 insertions(+), 13 deletions(-)
-
-diff --git a/src/renderer/sw_engine/tvgSwCommon.h b/src/renderer/sw_engine/tvgSwCommon.h
-index 893e9beca..158fe8ecd 100644
---- a/src/renderer/sw_engine/tvgSwCommon.h
-+++ b/src/renderer/sw_engine/tvgSwCommon.h
-@@ -491,7 +491,8 @@ SwFixed mathSin(SwFixed angle);
- void mathSplitCubic(SwPoint* base);
- SwFixed mathDiff(SwFixed angle1, SwFixed angle2);
- SwFixed mathLength(const SwPoint& pt);
--bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut);
-+bool mathSmallCubic(const SwPoint* base);
-+bool mathFlatCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut);
- SwFixed mathMean(SwFixed angle1, SwFixed angle2);
- SwPoint mathTransform(const Point* to, const Matrix& transform);
- bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, SwBBox& renderRegion, bool fastTrack);
-diff --git a/src/renderer/sw_engine/tvgSwMath.cpp b/src/renderer/sw_engine/tvgSwMath.cpp
-index 1093edd62..b311be05f 100644
---- a/src/renderer/sw_engine/tvgSwMath.cpp
-+++ b/src/renderer/sw_engine/tvgSwMath.cpp
-@@ -44,7 +44,17 @@ SwFixed mathMean(SwFixed angle1, SwFixed angle2)
- }
-
-
--bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut)
-+bool mathSmallCubic(const SwPoint* base)
-+{
-+ auto d1 = base[2] - base[3];
-+ auto d2 = base[1] - base[2];
-+ auto d3 = base[0] - base[1];
-+
-+ return d1.small() && d2.small() && d3.small();
-+}
-+
-+
-+bool mathFlatCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut)
- {
- auto d1 = base[2] - base[3];
- auto d2 = base[1] - base[2];
-@@ -52,12 +62,7 @@ bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, Sw
-
- if (d1.small()) {
- if (d2.small()) {
-- if (d3.small()) {
-- angleIn = angleMid = angleOut = 0;
-- return true;
-- } else {
-- angleIn = angleMid = angleOut = mathAtan(d3);
-- }
-+ angleIn = angleMid = angleOut = mathAtan(d3);
- } else {
- if (d3.small()) {
- angleIn = angleMid = angleOut = mathAtan(d2);
-diff --git a/src/renderer/sw_engine/tvgSwStroke.cpp b/src/renderer/sw_engine/tvgSwStroke.cpp
-index 575d12951..4679b72cc 100644
---- a/src/renderer/sw_engine/tvgSwStroke.cpp
-+++ b/src/renderer/sw_engine/tvgSwStroke.cpp
-@@ -441,11 +441,17 @@ static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl
- //initialize with current direction
- angleIn = angleOut = angleMid = stroke.angleIn;
-
-- if (arc < limit && !mathSmallCubic(arc, angleIn, angleMid, angleOut)) {
-- if (stroke.firstPt) stroke.angleIn = angleIn;
-- mathSplitCubic(arc);
-- arc += 3;
-- continue;
-+ if (arc < limit) {
-+ if (mathSmallCubic(arc)) {
-+ arc -= 3;
-+ continue;
-+ }
-+ if (!mathFlatCubic(arc, angleIn, angleMid, angleOut)) {
-+ if (stroke.firstPt) stroke.angleIn = angleIn;
-+ mathSplitCubic(arc);
-+ arc += 3;
-+ continue;
-+ }
- }
-
- if (firstArc) {
diff --git a/thirdparty/thorvg/patches/pr2716-text-drawing-reliability.patch b/thirdparty/thorvg/patches/pr2716-text-drawing-reliability.patch
new file mode 100644
index 0000000000..c79f7c63d6
--- /dev/null
+++ b/thirdparty/thorvg/patches/pr2716-text-drawing-reliability.patch
@@ -0,0 +1,25 @@
+From 41d67213607e7ff20b7a3ca833f1cfde9780da65 Mon Sep 17 00:00:00 2001
+From: Hermet Park <hermet@lottiefiles.com>
+Date: Sat, 7 Sep 2024 01:35:09 +0900
+Subject: [PATCH] renderer: ++reliability in text drawing
+
+Allow the canvas to pass through
+even if text elements are not properly supported.
+
+issue: https://github.com/thorvg/thorvg/issues/2715
+---
+ src/renderer/tvgText.h | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/thirdparty/thorvg/src/renderer/tvgText.h b/thirdparty/thorvg/src/renderer/tvgText.h
+index 746b85bea6..55d33ffd4b 100644
+--- a/thirdparty/thorvg/src/renderer/tvgText.h
++++ b/thirdparty/thorvg/src/renderer/tvgText.h
+@@ -89,6 +89,7 @@ struct Text::Impl
+
+ bool render(RenderMethod* renderer)
+ {
++ if (!loader) return true;
+ renderer->blend(paint->blend(), true);
+ return PP(shape)->render(renderer);
+ }
diff --git a/thirdparty/thorvg/patches/revert-tvgLines-bezier-precision-change.patch b/thirdparty/thorvg/patches/revert-tvgLines-bezier-precision-change.patch
new file mode 100644
index 0000000000..dd6c8ba5e7
--- /dev/null
+++ b/thirdparty/thorvg/patches/revert-tvgLines-bezier-precision-change.patch
@@ -0,0 +1,13 @@
+diff --git a/thirdparty/thorvg/src/common/tvgLines.cpp b/thirdparty/thorvg/src/common/tvgLines.cpp
+index 49d992f127..9d704900a5 100644
+--- a/thirdparty/thorvg/src/common/tvgLines.cpp
++++ b/thirdparty/thorvg/src/common/tvgLines.cpp
+@@ -79,7 +79,7 @@ float _bezAt(const Bezier& bz, float at, float length, LengthFunc lineLengthFunc
+ Bezier left;
+ bezSplitLeft(right, t, left);
+ length = _bezLength(left, lineLengthFunc);
+- if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < 1e-3f) {
++ if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < BEZIER_EPSILON) {
+ break;
+ }
+ if (length < at) {
diff --git a/thirdparty/thorvg/src/common/tvgLines.cpp b/thirdparty/thorvg/src/common/tvgLines.cpp
index 49d992f127..9d704900a5 100644
--- a/thirdparty/thorvg/src/common/tvgLines.cpp
+++ b/thirdparty/thorvg/src/common/tvgLines.cpp
@@ -79,7 +79,7 @@ float _bezAt(const Bezier& bz, float at, float length, LengthFunc lineLengthFunc
Bezier left;
bezSplitLeft(right, t, left);
length = _bezLength(left, lineLengthFunc);
- if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < 1e-3f) {
+ if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < BEZIER_EPSILON) {
break;
}
if (length < at) {
diff --git a/thirdparty/thorvg/src/common/tvgMath.cpp b/thirdparty/thorvg/src/common/tvgMath.cpp
index 0254cce9b8..c03b54e5f8 100644
--- a/thirdparty/thorvg/src/common/tvgMath.cpp
+++ b/thirdparty/thorvg/src/common/tvgMath.cpp
@@ -43,9 +43,8 @@ bool mathInverse(const Matrix* m, Matrix* out)
m->e12 * (m->e21 * m->e33 - m->e23 * m->e31) +
m->e13 * (m->e21 * m->e32 - m->e22 * m->e31);
- if (mathZero(det)) return false;
-
- auto invDet = 1 / det;
+ auto invDet = 1.0f / det;
+ if (std::isinf(invDet)) return false;
out->e11 = (m->e22 * m->e33 - m->e32 * m->e23) * invDet;
out->e12 = (m->e13 * m->e32 - m->e12 * m->e33) * invDet;
@@ -137,7 +136,6 @@ Point operator*(const Point& pt, const Matrix& m)
uint8_t mathLerp(const uint8_t &start, const uint8_t &end, float t)
{
auto result = static_cast<int>(start + (end - start) * t);
- if (result > 255) result = 255;
- else if (result < 0) result = 0;
+ mathClamp(result, 0, 255);
return static_cast<uint8_t>(result);
}
diff --git a/thirdparty/thorvg/src/common/tvgMath.h b/thirdparty/thorvg/src/common/tvgMath.h
index df39e3b9af..50786754a1 100644
--- a/thirdparty/thorvg/src/common/tvgMath.h
+++ b/thirdparty/thorvg/src/common/tvgMath.h
@@ -26,7 +26,7 @@
#define _USE_MATH_DEFINES
#include <float.h>
-#include <math.h>
+#include <cmath>
#include "tvgCommon.h"
#define MATH_PI 3.14159265358979323846f
@@ -68,6 +68,13 @@ static inline bool mathEqual(float a, float b)
}
+template <typename T>
+static inline void mathClamp(T& v, const T& min, const T& max)
+{
+ if (v < min) v = min;
+ else if (v > max) v = max;
+}
+
/************************************************************************/
/* Matrix functions */
/************************************************************************/
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
index 5aab4f1b0d..cccd056a13 100644
--- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
@@ -3288,6 +3288,7 @@ static void _svgLoaderParserXmlClose(SvgLoaderData* loader, const char* content,
for (unsigned int i = 0; i < sizeof(graphicsTags) / sizeof(graphicsTags[0]); i++) {
if (!strncmp(tagName, graphicsTags[i].tag, sz)) {
loader->currentGraphicsNode = nullptr;
+ if (!strncmp(tagName, "text", 4)) loader->openedTag = OpenedTagType::Other;
loader->stack.pop();
break;
}
@@ -3361,11 +3362,9 @@ static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content,
node = method(loader, parent, attrs, attrsLength, simpleXmlParseAttributes);
if (node && !empty) {
if (!strcmp(tagName, "text")) loader->openedTag = OpenedTagType::Text;
- else {
- auto defs = _createDefsNode(loader, nullptr, nullptr, 0, nullptr);
- loader->stack.push(defs);
- loader->currentGraphicsNode = node;
- }
+ auto defs = _createDefsNode(loader, nullptr, nullptr, 0, nullptr);
+ loader->stack.push(defs);
+ loader->currentGraphicsNode = node;
}
} else if ((gradientMethod = _findGradientFactory(tagName))) {
SvgStyleGradient* gradient;
@@ -3403,7 +3402,6 @@ static void _svgLoaderParserText(SvgLoaderData* loader, const char* content, uns
auto text = &loader->svgParse->node->node.text;
if (text->text) free(text->text);
text->text = strDuplicate(content, length);
- loader->openedTag = OpenedTagType::Other;
}
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h
index 09b75d370b..20442c1c20 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h
@@ -495,8 +495,7 @@ SwFixed mathSin(SwFixed angle);
void mathSplitCubic(SwPoint* base);
SwFixed mathDiff(SwFixed angle1, SwFixed angle2);
SwFixed mathLength(const SwPoint& pt);
-bool mathSmallCubic(const SwPoint* base);
-bool mathFlatCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut);
+bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut);
SwFixed mathMean(SwFixed angle1, SwFixed angle2);
SwPoint mathTransform(const Point* to, const Matrix& transform);
bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, SwBBox& renderRegion, bool fastTrack);
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp
index 60dbbc4fbc..fb809c4f7e 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp
@@ -44,17 +44,7 @@ SwFixed mathMean(SwFixed angle1, SwFixed angle2)
}
-bool mathSmallCubic(const SwPoint* base)
-{
- auto d1 = base[2] - base[3];
- auto d2 = base[1] - base[2];
- auto d3 = base[0] - base[1];
-
- return d1.small() && d2.small() && d3.small();
-}
-
-
-bool mathFlatCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut)
+bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut)
{
auto d1 = base[2] - base[3];
auto d2 = base[1] - base[2];
@@ -62,7 +52,12 @@ bool mathFlatCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwF
if (d1.small()) {
if (d2.small()) {
- angleIn = angleMid = angleOut = mathAtan(d3);
+ if (d3.small()) {
+ angleIn = angleMid = angleOut = 0;
+ return true;
+ } else {
+ angleIn = angleMid = angleOut = mathAtan(d3);
+ }
} else {
if (d3.small()) {
angleIn = angleMid = angleOut = mathAtan(d2);
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp
index 96c3bc28b9..24c4a9e372 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp
@@ -210,7 +210,7 @@ static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ct
}
_outlineCubicTo(*dash.outline, &cur.ctrl1, &cur.ctrl2, &cur.end, transform);
}
- if (dash.curLen < 1 && TO_SWCOORD(len) > 1) {
+ if (dash.curLen < 0.1f && TO_SWCOORD(len) > 1) {
//move to next dash
dash.curIdx = (dash.curIdx + 1) % dash.cnt;
dash.curLen = dash.pattern[dash.curIdx];
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp
index e0e74ce53c..75ac96be04 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp
@@ -441,17 +441,11 @@ static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl
//initialize with current direction
angleIn = angleOut = angleMid = stroke.angleIn;
- if (arc < limit) {
- if (mathSmallCubic(arc)) {
- arc -= 3;
- continue;
- }
- if (!mathFlatCubic(arc, angleIn, angleMid, angleOut)) {
- if (stroke.firstPt) stroke.angleIn = angleIn;
- mathSplitCubic(arc);
- arc += 3;
- continue;
- }
+ if (arc < limit && !mathSmallCubic(arc, angleIn, angleMid, angleOut)) {
+ if (stroke.firstPt) stroke.angleIn = angleIn;
+ mathSplitCubic(arc);
+ arc += 3;
+ continue;
}
if (firstArc) {
diff --git a/thirdparty/thorvg/src/renderer/tvgText.h b/thirdparty/thorvg/src/renderer/tvgText.h
index 746b85bea6..55d33ffd4b 100644
--- a/thirdparty/thorvg/src/renderer/tvgText.h
+++ b/thirdparty/thorvg/src/renderer/tvgText.h
@@ -89,6 +89,7 @@ struct Text::Impl
bool render(RenderMethod* renderer)
{
+ if (!loader) return true;
renderer->blend(paint->blend(), true);
return PP(shape)->render(renderer);
}
diff --git a/thirdparty/thorvg/update-thorvg.sh b/thirdparty/thorvg/update-thorvg.sh
index 51dc156661..1ec21f3dd8 100755
--- a/thirdparty/thorvg/update-thorvg.sh
+++ b/thirdparty/thorvg/update-thorvg.sh
@@ -1,6 +1,6 @@
#!/bin/bash -e
-VERSION=0.14.8
+VERSION=0.14.9
# Uncomment and set a git hash to use specific commit instead of tag.
#GIT_COMMIT=